docs: adopt dependencies-over-hand-rolling policy from NIH audit

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).
This commit is contained in:
Tianyi Cui
2026-07-26 15:54:36 +08:00
parent 4c3e4d172f
commit 3a88912a22
30 changed files with 789 additions and 3 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
2026-07-26-dependency-swaps-rejected-by-nih-audit.md: 6ee4ce36bcc25b206eebedd18270021e4937761f
2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md: b983dfcfa12171bfe1ae9bc79936d3a5876e5e68

View File

@@ -0,0 +1,78 @@
# Agent Note: Dependency swaps rejected by the 2026-07 NIH audit
Status: rejected — every swap below fails the net-simplification bar on evidence; recorded so the survey is not re-run from scratch
English | [中文](2026-07-26-dependency-swaps-rejected-by-nih-audit.zh.md)
## Problem
A repository-wide "Not Invented Here" audit (2026-07-26, ten parallel surveys covering every package group, scripts/, native/, vendor/ edges, python/, test infrastructure, and CI) asked of each hand-rolled surface: would a maintained external package or Node builtin delete it with a net win under the [dependency policy](../../implemented/process/2026-07-26-dependencies-over-hand-rolling.md)? The positive findings became their own proposed notes. The negative verdicts carry equal value — each names a plausible-looking swap whose hand-rolled shape is load-bearing — but would otherwise live only in a PR body. This note freezes them.
## Proposal
Adopt the following dependency swaps. Rejected — per-item evidence below; a future proposal for any item must beat its recorded reason, not just re-cite the policy.
**Protocol and parsing:**
- **`vscode-jsonrpc` for LSP base-protocol framing/correlation** (`lsp-local`): the swappable core is ~255 of 2,112 src lines; the package cannot express the configured `maxMessageBytes` incoming-size bound (restoring it means rebuilding the deleted framing), inverts the cancel-grace teardown semantics (`raceAbort` rejects immediately then tears down; vscode-jsonrpc keeps the promise pending), errors on pre-header stdout banners real servers emit, and is CJS in an ESM-everywhere repo. The [LSP seam note](../../implemented/architecture/2026-07-15-lsp-capability-seam.md) assigns JSON-RPC ownership to `dsh-lsp-local`; this audit is the explicit on-record weighing of the dependency it lacked.
- **`vscode-languageserver-types` for lsp-local's wire-type subset**: ~80 type lines and ~45 guard lines, but upstream guards differ in both directions (accept `uri: undefined` the repo must reject; require `targetRange` the repo tolerates absent), and the initialize-result shapes live in `vscode-languageserver-protocol`, dragging `vscode-jsonrpc` in as a runtime dep — ~1 MB for 80 spec-exact lines.
- **`json-rpc-2.0` for `dsh-jsonrpc`**: deletable correlation/dispatch is real (~100130 lines) but the NDJSON wire must stay bit-identical for the hand-rolled Python SDK client, the package is single-maintainer, and the [GUI RPC note](../../implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md) already treats this package as a frozen narrow surface. `vscode-jsonrpc` is a worse fit still (Content-Length framing, cancellation vocabulary the protocol lacks).
- **`jsonrpcclient` for the Python SDK client**: v4 builds/parses messages only — ~20 lines — while the 500 lines that matter (subprocess lifecycle, threaded reader, id correlation, bidirectional server-role responses) stay; the library is in low-maintenance mode.
- **`eventsource-parser` for apiproxy's `readSse`**: only ~15 lines of framing are deletable, both wire ends are in-repo so spec conformance is moot, and it would add a dep to a browser-safe package. (Contrast with the [llm-deepseek proposal](../../proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.md), where a real provider sits across the wire.)
**Retry, timers, async:**
- **`p-retry`/`exponential-backoff` for `llm-retry`**: wrong execution model — the plugin is a decision-returning waterfall listener and the agent loop owns re-execution from the durable log; there is no function to re-invoke, which is those libraries' entire API. Provider `Retry-After` override, budget from prior-failure codes, durable `llm/retry` events, and HMR-quiescent abort are all uncovered. [Bounded-recovery note](../../implemented/architecture/2026-06-21-bounded-llm-request-recovery.md) already rejected SDK-owned retries.
- **`p-timeout`/`AbortSignal.timeout` for `dsh-timeout`**: the builtin cannot be disarmed early and carries a generic `TimeoutError`, not the capability-coded `TimeoutReason` that distinguishes nested deadlines; `idleWatchdog`'s per-demand rearm has no equivalent. [Timeout-library note](../../implemented/architecture/2026-07-06-timeout-deadline-library.md) owns the design.
- **`p-limit`/`p-queue` for the agent-loop tool-call pool**: pool bookkeeping is ~25 lines; the substance (model-ordered commits, mid-group reclassification, exclusive barriers, abort-drain with synthetic durable results) is not a concurrency-limiter shape.
- **`p-queue`/`async-mutex` for per-key promise-chain serializers** (`fs-local`, `storage-domain`): 814-line serializers; the packages are strictly larger than the code they would delete.
- **`events.once` + `AbortSignal.timeout` for subagent-subprocess `exitsWithin`**: `events.once` rejects if `error` fires first, but the hand-roll deliberately ignores `error` (captured separately by the spawn-failure path); the swap changes teardown-race behavior in exactly the code whose semantics are teardown races.
**Data and validation:**
- **Ajv for the tools JSON Schema validator**: the [schema-DSL note](../../implemented/architecture/2026-07-20-unified-json-value-schema-dsl.md) explicitly rejected accepting a larger schema language; the validator also does realm-intrinsic prototype checks Ajv does not.
- **`structuredClone` for session `snapshotJsonValue`/`isJsonValue`**: it is a validator + detacher enforcing the lossless-JSON boundary with single-read-per-getter and cross-realm intrinsic checks; `structuredClone` accepts Map/Date/-0 and enforces nothing. Same for the deliberately dependency-free `code-runtime-worker` mirror hardened against a model-mutated realm.
- **`fast-deep-equal` for session surface `isDeepEqualJson`** and **`safe-stable-stringify` for repeat-tool-guard canonicalization**: both swaps work mechanically but each trades ~1720 commented, tested lines for the first external runtime dependency of a core package — negative net at this size.
- **zod/valibot for durable-event strict decoders** (goal fold, tool-ralph, session): exact-key fail-loud decoders at durable boundaries with event-specific messages; a second schema library beside repo-standard schemastery is a policy change, not a deletion.
- **`gpt-tokenizer`/tiktoken for token-meter**: the [replay-token-meter note](../../implemented/architecture/2026-07-15-replay-token-meter-service.md) explicitly rejected tokenizer backends; a GPT BPE is also the wrong tokenizer for DeepSeek models, and ~350 of the package's lines are replay-fold bookkeeping no tokenizer covers.
- **`partial-json` for streamed tool-call arguments**: nothing to replace — arguments stay raw JSON strings end-to-end by documented contract; `JSON.parse` runs only on complete payloads.
**Filesystem, subprocess, terminal:**
- **`write-file-atomic` for fs-local/storage-json atomic writes**: the packages lack the private 0700 staging dir, Win32 DACL copy/`ReplaceFileW`, AbortSignal support, and parent-dir fsync — each the point of the hand-roll. The koffi Win32 bindings themselves are justified by the [Windows durable-publish note](../../implemented/architecture/2026-07-05-windows-jsonl-durable-publish.md).
- **`fzstd`/native zstd packages for JSONL frame scanning**: `node:zlib`'s builtin zstd already does the compression ([zstd note](../../implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md), which explicitly rejected an external native dependency); the remaining `scanZstdFrames` locates RFC 8878 frame boundaries *without decompressing* for torn-tail repair, which no package exposes.
- **`picomatch`/`tinyglobby`/`ignore` for fs search**: no glob engine exists — both discovery tools shell out to ripgrep per the [bash-backed discovery note](../../implemented/feature/2026-07-09-bash-backed-grep-glob-discovery.md).
- **`istextorbinary`/`chardet` for text detection**: the hand-roll is a ~15-line NUL-sample plus fatal `TextDecoder`; heuristic packages are larger and would change which files the model can read (model-visible `FS_NOT_TEXT` drift).
- **`shell-quote` for POSIX single-quoting**: two 1-line quoting helpers with exhaustive tests versus a maintenance-mode package with a CVE history and different escaping output — a safety boundary is the wrong place to save one line.
- **`strip-ansi` for pty sanitization**: the pty sanitizer is a streaming state machine with split-sequence carry across chunks and OSC `133;D` prompt-marker extraction (the shell-readiness signal); stateless strippers replace ~20 inner lines while all state machinery stays. `stripVTControlCharacters` also demonstrably leaks unterminated-OSC payloads the session-title normalizer must strip (anti-spoofing).
- **`pidtree`/`ps-tree` for the pty process inspector**: bare PID trees; the code needs start-time identity against PID reuse plus `/proc` stdin-wait detection no package does.
- **`execa` for the subagent-subprocess dispose ladder**: `forceKillAfterDelay` covers SIGTERM→SIGKILL but not the stdin-EOF-first cooperative tier or the reject-if-no-exit-edge contract; adopting it here rewrites spawn sites while keeping the ladder. (Test-infrastructure spawn plumbing is different — see the [execa proposal](../../proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.md).)
- **`tree-kill` for acp-snapshot teardown and lsp process kill**: the lines are drain-ordering/error-propagation, not tree traversal; lsp/bash already use detached process groups + taskkill.
- **node-pty everywhere for the TUI test driver**: [Windows-TUI note](../../implemented/feature/2026-07-20-windows-tui-support.md) explicitly rejected node-pty-on-every-host; it is already the Windows leg.
**Servers and HTTP:**
- **`msw` for llm-mock-server**: the server exists to fault the wire — socket destroy, mid-SSE disconnect, stall, pre-listen refusal — for real HTTP adapters and subprocesses; in-process interception can express none of that. [Wire-fault-server note](../../implemented/testing/2026-07-25-scriptable-llm-wire-fault-server.md) owns the design.
- **`hono`/`sirv` for host/webserver**: the core is a disposer-based dynamic route registry (registrations-are-effects contract, HMR unregistration) plus index-HTML transform taps; hono routers are add-only, and static middleware cannot serve the transformed index. ~244 lines total, genuinely small.
- **`@mozilla/readability`/`iconv-lite` for web-fetch-local**: the provider returns raw HTML; charset handling is already the builtin `TextDecoder`; MIME parsing is ~11 lines; redirect following is same-origin security policy.
**SQLite and storage:**
- **`better-sqlite3` for the three SQLite backends**: all use builtin `node:sqlite`, intentional twice over — it gates the [Node engine floor](../../implemented/process/2026-07-06-node-engine-floor.md) and works inside the single-file executable where a native addon would complicate packaging. No hand-rolled migrations or busy-retry loops exist.
**Repo tooling:**
- **`wireit` for `run-gates.ts`**: could express the `needs:` graph, but allowFailure observational legs and mode-specific concurrency caps have no equivalent, caching must be defensively disabled for a correctness gate runner, and every CI workflow invocation would restructure. The [parallel-gates note](../../implemented/process/2026-07-06-parallel-pre-push-gates.md) accepts a custom scheduler as the cost; keep is defensible.
- **`@arethetypeswrong/cli` for `verify-node-next-types`**: attw is per-package (100+ invocations vs one fast whole-workspace compile) and does not check the repo-specific explicit-`.ts`-specifier invariant, so the scan half stays regardless. Recorded as considered; keep the script.
- **`syncpack`/`manypkg` for `check-workspace-constraints.ts`**: they cover ~20 lines of range alignment; the load-bearing 200+ lines (computed `files` lists, cordis peer=dev pairing, hierarchy shape) are repo policy no generic engine expresses.
- **`remark-validate-links` for `verify-md-links.ts`**: the gate rides the repo's shared mdast toolchain; adopting remark-cli adds a second markdown stack to delete one small file.
- **`prebuildify`/`node-gyp-build` for the landlock launcher packaging**: inapplicable — those load `.node` addons via dlopen; the launcher ships a standalone exec'd static binary, and per-platform `optionalDependencies` *is* the ecosystem convention for binaries.
- **Replacing the Landlock launcher itself with `@landstrip/landstrip`**: fails the security-invariant test — the launcher is a ~300-line reviewable C file with byte-pinned provenance that already migrated away from a Rust dependency; a single-maintainer LGPL Rust binary set is a larger audit surface with weaker provenance. (The unbuilt Windows rung is a different question — see the [landstrip evaluation proposal](../../proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md).)
- **`hatch-nodejs-version` for Python release versioning**: roughly LOC-neutral (a custom metadata hook replaces the regex), inverts the recorded decision that the dev sentinel never determines a release version, and puts a single-maintainer build plugin in the release supply chain.
- **YAML consolidation (`js-yaml` vs `yaml`)**: the repo carries both parsers, with the `!!js` tag defined three times on js-yaml (vendored include, app-boot, apps/cli) and twice on `yaml` (sdk-telemetry's `ScalarTag`, sdk-helper's comment-preserving Document editing). The direction is forced — js-yaml cannot replace `yaml` (sdk-helper needs the Document API) — but migrating the js-yaml sites cannot retire the library either (the vendored include pins it) and would put two parsers in charge of one dialect that must agree exactly, against the [personal-config note](../../implemented/feature/2026-07-20-dsh-cli-personal-config.md)'s deliberate load-only-copy parity. Deletable: ~2025 lines of duplicate tag definitions and two `@types/js-yaml` entries. The consolidation moment is a future include sync, not now.
## Alternatives considered
- **Record nothing and let the PR body carry the verdicts.** Rejected: PR bodies are not part of the maintained record, and the whole point of surveying is that the next audit starts from these verdicts instead of re-deriving them.
- **One rejected note per item.** Rejected: ~30 files of ceremony for verdicts that share one evidence standard and one fate; per-item notes are warranted only if an item is re-proposed with new evidence.
- **Fold each verdict into the implemented note that owns the seam.** Partially done — where an owning note already rejected the alternative (retry, token-meter, schema DSL, zstd, sandbox, node-pty), this note cites rather than duplicates it. The remaining items have no owning note, which is why they are recorded here.

View File

@@ -0,0 +1,78 @@
# Agent Note: 2026-07 NIH 审计否决的依赖替换
Status: rejected — 下列每一项替换在证据上都未达到净简化门槛;记录在案,以免这轮普查日后从零重来
[English](2026-07-26-dependency-swaps-rejected-by-nih-audit.md) | 中文
## 问题
一次仓库级的「Not Invented Here非我发明」审计2026-07-26十路并行普查覆盖每个包package分组、scripts/、native/、vendor/ 边界、python/、测试基础设施与 CI对每一处手写接口面追问同一个问题在[依赖政策](../../implemented/process/2026-07-26-dependencies-over-hand-rolling.md)之下,是否有持续维护的外部包或 Node 内置能力能以净收益把它删除?得出肯定结论的发现已各自写成独立的提案 Agent Noteagent 决策记录)。否定裁定的价值不相上下——每一条都点名了一个看似可行、实则手写形态在承重的替换——但否则它们只会留存在某个 PRPull Request正文里。本 note 将它们固化在案。
## 提案
采纳下列依赖替换。已否决——逐项证据见下;未来针对任何一项的提案都必须胜过其记录在案的理由,而不能只是重新援引政策。
**协议与解析:**
- **以 `vscode-jsonrpc` 承担 LSP 基础协议的分帧/关联**`lsp-local`):可替换的核心只占 src 全部 2,112 行中的约 255 行;该包无法表达可配置的 `maxMessageBytes` 入站大小上限(要恢复它就得重建被删掉的分帧代码),反转了取消宽限期的拆除语义(`raceAbort` 立即 reject 再拆除vscode-jsonrpc 让 promise 保持挂起),会在真实服务器输出的 header 前 stdout 横幅上报错,而且在这个 ESM 通行的仓库里它是 CJS。[LSP seam 决策](../../implemented/architecture/2026-07-15-lsp-capability-seam.md)把 JSON-RPC 的所有权划给 `dsh-lsp-local`;本次审计正是对该决策当时缺失的这项依赖权衡的明文记录。
- **以 `vscode-languageserver-types` 承担 lsp-local 的协议类型子集**:约 80 行类型加约 45 行守卫,但上游守卫在两个方向上都与本仓库不一致(接受本仓库必须拒绝的 `uri: undefined`;强制要求本仓库容忍缺失的 `targetRange`),而且 initialize 结果的形状住在 `vscode-languageserver-protocol` 里,会把 `vscode-jsonrpc` 拖成运行时依赖——为 80 行严格贴合规范的代码付出约 1 MB。
- **以 `json-rpc-2.0` 替换 `dsh-jsonrpc`**:可删除的关联/分发代码确实存在(约 100130 行),但 NDJSON 协议格式wire format必须与手写的 Python SDK 客户端逐位一致,该包只有单一维护者,且 [GUI RPC 决策](../../implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)已把这个包当作冻结的窄接口面对待。`vscode-jsonrpc` 更不合适Content-Length 分帧、该协议并不具备的取消词汇)。
- **以 `jsonrpcclient` 承担 Python SDK 客户端**v4 只做消息的构造/解析——约 20 行——而真正要紧的 500 行子进程生命周期、线程化读取器、id 关联、双向的服务端角色应答)全都保留;该库处于低维护模式。
- **以 `eventsource-parser` 替换 apiproxy 的 `readSse`**:可删除的分帧只有约 15 行,线路两端都在仓库内,规范符合性无关紧要,而且这会给一个浏览器安全的包添加依赖。(对比 [llm-deepseek 提案](../../proposed/simplification/2026-07-26-eventsource-parser-for-deepseek-sse.md):那里线路对面是真实的提供方。)
**重试、定时器与异步:**
- **以 `p-retry`/`exponential-backoff` 替换 `llm-retry`**:执行模型不对——该插件是一个返回决策的 waterfall瀑布式事件监听器重新执行由 agent loop智能体循环依据持久日志负责根本不存在可供重新调用的函数而那恰是这些库的全部 API。提供方 `Retry-After` 覆写、依据先前失败代码计算预算、持久化的 `llm/retry` 事件、HMR热模块替换完全停稳式中止全都无从覆盖。[LLM大语言模型请求受限恢复决策](../../implemented/architecture/2026-06-21-bounded-llm-request-recovery.md)已经否决了由 SDK 持有的重试。
- **以 `p-timeout`/`AbortSignal.timeout` 替换 `dsh-timeout`**:内置能力无法提前解除,抛出的是通用 `TimeoutError`,而不是能区分嵌套截止时限、按能力编码的 `TimeoutReason``idleWatchdog` 按需逐次重新装定的能力没有等价物。设计归[超时库决策](../../implemented/architecture/2026-07-06-timeout-deadline-library.md)所有。
- **以 `p-limit`/`p-queue` 替换 agent-loop 的工具调用池**:池的簿记只有约 25 行;实质部分(按模型顺序提交、组中途重新分类、排他屏障、带合成持久结果的中止排空)根本不是并发限制器的形状。
- **以 `p-queue`/`async-mutex` 替换按 key 的 promise 链串行器**`fs-local``storage-domain`):串行器只有 814 行;这些包严格大于它们所能删除的代码。
- **以 `events.once` + `AbortSignal.timeout` 替换 subagent-subprocess 的 `exitsWithin`**`error` 先触发时 `events.once` 会 reject而手写实现有意忽略 `error`(由 spawn 失败路径单独捕获);这次替换恰恰会在语义本身就是拆除竞态的那段代码里改变拆除竞态行为。
**数据与校验:**
- **以 Ajv 承担 tools 的 JSON Schema 校验器**[schema DSL 决策](../../implemented/architecture/2026-07-20-unified-json-value-schema-dsl.md)已明确否决接纳更大的 schema 语言;这个校验器还会做 Ajv 不做的、针对 realm 内建原型的检查。
- **以 `structuredClone` 替换会话的 `snapshotJsonValue`/`isJsonValue`**:它是校验器加分离器,以「每个 getter 只读一次」和跨 realm 内建对象检查强制执行无损 JSON 边界;`structuredClone` 接受 Map/Date/-0什么都不强制。有意保持零依赖、针对被模型篡改的 realm 做过加固的 `code-runtime-worker` 镜像实现同理。
- **以 `fast-deep-equal` 替换会话接口面的 `isDeepEqualJson`**、**以 `safe-stable-stringify` 承担 repeat-tool-guard 的规范化**:两项替换在机械层面都可行,但每一项都是拿约 1720 行带注释、有测试的代码,去换一个核心包的第一个外部运行时依赖——在这个体量上是净亏损。
- **以 zod/valibot 承担持久事件的严格解码器**goal fold、tool-ralph、session它们是位于持久化边界、键集精确匹配、失败即大声报错、带事件专属报错信息的解码器在仓库标准 schemastery 之外再放一个 schema 库是政策变更,不是删除。
- **以 `gpt-tokenizer`/tiktoken 替换 token-meter**[回放 token 计量决策](../../implemented/architecture/2026-07-15-replay-token-meter-service.md)已明确否决分词器后端GPT 的 BPE 对 DeepSeek 模型来说也是错误的分词器,而且这个包约 350 行是回放折叠簿记,任何分词器都覆盖不了。
- **以 `partial-json` 处理流式工具调用参数**:无可替换——按已记录的契约,参数端到端保持为原始 JSON 字符串;`JSON.parse` 只在完整载荷上运行。
**文件系统、子进程与终端:**
- **以 `write-file-atomic` 承担 fs-local/storage-json 的原子写**:这些包缺少私有 0700 暂存目录、Win32 DACL 复制/`ReplaceFileW`、AbortSignal 支持和父目录 fsync——每一项都正是手写实现的意义所在。koffi Win32 绑定本身由 [Windows 持久发布决策](../../implemented/architecture/2026-07-05-windows-jsonl-durable-publish.md)提供依据。
- **以 `fzstd`/原生 zstd 包承担 JSONL 帧扫描**`node:zlib` 内置的 zstd 已经负责压缩([zstd 决策](../../implemented/architecture/2026-07-19-zstandard-jsonl-session-logs.md),其中明确否决了外部原生依赖);剩下的 `scanZstdFrames` 为撕裂尾部修复*不做解压*地定位 RFC 8878 帧边界,没有任何包公开这项能力。
- **以 `picomatch`/`tinyglobby`/`ignore` 承担 fs 搜索**:根本不存在 glob 引擎——依照 [bash 承载的发现工具决策](../../implemented/feature/2026-07-09-bash-backed-grep-glob-discovery.md),两个发现类工具都通过 shell 调用 ripgrep。
- **以 `istextorbinary`/`chardet` 承担文本检测**:手写实现是约 15 行的 NUL 采样加 fatal 模式的 `TextDecoder`;启发式包体量更大,还会改变模型能读到哪些文件(模型可见的 `FS_NOT_TEXT` 漂移)。
- **以 `shell-quote` 承担 POSIX 单引号包裹**:两个各 1 行、测试详尽的引号辅助函数,对上一个处于维护模式、有 CVE 历史、转义输出还不一样的包——安全边界不是省一行代码的地方。
- **以 `strip-ansi` 承担 pty 净化**pty 净化器是一台流式状态机,带跨分片的断裂序列续接和 OSC `133;D` 提示符标记提取shell 就绪信号);无状态的剥离器只能替掉约 20 行内层代码,全部状态机构件原样保留。`stripVTControlCharacters` 还被实证会泄漏未终止的 OSC 载荷,会话标题归一化器必须剥除它们(反欺骗)。
- **以 `pidtree`/`ps-tree` 承担 pty 进程巡检器**:它们只给裸 PID 树;这段代码需要对抗 PID 复用的启动时间身份校验,加上 `/proc` stdin 等待检测,没有包做这些。
- **以 `execa` 承担 subagent-subprocess 的 dispose资源释放阶梯**`forceKillAfterDelay` 覆盖 SIGTERM→SIGKILL但覆盖不了先发 stdin EOF 的协作层级,也覆盖不了「无退出沿即 reject」契约在这里采用它意味着重写各 spawn 调用点、同时阶梯照旧保留。(测试基础设施的 spawn 管线是另一回事——见 [execa 提案](../../proposed/testing/2026-07-26-execa-for-test-subprocess-plumbing.md)。)
- **以 `tree-kill` 承担 acp-snapshot 拆除与 lsp 进程终止**那些代码行做的是排空顺序与错误传播不是进程树遍历lsp/bash 已经使用分离的进程组加 taskkill。
- **在 TUI 测试驱动器上到处使用 node-pty**[Windows TUI 决策](../../implemented/feature/2026-07-20-windows-tui-support.md)已明确否决在每个宿主上都用 node-pty它已经是 Windows 那一条腿。
**服务器与 HTTP**
- **以 `msw` 替换 llm-mock-server**这个服务器的存在意义就是在线路上制造故障——socket 销毁、SSEServer-Sent Events中途断连、停滞、监听前拒绝——服务对象是真实的 HTTP 适配器和子进程;进程内拦截一样都表达不了。设计归[线路故障服务器决策](../../implemented/testing/2026-07-25-scriptable-llm-wire-fault-server.md)所有。
- **以 `hono`/`sirv` 承担 host/webserver**:核心是基于 disposer 的动态路由注册表「注册即效果」契约、HMR 反注册)加 index HTML 变换挂点hono 的路由器只增不减,静态中间件也无法伺服变换后的 index。总共约 244 行,确实很小。
- **以 `@mozilla/readability`/`iconv-lite` 承担 web-fetch-local**:该提供方返回原始 HTML字符集处理已经是内置的 `TextDecoder`MIME 解析约 11 行;重定向跟随是同源安全策略。
**SQLite 与存储:**
- **以 `better-sqlite3` 承担三个 SQLite 后端**:三者全部使用内置 `node:sqlite`,且是双重有意为之——它是 [Node 引擎下限](../../implemented/process/2026-07-06-node-engine-floor.md)的把关依据,也能在单文件可执行体内工作,原生 addon 反而会让打包复杂化。不存在任何手写的迁移或 busy 重试循环。
**仓库工具链:**
- **以 `wireit` 替换 `run-gates.ts`**:它能表达 `needs:` 图,但 allowFailure 观测支路和按模式设置的并发上限没有等价物,对一个正确性门禁运行器来说缓存必须防御性禁用,而且每一处 CI 工作流调用都要重构。[并行门禁决策](../../implemented/process/2026-07-06-parallel-pre-push-gates.md)把自研调度器认作代价;保留是站得住的。
- **以 `@arethetypeswrong/cli` 替换 `verify-node-next-types`**attw 按包运行100+ 次调用对一次快速的全工作区编译),而且不检查仓库特有的显式 `.ts` 说明符不变式,因此扫描的那一半无论如何都得保留。记录为已考虑;保留脚本。
- **以 `syncpack`/`manypkg` 替换 `check-workspace-constraints.ts`**:它们只覆盖约 20 行的版本范围对齐;承重的 200+ 行(计算生成的 `files` 列表、cordis peer=dev 配对、层级形状)是仓库政策,没有通用引擎能表达。
- **以 `remark-validate-links` 替换 `verify-md-links.ts`**:该门禁搭载仓库共享的 mdast 工具链;采用 remark-cli 等于为删掉一个小文件而增加第二套 markdown 技术栈。
- **以 `prebuildify`/`node-gyp-build` 承担 landlock 启动器打包**:不适用——那些工具通过 dlopen 加载 `.node` addon这个启动器交付的是独立 exec 的静态二进制,而按平台划分的 `optionalDependencies` 恰恰*就是*二进制分发的生态惯例。
- **以 `@landstrip/landstrip` 替换 Landlock 启动器本身**:未通过安全不变式检验——启动器是一个约 300 行、可完整评审、来源逐字节锁定的 C 文件,且早已从一个 Rust 依赖迁移出来;单一维护者的 LGPL Rust 二进制集合是更大的审计面加更弱的来源保障。(尚未构建的 Windows 层级是另一个问题——见 [landstrip 评估提案](../../proposed/feature/2026-07-26-evaluate-landstrip-for-windows-sandbox-rung.md)。)
- **以 `hatch-nodejs-version` 承担 Python 发布版本号**:代码行数大致持平(一个自定义 metadata 钩子换掉那个正则却反转了「dev 哨兵值绝不决定发布版本」这条记录在案的决策,还把一个单一维护者的构建插件放进发布供应链。
- **YAML 归一(`js-yaml``yaml`**:仓库同时携带两个解析器,`!!js` 标签在 js-yaml 上定义了三次vendor 收录的 include、app-boot、apps/cli`yaml` 上定义了两次sdk-telemetry 的 `ScalarTag`、sdk-helper 的保留注释式 Document 编辑。方向是被迫的——js-yaml 无法取代 `yaml`sdk-helper 需要 Document API——但迁移 js-yaml 各调用点也退休不了这个库vendor 收录的 include 锁定了它),还会让两个解析器共管一种必须完全一致的方言,违背[个人配置决策](../../implemented/feature/2026-07-20-dsh-cli-personal-config.md)刻意的「仅加载副本」对等性。可删除的:约 2025 行重复标签定义和两条 `@types/js-yaml` 条目。归一的时机是未来某次 include 同步,不是现在。
## 曾考虑的替代方案
- **什么都不记录,让 PR 正文承载这些裁定。** 不予采纳PR 正文不属于受维护的记录,而普查的全部意义就在于下一次审计从这些裁定出发,而不是重新推导。
- **每一项各写一份 rejected note。** 不予采纳:为共享同一套证据标准、同一种命运的裁定制造约 30 个文件的仪式感;只有当某一项带着新证据被重新提出时,逐项 note 才有必要。
- **把每条裁定并入拥有该 seam 的 implemented note。** 部分已做——凡是持有方 note 已经否决过该替代方案的重试、token 计量、schema DSL、zstd、沙箱、node-pty本 note 一律援引而不重复。其余各项没有持有方 note这正是它们记录于此的原因。