specflow-cc 1.12.0 → 1.14.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 +74 -0
- package/README.md +22 -8
- package/agents/impl-reviewer.md +8 -0
- package/agents/sf-spec-executor-orchestrator.md +8 -0
- package/agents/spec-auditor.md +8 -0
- package/agents/spec-creator.md +13 -2
- package/agents/spec-executor-orchestrator.md +21 -0
- package/agents/spec-executor.md +8 -0
- package/agents/spec-reviser.md +8 -0
- package/agents/spec-splitter.md +11 -1
- package/bin/install.js +20 -0
- package/bin/lib/config.cjs +91 -0
- package/bin/lib/core.cjs +120 -0
- package/bin/lib/spec.cjs +130 -0
- package/bin/lib/state.cjs +241 -0
- package/bin/lib/verify.cjs +117 -0
- package/bin/sf-tools.cjs +103 -0
- package/commands/sf/audit.md +11 -69
- package/commands/sf/autopilot.md +601 -0
- package/commands/sf/done.md +32 -71
- package/commands/sf/health.md +220 -0
- package/commands/sf/help.md +16 -0
- package/commands/sf/review.md +11 -69
- package/commands/sf/revise.md +4 -7
- package/commands/sf/run.md +11 -69
- package/commands/sf/split.md +14 -3
- package/commands/sf/validate.md +154 -0
- package/hooks/context-monitor.js +121 -0
- package/hooks/statusline.js +17 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,80 @@ 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.14.0] - 2026-03-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Context Monitor Hook** — agent-facing context awareness via PostToolUse hook
|
|
13
|
+
- Statusline writes bridge file to `/tmp/claude-ctx-{session}.json`
|
|
14
|
+
- New `hooks/context-monitor.js` reads metrics and injects WARNING (35% remaining) / CRITICAL (25%) warnings into agent context
|
|
15
|
+
- Debounce (5 tool uses between warnings), severity escalation bypasses debounce
|
|
16
|
+
- Integrates with `/sf:pause` for graceful session saves
|
|
17
|
+
- Installer auto-registers the hook in settings.json
|
|
18
|
+
|
|
19
|
+
- **`/sf:health`** — diagnose `.specflow/` directory integrity
|
|
20
|
+
- 13 error codes across 3 severity levels (error, warning, info)
|
|
21
|
+
- Checks: STATE.md integrity, orphaned specs, queue consistency, missing directories, stale execution state
|
|
22
|
+
- `--repair` flag for safe auto-fixes (create missing dirs, regenerate STATE.md, clear stale state)
|
|
23
|
+
- Repair verification: re-runs checks after repair to confirm resolution
|
|
24
|
+
|
|
25
|
+
- **`/sf:validate`** — run validation checklist from specification
|
|
26
|
+
- Executes automated checks (test commands), code verifications (grep/glob), and manual prompts
|
|
27
|
+
- Pass/fail report per checklist item with overall validation status
|
|
28
|
+
- Graceful handling when spec has no validation checklist
|
|
29
|
+
|
|
30
|
+
- **Validation Checklist in spec template** — spec-creator generates `## Validation Checklist` section for medium/large specs
|
|
31
|
+
- 3-5 concrete verification steps with expected outcomes
|
|
32
|
+
- Each item: action + expected result (e.g., "Run `npm test` — all pass")
|
|
33
|
+
|
|
34
|
+
- **Enriched completion summaries** in `/sf:done`
|
|
35
|
+
- New sections: Outcome, Key Files, Patterns Established, Deviations
|
|
36
|
+
- Decisions extracted from both spec content and completion section
|
|
37
|
+
|
|
38
|
+
- **Centralized CLI Tooling** (`bin/sf-tools.cjs`) — single Node.js CLI for SpecFlow operations
|
|
39
|
+
- `spec load <id>` — parse spec file, return frontmatter + sections as JSON
|
|
40
|
+
- `spec list [--status <s>]` — list specs with optional status filter
|
|
41
|
+
- `spec next-id` — next available SPEC-XXX number (checks specs/ + archive/)
|
|
42
|
+
- `queue next` — first actionable spec from queue
|
|
43
|
+
- `state get` / `state set-active <id> <status>` — STATE.md CRUD
|
|
44
|
+
- `resolve-model <agent-type>` — model resolution by profile
|
|
45
|
+
- `verify-structure` — `.specflow/` integrity checks
|
|
46
|
+
- `generate-slug <text>` — URL-safe slug generation
|
|
47
|
+
- Modular architecture: `bin/lib/core.cjs`, `state.cjs`, `spec.cjs`, `config.cjs`, `verify.cjs`
|
|
48
|
+
- 42 tests using Node.js `assert` (no external dependencies)
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- Parent spec now correctly archived after `/sf:split`
|
|
53
|
+
- `.specflow/` directory excluded from git tracking
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## [1.13.0] - 2026-02-11
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **Autopilot mode** (`/sf:autopilot`) — run the full spec lifecycle autonomously
|
|
62
|
+
- Single spec: `/sf:autopilot` or `/sf:autopilot SPEC-XXX`
|
|
63
|
+
- Batch mode: `/sf:autopilot --all` processes entire queue sequentially
|
|
64
|
+
- Cycle detection: configurable limits for audit (default: 3) and fix (default: 3) cycles
|
|
65
|
+
- Graceful halt on `needs_decomposition` or `paused` specs
|
|
66
|
+
- Summary report with per-spec outcomes and cycle counts
|
|
67
|
+
- Agent failure handling: continues batch on single-spec failure
|
|
68
|
+
- Configurable via `.specflow/config.json` under `"autopilot"` key
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
|
|
72
|
+
- **Replaced all Bash/awk/sed markdown mutations** with Read+Write tool instructions across 13 agent and command files
|
|
73
|
+
- Eliminates fragile shell-based file editing that could corrupt markdown structure
|
|
74
|
+
- All STATE.md, spec, and archive updates now use explicit Read→Write pattern
|
|
75
|
+
- Affected: spec-creator, spec-auditor, spec-reviser, spec-splitter, spec-executor, spec-executor-orchestrator, impl-reviewer, and 6 command files
|
|
76
|
+
|
|
77
|
+
- `/sf:help` — added Autonomous Execution section with autopilot commands
|
|
78
|
+
- README — added autopilot to workflow diagram, commands table, and typical session
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
8
82
|
## [1.12.0] - 2026-02-10
|
|
9
83
|
|
|
10
84
|
### Added
|
package/README.md
CHANGED
|
@@ -200,15 +200,16 @@ If issues are found, `/sf:fix` addresses them. Loop until approved.
|
|
|
200
200
|
|
|
201
201
|
---
|
|
202
202
|
|
|
203
|
-
### 5. Verify (Optional)
|
|
203
|
+
### 5. Validate & Verify (Optional)
|
|
204
204
|
|
|
205
205
|
```
|
|
206
|
+
/sf:validate
|
|
206
207
|
/sf:verify
|
|
207
208
|
```
|
|
208
209
|
|
|
209
|
-
|
|
210
|
+
**Validate** runs the spec's validation checklist — automated test commands, code checks, and manual verification prompts. Pass/fail per item.
|
|
210
211
|
|
|
211
|
-
|
|
212
|
+
**Verify** walks you through interactive acceptance testing:
|
|
212
213
|
|
|
213
214
|
- "Can you log in with OAuth?"
|
|
214
215
|
- "Does the redirect work?"
|
|
@@ -216,7 +217,7 @@ This step walks you through manual verification:
|
|
|
216
217
|
|
|
217
218
|
You confirm each item. If something's broken, the system helps diagnose and creates fix plans.
|
|
218
219
|
|
|
219
|
-
**Creates:**
|
|
220
|
+
**Creates:** Validation/verification record
|
|
220
221
|
|
|
221
222
|
---
|
|
222
223
|
|
|
@@ -237,10 +238,12 @@ Your spec becomes documentation: why the code exists, what decisions were made,
|
|
|
237
238
|
```
|
|
238
239
|
/sf:quick (trivial tasks)
|
|
239
240
|
↓
|
|
240
|
-
/sf:new
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
/sf:new → /sf:audit → /sf:run → /sf:review → /sf:validate → /sf:verify → /sf:done
|
|
242
|
+
↓ ↓ ↓ ↓
|
|
243
|
+
/sf:revise /sf:fix (checklist) (optional UAT)
|
|
244
|
+
(if needed) (if needed)
|
|
245
|
+
|
|
246
|
+
/sf:autopilot — runs the entire flow above automatically
|
|
244
247
|
```
|
|
245
248
|
|
|
246
249
|
**Key principle:** Audits and reviews run in fresh context — no bias from creation.
|
|
@@ -310,8 +313,10 @@ Six months later, you can read the spec and understand not just *what* was built
|
|
|
310
313
|
| `/sf:run` | Implement specification |
|
|
311
314
|
| `/sf:review` | Review implementation (fresh context) |
|
|
312
315
|
| `/sf:fix` | Fix based on review feedback |
|
|
316
|
+
| `/sf:validate` | Run validation checklist from spec |
|
|
313
317
|
| `/sf:verify` | Interactive user acceptance testing |
|
|
314
318
|
| `/sf:done` | Complete and archive |
|
|
319
|
+
| `/sf:autopilot` | Run full lifecycle autonomously |
|
|
315
320
|
|
|
316
321
|
**Quick mode:**
|
|
317
322
|
|
|
@@ -393,6 +398,7 @@ before showing interactive options.
|
|
|
393
398
|
| `/sf:deps` | Show spec dependencies |
|
|
394
399
|
| `/sf:pause` | Save session context |
|
|
395
400
|
| `/sf:resume` | Restore session |
|
|
401
|
+
| `/sf:health` | Diagnose `.specflow/` integrity |
|
|
396
402
|
| `/sf:help` | Command reference |
|
|
397
403
|
|
|
398
404
|
---
|
|
@@ -455,6 +461,10 @@ Use `max` for maximum quality everywhere, `quality` for critical features, `budg
|
|
|
455
461
|
/sf:review # Fresh context review
|
|
456
462
|
/sf:verify # Manual verification
|
|
457
463
|
/sf:done # Archive
|
|
464
|
+
|
|
465
|
+
# Or skip manual steps — run everything autonomously
|
|
466
|
+
/sf:autopilot # Process active spec end-to-end
|
|
467
|
+
/sf:autopilot --all # Process entire queue
|
|
458
468
|
```
|
|
459
469
|
|
|
460
470
|
---
|
|
@@ -474,6 +484,10 @@ Use `max` for maximum quality everywhere, `quality` for critical features, `budg
|
|
|
474
484
|
- Use `/sf:split` to decompose into smaller specs
|
|
475
485
|
- Or let the system auto-decompose during `/sf:run`
|
|
476
486
|
|
|
487
|
+
**STATE.md or queue seems corrupted?**
|
|
488
|
+
- Run `/sf:health` to diagnose issues
|
|
489
|
+
- Use `/sf:health --repair` for safe auto-fixes
|
|
490
|
+
|
|
477
491
|
---
|
|
478
492
|
|
|
479
493
|
## Philosophy
|
package/agents/impl-reviewer.md
CHANGED
|
@@ -288,6 +288,14 @@ Append to specification's Review History:
|
|
|
288
288
|
- If APPROVED: Status → "done", Next Step → "/sf:done"
|
|
289
289
|
- If CHANGES_REQUESTED: Status → "review", Next Step → "/sf:fix"
|
|
290
290
|
|
|
291
|
+
Update STATE.md by reading the current file content, then writing the updated file with:
|
|
292
|
+
- "**Status:**" line changed to the new status
|
|
293
|
+
- "**Next Step:**" line changed to the new next step
|
|
294
|
+
- No other content modified
|
|
295
|
+
|
|
296
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
297
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
298
|
+
|
|
291
299
|
</process>
|
|
292
300
|
|
|
293
301
|
<output>
|
|
@@ -389,6 +389,14 @@ Update ONLY the Current Position section:
|
|
|
389
389
|
- Status → "review"
|
|
390
390
|
- Next Step → "/sf:review"
|
|
391
391
|
|
|
392
|
+
Update STATE.md by reading the current file content, then writing the updated file with:
|
|
393
|
+
- "**Status:**" line changed to the new status
|
|
394
|
+
- "**Next Step:**" line changed to the new next step
|
|
395
|
+
- No other content modified
|
|
396
|
+
|
|
397
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
398
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
399
|
+
|
|
392
400
|
**CRITICAL — DO NOT go beyond this:**
|
|
393
401
|
- Do NOT move the spec to Completed Specifications table
|
|
394
402
|
- Do NOT remove the spec from Queue table
|
package/agents/spec-auditor.md
CHANGED
|
@@ -685,6 +685,14 @@ Update status:
|
|
|
685
685
|
- If NEEDS_DECOMPOSITION: Status → "needs_decomposition", Next Step → "/sf:split or /sf:run --parallel"
|
|
686
686
|
- If NEEDS_REVISION: Status → "revision_requested", Next Step → "/sf:revise"
|
|
687
687
|
|
|
688
|
+
Update STATE.md by reading the current file content, then writing the updated file with:
|
|
689
|
+
- "**Status:**" line changed to the new status
|
|
690
|
+
- "**Next Step:**" line changed to the new next step
|
|
691
|
+
- No other content modified
|
|
692
|
+
|
|
693
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
694
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
695
|
+
|
|
688
696
|
</process>
|
|
689
697
|
|
|
690
698
|
<output>
|
package/agents/spec-creator.md
CHANGED
|
@@ -146,8 +146,9 @@ Write to `.specflow/specs/SPEC-XXX.md` using the template structure:
|
|
|
146
146
|
4. **Task:** What to do
|
|
147
147
|
5. **Requirements:** Files, interfaces, deletions
|
|
148
148
|
6. **Acceptance Criteria:** Specific, measurable
|
|
149
|
-
7. **
|
|
150
|
-
8. **
|
|
149
|
+
7. **Validation Checklist** (medium/large specs only): 3-5 concrete verification steps with expected outcomes. Each item = action + expected result. Examples: "Run `npm test` — all pass", "POST /api/users with invalid email — returns 422", "Open settings page — new toggle visible"
|
|
150
|
+
8. **Constraints:** What NOT to do
|
|
151
|
+
9. **Assumptions:** What you assumed (clearly marked)
|
|
151
152
|
- **If `<prior_discussion>` provided:** Decisions from discussion are facts, not assumptions
|
|
152
153
|
|
|
153
154
|
## Step 5.5: Generate Implementation Tasks (for medium and large specs)
|
|
@@ -241,6 +242,16 @@ Update `.specflow/STATE.md`:
|
|
|
241
242
|
- Set Next Step to "/sf:audit"
|
|
242
243
|
- Add spec to Queue
|
|
243
244
|
|
|
245
|
+
Update STATE.md by reading the current file content, then writing the updated file with:
|
|
246
|
+
- "**Active Specification:**" line changed to the new spec
|
|
247
|
+
- "**Status:**" line changed to "drafting"
|
|
248
|
+
- "**Next Step:**" line changed to "/sf:audit"
|
|
249
|
+
- Queue table updated with new spec entry
|
|
250
|
+
- No other content modified
|
|
251
|
+
|
|
252
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
253
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
254
|
+
|
|
244
255
|
**If `<prior_discussion>` provided:**
|
|
245
256
|
Update the discussion file (PRE-XXX.md or DISC-XXX.md):
|
|
246
257
|
- Set `used_by: SPEC-XXX` in frontmatter
|
|
@@ -330,6 +330,10 @@ Write `.specflow/execution/SPEC-XXX-state.json`:
|
|
|
330
330
|
| SPEC-XXX | orchestrated | Wave 0/{total} (0%) | {timestamp} |
|
|
331
331
|
```
|
|
332
332
|
|
|
333
|
+
Update STATE.md by reading the current file content, then writing the updated file with the Execution Status table row added/updated.
|
|
334
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
335
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
336
|
+
|
|
333
337
|
## Step 3: Execute Waves
|
|
334
338
|
|
|
335
339
|
For each wave:
|
|
@@ -597,6 +601,10 @@ After wave completes (all groups done):
|
|
|
597
601
|
| SPEC-XXX | orchestrated | Wave 2/{total} (67%) | {timestamp} |
|
|
598
602
|
```
|
|
599
603
|
|
|
604
|
+
Update STATE.md by reading the current file content, then writing the updated file with the Execution Status table row updated for this wave.
|
|
605
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
606
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
607
|
+
|
|
600
608
|
## Step 4: Aggregate Results
|
|
601
609
|
|
|
602
610
|
Combine all worker results:
|
|
@@ -683,6 +691,10 @@ rm .specflow/execution/SPEC-XXX-state.json
|
|
|
683
691
|
- Change row to show "Complete" or remove row entirely
|
|
684
692
|
- Or archive: `mv .specflow/execution/SPEC-XXX-state.json .specflow/execution/archive/`
|
|
685
693
|
|
|
694
|
+
Update STATE.md by reading the current file content, then writing the updated file with the Execution Status table row removed or updated to "Complete".
|
|
695
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
696
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
697
|
+
|
|
686
698
|
**Note:** Only delete on FULL success. If any groups failed or are partial, keep state file for potential retry.
|
|
687
699
|
|
|
688
700
|
## Step 7: Update STATE.md
|
|
@@ -692,6 +704,15 @@ Update ONLY the Current Position section:
|
|
|
692
704
|
- Next Step → "/sf:review"
|
|
693
705
|
- Remove or update Execution Status row
|
|
694
706
|
|
|
707
|
+
Update STATE.md by reading the current file content, then writing the updated file with:
|
|
708
|
+
- "**Status:**" line changed to the new status
|
|
709
|
+
- "**Next Step:**" line changed to the new next step
|
|
710
|
+
- Execution Status row removed or updated
|
|
711
|
+
- No other content modified
|
|
712
|
+
|
|
713
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
714
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
715
|
+
|
|
695
716
|
**CRITICAL — DO NOT go beyond this:**
|
|
696
717
|
- Do NOT move the spec to Completed Specifications table
|
|
697
718
|
- Do NOT remove the spec from Queue table
|
package/agents/spec-executor.md
CHANGED
|
@@ -268,6 +268,14 @@ Update ONLY the Current Position section:
|
|
|
268
268
|
- Status → "review"
|
|
269
269
|
- Next Step → "/sf:review"
|
|
270
270
|
|
|
271
|
+
Update STATE.md by reading the current file content, then writing the updated file with:
|
|
272
|
+
- "**Status:**" line changed to the new status
|
|
273
|
+
- "**Next Step:**" line changed to the new next step
|
|
274
|
+
- No other content modified
|
|
275
|
+
|
|
276
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
277
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
278
|
+
|
|
271
279
|
**CRITICAL — DO NOT go beyond this:**
|
|
272
280
|
- Do NOT move the spec to Completed Specifications table
|
|
273
281
|
- Do NOT remove the spec from Queue table
|
package/agents/spec-reviser.md
CHANGED
|
@@ -139,6 +139,14 @@ Set status to "auditing" (ready for re-audit).
|
|
|
139
139
|
- Status → "auditing"
|
|
140
140
|
- Next Step → "/sf:audit"
|
|
141
141
|
|
|
142
|
+
Update STATE.md by reading the current file content, then writing the updated file with:
|
|
143
|
+
- "**Status:**" line changed to the new status
|
|
144
|
+
- "**Next Step:**" line changed to the new next step
|
|
145
|
+
- No other content modified
|
|
146
|
+
|
|
147
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
148
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
149
|
+
|
|
142
150
|
</process>
|
|
143
151
|
|
|
144
152
|
<output>
|
package/agents/spec-splitter.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sf-spec-splitter
|
|
3
3
|
description: Analyzes large specifications and splits them into manageable sub-specifications with dependencies
|
|
4
|
-
tools: Read, Write, Glob, Grep
|
|
4
|
+
tools: Read, Write, Glob, Grep, Bash
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<role>
|
|
@@ -188,6 +188,16 @@ Update `.specflow/STATE.md`:
|
|
|
188
188
|
- Set first child (no dependencies) as Active Specification
|
|
189
189
|
- Add note to Decisions: "Split SPEC-XXX into N parts"
|
|
190
190
|
|
|
191
|
+
Update STATE.md by reading the current file content, then writing the updated file with:
|
|
192
|
+
- Parent spec removed from Queue table
|
|
193
|
+
- All child specs added to Queue table in dependency order
|
|
194
|
+
- First child (no dependencies) set as Active Specification
|
|
195
|
+
- Decisions section updated with split note
|
|
196
|
+
- No other content modified
|
|
197
|
+
|
|
198
|
+
Use the Read tool to read `.specflow/STATE.md`, then use the Write tool to write the updated content.
|
|
199
|
+
Do NOT use Bash (awk, sed, or echo) to modify `.specflow/STATE.md`.
|
|
200
|
+
|
|
191
201
|
</process>
|
|
192
202
|
|
|
193
203
|
<output>
|
package/bin/install.js
CHANGED
|
@@ -266,6 +266,26 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
266
266
|
console.log(` ${green}✓${reset} Configured statusline`);
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
// Configure context-monitor hook
|
|
270
|
+
const isGlobal = statuslineCommand.includes('$HOME');
|
|
271
|
+
const monitorCommand = isGlobal
|
|
272
|
+
? 'node "$HOME/.claude/hooks/context-monitor.js"'
|
|
273
|
+
: 'node .claude/hooks/context-monitor.js';
|
|
274
|
+
|
|
275
|
+
if (!settings.hooks) settings.hooks = {};
|
|
276
|
+
if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
|
|
277
|
+
|
|
278
|
+
const hasMonitor = settings.hooks.PostToolUse.some(h =>
|
|
279
|
+
h.command && h.command.includes('context-monitor')
|
|
280
|
+
);
|
|
281
|
+
if (!hasMonitor) {
|
|
282
|
+
settings.hooks.PostToolUse.push({
|
|
283
|
+
type: 'command',
|
|
284
|
+
command: monitorCommand
|
|
285
|
+
});
|
|
286
|
+
console.log(` ${green}✓${reset} Configured context monitor hook`);
|
|
287
|
+
}
|
|
288
|
+
|
|
269
289
|
writeSettings(settingsPath, settings);
|
|
270
290
|
|
|
271
291
|
console.log(`
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/lib/config.cjs — Config reading and model resolution
|
|
3
|
+
*
|
|
4
|
+
* Exports: MODEL_PROFILES, loadConfig(), cmdResolveModel()
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { output, error, safeReadFile } = require('./core.cjs');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hardcoded MODEL_PROFILES table.
|
|
14
|
+
* MUST exactly match the profile table in commands/sf/run.md.
|
|
15
|
+
*/
|
|
16
|
+
const MODEL_PROFILES = {
|
|
17
|
+
'spec-creator': { max: 'opus', quality: 'opus', balanced: 'opus', budget: 'sonnet' },
|
|
18
|
+
'spec-auditor': { max: 'opus', quality: 'opus', balanced: 'opus', budget: 'sonnet' },
|
|
19
|
+
'spec-splitter': { max: 'opus', quality: 'opus', balanced: 'opus', budget: 'sonnet' },
|
|
20
|
+
'discusser': { max: 'opus', quality: 'opus', balanced: 'opus', budget: 'sonnet' },
|
|
21
|
+
'spec-executor': { max: 'opus', quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
|
22
|
+
'spec-executor-orchestrator': { max: 'opus', quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
|
23
|
+
'spec-executor-worker': { max: 'opus', quality: 'opus', balanced: 'sonnet', budget: 'sonnet' },
|
|
24
|
+
'impl-reviewer': { max: 'opus', quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
|
25
|
+
'spec-reviser': { max: 'opus', quality: 'sonnet', balanced: 'sonnet', budget: 'sonnet' },
|
|
26
|
+
'researcher': { max: 'opus', quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
|
27
|
+
'codebase-scanner': { max: 'opus', quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Load .specflow/config.json. Returns defaults if not found.
|
|
32
|
+
* @param {string} cwd - Working directory
|
|
33
|
+
* @returns {Object} Config object
|
|
34
|
+
*/
|
|
35
|
+
function loadConfig(cwd) {
|
|
36
|
+
const configPath = path.join(cwd, '.specflow', 'config.json');
|
|
37
|
+
const content = safeReadFile(configPath);
|
|
38
|
+
|
|
39
|
+
if (!content) {
|
|
40
|
+
return { model_profile: 'balanced' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const config = JSON.parse(content);
|
|
45
|
+
return {
|
|
46
|
+
model_profile: config.model_profile || 'balanced',
|
|
47
|
+
...config,
|
|
48
|
+
};
|
|
49
|
+
} catch (e) {
|
|
50
|
+
return { model_profile: 'balanced' };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve model for a given agent type based on current profile.
|
|
56
|
+
* @param {string} cwd - Working directory
|
|
57
|
+
* @param {string} agentType - Agent type (e.g., "spec-executor")
|
|
58
|
+
* @param {boolean} raw - Output raw string
|
|
59
|
+
*/
|
|
60
|
+
function cmdResolveModel(cwd, agentType, raw) {
|
|
61
|
+
if (!agentType) {
|
|
62
|
+
error('Missing agent type. Usage: resolve-model <agent-type>');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const config = loadConfig(cwd);
|
|
66
|
+
const profile = config.model_profile || 'balanced';
|
|
67
|
+
|
|
68
|
+
const agentProfiles = MODEL_PROFILES[agentType];
|
|
69
|
+
|
|
70
|
+
if (!agentProfiles) {
|
|
71
|
+
error('Unknown agent type: ' + agentType + '. Known types: ' + Object.keys(MODEL_PROFILES).join(', '));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const model = agentProfiles[profile];
|
|
75
|
+
|
|
76
|
+
if (!model) {
|
|
77
|
+
error('Unknown profile: ' + profile + '. Known profiles: max, quality, balanced, budget');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
output({
|
|
81
|
+
agent: agentType,
|
|
82
|
+
profile: profile,
|
|
83
|
+
model: model,
|
|
84
|
+
}, raw, model);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
MODEL_PROFILES,
|
|
89
|
+
loadConfig,
|
|
90
|
+
cmdResolveModel,
|
|
91
|
+
};
|
package/bin/lib/core.cjs
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/lib/core.cjs — Shared utilities for sf-tools CLI
|
|
3
|
+
*
|
|
4
|
+
* Exports: output(), error(), safeReadFile(), parseFrontmatter(), generateSlug()
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Output result to stdout as JSON, or raw string if raw flag is set.
|
|
14
|
+
* @param {*} result - The result object to output
|
|
15
|
+
* @param {boolean} raw - If true, output rawValue as plain string
|
|
16
|
+
* @param {string} [rawValue] - Plain string to output when raw is true
|
|
17
|
+
*/
|
|
18
|
+
function output(result, raw, rawValue) {
|
|
19
|
+
if (raw && rawValue !== undefined) {
|
|
20
|
+
process.stdout.write(String(rawValue) + '\n');
|
|
21
|
+
} else {
|
|
22
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Output error message to stderr and exit with code 1.
|
|
28
|
+
* @param {string} message - Error message
|
|
29
|
+
*/
|
|
30
|
+
function error(message) {
|
|
31
|
+
process.stderr.write('Error: ' + message + '\n');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Safely read a file, returning its contents as a string or null if not found.
|
|
37
|
+
* @param {string} filePath - Absolute or relative path to the file
|
|
38
|
+
* @returns {string|null} File contents or null
|
|
39
|
+
*/
|
|
40
|
+
function safeReadFile(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse YAML frontmatter from markdown content.
|
|
50
|
+
* Expects `---` delimited YAML at the start of the file.
|
|
51
|
+
* Returns simple key: value pairs only (no nested objects, no arrays).
|
|
52
|
+
* All values are returned as strings. No type coercion is performed.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} content - Full file content
|
|
55
|
+
* @returns {{ frontmatter: Object, body: string }}
|
|
56
|
+
*/
|
|
57
|
+
function parseFrontmatter(content) {
|
|
58
|
+
if (!content || typeof content !== 'string') {
|
|
59
|
+
return { frontmatter: {}, body: content || '' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const trimmed = content.trimStart();
|
|
63
|
+
|
|
64
|
+
if (!trimmed.startsWith('---')) {
|
|
65
|
+
return { frontmatter: {}, body: content };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Find the closing ---
|
|
69
|
+
const secondDash = trimmed.indexOf('---', 3);
|
|
70
|
+
if (secondDash === -1) {
|
|
71
|
+
return { frontmatter: {}, body: content };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const yamlBlock = trimmed.substring(3, secondDash).trim();
|
|
75
|
+
const body = trimmed.substring(secondDash + 3).replace(/^\r?\n/, '');
|
|
76
|
+
|
|
77
|
+
const frontmatter = {};
|
|
78
|
+
const lines = yamlBlock.split('\n');
|
|
79
|
+
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
const trimmedLine = line.trim();
|
|
82
|
+
if (!trimmedLine || trimmedLine.startsWith('#')) continue;
|
|
83
|
+
|
|
84
|
+
const colonIndex = trimmedLine.indexOf(':');
|
|
85
|
+
if (colonIndex === -1) continue;
|
|
86
|
+
|
|
87
|
+
const key = trimmedLine.substring(0, colonIndex).trim();
|
|
88
|
+
const value = trimmedLine.substring(colonIndex + 1).trim();
|
|
89
|
+
|
|
90
|
+
// All values returned as strings — no type coercion
|
|
91
|
+
frontmatter[key] = value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { frontmatter, body };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generate a URL-safe slug from text.
|
|
99
|
+
* Lowercase, replace spaces/special chars with hyphens, collapse multiples, trim edges.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} text - Input text
|
|
102
|
+
* @returns {string} URL-safe slug
|
|
103
|
+
*/
|
|
104
|
+
function generateSlug(text) {
|
|
105
|
+
if (!text || typeof text !== 'string') return '';
|
|
106
|
+
|
|
107
|
+
return text
|
|
108
|
+
.toLowerCase()
|
|
109
|
+
.replace(/[^a-z0-9]+/g, '-') // replace non-alphanumeric with hyphens
|
|
110
|
+
.replace(/-+/g, '-') // collapse multiple hyphens
|
|
111
|
+
.replace(/^-|-$/g, ''); // trim leading/trailing hyphens
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
output,
|
|
116
|
+
error,
|
|
117
|
+
safeReadFile,
|
|
118
|
+
parseFrontmatter,
|
|
119
|
+
generateSlug,
|
|
120
|
+
};
|