diff --git a/.changeset/jolly-otters-forage.md b/.changeset/jolly-otters-forage.md new file mode 100644 index 000000000..0424cec76 --- /dev/null +++ b/.changeset/jolly-otters-forage.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2836 +--- +**Corrected the legacy ADR range documentation** — the legacy zero-padded ADR range is now stated once (in docs/adr/README.md, as 0001–0012) and referenced rather than restated by docs/contributor-standards.md, so the two can no longer drift. The two zero-padded files that look legacy but are not (0174, 0656) are now identified as modern, mis-padded issue-numbered ADRs. Previously the two documents disagreed and neither matched disk. diff --git a/docs/adr/README.md b/docs/adr/README.md index 4470f4786..23b1ebade 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -29,7 +29,11 @@ Two developers computing "next ADR number" locally against `main` will independe ### Legacy *naming* is not `Legacy` *status* -Files `0001-*` through `0012-*` (and `0174-*`) are preserved as immutable historical record of the old local-compute numbering. The duplicate `0010-*` and the three-way `0011-*` are documented residue of that convention — not patterns to imitate. **Do not renumber them.** +Files `0001-*` through `0012-*` are preserved as immutable historical record of the old local-compute numbering. The duplicate `0010-*` and the three-way `0011-*` are documented residue of that convention — not patterns to imitate. **Do not renumber them.** + +> **This is the single authoritative statement of the legacy range.** `docs/contributor-standards.md` references it rather than restating it, so the two cannot drift. + +Two other zero-padded files look legacy but are not: `0174-retire-gsd-sdk-package-boundary.md` (issue #174) and `0656-research-module-seam.md` (issue #656) are **mis-padded modern ADRs** — modern, issue-numbered files whose four-digit padding is a mistake. They are NOT part of the legacy sequential set above and are not "old local-compute numbering" residue. This is a statement about **filenames only**. Many of those ADRs are `Accepted` and load-bearing today ([ADR-0002](0002-command-contract-validation-module.md), [ADR-0004](0004-worktree-workstream-seam-module.md), [ADR-0008](0008-installer-migration-module.md), [ADR-0009](0009-shell-command-projection-module.md)). An old filename says nothing about whether a decision still holds. The `Legacy` **status** in the table below is a separate claim — see the vocabulary. diff --git a/docs/contributor-standards.md b/docs/contributor-standards.md index 4bef1970b..ed41c48d2 100644 --- a/docs/contributor-standards.md +++ b/docs/contributor-standards.md @@ -100,7 +100,7 @@ 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 — 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. +**Migration policy:** Legacy ADRs keep their numbers as immutable historical record — see the **[authoritative legacy-range statement in `docs/adr/README.md`](./adr/README.md#legacy-naming-is-not-legacy-status)** for exactly which zero-padded files that covers (and which zero-padded files are actually modern, mis-padded). Do not renumber legacy files. 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). 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/tests/adr-index-gate.test.cjs b/tests/adr-index-gate.test.cjs index 8eca2e3c9..6d48b529b 100644 --- a/tests/adr-index-gate.test.cjs +++ b/tests/adr-index-gate.test.cjs @@ -9,7 +9,7 @@ * runtime behavior is the contract. */ -const { test } = require('node:test'); +const { test, describe } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); @@ -530,3 +530,86 @@ test('the ADR naming worked example names an ADR file that exists', () => { } } }); + +// ─── #2705: legacy ADR range is single-sourced and matches disk ───────────── +// +// The legacy zero-padded ADR range used to be stated two different ways +// (contributor-standards.md vs adr/README.md), both wrong. Now adr/README.md is +// the single authoritative statement and contributor-standards.md references it. +// The legacy set is the zero-padded ADRs whose numbers do NOT correspond to a +// same-numbered repository issue; 0174 and 0656 are modern mis-padded files. + +describe('#2705: legacy ADR range single-sourced and accurate', () => { + const STANDARDS = path.join(REPO_ROOT, 'docs', 'contributor-standards.md'); + const README = path.join(REPO_ROOT, 'docs', 'adr', 'README.md'); + + // Zero-padded ADR files on disk (4-digit prefix). + function zeroPaddedAdrFiles() { + return fs.readdirSync(ADR_DIR) + .filter((f) => /^\d{4}-.*\.md$/.test(f)) + .map((f) => ({ file: f, num: Number(f.slice(0, 4)) })); + } + + // A zero-padded number is "legacy sequential" iff no same-numbered issue-shaped + // ADR exists (i.e. the 4-digit number is NOT an issue number reused). 0001–0012 + // are sequential (collisions on disk corroborate); 0174/0656 match issues. + function legacyNumbers() { + const all = zeroPaddedAdrFiles(); + const nums = new Set(all.map((a) => a.num)); + const legacy = new Set(); + for (const n of nums) { + // Sequential legacy range: numbers 1..12 have intra-range duplicates on disk + // (0010 x2, 0011 x3) and do not correspond to same-numbered modern files. + if (n >= 1 && n <= 12) legacy.add(n); + } + return legacy; + } + + test('exactly one doc restates the legacy range; the other references it', () => { + const readme = fs.readFileSync(README, 'utf8'); + const standards = fs.readFileSync(STANDARDS, 'utf8'); + // The authoritative "0001-* through 0012-*" statement lives in README. The + // range tokens are backtick-delimited in the prose (`0001-*` through `0012-*`), + // so match the digits+through+digits ignoring the backtick/asterisk escapes. + assert.match(readme, /0001-\*[^A-Za-z0-9]*through[^A-Za-z0-9]*0012-\*/, 'README must carry the authoritative legacy-range statement'); + // contributor-standards must NOT restate the range — it must cross-reference README. + assert.doesNotMatch( + standards, + /0001-\*[^A-Za-z0-9]*through[^A-Za-z0-9]*001[12]-\*/, + 'contributor-standards.md must not restate the legacy range (single-sourced in README); it should reference it', + ); + assert.match( + standards, + /adr\/README\.md/, + 'contributor-standards.md must reference docs/adr/README.md for the legacy range', + ); + }); + + test('README legacy range matches the on-disk legacy set and excludes mis-padded modern 0174/0656', () => { + const readme = fs.readFileSync(README, 'utf8'); + // The legacy clause must NOT include 0174 (the prior wrong "(and 0174-*)" clause). + assert.doesNotMatch( + readme, + /through 0012-\* \(and 0174-\*\)/, + 'README must not classify 0174 as legacy residue (it is a modern mis-padded ADR)', + ); + // The legacy set on disk (0001–0012) all exist as zero-padded files. + const legacy = legacyNumbers(); + for (const n of legacy) { + const pad = String(n).padStart(4, '0'); + const matches = fs.readdirSync(ADR_DIR).filter((f) => f.startsWith(`${pad}-`)); + assert.ok(matches.length > 0, `legacy ADR ${pad}-* must exist on disk`); + } + }); + + test('README identifies 0174 and 0656 as mis-padded modern ADRs', () => { + const readme = fs.readFileSync(README, 'utf8'); + assert.match(readme, /0174/, 'README must mention 0174'); + assert.match(readme, /0656/, 'README must mention 0656'); + assert.match( + readme, + /modern.*mis-padded|mis-padded.*modern/i, + 'README must identify the zero-padded modern ADRs as mis-padded modern files, not legacy residue', + ); + }); +});