specweave 0.28.61 → 0.28.63

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.
Files changed (47) hide show
  1. package/CLAUDE.md +23 -0
  2. package/README.md +23 -1
  3. package/dist/src/cli/helpers/init/ado-repo-cloning.d.ts.map +1 -1
  4. package/dist/src/cli/helpers/init/ado-repo-cloning.js +47 -84
  5. package/dist/src/cli/helpers/init/ado-repo-cloning.js.map +1 -1
  6. package/dist/src/cli/workers/clone-worker.d.ts +18 -0
  7. package/dist/src/cli/workers/clone-worker.d.ts.map +1 -0
  8. package/dist/src/cli/workers/clone-worker.js +191 -0
  9. package/dist/src/cli/workers/clone-worker.js.map +1 -0
  10. package/dist/src/core/background/index.d.ts +2 -1
  11. package/dist/src/core/background/index.d.ts.map +1 -1
  12. package/dist/src/core/background/index.js +1 -1
  13. package/dist/src/core/background/index.js.map +1 -1
  14. package/dist/src/core/background/job-launcher.d.ts +20 -0
  15. package/dist/src/core/background/job-launcher.d.ts.map +1 -1
  16. package/dist/src/core/background/job-launcher.js +88 -4
  17. package/dist/src/core/background/job-launcher.js.map +1 -1
  18. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  19. package/dist/src/core/increment/metadata-manager.js +11 -0
  20. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  21. package/dist/src/core/types/increment-metadata.d.ts +33 -2
  22. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  23. package/dist/src/core/types/increment-metadata.js +32 -5
  24. package/dist/src/core/types/increment-metadata.js.map +1 -1
  25. package/package.json +1 -1
  26. package/plugins/specweave/commands/specweave-done.md +30 -1
  27. package/plugins/specweave/commands/specweave-jobs.md +7 -7
  28. package/plugins/specweave/commands/specweave-next.md +66 -14
  29. package/plugins/specweave/hooks/hooks.json +12 -0
  30. package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +85 -0
  31. package/plugins/specweave/hooks/v2/detectors/us-completion-detector.sh +148 -0
  32. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +73 -15
  33. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +81 -0
  34. package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +4 -0
  35. package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +23 -2
  36. package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +24 -3
  37. package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +193 -0
  38. package/plugins/specweave/hooks/v2/handlers/status-line-handler.sh +165 -0
  39. package/plugins/specweave/hooks/v2/handlers/status-update.sh +12 -1
  40. package/plugins/specweave/hooks/v2/queue/dequeue.sh +4 -0
  41. package/plugins/specweave/hooks/v2/queue/enqueue.sh +50 -12
  42. package/plugins/specweave/hooks/v2/queue/processor.sh +141 -12
  43. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +11 -0
  44. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  45. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +33 -2
  46. package/plugins/specweave/lib/vendor/core/types/increment-metadata.js +32 -5
  47. package/plugins/specweave/lib/vendor/core/types/increment-metadata.js.map +1 -1
@@ -9,6 +9,8 @@ description: Smart increment transition - auto-close current if ready, intellige
9
9
 
10
10
  You are helping the user complete their current increment and move to the next one with intelligent suggestions.
11
11
 
12
+ **CRITICAL (v0.28.63+)**: This command now requires **EXPLICIT USER CONFIRMATION** before closing any increment! This prevents the auto-completion bug where increments were marked "completed" without user approval.
13
+
12
14
  ## Usage
13
15
 
14
16
  ```bash
@@ -24,9 +26,10 @@ You are helping the user complete their current increment and move to the next o
24
26
  The `/specweave:next` command is your **workflow continuation** command. It:
25
27
 
26
28
  1. **Validates current increment** - Checks if work is complete
27
- 2. **Auto-closes if ready** - PM validates and closes automatically
28
- 3. **Suggests next work** - Intelligent recommendations from backlog or prompt for new
29
- 4. **Smooth transition** - No manual `/done` + `/inc` needed
29
+ 2. **Transitions to ready_for_review** - If all tasks done, auto-transitions to `ready_for_review`
30
+ 3. **ASKS USER FOR CONFIRMATION** - NEVER auto-closes! Always asks "Ready to close this increment?"
31
+ 4. **Closes on explicit approval** - Only marks `completed` if user confirms
32
+ 5. **Suggests next work** - Intelligent recommendations from backlog or prompt for new
30
33
 
31
34
  ---
32
35
 
@@ -131,7 +134,9 @@ Status: ✅ PASS
131
134
 
132
135
  **Based on PM validation results**:
133
136
 
134
- #### Scenario A: All Gates Pass ✅ (Auto-Close)
137
+ #### Scenario A: All Gates Pass ✅ (ASK USER CONFIRMATION - v0.28.63+)
138
+
139
+ **CRITICAL**: NEVER auto-close! Always ask for user confirmation to prevent the auto-completion bug.
135
140
 
136
141
  ```
137
142
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -142,11 +147,43 @@ PM VALIDATION: ✅ READY TO CLOSE
142
147
  ✅ Gate 2: Tests (70/70 passing, 89% coverage)
143
148
  ✅ Gate 3: Docs (all current)
144
149
 
145
- Increment 0001-user-authentication is complete!
150
+ Increment 0001-user-authentication is ready for closure!
151
+
152
+ 📋 Status: ready_for_review (awaiting your approval)
153
+
154
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
155
+ ⚠️ CONFIRMATION REQUIRED (v0.28.63+)
156
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
157
+
158
+ This will permanently mark the increment as COMPLETED.
159
+
160
+ Please confirm: Do you want to close this increment?
146
161
 
147
- 🎯 Auto-closing increment...
148
- Updated status: in-progress completed
162
+ A. Yes, close it - I've reviewed the work and it's complete
163
+ B. No, keep it open - I need to review or make changes first
164
+ ```
165
+
166
+ **🔥 CRITICAL**: Use the AskUserQuestion tool to get explicit confirmation:
167
+ ```
168
+ AskUserQuestion({
169
+ questions: [{
170
+ header: "Close increment?",
171
+ question: "All PM gates passed. Ready to permanently close this increment?",
172
+ options: [
173
+ { label: "Yes, close it", description: "Mark as completed (irreversible)" },
174
+ { label: "No, keep open", description: "Stay at ready_for_review status" }
175
+ ],
176
+ multiSelect: false
177
+ }]
178
+ })
179
+ ```
180
+
181
+ **Only if user confirms "Yes"**:
182
+ ```
183
+ 🎯 Closing increment with your approval...
184
+ ✓ Updated status: ready_for_review → completed
149
185
  ✓ Set completion date: 2025-10-28
186
+ ✓ Set approvedAt timestamp
150
187
  ✓ Generated completion report
151
188
  ✓ Freed WIP slot (1/2 → 0/2)
152
189
 
@@ -412,8 +449,28 @@ Active: 0001-user-authentication
412
449
  ✅ Gate 2: All tests passing (70/70, 89% coverage)
413
450
  ✅ Gate 3: Documentation updated
414
451
 
415
- 🎯 Auto-closing increment 0001...
416
- ✓ Status: completed
452
+ All gates passed! Transitioning to ready_for_review...
453
+
454
+ 📋 Status: ready_for_review (awaiting your approval)
455
+
456
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
457
+ ⚠️ CONFIRMATION REQUIRED (v0.28.63+)
458
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
459
+
460
+ This will permanently mark increment 0001 as COMPLETED.
461
+
462
+ Please confirm: Do you want to close this increment?
463
+ ```
464
+
465
+ **🔥 Claude uses AskUserQuestion tool here to get explicit confirmation**
466
+
467
+ **User confirms: "Yes, close it"**
468
+
469
+ **Output (after confirmation)**:
470
+ ```
471
+ 🎯 Closing increment with your approval...
472
+ ✓ Status: ready_for_review → completed
473
+ ✓ Set approvedAt: 2025-10-28
417
474
  ✓ Completion report generated
418
475
  ✓ WIP freed (1/2 → 0/2)
419
476
 
@@ -427,11 +484,6 @@ Running quality assessment...
427
484
 
428
485
  Overall Score: 87/100 (GOOD) ✓
429
486
 
430
- Dimension Scores:
431
- Clarity: 92/100 ✓✓
432
- Testability: 85/100 ✓
433
- Risk Assessment: 75/100 ✓
434
-
435
487
  Quality Gate Decision: ✅ PASS
436
488
 
437
489
  📋 Report: .specweave/increments/0001-user-authentication/reports/qa-post-closure.md
@@ -10,6 +10,18 @@
10
10
  ]
11
11
  }
12
12
  ],
13
+ "PreToolUse": [
14
+ {
15
+ "matcher": "Edit|Write",
16
+ "matcher_content": "metadata\\.json.*completed",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/v2/guards/completion-guard.sh"
21
+ }
22
+ ]
23
+ }
24
+ ],
13
25
  "PostToolUse": [
14
26
  {
15
27
  "matcher": "Edit|Write",
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ # lifecycle-detector.sh - Detect increment lifecycle changes
3
+ # Events: increment.created, increment.done, increment.archived, increment.reopened
4
+ #
5
+ # Called from post-tool-use.sh when metadata.json is edited
6
+ # Compares current vs previous status to detect transitions
7
+ #
8
+ # IMPORTANT: This script must be fast (<10ms) and never crash
9
+ set +e
10
+
11
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
12
+
13
+ INC_ID="${1:-}"
14
+ [[ -z "$INC_ID" ]] && exit 0
15
+
16
+ # Find project root
17
+ PROJECT_ROOT="$PWD"
18
+ while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
19
+ PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
20
+ done
21
+ [[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
22
+
23
+ STATE_DIR="$PROJECT_ROOT/.specweave/state"
24
+ PREV_STATUS_FILE="$STATE_DIR/.prev-status-$INC_ID"
25
+ META_FILE="$PROJECT_ROOT/.specweave/increments/$INC_ID/metadata.json"
26
+ ARCHIVE_META="$PROJECT_ROOT/.specweave/increments/_archive/$INC_ID/metadata.json"
27
+
28
+ mkdir -p "$STATE_DIR" 2>/dev/null
29
+
30
+ # Detect event
31
+ EVENT=""
32
+ EVENT_DATA="$INC_ID"
33
+
34
+ # Check if archived (folder moved to _archive/)
35
+ if [[ -f "$ARCHIVE_META" ]] && [[ ! -f "$META_FILE" ]]; then
36
+ # Check if we already detected this
37
+ PREV=$(cat "$PREV_STATUS_FILE" 2>/dev/null || echo "")
38
+ if [[ "$PREV" != "archived" ]]; then
39
+ EVENT="increment.archived"
40
+ echo "archived" > "$PREV_STATUS_FILE"
41
+ fi
42
+
43
+ elif [[ -f "$META_FILE" ]]; then
44
+ # Get current status (fast grep, no jq)
45
+ CURRENT_STATUS=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' "$META_FILE" | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
46
+ [[ -z "$CURRENT_STATUS" ]] && exit 0
47
+
48
+ # Get previous status
49
+ PREV_STATUS=$(cat "$PREV_STATUS_FILE" 2>/dev/null || echo "")
50
+
51
+ # Detect transitions
52
+ if [[ -z "$PREV_STATUS" ]]; then
53
+ # First time seeing this increment
54
+ if [[ "$CURRENT_STATUS" == "planning" ]] || [[ "$CURRENT_STATUS" == "active" ]]; then
55
+ EVENT="increment.created"
56
+ fi
57
+ elif [[ "$PREV_STATUS" != "$CURRENT_STATUS" ]]; then
58
+ # Status changed
59
+ case "$CURRENT_STATUS" in
60
+ completed)
61
+ EVENT="increment.done"
62
+ ;;
63
+ active)
64
+ # Was it completed before? That's a reopen
65
+ if [[ "$PREV_STATUS" == "completed" ]]; then
66
+ EVENT="increment.reopened"
67
+ fi
68
+ ;;
69
+ paused|abandoned)
70
+ # Status changes we don't emit events for
71
+ ;;
72
+ esac
73
+ fi
74
+
75
+ # Save current status
76
+ echo "$CURRENT_STATUS" > "$PREV_STATUS_FILE"
77
+ fi
78
+
79
+ # Fire event if detected
80
+ if [[ -n "$EVENT" ]]; then
81
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
82
+ bash "$HOOK_DIR/queue/enqueue.sh" "$EVENT" "$EVENT_DATA" 2>/dev/null
83
+ fi
84
+
85
+ exit 0
@@ -0,0 +1,148 @@
1
+ #!/bin/bash
2
+ # us-completion-detector.sh - Detect user story completion
3
+ # Events: user-story.completed, user-story.reopened
4
+ #
5
+ # A user story is complete when:
6
+ # 1. ALL tasks for that US are completed ([x])
7
+ # 2. ALL ACs for that US are checked ([x])
8
+ #
9
+ # Called from post-tool-use.sh when tasks.md or spec.md is edited
10
+ #
11
+ # IMPORTANT: This script must be fast (<50ms) and never crash
12
+ set +e
13
+
14
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
15
+
16
+ INC_ID="${1:-}"
17
+ [[ -z "$INC_ID" ]] && exit 0
18
+
19
+ # Find project root
20
+ PROJECT_ROOT="$PWD"
21
+ while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
22
+ PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
23
+ done
24
+ [[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
25
+
26
+ STATE_DIR="$PROJECT_ROOT/.specweave/state"
27
+ TASKS_FILE="$PROJECT_ROOT/.specweave/increments/$INC_ID/tasks.md"
28
+ SPEC_FILE="$PROJECT_ROOT/.specweave/increments/$INC_ID/spec.md"
29
+ US_STATE_FILE="$STATE_DIR/.us-completion-$INC_ID"
30
+
31
+ mkdir -p "$STATE_DIR" 2>/dev/null
32
+
33
+ [[ ! -f "$TASKS_FILE" ]] && exit 0
34
+ [[ ! -f "$SPEC_FILE" ]] && exit 0
35
+
36
+ # Parse tasks.md to find US -> Tasks mapping and completion status
37
+ # Format: ### T-001: Title
38
+ # **Satisfies ACs**: AC-US1-01, AC-US1-02
39
+ # **Status**: [x] completed
40
+
41
+ declare -A US_TASKS_TOTAL
42
+ declare -A US_TASKS_DONE
43
+
44
+ # Parse task blocks
45
+ CURRENT_TASK=""
46
+ CURRENT_STATUS=""
47
+ CURRENT_US=""
48
+
49
+ while IFS= read -r line; do
50
+ # Detect task header
51
+ if [[ "$line" =~ ^###[[:space:]]+T-[0-9]+ ]]; then
52
+ # Process previous task
53
+ if [[ -n "$CURRENT_US" ]] && [[ -n "$CURRENT_TASK" ]]; then
54
+ US_TASKS_TOTAL["$CURRENT_US"]=$((${US_TASKS_TOTAL["$CURRENT_US"]:-0} + 1))
55
+ if [[ "$CURRENT_STATUS" == "done" ]]; then
56
+ US_TASKS_DONE["$CURRENT_US"]=$((${US_TASKS_DONE["$CURRENT_US"]:-0} + 1))
57
+ fi
58
+ fi
59
+ CURRENT_TASK=$(echo "$line" | grep -o 'T-[0-9][0-9][0-9]' | head -1)
60
+ CURRENT_STATUS=""
61
+ CURRENT_US=""
62
+ fi
63
+
64
+ # Detect User Story reference
65
+ if [[ "$line" =~ User[[:space:]]*Story.*:.*US-[0-9]+ ]]; then
66
+ CURRENT_US=$(echo "$line" | grep -o 'US-[0-9][0-9][0-9]' | head -1)
67
+ fi
68
+
69
+ # Detect completion status
70
+ if [[ "$line" =~ Status.*\[x\] ]]; then
71
+ CURRENT_STATUS="done"
72
+ fi
73
+ done < "$TASKS_FILE"
74
+
75
+ # Process last task
76
+ if [[ -n "$CURRENT_US" ]] && [[ -n "$CURRENT_TASK" ]]; then
77
+ US_TASKS_TOTAL["$CURRENT_US"]=$((${US_TASKS_TOTAL["$CURRENT_US"]:-0} + 1))
78
+ if [[ "$CURRENT_STATUS" == "done" ]]; then
79
+ US_TASKS_DONE["$CURRENT_US"]=$((${US_TASKS_DONE["$CURRENT_US"]:-0} + 1))
80
+ fi
81
+ fi
82
+
83
+ # Parse spec.md for AC completion status
84
+ # Format: - [x] **AC-US1-01**: Description
85
+ declare -A US_ACS_TOTAL
86
+ declare -A US_ACS_DONE
87
+
88
+ while IFS= read -r line; do
89
+ # Find AC lines with US reference
90
+ if [[ "$line" =~ AC-US([0-9]+)-[0-9]+ ]]; then
91
+ US_NUM="${BASH_REMATCH[1]}"
92
+ US_ID="US-${US_NUM}"
93
+
94
+ US_ACS_TOTAL["$US_ID"]=$((${US_ACS_TOTAL["$US_ID"]:-0} + 1))
95
+
96
+ if [[ "$line" =~ \[x\] ]]; then
97
+ US_ACS_DONE["$US_ID"]=$((${US_ACS_DONE["$US_ID"]:-0} + 1))
98
+ fi
99
+ fi
100
+ done < "$SPEC_FILE"
101
+
102
+ # Load previous completion state
103
+ declare -A PREV_COMPLETE
104
+ if [[ -f "$US_STATE_FILE" ]]; then
105
+ while IFS='=' read -r us status; do
106
+ [[ -n "$us" ]] && PREV_COMPLETE["$us"]="$status"
107
+ done < "$US_STATE_FILE"
108
+ fi
109
+
110
+ # Check completion for each US
111
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
112
+ NEW_STATE=""
113
+
114
+ for US_ID in "${!US_TASKS_TOTAL[@]}"; do
115
+ TASKS_TOTAL=${US_TASKS_TOTAL["$US_ID"]:-0}
116
+ TASKS_DONE=${US_TASKS_DONE["$US_ID"]:-0}
117
+ ACS_TOTAL=${US_ACS_TOTAL["$US_ID"]:-0}
118
+ ACS_DONE=${US_ACS_DONE["$US_ID"]:-0}
119
+
120
+ # US is complete if ALL tasks done AND ALL ACs checked
121
+ CURRENT_COMPLETE="no"
122
+ if [[ $TASKS_TOTAL -gt 0 ]] && [[ $TASKS_DONE -eq $TASKS_TOTAL ]]; then
123
+ if [[ $ACS_TOTAL -gt 0 ]] && [[ $ACS_DONE -eq $ACS_TOTAL ]]; then
124
+ CURRENT_COMPLETE="yes"
125
+ elif [[ $ACS_TOTAL -eq 0 ]]; then
126
+ # No ACs defined, just check tasks
127
+ CURRENT_COMPLETE="yes"
128
+ fi
129
+ fi
130
+
131
+ PREV="${PREV_COMPLETE["$US_ID"]:-no}"
132
+
133
+ # Detect transitions
134
+ if [[ "$CURRENT_COMPLETE" == "yes" ]] && [[ "$PREV" == "no" ]]; then
135
+ # User story just completed
136
+ bash "$HOOK_DIR/queue/enqueue.sh" "user-story.completed" "$INC_ID:$US_ID" 2>/dev/null
137
+ elif [[ "$CURRENT_COMPLETE" == "no" ]] && [[ "$PREV" == "yes" ]]; then
138
+ # User story reopened
139
+ bash "$HOOK_DIR/queue/enqueue.sh" "user-story.reopened" "$INC_ID:$US_ID" 2>/dev/null
140
+ fi
141
+
142
+ NEW_STATE="${NEW_STATE}${US_ID}=${CURRENT_COMPLETE}\n"
143
+ done
144
+
145
+ # Save new state
146
+ echo -e "$NEW_STATE" > "$US_STATE_FILE"
147
+
148
+ exit 0
@@ -1,7 +1,20 @@
1
1
  #!/bin/bash
2
2
  # post-tool-use.sh - Single dispatcher for ALL PostToolUse events
3
3
  # Replaces: post-task-edit, post-metadata-change, post-increment-planning, etc.
4
- # Goal: <10ms execution, queue heavy work for async processing
4
+ #
5
+ # Architecture (EDA v2):
6
+ # - Detectors run synchronously (fast, detect state transitions)
7
+ # - Detectors emit events to queue
8
+ # - Handlers process events asynchronously from queue
9
+ #
10
+ # Event flow:
11
+ # 1. metadata.json change -> lifecycle-detector -> increment.* events
12
+ # 2. tasks.md/spec.md change -> us-completion-detector -> user-story.* events
13
+ # 3. Events queued -> processor routes to handlers
14
+ #
15
+ # Goal: <10ms execution, all heavy work through event queue
16
+ #
17
+ # IMPORTANT: Never crash Claude, always exit 0
5
18
  set +e
6
19
 
7
20
  [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
@@ -20,25 +33,70 @@ INPUT=$(cat)
20
33
  FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
21
34
  [[ -z "$FILE_PATH" ]] && exit 0
22
35
 
23
- # Detect event type based on file
24
- EVENT_TYPE=""
36
+ # Extract increment ID from path
37
+ INC_ID=$(echo "$FILE_PATH" | grep -o '[0-9][0-9][0-9][0-9]-[^/]*' | head -1)
38
+ [[ -z "$INC_ID" ]] && exit 0
39
+
40
+ HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
41
+ DETECTOR_DIR="$HOOK_DIR/detectors"
42
+
43
+ # ============================================================================
44
+ # EDA DISPATCHER: Route to detectors based on file type
45
+ # Detectors detect state transitions and emit events to queue
46
+ # All heavy work is done by handlers processing the queue
47
+ # ============================================================================
48
+
25
49
  case "$FILE_PATH" in
26
- */.specweave/increments/*/tasks.md) EVENT_TYPE="task.updated" ;;
27
- */.specweave/increments/*/spec.md) EVENT_TYPE="spec.updated" ;;
28
- */.specweave/increments/*/metadata.json) EVENT_TYPE="metadata.changed" ;;
29
- */.specweave/increments/*/plan.md) EVENT_TYPE="plan.updated" ;;
30
- *) exit 0 ;; # Not a specweave file, ignore
50
+ */.specweave/increments/*/metadata.json)
51
+ # Metadata changed -> check for lifecycle transitions
52
+ # Events: increment.created, increment.done, increment.archived, increment.reopened
53
+ bash "$DETECTOR_DIR/lifecycle-detector.sh" "$INC_ID" 2>/dev/null &
54
+ ;;
55
+
56
+ */.specweave/increments/*/tasks.md|*/.specweave/increments/*/spec.md)
57
+ # Tasks or spec changed -> check for US completion
58
+ # Events: user-story.completed, user-story.reopened
59
+ bash "$DETECTOR_DIR/us-completion-detector.sh" "$INC_ID" 2>/dev/null &
60
+
61
+ # Also queue legacy event for backward compatibility
62
+ if [[ "$FILE_PATH" == *tasks.md ]]; then
63
+ bash "$HOOK_DIR/queue/enqueue.sh" "task.updated" "$INC_ID" 2>/dev/null &
64
+ else
65
+ bash "$HOOK_DIR/queue/enqueue.sh" "spec.updated" "$INC_ID" 2>/dev/null &
66
+ fi
67
+ ;;
68
+
69
+ */.specweave/increments/*/plan.md)
70
+ # Plan updated (for future use, currently no special handling)
71
+ bash "$HOOK_DIR/queue/enqueue.sh" "plan.updated" "$INC_ID" 2>/dev/null &
72
+ ;;
73
+
74
+ *)
75
+ # Not a specweave increment file, ignore
76
+ exit 0
77
+ ;;
31
78
  esac
32
79
 
33
- # Extract increment ID
34
- INC_ID=$(echo "$FILE_PATH" | grep -o '[0-9][0-9][0-9][0-9]-[^/]*' | head -1)
80
+ # NOTE: Removed synchronous status-update.sh call
81
+ # Status line updates are now EVENT-DRIVEN:
82
+ # - Updated on user-story.completed/reopened events
83
+ # - Updated on increment lifecycle events
84
+ # - NOT updated on every file edit (reduces flickering, race conditions)
35
85
 
36
- HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
86
+ # Ensure processor is running to handle queued events
87
+ # (Processor may have timed out from idle after 60s)
88
+ PROCESSOR="$HOOK_DIR/queue/processor.sh"
89
+ PID_FILE="$PROJECT_ROOT/.specweave/state/.processor.pid"
37
90
 
38
- # SYNCHRONOUS: Status update only (fast, always needed)
39
- bash "$HOOK_DIR/handlers/status-update.sh" "$INC_ID" 2>/dev/null &
91
+ # Quick check: if PID file exists and process running, skip
92
+ if [[ -f "$PID_FILE" ]]; then
93
+ PROC_PID=$(cat "$PID_FILE" 2>/dev/null)
94
+ if [[ -n "$PROC_PID" ]] && kill -0 "$PROC_PID" 2>/dev/null; then
95
+ exit 0 # Processor running, events will be processed
96
+ fi
97
+ fi
40
98
 
41
- # ASYNC: Queue heavy operations for background processing
42
- bash "$HOOK_DIR/queue/enqueue.sh" "$EVENT_TYPE" "$INC_ID" 2>/dev/null
99
+ # Start processor in background (non-daemon mode for quick processing)
100
+ [[ -f "$PROCESSOR" ]] && nohup bash "$PROCESSOR" > /dev/null 2>&1 &
43
101
 
44
102
  exit 0
@@ -0,0 +1,81 @@
1
+ #!/bin/bash
2
+ # completion-guard.sh - Block direct editing of metadata.json to "completed" status
3
+ #
4
+ # v0.28.63+: Prevents the auto-completion bug by blocking direct status changes to completed.
5
+ # Status MUST go through ready_for_review first, and only /specweave:done can mark completed.
6
+ #
7
+ # PreToolUse hook - can BLOCK the tool call by returning non-zero exit code
8
+ #
9
+ # IMPORTANT: This is a safety guard. Exit 0 allows, exit 2 blocks.
10
+ set +e
11
+
12
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
13
+
14
+ # Read stdin for tool input
15
+ INPUT=$(cat)
16
+
17
+ # Check if this is editing metadata.json with status: completed
18
+ # Pattern: file_path contains metadata.json AND (new_string OR content) contains "status"..."completed"
19
+
20
+ # Extract file_path
21
+ FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
22
+
23
+ # Only care about metadata.json files
24
+ if [[ "$FILE_PATH" != *metadata.json ]]; then
25
+ exit 0 # Allow
26
+ fi
27
+
28
+ # Extract the content being written (new_string for Edit, content for Write)
29
+ NEW_CONTENT=$(echo "$INPUT" | grep -o '"new_string"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1)
30
+ if [[ -z "$NEW_CONTENT" ]]; then
31
+ NEW_CONTENT=$(echo "$INPUT" | grep -o '"content"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1)
32
+ fi
33
+
34
+ # Check if trying to set status to "completed" directly
35
+ # This is a simple pattern match - if the edit/write contains status...completed
36
+ if echo "$NEW_CONTENT" | grep -q '"status"[[:space:]]*:[[:space:]]*"completed"'; then
37
+ # Read current status from file to check if coming from ready_for_review
38
+ if [[ -f "$FILE_PATH" ]]; then
39
+ CURRENT_STATUS=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' "$FILE_PATH" | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
40
+
41
+ if [[ "$CURRENT_STATUS" == "ready_for_review" ]]; then
42
+ # This is a valid transition - allow
43
+ exit 0
44
+ fi
45
+ fi
46
+
47
+ # BLOCK - trying to set completed without going through ready_for_review
48
+ cat << 'EOF'
49
+
50
+ ==============================================================================
51
+ BLOCKED: Direct status change to "completed" is not allowed (v0.28.63+)
52
+ ==============================================================================
53
+
54
+ You cannot directly set status to "completed" in metadata.json.
55
+
56
+ This prevents the auto-completion bug where increments get marked as
57
+ completed without proper validation.
58
+
59
+ CORRECT WORKFLOW:
60
+ 1. All tasks completed -> status auto-transitions to "ready_for_review"
61
+ 2. Run /specweave:done <increment-id> with explicit user confirmation
62
+ 3. Only then does status become "completed"
63
+
64
+ WHY THIS MATTERS:
65
+ - Ensures all ACs are checked in spec.md before closure
66
+ - Requires explicit user approval
67
+ - Maintains audit trail (approvedAt timestamp)
68
+
69
+ If you're implementing closure logic, use:
70
+ MetadataManager.updateStatus(incrementId, IncrementStatus.COMPLETED)
71
+
72
+ This will only succeed if current status is "ready_for_review".
73
+
74
+ ==============================================================================
75
+ EOF
76
+
77
+ exit 2 # Block the tool call
78
+ fi
79
+
80
+ # Allow other edits to metadata.json
81
+ exit 0
@@ -2,8 +2,12 @@
2
2
  # ac-validation-handler.sh - Validate AC completion status
3
3
  # Checks that completed tasks have their ACs checked in spec.md
4
4
  # Non-blocking, logs warnings only
5
+ #
6
+ # IMPORTANT: Never crash Claude, always exit 0
5
7
  set +e
6
8
 
9
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
10
+
7
11
  INC_ID="${1:-}"
8
12
  [[ -z "$INC_ID" ]] && exit 0
9
13
 
@@ -1,8 +1,12 @@
1
1
  #!/bin/bash
2
2
  # github-sync-handler.sh - Sync increment status to GitHub issue
3
3
  # Called async by processor, non-blocking, error-tolerant
4
+ #
5
+ # IMPORTANT: Never crash Claude, always exit 0
4
6
  set +e
5
7
 
8
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
9
+
6
10
  INC_ID="${1:-}"
7
11
  [[ -z "$INC_ID" ]] && exit 0
8
12
 
@@ -23,11 +27,28 @@ GITHUB_ENABLED=$(grep -o '"enabled"[[:space:]]*:[[:space:]]*true' "$CONFIG_FILE"
23
27
  # Throttle: max once per 5 minutes per increment
24
28
  THROTTLE_FILE="$PROJECT_ROOT/.specweave/state/.github-sync-$INC_ID"
25
29
  if [[ -f "$THROTTLE_FILE" ]]; then
26
- AGE=$(($(date +%s) - $(stat -f %m "$THROTTLE_FILE" 2>/dev/null || echo 0)))
30
+ if [[ "$(uname)" == "Darwin" ]]; then
31
+ AGE=$(($(date +%s) - $(stat -f %m "$THROTTLE_FILE" 2>/dev/null || echo 0)))
32
+ else
33
+ AGE=$(($(date +%s) - $(stat -c %Y "$THROTTLE_FILE" 2>/dev/null || echo 0)))
34
+ fi
27
35
  [[ $AGE -lt 300 ]] && exit 0
28
36
  fi
29
37
  touch "$THROTTLE_FILE"
30
38
 
39
+ # Cross-platform timeout wrapper
40
+ run_with_timeout() {
41
+ local timeout_secs="$1"
42
+ shift
43
+ if command -v timeout >/dev/null 2>&1; then
44
+ timeout "$timeout_secs" "$@" 2>/dev/null || true
45
+ elif command -v gtimeout >/dev/null 2>&1; then
46
+ gtimeout "$timeout_secs" "$@" 2>/dev/null || true
47
+ else
48
+ "$@" 2>/dev/null || true
49
+ fi
50
+ }
51
+
31
52
  # Load GitHub token
32
53
  GITHUB_TOKEN=""
33
54
  [[ -f "$PROJECT_ROOT/.env" ]] && GITHUB_TOKEN=$(grep -E "^GITHUB_TOKEN=" "$PROJECT_ROOT/.env" | cut -d'=' -f2- | tr -d '"'"'")
@@ -50,5 +71,5 @@ FEATURE_ID=""
50
71
 
51
72
  # Run sync (timeout 60s)
52
73
  cd "$PROJECT_ROOT" || exit 0
53
- GITHUB_TOKEN="$GITHUB_TOKEN" timeout 60 node "$SYNC_SCRIPT" "$FEATURE_ID" >/dev/null 2>&1
74
+ GITHUB_TOKEN="$GITHUB_TOKEN" run_with_timeout 60 node "$SYNC_SCRIPT" "$FEATURE_ID" >/dev/null 2>&1
54
75
  exit 0