prefer-upstream
Before writing custom code for a common task, search for existing well-maintained packages or tools that already solve the problem.
This skill is the search procedure for the don’t-reinvent-the-wheel (DRW) principle — see shared/principles/dont-reinvent-wheel.md for the umbrella statement, including checking our own repos, the fork-or-contribute preference for close-but-not-exact matches, and the review-side application.
When this fires (automatically)
- About to write a utility function (string manipulation, file parsing, API wrappers, data transformation)
- Implementing something that “feels generic” — not specific to this project
- Building CI/CD helpers, linters, formatters, or test infrastructure
- Any time you think “surely someone has done this before”
Where to look (by ecosystem)
R
- r-lib org: usethis, devtools, pkgdown, lintr, styler, testthat, covr, rcmdcheck, desc, fs, cli, rlang, withr, callr, processx
- tidyverse org: dplyr, tidyr, purrr, stringr, readr, forcats, lubridate, glue, tibble
- ropensci org: specialized packages for data access, APIs, etc.
- CRAN Task Views: curated lists by topic
Python
- PyPI standard ecosystem: requests, click, rich, pydantic, httpx
- Scientific: numpy, pandas, scipy, scikit-learn
Shell / CI
- Your project’s shared CI templates (e.g., reusable workflow libraries)
- Standard Unix tools before custom scripts
- GitHub Actions marketplace / GitLab CI templates
JavaScript/TypeScript
- npm ecosystem: well-maintained packages with good test coverage
AI Agent Plugins & Tools
- Plugin marketplaces:
claude plugin marketplace list,codex plugin marketplace list,.agents/plugins.json,.cursor-plugin/ - MCP servers: official registries, package managers (
brew install <server>), local typed CLI wrappers (seeuse-pluginsanduse-mcp-servers)
Decision criteria
| Factor | Build custom | Use upstream |
|---|---|---|
| Exact match exists with active maintenance | ❌ | ✅ |
| Close match exists, needs minor wrapping | ❌ | ✅ (wrap it) |
| Upstream exists but unmaintained (>2yr) | Maybe | ⚠️ Evaluate |
| Problem is highly project-specific | ✅ | ❌ |
| Upstream has heavy dependencies you don’t want | ✅ | ❌ |
| Learning exercise / pedagogical code | ✅ | ❌ |
Most of those rows are facts about the world, but two are choices: “Upstream has heavy dependencies you don’t want” and “Learning exercise / pedagogical code”. A choice is a fine reason when it is the point of the work — pedagogical code is written in order to be written — and not when it exists only to rule the upstream out. So ask where the choice came from before either row decides anything. A constraint your own change authored — “this script runs with no packages installed”, “this PR decided not to add a dependency” — is the case that fails that test, because the change it would justify is what created it. Relax it (add the dependency, fix the CI job) and re-read the table against the relaxed environment. See the DRW fragment’s “A constraint your own change authored is not evidence against an upstream”.
Process
- Identify the generic problem — separate project-specific logic from the reusable utility layer
- Search — check the relevant ecosystem orgs, package indices, and GitHub/GitLab
- Evaluate — is it actively maintained? Good test coverage? Reasonable dependencies? Compatible license?
- Classify any constraint that rules a candidate out as external (a platform limit, an upstream API, a license, a policy) or self-imposed (a choice in this change or an earlier one of ours). Relax a self-imposed one and re-evaluate. It may stand as a reason only once relaxing it is shown to be genuinely unavailable — say who would have to authorize the relaxation when it is not yours to make, rather than treating your own lack of access as the constraint.
- Recommend — if a good upstream exists, suggest it to the user before writing custom code. Include:
- Package name and link
- How it solves the problem
- Any wrapping needed
- If a close-but-not-exact match exists — prefer contributing the missing piece upstream, or forking, over re-building from scratch (see the DRW fragment’s fork-or-contribute section and its
upstream-issues/scout-peersgates) - If no upstream exists — proceed with custom implementation, but note in comments that you checked and nothing fit, and record the search terms and every candidate found in the PR body — review is a weak layer for catching a reimplementation, so the written record is what a later reader has instead
Anti-patterns to avoid
- Reimplementing
glue::glue()withpaste0()and manual substitution - Writing custom YAML/JSON parsers when
yaml/jsonliteexist - Hand-rolling HTTP retry logic when
httr2handles it - Building custom test infrastructure when
testthatcovers the need - Writing shell scripts for tasks that
usethisordevtoolsalready do