build: doc-sync gates — typecheck doc code blocks + verify event taxonomy (RFC 006 pts 1-2)
Two tsx CI gates make doc/code drift fail fast: - doc-typecheck extracts every fenced ts block from README/docs/package READMEs, compiles them with tsc --noEmit against a temp project (vendor->lib, harness->src paths from tsconfig.typecheck.json), and fails on errors. Deliberate sketches opt out with ```ts ignore-check; the opt-out ratio is reported and capped. - verify-event-taxonomy asserts the docs/architecture.md taxonomy table names exactly the events declared in the interface Events blocks. This surfaced three events the table had been missing (tools/change, llm/adapter-change, system-prompt/change), now added. Doc snippets made compilable with stub imports/declares (1 genuine sketch ignored). Wired into CI after typecheck. API reports (RFC 006 pt 3) deferred. Graduates RFC 006 pts 1-2 -> ADR 0014.
This commit is contained in:
23
docs/adr/0014-doc-sync-enforcement.md
Normal file
23
docs/adr/0014-doc-sync-enforcement.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# ADR 0014: Doc-sync enforcement
|
||||
|
||||
Status: accepted (2026-06-14)
|
||||
|
||||
## Context
|
||||
|
||||
AGENTS.md promises that docs and code stay strictly in sync, but the promise was verified by eyeball. Review caught drift twice — a cookbook example contradicting the type policy, and a README citing the wrong `registerAdapter` call. Out-of-sync docs are worse than no docs, and this codebase is built primarily by agents that follow gates far more reliably than prose (ADR 0007). Two classes of doc drift are mechanically checkable: code blocks that no longer compile, and the event-taxonomy table that duplicates the `interface Events` declarations.
|
||||
|
||||
## Decision
|
||||
|
||||
Two CI gates, mirroring the existing `scripts/` style (tsx ESM, one job each):
|
||||
|
||||
1. **`doc-typecheck`** extracts every fenced ` ```ts ` block from `README.md`, `docs/**`, and `packages/*/README.md`, writes them to a temp project, and compiles with `tsc --noEmit`. The temp tsconfig copies only resolution-relevant options and the workspace `paths` map from `tsconfig.typecheck.json` (vendor → built `lib`, harness → `src`) — resolving vendor to `lib` is essential, or tsc type-checks raw vendor source and floods the run. A block that is a deliberate sketch opts out with an explicit ` ```ts ignore-check ` info string; the script reports the opt-out ratio and fails if it exceeds half, so the escape hatch can't quietly become the norm.
|
||||
2. **`verify-event-taxonomy`** extracts the event names from the `interface Events` blocks across `packages/*/src` and from the taxonomy table in `docs/architecture.md`, and asserts the two sets match exactly. Verify, don't generate: the table keeps its hand-written Mode/Purpose columns; only the set of names is checked. (Landing this surfaced three events the table had been missing — `tools/change`, `llm/adapter-change`, `system-prompt/change`.)
|
||||
|
||||
Both run in CI after `yarn typecheck` (which emits the vendor `lib/` that doc-typecheck resolves against). API-extractor golden reports (RFC 006 part 3) were deliberately **deferred** — low value for an internal monorepo where reviewers already see the source diff, and a heavy, finicky dependency.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Doc drift in the two checkable classes now fails CI instead of waiting for a reviewer to notice. This is an instance of ADR 0007's "mechanical gates over prose."
|
||||
- Making doc snippets compile costs a few stub imports/`declare`s; the `ignore-check` ratio must stay low or the gate is theater (the ratio guard enforces this).
|
||||
- The taxonomy check is name-only — a wrong Mode or Purpose column still needs human review. Generating the table from source was considered and rejected as more machinery than the problem warrants.
|
||||
- API reports remain available to revisit if the packages are ever published externally.
|
||||
@@ -25,3 +25,4 @@ Do NOT write an ADR for: a mechanical or local choice (a variable name, a one-fi
|
||||
| [0011](0011-runtime-arg-validation.md) | Runtime arg validation at the model boundary | accepted |
|
||||
| [0012](0012-dev-invariants-over-deep-readonly.md) | Dev-mode invariants over compile-time deep-readonly | accepted |
|
||||
| [0013](0013-property-based-testing.md) | Property-based testing for protocol-shaped code | accepted |
|
||||
| [0014](0014-doc-sync-enforcement.md) | Doc-sync enforcement (doc code blocks + event taxonomy) | accepted |
|
||||
|
||||
@@ -164,7 +164,7 @@ Error containment: a throwing `agent/turn-continuation` listener or a rejecting
|
||||
|
||||
### Event taxonomy
|
||||
|
||||
Declared in `@deepseek-ai/dsh-agent` (so nothing depends on the loop package).
|
||||
The `agent/*` events are declared in `@deepseek-ai/dsh-agent` (so nothing depends on the loop package); each other service declares its own events (`tools/*`, `llm/*`, `system-prompt/*`, `session/*`). The table below is CI-verified against the `interface Events` declarations in source (`scripts/verify-event-taxonomy.ts`).
|
||||
|
||||
| Event | Mode | Purpose |
|
||||
|---|---|---|
|
||||
@@ -177,8 +177,11 @@ Declared in `@deepseek-ai/dsh-agent` (so nothing depends on the loop package).
|
||||
| `agent/turn-continuation` | **waterfall** | override the continue/stop decision |
|
||||
| `agent/error` | emit | step/turn errors |
|
||||
| `tools/execute` (dsh-tools) | **waterfall** | wrap/veto/sandbox tool execution |
|
||||
| `tools/change` (dsh-tools) | emit | a tool was registered/unregistered |
|
||||
| `llm/stream` / `llm/generate` (dsh-llm) | **waterfall** | model-call interception |
|
||||
| `llm/adapter-change` (dsh-llm) | emit | an adapter was registered/unregistered |
|
||||
| `system-prompt/assemble` (dsh-system-prompt) | **waterfall** | mutate the assembly |
|
||||
| `system-prompt/change` (dsh-system-prompt) | emit | a section/tool-provider changed |
|
||||
| `session/created` / `session/event` (dsh-session) | emit | session lifecycle + log feed |
|
||||
| `session/flush` (dsh-session) | parallel (awaited) | durability checkpoint |
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ How to give the model a new capability. Reference implementations: `examples/ech
|
||||
## The minimal shape
|
||||
|
||||
```ts
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import type { Context } from 'cordis'
|
||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ How to connect a new model provider. Reference implementations: `packages/llm-de
|
||||
|
||||
## The shape
|
||||
|
||||
```ts
|
||||
```ts ignore-check
|
||||
class MyAdapter extends LlmAdapter {
|
||||
async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> { … }
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ A tool registers on `ctx.tools`. The annotated `defineTool` example (typed `exec
|
||||
A hook wraps the `tools/execute` waterfall to veto or rewrite a call — the seam where sandbox, permission, and plan-mode plugins live.
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
import type { ToolExecution } from '@deepseek-ai/dsh-tools'
|
||||
|
||||
declare function isAllowed(exec: ToolExecution): Promise<boolean>
|
||||
|
||||
export const name = 'permission-gate'
|
||||
|
||||
export function apply(ctx: Context) {
|
||||
@@ -32,6 +37,11 @@ export function apply(ctx: Context) {
|
||||
A UI plugin consumes `agent/stream-chunk` and session events for rendering, and drives input back in via `agent.send()` / `agent.steer()`.
|
||||
|
||||
```ts
|
||||
import type { Context } from 'cordis'
|
||||
|
||||
declare function render(text: string): void
|
||||
declare function onUserInput(handler: (text: string) => void): void
|
||||
|
||||
export const name = 'my-ui'
|
||||
export const inject = ['agents']
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# RFC 006: Doc-sync enforcement and API reports
|
||||
|
||||
Status: proposed
|
||||
Status: implemented (parts 1-2) — see [ADR 0014](../adr/0014-doc-sync-enforcement.md). Part 3 (API reports) deferred.
|
||||
|
||||
## Problem
|
||||
|
||||
|
||||
@@ -9,6 +9,6 @@ Proposals for substantial future work — reviewed before implementation, unlike
|
||||
| [003](003-deterministic-and-stress-testing.md) | Deterministic tests + replay invariant fixture + race stress | proposed |
|
||||
| [004](004-architectural-conformance.md) | Architectural rules: dependency-cruiser, adapter conformance kit | proposed |
|
||||
| [005](005-runtime-validation-and-error-taxonomy.md) | Runtime arg validation, structured error taxonomy, dev-mode invariants | partially implemented |
|
||||
| [006](006-doc-sync-and-api-reports.md) | Doc-sync enforcement and API extractor reports | proposed |
|
||||
| [006](006-doc-sync-and-api-reports.md) | Doc-sync enforcement and API extractor reports | implemented (pts 1-2; pt 3 deferred) |
|
||||
| [007](007-supply-chain-and-vendor-drift.md) | Supply chain checks and vendor drift verification | proposed |
|
||||
| [008](008-immutable-public-surfaces.md) | Deep-readonly public surfaces | implemented (revised) |
|
||||
|
||||
Reference in New Issue
Block a user