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,243 +0,0 @@
1
- #!/bin/bash
2
-
3
- # SpecWeave Post-Increment-Status-Change Hook (v0.28.33)
4
- # Runs automatically after increment status changes (pause/resume/abandon)
5
- #
6
- # Trigger: /sw:pause, /sw:resume, /sw:abandon commands
7
- # Purpose: Sync GitHub issue state with increment status
8
- #
9
- # What it does:
10
- # 1. Detects status change (paused, resumed, abandoned)
11
- # 2. Posts comment to GitHub issue
12
- # 3. NEW (v0.28.33): Reopens issues when resumed, closes when abandoned
13
- #
14
- # Usage:
15
- # ./post-increment-status-change.sh <incrementId> <newStatus> <reason>
16
- #
17
- # Example:
18
- # ./post-increment-status-change.sh 0015-hierarchical-sync paused "Waiting for API keys"
19
-
20
- set +e # EMERGENCY FIX: Prevents Claude Code crashes
21
-
22
- # EMERGENCY KILL SWITCH
23
- if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
24
- exit 0
25
- fi
26
-
27
- # Find project root
28
- find_project_root() {
29
- local dir="$1"
30
- while [ "$dir" != "/" ]; do
31
- if [ -d "$dir/.specweave" ]; then
32
- echo "$dir"
33
- return 0
34
- fi
35
- dir="$(dirname "$dir")"
36
- done
37
- pwd
38
- }
39
-
40
- PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
41
- cd "$PROJECT_ROOT" 2>/dev/null || true
42
-
43
- # Configuration
44
- LOGS_DIR=".specweave/logs"
45
- DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
46
- HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
47
-
48
- mkdir -p "$LOGS_DIR" 2>/dev/null || true
49
-
50
- # Arguments
51
- INCREMENT_ID="$1"
52
- NEW_STATUS="$2"
53
- REASON="$3"
54
-
55
- if [ -z "$INCREMENT_ID" ] || [ -z "$NEW_STATUS" ]; then
56
- echo "Usage: $0 <incrementId> <newStatus> [reason]" >&2
57
- echo "Example: $0 0015-hierarchical-sync paused 'Waiting for API'" >&2
58
- exit 1
59
- fi
60
-
61
- echo "[$(date)] 📊 Status changed: $NEW_STATUS" >> "$DEBUG_LOG" 2>/dev/null || true
62
-
63
- # Validate status
64
- case "$NEW_STATUS" in
65
- paused|abandoned|active|planning|backlog|ready_for_review|completed)
66
- ;;
67
- *)
68
- echo "[$(date)] ⚠️ Unknown status: $NEW_STATUS (skipping sync)" >> "$DEBUG_LOG" 2>/dev/null || true
69
- exit 0
70
- ;;
71
- esac
72
-
73
- # Check if GitHub CLI available
74
- if ! command -v gh &> /dev/null; then
75
- echo "[$(date)] ℹ️ GitHub CLI not found, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
76
- exit 0
77
- fi
78
-
79
- # Check if authenticated
80
- if ! gh auth status &> /dev/null; then
81
- echo "[$(date)] ℹ️ GitHub CLI not authenticated, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
82
- exit 0
83
- fi
84
-
85
- # Load metadata
86
- METADATA_FILE=".specweave/increments/$INCREMENT_ID/metadata.json"
87
-
88
- if [ ! -f "$METADATA_FILE" ]; then
89
- echo "[$(date)] ℹ️ No metadata.json found, skipping sync" >> "$DEBUG_LOG" 2>/dev/null || true
90
- exit 0
91
- fi
92
-
93
- # Extract GitHub issue number (main increment issue)
94
- GITHUB_ISSUE=$(jq -r '.github.issue // empty' "$METADATA_FILE" 2>/dev/null)
95
-
96
- # Detect repository
97
- GITHUB_REMOTE=$(git remote get-url origin 2>/dev/null || echo "")
98
- REPO_MATCH=$(echo "$GITHUB_REMOTE" | grep -o 'github\.com[:/][^/]*/[^/]*' | sed 's/github\.com[:/]//')
99
-
100
- if [ -z "$REPO_MATCH" ]; then
101
- echo "[$(date)] ⚠️ Could not detect GitHub repository" >> "$DEBUG_LOG" 2>/dev/null || true
102
- exit 0
103
- fi
104
-
105
- # ============================================================================
106
- # STATUS-BASED ACTIONS (NEW in v0.28.33)
107
- # ============================================================================
108
-
109
- case "$NEW_STATUS" in
110
- active|planning|backlog|ready_for_review)
111
- # ========================================================================
112
- # REOPEN GitHub Issues (NEW)
113
- # ========================================================================
114
- # When increment becomes active (including resumed from pause), reopen all closed GitHub issues
115
- echo "[$(date)] ▶️ Status is $NEW_STATUS - checking if issues need reopening" >> "$DEBUG_LOG" 2>/dev/null || true
116
-
117
- if command -v node &> /dev/null; then
118
- # Find reopen script
119
- REOPEN_SCRIPT=""
120
- if [ -f "$PROJECT_ROOT/plugins/specweave/lib/hooks/reopen-github-issues.js" ]; then
121
- REOPEN_SCRIPT="$PROJECT_ROOT/plugins/specweave/lib/hooks/reopen-github-issues.js"
122
- elif [ -f "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/reopen-github-issues.js" ]; then
123
- REOPEN_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/reopen-github-issues.js"
124
- elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/reopen-github-issues.js" ]; then
125
- REOPEN_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/reopen-github-issues.js"
126
- elif [ -n "${CLAUDE_PLUGIN_ROOT}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/lib/hooks/reopen-github-issues.js" ]; then
127
- REOPEN_SCRIPT="${CLAUDE_PLUGIN_ROOT}/lib/hooks/reopen-github-issues.js"
128
- fi
129
-
130
- if [ -n "$REOPEN_SCRIPT" ]; then
131
- # Load GITHUB_TOKEN from .env
132
- if [ -f "$PROJECT_ROOT/.env" ]; then
133
- GITHUB_TOKEN_FROM_ENV=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
134
- if [ -n "$GITHUB_TOKEN_FROM_ENV" ]; then
135
- export GITHUB_TOKEN="$GITHUB_TOKEN_FROM_ENV"
136
- fi
137
- fi
138
-
139
- echo "▶️ Reopening GitHub issues for resumed increment..."
140
- (cd "$PROJECT_ROOT" && node "$REOPEN_SCRIPT" "$INCREMENT_ID" "${REASON:-Increment resumed}") 2>&1 | tee -a "$DEBUG_LOG" || {
141
- echo "[$(date)] ⚠️ Failed to reopen issues (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
142
- }
143
- else
144
- echo "[$(date)] ⚠️ reopen-github-issues.js not found" >> "$DEBUG_LOG" 2>/dev/null || true
145
- fi
146
- fi
147
- ;;
148
-
149
- abandoned)
150
- # ========================================================================
151
- # CLOSE GitHub Issues on Abandon (NEW)
152
- # ========================================================================
153
- echo "[$(date)] 🗑️ Status is abandoned - closing GitHub issues" >> "$DEBUG_LOG" 2>/dev/null || true
154
-
155
- if command -v node &> /dev/null; then
156
- # Find close script
157
- CLOSE_SCRIPT=""
158
- if [ -f "$PROJECT_ROOT/plugins/specweave/lib/hooks/close-github-issues-abandoned.js" ]; then
159
- CLOSE_SCRIPT="$PROJECT_ROOT/plugins/specweave/lib/hooks/close-github-issues-abandoned.js"
160
- elif [ -f "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/close-github-issues-abandoned.js" ]; then
161
- CLOSE_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/close-github-issues-abandoned.js"
162
- elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/close-github-issues-abandoned.js" ]; then
163
- CLOSE_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/close-github-issues-abandoned.js"
164
- elif [ -n "${CLAUDE_PLUGIN_ROOT}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/lib/hooks/close-github-issues-abandoned.js" ]; then
165
- CLOSE_SCRIPT="${CLAUDE_PLUGIN_ROOT}/lib/hooks/close-github-issues-abandoned.js"
166
- fi
167
-
168
- if [ -n "$CLOSE_SCRIPT" ]; then
169
- # Load GITHUB_TOKEN from .env
170
- if [ -f "$PROJECT_ROOT/.env" ]; then
171
- GITHUB_TOKEN_FROM_ENV=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
172
- if [ -n "$GITHUB_TOKEN_FROM_ENV" ]; then
173
- export GITHUB_TOKEN="$GITHUB_TOKEN_FROM_ENV"
174
- fi
175
- fi
176
-
177
- echo "🗑️ Closing GitHub issues for abandoned increment..."
178
- (cd "$PROJECT_ROOT" && node "$CLOSE_SCRIPT" "$INCREMENT_ID" "${REASON:-Increment abandoned}") 2>&1 | tee -a "$DEBUG_LOG" || {
179
- echo "[$(date)] ⚠️ Failed to close issues (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
180
- }
181
- else
182
- echo "[$(date)] ⚠️ close-github-issues-abandoned.js not found" >> "$DEBUG_LOG" 2>/dev/null || true
183
- fi
184
- fi
185
- ;;
186
-
187
- paused)
188
- # Paused: Just post comment (no close/reopen)
189
- echo "[$(date)] ⏸️ Status is paused - posting comment only" >> "$DEBUG_LOG" 2>/dev/null || true
190
- ;;
191
- esac
192
-
193
- # ============================================================================
194
- # POST COMMENT TO MAIN ISSUE (always, for all status changes)
195
- # ============================================================================
196
-
197
- if [ -n "$GITHUB_ISSUE" ]; then
198
- echo "[$(date)] 🔄 Posting status change to GitHub issue #$GITHUB_ISSUE" >> "$DEBUG_LOG" 2>/dev/null || true
199
-
200
- # Build comment based on status
201
- EMOJI=""
202
- TITLE=""
203
- case "$NEW_STATUS" in
204
- paused)
205
- EMOJI="⏸️"
206
- TITLE="Increment Paused"
207
- ;;
208
- resumed|active|in-progress)
209
- EMOJI="▶️"
210
- TITLE="Increment Resumed"
211
- ;;
212
- abandoned)
213
- EMOJI="🗑️"
214
- TITLE="Increment Abandoned"
215
- ;;
216
- esac
217
-
218
- TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
219
-
220
- COMMENT="$EMOJI **$TITLE**
221
-
222
- **Reason**: ${REASON:-Not specified}
223
-
224
- **Timestamp**: $TIMESTAMP
225
-
226
- ---
227
- 🤖 Auto-updated by SpecWeave"
228
-
229
- # Post comment
230
- echo "$COMMENT" | gh issue comment "$GITHUB_ISSUE" --body-file - 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
231
- echo "[$(date)] ⚠️ Failed to post comment (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
232
- }
233
-
234
- echo "[$(date)] ✅ Status change synced to GitHub" >> "$DEBUG_LOG" 2>/dev/null || true
235
- else
236
- echo "[$(date)] ℹ️ No main GitHub issue linked" >> "$DEBUG_LOG" 2>/dev/null || true
237
- fi
238
-
239
- # Update status line cache (status changed - may affect which increment is "current")
240
- bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
241
-
242
- # Return success (non-blocking)
243
- exit 0
@@ -1,246 +0,0 @@
1
- #!/bin/bash
2
- #
3
- # Post-Metadata-Change Hook: Dispatcher for Increment Lifecycle Events
4
- #
5
- # Triggers: After Write/Edit modifies metadata.json
6
- # Purpose: Detect WHAT changed in metadata and call appropriate lifecycle hook
7
- #
8
- # Architecture:
9
- # - metadata.json is the source of truth for increment state
10
- # - Different state changes require different actions:
11
- # * status: "completed" → Call post-increment-completion.sh
12
- # * status: "paused"|"resumed"|"abandoned" → Call post-increment-status-change.sh
13
- # * other changes → Update status line only
14
- #
15
- # This fixes the critical bug where status line never updates on increment closure
16
- # because post-increment-completion.sh was orphaned (never registered or called).
17
- #
18
- # Related Incident: 2025-11-20 - Increment 0047 completed but status line still shows active
19
- # Root Cause: metadata.json writes don't trigger status line refresh
20
- # Fix: This hook dispatches to post-increment-completion.sh which updates status line
21
-
22
- # EMERGENCY FIX v0.24.3: Remove set -e - it causes Claude Code crashes!
23
- set +e
24
-
25
- # Find project root (must be BEFORE recursion guard to get PROJECT_ROOT)
26
- find_project_root() {
27
- local dir="$PWD"
28
- while [[ "$dir" != "/" ]]; do
29
- if [[ -d "$dir/.specweave" ]]; then
30
- echo "$dir"
31
- return 0
32
- fi
33
- dir=$(dirname "$dir")
34
- done
35
- echo "$PWD"
36
- }
37
-
38
- PROJECT_ROOT=$(find_project_root)
39
- LOGS_DIR="$PROJECT_ROOT/.specweave/logs"
40
- DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
41
- HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
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
-
61
- # Ensure logs directory exists
62
- mkdir -p "$LOGS_DIR" 2>/dev/null || true
63
-
64
- # ============================================================================
65
- # CREATE RECURSION GUARD (v0.28.2 - CRITICAL FIX)
66
- # ============================================================================
67
- # PROBLEM FIXED: Guard was only created in post-increment-completion.sh, but
68
- # post-increment-status-change.sh had NO guard protection.
69
- #
70
- # SOLUTION: Create guard here in the dispatcher so ALL dispatches are protected.
71
- # The sub-hooks (post-increment-completion.sh, post-increment-status-change.sh)
72
- # will see this guard and exit early if they're called recursively.
73
- #
74
- # This prevents infinite loops if any sync operation modifies metadata.json.
75
-
76
- mkdir -p "$PROJECT_ROOT/.specweave/state" 2>/dev/null || true
77
- touch "$RECURSION_GUARD_FILE"
78
-
79
- # Ensure guard file is ALWAYS removed when script exits (even on error)
80
- trap 'rm -f "$RECURSION_GUARD_FILE" 2>/dev/null || true' EXIT SIGINT SIGTERM
81
-
82
- # ============================================================================
83
- # READ STDIN (v0.26.1 - CRITICAL FIX)
84
- # ============================================================================
85
- # PostToolUse hooks receive JSON data from Claude Code via STDIN, NOT env vars!
86
- # This was the critical bug: hook was looking for environment variables that don't exist.
87
- #
88
- # Example STDIN data:
89
- # {"tool": "Edit", "tool_input": {"file_path": "/path/to/file.md", ...}}
90
- # {"tool": "Write", "tool_input": {"file_path": "/path/to/file.md", ...}}
91
-
92
- STDIN_DATA=$(mktemp)
93
- cat > "$STDIN_DATA"
94
-
95
- echo "[$(date)] post-metadata-change: Hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
96
- echo "[$(date)] Input JSON:" >> "$DEBUG_LOG" 2>/dev/null || true
97
- cat "$STDIN_DATA" >> "$DEBUG_LOG" 2>/dev/null || true
98
- echo "" >> "$DEBUG_LOG" 2>/dev/null || true
99
-
100
- # ============================================================================
101
- # EARLY EXIT OPTIMIZATION (v0.25.0): Ultra-Fast Rejection of Non-Metadata Changes
102
- # ============================================================================
103
- # This hook should ONLY run for metadata.json changes.
104
- # 99.9% of Edit/Write operations are NOT metadata.json.
105
- # Do fastest possible check first to minimize overhead.
106
-
107
- # Quick check: If STDIN doesn't contain "metadata.json", exit immediately
108
- if ! grep -q "metadata\.json" "$STDIN_DATA" 2>/dev/null; then
109
- echo "[$(date)] post-metadata-change: Not metadata.json - exiting" >> "$DEBUG_LOG" 2>/dev/null || true
110
- rm -f "$STDIN_DATA"
111
- exit 0 # Fast path: Not metadata.json
112
- fi
113
-
114
- echo "[$(date)] post-metadata-change: metadata.json detected in input" >> "$DEBUG_LOG" 2>/dev/null || true
115
-
116
- # ============================================================================
117
- # EXTRACT FILE PATH FROM STDIN JSON
118
- # ============================================================================
119
-
120
- # Parse file_path from JSON (handles both Edit and Write tool formats)
121
- MODIFIED_FILE=$(grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' "$STDIN_DATA" | head -1 | sed 's/.*"\([^"]*\)".*/\1/' || echo "")
122
-
123
- # Clean up temp file
124
- rm -f "$STDIN_DATA"
125
-
126
- echo "[$(date)] post-metadata-change: Detected file: ${MODIFIED_FILE:-<none>}" >> "$DEBUG_LOG" 2>/dev/null || true
127
-
128
- # Check if this is a metadata.json change in an increment folder
129
- if [[ -z "$MODIFIED_FILE" ]] || [[ "$MODIFIED_FILE" != *"/metadata.json" ]] || [[ "$MODIFIED_FILE" != *"/.specweave/increments/"* ]]; then
130
- # Not a metadata.json change in increments folder - exit silently
131
- exit 0
132
- fi
133
-
134
- # Exclude archived increments (shouldn't affect status line)
135
- if [[ "$MODIFIED_FILE" == *"/_archive/"* ]]; then
136
- echo "[$(date)] post-metadata-change: Archived increment - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
137
- exit 0
138
- fi
139
-
140
- echo "[$(date)] post-metadata-change: metadata.json changed - analyzing..." >> "$DEBUG_LOG" 2>/dev/null || true
141
-
142
- # Extract increment ID from path
143
- # Path format: /path/to/project/.specweave/increments/0047-name/metadata.json
144
- INCREMENT_ID=$(echo "$MODIFIED_FILE" | grep -o '\.specweave/increments/[^/]*' | sed 's/\.specweave\/increments\///')
145
-
146
- if [[ -z "$INCREMENT_ID" ]]; then
147
- echo "[$(date)] post-metadata-change: Could not extract increment ID from path: $MODIFIED_FILE" >> "$DEBUG_LOG" 2>/dev/null || true
148
- exit 0
149
- fi
150
-
151
- echo "[$(date)] post-metadata-change: Increment ID: $INCREMENT_ID" >> "$DEBUG_LOG" 2>/dev/null || true
152
-
153
- # Read the metadata.json to detect what changed
154
- METADATA_PATH="$PROJECT_ROOT/.specweave/increments/$INCREMENT_ID/metadata.json"
155
-
156
- if [[ ! -f "$METADATA_PATH" ]]; then
157
- echo "[$(date)] post-metadata-change: metadata.json not found at $METADATA_PATH" >> "$DEBUG_LOG" 2>/dev/null || true
158
- exit 0
159
- fi
160
-
161
- # Check if jq is available for parsing JSON
162
- if ! command -v jq &> /dev/null; then
163
- echo "[$(date)] post-metadata-change: jq not found - updating status line as fallback" >> "$DEBUG_LOG" 2>/dev/null || true
164
- bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
165
- exit 0
166
- fi
167
-
168
- # Extract current status
169
- CURRENT_STATUS=$(jq -r '.status // "unknown"' "$METADATA_PATH" 2>/dev/null)
170
-
171
- echo "[$(date)] post-metadata-change: Current status: $CURRENT_STATUS" >> "$DEBUG_LOG" 2>/dev/null || true
172
-
173
- # ============================================================================
174
- # CRITICAL FIX (v0.28.12): Remove guard BEFORE calling sub-hooks
175
- # ============================================================================
176
- # PROBLEM: Sub-hooks (post-increment-completion.sh) check for recursion guard
177
- # and exit immediately if it exists. But the guard was created HERE (line 77),
178
- # so sub-hooks NEVER ran - causing status line to never update!
179
- #
180
- # SOLUTION: Temporarily remove the guard before calling sub-hooks.
181
- # The guard's purpose is to prevent THIS hook from being called recursively
182
- # (if sub-hooks modify metadata.json). Sub-hooks have their OWN guards.
183
- #
184
- # See: Root cause analysis 2025-11-25 - status line never updates after /done
185
- rm -f "$RECURSION_GUARD_FILE" 2>/dev/null || true
186
-
187
- # Dispatch to appropriate lifecycle hook based on status
188
- case "$CURRENT_STATUS" in
189
- completed)
190
- # Increment completed - call post-increment-completion.sh
191
- # This hook handles:
192
- # - Closing GitHub issues
193
- # - Syncing living docs
194
- # - Updating status line
195
- echo "[$(date)] post-metadata-change: Increment completed - calling post-increment-completion.sh" >> "$DEBUG_LOG" 2>/dev/null || true
196
-
197
- if [[ -x "$HOOK_DIR/post-increment-completion.sh" ]]; then
198
- bash "$HOOK_DIR/post-increment-completion.sh" "$INCREMENT_ID" 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
199
- echo "[$(date)] post-metadata-change: post-increment-completion.sh failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
200
- }
201
- else
202
- echo "[$(date)] post-metadata-change: post-increment-completion.sh not found or not executable" >> "$DEBUG_LOG" 2>/dev/null || true
203
- fi
204
- # ALWAYS update status line after completion (force bypass TTL cache)
205
- bash "$HOOK_DIR/lib/update-status-line.sh" --force 2>/dev/null || true
206
- ;;
207
-
208
- paused|resumed|abandoned)
209
- # Status change - call post-increment-status-change.sh
210
- # Note: This typically gets called manually by /sw:pause commands
211
- # But we handle it here for completeness
212
- echo "[$(date)] post-metadata-change: Status changed to $CURRENT_STATUS - calling post-increment-status-change.sh" >> "$DEBUG_LOG" 2>/dev/null || true
213
-
214
- if [[ -x "$HOOK_DIR/post-increment-status-change.sh" ]]; then
215
- # Extract reason if available
216
- REASON=$(jq -r '.statusReason // "Not specified"' "$METADATA_PATH" 2>/dev/null)
217
- bash "$HOOK_DIR/post-increment-status-change.sh" "$INCREMENT_ID" "$CURRENT_STATUS" "$REASON" 2>&1 | tee -a "$DEBUG_LOG" >/dev/null || {
218
- echo "[$(date)] post-metadata-change: post-increment-status-change.sh failed (non-blocking)" >> "$DEBUG_LOG" 2>/dev/null || true
219
- }
220
- else
221
- echo "[$(date)] post-metadata-change: post-increment-status-change.sh not found" >> "$DEBUG_LOG" 2>/dev/null || true
222
- fi
223
- # ALWAYS update status line after status change (force bypass TTL cache)
224
- bash "$HOOK_DIR/lib/update-status-line.sh" --force 2>/dev/null || true
225
- ;;
226
-
227
- active|planning|backlog|ready_for_review)
228
- # Increment became active - MUST register in active-increment.json!
229
- # CRITICAL FIX (v0.26.15): post-task-completion.sh depends on this file
230
- # Without registration, ALL sync operations are skipped!
231
- echo "[$(date)] post-metadata-change: Status is $CURRENT_STATUS - registering as active + updating status line" >> "$DEBUG_LOG" 2>/dev/null || true
232
- bash "$HOOK_DIR/lib/update-active-increment.sh" 2>/dev/null || true
233
- bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
234
- ;;
235
-
236
- *)
237
- # Other metadata changes (e.g., task completion count, AC count)
238
- # Just update status line to reflect new progress
239
- echo "[$(date)] post-metadata-change: Status is $CURRENT_STATUS - updating status line only" >> "$DEBUG_LOG" 2>/dev/null || true
240
- bash "$HOOK_DIR/lib/update-status-line.sh" 2>/dev/null || true
241
- ;;
242
- esac
243
-
244
- echo "[$(date)] post-metadata-change: Complete" >> "$DEBUG_LOG" 2>/dev/null || true
245
-
246
- exit 0
@@ -1,158 +0,0 @@
1
- #!/bin/bash
2
-
3
- ###############################################################################
4
- # SpecWeave Post-Spec-Update Hook
5
- #
6
- # CRITICAL ARCHITECTURE:
7
- # - Fires when .specweave/docs/internal/specs/spec-*.md is updated
8
- # - Auto-syncs to linked external tool (GitHub Project/Jira Epic/ADO Feature)
9
- # - Replaces increment-based sync (which was wrong!)
10
- #
11
- # Trigger Points:
12
- # 1. After /sw:sync-docs update (spec updated from increment)
13
- # 2. After manual spec.md edits (user updates living docs directly)
14
- # 3. After bidirectional sync from external tool
15
- #
16
- # What It Does:
17
- # - Detects which spec was updated
18
- # - Checks if spec is linked to external tool (frontmatter: externalLinks)
19
- # - Syncs changes to GitHub Project / Jira Epic / ADO Feature
20
- # - Updates user stories, acceptance criteria, progress
21
- #
22
- # Usage:
23
- # post-spec-update.sh <spec-file-path>
24
- #
25
- # Example:
26
- # post-spec-update.sh .specweave/docs/internal/specs/spec-001-core-framework.md
27
- #
28
- ###############################################################################
29
-
30
- set +e # EMERGENCY FIX: Changed from set -euo pipefail to prevent Claude Code crashes
31
-
32
- # Arguments
33
- SPEC_FILE_PATH="${1:-}"
34
-
35
- # Validate arguments
36
- if [[ -z "$SPEC_FILE_PATH" ]]; then
37
- echo "❌ Error: Spec file path required"
38
- echo "Usage: post-spec-update.sh <spec-file-path>"
39
- exit 1
40
- fi
41
-
42
- # Check if spec file exists
43
- if [[ ! -f "$SPEC_FILE_PATH" ]]; then
44
- echo "❌ Error: Spec file not found: $SPEC_FILE_PATH"
45
- exit 1
46
- fi
47
-
48
- # Extract spec ID from file path
49
- # Example: .specweave/docs/internal/specs/spec-001-core-framework.md → spec-001
50
- SPEC_ID=$(basename "$SPEC_FILE_PATH" .md)
51
-
52
- echo ""
53
- echo "🔗 Post-Spec-Update Hook"
54
- echo " Spec: $SPEC_ID"
55
- echo " File: $SPEC_FILE_PATH"
56
-
57
- # Load config to check if auto-sync is enabled
58
- CONFIG_FILE=".specweave/config.json"
59
- if [[ ! -f "$CONFIG_FILE" ]]; then
60
- echo " ℹ️ No config file found, skipping auto-sync"
61
- exit 0
62
- fi
63
-
64
- # Check if auto-sync is enabled
65
- AUTO_SYNC=$(jq -r '.hooks.post_spec_update.auto_sync // true' "$CONFIG_FILE")
66
-
67
- if [[ "$AUTO_SYNC" != "true" ]]; then
68
- echo " ℹ️ Auto-sync disabled in config, skipping"
69
- exit 0
70
- fi
71
-
72
- # Parse spec frontmatter to detect external links
73
- # Use gray-matter or similar to extract YAML frontmatter
74
- # For now, use simple grep/sed approach
75
-
76
- # Check if GitHub link exists
77
- GITHUB_LINK=$(grep -A 10 "^externalLinks:" "$SPEC_FILE_PATH" | grep -A 5 "github:" | grep "projectId:" | sed 's/.*projectId: *//; s/ *$//' || echo "")
78
-
79
- # Check if Jira link exists
80
- JIRA_LINK=$(grep -A 10 "^externalLinks:" "$SPEC_FILE_PATH" | grep -A 5 "jira:" | grep "epicKey:" | sed 's/.*epicKey: *//; s/ *$//' || echo "")
81
-
82
- # Check if ADO link exists
83
- ADO_LINK=$(grep -A 10 "^externalLinks:" "$SPEC_FILE_PATH" | grep -A 5 "ado:" | grep "featureId:" | sed 's/.*featureId: *//; s/ *$//' || echo "")
84
-
85
- # Determine which provider to sync
86
- PROVIDER=""
87
- if [[ -n "$GITHUB_LINK" ]]; then
88
- PROVIDER="github"
89
- EXTERNAL_ID="$GITHUB_LINK"
90
- elif [[ -n "$JIRA_LINK" ]]; then
91
- PROVIDER="jira"
92
- EXTERNAL_ID="$JIRA_LINK"
93
- elif [[ -n "$ADO_LINK" ]]; then
94
- PROVIDER="ado"
95
- EXTERNAL_ID="$ADO_LINK"
96
- fi
97
-
98
- # No external link found - skip sync
99
- if [[ -z "$PROVIDER" ]]; then
100
- echo " ℹ️ Spec not linked to external tool, skipping sync"
101
- echo " Hint: Use /sw-github:sync-spec or /sw-jira:sync-spec to link"
102
- exit 0
103
- fi
104
-
105
- echo " 🔗 Detected external link: $PROVIDER (ID: $EXTERNAL_ID)"
106
-
107
- # Sync to external tool based on provider
108
- case "$PROVIDER" in
109
- github)
110
- echo " 🔄 Syncing to GitHub Project..."
111
-
112
- # Check if GitHub CLI is available
113
- if ! command -v gh &> /dev/null; then
114
- echo " ⚠️ GitHub CLI (gh) not found, skipping sync"
115
- exit 0
116
- fi
117
-
118
- # TODO: Call GitHub spec sync
119
- # For now, just log the action
120
- echo " ✅ GitHub sync queued (implementation pending)"
121
- ;;
122
-
123
- jira)
124
- echo " 🔄 Syncing to Jira Epic..."
125
-
126
- # Check if Jira config exists
127
- if [[ -z "${JIRA_DOMAIN:-}" ]]; then
128
- echo " ⚠️ Jira not configured (.env), skipping sync"
129
- exit 0
130
- fi
131
-
132
- # TODO: Call Jira spec sync
133
- echo " ✅ Jira sync queued (implementation pending)"
134
- ;;
135
-
136
- ado)
137
- echo " 🔄 Syncing to Azure DevOps Feature..."
138
-
139
- # Check if ADO config exists
140
- if [[ -z "${ADO_ORGANIZATION:-}" ]]; then
141
- echo " ⚠️ ADO not configured (.env), skipping sync"
142
- exit 0
143
- fi
144
-
145
- # TODO: Call ADO spec sync
146
- echo " ✅ ADO sync queued (implementation pending)"
147
- ;;
148
-
149
- *)
150
- echo " ⚠️ Unknown provider: $PROVIDER"
151
- exit 0
152
- ;;
153
- esac
154
-
155
- echo " ✅ Post-spec-update hook complete"
156
- echo ""
157
-
158
- exit 0