A repo-wide Not Invented Here audit (ten parallel domain surveys covering every package group, scripts/, native/, vendor/ edges, python/, test infrastructure, and CI) asked of each hand-rolled surface whether a maintained external package or Node builtin deletes it with a net win. Policy: new implemented process note records that a dependency which genuinely deletes owned code is a preferred simplification (bar: net deletion, health, boundary fit, settled seams stay); root AGENTS.md carries the one-line rule and dsh-find-simplifications now surveys for hand-rolled-where-a-dependency-exists candidates. Findings, all bilingual from birth: - proposed/simplification: eventsource-parser for llm-deepseek SSE, node:timers/promises for three hand-rolled sleeps, turndown (or minimal 'entities') for tool-web HTML->markdown, gate-script consolidation onto mdast/parseArgs/globSync - proposed/testing: execa + parseArgs + loadEnvFile + vi.waitFor for hand-rolled test subprocess plumbing - proposed/process: pnpm/action-setup for symmetric CI caching - proposed/feature: evaluate landstrip before building a Windows sandbox launcher - rejected/simplification: ~30 swap verdicts recorded (vscode-jsonrpc, p-retry, Ajv, write-file-atomic, msw, hono, better-sqlite3, wireit, landstrip-for-linux, YAML consolidation, ...) so the survey is not re-litigated from scratch Also drops the stale prompt/ entry from the AGENTS.md layout map (workspace instructions live in packages/context/workspace-context).
4.6 KiB
Agent Note: Prefer maintained dependencies over hand-rolling
Status: implemented
English | 中文
Problem
The harness hand-rolls a lot of infrastructure that mature external packages already provide. Some of that is deliberate — vendored Cordis (vendoring decision), the twin LLM adapters, schemastery as the config-schema standard — but much of it accreted from an unstated "avoid new dependencies" reflex: the repo-wide external dependency list stayed tiny while packages grew their own SSE parsers, protocol framers, retry loops, and glob matchers. Nothing in AGENTS.md actually stated a dependency policy, so agents inferred one from the existing pattern, and the inferred rule ("don't add deps") is stricter than anyone decided. That is the "Not Invented Here" fallacy operating by default: every hand-rolled clone of a well-maintained library is code we test, document, review, and debug ourselves, with none of the ecosystem's accumulated edge-case fixes.
Decision
Introducing an external dependency is a legitimate simplification, not a policy exception. When a well-maintained package (or a Node builtin at our engine floor) covers a hand-rolled surface, replacing the hand-rolled code is the preferred direction, subject to the same evidence standard as any other simplification: the swap must genuinely shrink what we own — code, tests, and contract surface — rather than merely relocate complexity behind a wrapper.
The bar for a new dependency:
- Net deletion. The dependency replaces real owned code (implementation + dedicated tests + docs), not hypothetical future code. A dep that only adds capability is a feature decision, not a simplification.
- Health. Actively maintained, widely used, sensible transitive footprint. A tiny unmaintained package trades our code for someone's abandoned code.
- Fit at the boundary. The package's semantics cover our actual contract; residual semantics we still hand-roll around it count against the swap.
- Not a settled seam. schemastery (config schemas), vendored Cordis, the
@earendil-workstwins, and other decisions recorded in implemented Agent Notes are not reopened by this policy; a swap that collapses a recorded design needs to beat the recorded rationale, not just cite this note.
packages/util/'s "zero-dependency" charter describes that group's export discipline — util packages stay free of harness dependencies so any group can depend on them — and does not ban external packages where they simplify; a util package whose entire job a maintained external package does better should be replaced by the dependency, not preserved for the charter.
Dependency-swap proposals are recorded as proposed/simplification Agent Notes like any other removal, with the candidate package, the deletable surface, residual semantics, and supply-chain considerations stated. The supply-chain proposal owns advisory scanning and update cadence for the dependency list this policy grows.
Alternatives considered
- Keep the implicit no-new-deps culture. Rejected: it was never a recorded decision, and its cost is concrete — hand-rolled protocol and parsing code duplicates battle-tested libraries, inflates the per-file coverage burden, and slows every reviewer who must re-derive edge cases the ecosystem already fixed.
- A hard allowlist of approved packages. Rejected: the repo is pre-release and the dependency set is small; a per-PR evidence bar (net deletion, health, fit) plus review keeps judgment where the context is, without a standing committee artifact that would itself need maintenance.
- Vendor every new dependency like Cordis. Rejected: vendoring is for packages we must patch or pin against upstream churn (vendoring decision); applying it broadly recreates the maintenance burden the dependency was meant to shed. Ordinary npm dependencies with lockfile pinning are the default.
Consequences
- Agents and contributors surveying for simplifications now treat "replace hand-rolled X with package Y" as in-scope output; dsh-find-simplifications carries the corresponding guidance.
- The dependency list will grow, and with it the supply-chain surface; the mitigations live in the supply-chain proposal, which this policy makes more urgent.
- Root
AGENTS.mdcarries the one-line rule; this note owns the rationale and the bar.