specweave 0.33.2 → 0.33.4
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 +133 -19
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +120 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.js +276 -0
- package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +4 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +13 -3
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +97 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.js +274 -0
- package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts +113 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.d.ts.map +1 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.js +254 -0
- package/dist/plugins/specweave-jira/lib/per-us-sync.js.map +1 -0
- package/dist/src/cli/cleanup-zombies.js +8 -5
- package/dist/src/cli/cleanup-zombies.js.map +1 -1
- package/dist/src/config/types.d.ts +203 -1208
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/core/config/config-manager.d.ts.map +1 -1
- package/dist/src/core/config/config-manager.js +58 -0
- package/dist/src/core/config/config-manager.js.map +1 -1
- package/dist/src/core/config/types.d.ts +80 -0
- package/dist/src/core/config/types.d.ts.map +1 -1
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/living-docs/cross-project-sync.d.ts +87 -15
- package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/cross-project-sync.js +147 -28
- package/dist/src/core/living-docs/cross-project-sync.js.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +26 -22
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/living-docs/types.d.ts +24 -3
- package/dist/src/core/living-docs/types.d.ts.map +1 -1
- package/dist/src/core/types/config.d.ts +79 -0
- package/dist/src/core/types/config.d.ts.map +1 -1
- package/dist/src/core/types/config.js.map +1 -1
- package/dist/src/importers/jira-importer.d.ts +10 -0
- package/dist/src/importers/jira-importer.d.ts.map +1 -1
- package/dist/src/importers/jira-importer.js +55 -5
- package/dist/src/importers/jira-importer.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +33 -140
- package/dist/src/init/architecture/types.d.ts.map +1 -1
- package/dist/src/init/compliance/types.d.ts +30 -27
- package/dist/src/init/compliance/types.d.ts.map +1 -1
- package/dist/src/init/repo/types.d.ts +11 -34
- package/dist/src/init/repo/types.d.ts.map +1 -1
- package/dist/src/init/research/src/config/types.d.ts +15 -82
- package/dist/src/init/research/src/config/types.d.ts.map +1 -1
- package/dist/src/init/research/types.d.ts +38 -93
- package/dist/src/init/research/types.d.ts.map +1 -1
- package/dist/src/init/team/types.d.ts +4 -42
- package/dist/src/init/team/types.d.ts.map +1 -1
- package/dist/src/sync/closure-metrics.d.ts +102 -0
- package/dist/src/sync/closure-metrics.d.ts.map +1 -0
- package/dist/src/sync/closure-metrics.js +267 -0
- package/dist/src/sync/closure-metrics.js.map +1 -0
- package/dist/src/sync/sync-coordinator.d.ts +49 -0
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +399 -37
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/dist/src/utils/notification-constants.d.ts +85 -0
- package/dist/src/utils/notification-constants.d.ts.map +1 -0
- package/dist/src/utils/notification-constants.js +129 -0
- package/dist/src/utils/notification-constants.js.map +1 -0
- package/dist/src/utils/platform-utils.d.ts +13 -3
- package/dist/src/utils/platform-utils.d.ts.map +1 -1
- package/dist/src/utils/platform-utils.js +17 -6
- package/dist/src/utils/platform-utils.js.map +1 -1
- package/dist/src/utils/project-resolver.d.ts +156 -0
- package/dist/src/utils/project-resolver.d.ts.map +1 -0
- package/dist/src/utils/project-resolver.js +587 -0
- package/dist/src/utils/project-resolver.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/commands/specweave-increment.md +46 -0
- package/plugins/specweave/commands/specweave-jobs.md +153 -8
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/spec-project-validator.sh +24 -2
- package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
- package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
- package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
- package/plugins/specweave/hooks/user-prompt-submit.sh +105 -3
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +281 -0
- package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +29 -0
- package/plugins/specweave/scripts/session-watchdog.sh +278 -130
- package/plugins/specweave/skills/increment-planner/SKILL.md +48 -18
- package/plugins/specweave/skills/increment-planner/templates/spec-multi-project.md +27 -14
- package/plugins/specweave/skills/increment-planner/templates/spec-single-project.md +16 -5
- package/plugins/specweave/skills/spec-generator/SKILL.md +74 -15
- package/plugins/specweave-ado/lib/per-us-sync.js +247 -0
- package/plugins/specweave-ado/lib/per-us-sync.ts +410 -0
- package/plugins/specweave-github/lib/github-client-v2.js +10 -3
- package/plugins/specweave-github/lib/github-client-v2.ts +15 -3
- package/plugins/specweave-github/lib/per-us-sync.js +241 -0
- package/plugins/specweave-github/lib/per-us-sync.ts +375 -0
- package/plugins/specweave-jira/lib/per-us-sync.js +224 -0
- package/plugins/specweave-jira/lib/per-us-sync.ts +366 -0
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +0 -738
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +0 -1107
|
@@ -364,6 +364,52 @@ Proceeding with hotfix 0006...
|
|
|
364
364
|
- Hotfixes and bugs bypass context switching warnings (emergency work)
|
|
365
365
|
- Use sparingly - discipline exists for a reason!
|
|
366
366
|
|
|
367
|
+
### Step 0D: Get Project Context (MANDATORY - RUN FIRST!)
|
|
368
|
+
|
|
369
|
+
**⛔ THIS STEP MUST BE COMPLETED BEFORE ANY SPEC GENERATION!**
|
|
370
|
+
|
|
371
|
+
**🚨 YOU MUST USE THE BASH TOOL TO RUN THIS COMMAND:**
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
specweave context projects
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**CAPTURE THE OUTPUT AND STORE IT:**
|
|
378
|
+
|
|
379
|
+
```json
|
|
380
|
+
// Example 1-level output:
|
|
381
|
+
{
|
|
382
|
+
"level": 1,
|
|
383
|
+
"projects": [{"id": "frontend-app"}, {"id": "backend-api"}, {"id": "shared"}]
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Example 2-level output:
|
|
387
|
+
{
|
|
388
|
+
"level": 2,
|
|
389
|
+
"projects": [{"id": "acme-corp"}],
|
|
390
|
+
"boardsByProject": {
|
|
391
|
+
"acme-corp": [{"id": "digital-ops"}, {"id": "mobile-team"}]
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**STORE THESE VALUES FOR USE IN STEP 5:**
|
|
397
|
+
```
|
|
398
|
+
STRUCTURE_LEVEL = 1 or 2
|
|
399
|
+
AVAILABLE_PROJECTS = ["frontend-app", "backend-api", "shared"]
|
|
400
|
+
AVAILABLE_BOARDS = {...} // for 2-level only
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**WHY THIS IS MANDATORY:**
|
|
404
|
+
- Without this data, spec.md will have `{{PROJECT_ID}}` placeholders
|
|
405
|
+
- Placeholders WILL BE BLOCKED by `spec-project-validator.sh` hook
|
|
406
|
+
- Living docs sync and external tool sync WILL FAIL
|
|
407
|
+
- User will get frustrated with blocked edits
|
|
408
|
+
|
|
409
|
+
**YOU MUST ACTUALLY RUN THE COMMAND** - reading documentation about it is NOT enough!
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
367
413
|
### Step 1: Find next increment number
|
|
368
414
|
|
|
369
415
|
- Scan `.specweave/increments/` directory (active increments)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: specweave:jobs
|
|
3
3
|
description: Show current work status (active increments, progress) and background jobs (imports, cloning). Even with no jobs, shows increment summary and helpful context.
|
|
4
|
-
usage: /specweave:jobs [--all] [--id <job-id>] [--resume <job-id>] [--kill <job-id>] [--follow <job-id>] [--logs <job-id>]
|
|
4
|
+
usage: /specweave:jobs [--all] [--id <job-id>] [--resume <job-id>] [--kill <job-id>] [--follow <job-id>] [--logs <job-id>] [--diagnostics]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Background Jobs Monitor
|
|
@@ -17,6 +17,7 @@ Monitor and manage long-running background operations:
|
|
|
17
17
|
- **Issue import** (10K+ items from GitHub/JIRA/ADO)
|
|
18
18
|
- **External sync** operations
|
|
19
19
|
- **Brownfield analysis** (codebase documentation gap detection)
|
|
20
|
+
- **Session health monitoring** (watchdog diagnostics)
|
|
20
21
|
|
|
21
22
|
**ASYNC ARCHITECTURE (2025-12-01)**:
|
|
22
23
|
- Jobs run as **detached processes** that survive terminal close
|
|
@@ -29,13 +30,14 @@ Monitor and manage long-running background operations:
|
|
|
29
30
|
|
|
30
31
|
| Option | Description |
|
|
31
32
|
|--------|-------------|
|
|
32
|
-
| (none) | Show active jobs |
|
|
33
|
+
| (none) | Show active jobs + session health |
|
|
33
34
|
| `--all` | Show all jobs (including completed) |
|
|
34
35
|
| `--id <jobId>` | Show details for specific job |
|
|
35
36
|
| `--follow <jobId>` | Follow job progress in real-time |
|
|
36
37
|
| `--logs <jobId>` | Show worker log output |
|
|
37
38
|
| `--resume <jobId>` | Resume paused job |
|
|
38
39
|
| `--kill <jobId>` | Kill running background job |
|
|
40
|
+
| `--diagnostics` | Show detailed watchdog diagnostics |
|
|
39
41
|
|
|
40
42
|
---
|
|
41
43
|
|
|
@@ -57,7 +59,9 @@ STATE_FILE=".specweave/state/background-jobs.json"
|
|
|
57
59
|
### Display Format
|
|
58
60
|
|
|
59
61
|
```
|
|
60
|
-
📋 Background Jobs
|
|
62
|
+
📋 Background Jobs & Session Health
|
|
63
|
+
|
|
64
|
+
🩺 Session Health: ✅ healthy (last check: 30s ago)
|
|
61
65
|
|
|
62
66
|
🔄 Running (2):
|
|
63
67
|
[abc12345] import-issues (ADO)
|
|
@@ -87,6 +91,7 @@ STATE_FILE=".specweave/state/background-jobs.json"
|
|
|
87
91
|
/specweave:jobs --logs abc12345 → View worker logs
|
|
88
92
|
/specweave:jobs --resume def67890 → Resume paused job
|
|
89
93
|
/specweave:jobs --kill abc12345 → Kill running job
|
|
94
|
+
/specweave:jobs --diagnostics → Show watchdog diagnostics
|
|
90
95
|
/specweave:jobs --all → Show all jobs (including old)
|
|
91
96
|
```
|
|
92
97
|
|
|
@@ -221,14 +226,57 @@ const result = await launchImportJob({
|
|
|
221
226
|
## Implementation
|
|
222
227
|
|
|
223
228
|
1. Read `.specweave/state/background-jobs.json`
|
|
224
|
-
2.
|
|
225
|
-
3.
|
|
226
|
-
4.
|
|
229
|
+
2. Read `.specweave/state/.watchdog-diagnostics.json` for session health
|
|
230
|
+
3. Parse job entries
|
|
231
|
+
4. Display formatted status with health indicator
|
|
232
|
+
5. For --resume, update job status and continue operation
|
|
233
|
+
6. For --diagnostics, show detailed watchdog checks
|
|
227
234
|
|
|
228
|
-
### State File
|
|
235
|
+
### State File Locations
|
|
229
236
|
|
|
230
237
|
```
|
|
231
|
-
.specweave/state/background-jobs.json
|
|
238
|
+
.specweave/state/background-jobs.json - Job status and progress
|
|
239
|
+
.specweave/state/.watchdog-diagnostics.json - Session health checks
|
|
240
|
+
.specweave/state/jobs/<jobId>/config.json - Job configuration
|
|
241
|
+
.specweave/state/jobs/<jobId>/worker.pid - Worker process ID
|
|
242
|
+
.specweave/state/jobs/<jobId>/worker.log - Worker output log
|
|
243
|
+
.specweave/logs/watchdog.log - Watchdog history
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Reading Session Health
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import * as fs from 'fs';
|
|
250
|
+
import * as path from 'path';
|
|
251
|
+
|
|
252
|
+
function getSessionHealth(projectPath: string): SessionHealth | null {
|
|
253
|
+
const diagnosticsPath = path.join(projectPath, '.specweave/state/.watchdog-diagnostics.json');
|
|
254
|
+
if (!fs.existsSync(diagnosticsPath)) {
|
|
255
|
+
return null; // Watchdog not running
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const content = fs.readFileSync(diagnosticsPath, 'utf-8');
|
|
260
|
+
return JSON.parse(content);
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
interface SessionHealth {
|
|
267
|
+
timestamp: string;
|
|
268
|
+
severity: 0 | 1 | 2; // INFO | WARNING | CRITICAL
|
|
269
|
+
status: 'healthy' | 'warning' | 'critical';
|
|
270
|
+
checks: {
|
|
271
|
+
lock: { severity: number; message: string };
|
|
272
|
+
zombies: { count: number; message: string };
|
|
273
|
+
mcp: { drops: number; message: string };
|
|
274
|
+
orphanedJobs: { count: number; message: string };
|
|
275
|
+
};
|
|
276
|
+
consecutiveWarnings: number;
|
|
277
|
+
thresholdSeconds: number;
|
|
278
|
+
checkIntervalSeconds: number;
|
|
279
|
+
}
|
|
232
280
|
```
|
|
233
281
|
|
|
234
282
|
### Job Types
|
|
@@ -299,9 +347,106 @@ If job failed:
|
|
|
299
347
|
|
|
300
348
|
---
|
|
301
349
|
|
|
350
|
+
## Session Health & Watchdog Diagnostics (v2.0)
|
|
351
|
+
|
|
352
|
+
The `/specweave:jobs` command now includes session health monitoring. The watchdog runs in the background and writes diagnostics that help explain any alerts you may have received.
|
|
353
|
+
|
|
354
|
+
### Display Format (with health status)
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
📋 Background Jobs & Session Health
|
|
358
|
+
|
|
359
|
+
🩺 Session Health: ✅ healthy
|
|
360
|
+
Last check: 30 seconds ago
|
|
361
|
+
Consecutive warnings: 0/3
|
|
362
|
+
|
|
363
|
+
🔄 Running (1):
|
|
364
|
+
[abc12345] import-issues (ADO)
|
|
365
|
+
Progress: 2,500/10,000 (25%)
|
|
366
|
+
...
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Diagnostics File
|
|
370
|
+
|
|
371
|
+
The watchdog writes diagnostics to `.specweave/state/.watchdog-diagnostics.json`:
|
|
372
|
+
|
|
373
|
+
```json
|
|
374
|
+
{
|
|
375
|
+
"timestamp": "2025-12-10T10:30:00Z",
|
|
376
|
+
"severity": 0,
|
|
377
|
+
"status": "healthy",
|
|
378
|
+
"checks": {
|
|
379
|
+
"lock": { "severity": 0, "message": "ok" },
|
|
380
|
+
"zombies": { "count": 0, "message": "none" },
|
|
381
|
+
"mcp": { "drops": 2, "message": "minor instability" },
|
|
382
|
+
"orphanedJobs": { "count": 0, "message": "none" }
|
|
383
|
+
},
|
|
384
|
+
"consecutiveWarnings": 0,
|
|
385
|
+
"thresholdSeconds": 300,
|
|
386
|
+
"checkIntervalSeconds": 60
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### View Detailed Diagnostics
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
/specweave:jobs --diagnostics
|
|
394
|
+
|
|
395
|
+
🩺 Watchdog Diagnostics
|
|
396
|
+
|
|
397
|
+
Overall Status: ✅ healthy
|
|
398
|
+
Last Check: 2025-12-10 10:30:00
|
|
399
|
+
Severity Level: INFO (0)
|
|
400
|
+
|
|
401
|
+
Checks:
|
|
402
|
+
📁 Lock File: ✅ ok
|
|
403
|
+
💀 Zombie Procs: ✅ none (0)
|
|
404
|
+
🔌 MCP Connection: ⚠️ 2 drops detected (minor instability)
|
|
405
|
+
📦 Orphaned Jobs: ✅ none (0)
|
|
406
|
+
|
|
407
|
+
Alert Threshold: 3 consecutive warnings
|
|
408
|
+
Current Warnings: 0/3
|
|
409
|
+
|
|
410
|
+
Severity Levels:
|
|
411
|
+
INFO (0) - Everything healthy, no action needed
|
|
412
|
+
WARNING (1) - Minor issue detected, monitoring (NO notification)
|
|
413
|
+
CRITICAL (2) - Real stuck condition detected → NOTIFICATION SENT
|
|
414
|
+
|
|
415
|
+
💡 The watchdog only sends notifications for CRITICAL issues
|
|
416
|
+
that persist across 3+ consecutive checks (prevents false positives).
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Why You Got a Notification
|
|
420
|
+
|
|
421
|
+
If you received a notification but your job completed successfully, here's what happened:
|
|
422
|
+
|
|
423
|
+
1. **Old behavior (v1)**: Watchdog triggered on stale lock files even if no process was stuck
|
|
424
|
+
2. **New behavior (v2)**: Watchdog verifies actual process state before alerting
|
|
425
|
+
|
|
426
|
+
Common false positive causes (now fixed):
|
|
427
|
+
- Stale `.processor.lock` file from completed job
|
|
428
|
+
- Missing heartbeat file (never written in normal operation)
|
|
429
|
+
- MCP connection drops (warning only, not critical)
|
|
430
|
+
|
|
431
|
+
**The v2 watchdog now requires**:
|
|
432
|
+
1. **Actual stuck process** (zombie heredoc, hung worker)
|
|
433
|
+
2. **3 consecutive checks** showing the same issue
|
|
434
|
+
3. **CRITICAL severity** (not just warnings)
|
|
435
|
+
|
|
436
|
+
### Watchdog Log
|
|
437
|
+
|
|
438
|
+
View historical watchdog checks:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
cat .specweave/logs/watchdog.log
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
302
446
|
## Notes
|
|
303
447
|
|
|
304
448
|
- Jobs persist across Claude sessions
|
|
305
449
|
- Paused jobs can be resumed later
|
|
306
450
|
- Completed jobs cleaned up after 10 entries
|
|
307
451
|
- Rate limiting auto-pauses and notifies
|
|
452
|
+
- Watchdog diagnostics available via `--diagnostics` flag
|
|
@@ -50,6 +50,16 @@
|
|
|
50
50
|
}
|
|
51
51
|
]
|
|
52
52
|
},
|
|
53
|
+
{
|
|
54
|
+
"matcher": "Write",
|
|
55
|
+
"matcher_content": "\\.specweave/increments/\\d{3,4}E?-[^/]+/spec\\.md",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/v2/guards/per-us-project-validator.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"decision\\\":\\\"allow\\\"}\")'"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
53
63
|
{
|
|
54
64
|
"matcher": "Write|Edit",
|
|
55
65
|
"matcher_content": "\\.specweave/docs/internal/specs/_features/",
|
|
@@ -61,18 +61,40 @@ fi
|
|
|
61
61
|
# Extract YAML frontmatter
|
|
62
62
|
FRONTMATTER=$(echo "$CONTENT" | sed -n '/^---$/,/^---$/p' | tail -n +2 | head -n -1)
|
|
63
63
|
|
|
64
|
-
# Check for unresolved project placeholder
|
|
64
|
+
# Check for unresolved project placeholder (legacy {{PROJECT_ID}})
|
|
65
65
|
if echo "$FRONTMATTER" | grep -q 'project:\s*{{PROJECT_ID}}'; then
|
|
66
66
|
echo '{"decision": "block", "reason": "spec.md has unresolved placeholder {{PROJECT_ID}}. Run '\''specweave context projects'\'' to get available projects, then select one."}'
|
|
67
67
|
exit 0
|
|
68
68
|
fi
|
|
69
69
|
|
|
70
|
-
# Check for unresolved board placeholder
|
|
70
|
+
# Check for unresolved board placeholder (legacy {{BOARD_ID}})
|
|
71
71
|
if echo "$FRONTMATTER" | grep -q 'board:\s*{{BOARD_ID}}'; then
|
|
72
72
|
echo '{"decision": "block", "reason": "spec.md has unresolved placeholder {{BOARD_ID}}. Run '\''specweave context boards --project=<id>'\'' to get available boards, then select one."}'
|
|
73
73
|
exit 0
|
|
74
74
|
fi
|
|
75
75
|
|
|
76
|
+
# Check for ANY unresolved {{...}} placeholder in frontmatter (v0.34.0+)
|
|
77
|
+
# This catches {{RESOLVED_PROJECT}}, {{RESOLVED_BOARD}}, and any other placeholders
|
|
78
|
+
if echo "$FRONTMATTER" | grep -qE '\{\{[A-Z_]+\}\}'; then
|
|
79
|
+
FOUND_PLACEHOLDERS=$(echo "$FRONTMATTER" | grep -oE '\{\{[A-Z_]+\}\}' | tr '\n' ', ' | sed 's/,$//')
|
|
80
|
+
echo "{\"decision\": \"block\", \"reason\": \"spec.md has unresolved placeholders: ${FOUND_PLACEHOLDERS}\\n\\nYOU MUST RESOLVE these BEFORE creating spec.md:\\n1. Run: specweave context projects\\n2. Parse the JSON output to get valid project/board IDs\\n3. Replace placeholders with actual values from step 2\\n\\n❌ FORBIDDEN: Using placeholder templates directly\\n✅ REQUIRED: Resolve ALL placeholders to actual values\"}"
|
|
81
|
+
exit 0
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Check for ANY unresolved {{...}} placeholder in full content (catches per-US **Project**: {{...}})
|
|
85
|
+
if echo "$CONTENT" | grep -qE '\*\*Project\*\*:\s*\{\{[A-Z_]+\}\}'; then
|
|
86
|
+
FOUND_PLACEHOLDERS=$(echo "$CONTENT" | grep -oE '\*\*Project\*\*:\s*\{\{[A-Z_]+\}\}' | head -1)
|
|
87
|
+
echo "{\"decision\": \"block\", \"reason\": \"spec.md has unresolved **Project**: placeholder\\n\\nFound: ${FOUND_PLACEHOLDERS}\\n\\nEach user story MUST have a resolved **Project**: field.\\n\\n1. Run: specweave context projects\\n2. Get valid project IDs from the JSON output\\n3. Replace the placeholder with an actual project ID\"}"
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# Check for unresolved **Board**: placeholders in full content (2-level structures)
|
|
92
|
+
if echo "$CONTENT" | grep -qE '\*\*Board\*\*:\s*\{\{[A-Z_]+\}\}'; then
|
|
93
|
+
FOUND_PLACEHOLDERS=$(echo "$CONTENT" | grep -oE '\*\*Board\*\*:\s*\{\{[A-Z_]+\}\}' | head -1)
|
|
94
|
+
echo "{\"decision\": \"block\", \"reason\": \"spec.md has unresolved **Board**: placeholder\\n\\nFound: ${FOUND_PLACEHOLDERS}\\n\\nEach user story MUST have a resolved **Board**: field (for 2-level structures).\\n\\n1. Run: specweave context projects\\n2. Get valid board IDs from boardsByProject in the JSON output\\n3. Replace the placeholder with an actual board ID\"}"
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
76
98
|
# Extract project and board from frontmatter
|
|
77
99
|
PROJECT=$(echo "$FRONTMATTER" | grep -E '^project:\s*' | sed 's/^project:\s*//' | tr -d '"'"'" | tr -d '[:space:]')
|
|
78
100
|
BOARD=$(echo "$FRONTMATTER" | grep -E '^board:\s*' | sed 's/^board:\s*//' | tr -d '"'"'" | tr -d '[:space:]')
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
@echo off
|
|
2
|
-
REM hook-wrapper.cmd - Windows resilient hook launcher
|
|
3
|
-
REM Prevents crashes when dispatcher.mjs is temporarily unavailable
|
|
4
|
-
|
|
5
|
-
setlocal enabledelayedexpansion
|
|
6
|
-
|
|
7
|
-
set "HOOK_TYPE=%~1"
|
|
8
|
-
if "%HOOK_TYPE%"=="" set "HOOK_TYPE=unknown"
|
|
9
|
-
|
|
10
|
-
set "SCRIPT_DIR=%~dp0"
|
|
11
|
-
set "DISPATCHER=%SCRIPT_DIR%dispatcher.mjs"
|
|
12
|
-
|
|
13
|
-
REM Check if dispatcher exists
|
|
14
|
-
if not exist "%DISPATCHER%" (
|
|
15
|
-
echo {"continue":true,"systemMessage":"Hook skipped: dispatcher.mjs not found"}
|
|
16
|
-
exit /b 0
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
REM Run dispatcher with error suppression
|
|
20
|
-
node "%DISPATCHER%" "%HOOK_TYPE%" 2>nul
|
|
21
|
-
if errorlevel 1 (
|
|
22
|
-
echo {"continue":true,"systemMessage":"Hook error, continuing"}
|
|
23
|
-
exit /b 0
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
exit /b 0
|
|
1
|
+
@echo off
|
|
2
|
+
REM hook-wrapper.cmd - Windows resilient hook launcher
|
|
3
|
+
REM Prevents crashes when dispatcher.mjs is temporarily unavailable
|
|
4
|
+
|
|
5
|
+
setlocal enabledelayedexpansion
|
|
6
|
+
|
|
7
|
+
set "HOOK_TYPE=%~1"
|
|
8
|
+
if "%HOOK_TYPE%"=="" set "HOOK_TYPE=unknown"
|
|
9
|
+
|
|
10
|
+
set "SCRIPT_DIR=%~dp0"
|
|
11
|
+
set "DISPATCHER=%SCRIPT_DIR%dispatcher.mjs"
|
|
12
|
+
|
|
13
|
+
REM Check if dispatcher exists
|
|
14
|
+
if not exist "%DISPATCHER%" (
|
|
15
|
+
echo {"continue":true,"systemMessage":"Hook skipped: dispatcher.mjs not found"}
|
|
16
|
+
exit /b 0
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
REM Run dispatcher with error suppression
|
|
20
|
+
node "%DISPATCHER%" "%HOOK_TYPE%" 2>nul
|
|
21
|
+
if errorlevel 1 (
|
|
22
|
+
echo {"continue":true,"systemMessage":"Hook error, continuing"}
|
|
23
|
+
exit /b 0
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
exit /b 0
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
@echo off
|
|
2
|
-
:: Universal Session Start Hook for Windows
|
|
3
|
-
:: Calls the Node.js dispatcher
|
|
4
|
-
|
|
5
|
-
:: Find node.exe
|
|
6
|
-
where node >nul 2>&1
|
|
7
|
-
if %ERRORLEVEL% neq 0 (
|
|
8
|
-
echo {"continue": true, "error": "Node.js not found"}
|
|
9
|
-
exit /b 0
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
:: Get the directory of this script
|
|
13
|
-
set "SCRIPT_DIR=%~dp0"
|
|
14
|
-
|
|
15
|
-
:: Run the dispatcher
|
|
16
|
-
node "%SCRIPT_DIR%dispatcher.mjs" session-start
|
|
1
|
+
@echo off
|
|
2
|
+
:: Universal Session Start Hook for Windows
|
|
3
|
+
:: Calls the Node.js dispatcher
|
|
4
|
+
|
|
5
|
+
:: Find node.exe
|
|
6
|
+
where node >nul 2>&1
|
|
7
|
+
if %ERRORLEVEL% neq 0 (
|
|
8
|
+
echo {"continue": true, "error": "Node.js not found"}
|
|
9
|
+
exit /b 0
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
:: Get the directory of this script
|
|
13
|
+
set "SCRIPT_DIR=%~dp0"
|
|
14
|
+
|
|
15
|
+
:: Run the dispatcher
|
|
16
|
+
node "%SCRIPT_DIR%dispatcher.mjs" session-start
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
# Universal Session Start Hook for Windows PowerShell
|
|
2
|
-
# Calls the Node.js dispatcher for cross-platform compatibility
|
|
3
|
-
|
|
4
|
-
# Find node.exe
|
|
5
|
-
$nodePath = Get-Command node -ErrorAction SilentlyContinue
|
|
6
|
-
|
|
7
|
-
if (-not $nodePath) {
|
|
8
|
-
Write-Host '{"continue": true, "error": "Node.js not found"}'
|
|
9
|
-
exit 0
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
# Get script directory
|
|
13
|
-
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
14
|
-
|
|
15
|
-
# Run the dispatcher
|
|
16
|
-
& node "$scriptDir\dispatcher.mjs" session-start
|
|
1
|
+
# Universal Session Start Hook for Windows PowerShell
|
|
2
|
+
# Calls the Node.js dispatcher for cross-platform compatibility
|
|
3
|
+
|
|
4
|
+
# Find node.exe
|
|
5
|
+
$nodePath = Get-Command node -ErrorAction SilentlyContinue
|
|
6
|
+
|
|
7
|
+
if (-not $nodePath) {
|
|
8
|
+
Write-Host '{"continue": true, "error": "Node.js not found"}'
|
|
9
|
+
exit 0
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# Get script directory
|
|
13
|
+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
14
|
+
|
|
15
|
+
# Run the dispatcher
|
|
16
|
+
& node "$scriptDir\dispatcher.mjs" session-start
|
|
@@ -204,7 +204,91 @@ fi
|
|
|
204
204
|
# DISCIPLINE VALIDATION: Warn about WIP limits (configurable, not hard block!)
|
|
205
205
|
# ==============================================================================
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
# ==============================================================================
|
|
208
|
+
# PROJECT CONTEXT + WIP LIMITS FOR /specweave:increment (v0.34.0)
|
|
209
|
+
# ==============================================================================
|
|
210
|
+
# CRITICAL: Inject project/board context BEFORE Claude generates spec.md
|
|
211
|
+
# This ensures Claude knows available projects and uses correct IDs
|
|
212
|
+
# ALSO: Check WIP limits in same block to avoid duplicate command detection
|
|
213
|
+
|
|
214
|
+
if echo "$PROMPT" | grep -qE "^/specweave:increment"; then
|
|
215
|
+
# Get project context (uses specweave CLI if available)
|
|
216
|
+
PROJECT_CONTEXT=""
|
|
217
|
+
|
|
218
|
+
if command -v specweave >/dev/null 2>&1; then
|
|
219
|
+
# Use CLI for accurate project/board detection
|
|
220
|
+
CONTEXT_JSON=$(specweave context projects 2>/dev/null || echo '{}')
|
|
221
|
+
|
|
222
|
+
# Validate JSON before parsing (defensive coding)
|
|
223
|
+
if [[ -n "$CONTEXT_JSON" ]] && [[ "$CONTEXT_JSON" != "{}" ]]; then
|
|
224
|
+
if command -v jq >/dev/null 2>&1; then
|
|
225
|
+
# Verify JSON is parseable before extracting fields
|
|
226
|
+
if ! echo "$CONTEXT_JSON" | jq empty 2>/dev/null; then
|
|
227
|
+
CONTEXT_JSON='{}' # Invalid JSON - reset to empty
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
if [[ -n "$CONTEXT_JSON" ]] && [[ "$CONTEXT_JSON" != "{}" ]]; then
|
|
233
|
+
# Parse JSON with jq
|
|
234
|
+
if command -v jq >/dev/null 2>&1; then
|
|
235
|
+
LEVEL=$(echo "$CONTEXT_JSON" | jq -r '.level // 1')
|
|
236
|
+
PROJECTS=$(echo "$CONTEXT_JSON" | jq -r '.projects | map(.id) | join(", ")' 2>/dev/null || echo "")
|
|
237
|
+
|
|
238
|
+
if [[ "$LEVEL" == "2" ]]; then
|
|
239
|
+
# 2-level structure: include boards
|
|
240
|
+
BOARDS_JSON=$(echo "$CONTEXT_JSON" | jq -r '.boardsByProject // {}' 2>/dev/null)
|
|
241
|
+
if [[ -n "$BOARDS_JSON" ]] && [[ "$BOARDS_JSON" != "{}" ]]; then
|
|
242
|
+
PROJECT_CONTEXT="\\n\\n📦 PROJECT CONTEXT (2-LEVEL STRUCTURE)\\n\\n"
|
|
243
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}⚠️ MANDATORY: Each User Story MUST have both:\\n"
|
|
244
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT} - **Project**: <project_id>\\n"
|
|
245
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT} - **Board**: <board_id>\\n\\n"
|
|
246
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}Available projects: ${PROJECTS}\\n"
|
|
247
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}Boards by project:\\n"
|
|
248
|
+
|
|
249
|
+
# Format boards
|
|
250
|
+
for proj in $(echo "$CONTEXT_JSON" | jq -r '.projects[].id' 2>/dev/null); do
|
|
251
|
+
PROJ_BOARDS=$(echo "$CONTEXT_JSON" | jq -r ".boardsByProject[\"$proj\"] | map(.id) | join(\", \")" 2>/dev/null || echo "")
|
|
252
|
+
[[ -n "$PROJ_BOARDS" ]] && PROJECT_CONTEXT="${PROJECT_CONTEXT} - ${proj}: ${PROJ_BOARDS}\\n"
|
|
253
|
+
done
|
|
254
|
+
|
|
255
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}\\n❌ FORBIDDEN: Comma-separated values (e.g., **Project**: fe, be)\\n"
|
|
256
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}✅ REQUIRED: One project + one board per User Story"
|
|
257
|
+
fi
|
|
258
|
+
elif [[ -n "$PROJECTS" ]]; then
|
|
259
|
+
# 1-level structure: projects only
|
|
260
|
+
PROJECT_COUNT=$(echo "$CONTEXT_JSON" | jq '.projects | length' 2>/dev/null || echo "0")
|
|
261
|
+
|
|
262
|
+
if [[ "$PROJECT_COUNT" -gt 1 ]]; then
|
|
263
|
+
PROJECT_CONTEXT="\\n\\n📦 PROJECT CONTEXT (MULTI-PROJECT)\\n\\n"
|
|
264
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}⚠️ MANDATORY: Each User Story MUST have:\\n"
|
|
265
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT} - **Project**: <project_id>\\n\\n"
|
|
266
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}Available projects: ${PROJECTS}\\n"
|
|
267
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}\\n❌ FORBIDDEN: Comma-separated values\\n"
|
|
268
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}✅ REQUIRED: One project per User Story"
|
|
269
|
+
elif [[ "$PROJECT_COUNT" -eq 1 ]]; then
|
|
270
|
+
# Single project: auto-select
|
|
271
|
+
SINGLE_PROJECT=$(echo "$CONTEXT_JSON" | jq -r '.projects[0].id' 2>/dev/null)
|
|
272
|
+
PROJECT_CONTEXT="\\n\\n📦 PROJECT CONTEXT\\n"
|
|
273
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}Single project detected: ${SINGLE_PROJECT} (auto-selected)"
|
|
274
|
+
fi
|
|
275
|
+
fi
|
|
276
|
+
fi
|
|
277
|
+
fi
|
|
278
|
+
else
|
|
279
|
+
# Fallback: Check for multi-project folders
|
|
280
|
+
if [[ -d "$SPECWEAVE_DIR/docs/internal/specs" ]]; then
|
|
281
|
+
PROJ_COUNT=$(find "$SPECWEAVE_DIR/docs/internal/specs" -maxdepth 1 -type d | wc -l)
|
|
282
|
+
if [[ "$PROJ_COUNT" -gt 2 ]]; then
|
|
283
|
+
PROJ_LIST=$(ls -1 "$SPECWEAVE_DIR/docs/internal/specs" 2>/dev/null | grep -v "_" | tr '\n' ', ' | sed 's/,$//')
|
|
284
|
+
PROJECT_CONTEXT="\\n\\n📦 PROJECT CONTEXT (MULTI-PROJECT)\\n"
|
|
285
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}⚠️ MANDATORY: Each User Story MUST have **Project**: field\\n"
|
|
286
|
+
PROJECT_CONTEXT="${PROJECT_CONTEXT}Available folders: ${PROJ_LIST}"
|
|
287
|
+
fi
|
|
288
|
+
fi
|
|
289
|
+
fi
|
|
290
|
+
|
|
291
|
+
# WIP LIMITS CHECK (inside same block - no duplicate command detection)
|
|
208
292
|
# Read limits from config.json (respect user's settings!)
|
|
209
293
|
CONFIG_FILE="$SPECWEAVE_DIR/config.json"
|
|
210
294
|
SOFT_LIMIT=1
|
|
@@ -226,13 +310,31 @@ if echo "$PROMPT" | grep -q "/specweave:increment"; then
|
|
|
226
310
|
|
|
227
311
|
# Above hard cap: strong warning but NOT a block (user decides!)
|
|
228
312
|
if [[ "$ACTIVE_COUNT" -ge "$HARD_CAP" ]]; then
|
|
229
|
-
|
|
313
|
+
WIP_MSG="⚠️ WIP LIMIT EXCEEDED (${ACTIVE_COUNT}/${HARD_CAP})\\n\\nYou have ${ACTIVE_COUNT} active increments (configured maximum: ${HARD_CAP})\\n\\nActive increments:\\n${ACTIVE_LIST}\\n\\n🧠 Research shows 3+ concurrent tasks = 40%% slower + more bugs\\n\\n💡 Options:\\n 1️⃣ Complete an increment: /specweave:done <id>\\n 2️⃣ Pause an increment: /specweave:pause <id>\\n 3️⃣ Increase limit: Edit .specweave/config.json limits.hardCap\\n 4️⃣ Continue anyway (not recommended)\\n\\n📝 To proceed anyway, just confirm your intent."
|
|
314
|
+
# Prepend project context if available
|
|
315
|
+
if [[ -n "$PROJECT_CONTEXT" ]]; then
|
|
316
|
+
printf '{"decision":"approve","systemMessage":"%s%s"}\n' "$PROJECT_CONTEXT" "$WIP_MSG"
|
|
317
|
+
else
|
|
318
|
+
printf '{"decision":"approve","systemMessage":"%s"}\n' "$WIP_MSG"
|
|
319
|
+
fi
|
|
230
320
|
exit 0
|
|
231
321
|
fi
|
|
232
322
|
|
|
233
323
|
# At soft limit: mild warning, approve
|
|
234
324
|
if [[ "$ACTIVE_COUNT" -ge "$SOFT_LIMIT" ]]; then
|
|
235
|
-
|
|
325
|
+
WIP_MSG="⚠️ WIP LIMIT REACHED (${ACTIVE_COUNT}/${SOFT_LIMIT})\\n\\nYou have ${ACTIVE_COUNT} active increment(s) (recommended limit: ${SOFT_LIMIT})\\n\\nActive increments:\\n${ACTIVE_LIST}\\n\\n🧠 Focus Principle: Fewer active increments = maximum productivity\\n\\n💡 Consider:\\n 1️⃣ Complete current work (recommended)\\n 2️⃣ Pause current work (/specweave:pause)\\n 3️⃣ Continue anyway\\n\\n⚠️ Emergency hotfix/bug? Use --type=hotfix or --type=bug"
|
|
326
|
+
# Prepend project context if available
|
|
327
|
+
if [[ -n "$PROJECT_CONTEXT" ]]; then
|
|
328
|
+
printf '{"decision":"approve","systemMessage":"%s%s"}\n' "$PROJECT_CONTEXT" "$WIP_MSG"
|
|
329
|
+
else
|
|
330
|
+
printf '{"decision":"approve","systemMessage":"%s"}\n' "$WIP_MSG"
|
|
331
|
+
fi
|
|
332
|
+
exit 0
|
|
333
|
+
fi
|
|
334
|
+
|
|
335
|
+
# No WIP limit warning, but we may have project context to inject
|
|
336
|
+
if [[ -n "$PROJECT_CONTEXT" ]]; then
|
|
337
|
+
printf '{"decision":"approve","systemMessage":"%s"}\n' "$PROJECT_CONTEXT"
|
|
236
338
|
exit 0
|
|
237
339
|
fi
|
|
238
340
|
fi
|