specflow-cc 1.18.2 → 1.19.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/CHANGELOG.md +17 -0
- package/agents/spec-auditor.md +50 -0
- package/bin/install.js +26 -0
- package/bin/lib/todo.cjs +94 -0
- package/bin/sf-tools.cjs +4 -1
- package/commands/sf/done.md +5 -1
- package/commands/sf/todo.md +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to SpecFlow will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.19.0] - 2026-04-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Deferred work detection in auditor** — the auditor now detects deferred work items (TODOs, FIXMEs, placeholder implementations) in the spec and flags them as findings (step 3.9.5)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **TODO INDEX.md auto-sync** — `INDEX.md` in `.specflow/todos/` was getting out of sync because `/sf:todo` and `/sf:done` modified TODO files without regenerating the index. Added `todo reindex` command to `sf-tools.cjs` and wired it into both `/sf:todo` (Step 6.5) and `/sf:done` (Step 7.5, after TODO file deletion)
|
|
17
|
+
|
|
18
|
+
## [1.18.3] - 2026-04-11
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Auto-heal for users upgrading from 1.18.0 / 1.18.1** — the installer now scans `hooks.PostToolUse` during every install and removes any flat `{ type, command }` entry that references `context-monitor`. These entries were written by the buggy `1.18.0`/`1.18.1` installer and would otherwise linger in `settings.json` next to the newly written correct matcher group, still tripping Claude Code's `"Expected array, but received undefined"` parse error. Foreign flat entries (belonging to other tools) are left untouched — only entries unambiguously written by prior SpecFlow versions are removed.
|
|
23
|
+
- Three new smoke tests cover clean auto-heal, preservation of foreign flat entries, and the mixed state (pre-existing correct hook + flat broken duplicate).
|
|
24
|
+
|
|
8
25
|
## [1.18.2] - 2026-04-11
|
|
9
26
|
|
|
10
27
|
### Fixed
|
package/agents/spec-auditor.md
CHANGED
|
@@ -457,6 +457,55 @@ Check if spec includes items marked as "Out of Scope" or "Deferred" in PROJECT.m
|
|
|
457
457
|
**If compliant:**
|
|
458
458
|
- Add to audit output: "Project compliance: ✓ Honors PROJECT.md decisions"
|
|
459
459
|
|
|
460
|
+
## Step 3.9.5: Deferred Work Detection
|
|
461
|
+
|
|
462
|
+
Scan the specification's prose sections for mentions of work that is explicitly deferred, out of scope, or planned for a follow-up. This prevents deferred work from being silently lost when the spec is archived.
|
|
463
|
+
|
|
464
|
+
### 3.9.5.1 Scan Sections
|
|
465
|
+
|
|
466
|
+
Search the following sections of the spec for deferred work signals:
|
|
467
|
+
- Context
|
|
468
|
+
- Goal Analysis
|
|
469
|
+
- Requirements
|
|
470
|
+
- Constraints
|
|
471
|
+
- Assumptions
|
|
472
|
+
- Acceptance Criteria
|
|
473
|
+
|
|
474
|
+
**Signal patterns to detect** (case-insensitive):
|
|
475
|
+
- "defer", "deferred"
|
|
476
|
+
- "follow-up", "follow up", "followup"
|
|
477
|
+
- "future work", "future spec", "future phase"
|
|
478
|
+
- "separate spec", "separate task"
|
|
479
|
+
- "out of scope", "out-of-scope"
|
|
480
|
+
- "not in this spec", "not included in this spec"
|
|
481
|
+
- "will be added later", "added in a later"
|
|
482
|
+
- "TBD", "to be determined"
|
|
483
|
+
- "planned for", "tracked as", "tracked in"
|
|
484
|
+
|
|
485
|
+
### 3.9.5.2 Cross-Reference with TODOs
|
|
486
|
+
|
|
487
|
+
For each deferred item found:
|
|
488
|
+
|
|
489
|
+
1. Extract a short description of the deferred work from the surrounding sentence
|
|
490
|
+
2. Check if a corresponding TODO already exists:
|
|
491
|
+
- Search `.specflow/todos/` files for keywords from the deferred item description
|
|
492
|
+
- If a matching TODO exists: note as covered
|
|
493
|
+
- If no matching TODO found: flag as untracked
|
|
494
|
+
|
|
495
|
+
### 3.9.5.3 Deferred Work Verdict
|
|
496
|
+
|
|
497
|
+
**If untracked deferred items found:**
|
|
498
|
+
- Add **Recommendation** with prefix `[Deferred]` for each:
|
|
499
|
+
- `"Deferred work mentioned but no TODO found: '{quoted phrase from spec}'. Create via /sf:todo to prevent scope loss."`
|
|
500
|
+
|
|
501
|
+
**If all deferred items have matching TODOs:**
|
|
502
|
+
- Add to audit output: `"Deferred work: ✓ All deferred items tracked"`
|
|
503
|
+
|
|
504
|
+
**If no deferred work mentions found:**
|
|
505
|
+
- Omit from audit output (no line needed)
|
|
506
|
+
|
|
507
|
+
**Note:** This is always a Recommendation, never Critical — the spec itself may be correct, and the TODO may have been created outside SpecFlow or may be intentionally deferred to a later planning session.
|
|
508
|
+
|
|
460
509
|
## Step 3.10: Language Profile Check
|
|
461
510
|
|
|
462
511
|
**Detection:** Check if PROJECT.md contains a `## Language Profile` section.
|
|
@@ -840,6 +889,7 @@ Tip: `/clear` recommended before `/sf:run` — executor needs fresh context
|
|
|
840
889
|
- [ ] PROJECT.md context loaded
|
|
841
890
|
- [ ] All 10 dimensions evaluated (clarity, completeness, testability, scope, feasibility, architecture, duplication, cognitive load, strategic fit, project compliance)
|
|
842
891
|
- [ ] Language profile checked (if present in PROJECT.md)
|
|
892
|
+
- [ ] Deferred work scanned and cross-referenced with TODOs
|
|
843
893
|
- [ ] Assumptions extracted and impact assessed
|
|
844
894
|
- [ ] Project alignment verified
|
|
845
895
|
- [ ] Project compliance verified (decisions, constraints, out-of-scope)
|
package/bin/install.js
CHANGED
|
@@ -289,6 +289,32 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
289
289
|
if (!settings.hooks) settings.hooks = {};
|
|
290
290
|
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
291
291
|
|
|
292
|
+
// Auto-heal: remove flat, broken context-monitor entries written by
|
|
293
|
+
// installer versions 1.18.0 and 1.18.1. Those versions pushed
|
|
294
|
+
// { type: "command", command: "..." } directly into PostToolUse, but
|
|
295
|
+
// Claude Code requires every PostToolUse entry to be a matcher group
|
|
296
|
+
// of shape { matcher?, hooks: [{ type, command }, ...] }. A flat entry
|
|
297
|
+
// makes Claude Code fail to parse settings.json entirely. Only entries
|
|
298
|
+
// that are unambiguously ours (flat + command references context-monitor)
|
|
299
|
+
// are removed; everything else is left untouched.
|
|
300
|
+
{
|
|
301
|
+
const before = settings.hooks.PostToolUse.length;
|
|
302
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter(entry => {
|
|
303
|
+
const isFlatBrokenMonitor =
|
|
304
|
+
entry &&
|
|
305
|
+
!Array.isArray(entry.hooks) &&
|
|
306
|
+
entry.type === 'command' &&
|
|
307
|
+
typeof entry.command === 'string' &&
|
|
308
|
+
entry.command.includes('context-monitor');
|
|
309
|
+
return !isFlatBrokenMonitor;
|
|
310
|
+
});
|
|
311
|
+
const removed = before - settings.hooks.PostToolUse.length;
|
|
312
|
+
if (removed > 0) {
|
|
313
|
+
const plural = removed === 1 ? 'y' : 'ies';
|
|
314
|
+
console.log(` ${green}✓${reset} Repaired settings.json (removed ${removed} broken hook entr${plural} from prior install)`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
292
318
|
// Claude Code expects each PostToolUse entry to be a matcher group:
|
|
293
319
|
// { matcher?: string, hooks: [{ type, command }, ...] }
|
|
294
320
|
// Detect an existing context-monitor hook inside any matcher group.
|
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,6 +12,7 @@
|
|
|
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
17
|
* state get Current active spec, status, next step
|
|
17
18
|
* state set-active <id> <status> [next] Update active spec in STATE.md
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
const { output, error, generateSlug } = require('./lib/core.cjs');
|
|
26
27
|
const { cmdStateGet, cmdStateSetActive, cmdQueueNext } = require('./lib/state.cjs');
|
|
27
28
|
const { cmdSpecLoad, cmdSpecList, cmdSpecNextId } = require('./lib/spec.cjs');
|
|
28
|
-
const { cmdTodoLoad, cmdTodoList, cmdTodoNextId } = require('./lib/todo.cjs');
|
|
29
|
+
const { cmdTodoLoad, cmdTodoList, cmdTodoNextId, cmdTodoReindex } = require('./lib/todo.cjs');
|
|
29
30
|
const { cmdResolveModel } = require('./lib/config.cjs');
|
|
30
31
|
const { cmdVerifyStructure } = require('./lib/verify.cjs');
|
|
31
32
|
|
|
@@ -59,6 +60,7 @@ const COMMANDS = {
|
|
|
59
60
|
},
|
|
60
61
|
'todo list': () => cmdTodoList(cwd, raw, { showAll: flags.all ?? false }),
|
|
61
62
|
'todo next-id': () => cmdTodoNextId(cwd, raw),
|
|
63
|
+
'todo reindex': () => cmdTodoReindex(cwd, raw),
|
|
62
64
|
'queue next': () => cmdQueueNext(cwd, raw),
|
|
63
65
|
'state get': () => cmdStateGet(cwd, raw),
|
|
64
66
|
'state set-active': () => {
|
|
@@ -90,6 +92,7 @@ Commands:
|
|
|
90
92
|
todo load <id> Parse TODO file, return frontmatter + body
|
|
91
93
|
todo list [--all] List TODOs sorted by priority (--all includes eliminated)
|
|
92
94
|
todo next-id Next available TODO-XXX number
|
|
95
|
+
todo reindex Regenerate INDEX.md from TODO files
|
|
93
96
|
queue next First actionable spec from queue table
|
|
94
97
|
state get Current active spec, status, next step
|
|
95
98
|
state set-active <id> <status> [next] Update active spec, status, next step
|
package/commands/sf/done.md
CHANGED
|
@@ -283,12 +283,16 @@ Check if the spec frontmatter contains a `source:` field (e.g., `source: TODO-00
|
|
|
283
283
|
[ -f .specflow/todos/{source}.md ] && echo "FOUND" || echo "NOT_FOUND"
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
-
2. **If FOUND:** Delete the file:
|
|
286
|
+
2. **If FOUND:** Delete the file and reindex:
|
|
287
287
|
|
|
288
288
|
```bash
|
|
289
289
|
rm .specflow/todos/{source}.md
|
|
290
290
|
```
|
|
291
291
|
|
|
292
|
+
```bash
|
|
293
|
+
node ~/.claude/specflow-cc/bin/sf-tools.cjs todo reindex
|
|
294
|
+
```
|
|
295
|
+
|
|
292
296
|
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
297
|
|
|
294
298
|
No "Last updated" lines to update in per-file format.
|
package/commands/sf/todo.md
CHANGED
|
@@ -96,6 +96,12 @@ created: {YYYY-MM-DD}
|
|
|
96
96
|
|
|
97
97
|
Do NOT create or modify TODO.md. Do NOT update any "Last updated" lines.
|
|
98
98
|
|
|
99
|
+
## Step 6.5: Reindex
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
node ~/.claude/specflow-cc/bin/sf-tools.cjs todo reindex
|
|
103
|
+
```
|
|
104
|
+
|
|
99
105
|
## Step 7: Display Confirmation
|
|
100
106
|
|
|
101
107
|
**IMPORTANT:** Output the following directly as formatted text, NOT wrapped in a markdown code block:
|