specweave 1.0.31 → 1.0.33
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-plugin/marketplace.json +1 -1
- package/CLAUDE.md +205 -148
- package/README.md +0 -2
- package/bin/specweave.js +11 -0
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/update-instructions.d.ts +16 -0
- package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
- package/dist/src/cli/commands/update-instructions.js +134 -0
- package/dist/src/cli/commands/update-instructions.js.map +1 -0
- package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
- package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/directory-structure.js +163 -33
- package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +2 -1
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +3 -1
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
- package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
- package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
- package/dist/src/config/types.d.ts +2 -2
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
- package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
- package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
- package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
- package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/index.js +15 -0
- package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
- package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
- package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
- package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
- package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
- package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
- package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
- package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
- package/dist/src/core/tools/index.d.ts +11 -0
- package/dist/src/core/tools/index.d.ts.map +1 -0
- package/dist/src/core/tools/index.js +10 -0
- package/dist/src/core/tools/index.js.map +1 -0
- package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
- package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
- package/dist/src/core/tools/tool-event-bus.js +84 -0
- package/dist/src/core/tools/tool-event-bus.js.map +1 -0
- package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
- package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
- package/dist/src/core/tools/tool-index-builder.js +289 -0
- package/dist/src/core/tools/tool-index-builder.js.map +1 -0
- package/dist/src/core/tools/tool-registry.d.ts +51 -0
- package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
- package/dist/src/core/tools/tool-registry.js +224 -0
- package/dist/src/core/tools/tool-registry.js.map +1 -0
- package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
- package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
- package/dist/src/core/tools/tool-search-engine.js +174 -0
- package/dist/src/core/tools/tool-search-engine.js.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
- package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
- package/dist/src/core/tools/types/tool-registry-types.js +7 -0
- package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
- package/dist/src/init/compliance/types.d.ts +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/hooks.json +3 -13
- package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
- package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
- package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
- package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
- package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
- package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
- package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
- package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
- package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
- package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
- package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
- package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
- package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
- package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
- package/plugins/specweave/hooks/v2/session-end.sh +3 -1
- package/plugins/specweave/hooks/v2/session-start.sh +3 -1
- package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
- package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
- package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
- package/plugins/specweave-mobile/README.md +55 -35
- package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
- package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
- package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
- package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
- package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
- package/plugins/specweave-release/commands/npm.md +61 -17
- package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
- package/src/templates/AGENTS.md.template +34 -0
- package/src/templates/CLAUDE.md.template +121 -155
- package/plugins/specweave/hooks/config-env-separator.sh +0 -99
- package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
- package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
- package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
- package/plugins/specweave/hooks/lib/logging.sh +0 -231
- package/plugins/specweave/hooks/lib/metrics.sh +0 -347
- package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
- package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
- package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
- package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
- package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
- package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
- package/plugins/specweave/hooks/v2/guards/per-us-project-validator.test.sh +0 -406
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# crash-prevention.sh - Unified Crash Prevention Runtime
|
|
3
|
-
#
|
|
4
|
-
# Consolidates ALL crash prevention patterns from 9 crash categories:
|
|
5
|
-
# 1. Bash heredoc hangs (infinite wait for EOF)
|
|
6
|
-
# 2. Context explosion (>280KB total)
|
|
7
|
-
# 3. Hook recursion loops
|
|
8
|
-
# 4. Process storms (bulk operations)
|
|
9
|
-
# 5. Agent chunking violations
|
|
10
|
-
# 6. Direct completion bypass
|
|
11
|
-
# 7. Hook registration duplicates
|
|
12
|
-
# 8. Context compaction deadlock
|
|
13
|
-
# 9. MCP connection drops
|
|
14
|
-
#
|
|
15
|
-
# This is the SINGLE SOURCE OF TRUTH for crash prevention.
|
|
16
|
-
# Consolidated from ADRs: 0060, 0068, 0073, 0127, 0128, 0130, 0133, 0157, 0189
|
|
17
|
-
#
|
|
18
|
-
# v0.33.0 - Initial consolidation
|
|
19
|
-
|
|
20
|
-
set +e
|
|
21
|
-
|
|
22
|
-
# ============================================================================
|
|
23
|
-
# CRASH CATEGORY 1: BASH HEREDOC DETECTION
|
|
24
|
-
# ============================================================================
|
|
25
|
-
|
|
26
|
-
# Patterns that cause INFINITE HANGS (shell waits forever for EOF)
|
|
27
|
-
detect_bash_hang_pattern() {
|
|
28
|
-
local command="$1"
|
|
29
|
-
|
|
30
|
-
# Heredoc patterns (MOST DANGEROUS)
|
|
31
|
-
if echo "$command" | grep -qE "<<-?[[:space:]]*['\"]?[A-Za-z_]+" 2>/dev/null; then
|
|
32
|
-
echo "heredoc"
|
|
33
|
-
return 0
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# Cat stdin redirect (waits forever)
|
|
37
|
-
if echo "$command" | grep -qE "^[[:space:]]*cat[[:space:]]+>[[:space:]]*[^>]" 2>/dev/null; then
|
|
38
|
-
echo "cat-stdin"
|
|
39
|
-
return 0
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# DD command with output file
|
|
43
|
-
if echo "$command" | grep -qE "^[[:space:]]*dd[[:space:]].*of=" 2>/dev/null; then
|
|
44
|
-
echo "dd-stdin"
|
|
45
|
-
return 0
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
# Echo/printf to file (truncation risk)
|
|
49
|
-
if echo "$command" | grep -qE "^[[:space:]]*(echo|printf)[[:space:]]" 2>/dev/null; then
|
|
50
|
-
if echo "$command" | grep -qE '>[[:space:]]*[^>]' 2>/dev/null; then
|
|
51
|
-
if ! echo "$command" | grep -qE '>>' 2>/dev/null; then
|
|
52
|
-
echo "echo-redirect"
|
|
53
|
-
return 0
|
|
54
|
-
fi
|
|
55
|
-
fi
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
return 1
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
# ============================================================================
|
|
62
|
-
# CRASH CATEGORY 2: CONTEXT BUDGET ESTIMATION
|
|
63
|
-
# ============================================================================
|
|
64
|
-
|
|
65
|
-
# Rough token estimation (1 token ≈ 4 chars)
|
|
66
|
-
estimate_tokens() {
|
|
67
|
-
local file="$1"
|
|
68
|
-
if [[ -f "$file" ]]; then
|
|
69
|
-
local chars
|
|
70
|
-
chars=$(wc -c < "$file" 2>/dev/null || echo "0")
|
|
71
|
-
echo $((chars / 4))
|
|
72
|
-
else
|
|
73
|
-
echo "0"
|
|
74
|
-
fi
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# Check if context is in danger zone
|
|
78
|
-
check_context_budget() {
|
|
79
|
-
local project_root="$1"
|
|
80
|
-
local active_increment="$2"
|
|
81
|
-
|
|
82
|
-
local total_tokens=0
|
|
83
|
-
local warning_threshold=150000 # ~150K tokens
|
|
84
|
-
local danger_threshold=200000 # ~200K tokens
|
|
85
|
-
|
|
86
|
-
# Estimate increment context
|
|
87
|
-
if [[ -n "$active_increment" ]] && [[ -d "$project_root/.specweave/increments/$active_increment" ]]; then
|
|
88
|
-
local inc_dir="$project_root/.specweave/increments/$active_increment"
|
|
89
|
-
|
|
90
|
-
local spec_tokens=$(estimate_tokens "$inc_dir/spec.md")
|
|
91
|
-
local plan_tokens=$(estimate_tokens "$inc_dir/plan.md")
|
|
92
|
-
local tasks_tokens=$(estimate_tokens "$inc_dir/tasks.md")
|
|
93
|
-
|
|
94
|
-
total_tokens=$((spec_tokens + plan_tokens + tasks_tokens))
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# Return status
|
|
98
|
-
if [[ $total_tokens -gt $danger_threshold ]]; then
|
|
99
|
-
echo "DANGER:$total_tokens"
|
|
100
|
-
return 2
|
|
101
|
-
elif [[ $total_tokens -gt $warning_threshold ]]; then
|
|
102
|
-
echo "WARNING:$total_tokens"
|
|
103
|
-
return 1
|
|
104
|
-
else
|
|
105
|
-
echo "OK:$total_tokens"
|
|
106
|
-
return 0
|
|
107
|
-
fi
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# Count tasks in active increment
|
|
111
|
-
count_increment_tasks() {
|
|
112
|
-
local project_root="$1"
|
|
113
|
-
local active_increment="$2"
|
|
114
|
-
|
|
115
|
-
local tasks_file="$project_root/.specweave/increments/$active_increment/tasks.md"
|
|
116
|
-
if [[ -f "$tasks_file" ]]; then
|
|
117
|
-
grep -c "^### T-" "$tasks_file" 2>/dev/null || echo "0"
|
|
118
|
-
else
|
|
119
|
-
echo "0"
|
|
120
|
-
fi
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
# ============================================================================
|
|
124
|
-
# CRASH CATEGORY 3: PROCESS STORM DETECTION
|
|
125
|
-
# ============================================================================
|
|
126
|
-
|
|
127
|
-
# Count running hook processes
|
|
128
|
-
count_hook_processes() {
|
|
129
|
-
ps aux 2>/dev/null | grep -c "specweave.*hook" || echo "0"
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
# Detect if we're in a process storm
|
|
133
|
-
detect_process_storm() {
|
|
134
|
-
local threshold="${1:-20}"
|
|
135
|
-
local count
|
|
136
|
-
count=$(count_hook_processes)
|
|
137
|
-
|
|
138
|
-
if [[ "$count" -gt "$threshold" ]]; then
|
|
139
|
-
echo "STORM:$count"
|
|
140
|
-
return 1
|
|
141
|
-
fi
|
|
142
|
-
echo "OK:$count"
|
|
143
|
-
return 0
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
# ============================================================================
|
|
147
|
-
# CRASH CATEGORY 4: ZOMBIE PROCESS DETECTION
|
|
148
|
-
# ============================================================================
|
|
149
|
-
|
|
150
|
-
# Find and kill zombie heredoc processes
|
|
151
|
-
kill_zombie_heredocs() {
|
|
152
|
-
# Kill any cat processes waiting for EOF
|
|
153
|
-
pkill -f "cat.*EOF" 2>/dev/null || true
|
|
154
|
-
pkill -f "cat.*<<" 2>/dev/null || true
|
|
155
|
-
|
|
156
|
-
# Kill stale bash processes related to specweave hooks
|
|
157
|
-
local stale_pids
|
|
158
|
-
stale_pids=$(ps aux 2>/dev/null | grep -E "bash.*specweave.*hook" | grep -v grep | awk '{print $2}' | head -10)
|
|
159
|
-
|
|
160
|
-
for pid in $stale_pids; do
|
|
161
|
-
# Check if process is older than 30 seconds
|
|
162
|
-
local elapsed
|
|
163
|
-
elapsed=$(ps -o etimes= -p "$pid" 2>/dev/null | tr -d ' ')
|
|
164
|
-
if [[ -n "$elapsed" ]] && [[ "$elapsed" -gt 30 ]]; then
|
|
165
|
-
kill -9 "$pid" 2>/dev/null || true
|
|
166
|
-
fi
|
|
167
|
-
done
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
# ============================================================================
|
|
171
|
-
# CRASH CATEGORY 5: LOCK FILE CLEANUP
|
|
172
|
-
# ============================================================================
|
|
173
|
-
|
|
174
|
-
# Clean stale lock files (older than 60s)
|
|
175
|
-
clean_stale_locks() {
|
|
176
|
-
local project_root="$1"
|
|
177
|
-
local state_dir="$project_root/.specweave/state"
|
|
178
|
-
|
|
179
|
-
[[ ! -d "$state_dir" ]] && return
|
|
180
|
-
|
|
181
|
-
find "$state_dir" -name "*.lock" -type d -mmin +1 2>/dev/null | while read -r lock; do
|
|
182
|
-
rmdir "$lock" 2>/dev/null || true
|
|
183
|
-
done
|
|
184
|
-
|
|
185
|
-
# Also clean stale guard files
|
|
186
|
-
find "$state_dir" -name ".hook-*-guard" -type f -mmin +1 2>/dev/null | while read -r guard; do
|
|
187
|
-
rm -f "$guard" 2>/dev/null
|
|
188
|
-
done
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
# ============================================================================
|
|
192
|
-
# CRASH CATEGORY 6: SESSION HEALTH CHECK
|
|
193
|
-
# ============================================================================
|
|
194
|
-
|
|
195
|
-
# Comprehensive health check
|
|
196
|
-
check_session_health() {
|
|
197
|
-
local project_root="$1"
|
|
198
|
-
local issues=()
|
|
199
|
-
|
|
200
|
-
# Check 1: Process storm
|
|
201
|
-
local storm_status
|
|
202
|
-
storm_status=$(detect_process_storm 15)
|
|
203
|
-
if [[ "$storm_status" == STORM* ]]; then
|
|
204
|
-
issues+=("Process storm detected: $storm_status")
|
|
205
|
-
fi
|
|
206
|
-
|
|
207
|
-
# Check 2: Stale locks
|
|
208
|
-
local stale_locks
|
|
209
|
-
stale_locks=$(find "$project_root/.specweave/state" -name "*.lock" -type d -mmin +1 2>/dev/null | wc -l | tr -d ' ')
|
|
210
|
-
if [[ "$stale_locks" -gt 0 ]]; then
|
|
211
|
-
issues+=("Stale locks: $stale_locks")
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
# Check 3: Circuit breaker status
|
|
215
|
-
local cb_file="$project_root/.specweave/state/.hook-circuit-breaker"
|
|
216
|
-
if [[ -f "$cb_file" ]]; then
|
|
217
|
-
local failures
|
|
218
|
-
failures=$(cat "$cb_file" 2>/dev/null || echo "0")
|
|
219
|
-
if [[ "$failures" -ge 3 ]]; then
|
|
220
|
-
issues+=("Circuit breaker OPEN: $failures failures")
|
|
221
|
-
fi
|
|
222
|
-
fi
|
|
223
|
-
|
|
224
|
-
# Check 4: Active increment task count
|
|
225
|
-
local active_inc
|
|
226
|
-
active_inc=$(find "$project_root/.specweave/increments" -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | head -1 | xargs basename 2>/dev/null)
|
|
227
|
-
if [[ -n "$active_inc" ]]; then
|
|
228
|
-
local task_count
|
|
229
|
-
task_count=$(count_increment_tasks "$project_root" "$active_inc")
|
|
230
|
-
if [[ "$task_count" -gt 8 ]]; then
|
|
231
|
-
issues+=("Task count exceeds limit: $task_count/8 in $active_inc")
|
|
232
|
-
fi
|
|
233
|
-
fi
|
|
234
|
-
|
|
235
|
-
# Report
|
|
236
|
-
if [[ ${#issues[@]} -eq 0 ]]; then
|
|
237
|
-
echo "HEALTHY"
|
|
238
|
-
return 0
|
|
239
|
-
else
|
|
240
|
-
echo "ISSUES:"
|
|
241
|
-
printf '%s\n' "${issues[@]}"
|
|
242
|
-
return 1
|
|
243
|
-
fi
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
# ============================================================================
|
|
247
|
-
# CRASH CATEGORY 7: EMERGENCY CLEANUP
|
|
248
|
-
# ============================================================================
|
|
249
|
-
|
|
250
|
-
# Nuclear option: clean ALL state
|
|
251
|
-
emergency_cleanup() {
|
|
252
|
-
local project_root="$1"
|
|
253
|
-
|
|
254
|
-
echo "=== EMERGENCY CLEANUP ==="
|
|
255
|
-
|
|
256
|
-
# 1. Kill zombie processes
|
|
257
|
-
echo "1. Killing zombie processes..."
|
|
258
|
-
kill_zombie_heredocs
|
|
259
|
-
pkill -9 -f "bash.*specweave" 2>/dev/null || true
|
|
260
|
-
|
|
261
|
-
# 2. Remove all locks
|
|
262
|
-
echo "2. Removing all locks..."
|
|
263
|
-
rm -rf "$project_root/.specweave/state/"*.lock 2>/dev/null
|
|
264
|
-
rm -f "$project_root/.specweave/state/.hook-"* 2>/dev/null
|
|
265
|
-
rm -f "$project_root/.specweave/state/.processor.lock" 2>/dev/null
|
|
266
|
-
|
|
267
|
-
# 3. Reset circuit breakers
|
|
268
|
-
echo "3. Resetting circuit breakers..."
|
|
269
|
-
rm -f "$project_root/.specweave/state/.hook-circuit-breaker"* 2>/dev/null
|
|
270
|
-
|
|
271
|
-
# 4. Clear dedup cache
|
|
272
|
-
echo "4. Clearing dedup cache..."
|
|
273
|
-
rm -rf "$project_root/.specweave/state/.dedup-cache" 2>/dev/null
|
|
274
|
-
|
|
275
|
-
# 5. Clear bulk operation counter
|
|
276
|
-
echo "5. Clearing bulk operation counter..."
|
|
277
|
-
rm -f "$project_root/.specweave/state/.bulk-op-counter" 2>/dev/null
|
|
278
|
-
|
|
279
|
-
echo "=== CLEANUP COMPLETE ==="
|
|
280
|
-
echo "Restart Claude Code to continue."
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
# ============================================================================
|
|
284
|
-
# RUNTIME GUARDS (for use in hooks)
|
|
285
|
-
# ============================================================================
|
|
286
|
-
|
|
287
|
-
# Pre-execution guard for any hook
|
|
288
|
-
guard_hook_execution() {
|
|
289
|
-
local hook_name="$1"
|
|
290
|
-
local project_root="$2"
|
|
291
|
-
|
|
292
|
-
# Check for process storm
|
|
293
|
-
local storm
|
|
294
|
-
storm=$(detect_process_storm 20)
|
|
295
|
-
if [[ "$storm" == STORM* ]]; then
|
|
296
|
-
echo "[GUARD] Blocking $hook_name: $storm" >&2
|
|
297
|
-
return 1
|
|
298
|
-
fi
|
|
299
|
-
|
|
300
|
-
return 0
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
# ============================================================================
|
|
304
|
-
# MAIN (when run directly)
|
|
305
|
-
# ============================================================================
|
|
306
|
-
|
|
307
|
-
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
308
|
-
case "${1:-health}" in
|
|
309
|
-
health)
|
|
310
|
-
PROJECT_ROOT=$(find "$PWD" -maxdepth 3 -type d -name ".specweave" 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
|
|
311
|
-
if [[ -n "$PROJECT_ROOT" ]]; then
|
|
312
|
-
check_session_health "$PROJECT_ROOT"
|
|
313
|
-
else
|
|
314
|
-
echo "Not in a SpecWeave project"
|
|
315
|
-
exit 1
|
|
316
|
-
fi
|
|
317
|
-
;;
|
|
318
|
-
cleanup)
|
|
319
|
-
PROJECT_ROOT=$(find "$PWD" -maxdepth 3 -type d -name ".specweave" 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
|
|
320
|
-
if [[ -n "$PROJECT_ROOT" ]]; then
|
|
321
|
-
emergency_cleanup "$PROJECT_ROOT"
|
|
322
|
-
else
|
|
323
|
-
echo "Not in a SpecWeave project"
|
|
324
|
-
exit 1
|
|
325
|
-
fi
|
|
326
|
-
;;
|
|
327
|
-
kill-zombies)
|
|
328
|
-
kill_zombie_heredocs
|
|
329
|
-
echo "Zombie processes cleaned"
|
|
330
|
-
;;
|
|
331
|
-
*)
|
|
332
|
-
echo "Usage: $0 {health|cleanup|kill-zombies}"
|
|
333
|
-
exit 1
|
|
334
|
-
;;
|
|
335
|
-
esac
|
|
336
|
-
fi
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# logging.sh - Structured logging for SpecWeave hooks
|
|
3
|
-
#
|
|
4
|
-
# FEATURES:
|
|
5
|
-
# - Structured JSON logs for machine parsing
|
|
6
|
-
# - Human-readable console output
|
|
7
|
-
# - Request ID tracing across hook chains
|
|
8
|
-
# - Log levels (DEBUG, INFO, WARN, ERROR)
|
|
9
|
-
# - Automatic log rotation
|
|
10
|
-
# - Performance timing
|
|
11
|
-
#
|
|
12
|
-
# USAGE:
|
|
13
|
-
# source logging.sh
|
|
14
|
-
# log_init "hook-name"
|
|
15
|
-
# log_info "Processing request"
|
|
16
|
-
# log_error "Something failed" "error_code=123"
|
|
17
|
-
# log_timing_start "operation"
|
|
18
|
-
# # ... do work ...
|
|
19
|
-
# log_timing_end "operation"
|
|
20
|
-
#
|
|
21
|
-
# v1.0.0 - Initial implementation (2025-12-17)
|
|
22
|
-
|
|
23
|
-
# === Configuration ===
|
|
24
|
-
LOG_DIR="${SPECWEAVE_LOG_DIR:-.specweave/logs/hooks}"
|
|
25
|
-
LOG_LEVEL="${LOG_LEVEL:-INFO}" # DEBUG, INFO, WARN, ERROR
|
|
26
|
-
LOG_FORMAT="${LOG_FORMAT:-json}" # json, text
|
|
27
|
-
LOG_MAX_SIZE_KB="${LOG_MAX_SIZE_KB:-1024}" # 1MB per file
|
|
28
|
-
LOG_ROTATION_COUNT="${LOG_ROTATION_COUNT:-5}"
|
|
29
|
-
|
|
30
|
-
# Log level numeric values
|
|
31
|
-
declare -A LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)
|
|
32
|
-
|
|
33
|
-
# Request context
|
|
34
|
-
_LOG_REQUEST_ID=""
|
|
35
|
-
_LOG_HOOK_NAME=""
|
|
36
|
-
_LOG_START_TIME=""
|
|
37
|
-
declare -A _LOG_TIMINGS
|
|
38
|
-
|
|
39
|
-
# === Initialization ===
|
|
40
|
-
log_init() {
|
|
41
|
-
local hook_name="$1"
|
|
42
|
-
|
|
43
|
-
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
|
44
|
-
|
|
45
|
-
_LOG_HOOK_NAME="$hook_name"
|
|
46
|
-
_LOG_REQUEST_ID="${REQUEST_ID:-$(generate_request_id)}"
|
|
47
|
-
_LOG_START_TIME=$(get_timestamp_ms)
|
|
48
|
-
|
|
49
|
-
export REQUEST_ID="$_LOG_REQUEST_ID"
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# === Request ID Generation ===
|
|
53
|
-
generate_request_id() {
|
|
54
|
-
# Format: HHMMSS-RANDOM (compact but unique enough for tracing)
|
|
55
|
-
local time_part
|
|
56
|
-
time_part=$(date +%H%M%S)
|
|
57
|
-
local random_part
|
|
58
|
-
random_part=$(printf '%04x' $((RANDOM % 65536)))
|
|
59
|
-
echo "${time_part}-${random_part}"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
# === Timestamp ===
|
|
63
|
-
get_timestamp_ms() {
|
|
64
|
-
if command -v gdate &>/dev/null; then
|
|
65
|
-
gdate +%s%3N
|
|
66
|
-
elif date +%s%N &>/dev/null 2>&1; then
|
|
67
|
-
echo $(($(date +%s%N) / 1000000))
|
|
68
|
-
else
|
|
69
|
-
echo "$(($(date +%s) * 1000))"
|
|
70
|
-
fi
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
get_timestamp_iso() {
|
|
74
|
-
date -u +"%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ"
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# === Level Check ===
|
|
78
|
-
_should_log() {
|
|
79
|
-
local level="$1"
|
|
80
|
-
local level_num="${LOG_LEVELS[$level]:-1}"
|
|
81
|
-
local threshold_num="${LOG_LEVELS[$LOG_LEVEL]:-1}"
|
|
82
|
-
[[ "$level_num" -ge "$threshold_num" ]]
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
# === Log Rotation ===
|
|
86
|
-
_rotate_log_if_needed() {
|
|
87
|
-
local log_file="$1"
|
|
88
|
-
|
|
89
|
-
[[ ! -f "$log_file" ]] && return
|
|
90
|
-
|
|
91
|
-
local size_kb
|
|
92
|
-
if [[ "$(uname)" == "Darwin" ]]; then
|
|
93
|
-
size_kb=$(($(stat -f %z "$log_file" 2>/dev/null || echo 0) / 1024))
|
|
94
|
-
else
|
|
95
|
-
size_kb=$(($(stat -c %s "$log_file" 2>/dev/null || echo 0) / 1024))
|
|
96
|
-
fi
|
|
97
|
-
|
|
98
|
-
if [[ "$size_kb" -gt "$LOG_MAX_SIZE_KB" ]]; then
|
|
99
|
-
# Rotate logs
|
|
100
|
-
for i in $(seq $((LOG_ROTATION_COUNT - 1)) -1 1); do
|
|
101
|
-
[[ -f "${log_file}.$i" ]] && mv "${log_file}.$i" "${log_file}.$((i + 1))" 2>/dev/null
|
|
102
|
-
done
|
|
103
|
-
mv "$log_file" "${log_file}.1" 2>/dev/null || true
|
|
104
|
-
fi
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
# === Core Logging Function ===
|
|
108
|
-
_log() {
|
|
109
|
-
local level="$1"
|
|
110
|
-
local message="$2"
|
|
111
|
-
shift 2
|
|
112
|
-
local extra_fields=("$@")
|
|
113
|
-
|
|
114
|
-
_should_log "$level" || return 0
|
|
115
|
-
|
|
116
|
-
local timestamp
|
|
117
|
-
timestamp=$(get_timestamp_iso)
|
|
118
|
-
|
|
119
|
-
local log_file="$LOG_DIR/${_LOG_HOOK_NAME:-hooks}.log"
|
|
120
|
-
_rotate_log_if_needed "$log_file"
|
|
121
|
-
|
|
122
|
-
if [[ "$LOG_FORMAT" == "json" ]]; then
|
|
123
|
-
# Build JSON log entry
|
|
124
|
-
local json="{\"ts\":\"$timestamp\",\"level\":\"$level\",\"hook\":\"${_LOG_HOOK_NAME:-unknown}\",\"rid\":\"${_LOG_REQUEST_ID:-none}\",\"msg\":\"$message\""
|
|
125
|
-
|
|
126
|
-
# Add extra fields
|
|
127
|
-
for field in "${extra_fields[@]}"; do
|
|
128
|
-
local key="${field%%=*}"
|
|
129
|
-
local value="${field#*=}"
|
|
130
|
-
# Escape quotes in value
|
|
131
|
-
value="${value//\"/\\\"}"
|
|
132
|
-
json="$json,\"$key\":\"$value\""
|
|
133
|
-
done
|
|
134
|
-
|
|
135
|
-
json="$json}"
|
|
136
|
-
echo "$json" >> "$log_file" 2>/dev/null || true
|
|
137
|
-
else
|
|
138
|
-
# Text format
|
|
139
|
-
local prefix="[$timestamp] [$level] [${_LOG_HOOK_NAME:-unknown}] [${_LOG_REQUEST_ID:-none}]"
|
|
140
|
-
echo "$prefix $message ${extra_fields[*]}" >> "$log_file" 2>/dev/null || true
|
|
141
|
-
fi
|
|
142
|
-
|
|
143
|
-
# Also output to stderr if DEBUG level and debug mode enabled
|
|
144
|
-
if [[ "$level" == "DEBUG" ]] && [[ "${HOOK_DEBUG:-0}" == "1" ]]; then
|
|
145
|
-
echo "[HOOK-$level] $_LOG_HOOK_NAME: $message" >&2
|
|
146
|
-
fi
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
# === Public Logging Functions ===
|
|
150
|
-
log_debug() {
|
|
151
|
-
_log "DEBUG" "$1" "${@:2}"
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
log_info() {
|
|
155
|
-
_log "INFO" "$1" "${@:2}"
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
log_warn() {
|
|
159
|
-
_log "WARN" "$1" "${@:2}"
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
log_error() {
|
|
163
|
-
_log "ERROR" "$1" "${@:2}"
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
# === Performance Timing ===
|
|
167
|
-
log_timing_start() {
|
|
168
|
-
local operation="$1"
|
|
169
|
-
_LOG_TIMINGS[$operation]=$(get_timestamp_ms)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
log_timing_end() {
|
|
173
|
-
local operation="$1"
|
|
174
|
-
local start_time="${_LOG_TIMINGS[$operation]:-0}"
|
|
175
|
-
|
|
176
|
-
if [[ "$start_time" -gt 0 ]]; then
|
|
177
|
-
local end_time
|
|
178
|
-
end_time=$(get_timestamp_ms)
|
|
179
|
-
local duration_ms=$((end_time - start_time))
|
|
180
|
-
|
|
181
|
-
log_debug "Timing: $operation completed" "duration_ms=$duration_ms"
|
|
182
|
-
unset "_LOG_TIMINGS[$operation]"
|
|
183
|
-
|
|
184
|
-
echo "$duration_ms"
|
|
185
|
-
else
|
|
186
|
-
echo "0"
|
|
187
|
-
fi
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
# === Request Summary ===
|
|
191
|
-
log_request_summary() {
|
|
192
|
-
local status="$1"
|
|
193
|
-
local details="$2"
|
|
194
|
-
|
|
195
|
-
local total_duration=0
|
|
196
|
-
if [[ -n "$_LOG_START_TIME" ]]; then
|
|
197
|
-
local end_time
|
|
198
|
-
end_time=$(get_timestamp_ms)
|
|
199
|
-
total_duration=$((end_time - _LOG_START_TIME))
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
|
-
log_info "Request completed" "status=$status" "total_ms=$total_duration" "details=$details"
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
# === Aggregate Log Stats ===
|
|
206
|
-
get_log_stats() {
|
|
207
|
-
local hook_name="${1:-$_LOG_HOOK_NAME}"
|
|
208
|
-
local log_file="$LOG_DIR/${hook_name}.log"
|
|
209
|
-
|
|
210
|
-
[[ ! -f "$log_file" ]] && echo '{"total":0,"errors":0,"warns":0}' && return
|
|
211
|
-
|
|
212
|
-
local total errors warns
|
|
213
|
-
total=$(wc -l < "$log_file" 2>/dev/null | tr -d ' ')
|
|
214
|
-
errors=$(grep -c '"level":"ERROR"' "$log_file" 2>/dev/null || echo 0)
|
|
215
|
-
warns=$(grep -c '"level":"WARN"' "$log_file" 2>/dev/null || echo 0)
|
|
216
|
-
|
|
217
|
-
echo "{\"total\":$total,\"errors\":$errors,\"warns\":$warns}"
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
# === Correlation ID Propagation ===
|
|
221
|
-
# Use this to pass request ID to child processes
|
|
222
|
-
export_request_context() {
|
|
223
|
-
export REQUEST_ID="$_LOG_REQUEST_ID"
|
|
224
|
-
export HOOK_NAME="$_LOG_HOOK_NAME"
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
# Import context from parent
|
|
228
|
-
import_request_context() {
|
|
229
|
-
_LOG_REQUEST_ID="${REQUEST_ID:-$(generate_request_id)}"
|
|
230
|
-
_LOG_HOOK_NAME="${HOOK_NAME:-unknown}"
|
|
231
|
-
}
|