specweave 1.0.205 → 1.0.206
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/CLAUDE.md +24 -24
- package/bin/specweave.js +28 -0
- package/dist/src/cli/commands/decision-log.d.ts +43 -0
- package/dist/src/cli/commands/decision-log.d.ts.map +1 -0
- package/dist/src/cli/commands/decision-log.js +216 -0
- package/dist/src/cli/commands/decision-log.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/hooks/stop-reflect.sh +169 -5
- package/plugins/specweave/skills/image-generation/SKILL.md +23 -0
- package/src/templates/AGENTS.md.template +46 -21
package/CLAUDE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- SW:META template="claude" version="1.0.
|
|
1
|
+
<!-- SW:META template="claude" version="1.0.205" sections="header,start,autodetect,metarule,rules,workflow,reflect,context,structure,taskformat,secrets,syncing,testing,tdd,api,limits,troubleshooting,lazyloading,principles,linking,mcp,auto,docs" -->
|
|
2
2
|
|
|
3
3
|
<!-- SW:SECTION:hook-priority version="1.0.171" -->
|
|
4
4
|
## ⛔ Hook Instructions Override Everything
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
**SKILL FIRST ≠ only one skill.** Chain: hook skill → `sw-frontend:*` / `sw-backend:*` / etc → implement.
|
|
14
14
|
<!-- SW:END:hook-priority -->
|
|
15
15
|
|
|
16
|
-
<!-- SW:SECTION:header version="1.0.
|
|
16
|
+
<!-- SW:SECTION:header version="1.0.205" -->
|
|
17
17
|
**Framework**: SpecWeave | **Truth**: `spec.md` + `tasks.md`
|
|
18
18
|
<!-- SW:END:header -->
|
|
19
19
|
|
|
@@ -123,7 +123,7 @@ claude plugin install sw@specweave --scope project # Team-shared
|
|
|
123
123
|
|
|
124
124
|
SpecWeave auto-installs: LSP → project scope, sw-* → user scope.
|
|
125
125
|
|
|
126
|
-
<!-- SW:SECTION:start version="1.0.
|
|
126
|
+
<!-- SW:SECTION:start version="1.0.205" -->
|
|
127
127
|
## Getting Started
|
|
128
128
|
|
|
129
129
|
**Initial increment**: `0001-project-setup` (auto-created by `specweave init`)
|
|
@@ -133,7 +133,7 @@ SpecWeave auto-installs: LSP → project scope, sw-* → user scope.
|
|
|
133
133
|
2. **Customize**: Edit spec.md and use for setup tasks
|
|
134
134
|
<!-- SW:END:start -->
|
|
135
135
|
|
|
136
|
-
<!-- SW:SECTION:autodetect version="1.0.
|
|
136
|
+
<!-- SW:SECTION:autodetect version="1.0.205" -->
|
|
137
137
|
## Auto-Detection
|
|
138
138
|
|
|
139
139
|
SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
@@ -143,7 +143,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
|
143
143
|
**Opt-out phrases**: "Just brainstorm first" | "Don't plan yet" | "Quick discussion" | "Let's explore ideas"
|
|
144
144
|
<!-- SW:END:autodetect -->
|
|
145
145
|
|
|
146
|
-
<!-- SW:SECTION:metarule version="1.0.
|
|
146
|
+
<!-- SW:SECTION:metarule version="1.0.205" -->
|
|
147
147
|
## Workflow Orchestration
|
|
148
148
|
|
|
149
149
|
### 1. Plan Mode Default
|
|
@@ -170,7 +170,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
|
170
170
|
```
|
|
171
171
|
<!-- SW:END:metarule -->
|
|
172
172
|
|
|
173
|
-
<!-- SW:SECTION:rules version="1.0.
|
|
173
|
+
<!-- SW:SECTION:rules version="1.0.205" -->
|
|
174
174
|
## Rules
|
|
175
175
|
|
|
176
176
|
1. **Files** → `.specweave/increments/####-name/` (see Structure section for details)
|
|
@@ -189,7 +189,7 @@ SpecWeave auto-detects product descriptions and routes to `/sw:increment`:
|
|
|
189
189
|
Use next available number. **NEVER create duplicate prefixes.**
|
|
190
190
|
<!-- SW:END:rules -->
|
|
191
191
|
|
|
192
|
-
<!-- SW:SECTION:workflow version="1.0.
|
|
192
|
+
<!-- SW:SECTION:workflow version="1.0.205" -->
|
|
193
193
|
## Workflow
|
|
194
194
|
|
|
195
195
|
`/sw:increment "X"` → `/sw:do` → `/sw:progress` → `/sw:done 0001`
|
|
@@ -235,7 +235,7 @@ project/
|
|
|
235
235
|
**NEVER assume single-repo mode without scanning first!**
|
|
236
236
|
<!-- SW:END:save-nested-repos -->
|
|
237
237
|
|
|
238
|
-
<!-- SW:SECTION:reflect version="1.0.
|
|
238
|
+
<!-- SW:SECTION:reflect version="1.0.205" -->
|
|
239
239
|
## Skill Memories
|
|
240
240
|
|
|
241
241
|
SpecWeave learns from corrections. Learnings saved here automatically. Edit or delete as needed.
|
|
@@ -254,7 +254,7 @@ SpecWeave learns from corrections. Learnings saved here automatically. Edit or d
|
|
|
254
254
|
- **2026-02-02**: Use subagents liberally for codebase analysis - up to 10+ concurrent for large-scale exploration
|
|
255
255
|
- **2026-02-02**: Prefer leaderboard-style reporting when analyzing usage patterns or identifying deletion candidates
|
|
256
256
|
|
|
257
|
-
<!-- SW:SECTION:context version="1.0.
|
|
257
|
+
<!-- SW:SECTION:context version="1.0.205" -->
|
|
258
258
|
## Context
|
|
259
259
|
|
|
260
260
|
**Before implementing**: Check ADRs at `.specweave/docs/internal/architecture/adr/`
|
|
@@ -262,7 +262,7 @@ SpecWeave learns from corrections. Learnings saved here automatically. Edit or d
|
|
|
262
262
|
**Load context**: `/sw:context <topic>` loads relevant living docs into conversation
|
|
263
263
|
<!-- SW:END:context -->
|
|
264
264
|
|
|
265
|
-
<!-- SW:SECTION:structure version="1.0.
|
|
265
|
+
<!-- SW:SECTION:structure version="1.0.205" -->
|
|
266
266
|
## Structure
|
|
267
267
|
|
|
268
268
|
```
|
|
@@ -277,7 +277,7 @@ SpecWeave learns from corrections. Learnings saved here automatically. Edit or d
|
|
|
277
277
|
**Everything else → subfolders**: `reports/` | `logs/` | `scripts/` | `backups/`
|
|
278
278
|
<!-- SW:END:structure -->
|
|
279
279
|
|
|
280
|
-
<!-- SW:SECTION:taskformat version="1.0.
|
|
280
|
+
<!-- SW:SECTION:taskformat version="1.0.205" -->
|
|
281
281
|
## Task Format
|
|
282
282
|
|
|
283
283
|
```markdown
|
|
@@ -287,7 +287,7 @@ SpecWeave learns from corrections. Learnings saved here automatically. Edit or d
|
|
|
287
287
|
```
|
|
288
288
|
<!-- SW:END:taskformat -->
|
|
289
289
|
|
|
290
|
-
<!-- SW:SECTION:secrets version="1.0.
|
|
290
|
+
<!-- SW:SECTION:secrets version="1.0.205" -->
|
|
291
291
|
## Secrets Check
|
|
292
292
|
|
|
293
293
|
**BEFORE CLI tools**: Check existing config first!
|
|
@@ -301,7 +301,7 @@ gh auth status
|
|
|
301
301
|
**SECURITY**: NEVER use `grep TOKEN .env` without `-q` flag - it exposes credentials in terminal!
|
|
302
302
|
<!-- SW:END:secrets -->
|
|
303
303
|
|
|
304
|
-
<!-- SW:SECTION:syncing version="1.0.
|
|
304
|
+
<!-- SW:SECTION:syncing version="1.0.205" -->
|
|
305
305
|
## External Sync (GitHub/JIRA/ADO)
|
|
306
306
|
|
|
307
307
|
**Commands**: `/sw-github:sync {id}` (issues) | `/sw:sync-specs` (living docs only)
|
|
@@ -311,7 +311,7 @@ gh auth status
|
|
|
311
311
|
**Config**: Set `sync.github.enabled: true` + `canUpdateExternalItems: true` in config.json
|
|
312
312
|
<!-- SW:END:syncing -->
|
|
313
313
|
|
|
314
|
-
<!-- SW:SECTION:testing version="1.0.
|
|
314
|
+
<!-- SW:SECTION:testing version="1.0.205" -->
|
|
315
315
|
## Testing
|
|
316
316
|
|
|
317
317
|
BDD in tasks.md | Unit >80% | `.test.ts` (Vitest)
|
|
@@ -323,7 +323,7 @@ vi.mock('./module', () => ({ func: mockFn }));
|
|
|
323
323
|
```
|
|
324
324
|
<!-- SW:END:testing -->
|
|
325
325
|
|
|
326
|
-
<!-- SW:SECTION:tdd version="1.0.
|
|
326
|
+
<!-- SW:SECTION:tdd version="1.0.205" -->
|
|
327
327
|
## TDD Mode (Test-Driven Development)
|
|
328
328
|
|
|
329
329
|
**When `testing.defaultTestMode: "TDD"` is configured**, follow RED-GREEN-REFACTOR discipline:
|
|
@@ -384,7 +384,7 @@ When TDD is enabled, tasks include phase markers:
|
|
|
384
384
|
**Rule**: Complete dependencies BEFORE dependent tasks (RED before GREEN).
|
|
385
385
|
<!-- SW:END:tdd -->
|
|
386
386
|
|
|
387
|
-
<!-- SW:SECTION:api version="1.0.
|
|
387
|
+
<!-- SW:SECTION:api version="1.0.205" -->
|
|
388
388
|
## API Development (OpenAPI-First)
|
|
389
389
|
|
|
390
390
|
**For API projects only.** Commands: `/sw:api-docs --all` | `--openapi` | `--postman` | `--validate`
|
|
@@ -392,13 +392,13 @@ When TDD is enabled, tasks include phase markers:
|
|
|
392
392
|
Enable in config: `{"apiDocs":{"enabled":true,"openApiPath":"openapi.yaml"}}`
|
|
393
393
|
<!-- SW:END:api -->
|
|
394
394
|
|
|
395
|
-
<!-- SW:SECTION:limits version="1.0.
|
|
395
|
+
<!-- SW:SECTION:limits version="1.0.205" -->
|
|
396
396
|
## Limits
|
|
397
397
|
|
|
398
398
|
**Max 1500 lines/file** — extract before adding
|
|
399
399
|
<!-- SW:END:limits -->
|
|
400
400
|
|
|
401
|
-
<!-- SW:SECTION:troubleshooting version="1.0.
|
|
401
|
+
<!-- SW:SECTION:troubleshooting version="1.0.205" -->
|
|
402
402
|
## Troubleshooting
|
|
403
403
|
|
|
404
404
|
| Issue | Fix |
|
|
@@ -411,7 +411,7 @@ Enable in config: `{"apiDocs":{"enabled":true,"openApiPath":"openapi.yaml"}}`
|
|
|
411
411
|
| Session stuck | Kill + `rm -f .specweave/state/*.lock` + restart |
|
|
412
412
|
<!-- SW:END:troubleshooting -->
|
|
413
413
|
|
|
414
|
-
<!-- SW:SECTION:lazyloading version="1.0.
|
|
414
|
+
<!-- SW:SECTION:lazyloading version="1.0.205" -->
|
|
415
415
|
## Plugin Auto-Loading
|
|
416
416
|
|
|
417
417
|
Plugins load automatically based on project type and keywords. Manual install if needed:
|
|
@@ -425,7 +425,7 @@ export SPECWEAVE_DISABLE_AUTO_LOAD=1 # Disable auto-load
|
|
|
425
425
|
**Token savings**: Core ~3-5K tokens vs all plugins ~60K+
|
|
426
426
|
<!-- SW:END:lazyloading -->
|
|
427
427
|
|
|
428
|
-
<!-- SW:SECTION:principles version="1.0.
|
|
428
|
+
<!-- SW:SECTION:principles version="1.0.205" -->
|
|
429
429
|
## Principles
|
|
430
430
|
|
|
431
431
|
### SpecWeave Principles
|
|
@@ -441,7 +441,7 @@ export SPECWEAVE_DISABLE_AUTO_LOAD=1 # Disable auto-load
|
|
|
441
441
|
- **Demand Elegance**: For non-trivial changes, pause and ask "is there a more elegant way?" - but skip this for simple, obvious fixes (don't over-engineer).
|
|
442
442
|
<!-- SW:END:principles -->
|
|
443
443
|
|
|
444
|
-
<!-- SW:SECTION:linking version="1.0.
|
|
444
|
+
<!-- SW:SECTION:linking version="1.0.205" -->
|
|
445
445
|
## Bidirectional Linking
|
|
446
446
|
|
|
447
447
|
Tasks ↔ User Stories auto-linked via AC-IDs: `AC-US1-01` → `US-001`
|
|
@@ -449,7 +449,7 @@ Tasks ↔ User Stories auto-linked via AC-IDs: `AC-US1-01` → `US-001`
|
|
|
449
449
|
Task format: `**AC**: AC-US1-01, AC-US1-02` (CRITICAL for linking)
|
|
450
450
|
<!-- SW:END:linking -->
|
|
451
451
|
|
|
452
|
-
<!-- SW:SECTION:mcp version="1.0.
|
|
452
|
+
<!-- SW:SECTION:mcp version="1.0.205" -->
|
|
453
453
|
## External Services
|
|
454
454
|
|
|
455
455
|
**Priority**: CLI tools first (simpler) → MCP for complex integrations
|
|
@@ -471,7 +471,7 @@ claude mcp add --transport stdio postgres -- npx -y @modelcontextprotocol/server
|
|
|
471
471
|
MCP supports lazy-loading (auto mode) - tools load on-demand when >10% context.
|
|
472
472
|
<!-- SW:END:mcp -->
|
|
473
473
|
|
|
474
|
-
<!-- SW:SECTION:auto version="1.0.
|
|
474
|
+
<!-- SW:SECTION:auto version="1.0.205" -->
|
|
475
475
|
## Auto Mode
|
|
476
476
|
|
|
477
477
|
**Commands**: `/sw:auto` (start) | `/sw:auto-status` (check) | `/sw:cancel-auto` (emergency only)
|
|
@@ -488,7 +488,7 @@ MCP supports lazy-loading (auto mode) - tools load on-demand when >10% context.
|
|
|
488
488
|
**STOP & ASK** if: Spec conflicts | Task unnecessary | Requirement ambiguous
|
|
489
489
|
<!-- SW:END:auto -->
|
|
490
490
|
|
|
491
|
-
<!-- SW:SECTION:docs version="1.0.
|
|
491
|
+
<!-- SW:SECTION:docs version="1.0.205" -->
|
|
492
492
|
## Docs
|
|
493
493
|
|
|
494
494
|
[spec-weave.com](https://spec-weave.com)
|
package/bin/specweave.js
CHANGED
|
@@ -409,6 +409,34 @@ program
|
|
|
409
409
|
});
|
|
410
410
|
});
|
|
411
411
|
|
|
412
|
+
// Decision log command - Query structured decision logs
|
|
413
|
+
program
|
|
414
|
+
.command('decision-log')
|
|
415
|
+
.description('Query structured decision logs from hooks')
|
|
416
|
+
.option('--hook <name>', 'Filter by hook name (stop-auto, stop-reflect)')
|
|
417
|
+
.option('--decision <type>', 'Filter by decision type (approve, block)')
|
|
418
|
+
.option('--since <window>', 'Filter by time window (1h, 24h, 7d)')
|
|
419
|
+
.option('--limit <number>', 'Number of entries to show (default: 20)', '20')
|
|
420
|
+
.option('--json', 'Output raw JSON format')
|
|
421
|
+
.option('--tail', 'Follow log in real-time (like tail -f)')
|
|
422
|
+
.action(async (options) => {
|
|
423
|
+
const { decisionLogCommand, decisionLogTail } = await import('../dist/src/cli/commands/decision-log.js');
|
|
424
|
+
if (options.tail) {
|
|
425
|
+
await decisionLogTail({
|
|
426
|
+
hook: options.hook,
|
|
427
|
+
decision: options.decision
|
|
428
|
+
});
|
|
429
|
+
} else {
|
|
430
|
+
await decisionLogCommand({
|
|
431
|
+
hook: options.hook,
|
|
432
|
+
decision: options.decision,
|
|
433
|
+
since: options.since,
|
|
434
|
+
limit: parseInt(options.limit, 10),
|
|
435
|
+
json: options.json
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
412
440
|
// Status line command - Display current increment progress
|
|
413
441
|
program
|
|
414
442
|
.command('status-line')
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file decision-log.ts
|
|
3
|
+
* @description CLI command to query structured decision logs from hooks
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* specweave decision-log # Show last 20 decisions
|
|
7
|
+
* specweave decision-log --hook stop-auto # Filter by hook
|
|
8
|
+
* specweave decision-log --decision block # Filter by decision type
|
|
9
|
+
* specweave decision-log --since 1h # Filter by time window
|
|
10
|
+
* specweave decision-log --json # Raw JSON output
|
|
11
|
+
* specweave decision-log --limit 50 # Custom limit
|
|
12
|
+
*/
|
|
13
|
+
export interface DecisionLogEntry {
|
|
14
|
+
timestamp: string;
|
|
15
|
+
hook: string;
|
|
16
|
+
decision: 'approve' | 'block';
|
|
17
|
+
reason: string;
|
|
18
|
+
reasonCode: string;
|
|
19
|
+
durationMs: number;
|
|
20
|
+
context: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export interface DecisionLogCommandOptions {
|
|
23
|
+
projectRoot?: string;
|
|
24
|
+
hook?: string;
|
|
25
|
+
decision?: 'approve' | 'block';
|
|
26
|
+
since?: string;
|
|
27
|
+
limit?: number;
|
|
28
|
+
json?: boolean;
|
|
29
|
+
tail?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface DecisionLogResult {
|
|
32
|
+
entries: DecisionLogEntry[];
|
|
33
|
+
format: 'table' | 'json';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* CLI command to query decision logs
|
|
37
|
+
*/
|
|
38
|
+
export declare function decisionLogCommand(options?: DecisionLogCommandOptions): Promise<DecisionLogResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Watch mode for --tail option (follows log file)
|
|
41
|
+
*/
|
|
42
|
+
export declare function decisionLogTail(options?: DecisionLogCommandOptions): Promise<void>;
|
|
43
|
+
//# sourceMappingURL=decision-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-log.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/decision-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAqJD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,iBAAiB,CAAC,CAmC5B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,IAAI,CAAC,CA2Df"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file decision-log.ts
|
|
3
|
+
* @description CLI command to query structured decision logs from hooks
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* specweave decision-log # Show last 20 decisions
|
|
7
|
+
* specweave decision-log --hook stop-auto # Filter by hook
|
|
8
|
+
* specweave decision-log --decision block # Filter by decision type
|
|
9
|
+
* specweave decision-log --since 1h # Filter by time window
|
|
10
|
+
* specweave decision-log --json # Raw JSON output
|
|
11
|
+
* specweave decision-log --limit 50 # Custom limit
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
/**
|
|
17
|
+
* Parse time window string (1h, 24h, 7d) to milliseconds
|
|
18
|
+
*/
|
|
19
|
+
function parseSinceWindow(since) {
|
|
20
|
+
const match = since.match(/^(\d+)(h|d|m)$/);
|
|
21
|
+
if (!match) {
|
|
22
|
+
throw new Error(`Invalid time window format: ${since}. Use 1h, 24h, 7d, etc.`);
|
|
23
|
+
}
|
|
24
|
+
const value = parseInt(match[1], 10);
|
|
25
|
+
const unit = match[2];
|
|
26
|
+
const multipliers = {
|
|
27
|
+
m: 60 * 1000, // minutes
|
|
28
|
+
h: 60 * 60 * 1000, // hours
|
|
29
|
+
d: 24 * 60 * 60 * 1000, // days
|
|
30
|
+
};
|
|
31
|
+
return value * multipliers[unit];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Read and parse the decisions.jsonl file
|
|
35
|
+
*/
|
|
36
|
+
async function readDecisionLog(logPath) {
|
|
37
|
+
if (!fs.existsSync(logPath)) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const content = fs.readFileSync(logPath, 'utf-8');
|
|
41
|
+
const lines = content.split('\n').filter((line) => line.trim());
|
|
42
|
+
const entries = [];
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
try {
|
|
45
|
+
const entry = JSON.parse(line);
|
|
46
|
+
entries.push(entry);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Skip malformed JSON lines
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return entries;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Filter entries based on options
|
|
56
|
+
*/
|
|
57
|
+
function filterEntries(entries, options) {
|
|
58
|
+
let filtered = [...entries];
|
|
59
|
+
// Filter by hook name
|
|
60
|
+
if (options.hook) {
|
|
61
|
+
filtered = filtered.filter((e) => e.hook === options.hook);
|
|
62
|
+
}
|
|
63
|
+
// Filter by decision type
|
|
64
|
+
if (options.decision) {
|
|
65
|
+
filtered = filtered.filter((e) => e.decision === options.decision);
|
|
66
|
+
}
|
|
67
|
+
// Filter by time window
|
|
68
|
+
if (options.since) {
|
|
69
|
+
const windowMs = parseSinceWindow(options.since);
|
|
70
|
+
const cutoff = Date.now() - windowMs;
|
|
71
|
+
filtered = filtered.filter((e) => {
|
|
72
|
+
const entryTime = new Date(e.timestamp).getTime();
|
|
73
|
+
return entryTime >= cutoff;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return filtered;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Sort entries by timestamp (most recent first) and apply limit
|
|
80
|
+
*/
|
|
81
|
+
function sortAndLimit(entries, limit) {
|
|
82
|
+
return entries
|
|
83
|
+
.sort((a, b) => {
|
|
84
|
+
const timeA = new Date(a.timestamp).getTime();
|
|
85
|
+
const timeB = new Date(b.timestamp).getTime();
|
|
86
|
+
return timeB - timeA; // Most recent first
|
|
87
|
+
})
|
|
88
|
+
.slice(0, limit);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Format a single entry for table display
|
|
92
|
+
*/
|
|
93
|
+
function formatEntryForTable(entry) {
|
|
94
|
+
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
95
|
+
const hook = entry.hook.padEnd(15);
|
|
96
|
+
const decision = entry.decision === 'approve'
|
|
97
|
+
? chalk.green('✓ approve')
|
|
98
|
+
: chalk.red('✗ block');
|
|
99
|
+
const decisionPadded = decision.padEnd(20);
|
|
100
|
+
const reason = entry.reason.substring(0, 40);
|
|
101
|
+
const duration = `${entry.durationMs}ms`;
|
|
102
|
+
return `${timestamp.padEnd(22)} ${hook} ${decisionPadded} ${duration.padEnd(10)} ${reason}`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Display entries in table format
|
|
106
|
+
*/
|
|
107
|
+
function displayTable(entries) {
|
|
108
|
+
if (entries.length === 0) {
|
|
109
|
+
console.log(chalk.yellow('No decision log entries found.'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log('\n' + chalk.bold.underline('Decision Log') + '\n');
|
|
113
|
+
console.log(chalk.bold('Timestamp'.padEnd(22) +
|
|
114
|
+
' Hook'.padEnd(16) +
|
|
115
|
+
' Decision'.padEnd(21) +
|
|
116
|
+
' Duration'.padEnd(11) +
|
|
117
|
+
' Reason'));
|
|
118
|
+
console.log(chalk.dim('─'.repeat(100)));
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
console.log(formatEntryForTable(entry));
|
|
121
|
+
}
|
|
122
|
+
console.log('\n' + chalk.dim(`Showing ${entries.length} entries`) + '\n');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Display entries in JSON format
|
|
126
|
+
*/
|
|
127
|
+
function displayJson(entries) {
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
console.log(JSON.stringify(entry));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* CLI command to query decision logs
|
|
134
|
+
*/
|
|
135
|
+
export async function decisionLogCommand(options = {}) {
|
|
136
|
+
const { projectRoot = process.cwd(), hook, decision, since, limit = 20, json = false, } = options;
|
|
137
|
+
// Determine log file path
|
|
138
|
+
const logPath = path.join(projectRoot, '.specweave', 'logs', 'decisions.jsonl');
|
|
139
|
+
// Read all entries
|
|
140
|
+
const allEntries = await readDecisionLog(logPath);
|
|
141
|
+
// Apply filters
|
|
142
|
+
const filtered = filterEntries(allEntries, { hook, decision, since });
|
|
143
|
+
// Sort and limit
|
|
144
|
+
const result = sortAndLimit(filtered, limit);
|
|
145
|
+
// Display output (only when running as CLI, not when testing)
|
|
146
|
+
if (!options.projectRoot || options.projectRoot === process.cwd()) {
|
|
147
|
+
if (json) {
|
|
148
|
+
displayJson(result);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
displayTable(result);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
entries: result,
|
|
156
|
+
format: json ? 'json' : 'table',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Watch mode for --tail option (follows log file)
|
|
161
|
+
*/
|
|
162
|
+
export async function decisionLogTail(options = {}) {
|
|
163
|
+
const { projectRoot = process.cwd(), hook, decision } = options;
|
|
164
|
+
const logPath = path.join(projectRoot, '.specweave', 'logs', 'decisions.jsonl');
|
|
165
|
+
console.log(chalk.yellow(`Watching ${logPath} for new entries...`));
|
|
166
|
+
console.log(chalk.dim('Press Ctrl+C to stop.\n'));
|
|
167
|
+
// Track file position
|
|
168
|
+
let lastSize = 0;
|
|
169
|
+
if (fs.existsSync(logPath)) {
|
|
170
|
+
const stats = fs.statSync(logPath);
|
|
171
|
+
lastSize = stats.size;
|
|
172
|
+
}
|
|
173
|
+
// Watch for changes
|
|
174
|
+
const watcher = fs.watch(path.dirname(logPath), (event, filename) => {
|
|
175
|
+
if (filename === 'decisions.jsonl') {
|
|
176
|
+
if (!fs.existsSync(logPath))
|
|
177
|
+
return;
|
|
178
|
+
const stats = fs.statSync(logPath);
|
|
179
|
+
if (stats.size > lastSize) {
|
|
180
|
+
// Read new content
|
|
181
|
+
const fd = fs.openSync(logPath, 'r');
|
|
182
|
+
const buffer = Buffer.alloc(stats.size - lastSize);
|
|
183
|
+
fs.readSync(fd, buffer, 0, buffer.length, lastSize);
|
|
184
|
+
fs.closeSync(fd);
|
|
185
|
+
const newContent = buffer.toString('utf-8');
|
|
186
|
+
const lines = newContent.split('\n').filter((l) => l.trim());
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
try {
|
|
189
|
+
const entry = JSON.parse(line);
|
|
190
|
+
// Apply filters
|
|
191
|
+
if (hook && entry.hook !== hook)
|
|
192
|
+
continue;
|
|
193
|
+
if (decision && entry.decision !== decision)
|
|
194
|
+
continue;
|
|
195
|
+
console.log(formatEntryForTable(entry));
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Skip malformed lines
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
lastSize = stats.size;
|
|
202
|
+
}
|
|
203
|
+
else if (stats.size < lastSize) {
|
|
204
|
+
// File was truncated/rotated
|
|
205
|
+
lastSize = stats.size;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
// Keep process alive
|
|
210
|
+
process.on('SIGINT', () => {
|
|
211
|
+
watcher.close();
|
|
212
|
+
console.log('\nStopped watching.');
|
|
213
|
+
process.exit(0);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=decision-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decision-log.js","sourceRoot":"","sources":["../../../../src/cli/commands/decision-log.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.206",
|
|
4
4
|
"description": "Spec-driven development framework for Claude Code. AI-native workflow with living documentation, intelligent agents, and multilingual support (9 languages). Enterprise-grade traceability with permanent specs and temporary increments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# stop-reflect.sh - Session Reflection Hook (v3.
|
|
2
|
+
# stop-reflect.sh - Session Reflection Hook (v3.1 - Structured Decision Logging)
|
|
3
3
|
#
|
|
4
|
-
# ARCHITECTURE (v3.
|
|
4
|
+
# ARCHITECTURE (v3.1):
|
|
5
5
|
# - Uses specweave CLI (TypeScript) for reflection
|
|
6
6
|
# - Learnings go to CLAUDE.md under "## Skill Memories" section
|
|
7
7
|
# - User can disable via config: { "reflect": { "enabled": false } }
|
|
8
|
+
# - Structured decision logging to .specweave/logs/decisions.jsonl
|
|
8
9
|
#
|
|
9
10
|
# This hook ALWAYS approves (never blocks sessions) but may trigger
|
|
10
11
|
# background learning extraction if enabled.
|
|
11
12
|
|
|
12
13
|
set +e # Never fail - this is a non-critical hook
|
|
13
14
|
|
|
15
|
+
# Capture start time for duration tracking (macOS doesn't support %N, fallback to seconds only)
|
|
16
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
17
|
+
_START_TIME_MS=$(($(date +%s) * 1000))
|
|
18
|
+
else
|
|
19
|
+
_START_TIME_MS=$(($(date +%s) * 1000 + $(date +%N 2>/dev/null | cut -c1-3 || echo "0")))
|
|
20
|
+
fi
|
|
21
|
+
|
|
14
22
|
# Read input from stdin (required by Claude Code hooks)
|
|
15
23
|
INPUT=$(cat)
|
|
16
24
|
|
|
@@ -23,6 +31,23 @@ CONFIG_FILE="$PROJECT_ROOT/.specweave/config.json"
|
|
|
23
31
|
LOGS_DIR="$PROJECT_ROOT/.specweave/logs/reflect"
|
|
24
32
|
STATE_DIR="$PROJECT_ROOT/.specweave/state"
|
|
25
33
|
|
|
34
|
+
# Source the shared decision logging utility
|
|
35
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
if [ -f "$SCRIPT_DIR/log-decision.sh" ]; then
|
|
37
|
+
source "$SCRIPT_DIR/log-decision.sh"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Get duration in milliseconds
|
|
41
|
+
_get_duration_ms() {
|
|
42
|
+
local end_time_ms
|
|
43
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
44
|
+
end_time_ms=$(($(date +%s) * 1000))
|
|
45
|
+
else
|
|
46
|
+
end_time_ms=$(($(date +%s) * 1000 + $(date +%N 2>/dev/null | cut -c1-3 || echo "0")))
|
|
47
|
+
fi
|
|
48
|
+
echo $((end_time_ms - _START_TIME_MS))
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
# Ensure logs directory exists
|
|
27
52
|
mkdir -p "$LOGS_DIR" 2>/dev/null || true
|
|
28
53
|
|
|
@@ -44,13 +69,45 @@ log_reflect() {
|
|
|
44
69
|
echo "[$timestamp] [$level] $*" >> "$log_file" 2>/dev/null || true
|
|
45
70
|
}
|
|
46
71
|
|
|
72
|
+
# Log decision with context for structured logging
|
|
73
|
+
log_reflect_decision() {
|
|
74
|
+
local reason_code="$1"
|
|
75
|
+
local reason="$2"
|
|
76
|
+
local transcript_lines="${3:-0}"
|
|
77
|
+
local reflection_enabled="${4:-true}"
|
|
78
|
+
local learnings_extracted="${5:-0}"
|
|
79
|
+
local learning_categories_json="${6:-[]}"
|
|
80
|
+
|
|
81
|
+
if type log_decision &>/dev/null; then
|
|
82
|
+
local context_json
|
|
83
|
+
context_json=$(jq -n \
|
|
84
|
+
--argjson transcriptLines "$transcript_lines" \
|
|
85
|
+
--argjson reflectionEnabled "$reflection_enabled" \
|
|
86
|
+
--argjson learningsExtracted "$learnings_extracted" \
|
|
87
|
+
--argjson learningCategories "$learning_categories_json" \
|
|
88
|
+
--arg exitReason "$reason_code" \
|
|
89
|
+
--arg triggerReason "session_end" \
|
|
90
|
+
'{
|
|
91
|
+
transcriptLines: $transcriptLines,
|
|
92
|
+
reflectionEnabled: $reflectionEnabled,
|
|
93
|
+
learningsExtracted: $learningsExtracted,
|
|
94
|
+
learningCategories: $learningCategories,
|
|
95
|
+
exitReason: $exitReason,
|
|
96
|
+
triggerReason: $triggerReason
|
|
97
|
+
}')
|
|
98
|
+
|
|
99
|
+
log_decision "stop-reflect" "approve" "$reason_code" "$reason" "$context_json" "$(_get_duration_ms)"
|
|
100
|
+
fi
|
|
101
|
+
}
|
|
102
|
+
|
|
47
103
|
# Check if reflection is enabled
|
|
48
104
|
is_reflect_enabled() {
|
|
49
105
|
if [ ! -f "$CONFIG_FILE" ]; then
|
|
50
106
|
return 0 # Default to enabled if no config
|
|
51
107
|
fi
|
|
52
108
|
|
|
53
|
-
|
|
109
|
+
# Note: can't use // true because jq treats false as falsy
|
|
110
|
+
local enabled=$(jq -r 'if .reflect.enabled == false then "false" else "true" end' "$CONFIG_FILE" 2>/dev/null)
|
|
54
111
|
[ "$enabled" = "true" ]
|
|
55
112
|
}
|
|
56
113
|
|
|
@@ -131,6 +188,82 @@ run_reflection() {
|
|
|
131
188
|
return 0
|
|
132
189
|
}
|
|
133
190
|
|
|
191
|
+
# Run reflection with structured logging (synchronous for test purposes, captures learnings)
|
|
192
|
+
run_reflection_with_logging() {
|
|
193
|
+
local transcript="$1"
|
|
194
|
+
local transcript_lines="$2"
|
|
195
|
+
local learnings_extracted=0
|
|
196
|
+
local learning_categories="[]"
|
|
197
|
+
local exit_reason="nothing_to_learn"
|
|
198
|
+
local reason_msg="No learnings extracted"
|
|
199
|
+
|
|
200
|
+
log_reflect "info" "Starting reflection ($transcript_lines lines)"
|
|
201
|
+
|
|
202
|
+
# Use specweave CLI (TypeScript implementation)
|
|
203
|
+
if command -v specweave >/dev/null 2>&1; then
|
|
204
|
+
# Create a temp file to capture output
|
|
205
|
+
local temp_output
|
|
206
|
+
temp_output=$(mktemp)
|
|
207
|
+
|
|
208
|
+
(
|
|
209
|
+
cd "$PROJECT_ROOT"
|
|
210
|
+
run_with_timeout 60 specweave reflect-stop "$transcript" --silent 2>&1 | tee "$temp_output" >> "$LOGS_DIR/auto-reflect.log"
|
|
211
|
+
local result=$?
|
|
212
|
+
|
|
213
|
+
# Parse learnings from output
|
|
214
|
+
if [ -f "$temp_output" ]; then
|
|
215
|
+
# Try to extract learnings count from output
|
|
216
|
+
local count_line
|
|
217
|
+
count_line=$(grep -i "learnings extracted:" "$temp_output" 2>/dev/null | head -1)
|
|
218
|
+
if [ -n "$count_line" ]; then
|
|
219
|
+
learnings_extracted=$(echo "$count_line" | grep -oE '[0-9]+' | head -1)
|
|
220
|
+
learnings_extracted=${learnings_extracted:-0}
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# Try to extract categories from output
|
|
224
|
+
local cat_line
|
|
225
|
+
cat_line=$(grep -i "categories:" "$temp_output" 2>/dev/null | head -1)
|
|
226
|
+
if [ -n "$cat_line" ]; then
|
|
227
|
+
# Convert "Categories: devops, logging" to JSON array
|
|
228
|
+
local cats
|
|
229
|
+
cats=$(echo "$cat_line" | sed 's/.*[Cc]ategories:[[:space:]]*//' | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' | jq -R . | jq -s .)
|
|
230
|
+
learning_categories=${cats:-"[]"}
|
|
231
|
+
fi
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
# Determine exit reason based on result and learnings
|
|
235
|
+
if [ $result -eq 124 ]; then
|
|
236
|
+
exit_reason="timeout"
|
|
237
|
+
reason_msg="Reflection timed out"
|
|
238
|
+
log_reflect "warn" "Reflection timed out"
|
|
239
|
+
elif [ $result -ne 0 ]; then
|
|
240
|
+
exit_reason="error"
|
|
241
|
+
reason_msg="Reflection error (exit $result)"
|
|
242
|
+
log_reflect "warn" "Reflection completed with exit code $result"
|
|
243
|
+
elif [ "$learnings_extracted" -gt 0 ]; then
|
|
244
|
+
exit_reason="learnings_saved"
|
|
245
|
+
reason_msg="Extracted $learnings_extracted learnings"
|
|
246
|
+
log_reflect "info" "Reflection completed successfully"
|
|
247
|
+
else
|
|
248
|
+
exit_reason="nothing_to_learn"
|
|
249
|
+
reason_msg="No learnings to extract"
|
|
250
|
+
log_reflect "info" "Reflection completed - no learnings"
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Log the decision with full context
|
|
254
|
+
log_reflect_decision "$exit_reason" "$reason_msg" "$transcript_lines" true "$learnings_extracted" "$learning_categories"
|
|
255
|
+
|
|
256
|
+
rm -f "$temp_output" 2>/dev/null
|
|
257
|
+
) &
|
|
258
|
+
log_reflect "info" "Reflection started in background"
|
|
259
|
+
else
|
|
260
|
+
log_reflect "warn" "specweave CLI not found"
|
|
261
|
+
log_reflect_decision "error" "specweave CLI not found" "$transcript_lines" true 0 "[]"
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
return 0
|
|
265
|
+
}
|
|
266
|
+
|
|
134
267
|
# Main execution
|
|
135
268
|
main() {
|
|
136
269
|
# SILENT approve - prevents UI feedback loops
|
|
@@ -139,14 +272,45 @@ main() {
|
|
|
139
272
|
# Always cleanup ephemeral session state
|
|
140
273
|
cleanup_session_state
|
|
141
274
|
|
|
275
|
+
# Check if reflection is enabled first
|
|
276
|
+
local reflect_enabled=true
|
|
277
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
278
|
+
local enabled_val
|
|
279
|
+
# Note: can't use // true because jq treats false as falsy
|
|
280
|
+
# Use if-then-else to properly handle boolean false
|
|
281
|
+
enabled_val=$(jq -r 'if .reflect.enabled == false then "false" else "true" end' "$CONFIG_FILE" 2>/dev/null)
|
|
282
|
+
if [ "$enabled_val" = "false" ]; then
|
|
283
|
+
reflect_enabled=false
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
|
|
142
287
|
# Quick bail if no transcript
|
|
143
288
|
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
289
|
+
log_reflect_decision "no_transcript" "No transcript available" 0 "$reflect_enabled" 0 "[]"
|
|
290
|
+
echo "$silent_approve"
|
|
291
|
+
exit 0
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
# Get transcript line count
|
|
295
|
+
local transcript_lines
|
|
296
|
+
transcript_lines=$(wc -l < "$TRANSCRIPT_PATH" 2>/dev/null | tr -d ' ') || transcript_lines=0
|
|
297
|
+
|
|
298
|
+
# Check if reflection is disabled
|
|
299
|
+
if [ "$reflect_enabled" = "false" ]; then
|
|
300
|
+
log_reflect_decision "disabled" "Reflection disabled in config" "$transcript_lines" false 0 "[]"
|
|
301
|
+
echo "$silent_approve"
|
|
302
|
+
exit 0
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
# Check if transcript is too short
|
|
306
|
+
if [ "$transcript_lines" -lt 10 ]; then
|
|
307
|
+
log_reflect_decision "transcript_too_short" "Transcript too short ($transcript_lines lines)" "$transcript_lines" true 0 "[]"
|
|
144
308
|
echo "$silent_approve"
|
|
145
309
|
exit 0
|
|
146
310
|
fi
|
|
147
311
|
|
|
148
|
-
# Run reflection (async, non-blocking)
|
|
149
|
-
|
|
312
|
+
# Run reflection (async, non-blocking) and capture learnings
|
|
313
|
+
run_reflection_with_logging "$TRANSCRIPT_PATH" "$transcript_lines"
|
|
150
314
|
|
|
151
315
|
# Always approve
|
|
152
316
|
echo "$silent_approve"
|
|
@@ -14,6 +14,29 @@ model: opus
|
|
|
14
14
|
|
|
15
15
|
Expert in generating professional-quality images using multi-provider resilience: **Pollinations.ai** (primary) with **Stable Horde** (fallback) - both FREE, no API keys required.
|
|
16
16
|
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🛑 STEP 1: RUN THIS BASH COMMAND FIRST (MANDATORY - NO EXCEPTIONS)
|
|
20
|
+
|
|
21
|
+
**YOU MUST EXECUTE THIS BASH COMMAND BEFORE DOING ANYTHING ELSE.**
|
|
22
|
+
|
|
23
|
+
This is not optional. Do not skip. Do not generate URLs first. **RUN THIS FIRST:**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
TEMP=$(mktemp) && curl -s -L -o "$TEMP" --max-time 10 "https://image.pollinations.ai/prompt/test?width=64&height=64&nologo=true" 2>/dev/null && TYPE=$(file -b "$TEMP" | cut -d',' -f1) && rm -f "$TEMP" && if [[ "$TYPE" == "PNG image data" ]] || [[ "$TYPE" == "JPEG image data" ]]; then echo "PROVIDER=pollinations"; else echo "PROVIDER=stablehorde"; echo "REASON=Got $TYPE instead of image"; fi
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Based on Output:
|
|
30
|
+
|
|
31
|
+
| Output | What To Do |
|
|
32
|
+
|--------|------------|
|
|
33
|
+
| `PROVIDER=pollinations` | ✅ Use Pollinations.ai URLs below |
|
|
34
|
+
| `PROVIDER=stablehorde` | 🔄 **SKIP Pollinations entirely** → Use Stable Horde API (see fallback section) |
|
|
35
|
+
|
|
36
|
+
**If you did NOT run the Bash command first, STOP and run it now.**
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
17
40
|
## ⚠️ CRITICAL: Health Check First (MANDATORY)
|
|
18
41
|
|
|
19
42
|
**BEFORE generating any image, ALWAYS run this CONTENT-BASED health check:**
|
|
@@ -107,45 +107,70 @@
|
|
|
107
107
|
|
|
108
108
|
**Claude Code has built-in orchestration features. Non-Claude tools must implement these manually.**
|
|
109
109
|
|
|
110
|
-
### 1. Plan Mode Default
|
|
110
|
+
### 1. Plan Mode Default (Use SpecWeave Increments!)
|
|
111
111
|
|
|
112
|
-
**Claude Code**: Has `EnterPlanMode` tool
|
|
112
|
+
**Claude Code**: Has `EnterPlanMode` tool → triggers `/sw:increment` workflow automatically.
|
|
113
113
|
|
|
114
|
-
**Non-Claude Tools -
|
|
114
|
+
**Non-Claude Tools - Use SpecWeave Increment Structure:**
|
|
115
115
|
```
|
|
116
116
|
BEFORE implementing ANY non-trivial task (3+ steps):
|
|
117
117
|
|
|
118
118
|
1. STOP - Don't start coding immediately
|
|
119
|
-
2. Create `.specweave/increments/XXXX-feature
|
|
120
|
-
3.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
2. Create increment folder: `.specweave/increments/XXXX-feature/`
|
|
120
|
+
3. Create the 3 required files:
|
|
121
|
+
- spec.md → WHAT & WHY (user stories, acceptance criteria)
|
|
122
|
+
- plan.md → HOW (architecture, approach, risks)
|
|
123
|
+
- tasks.md → Task checklist with test plans
|
|
124
|
+
4. GET USER APPROVAL before implementing
|
|
124
125
|
|
|
125
126
|
If something goes sideways during implementation:
|
|
126
127
|
→ STOP and re-plan (don't keep pushing)
|
|
127
|
-
→ Update plan.md with revised approach
|
|
128
|
+
→ Update spec.md/plan.md with revised approach
|
|
128
129
|
→ Get approval again if scope changed
|
|
129
130
|
```
|
|
130
131
|
|
|
131
|
-
**Planning
|
|
132
|
+
**SpecWeave Planning Files:**
|
|
133
|
+
|
|
134
|
+
**spec.md** (WHAT & WHY):
|
|
135
|
+
```markdown
|
|
136
|
+
---
|
|
137
|
+
increment: 0001-feature-name
|
|
138
|
+
title: "Feature Title"
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### US-001: User Story Title
|
|
142
|
+
**Project**: my-app # ← MANDATORY! Get from: specweave context projects
|
|
143
|
+
|
|
144
|
+
**As a** [user type]
|
|
145
|
+
**I want** [goal]
|
|
146
|
+
**So that** [benefit]
|
|
147
|
+
|
|
148
|
+
**Acceptance Criteria**:
|
|
149
|
+
- [ ] **AC-US1-01**: [Criterion 1]
|
|
150
|
+
- [ ] **AC-US1-02**: [Criterion 2]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**plan.md** (HOW):
|
|
132
154
|
```markdown
|
|
133
155
|
# Plan: Feature Name
|
|
134
156
|
|
|
135
157
|
## Approach
|
|
136
|
-
[High-level approach
|
|
137
|
-
|
|
138
|
-
## Steps
|
|
139
|
-
1. [ ] Step one
|
|
140
|
-
2. [ ] Step two (depends on: #1)
|
|
141
|
-
3. [ ] Step three
|
|
158
|
+
[High-level architecture/approach]
|
|
142
159
|
|
|
143
|
-
## Risks
|
|
160
|
+
## Risks & Decisions
|
|
144
161
|
- [ ] Decision: [question needing user input]
|
|
145
162
|
- Risk: [potential issue and mitigation]
|
|
163
|
+
```
|
|
146
164
|
|
|
147
|
-
|
|
148
|
-
|
|
165
|
+
**tasks.md** (Checklist):
|
|
166
|
+
```markdown
|
|
167
|
+
### T-001: Task Title
|
|
168
|
+
**User Story**: US-001
|
|
169
|
+
**Satisfies ACs**: AC-US1-01
|
|
170
|
+
**Status**: [ ] pending
|
|
171
|
+
|
|
172
|
+
**Test Plan** (BDD):
|
|
173
|
+
- Given [context] → When [action] → Then [result]
|
|
149
174
|
```
|
|
150
175
|
|
|
151
176
|
### 2. Subagent Strategy (Parallel Execution)
|
|
@@ -304,9 +329,9 @@ git diff # Review what actually changed
|
|
|
304
329
|
|
|
305
330
|
| Capability | Claude Code | Non-Claude Tools |
|
|
306
331
|
|------------|-------------|------------------|
|
|
307
|
-
| **Plan Mode** | `EnterPlanMode`
|
|
332
|
+
| **Plan Mode** | `EnterPlanMode` → `/sw:increment` | Manual: Create spec.md + plan.md + tasks.md |
|
|
308
333
|
| **Subagents** | `Task` tool spawns parallel agents | Manual: Split work, parallel prompts |
|
|
309
|
-
| **Verification** | PostToolUse hooks validate | Manual: Run tests, check checklist |
|
|
334
|
+
| **Verification** | PostToolUse hooks validate | Manual: Run tests, check AC checklist |
|
|
310
335
|
| **Hooks** | Auto-run on events | YOU must mimic (see below) |
|
|
311
336
|
| **Task sync** | Automatic AC updates | Manual: Edit tasks.md + spec.md |
|
|
312
337
|
| **Commands** | Slash syntax works | Read command .md, follow manually |
|