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 CHANGED
@@ -1,4 +1,4 @@
1
- <!-- SW:META template="claude" version="1.0.203" sections="header,start,autodetect,metarule,rules,workflow,reflect,context,structure,taskformat,secrets,syncing,testing,tdd,api,limits,troubleshooting,lazyloading,principles,linking,mcp,auto,docs" -->
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.202" -->
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.202" -->
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.202" -->
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.203" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.203" -->
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.202" -->
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.203" -->
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.202" -->
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.202" -->
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.202" -->
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.202" -->
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.205",
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.0 - Uses TypeScript)
2
+ # stop-reflect.sh - Session Reflection Hook (v3.1 - Structured Decision Logging)
3
3
  #
4
- # ARCHITECTURE (v3.0):
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
- local enabled=$(jq -r '.reflect.enabled // true' "$CONFIG_FILE" 2>/dev/null)
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
- run_reflection "$TRANSCRIPT_PATH"
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 that automatically enters planning state for complex tasks.
112
+ **Claude Code**: Has `EnterPlanMode` tool triggers `/sw:increment` workflow automatically.
113
113
 
114
- **Non-Claude Tools - Manual Planning Protocol:**
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/plan.md`
120
- 3. List all steps required
121
- 4. Identify dependencies between steps
122
- 5. Note architectural decisions needed
123
- 6. GET USER APPROVAL before implementing
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 Template (plan.md):**
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 description]
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/Decisions
160
+ ## Risks & Decisions
144
161
  - [ ] Decision: [question needing user input]
145
162
  - Risk: [potential issue and mitigation]
163
+ ```
146
164
 
147
- ## Approval
148
- - [ ] User approved this plan
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` tool (automatic) | Manual: Create plan.md, get approval |
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 |