* 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
This commit is contained in:
5
.changeset/fix-3135-capture-backlog-workflow.md
Normal file
5
.changeset/fix-3135-capture-backlog-workflow.md
Normal file
@@ -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.
|
||||
@@ -104,6 +104,7 @@
|
||||
"/gsd-workstreams"
|
||||
],
|
||||
"workflows": [
|
||||
"add-backlog.md",
|
||||
"add-phase.md",
|
||||
"add-tests.md",
|
||||
"add-todo.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 `<purpose>` 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` |
|
||||
|
||||
85
get-shit-done/workflows/add-backlog.md
Normal file
85
get-shit-done/workflows/add-backlog.md
Normal file
@@ -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.
|
||||
|
||||
<process>
|
||||
|
||||
## 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.
|
||||
```
|
||||
|
||||
</process>
|
||||
|
||||
<notes>
|
||||
- 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
|
||||
</notes>
|
||||
183
tests/bug-3135-capture-backlog-workflow.test.cjs
Normal file
183
tests/bug-3135-capture-backlog-workflow.test.cjs
Normal file
@@ -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(/<execution_context(?:_extended)?>([\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(/<execution_context(?:_extended)?>([\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`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user