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.
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,33 +1,17 @@
1
1
  #!/bin/bash
2
- # common-setup.sh - Unified Hook Library for Crash Prevention
2
+ # common-setup.sh - Minimal Hook Library
3
3
  #
4
- # This library consolidates ALL common patterns from 19+ hooks into
5
- # a single source of truth. Extracted from ADRs: 0060, 0068, 0073,
6
- # 0128, 0130, 0157, 0189.
4
+ # Only essential utilities for hooks. Crash prevention is handled by:
5
+ # - Claude Code's native tool timeouts
6
+ # - fail-fast-wrapper.sh (5s timeout for hook scripts)
7
7
  #
8
8
  # USAGE:
9
9
  # source "${CLAUDE_PLUGIN_ROOT}/hooks/lib/common-setup.sh"
10
- # setup_hook_environment || exit 0
11
- #
12
- # v0.33.0 - Consolidated from 7 ADRs (2,500+ lines → 200 lines)
13
-
14
- set +e # NEVER use set -e in hooks (ADR-0157 Rule 2)
15
-
16
- # ============================================================================
17
- # CONFIGURATION
18
- # ============================================================================
19
-
20
- export HOOK_VERSION="0.33.0"
21
- export HOOK_TIMEOUT="${HOOK_TIMEOUT:-5}"
22
- export HOOK_DEBUG="${HOOK_DEBUG:-0}"
23
10
 
24
- # State directories (lazy-created)
25
- _STATE_DIR=""
26
- _CACHE_DIR=""
27
- _LOG_DIR=""
11
+ set +e # NEVER use set -e in hooks
28
12
 
29
13
  # ============================================================================
30
- # 1. PROJECT ROOT DETECTION (ADR-0068, ADR-0128)
14
+ # PROJECT ROOT DETECTION
31
15
  # ============================================================================
32
16
 
33
17
  find_project_root() {
@@ -43,333 +27,75 @@ find_project_root() {
43
27
  }
44
28
 
45
29
  # ============================================================================
46
- # 2. EMERGENCY KILL SWITCH (ADR-0068 Layer 1, ADR-0157 Rule 4)
30
+ # KILL SWITCH
47
31
  # ============================================================================
48
32
 
49
33
  check_kill_switch() {
50
- if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
51
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Kill switch active" >&2
52
- return 1
53
- fi
54
- return 0
34
+ [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]
55
35
  }
56
36
 
57
37
  # ============================================================================
58
- # 3. CIRCUIT BREAKER (ADR-0068 Layer 2)
38
+ # PRE-TOOL-USE GUARD HELPERS
59
39
  # ============================================================================
60
40
 
61
- # Circuit breaker state (3 failures = trip)
62
- _CIRCUIT_BREAKER_FILE=""
63
- _CIRCUIT_BREAKER_THRESHOLD=3
64
-
65
- init_circuit_breaker() {
66
- local project_root="$1"
67
- _CIRCUIT_BREAKER_FILE="${project_root}/.specweave/state/.hook-circuit-breaker"
68
- mkdir -p "$(dirname "$_CIRCUIT_BREAKER_FILE")" 2>/dev/null
69
- }
41
+ # Global variables set by init_pretool_guard
42
+ HOOK_INPUT=""
43
+ HOOK_TOOL_NAME=""
44
+ HOOK_FILE_PATH=""
45
+ HOOK_CONTENT=""
70
46
 
71
- check_circuit_breaker() {
72
- [[ -z "$_CIRCUIT_BREAKER_FILE" ]] && return 0
73
-
74
- if [[ -f "$_CIRCUIT_BREAKER_FILE" ]]; then
75
- local failures
76
- failures=$(cat "$_CIRCUIT_BREAKER_FILE" 2>/dev/null || echo "0")
77
- if [[ "$failures" -ge "$_CIRCUIT_BREAKER_THRESHOLD" ]]; then
78
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Circuit breaker OPEN ($failures failures)" >&2
79
- return 1
80
- fi
47
+ # Initialize PreToolUse guard environment
48
+ # Returns 1 if hook should exit early (kill switch, no jq, etc.)
49
+ init_pretool_guard() {
50
+ if [[ "${SPECWEAVE_DISABLE_HOOKS:-0}" == "1" ]]; then
51
+ echo '{"decision":"allow"}'
52
+ return 1
81
53
  fi
82
- return 0
83
- }
84
-
85
- record_circuit_breaker_failure() {
86
- [[ -z "$_CIRCUIT_BREAKER_FILE" ]] && return
87
-
88
- local failures
89
- failures=$(cat "$_CIRCUIT_BREAKER_FILE" 2>/dev/null || echo "0")
90
- echo "$((failures + 1))" > "$_CIRCUIT_BREAKER_FILE"
91
- }
92
-
93
- reset_circuit_breaker() {
94
- [[ -n "$_CIRCUIT_BREAKER_FILE" ]] && rm -f "$_CIRCUIT_BREAKER_FILE"
95
- }
96
-
97
- # ============================================================================
98
- # 4. FILE-BASED RECURSION GUARD (ADR-0073 - replaces failed env var approach)
99
- # ============================================================================
100
-
101
- _RECURSION_GUARD_FILE=""
102
-
103
- init_recursion_guard() {
104
- local project_root="$1"
105
- _RECURSION_GUARD_FILE="${project_root}/.specweave/state/.hook-recursion-guard"
106
- }
107
-
108
- check_recursion_guard() {
109
- [[ -z "$_RECURSION_GUARD_FILE" ]] && return 0
110
54
 
111
- if [[ -f "$_RECURSION_GUARD_FILE" ]]; then
112
- # Check if guard is stale (>30s old)
113
- local guard_age
114
- if [[ "$(uname)" == "Darwin" ]]; then
115
- guard_age=$(( $(date +%s) - $(stat -f %m "$_RECURSION_GUARD_FILE" 2>/dev/null || echo "0") ))
116
- else
117
- guard_age=$(( $(date +%s) - $(stat -c %Y "$_RECURSION_GUARD_FILE" 2>/dev/null || echo "0") ))
118
- fi
55
+ HOOK_INPUT=$(cat 2>/dev/null || echo '{}')
119
56
 
120
- if [[ "$guard_age" -lt 30 ]]; then
121
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Recursion guard active (${guard_age}s old)" >&2
122
- return 1
123
- else
124
- # Stale guard - clean it up
125
- rm -f "$_RECURSION_GUARD_FILE"
126
- fi
57
+ if ! command -v jq >/dev/null 2>&1; then
58
+ echo '{"decision":"allow"}'
59
+ return 1
127
60
  fi
128
- return 0
129
- }
130
61
 
131
- acquire_recursion_guard() {
132
- [[ -z "$_RECURSION_GUARD_FILE" ]] && return 1
62
+ HOOK_TOOL_NAME=$(echo "$HOOK_INPUT" | jq -r '.tool_name // ""' 2>/dev/null || echo "")
63
+ HOOK_FILE_PATH=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // .file_path // ""' 2>/dev/null || echo "")
64
+ HOOK_CONTENT=$(echo "$HOOK_INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""' 2>/dev/null || echo "")
133
65
 
134
- touch "$_RECURSION_GUARD_FILE"
135
- # Auto-cleanup on exit
136
- trap 'rm -f "$_RECURSION_GUARD_FILE" 2>/dev/null' EXIT
137
66
  return 0
138
67
  }
139
68
 
140
- release_recursion_guard() {
141
- [[ -n "$_RECURSION_GUARD_FILE" ]] && rm -f "$_RECURSION_GUARD_FILE" 2>/dev/null
142
- }
143
-
144
- # ============================================================================
145
- # 5. LOCK FILE ACQUISITION (ADR-0068 Layer 3)
146
- # ============================================================================
147
-
148
- _LOCK_FILE=""
149
- _LOCK_TIMEOUT=5
150
-
151
- acquire_hook_lock() {
152
- local lock_name="${1:-default}"
153
- local project_root="${2:-$(find_project_root)}"
154
-
155
- _LOCK_FILE="${project_root}/.specweave/state/.hook-${lock_name}.lock"
156
- mkdir -p "$(dirname "$_LOCK_FILE")" 2>/dev/null
157
-
158
- local attempts=0
159
- while [[ $attempts -lt $((_LOCK_TIMEOUT * 5)) ]]; do
160
- if mkdir "$_LOCK_FILE" 2>/dev/null; then
161
- trap 'rmdir "$_LOCK_FILE" 2>/dev/null || true' EXIT
162
- return 0
163
- fi
164
-
165
- # Check for stale lock (>60s old)
166
- if [[ -d "$_LOCK_FILE" ]]; then
167
- local lock_age
168
- if [[ "$(uname)" == "Darwin" ]]; then
169
- lock_age=$(( $(date +%s) - $(stat -f %m "$_LOCK_FILE" 2>/dev/null || echo "0") ))
170
- else
171
- lock_age=$(( $(date +%s) - $(stat -c %Y "$_LOCK_FILE" 2>/dev/null || echo "0") ))
172
- fi
173
-
174
- if [[ "$lock_age" -gt 60 ]]; then
175
- rmdir "$_LOCK_FILE" 2>/dev/null
176
- continue
177
- fi
178
- fi
179
-
180
- sleep 0.2
181
- attempts=$((attempts + 1))
182
- done
183
-
184
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Lock acquisition timeout: $lock_name" >&2
185
- return 1
186
- }
187
-
188
- release_hook_lock() {
189
- [[ -n "$_LOCK_FILE" ]] && rmdir "$_LOCK_FILE" 2>/dev/null
190
- _LOCK_FILE=""
191
- }
192
-
193
- # ============================================================================
194
- # 6. DEBOUNCING (ADR-0060 Tier 1, ADR-0130)
195
- # ============================================================================
196
-
197
- _DEBOUNCE_FILE=""
198
- _DEBOUNCE_WINDOW=5 # seconds
199
-
200
- check_debounce() {
201
- local debounce_key="${1:-default}"
202
- local project_root="${2:-$(find_project_root)}"
203
-
204
- _DEBOUNCE_FILE="${project_root}/.specweave/state/.last-hook-${debounce_key}"
205
-
206
- if [[ -f "$_DEBOUNCE_FILE" ]]; then
207
- local last_run
208
- last_run=$(cat "$_DEBOUNCE_FILE" 2>/dev/null || echo "0")
209
- local now
210
- now=$(date +%s)
211
-
212
- if [[ $((now - last_run)) -lt $_DEBOUNCE_WINDOW ]]; then
213
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Debounced: $debounce_key" >&2
214
- return 1
215
- fi
69
+ # Allow the tool call with optional message
70
+ guard_allow() {
71
+ local message="${1:-}"
72
+ if [[ -n "$message" ]]; then
73
+ printf '{"decision":"allow","message":"%s"}\n' "$message"
74
+ else
75
+ echo '{"decision":"allow"}'
216
76
  fi
217
- return 0
218
- }
219
-
220
- record_debounce() {
221
- [[ -n "$_DEBOUNCE_FILE" ]] && date +%s > "$_DEBOUNCE_FILE"
77
+ exit 0
222
78
  }
223
79
 
224
- # ============================================================================
225
- # 7. BULK OPERATION DETECTION (ADR-0130)
226
- # ============================================================================
227
-
228
- _BULK_COUNTER_FILE=""
229
- _BULK_THRESHOLD=5 # 5+ ops in 10s = bulk mode
230
- _BULK_WINDOW=10
231
-
232
- is_bulk_operation() {
233
- local project_root="${1:-$(find_project_root)}"
234
-
235
- _BULK_COUNTER_FILE="${project_root}/.specweave/state/.bulk-op-counter"
236
- mkdir -p "$(dirname "$_BULK_COUNTER_FILE")" 2>/dev/null
237
-
238
- local now
239
- now=$(date +%s)
240
-
241
- # Read current count and timestamp
242
- local count=0
243
- local start_time=$now
244
-
245
- if [[ -f "$_BULK_COUNTER_FILE" ]]; then
246
- read -r count start_time < "$_BULK_COUNTER_FILE" 2>/dev/null || true
247
-
248
- # Reset if window expired
249
- if [[ $((now - start_time)) -gt $_BULK_WINDOW ]]; then
250
- count=0
251
- start_time=$now
252
- fi
253
- fi
254
-
255
- # Increment and save
256
- count=$((count + 1))
257
- echo "$count $start_time" > "$_BULK_COUNTER_FILE"
258
-
259
- if [[ $count -ge $_BULK_THRESHOLD ]]; then
260
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Bulk operation detected ($count ops in window)" >&2
261
- return 0 # IS bulk operation
262
- fi
263
- return 1 # NOT bulk operation
80
+ # Block the tool call with reason
81
+ guard_block() {
82
+ local reason="$1"
83
+ reason=$(echo "$reason" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')
84
+ printf '{"decision":"block","reason":"%s"}\n' "$reason"
85
+ exit 2
264
86
  }
265
87
 
266
88
  # ============================================================================
267
- # 8. LOG ROTATION (ADR-0060)
89
+ # SIMPLE LOGGING (optional, writes to .specweave/logs/hooks.log)
268
90
  # ============================================================================
269
91
 
270
- _LOG_FILE=""
271
- _LOG_MAX_SIZE=1048576 # 1MB
272
-
273
- init_log() {
274
- local log_name="${1:-hooks}"
275
- local project_root="${2:-$(find_project_root)}"
276
-
277
- _LOG_DIR="${project_root}/.specweave/logs"
278
- mkdir -p "$_LOG_DIR" 2>/dev/null
279
- _LOG_FILE="${_LOG_DIR}/${log_name}.log"
280
-
281
- # Rotate if too large
282
- if [[ -f "$_LOG_FILE" ]]; then
283
- local size
284
- size=$(stat -f%z "$_LOG_FILE" 2>/dev/null || stat -c%s "$_LOG_FILE" 2>/dev/null || echo "0")
285
- if [[ "$size" -gt "$_LOG_MAX_SIZE" ]]; then
286
- mv "$_LOG_FILE" "${_LOG_FILE}.1" 2>/dev/null
287
- fi
288
- fi
289
- }
290
-
291
92
  log_hook() {
292
93
  local level="$1"
293
94
  shift
294
95
  local msg="$*"
295
-
296
- [[ -z "$_LOG_FILE" ]] && return
297
- echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [$level] $msg" >> "$_LOG_FILE"
298
- }
299
-
300
- # ============================================================================
301
- # 9. UNIFIED SETUP (ALL CHECKS IN ONE CALL)
302
- # ============================================================================
303
-
304
- setup_hook_environment() {
305
- local hook_name="${1:-unnamed}"
306
- local require_lock="${2:-false}"
307
- local enable_recursion_guard="${3:-false}"
308
-
309
- # Find project root
310
- PROJECT_ROOT=$(find_project_root)
311
- if [[ -z "$PROJECT_ROOT" ]]; then
312
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Not in SpecWeave project" >&2
313
- return 1
314
- fi
315
- export PROJECT_ROOT
316
-
317
- # Initialize state directory
318
- _STATE_DIR="${PROJECT_ROOT}/.specweave/state"
319
- mkdir -p "$_STATE_DIR" 2>/dev/null
320
-
321
- # Layer 1: Kill switch
322
- check_kill_switch || return 1
323
-
324
- # Layer 2: Circuit breaker
325
- init_circuit_breaker "$PROJECT_ROOT"
326
- check_circuit_breaker || return 1
327
-
328
- # Layer 3: Recursion guard (if enabled)
329
- if [[ "$enable_recursion_guard" == "true" ]]; then
330
- init_recursion_guard "$PROJECT_ROOT"
331
- check_recursion_guard || return 1
332
- fi
333
-
334
- # Layer 4: Lock (if required)
335
- if [[ "$require_lock" == "true" ]]; then
336
- acquire_hook_lock "$hook_name" "$PROJECT_ROOT" || return 1
337
- fi
338
-
339
- # Layer 5: Debouncing
340
- check_debounce "$hook_name" "$PROJECT_ROOT" || return 1
341
-
342
- # Initialize logging
343
- init_log "hooks" "$PROJECT_ROOT"
344
-
345
- [[ "$HOOK_DEBUG" == "1" ]] && echo "[HOOK] Environment ready: $hook_name" >&2
346
- return 0
347
- }
348
-
349
- # ============================================================================
350
- # 10. SAFE EXIT HANDLERS
351
- # ============================================================================
352
-
353
- # Always exit 0 from hooks (ADR-0157 Rule 6)
354
- # Exception: PreToolUse guards exit 2 to block
355
- hook_exit_success() {
356
- release_hook_lock
357
- release_recursion_guard
358
- exit 0
359
- }
360
-
361
- hook_exit_block() {
362
- release_hook_lock
363
- release_recursion_guard
364
- exit 2 # Block PreToolUse
365
- }
366
-
367
- # Record failure and exit safely
368
- hook_exit_with_failure() {
369
- local msg="$1"
370
- record_circuit_breaker_failure
371
- log_hook "ERROR" "$msg"
372
- release_hook_lock
373
- release_recursion_guard
374
- exit 0 # Still exit 0 to not break Claude
96
+ local project_root
97
+ project_root=$(find_project_root) || return
98
+ local log_file="$project_root/.specweave/logs/hooks.log"
99
+ mkdir -p "$(dirname "$log_file")" 2>/dev/null
100
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [$level] $msg" >> "$log_file" 2>/dev/null
375
101
  }
@@ -203,7 +203,7 @@ case "${1:-}" in
203
203
  transfer)
204
204
  if [[ $# -ne 3 ]]; then
205
205
  echo "Usage: $0 transfer <source_increment> <target_increment>"
206
- exit 1
206
+ exit 0 # SAFETY: Never use exit 1 in hooks
207
207
  fi
208
208
  transfer_work "$2" "$3"
209
209
  ;;
@@ -211,7 +211,7 @@ case "${1:-}" in
211
211
  adjust-wip)
212
212
  if [[ $# -ne 2 ]]; then
213
213
  echo "Usage: $0 adjust-wip <new_limit>"
214
- exit 1
214
+ exit 0 # SAFETY: Never use exit 1 in hooks
215
215
  fi
216
216
  adjust_wip_limit "$2"
217
217
  ;;
@@ -219,7 +219,7 @@ case "${1:-}" in
219
219
  force-close)
220
220
  if [[ $# -ne 2 ]]; then
221
221
  echo "Usage: $0 force-close <increment_id>"
222
- exit 1
222
+ exit 0 # SAFETY: Never use exit 1 in hooks
223
223
  fi
224
224
  force_close "$2"
225
225
  ;;
@@ -227,7 +227,7 @@ case "${1:-}" in
227
227
  count-incomplete)
228
228
  if [[ $# -ne 2 ]]; then
229
229
  echo "Usage: $0 count-incomplete <increment_id>"
230
- exit 1
230
+ exit 0 # SAFETY: Never use exit 1 in hooks
231
231
  fi
232
232
  count_incomplete_tasks "$2"
233
233
  ;;
@@ -240,6 +240,6 @@ case "${1:-}" in
240
240
  echo " $0 adjust-wip <limit> # Adjust WIP limit temporarily"
241
241
  echo " $0 force-close <increment> # Force-close increment"
242
242
  echo " $0 count-incomplete <increment> # Count incomplete tasks"
243
- exit 1
243
+ exit 0 # SAFETY: Never use exit 1 in hooks
244
244
  ;;
245
245
  esac
@@ -21,10 +21,10 @@ set +e # EMERGENCY FIX: Changed from set -euo pipefail to prevent Claude Code c
21
21
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
22
  PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
23
23
 
24
- # Input validation
24
+ # Input validation - exit 0 for safety (never exit 1 in hooks)
25
25
  if [ $# -lt 1 ]; then
26
26
  echo "Usage: $0 <spec-path>" >&2
27
- exit 1
27
+ exit 0 # SAFETY: Never use exit 1 in hooks
28
28
  fi
29
29
 
30
30
  SPEC_PATH="$1"
@@ -32,7 +32,7 @@ SPEC_PATH="$1"
32
32
  # Validate spec file exists
33
33
  if [ ! -f "$SPEC_PATH" ]; then
34
34
  echo "❌ Spec file not found: $SPEC_PATH" >&2
35
- exit 1
35
+ exit 0 # SAFETY: Never use exit 1 in hooks
36
36
  fi
37
37
 
38
38
  # Check if sync is enabled
@@ -105,7 +105,7 @@ fi
105
105
  # Validate provider
106
106
  if [[ ! "$PROVIDER" =~ ^(github|jira|ado)$ ]]; then
107
107
  echo "❌ Invalid provider: $PROVIDER (must be github, jira, or ado)" >&2
108
- exit 1
108
+ exit 0 # SAFETY: Never use exit 1 in hooks
109
109
  fi
110
110
 
111
111
  # Check if sync CLI command exists
@@ -119,7 +119,7 @@ fi
119
119
  # Check if Node.js is available
120
120
  if ! command -v node &> /dev/null; then
121
121
  echo "❌ Node.js not found, cannot sync spec content" >&2
122
- exit 1
122
+ exit 0 # SAFETY: Never use exit 1 in hooks
123
123
  fi
124
124
 
125
125
  echo ""
@@ -12,7 +12,7 @@
12
12
  * - session-start
13
13
  * - post-tool-use
14
14
  * - completion-guard
15
- * - bash-file-guard
15
+ * - increment-duplicate-guard
16
16
  *
17
17
  * @module hooks/universal/dispatcher
18
18
  */
@@ -322,10 +322,9 @@ async function main() {
322
322
  await fallbackToBash('completion-guard.sh', 'guards');
323
323
  break;
324
324
 
325
- case 'bash-file-guard':
326
- // CRITICAL: Prevents infinite hangs from heredoc/echo file creation
327
- // See CLAUDE.md Rule 9 for why this is essential
328
- await fallbackToBash('bash-file-guard.sh', 'guards');
325
+ case 'increment-duplicate-guard':
326
+ // Prevents duplicate increment IDs
327
+ await fallbackToBash('increment-duplicate-guard.sh', 'guards');
329
328
  break;
330
329
 
331
330
  default: