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.
- package/CLAUDE.md +23 -0
- package/README.md +23 -1
- package/dist/src/cli/helpers/init/ado-repo-cloning.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/ado-repo-cloning.js +47 -84
- package/dist/src/cli/helpers/init/ado-repo-cloning.js.map +1 -1
- package/dist/src/cli/workers/clone-worker.d.ts +18 -0
- package/dist/src/cli/workers/clone-worker.d.ts.map +1 -0
- package/dist/src/cli/workers/clone-worker.js +191 -0
- package/dist/src/cli/workers/clone-worker.js.map +1 -0
- package/dist/src/core/background/index.d.ts +2 -1
- package/dist/src/core/background/index.d.ts.map +1 -1
- package/dist/src/core/background/index.js +1 -1
- package/dist/src/core/background/index.js.map +1 -1
- package/dist/src/core/background/job-launcher.d.ts +20 -0
- package/dist/src/core/background/job-launcher.d.ts.map +1 -1
- package/dist/src/core/background/job-launcher.js +88 -4
- package/dist/src/core/background/job-launcher.js.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +11 -0
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/types/increment-metadata.d.ts +33 -2
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/core/types/increment-metadata.js +32 -5
- package/dist/src/core/types/increment-metadata.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/commands/specweave-done.md +30 -1
- package/plugins/specweave/commands/specweave-jobs.md +7 -7
- package/plugins/specweave/commands/specweave-next.md +66 -14
- package/plugins/specweave/hooks/hooks.json +12 -0
- package/plugins/specweave/hooks/v2/detectors/lifecycle-detector.sh +85 -0
- package/plugins/specweave/hooks/v2/detectors/us-completion-detector.sh +148 -0
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +73 -15
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +81 -0
- package/plugins/specweave/hooks/v2/handlers/ac-validation-handler.sh +4 -0
- package/plugins/specweave/hooks/v2/handlers/github-sync-handler.sh +23 -2
- package/plugins/specweave/hooks/v2/handlers/living-docs-handler.sh +24 -3
- package/plugins/specweave/hooks/v2/handlers/living-specs-handler.sh +193 -0
- package/plugins/specweave/hooks/v2/handlers/status-line-handler.sh +165 -0
- package/plugins/specweave/hooks/v2/handlers/status-update.sh +12 -1
- package/plugins/specweave/hooks/v2/queue/dequeue.sh +4 -0
- package/plugins/specweave/hooks/v2/queue/enqueue.sh +50 -12
- package/plugins/specweave/hooks/v2/queue/processor.sh +141 -12
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +11 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +33 -2
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.js +32 -5
- 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. **
|
|
28
|
-
3. **
|
|
29
|
-
4. **
|
|
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 ✅ (
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
|
|
416
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
24
|
-
|
|
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/*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
#
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
39
|
-
|
|
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
|
-
#
|
|
42
|
-
|
|
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
|
-
|
|
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"
|
|
74
|
+
GITHUB_TOKEN="$GITHUB_TOKEN" run_with_timeout 60 node "$SYNC_SCRIPT" "$FEATURE_ID" >/dev/null 2>&1
|
|
54
75
|
exit 0
|