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:
@@ -12,7 +12,7 @@ Example-based tests pin the cases we thought of. The harness's core is protocol-
|
|||||||
|
|
||||||
## Decision
|
## 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-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.
|
- **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.
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ The split is not mandatory when the parts are genuinely one concern: the LLM sea
|
|||||||
|
|
||||||
## Consequences
|
## 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.
|
||||||
|
|||||||
@@ -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.
|
- 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.
|
- 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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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:
|
**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:
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,13 @@
|
|||||||
* resolved against the linking file's directory, and the result must exist on
|
* resolved against the linking file's directory, and the result must exist on
|
||||||
* disk. This is checker, not fixer: it reports and never rewrites.
|
* disk. This is checker, not fixer: it reports and never rewrites.
|
||||||
*
|
*
|
||||||
* Scope mirrors the other doc-sync gates plus the two AGENTS.md files:
|
* Scope is the other doc-sync gates' set plus the two AGENTS.md files AND the
|
||||||
* README.md, docs/** /*.md, packages/* /README.md, AGENTS.md, packages/AGENTS.md.
|
* repo-authored agent-skill Markdown under `.agents/skills/` — those skill
|
||||||
* The root and packages/ CLAUDE.md are symlinks to the AGENTS.md files, so they
|
* files cross-link into the docs tree (e.g. the dsh-code-review skill cites the
|
||||||
* are deduped by real path.
|
* 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`.
|
* Run: `tsx scripts/verify-md-links.ts`.
|
||||||
*/
|
*/
|
||||||
@@ -38,8 +41,18 @@ import type { Nodes } from 'mdast'
|
|||||||
|
|
||||||
const root = resolve(import.meta.dirname, '..')
|
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. */
|
/** A broken relative link: a target path that does not resolve to a file. */
|
||||||
interface Violation {
|
interface Violation {
|
||||||
@@ -62,9 +75,24 @@ function isExternalOrAnchor(url: string): boolean {
|
|||||||
return /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)
|
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 {
|
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. */
|
/** Find every broken relative cross-link in one Markdown file via its AST. */
|
||||||
|
|||||||
Reference in New Issue
Block a user