* refactor(#1170): remove hand-maintained INVENTORY count scalars The `(N shipped)` heading counts in docs/INVENTORY.md were absolute scalars that collided silently on merge: two branches each bumping the same integer to N+1 produced a clean git merge whose value the merged filesystem (N+2) contradicted, hard-failing inventory-counts.test.cjs on the CI merge commit across all platforms (DEFECT.INVENTORY-MERGE-UNDERCOUNT). - Strip the six `(N shipped)` heading counts + the two prose footnote counts; repoint the intro to INVENTORY-MANIFEST.json as the registry. - Drop the decorative `generated` date from the manifest + its strip-before-compare branch in gen-inventory-manifest.cjs (it conflicted on cross-day merges and is read by nothing). - Delete inventory-counts.test.cjs (scalar-vs-disk gate, the collision source); its drift protection is subsumed by the merge-safe set-membership test inventory-manifest-sync.test.cjs, which stays as the sole gate. - Add inventory-headings-countfree.test.cjs guard (fails if a count is re-added to a heading). - Fix already-broken count-bearing cross-doc anchors to stable count-free slugs in ARCHITECTURE.md + multi-agent-orchestration.md. - Retire the now-impossible DEFECT.INVENTORY-MERGE-UNDERCOUNT + obsolete RULESET.DOC-CONSISTENCY in CONTEXT.md; de-count DEFECT.INVENTORY-DRIFT; correct stale MANIFEST-CANONICAL-KEY (all six families canonical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#1170): backfill changeset PR number (#1179) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
5
.changeset/clever-otters-forage.md
Normal file
5
.changeset/clever-otters-forage.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Changed
|
||||
pr: 1179
|
||||
---
|
||||
**INVENTORY.md no longer carries `(N shipped)` count scalars** — the hand-maintained absolute counts collided silently on merge (two branches each bumping the same integer to N+1 while the merged tree held N+2), red-flagging CI on the merge commit across all platforms. The manifest's name-set is now the sole registry, anchors are count-free and stable, and a guard test blocks re-adding a count. (#1179)
|
||||
20
CONTEXT.md
20
CONTEXT.md
@@ -298,7 +298,7 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint-
|
||||
`RULESET.GEMINI.TEST_SENTINEL=convertClaudeToGeminiAgent regression should assert tools excludes ask_user, body excludes AskUserQuestion/ask_user, and Read still maps to read_file`
|
||||
|
||||
`RULESET.ADR-HEADER=every docs/adr/NNNN-*.md must open with - **Status:** Accepted|Proposed|Deprecated + - **Date:** YYYY-MM-DD immediately after title`
|
||||
`RULESET.MANIFEST-CANONICAL-KEY=docs/INVENTORY-MANIFEST.json — only families.workflows is canonical (read by tooling); top-level workflows key is stale, delete if present`
|
||||
`RULESET.MANIFEST-CANONICAL-KEY=docs/INVENTORY-MANIFEST.json has a single top-level key: families; ALL SIX families.* arrays (agents/commands/workflows/references/cli_modules/hooks) are canonical, consumed by test suites — tests/inventory-manifest-sync.test.cjs reads all six, edit-phase/enh-2380/enh-2430 tests read commands+workflows; the old generated date field and the stale top-level workflows key are both gone; regen via node scripts/gen-inventory-manifest.cjs --write`
|
||||
|
||||
`RULESET.PR-SCOPE.one-concern-per-pr=split unrelated changes into separate PRs; cherry-pick doc changes to dedicated docs/ branch immediately, then force-push original to remove the commit`
|
||||
|
||||
@@ -306,8 +306,6 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint-
|
||||
|
||||
`RULESET.CR-THREAD-RESOLVE=after adding // allow-test-rule: to silence lint, resolve existing inline CR threads via graphql resolveReviewThread mutation before merge — open threads mislead future reviewers; pattern: gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"PRRT_..."}) { thread { isResolved } } }'`
|
||||
|
||||
`RULESET.DOC-CONSISTENCY=when heading says (N shipped) and footnote says N-1 top-level references, update both; CR catches every time`
|
||||
|
||||
---
|
||||
|
||||
## CodeRabbit + repo-process guards (machine-oriented predicates)
|
||||
@@ -558,14 +556,10 @@ The canonical lint infrastructure adopted in ADR 452 (`docs/adr/452-eslint-lint-
|
||||
`DEFECT.PROMPT-INJECTION-SCAN-COLLISION.detect=any new bare <system|assistant|human|user> tag in agents/*.md`
|
||||
`DEFECT.PROMPT-INJECTION-SCAN-COLLISION.fix-forward=hyphenate the tag (<human-check>, <assistant-prompt>) — scanner regex matches bare names only`
|
||||
|
||||
`DEFECT.INVENTORY-DRIFT.symptom=new file added under gsd-core/references/ or gsd-core/workflows/ without updating docs/INVENTORY.md count + row AND docs/INVENTORY-MANIFEST.json`
|
||||
`DEFECT.INVENTORY-DRIFT.examples=#3309 planner-human-verify-mode.md (caught by tests/inventory-counts.test.cjs + tests/inventory-manifest-sync.test.cjs)`
|
||||
`DEFECT.INVENTORY-DRIFT.detect=tests/inventory-* fails with "References (N shipped) disagrees with filesystem" or "New surfaces not in manifest"`
|
||||
`DEFECT.INVENTORY-DRIFT.fix-forward=update INVENTORY.md headline count + row entry + footnote count; run node scripts/gen-inventory-manifest.cjs --write to regen INVENTORY-MANIFEST.json; only families.workflows is canonical (top-level workflows key is stale)`
|
||||
`DEFECT.INVENTORY-MERGE-UNDERCOUNT.symptom=docs/INVENTORY.md "## CLI Modules (N shipped)" (and the other family headlines) is an ABSOLUTE count of gsd-core/bin/lib/*.cjs, NOT a delta; two branches that each add a module both bump N->N+1, so merging them keeps N+1 while the merged filesystem has N+2`
|
||||
`DEFECT.INVENTORY-MERGE-UNDERCOUNT.detect=tests/inventory-counts.test.cjs HARD-FAILS ("CLI Modules (N shipped) matches gsd-core/bin/lib/", "headline counts match the filesystem") on the CI MERGE-COMMIT across ALL platforms (macos/ubuntu/windows) even though the branch passed local gsd-test; gsd-test runs the branch ALONE, CI tests branch MERGED with current next, so concurrent module-adds on next are invisible locally (hit #844, #1059)`
|
||||
`DEFECT.INVENTORY-MERGE-UNDERCOUNT.fix-forward=after merging origin/next into ANY branch that adds/removes an inventoried artifact (bin/lib module, references/*, workflows/*), rebuild then RE-DERIVE the count from "ls gsd-core/bin/lib/*.cjs | wc -l" (plus per-family counts), set the INVENTORY.md headline to match, run node scripts/gen-inventory-manifest.cjs --write; NEVER trust the merged headline integer`
|
||||
`DEFECT.INVENTORY-MERGE-UNDERCOUNT.prevention=for any module-adding branch, merge origin/next + reconcile INVENTORY BEFORE EVERY push (not only at branch creation); the longer the branch lives the more certain the drift`
|
||||
`DEFECT.INVENTORY-DRIFT.symptom=new file added under gsd-core/references/ or gsd-core/workflows/ without updating docs/INVENTORY.md row AND docs/INVENTORY-MANIFEST.json`
|
||||
`DEFECT.INVENTORY-DRIFT.examples=#3309 planner-human-verify-mode.md (caught by tests/inventory-manifest-sync.test.cjs)`
|
||||
`DEFECT.INVENTORY-DRIFT.detect=tests/inventory-manifest-sync.test.cjs fails with "New surfaces not in manifest"; tests/inventory-headings-countfree.test.cjs fails if a (N shipped) count is re-added to a heading`
|
||||
`DEFECT.INVENTORY-DRIFT.fix-forward=update INVENTORY.md row entry; run node scripts/gen-inventory-manifest.cjs --write to regen INVENTORY-MANIFEST.json (all six families.* arrays are canonical — see RULESET.MANIFEST-CANONICAL-KEY)`
|
||||
|
||||
`DEFECT.AGENT-FILE-SIZE-CAP-BREACH.symptom=adding to agents/gsd-planner.md (or other large agent files) exceeds the 45K char extraction-evidence threshold`
|
||||
`DEFECT.AGENT-FILE-SIZE-CAP-BREACH.state=gsd-planner.md is already 49,121 chars on main (over 45K); test fails on main; net-new content makes it strictly worse`
|
||||
@@ -763,9 +757,9 @@ Full detail in `~/.claude/skills/gsd-pr-fix-discipline/SKILL.md`. AI agents MUST
|
||||
|
||||
### INVENTORY / manifest drift
|
||||
|
||||
- **Symptom:** `inventory-counts.test.cjs` fails — `"<dir> (N shipped)" disagrees with filesystem (N+1)`
|
||||
- **Symptom:** `tests/inventory-manifest-sync.test.cjs` fails — `"New surfaces not in manifest"`; or `tests/inventory-headings-countfree.test.cjs` fails if a `(N shipped)` count was re-added to a heading
|
||||
- **Affected this session:** #154, #156, #143, #155, #169
|
||||
- **Fix:** Add row to `docs/INVENTORY.md` CLI Modules table + increment headline count + `node scripts/gen-inventory-manifest.cjs --write`
|
||||
- **Fix:** Add row to `docs/INVENTORY.md` + `node scripts/gen-inventory-manifest.cjs --write`
|
||||
|
||||
### Slash command two-tier confusion
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ Specialized agent definitions with frontmatter specifying:
|
||||
|
||||
### References (`gsd-core/references/*.md`)
|
||||
|
||||
Shared knowledge documents that workflows and agents `@-reference` (see [`docs/INVENTORY.md`](INVENTORY.md#references-41-shipped) for the authoritative count and full roster):
|
||||
Shared knowledge documents that workflows and agents `@-reference` (see [`docs/INVENTORY.md`](INVENTORY.md#references) for the authoritative full roster):
|
||||
|
||||
**Core references:**
|
||||
|
||||
@@ -299,7 +299,7 @@ Runtime hooks that integrate with the host AI agent:
|
||||
| `gsd-validate-commit.sh` | `PostToolUse` | Commit validation for conventional commit enforcement |
|
||||
| `gsd-phase-boundary.sh` | `PostToolUse` | Phase boundary detection for workflow transitions |
|
||||
|
||||
See [`docs/INVENTORY.md`](INVENTORY.md#hooks-11-shipped) for the authoritative 11-hook roster.
|
||||
See [`docs/INVENTORY.md`](INVENTORY.md#hooks) for the authoritative hook roster.
|
||||
|
||||
### Command Routing Hub (`gsd-core/bin/lib/command-routing-hub.cjs`)
|
||||
|
||||
@@ -338,7 +338,7 @@ Agents always return a `RESEARCH.md` path, never raw fetched content. Context di
|
||||
|
||||
### CLI Tools (`gsd-core/bin/`)
|
||||
|
||||
Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `gsd-core/bin/lib/` (see [`docs/INVENTORY.md`](INVENTORY.md#cli-modules-104-shipped) for the authoritative roster):
|
||||
Node.js CLI utility (`gsd-tools.cjs`) with domain modules split across `gsd-core/bin/lib/` (see [`docs/INVENTORY.md`](INVENTORY.md#cli-modules) for the authoritative roster):
|
||||
|
||||
|
||||
| Module | Responsibility |
|
||||
@@ -409,7 +409,7 @@ Orchestrator (workflow .md)
|
||||
|
||||
### Primary Agent Spawn Categories
|
||||
|
||||
Conceptual spawn-pattern taxonomy for the 21 primary agents. For the authoritative 31-agent roster (including the 10 advanced/specialized agents such as `gsd-pattern-mapper`, `gsd-code-reviewer`, `gsd-code-fixer`, `gsd-ai-researcher`, `gsd-domain-researcher`, `gsd-eval-planner`, `gsd-eval-auditor`, `gsd-framework-selector`, `gsd-debug-session-manager`, `gsd-intel-updater`), see [`docs/INVENTORY.md`](INVENTORY.md#agents-31-shipped).
|
||||
Conceptual spawn-pattern taxonomy for the primary agents. For the authoritative agent roster (including the advanced/specialized agents such as `gsd-pattern-mapper`, `gsd-code-reviewer`, `gsd-code-fixer`, `gsd-ai-researcher`, `gsd-domain-researcher`, `gsd-eval-planner`, `gsd-eval-auditor`, `gsd-framework-selector`, `gsd-debug-session-manager`, `gsd-intel-updater`), see [`docs/INVENTORY.md`](INVENTORY.md#agents).
|
||||
|
||||
|
||||
| Category | Agents | Parallelism |
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"generated": "2026-06-13",
|
||||
"families": {
|
||||
"agents": [
|
||||
"gsd-advisor-researcher",
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
|
||||
## How To Use This File
|
||||
|
||||
- Counts here are derived from the filesystem at the v1.36.0 pin and may drift between releases. For live counts, run `ls commands/gsd/*.md | wc -l`, `ls agents/gsd-*.md | wc -l`, etc. against the checkout.
|
||||
- The machine-readable roster lives in `docs/INVENTORY-MANIFEST.json` (regenerated by `scripts/gen-inventory-manifest.cjs --write`). For live counts, run `ls agents/gsd-*.md | wc -l` etc. against the checkout.
|
||||
- This file enumerates every shipped surface across all six families (agents, commands, workflows, references, CLI modules, hooks). Broad docs may render narrative or curated subsets; when they disagree with the filesystem, this file and the directory listings are authoritative.
|
||||
- New surfaces added after v1.36.0 should land here first, then propagate to the broad docs. The drift-control tests in `tests/inventory-counts.test.cjs`, `tests/commands-doc-parity.test.cjs`, `tests/agents-doc-parity.test.cjs`, `tests/cli-modules-doc-parity.test.cjs`, `tests/hooks-doc-parity.test.cjs`, `tests/architecture-counts.test.cjs`, and `tests/command-count-sync.test.cjs` anchor the counts and roster contents against the filesystem.
|
||||
- New surfaces should land here first, then propagate to the broad docs. The drift-control tests in `tests/inventory-manifest-sync.test.cjs`, `tests/commands-doc-parity.test.cjs`, `tests/agents-doc-parity.test.cjs`, `tests/cli-modules-doc-parity.test.cjs`, `tests/hooks-doc-parity.test.cjs`, and `tests/command-count-sync.test.cjs` anchor the roster contents against the filesystem.
|
||||
|
||||
This is the authoritative roster of every shipped GSD Core surface. See the [docs index](README.md) to navigate by topic.
|
||||
|
||||
---
|
||||
|
||||
## Agents (33 shipped)
|
||||
## Agents
|
||||
|
||||
Full roster at `agents/gsd-*.md`. The "Primary doc" column flags whether [`docs/AGENTS.md`](AGENTS.md) carries a full role card (*primary*), a short stub in the "Advanced and Specialized Agents" section (*advanced stub*), or no coverage (*inventory only*).
|
||||
|
||||
@@ -52,11 +52,11 @@ Full roster at `agents/gsd-*.md`. The "Primary doc" column flags whether [`docs/
|
||||
| gsd-doc-classifier | Classifies a single planning document as ADR, PRD, SPEC, DOC, or UNKNOWN; spawned in parallel to process the doc corpus. | `/gsd-ingest-docs` | advanced stub |
|
||||
| gsd-doc-synthesizer | Synthesizes classified planning docs into a single consolidated context with precedence rules, cycle detection, and three-bucket conflicts report. | `/gsd-ingest-docs` | advanced stub |
|
||||
|
||||
**Coverage note.** `docs/AGENTS.md` gives full role cards for 21 primary agents plus concise stubs for the 12 advanced agents. The Agent Tool Permissions Summary in that file covers only the primary 21 agents; the advanced agents' tool lists are captured in their per-agent frontmatter in `agents/gsd-*.md`.
|
||||
**Coverage note.** `docs/AGENTS.md` gives full role cards for the primary agents plus concise stubs for the advanced agents. The Agent Tool Permissions Summary in that file covers only the primary agents; the advanced agents' tool lists are captured in their per-agent frontmatter in `agents/gsd-*.md`.
|
||||
|
||||
---
|
||||
|
||||
## Commands (67 shipped)
|
||||
## Commands
|
||||
|
||||
Full roster at `commands/gsd/*.md`. The groupings below mirror `docs/COMMANDS.md` section order; each row carries the command name, a one-line role derived from the command's frontmatter `description:`, and a link to the source file. `tests/command-count-sync.test.cjs` locks the count against the filesystem.
|
||||
|
||||
@@ -166,7 +166,7 @@ These six routers are descriptor-only entries that the model picks first; the bo
|
||||
|
||||
---
|
||||
|
||||
## Workflows (88 shipped)
|
||||
## Workflows
|
||||
|
||||
Full roster at `gsd-core/workflows/*.md`. Workflows are thin orchestrators that commands reference internally; most are not read directly by end users. Rows below map each workflow file to its role (derived from the `<purpose>` block) and, where applicable, to the command that invokes it.
|
||||
|
||||
@@ -264,7 +264,7 @@ Full roster at `gsd-core/workflows/*.md`. Workflows are thin orchestrators that
|
||||
|
||||
---
|
||||
|
||||
## References (69 shipped)
|
||||
## References
|
||||
|
||||
Full roster at `gsd-core/references/*.md`. References are shared knowledge documents that workflows and agents `@-reference`. The groupings below match [`docs/ARCHITECTURE.md`](ARCHITECTURE.md#references-gsd-corereferencesmd) — core, workflow, thinking-model clusters, and the modular planner decomposition.
|
||||
|
||||
@@ -368,11 +368,11 @@ The `gsd-planner` agent is decomposed into a core agent plus reference modules t
|
||||
| `user-story-template.md` | User story format for MVP planning — "As a / I want to / So that" structured fields. |
|
||||
| `spidr-splitting.md` | SPIDR splitting decomposition rules for handling large user stories in MVP mode. |
|
||||
|
||||
> **Subdirectory:** `gsd-core/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not counted in the 64 top-level references.
|
||||
> **Subdirectory:** `gsd-core/references/few-shot-examples/` contains additional few-shot examples (`plan-checker.md`, `verifier.md`) that are referenced from specific agents. These are not among the top-level references.
|
||||
|
||||
---
|
||||
|
||||
## CLI Modules (113 shipped)
|
||||
## CLI Modules
|
||||
|
||||
Full listing: `gsd-core/bin/lib/*.cjs`.
|
||||
|
||||
@@ -496,7 +496,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
|
||||
|
||||
---
|
||||
|
||||
## Hooks (17 shipped)
|
||||
## Hooks
|
||||
|
||||
Full listing: `hooks/`.
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ and write a single output document gets exactly those permissions — no Bash
|
||||
execution, no access to broader state. That constraint is intentional: it
|
||||
keeps the blast radius small if an agent behaves unexpectedly.
|
||||
|
||||
For the complete 31-agent roster, see [Inventory](../INVENTORY.md#agents-31-shipped).
|
||||
For the complete agent roster, see [Inventory](../INVENTORY.md#agents).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ const FAMILIES = [
|
||||
];
|
||||
|
||||
function buildManifest() {
|
||||
const manifest = { generated: new Date().toISOString().slice(0, 10), families: {} };
|
||||
const manifest = { families: {} };
|
||||
for (const { name, dir, filter, toName } of FAMILIES) {
|
||||
manifest.families[name] = fs
|
||||
.readdirSync(dir)
|
||||
@@ -78,9 +78,6 @@ function main() {
|
||||
if (flag === '--check') {
|
||||
const committed = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
|
||||
const live = buildManifest();
|
||||
// Strip the generated date for comparison
|
||||
delete committed.generated;
|
||||
delete live.generated;
|
||||
const committedStr = JSON.stringify(committed, null, 2);
|
||||
const liveStr = JSON.stringify(live, null, 2);
|
||||
if (committedStr !== liveStr) {
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// allow-test-rule: source-text-is-the-product
|
||||
// Workflow .md / agent .md / command .md / reference .md files — their text
|
||||
// IS what the runtime loads. Testing text content tests the deployed contract.
|
||||
// Per CONTRIBUTING.md exception matrix.
|
||||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Locks docs/INVENTORY.md's "(N shipped)" headline counts against the
|
||||
* filesystem for each of the six families. INVENTORY.md is the
|
||||
* authoritative roster — if a surface ships, its row must exist here
|
||||
* and the headline count must match ls.
|
||||
*
|
||||
* Both sides are computed at test runtime — no hardcoded numbers.
|
||||
*
|
||||
* Related: docs readiness refresh, lane-12 recommendation.
|
||||
*/
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const INVENTORY_MD = path.join(ROOT, 'docs', 'INVENTORY.md');
|
||||
const INVENTORY = fs.readFileSync(INVENTORY_MD, 'utf8');
|
||||
|
||||
const FAMILIES = [
|
||||
{ label: 'Agents', dir: 'agents', filter: (f) => /^gsd-.*\.md$/.test(f) },
|
||||
{ label: 'Commands', dir: 'commands/gsd', filter: (f) => f.endsWith('.md') },
|
||||
{ label: 'Workflows', dir: 'gsd-core/workflows', filter: (f) => f.endsWith('.md') },
|
||||
{ label: 'References', dir: 'gsd-core/references', filter: (f) => f.endsWith('.md') },
|
||||
{ label: 'CLI Modules', dir: 'gsd-core/bin/lib', filter: (f) => f.endsWith('.cjs') },
|
||||
{ label: 'Hooks', dir: 'hooks', filter: (f) => /\.(js|sh)$/.test(f) },
|
||||
];
|
||||
|
||||
function headlineCount(label) {
|
||||
const re = new RegExp(`^##\\s+${label}\\s+\\((\\d+)\\s+shipped\\)`, 'm');
|
||||
const m = INVENTORY.match(re);
|
||||
assert.ok(m, `docs/INVENTORY.md is missing the "## ${label} (N shipped)" header`);
|
||||
return parseInt(m[1], 10);
|
||||
}
|
||||
|
||||
function fsCount(relDir, filter) {
|
||||
return fs
|
||||
.readdirSync(path.join(ROOT, relDir))
|
||||
.filter((name) => fs.statSync(path.join(ROOT, relDir, name)).isFile())
|
||||
.filter(filter)
|
||||
.length;
|
||||
}
|
||||
|
||||
describe('docs/INVENTORY.md headline counts match the filesystem', () => {
|
||||
for (const { label, dir, filter } of FAMILIES) {
|
||||
test(`"${label} (N shipped)" matches ${dir}/`, () => {
|
||||
const documented = headlineCount(label);
|
||||
const actual = fsCount(dir, filter);
|
||||
assert.strictEqual(
|
||||
documented,
|
||||
actual,
|
||||
`docs/INVENTORY.md "${label} (${documented} shipped)" disagrees with ${dir}/ file count (${actual}) — update the headline and the row list`,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
33
tests/inventory-headings-countfree.test.cjs
Normal file
33
tests/inventory-headings-countfree.test.cjs
Normal file
@@ -0,0 +1,33 @@
|
||||
// allow-test-rule: runtime-contract-is-the-product — INVENTORY.md heading format is the shipped doc surface being locked
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Guards that docs/INVENTORY.md does NOT contain "(N shipped)" count
|
||||
* scalars in section headings. Hard counts in shared-line headings cause
|
||||
* silent undercount when two branches each add a module (DEFECT.INVENTORY-MERGE-UNDERCOUNT).
|
||||
*
|
||||
* Regression-must-fail-first: run this BEFORE removing the counts to confirm
|
||||
* it catches the bad heading format, then after removal to confirm it passes.
|
||||
*/
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const INVENTORY_PATH = path.join(ROOT, 'docs', 'INVENTORY.md');
|
||||
|
||||
test('docs/INVENTORY.md has no "(N shipped)" count scalars in headings', () => {
|
||||
const content = fs.readFileSync(INVENTORY_PATH, 'utf8');
|
||||
const offenders = content
|
||||
.split('\n')
|
||||
.filter((line) => /^##\s+.+\(\d+\s+shipped\)/.test(line));
|
||||
|
||||
assert.ok(
|
||||
offenders.length === 0,
|
||||
'docs/INVENTORY.md still has hard-count headings (DEFECT.INVENTORY-MERGE-UNDERCOUNT).\n' +
|
||||
'Remove the "(N shipped)" parenthetical from each:\n' +
|
||||
offenders.map((l) => ' ' + l).join('\n'),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user