specweave 1.0.30 → 1.0.32

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 (79) hide show
  1. package/CLAUDE.md +140 -1235
  2. package/bin/specweave.js +23 -0
  3. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +3 -0
  4. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  5. package/dist/plugins/specweave-github/lib/github-client-v2.js +39 -0
  6. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  7. package/dist/src/cli/commands/set-sync-target.d.ts +41 -0
  8. package/dist/src/cli/commands/set-sync-target.d.ts.map +1 -0
  9. package/dist/src/cli/commands/set-sync-target.js +126 -0
  10. package/dist/src/cli/commands/set-sync-target.js.map +1 -0
  11. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  12. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +5 -8
  13. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  14. package/dist/src/core/hooks/HookScanner.d.ts +32 -0
  15. package/dist/src/core/hooks/HookScanner.d.ts.map +1 -1
  16. package/dist/src/core/hooks/HookScanner.js +125 -1
  17. package/dist/src/core/hooks/HookScanner.js.map +1 -1
  18. package/dist/src/core/hooks/types.d.ts +10 -1
  19. package/dist/src/core/hooks/types.d.ts.map +1 -1
  20. package/dist/src/core/increment/metadata-manager.d.ts +67 -1
  21. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  22. package/dist/src/core/increment/metadata-manager.js +93 -0
  23. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  24. package/dist/src/core/project/index.d.ts +21 -0
  25. package/dist/src/core/project/index.d.ts.map +1 -0
  26. package/dist/src/core/project/index.js +22 -0
  27. package/dist/src/core/project/index.js.map +1 -0
  28. package/dist/src/core/project/project-service.d.ts +122 -0
  29. package/dist/src/core/project/project-service.d.ts.map +1 -0
  30. package/dist/src/core/project/project-service.js +334 -0
  31. package/dist/src/core/project/project-service.js.map +1 -0
  32. package/dist/src/core/sync/external-tool-resolver.d.ts +171 -0
  33. package/dist/src/core/sync/external-tool-resolver.d.ts.map +1 -0
  34. package/dist/src/core/sync/external-tool-resolver.js +569 -0
  35. package/dist/src/core/sync/external-tool-resolver.js.map +1 -0
  36. package/dist/src/core/types/increment-metadata.d.ts +92 -0
  37. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  38. package/dist/src/hooks/processor.d.ts +7 -3
  39. package/dist/src/hooks/processor.d.ts.map +1 -1
  40. package/dist/src/hooks/processor.js +11 -5
  41. package/dist/src/hooks/processor.js.map +1 -1
  42. package/package.json +1 -1
  43. package/plugins/specweave/hooks/hooks.json +0 -69
  44. package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +96 -0
  45. package/plugins/specweave/hooks/v2/queue/processor.sh +13 -5
  46. package/plugins/specweave/lib/hooks/project-bridge.js +76 -0
  47. package/plugins/specweave/lib/hooks/update-tasks-md.js +0 -0
  48. package/plugins/specweave/lib/hooks/us-completion-orchestrator.js +0 -0
  49. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +67 -1
  50. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +93 -0
  51. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  52. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +92 -0
  53. package/plugins/specweave-github/lib/github-client-v2.js +39 -0
  54. package/plugins/specweave-github/lib/github-client-v2.ts +44 -0
  55. package/plugins/specweave/hooks/docs-changed.sh +0 -87
  56. package/plugins/specweave/hooks/human-input-required.sh +0 -83
  57. package/plugins/specweave/hooks/post-edit-write-consolidated.sh +0 -428
  58. package/plugins/specweave/hooks/post-first-increment.sh +0 -61
  59. package/plugins/specweave/hooks/post-increment-change.sh +0 -103
  60. package/plugins/specweave/hooks/post-increment-completion.sh +0 -513
  61. package/plugins/specweave/hooks/post-increment-planning.sh +0 -1204
  62. package/plugins/specweave/hooks/post-increment-status-change.sh +0 -243
  63. package/plugins/specweave/hooks/post-metadata-change.sh +0 -246
  64. package/plugins/specweave/hooks/post-spec-update.sh +0 -158
  65. package/plugins/specweave/hooks/post-task-completion.sh +0 -557
  66. package/plugins/specweave/hooks/post-task-edit.sh +0 -47
  67. package/plugins/specweave/hooks/post-user-story-complete.sh +0 -230
  68. package/plugins/specweave/hooks/pre-command-deduplication.sh +0 -68
  69. package/plugins/specweave/hooks/pre-edit-write-consolidated.sh +0 -225
  70. package/plugins/specweave/hooks/pre-implementation.sh +0 -75
  71. package/plugins/specweave/hooks/pre-increment-start.sh +0 -173
  72. package/plugins/specweave/hooks/pre-task-completion-edit.sh +0 -355
  73. package/plugins/specweave/hooks/pre-task-completion.sh +0 -269
  74. package/plugins/specweave/hooks/pre-tool-use.sh +0 -137
  75. package/plugins/specweave/hooks/session-start-reconcile.sh +0 -139
  76. package/plugins/specweave/hooks/shared/bulk-operation-detector.sh +0 -167
  77. package/plugins/specweave/hooks/test-pretooluse-env.sh +0 -72
  78. package/plugins/specweave/hooks/validate-increment-completion.sh +0 -113
  79. package/plugins/specweave/lib/hooks/consolidated-sync.js +0 -288
@@ -1,557 +0,0 @@
1
- #!/bin/bash
2
-
3
- # SpecWeave Post-Task-Completion Hook
4
- # Runs automatically after ANY task is marked complete via TodoWrite
5
- #
6
- # SMART SESSION-END DETECTION (v2.0):
7
- # =====================================
8
- # Problem: Claude creates multiple todo lists in one conversation
9
- # - List 1: [A, B, C] → completes → sound plays
10
- # - List 2: [D, E] → completes 30s later → sound plays again
11
- # - User hears sounds while Claude is still working!
12
- #
13
- # Solution: Inactivity-based detection
14
- # - Track time gaps BETWEEN TodoWrite calls
15
- # - If all tasks complete AND gap > INACTIVITY_THRESHOLD (15s)
16
- # → Session is winding down → Play sound
17
- # - If rapid completions (gap < threshold)
18
- # → Claude still actively working → Skip sound
19
- #
20
- # Example:
21
- # 10:00:00 - Task done (gap: 5s) → skip sound
22
- # 10:00:05 - Task done (gap: 5s) → skip sound
23
- # 10:00:10 - All done (gap: 5s) → skip sound (rapid work)
24
- # ...
25
- # 10:01:00 - All done (gap: 50s) → PLAY SOUND! (session ending)
26
- #
27
- # DEBOUNCING: Prevents duplicate fires (Claude Code calls hooks twice)
28
-
29
- # EMERGENCY FIXES (v0.24.3): CRITICAL SAFETY FIRST
30
- # - Remove set -e completely to prevent any errors from propagating
31
- # - Kill switch: SPECWEAVE_DISABLE_HOOKS=1 disables all hooks
32
- # - Circuit breaker: Auto-disable after 3 consecutive failures
33
- # - File locking: Only 1 instance can run at a time
34
- # - Aggressive debouncing: 5 seconds (was 5s, keeping it)
35
- # - Complete error isolation: ALL background work wrapped
36
-
37
- set +e # NEVER use set -e in hooks - it causes crashes
38
-
39
- # ============================================================================
40
- # PROJECT ROOT DETECTION (MUST BE FIRST - v0.26.1 FIX)
41
- # ============================================================================
42
- # CRITICAL FIX: PROJECT_ROOT must be defined BEFORE recursion guard creation
43
- # BUG (v0.26.0): Used $PROJECT_ROOT on line 71 but defined it on line 112
44
- # RESULT: Guard file created at wrong path (/.specweave/...) → recursion not prevented
45
- # See: Incident 2025-11-24 (3x PreToolUse hook fires, Claude Code crash)
46
-
47
- # Find project root by searching upward for .specweave/ directory
48
- # Works regardless of where hook is installed (source or .claude/hooks/)
49
- find_project_root() {
50
- local dir="$1"
51
- while [ "$dir" != "/" ]; do
52
- if [ -d "$dir/.specweave" ]; then
53
- echo "$dir"
54
- return 0
55
- fi
56
- dir="$(dirname "$dir")"
57
- done
58
- # Fallback: try current directory
59
- if [ -d "$(pwd)/.specweave" ]; then
60
- pwd
61
- else
62
- echo "$(pwd)"
63
- fi
64
- }
65
-
66
- PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
67
- cd "$PROJECT_ROOT" 2>/dev/null || true
68
-
69
- # ============================================================================
70
- # ULTRA-FAST EARLY EXIT FOR NON-SPECWEAVE PROJECTS (T-006 - v0.26.15)
71
- # ============================================================================
72
- # Skip ALL processing if not a SpecWeave project - saves ~50-100ms
73
- if [[ ! -d "$PROJECT_ROOT/.specweave" ]]; then
74
- exit 0
75
- fi
76
-
77
- # ============================================================================
78
- # RECURSION PREVENTION (CRITICAL - v0.26.0 - FILE-BASED GUARD)
79
- # ============================================================================
80
- # PROBLEM: Hooks that write files trigger other hooks, causing infinite loops.
81
- # OLD SOLUTION (v0.25.1): Environment variable SPECWEAVE_IN_HOOK=1
82
- # WHY IT FAILED: Background processes (&) create NEW shells that don't inherit env vars!
83
- #
84
- # NEW SOLUTION (v0.26.0): File-based recursion guard
85
- # - Guard file exists = already inside hook chain
86
- # - Works across ALL processes (not just current shell)
87
- # - Atomic operation (mkdir -p ensures thread safety)
88
- # - Cleanup guaranteed by trap EXIT
89
- #
90
- # Example infinite loop (BEFORE fix):
91
- # TodoWrite → post-task-completion.sh (sets SPECWEAVE_IN_HOOK=1)
92
- # → spawns background process (&)
93
- # → consolidated-sync.js (SPECWEAVE_IN_HOOK=0! lost!)
94
- # → fs.writeFile(tasks.md)
95
- # → post-edit-write-consolidated.sh (guard fails!)
96
- # → INFINITE RECURSION → 27 duplicate GitHub comments!
97
- #
98
- # With file-based guard (AFTER fix):
99
- # TodoWrite → post-task-completion.sh (creates .hook-recursion-guard file)
100
- # → spawns background process (&)
101
- # → consolidated-sync.js (inherits guard file!)
102
- # → fs.writeFile(tasks.md)
103
- # → post-edit-write-consolidated.sh (checks file, exits)
104
- # → NO RECURSION ✅
105
- #
106
- # See: .specweave/increments/0051-*/reports/GITHUB-COMMENT-RECURSION-ROOT-CAUSE-2025-11-24.md
107
- # See: ADR-0073 (Hook Recursion Prevention Strategy)
108
-
109
- RECURSION_GUARD_FILE="$PROJECT_ROOT/.specweave/state/.hook-recursion-guard"
110
-
111
- if [[ -f "$RECURSION_GUARD_FILE" ]]; then
112
- # Silent exit - we're already inside a hook chain
113
- echo "[$(date)] ⏭️ Recursion guard detected - skipping (already in hook)" >> "$DEBUG_LOG" 2>/dev/null || true
114
- exit 0
115
- fi
116
-
117
- # Create guard file (atomic operation)
118
- mkdir -p "$PROJECT_ROOT/.specweave/state" 2>/dev/null || true
119
- touch "$RECURSION_GUARD_FILE"
120
-
121
- # Ensure guard file is ALWAYS removed when script exits (even on error)
122
- trap 'rm -f "$RECURSION_GUARD_FILE" 2>/dev/null || true' EXIT SIGINT SIGTERM
123
-
124
- echo "[$(date)] 🔒 Recursion guard created" >> "$DEBUG_LOG" 2>/dev/null || true
125
-
126
- # EMERGENCY KILL SWITCH (after recursion guard)
127
- if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
128
- exit 0
129
- fi
130
-
131
- # ============================================================================
132
- # EMERGENCY SAFETY CHECKS
133
- # ============================================================================
134
-
135
- # CIRCUIT BREAKER: Auto-disable after consecutive failures
136
- CIRCUIT_BREAKER_FILE=".specweave/state/.hook-circuit-breaker"
137
- CIRCUIT_BREAKER_THRESHOLD=3
138
-
139
- mkdir -p ".specweave/state" 2>/dev/null || true
140
-
141
- if [[ -f "$CIRCUIT_BREAKER_FILE" ]]; then
142
- FAILURE_COUNT=$(cat "$CIRCUIT_BREAKER_FILE" 2>/dev/null || echo 0)
143
- if (( FAILURE_COUNT >= CIRCUIT_BREAKER_THRESHOLD )); then
144
- # Circuit breaker is OPEN - hooks are disabled
145
- exit 0
146
- fi
147
- fi
148
-
149
- # FILE LOCK: Skip - lock moved INSIDE background subshell (v0.24.4 fix)
150
- # Why: Lock must protect the ACTUAL work, not just the script startup
151
- # Old behavior: Lock released when main script exits (before background work completes)
152
- # New behavior: Lock held until background work completes (inside subshell)
153
- # This prevents race conditions where rapid TodoWrite calls spawn multiple concurrent processes
154
-
155
- # ============================================================================
156
- # CONFIGURATION
157
- # ============================================================================
158
-
159
- # Debounce window to prevent duplicate hook fires
160
- # AGGRESSIVE: 5 seconds to prevent rapid-fire executions
161
- DEBOUNCE_SECONDS=5
162
-
163
- # Inactivity threshold to detect session end
164
- # If gap between TodoWrite calls > this value, assume session is ending
165
- INACTIVITY_THRESHOLD=120 # seconds (2 minutes - increased from 15s to reduce false positives)
166
-
167
- # File paths
168
- LOGS_DIR=".specweave/logs"
169
- LAST_FIRE_FILE="$LOGS_DIR/last-hook-fire"
170
- LAST_TODOWRITE_FILE="$LOGS_DIR/last-todowrite-time"
171
- DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
172
- TASKS_LOG="$LOGS_DIR/tasks.log"
173
-
174
- mkdir -p "$LOGS_DIR" 2>/dev/null || true
175
-
176
- # Log rotation: Keep tasks.log under 100KB (keep last 200 lines)
177
- if [[ -f "$TASKS_LOG" ]] && [[ $(wc -c < "$TASKS_LOG" 2>/dev/null || echo 0) -gt 102400 ]]; then
178
- tail -200 "$TASKS_LOG" > "$TASKS_LOG.tmp" 2>/dev/null || true
179
- mv "$TASKS_LOG.tmp" "$TASKS_LOG" 2>/dev/null || true
180
- echo "[$(date)] Log rotated (was >100KB)" >> "$TASKS_LOG" 2>/dev/null || true
181
- fi
182
-
183
- # ============================================================================
184
- # DEBOUNCING
185
- # ============================================================================
186
-
187
- CURRENT_TIME=$(date +%s)
188
-
189
- # Skip if hook fired within last N seconds (prevents duplicates)
190
- if [ -f "$LAST_FIRE_FILE" ]; then
191
- LAST_FIRE=$(cat "$LAST_FIRE_FILE" 2>/dev/null || echo "0")
192
- TIME_DIFF=$((CURRENT_TIME - LAST_FIRE))
193
-
194
- if [ "$TIME_DIFF" -lt "$DEBOUNCE_SECONDS" ]; then
195
- echo "[$(date)] ⏭️ Debounced (last fire: ${TIME_DIFF}s ago)" >> "$DEBUG_LOG" 2>/dev/null || true
196
- echo '{"continue":true}'
197
- exit 0
198
- fi
199
- fi
200
-
201
- echo "$CURRENT_TIME" > "$LAST_FIRE_FILE"
202
-
203
- # ============================================================================
204
- # CAPTURE INPUT
205
- # ============================================================================
206
-
207
- STDIN_DATA=$(mktemp)
208
- cat > "$STDIN_DATA"
209
-
210
- echo "[$(date)] 📋 TodoWrite hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
211
- echo "[$(date)] Input JSON:" >> "$DEBUG_LOG" 2>/dev/null || true
212
- cat "$STDIN_DATA" >> "$DEBUG_LOG" 2>/dev/null || true
213
- echo "" >> "$DEBUG_LOG" 2>/dev/null || true
214
-
215
- # ============================================================================
216
- # INACTIVITY DETECTION
217
- # ============================================================================
218
-
219
- INACTIVITY_GAP=0
220
- PREVIOUS_TODOWRITE_TIME=0
221
-
222
- if [ -f "$LAST_TODOWRITE_FILE" ]; then
223
- PREVIOUS_TODOWRITE_TIME=$(cat "$LAST_TODOWRITE_FILE" 2>/dev/null || echo "0")
224
- INACTIVITY_GAP=$((CURRENT_TIME - PREVIOUS_TODOWRITE_TIME))
225
- echo "[$(date)] ⏱️ Inactivity gap: ${INACTIVITY_GAP}s (threshold: ${INACTIVITY_THRESHOLD}s)" >> "$DEBUG_LOG" 2>/dev/null || true
226
- else
227
- echo "[$(date)] 🆕 First TodoWrite in session" >> "$DEBUG_LOG" 2>/dev/null || true
228
- fi
229
-
230
- # Save current timestamp for next call
231
- echo "$CURRENT_TIME" > "$LAST_TODOWRITE_FILE"
232
-
233
- # ============================================================================
234
- # PARSE TASK COMPLETION STATE
235
- # ============================================================================
236
-
237
- ALL_COMPLETED=false
238
-
239
- if command -v jq >/dev/null 2>&1; then
240
- # Use jq if available (more reliable)
241
- PENDING_COUNT=$(jq -r '.tool_input.todos // [] | map(select(.status != "completed")) | length' "$STDIN_DATA" 2>/dev/null || echo "1")
242
- TOTAL_COUNT=$(jq -r '.tool_input.todos // [] | length' "$STDIN_DATA" 2>/dev/null || echo "0")
243
-
244
- echo "[$(date)] 📊 Tasks: $((TOTAL_COUNT - PENDING_COUNT))/$TOTAL_COUNT completed" >> "$DEBUG_LOG" 2>/dev/null || true
245
-
246
- if [ "$PENDING_COUNT" = "0" ] && [ "$TOTAL_COUNT" != "0" ]; then
247
- ALL_COMPLETED=true
248
- fi
249
- else
250
- # Fallback: Simple grep check (less reliable but works without jq)
251
- if grep -q '"status":"pending"\|"status":"in_progress"' "$STDIN_DATA" 2>/dev/null; then
252
- ALL_COMPLETED=false
253
- else
254
- ALL_COMPLETED=true
255
- fi
256
- fi
257
-
258
- rm -f "$STDIN_DATA"
259
-
260
- # ============================================================================
261
- # SESSION-END DETECTION LOGIC
262
- # ============================================================================
263
-
264
- SESSION_ENDING=false
265
- DECISION_REASON=""
266
-
267
- if [ "$ALL_COMPLETED" = "true" ]; then
268
- if [ "$INACTIVITY_GAP" -ge "$INACTIVITY_THRESHOLD" ]; then
269
- SESSION_ENDING=true
270
- DECISION_REASON="All tasks complete + ${INACTIVITY_GAP}s inactivity ≥ ${INACTIVITY_THRESHOLD}s threshold"
271
- echo "[$(date)] 🎉 SESSION ENDING DETECTED! ($DECISION_REASON)" >> "$DEBUG_LOG" 2>/dev/null || true
272
- else
273
- DECISION_REASON="All tasks complete, but rapid activity (${INACTIVITY_GAP}s < ${INACTIVITY_THRESHOLD}s) - Claude likely creating more work"
274
- echo "[$(date)] ⚡ $DECISION_REASON (no sound)" >> "$DEBUG_LOG" 2>/dev/null || true
275
- fi
276
- else
277
- DECISION_REASON="Tasks remaining in current list"
278
- echo "[$(date)] 🔄 $DECISION_REASON (no sound)" >> "$DEBUG_LOG" 2>/dev/null || true
279
- fi
280
-
281
- # ============================================================================
282
- # CONSOLIDATED BACKGROUND WORK (ALL I/O OPERATIONS IN SINGLE PROCESS)
283
- # ============================================================================
284
- # EMERGENCY FIX: Instead of spawning 6+ separate Node.js processes, consolidate
285
- # ALL background work into a single background job with complete error isolation
286
- # This prevents process exhaustion that causes Claude Code crashes
287
-
288
- (
289
- set +e # Disable error propagation in background job
290
-
291
- # ============================================================================
292
- # FILE LOCK (v0.24.4): Protect ACTUAL background work, not just script startup
293
- # ============================================================================
294
- # CRITICAL FIX: Lock was previously in main script, released before work completed
295
- # NOW: Lock is INSIDE background subshell, held until ALL work completes
296
- # This prevents race conditions from rapid TodoWrite calls
297
-
298
- LOCK_FILE=".specweave/state/.hook-post-task.lock"
299
- LOCK_TIMEOUT=30 # seconds (longer timeout for background work)
300
-
301
- LOCK_ACQUIRED=false
302
- for i in {1..30}; do
303
- if mkdir "$LOCK_FILE" 2>/dev/null; then
304
- LOCK_ACQUIRED=true
305
- # Ensure lock is removed when background job exits
306
- trap 'rmdir "$LOCK_FILE" 2>/dev/null || true' EXIT
307
- break
308
- fi
309
-
310
- # Check for stale lock
311
- if [[ -d "$LOCK_FILE" ]]; then
312
- LOCK_AGE=$(($(date +%s) - $(stat -f "%m" "$LOCK_FILE" 2>/dev/null || echo 0)))
313
- if (( LOCK_AGE > LOCK_TIMEOUT )); then
314
- echo "[$(date)] 🔓 Removing stale lock (age: ${LOCK_AGE}s)" >> "$DEBUG_LOG" 2>/dev/null || true
315
- rmdir "$LOCK_FILE" 2>/dev/null || true
316
- continue
317
- fi
318
- fi
319
-
320
- sleep 1 # Wait longer between attempts (background work can take time)
321
- done
322
-
323
- if [[ "$LOCK_ACQUIRED" == "false" ]]; then
324
- echo "[$(date)] ⏭️ Another sync in progress, skipping (lock held)" >> "$DEBUG_LOG" 2>/dev/null || true
325
- exit 0
326
- fi
327
-
328
- echo "[$(date)] 🔒 Lock acquired, starting background work" >> "$DEBUG_LOG" 2>/dev/null || true
329
-
330
- # ============================================================================
331
- # CRITICAL FIX: Read active increments from state file (NOT time-based detection)
332
- # ============================================================================
333
- # WHY: The old logic used 'ls -td' which:
334
- # 1. Could pick up completed increments if recently modified
335
- # 2. Caused infinite loops on increments with bad AC data
336
- # 3. Wasted resources syncing 50+ completed increments
337
- #
338
- # NEW: Only sync increments that are ACTIVELY being worked on
339
- # Source of truth: .specweave/state/active-increment.json
340
- # ============================================================================
341
-
342
- ACTIVE_STATE_FILE=".specweave/state/active-increment.json"
343
- ACTIVE_INCREMENTS=()
344
-
345
- if [[ ! -f "$ACTIVE_STATE_FILE" ]]; then
346
- echo "[$(date)] ⚠️ No active state file found, skipping all background work" >> "$DEBUG_LOG" 2>/dev/null || true
347
- echo "0" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true # Reset on success
348
- exit 0
349
- fi
350
-
351
- if command -v jq >/dev/null 2>&1; then
352
- # Use jq to parse the active increments array
353
- # NOTE: mapfile requires bash 4+, macOS has bash 3.2, use while read instead
354
- while IFS= read -r increment; do
355
- ACTIVE_INCREMENTS+=("$increment")
356
- done < <(jq -r '.ids[]' "$ACTIVE_STATE_FILE" 2>/dev/null)
357
- else
358
- # Fallback: simple grep parsing (less reliable, but works without jq)
359
- # Match only increment IDs: 4 digits, dash, then letters/numbers/dashes
360
- echo "[$(date)] ⚠️ jq not found, using fallback parsing" >> "$DEBUG_LOG" 2>/dev/null || true
361
- ACTIVE_INCREMENTS=($(grep -o '"[0-9]\{4\}-[a-zA-Z0-9][a-zA-Z0-9_-]*"' "$ACTIVE_STATE_FILE" 2>/dev/null | tr -d '"'))
362
- fi
363
-
364
- # If no active increments, skip all work
365
- if [[ ${#ACTIVE_INCREMENTS[@]} -eq 0 ]]; then
366
- echo "[$(date)] ✓ No active increments, skipping all background work (this is normal)" >> "$DEBUG_LOG" 2>/dev/null || true
367
- echo "0" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true # Reset on success
368
- exit 0
369
- fi
370
-
371
- echo "[$(date)] 📋 Found ${#ACTIVE_INCREMENTS[@]} active increment(s): ${ACTIVE_INCREMENTS[*]}" >> "$DEBUG_LOG" 2>/dev/null || true
372
-
373
- # Only proceed if Node.js is available
374
- if ! command -v node &> /dev/null; then
375
- echo "[$(date)] Node.js not found, skipping background work" >> "$DEBUG_LOG" 2>/dev/null || true
376
- echo "0" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
377
- exit 0
378
- fi
379
-
380
- # Track if ANY operation succeeded (for circuit breaker)
381
- ANY_SUCCESS=false
382
-
383
- # ============================================================================
384
- # PROCESS EACH ACTIVE INCREMENT
385
- # ============================================================================
386
- for CURRENT_INCREMENT in "${ACTIVE_INCREMENTS[@]}"; do
387
- echo "[$(date)] 🔄 Processing increment: $CURRENT_INCREMENT" >> "$DEBUG_LOG" 2>/dev/null || true
388
-
389
- # Safety check: Verify increment exists and is not archived
390
- if [[ ! -d ".specweave/increments/$CURRENT_INCREMENT" ]]; then
391
- echo "[$(date)] ⚠️ Increment $CURRENT_INCREMENT not found, skipping" >> "$DEBUG_LOG" 2>/dev/null || true
392
- continue
393
- fi
394
-
395
- if [[ -d ".specweave/increments/_archive/$CURRENT_INCREMENT" ]]; then
396
- echo "[$(date)] ⚠️ Increment $CURRENT_INCREMENT is archived, skipping" >> "$DEBUG_LOG" 2>/dev/null || true
397
- continue
398
- fi
399
-
400
- # Additional safety: Check metadata status (skip completed/abandoned)
401
- METADATA_FILE=".specweave/increments/$CURRENT_INCREMENT/metadata.json"
402
- if [[ -f "$METADATA_FILE" ]] && command -v jq >/dev/null 2>&1; then
403
- INCREMENT_STATUS=$(jq -r '.status // "active"' "$METADATA_FILE" 2>/dev/null || echo "active")
404
- if [[ "$INCREMENT_STATUS" == "completed" ]] || [[ "$INCREMENT_STATUS" == "abandoned" ]]; then
405
- echo "[$(date)] ⏭️ Skipping $CURRENT_INCREMENT (status: $INCREMENT_STATUS)" >> "$DEBUG_LOG" 2>/dev/null || true
406
- continue
407
- fi
408
- fi
409
-
410
- # ============================================================================
411
- # CONSOLIDATED SYNC (v0.24.4 - PERFORMANCE OPTIMIZATION)
412
- # ============================================================================
413
- # EMERGENCY FIX (v0.26.0-hotfix): DISABLED TO PREVENT CLAUDE CODE CRASHES
414
- #
415
- # ROOT CAUSE: consolidated-sync.js makes 5+ Edit/Write operations, each triggering
416
- # 3 hooks (PreToolUse, PostToolUse×2), resulting in 15+ hook invocations per task.
417
- # This causes process exhaustion and Claude Code crashes.
418
- #
419
- # NEW STRATEGY: Run consolidated sync ONLY at:
420
- # 1. Session end (all tasks done + 120s inactivity)
421
- # 2. Manual sync (/sw:sync-docs command)
422
- # 3. Increment closure (/sw:done validation)
423
- #
424
- # See: .specweave/increments/0051-*/reports/CLAUDE-CODE-CRASH-ROOT-CAUSE-2025-11-23.md
425
- # See: ADR-0072 (Post-Task Hook Simplification)
426
- # ============================================================================
427
-
428
- # RE-ENABLED: Automatic sync on each TodoWrite (ACCEPTING CRASH RISK)
429
- # WARNING: This can cause Claude Code crashes if too many TodoWrite events fire rapidly
430
- # User explicitly requested re-enabling despite crash risk
431
-
432
- echo "[$(date)] 🚀 Running consolidated sync" >> "$DEBUG_LOG" 2>/dev/null || true
433
-
434
- # Find consolidated sync script
435
- CONSOLIDATED_SCRIPT=""
436
- if [ -f "$PROJECT_ROOT/plugins/specweave/lib/hooks/consolidated-sync.js" ]; then
437
- CONSOLIDATED_SCRIPT="$PROJECT_ROOT/plugins/specweave/lib/hooks/consolidated-sync.js"
438
- elif [ -f "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/consolidated-sync.js" ]; then
439
- CONSOLIDATED_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/consolidated-sync.js"
440
- elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/consolidated-sync.js" ]; then
441
- CONSOLIDATED_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/consolidated-sync.js"
442
- elif [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/lib/hooks/consolidated-sync.js" ]; then
443
- CONSOLIDATED_SCRIPT="${CLAUDE_PLUGIN_ROOT}/lib/hooks/consolidated-sync.js"
444
- fi
445
-
446
- if [ -n "$CONSOLIDATED_SCRIPT" ]; then
447
- # Load GITHUB_TOKEN from .env for gh CLI authentication
448
- if [ -f "$PROJECT_ROOT/.env" ]; then
449
- # Extract GITHUB_TOKEN from .env (handles various formats)
450
- GITHUB_TOKEN_FROM_ENV=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
451
- if [ -n "$GITHUB_TOKEN_FROM_ENV" ]; then
452
- export GITHUB_TOKEN="$GITHUB_TOKEN_FROM_ENV"
453
- echo "[$(date)] ✅ GitHub token loaded from .env" >> "$DEBUG_LOG" 2>/dev/null || true
454
- fi
455
- fi
456
-
457
- # FIX (v0.26.0): Skip GitHub sync in post-task-completion hook
458
- # GitHub sync should ONLY run on increment COMPLETION, not task completion
459
- # This prevents 27 duplicate comments (see root cause analysis)
460
- export SKIP_GITHUB_SYNC=true
461
-
462
- # v0.26.1: SKIP_US_SYNC removed - no longer needed!
463
- # Automatic US sync restored with multi-layer protection:
464
- # 1. SKIP_EXTERNAL_SYNC guard at LivingDocsSync layer (prevents recursion)
465
- # 2. Smart throttle (60s window prevents spam)
466
- # 3. fs.writeFile() confirmed to NOT trigger hooks (test validated)
467
- # See: FS-WRITEFILE-HOOK-TEST-RESULTS-2025-11-24.md, ADR-0129
468
-
469
- # Run consolidated sync (single Node.js process handles ALL operations)
470
- if (cd "$PROJECT_ROOT" && node "$CONSOLIDATED_SCRIPT" "$CURRENT_INCREMENT") >> "$DEBUG_LOG" 2>&1; then
471
- echo "[$(date)] ✅ Consolidated sync completed" >> "$DEBUG_LOG" 2>/dev/null || true
472
- ANY_SUCCESS=true
473
- else
474
- echo "[$(date)] ⚠️ Consolidated sync failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
475
- fi
476
- else
477
- echo "[$(date)] ⚠️ consolidated-sync.js not found, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
478
- fi
479
-
480
- done # End of ACTIVE_INCREMENTS loop
481
-
482
- # ============================================================================
483
- # 6. STATUS LINE UPDATE (GLOBAL - outside loop)
484
- # ============================================================================
485
- echo "[$(date)] 📊 Updating status line" >> "$DEBUG_LOG" 2>/dev/null || true
486
-
487
- HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
488
- if bash "$HOOK_DIR/lib/update-status-line.sh" >> "$DEBUG_LOG" 2>&1; then
489
- echo "[$(date)] ✅ Status line updated" >> "$DEBUG_LOG" 2>/dev/null || true
490
- ANY_SUCCESS=true
491
- else
492
- echo "[$(date)] ⚠️ Status line update failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
493
- fi
494
-
495
- # ============================================================================
496
- # CIRCUIT BREAKER UPDATE
497
- # ============================================================================
498
- if [ "$ANY_SUCCESS" = "true" ]; then
499
- echo "0" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
500
- else
501
- CURRENT_FAILURES=$(cat "$CIRCUIT_BREAKER_FILE" 2>/dev/null || echo 0)
502
- echo "$((CURRENT_FAILURES + 1))" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
503
- fi
504
-
505
- echo "[$(date)] Consolidated background work completed (success=$ANY_SUCCESS)" >> "$DEBUG_LOG" 2>/dev/null || true
506
-
507
- ) &
508
-
509
- # Disown background process immediately
510
- disown 2>/dev/null || true
511
-
512
- # ============================================================================
513
- # PLAY SOUND (only if session is truly ending)
514
- # ============================================================================
515
-
516
- play_sound() {
517
- case "$(uname -s)" in
518
- Darwin)
519
- afplay /System/Library/Sounds/Glass.aiff 2>/dev/null || true
520
- ;;
521
- Linux)
522
- paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || \
523
- aplay /usr/share/sounds/alsa/Front_Center.wav 2>/dev/null || true
524
- ;;
525
- MINGW*|MSYS*|CYGWIN*)
526
- powershell -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\chimes.wav').PlaySync();" 2>/dev/null || true
527
- ;;
528
- esac
529
- }
530
-
531
- if [ "$SESSION_ENDING" = "true" ]; then
532
- echo "[$(date)] 🔔 Playing completion sound" >> "$DEBUG_LOG" 2>/dev/null || true
533
- play_sound
534
- fi
535
-
536
- # ============================================================================
537
- # LOGGING
538
- # ============================================================================
539
-
540
- echo "[$(date)] Status: All_completed=$ALL_COMPLETED, Session_ending=$SESSION_ENDING, Inactivity=${INACTIVITY_GAP}s" >> "$TASKS_LOG" 2>/dev/null || true
541
- echo "[$(date)] Reason: $DECISION_REASON" >> "$TASKS_LOG" 2>/dev/null || true
542
- echo "---" >> "$DEBUG_LOG" 2>/dev/null || true
543
-
544
- # ============================================================================
545
- # OUTPUT TO CLAUDE
546
- # ============================================================================
547
-
548
- if [ "$SESSION_ENDING" = "true" ]; then
549
- printf '{"continue":true,"systemMessage":"🎉 ALL WORK COMPLETED! Session ending detected (%ss inactivity). Remember to update documentation with inline edits to CLAUDE.md and README.md as needed."}\n' "$INACTIVITY_GAP"
550
- elif [ "$ALL_COMPLETED" = "true" ]; then
551
- printf '{"continue":true,"systemMessage":"✅ Task batch completed (%ss since last activity). Continuing work..."}\n' "$INACTIVITY_GAP"
552
- else
553
- echo '{"continue":true,"systemMessage":"✅ Task completed. More tasks remaining - keep going!"}'
554
- fi
555
-
556
- # ALWAYS exit 0 - NEVER let hook errors crash Claude Code
557
- exit 0
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # post-task-edit.sh (v0.26.16)
4
- #
5
- # Lightweight hook triggered after Edit on tasks.md
6
- # ONLY updates status line cache - no heavy processing!
7
- #
8
- # Purpose: Keep status line in sync when tasks are marked complete via Edit
9
- #
10
- # Performance target: <20ms (just calls update-status-line.sh)
11
- #
12
- set +e
13
-
14
- # EMERGENCY KILL SWITCH
15
- [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
16
-
17
- # Find hook directory
18
- HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
-
20
- # Consume stdin (required for PostToolUse hooks)
21
- cat > /dev/null
22
-
23
- # Update status line cache (force refresh by removing mtime marker)
24
- PROJECT_ROOT="$PWD"
25
- while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
26
- PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
27
- done
28
-
29
- if [[ -d "$PROJECT_ROOT/.specweave" ]]; then
30
- # Remove mtime markers to force full refresh
31
- rm -f "$PROJECT_ROOT/.specweave/state/.status-mtime-"* 2>/dev/null || true
32
-
33
- # Update status line
34
- bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
35
-
36
- # CRASH PREVENTION: Check task count in active increments
37
- for tasks_file in "$PROJECT_ROOT/.specweave/increments"/[0-9]*/tasks.md; do
38
- [[ -f "$tasks_file" ]] || continue
39
- task_count=$(grep -c "^### T-" "$tasks_file" 2>/dev/null || echo "0")
40
- if [[ "$task_count" -gt 8 ]]; then
41
- inc_name=$(basename "$(dirname "$tasks_file")")
42
- echo "⚠️ CRASH RISK: $inc_name has $task_count tasks (max 8). Split increment!" >&2
43
- fi
44
- done
45
- fi
46
-
47
- exit 0