docs: address Codex review of the RFC reorg

- Fix two root-AGENTS.md cross-links that the depth bump left pointing at the
  new docs/AGENTS.md instead of the root file they cite (capability-seams,
  optional-code-mode). These resolved on disk so verify-md-links passed — the
  gate checks existence, not which file you meant; corrected to ../../../.
- Broaden verify-md-links scope to .agents/skills/**/*.md: this PR rewrote the
  dsh-code-review skill's links into the RFC tree, but the skill dir was outside
  the gate, so a broken skill link would have passed silently.
- Percent-decode the path component before the existence check, so a valid
  encoded relative target (My%20File.md) is not falsely reported broken; a
  malformed escape (%zz) is reported broken rather than crashing the gate.
- Drop the merged property-testing RFC's "nightly CI job 100x" claim: that line
  came from the original proposal, not the accepted decision, and CI has only
  push/pull_request triggers — note it as possible future work instead.

doc-sync (incl. verify-md-links over 58 files), doc-typecheck, lint pass.
This commit is contained in:
Tianyi Cui
2026-06-18 02:41:19 +08:00
parent 7c400e9c02
commit 27f5f84e3b
5 changed files with 40 additions and 12 deletions

View File

@@ -12,7 +12,7 @@ Example-based tests pin the cases we thought of. The harness's core is protocol-
## Decision
Adopt `fast-check` (a root devDependency) with one `tests/properties.spec.ts` per protocol-shaped package, generators tuned for *realistic-but-adversarial* inputs (not uniform noise) and `numRuns` kept so the suite stays well under ~10s locally, with a nightly CI job running 100× the iterations. Failures print a reproducible seed.
Adopt `fast-check` (a root devDependency) with one `tests/properties.spec.ts` per protocol-shaped package, generators tuned for *realistic-but-adversarial* inputs (not uniform noise) and `numRuns` kept so the suite stays well under ~10s locally. Failures print a reproducible seed. (The original proposal also sketched a nightly CI job running 100× the iterations; that was not shipped — the property suite runs only in the normal `push`/`pull_request` CI, and a scheduled high-iteration job remains possible future work.)
- **dsh-llm / BlockAssembler:** arbitrary chunk streams (valid + malformed: duplicate indices, stragglers, missing block-start). Invariants: `flushReady()+flushRemaining() ≡ blocks()` in order; the streamed prefix is always a prefix of final `blocks()`; partial count ≤ distinct indices; re-assembly idempotent.
- **dsh-session:** arbitrary event logs. Invariants: `deriveMessages` deterministic; replay-from-seed identical; seq strictly monotonic; non-message events never affect derived history; derived content is decoupled from the log.

View File

@@ -26,4 +26,4 @@ The split is not mandatory when the parts are genuinely one concern: the LLM sea
## Consequences
More packages and more boilerplate per capability (a `package.json`/`tsconfig`/README trio, the inject wiring). Bought: implementations and consumers ship and version independently, and a new backend never risks the model-facing contract. The rule is documented in [AGENTS.md](../../AGENTS.md) § Conventions ("Capability seams are three packages") and [architecture.md](../../architecture.md) § "Capability seams"; the bash trio is the reference template. When to fold vs. split is a judgment call the architecture doc spells out — this RFC records *why* the default is to split.
More packages and more boilerplate per capability (a `package.json`/`tsconfig`/README trio, the inject wiring). Bought: implementations and consumers ship and version independently, and a new backend never risks the model-facing contract. The rule is documented in [AGENTS.md](../../../AGENTS.md) § Conventions ("Capability seams are three packages") and [architecture.md](../../architecture.md) § "Capability seams"; the bash trio is the reference template. When to fold vs. split is a judgment call the architecture doc spells out — this RFC records *why* the default is to split.

View File

@@ -16,7 +16,7 @@ A fourth `doc-sync` gate, `verify-md-links` (`scripts/verify-md-links.ts`), mirr
- Check a target only when it is a **relative path**. Skip scheme-qualified URLs (`https:`, `mailto:`, …), protocol-relative (`//host`), root-absolute (`/path` — no stable base in a checkout), and pure in-page anchors (`#section`). Strip any `#fragment`/`?query`, resolve the path against the linking file's directory, and assert it exists on disk.
- Report and never rewrite; exit non-zero on the first broken link found.
Scope matches the other gates plus the AGENTS.md pair: `README.md`, `docs/**/*.md`, `packages/*/README.md`, `AGENTS.md`, `packages/AGENTS.md`, deduped by real path (the `CLAUDE.md` symlinks resolve onto the AGENTS.md files). It is wired into the `doc-sync` script that the lefthook pre-push hook and CI both run, so a broken link fails locally before a push — consistent with [mechanical quality gates](2026-06-11-quality-gates.md).
Scope matches the other gates plus the AGENTS.md pair and the repo-authored agent-skill Markdown under `.agents/skills/` (those skill files cross-link into the docs tree, so this reorg rewrote links in them too): `README.md`, `docs/**/*.md`, `packages/*/README.md`, `AGENTS.md`, `packages/AGENTS.md`, `.agents/skills/**/*.md`, deduped by real path (the `CLAUDE.md` symlinks resolve onto the AGENTS.md files). It is wired into the `doc-sync` script that the lefthook pre-push hook and CI both run, so a broken link fails locally before a push — consistent with [mechanical quality gates](2026-06-11-quality-gates.md).
This gate checks *existence*, not anchor validity: a link to a real file with a `#wrong-heading` fragment still passes (the file resolves; the fragment is stripped). Anchor-level checking is a heavier, lower-value follow-up — file-level dead links are the failure that actually bit us.

View File

@@ -38,7 +38,7 @@ Per the "explicit > implicit at seams" convention, the request spells out every
These are illustrations of the seam's reach, **not commitments** — the MVP ships only the TypeScript path. The honest caveat is that the *execution* contract is language-agnostic but the *presentation* is not: the SDK-generation pipeline below (§3a and the `jsonSchemaToTs` codegen, which emits a TypeScript `.d.ts`) is TypeScript-specific, so a non-TS backend pairs the shared `CodeRuntime` contract with its own language-appropriate SDK generator and system-prompt section (a `.pyi` stub and Python usage instructions for the Python backend, AssemblyScript-flavored types for that one). The runtime seam is reused as-is; only the codegen/prompt half is per-language.
**2. Implementation package `packages/code-runtime-vm/`** — a new package `@deepseek-ai/dsh-code-runtime-vm`, the `node:vm` reference stub. It type-erases the model's TypeScript via the compiler's `transpileModule` (or sucrase) — the types exist only to guide the model; the runtime is plain JS — then wraps the body in an async IIFE for top-level `await` (Cloudflare's `NodeVMExecutor` does literally `new AsyncFunction("codemode", "return await (${code})()")`), runs it in a `vm.Context` whose globals are a capturing `console` and the SDK namespace objects, awaits the IIFE, and captures the return value, the buffered logs, and any thrown error (as `error: string`). It applies an **output cap** (truncate captured logs) and a **timeout tied to `request.signal`**. These caps limit blast radius; **they are not a security boundary**. node:vm is **not** isolation: withholding `require`/`process` does not contain anything (code escapes via `constructor`/prototype reflection), and per [AGENTS.md](../../AGENTS.md) the harness must never hand model output the ambient environment.
**2. Implementation package `packages/code-runtime-vm/`** — a new package `@deepseek-ai/dsh-code-runtime-vm`, the `node:vm` reference stub. It type-erases the model's TypeScript via the compiler's `transpileModule` (or sucrase) — the types exist only to guide the model; the runtime is plain JS — then wraps the body in an async IIFE for top-level `await` (Cloudflare's `NodeVMExecutor` does literally `new AsyncFunction("codemode", "return await (${code})()")`), runs it in a `vm.Context` whose globals are a capturing `console` and the SDK namespace objects, awaits the IIFE, and captures the return value, the buffered logs, and any thrown error (as `error: string`). It applies an **output cap** (truncate captured logs) and a **timeout tied to `request.signal`**. These caps limit blast radius; **they are not a security boundary**. node:vm is **not** isolation: withholding `require`/`process` does not contain anything (code escapes via `constructor`/prototype reflection), and per [AGENTS.md](../../../AGENTS.md) the harness must never hand model output the ambient environment.
**The unsafe-runtime guard is enforceable, not a README warning.** Because a README caveat is not a control — and AGENTS.md's "never hand model output ambient authority" is a hard rule, not advice — the design makes the danger refuse to run by construction. Two layers:

View File

@@ -20,10 +20,13 @@
* resolved against the linking file's directory, and the result must exist on
* disk. This is checker, not fixer: it reports and never rewrites.
*
* Scope mirrors the other doc-sync gates plus the two AGENTS.md files:
* README.md, docs/** /*.md, packages/* /README.md, AGENTS.md, packages/AGENTS.md.
* The root and packages/ CLAUDE.md are symlinks to the AGENTS.md files, so they
* are deduped by real path.
* Scope is the other doc-sync gates' set plus the two AGENTS.md files AND the
* repo-authored agent-skill Markdown under `.agents/skills/` — those skill
* files cross-link into the docs tree (e.g. the dsh-code-review skill cites the
* RFC index), so a rename must not silently break them either: README.md,
* docs/** /*.md, packages/* /README.md, AGENTS.md, packages/AGENTS.md,
* .agents/skills/** /*.md. The root and packages/ CLAUDE.md are symlinks to the
* AGENTS.md files, so they are deduped by real path.
*
* Run: `tsx scripts/verify-md-links.ts`.
*/
@@ -38,8 +41,18 @@ import type { Nodes } from 'mdast'
const root = resolve(import.meta.dirname, '..')
/** Files to check: doc-typecheck's scope plus the AGENTS.md pair. */
const PATTERNS = ['README.md', 'docs/**/*.md', 'packages/*/README.md', 'AGENTS.md', 'packages/AGENTS.md']
/**
* Files to check: doc-typecheck's scope, the AGENTS.md pair, and repo-authored
* agent-skill Markdown (which this repo's own docs reorg rewrites links in).
*/
const PATTERNS = [
'README.md',
'docs/**/*.md',
'packages/*/README.md',
'AGENTS.md',
'packages/AGENTS.md',
'.agents/skills/**/*.md',
]
/** A broken relative link: a target path that does not resolve to a file. */
interface Violation {
@@ -62,9 +75,24 @@ function isExternalOrAnchor(url: string): boolean {
return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)
}
/** Strip the `#fragment` and `?query` from a link target, leaving the path. */
/**
* Strip the `#fragment` and `?query` from a link target, then percent-decode
* the remaining path so an encoded target (`My%20File.md`, `READ%4DE.md`)
* probes the real filename on disk, the way a Markdown renderer resolves it. A
* malformed escape (`%zz`) makes `decodeURIComponent` throw; we keep the raw
* path in that case so the link is reported as broken (a `%zz` target is not a
* file anyone meant to link) rather than crashing the gate.
*/
function pathPart(url: string): string {
return url.replace(/[#?].*$/, '')
const raw = url.replace(/[#?].*$/, '')
try {
return decodeURIComponent(raw)
} catch {
// decodeURIComponent throws only on a malformed percent-escape; the raw
// string is then a path no renderer resolves, so fall through to the
// existence check, which reports it broken.
return raw
}
}
/** Find every broken relative cross-link in one Markdown file via its AST. */