From 5bccda19d353cc18e6f746a962d5400cbd5319ce Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:06:20 +0800 Subject: [PATCH] docs(rfc): keep implemented RFCs current; rewrite the llm-replay section in place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrite the snapshot-test RFC's replay-plugin section to state current reality directly (the plugin is the @deepseek-ai/dsh-llm-replay package, under the coverage gate) instead of keeping the old example-local text with a "superseded" note bolted on. - Add docs/rfc/implemented/AGENTS.md (+ CLAUDE.md symlink): an implemented RFC must be kept current with what actually shipped — update paths/names/structure in the same change that moves the code, in place, not as an append-only changelog of its own drift. A reversal of the DECISION is still a new RFC. - Reconcile docs/rfc/README.md: the "never edited into a different decision" rule now distinguishes tracking where a decision lives (required) from flipping the decision (forbidden), and the implemented/ bullet points at the new convention. --- docs/rfc/README.md | 4 ++-- .../implemented/2026-06-19-acp-snapshot-tests.md | 6 ++---- docs/rfc/implemented/AGENTS.md | 15 +++++++++++++++ docs/rfc/implemented/CLAUDE.md | 1 + 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 docs/rfc/implemented/AGENTS.md create mode 120000 docs/rfc/implemented/CLAUDE.md diff --git a/docs/rfc/README.md b/docs/rfc/README.md index 62d625cc84..e7166e7691 100644 --- a/docs/rfc/README.md +++ b/docs/rfc/README.md @@ -7,7 +7,7 @@ One kind of design doc lives here. An **RFC** records a decision or proposal tha Files are grouped by lifecycle into three folders, and an RFC moves between them as its status changes: - **`proposed/`** — proposals reviewed before implementation; not yet built (or only partly). -- **`implemented/`** — the decision shipped. The file records what was decided and what was rejected. +- **`implemented/`** — the decision shipped. The file records what was decided and what was rejected, and is **kept current with what actually shipped**: when the code later moves a file, renames a package, or changes a key/default, the RFC is updated in the same change to match (facts only — paths, names, structure — not the decision itself). See [implemented/AGENTS.md](implemented/AGENTS.md). - **`rejected/`** — the proposal was considered and declined. Kept for the record so the rejection isn't re-litigated. Each file is named `yyyy-mm-dd-topic-title.md`, where the date is when the topic was **first proposed** (per git history). Cross-references between RFCs use relative markdown links (`[topic](../implemented/2026-…-….md)`) — never bare prose or numbers — so they are mechanically checkable and survive moves between folders. @@ -16,7 +16,7 @@ Each file is named `yyyy-mm-dd-topic-title.md`, where the date is when the topic Write an RFC when a decision is **durable** (it shapes the codebase beyond a single function or package), **contested** (there was a real alternative a reasonable engineer might have chosen), and **surprising** (a future reader would otherwise ask "why on earth is it done this way?"). A proposal for substantial future work starts in `proposed/`; a decision already made starts in `implemented/`. -Do NOT write one for a mechanical or local choice (a variable name, a one-file refactor), for anything already enforced and explained by a gate or a convention in AGENTS.md, or for a still-provisional decision tagged `TODO(...)` in the code — record those as TODOs and promote to an RFC only once they settle. An RFC is never edited into a different decision: supersede it with a new one and cross-link. +Do NOT write one for a mechanical or local choice (a variable name, a one-file refactor), for anything already enforced and explained by a gate or a convention in AGENTS.md, or for a still-provisional decision tagged `TODO(...)` in the code — record those as TODOs and promote to an RFC only once they settle. An RFC is never edited into a *different decision*: supersede it with a new one and cross-link. (Editing an `implemented/` RFC to track where its already-made decision now *lives* — a moved file, a renamed package — is not a different decision and is required, not forbidden; see [implemented/AGENTS.md](implemented/AGENTS.md).) ## Proposed diff --git a/docs/rfc/implemented/2026-06-19-acp-snapshot-tests.md b/docs/rfc/implemented/2026-06-19-acp-snapshot-tests.md index f720f96354..cf61386509 100644 --- a/docs/rfc/implemented/2026-06-19-acp-snapshot-tests.md +++ b/docs/rfc/implemented/2026-06-19-acp-snapshot-tests.md @@ -64,11 +64,9 @@ Both surfaces contain non-deterministic values that a pure normalization functio Determinism of the tool environment comes from a per-test `mkdtemp` cwd, the executor's existing secret-scrubbing env (`/KEY|SECRET|TOKEN/i`), the fresh non-login `bash -c` per call, and the normalization pass — **not** from an OS sandbox. A real rootless sandbox (bwrap on Linux, sandbox-exec/Seatbelt on macOS) is the established cross-platform pattern (Claude Code, Codex), but it is per-OS, fragile on newer kernels (Ubuntu 24.04+ AppArmor blocks unprivileged user namespaces), and unnecessary for transcript determinism. It is reserved as a future tier via the documented `BashExecutor` capability seam ([a sandboxing executor replaces dsh-bash-local without touching a tool schema](2026-06-13-capability-seams.md)) — a new `bash-*` package, not a change here. Scenarios keep bash commands tightly constrained (no `date`/`env`/background/large-output) so the temp-dir tier suffices. -### Example-local plugin, not a new package +### The replay plugin is its own package -> **Superseded (2026-06-19, same day):** the replay plugin was subsequently promoted to its own package, `@deepseek-ai/dsh-llm-replay` (`packages/llm-replay/`), and the snapshot config now references it by package name. The reason was not a second consumer but the per-file 100% coverage gate: logic under `examples/` is not measured, so the derive/parse/replay branches were unguarded — packaging them subjects all branches to the gate. The paragraph below records the original (now-outdated) decision. - -The replay plugin lives at `examples/acp-agent/src/llm-replay.ts`, referenced from the snapshot config by relative path — exactly how echo-agent wires its [mock-llm.ts](../../../examples/echo-agent/src/mock-llm.ts). It is test/example infrastructure with one consumer; the capability-seams rule says not to split into a published `packages/` trio preemptively. It is promoted to a package only when a second example needs it. +The replay plugin lives in its own package, `@deepseek-ai/dsh-llm-replay` (`packages/llm-replay/`), and the snapshot config references it by package name. It is the keyless replacement for the real LLM adapter: it installs an `llm/stream` waterfall listener and short-circuits it, serving model streams reconstructed from a recorded session JSONL. Its sole consumer is the ACP snapshot harness here, but it is a package (not example-local glue like echo-agent's [mock-llm.ts](../../../examples/echo-agent/src/mock-llm.ts)) so that its derive/parse/replay branches fall under the per-file 100% coverage gate on package `src` trees — logic under `examples/` is not measured by that gate, which would leave those branches unguarded. ### Two subcommands, replay in the default gate diff --git a/docs/rfc/implemented/AGENTS.md b/docs/rfc/implemented/AGENTS.md new file mode 100644 index 0000000000..e5ddc5eeea --- /dev/null +++ b/docs/rfc/implemented/AGENTS.md @@ -0,0 +1,15 @@ +# AGENTS.md — Implemented RFCs + +These are RFCs whose decision has **shipped**. The repo-wide and docs-wide rules still apply ([root AGENTS.md](../../../AGENTS.md) § "Type Safety and Documentation", [docs/AGENTS.md](../../AGENTS.md)); this file adds one rule specific to this folder. + +## Keep an implemented RFC current with what actually shipped + +An RFC in `implemented/` describes a decision that is now **live code**. Keep its description of the shipped reality accurate: when the implementation later moves a file, renames a package or symbol, changes a config key/default/error code, or relocates a plugin, update the RFC in the **same change** that touches the code — exactly as you would a package README. A stale implemented RFC (pointing at a path that no longer exists, naming a package that was renamed, describing a structure that was refactored) is worse than no RFC: a future reader trusts it and is misled. + +Update it **in place** to state the current truth. Do **not** leave the outdated text in and bolt on a "superseded / now actually…" note — that makes the document a changelog of its own drift and forces the reader to reconstruct the present from a pile of corrections. Write what is true now. + +### This is not a license to rewrite the *decision* + +Keeping the shipped-state description current is about **facts** (paths, names, structure, defaults) — not about silently flipping the **decision and its rationale** into a different one. If the underlying choice itself is reversed or materially changed (not just relocated), that is a new decision: write a new RFC and cross-link, per [rfc/README.md](../README.md) ("An RFC is never edited into a different decision"). The line: a refactor that moves where the decision is *realized* → edit this RFC to match; a reversal of *what was decided* → a new RFC. + +When in doubt, ask whether a reader following this RFC to the code would land on something real. If not, it needs updating. diff --git a/docs/rfc/implemented/CLAUDE.md b/docs/rfc/implemented/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/docs/rfc/implemented/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file