specweave 1.0.32 → 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.
Files changed (123) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CLAUDE.md +205 -148
  3. package/README.md +0 -2
  4. package/bin/specweave.js +11 -0
  5. package/dist/src/cli/commands/init.js +1 -1
  6. package/dist/src/cli/commands/init.js.map +1 -1
  7. package/dist/src/cli/commands/update-instructions.d.ts +16 -0
  8. package/dist/src/cli/commands/update-instructions.d.ts.map +1 -0
  9. package/dist/src/cli/commands/update-instructions.js +134 -0
  10. package/dist/src/cli/commands/update-instructions.js.map +1 -0
  11. package/dist/src/cli/helpers/init/directory-structure.d.ts +28 -1
  12. package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
  13. package/dist/src/cli/helpers/init/directory-structure.js +163 -33
  14. package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
  15. package/dist/src/cli/helpers/init/index.d.ts +2 -1
  16. package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
  17. package/dist/src/cli/helpers/init/index.js +3 -1
  18. package/dist/src/cli/helpers/init/index.js.map +1 -1
  19. package/dist/src/cli/helpers/init/instruction-file-merger.d.ts +23 -0
  20. package/dist/src/cli/helpers/init/instruction-file-merger.d.ts.map +1 -0
  21. package/dist/src/cli/helpers/init/instruction-file-merger.js +243 -0
  22. package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -0
  23. package/dist/src/cli/helpers/init/plugin-installer.js +49 -0
  24. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  25. package/dist/src/config/types.d.ts +2 -2
  26. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +26 -0
  27. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -1
  28. package/dist/src/core/living-docs/external-sync-orchestrator.js +61 -0
  29. package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -1
  30. package/dist/src/core/living-docs/scaffolding/index.d.ts +12 -0
  31. package/dist/src/core/living-docs/scaffolding/index.d.ts.map +1 -0
  32. package/dist/src/core/living-docs/scaffolding/index.js +15 -0
  33. package/dist/src/core/living-docs/scaffolding/index.js.map +1 -0
  34. package/dist/src/core/living-docs/scaffolding/merger.d.ts +183 -0
  35. package/dist/src/core/living-docs/scaffolding/merger.d.ts.map +1 -0
  36. package/dist/src/core/living-docs/scaffolding/merger.js +523 -0
  37. package/dist/src/core/living-docs/scaffolding/merger.js.map +1 -0
  38. package/dist/src/core/living-docs/scaffolding/scaffold.d.ts +102 -0
  39. package/dist/src/core/living-docs/scaffolding/scaffold.d.ts.map +1 -0
  40. package/dist/src/core/living-docs/scaffolding/scaffold.js +346 -0
  41. package/dist/src/core/living-docs/scaffolding/scaffold.js.map +1 -0
  42. package/dist/src/core/living-docs/scaffolding/template-engine.d.ts +108 -0
  43. package/dist/src/core/living-docs/scaffolding/template-engine.d.ts.map +1 -0
  44. package/dist/src/core/living-docs/scaffolding/template-engine.js +204 -0
  45. package/dist/src/core/living-docs/scaffolding/template-engine.js.map +1 -0
  46. package/dist/src/core/living-docs/sync-helpers/generators.d.ts +38 -2
  47. package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
  48. package/dist/src/core/living-docs/sync-helpers/generators.js +65 -10
  49. package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
  50. package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
  51. package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
  52. package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
  53. package/dist/src/core/tools/index.d.ts +11 -0
  54. package/dist/src/core/tools/index.d.ts.map +1 -0
  55. package/dist/src/core/tools/index.js +10 -0
  56. package/dist/src/core/tools/index.js.map +1 -0
  57. package/dist/src/core/tools/tool-event-bus.d.ts +33 -0
  58. package/dist/src/core/tools/tool-event-bus.d.ts.map +1 -0
  59. package/dist/src/core/tools/tool-event-bus.js +84 -0
  60. package/dist/src/core/tools/tool-event-bus.js.map +1 -0
  61. package/dist/src/core/tools/tool-index-builder.d.ts +27 -0
  62. package/dist/src/core/tools/tool-index-builder.d.ts.map +1 -0
  63. package/dist/src/core/tools/tool-index-builder.js +289 -0
  64. package/dist/src/core/tools/tool-index-builder.js.map +1 -0
  65. package/dist/src/core/tools/tool-registry.d.ts +51 -0
  66. package/dist/src/core/tools/tool-registry.d.ts.map +1 -0
  67. package/dist/src/core/tools/tool-registry.js +224 -0
  68. package/dist/src/core/tools/tool-registry.js.map +1 -0
  69. package/dist/src/core/tools/tool-search-engine.d.ts +22 -0
  70. package/dist/src/core/tools/tool-search-engine.d.ts.map +1 -0
  71. package/dist/src/core/tools/tool-search-engine.js +174 -0
  72. package/dist/src/core/tools/tool-search-engine.js.map +1 -0
  73. package/dist/src/core/tools/types/tool-registry-types.d.ts +112 -0
  74. package/dist/src/core/tools/types/tool-registry-types.d.ts.map +1 -0
  75. package/dist/src/core/tools/types/tool-registry-types.js +7 -0
  76. package/dist/src/core/tools/types/tool-registry-types.js.map +1 -0
  77. package/dist/src/init/compliance/types.d.ts +1 -1
  78. package/package.json +1 -1
  79. package/plugins/specweave/hooks/hooks.json +3 -13
  80. package/plugins/specweave/hooks/lib/common-setup.sh +47 -321
  81. package/plugins/specweave/hooks/lib/migrate-increment-work.sh +5 -5
  82. package/plugins/specweave/hooks/lib/sync-spec-content.sh +5 -5
  83. package/plugins/specweave/hooks/universal/dispatcher.mjs +4 -5
  84. package/plugins/specweave/hooks/universal/fail-fast-wrapper.sh +43 -296
  85. package/plugins/specweave/hooks/universal/hook-wrapper.sh +3 -1
  86. package/plugins/specweave/hooks/user-prompt-submit.sh +1 -1
  87. package/plugins/specweave/hooks/v2/dispatchers/post-tool-use.sh +2 -2
  88. package/plugins/specweave/hooks/v2/dispatchers/session-start.sh +1 -10
  89. package/plugins/specweave/hooks/v2/guards/completion-guard.sh +12 -29
  90. package/plugins/specweave/hooks/v2/guards/increment-duplicate-guard.sh +27 -29
  91. package/plugins/specweave/hooks/v2/guards/metadata-json-guard.sh +10 -4
  92. package/plugins/specweave/hooks/v2/guards/spec-validation-guard.sh +139 -0
  93. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +4 -2
  94. package/plugins/specweave/hooks/v2/session-end.sh +3 -1
  95. package/plugins/specweave/hooks/v2/session-start.sh +3 -1
  96. package/plugins/specweave/skills/increment-planner/templates/plan.md +14 -0
  97. package/plugins/specweave/skills/update-instructions/SKILL.md +80 -0
  98. package/plugins/specweave-ado/hooks/post-living-docs-update.sh +1 -1
  99. package/plugins/specweave-mobile/README.md +55 -35
  100. package/plugins/specweave-mobile/agents/mobile-architect/AGENT.md +805 -329
  101. package/plugins/specweave-mobile/skills/expo-workflow/SKILL.md +226 -9
  102. package/plugins/specweave-mobile/skills/native-modules/SKILL.md +221 -20
  103. package/plugins/specweave-mobile/skills/performance-optimization/SKILL.md +186 -14
  104. package/plugins/specweave-mobile/skills/react-native-setup/SKILL.md +151 -54
  105. package/plugins/specweave-release/commands/npm.md +61 -17
  106. package/plugins/specweave-release/hooks/post-task-completion.sh +2 -3
  107. package/src/templates/AGENTS.md.template +34 -0
  108. package/src/templates/CLAUDE.md.template +121 -155
  109. package/plugins/specweave/hooks/config-env-separator.sh +0 -99
  110. package/plugins/specweave/hooks/github-metadata-guard.sh +0 -73
  111. package/plugins/specweave/hooks/lib/circuit-breaker.sh +0 -381
  112. package/plugins/specweave/hooks/lib/crash-prevention.sh +0 -336
  113. package/plugins/specweave/hooks/lib/logging.sh +0 -231
  114. package/plugins/specweave/hooks/lib/metrics.sh +0 -347
  115. package/plugins/specweave/hooks/lib/semaphore.sh +0 -216
  116. package/plugins/specweave/hooks/project-folder-guard.sh +0 -274
  117. package/plugins/specweave/hooks/spec-project-validator.sh +0 -210
  118. package/plugins/specweave/hooks/v2/guards/bash-file-guard.sh +0 -212
  119. package/plugins/specweave/hooks/v2/guards/bash-file-guard.test.sh +0 -163
  120. package/plugins/specweave/hooks/v2/guards/features-folder-guard.sh +0 -51
  121. package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +0 -63
  122. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.sh +0 -335
  123. package/plugins/specweave/hooks/v2/guards/per-us-project-validator.test.sh +0 -406
@@ -1,317 +1,64 @@
1
1
  #!/bin/bash
2
- # fail-fast-wrapper.sh - HARD TIMEOUT wrapper for all hooks with proper concurrency control
3
- # If ANY hook takes longer than HOOK_TIMEOUT, it gets KILLED.
2
+ # fail-fast-wrapper.sh - Simple timeout wrapper for hooks
4
3
  #
5
4
  # Usage: bash fail-fast-wrapper.sh <hook-script> [args...]
6
5
  #
7
6
  # Environment:
8
- # HOOK_TIMEOUT - max seconds (default: 5)
9
- # HOOK_DEBUG - set to 1 for verbose logging
10
- # HOOK_MAX_CONCURRENT - max concurrent hooks (default: 15)
11
- # HOOK_ACQUIRE_TIMEOUT_MS - semaphore acquire timeout (default: 3000)
7
+ # HOOK_TIMEOUT - max seconds (default: 5)
12
8
  #
13
- # Exit behavior:
14
- # - Returns hook output on success
15
- # - Returns safe JSON on timeout ({"continue":true} or {"decision":"approve"})
16
- # - NEVER hangs - timeout is enforced with SIGKILL
17
- #
18
- # CONCURRENCY CONTROL (v1.0.30):
19
- # - Semaphore-based concurrency limiting (NOT process storm detection)
20
- # - Proper circuit breaker with CLOSED/OPEN/HALF_OPEN states
21
- # - Structured logging with request tracing
22
- # - Metrics collection for observability
23
- #
24
- # v0.33.0 - Enhanced with crash prevention integration
25
- # v1.0.30 - Complete rewrite with proper concurrency primitives
26
-
27
- set -o pipefail
28
-
29
- # === Configuration ===
30
- HOOK_TIMEOUT="${HOOK_TIMEOUT:-5}" # 5 seconds - more than enough for any hook
31
- HOOK_DEBUG="${HOOK_DEBUG:-0}"
32
- HOOK_MAX_CONCURRENT="${HOOK_MAX_CONCURRENT:-15}" # Max concurrent hooks
33
- HOOK_ACQUIRE_TIMEOUT_MS="${HOOK_ACQUIRE_TIMEOUT_MS:-3000}" # 3 seconds to acquire semaphore
34
- LOG_FILE="${HOME}/.claude/hook-failures.log"
35
-
36
- # === Library paths ===
37
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
38
- LIB_DIR="${SCRIPT_DIR}/../lib"
39
-
40
- # === Source libraries (fail gracefully if missing) ===
41
- SEMAPHORE_LOADED=false
42
- CIRCUIT_BREAKER_LOADED=false
43
- LOGGING_LOADED=false
44
- METRICS_LOADED=false
9
+ # Returns safe JSON on timeout or error. Never hangs.
45
10
 
46
- # Set state dir for all libraries
47
- export SPECWEAVE_STATE_DIR="${SPECWEAVE_STATE_DIR:-.specweave/state}"
48
- export SPECWEAVE_LOG_DIR="${SPECWEAVE_LOG_DIR:-.specweave/logs/hooks}"
11
+ set +e
49
12
 
50
- if [[ -f "$LIB_DIR/semaphore.sh" ]]; then
51
- source "$LIB_DIR/semaphore.sh" 2>/dev/null && SEMAPHORE_LOADED=true
52
- fi
53
-
54
- if [[ -f "$LIB_DIR/circuit-breaker.sh" ]]; then
55
- source "$LIB_DIR/circuit-breaker.sh" 2>/dev/null && CIRCUIT_BREAKER_LOADED=true
56
- fi
57
-
58
- if [[ -f "$LIB_DIR/logging.sh" ]]; then
59
- source "$LIB_DIR/logging.sh" 2>/dev/null && LOGGING_LOADED=true
60
- fi
13
+ HOOK_TIMEOUT="${HOOK_TIMEOUT:-5}"
61
14
 
62
- if [[ -f "$LIB_DIR/metrics.sh" ]]; then
63
- source "$LIB_DIR/metrics.sh" 2>/dev/null && METRICS_LOADED=true
64
- fi
65
-
66
- # Legacy crash prevention (fallback)
67
- CRASH_PREVENTION="${LIB_DIR}/crash-prevention.sh"
68
- if [[ -f "$CRASH_PREVENTION" ]]; then
69
- source "$CRASH_PREVENTION" 2>/dev/null || true
70
- fi
71
-
72
- # === Helper functions ===
73
- log_debug() {
74
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[DEBUG $(date +%H:%M:%S)] $*" >&2
75
- }
76
-
77
- log_failure() {
78
- local msg="$1"
79
- mkdir -p "$(dirname "$LOG_FILE")"
80
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] HOOK FAILURE: $msg" >> "$LOG_FILE"
81
- }
82
-
83
- # === Safe JSON output based on hook type ===
15
+ # Safe output based on hook type
84
16
  get_safe_output() {
85
17
  local script="$1"
86
- # PreToolUse hooks need "decision" format
87
- if [[ "$script" == *"guard"* ]] || [[ "$script" == *"validator"* ]] || [[ "$script" == *"PreToolUse"* ]]; then
18
+ if [[ "$script" == *"guard"* ]] || [[ "$script" == *"validator"* ]]; then
88
19
  echo '{"decision":"allow"}'
89
20
  else
90
21
  echo '{"continue":true}'
91
22
  fi
92
23
  }
93
24
 
94
- # === Get hook name from script path ===
95
- get_hook_name() {
96
- local script="$1"
97
- basename "$script" .sh | tr '/' '-'
98
- }
99
-
100
- # === Read stdin with timeout ===
101
- # Critical: stdin can block forever if not handled properly
102
- read_stdin_with_timeout() {
103
- local stdin_content=""
104
-
105
- # Use read with timeout (integer seconds for bash compatibility)
106
- if read -t 1 -r line; then
107
- stdin_content="$line"
108
- # Continue reading remaining lines (no timeout - stdin should be closed)
109
- while IFS= read -r line; do
110
- stdin_content="${stdin_content}"$'\n'"${line}"
111
- done
112
- fi
113
-
114
- echo "$stdin_content"
115
- }
116
-
117
- # === Main execution ===
118
- main() {
119
- local script="$1"
120
- shift
121
- local args=("$@")
122
-
123
- if [[ -z "$script" ]]; then
124
- echo '{"continue":true}'
125
- exit 0
126
- fi
127
-
128
- if [[ ! -f "$script" ]]; then
129
- log_debug "Script not found: $script"
130
- echo '{"continue":true}'
131
- exit 0
132
- fi
133
-
134
- local hook_name
135
- hook_name=$(get_hook_name "$script")
136
-
137
- # === Initialize libraries ===
138
- if [[ "$LOGGING_LOADED" == "true" ]]; then
139
- log_init "$hook_name"
140
- log_debug "Starting hook execution" "script=$script"
141
- fi
142
-
143
- if [[ "$METRICS_LOADED" == "true" ]]; then
144
- metrics_init "$hook_name"
145
- metrics_start_request
146
- fi
147
-
148
- # === CIRCUIT BREAKER CHECK ===
149
- # If circuit is open for this hook, fail fast
150
- if [[ "$CIRCUIT_BREAKER_LOADED" == "true" ]]; then
151
- if ! cb_allow_request "$hook_name"; then
152
- local cb_status
153
- cb_status=$(cb_get_status "$hook_name")
154
- log_debug "Circuit breaker OPEN for $hook_name: $cb_status"
155
-
156
- if [[ "$LOGGING_LOADED" == "true" ]]; then
157
- log_warn "Circuit breaker open - failing fast" "hook=$hook_name"
158
- fi
159
-
160
- if [[ "$METRICS_LOADED" == "true" ]]; then
161
- metrics_end_request "skipped"
162
- fi
163
-
164
- get_safe_output "$script"
165
- exit 0
166
- fi
167
- fi
168
-
169
- # === SEMAPHORE: Acquire concurrency slot ===
170
- local semaphore_acquired=false
171
- if [[ "$SEMAPHORE_LOADED" == "true" ]]; then
172
- if acquire_semaphore "hooks" "$HOOK_MAX_CONCURRENT" "$HOOK_ACQUIRE_TIMEOUT_MS"; then
173
- semaphore_acquired=true
174
- log_debug "Acquired semaphore slot for $hook_name"
175
- else
176
- # Could not acquire semaphore in time - graceful degradation
177
- log_debug "Semaphore timeout for $hook_name - graceful skip"
178
-
179
- if [[ "$LOGGING_LOADED" == "true" ]]; then
180
- log_warn "Semaphore acquisition timeout - graceful degradation" "hook=$hook_name" "max_concurrent=$HOOK_MAX_CONCURRENT"
181
- fi
182
-
183
- if [[ "$METRICS_LOADED" == "true" ]]; then
184
- metrics_end_request "skipped"
185
- fi
186
-
187
- # DON'T record as failure - this is graceful degradation, not an error
188
- get_safe_output "$script"
189
- exit 0
190
- fi
191
- fi
192
-
193
- # Ensure semaphore is released on exit
194
- trap 'release_semaphore 2>/dev/null || true' EXIT INT TERM
195
-
196
- log_debug "Executing: $script (timeout: ${HOOK_TIMEOUT}s)"
197
-
198
- # Read stdin first (with its own timeout)
199
- local stdin_content
200
- stdin_content=$(read_stdin_with_timeout)
201
-
202
- # Execute the hook with hard timeout
203
- local output
204
- local exit_code
205
- local tmp_out
206
- tmp_out=$(mktemp)
207
-
208
- # Run with timeout - kill entire process group on timeout
209
- if command -v gtimeout >/dev/null 2>&1; then
210
- # macOS with coreutils
211
- echo "$stdin_content" | gtimeout --kill-after=2 "$HOOK_TIMEOUT" bash "$script" "${args[@]}" > "$tmp_out" 2>/dev/null
212
- exit_code=$?
213
- elif command -v timeout >/dev/null 2>&1; then
214
- # Linux
215
- echo "$stdin_content" | timeout --kill-after=2 "$HOOK_TIMEOUT" bash "$script" "${args[@]}" > "$tmp_out" 2>/dev/null
216
- exit_code=$?
217
- else
218
- # Fallback: manual timeout using background process
219
- (
220
- echo "$stdin_content" | bash "$script" "${args[@]}" > "$tmp_out" 2>/dev/null
221
- ) &
222
- local pid=$!
223
-
224
- # Wait with timeout
225
- local count=0
226
- while kill -0 "$pid" 2>/dev/null && [[ $count -lt $((HOOK_TIMEOUT * 10)) ]]; do
227
- sleep 0.1
228
- count=$((count + 1))
229
- done
230
-
231
- if kill -0 "$pid" 2>/dev/null; then
232
- # Still running - kill it!
233
- kill -9 "$pid" 2>/dev/null
234
- wait "$pid" 2>/dev/null
235
- exit_code=124 # timeout exit code
236
- else
237
- wait "$pid"
238
- exit_code=$?
239
- fi
240
- fi
241
-
242
- output=$(cat "$tmp_out" 2>/dev/null)
243
- rm -f "$tmp_out"
244
-
245
- # Release semaphore immediately after execution
246
- if [[ "$semaphore_acquired" == "true" ]]; then
247
- release_semaphore
248
- fi
249
-
250
- # Handle timeout (exit code 124 or 137)
251
- if [[ $exit_code -eq 124 ]] || [[ $exit_code -eq 137 ]]; then
252
- log_failure "$script - killed after ${HOOK_TIMEOUT}s"
253
- log_debug "TIMEOUT: $script killed after ${HOOK_TIMEOUT}s"
254
-
255
- if [[ "$LOGGING_LOADED" == "true" ]]; then
256
- log_error "Hook timeout - killed after ${HOOK_TIMEOUT}s" "hook=$hook_name"
257
- fi
258
-
259
- if [[ "$METRICS_LOADED" == "true" ]]; then
260
- metrics_end_request "timeout"
261
- fi
262
-
263
- # Record failure for circuit breaker
264
- if [[ "$CIRCUIT_BREAKER_LOADED" == "true" ]]; then
265
- cb_record_failure "$hook_name"
266
- fi
267
-
268
- # Clean up potential zombie processes (legacy)
269
- if type kill_zombie_heredocs &>/dev/null; then
270
- kill_zombie_heredocs 2>/dev/null || true
271
- fi
272
-
273
- get_safe_output "$script"
274
- exit 0
275
- fi
276
-
277
- # Handle hook errors (non-zero exit, excluding block exit code 2)
278
- if [[ $exit_code -ne 0 ]] && [[ $exit_code -ne 2 ]]; then
279
- log_debug "Hook error: $script exited with $exit_code"
280
-
281
- if [[ "$LOGGING_LOADED" == "true" ]]; then
282
- log_warn "Hook exited with error" "hook=$hook_name" "exit_code=$exit_code"
283
- fi
284
-
285
- if [[ "$METRICS_LOADED" == "true" ]]; then
286
- metrics_end_request "failure"
287
- fi
288
-
289
- if [[ "$CIRCUIT_BREAKER_LOADED" == "true" ]]; then
290
- cb_record_failure "$hook_name"
291
- fi
292
- else
293
- # Success!
294
- if [[ "$METRICS_LOADED" == "true" ]]; then
295
- metrics_end_request "success"
296
- fi
297
-
298
- if [[ "$CIRCUIT_BREAKER_LOADED" == "true" ]]; then
299
- cb_record_success "$hook_name"
300
- fi
301
-
302
- if [[ "$LOGGING_LOADED" == "true" ]]; then
303
- log_debug "Hook completed successfully" "hook=$hook_name"
304
- fi
305
- fi
25
+ script="$1"
26
+ shift
27
+
28
+ # No script or doesn't exist = safe default
29
+ [[ -z "$script" || ! -f "$script" ]] && echo '{"continue":true}' && exit 0
30
+
31
+ # Read stdin
32
+ stdin_content=$(cat 2>/dev/null || echo '{}')
33
+
34
+ # Run with timeout
35
+ tmp_out=$(mktemp)
36
+ if command -v gtimeout >/dev/null 2>&1; then
37
+ echo "$stdin_content" | gtimeout --kill-after=2 "$HOOK_TIMEOUT" bash "$script" "$@" > "$tmp_out" 2>/dev/null
38
+ exit_code=$?
39
+ elif command -v timeout >/dev/null 2>&1; then
40
+ echo "$stdin_content" | timeout --kill-after=2 "$HOOK_TIMEOUT" bash "$script" "$@" > "$tmp_out" 2>/dev/null
41
+ exit_code=$?
42
+ else
43
+ # Fallback: run without timeout (rare - most systems have timeout)
44
+ echo "$stdin_content" | bash "$script" "$@" > "$tmp_out" 2>/dev/null
45
+ exit_code=$?
46
+ fi
306
47
 
307
- # Return output or safe default
308
- if [[ -n "$output" ]]; then
309
- echo "$output"
310
- else
311
- get_safe_output "$script"
312
- fi
48
+ output=$(cat "$tmp_out" 2>/dev/null)
49
+ rm -f "$tmp_out"
313
50
 
51
+ # Timeout = safe default
52
+ if [[ $exit_code -eq 124 ]] || [[ $exit_code -eq 137 ]]; then
53
+ get_safe_output "$script"
314
54
  exit 0
315
- }
55
+ fi
56
+
57
+ # Return output or safe default
58
+ if [[ -n "$output" ]]; then
59
+ echo "$output"
60
+ else
61
+ get_safe_output "$script"
62
+ fi
316
63
 
317
- main "$@"
64
+ exit 0
@@ -8,8 +8,10 @@
8
8
  #
9
9
  # Usage: bash hook-wrapper.sh <hook-type>
10
10
  # Where hook-type is: session-start, post-tool-use, completion-guard
11
+ #
12
+ # v0.35.3 - Fixed: use set +e for hook safety
11
13
 
12
- set -e
14
+ set +e # CRITICAL: Never use set -e in hooks (causes cascading failures)
13
15
 
14
16
  HOOK_TYPE="${1:-unknown}"
15
17
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -18,7 +18,7 @@ set +e
18
18
  # ==============================================================================
19
19
  # ULTRA-FAST EARLY EXIT (before ANY processing)
20
20
  # ==============================================================================
21
- INPUT=$(cat)
21
+ INPUT=$(cat 2>/dev/null || echo '{}')
22
22
 
23
23
  # Use jq if available (10x faster than node), fallback to simple grep
24
24
  if command -v jq >/dev/null 2>&1; then
@@ -26,8 +26,8 @@ while [[ "$PROJECT_ROOT" != "/" ]] && [[ ! -d "$PROJECT_ROOT/.specweave" ]]; do
26
26
  done
27
27
  [[ ! -d "$PROJECT_ROOT/.specweave" ]] && exit 0
28
28
 
29
- # Read stdin (tool result JSON)
30
- INPUT=$(cat)
29
+ # Read stdin (tool result JSON) - with safe fallback to prevent hanging
30
+ INPUT=$(cat 2>/dev/null || echo '{}')
31
31
 
32
32
  # Extract file path (fast grep, no jq)
33
33
  FILE_PATH=$(echo "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
@@ -15,17 +15,8 @@ done
15
15
  # Consume stdin
16
16
  cat > /dev/null
17
17
 
18
- # === CRITICAL: Guard Verification ===
19
- # Check that essential guards are available to prevent session hangs
18
+ # Hook directory for finding other scripts
20
19
  HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
- GUARDS_DIR="$HOOK_DIR/../guards"
22
- BASH_FILE_GUARD="$GUARDS_DIR/bash-file-guard.sh"
23
-
24
- if [[ ! -f "$BASH_FILE_GUARD" ]] || [[ ! -x "$BASH_FILE_GUARD" ]]; then
25
- # Output warning as system message
26
- echo '{"continue":true,"systemMessage":"⚠️ CRITICAL: bash-file-guard.sh not found or not executable! Session hang protection DISABLED. Run: bash scripts/refresh-marketplace.sh"}'
27
- # Continue session but warn about missing protection
28
- fi
29
20
 
30
21
  # === MCP Connection Health Check ===
31
22
  # Check for recent MCP drops that could cause session hangs
@@ -7,12 +7,13 @@
7
7
  # PreToolUse hook - can BLOCK the tool call by returning non-zero exit code
8
8
  #
9
9
  # IMPORTANT: This is a safety guard. Exit 0 allows, exit 2 blocks.
10
+ # All exit paths MUST output JSON for proper Claude Code handling.
10
11
  set +e
11
12
 
12
- [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
13
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && echo '{"decision":"allow"}' && exit 0
13
14
 
14
15
  # Read stdin for tool input
15
- INPUT=$(cat)
16
+ INPUT=$(cat 2>/dev/null || echo '{}')
16
17
 
17
18
  # Check if this is editing metadata.json with status: completed
18
19
  # Pattern: file_path contains metadata.json AND (new_string OR content) contains "status"..."completed"
@@ -27,6 +28,7 @@ fi
27
28
 
28
29
  # Only care about metadata.json files
29
30
  if [[ "$FILE_PATH" != *metadata.json ]]; then
31
+ echo '{"decision":"allow"}'
30
32
  exit 0 # Allow
31
33
  fi
32
34
 
@@ -50,40 +52,21 @@ if echo "$NEW_CONTENT" | grep -q '"status"[[:space:]]*:[[:space:]]*"completed"';
50
52
 
51
53
  if [[ "$CURRENT_STATUS" == "ready_for_review" ]]; then
52
54
  # This is a valid transition - allow
55
+ echo '{"decision":"allow"}'
53
56
  exit 0
54
57
  fi
55
58
  fi
56
59
 
57
60
  # BLOCK - trying to set completed without going through ready_for_review
58
- echo ""
59
- echo "=============================================================================="
60
- echo " BLOCKED: Direct status change to \"completed\" is not allowed (v0.28.63+)"
61
- echo "=============================================================================="
62
- echo ""
63
- echo "You cannot directly set status to \"completed\" in metadata.json."
64
- echo ""
65
- echo "This prevents the auto-completion bug where increments get marked as"
66
- echo "completed without proper validation."
67
- echo ""
68
- echo "CORRECT WORKFLOW:"
69
- echo "1. All tasks completed -> status auto-transitions to \"ready_for_review\""
70
- echo "2. Run /sw:done <increment-id> with explicit user confirmation"
71
- echo "3. Only then does status become \"completed\""
72
- echo ""
73
- echo "WHY THIS MATTERS:"
74
- echo "- Ensures all ACs are checked in spec.md before closure"
75
- echo "- Requires explicit user approval"
76
- echo "- Maintains audit trail (approvedAt timestamp)"
77
- echo ""
78
- echo "If you're implementing closure logic, use:"
79
- echo " MetadataManager.updateStatus(incrementId, IncrementStatus.COMPLETED)"
80
- echo ""
81
- echo "This will only succeed if current status is \"ready_for_review\"."
82
- echo ""
83
- echo "=============================================================================="
84
-
61
+ cat << 'BLOCK_EOF'
62
+ {
63
+ "decision": "block",
64
+ "reason": "🚫 BLOCKED: Direct status change to \"completed\" is not allowed (v0.28.63+)\n\nYou cannot directly set status to \"completed\" in metadata.json.\n\nCORRECT WORKFLOW:\n1. All tasks completed → status auto-transitions to \"ready_for_review\"\n2. Run /sw:done <increment-id> with explicit user confirmation\n3. Only then does status become \"completed\"\n\nWHY THIS MATTERS:\n• Ensures all ACs are checked in spec.md before closure\n• Requires explicit user approval\n• Maintains audit trail (approvedAt timestamp)\n\n🔧 If implementing closure logic, use:\n MetadataManager.updateStatus(incrementId, IncrementStatus.COMPLETED)"
65
+ }
66
+ BLOCK_EOF
85
67
  exit 2 # Block the tool call
86
68
  fi
87
69
 
88
70
  # Allow other edits to metadata.json
71
+ echo '{"decision":"allow"}'
89
72
  exit 0
@@ -10,13 +10,13 @@
10
10
  # - 0121-ado-jira-feature-parity-p2-p3
11
11
  # - 0121-intelligent-living-docs-content
12
12
  #
13
- # Exit 0 = allow, Exit 2 = block
13
+ # Exit 0 = allow (with JSON), Exit 2 = block (with JSON)
14
14
  set +e
15
15
 
16
- [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && exit 0
16
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]] && echo '{"decision":"allow"}' && exit 0
17
17
 
18
18
  # Read stdin for tool input
19
- INPUT=$(cat)
19
+ INPUT=$(cat 2>/dev/null || echo '{}')
20
20
 
21
21
  # Extract file_path from the tool call
22
22
  # Claude Code passes tool input in .tool_input.file_path format
@@ -28,6 +28,7 @@ fi
28
28
 
29
29
  # Only care about .specweave/increments/ paths
30
30
  if [[ "$FILE_PATH" != *.specweave/increments/* ]]; then
31
+ echo '{"decision":"allow"}'
31
32
  exit 0 # Not an increment file - allow
32
33
  fi
33
34
 
@@ -43,6 +44,7 @@ INCREMENT_FOLDER=$(echo "$AFTER_INCREMENTS" | cut -d'/' -f1)
43
44
 
44
45
  # Skip special folders
45
46
  if [[ "$INCREMENT_FOLDER" == "_archive" ]] || [[ "$INCREMENT_FOLDER" == "_abandoned" ]] || [[ "$INCREMENT_FOLDER" == "_paused" ]] || [[ "$INCREMENT_FOLDER" == "README.md" ]]; then
47
+ echo '{"decision":"allow"}'
46
48
  exit 0
47
49
  fi
48
50
 
@@ -50,6 +52,7 @@ fi
50
52
  INCREMENT_NUM=$(echo "$INCREMENT_FOLDER" | grep -oE '^[0-9]{3,4}' | head -1)
51
53
 
52
54
  if [[ -z "$INCREMENT_NUM" ]]; then
55
+ echo '{"decision":"allow"}'
53
56
  exit 0 # Not a standard increment folder pattern - allow
54
57
  fi
55
58
 
@@ -60,7 +63,9 @@ INCREMENT_NUM=$(printf "%04d" "$((10#$INCREMENT_NUM))")
60
63
  INCREMENTS_DIR=$(echo "$FILE_PATH" | grep -o '.*/\.specweave/increments' | head -1)
61
64
 
62
65
  if [[ ! -d "$INCREMENTS_DIR" ]]; then
63
- exit 0 # Increments directory doesn't exist yet - allow (first increment)
66
+ # Increments directory doesn't exist yet - first increment, allow creation
67
+ echo '{"decision":"allow"}'
68
+ exit 0
64
69
  fi
65
70
 
66
71
  # Scan ALL directories for existing increment with the same number
@@ -104,37 +109,30 @@ for DIR in "${DIRS_TO_CHECK[@]}"; do
104
109
  done < <(find "$DIR" -maxdepth 1 -type d -name "${INCREMENT_NUM}*" -print0 2>/dev/null)
105
110
  done
106
111
 
107
- # If duplicates found, BLOCK the operation
112
+ # If duplicates found, BLOCK the operation and provide the correct number to use
108
113
  if [[ ${#FOUND_DUPLICATES[@]} -gt 0 ]]; then
109
- echo ""
110
- echo "=============================================================================="
111
- echo " BLOCKED: Duplicate increment ID detected (v0.33.0+)"
112
- echo "=============================================================================="
113
- echo ""
114
- echo "You are trying to create increment: $INCREMENT_FOLDER"
115
- echo "But increment number $INCREMENT_NUM already exists:"
116
- echo ""
114
+ # Format duplicates for JSON
115
+ DUP_LIST=""
117
116
  for DUP in "${FOUND_DUPLICATES[@]}"; do
118
- echo " - $DUP"
117
+ DUP_LIST="${DUP_LIST}\\n - ${DUP}"
118
+ done
119
+
120
+ # Calculate the next available number using gap-filling strategy
121
+ EXISTING_NUMS=$(find "$INCREMENTS_DIR" "$INCREMENTS_DIR/_archive" "$INCREMENTS_DIR/_abandoned" "$INCREMENTS_DIR/_paused" -maxdepth 1 -type d -name "[0-9]*-*" 2>/dev/null | xargs -I {} basename {} | grep -oE '^[0-9]{3,4}' | sort -n | uniq)
122
+ NEXT_NUM=1
123
+ while echo "$EXISTING_NUMS" | grep -q "^$(printf "%04d" $NEXT_NUM)$\|^$(printf "%03d" $NEXT_NUM)$"; do
124
+ NEXT_NUM=$((NEXT_NUM + 1))
119
125
  done
120
- echo ""
121
- echo "IMPORTANT: Increment IDs MUST be unique across all directories:"
122
- echo " - Active increments"
123
- echo " - Archived increments (_archive/)"
124
- echo " - Abandoned increments (_abandoned/)"
125
- echo " - Paused increments (_paused/)"
126
- echo ""
127
- echo "NOTE: 0001 and 0001E share the SAME base number and cannot coexist!"
128
- echo ""
129
- echo "TO FIX:"
130
- echo "1. Use a different increment number"
131
- echo "2. Get the next available number:"
132
- echo " node -e \"import('./dist/core/increment/increment-utils.js').then(m => console.log(m.IncrementNumberManager.getNextIncrementNumber()))\""
133
- echo ""
134
- echo "=============================================================================="
126
+ NEXT_NUM_PADDED=$(printf "%04d" $NEXT_NUM)
127
+
128
+ # Extract the name part from the attempted folder
129
+ FOLDER_NAME_PART=$(echo "$INCREMENT_FOLDER" | sed 's/^[0-9]\{3,4\}E\?-//')
130
+ SUGGESTED_FOLDER="${NEXT_NUM_PADDED}-${FOLDER_NAME_PART}"
135
131
 
132
+ printf '{"decision":"block","reason":"🚫 DUPLICATE INCREMENT ID - Use this instead:\\n\\n✅ CORRECT: %s\\n❌ ATTEMPTED: %s\\n\\nNumber %s already exists:%s\\n\\n🔧 IMMEDIATE FIX: Replace your file path with:\\n .specweave/increments/%s/...\\n\\nThe guard calculated the next available number for you."}\n' "$SUGGESTED_FOLDER" "$INCREMENT_FOLDER" "$INCREMENT_NUM" "$DUP_LIST" "$SUGGESTED_FOLDER"
136
133
  exit 2 # Block the tool call
137
134
  fi
138
135
 
139
136
  # No duplicates - allow
137
+ echo '{"decision":"allow"}'
140
138
  exit 0
@@ -25,7 +25,7 @@
25
25
  #
26
26
  # v0.34.0 - Initial implementation based on user project bug analysis
27
27
 
28
- set -e
28
+ set +e # CRITICAL: Never use set -e in hooks (causes cascading failures)
29
29
 
30
30
  # Check for force bypass
31
31
  if [ "$SPECWEAVE_FORCE_METADATA" = "1" ]; then
@@ -39,10 +39,16 @@ if [ "$SPECWEAVE_DISABLE_HOOKS" = "1" ]; then
39
39
  exit 0
40
40
  fi
41
41
 
42
- # Read tool input from stdin
43
- INPUT=$(cat)
42
+ # Read tool input from stdin (safe handling)
43
+ INPUT=$(cat 2>/dev/null || echo '{}')
44
44
 
45
- # Extract tool name
45
+ # Check jq availability - allow if not present
46
+ if ! command -v jq >/dev/null 2>&1; then
47
+ echo '{"decision": "allow"}'
48
+ exit 0
49
+ fi
50
+
51
+ # Extract tool name - with jq fallback
46
52
  TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // .tool_input.tool_name // ""' 2>/dev/null || echo "")
47
53
 
48
54
  # Only validate Write tool calls