Codex review: the RFC claims the fixtures prove each guard fires, but the
binding-pattern guards (events + services), the service no-prose branch,
and the empty-@param/@returns-description branches had no focused tests.
Add the five missing cases; every violation branch in the generator now
has a matching fixture.
gen-cordis-catalog now hard-errors (aggregated, not fail-fast) when an
event lacks description prose or a payload @param, or a public service
method lacks JSDoc, a @param per parameter, a @returns on a non-void
result, or an explicit return type annotation. The this receiver and the
trailing waterfall next are exempt on events (mode machinery owned by
@mode); a stale @param naming no real parameter errors, mirroring the
@mode contradiction check. parseJsDoc now ends prose at the first block
tag (standard JSDoc semantics), so the tags never change the rendered
catalog — only Source: line pointers moved.
Fills the ~139 gaps found across the 15 surface files, extends the spec
with negative-path fixtures for every new guard plus the exemptions,
records the decision as an implemented process RFC, and extends the
AGENTS.md typed-events bullet with the authoring rule. Runs inside
verify-cordis-catalog -> doc-sync, so CI and pre-push enforce it with
zero new wiring.
Master's docs-overhaul stack (#142-#144) rewrote AGENTS.md into the
slim budget-gated form and repointed the review skill's citations.
Resolutions:
- AGENTS.md: master's rewrite wins; the no-hardcoded-tunables
convention is re-added as one terse bullet in the new style, after
'Explicit > implicit at package seams'. Within the verify-doc-budgets
ceiling, so no displacement or raise needed.
- dsh-code-review SKILL.md: master's repointed citations win; the
hardcoded-tunables reviewer check and the Conventions keyword are
re-applied on top.
- packages/README.md: master replaced the hand-maintained dependency
list (which carried this branch's chars-per-token wording) with a
pointer to the generated module graph — master's side taken whole;
the estimator wording lives on in the compact package READMEs.
The convergence pass found four summary-level sites still describing
the estimator as fixed char/4: packages/README.md (twice), the compact
group and interface READMEs, and compact-basic's package.json
description. All now say chars-per-token with the charsPerToken
default, matching the authoritative package README/module doc/RFC.
A Codex review pass on the draft caught four real gaps and two solid
suggestions; all addressed except one pushed back on the merits:
- hooks-claude/hooks-codex: stderrSummaryMaxChars was the one new knob
with NO range validation — a negative/NaN cap would silently
misbehave inside slice(). Both bridges now assert a positive integer
at the TOP of apply() (before the config-file parse's early return,
so a bad value fails the load loudly), with rejection tests.
- tool-fs: the read caps count lines/chars/bytes, so positive-FINITE
was too loose (a fractional readLimit would flow into windowing
arithmetic and the schema description). All four now require a
positive integer, matching tool-web's cap.
- Doc drift the gates cannot catch: tool-web's README tools table
still named WEB_SEARCH_MAX_RESULTS as the mechanism; compact-basic's
README/module doc and the compaction-capability-seam RFC still
described estimation as fixed char/4 rather than the charsPerToken
default.
- subagent-acp: the dispose graces were tested only at the
startAcpRun level, so a regression that stopped threading plugin
config into AcpRunSpec would have survived. A provider-path test now
drives the trap-escalation scenario through ctx.subagents.start with
small config graces and bounds dispose at 4s.
Pushed back on: converting compact-basic's charsPerToken to a
schemastery field. The package's whole config is deliberately
hand-rolled (resolveConfig, every threshold REQUIRED with no default —
a documented design posture); one schemastery field beside it would be
incoherent. The knob is cordis.yml-reachable, defaulted, and validated,
which is what the convention requires; migrating the package to
schemastery wholesale is pre-existing config-surface hygiene out of
this change's scope.
The audit swept every packages/*/* plugin for the new AGENTS.md
convention (no hardcoded tunables in plugins) and exposes each finding
as a defaulted, validated Config field. Defaults are the previously
hardcoded values throughout, so no deployment or golden changes.
- tool-fs (had NO Config): readLimit, readMaxLineLength, readMaxBytes,
readStreamMinSize. The caps thread through ReadToolCaps/ReadWindow —
read-render already documented that the consumer applies the caps, so
they become explicit per-request fields.
- tool-web: searchMaxResults (WEB_SEARCH_MAX_RESULTS stays as the
schemastery default). Also fixes the stale GREP_LIMIT references in
search.ts and the web-capability-seam RFC (no such constant exists).
- bash-local: graceMs (SIGTERM->SIGKILL escalation grace). The
RunInternals.graceMs test seam is gone: graceMs is now a required
SpawnSpec field filled from config, so tests exercise the real
config path and the defaults live in exactly one place.
- subagent-acp: disposeEofGraceMs / disposeGraceMs. The AcpRunSpec
fields become required for the same one-defaulting-layer reason.
- session-persistence-sqlite: journalMode ('wal' default; the
rollback-journal modes serve filesystems where WAL's shared-memory
files do not work, e.g. network mounts).
- hooks-claude + hooks-codex: stderrSummaryMaxChars for the persisted
hook/result stderr summary. The duplicated summarize() helpers merge
into hook-protocol's summarizeStderr(stderr, maxChars), beside the
HookResultRecord field it feeds, with the bound parameterized the
same way runHook's defaultTimeoutMs already is.
- compact-basic: charsPerToken for the token estimator (default 4, the
English-text heuristic; CJK-heavy deployments need ~1-2 or compaction
fires far too late). Also corrects the BasicCompactService class doc,
which claimed defaults the required-field config never had.
- fs-local: deletes the dead STREAM_MIN_SIZE constant and the dead
FsIoInternals.streamMinSize seam — the read-routing bound lives in
the consumer (tool-fs), where it is now config. This is item 1 of
the proposed prune-write-only-fs-surface RFC, annotated accordingly.
Every new field gets range validation (following the existing
assertPositiveFinite pattern), a README row, and tests covering the
configured behavior, the schema default, and load-time rejection.
A number or string that two reasonable deployments could want set
differently — a timeout, grace period, output cap, result-count limit,
model name, base URL — belongs on the plugin's schemastery Config with
the shipped value as its default, not in a bare literal or module
constant. A DEFAULT_* constant or a test-only injection seam is not
configurability: the test is whether a cordis.yml deployment can change
the value without a code edit. Protocol/wire constants, semantic
constants, values pinned by an external spec, and security invariants
stay hardcoded.
The convention lands in AGENTS.md § Conventions (the authoritative
source the review skill cites); dsh-code-review gains the matching
reviewer-only check, since no mechanical gate can detect a hardcoded
tunable.
ImageBlock had no production producer and every consumer dropped it:
the deepseek serializer skipped it, the pi-ai converter skipped it as
unrepresentable, the ACP bridge neither advertises image prompt
capability nor forwards image blocks, and compact-basic charged a flat
85-token estimate and rendered an [image] placeholder. A block
constructed today would silently vanish from the wire — the vocabulary
advertised a capability no path honors, the silent-data-loss shape the
defensive patterns warn against. The only constructors were tests
pinning the skip/estimate branches.
Remove ImageBlock and its ContentBlockMap entry (its cache?: CacheHint
field leaves with it; CacheHint itself and the other two cache? fields
are out of scope). compact-basic loses its explicit image estimate and
placeholder arms (the merge-extensible default arms absorb the case);
the deepseek serializer, pi-ai converter, and ACP codec already handled
image in their default arms, so only their image-naming comments
change. The codec's inbound rejection of ACP-protocol image prompt
content stays — that guards wire content a client can send regardless
of our vocabulary.
Tests that constructed harness image blocks to pin the removed branches
are dropped (the 85-token estimate pin) or retargeted onto plugin-added
block types / other non-text blocks, which the surviving default arms
own. Docs, the type-equiv pastes, and the content-block vocabulary
RFC's block list and multimodal-home consequence are updated in the
same change; the RFC moves to implemented/ and the index is
regenerated. A real multimodal feature reintroduces image via
declaration merging together with the adapter mapping, ACP
advertisement, and compaction pricing that honor it.
Exact-size ceilings turned every two-word wording fix into a gate
event. The policy amends to: a ceiling sits at least 5% above the
doc's current size (pre-rewrite) and keeps that margin when ratcheted
to target — routine edits pass, real growth still trips the gate.
Amended together in all four policy homes (docs/AGENTS.md § Budgets,
the doc-tiers RFC, the gate script's module comment, the skill's
ratchet rule) plus the manifest values, so prose and mechanics stay
consistent.
stdio-chat.spec.ts imports ContentBlock/StreamChunk types from
@deepseek-ai/dsh-llm; the manifest entry lived on the folded package
and did not move with the tests. Declared peer+dev like the module's
other harness deps (the src consumes dsh-llm vocabulary through the
session events it renders).
docs/testing.md's real-API tier bullet now names the provider-specific
key gating (EXA_API_KEY, PERPLEXITY_API_KEY, ...): each suite
self-skips on its own key, so a DEEPSEEK_API_KEY-only run has not
exercised the provider smokes.
- session.md § TurnEndReasonMap now carries the full precedence rule
the old architecture.md stated and the loop implements (loop.ts's
stepFinishReason carry-forward): max-tokens wins over completed only;
disposed/aborted/error take precedence. The architecture.md pointer
was otherwise citing an incomplete home.
- The cookbook feature-map compaction row no longer reads as if a
model-facing /compact tool ships: a manual trigger invokes the same
ctx.compact routine; the consumer tool is deferred per the
compaction RFC.
The stdio app's module doc and its unit suite's doc carried the claim
the review already corrected elsewhere: an inject-less app boots past a
collapsed export shape, so the smoke proves the tree runs while the
unwrapExports assertion in the unit suite pins the shape.
vitest.config.ts coverage comments (excessive-tests welcome -> docs/testing.md;
v8-ignore reason rule -> the quality-gates RFC) and the acp bridge.spec
resource-ownership comment -> docs/testing.md. Postmortem 0001's summary now
names packages/AGENTS.md as the export-shape rule's home. Repo-wide sweep from
the root (all file types, vendor/lib excluded) shows every remaining AGENTS.md
citation resolves to a rule that exists where cited.
The packages/README.md group table still described support/ as holding
the stdio UI; and three prose sites credited the keyless smokes with
guarding the app export shape, which a bundle without inject cannot do
(a stray default boots rather than crashes) — the shape is pinned by
the stdio-agent unit suite's explicit unwrapExports assertion; the
smokes prove the composed tree boots.
The package-topology paragraph of the web capability seam RFC still
described ctx.web as carrying 'one small selection-status layer',
contradicting the shipped surface the rest of the amended RFC records
(no aggregated status query; execution-time WebError explains a
selection failure). State the current shape: two capability kinds and a
richer selection policy whose failure the thrown error explains.
The definitive sweep (audit every AGENTS.md mention in packages/, docs/,
examples/, scripts/) found thirteen more citations of relocated policy
and two citations of rules that never existed as quoted:
- with-key policy comments (web deepseek/perplexity e2e headers) ->
docs/testing.md; real-impl-over-mock comments (acp harness, load,
stream-update specs) -> docs/testing.md; defensive-pattern quotes
(acp index.ts x3, stream-update) -> docs/defensive-patterns.md.
- md-tier repoints: real-api-e2e RFC, tool-schema-catalog RFC,
postmortem 0001 guardrail row, adding-a-package cookbook,
drop-bash-output-spill-files RFC, acp-subagent-backend RFC phrasing.
- Two false attributions dropped in favor of self-contained reasoning:
tool-todo's 'don't validate scenarios that can't happen' and the
bash-stdin-env RFC's 'Don't add features beyond what the task
requires' (neither rule ever existed under those names).
- Citations of the two 'not golden truth' doctrines stay: those bullets
survive verbatim in the root conventions.
Note: packages/support/ui-stdio readline TTY spec flakes under full
coverage on a heavily loaded box (passes standalone and passed the
same tree's coverage run minutes earlier); untouched by this stack.
WebService exposed an observation surface nothing in production observes:
the web/providers-change event (declared, emitted on every provider
registration/disposal, rollback-yield ordered before the emit solely so a
throwing change listener unwinds the registration) and the aggregated
searchStatus()/fetchStatus() query with its WebCapabilityStatus union.
dsh-tool-web executes through ctx.web.search()/fetch() and routes on the
structured WebError codes selection throws at execution time; tool
registration follows product enablement, not provider availability. The
only listeners/callers were the web packages' own tests, and the
tool-web README / architecture.md prose claiming the tool 'reads only the
aggregated searchStatus()/fetchStatus()' had drifted from the call sites.
Remove the event declaration, both emits, and the rollback-before-emit
machinery (the plain ctx.effect disposer keeps HMR cleanup, matching
LlmService.registerAdapter). Remove searchStatus()/fetchStatus(),
resolveStatus(), and WebCapabilityStatus; the provider-private status()
stays as the execution-time selection input. Delete the listener-throw
rollback test, and rewrite every event/status assertion across the web
packages' tests onto caller-observable behavior: a successful
search()/fetch() or the structured WEB_PROVIDER_* codes. Regenerate the
cordis catalog; update the web/tool-web READMEs, the architecture.md web
paragraph, core-data-structures/web.md, and the type-equiv manifest; amend
the web capability seam RFC's facts to the shipped surface. This follows
the llm/adapter-change precedent: a boot-time backend-registry signal and
an availability probe distinct from executing both sit on the cut side of
its keep/cut criterion.
RFC: docs/rfc/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md
The readline UI lives inside @deepseek-ai/dsh-stdio-agent as the
in-package stdio-chat module; the packages/support/ui-stdio package is
gone. The app's front-door cluster always includes this UI and nothing
else composes it, so the boundary bought manifest/tsconfig/module-graph/
README/publint surface for a helper that is not independently
swappable — and a product app no longer depends on a support package
documented as not-product-surface.
createStdioChat, the StdioRuntime test seam, and both unit suites moved
verbatim (imports rewired to the module path); the named
name/inject/Config/apply export shape stays, being the contract the
app's ctx.plugin mount consumes. Coverage stays per-file 100%; the
built-bin smoke under plain node and both keyless Loader-path smokes
prove the published artifact and the demos end-to-end.
Implements docs/rfc/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md
(moved from proposed/ and amended to the shipped shape).
- Restore the universal JSDoc rule the rewrite dropped (module doc
comment + semantic JSDoc on every export), in root AGENTS.md § Type
safety and documentation — the generated-catalog RFC cites it as the
rule the generator enforces at the source.
- Repoint the six remaining citations of moved content that the
section-name grep missed (rule-title quotes and prose references):
agent-loop agent.ts, acp index.ts, acp turns.spec.ts, the Exa e2e
header, the real-api-e2e RFC, the doc-sync-enforcement RFC amendment,
and rfc/implemented/AGENTS.md's section-name casing.
- Fix two docs/testing.md overstatements: the unit tier also runs
examples/*/tests specs, and keyless-by-nature examples have no
with-key smoke.
- Displacement trims keep root AGENTS.md at 1,498/1,500.
Codex review found three gaps between the documented contract and what
the gate enforced, each proven by a failing probe before this fix:
- a generated region must sit directly under its own '## {Lifecycle}'
heading (the last H2 above the begin marker), so a drifted heading
can no longer leave the tables filed under the wrong section;
- the lifecycle set is closed like the class set: an unknown directory
under docs/rfc/ is a structure error, not an invisible subtree;
- an index-shaped table row linking into a lifecycle folder OUTSIDE the
generated regions is an error (prose links to RFCs stay legal), so
'listed exactly once' cannot be violated by a hand-added row.
knip: the walker/renderer internals (the closed sets, the heading and
marker helpers, the per-lifecycle renderer) have no importer beyond
this module — the public surface is rfcRoot, walkRfcTree, and
spliceReadme, the three names the generator CLI and the gate consume.
docs/rfc/README.md's per-lifecycle tables are now generated between
gen-rfc-index marker comments from each RFC's path (lifecycle/class),
H1 title (optional 'RFC: ' prefix stripped), and filename date, sorted
by date then filename — the one docs region every proposal wave edits
and every concurrent branch conflicts on becomes derived state.
scripts/rfc-index.ts owns the shared walker (closed lifecycle/class
sets, structure rules, parseable-H1 requirement) and the renderer;
gen-rfc-index.ts is the writer CLI; verify-rfc-classification.ts keeps
the structure check and asserts the committed regions byte-match a
fresh render (freshness subsumes the index-completeness check, since a
generated-from-disk table is definitionally complete and correctly
headed). A malformed H1 is a hard error in both directions, so the H1
is now load-bearing as the title source — the one nonconforming H1
(a status suffix duplicating the path) is normalized.
Implements docs/rfc/implemented/process/2026-07-04-generate-rfc-index-tables.md
(moved from proposed/ and amended to the shipped mechanics); the
classification RFC's verify-only stance carries the supersession
cross-link per implemented/AGENTS.md.