From 004c7532b6628589c2664236bce87bda191fe203 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 28 Aug 2026 14:44:01 -0400 Subject: [PATCH] fix(#3796): write the audit report to the single-version filename every reader expects (#4007) * test(#3796): the audit report writer and readers must agree on the filename * fix(#3796): write the audit report to the single-version filename every reader expects * chore(#3796): changeset fragment (pr number backfilled after PR creation) * chore(#3796): backfill changeset PR number (4007) --------- Co-authored-by: sim --- .changeset/nimble-lynx-caper.md | 5 +++ gsd-core/workflows/audit-milestone.md | 2 +- scripts/lint-test-file-count.allowlist.json | 8 ++++ tests/audit-milestone-filename-guard.test.cjs | 39 +++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .changeset/nimble-lynx-caper.md create mode 100644 tests/audit-milestone-filename-guard.test.cjs diff --git a/.changeset/nimble-lynx-caper.md b/.changeset/nimble-lynx-caper.md new file mode 100644 index 000000000..23da170e5 --- /dev/null +++ b/.changeset/nimble-lynx-caper.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 4007 +--- +**The milestone audit report is written where its readers look for it** — `/gsd-audit-milestone` created the report at a doubled `.planning/v{version}-v{version}-MILESTONE-AUDIT.md` path while every downstream reference (Report pointers, the `cat`, the completion checklist) reads the single-version `v{version}-MILESTONE-AUDIT.md`, so the report silently landed unread. (#3796) diff --git a/gsd-core/workflows/audit-milestone.md b/gsd-core/workflows/audit-milestone.md index f3a72520f..daa35ea8c 100644 --- a/gsd-core/workflows/audit-milestone.md +++ b/gsd-core/workflows/audit-milestone.md @@ -183,7 +183,7 @@ Discovery only — never auto-calls `/gsd:validate-phase`. ## 6. Aggregate into v{version}-MILESTONE-AUDIT.md -Create `.planning/v{version}-v{version}-MILESTONE-AUDIT.md` with: +Create `.planning/v{version}-MILESTONE-AUDIT.md` with: ```yaml --- diff --git a/scripts/lint-test-file-count.allowlist.json b/scripts/lint-test-file-count.allowlist.json index d79c9a65a..6a4af9803 100644 --- a/scripts/lint-test-file-count.allowlist.json +++ b/scripts/lint-test-file-count.allowlist.json @@ -249,6 +249,14 @@ ], "issue": "3873", "justification": "ADR-3473 \u00a78.8 makes docs/reference/state-md.md and its four locale siblings generated-and-committed and adds the repo's first locale-parity check (verified: no locale-parity lint precedent exists here). That check is a distinct concern from docs-update (content freshness) and docs-parity-live-registry (registry parity); folding it into either would place an unrelated subject inside them purely to satisfy a count. The section it guards is absent from all four translations today and documents the status enum behind #3853." + }, + "audit": { + "files": [ + "audit-command-cutover.test.cjs", + "audit-fix-command.test.cjs", + "audit-milestone-filename-guard.test.cjs" + ], + "issue": "#3796 \u2014 the third file is a structural guard over the shipped audit-milestone workflow text (writer/reader filename agreement), not a behavior suite of the audit module; consolidation into the CLI suites would put a source-text scan behind spawn-heavy fixtures." } } } diff --git a/tests/audit-milestone-filename-guard.test.cjs b/tests/audit-milestone-filename-guard.test.cjs new file mode 100644 index 000000000..93eed8613 --- /dev/null +++ b/tests/audit-milestone-filename-guard.test.cjs @@ -0,0 +1,39 @@ +'use strict'; + +// ───────────────────────────────────────────────────────────────────────────── +// #3796 — the audit-milestone report writer and its readers must agree on +// the report's filename. +// +// The single writer line created `.planning/v{version}-v{version}-MILESTONE +// -AUDIT.md` (a doubled version segment) while every downstream reference — +// the step's own heading, the Report pointers, the `cat`, and the completion +// checklist — reads `v{version}-MILESTONE-AUDIT.md`. The report landed at a +// path no reader ever looked at. +// ───────────────────────────────────────────────────────────────────────────── + +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const AUDIT_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'audit-milestone.md'); + +// audit-milestone.md is shipped workflow text — the bytes ARE what the +// runtime loads; a structural scan over it tests the deployed contract. +test('#3796: the audit report writer and readers agree on the single-version filename', () => { + const md = fs.readFileSync(AUDIT_MD, 'utf-8'); + assert.ok( + !md.includes('v{version}-v{version}'), + '#3796: no doubled version segment may appear anywhere in the workflow', + ); + const writer = /^Create `\.planning\/v\{version\}-MILESTONE-AUDIT\.md` with:$/m; + assert.ok( + writer.test(md), + '#3796: the writer line must create exactly .planning/v{version}-MILESTONE-AUDIT.md', + ); + const readers = md.match(/v\{version\}-MILESTONE-AUDIT\.md/g) || []; + assert.ok( + readers.length >= 5, + `the single-version readers (heading, Report pointers, cat, checklist) must remain; found ${readers.length}`, + ); +});