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.
- package/CLAUDE.md +140 -1235
- package/bin/specweave.js +23 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts +3 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-client-v2.js +39 -0
- package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
- package/dist/src/cli/commands/set-sync-target.d.ts +41 -0
- package/dist/src/cli/commands/set-sync-target.d.ts.map +1 -0
- package/dist/src/cli/commands/set-sync-target.js +126 -0
- package/dist/src/cli/commands/set-sync-target.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +5 -8
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
- package/dist/src/core/hooks/HookScanner.d.ts +32 -0
- package/dist/src/core/hooks/HookScanner.d.ts.map +1 -1
- package/dist/src/core/hooks/HookScanner.js +125 -1
- package/dist/src/core/hooks/HookScanner.js.map +1 -1
- package/dist/src/core/hooks/types.d.ts +10 -1
- package/dist/src/core/hooks/types.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts +67 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +93 -0
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/project/index.d.ts +21 -0
- package/dist/src/core/project/index.d.ts.map +1 -0
- package/dist/src/core/project/index.js +22 -0
- package/dist/src/core/project/index.js.map +1 -0
- package/dist/src/core/project/project-service.d.ts +122 -0
- package/dist/src/core/project/project-service.d.ts.map +1 -0
- package/dist/src/core/project/project-service.js +334 -0
- package/dist/src/core/project/project-service.js.map +1 -0
- package/dist/src/core/sync/external-tool-resolver.d.ts +171 -0
- package/dist/src/core/sync/external-tool-resolver.d.ts.map +1 -0
- package/dist/src/core/sync/external-tool-resolver.js +569 -0
- package/dist/src/core/sync/external-tool-resolver.js.map +1 -0
- package/dist/src/core/types/increment-metadata.d.ts +92 -0
- package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
- package/dist/src/hooks/processor.d.ts +7 -3
- package/dist/src/hooks/processor.d.ts.map +1 -1
- package/dist/src/hooks/processor.js +11 -5
- package/dist/src/hooks/processor.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +0 -69
- package/plugins/specweave/hooks/v2/handlers/project-bridge-handler.sh +96 -0
- package/plugins/specweave/hooks/v2/queue/processor.sh +13 -5
- package/plugins/specweave/lib/hooks/project-bridge.js +76 -0
- package/plugins/specweave/lib/hooks/update-tasks-md.js +0 -0
- package/plugins/specweave/lib/hooks/us-completion-orchestrator.js +0 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +67 -1
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +93 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +92 -0
- package/plugins/specweave-github/lib/github-client-v2.js +39 -0
- package/plugins/specweave-github/lib/github-client-v2.ts +44 -0
- package/plugins/specweave/hooks/docs-changed.sh +0 -87
- package/plugins/specweave/hooks/human-input-required.sh +0 -83
- package/plugins/specweave/hooks/post-edit-write-consolidated.sh +0 -428
- package/plugins/specweave/hooks/post-first-increment.sh +0 -61
- package/plugins/specweave/hooks/post-increment-change.sh +0 -103
- package/plugins/specweave/hooks/post-increment-completion.sh +0 -513
- package/plugins/specweave/hooks/post-increment-planning.sh +0 -1204
- package/plugins/specweave/hooks/post-increment-status-change.sh +0 -243
- package/plugins/specweave/hooks/post-metadata-change.sh +0 -246
- package/plugins/specweave/hooks/post-spec-update.sh +0 -158
- package/plugins/specweave/hooks/post-task-completion.sh +0 -557
- package/plugins/specweave/hooks/post-task-edit.sh +0 -47
- package/plugins/specweave/hooks/post-user-story-complete.sh +0 -230
- package/plugins/specweave/hooks/pre-command-deduplication.sh +0 -68
- package/plugins/specweave/hooks/pre-edit-write-consolidated.sh +0 -225
- package/plugins/specweave/hooks/pre-implementation.sh +0 -75
- package/plugins/specweave/hooks/pre-increment-start.sh +0 -173
- package/plugins/specweave/hooks/pre-task-completion-edit.sh +0 -355
- package/plugins/specweave/hooks/pre-task-completion.sh +0 -269
- package/plugins/specweave/hooks/pre-tool-use.sh +0 -137
- package/plugins/specweave/hooks/session-start-reconcile.sh +0 -139
- package/plugins/specweave/hooks/shared/bulk-operation-detector.sh +0 -167
- package/plugins/specweave/hooks/test-pretooluse-env.sh +0 -72
- package/plugins/specweave/hooks/validate-increment-completion.sh +0 -113
- package/plugins/specweave/lib/hooks/consolidated-sync.js +0 -288
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# SpecWeave Pre-Task-Completion Hook
|
|
4
|
-
# CRITICAL QUALITY GATE: Validates AC tests before allowing task completion
|
|
5
|
-
#
|
|
6
|
-
# Runs automatically BEFORE any task is marked complete via TodoWrite
|
|
7
|
-
#
|
|
8
|
-
# WORKFLOW:
|
|
9
|
-
# =========
|
|
10
|
-
# 1. TodoWrite called with status="completed"
|
|
11
|
-
# 2. This hook fires (pre-completion validation)
|
|
12
|
-
# 3. Extract task ID from TodoWrite input
|
|
13
|
-
# 4. Find task in tasks.md
|
|
14
|
-
# 5. Run AC test validator
|
|
15
|
-
# 6. If tests PASS → Allow completion (continue: true)
|
|
16
|
-
# 7. If tests FAIL → Block completion (continue: false, show error)
|
|
17
|
-
#
|
|
18
|
-
# ENFORCEMENT:
|
|
19
|
-
# ============
|
|
20
|
-
# This is the ONLY way to mark tasks complete in SpecWeave.
|
|
21
|
-
# Manual edits to tasks.md are detected and flagged by pre-commit hooks.
|
|
22
|
-
|
|
23
|
-
set +e # EMERGENCY FIX: Prevents Claude Code crashes
|
|
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
|
-
|
|
36
|
-
# EMERGENCY KILL SWITCH
|
|
37
|
-
if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
|
|
38
|
-
exit 0
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
# Capture stdin FIRST to extract cwd from Claude's JSON input
|
|
42
|
-
_EARLY_STDIN=$(mktemp)
|
|
43
|
-
cat > "$_EARLY_STDIN"
|
|
44
|
-
|
|
45
|
-
# Extract cwd from JSON (Claude always provides this)
|
|
46
|
-
if command -v jq >/dev/null 2>&1; then
|
|
47
|
-
PROJECT_ROOT=$(jq -r '.cwd // empty' "$_EARLY_STDIN" 2>/dev/null)
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Fallback: find project root by walking up from script location
|
|
51
|
-
if [ -z "$PROJECT_ROOT" ] || [ ! -d "$PROJECT_ROOT/.specweave" ]; then
|
|
52
|
-
find_project_root() {
|
|
53
|
-
local dir="$1"
|
|
54
|
-
while [ "$dir" != "/" ]; do
|
|
55
|
-
if [ -d "$dir/.specweave" ]; then
|
|
56
|
-
echo "$dir"
|
|
57
|
-
return 0
|
|
58
|
-
fi
|
|
59
|
-
dir="$(dirname "$dir")"
|
|
60
|
-
done
|
|
61
|
-
pwd
|
|
62
|
-
}
|
|
63
|
-
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
|
-
cd "$PROJECT_ROOT" 2>/dev/null || true
|
|
67
|
-
|
|
68
|
-
# ============================================================================
|
|
69
|
-
# EMERGENCY SAFETY CHECKS (v0.24.4 - Performance Fix)
|
|
70
|
-
# ============================================================================
|
|
71
|
-
|
|
72
|
-
LOGS_DIR=".specweave/logs"
|
|
73
|
-
DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
|
|
74
|
-
mkdir -p "$LOGS_DIR" 2>/dev/null || true
|
|
75
|
-
|
|
76
|
-
# CIRCUIT BREAKER: Auto-disable after consecutive failures
|
|
77
|
-
CIRCUIT_BREAKER_FILE=".specweave/state/.hook-circuit-breaker-pre"
|
|
78
|
-
CIRCUIT_BREAKER_THRESHOLD=3
|
|
79
|
-
|
|
80
|
-
mkdir -p ".specweave/state" 2>/dev/null || true
|
|
81
|
-
|
|
82
|
-
if [[ -f "$CIRCUIT_BREAKER_FILE" ]]; then
|
|
83
|
-
FAILURE_COUNT=$(cat "$CIRCUIT_BREAKER_FILE" 2>/dev/null || echo 0)
|
|
84
|
-
if (( FAILURE_COUNT >= CIRCUIT_BREAKER_THRESHOLD )); then
|
|
85
|
-
# Circuit breaker is OPEN - hooks are disabled
|
|
86
|
-
exit 0
|
|
87
|
-
fi
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# FILE LOCK: Only allow 1 pre-task-completion hook at a time
|
|
91
|
-
LOCK_FILE=".specweave/state/.hook-pre-task.lock"
|
|
92
|
-
LOCK_TIMEOUT=5 # seconds (shorter than PostToolUse)
|
|
93
|
-
|
|
94
|
-
LOCK_ACQUIRED=false
|
|
95
|
-
for i in {1..5}; do
|
|
96
|
-
if mkdir "$LOCK_FILE" 2>/dev/null; then
|
|
97
|
-
LOCK_ACQUIRED=true
|
|
98
|
-
trap 'rmdir "$LOCK_FILE" 2>/dev/null || true' EXIT
|
|
99
|
-
break
|
|
100
|
-
fi
|
|
101
|
-
|
|
102
|
-
# Check for stale lock
|
|
103
|
-
if [[ -d "$LOCK_FILE" ]]; then
|
|
104
|
-
LOCK_AGE=$(($(date +%s) - $(stat -f "%m" "$LOCK_FILE" 2>/dev/null || echo 0)))
|
|
105
|
-
if (( LOCK_AGE > LOCK_TIMEOUT )); then
|
|
106
|
-
rmdir "$LOCK_FILE" 2>/dev/null || true
|
|
107
|
-
continue
|
|
108
|
-
fi
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
sleep 0.1
|
|
112
|
-
done
|
|
113
|
-
|
|
114
|
-
if [[ "$LOCK_ACQUIRED" == "false" ]]; then
|
|
115
|
-
# Another instance is running, skip
|
|
116
|
-
exit 0
|
|
117
|
-
fi
|
|
118
|
-
|
|
119
|
-
# DEBOUNCING: Prevent duplicate hook fires
|
|
120
|
-
LAST_FIRE_FILE="$LOGS_DIR/last-pre-hook-fire"
|
|
121
|
-
DEBOUNCE_SECONDS=5 # Same as PostToolUse
|
|
122
|
-
|
|
123
|
-
CURRENT_TIME=$(date +%s)
|
|
124
|
-
|
|
125
|
-
if [ -f "$LAST_FIRE_FILE" ]; then
|
|
126
|
-
LAST_FIRE=$(cat "$LAST_FIRE_FILE" 2>/dev/null || echo "0")
|
|
127
|
-
TIME_DIFF=$((CURRENT_TIME - LAST_FIRE))
|
|
128
|
-
|
|
129
|
-
if [ "$TIME_DIFF" -lt "$DEBOUNCE_SECONDS" ]; then
|
|
130
|
-
# Debounced - skip this execution
|
|
131
|
-
exit 0
|
|
132
|
-
fi
|
|
133
|
-
fi
|
|
134
|
-
|
|
135
|
-
echo "$CURRENT_TIME" > "$LAST_FIRE_FILE"
|
|
136
|
-
|
|
137
|
-
echo "[$(date)] 🔒 Pre-task-completion hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
138
|
-
|
|
139
|
-
# ============================================================================
|
|
140
|
-
# CAPTURE INPUT
|
|
141
|
-
# ============================================================================
|
|
142
|
-
|
|
143
|
-
# Reuse the early stdin capture
|
|
144
|
-
STDIN_DATA="$_EARLY_STDIN"
|
|
145
|
-
|
|
146
|
-
echo "[$(date)] Input JSON:" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
147
|
-
cat "$STDIN_DATA" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
148
|
-
|
|
149
|
-
# ============================================================================
|
|
150
|
-
# CHECK FOR TASK COMPLETION
|
|
151
|
-
# ============================================================================
|
|
152
|
-
|
|
153
|
-
# Only validate if a task is being marked complete
|
|
154
|
-
COMPLETING_TASK=false
|
|
155
|
-
|
|
156
|
-
if command -v jq >/dev/null 2>&1; then
|
|
157
|
-
# Check if any task is transitioning to "completed" status
|
|
158
|
-
COMPLETED_COUNT=$(jq -r '.tool_input.todos // [] | map(select(.status == "completed")) | length' "$STDIN_DATA" 2>/dev/null || echo "0")
|
|
159
|
-
|
|
160
|
-
if [ "$COMPLETED_COUNT" != "0" ]; then
|
|
161
|
-
COMPLETING_TASK=true
|
|
162
|
-
echo "[$(date)] ✓ Detected task completion (${COMPLETED_COUNT} tasks)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
163
|
-
fi
|
|
164
|
-
fi
|
|
165
|
-
|
|
166
|
-
# If no tasks being completed, allow without validation
|
|
167
|
-
if [ "$COMPLETING_TASK" = "false" ]; then
|
|
168
|
-
echo "[$(date)] ⏭️ No tasks being completed, skipping validation" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
169
|
-
rm -f "$STDIN_DATA"
|
|
170
|
-
echo '{"continue":true}'
|
|
171
|
-
exit 0
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
# ============================================================================
|
|
175
|
-
# DETECT CURRENT INCREMENT
|
|
176
|
-
# ============================================================================
|
|
177
|
-
|
|
178
|
-
CURRENT_INCREMENT=$(ls -td .specweave/increments/*/ 2>/dev/null | xargs -n1 basename | grep -v "_backlog" | grep -v "_archive" | grep -v "_working" | head -1)
|
|
179
|
-
|
|
180
|
-
if [ -z "$CURRENT_INCREMENT" ]; then
|
|
181
|
-
echo "[$(date)] ℹ️ No active increment found, skipping validation" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
182
|
-
rm -f "$STDIN_DATA"
|
|
183
|
-
echo '{"continue":true}'
|
|
184
|
-
exit 0
|
|
185
|
-
fi
|
|
186
|
-
|
|
187
|
-
TASKS_MD=".specweave/increments/$CURRENT_INCREMENT/tasks.md"
|
|
188
|
-
|
|
189
|
-
if [ ! -f "$TASKS_MD" ]; then
|
|
190
|
-
echo "[$(date)] ℹ️ tasks.md not found for $CURRENT_INCREMENT (increment may be in planning stage)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
191
|
-
rm -f "$STDIN_DATA"
|
|
192
|
-
echo '{"continue":true}'
|
|
193
|
-
exit 0
|
|
194
|
-
fi
|
|
195
|
-
|
|
196
|
-
# ============================================================================
|
|
197
|
-
# RUN AC TEST VALIDATION
|
|
198
|
-
# ============================================================================
|
|
199
|
-
|
|
200
|
-
echo "[$(date)] 🧪 Running AC test validation for $CURRENT_INCREMENT" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
201
|
-
|
|
202
|
-
# Determine which validation script to use
|
|
203
|
-
# Priority order:
|
|
204
|
-
# 1. SpecWeave development (dist/src/core/) - for contributors
|
|
205
|
-
# 2. npm package installation (node_modules/specweave/dist/)
|
|
206
|
-
# 3. Marketplace plugin (lib/vendor/core/) - for end users
|
|
207
|
-
VALIDATOR_SCRIPT=""
|
|
208
|
-
if [ -f "$PROJECT_ROOT/dist/src/core/ac-test-validator-cli.js" ]; then
|
|
209
|
-
# SpecWeave development repo
|
|
210
|
-
VALIDATOR_SCRIPT="$PROJECT_ROOT/dist/src/core/ac-test-validator-cli.js"
|
|
211
|
-
elif [ -f "$PROJECT_ROOT/node_modules/specweave/dist/src/core/ac-test-validator-cli.js" ]; then
|
|
212
|
-
# npm package installation
|
|
213
|
-
VALIDATOR_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/src/core/ac-test-validator-cli.js"
|
|
214
|
-
elif [ -n "${CLAUDE_PLUGIN_ROOT}" ] && [ -f "${CLAUDE_PLUGIN_ROOT}/lib/vendor/core/ac-test-validator-cli.js" ]; then
|
|
215
|
-
# Marketplace plugin (bundled in lib/vendor/)
|
|
216
|
-
VALIDATOR_SCRIPT="${CLAUDE_PLUGIN_ROOT}/lib/vendor/core/ac-test-validator-cli.js"
|
|
217
|
-
fi
|
|
218
|
-
|
|
219
|
-
if [ -z "$VALIDATOR_SCRIPT" ] || ! command -v node &> /dev/null; then
|
|
220
|
-
echo "[$(date)] ⚠️ AC test validator not found or Node.js missing" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
221
|
-
rm -f "$STDIN_DATA"
|
|
222
|
-
echo '{"continue":true,"systemMessage":"⚠️ Warning: AC test validator not available. Task completion validation skipped. Install Node.js and rebuild SpecWeave to enable validation."}'
|
|
223
|
-
exit 0
|
|
224
|
-
fi
|
|
225
|
-
|
|
226
|
-
# Run validator (captures exit code)
|
|
227
|
-
VALIDATION_OUTPUT=$(mktemp)
|
|
228
|
-
VALIDATION_EXIT_CODE=0
|
|
229
|
-
|
|
230
|
-
(cd "$PROJECT_ROOT" && node "$VALIDATOR_SCRIPT" "$CURRENT_INCREMENT") > "$VALIDATION_OUTPUT" 2>&1 || VALIDATION_EXIT_CODE=$?
|
|
231
|
-
|
|
232
|
-
echo "[$(date)] Validator exit code: $VALIDATION_EXIT_CODE" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
233
|
-
cat "$VALIDATION_OUTPUT" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
234
|
-
|
|
235
|
-
rm -f "$STDIN_DATA"
|
|
236
|
-
|
|
237
|
-
# ============================================================================
|
|
238
|
-
# DECISION LOGIC
|
|
239
|
-
# ============================================================================
|
|
240
|
-
|
|
241
|
-
if [ "$VALIDATION_EXIT_CODE" = "0" ]; then
|
|
242
|
-
# Validation passed - allow completion
|
|
243
|
-
echo "[$(date)] ✅ AC test validation passed" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
244
|
-
|
|
245
|
-
# Reset circuit breaker on success
|
|
246
|
-
echo "0" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
|
|
247
|
-
|
|
248
|
-
VALIDATION_SUMMARY=$(cat "$VALIDATION_OUTPUT" | tail -5 | tr '\n' ' ' | sed 's/"/\\"/g')
|
|
249
|
-
|
|
250
|
-
rm -f "$VALIDATION_OUTPUT"
|
|
251
|
-
|
|
252
|
-
printf '{"continue":true,"systemMessage":"✅ AC Test Validation Passed: All acceptance criteria have passing tests. Task completion allowed. %s"}\n' "$VALIDATION_SUMMARY"
|
|
253
|
-
else
|
|
254
|
-
# Validation failed - block completion
|
|
255
|
-
echo "[$(date)] ❌ AC test validation failed" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
256
|
-
|
|
257
|
-
# Increment circuit breaker on failure
|
|
258
|
-
CURRENT_FAILURES=$(cat "$CIRCUIT_BREAKER_FILE" 2>/dev/null || echo 0)
|
|
259
|
-
echo "$((CURRENT_FAILURES + 1))" > "$CIRCUIT_BREAKER_FILE" 2>/dev/null || true
|
|
260
|
-
|
|
261
|
-
VALIDATION_ERROR=$(cat "$VALIDATION_OUTPUT" | grep -A 10 "VALIDATION FAILED" | tr '\n' ' ' | cut -c 1-300 | sed 's/"/\\"/g')
|
|
262
|
-
|
|
263
|
-
rm -f "$VALIDATION_OUTPUT"
|
|
264
|
-
|
|
265
|
-
printf '{"continue":false,"systemMessage":"❌ AC TEST VALIDATION FAILED: Cannot mark task as complete until all acceptance criteria have passing tests. %s\\n\\nFix the failing tests and try again. Run tests manually: npm test"}\n' "$VALIDATION_ERROR"
|
|
266
|
-
fi
|
|
267
|
-
|
|
268
|
-
# ALWAYS exit 0 - NEVER let hook errors crash Claude Code
|
|
269
|
-
exit 0
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# SpecWeave Pre-Tool-Use Hook
|
|
4
|
-
# Runs BEFORE Claude calls any tool (PreToolUse event)
|
|
5
|
-
#
|
|
6
|
-
# PURPOSE: Detect when Claude asks questions via AskUserQuestion
|
|
7
|
-
# - Plays sound IMMEDIATELY when question is about to be asked
|
|
8
|
-
# - Complements post-task-completion hook (which only fires after TodoWrite)
|
|
9
|
-
# - Ensures user is always notified when Claude needs input
|
|
10
|
-
#
|
|
11
|
-
# SCOPE:
|
|
12
|
-
# - This hook fires for ALL tool calls (Read, Edit, Write, AskUserQuestion, etc.)
|
|
13
|
-
# - We filter for AskUserQuestion specifically to play sound
|
|
14
|
-
# - Non-blocking and fast (<10ms overhead)
|
|
15
|
-
|
|
16
|
-
set +e # EMERGENCY FIX: Prevents Claude Code crashes
|
|
17
|
-
|
|
18
|
-
# EMERGENCY KILL SWITCH
|
|
19
|
-
if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
|
|
20
|
-
exit 0
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
# ============================================================================
|
|
24
|
-
# CONFIGURATION
|
|
25
|
-
# ============================================================================
|
|
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
|
-
# Fallback
|
|
38
|
-
if [ -d "$(pwd)/.specweave" ]; then
|
|
39
|
-
pwd
|
|
40
|
-
else
|
|
41
|
-
echo "$(pwd)"
|
|
42
|
-
fi
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
PROJECT_ROOT="$(find_project_root "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")"
|
|
46
|
-
cd "$PROJECT_ROOT" 2>/dev/null || true
|
|
47
|
-
|
|
48
|
-
# ULTRA-FAST EARLY EXIT FOR NON-SPECWEAVE PROJECTS (T-006 - v0.26.15)
|
|
49
|
-
if [[ ! -d "$PROJECT_ROOT/.specweave" ]]; then
|
|
50
|
-
exit 0
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
LOGS_DIR=".specweave/logs"
|
|
54
|
-
DEBUG_LOG="$LOGS_DIR/hooks-debug.log"
|
|
55
|
-
|
|
56
|
-
mkdir -p "$LOGS_DIR" 2>/dev/null || true
|
|
57
|
-
|
|
58
|
-
# ============================================================================
|
|
59
|
-
# CAPTURE INPUT (Tool Call Details)
|
|
60
|
-
# ============================================================================
|
|
61
|
-
|
|
62
|
-
STDIN_DATA=$(mktemp)
|
|
63
|
-
cat > "$STDIN_DATA"
|
|
64
|
-
|
|
65
|
-
# Log the tool call for debugging
|
|
66
|
-
echo "[$(date)] 🔧 PreToolUse hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
67
|
-
echo "[$(date)] Tool call JSON:" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
68
|
-
cat "$STDIN_DATA" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
69
|
-
echo "" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
70
|
-
|
|
71
|
-
# ============================================================================
|
|
72
|
-
# DETECT AskUserQuestion TOOL
|
|
73
|
-
# ============================================================================
|
|
74
|
-
|
|
75
|
-
TOOL_NAME=""
|
|
76
|
-
|
|
77
|
-
if command -v jq >/dev/null 2>&1; then
|
|
78
|
-
# Use jq if available (most reliable)
|
|
79
|
-
TOOL_NAME=$(jq -r '.tool_name // empty' "$STDIN_DATA" 2>/dev/null)
|
|
80
|
-
else
|
|
81
|
-
# Fallback: grep-based detection
|
|
82
|
-
if grep -q '"tool_name"' "$STDIN_DATA" 2>/dev/null; then
|
|
83
|
-
TOOL_NAME=$(grep -o '"tool_name":"[^"]*"' "$STDIN_DATA" | cut -d'"' -f4)
|
|
84
|
-
fi
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
|
-
echo "[$(date)] Tool name: $TOOL_NAME" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
88
|
-
|
|
89
|
-
# ============================================================================
|
|
90
|
-
# PLAY SOUND IF AskUserQuestion
|
|
91
|
-
# ============================================================================
|
|
92
|
-
|
|
93
|
-
play_sound() {
|
|
94
|
-
case "$(uname -s)" in
|
|
95
|
-
Darwin)
|
|
96
|
-
# macOS: Use afplay with a distinctive sound for questions
|
|
97
|
-
afplay /System/Library/Sounds/Tink.aiff 2>/dev/null || true
|
|
98
|
-
;;
|
|
99
|
-
Linux)
|
|
100
|
-
# Linux: Use paplay or aplay
|
|
101
|
-
paplay /usr/share/sounds/freedesktop/stereo/dialog-question.oga 2>/dev/null || \
|
|
102
|
-
paplay /usr/share/sounds/freedesktop/stereo/message-new-instant.oga 2>/dev/null || \
|
|
103
|
-
aplay /usr/share/sounds/alsa/Front_Center.wav 2>/dev/null || true
|
|
104
|
-
;;
|
|
105
|
-
MINGW*|MSYS*|CYGWIN*)
|
|
106
|
-
# Windows: Use PowerShell
|
|
107
|
-
powershell -c "(New-Object Media.SoundPlayer 'C:\Windows\Media\Windows Notify.wav').PlaySync();" 2>/dev/null || true
|
|
108
|
-
;;
|
|
109
|
-
esac
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if [ "$TOOL_NAME" = "AskUserQuestion" ]; then
|
|
113
|
-
echo "[$(date)] 🔔 QUESTION DETECTED! Playing notification sound" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
114
|
-
play_sound
|
|
115
|
-
|
|
116
|
-
# Log this event
|
|
117
|
-
echo "[$(date)] Claude is asking for user input via AskUserQuestion" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
# ============================================================================
|
|
121
|
-
# CLEANUP
|
|
122
|
-
# ============================================================================
|
|
123
|
-
|
|
124
|
-
rm -f "$STDIN_DATA"
|
|
125
|
-
|
|
126
|
-
# ============================================================================
|
|
127
|
-
# OUTPUT TO CLAUDE (Always continue)
|
|
128
|
-
# ============================================================================
|
|
129
|
-
|
|
130
|
-
if [ "$TOOL_NAME" = "AskUserQuestion" ]; then
|
|
131
|
-
echo '{"continue":true,"systemMessage":"🔔 Sound played - user notified of question request"}'
|
|
132
|
-
else
|
|
133
|
-
echo '{"continue":true}'
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# ALWAYS exit 0 - NEVER let hook errors crash Claude Code
|
|
137
|
-
exit 0
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# SpecWeave Session Start Reconcile Hook (NEW in v0.28.33)
|
|
4
|
-
#
|
|
5
|
-
# Runs on Claude Code session start to detect and fix GitHub status drift.
|
|
6
|
-
# This is a lightweight hook that runs in background to not block startup.
|
|
7
|
-
#
|
|
8
|
-
# Features:
|
|
9
|
-
# - Debounced: Only runs if >1 hour since last reconcile
|
|
10
|
-
# - Non-blocking: Runs in background, errors logged but don't crash
|
|
11
|
-
# - Optional: Controlled by config.sync.github.autoReconcileOnSessionStart
|
|
12
|
-
#
|
|
13
|
-
# Trigger: SessionStart hook event
|
|
14
|
-
|
|
15
|
-
set +e # Prevent crashes
|
|
16
|
-
|
|
17
|
-
# EMERGENCY KILL SWITCH
|
|
18
|
-
if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
|
|
19
|
-
exit 0
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
# Find project root
|
|
23
|
-
find_project_root() {
|
|
24
|
-
local dir="$PWD"
|
|
25
|
-
while [[ "$dir" != "/" ]]; do
|
|
26
|
-
if [[ -d "$dir/.specweave" ]]; then
|
|
27
|
-
echo "$dir"
|
|
28
|
-
return 0
|
|
29
|
-
fi
|
|
30
|
-
dir=$(dirname "$dir")
|
|
31
|
-
done
|
|
32
|
-
echo "$PWD"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
PROJECT_ROOT=$(find_project_root)
|
|
36
|
-
CONFIG_FILE="$PROJECT_ROOT/.specweave/config.json"
|
|
37
|
-
STATE_DIR="$PROJECT_ROOT/.specweave/state"
|
|
38
|
-
LAST_RECONCILE_FILE="$STATE_DIR/.last-reconcile-timestamp"
|
|
39
|
-
DEBUG_LOG="$PROJECT_ROOT/.specweave/logs/hooks-debug.log"
|
|
40
|
-
|
|
41
|
-
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
42
|
-
mkdir -p "$(dirname "$DEBUG_LOG")" 2>/dev/null || true
|
|
43
|
-
|
|
44
|
-
# Consume stdin (required for hooks)
|
|
45
|
-
cat > /dev/null
|
|
46
|
-
|
|
47
|
-
echo "[$(date)] session-start-reconcile: Hook fired" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
48
|
-
|
|
49
|
-
# ============================================================================
|
|
50
|
-
# CHECK IF AUTO-RECONCILE IS ENABLED
|
|
51
|
-
# ============================================================================
|
|
52
|
-
|
|
53
|
-
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
54
|
-
echo "[$(date)] session-start-reconcile: No config.json - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
55
|
-
exit 0
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
# Check config (requires jq)
|
|
59
|
-
if ! command -v jq &> /dev/null; then
|
|
60
|
-
echo "[$(date)] session-start-reconcile: jq not found - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
61
|
-
exit 0
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
# Check if auto-reconcile is enabled
|
|
65
|
-
AUTO_RECONCILE=$(jq -r '.sync.github.autoReconcileOnSessionStart // false' "$CONFIG_FILE" 2>/dev/null)
|
|
66
|
-
|
|
67
|
-
if [[ "$AUTO_RECONCILE" != "true" ]]; then
|
|
68
|
-
echo "[$(date)] session-start-reconcile: Disabled in config - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
69
|
-
exit 0
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
echo "[$(date)] session-start-reconcile: Auto-reconcile enabled" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
73
|
-
|
|
74
|
-
# ============================================================================
|
|
75
|
-
# DEBOUNCE: Only run if >1 hour since last reconcile
|
|
76
|
-
# ============================================================================
|
|
77
|
-
|
|
78
|
-
DEBOUNCE_SECONDS=3600 # 1 hour
|
|
79
|
-
|
|
80
|
-
if [[ -f "$LAST_RECONCILE_FILE" ]]; then
|
|
81
|
-
LAST_TIMESTAMP=$(cat "$LAST_RECONCILE_FILE" 2>/dev/null || echo "0")
|
|
82
|
-
CURRENT_TIMESTAMP=$(date +%s)
|
|
83
|
-
ELAPSED=$((CURRENT_TIMESTAMP - LAST_TIMESTAMP))
|
|
84
|
-
|
|
85
|
-
if [[ $ELAPSED -lt $DEBOUNCE_SECONDS ]]; then
|
|
86
|
-
REMAINING=$((DEBOUNCE_SECONDS - ELAPSED))
|
|
87
|
-
echo "[$(date)] session-start-reconcile: Debounced - last run ${ELAPSED}s ago (wait ${REMAINING}s)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
88
|
-
exit 0
|
|
89
|
-
fi
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
echo "[$(date)] session-start-reconcile: Debounce passed - running reconcile" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
93
|
-
|
|
94
|
-
# Update timestamp BEFORE running (prevents concurrent runs)
|
|
95
|
-
date +%s > "$LAST_RECONCILE_FILE"
|
|
96
|
-
|
|
97
|
-
# ============================================================================
|
|
98
|
-
# RUN RECONCILE IN BACKGROUND (non-blocking)
|
|
99
|
-
# ============================================================================
|
|
100
|
-
|
|
101
|
-
if ! command -v node &> /dev/null; then
|
|
102
|
-
echo "[$(date)] session-start-reconcile: Node.js not found - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
103
|
-
exit 0
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
# Find reconcile script
|
|
107
|
-
RECONCILE_SCRIPT=""
|
|
108
|
-
if [[ -f "$PROJECT_ROOT/plugins/specweave/lib/hooks/session-start-reconcile-worker.js" ]]; then
|
|
109
|
-
RECONCILE_SCRIPT="$PROJECT_ROOT/plugins/specweave/lib/hooks/session-start-reconcile-worker.js"
|
|
110
|
-
elif [[ -f "$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/session-start-reconcile-worker.js" ]]; then
|
|
111
|
-
RECONCILE_SCRIPT="$PROJECT_ROOT/dist/plugins/specweave/lib/hooks/session-start-reconcile-worker.js"
|
|
112
|
-
elif [[ -f "$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/session-start-reconcile-worker.js" ]]; then
|
|
113
|
-
RECONCILE_SCRIPT="$PROJECT_ROOT/node_modules/specweave/dist/plugins/specweave/lib/hooks/session-start-reconcile-worker.js"
|
|
114
|
-
fi
|
|
115
|
-
|
|
116
|
-
if [[ -z "$RECONCILE_SCRIPT" ]]; then
|
|
117
|
-
echo "[$(date)] session-start-reconcile: Worker script not found - skipping" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
118
|
-
exit 0
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
|
-
# Load GITHUB_TOKEN from .env
|
|
122
|
-
if [[ -f "$PROJECT_ROOT/.env" ]]; then
|
|
123
|
-
GITHUB_TOKEN_FROM_ENV=$(grep -E '^GITHUB_TOKEN=' "$PROJECT_ROOT/.env" 2>/dev/null | head -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
|
|
124
|
-
if [[ -n "$GITHUB_TOKEN_FROM_ENV" ]]; then
|
|
125
|
-
export GITHUB_TOKEN="$GITHUB_TOKEN_FROM_ENV"
|
|
126
|
-
fi
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
# Run in background with nohup (fully detached)
|
|
130
|
-
# Output goes to debug log, doesn't block session start
|
|
131
|
-
echo "[$(date)] session-start-reconcile: Starting background reconcile" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
132
|
-
|
|
133
|
-
(
|
|
134
|
-
cd "$PROJECT_ROOT" && \
|
|
135
|
-
node "$RECONCILE_SCRIPT" >> "$DEBUG_LOG" 2>&1
|
|
136
|
-
) &
|
|
137
|
-
|
|
138
|
-
# Immediately exit (don't wait for background job)
|
|
139
|
-
exit 0
|