oh-my-customcode 1.1.54 → 1.1.56
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/dist/cli/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/.claude/hooks/hooks.json +19 -19
- package/templates/.claude/hooks/scripts/agent-start-recorder.sh +5 -5
- package/templates/.claude/hooks/scripts/agent-teams-advisor.sh +4 -4
- package/templates/.claude/hooks/scripts/audit-log.sh +6 -6
- package/templates/.claude/hooks/scripts/auto-continue-guard.sh +1 -1
- package/templates/.claude/hooks/scripts/auto-dev-token-summary.sh +6 -6
- package/templates/.claude/hooks/scripts/auto-dev-token-tracker.sh +6 -6
- package/templates/.claude/hooks/scripts/content-hash-validator.sh +4 -4
- package/templates/.claude/hooks/scripts/context-budget-advisor.sh +3 -3
- package/templates/.claude/hooks/scripts/cost-cap-advisor.sh +6 -6
- package/templates/.claude/hooks/scripts/cwd-change-detector.sh +3 -3
- package/templates/.claude/hooks/scripts/destructive-git-guard.sh +2 -2
- package/templates/.claude/hooks/scripts/eval-core-batch-save.sh +2 -2
- package/templates/.claude/hooks/scripts/feedback-collector.sh +7 -7
- package/templates/.claude/hooks/scripts/file-change-validator.sh +4 -4
- package/templates/.claude/hooks/scripts/git-delegation-guard.sh +3 -3
- package/templates/.claude/hooks/scripts/model-escalation-advisor.sh +4 -4
- package/templates/.claude/hooks/scripts/omcustom-auto-update.sh +6 -6
- package/templates/.claude/hooks/scripts/playwright-compress.sh +4 -4
- package/templates/.claude/hooks/scripts/plugin-cache-check.sh +2 -2
- package/templates/.claude/hooks/scripts/r007-r008-drift-advisor.sh +26 -3
- package/templates/.claude/hooks/scripts/rtk-intercept.sh +9 -9
- package/templates/.claude/hooks/scripts/rule-deletion-guard.sh +5 -5
- package/templates/.claude/hooks/scripts/schema-validator.sh +10 -10
- package/templates/.claude/hooks/scripts/secret-filter.sh +4 -4
- package/templates/.claude/hooks/scripts/session-autofix-prompt.sh +3 -3
- package/templates/.claude/hooks/scripts/session-autofix.sh +1 -1
- package/templates/.claude/hooks/scripts/session-env-check.sh +1 -1
- package/templates/.claude/hooks/scripts/session-reflection.sh +15 -15
- package/templates/.claude/hooks/scripts/skill-extractor-analyzer.sh +2 -2
- package/templates/.claude/hooks/scripts/stale-todo-scanner.sh +1 -1
- package/templates/.claude/hooks/scripts/stall-detection-advisor.sh +11 -11
- package/templates/.claude/hooks/scripts/stop-console-audit.sh +1 -1
- package/templates/.claude/hooks/scripts/stuck-detector.sh +81 -25
- package/templates/.claude/hooks/scripts/subagent-failure-advisor.sh +36 -0
- package/templates/.claude/hooks/scripts/task-outcome-recorder.sh +8 -8
- package/templates/.claude/hooks/scripts/task-state-precompact.sh +1 -1
- package/templates/.claude/settings.json +19 -19
- package/templates/manifest.json +1 -1
|
@@ -16,29 +16,28 @@ command -v jq >/dev/null 2>&1 || exit 0
|
|
|
16
16
|
HARD_BLOCK_THRESHOLD=${CLAUDE_STUCK_THRESHOLD:-3}
|
|
17
17
|
|
|
18
18
|
# Determine if a Bash command is read-only (metadata/query only, no side effects).
|
|
19
|
-
# Conservative: any ambiguity (
|
|
19
|
+
# Conservative: any ambiguity (redirection, substitution, "||", or an
|
|
20
20
|
# unrecognized command/subcommand) is treated as NOT read-only (write).
|
|
21
|
-
#
|
|
22
|
-
# (
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
21
|
+
# Compound commands chained with "&&", ";", or a single "|" are split into
|
|
22
|
+
# segments (#1629) — the whole command is read-only ONLY when EVERY segment
|
|
23
|
+
# is read-only; one write segment (or one unparseable/empty segment) makes
|
|
24
|
+
# the whole command a write.
|
|
25
|
+
# Used to exclude read-only Bash polling from Hard Block Checks 1 and 3
|
|
26
|
+
# (same-path / same-tool+target consecutive-repeat blocking) AND the Signal 3
|
|
27
|
+
# tool-spam advisory below — Signal 1 (repeated-error advisory) and Hard
|
|
28
|
+
# Block Check 2 (same error hash) are intentionally unaffected: repeated
|
|
29
|
+
# errors are a genuine stuck signal regardless of read/write (issue #1625).
|
|
30
|
+
|
|
31
|
+
# Classify a SINGLE (non-compound) command segment as read-only. Callers
|
|
32
|
+
# (is_readonly_bash_command) are responsible for stripping ambiguous
|
|
33
|
+
# constructs and splitting compound commands before invoking this directly.
|
|
34
|
+
is_readonly_single_command() {
|
|
27
35
|
local cmd="$1"
|
|
28
|
-
cmd="$(printf '%s' "$cmd" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
29
36
|
if [ -z "$cmd" ]; then
|
|
30
37
|
echo "false"
|
|
31
38
|
return 0
|
|
32
39
|
fi
|
|
33
40
|
|
|
34
|
-
# Any chaining/redirection/substitution metachar => ambiguous, treat as write
|
|
35
|
-
case "$cmd" in
|
|
36
|
-
*'>'*|*'|'*|*'&&'*|*';'*|*'$('*|*'`'*|*'<('*)
|
|
37
|
-
echo "false"
|
|
38
|
-
return 0
|
|
39
|
-
;;
|
|
40
|
-
esac
|
|
41
|
-
|
|
42
41
|
local parts=()
|
|
43
42
|
read -ra parts <<< "$cmd"
|
|
44
43
|
local w1="${parts[0]:-}"
|
|
@@ -117,11 +116,62 @@ is_readonly_bash_command() {
|
|
|
117
116
|
esac
|
|
118
117
|
}
|
|
119
118
|
|
|
119
|
+
is_readonly_bash_command() {
|
|
120
|
+
local cmd="$1"
|
|
121
|
+
cmd="$(printf '%s' "$cmd" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
122
|
+
if [ -z "$cmd" ]; then
|
|
123
|
+
echo "false"
|
|
124
|
+
return 0
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Truly ambiguous constructs: redirection, command/process substitution, or
|
|
128
|
+
# "||" (conditional-OR — intentionally NOT decomposed into segments, since
|
|
129
|
+
# its branch that actually runs depends on the first segment's exit code)
|
|
130
|
+
# => always treated as write.
|
|
131
|
+
case "$cmd" in
|
|
132
|
+
*'>'*|*'||'*|*'$('*|*'`'*|*'<('*)
|
|
133
|
+
echo "false"
|
|
134
|
+
return 0
|
|
135
|
+
;;
|
|
136
|
+
esac
|
|
137
|
+
|
|
138
|
+
# Compound command: split on "&&", ";", or a single "|" into segments and
|
|
139
|
+
# require EVERY segment to be read-only (#1629). An empty segment (e.g. a
|
|
140
|
+
# trailing separator) is treated as ambiguous => write, same as any
|
|
141
|
+
# segment that fails single-command classification.
|
|
142
|
+
if [[ "$cmd" == *'&&'* || "$cmd" == *';'* || "$cmd" == *'|'* ]]; then
|
|
143
|
+
local normalized="$cmd"
|
|
144
|
+
normalized="${normalized//&&/$'\n'}"
|
|
145
|
+
normalized="${normalized//;/$'\n'}"
|
|
146
|
+
normalized="${normalized//|/$'\n'}"
|
|
147
|
+
local seg
|
|
148
|
+
while IFS= read -r seg; do
|
|
149
|
+
seg="$(printf '%s' "$seg" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
|
|
150
|
+
if [ -z "$seg" ]; then
|
|
151
|
+
echo "false"
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
if [ "$(is_readonly_single_command "$seg")" != "true" ]; then
|
|
155
|
+
echo "false"
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
done <<< "$normalized"
|
|
159
|
+
echo "true"
|
|
160
|
+
return 0
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
is_readonly_single_command "$cmd"
|
|
164
|
+
}
|
|
165
|
+
|
|
120
166
|
input=$(cat)
|
|
121
167
|
|
|
122
168
|
# Extract tool info
|
|
123
169
|
tool_name=$(printf '%s' "$input" | jq -r '.tool_name // "unknown"')
|
|
124
|
-
|
|
170
|
+
# 300 (not 120): a 120-char cutoff let two genuinely different long Bash
|
|
171
|
+
# commands collide on their shared 120-char prefix, causing a false-positive
|
|
172
|
+
# same-path/same-tool+target hard-block. 300 chars covers the vast majority
|
|
173
|
+
# of real commands without their differing tail being cut off (#1629).
|
|
174
|
+
file_path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.command // ""' | head -c 300)
|
|
125
175
|
is_error=$(printf '%s' "$input" | jq -r '.tool_output.is_error // false')
|
|
126
176
|
output_preview=$(printf '%s' "$input" | jq -r '.tool_output.output // ""' | head -c 200)
|
|
127
177
|
raw_command=$(printf '%s' "$input" | jq -r '.tool_input.command // ""')
|
|
@@ -150,9 +200,10 @@ entry=$(jq -cn \
|
|
|
150
200
|
--arg err "$is_error" \
|
|
151
201
|
--arg hash "$error_hash" \
|
|
152
202
|
--arg preview "$output_preview" \
|
|
153
|
-
|
|
203
|
+
--arg readonly "$is_readonly" \
|
|
204
|
+
'{timestamp: $ts, tool: $tool, path: $path, is_error: $err, error_hash: $hash, preview: $preview, readonly: $readonly}')
|
|
154
205
|
|
|
155
|
-
|
|
206
|
+
printf '%s\n' "$entry" >> "$HISTORY_FILE"
|
|
156
207
|
|
|
157
208
|
# Ring buffer: keep last 100 entries
|
|
158
209
|
if [ -f "$HISTORY_FILE" ]; then
|
|
@@ -167,13 +218,13 @@ fi
|
|
|
167
218
|
|
|
168
219
|
# Only check for patterns if we have enough history
|
|
169
220
|
if [ ! -f "$HISTORY_FILE" ]; then
|
|
170
|
-
|
|
221
|
+
printf '%s\n' "$input"
|
|
171
222
|
exit 0
|
|
172
223
|
fi
|
|
173
224
|
|
|
174
225
|
recent_count=$(wc -l < "$HISTORY_FILE")
|
|
175
226
|
if [ "$recent_count" -lt 3 ]; then
|
|
176
|
-
|
|
227
|
+
printf '%s\n' "$input"
|
|
177
228
|
exit 0
|
|
178
229
|
fi
|
|
179
230
|
|
|
@@ -214,8 +265,13 @@ if [ "$stuck_detected" = false ] && { [ "$tool_name" = "Edit" ] || [ "$tool_name
|
|
|
214
265
|
fi
|
|
215
266
|
|
|
216
267
|
# Signal 3: Tool spam (same tool 5+ times in last 8 entries)
|
|
217
|
-
|
|
218
|
-
|
|
268
|
+
# Read-only Bash polling is excluded from this count (issue #1629): skip
|
|
269
|
+
# entirely when the CURRENT call is read-only, and exclude prior read-only
|
|
270
|
+
# entries from the historical count so a mix of harmless read-only Bash
|
|
271
|
+
# calls (git status / gh view / ls / ...) doesn't inflate the "same tool
|
|
272
|
+
# called N times" advisory.
|
|
273
|
+
if [ "$stuck_detected" = false ] && [ "$is_readonly" != "true" ]; then
|
|
274
|
+
tool_repeat=$(tail -8 "$HISTORY_FILE" | grep -v "\"readonly\":\"true\"" | grep -c "\"tool\":\"${tool_name}\"" 2>/dev/null || echo "0")
|
|
219
275
|
if [ "$tool_repeat" -ge 5 ]; then
|
|
220
276
|
stuck_detected=true
|
|
221
277
|
signal_type="Tool loop"
|
|
@@ -291,7 +347,7 @@ if [ "$hard_block" = true ]; then
|
|
|
291
347
|
echo " Action: Blocking this tool call to break the stuck loop." >&2
|
|
292
348
|
echo " Recovery: Step back, re-read the error, and try a fundamentally different approach." >&2
|
|
293
349
|
echo "=====================================" >&2
|
|
294
|
-
|
|
350
|
+
printf '%s\n' "$input"
|
|
295
351
|
HOOK_END=$(date +%s%N 2>/dev/null || echo 0)
|
|
296
352
|
if [ "$HOOK_START" != "0" ] && [ "$HOOK_END" != "0" ]; then
|
|
297
353
|
HOOK_MS=$(( (HOOK_END - HOOK_START) / 1000000 ))
|
|
@@ -301,7 +357,7 @@ if [ "$hard_block" = true ]; then
|
|
|
301
357
|
fi
|
|
302
358
|
|
|
303
359
|
# Pass through
|
|
304
|
-
|
|
360
|
+
printf '%s\n' "$input"
|
|
305
361
|
HOOK_END=$(date +%s%N 2>/dev/null || echo 0)
|
|
306
362
|
if [ "$HOOK_START" != "0" ] && [ "$HOOK_END" != "0" ]; then
|
|
307
363
|
HOOK_MS=$(( (HOOK_END - HOOK_START) / 1000000 ))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Dependency check: exit silently if jq not available
|
|
5
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
6
|
+
|
|
7
|
+
# Subagent Failure Advisor Hook
|
|
8
|
+
# Trigger: SubagentStop
|
|
9
|
+
# Purpose: Advisory-only replacement for the removed `type: "prompt"` SubagentStop hook
|
|
10
|
+
# (#1631). The prompt hook asked an LLM judge whether the CURRENT session's own
|
|
11
|
+
# background_tasks entry had transitioned to "completed" before allowing Stop —
|
|
12
|
+
# but that transition only happens AFTER the SubagentStop hook chain resolves, so the
|
|
13
|
+
# judgment was structurally always "not yet completed" for a subagent evaluating its
|
|
14
|
+
# OWN stop. That produced a self-referential `Stop hook feedback:` re-injection loop
|
|
15
|
+
# with no dedup, observed to repeat 8-10x per session before manual override.
|
|
16
|
+
# Fix: replace the blocking LLM judge with a plain stderr-only warning that never
|
|
17
|
+
# triggers the Stop-hook-feedback re-injection path. No block, no LLM call, no loop.
|
|
18
|
+
# Protocol: stdin JSON -> read tool_output.is_error -> stderr warning if failed -> pass through -> exit 0 (always)
|
|
19
|
+
|
|
20
|
+
input=$(cat)
|
|
21
|
+
|
|
22
|
+
is_error=$(printf '%s\n' "$input" | jq -r '.tool_output.is_error // false' 2>/dev/null || echo "false")
|
|
23
|
+
|
|
24
|
+
if [ "$is_error" = "true" ]; then
|
|
25
|
+
agent_type=$(printf '%s\n' "$input" | jq -r '.tool_input.subagent_type // .agent_type // "unknown"' 2>/dev/null || echo "unknown")
|
|
26
|
+
error_summary=$(printf '%s\n' "$input" | jq -r '.tool_output.output // ""' 2>/dev/null | head -c 200)
|
|
27
|
+
echo "" >&2
|
|
28
|
+
echo "--- [Subagent Failure Advisor] background subagent (${agent_type}) reported an error ---" >&2
|
|
29
|
+
echo " ${error_summary}" >&2
|
|
30
|
+
echo " If it FAILED, do NOT auto-continue -- report the failure and wait for user input." >&2
|
|
31
|
+
echo "-----------------------------------------------------------------------------------" >&2
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Always pass through -- advisory only, never blocks, never triggers Stop hook feedback re-injection
|
|
35
|
+
printf '%s\n' "$input"
|
|
36
|
+
exit 0
|
|
@@ -12,9 +12,9 @@ command -v jq >/dev/null 2>&1 || exit 0
|
|
|
12
12
|
input=$(cat)
|
|
13
13
|
|
|
14
14
|
# Extract task info — support both PostToolUse (tool_input.*) and SubagentStop (top-level) shapes
|
|
15
|
-
agent_type=$(
|
|
16
|
-
model=$(
|
|
17
|
-
description=$(
|
|
15
|
+
agent_type=$(printf '%s\n' "$input" | jq -r '.tool_input.subagent_type // .agent_type // "unknown"')
|
|
16
|
+
model=$(printf '%s\n' "$input" | jq -r '.tool_input.model // .model // "inherit"')
|
|
17
|
+
description=$(printf '%s\n' "$input" | jq -r '.tool_input.description // .description // ""' | head -c 80)
|
|
18
18
|
|
|
19
19
|
# Extract skill name from description or prompt
|
|
20
20
|
skill_name=""
|
|
@@ -23,16 +23,16 @@ if echo "$description" | grep -qiE '(skill:|routing|→.*skill)'; then
|
|
|
23
23
|
fi
|
|
24
24
|
# Fallback: check prompt field for "Skill: {name}" pattern
|
|
25
25
|
if [ -z "$skill_name" ]; then
|
|
26
|
-
prompt=$(
|
|
26
|
+
prompt=$(printf '%s\n' "$input" | jq -r '.tool_input.prompt // ""' | head -c 500)
|
|
27
27
|
skill_name=$(echo "$prompt" | grep -oiE 'Skill:\s*[a-z]+-[a-z]+(-[a-z]+)*' | sed 's/[Ss]kill:\s*//' | head -1)
|
|
28
28
|
fi
|
|
29
29
|
|
|
30
30
|
# Determine outcome
|
|
31
|
-
is_error=$(
|
|
31
|
+
is_error=$(printf '%s\n' "$input" | jq -r '.tool_output.is_error // false')
|
|
32
32
|
|
|
33
33
|
if [ "$is_error" = "true" ]; then
|
|
34
34
|
outcome="failure"
|
|
35
|
-
error_summary=$(
|
|
35
|
+
error_summary=$(printf '%s\n' "$input" | jq -r '.tool_output.output // ""' | head -c 200)
|
|
36
36
|
else
|
|
37
37
|
outcome="success"
|
|
38
38
|
error_summary=""
|
|
@@ -94,7 +94,7 @@ entry=$(jq -n \
|
|
|
94
94
|
--arg dur "$duration_seconds" \
|
|
95
95
|
'{timestamp: $ts, agent_type: $agent, model: $model, outcome: $outcome, pattern_used: $pattern, skill: $skill, description: $desc, error_summary: $err, duration_seconds: ($dur | tonumber)}')
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
printf '%s\n' "$entry" >> "$OUTCOME_FILE"
|
|
98
98
|
|
|
99
99
|
# Ring buffer: keep last 50 entries
|
|
100
100
|
if [ -f "$OUTCOME_FILE" ]; then
|
|
@@ -115,5 +115,5 @@ if [ "$outcome" = "failure" ]; then
|
|
|
115
115
|
fi
|
|
116
116
|
|
|
117
117
|
# Pass through
|
|
118
|
-
|
|
118
|
+
printf '%s\n' "$input"
|
|
119
119
|
exit 0
|
|
@@ -49,7 +49,7 @@ collect_state() {
|
|
|
49
49
|
state=$(collect_state 2>/dev/null)
|
|
50
50
|
|
|
51
51
|
if [ -n "$state" ] && [ "$state" != "{}" ]; then
|
|
52
|
-
|
|
52
|
+
printf '%s\n' "$state" > "$STATE_FILE"
|
|
53
53
|
echo "[PreCompact] Task state saved to $STATE_FILE" >&2
|
|
54
54
|
else
|
|
55
55
|
echo "[PreCompact] No active task state to save" >&2
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"hooks": [
|
|
19
19
|
{
|
|
20
20
|
"type": "command",
|
|
21
|
-
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.command // \"\") | test(\"(npm run dev|pnpm( run)? dev|yarn dev|bun run dev)\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\ncmd=$(
|
|
21
|
+
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.command // \"\") | test(\"(npm run dev|pnpm( run)? dev|yarn dev|bun run dev)\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\ncmd=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.command // \"\"')\n\n# Block dev servers that aren't run in tmux\necho '[Hook] BLOCKED: Dev server must run in tmux for log access' >&2\necho '[Hook] Use this command instead:' >&2\necho \"[Hook] tmux new-session -d -s dev 'npm run dev'\" >&2\necho '[Hook] Then: tmux attach -t dev' >&2\nexit 1\n )\nelse\n printf '%s' \"$input\"\nfi"
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
24
|
"matcher": "Bash",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"hooks": [
|
|
29
29
|
{
|
|
30
30
|
"type": "command",
|
|
31
|
-
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.command // \"\") | test(\"(npm (install|test)|pnpm (install|test)|yarn (install|test)|bun (install|test)|cargo build|make|docker|pytest|vitest|playwright)\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nif [ -z \"$TMUX\" ]; then\n echo '[Hook] Consider running in tmux for session persistence' >&2\n echo '[Hook] tmux new -s dev | tmux attach -t dev' >&2\nfi\
|
|
31
|
+
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.command // \"\") | test(\"(npm (install|test)|pnpm (install|test)|yarn (install|test)|bun (install|test)|cargo build|make|docker|pytest|vitest|playwright)\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nif [ -z \"$TMUX\" ]; then\n echo '[Hook] Consider running in tmux for session persistence' >&2\n echo '[Hook] tmux new -s dev | tmux attach -t dev' >&2\nfi\nprintf '%s\\n' \"$input\"\n )\nelse\n printf '%s' \"$input\"\nfi"
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"matcher": "Bash",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"hooks": [
|
|
49
49
|
{
|
|
50
50
|
"type": "command",
|
|
51
|
-
"command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(
|
|
51
|
+
"command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.file_path // \"\"')\n\nif [[ \"$file_path\" =~ \\.(md|txt)$ ]] && [[ ! \"$file_path\" =~ (README|CLAUDE|AGENT|SKILL)\\.md$ ]]; then\n echo \"[Hook] BLOCKED: Unnecessary documentation file creation\" >&2\n echo \"[Hook] File: $file_path\" >&2\n echo \"[Hook] Use README.md or AGENT.md for documentation instead\" >&2\n exit 1\nfi\n\nprintf '%s\\n' \"$input\""
|
|
52
52
|
}
|
|
53
53
|
],
|
|
54
54
|
"matcher": "Write",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
"hooks": [
|
|
119
119
|
{
|
|
120
120
|
"type": "command",
|
|
121
|
-
"command": "#!/bin/bash\ninput=$(cat)\nagent_type=$(
|
|
121
|
+
"command": "#!/bin/bash\ninput=$(cat)\nagent_type=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.subagent_type // \"unknown\"')\nmodel=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.model // \"inherit\"')\ndesc=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.description // \"\"' | head -c 40)\nresume=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.resume // empty')\nif [ -n \"$resume\" ]; then\n echo \"─── [Resume] ${agent_type}:${model} | ${desc} ───\" >&2\nelse\n echo \"─── [Spawn] ${agent_type}:${model} | ${desc} ───\" >&2\nfi\nprintf '%s\\n' \"$input\""
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
"type": "command",
|
|
@@ -256,7 +256,7 @@
|
|
|
256
256
|
"hooks": [
|
|
257
257
|
{
|
|
258
258
|
"type": "command",
|
|
259
|
-
"command": "#!/bin/bash\ninput=$(cat)\nagent_type=$(
|
|
259
|
+
"command": "#!/bin/bash\ninput=$(cat)\nagent_type=$(printf '%s\\n' \"$input\" | jq -r '.agent_type // \"unknown\"')\nmodel=$(printf '%s\\n' \"$input\" | jq -r '.model // \"inherit\"')\ndesc=$(printf '%s\\n' \"$input\" | jq -r '.description // \"\"' | head -c 40)\necho \"─── [SubagentStart] ${agent_type}:${model} | ${desc} ───\" >&2\nprintf '%s\\n' \"$input\""
|
|
260
260
|
},
|
|
261
261
|
{
|
|
262
262
|
"type": "command",
|
|
@@ -287,12 +287,12 @@
|
|
|
287
287
|
"command": "bash .claude/hooks/scripts/r007-r008-drift-advisor.sh"
|
|
288
288
|
},
|
|
289
289
|
{
|
|
290
|
-
"type": "
|
|
291
|
-
"
|
|
290
|
+
"type": "command",
|
|
291
|
+
"command": "bash .claude/hooks/scripts/subagent-failure-advisor.sh"
|
|
292
292
|
}
|
|
293
293
|
],
|
|
294
294
|
"matcher": "*",
|
|
295
|
-
"description": "Record agent outcomes + auto-continue workflow + R007/R008 drift advisory on subagent completion (autonomous-loop re-entry, #1545)"
|
|
295
|
+
"description": "Record agent outcomes + auto-continue workflow + R007/R008 drift advisory + failure advisory on subagent completion (autonomous-loop re-entry, #1545; #1631 replaced the self-referential prompt-type judge hook with an advisory-only command hook)"
|
|
296
296
|
}
|
|
297
297
|
],
|
|
298
298
|
"CwdChanged": [
|
|
@@ -324,7 +324,7 @@
|
|
|
324
324
|
"hooks": [
|
|
325
325
|
{
|
|
326
326
|
"type": "command",
|
|
327
|
-
"command": "#!/bin/bash\ninput=$(cat)\nmsg=$(
|
|
327
|
+
"command": "#!/bin/bash\ninput=$(cat)\nmsg=$(printf '%s\\n' \"$input\" | jq -r '.message // \"\"' 2>/dev/null)\nif [ -n \"$msg\" ]; then\n echo \"[Hook] Notification: $msg\" >&2\nfi\nprintf '%s\\n' \"$input\""
|
|
328
328
|
}
|
|
329
329
|
],
|
|
330
330
|
"matcher": "*",
|
|
@@ -336,7 +336,7 @@
|
|
|
336
336
|
"hooks": [
|
|
337
337
|
{
|
|
338
338
|
"type": "command",
|
|
339
|
-
"command": "#!/bin/bash\ninput=$(cat)\nteammate=$(
|
|
339
|
+
"command": "#!/bin/bash\ninput=$(cat)\nteammate=$(printf '%s\\n' \"$input\" | jq -r '.teammate_id // \"unknown\"' 2>/dev/null)\necho \"[Hook] TeammateIdle: $teammate — available for reassignment\" >&2\nprintf '%s\\n' \"$input\""
|
|
340
340
|
}
|
|
341
341
|
],
|
|
342
342
|
"matcher": "*",
|
|
@@ -348,7 +348,7 @@
|
|
|
348
348
|
"hooks": [
|
|
349
349
|
{
|
|
350
350
|
"type": "command",
|
|
351
|
-
"command": "#!/bin/bash\ninput=$(cat)\ntask_id=$(
|
|
351
|
+
"command": "#!/bin/bash\ninput=$(cat)\ntask_id=$(printf '%s\\n' \"$input\" | jq -r '.task_id // \"\"' 2>/dev/null)\ndesc=$(printf '%s\\n' \"$input\" | jq -r '.description // \"\"' 2>/dev/null | head -c 60)\necho \"[Hook] TaskCreated: $task_id — $desc\" >&2\nprintf '%s\\n' \"$input\""
|
|
352
352
|
}
|
|
353
353
|
],
|
|
354
354
|
"matcher": "*",
|
|
@@ -360,7 +360,7 @@
|
|
|
360
360
|
"hooks": [
|
|
361
361
|
{
|
|
362
362
|
"type": "command",
|
|
363
|
-
"command": "#!/bin/bash\ninput=$(cat)\ntask_id=$(
|
|
363
|
+
"command": "#!/bin/bash\ninput=$(cat)\ntask_id=$(printf '%s\\n' \"$input\" | jq -r '.task_id // \"\"' 2>/dev/null)\nresult=$(printf '%s\\n' \"$input\" | jq -r '.result // \"\"' 2>/dev/null | head -c 60)\necho \"[Hook] TaskCompleted: $task_id — $result\" >&2\nprintf '%s\\n' \"$input\""
|
|
364
364
|
}
|
|
365
365
|
],
|
|
366
366
|
"matcher": "*",
|
|
@@ -396,7 +396,7 @@
|
|
|
396
396
|
"hooks": [
|
|
397
397
|
{
|
|
398
398
|
"type": "command",
|
|
399
|
-
"command": "#!/bin/bash\ninput=$(cat)\ncmd=$(
|
|
399
|
+
"command": "#!/bin/bash\ninput=$(cat)\ncmd=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.command')\n\nif echo \"$cmd\" | grep -qE 'gh pr create'; then\n output=$(printf '%s\\n' \"$input\" | jq -r '.tool_output.output // \"\"')\n pr_url=$(echo \"$output\" | grep -oE 'https://github.com/[^/]+/[^/]+/pull/[0-9]+')\n \n if [ -n \"$pr_url\" ]; then\n echo \"[Hook] PR created: $pr_url\" >&2\n echo \"[Hook] Checking GitHub Actions status...\" >&2\n repo=$(echo \"$pr_url\" | sed -E 's|https://github.com/([^/]+/[^/]+)/pull/[0-9]+|\\1|')\n pr_num=$(echo \"$pr_url\" | sed -E 's|.*/pull/([0-9]+)|\\1|')\n echo \"[Hook] To review PR: gh pr review $pr_num --repo $repo\" >&2\n fi\nfi\n\nprintf '%s\\n' \"$input\""
|
|
400
400
|
}
|
|
401
401
|
],
|
|
402
402
|
"matcher": "Bash",
|
|
@@ -406,7 +406,7 @@
|
|
|
406
406
|
"hooks": [
|
|
407
407
|
{
|
|
408
408
|
"type": "command",
|
|
409
|
-
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(ts|tsx|js|jsx)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(
|
|
409
|
+
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(ts|tsx|js|jsx)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.file_path // \"\"')\n\nif [ -n \"$file_path\" ] && [ -f \"$file_path\" ]; then\n if command -v prettier >/dev/null 2>&1; then\n prettier --write \"$file_path\" 2>&1 | head -5 >&2\n fi\nfi\n\nprintf '%s\\n' \"$input\"\n )\nelse\n printf '%s' \"$input\"\nfi"
|
|
410
410
|
}
|
|
411
411
|
],
|
|
412
412
|
"matcher": "Edit",
|
|
@@ -416,7 +416,7 @@
|
|
|
416
416
|
"hooks": [
|
|
417
417
|
{
|
|
418
418
|
"type": "command",
|
|
419
|
-
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(ts|tsx)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(
|
|
419
|
+
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(ts|tsx)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.file_path // \"\"')\n\nif [ -n \"$file_path\" ] && [ -f \"$file_path\" ]; then\n dir=$(dirname \"$file_path\")\n project_root=\"$dir\"\n while [ \"$project_root\" != \"/\" ] && [ ! -f \"$project_root/package.json\" ]; do\n project_root=$(dirname \"$project_root\")\n done\n \n if [ -f \"$project_root/tsconfig.json\" ]; then\n cd \"$project_root\" && npx tsc --noEmit --pretty false 2>&1 | grep \"$file_path\" | head -10 >&2 || true\n fi\nfi\n\nprintf '%s\\n' \"$input\"\n )\nelse\n printf '%s' \"$input\"\nfi",
|
|
420
420
|
"continueOnBlock": true
|
|
421
421
|
}
|
|
422
422
|
],
|
|
@@ -427,7 +427,7 @@
|
|
|
427
427
|
"hooks": [
|
|
428
428
|
{
|
|
429
429
|
"type": "command",
|
|
430
|
-
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(ts|tsx|js|jsx)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(
|
|
430
|
+
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(ts|tsx|js|jsx)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.file_path // \"\"')\n\nif [ -n \"$file_path\" ] && [ -f \"$file_path\" ]; then\n console_logs=$(grep -n \"console\\\\.log\" \"$file_path\" 2>/dev/null || true)\n \n if [ -n \"$console_logs\" ]; then\n echo \"[Hook] WARNING: console.log found in $file_path\" >&2\n echo \"$console_logs\" | head -5 >&2\n echo \"[Hook] Remove console.log before committing\" >&2\n fi\nfi\n\nprintf '%s\\n' \"$input\"\n )\nelse\n printf '%s' \"$input\"\nfi",
|
|
431
431
|
"continueOnBlock": true
|
|
432
432
|
}
|
|
433
433
|
],
|
|
@@ -438,7 +438,7 @@
|
|
|
438
438
|
"hooks": [
|
|
439
439
|
{
|
|
440
440
|
"type": "command",
|
|
441
|
-
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(go)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(
|
|
441
|
+
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(go)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.file_path // \"\"')\n\nif [ -n \"$file_path\" ] && [ -f \"$file_path\" ]; then\n if command -v gofmt >/dev/null 2>&1; then\n gofmt -w \"$file_path\" 2>&1 >&2\n fi\nfi\n\nprintf '%s\\n' \"$input\"\n )\nelse\n printf '%s' \"$input\"\nfi"
|
|
442
442
|
}
|
|
443
443
|
],
|
|
444
444
|
"matcher": "Edit",
|
|
@@ -448,7 +448,7 @@
|
|
|
448
448
|
"hooks": [
|
|
449
449
|
{
|
|
450
450
|
"type": "command",
|
|
451
|
-
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(py)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(
|
|
451
|
+
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(py)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.file_path // \"\"')\n\nif [ -n \"$file_path\" ] && [ -f \"$file_path\" ]; then\n # Format with ruff\n if command -v ruff >/dev/null 2>&1; then\n ruff format --quiet \"$file_path\" 2>&1 >&2\n ruff check --fix --quiet \"$file_path\" 2>&1 >&2 || true\n echo \"[Hook] Python formatted with ruff\" >&2\n fi\nfi\n\nprintf '%s\\n' \"$input\"\n )\nelse\n printf '%s' \"$input\"\nfi",
|
|
452
452
|
"continueOnBlock": true
|
|
453
453
|
}
|
|
454
454
|
],
|
|
@@ -459,7 +459,7 @@
|
|
|
459
459
|
"hooks": [
|
|
460
460
|
{
|
|
461
461
|
"type": "command",
|
|
462
|
-
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(py)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(
|
|
462
|
+
"command": "input=$(cat)\nif printf '%s' \"$input\" | jq -e '((.tool_input.file_path // \"\") | test(\"\\\\.(py)$\"))' >/dev/null 2>&1; then\n printf '%s' \"$input\" | (\n#!/bin/bash\ninput=$(cat)\nfile_path=$(printf '%s\\n' \"$input\" | jq -r '.tool_input.file_path // \"\"')\n\nif [ -n \"$file_path\" ] && [ -f \"$file_path\" ]; then\n # Type check with ty\n if command -v ty >/dev/null 2>&1; then\n dir=$(dirname \"$file_path\")\n project_root=\"$dir\"\n while [ \"$project_root\" != \"/\" ] && [ ! -f \"$project_root/pyproject.toml\" ]; do\n project_root=$(dirname \"$project_root\")\n done\n \n if [ -f \"$project_root/pyproject.toml\" ]; then\n cd \"$project_root\" && ty check \"$file_path\" 2>&1 | head -10 >&2 || true\n else\n ty check \"$file_path\" 2>&1 | head -10 >&2 || true\n fi\n fi\nfi\n\nprintf '%s\\n' \"$input\"\n )\nelse\n printf '%s' \"$input\"\nfi",
|
|
463
463
|
"continueOnBlock": true
|
|
464
464
|
}
|
|
465
465
|
],
|
package/templates/manifest.json
CHANGED