diff --git a/.changeset/clever-otters-forage.md b/.changeset/clever-otters-forage.md new file mode 100644 index 000000000..1807a73ee --- /dev/null +++ b/.changeset/clever-otters-forage.md @@ -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) diff --git a/CONTEXT.md b/CONTEXT.md index 43bd00659..6b4bdf2da 100644 --- a/CONTEXT.md +++ b/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 tag in agents/*.md` `DEFECT.PROMPT-INJECTION-SCAN-COLLISION.fix-forward=hyphenate the tag (, ) — 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 — `" (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 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0b6bfc8ff..b69f4c6cd 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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 | diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index e95643b6d..67722ca60 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -1,5 +1,4 @@ { - "generated": "2026-06-13", "families": { "agents": [ "gsd-advisor-researcher", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index dfd054cc8..328c47fe0 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -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 `` 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/`. diff --git a/docs/explanation/multi-agent-orchestration.md b/docs/explanation/multi-agent-orchestration.md index c06e4b482..bc2550e3c 100644 --- a/docs/explanation/multi-agent-orchestration.md +++ b/docs/explanation/multi-agent-orchestration.md @@ -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). --- diff --git a/scripts/gen-inventory-manifest.cjs b/scripts/gen-inventory-manifest.cjs index 40a859c7c..193bdf290 100644 --- a/scripts/gen-inventory-manifest.cjs +++ b/scripts/gen-inventory-manifest.cjs @@ -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) { diff --git a/tests/inventory-counts.test.cjs b/tests/inventory-counts.test.cjs deleted file mode 100644 index 37f8f3413..000000000 --- a/tests/inventory-counts.test.cjs +++ /dev/null @@ -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`, - ); - }); - } -}); diff --git a/tests/inventory-headings-countfree.test.cjs b/tests/inventory-headings-countfree.test.cjs new file mode 100644 index 000000000..53335e567 --- /dev/null +++ b/tests/inventory-headings-countfree.test.cjs @@ -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'), + ); +});