docs(rfc): keep implemented RFCs current; rewrite the llm-replay section in place

- 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.
This commit is contained in:
Tianyi Cui
2026-06-19 17:06:20 +08:00
parent 4f291953ea
commit 5bccda19d3
4 changed files with 20 additions and 6 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -0,0 +1 @@
AGENTS.md