oh-my-customcode 1.1.55 → 1.1.57

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 (42) hide show
  1. package/dist/cli/index.js +5 -1
  2. package/dist/index.js +5 -1
  3. package/package.json +1 -1
  4. package/templates/.claude/hooks/hooks.json +21 -21
  5. package/templates/.claude/hooks/scripts/agent-start-recorder.sh +5 -5
  6. package/templates/.claude/hooks/scripts/agent-teams-advisor.sh +4 -4
  7. package/templates/.claude/hooks/scripts/audit-log.sh +6 -6
  8. package/templates/.claude/hooks/scripts/auto-continue-guard.sh +1 -1
  9. package/templates/.claude/hooks/scripts/auto-dev-token-summary.sh +6 -6
  10. package/templates/.claude/hooks/scripts/auto-dev-token-tracker.sh +6 -6
  11. package/templates/.claude/hooks/scripts/claude-md-reinject.sh +9 -4
  12. package/templates/.claude/hooks/scripts/content-hash-validator.sh +4 -4
  13. package/templates/.claude/hooks/scripts/context-budget-advisor.sh +3 -3
  14. package/templates/.claude/hooks/scripts/cost-cap-advisor.sh +6 -6
  15. package/templates/.claude/hooks/scripts/cwd-change-detector.sh +3 -3
  16. package/templates/.claude/hooks/scripts/destructive-git-guard.sh +2 -2
  17. package/templates/.claude/hooks/scripts/eval-core-batch-save.sh +2 -2
  18. package/templates/.claude/hooks/scripts/feedback-collector.sh +7 -7
  19. package/templates/.claude/hooks/scripts/file-change-validator.sh +4 -4
  20. package/templates/.claude/hooks/scripts/git-delegation-guard.sh +3 -3
  21. package/templates/.claude/hooks/scripts/model-escalation-advisor.sh +4 -4
  22. package/templates/.claude/hooks/scripts/omcustom-auto-update.sh +6 -6
  23. package/templates/.claude/hooks/scripts/playwright-compress.sh +4 -4
  24. package/templates/.claude/hooks/scripts/plugin-cache-check.sh +2 -2
  25. package/templates/.claude/hooks/scripts/rtk-intercept.sh +9 -9
  26. package/templates/.claude/hooks/scripts/rule-deletion-guard.sh +5 -5
  27. package/templates/.claude/hooks/scripts/schema-validator.sh +10 -10
  28. package/templates/.claude/hooks/scripts/secret-filter.sh +4 -4
  29. package/templates/.claude/hooks/scripts/session-autofix-prompt.sh +3 -3
  30. package/templates/.claude/hooks/scripts/session-autofix.sh +1 -1
  31. package/templates/.claude/hooks/scripts/session-env-check.sh +1 -1
  32. package/templates/.claude/hooks/scripts/session-reflection.sh +15 -15
  33. package/templates/.claude/hooks/scripts/skill-extractor-analyzer.sh +2 -2
  34. package/templates/.claude/hooks/scripts/stale-todo-scanner.sh +1 -1
  35. package/templates/.claude/hooks/scripts/stall-detection-advisor.sh +11 -11
  36. package/templates/.claude/hooks/scripts/stop-console-audit.sh +1 -1
  37. package/templates/.claude/hooks/scripts/stuck-detector.sh +5 -5
  38. package/templates/.claude/hooks/scripts/subagent-failure-advisor.sh +36 -0
  39. package/templates/.claude/hooks/scripts/task-outcome-recorder.sh +8 -8
  40. package/templates/.claude/hooks/scripts/task-state-precompact.sh +1 -1
  41. package/templates/.claude/settings.json +21 -21
  42. package/templates/manifest.json +1 -1
package/dist/cli/index.js CHANGED
@@ -243,7 +243,7 @@ var init_package = __esm(() => {
243
243
  workspaces: [
244
244
  "packages/*"
245
245
  ],
246
- version: "1.1.55",
246
+ version: "1.1.57",
247
247
  description: "Batteries-included agent harness for Claude Code",
248
248
  type: "module",
249
249
  bin: {
@@ -28198,10 +28198,14 @@ function analyzeMixedAndExpr(ast, raw) {
28198
28198
  const fieldNode = fieldParts.reduce((acc, part) => acc === null ? part : { kind: "and", left: acc, right: part }, null);
28199
28199
  return { toolRegex, fieldNode };
28200
28200
  }
28201
+ var BARE_LITERAL_MATCHER = /^[A-Za-z_][A-Za-z0-9_-]*$/;
28201
28202
  function analyzeMatcher(raw) {
28202
28203
  if (raw === undefined || raw === "*" || raw.trim() === "") {
28203
28204
  return { toolRegex: "*", fieldNode: null };
28204
28205
  }
28206
+ if (BARE_LITERAL_MATCHER.test(raw)) {
28207
+ return { toolRegex: raw, fieldNode: null };
28208
+ }
28205
28209
  let ast;
28206
28210
  try {
28207
28211
  ast = parseMatcherExpr(raw);
package/dist/index.js CHANGED
@@ -1556,10 +1556,14 @@ function analyzeMixedAndExpr(ast, raw) {
1556
1556
  const fieldNode = fieldParts.reduce((acc, part) => acc === null ? part : { kind: "and", left: acc, right: part }, null);
1557
1557
  return { toolRegex, fieldNode };
1558
1558
  }
1559
+ var BARE_LITERAL_MATCHER = /^[A-Za-z_][A-Za-z0-9_-]*$/;
1559
1560
  function analyzeMatcher(raw) {
1560
1561
  if (raw === undefined || raw === "*" || raw.trim() === "") {
1561
1562
  return { toolRegex: "*", fieldNode: null };
1562
1563
  }
1564
+ if (BARE_LITERAL_MATCHER.test(raw)) {
1565
+ return { toolRegex: raw, fieldNode: null };
1566
+ }
1563
1567
  let ast;
1564
1568
  try {
1565
1569
  ast = parseMatcherExpr(raw);
@@ -2535,7 +2539,7 @@ var package_default = {
2535
2539
  workspaces: [
2536
2540
  "packages/*"
2537
2541
  ],
2538
- version: "1.1.55",
2542
+ version: "1.1.57",
2539
2543
  description: "Batteries-included agent harness for Claude Code",
2540
2544
  type: "module",
2541
2545
  bin: {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "1.1.55",
6
+ "version": "1.1.57",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "hooks": [
18
18
  {
19
19
  "type": "command",
20
- "command": "#!/bin/bash\ninput=$(cat)\ncmd=$(echo \"$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"
20
+ "command": "#!/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"
21
21
  }
22
22
  ],
23
23
  "description": "Block dev servers outside tmux - ensures you can access logs"
@@ -27,7 +27,7 @@
27
27
  "hooks": [
28
28
  {
29
29
  "type": "command",
30
- "command": "#!/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\necho \"$input\""
30
+ "command": "#!/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\""
31
31
  }
32
32
  ],
33
33
  "description": "Reminder to use tmux for long-running commands"
@@ -47,7 +47,7 @@
47
47
  "hooks": [
48
48
  {
49
49
  "type": "command",
50
- "command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(echo \"$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\necho \"$input\""
50
+ "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\""
51
51
  }
52
52
  ],
53
53
  "description": "Block creation of random .md files - keeps docs consolidated"
@@ -117,7 +117,7 @@
117
117
  "hooks": [
118
118
  {
119
119
  "type": "command",
120
- "command": "#!/bin/bash\ninput=$(cat)\nagent_type=$(echo \"$input\" | jq -r '.tool_input.subagent_type // \"unknown\"')\nmodel=$(echo \"$input\" | jq -r '.tool_input.model // \"inherit\"')\ndesc=$(echo \"$input\" | jq -r '.tool_input.description // \"\"' | head -c 40)\nresume=$(echo \"$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\necho \"$input\""
120
+ "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\""
121
121
  },
122
122
  {
123
123
  "type": "command",
@@ -197,14 +197,14 @@
197
197
  "description": "Advisory check for plugin cache directories missing node_modules (#1207)"
198
198
  },
199
199
  {
200
- "matcher": "*",
200
+ "matcher": "compact",
201
201
  "hooks": [
202
202
  {
203
203
  "type": "command",
204
204
  "command": "bash .claude/hooks/scripts/claude-md-reinject.sh"
205
205
  }
206
206
  ],
207
- "description": "Re-inject project CLAUDE.md into model context on session start/resume/clear and compact re-entry — matcher \"*\" covers all SessionStart sources including \"compact\" (#1617)"
207
+ "description": "Re-inject project CLAUDE.md into model context on compact re-entry only — CC already natively auto-loads CLAUDE.md on startup/resume/clear, so re-injecting there is pure duplication (~13.8KB/session); matcher narrowed to \"compact\" to keep the original #1617 purpose (post-compact amnesia defense) without the startup double-injection (#1626)"
208
208
  }
209
209
  ],
210
210
  "UserPromptSubmit": [
@@ -255,7 +255,7 @@
255
255
  "hooks": [
256
256
  {
257
257
  "type": "command",
258
- "command": "#!/bin/bash\ninput=$(cat)\nagent_type=$(echo \"$input\" | jq -r '.agent_type // \"unknown\"')\nmodel=$(echo \"$input\" | jq -r '.model // \"inherit\"')\ndesc=$(echo \"$input\" | jq -r '.description // \"\"' | head -c 40)\necho \"─── [SubagentStart] ${agent_type}:${model} | ${desc} ───\" >&2\necho \"$input\""
258
+ "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\""
259
259
  },
260
260
  {
261
261
  "type": "command",
@@ -286,11 +286,11 @@
286
286
  "command": "bash .claude/hooks/scripts/r007-r008-drift-advisor.sh"
287
287
  },
288
288
  {
289
- "type": "prompt",
290
- "prompt": "A background subagent just completed. If it FAILED, do NOT auto-continue — report the failure and wait for user input."
289
+ "type": "command",
290
+ "command": "bash .claude/hooks/scripts/subagent-failure-advisor.sh"
291
291
  }
292
292
  ],
293
- "description": "Record agent outcomes + auto-continue workflow + R007/R008 drift advisory on subagent completion (autonomous-loop re-entry, #1545)"
293
+ "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)"
294
294
  }
295
295
  ],
296
296
  "CwdChanged": [
@@ -323,7 +323,7 @@
323
323
  "hooks": [
324
324
  {
325
325
  "type": "command",
326
- "command": "#!/bin/bash\ninput=$(cat)\nmsg=$(echo \"$input\" | jq -r '.message // \"\"' 2>/dev/null)\nif [ -n \"$msg\" ]; then\n echo \"[Hook] Notification: $msg\" >&2\nfi\necho \"$input\""
326
+ "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\""
327
327
  }
328
328
  ],
329
329
  "description": "Display notifications for long-running operation completions"
@@ -335,7 +335,7 @@
335
335
  "hooks": [
336
336
  {
337
337
  "type": "command",
338
- "command": "#!/bin/bash\ninput=$(cat)\nteammate=$(echo \"$input\" | jq -r '.teammate_id // \"unknown\"' 2>/dev/null)\necho \"[Hook] TeammateIdle: $teammate — available for reassignment\" >&2\necho \"$input\""
338
+ "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\""
339
339
  }
340
340
  ],
341
341
  "description": "Track idle Agent Teams members for work reassignment"
@@ -347,7 +347,7 @@
347
347
  "hooks": [
348
348
  {
349
349
  "type": "command",
350
- "command": "#!/bin/bash\ninput=$(cat)\ntask_id=$(echo \"$input\" | jq -r '.task_id // \"\"' 2>/dev/null)\ndesc=$(echo \"$input\" | jq -r '.description // \"\"' 2>/dev/null | head -c 60)\necho \"[Hook] TaskCreated: $task_id — $desc\" >&2\necho \"$input\""
350
+ "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\""
351
351
  }
352
352
  ],
353
353
  "description": "Log task creation events for Agent Teams coordination"
@@ -359,7 +359,7 @@
359
359
  "hooks": [
360
360
  {
361
361
  "type": "command",
362
- "command": "#!/bin/bash\ninput=$(cat)\ntask_id=$(echo \"$input\" | jq -r '.task_id // \"\"' 2>/dev/null)\nresult=$(echo \"$input\" | jq -r '.result // \"\"' 2>/dev/null | head -c 60)\necho \"[Hook] TaskCompleted: $task_id — $result\" >&2\necho \"$input\""
362
+ "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\""
363
363
  }
364
364
  ],
365
365
  "description": "Log task completion events for Agent Teams coordination"
@@ -395,7 +395,7 @@
395
395
  "hooks": [
396
396
  {
397
397
  "type": "command",
398
- "command": "#!/bin/bash\ninput=$(cat)\ncmd=$(echo \"$input\" | jq -r '.tool_input.command')\n\nif echo \"$cmd\" | grep -qE 'gh pr create'; then\n output=$(echo \"$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\necho \"$input\""
398
+ "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\""
399
399
  }
400
400
  ],
401
401
  "description": "Log PR URL and provide review command after PR creation"
@@ -405,7 +405,7 @@
405
405
  "hooks": [
406
406
  {
407
407
  "type": "command",
408
- "command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(echo \"$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\necho \"$input\""
408
+ "command": "#!/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\""
409
409
  }
410
410
  ],
411
411
  "description": "Auto-format JS/TS files with Prettier after edits"
@@ -415,7 +415,7 @@
415
415
  "hooks": [
416
416
  {
417
417
  "type": "command",
418
- "command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(echo \"$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\necho \"$input\"",
418
+ "command": "#!/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\"",
419
419
  "continueOnBlock": true
420
420
  }
421
421
  ],
@@ -426,7 +426,7 @@
426
426
  "hooks": [
427
427
  {
428
428
  "type": "command",
429
- "command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(echo \"$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\necho \"$input\"",
429
+ "command": "#!/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\"",
430
430
  "continueOnBlock": true
431
431
  }
432
432
  ],
@@ -437,7 +437,7 @@
437
437
  "hooks": [
438
438
  {
439
439
  "type": "command",
440
- "command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(echo \"$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\necho \"$input\""
440
+ "command": "#!/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\""
441
441
  }
442
442
  ],
443
443
  "description": "Auto-format Go files with gofmt after edits"
@@ -447,7 +447,7 @@
447
447
  "hooks": [
448
448
  {
449
449
  "type": "command",
450
- "command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(echo \"$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\necho \"$input\"",
450
+ "command": "#!/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\"",
451
451
  "continueOnBlock": true
452
452
  }
453
453
  ],
@@ -458,7 +458,7 @@
458
458
  "hooks": [
459
459
  {
460
460
  "type": "command",
461
- "command": "#!/bin/bash\ninput=$(cat)\nfile_path=$(echo \"$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\necho \"$input\"",
461
+ "command": "#!/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\"",
462
462
  "continueOnBlock": true
463
463
  }
464
464
  ],
@@ -10,9 +10,9 @@ command -v jq >/dev/null 2>&1 || { cat; exit 0; }
10
10
 
11
11
  input=$(cat)
12
12
 
13
- agent_type=$(echo "$input" | jq -r '.agent_type // "unknown"')
14
- model=$(echo "$input" | jq -r '.model // "inherit"')
15
- description=$(echo "$input" | jq -r '.description // ""' | head -c 80)
13
+ agent_type=$(printf '%s\n' "$input" | jq -r '.agent_type // "unknown"')
14
+ model=$(printf '%s\n' "$input" | jq -r '.model // "inherit"')
15
+ description=$(printf '%s\n' "$input" | jq -r '.description // ""' | head -c 80)
16
16
 
17
17
  AGENT_START_FILE="/tmp/.claude-agent-starts-${PPID}"
18
18
 
@@ -25,7 +25,7 @@ entry=$(jq -cn \
25
25
  --arg desc "$description" \
26
26
  '{start_epoch: $ts, agent_type: $agent, model: $model, description: $desc}')
27
27
 
28
- echo "$entry" >> "$AGENT_START_FILE"
28
+ printf '%s\n' "$entry" >> "$AGENT_START_FILE"
29
29
 
30
30
  # Ring buffer: 50 max
31
31
  if [ -f "$AGENT_START_FILE" ]; then
@@ -36,5 +36,5 @@ if [ -f "$AGENT_START_FILE" ]; then
36
36
  fi
37
37
  fi
38
38
 
39
- echo "$input"
39
+ printf '%s\n' "$input"
40
40
  exit 0
@@ -21,7 +21,7 @@ ENV_STATUS="/tmp/.claude-env-status-${PPID}"
21
21
  if [ -f "$ENV_STATUS" ]; then
22
22
  teams_status=$(grep "agent_teams=" "$ENV_STATUS" 2>/dev/null | cut -d= -f2 || echo "unknown")
23
23
  if [ "$teams_status" != "enabled" ]; then
24
- echo "$input"
24
+ printf '%s\n' "$input"
25
25
  exit 0
26
26
  fi
27
27
  fi
@@ -43,8 +43,8 @@ if [ -f "$RELEASE_PLAN" ]; then
43
43
  fi
44
44
 
45
45
  # Extract task info from input
46
- agent_type=$(echo "$input" | jq -r '.tool_input.subagent_type // "unknown"')
47
- prompt_preview=$(echo "$input" | jq -r '.tool_input.description // ""' | head -c 60)
46
+ agent_type=$(printf '%s\n' "$input" | jq -r '.tool_input.subagent_type // "unknown"')
47
+ prompt_preview=$(printf '%s\n' "$input" | jq -r '.tool_input.description // ""' | head -c 60)
48
48
 
49
49
  # Session-scoped counter using parent PID as session identifier
50
50
  COUNTER_FILE="/tmp/.claude-task-count-${PPID}"
@@ -77,5 +77,5 @@ elif [ "$COUNT" -ge 2 ]; then
77
77
  fi
78
78
 
79
79
  # Always pass through -- advisory only, never blocks
80
- echo "$input"
80
+ printf '%s\n' "$input"
81
81
  exit 0
@@ -14,11 +14,11 @@ command -v jq >/dev/null 2>&1 || exit 0
14
14
  input=$(cat)
15
15
 
16
16
  # Extract fields from hook input
17
- tool_name=$(echo "$input" | jq -r '.tool_name // "unknown"')
18
- file_path=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.command // ""' | head -c 200)
19
- agent_type=$(echo "$input" | jq -r '.agent_type // "unknown"')
20
- model=$(echo "$input" | jq -r '.model // "unknown"')
21
- is_error=$(echo "$input" | jq -r '.tool_output.is_error // false')
17
+ tool_name=$(printf '%s\n' "$input" | jq -r '.tool_name // "unknown"')
18
+ file_path=$(printf '%s\n' "$input" | jq -r '.tool_input.file_path // .tool_input.command // ""' | head -c 200)
19
+ agent_type=$(printf '%s\n' "$input" | jq -r '.agent_type // "unknown"')
20
+ model=$(printf '%s\n' "$input" | jq -r '.model // "unknown"')
21
+ is_error=$(printf '%s\n' "$input" | jq -r '.tool_output.is_error // false')
22
22
  timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
23
23
 
24
24
  # Determine outcome
@@ -55,7 +55,7 @@ if [ -f "$AUDIT_LOG" ]; then
55
55
  fi
56
56
 
57
57
  # Pass through
58
- echo "$input"
58
+ printf '%s\n' "$input"
59
59
  HOOK_END=$(date +%s%N 2>/dev/null || echo 0)
60
60
  if [ "$HOOK_START" != "0" ] && [ "$HOOK_END" != "0" ]; then
61
61
  HOOK_MS=$(( (HOOK_END - HOOK_START) / 1000000 ))
@@ -29,5 +29,5 @@ if [ "$count" -ge 4 ]; then
29
29
  echo '[AutoContinue] SAFETY: auto-continue limit (3) reached. Pausing.' >&2
30
30
  fi
31
31
 
32
- echo "$input"
32
+ printf '%s\n' "$input"
33
33
  exit 0
@@ -32,7 +32,7 @@ summary=$(jq -s -r '
32
32
  [ "$summary" = "[]" ] && exit 0
33
33
 
34
34
  # Totals
35
- totals=$(echo "$summary" | jq -r '
35
+ totals=$(printf '%s\n' "$summary" | jq -r '
36
36
  {
37
37
  total_calls: (map(.calls) | add),
38
38
  total_in: (map(.tokens_in) | add),
@@ -40,10 +40,10 @@ totals=$(echo "$summary" | jq -r '
40
40
  grand: (map(.total) | add)
41
41
  }
42
42
  ')
43
- total_calls=$(echo "$totals" | jq -r '.total_calls')
44
- total_in=$(echo "$totals" | jq -r '.total_in')
45
- total_out=$(echo "$totals" | jq -r '.total_out')
46
- grand=$(echo "$totals" | jq -r '.grand')
43
+ total_calls=$(printf '%s\n' "$totals" | jq -r '.total_calls')
44
+ total_in=$(printf '%s\n' "$totals" | jq -r '.total_in')
45
+ total_out=$(printf '%s\n' "$totals" | jq -r '.total_out')
46
+ grand=$(printf '%s\n' "$totals" | jq -r '.grand')
47
47
 
48
48
  {
49
49
  echo ""
@@ -52,7 +52,7 @@ grand=$(echo "$totals" | jq -r '.grand')
52
52
  echo ""
53
53
  printf "| %-32s | %5s | %10s | %10s | %10s |\n" "Phase" "Calls" "Tokens In" "Tokens Out" "Total"
54
54
  printf "| %-32s | %5s | %10s | %10s | %10s |\n" "--------------------------------" "-----" "----------" "----------" "----------"
55
- echo "$summary" | jq -r '.[] | "\(.phase)\t\(.calls)\t\(.tokens_in)\t\(.tokens_out)\t\(.total)"' | \
55
+ printf '%s\n' "$summary" | jq -r '.[] | "\(.phase)\t\(.calls)\t\(.tokens_in)\t\(.tokens_out)\t\(.total)"' | \
56
56
  while IFS=$'\t' read -r phase calls tin tout tot; do
57
57
  phase_trunc=$(printf "%s" "$phase" | head -c 32)
58
58
  printf "| %-32s | %5s | %10s | %10s | %10s |\n" "$phase_trunc" "$calls" "$tin" "$tout" "$tot"
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
 
10
10
  # Always pass through input no matter what
11
11
  input=$(cat)
12
- trap 'echo "$input"' EXIT
12
+ trap 'printf "%s\n" "$input"' EXIT
13
13
 
14
14
  # Gate: only active during auto-dev pipeline
15
15
  PIPELINE_STATE="/tmp/.claude-pipeline-auto-dev-${PPID}.json"
@@ -21,10 +21,10 @@ command -v jq >/dev/null 2>&1 || exit 0
21
21
  LOG_FILE="/tmp/auto-dev-spend-${PPID}.json"
22
22
 
23
23
  # Extract fields (PostToolUse Agent shape)
24
- agent_type=$(echo "$input" | jq -r '.tool_input.subagent_type // "unknown"' 2>/dev/null || echo "unknown")
25
- description=$(echo "$input" | jq -r '.tool_input.description // ""' 2>/dev/null || echo "")
26
- prompt_text=$(echo "$input" | jq -r '.tool_input.prompt // ""' 2>/dev/null || echo "")
27
- output_text=$(echo "$input" | jq -r '.tool_output.output // .tool_output // ""' 2>/dev/null || echo "")
24
+ agent_type=$(printf '%s\n' "$input" | jq -r '.tool_input.subagent_type // "unknown"' 2>/dev/null || echo "unknown")
25
+ description=$(printf '%s\n' "$input" | jq -r '.tool_input.description // ""' 2>/dev/null || echo "")
26
+ prompt_text=$(printf '%s\n' "$input" | jq -r '.tool_input.prompt // ""' 2>/dev/null || echo "")
27
+ output_text=$(printf '%s\n' "$input" | jq -r '.tool_output.output // .tool_output // ""' 2>/dev/null || echo "")
28
28
 
29
29
  # Derive phase from pipeline state (current_phase) or fallback to description prefix
30
30
  phase=$(jq -r '.current_phase // .phase // "unknown"' "$PIPELINE_STATE" 2>/dev/null || echo "unknown")
@@ -52,6 +52,6 @@ entry=$(jq -n -c \
52
52
  --argjson tout "$tokens_out" \
53
53
  '{ts: $ts, phase: $phase, agent: $agent, tokens_in: $tin, tokens_out: $tout}' 2>/dev/null) || exit 0
54
54
 
55
- echo "$entry" >> "$LOG_FILE" 2>/dev/null || true
55
+ printf '%s\n' "$entry" >> "$LOG_FILE" 2>/dev/null || true
56
56
 
57
57
  exit 0
@@ -6,13 +6,18 @@
6
6
  # 규칙 amnesia가 재발한다. 기존 PostCompact 훅(hooks.json)은 "prompt" 타입으로 요약 지침만
7
7
  # 재주입할 뿐, CLAUDE.md 원문 전체를 재주입하지 않는다.
8
8
  #
9
- # 배선 이벤트 (Phase 1 판단, #1617):
9
+ # 배선 이벤트 (Phase 1 판단, #1617 / matcher 협소화 #1626):
10
10
  # R006 실측 기준(이 저장소 rules/MUST-agent-design.md) additionalContext 지원 이벤트 목록에
11
11
  # SessionStart는 있고 PostCompact는 없다. 한편 공식 문서(hooks.md, 2026-08-29 재확인)의
12
12
  # SessionStart matcher는 startup / resume / clear / compact 네 가지이며, "compact"는
13
- # auto/manual compact 직후를 가리킨다 — 즉 "session start 전체와 compact 재개 이후 재주입"
14
- # 요구사항은 SessionStart 단일 이벤트(matcher "*")로 전부 커버된다. 별도 PostCompact 배선은
15
- # 불필요 — 공식 문서 이벤트 목록(### 헤더 스캔)에 PostCompact 자체가 없다(PreCompact만 존재).
13
+ # auto/manual compact 직후를 가리킨다. 별도 PostCompact 배선은 불필요 — 공식 문서 이벤트
14
+ # 목록(### 헤더 스캔)에 PostCompact 자체가 없다(PreCompact만 존재).
15
+ #
16
+ # #1626 실측: startup 시점 재주입은 CC의 네이티브 CLAUDE.md 자동 로드와 중복(13,817 B/세션
17
+ # 순수 낭비)이며, resume/clear는 이전 컨텍스트가 그대로 복원되므로 재주입이 불요하다.
18
+ # 원 목적(#1617 — compact 후 규칙 amnesia 방지)은 "compact" 소스만으로 충분히 달성되므로,
19
+ # hooks.json의 matcher를 "*" → "compact"로 좁혔다. 이 스크립트 자체는 source 필터링을
20
+ # 하지 않으므로(아래 source_field는 로그 헤더용) hooks.json matcher가 유일한 게이트다.
16
21
  #
17
22
  # 동작:
18
23
  # stdin JSON의 .source(startup/resume/clear/compact/기타)를 로그 헤더에 포함해
@@ -15,12 +15,12 @@ input=$(cat)
15
15
  # Hash store (PPID-scoped, session-only)
16
16
  HASH_STORE="/tmp/.claude-content-hashes-${PPID}"
17
17
 
18
- tool_name=$(echo "$input" | jq -r '.tool_name // "unknown"')
18
+ tool_name=$(printf '%s\n' "$input" | jq -r '.tool_name // "unknown"')
19
19
 
20
20
  case "$tool_name" in
21
21
  "Read")
22
22
  # Store content hash for the file that was just read
23
- file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
23
+ file_path=$(printf '%s\n' "$input" | jq -r '.tool_input.file_path // ""')
24
24
 
25
25
  if [ -n "$file_path" ] && [ -f "$file_path" ]; then
26
26
  content_hash=$(md5 -q "$file_path" 2>/dev/null || md5sum "$file_path" 2>/dev/null | cut -d' ' -f1 || echo "unknown")
@@ -43,7 +43,7 @@ case "$tool_name" in
43
43
 
44
44
  "Edit")
45
45
  # Validate that file hasn't changed since last Read
46
- file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
46
+ file_path=$(printf '%s\n' "$input" | jq -r '.tool_input.file_path // ""')
47
47
 
48
48
  if [ -n "$file_path" ] && [ -f "$HASH_STORE" ] && [ -f "$file_path" ]; then
49
49
  stored_hash=$(grep "\"path\":\"${file_path}\"" "$HASH_STORE" 2>/dev/null | tail -1 | jq -r '.hash // ""' 2>/dev/null || echo "")
@@ -71,5 +71,5 @@ if [ -f "$HASH_STORE" ]; then
71
71
  fi
72
72
 
73
73
  # Always pass through
74
- echo "$input"
74
+ printf '%s\n' "$input"
75
75
  exit 0
@@ -33,7 +33,7 @@ read_count=${read_count:-0}
33
33
  agent_count=${agent_count:-0}
34
34
 
35
35
  # Determine tool type from input
36
- TOOL=$(echo "$input" | jq -r '.tool_name // ""' 2>/dev/null || echo "")
36
+ TOOL=$(printf '%s\n' "$input" | jq -r '.tool_name // ""' 2>/dev/null || echo "")
37
37
  tool_count=$((tool_count + 1))
38
38
 
39
39
  case "$TOOL" in
@@ -95,7 +95,7 @@ if [ "$tool_count" -ge "$THRESHOLD" ] && [ ! -f "$BLOCK_FILE" ]; then
95
95
  HOOK_MS=$(( (HOOK_END - HOOK_START) / 1000000 ))
96
96
  echo "[Hook Perf] $(basename "$0"): ${HOOK_MS}ms" >> "/tmp/.claude-hook-perf-${PPID}.log"
97
97
  fi
98
- echo "$input"
98
+ printf '%s\n' "$input"
99
99
  exit 2
100
100
  fi
101
101
 
@@ -112,7 +112,7 @@ if [ "$tool_count" -gt 0 ] && [ $((tool_count % 50)) -eq 0 ]; then
112
112
  fi
113
113
 
114
114
  # Pass through
115
- echo "$input"
115
+ printf '%s\n' "$input"
116
116
  HOOK_END=$(date +%s%N 2>/dev/null || echo 0)
117
117
  if [ "$HOOK_START" != "0" ] && [ "$HOOK_END" != "0" ]; then
118
118
  HOOK_MS=$(( (HOOK_END - HOOK_START) / 1000000 ))
@@ -15,19 +15,19 @@ COST_CAP="${CLAUDE_COST_CAP:-5.00}"
15
15
 
16
16
  # Check if cost data is available
17
17
  if [ ! -f "$COST_FILE" ]; then
18
- echo "$input"
18
+ printf '%s\n' "$input"
19
19
  exit 0
20
20
  fi
21
21
 
22
22
  # TSV: cost_usd, ctx_pct, timestamp, rl_5h_pct, rl_7d_pct, rl_5h_resets, rl_7d_resets
23
23
  IFS=$'\t' read -r cost_usd ctx_pct timestamp _rl_5h _rl_7d _rl_5h_resets _rl_7d_resets < "$COST_FILE" 2>/dev/null || {
24
- echo "$input"
24
+ printf '%s\n' "$input"
25
25
  exit 0
26
26
  }
27
27
 
28
28
  # Validate cost_usd is a number
29
29
  if ! printf '%f' "$cost_usd" >/dev/null 2>&1; then
30
- echo "$input"
30
+ printf '%s\n' "$input"
31
31
  exit 0
32
32
  fi
33
33
 
@@ -39,7 +39,7 @@ cost_pct=$(echo "scale=0; $cost_usd * 100 / $COST_CAP" | bc 2>/dev/null || echo
39
39
  now=$(date +%s)
40
40
  age=$((now - ${timestamp:-0}))
41
41
  if [ "$age" -gt 60 ]; then
42
- echo "$input"
42
+ printf '%s\n' "$input"
43
43
  exit 0
44
44
  fi
45
45
 
@@ -54,7 +54,7 @@ if [ "$cost_pct" -ge 100 ] && [ "$last_level" != "100" ]; then
54
54
  echo "[Cost Cap] Session cost \$${cost_usd} has reached cap \$${COST_CAP} (${cost_pct}%)" >&2
55
55
  echo "[Cost Cap] Consider wrapping up or increasing CLAUDE_COST_CAP" >&2
56
56
  echo "100" > "$ADVISORY_FILE"
57
- echo "$input"
57
+ printf '%s\n' "$input"
58
58
  exit 2
59
59
  elif [ "$cost_pct" -ge 90 ] && [ "$last_level" != "90" ] && [ "$last_level" != "100" ]; then
60
60
  echo "[Cost Cap] Session cost \$${cost_usd} at 90% of cap \$${COST_CAP}" >&2
@@ -69,5 +69,5 @@ elif [ "$cost_pct" -ge 50 ] && [ -z "$last_level" ]; then
69
69
  fi
70
70
 
71
71
  # Pass through — advisory only
72
- echo "$input"
72
+ printf '%s\n' "$input"
73
73
  exit 0
@@ -3,10 +3,10 @@
3
3
  # Advisory only — provides context hints when cwd changes
4
4
 
5
5
  input=$(cat)
6
- new_cwd=$(echo "$input" | jq -r '.new_cwd // ""' 2>/dev/null)
6
+ new_cwd=$(printf '%s\n' "$input" | jq -r '.new_cwd // ""' 2>/dev/null)
7
7
 
8
8
  if [ -z "$new_cwd" ] || [ ! -d "$new_cwd" ]; then
9
- echo "$input"
9
+ printf '%s\n' "$input"
10
10
  exit 0
11
11
  fi
12
12
 
@@ -33,4 +33,4 @@ if [ -n "$hints" ]; then
33
33
  printf "%b" "$hints" >&2
34
34
  fi
35
35
 
36
- echo "$input"
36
+ printf '%s\n' "$input"
@@ -9,7 +9,7 @@
9
9
 
10
10
  input=$(cat)
11
11
  if command -v jq >/dev/null 2>&1; then
12
- command_text=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null)
12
+ command_text=$(printf '%s\n' "$input" | jq -r '.tool_input.command // ""' 2>/dev/null)
13
13
  elif command -v python3 >/dev/null 2>&1; then
14
14
  command_text=$(INPUT_JSON="$input" python3 - <<'PY' 2>/dev/null
15
15
  import json, os
@@ -57,4 +57,4 @@ if [ -n "$matched" ]; then
57
57
  fi
58
58
 
59
59
  # Always pass through - this hook is advisory only.
60
- echo "$input"
60
+ printf '%s\n' "$input"
@@ -14,7 +14,7 @@ PPID_FILE="/tmp/.claude-task-outcomes-${PPID}"
14
14
 
15
15
  # Only attempt collection if outcome file exists
16
16
  if [ ! -f "$PPID_FILE" ]; then
17
- echo "$input"
17
+ printf '%s\n' "$input"
18
18
  exit 0
19
19
  fi
20
20
 
@@ -42,5 +42,5 @@ if [ -n "$EVAL_CORE" ]; then
42
42
  fi
43
43
 
44
44
  # Always pass through input and exit 0 (advisory only)
45
- echo "$input"
45
+ printf '%s\n' "$input"
46
46
  exit 0