docs(adr): add docs/adr/README.md index and structural ADR test (#3302)

* docs(adr): add docs/adr/README.md index and structural ADR test (#3271)

- Add docs/adr/README.md as an indexed entry point linking all 7 ADRs
- Add tests/enh-3271-sdk-adr-structure.test.cjs: structural assertions that
  ADR 0005 and 0006 exist, have required headings and Status/Date metadata,
  and that README links every ADR file by filename
- Update CHANGELOG.md with Enhancement entry
- Add .changeset/3271-sdk-adr-structure.md

ADRs 0005 (SDK architecture seam-map) and 0006 (planning-path projection
module) already landed on main. This PR completes issue #3271 by adding the
README index and the structural test gate that enforces ADR completeness
going forward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: set changeset pr: 3302

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): exclude self-reference from ADR 0005 cross-ref count (#3271)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: drop redundant CHANGELOG.md edit (use .changeset/ fragment per CONTRIBUTING.md)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-09 11:46:28 -04:00
committed by GitHub
parent 1a49d2fcfc
commit 706ddb5ea5
3 changed files with 243 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
---
type: Enhancement
pr: 3302
---
**`docs/adr/` index and SDK seam ADRs (#3271)** — added `docs/adr/README.md` as an indexed entry point for all Architecture Decision Records, linking all seven ADRs. ADR 0005 documents the top-level SDK architecture seam map (Dispatch Policy Module, Model Catalog Module, Planning Workspace Module, SDK Package Seam Module, Planning Path Projection Module). ADR 0006 documents how SDK query handlers project planning paths (`cwd → effectiveRoot → .planning/<project>/...`). A structural test (`tests/enh-3271-sdk-adr-structure.test.cjs`) asserts each ADR has required headings and Status/Date metadata, and that the README links every ADR file by filename.

23
docs/adr/README.md Normal file
View File

@@ -0,0 +1,23 @@
# Architecture Decision Records
This directory contains Architecture Decision Records (ADRs) for GSD.
Each ADR documents one architectural decision: what was decided, why, and what consequences follow. ADRs are append-only. Amendments extend existing ADRs with a dated section rather than replacing them.
## Index
| ADR | Title | Status |
|-----|-------|--------|
| [0001-dispatch-policy-module.md](0001-dispatch-policy-module.md) | Dispatch policy module as single seam for query execution outcomes | Accepted |
| [0002-command-contract-validation-module.md](0002-command-contract-validation-module.md) | Command Contract Validation Module | Accepted |
| [0003-model-catalog-module.md](0003-model-catalog-module.md) | Model Catalog Module as single source of truth for agent profiles and runtime tier defaults | Accepted |
| [0004-worktree-workstream-seam-module.md](0004-worktree-workstream-seam-module.md) | Planning Workspace Module as single seam for worktree and workstream state | Accepted |
| [0005-sdk-architecture-seam-map.md](0005-sdk-architecture-seam-map.md) | SDK Architecture seam map for query/runtime surfaces | Accepted |
| [0006-planning-path-projection-module.md](0006-planning-path-projection-module.md) | Planning Path Projection Module for SDK query handlers | Accepted |
| [0007-sdk-package-seam-module.md](0007-sdk-package-seam-module.md) | SDK Package Seam Module owns SDK-to-get-shit-done-cc compatibility | Accepted |
## Seam map
ADR 0005 is the top-level SDK seam index. It references per-seam ADRs and states the narrow-waist principle each seam follows. Use it as the entry point for understanding SDK module ownership.
ADR 0006 documents how SDK query handlers project planning paths (`cwd → effectiveRoot → .planning/<project>/...`). Cross-reference with the Planning Workspace Module (ADR 0004) for workstream pointer policy.

View File

@@ -0,0 +1,215 @@
/**
* Structural tests for ADR 0005 (SDK architecture seam-map) and
* ADR 0006 (planning-path projection module), per issue #3271.
*
* Assertions parse the markdown by splitting on heading lines and inspect
* typed records. The docs/adr/README.md index must exist and reference
* both ADRs by filename.
*/
// allow-test-rule: heading-split structural parser for ADR markdown documents.
// Assertions target typed records (heading sets, status strings, filename refs),
// not raw .includes()/.match() on prose.
'use strict';
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const ADR_DIR = path.join(__dirname, '..', 'docs', 'adr');
const README_PATH = path.join(ADR_DIR, 'README.md');
// --- Helpers -----------------------------------------------------------------
function parseAdr(filePath) {
let raw;
try {
raw = fs.readFileSync(filePath, 'utf8');
} catch (err) {
throw new Error(`Cannot read ADR file: ${filePath} — ${err.message}`);
}
const lines = raw.split('\n');
let title = null;
const headings = [];
let status = null;
let date = null;
for (const line of lines) {
const h1 = line.match(/^#\s+(.+)$/);
if (h1 && title === null) { title = h1[1].trim(); continue; }
const h2 = line.match(/^##\s+(.+)$/);
if (h2) { headings.push(h2[1].trim().toLowerCase()); continue; }
const statusMatch = line.match(/\*\*Status:\*\*\s*(.+)/);
if (statusMatch && status === null) { status = statusMatch[1].trim(); continue; }
const dateMatch = line.match(/\*\*Date:\*\*\s*(.+)/);
if (dateMatch && date === null) { date = dateMatch[1].trim(); }
}
return { title, headings, status, date };
}
function parseReadmeIndex(filePath) {
let raw;
try {
raw = fs.readFileSync(filePath, 'utf8');
} catch (err) {
throw new Error(`Cannot read ADR README: ${filePath} — ${err.message}`);
}
const lines = raw.split('\n');
const linkedFiles = [];
for (const line of lines) {
const linkRe = /\[.*?\]\(\.?\/?([^)]+\.md)\)/g;
let m;
while ((m = linkRe.exec(line)) !== null) {
linkedFiles.push(path.basename(m[1]));
}
}
return { linkedFiles };
}
// --- ADR 0005: SDK Architecture seam-map -------------------------------------
describe('ADR 0005 — SDK architecture seam-map', () => {
const adrPath = path.join(ADR_DIR, '0005-sdk-architecture-seam-map.md');
test('file exists', () => {
assert.ok(fs.existsSync(adrPath), `Expected ADR file to exist: ${adrPath}`);
});
test('has a title (H1 heading)', () => {
const { title } = parseAdr(adrPath);
assert.ok(title && title.length > 0, 'ADR must have a non-empty H1 title');
});
test('has Status metadata', () => {
const { status } = parseAdr(adrPath);
assert.ok(status && status.length > 0, 'ADR must have a **Status:** line');
});
test('has Date metadata', () => {
const { date } = parseAdr(adrPath);
assert.ok(date && date.length > 0, 'ADR must have a **Date:** line');
});
test('has Decision section', () => {
const { headings } = parseAdr(adrPath);
assert.ok(
headings.some(h => h === 'decision'),
`ADR must have a ## Decision section. Found headings: ${headings.join(', ')}`
);
});
test('has Consequences section', () => {
const { headings } = parseAdr(adrPath);
assert.ok(
headings.some(h => h === 'consequences'),
`ADR must have a ## Consequences section. Found headings: ${headings.join(', ')}`
);
});
test('cross-references at least two other ADR files', () => {
// allow-test-rule: reading markdown link targets and backtick code spans
// from ADR to build a typed set of referenced filenames.
const raw = fs.readFileSync(adrPath, 'utf8');
const refs = new Set();
const linkRe = /\((\d{4}-[^)]*\.md)\)/g;
let m;
while ((m = linkRe.exec(raw)) !== null) { refs.add(path.basename(m[1])); }
const codeRe = /`(\d{4}-[^`]*\.md)`/g;
while ((m = codeRe.exec(raw)) !== null) { refs.add(path.basename(m[1])); }
refs.delete('0005-sdk-architecture-seam-map.md');
assert.ok(
refs.size >= 2,
`Seam-map ADR must cross-reference at least 2 other ADR files. Found: ${[...refs].join(', ')}`
);
});
});
// --- ADR 0006: Planning Path Projection Module --------------------------------
describe('ADR 0006 — planning-path projection module', () => {
const adrPath = path.join(ADR_DIR, '0006-planning-path-projection-module.md');
test('file exists', () => {
assert.ok(fs.existsSync(adrPath), `Expected ADR file to exist: ${adrPath}`);
});
test('has a title (H1 heading)', () => {
const { title } = parseAdr(adrPath);
assert.ok(title && title.length > 0, 'ADR must have a non-empty H1 title');
});
test('has Status metadata', () => {
const { status } = parseAdr(adrPath);
assert.ok(status && status.length > 0, 'ADR must have a **Status:** line');
});
test('has Date metadata', () => {
const { date } = parseAdr(adrPath);
assert.ok(date && date.length > 0, 'ADR must have a **Date:** line');
});
test('has Decision section', () => {
const { headings } = parseAdr(adrPath);
assert.ok(
headings.some(h => h === 'decision'),
`ADR must have a ## Decision section. Found headings: ${headings.join(', ')}`
);
});
test('has Consequences section', () => {
const { headings } = parseAdr(adrPath);
assert.ok(
headings.some(h => h === 'consequences'),
`ADR must have a ## Consequences section. Found headings: ${headings.join(', ')}`
);
});
});
// --- docs/adr/README.md index ------------------------------------------------
describe('docs/adr/README.md index', () => {
test('README file exists', () => {
assert.ok(
fs.existsSync(README_PATH),
`Expected docs/adr/README.md to exist: ${README_PATH}`
);
});
test('links to ADR 0005', () => {
const { linkedFiles } = parseReadmeIndex(README_PATH);
assert.ok(
linkedFiles.some(f => f === '0005-sdk-architecture-seam-map.md'),
`README must link to 0005-sdk-architecture-seam-map.md. Found: ${linkedFiles.join(', ')}`
);
});
test('links to ADR 0006', () => {
const { linkedFiles } = parseReadmeIndex(README_PATH);
assert.ok(
linkedFiles.some(f => f === '0006-planning-path-projection-module.md'),
`README must link to 0006-planning-path-projection-module.md. Found: ${linkedFiles.join(', ')}`
);
});
test('links to all existing ADR files', () => {
const existingAdrs = fs.readdirSync(ADR_DIR)
.filter(f => /^\d{4}-.*\.md$/.test(f))
.sort();
const { linkedFiles } = parseReadmeIndex(README_PATH);
for (const adrFile of existingAdrs) {
assert.ok(
linkedFiles.includes(adrFile),
`README must link to every ADR. Missing: ${adrFile}`
);
}
});
});