diff --git a/.changeset/adr-corpus-reference-repairs.md b/.changeset/adr-corpus-reference-repairs.md new file mode 100644 index 000000000..fda726844 --- /dev/null +++ b/.changeset/adr-corpus-reference-repairs.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2692 +--- +**Seven dangling references in the ADR corpus and contributor docs now resolve** — (1) `docs/adr/1239-gsd-embeddable-orchestration-engine.md` linked the host-integration capability matrix as `reference/…` from inside `docs/adr/`, resolving to the nonexistent `docs/adr/reference/`; all three occurrences now use `../reference/…`, and the two whose link text promises `§codex` now carry the matching `#codex` fragment. (2) `src/plan-drift-guard.cts` cited `docs/adr/0022-source-grounding-drift-guard.md`, a path that has never existed — corrected to the real `docs/adr/22-plan-drift-guard.md`; because the file is compiled into the shipped payload, the bad citation was shipping to users. (3) `CONTRIBUTING.md` and `docs/contributor-standards.md` illustrated the ADR naming convention with issue `#3485`, a pre-rename number from `get-shit-done-redux` that does not resolve in `open-gsd/gsd-core` — the worked example now uses `#2264`, which does, and the one genuinely historical `#3485` reference is annotated rather than rewritten. (4) `docs/adr/857-capability-system.md`'s H1 still carried a `[Proposed]` status bracket contradicting its `Accepted — ratified 2026-07-17` Status field; the ADR index generator strips the bracket for display, so the contradiction was invisible to the gate. (5) `scripts/gen-adr-index.cjs`'s back-link comment still described ADR-857 as `Proposed` and its claim over ADR-0011/ADR-58 as a supersession — both restated at the 2026-07-17 ratification, when the claim became `Subsumes` and the reciprocal back-links were added. (6) `docs/how-to/install-on-your-runtime.md` linked that same capability matrix as a bare `host-integration-capability-matrix.md` from inside `docs/how-to/` in its ZCode and pi sections — the identical defect as (1), so both now use `../reference/…`. (7) `docs/CONFIGURATION.md` cited ADR-1244 as `adr/1244-runtime-capability-registry-overlay.md`; the file is `adr/1244-capability-ecosystem.md`. (#2691) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1e9cbb62..860df7be8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -98,7 +98,7 @@ An ADR (Architecture Decision Record) documents a significant architectural deci **Do not compute a "next number" locally.** Any PR that uses the legacy `NNNN-*` sequential pattern for a *new* ADR or PRD will be asked to rename the file to the `-.md` format before merge. -**Example:** Issue #3485 was opened, approved, and its number became the prefix: `docs/adr/3485-adr-prd-naming-convention.md` on branch `docs/3485-adr-prd-naming-convention`. +**Example:** Issue #2264 was opened, approved, and its number became the prefix: `docs/adr/2264-golden-parity-redesign.md`. **Rejection reasons:** Issue not approved before file was created, filename uses local-compute sequential number instead of issue#, multiple decisions bundled in one PR, file placed in wrong directory (`docs/adr/` vs `docs/prd/`). diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 5f8c432c6..25d468ac9 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -805,7 +805,7 @@ If a skipped or load-failed overlay capability (for example, one whose `engines. Config keys declared in an overlay capability's `.config` slice federate into the `loadConfig` return value via the same Federated Config channel as first-party capability keys. They appear as valid keys in `config-schema.cjs` (`isValidConfigKey`) and in the runtime config schema, so overlay capabilities can declare project-local config toggles without editing the central config schema. -> **See also:** [`docs/reference/capability-manifest.md`](reference/capability-manifest.md) for the full `capability.json` schema, [`docs/how-to/import-a-capability-from-a-url.md`](how-to/import-a-capability-from-a-url.md) for installation steps, and [ADR-1244](adr/1244-runtime-capability-registry-overlay.md) for the design record. +> **See also:** [`docs/reference/capability-manifest.md`](reference/capability-manifest.md) for the full `capability.json` schema, [`docs/how-to/import-a-capability-from-a-url.md`](how-to/import-a-capability-from-a-url.md) for installation steps, and [ADR-1244](adr/1244-capability-ecosystem.md) for the design record. --- diff --git a/docs/adr/1239-gsd-embeddable-orchestration-engine.md b/docs/adr/1239-gsd-embeddable-orchestration-engine.md index 85df0b854..41c6102e9 100644 --- a/docs/adr/1239-gsd-embeddable-orchestration-engine.md +++ b/docs/adr/1239-gsd-embeddable-orchestration-engine.md @@ -96,7 +96,7 @@ Phase A is **implemented** and Phases B–E have landed, so this ADR is **Accept - **Overlap resolutions (explicit):** `commandStyle` (GSD emission style, retained) ⊥ `commandSurface` (host surface type); `hookEvents` dialect ⊥ `hookBus` ownership (a host with `hooksSurface:none` may still be `hookBus:host` — e.g. opencode); `runtimeCompat` (feature→host) stays an independent override, orthogonal to these runtime→engine axes. - **`extensionEvents` vocabulary (amendment, #1943).** The OpenCode extension-system event subset is a SEPARATE descriptor field + closed vocabulary, **not** a `hookEvents` value. `hookEvents` is the *managed-hook* dialect only (`claude`/`gemini`) — the event names GSD writes into a declarative host's settings.json. `extensionEvents` is the *plugin/extension-system* event surface an imperative host exposes: `{ opencode, pi, none }` (OpenCode ~25 plugin events; pi ~30 fine-grained events; `none` = the host exposes no extension surface and the engine owns the bus, e.g. VS Code). The former `opencode-subset` `hookEvents` value was this concept misfiled; it is now `extensionEvents: opencode`. Keeping them separate preserves the `hooksSurface:"none" ⇔ no-hookEvents` invariant (OpenCode declares `extensionEvents`, not `hookEvents`). Resolved by `extensionEventSurfaceFor` in `src/host-integration.cts`, validated by `VALID_EXTENSION_EVENTS` in `capability-validator.cjs`. -**Every per-host axis value is documentation-sourced, with citations.** Each of the 8 axes for all 16 installed CLIs was determined from that CLI's authoritative documentation (Context7 + the official dev docs/source), never inferred. The full per-CLI, per-axis matrix — value, source, and an evidence quote — is recorded in [`docs/reference/host-integration-capability-matrix.md`](reference/host-integration-capability-matrix.md), the deployment source-of-truth that Phases B–E build on. Where a CLI's docs genuinely do not state an axis, the descriptor carries the explicit `undocumented` sentinel (which `negotiateHostCapabilities` fail-closes on) rather than a guessed value — 22 such markers exist today, each with its search trail in the matrix. Two findings corrected this ADR's original appendix matrix: (1) current OpenAI **Codex** docs document slash-commands, so its `commandSurface` is `slash-file`, not `prose-only`; (2) several hosts run non-Node runtimes (opencode & kilo on **bun**; hermes & kimi on **python**; antigravity on **go**), so the `runtime` axis vocabulary was widened to `node|bun|sandboxed-web|python|go|rust|electron|other`. The documented `embeddingMode` split (9 imperative / 7 declarative, above) likewise reflects each CLI's real plugin/extension API, not a profile assumption. +**Every per-host axis value is documentation-sourced, with citations.** Each of the 8 axes for all 16 installed CLIs was determined from that CLI's authoritative documentation (Context7 + the official dev docs/source), never inferred. The full per-CLI, per-axis matrix — value, source, and an evidence quote — is recorded in [`docs/reference/host-integration-capability-matrix.md`](../reference/host-integration-capability-matrix.md), the deployment source-of-truth that Phases B–E build on. Where a CLI's docs genuinely do not state an axis, the descriptor carries the explicit `undocumented` sentinel (which `negotiateHostCapabilities` fail-closes on) rather than a guessed value — 22 such markers exist today, each with its search trail in the matrix. Two findings corrected this ADR's original appendix matrix: (1) current OpenAI **Codex** docs document slash-commands, so its `commandSurface` is `slash-file`, not `prose-only`; (2) several hosts run non-Node runtimes (opencode & kilo on **bun**; hermes & kimi on **python**; antigravity on **go**), so the `runtime` axis vocabulary was widened to `node|bun|sandboxed-web|python|go|rust|electron|other`. The documented `embeddingMode` split (9 imperative / 7 declarative, above) likewise reflects each CLI's real plugin/extension API, not a profile assumption. No consumer wires the negotiated result yet — Phase A is interface-definition only; the engine↔host boundary (Phase B) and the adapters (Phase C) are where it is consumed. @@ -195,7 +195,7 @@ Because OpenCode consumes MCP, the **companion MCP server** (the MemPalace patte ## Codex binding (worked host-plugin) -> **Amendment — Codex worked binding + `dispatch.isolation` capability (#2584, 2026-07-24).** Two things: (1) it introduces a **general, per-host-negotiated capability** — the `dispatch.isolation` sub-field, which declares how each host isolates concurrent same-wave executors (six hosts have confirmed support; see the support table below), enabling parallel `execute-phase` waves on hosts beyond Claude; and (2) it consolidates Codex's Phase-D-shipped six-point Host-Integration binding (#2088) into this ADR — the sibling of the OpenCode binding above — because Codex is the first *orchestrator-managed* consumer of the new capability. Per-axis values are the [capability matrix §codex](reference/host-integration-capability-matrix.md). Sequencing: the *code* builds on the worktree merge/cleanup gauntlet (open bug #2556) and lands after it; this design + the descriptor/matrix change do not. +> **Amendment — Codex worked binding + `dispatch.isolation` capability (#2584, 2026-07-24).** Two things: (1) it introduces a **general, per-host-negotiated capability** — the `dispatch.isolation` sub-field, which declares how each host isolates concurrent same-wave executors (six hosts have confirmed support; see the support table below), enabling parallel `execute-phase` waves on hosts beyond Claude; and (2) it consolidates Codex's Phase-D-shipped six-point Host-Integration binding (#2088) into this ADR — the sibling of the OpenCode binding above — because Codex is the first *orchestrator-managed* consumer of the new capability. Per-axis values are the [capability matrix §codex](../reference/host-integration-capability-matrix.md#codex). Sequencing: the *code* builds on the worktree merge/cleanup gauntlet (open bug #2556) and lands after it; this design + the descriptor/matrix change do not. ### What Codex integration actually is (the binding substrate) @@ -203,7 +203,7 @@ Codex is a **declarative-CLI** host (ADR-1239 profile `declarative-cli`). It exp ### Six interface points → Codex primitives -Codex's full negotiated binding — the per-axis value, its documentation citation, and the evidence quote for all six interface points — is the **[capability matrix §codex](reference/host-integration-capability-matrix.md)**, the deployment source of truth. In summary: `embeddingMode: declarative` · `commandSurface: slash-file` · `modelMode: passive` · `hookBus: host` (10-event `hooks.json`) · `stateIO: filesystem` · `transport: mcp` · `runtime: node` · `effortSurface: argv`. Integration is **Phase-D-dogfood complete** (#2088): declarative embedding adapter, install/uninstall byte-parity-gated (`golden-install-parity/codex.json`), the `runtime === 'codex'` projection folded into `hostBehaviors`. +Codex's full negotiated binding — the per-axis value, its documentation citation, and the evidence quote for all six interface points — is the **[capability matrix §codex](../reference/host-integration-capability-matrix.md#codex)**, the deployment source of truth. In summary: `embeddingMode: declarative` · `commandSurface: slash-file` · `modelMode: passive` · `hookBus: host` (10-event `hooks.json`) · `stateIO: filesystem` · `transport: mcp` · `runtime: node` · `effortSurface: argv`. Integration is **Phase-D-dogfood complete** (#2088): declarative embedding adapter, install/uninstall byte-parity-gated (`golden-install-parity/codex.json`), the `runtime === 'codex'` projection folded into `hostBehaviors`. The one interface point this amendment changes is **Dispatch** — specifically executor isolation for wave parallelism, below. diff --git a/docs/adr/857-capability-system.md b/docs/adr/857-capability-system.md index 3ad4938f6..1094419af 100644 --- a/docs/adr/857-capability-system.md +++ b/docs/adr/857-capability-system.md @@ -1,4 +1,4 @@ -# ADR-857: Capability system — five-step loop as core, features as plug-ins behind Loop Extension Points [Proposed] +# ADR-857: Capability system — five-step loop as core, features as plug-ins behind Loop Extension Points [Accepted] - **Status:** Accepted — ratified 2026-07-17 (originally Proposed 2026-06-08); see "Ratification" below - **Date:** 2026-06-08 diff --git a/docs/contributor-standards.md b/docs/contributor-standards.md index a58501b05..4bef1970b 100644 --- a/docs/contributor-standards.md +++ b/docs/contributor-standards.md @@ -96,11 +96,11 @@ docs/adr/-.md (new ADRs) docs/prd/-.md (new PRDs) ``` -Example: `docs/adr/3485-adr-prd-naming-convention.md`. +Example: `docs/adr/2264-golden-parity-redesign.md`. **Why:** GitHub issue numbers are server-assigned and atomic — the reservation mechanism already exists because the issue-first rule requires it. Promoting the issue# to the artifact ID eliminates the entire collision class that the `NNNN-*` local-compute scheme created (see the `0010-*` × 2 and `0011-*` × 3 duplicates on disk). -**Migration policy:** Legacy ADRs `0001-*` through `0011-*` keep their numbers as immutable historical record. The new convention applies to all ADRs and PRDs created on or after the merge of the implementing PR (#3485). Do not renumber legacy files. +**Migration policy:** Legacy ADRs `0001-*` through `0011-*` keep their numbers as immutable historical record. The new convention applies to all ADRs and PRDs created on or after the merge of the implementing PR (#3485 — a pre-rename number from `get-shit-done-redux`; it does not resolve in `open-gsd/gsd-core`, whose issue numbering restarted at the rename). Do not renumber legacy files. For the end-to-end workflow — opening the issue, waiting for approval, creating the file, and submitting the PR — see **[CONTRIBUTING.md — "Proposing an ADR or PRD"](../CONTRIBUTING.md#proposing-an-adr-or-prd)**. diff --git a/docs/how-to/install-on-your-runtime.md b/docs/how-to/install-on-your-runtime.md index 830e37cf7..9cea2cc22 100644 --- a/docs/how-to/install-on-your-runtime.md +++ b/docs/how-to/install-on-your-runtime.md @@ -457,7 +457,7 @@ npx @opengsd/gsd-core@latest --zcode --global ZCode's skill format is identical to Claude Code's, so no runtime-specific converter is required — GSD lands as a pure declarative descriptor with no hardcoded installer branches. ZCode also natively imports skills and MCP config from `~/.claude`; if you install GSD for **both** Claude and ZCode, you may see duplicate GSD skills inside ZCode, which is expected. To connect ZCode's MCP servers to GSD's companion server, see [how to connect the GSD MCP server](connect-gsd-mcp-server.md). -GSD's hook-automation and native-MCP-registration integrations are not yet wired for ZCode — both are blocked on ZCode not yet publishing the on-disk config format for its plugin `Hook` component or the settings filename/schema for its MCP store. See the [`## zcode`](host-integration-capability-matrix.md#zcode) section of the host-integration capability matrix for the cited source URLs. +GSD's hook-automation and native-MCP-registration integrations are not yet wired for ZCode — both are blocked on ZCode not yet publishing the on-disk config format for its plugin `Hook` component or the settings filename/schema for its MCP store. See the [`## zcode`](../reference/host-integration-capability-matrix.md#zcode) section of the host-integration capability matrix for the cited source URLs. --- @@ -473,7 +473,7 @@ npx @opengsd/gsd-core@latest --pi --global The `.js` suffix is load-bearing: pi auto-discovers extensions by scanning that directory and keeping only names ending in `.ts` or `.js`, and it skips anything else **silently** — no error, no log line. GSD shipped the file as `gsd.cjs` through 1.7.0, which pi therefore never loaded, so `/gsd` never appeared ([#2470](https://github.com/open-gsd/gsd-core/issues/2470)). Upgrading removes the stale `gsd.cjs`; if you had added a manual `extensions` entry in `~/.pi/agent/settings.json` as a workaround, you can drop it. -The extension registers a `/gsd` command and a `gsd_invoke` tool that dispatch GSD commands via a bounded subprocess call to `gsd-core/bin/gsd-tools.cjs` (no fully-populated in-process command-routing hub exists — see the matrix's Stage 2 note). This is a **plugin-only install**: pi has no shared-settings hook surface (`hooksSurface: none`) and, unlike Claude/OpenCode/Kilo, no host-read markdown surface at all — pi's `/gsd` command is registered programmatically by the extension, not discovered from files, so GSD installs the extension plus its universal `gsd-core/` engine payload and the shared `hooks/`/`hooks/lib/` bundle (spawned by the extension itself, not by any config-file hook bus), and does **not** write any `commands/`, `agents/`, or `skills/` directory for pi. The extension bridges GSD's `session_start`/`before_agent_start`/`session_before_compact`/`tool_call` lifecycle events to those staged `hooks/` scripts as bounded, fail-open subprocesses, and steers pi's active model (`modelMode: active`) to a tier-resolved bare anthropic id via `pi.on('before_provider_request', ...)`. See the [`## pi`](host-integration-capability-matrix.md#pi) section of the host-integration capability matrix for the negotiated axes and citations. +The extension registers a `/gsd` command and a `gsd_invoke` tool that dispatch GSD commands via a bounded subprocess call to `gsd-core/bin/gsd-tools.cjs` (no fully-populated in-process command-routing hub exists — see the matrix's Stage 2 note). This is a **plugin-only install**: pi has no shared-settings hook surface (`hooksSurface: none`) and, unlike Claude/OpenCode/Kilo, no host-read markdown surface at all — pi's `/gsd` command is registered programmatically by the extension, not discovered from files, so GSD installs the extension plus its universal `gsd-core/` engine payload and the shared `hooks/`/`hooks/lib/` bundle (spawned by the extension itself, not by any config-file hook bus), and does **not** write any `commands/`, `agents/`, or `skills/` directory for pi. The extension bridges GSD's `session_start`/`before_agent_start`/`session_before_compact`/`tool_call` lifecycle events to those staged `hooks/` scripts as bounded, fail-open subprocesses, and steers pi's active model (`modelMode: active`) to a tier-resolved bare anthropic id via `pi.on('before_provider_request', ...)`. See the [`## pi`](../reference/host-integration-capability-matrix.md#pi) section of the host-integration capability matrix for the negotiated axes and citations. --- diff --git a/scripts/gen-adr-index.cjs b/scripts/gen-adr-index.cjs index 074f3de07..37b394da4 100644 --- a/scripts/gen-adr-index.cjs +++ b/scripts/gen-adr-index.cjs @@ -347,10 +347,12 @@ function validate({ adrs, byFile, byId, nonConforming }) { // // Only a RATIFIED (Accepted) claimant is owed the back-link. A Proposed ADR's // supersession claim is prospective — it has not taken effect, so stamping its - // target as superseded would assert something untrue (ADR-857 is Proposed and - // claims to generalize ADR-0011/ADR-58, both of which are Accepted and live). - // When such an ADR is ratified to Accepted, this check starts demanding the - // back-links at exactly the right moment. + // target as superseded would assert something untrue. When such an ADR is + // ratified to Accepted, this check starts demanding the back-links at exactly + // the right moment — as ADR-857 shows: it was Proposed when this guard was + // written, was ratified to Accepted on 2026-07-17 (its claim over ADR-0011 / + // ADR-58 restated as "Subsumes", since both remain Accepted and live), and + // both targets now carry the reciprocal "Subsumed by" back-link this demands. const OPPOSITE = { out: 'in', in: 'out' }; for (const a of adrs) { for (const kind of Object.keys(RELATION_SPEC)) { diff --git a/src/plan-drift-guard.cts b/src/plan-drift-guard.cts index f3e259cba..73c656a9c 100644 --- a/src/plan-drift-guard.cts +++ b/src/plan-drift-guard.cts @@ -2,7 +2,7 @@ * ADR-22 Drift-Guard Decision Module * * Implements the authority ladder and severity classification table from - * ADR-22 (docs/adr/0022-source-grounding-drift-guard.md). + * ADR-22 (docs/adr/22-plan-drift-guard.md). * * Design constraints: * - Pure module: no I/O, no require() calls, no side effects. diff --git a/tests/adr-index-gate.test.cjs b/tests/adr-index-gate.test.cjs index f93d572de..8eca2e3c9 100644 --- a/tests/adr-index-gate.test.cjs +++ b/tests/adr-index-gate.test.cjs @@ -436,3 +436,97 @@ test('the real repo corpus is clean and its index is current', () => { const res = run(REPO_ROOT, ['--check']); assert.equal(res.status, 0, `docs/adr/ must satisfy its own gate:\n${res.stderr}`); }); + +// --- regressions ----------------------------------------------------------- +// +// The --check gate above passes on a corpus that still carries dangling +// references: it validates naming, relation symmetry, and index freshness, but +// it never resolves a link target and it STRIPS the H1 status bracket +// (gen-adr-index.cjs) rather than comparing it. Both defect classes below were +// green under `--check` while broken. These assert on the real corpus, so +// reverting the repair re-reds them. + +const ADR_DIR = path.join(REPO_ROOT, 'docs', 'adr'); +const STATUS_TOKENS = ['Accepted', 'Proposed', 'Superseded', 'Legacy', 'Retired']; + +function adrMarkdownFiles() { + return fs.readdirSync(ADR_DIR).filter((f) => f.endsWith('.md')); +} + +test('every relative markdown link in docs/adr/ resolves to a file that exists', () => { + const dangling = []; + for (const file of adrMarkdownFiles()) { + const body = fs.readFileSync(path.join(ADR_DIR, file), 'utf8'); + for (const match of body.matchAll(/\]\(([^)#:\s]+\.md)(?:#[^)]*)?\)/g)) { + const target = match[1]; + if (!fs.existsSync(path.resolve(ADR_DIR, target))) { + dangling.push(`${file} -> ${target}`); + } + } + } + assert.deepEqual( + dangling, + [], + `dangling relative links in docs/adr/ (a link written as reference/x.md from inside docs/adr/ resolves to the nonexistent docs/adr/reference/):\n${dangling.join('\n')}`, + ); +}); + +test('no ADR H1 status bracket contradicts its Status field', () => { + // The index generator strips a trailing "[Proposed]"-style bracket for + // display instead of comparing it, so a stale bracket is invisible to the + // gate while still being the first thing a reader sees. + const mismatches = []; + for (const file of adrMarkdownFiles()) { + if (file === 'README.md') continue; + const lines = fs.readFileSync(path.join(ADR_DIR, file), 'utf8').split(/\r?\n/); + const heading = lines.find((l) => /^#\s/.test(l)) || ''; + const bracket = heading.match(/\[(Proposed|Accepted|Superseded|Legacy|Retired)\]\s*$/i); + if (!bracket) continue; + const statusLine = lines.find((l) => /^\s*[-*]?\s*\*\*Status/.test(l)) || ''; + // Resolve by earliest position in the line, not by STATUS_TOKENS order: a + // Status field like "Superseded by ADR-X (was Accepted ...)" mentions two + // tokens, and array order would pick 'Accepted' and report a false mismatch + // against a correct [Superseded] bracket. + let token; + let tokenAt = Infinity; + for (const s of STATUS_TOKENS) { + const at = statusLine.search(new RegExp(`\\b${s}\\b`, 'i')); + if (at !== -1 && at < tokenAt) { + tokenAt = at; + token = s; + } + } + if (token && token.toLowerCase() !== bracket[1].toLowerCase()) { + mismatches.push(`${file}: H1 says [${bracket[1]}], Status field says ${token}`); + } + } + assert.deepEqual(mismatches, [], `H1 bracket contradicts Status:\n${mismatches.join('\n')}`); +}); + +test('the ADR path cited by src/plan-drift-guard.cts exists', () => { + // This module is compiled into the published payload, so a wrong citation + // here ships to users. + const src = fs.readFileSync(path.join(REPO_ROOT, 'src', 'plan-drift-guard.cts'), 'utf8'); + const cited = [...src.matchAll(/docs\/adr\/([A-Za-z0-9._-]+\.md)/g)].map((m) => m[1]); + assert.notEqual(cited.length, 0, 'expected plan-drift-guard.cts to cite its governing ADR'); + for (const name of cited) { + assert.ok( + fs.existsSync(path.join(ADR_DIR, name)), + `src/plan-drift-guard.cts cites docs/adr/${name}, which does not exist`, + ); + } +}); + +test('the ADR naming worked example names an ADR file that exists', () => { + for (const rel of ['CONTRIBUTING.md', path.join('docs', 'contributor-standards.md')]) { + const body = fs.readFileSync(path.join(REPO_ROOT, rel), 'utf8'); + const examples = [...body.matchAll(/docs\/adr\/(\d+-[a-z0-9-]+\.md)/g)].map((m) => m[1]); + assert.notEqual(examples.length, 0, `expected ${rel} to show a worked ADR-naming example`); + for (const name of examples) { + assert.ok( + fs.existsSync(path.join(ADR_DIR, name)), + `${rel} illustrates the naming convention with docs/adr/${name}, which does not exist`, + ); + } + } +});