specweave 0.24.9 → 0.24.13

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 (57) hide show
  1. package/CLAUDE.md +158 -10
  2. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +28 -0
  3. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  4. package/dist/plugins/specweave-github/lib/github-client-v2.js +47 -0
  5. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  6. package/dist/src/cli/commands/init.d.ts.map +1 -1
  7. package/dist/src/cli/commands/init.js +15 -7
  8. package/dist/src/cli/commands/init.js.map +1 -1
  9. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  10. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +13 -1
  11. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  12. package/dist/src/cli/helpers/issue-tracker/index.js +4 -4
  13. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  14. package/dist/src/cli/helpers/issue-tracker/types.d.ts +1 -1
  15. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  16. package/dist/src/core/config/types.d.ts +46 -12
  17. package/dist/src/core/config/types.d.ts.map +1 -1
  18. package/dist/src/core/config/types.js +0 -5
  19. package/dist/src/core/config/types.js.map +1 -1
  20. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -1
  21. package/dist/src/core/repo-structure/repo-bulk-discovery.js +20 -5
  22. package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -1
  23. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  24. package/dist/src/core/repo-structure/repo-structure-manager.js +29 -14
  25. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  26. package/dist/src/sync/format-preservation-sync.d.ts.map +1 -1
  27. package/dist/src/sync/format-preservation-sync.js +23 -5
  28. package/dist/src/sync/format-preservation-sync.js.map +1 -1
  29. package/dist/src/sync/frontmatter-updater.d.ts +57 -0
  30. package/dist/src/sync/frontmatter-updater.d.ts.map +1 -0
  31. package/dist/src/sync/frontmatter-updater.js +147 -0
  32. package/dist/src/sync/frontmatter-updater.js.map +1 -0
  33. package/dist/src/sync/sync-coordinator.d.ts +22 -1
  34. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  35. package/dist/src/sync/sync-coordinator.js +268 -21
  36. package/dist/src/sync/sync-coordinator.js.map +1 -1
  37. package/dist/src/types/living-docs-us-file.d.ts +18 -0
  38. package/dist/src/types/living-docs-us-file.d.ts.map +1 -1
  39. package/dist/src/types/living-docs-us-file.js.map +1 -1
  40. package/package.json +1 -1
  41. package/plugins/specweave/.claude-plugin/plugin.json +17 -11
  42. package/plugins/specweave/agents/architect/AGENT.md +115 -45
  43. package/plugins/specweave/agents/test-aware-planner/AGENT.md +76 -6
  44. package/plugins/specweave/hooks/lib/update-status-line.sh +19 -0
  45. package/plugins/specweave/hooks/post-edit-write-consolidated.sh +335 -0
  46. package/plugins/specweave/hooks/post-metadata-change.sh +40 -6
  47. package/plugins/specweave/hooks/post-task-completion.sh +93 -9
  48. package/plugins/specweave/hooks/pre-edit-spec.sh +0 -11
  49. package/plugins/specweave/hooks/pre-edit-write-consolidated.sh +188 -0
  50. package/plugins/specweave/hooks/pre-task-completion.sh +11 -0
  51. package/plugins/specweave/hooks/pre-write-spec.sh +0 -11
  52. package/plugins/specweave/lib/hooks/consolidated-sync.js +61 -3
  53. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +294 -0
  54. package/plugins/specweave-github/lib/github-client-v2.js +46 -0
  55. package/plugins/specweave-github/lib/github-client-v2.ts +65 -0
  56. package/plugins/specweave-release/commands/specweave-release-npm.md +130 -2
  57. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +60 -0
@@ -22,12 +22,7 @@
22
22
  # EMERGENCY FIX v0.24.3: Remove set -e - it causes Claude Code crashes!
23
23
  set +e
24
24
 
25
- # EMERGENCY KILL SWITCH
26
- if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
27
- exit 0
28
- fi
29
-
30
- # Find project root
25
+ # Find project root (must be BEFORE recursion guard to get PROJECT_ROOT)
31
26
  find_project_root() {
32
27
  local dir="$PWD"
33
28
  while [[ "$dir" != "/" ]]; do
@@ -45,9 +40,48 @@ LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
45
40
  DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
46
41
  HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
47
42
 
43
+ # ============================================================================
44
+ # RECURSION PREVENTION (CRITICAL - v0.26.0 - FILE-BASED GUARD)
45
+ # ============================================================================
46
+ # NEW SOLUTION (v0.26.0): File-based recursion guard
47
+ # See: ADR-0073 (Hook Recursion Prevention Strategy)
48
+
49
+ RECURSION_GUARD_FILE="$PROJECT_ROOT/.specweave/state/.hook-recursion-guard"
50
+
51
+ if [[ -f "$RECURSION_GUARD_FILE" ]]; then
52
+ # Silent exit - we're already inside a hook chain
53
+ exit 0
54
+ fi
55
+
56
+ # EMERGENCY KILL SWITCH
57
+ if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
58
+ exit 0
59
+ fi
60
+
48
61
  # Ensure logs directory exists
49
62
  mkdir -p "$LOGS_DIR" 2>/dev/null || true
50
63
 
64
+ # ============================================================================
65
+ # EARLY EXIT OPTIMIZATION (v0.25.0): Ultra-Fast Rejection of Non-Metadata Changes
66
+ # ============================================================================
67
+ # This hook should ONLY run for metadata.json changes.
68
+ # 99.9% of Edit/Write operations are NOT metadata.json.
69
+ # Do fastest possible check first to minimize overhead.
70
+
71
+ # Quick check: If TOOL_USE_CONTENT doesn't contain "metadata.json", exit immediately
72
+ if [[ -n "${TOOL_USE_CONTENT:-}" ]] && [[ "$TOOL_USE_CONTENT" != *"metadata.json"* ]]; then
73
+ exit 0 # Fast path: Not metadata.json
74
+ fi
75
+
76
+ # Quick check: If TOOL_USE_ARGS doesn't contain "metadata.json", exit immediately
77
+ if [[ -n "${TOOL_USE_ARGS:-}" ]] && [[ "$TOOL_USE_ARGS" != *"metadata.json"* ]]; then
78
+ exit 0 # Fast path: Not metadata.json
79
+ fi
80
+
81
+ # ============================================================================
82
+ # STANDARD FILE DETECTION (Only if quick checks passed)
83
+ # ============================================================================
84
+
51
85
  # Extract modified file from environment variables (Claude Code provides this)
52
86
  MODIFIED_FILE=""
53
87
 
@@ -36,10 +36,13 @@
36
36
 
37
37
  set +e # NEVER use set -e in hooks - it causes crashes
38
38
 
39
- # EMERGENCY KILL SWITCH FIRST (before anything else)
40
- if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
41
- exit 0
42
- fi
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)
43
46
 
44
47
  # Find project root by searching upward for .specweave/ directory
45
48
  # Works regardless of where hook is installed (source or .claude/hooks/)
@@ -63,6 +66,60 @@ find_project_root() {
63
66
  PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
64
67
  cd "$PROJECT_ROOT" 2>/dev/null || true
65
68
 
69
+ # ============================================================================
70
+ # RECURSION PREVENTION (CRITICAL - v0.26.0 - FILE-BASED GUARD)
71
+ # ============================================================================
72
+ # PROBLEM: Hooks that write files trigger other hooks, causing infinite loops.
73
+ # OLD SOLUTION (v0.25.1): Environment variable SPECWEAVE_IN_HOOK=1
74
+ # WHY IT FAILED: Background processes (&) create NEW shells that don't inherit env vars!
75
+ #
76
+ # NEW SOLUTION (v0.26.0): File-based recursion guard
77
+ # - Guard file exists = already inside hook chain
78
+ # - Works across ALL processes (not just current shell)
79
+ # - Atomic operation (mkdir -p ensures thread safety)
80
+ # - Cleanup guaranteed by trap EXIT
81
+ #
82
+ # Example infinite loop (BEFORE fix):
83
+ # TodoWrite → post-task-completion.sh (sets SPECWEAVE_IN_HOOK=1)
84
+ # → spawns background process (&)
85
+ # → consolidated-sync.js (SPECWEAVE_IN_HOOK=0! lost!)
86
+ # → fs.writeFile(tasks.md)
87
+ # → post-edit-write-consolidated.sh (guard fails!)
88
+ # → INFINITE RECURSION → 27 duplicate GitHub comments!
89
+ #
90
+ # With file-based guard (AFTER fix):
91
+ # TodoWrite → post-task-completion.sh (creates .hook-recursion-guard file)
92
+ # → spawns background process (&)
93
+ # → consolidated-sync.js (inherits guard file!)
94
+ # → fs.writeFile(tasks.md)
95
+ # → post-edit-write-consolidated.sh (checks file, exits)
96
+ # → NO RECURSION āœ…
97
+ #
98
+ # See: .specweave/increments/0051-*/reports/GITHUB-COMMENT-RECURSION-ROOT-CAUSE-2025-11-24.md
99
+ # See: ADR-0073 (Hook Recursion Prevention Strategy)
100
+
101
+ RECURSION_GUARD_FILE="$PROJECT_ROOT/.specweave/state/.hook-recursion-guard"
102
+
103
+ if [[ -f "$RECURSION_GUARD_FILE" ]]; then
104
+ # Silent exit - we're already inside a hook chain
105
+ echo "[$(date)] ā­ļø Recursion guard detected - skipping (already in hook)" >> "$DEBUG_LOG" 2>/dev/null || true
106
+ exit 0
107
+ fi
108
+
109
+ # Create guard file (atomic operation)
110
+ mkdir -p "$PROJECT_ROOT/.specweave/state" 2>/dev/null || true
111
+ touch "$RECURSION_GUARD_FILE"
112
+
113
+ # Ensure guard file is ALWAYS removed when script exits (even on error)
114
+ trap 'rm -f "$RECURSION_GUARD_FILE" 2>/dev/null || true' EXIT SIGINT SIGTERM
115
+
116
+ echo "[$(date)] šŸ”’ Recursion guard created" >> "$DEBUG_LOG" 2>/dev/null || true
117
+
118
+ # EMERGENCY KILL SWITCH (after recursion guard)
119
+ if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
120
+ exit 0
121
+ fi
122
+
66
123
  # ============================================================================
67
124
  # EMERGENCY SAFETY CHECKS
68
125
  # ============================================================================
@@ -349,13 +406,25 @@ fi
349
406
  # ============================================================================
350
407
  # CONSOLIDATED SYNC (v0.24.4 - PERFORMANCE OPTIMIZATION)
351
408
  # ============================================================================
352
- # REPLACES: 5-6 separate Node.js spawns with SINGLE consolidated process
353
- # BEFORE: update-tasks-md.js, sync-living-docs.js, update-ac-status.js,
354
- # translate-living-docs.js, prepare-reflection-context.js
355
- # AFTER: consolidated-sync.js (runs all operations sequentially)
356
- # IMPACT: 83% reduction in process spawning overhead
409
+ # EMERGENCY FIX (v0.26.0-hotfix): DISABLED TO PREVENT CLAUDE CODE CRASHES
410
+ #
411
+ # ROOT CAUSE: consolidated-sync.js makes 5+ Edit/Write operations, each triggering
412
+ # 3 hooks (PreToolUse, PostToolUseƗ2), resulting in 15+ hook invocations per task.
413
+ # This causes process exhaustion and Claude Code crashes.
414
+ #
415
+ # NEW STRATEGY: Run consolidated sync ONLY at:
416
+ # 1. Session end (all tasks done + 120s inactivity)
417
+ # 2. Manual sync (/specweave:sync-docs command)
418
+ # 3. Increment closure (/specweave:done validation)
419
+ #
420
+ # See: .specweave/increments/0051-*/reports/CLAUDE-CODE-CRASH-ROOT-CAUSE-2025-11-23.md
421
+ # See: ADR-0072 (Post-Task Hook Simplification)
357
422
  # ============================================================================
358
423
 
424
+ # RE-ENABLED: Automatic sync on each TodoWrite (ACCEPTING CRASH RISK)
425
+ # WARNING: This can cause Claude Code crashes if too many TodoWrite events fire rapidly
426
+ # User explicitly requested re-enabling despite crash risk
427
+
359
428
  echo "[$(date)] šŸš€ Running consolidated sync" >> "$DEBUG_LOG" 2>/dev/null || true
360
429
 
361
430
  # Find consolidated sync script
@@ -371,6 +440,21 @@ fi
371
440
  fi
372
441
 
373
442
  if [ -n "$CONSOLIDATED_SCRIPT" ]; then
443
+ # Load GITHUB_TOKEN from .env for gh CLI authentication
444
+ if [ -f "$PROJECT_ROOT/.env" ]; then
445
+ # Extract GITHUB_TOKEN from .env (handles various formats)
446
+ GITHUB_TOKEN_FROM_ENV=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
447
+ if [ -n "$GITHUB_TOKEN_FROM_ENV" ]; then
448
+ export GITHUB_TOKEN="$GITHUB_TOKEN_FROM_ENV"
449
+ echo "[$(date)] āœ… GitHub token loaded from .env" >> "$DEBUG_LOG" 2>/dev/null || true
450
+ fi
451
+ fi
452
+
453
+ # FIX (v0.26.0): Skip GitHub sync in post-task-completion hook
454
+ # GitHub sync should ONLY run on increment COMPLETION, not task completion
455
+ # This prevents 27 duplicate comments (see root cause analysis)
456
+ export SKIP_GITHUB_SYNC=true
457
+
374
458
  # Run consolidated sync (single Node.js process handles ALL operations)
375
459
  if (cd "$PROJECT_ROOT" && node "$CONSOLIDATED_SCRIPT" "$CURRENT_INCREMENT") >> "$DEBUG_LOG" 2>&1; then
376
460
  echo "[$(date)] āœ… Consolidated sync completed" >> "$DEBUG_LOG" 2>/dev/null || true
@@ -101,17 +101,6 @@ else
101
101
  exit 0 # Let PostToolUse handle it with mtime fallback
102
102
  fi
103
103
 
104
- # ============================================================================
105
- # EARLY EXIT: Only process .specweave/ files (v0.24.4 Performance Fix)
106
- # ============================================================================
107
- # 90% of Edit operations are on non-SpecWeave files (src/, tests/, node_modules/)
108
- # Exit immediately for non-.specweave/ files to reduce hook overhead by 90%
109
-
110
- if [[ "$FILE_PATH" != *"/.specweave/"* ]]; then
111
- echo "[$(date)] pre-edit-spec: Not a .specweave/ file, skipping (performance optimization)" >> "$DEBUG_LOG" 2>/dev/null || true
112
- exit 0
113
- fi
114
-
115
104
  # Check if this is a spec.md or tasks.md file in increments folder
116
105
  IS_SPEC_FILE=false
117
106
  if [[ "$FILE_PATH" == *"/spec.md" ]] || [[ "$FILE_PATH" == *"/tasks.md" ]]; then
@@ -0,0 +1,188 @@
1
+ #!/bin/bash
2
+ #
3
+ # Pre-Edit/Write Consolidated Hook: Capture File Path BEFORE Edit/Write Executes
4
+ #
5
+ # Purpose: Unified hook for both Edit and Write tools
6
+ # Strategy: Detect file path, signal post-hook if it's a spec/tasks file
7
+ #
8
+ # CONSOLIDATION (v0.25.0):
9
+ # - Replaces pre-edit-spec.sh and pre-write-spec.sh (identical code)
10
+ # - Reduces hook overhead by 50% (2 pre-hooks → 1)
11
+ # - Single point of maintenance
12
+ #
13
+ # TIER 2 COORDINATION:
14
+ # 1. Extract file_path from TOOL_USE_ARGS (reliable in PreToolUse)
15
+ # 2. If it's spec.md/tasks.md in increments folder, signal PostToolUse hook
16
+ # 3. Write file path to .pending-status-update for PostToolUse to consume
17
+ #
18
+ # Architecture:
19
+ # PreToolUse:Edit/Write → pre-edit-write-consolidated.sh (this file)
20
+ # ↓ writes to
21
+ # .specweave/state/.pending-status-update
22
+ # ↓ read by
23
+ # PostToolUse:Edit/Write → post-edit-write-consolidated.sh
24
+ #
25
+ # Version: v0.25.0 (HOOK CONSOLIDATION)
26
+ # Date: 2025-11-23
27
+ #
28
+ # EMERGENCY FIXES (v0.24.3):
29
+ # - Kill switch: Set SPECWEAVE_DISABLE_HOOKS=1 to disable ALL hooks
30
+ # - Circuit breaker: Auto-disable after 3 consecutive failures
31
+ # - File locking: Prevent concurrent executions
32
+ # - Complete error isolation: Never let errors reach Claude Code
33
+
34
+ # EMERGENCY FIX: Remove set -e - it causes Claude Code crashes!
35
+ set +e
36
+
37
+ # Find project root (must be BEFORE recursion guard to get PROJECT_ROOT)
38
+ find_project_root() {
39
+ local dir="$PWD"
40
+ while [[ "$dir" != "/" ]]; do
41
+ if [[ -d "$dir/.specweave" ]]; then
42
+ echo "$dir"
43
+ return 0
44
+ fi
45
+ dir=$(dirname "$dir")
46
+ done
47
+ echo "$PWD"
48
+ }
49
+
50
+ PROJECT_ROOT=$(find_project_root)
51
+ STATE_DIR="$PROJECT_ROOT/.specweave/state"
52
+ LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
53
+ DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
54
+ PENDING_FILE="$STATE_DIR/.pending-status-update"
55
+ METRICS_FILE="$STATE_DIR/hook-metrics.jsonl"
56
+
57
+ # ============================================================================
58
+ # RECURSION PREVENTION (CRITICAL - v0.26.0 - FILE-BASED GUARD)
59
+ # ============================================================================
60
+ # NEW SOLUTION (v0.26.0): File-based recursion guard
61
+ # - Guard file exists = already inside hook chain
62
+ # - Works across ALL processes (not just current shell)
63
+ #
64
+ # See: ADR-0073 (Hook Recursion Prevention Strategy)
65
+
66
+ RECURSION_GUARD_FILE="$PROJECT_ROOT/.specweave/state/.hook-recursion-guard"
67
+
68
+ if [[ -f "$RECURSION_GUARD_FILE" ]]; then
69
+ # Silent exit - we're already inside a hook chain
70
+ exit 0
71
+ fi
72
+
73
+ # EMERGENCY KILL SWITCH: Disable all hooks if env variable set
74
+ if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
75
+ exit 0
76
+ fi
77
+
78
+ # Ensure directories exist
79
+ mkdir -p "$STATE_DIR" "$LOGS_DIR" 2>/dev/null || true
80
+
81
+ # ============================================================================
82
+ # TIER 2: Extract File Path from Tool Arguments
83
+ # ============================================================================
84
+ # PreToolUse should have access to tool arguments BEFORE execution
85
+ # Try multiple methods to extract file_path
86
+
87
+ FILE_PATH=""
88
+
89
+ # Method 1: TOOL_USE_ARGS environment variable (primary for PreToolUse)
90
+ if [[ -n "${TOOL_USE_ARGS:-}" ]]; then
91
+ # Try to parse JSON with jq if available
92
+ if command -v jq &> /dev/null; then
93
+ FILE_PATH=$(echo "$TOOL_USE_ARGS" | jq -r '.file_path // empty' 2>/dev/null || echo "")
94
+ fi
95
+
96
+ # Fallback: Regex extraction if jq not available or failed
97
+ if [[ -z "$FILE_PATH" ]]; then
98
+ FILE_PATH=$(echo "$TOOL_USE_ARGS" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
99
+ fi
100
+ fi
101
+
102
+ # Method 2: TOOL_USE_CONTENT (fallback)
103
+ if [[ -z "$FILE_PATH" ]] && [[ -n "${TOOL_USE_CONTENT:-}" ]]; then
104
+ FILE_PATH="$TOOL_USE_CONTENT"
105
+ fi
106
+
107
+ # Method 3: Parse from stdin (last resort - experimental)
108
+ if [[ -z "$FILE_PATH" ]] && [[ ! -t 0 ]]; then
109
+ # Read stdin and try to extract file_path
110
+ STDIN_DATA=$(cat 2>/dev/null || echo "")
111
+ if [[ -n "$STDIN_DATA" ]] && command -v jq &> /dev/null; then
112
+ FILE_PATH=$(echo "$STDIN_DATA" | jq -r '.file_path // empty' 2>/dev/null || echo "")
113
+ fi
114
+ fi
115
+
116
+ # ============================================================================
117
+ # TIER 2: Signal Detection and Validation
118
+ # ============================================================================
119
+
120
+ # Log what we detected (for debugging PreToolUse effectiveness)
121
+ if [[ -n "$FILE_PATH" ]]; then
122
+ echo "[$(date)] pre-edit-write: Detected file_path: $FILE_PATH" >> "$DEBUG_LOG" 2>/dev/null || true
123
+ else
124
+ echo "[$(date)] pre-edit-write: No file_path detected (will fall back to Tier 1)" >> "$DEBUG_LOG" 2>/dev/null || true
125
+ exit 0 # Let PostToolUse handle it with mtime fallback
126
+ fi
127
+
128
+ # ============================================================================
129
+ # EARLY EXIT: Only process .specweave/ files (v0.24.4 Performance Fix)
130
+ # ============================================================================
131
+ # 90% of Edit/Write operations are on non-SpecWeave files (src/, tests/, node_modules/)
132
+ # Exit immediately for non-.specweave/ files to reduce hook overhead by 90%
133
+
134
+ # Handle both absolute and relative paths: /.specweave/ or .specweave/
135
+ if [[ "$FILE_PATH" != *"/.specweave/"* ]] && [[ "$FILE_PATH" != ".specweave/"* ]]; then
136
+ echo "[$(date)] pre-edit-write: Not a .specweave/ file, skipping (performance optimization)" >> "$DEBUG_LOG" 2>/dev/null || true
137
+ exit 0
138
+ fi
139
+
140
+ # Check if this is a spec.md or tasks.md file in increments folder
141
+ IS_SPEC_FILE=false
142
+ if [[ "$FILE_PATH" == *"/spec.md" ]] || [[ "$FILE_PATH" == *"/tasks.md" ]]; then
143
+ # Handle both absolute (/.specweave/increments/) and relative (.specweave/increments/) paths
144
+ if [[ "$FILE_PATH" == *"/.specweave/increments/"* ]] || [[ "$FILE_PATH" == ".specweave/increments/"* ]]; then
145
+ # Exclude archived increments
146
+ if [[ "$FILE_PATH" != *"/_archive/"* ]]; then
147
+ IS_SPEC_FILE=true
148
+ fi
149
+ fi
150
+ fi
151
+
152
+ # If not a spec/tasks file, exit silently (no signal to PostToolUse)
153
+ if [[ "$IS_SPEC_FILE" != "true" ]]; then
154
+ echo "[$(date)] pre-edit-write: Not a spec/tasks file - no signal" >> "$DEBUG_LOG" 2>/dev/null || true
155
+ exit 0
156
+ fi
157
+
158
+ # ============================================================================
159
+ # TIER 2: Write Signal for PostToolUse Hook
160
+ # ============================================================================
161
+
162
+ # Write file path to pending file for PostToolUse to consume
163
+ echo "$FILE_PATH" > "$PENDING_FILE" 2>/dev/null || true
164
+
165
+ echo "[$(date)] pre-edit-write: Signaled PostToolUse for: $FILE_PATH" >> "$DEBUG_LOG" 2>/dev/null || true
166
+
167
+ # ============================================================================
168
+ # TIER 2: Metrics Collection
169
+ # ============================================================================
170
+
171
+ # Record metrics (JSONL format - one JSON object per line)
172
+ TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
173
+ METRIC_ENTRY="{\"timestamp\":\"$TIMESTAMP\",\"hook\":\"pre-edit-write\",\"event\":\"file_detected\",\"file_path\":\"$FILE_PATH\",\"method\":\"TOOL_USE_ARGS\"}"
174
+
175
+ # Append to metrics file (JSONL)
176
+ echo "$METRIC_ENTRY" >> "$METRICS_FILE" 2>/dev/null || true
177
+
178
+ # Log rotation for metrics (keep last 1000 entries)
179
+ if [[ -f "$METRICS_FILE" ]]; then
180
+ LINE_COUNT=$(wc -l < "$METRICS_FILE" 2>/dev/null || echo 0)
181
+ if (( LINE_COUNT > 1000 )); then
182
+ tail -1000 "$METRICS_FILE" > "$METRICS_FILE.tmp" 2>/dev/null || true
183
+ mv "$METRICS_FILE.tmp" "$METRICS_FILE" 2>/dev/null || true
184
+ fi
185
+ fi
186
+
187
+ # ALWAYS exit 0 - NEVER let hook errors crash Claude Code
188
+ exit 0
@@ -22,6 +22,17 @@
22
22
 
23
23
  set +e # EMERGENCY FIX: Prevents Claude Code crashes
24
24
 
25
+ # ============================================================================
26
+ # RECURSION PREVENTION (CRITICAL - v0.25.1)
27
+ # ============================================================================
28
+ # Skip if we're already inside a hook to prevent infinite recursion
29
+ if [[ "${SPECWEAVE_IN_HOOK:-0}" == "1" ]]; then
30
+ exit 0
31
+ fi
32
+
33
+ # Mark that we're now inside a hook
34
+ export SPECWEAVE_IN_HOOK=1
35
+
25
36
  # EMERGENCY KILL SWITCH
26
37
  if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
27
38
  exit 0
@@ -101,17 +101,6 @@ else
101
101
  exit 0 # Let PostToolUse handle it with mtime fallback
102
102
  fi
103
103
 
104
- # ============================================================================
105
- # EARLY EXIT: Only process .specweave/ files (v0.24.4 Performance Fix)
106
- # ============================================================================
107
- # 90% of Write operations are on non-SpecWeave files (src/, tests/, node_modules/)
108
- # Exit immediately for non-.specweave/ files to reduce hook overhead by 90%
109
-
110
- if [[ "$FILE_PATH" != *"/.specweave/"* ]]; then
111
- echo "[$(date)] pre-write-spec: Not a .specweave/ file, skipping (performance optimization)" >> "$DEBUG_LOG" 2>/dev/null || true
112
- exit 0
113
- fi
114
-
115
104
  # Check if this is a spec.md or tasks.md file in increments folder
116
105
  IS_SPEC_FILE=false
117
106
  if [[ "$FILE_PATH" == *"/spec.md" ]] || [[ "$FILE_PATH" == *"/tasks.md" ]]; then
@@ -42,6 +42,11 @@ import { translateLivingDocs } from './translate-living-docs.js';
42
42
  // Import for AC status (uses ACStatusManager directly)
43
43
  import { ACStatusManager } from '../vendor/core/increment/ac-status-manager.js';
44
44
 
45
+ // Import for GitHub sync (uses SyncCoordinator directly)
46
+ // NOTE: Import from project root dist (not relative path)
47
+ import { SyncCoordinator } from '../../../../dist/src/sync/sync-coordinator.js';
48
+ import { consoleLogger } from '../vendor/utils/logger.js';
49
+
45
50
  // ============================================================================
46
51
  // WRAPPER: UPDATE AC STATUS
47
52
  // ============================================================================
@@ -87,6 +92,36 @@ async function updateACStatus(incrementId) {
87
92
  }
88
93
  }
89
94
 
95
+ // ============================================================================
96
+ // WRAPPER: GITHUB SYNC
97
+ // ============================================================================
98
+ async function syncGitHub(incrementId) {
99
+ try {
100
+ console.log(`\nšŸ”— [5/5] Syncing to GitHub...`);
101
+
102
+ const projectRoot = process.cwd();
103
+
104
+ const coordinator = new SyncCoordinator({
105
+ projectRoot,
106
+ incrementId,
107
+ logger: consoleLogger
108
+ });
109
+
110
+ const result = await coordinator.syncIncrementCompletion();
111
+
112
+ if (result.success) {
113
+ console.log(`āœ… GitHub sync completed (${result.userStoriesSynced} user stories synced)`);
114
+ } else {
115
+ console.warn(`āš ļø GitHub sync had errors (see logs)`);
116
+ }
117
+
118
+ return { success: result.success, result };
119
+ } catch (error) {
120
+ console.error('āŒ Error syncing to GitHub:', error.message);
121
+ return { success: false, error: error.message };
122
+ }
123
+ }
124
+
90
125
  // ============================================================================
91
126
  // MAIN EXECUTION
92
127
  // ============================================================================
@@ -100,7 +135,7 @@ async function runConsolidatedSync(incrementId) {
100
135
 
101
136
  try {
102
137
  // OPERATION 1: Update tasks.md (uses imported function)
103
- console.log('\nšŸ”„ [1/4] Updating tasks.md...');
138
+ console.log('\nšŸ”„ [1/5] Updating tasks.md...');
104
139
  try {
105
140
  await updateTasksMd(incrementId);
106
141
  results.updateTasks = { success: true };
@@ -110,7 +145,7 @@ async function runConsolidatedSync(incrementId) {
110
145
  }
111
146
 
112
147
  // OPERATION 2: Sync living docs (uses imported function)
113
- console.log('\nšŸ“š [2/4] Syncing living docs...');
148
+ console.log('\nšŸ“š [2/5] Syncing living docs...');
114
149
  try {
115
150
  await syncLivingDocs(incrementId);
116
151
  results.syncDocs = { success: true };
@@ -128,7 +163,7 @@ async function runConsolidatedSync(incrementId) {
128
163
  }
129
164
 
130
165
  // OPERATION 4: Translate living docs (uses imported function)
131
- console.log('\n🌐 [4/4] Checking translation needs...');
166
+ console.log('\n🌐 [4/5] Checking translation needs...');
132
167
  try {
133
168
  await translateLivingDocs(incrementId);
134
169
  results.translate = { success: true };
@@ -137,6 +172,29 @@ async function runConsolidatedSync(incrementId) {
137
172
  results.translate = { success: false, error: error.message };
138
173
  }
139
174
 
175
+ // OPERATION 5: Sync to GitHub (NEW in v0.24.0+)
176
+ // FIX (v0.26.0): Skip GitHub sync in post-task-completion hook!
177
+ // WHY: GitHub sync should ONLY run on increment COMPLETION, not task completion
178
+ // This prevents 27 duplicate comments on every TodoWrite (see root cause analysis)
179
+ //
180
+ // GitHub sync is now ONLY triggered by:
181
+ // - post-increment-completion.sh (when status → "completed")
182
+ // - Manual sync via /specweave-github:sync command
183
+ //
184
+ // See: .specweave/increments/0051-*/reports/GITHUB-COMMENT-RECURSION-ROOT-CAUSE-2025-11-24.md
185
+ if (process.env.SKIP_GITHUB_SYNC === 'true') {
186
+ console.log('\nā­ļø [5/5] GitHub sync SKIPPED (called from post-task-completion hook)');
187
+ console.log(' GitHub sync will run automatically on increment completion.');
188
+ results.syncGitHub = { success: true, skipped: true };
189
+ } else {
190
+ try {
191
+ results.syncGitHub = await syncGitHub(incrementId);
192
+ } catch (error) {
193
+ console.error('āŒ Error syncing to GitHub:', error.message);
194
+ results.syncGitHub = { success: false, error: error.message };
195
+ }
196
+ }
197
+
140
198
  const duration = Date.now() - startTime;
141
199
 
142
200
  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');