* feat(#2996): inventory the workflow fragment tree as its own families Epic #1671 Phase 6.5, the epic's last deliverable. 47 step files across 15 workflows and 13 mode files were invisible to docs/INVENTORY-MANIFEST.json. Not through a missed row — through construction: buildManifest walks each family with a flat readdirSync + isFile() and never recurses, so nothing under gsd-core/workflows/<wf>/ could ever appear. modes/ has been invisible that way since #717 without any gate firing, which is the evidence that this is a generator gap rather than someone forgetting a row. Two new families, workflow_steps and workflow_modes, keyed by <workflow>/<subdir>/<file> rather than a bare basename. That is deliberate: two workflows may each own a regression-gate.md, and a step file may share a name with a top-level workflow. The manifest is compared by JSON equality, so a basename collision would silently drop an entry and read as "up to date". Recursion is bounded at exactly one named subdirectory, and a limit+1 test pins that bound so it cannot quietly become a general walk. tests/inventory-manifest-sync.test.cjs carried its OWN duplicate copy of the FAMILIES table — the DEFECT.GENERATIVE-FIX divergence class. Adding a family to the generator alone would have left that test verifying six of eight families while still reporting green. The table now lives once in the generator and is imported, so the two surfaces cannot drift; runMain is guarded behind require.main so importing does not execute the CLI. The per-file roster stays in the generated manifest rather than being copied into INVENTORY.md: 60 hand-maintained rows in lockstep with a generated artifact is precisely the drift this file exists to catch. CONTEXT.md's RULESET.MANIFEST-CANONICAL-KEY and DEFECT.INVENTORY-DRIFT both said "six families" and now say eight, with the two key shapes and the import rule recorded. The non-shipping example index was regenerated for the same edits. Note on scope: this issue also asked for a one-fragment-edit proof. That landed independently as PR #3046 and is not rebuilt here. Refs #2996 * fix(#2996): correct a fabricated roster and an inert coverage pragma Isolated review returned one blocker and three lesser findings. All four were real; all four are fixed. BLOCKER — docs/INVENTORY.md claimed the workflow_modes roster was "discuss-phase, sketch". There is no gsd-core/workflows/sketch/ and never has been; the second member is `help` (4 mode files), exactly as the manifest generated by this same diff already listed. A doc contradicting the manifest it describes, in the PR whose whole purpose is closing doc/reality drift. The adjacent hand-maintained "15 workflows" count is also removed: an unenforced number in a table cell is the same staleness class this file exists to catch, and no test guards table-cell counts. MAJOR — the CLI entry guard carried `/* istanbul ignore next */`, which excludes nothing here. This repo measures coverage with c8 (test:coverage:scripts-floor, 55% floor over scripts/**/*.cjs), and c8/v8-to-istanbul honors only `/* c8 ignore next */`. The pragma looked like it was doing something and was not — the same failure shape as a marker that looks like working gating. MINOR — collectNested called statSync/readdirSync unguarded, so a dangling symlink or an EACCES directory under any workflow's steps/ would throw uncaught and red the manifest gate for the entire repo. An entry that cannot be statted is, for inventory purposes, not a countable file — the same disposition as "not a directory". Row 13c pins the behavior with a real dangling symlink. Refs #2996 * chore(#2996): backfill changeset pr number to 3061 * test(#2996): guard the dangling-symlink row on Windows fs.symlinkSync throws EPERM on Windows without elevation or Developer Mode, so row 13c would red the Windows lane. Guarded with the repo's idiom — a process.platform check plus a genuine t.skip() carrying its reason, never a bare return, which node:test counts as a PASS and would hide the gap. Worth recording why this was not caught here: CI classified this PR's diff as inert (no bin/, gsd-core/, or src/ changes), so the full test matrix was SKIPPED entirely — the 'full test (${{ matrix.os }}, ...)' job shows as skipping with its matrix expression unexpanded. The Windows lane never ran. It would have fired on the next PR that does touch core code, in someone else's change. --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -347,7 +347,7 @@
|
||||
{
|
||||
"id": "DEFECT.INVENTORY-DRIFT.fix-forward",
|
||||
"klass": "DEFECT",
|
||||
"value": "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)"
|
||||
"value": "update INVENTORY.md row entry; run node scripts/gen-inventory-manifest.cjs --write to regen INVENTORY-MANIFEST.json (all eight families.* arrays are canonical — see RULESET.MANIFEST-CANONICAL-KEY); a workflow SUB-file (gsd-core/workflows/<wf>/steps/*.md or modes/*.md) lands in workflow_steps/workflow_modes, not in workflows, which is keyed by bare basename and cannot hold a nested path"
|
||||
},
|
||||
{
|
||||
"id": "DEFECT.INVENTORY-DRIFT.symptom",
|
||||
@@ -1792,7 +1792,7 @@
|
||||
{
|
||||
"id": "RULESET.MANIFEST-CANONICAL-KEY",
|
||||
"klass": "RULESET",
|
||||
"value": "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"
|
||||
"value": "docs/INVENTORY-MANIFEST.json has a single top-level key: families; ALL EIGHT families.* arrays (agents/commands/workflows/references/cli_modules/hooks flat, plus workflow_modes/workflow_steps nested — #2996, epic #1671 Phase 6.5) are canonical, consumed by test suites — tests/inventory-manifest-sync.test.cjs reads all eight, edit-phase/enh-2380/enh-2430 tests read commands+workflows; the six flat families are keyed by BARE BASENAME while the two nested families are keyed by <workflow>/<subdir>/<file> path, deliberately, because two workflows may each own a same-named step file and a basename key would silently drop one under a JSON-equality comparison; recursion is bounded at exactly one named subdirectory, never a general walk; the family tables live ONCE in scripts/gen-inventory-manifest.cjs and are IMPORTED by the test (the test formerly redeclared them, a DEFECT.GENERATIVE-FIX divergence that let a new family be verified by nobody while still reporting green); the old generated date field and the stale top-level workflows key are both gone; regen via node scripts/gen-inventory-manifest.cjs --write, AFTER build:lib"
|
||||
},
|
||||
{
|
||||
"id": "RULESET.PR-FLOW.docker-before-push",
|
||||
|
||||
@@ -508,6 +508,70 @@
|
||||
"gsd-workflow-guard.js",
|
||||
"gsd-worktree-path-guard.js",
|
||||
"gsd-write-guard.js"
|
||||
],
|
||||
"workflow_modes": [
|
||||
"discuss-phase/modes/advisor.md",
|
||||
"discuss-phase/modes/all.md",
|
||||
"discuss-phase/modes/analyze.md",
|
||||
"discuss-phase/modes/auto.md",
|
||||
"discuss-phase/modes/batch.md",
|
||||
"discuss-phase/modes/chain.md",
|
||||
"discuss-phase/modes/default.md",
|
||||
"discuss-phase/modes/power.md",
|
||||
"discuss-phase/modes/text.md",
|
||||
"help/modes/brief.md",
|
||||
"help/modes/default.md",
|
||||
"help/modes/full.md",
|
||||
"help/modes/topic.md"
|
||||
],
|
||||
"workflow_steps": [
|
||||
"autonomous/steps/converge-banner.md",
|
||||
"autonomous/steps/converge-dispatch-bg.md",
|
||||
"autonomous/steps/converge-dispatch-inline.md",
|
||||
"autonomous/steps/converge-fail-fast.md",
|
||||
"autonomous/steps/converge-loop.md",
|
||||
"code-review/steps/dispatch-fix.md",
|
||||
"code-review/steps/structural-pre-pass.md",
|
||||
"complete-milestone/steps/git-tag.md",
|
||||
"discuss-phase-assumptions/steps/auto-advance-dispatch.md",
|
||||
"docs-update/steps/dispatch-monorepo-packages.md",
|
||||
"execute-phase/steps/codebase-drift-gate.md",
|
||||
"execute-phase/steps/executor-isolation-dispatch.md",
|
||||
"execute-phase/steps/gap-closure-artifacts.md",
|
||||
"execute-phase/steps/partial-wave.md",
|
||||
"execute-phase/steps/per-plan-worktree-gate.md",
|
||||
"execute-phase/steps/post-merge-gate.md",
|
||||
"execute-phase/steps/regression-gate-run.md",
|
||||
"execute-phase/steps/regression-gate.md",
|
||||
"execute-phase/steps/worktree-recovery-policy.md",
|
||||
"new-milestone/steps/project-md-milestone-write.md",
|
||||
"new-milestone/steps/reset-phase-safety.md",
|
||||
"new-project/steps/auto-mode-config.md",
|
||||
"new-project/steps/auto-mode-detection.md",
|
||||
"new-project/steps/codebase-map-offer.md",
|
||||
"plan-phase/steps/adr-ingest-express-path.md",
|
||||
"plan-phase/steps/chunked-planning-mode.md",
|
||||
"plan-phase/steps/closed-phase-gate.md",
|
||||
"plan-phase/steps/prd-express-gate.md",
|
||||
"plan-phase/steps/prd-express-path.md",
|
||||
"plan-phase/steps/research-only-early-exit.md",
|
||||
"plan-phase/steps/research-only-modifiers.md",
|
||||
"plan-phase/steps/reviews-prerequisite.md",
|
||||
"plan-phase/steps/stall-detection-helpers.md",
|
||||
"plan-phase/steps/windows-troubleshooting.md",
|
||||
"progress/steps/forensic-audit.md",
|
||||
"progress/steps/mvp-display.md",
|
||||
"quick/steps/discussion-phase.md",
|
||||
"quick/steps/plan-checker-loop.md",
|
||||
"quick/steps/quick-verification.md",
|
||||
"quick/steps/research-phase.md",
|
||||
"quick/steps/worktree-pre-dispatch-commit.md",
|
||||
"review/steps/reviewer-instances-note-1.md",
|
||||
"review/steps/reviewer-instances-note-2.md",
|
||||
"transition/steps/workstream-collision-check.md",
|
||||
"update/steps/channel-banner.md",
|
||||
"verify-work/steps/automated-ui-verification.md",
|
||||
"verify-work/steps/mvp-uat-framing.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +269,26 @@ Full roster at `gsd-core/workflows/*.md`. Workflows are thin orchestrators that
|
||||
|
||||
> **Note:** Some workflows have no direct user-facing command (e.g. `execute-plan.md`, `verify-phase.md`, `transition.md`, `node-repair.md`, `diagnose-issues.md`) — they are invoked internally by orchestrator workflows. `discovery-phase.md` is an alternate entry for `/gsd-new-project`.
|
||||
|
||||
### Workflow Sub-Files
|
||||
|
||||
A workflow may own two kinds of sub-file. Both live under `gsd-core/workflows/<workflow>/` and
|
||||
neither is separately invocable — the parent workflow reaches them.
|
||||
|
||||
| Subdirectory | What it holds | Manifest family | Roster |
|
||||
|---|---|---|---|
|
||||
| `<workflow>/steps/*.md` | Gated section bodies extracted by the fragment model (ADR-1671, epic #1671 Phases 6.1–6.3). The parent carries a `section_manifest`-gated stub; `gsd-core/workflows/section-manifest.json` names which step a given invocation reads. | `workflow_steps` | See `docs/INVENTORY-MANIFEST.json` for the authoritative per-file list |
|
||||
| `<workflow>/modes/*.md` | Progressive-disclosure mode files (#717). The parent dispatches to exactly one; `discuss-phase/modes/` is the canonical example. | `workflow_modes` | `discuss-phase`, `help` |
|
||||
|
||||
Both families are keyed by `<workflow>/<subdir>/<file>.md` rather than a bare filename, because two
|
||||
workflows may each own a step of the same name — `families.workflows` uses bare basenames and
|
||||
cannot represent these without collision.
|
||||
|
||||
**Adding a step or mode file requires no hand-written row here.** Run
|
||||
`node scripts/gen-inventory-manifest.cjs --write` (after `build:lib`) and the manifest picks it up;
|
||||
`tests/inventory-manifest-sync.test.cjs` fails if you forget. The per-file roster deliberately lives
|
||||
in `docs/INVENTORY-MANIFEST.json` rather than being duplicated in this table — 60 rows that must be
|
||||
hand-maintained in lockstep with a generated artifact is the drift this file exists to catch.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
Reference in New Issue
Block a user