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,33 +1,17 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# common-setup.sh -
|
|
2
|
+
# common-setup.sh - Minimal Hook Library
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
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
|
-
#
|
|
25
|
-
_STATE_DIR=""
|
|
26
|
-
_CACHE_DIR=""
|
|
27
|
-
_LOG_DIR=""
|
|
11
|
+
set +e # NEVER use set -e in hooks
|
|
28
12
|
|
|
29
13
|
# ============================================================================
|
|
30
|
-
#
|
|
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
|
-
#
|
|
30
|
+
# KILL SWITCH
|
|
47
31
|
# ============================================================================
|
|
48
32
|
|
|
49
33
|
check_kill_switch() {
|
|
50
|
-
|
|
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
|
-
#
|
|
38
|
+
# PRE-TOOL-USE GUARD HELPERS
|
|
59
39
|
# ============================================================================
|
|
60
40
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if [[
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
record_debounce() {
|
|
221
|
-
[[ -n "$_DEBOUNCE_FILE" ]] && date +%s > "$_DEBOUNCE_FILE"
|
|
77
|
+
exit 0
|
|
222
78
|
}
|
|
223
79
|
|
|
224
|
-
#
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
297
|
-
|
|
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
|
-
* -
|
|
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 '
|
|
326
|
-
//
|
|
327
|
-
|
|
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:
|