From c2b3f02d41217fab4e8fc91d923f5b9e4c11d30f Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 5 May 2026 15:02:38 -0400 Subject: [PATCH] =?UTF-8?q?fix(#3135):=20restore=20workflows/add-backlog.m?= =?UTF-8?q?d=20=E2=80=94=20capture=20--backlog=20had=20no=20workflow=20to?= =?UTF-8?q?=20load=20(#3147)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#3121): implement commands verb in SDK native registry - Add commandsList handler — returns sorted JSON array of all registered verb strings; satisfies workstream-flag.md + agent tooling discoverability - Register ['commands', commandsList] in DECISION_ROUTING_STATIC_CATALOG - Add golden-policy exemption (SDK-only, no CJS mirror needed) - check.decision-coverage-plan/verify were already registered; commands was the remaining gap Closes #3121 * fix(#3135): restore workflows/add-backlog.md — capture --backlog had no workflow to load Root cause: PR #2824 consolidated add-backlog into gsd-capture --backlog and wired capture.md to delegate to workflows/add-backlog.md via execution_context. The workflow file was never created (same gap class as reapply-patches.md which was caught and fixed in the same PR). With no file to load, the agent had no implementation steps to follow when --backlog was invoked. Fix: - Restore get-shit-done/workflows/add-backlog.md with full process from deleted commands/gsd/add-backlog.md (phase.next-decimal, ROADMAP write, mkdir, commit) - Preserve #2280 ordering invariant: ROADMAP entry written before directory - Fix docs/INVENTORY.md: remove incorrect attribution of --backlog to add-todo.md, add add-backlog.md row, bump workflow count 84→85 - Update docs/INVENTORY-MANIFEST.json - Add regression test: every execution_context @-reference in commands/gsd/*.md must resolve to an existing workflow file on disk Closes #3135 --- .../fix-3135-capture-backlog-workflow.md | 5 + docs/INVENTORY-MANIFEST.json | 1 + docs/INVENTORY.md | 5 +- get-shit-done/workflows/add-backlog.md | 85 ++++++++ ...bug-3135-capture-backlog-workflow.test.cjs | 183 ++++++++++++++++++ 5 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-3135-capture-backlog-workflow.md create mode 100644 get-shit-done/workflows/add-backlog.md create mode 100644 tests/bug-3135-capture-backlog-workflow.test.cjs diff --git a/.changeset/fix-3135-capture-backlog-workflow.md b/.changeset/fix-3135-capture-backlog-workflow.md new file mode 100644 index 000000000..a80585edb --- /dev/null +++ b/.changeset/fix-3135-capture-backlog-workflow.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3135 +--- +**`/gsd-capture --backlog` now has a workflow to load** — PR #2824 consolidated `add-backlog` into the `--backlog` flag on `/gsd-capture` and wired `commands/gsd/capture.md` to delegate to `workflows/add-backlog.md` via `execution_context`. The workflow file was never created, leaving the routing with no implementation to load. Restores `get-shit-done/workflows/add-backlog.md` with the full process from the deleted `commands/gsd/add-backlog.md`: find next 999.x slot via `phase.next-decimal`, write ROADMAP entry before creating the phase directory (preserving the #2280 ordering invariant), create `.planning/phases/{N}-{slug}/`, and commit. Also fixes `docs/INVENTORY.md` which incorrectly attributed `--backlog` routing to `add-todo.md`. Adds a broad regression test that every `execution_context` `@`-reference in any `commands/gsd/*.md` resolves to an existing workflow file, preventing this class of gap from silently re-appearing. Closes #3135. diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index 880a03fd2..41b4b6966 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -104,6 +104,7 @@ "/gsd-workstreams" ], "workflows": [ + "add-backlog.md", "add-phase.md", "add-tests.md", "add-todo.md", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index 569724dd8..c447780b8 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -162,15 +162,16 @@ These six routers are descriptor-only entries that the model picks first; the bo --- -## Workflows (84 shipped) +## Workflows (85 shipped) Full roster at `get-shit-done/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. | Workflow | Role | Invoked by | |----------|------|------------| +| `add-backlog.md` | Add a backlog item to ROADMAP.md using 999.x numbering. | `/gsd-capture --backlog` | | `add-phase.md` | Add a new integer phase to the end of the current milestone in the roadmap. | `/gsd-phase` (default) | | `add-tests.md` | Generate unit and E2E tests for a completed phase based on its artifacts. | `/gsd-add-tests` | -| `add-todo.md` | Capture an idea or task that surfaces during a session as a structured todo. | `/gsd-capture` (default), `/gsd-capture --backlog` | +| `add-todo.md` | Capture an idea or task that surfaces during a session as a structured todo. | `/gsd-capture` (default) | | `ai-integration-phase.md` | Orchestrate framework selection → AI research → domain research → eval planning into AI-SPEC.md. | `/gsd-ai-integration-phase` | | `analyze-dependencies.md` | Analyze ROADMAP.md phases for file overlap and semantic dependencies; suggest `Depends on` edges. | `/gsd-manager --analyze-deps` | | `audit-fix.md` | Autonomous audit-to-fix pipeline — run audit, parse, classify, fix, test, commit. | `/gsd-audit-fix` | diff --git a/get-shit-done/workflows/add-backlog.md b/get-shit-done/workflows/add-backlog.md new file mode 100644 index 000000000..7ea896b29 --- /dev/null +++ b/get-shit-done/workflows/add-backlog.md @@ -0,0 +1,85 @@ +# Add Backlog Item Workflow + +Invoked by `/gsd-capture --backlog` (`commands/gsd/capture.md`). + +Adds an idea to the ROADMAP.md backlog parking lot using 999.x numbering. Backlog items +are unsequenced ideas that aren't ready for active planning — they live outside the normal +phase sequence and accumulate context over time. + + + +## Step 1: Read ROADMAP.md + +Check for existing backlog entries: + +```bash +cat .planning/ROADMAP.md +``` + +## Step 2: Find next backlog number + +```bash +NEXT=$(gsd-sdk query phase.next-decimal 999 --raw) +``` + +If no 999.x phases exist yet, `phase.next-decimal` returns `999.1`. Sparse numbering +is fine (e.g. 999.1, 999.3) — always use `phase.next-decimal`, never guess. + +## Step 3: Write ROADMAP entry + +**Write the ROADMAP entry BEFORE creating the directory.** Directory existence is a +reliable indicator that the phase is already registered, which prevents false duplicate +detection in any hook that checks for existing 999.x directories (#2280). + +Add under a `## Backlog` section. If the section doesn't exist, create it at the end +of ROADMAP.md: + +```markdown +## Backlog + +### Phase {NEXT}: {description} (BACKLOG) + +**Goal:** [Captured for future planning] +**Requirements:** TBD +**Plans:** 0 plans + +Plans: +- [ ] TBD (promote with /gsd-review-backlog when ready) +``` + +## Step 4: Create the phase directory + +```bash +SLUG=$(gsd-sdk query generate-slug "$ARGUMENTS" --raw) +mkdir -p ".planning/phases/${NEXT}-${SLUG}" +touch ".planning/phases/${NEXT}-${SLUG}/.gitkeep" +``` + +## Step 5: Commit + +```bash +gsd-sdk query commit "docs: add backlog item ${NEXT} — ${ARGUMENTS}" --files .planning/ROADMAP.md ".planning/phases/${NEXT}-${SLUG}/.gitkeep" +``` + +## Step 6: Report + +``` +## 📋 Backlog Item Added + +Phase {NEXT}: {description} +Directory: .planning/phases/{NEXT}-{slug}/ + +This item lives in the backlog parking lot. +Use /gsd-discuss-phase {NEXT} to explore it further. +Use /gsd-review-backlog to promote items to active milestone. +``` + + + + +- 999.x numbering keeps backlog items out of the active phase sequence +- Phase directories are created immediately so /gsd-discuss-phase and /gsd-plan-phase work on them +- No `Depends on:` field — backlog items are unsequenced by definition +- Sparse numbering is fine (999.1, 999.3) — always uses next-decimal +- Promote backlog items to the active milestone with /gsd-review-backlog + diff --git a/tests/bug-3135-capture-backlog-workflow.test.cjs b/tests/bug-3135-capture-backlog-workflow.test.cjs new file mode 100644 index 000000000..99daffc18 --- /dev/null +++ b/tests/bug-3135-capture-backlog-workflow.test.cjs @@ -0,0 +1,183 @@ +// allow-test-rule: source-text-is-the-product — workflow and command .md files +// ARE what the runtime loads; asserting their existence and behavioral content +// tests the deployed skill surface contract, not implementation internals. + +'use strict'; + +// Regression tests for bug #3135. +// +// PR #2824 consolidated add-backlog into `gsd-capture --backlog` by creating +// a routing wrapper in commands/gsd/capture.md that delegates to +// workflows/add-backlog.md via execution_context. The workflow file was never +// created. Same gap class as reapply-patches.md (found and fixed in the same PR). +// +// Fix: create get-shit-done/workflows/add-backlog.md with the full process +// ported from the deleted commands/gsd/add-backlog.md (git ref 87917131^). +// +// Also adds a broad regression: every @-reference in any commands/gsd/*.md +// execution_context block must resolve to an existing workflow file. + +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.join(__dirname, '..'); +const WORKFLOW = path.join(ROOT, 'get-shit-done', 'workflows', 'add-backlog.md'); +const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd'); +const WORKFLOWS_DIR = path.join(ROOT, 'get-shit-done', 'workflows'); + +// ─── #3135: add-backlog workflow ───────────────────────────────────────────── + +describe('#3135: get-shit-done/workflows/add-backlog.md', () => { + test('file exists', () => { + assert.ok( + fs.existsSync(WORKFLOW), + 'get-shit-done/workflows/add-backlog.md does not exist — capture --backlog has no implementation to load', + ); + }); + + test('uses gsd-sdk query phase.next-decimal to find next 999.x slot', () => { + const src = fs.readFileSync(WORKFLOW, 'utf8'); + assert.ok( + src.includes('phase.next-decimal'), + 'add-backlog.md must use gsd-sdk query phase.next-decimal to find the next 999.x number', + ); + }); + + test('writes to ROADMAP.md', () => { + const src = fs.readFileSync(WORKFLOW, 'utf8'); + assert.ok(src.includes('ROADMAP.md'), 'add-backlog.md must write to ROADMAP.md'); + }); + + test('creates a .planning/phases/ directory', () => { + const src = fs.readFileSync(WORKFLOW, 'utf8'); + assert.ok( + src.includes('.planning/phases') || src.includes('planning/phases'), + 'add-backlog.md must create a phase directory under .planning/phases/', + ); + }); + + test('uses generate-slug for the directory name', () => { + const src = fs.readFileSync(WORKFLOW, 'utf8'); + assert.ok( + src.includes('generate-slug'), + 'add-backlog.md must use gsd-sdk query generate-slug to build the phase directory slug', + ); + }); + + test('commits via gsd-sdk query commit', () => { + const src = fs.readFileSync(WORKFLOW, 'utf8'); + assert.ok( + src.includes('gsd-sdk query commit') || src.includes('query commit'), + 'add-backlog.md must commit via gsd-sdk query commit', + ); + }); + + test('writes ROADMAP entry before creating directory (#2280 ordering invariant)', () => { + const src = fs.readFileSync(WORKFLOW, 'utf8'); + const roadmapIdx = src.indexOf('ROADMAP.md'); + const mkdirIdx = src.search(/mkdir|\.gitkeep/); + assert.ok(roadmapIdx !== -1, 'ROADMAP.md write step not found'); + assert.ok(mkdirIdx !== -1, 'directory creation step not found'); + assert.ok( + roadmapIdx < mkdirIdx, + 'ROADMAP.md entry must be written BEFORE the phase directory is created (#2280 ordering invariant)', + ); + }); + + test('uses 999.x numbering for backlog items', () => { + const src = fs.readFileSync(WORKFLOW, 'utf8'); + assert.ok( + src.includes('999'), + 'add-backlog.md must document 999.x numbering scheme for backlog items', + ); + }); + + test('documents /gsd-review-backlog for promotion', () => { + const src = fs.readFileSync(WORKFLOW, 'utf8'); + assert.ok( + src.includes('review-backlog') || src.includes('gsd-review-backlog'), + 'add-backlog.md should mention /gsd-review-backlog for promoting items to active milestone', + ); + }); +}); + +// ─── capture.md routing integrity ──────────────────────────────────────────── + +describe('#3135: capture.md correctly routes --backlog to add-backlog workflow', () => { + function executionContextIncludes(body) { + const blocks = [ + ...body.matchAll(/([\s\S]*?)<\/execution_context(?:_extended)?>/g), + ].map((m) => m[1]); + const targets = []; + for (const blk of blocks) { + for (const line of blk.split('\n')) { + const t = line.trim(); + if (!t.startsWith('@')) continue; + const rel = t.replace(/^@~?\/?(?:\.claude\/)?(?:get-shit-done\/)?/, ''); + targets.push(rel); + } + } + return targets; + } + + test('capture.md execution_context @-includes add-backlog.md', () => { + const body = fs.readFileSync(path.join(COMMANDS_DIR, 'capture.md'), 'utf8'); + const targets = executionContextIncludes(body); + assert.ok( + targets.some((t) => /(^|\/)workflows\/add-backlog\.md$/.test(t)), + `capture.md execution_context must @-include workflows/add-backlog.md; got: ${JSON.stringify(targets)}`, + ); + }); +}); + +// ─── Broad regression: all execution_context @-refs must resolve ───────────── + +describe('regression: every execution_context @-reference in commands/gsd/*.md resolves to an existing workflow file', () => { + // Extract @-references from execution_context blocks, normalised to the + // get-shit-done/workflows/ relative tail so we can resolve them on disk. + function extractWorkflowRefs(filePath) { + const body = fs.readFileSync(filePath, 'utf8'); + const blocks = [ + ...body.matchAll(/([\s\S]*?)<\/execution_context(?:_extended)?>/g), + ].map((m) => m[1]); + const refs = []; + for (const blk of blocks) { + for (const line of blk.split('\n')) { + const t = line.trim(); + if (!t.startsWith('@')) continue; + // Only care about workflow references (skip non-workflow @-refs) + if (!t.includes('/workflows/')) continue; + // Normalise: drop everything up to and including 'get-shit-done/' + const match = t.match(/get-shit-done\/(workflows\/.+\.md)/); + if (match) refs.push(match[1]); + } + } + return refs; + } + + const commandFiles = fs + .readdirSync(COMMANDS_DIR) + .filter((f) => f.endsWith('.md')) + .map((f) => path.join(COMMANDS_DIR, f)); + + for (const cmdFile of commandFiles) { + const cmdName = path.basename(cmdFile); + let refs; + try { + refs = extractWorkflowRefs(cmdFile); + } catch { + continue; + } + for (const ref of refs) { + test(`${cmdName}: @-ref '${ref}' exists on disk`, () => { + const absPath = path.join(ROOT, 'get-shit-done', ref); + assert.ok( + fs.existsSync(absPath), + `${cmdName} references @${ref} in execution_context but get-shit-done/${ref} does not exist`, + ); + }); + } + } +});