specflow-cc 1.18.3 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/lib/todo.cjs CHANGED
@@ -231,8 +231,102 @@ function cmdTodoNextId(cwd, raw) {
231
231
  }, raw, nextId);
232
232
  }
233
233
 
234
+ /**
235
+ * Reindex: scan TODO-*.md files, regenerate INDEX.md.
236
+ *
237
+ * @param {string} cwd - Working directory
238
+ * @param {boolean} raw - Output raw string
239
+ */
240
+ function cmdTodoReindex(cwd, raw) {
241
+ const todosDir = path.join(cwd, '.specflow', 'todos');
242
+
243
+ // Collect per-file TODOs
244
+ let perFiles;
245
+ try {
246
+ perFiles = fs.readdirSync(todosDir).filter(f => /^TODO-\d+\.md$/.test(f)).sort();
247
+ } catch (e) {
248
+ perFiles = [];
249
+ }
250
+
251
+ const todos = [];
252
+
253
+ for (const file of perFiles) {
254
+ const content = safeReadFile(path.join(todosDir, file));
255
+ if (!content) continue;
256
+
257
+ const parsed = parseFrontmatter(content);
258
+ const fm = parsed.frontmatter;
259
+
260
+ // Strip surrounding quotes from title (YAML may preserve them)
261
+ let title = fm.title || '';
262
+ if ((title.startsWith('"') && title.endsWith('"')) || (title.startsWith("'") && title.endsWith("'"))) {
263
+ title = title.slice(1, -1);
264
+ }
265
+
266
+ todos.push({
267
+ id: fm.id || file.replace('.md', ''),
268
+ title,
269
+ priority: fm.priority || '—',
270
+ status: fm.status || 'open',
271
+ created: fm.created || '',
272
+ });
273
+ }
274
+
275
+ // Sort by priority (high > medium > low > unset), then by created date
276
+ todos.sort((a, b) => {
277
+ const pa = priorityKey(a.priority);
278
+ const pb = priorityKey(b.priority);
279
+ if (pa !== pb) return pa - pb;
280
+ if (a.created < b.created) return -1;
281
+ if (a.created > b.created) return 1;
282
+ return 0;
283
+ });
284
+
285
+ // Count by priority
286
+ const counts = { high: 0, medium: 0, low: 0, unset: 0 };
287
+ for (const t of todos) {
288
+ if (t.priority === 'high') counts.high++;
289
+ else if (t.priority === 'medium') counts.medium++;
290
+ else if (t.priority === 'low') counts.low++;
291
+ else counts.unset++;
292
+ }
293
+
294
+ // Build INDEX.md
295
+ const lines = [
296
+ '# To-Do Index',
297
+ '',
298
+ '> Auto-generated from individual TODO files. Do not edit manually.',
299
+ '> Regenerate with `/sf:todos`.',
300
+ '',
301
+ '| # | ID | Title | Priority | Status | Created |',
302
+ '|---|-----|-------|----------|--------|---------|',
303
+ ];
304
+
305
+ for (let i = 0; i < todos.length; i++) {
306
+ const t = todos[i];
307
+ let title = t.title;
308
+ if (title.length > 50) title = title.slice(0, 50) + '...';
309
+ lines.push(`| ${i + 1} | ${t.id} | ${title} | ${t.priority} | ${t.status} | ${t.created} |`);
310
+ }
311
+
312
+ lines.push('');
313
+ lines.push(`**Total:** ${todos.length} items (${counts.high} high, ${counts.medium} medium, ${counts.low} low, ${counts.unset} unset)`);
314
+ lines.push('');
315
+ lines.push('---');
316
+ const now = new Date();
317
+ const timestamp = now.toISOString().replace('T', ' ').slice(0, 16);
318
+ lines.push(`*Last regenerated: ${timestamp}*`);
319
+ lines.push('');
320
+
321
+ const indexPath = path.join(todosDir, 'INDEX.md');
322
+ fs.writeFileSync(indexPath, lines.join('\n'), 'utf8');
323
+
324
+ output({ reindexed: todos.length, path: indexPath }, raw, `Reindexed ${todos.length} TODOs → INDEX.md`);
325
+ }
326
+
234
327
  module.exports = {
235
328
  cmdTodoLoad,
236
329
  cmdTodoList,
237
330
  cmdTodoNextId,
331
+ cmdTodoReindex,
238
332
  };
package/bin/sf-tools.cjs CHANGED
@@ -12,9 +12,15 @@
12
12
  * todo load <id> Parse TODO file, return frontmatter + body
13
13
  * todo list [--all] List all TODOs sorted by priority
14
14
  * todo next-id Next available TODO-XXX number
15
+ * todo reindex Regenerate INDEX.md from TODO files
15
16
  * queue next First actionable spec from queue
16
- * state get Current active spec, status, next step
17
- * state set-active <id> <status> [next] Update active spec in STATE.md
17
+ * state get Current active spec, status, next step (legacy shim)
18
+ * state set-active <id> <status> [next] Update active spec in STATE.md (legacy shim)
19
+ * state list-active List all active specs from Active Specifications table
20
+ * state add-active <id> <status> <next> Append/update one row in Active Specifications table
21
+ * state remove-active <id> Remove one row from Active Specifications table
22
+ * state resolve [id] Resolve active spec; emit JSON contract
23
+ * state migrate One-shot idempotent migration to new schema
18
24
  * resolve-model <agent-type> Model for agent by current profile
19
25
  * verify-structure Check .specflow/ integrity
20
26
  * generate-slug <text> Text to URL-safe slug
@@ -23,9 +29,18 @@
23
29
  'use strict';
24
30
 
25
31
  const { output, error, generateSlug } = require('./lib/core.cjs');
26
- const { cmdStateGet, cmdStateSetActive, cmdQueueNext } = require('./lib/state.cjs');
32
+ const {
33
+ cmdStateGet,
34
+ cmdStateSetActive,
35
+ cmdStateListActive,
36
+ cmdStateAddActive,
37
+ cmdStateRemoveActive,
38
+ cmdStateResolve,
39
+ cmdStateMigrate,
40
+ cmdQueueNext,
41
+ } = require('./lib/state.cjs');
27
42
  const { cmdSpecLoad, cmdSpecList, cmdSpecNextId } = require('./lib/spec.cjs');
28
- const { cmdTodoLoad, cmdTodoList, cmdTodoNextId } = require('./lib/todo.cjs');
43
+ const { cmdTodoLoad, cmdTodoList, cmdTodoNextId, cmdTodoReindex } = require('./lib/todo.cjs');
29
44
  const { cmdResolveModel } = require('./lib/config.cjs');
30
45
  const { cmdVerifyStructure } = require('./lib/verify.cjs');
31
46
 
@@ -59,14 +74,44 @@ const COMMANDS = {
59
74
  },
60
75
  'todo list': () => cmdTodoList(cwd, raw, { showAll: flags.all ?? false }),
61
76
  'todo next-id': () => cmdTodoNextId(cwd, raw),
77
+ 'todo reindex': () => cmdTodoReindex(cwd, raw),
62
78
  'queue next': () => cmdQueueNext(cwd, raw),
79
+
80
+ // Legacy shims (backwards compatible)
63
81
  'state get': () => cmdStateGet(cwd, raw),
64
82
  'state set-active': () => {
65
83
  if (!filteredArgs[2] || !filteredArgs[3]) {
66
84
  error('Missing arguments. Usage: state set-active <id> <status> [next_step]');
67
85
  }
68
- cmdStateSetActive(cwd, filteredArgs[2], filteredArgs[3], filteredArgs[4], raw);
86
+ Promise.resolve(cmdStateSetActive(cwd, filteredArgs[2], filteredArgs[3], filteredArgs[4], raw))
87
+ .catch(e => error(e.message));
88
+ },
89
+
90
+ // New multi-spec state commands
91
+ 'state list-active': () => cmdStateListActive(cwd, raw),
92
+ 'state add-active': () => {
93
+ if (!filteredArgs[2] || !filteredArgs[3]) {
94
+ error('Missing arguments. Usage: state add-active <id> <status> <next_step>');
95
+ }
96
+ Promise.resolve(cmdStateAddActive(cwd, filteredArgs[2], filteredArgs[3], filteredArgs[4] || '', raw))
97
+ .catch(e => error(e.message));
69
98
  },
99
+ 'state remove-active': () => {
100
+ if (!filteredArgs[2]) {
101
+ error('Missing arguments. Usage: state remove-active <id>');
102
+ }
103
+ Promise.resolve(cmdStateRemoveActive(cwd, filteredArgs[2], raw))
104
+ .catch(e => error(e.message));
105
+ },
106
+ 'state resolve': () => {
107
+ // Optional specId argument (filteredArgs[2])
108
+ cmdStateResolve(cwd, filteredArgs[2] || undefined, raw);
109
+ },
110
+ 'state migrate': () => {
111
+ Promise.resolve(cmdStateMigrate(cwd, raw))
112
+ .catch(e => error(e.message));
113
+ },
114
+
70
115
  'resolve-model': () => {
71
116
  if (!filteredArgs[1]) error('Missing agent type. Usage: resolve-model <agent-type>');
72
117
  cmdResolveModel(cwd, filteredArgs[1], raw);
@@ -90,9 +135,15 @@ Commands:
90
135
  todo load <id> Parse TODO file, return frontmatter + body
91
136
  todo list [--all] List TODOs sorted by priority (--all includes eliminated)
92
137
  todo next-id Next available TODO-XXX number
138
+ todo reindex Regenerate INDEX.md from TODO files
93
139
  queue next First actionable spec from queue table
94
- state get Current active spec, status, next step
95
- state set-active <id> <status> [next] Update active spec, status, next step
140
+ state get Current active spec, status, next step (legacy shim)
141
+ state set-active <id> <status> [next] Update active spec, status, next step (legacy shim)
142
+ state list-active List all rows in Active Specifications table
143
+ state add-active <id> <status> <next> Append/update one row (under advisory lock)
144
+ state remove-active <id> Remove one row (under advisory lock)
145
+ state resolve [SPEC-ID] Resolve active spec; emit JSON contract
146
+ state migrate One-shot idempotent migration to new schema
96
147
  resolve-model <agent-type> Resolve model for agent by current profile
97
148
  verify-structure Check .specflow/ directory integrity
98
149
  generate-slug <text> Convert text to URL-safe slug
@@ -2,6 +2,7 @@
2
2
  name: sf:audit
3
3
  description: Audit the active specification in a fresh context
4
4
  argument-hint: "[SPEC-XXX] [--import \"feedback\"]"
5
+ # SPEC-011: Accepts optional SPEC-XXX as first positional arg; resolves via state resolve
5
6
  allowed-tools:
6
7
  - Read
7
8
  - Write
@@ -40,17 +41,27 @@ Run `/sf:init` first.
40
41
  ```
41
42
  Exit.
42
43
 
43
- ## Step 2: Get Active Specification
44
+ ## Step 2: Resolve Active Specification
44
45
 
45
- Read `.specflow/STATE.md` and extract Active Specification.
46
+ Call `node bin/sf-tools.cjs state resolve $ARGUMENTS` (pass the optional SPEC-XXX arg if provided).
46
47
 
47
- **If no active specification:**
48
- ```
49
- No active specification to audit.
48
+ Parse the JSON response:
49
+ - `{"action":"use","id":"SPEC-XXX"}` → proceed with SPEC-XXX
50
+ - `{"action":"error","code":"NO_ACTIVE_SPEC"}` display error and exit:
51
+ ```
52
+ No active specification to audit.
50
53
 
51
- Run `/sf:new "task description"` to create one.
52
- ```
53
- Exit.
54
+ Run `/sf:new "task description"` to create one.
55
+ ```
56
+ - `{"action":"error","code":"SPEC_NOT_ACTIVE","id":"SPEC-XXX"}` → display error and exit:
57
+ ```
58
+ SPEC-XXX is not in the Active Specifications table.
59
+ ```
60
+ - `{"action":"ask","options":[...]}` → use AskUserQuestion to show picker:
61
+ ```
62
+ Multiple active specifications. Which one to audit?
63
+ Options: {id — title (status)} for each entry
64
+ ```
54
65
 
55
66
  ## Step 3: Load Specification
56
67
 
@@ -143,10 +154,11 @@ In spec frontmatter, set: `status: revision_requested`
143
154
 
144
155
  ### 4.6 Update STATE.md
145
156
 
146
- Update STATE.md:
147
- - Status → "external_review"
148
- - Next Step "/sf:revise"
149
- - Add decision: "Imported external feedback for SPEC-XXX"
157
+ Update STATE.md via CLI:
158
+ ```bash
159
+ node bin/sf-tools.cjs state add-active SPEC-XXX external_review /sf:revise
160
+ ```
161
+ Add decision: "Imported external feedback for SPEC-XXX"
150
162
 
151
163
  ### 4.7 Display Import Result
152
164
 
@@ -2,6 +2,7 @@
2
2
  name: sf:autopilot
3
3
  description: Run full spec lifecycle autonomously (audit -> run -> review -> done)
4
4
  argument-hint: "[SPEC-XXX] [--all]"
5
+ # SPEC-011: Accepts optional SPEC-XXX; resolves via state resolve; FAILS when N>1 and no ID (no picker)
5
6
  allowed-tools:
6
7
  - Read
7
8
  - Write
@@ -43,25 +44,42 @@ Parse the command argument to determine execution mode:
43
44
 
44
45
  | Argument | Mode | Behavior |
45
46
  |----------|------|----------|
46
- | (none) | single | Process the active spec in STATE.md |
47
- | `SPEC-XXX` | single | Set SPEC-XXX as active, then process it |
48
- | `--all` | batch | Process all actionable specs in Queue order |
47
+ | (none) | single | Resolve active spec; fail fast if N>1 (no picker) |
48
+ | `SPEC-XXX` | single | Process this explicit spec ID |
49
+ | `--all` | batch | Process all actionable specs in Queue order; still requires explicit SPEC-ID if N>1 |
49
50
 
50
- **If single mode:**
51
- - If SPEC-XXX argument provided: update STATE.md to set it as active spec
52
- - If no argument and no active spec exists: display error and exit
51
+ **CRITICAL N>1 guard (autopilot must be unambiguous):**
53
52
 
54
- **If batch mode (--all):**
55
- - Identify all actionable specs from Queue (any spec with status: draft, auditing, revision_requested, audited, running, review)
53
+ Call `node bin/sf-tools.cjs state resolve $SPEC_ID_ARG` (pass SPEC-ID arg if provided; omit if not).
56
54
 
57
- **Error case (single mode, no active, no argument):**
58
- ```
59
- No active specification to process.
55
+ Parse the JSON response:
56
+ - `{"action":"use","id":"SPEC-XXX"}` → proceed with SPEC-XXX
57
+ - `{"action":"error","code":"NO_ACTIVE_SPEC"}` display error and exit:
58
+ ```
59
+ No active specification to process.
60
60
 
61
- Provide a spec ID: `/sf:autopilot SPEC-XXX`
62
- Or run on all specs: `/sf:autopilot --all`
63
- ```
64
- Exit.
61
+ Provide a spec ID: `/sf:autopilot SPEC-XXX`
62
+ Or run on all specs: `/sf:autopilot --all`
63
+ ```
64
+ - `{"action":"error","code":"SPEC_NOT_ACTIVE","id":"SPEC-XXX"}` → display error and exit:
65
+ ```
66
+ SPEC-XXX is not in the Active Specifications table.
67
+ ```
68
+ - `{"action":"ask","options":[...]}` → **FAIL FAST** (do NOT show picker):
69
+ ```
70
+ Autopilot requires explicit SPEC-ID when >1 active specs.
71
+
72
+ Active specs: {list SPEC-IDs from options}
73
+
74
+ Provide a spec ID: `/sf:autopilot SPEC-XXX`
75
+ ```
76
+ Exit. (This applies to both plain `/sf:autopilot` AND `/sf:autopilot --all` without a SPEC-ID. `--all` controls within-spec behavior, not multi-spec iteration.)
77
+
78
+ **If single mode with explicit SPEC-ID:**
79
+ - SPEC-ID was resolved via state resolve above (action:use)
80
+
81
+ **If batch mode (--all):**
82
+ - Identify all actionable specs from Queue (any spec with status: draft, auditing, revision_requested, audited, running, review)
65
83
 
66
84
  ## Step 3: Set Configuration Constants
67
85
 
@@ -107,8 +125,7 @@ current_spec = null
107
125
 
108
126
  ### 5.1 Load Current Spec State
109
127
 
110
- Read `.specflow/STATE.md` to get active specification.
111
- Read `.specflow/specs/SPEC-XXX.md` to get frontmatter status.
128
+ Read `.specflow/specs/SPEC-XXX.md` to get frontmatter status (spec ID resolved in Step 2).
112
129
 
113
130
  ### 5.2 Determine Current Phase
114
131
 
@@ -255,10 +272,11 @@ mv .specflow/specs/SPEC-XXX.md .specflow/archive/
255
272
  ```
256
273
 
257
274
  5. **Update STATE.md:**
258
- - Active Specification → "none"
259
- - Status → "idle"
260
- - Next Step → "/sf:new or /sf:next"
261
- - Remove SPEC-XXX row from Queue table
275
+ Remove SPEC-XXX from Active Specifications table:
276
+ ```bash
277
+ node bin/sf-tools.cjs state remove-active SPEC-XXX
278
+ ```
279
+ Remove SPEC-XXX row from Queue table (using Read+Write).
262
280
 
263
281
  6. **Check STATE.md size and rotate** (same logic as 5.5 step 2)
264
282
 
@@ -62,9 +62,10 @@ Determine discussion mode from arguments:
62
62
  - Mode: `requirements-gathering`
63
63
 
64
64
  **Case E: No arguments**
65
- - Check STATE.md for active spec
66
- - If active spec exists: discuss that spec
67
- - If no active spec: ask what to discuss
65
+ - Call `node bin/sf-tools.cjs state resolve` to resolve active spec
66
+ - `{"action":"use","id":"SPEC-XXX"}` discuss that spec
67
+ - `{"action":"error","code":"NO_ACTIVE_SPEC"}` ask what to discuss
68
+ - `{"action":"ask","options":[...]}` → use AskUserQuestion to pick which spec to discuss
68
69
 
69
70
  ## 3. Load Context
70
71
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: sf:done
3
3
  description: Finalize specification, archive, and update state
4
+ # SPEC-011: Accepts optional SPEC-XXX as first positional arg; resolves via state resolve
4
5
  allowed-tools:
5
6
  - Read
6
7
  - Write
@@ -36,17 +37,27 @@ Run `/sf:init` first.
36
37
  ```
37
38
  Exit.
38
39
 
39
- ## Step 2: Get Active Specification
40
+ ## Step 2: Resolve Active Specification
40
41
 
41
- Read `.specflow/STATE.md` and extract Active Specification.
42
+ Call `node bin/sf-tools.cjs state resolve $ARGUMENTS` (pass the optional SPEC-XXX arg if provided).
42
43
 
43
- **If no active specification:**
44
- ```
45
- No active specification to finalize.
44
+ Parse the JSON response:
45
+ - `{"action":"use","id":"SPEC-XXX"}` → proceed with SPEC-XXX
46
+ - `{"action":"error","code":"NO_ACTIVE_SPEC"}` display error and exit:
47
+ ```
48
+ No active specification to finalize.
46
49
 
47
- Run `/sf:new "task description"` to create one.
48
- ```
49
- Exit.
50
+ Run `/sf:new "task description"` to create one.
51
+ ```
52
+ - `{"action":"error","code":"SPEC_NOT_ACTIVE","id":"SPEC-XXX"}` → display error and exit:
53
+ ```
54
+ SPEC-XXX is not in the Active Specifications table.
55
+ ```
56
+ - `{"action":"ask","options":[...]}` → use AskUserQuestion to show picker:
57
+ ```
58
+ Multiple active specifications. Which one to finalize?
59
+ Options: {id — title (status)} for each entry
60
+ ```
50
61
 
51
62
  ## Step 3: Load Specification
52
63
 
@@ -283,12 +294,16 @@ Check if the spec frontmatter contains a `source:` field (e.g., `source: TODO-00
283
294
  [ -f .specflow/todos/{source}.md ] && echo "FOUND" || echo "NOT_FOUND"
284
295
  ```
285
296
 
286
- 2. **If FOUND:** Delete the file:
297
+ 2. **If FOUND:** Delete the file and reindex:
287
298
 
288
299
  ```bash
289
300
  rm .specflow/todos/{source}.md
290
301
  ```
291
302
 
303
+ ```bash
304
+ node ~/.claude/specflow-cc/bin/sf-tools.cjs todo reindex
305
+ ```
306
+
292
307
  3. **If NOT_FOUND (backward compatibility):** Also check legacy format — look in `.specflow/todos/TODO.md` for the referenced ID. If found there, remove the block using the Edit tool.
293
308
 
294
309
  No "Last updated" lines to update in per-file format.
@@ -305,11 +320,11 @@ mv .specflow/specs/SPEC-XXX.md .specflow/archive/
305
320
 
306
321
  ## Step 9: Update STATE.md
307
322
 
308
- ### Clear Active Specification
323
+ ### Remove from Active Specifications Table
309
324
 
310
- - Active Specification → "none"
311
- - Status "idle"
312
- - Next Step → "/sf:new or /sf:next"
325
+ ```bash
326
+ node bin/sf-tools.cjs state remove-active SPEC-XXX
327
+ ```
313
328
 
314
329
  ### Remove from Queue
315
330
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: sf:fix
3
3
  description: Fix implementation based on review feedback
4
+ # SPEC-011: Accepts optional SPEC-XXX as first positional arg; resolves via state resolve
4
5
  allowed-tools:
5
6
  - Read
6
7
  - Write
@@ -38,17 +39,27 @@ Run `/sf:init` first.
38
39
  ```
39
40
  Exit.
40
41
 
41
- ## Step 2: Get Active Specification
42
+ ## Step 2: Resolve Active Specification
42
43
 
43
- Read `.specflow/STATE.md` and extract Active Specification.
44
+ Call `node bin/sf-tools.cjs state resolve $ARGUMENTS` (pass the optional SPEC-XXX arg if provided).
44
45
 
45
- **If no active specification:**
46
- ```
47
- No active specification to fix.
46
+ Parse the JSON response:
47
+ - `{"action":"use","id":"SPEC-XXX"}` → proceed with SPEC-XXX
48
+ - `{"action":"error","code":"NO_ACTIVE_SPEC"}` display error and exit:
49
+ ```
50
+ No active specification to fix.
48
51
 
49
- Run `/sf:new "task description"` to create one.
50
- ```
51
- Exit.
52
+ Run `/sf:new "task description"` to create one.
53
+ ```
54
+ - `{"action":"error","code":"SPEC_NOT_ACTIVE","id":"SPEC-XXX"}` → display error and exit:
55
+ ```
56
+ SPEC-XXX is not in the Active Specifications table.
57
+ ```
58
+ - `{"action":"ask","options":[...]}` → use AskUserQuestion to show picker:
59
+ ```
60
+ Multiple active specifications. Which one to fix?
61
+ Options: {id — title (status)} for each entry
62
+ ```
52
63
 
53
64
  ## Step 3: Load Specification
54
65
 
@@ -164,8 +175,9 @@ Append to Review History:
164
175
 
165
176
  ## Step 8: Update STATE.md
166
177
 
167
- - Status → "review" (ready for re-review)
168
- - Next Step "/sf:review"
178
+ ```bash
179
+ node bin/sf-tools.cjs state add-active SPEC-XXX review /sf:review
180
+ ```
169
181
 
170
182
  ## Step 9: Display Result
171
183
 
@@ -2,6 +2,7 @@
2
2
  name: sf:health
3
3
  description: Diagnose .specflow/ directory health and optionally repair issues
4
4
  argument-hint: [--repair]
5
+ # SPEC-011: Invokes migrate-state on entry; re-stamps STATE.md header from template after migration (--repair path)
5
6
  allowed-tools:
6
7
  - Read
7
8
  - Bash
@@ -32,7 +33,19 @@ SpecFlow not initialized. Run `/sf:init` first.
32
33
  ```
33
34
  Exit.
34
35
 
35
- ## Step 2: Parse Arguments
36
+ ## Step 2: Invoke STATE.md Migration (entry point)
37
+
38
+ Run migration on entry — idempotent, no-op when already migrated:
39
+
40
+ ```bash
41
+ node bin/sf-tools.cjs state migrate
42
+ ```
43
+
44
+ Parse the response:
45
+ - `{"migrated":true,...}` → Migration completed. After migration, re-stamp the `## Active Specifications` header block in STATE.md from `templates/state.md` to reconcile any format drift (non-destructive: Queue, Decisions, Notes sections are preserved; only the Active Specifications block is normalized). This is the `--repair` path.
46
+ - `{"migrated":false,...}` → Already migrated, no action needed.
47
+
48
+ ## Step 2.5: Parse Arguments
36
49
 
37
50
  Check if `--repair` flag is present.
38
51
 
@@ -71,9 +84,9 @@ For each check:
71
84
  Read STATE.md and validate:
72
85
 
73
86
  **E003: Active spec references non-existent file**
74
- - Extract active spec ID from `## Active Specification`
75
- - If not "—" or empty, check `.specflow/specs/{ID}.md` exists
76
- - If missing: error (repairable — clear active spec to "—")
87
+ - List all active specs via `node bin/sf-tools.cjs state list-active`
88
+ - For each SPEC-ID, check `.specflow/specs/{ID}.md` exists
89
+ - If missing: error (repairable — remove that row via `state remove-active`)
77
90
 
78
91
  **W005: Queue references non-existent spec file**
79
92
  - Parse queue table rows
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: sf:help
3
3
  description: Show SpecFlow help and command reference
4
+ # SPEC-011: Uses state list-active to show active specs in overview; no state resolve needed (no single-spec operations)
4
5
  allowed-tools:
5
6
  - Read
6
7
  - Glob
@@ -203,6 +204,11 @@ Exit.
203
204
 
204
205
  ## Step 2b: Overview Help
205
206
 
207
+ Show active specs count (uses list-active for multi-spec awareness):
208
+ ```bash
209
+ node bin/sf-tools.cjs state list-active --raw
210
+ ```
211
+
206
212
  Display full command reference:
207
213
 
208
214
  ```
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: sf:pause
3
3
  description: Save current work context for later resumption
4
+ # SPEC-011: Accepts optional SPEC-XXX as first positional arg; resolves via state resolve
4
5
  allowed-tools:
5
6
  - Read
6
7
  - Write
@@ -33,20 +34,26 @@ Run `/sf:init` to start.
33
34
  ```
34
35
  Exit.
35
36
 
36
- ## Step 2: Get Current State
37
+ ## Step 2: Resolve Active Specification
37
38
 
38
- Read `.specflow/STATE.md` and extract:
39
- - Active Specification
40
- - Status
41
- - Next Step
39
+ Call `node bin/sf-tools.cjs state resolve $ARGUMENTS` (pass the optional SPEC-XXX arg if provided).
42
40
 
43
- **If no active specification:**
44
- ```
45
- No active work to pause.
41
+ Parse the JSON response:
42
+ - `{"action":"use","id":"SPEC-XXX"}` → proceed with SPEC-XXX
43
+ - `{"action":"error","code":"NO_ACTIVE_SPEC"}` display info and allow general notes:
44
+ ```
45
+ No active work to pause.
46
46
 
47
- Current state: idle
48
- ```
49
- But still allow pausing to capture general notes.
47
+ Current state: idle
48
+ ```
49
+ Still allow pausing to capture general notes.
50
+ - `{"action":"ask","options":[...]}` → use AskUserQuestion to show picker:
51
+ ```
52
+ Multiple active specifications. Which session to pause?
53
+ Options: {id — title (status)} for each entry
54
+ ```
55
+
56
+ Also call `node bin/sf-tools.cjs state list-active` to capture all active specs in the pause file.
50
57
 
51
58
  ## Step 3: Load Active Specification Details
52
59
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: sf:review
3
3
  description: Review the implementation against specification
4
+ # SPEC-011: Accepts optional SPEC-XXX as first positional arg; resolves via state resolve
4
5
  allowed-tools:
5
6
  - Read
6
7
  - Write
@@ -36,17 +37,27 @@ Run `/sf:init` first.
36
37
  ```
37
38
  Exit.
38
39
 
39
- ## Step 2: Get Active Specification
40
+ ## Step 2: Resolve Active Specification
40
41
 
41
- Read `.specflow/STATE.md` and extract Active Specification.
42
+ Call `node bin/sf-tools.cjs state resolve $ARGUMENTS` (pass the optional SPEC-XXX arg if provided).
42
43
 
43
- **If no active specification:**
44
- ```
45
- No active specification to review.
44
+ Parse the JSON response:
45
+ - `{"action":"use","id":"SPEC-XXX"}` → proceed with SPEC-XXX
46
+ - `{"action":"error","code":"NO_ACTIVE_SPEC"}` display error and exit:
47
+ ```
48
+ No active specification to review.
46
49
 
47
- Run `/sf:new "task description"` to create one.
48
- ```
49
- Exit.
50
+ Run `/sf:new "task description"` to create one.
51
+ ```
52
+ - `{"action":"error","code":"SPEC_NOT_ACTIVE","id":"SPEC-XXX"}` → display error and exit:
53
+ ```
54
+ SPEC-XXX is not in the Active Specifications table.
55
+ ```
56
+ - `{"action":"ask","options":[...]}` → use AskUserQuestion to show picker:
57
+ ```
58
+ Multiple active specifications. Which one to review?
59
+ Options: {id — title (status)} for each entry
60
+ ```
50
61
 
51
62
  ## Step 3: Load Specification
52
63
 
@@ -319,8 +330,12 @@ Append Review History to spec.
319
330
 
320
331
  ### Update STATE.md
321
332
 
322
- - If APPROVED: Status → "done", Next Step → "/sf:done"
323
- - If CHANGES_REQUESTED: Status → "review", Next Step → "/sf:fix"
333
+ ```bash
334
+ # If APPROVED:
335
+ node bin/sf-tools.cjs state add-active SPEC-XXX done /sf:done
336
+ # If CHANGES_REQUESTED:
337
+ node bin/sf-tools.cjs state add-active SPEC-XXX review /sf:fix
338
+ ```
324
339
 
325
340
  </fallback>
326
341