specflow-cc 1.19.0 → 1.20.1

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
@@ -295,8 +295,10 @@ function cmdTodoReindex(cwd, raw) {
295
295
  const lines = [
296
296
  '# To-Do Index',
297
297
  '',
298
- '> Auto-generated from individual TODO files. Do not edit manually.',
299
- '> Regenerate with `/sf:todos`.',
298
+ '> Cache of individual TODO files. Refreshed when `/sf:todos` runs OR when an',
299
+ '> INDEX-mutating command explicitly invokes the regen helper',
300
+ '> (`node bin/sf-tools.cjs todo reindex`). Do not edit manually — changes will',
301
+ '> be overwritten on the next regen.',
300
302
  '',
301
303
  '| # | ID | Title | Priority | Status | Created |',
302
304
  '|---|-----|-------|----------|--------|---------|',
@@ -324,9 +326,79 @@ function cmdTodoReindex(cwd, raw) {
324
326
  output({ reindexed: todos.length, path: indexPath }, raw, `Reindexed ${todos.length} TODOs → INDEX.md`);
325
327
  }
326
328
 
329
+ /**
330
+ * Check whether INDEX.md is stale relative to TODO-*.md files.
331
+ *
332
+ * Stale = the set of TODO-XXX IDs on disk diverges from the set of TODO-XXX IDs
333
+ * listed in INDEX.md (file deleted but still in INDEX, or file present but missing).
334
+ *
335
+ * NOTE: Eliminated TODOs (`status: eliminated`) still appear in `/sf:todos --all`
336
+ * regenerated INDEX.md output, so they are NOT filtered here — both sides see them.
337
+ *
338
+ * Output JSON: { stale, missing_from_index, extra_in_index, index_exists }
339
+ * - missing_from_index: file exists on disk but not in INDEX.md
340
+ * - extra_in_index: ID listed in INDEX.md but no file on disk
341
+ *
342
+ * @param {string} cwd - Working directory
343
+ * @param {boolean} raw - Output mode
344
+ */
345
+ function cmdTodoCheckStale(cwd, raw) {
346
+ const todosDir = path.join(cwd, '.specflow', 'todos');
347
+ const indexPath = path.join(todosDir, 'INDEX.md');
348
+
349
+ // Collect IDs from disk
350
+ const diskIds = new Set();
351
+ try {
352
+ for (const f of fs.readdirSync(todosDir)) {
353
+ const m = f.match(/^(TODO-\d+)\.md$/);
354
+ if (m) diskIds.add(m[1]);
355
+ }
356
+ } catch (e) {
357
+ // todos dir missing — treat as empty
358
+ }
359
+
360
+ // Collect IDs referenced in INDEX.md (parse only the table rows)
361
+ const indexIds = new Set();
362
+ const indexContent = safeReadFile(indexPath);
363
+ const indexExists = indexContent !== null;
364
+
365
+ if (indexContent) {
366
+ // Match TODO-XXX in pipe-table cells: "| N | TODO-001 | ..."
367
+ const regex = /\|\s*\d+\s*\|\s*(TODO-\d+)\s*\|/g;
368
+ let m;
369
+ while ((m = regex.exec(indexContent)) !== null) {
370
+ indexIds.add(m[1]);
371
+ }
372
+ }
373
+
374
+ const missingFromIndex = [...diskIds].filter(id => !indexIds.has(id)).sort();
375
+ const extraInIndex = [...indexIds].filter(id => !diskIds.has(id)).sort();
376
+
377
+ // If INDEX.md does not exist but there are TODO files, INDEX is stale.
378
+ // If INDEX.md does not exist and no TODO files, not stale (nothing to track).
379
+ const stale =
380
+ missingFromIndex.length > 0 ||
381
+ extraInIndex.length > 0 ||
382
+ (!indexExists && diskIds.size > 0);
383
+
384
+ output(
385
+ {
386
+ stale,
387
+ index_exists: indexExists,
388
+ todo_count: diskIds.size,
389
+ index_count: indexIds.size,
390
+ missing_from_index: missingFromIndex,
391
+ extra_in_index: extraInIndex,
392
+ },
393
+ raw,
394
+ stale ? 'STALE' : 'FRESH'
395
+ );
396
+ }
397
+
327
398
  module.exports = {
328
399
  cmdTodoLoad,
329
400
  cmdTodoList,
330
401
  cmdTodoNextId,
331
402
  cmdTodoReindex,
403
+ cmdTodoCheckStale,
332
404
  };
package/bin/sf-tools.cjs CHANGED
@@ -13,9 +13,15 @@
13
13
  * todo list [--all] List all TODOs sorted by priority
14
14
  * todo next-id Next available TODO-XXX number
15
15
  * todo reindex Regenerate INDEX.md from TODO files
16
+ * todo check-stale Report drift between TODO-*.md and INDEX.md
16
17
  * queue next First actionable spec from queue
17
- * state get Current active spec, status, next step
18
- * state set-active <id> <status> [next] Update active spec in STATE.md
18
+ * state get Current active spec, status, next step (legacy shim)
19
+ * state set-active <id> <status> [next] Update active spec in STATE.md (legacy shim)
20
+ * state list-active List all active specs from Active Specifications table
21
+ * state add-active <id> <status> <next> Append/update one row in Active Specifications table
22
+ * state remove-active <id> Remove one row from Active Specifications table
23
+ * state resolve [id] Resolve active spec; emit JSON contract
24
+ * state migrate One-shot idempotent migration to new schema
19
25
  * resolve-model <agent-type> Model for agent by current profile
20
26
  * verify-structure Check .specflow/ integrity
21
27
  * generate-slug <text> Text to URL-safe slug
@@ -24,9 +30,18 @@
24
30
  'use strict';
25
31
 
26
32
  const { output, error, generateSlug } = require('./lib/core.cjs');
27
- const { cmdStateGet, cmdStateSetActive, cmdQueueNext } = require('./lib/state.cjs');
33
+ const {
34
+ cmdStateGet,
35
+ cmdStateSetActive,
36
+ cmdStateListActive,
37
+ cmdStateAddActive,
38
+ cmdStateRemoveActive,
39
+ cmdStateResolve,
40
+ cmdStateMigrate,
41
+ cmdQueueNext,
42
+ } = require('./lib/state.cjs');
28
43
  const { cmdSpecLoad, cmdSpecList, cmdSpecNextId } = require('./lib/spec.cjs');
29
- const { cmdTodoLoad, cmdTodoList, cmdTodoNextId, cmdTodoReindex } = require('./lib/todo.cjs');
44
+ const { cmdTodoLoad, cmdTodoList, cmdTodoNextId, cmdTodoReindex, cmdTodoCheckStale } = require('./lib/todo.cjs');
30
45
  const { cmdResolveModel } = require('./lib/config.cjs');
31
46
  const { cmdVerifyStructure } = require('./lib/verify.cjs');
32
47
 
@@ -61,14 +76,44 @@ const COMMANDS = {
61
76
  'todo list': () => cmdTodoList(cwd, raw, { showAll: flags.all ?? false }),
62
77
  'todo next-id': () => cmdTodoNextId(cwd, raw),
63
78
  'todo reindex': () => cmdTodoReindex(cwd, raw),
79
+ 'todo check-stale': () => cmdTodoCheckStale(cwd, raw),
64
80
  'queue next': () => cmdQueueNext(cwd, raw),
81
+
82
+ // Legacy shims (backwards compatible)
65
83
  'state get': () => cmdStateGet(cwd, raw),
66
84
  'state set-active': () => {
67
85
  if (!filteredArgs[2] || !filteredArgs[3]) {
68
86
  error('Missing arguments. Usage: state set-active <id> <status> [next_step]');
69
87
  }
70
- cmdStateSetActive(cwd, filteredArgs[2], filteredArgs[3], filteredArgs[4], raw);
88
+ Promise.resolve(cmdStateSetActive(cwd, filteredArgs[2], filteredArgs[3], filteredArgs[4], raw))
89
+ .catch(e => error(e.message));
90
+ },
91
+
92
+ // New multi-spec state commands
93
+ 'state list-active': () => cmdStateListActive(cwd, raw),
94
+ 'state add-active': () => {
95
+ if (!filteredArgs[2] || !filteredArgs[3]) {
96
+ error('Missing arguments. Usage: state add-active <id> <status> <next_step>');
97
+ }
98
+ Promise.resolve(cmdStateAddActive(cwd, filteredArgs[2], filteredArgs[3], filteredArgs[4] || '', raw))
99
+ .catch(e => error(e.message));
71
100
  },
101
+ 'state remove-active': () => {
102
+ if (!filteredArgs[2]) {
103
+ error('Missing arguments. Usage: state remove-active <id>');
104
+ }
105
+ Promise.resolve(cmdStateRemoveActive(cwd, filteredArgs[2], raw))
106
+ .catch(e => error(e.message));
107
+ },
108
+ 'state resolve': () => {
109
+ // Optional specId argument (filteredArgs[2])
110
+ cmdStateResolve(cwd, filteredArgs[2] || undefined, raw);
111
+ },
112
+ 'state migrate': () => {
113
+ Promise.resolve(cmdStateMigrate(cwd, raw))
114
+ .catch(e => error(e.message));
115
+ },
116
+
72
117
  'resolve-model': () => {
73
118
  if (!filteredArgs[1]) error('Missing agent type. Usage: resolve-model <agent-type>');
74
119
  cmdResolveModel(cwd, filteredArgs[1], raw);
@@ -93,9 +138,15 @@ Commands:
93
138
  todo list [--all] List TODOs sorted by priority (--all includes eliminated)
94
139
  todo next-id Next available TODO-XXX number
95
140
  todo reindex Regenerate INDEX.md from TODO files
141
+ todo check-stale Report drift between TODO-*.md and INDEX.md
96
142
  queue next First actionable spec from queue table
97
- state get Current active spec, status, next step
98
- state set-active <id> <status> [next] Update active spec, status, next step
143
+ state get Current active spec, status, next step (legacy shim)
144
+ state set-active <id> <status> [next] Update active spec, status, next step (legacy shim)
145
+ state list-active List all rows in Active Specifications table
146
+ state add-active <id> <status> <next> Append/update one row (under advisory lock)
147
+ state remove-active <id> Remove one row (under advisory lock)
148
+ state resolve [SPEC-ID] Resolve active spec; emit JSON contract
149
+ state migrate One-shot idempotent migration to new schema
99
150
  resolve-model <agent-type> Resolve model for agent by current profile
100
151
  verify-structure Check .specflow/ directory integrity
101
152
  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
 
@@ -309,11 +320,11 @@ mv .specflow/specs/SPEC-XXX.md .specflow/archive/
309
320
 
310
321
  ## Step 9: Update STATE.md
311
322
 
312
- ### Clear Active Specification
323
+ ### Remove from Active Specifications Table
313
324
 
314
- - Active Specification → "none"
315
- - Status "idle"
316
- - Next Step → "/sf:new or /sf:next"
325
+ ```bash
326
+ node bin/sf-tools.cjs state remove-active SPEC-XXX
327
+ ```
317
328
 
318
329
  ### Remove from Queue
319
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
  ```
@@ -13,7 +13,7 @@ Migrate an existing monolithic `.specflow/todos/TODO.md` to the new per-file for
13
13
  This is a one-time migration command. After migration:
14
14
  - `TODO.md` is renamed to `TODO.md.bak` (NOT deleted — safety net)
15
15
  - Each TODO becomes its own `TODO-XXX.md` file
16
- - `INDEX.md` is generated from the new files
16
+ - `INDEX.md` is regenerated from the new files via the shared `todo reindex` helper
17
17
  - All other commands will use the new per-file format automatically
18
18
 
19
19
  Use `--dry-run` to preview the migration without writing any files.
@@ -173,24 +173,14 @@ created: {YYYY-MM-DD}
173
173
 
174
174
  ## Step 7: Generate INDEX.md
175
175
 
176
- Write `.specflow/todos/INDEX.md` with all migrated TODOs, sorted by priority then date:
176
+ Invoke the shared regen helper to build `.specflow/todos/INDEX.md` from the migrated files:
177
177
 
178
- ```markdown
179
- # To-Do Index
180
-
181
- > Auto-generated from individual TODO files. Do not edit manually.
182
- > Regenerate with `/sf:todos`.
183
-
184
- | # | ID | Title | Priority | Status | Created |
185
- |---|-----|-------|----------|--------|---------|
186
- {one row per TODO, sorted by priority then created date}
187
-
188
- **Total:** {N} items ({high} high, {medium} medium, {low} low, {unset} unset)
189
-
190
- ---
191
- *Last regenerated: {YYYY-MM-DD HH:MM}*
178
+ ```bash
179
+ node ~/.claude/specflow-cc/bin/sf-tools.cjs todo reindex
192
180
  ```
193
181
 
182
+ Do NOT write INDEX.md inline — the helper is the single source of truth for its layout (see `templates/todo-index.md`).
183
+
194
184
  ## Step 8: Rename Legacy TODO.md
195
185
 
196
186
  ```bash
@@ -245,7 +235,7 @@ Migrated {N} TODOs from TODO.md to per-file format.
245
235
  - [ ] Individual TODO-XXX.md files created for each block
246
236
  - [ ] Each file has valid YAML frontmatter (id, title, priority, status, created)
247
237
  - [ ] Title derived from description (first sentence, ~80 chars)
248
- - [ ] INDEX.md generated from migrated files
238
+ - [ ] INDEX.md regenerated via `node ~/.claude/specflow-cc/bin/sf-tools.cjs todo reindex`
249
239
  - [ ] TODO.md renamed to TODO.md.bak (NOT deleted)
250
240
  - [ ] Clear migration summary shown
251
241
  - [ ] Cleanup instructions provided
@@ -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