cc-safe-setup 28.4.8 → 28.5.0

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 (150) hide show
  1. package/COOKBOOK.md +143 -0
  2. package/README.md +1 -0
  3. package/examples/auto-approve-docker.sh +1 -0
  4. package/examples/auto-approve-test.sh +1 -0
  5. package/examples/auto-compact-prep.sh +19 -8
  6. package/examples/auto-git-checkpoint.sh +1 -0
  7. package/examples/backup-before-refactor.sh +1 -0
  8. package/examples/branch-naming-convention.sh +1 -0
  9. package/examples/check-accessibility.sh +1 -0
  10. package/examples/check-aria-labels.sh +1 -0
  11. package/examples/check-charset-meta.sh +1 -0
  12. package/examples/check-cookie-flags.sh +1 -0
  13. package/examples/check-cors-config.sh +1 -0
  14. package/examples/check-csp-headers.sh +1 -0
  15. package/examples/check-csrf-protection.sh +1 -0
  16. package/examples/check-dependency-age.sh +1 -0
  17. package/examples/check-dependency-license.sh +1 -0
  18. package/examples/check-dockerfile-best-practice.sh +1 -0
  19. package/examples/check-error-boundaries.sh +1 -0
  20. package/examples/check-error-handling.sh +1 -0
  21. package/examples/check-error-message.sh +1 -0
  22. package/examples/check-error-stack.sh +1 -0
  23. package/examples/check-favicon.sh +1 -0
  24. package/examples/check-git-hooks-compat.sh +1 -0
  25. package/examples/check-gitattributes.sh +1 -0
  26. package/examples/check-https-redirect.sh +1 -0
  27. package/examples/check-input-validation.sh +1 -0
  28. package/examples/check-lang-attribute.sh +1 -0
  29. package/examples/check-npm-scripts-exist.sh +1 -0
  30. package/examples/check-package-size.sh +1 -0
  31. package/examples/check-port-availability.sh +1 -0
  32. package/examples/check-rate-limiting.sh +1 -0
  33. package/examples/check-return-types.sh +1 -0
  34. package/examples/check-semantic-versioning.sh +1 -0
  35. package/examples/check-test-naming.sh +1 -0
  36. package/examples/check-tls-version.sh +1 -0
  37. package/examples/check-viewport-meta.sh +1 -0
  38. package/examples/claudemd-enforcer.sh +1 -0
  39. package/examples/cors-star-warn.sh +1 -0
  40. package/examples/docker-volume-guard.sh +1 -0
  41. package/examples/edit-verify.sh +1 -0
  42. package/examples/env-naming-convention.sh +1 -0
  43. package/examples/env-prod-guard.sh +1 -0
  44. package/examples/env-required-check.sh +1 -0
  45. package/examples/file-size-limit.sh +1 -0
  46. package/examples/git-hook-bypass-guard.sh +1 -0
  47. package/examples/git-merge-conflict-prevent.sh +1 -0
  48. package/examples/git-message-length.sh +1 -0
  49. package/examples/git-submodule-guard.sh +1 -0
  50. package/examples/git-tag-guard.sh +1 -0
  51. package/examples/gitignore-check.sh +1 -0
  52. package/examples/log-level-guard.sh +1 -0
  53. package/examples/max-file-count-guard.sh +1 -0
  54. package/examples/max-file-delete-count.sh +1 -0
  55. package/examples/max-function-length.sh +1 -0
  56. package/examples/max-import-count.sh +1 -0
  57. package/examples/max-subagent-count.sh +1 -0
  58. package/examples/mcp-tool-guard.sh +1 -0
  59. package/examples/no-absolute-import.sh +1 -0
  60. package/examples/no-alert-confirm-prompt.sh +1 -0
  61. package/examples/no-anonymous-default-export.sh +1 -0
  62. package/examples/no-any-type.sh +1 -0
  63. package/examples/no-assignment-in-condition.sh +1 -0
  64. package/examples/no-callback-hell.sh +1 -0
  65. package/examples/no-circular-dependency.sh +1 -0
  66. package/examples/no-cleartext-storage.sh +1 -0
  67. package/examples/no-commented-code.sh +1 -0
  68. package/examples/no-commit-fixup.sh +1 -0
  69. package/examples/no-console-assert.sh +1 -0
  70. package/examples/no-console-error-swallow.sh +1 -0
  71. package/examples/no-console-in-prod.sh +1 -0
  72. package/examples/no-console-log.sh +1 -0
  73. package/examples/no-console-time.sh +1 -0
  74. package/examples/no-curl-upload.sh +1 -0
  75. package/examples/no-dangerouslySetInnerHTML.sh +1 -0
  76. package/examples/no-debug-in-commit.sh +1 -0
  77. package/examples/no-deep-nesting.sh +1 -0
  78. package/examples/no-default-credentials.sh +1 -0
  79. package/examples/no-disabled-test.sh +1 -0
  80. package/examples/no-document-write.sh +1 -0
  81. package/examples/no-empty-function.sh +1 -0
  82. package/examples/no-eval-in-template.sh +1 -0
  83. package/examples/no-eval.sh +1 -0
  84. package/examples/no-exec-user-input.sh +1 -0
  85. package/examples/no-floating-promises.sh +1 -0
  86. package/examples/no-force-install.sh +1 -0
  87. package/examples/no-git-rebase-public.sh +1 -0
  88. package/examples/no-global-state.sh +1 -0
  89. package/examples/no-hardcoded-port.sh +1 -0
  90. package/examples/no-hardcoded-url.sh +1 -0
  91. package/examples/no-helmet-missing.sh +1 -0
  92. package/examples/no-http-without-https.sh +1 -0
  93. package/examples/no-inline-style.sh +1 -0
  94. package/examples/no-innerhtml.sh +1 -0
  95. package/examples/no-install-global.sh +1 -0
  96. package/examples/no-jwt-in-url.sh +1 -0
  97. package/examples/no-large-commit.sh +1 -0
  98. package/examples/no-localhost-expose.sh +1 -0
  99. package/examples/no-magic-number.sh +1 -0
  100. package/examples/no-md5-sha1.sh +1 -0
  101. package/examples/no-mixed-line-endings.sh +1 -0
  102. package/examples/no-mutation-in-reducer.sh +1 -0
  103. package/examples/no-nested-ternary.sh +1 -0
  104. package/examples/no-network-exfil.sh +1 -0
  105. package/examples/no-open-redirect.sh +1 -0
  106. package/examples/no-package-downgrade.sh +1 -0
  107. package/examples/no-package-lock-edit.sh +1 -0
  108. package/examples/no-path-join-user-input.sh +1 -0
  109. package/examples/no-port-bind.sh +1 -0
  110. package/examples/no-process-exit.sh +1 -0
  111. package/examples/no-prototype-pollution.sh +1 -0
  112. package/examples/no-push-without-ci.sh +1 -0
  113. package/examples/no-raw-password-in-url.sh +1 -0
  114. package/examples/no-root-write.sh +1 -0
  115. package/examples/no-secrets-in-logs.sh +1 -0
  116. package/examples/no-sensitive-log.sh +1 -0
  117. package/examples/no-sleep-in-hooks.sh +1 -0
  118. package/examples/no-string-concat-sql.sh +1 -0
  119. package/examples/no-sudo-guard.sh +1 -0
  120. package/examples/no-sync-fs.sh +1 -0
  121. package/examples/no-todo-in-merge.sh +1 -0
  122. package/examples/no-todo-without-issue.sh +1 -0
  123. package/examples/no-unused-import.sh +1 -0
  124. package/examples/no-var-keyword.sh +1 -0
  125. package/examples/no-wildcard-cors.sh +1 -0
  126. package/examples/no-wildcard-delete.sh +1 -0
  127. package/examples/no-wildcard-import.sh +1 -0
  128. package/examples/no-with-statement.sh +1 -0
  129. package/examples/no-write-outside-src.sh +1 -0
  130. package/examples/no-xml-external-entity.sh +1 -0
  131. package/examples/npm-audit-warn.sh +1 -0
  132. package/examples/npm-publish-guard.sh +1 -0
  133. package/examples/npm-script-injection.sh +1 -0
  134. package/examples/package-json-guard.sh +1 -0
  135. package/examples/pr-description-check.sh +1 -0
  136. package/examples/prefer-const.sh +1 -0
  137. package/examples/prefer-optional-chaining.sh +1 -0
  138. package/examples/rate-limit-guard.sh +1 -0
  139. package/examples/readme-exists-check.sh +1 -0
  140. package/examples/session-state-saver.sh +1 -0
  141. package/examples/session-summary.sh +1 -0
  142. package/examples/skill-gate.sh +1 -0
  143. package/examples/sql-injection-detect.sh +1 -0
  144. package/examples/subagent-budget-guard.sh +1 -0
  145. package/examples/test-before-commit.sh +1 -0
  146. package/examples/timezone-guard.sh +1 -0
  147. package/examples/usage-warn.sh +1 -0
  148. package/examples/write-test-ratio.sh +1 -0
  149. package/index.mjs +1 -1
  150. package/package.json +1 -1
package/COOKBOOK.md ADDED
@@ -0,0 +1,143 @@
1
+ # Cookbook — cc-safe-setup Recipes
2
+
3
+ Real-world recipes for common safety scenarios. Each recipe is a single command.
4
+
5
+ ## Getting Started
6
+
7
+ | I want to... | Command |
8
+ |---|---|
9
+ | Install basic safety | `npx cc-safe-setup` |
10
+ | Maximum protection | `npx cc-safe-setup --shield` |
11
+ | Check my setup | `npx cc-safe-setup --doctor` |
12
+ | See my safety score | `npx cc-safe-setup --audit` |
13
+
14
+ ## Blocking Dangerous Commands
15
+
16
+ ### Block rm -rf on home/root
17
+ Already included in the default install. To verify:
18
+ ```bash
19
+ npx cc-safe-setup --simulate "rm -rf ~"
20
+ # Expected: BLOCKED
21
+ ```
22
+
23
+ ### Block database wipes
24
+ ```bash
25
+ npx cc-safe-setup --install-example block-database-wipe
26
+ ```
27
+ Blocks: `prisma migrate reset`, `rails db:drop`, `DROP TABLE`, etc.
28
+
29
+ ### Block npm publish accidents
30
+ ```bash
31
+ npx cc-safe-setup --install-example npm-publish-guard
32
+ ```
33
+
34
+ ## Auto-Approving Safe Commands
35
+
36
+ ### Approve read-only commands (cat, ls, grep)
37
+ ```bash
38
+ npx cc-safe-setup --install-example auto-approve-readonly
39
+ ```
40
+
41
+ ### Approve test runners
42
+ ```bash
43
+ npx cc-safe-setup --install-example auto-approve-test
44
+ ```
45
+ Covers: `npm test`, `pytest`, `go test`, `cargo test`, `jest`, `vitest`
46
+
47
+ ### Approve git read commands (status, log, diff)
48
+ ```bash
49
+ npx cc-safe-setup --install-example auto-approve-git-read
50
+ ```
51
+
52
+ ## File Protection
53
+
54
+ ### Protect .env files from edits
55
+ ```bash
56
+ npx cc-safe-setup --protect .env
57
+ ```
58
+
59
+ ### Protect CLAUDE.md from unauthorized changes
60
+ ```bash
61
+ npx cc-safe-setup --install-example protect-claudemd
62
+ ```
63
+
64
+ ### Protect dotfiles (~/.bashrc, ~/.aws/)
65
+ ```bash
66
+ npx cc-safe-setup --install-example protect-dotfiles
67
+ ```
68
+
69
+ ## YAML Rules (No Coding)
70
+
71
+ Write rules in YAML, compile to hooks:
72
+
73
+ ```yaml
74
+ # rules.yaml
75
+ - block: "rm -rf on root"
76
+ pattern: "rm\s+-rf\s+(\/$|~)"
77
+
78
+ - approve: "read-only commands"
79
+ commands: [cat, ls, grep, head, tail]
80
+
81
+ - protect: ".env"
82
+ ```
83
+
84
+ ```bash
85
+ npx cc-safe-setup --rules rules.yaml
86
+ ```
87
+
88
+ ## Monitoring & Recovery
89
+
90
+ ### Auto-save checkpoint before compaction
91
+ ```bash
92
+ npx cc-safe-setup --install-example auto-compact-prep
93
+ ```
94
+
95
+ ### Track context window usage
96
+ ```bash
97
+ npx cc-safe-setup --install-example compact-reminder
98
+ ```
99
+
100
+ ### Fix hook permissions on Windows/plugins
101
+ ```bash
102
+ npx cc-safe-setup --install-example hook-permission-fixer
103
+ ```
104
+
105
+ ### Prevent tool call loops
106
+ ```bash
107
+ npx cc-safe-setup --install-example response-budget-guard
108
+ ```
109
+
110
+ ## Diagnosing Problems
111
+
112
+ ### Why isn't my hook working?
113
+ ```bash
114
+ npx cc-safe-setup --doctor
115
+ ```
116
+ Checks: jq, settings.json, file existence, permissions, shebangs, exit codes.
117
+
118
+ ### Test a specific hook
119
+ ```bash
120
+ npx cc-safe-setup --test-hook destructive-guard
121
+ ```
122
+
123
+ ### Preview how hooks react to a command
124
+ ```bash
125
+ npx cc-safe-setup --simulate "git push --force origin main"
126
+ ```
127
+
128
+ ## Web Tools
129
+
130
+ All browser-based, nothing leaves your machine:
131
+
132
+ - [Safety Hub](https://yurukusa.github.io/cc-safe-setup/hub.html) — All 23 tools
133
+ - [Validator](https://yurukusa.github.io/cc-safe-setup/validator.html) — Paste settings.json, get score
134
+ - [Permission Checker](https://yurukusa.github.io/cc-safe-setup/permission-checker.html) — Find broken paths
135
+ - [Playground](https://yurukusa.github.io/cc-safe-setup/playground.html) — Write and test hooks
136
+ - [Hook Builder](https://yurukusa.github.io/cc-safe-setup/builder.html) — Generate hooks from English
137
+
138
+ ## Further Reading
139
+
140
+ - [Getting Started](https://yurukusa.github.io/cc-safe-setup/getting-started.html)
141
+ - [Common Mistakes](https://yurukusa.github.io/cc-safe-setup/common-mistakes.html)
142
+ - [Troubleshooting](TROUBLESHOOTING.md)
143
+ - [Settings Reference](SETTINGS_REFERENCE.md)
package/README.md CHANGED
@@ -356,6 +356,7 @@ See [Issue #1](https://github.com/yurukusa/cc-safe-setup/issues/1) for details.
356
356
 
357
357
  ## Learn More
358
358
 
359
+ - [Cookbook](COOKBOOK.md) — 26 practical recipes (block, approve, protect, monitor, diagnose)
359
360
  - [Official Hooks Reference](https://docs.anthropic.com/en/docs/claude-code/hooks) — Claude Code hooks documentation
360
361
  - [Hooks Cookbook](https://github.com/yurukusa/claude-code-hooks/blob/main/COOKBOOK.md) — 25 recipes from real GitHub Issues ([interactive version](https://yurukusa.github.io/claude-code-hooks/))
361
362
  - [Japanese guide (Qiita)](https://qiita.com/yurukusa/items/a9714b33f5d974e8f1e8) — この記事の日本語解説
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
3
4
  [ "$TOOL" != "Bash" ] && exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  if echo "$COMMAND" | grep -qE '^\s*(npm\s+test|npm\s+run\s+test|npx\s+(jest|vitest|mocha|ava|tap|playwright\s+test|cypress\s+run)|yarn\s+test|pnpm\s+test|bun\s+test)\b'; then
@@ -1,3 +1,18 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # auto-compact-prep.sh — Save checkpoint before context compaction
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Tracks tool call count per session. When threshold is reached,
7
+ # saves a checkpoint file with git state so Claude can recover
8
+ # context after automatic compaction.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: ""
11
+ #
12
+ # CONFIG:
13
+ # CC_COMPACT_PREP_THRESHOLD=200 (save checkpoint after N tool calls)
14
+ # ================================================================
15
+
1
16
  INPUT=$(cat)
2
17
  STATE_DIR="${HOME}/.claude"
3
18
  COUNTER_FILE="${STATE_DIR}/session-call-count"
@@ -5,15 +20,11 @@ PREP_FLAG="${STATE_DIR}/compact-prep-done"
5
20
  CHECKPOINT=".claude/pre-compact-checkpoint.md"
6
21
  COUNT=0
7
22
  [ -f "$COUNTER_FILE" ] && COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
8
- if [ "$COUNT" -eq 0 ]; then
9
- COUNT=1
10
- echo "$COUNT" > "$COUNTER_FILE"
11
- else
12
- COUNT=$((COUNT + 1))
13
- echo "$COUNT" > "$COUNTER_FILE"
14
- fi
23
+ COUNT=$((COUNT + 1))
24
+ echo "$COUNT" > "$COUNTER_FILE"
25
+
15
26
  THRESHOLD=${CC_COMPACT_PREP_THRESHOLD:-200}
16
- if (( COUNT >= THRESHOLD )) && [ ! -f "$PREP_FLAG" ]; then
27
+ if [ "$COUNT" -ge "$THRESHOLD" ] && [ ! -f "$PREP_FLAG" ]; then
17
28
  mkdir -p "$(dirname "$CHECKPOINT")" 2>/dev/null
18
29
  BRANCH=$(git branch --show-current 2>/dev/null || echo "?")
19
30
  DIRTY=$(git status --porcelain 2>/dev/null | wc -l)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
3
4
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
4
  CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
4
  CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "<img[^>]+(?!alt=)" && echo "NOTE: img without alt attribute" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "<(button|a|input)[^>]*>" && ! echo "$CONTENT" | grep -q "aria-" && echo "NOTE: Interactive element without ARIA" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -q "<head" && ! echo "$CONTENT" | grep -q "charset" && echo "NOTE: Missing charset meta" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "setCookie|res\.cookie" && ! echo "$CONTENT" | grep -q "secure" && echo "NOTE: Cookie without secure flag" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "cors\(\{.*origin.*true" && echo "NOTE: Permissive CORS config" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "Content-Security-Policy" || (echo "$CONTENT" | grep -q "helmet" && echo "NOTE: Consider adding CSP headers" >&2)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "<form.*method.*POST" && ! echo "$CONTENT" | grep -qE "csrf|_token|csrfmiddleware" && echo "NOTE: Form without CSRF protection" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "NOTE: Run npm outdated to check dependency age" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  COMMAND=$(cat | jq -r ".tool_input.command // empty" 2>/dev/null); echo "$COMMAND" | grep -qE "npm\s+install\s+\w" && echo "NOTE: Check dependency license before adding" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  FILE=$(cat | jq -r '.tool_input.file_path // empty' 2>/dev/null)
2
3
  case "$FILE" in *Dockerfile*) ;; *) exit 0;; esac
3
4
  CONTENT=$(cat | jq -r '.tool_input.new_string // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "class.*extends.*Component|function.*\(\)" && echo "$CONTENT" | grep -q "render" && ! echo "$CONTENT" | grep -q "ErrorBoundary" && echo "NOTE: Consider adding ErrorBoundary" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "\.then\(" && ! echo "$CONTENT" | grep -q "\.catch" && echo "NOTE: Promise without .catch()" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "throw new Error\(['\"](error|Error|something went wrong)" && echo "NOTE: Generic error message — be specific" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "res\.(send|json)\(.*err\.(stack|message)" && echo "WARNING: Exposing error details to client" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -q "<head" && ! echo "$CONTENT" | grep -q "favicon" && echo "NOTE: Missing favicon link" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
4
  [ -z "$COMMAND" ] && exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  # TRIGGER: PreToolUse MATCHER: "Bash"
2
3
  COMMAND=$(cat | jq -r ".tool_input.command // empty" 2>/dev/null)
3
4
  echo "$COMMAND" | grep -qE "git\s+add.*\.(zip|tar|bin|exe)" && [ ! -f ".gitattributes" ] && echo "NOTE: Binary file without .gitattributes LFS config" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "http://" && echo "$CONTENT" | grep -q "redirect" && ! echo "$CONTENT" | grep -q "https" && echo "NOTE: HTTP redirect without HTTPS" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "req\.(body|query|params)\.\w+" && ! echo "$CONTENT" | grep -qE "validate|sanitize|Joi|zod|yup" && echo "NOTE: User input without validation" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "<html[^>]*>" && ! echo "$CONTENT" | grep -q "lang=" && echo "NOTE: Missing lang attribute" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  FILE=$(cat | jq -r ".tool_input.file_path // empty" 2>/dev/null); case "$FILE" in *package.json) ;; *) exit 0;; esac; echo "$CONTENT" | grep -qE "npm run [a-z]+" && echo "NOTE: Verify referenced npm scripts exist" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  FILE=$(cat | jq -r '.tool_input.file_path // empty' 2>/dev/null)
2
3
  case "$FILE" in *package.json) ;; *) exit 0;; esac
3
4
  CONTENT=$(cat | jq -r '.tool_input.new_string // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  COMMAND=$(cat | jq -r ".tool_input.command // empty" 2>/dev/null); echo "$COMMAND" | grep -qE "listen\(|--port|:3000|:8080" && echo "NOTE: Check port availability before starting server" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "app\.(get|post|put|delete)\(" && ! echo "$CONTENT" | grep -q "rateLimit" && echo "NOTE: API endpoint without rate limiting" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "function\s+\w+\([^)]*\)\s*{" && ! echo "$CONTENT" | grep -q ": " && echo "NOTE: Missing return type annotation" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "\"version\":\s*\"[^0-9]" && echo "NOTE: Non-semver version string" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "it\(['\"](test|check|should)\s" && echo "NOTE: Non-descriptive test name" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "TLSv1[^.]|SSLv3" && echo "WARNING: Weak TLS version" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -q "<head" && ! echo "$CONTENT" | grep -q "viewport" && echo "NOTE: Missing viewport meta" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
3
4
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  # PostToolUse matcher: Edit|Write
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  if echo "$COMMAND" | grep -qE '\bdocker\s+volume\s+(rm|prune)\b'; then
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
3
4
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  # TRIGGER: PostToolUse MATCHER: "Edit|Write"
2
3
  CONTENT=$(cat | jq -r ".tool_input.new_string // empty" 2>/dev/null)
3
4
  [ -z "$CONTENT" ] && exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  if echo "$COMMAND" | grep -qiE "(NODE_ENV|RAILS_ENV|FLASK_ENV)=production"; then echo "WARNING: Production env detected in command" >&2; fi
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "process\.env\.\w+\s*\|\|" || echo "$CONTENT" | grep -qE "process\.env\.\w+!" && echo "NOTE: Env var without default — add fallback" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
4
  CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  if echo "$COMMAND" | grep -qE "git\s+(commit|push|merge).*--no-verify"; then echo "WARNING: --no-verify bypasses git hooks" >&2; fi
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
3
4
  [ -z "$CONTENT" ] && exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  echo "$COMMAND" | grep -qE "git\s+commit\s+-m" || exit 0; MSG=$(echo "$COMMAND" | grep -oP "(?<=-m\s[\x27\x22])[^\x27\x22]+"); [ ${#MSG} -lt 10 ] && echo "WARNING: Commit message too short (${#MSG} chars)" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  if echo "$COMMAND" | grep -qE '\bgit\s+submodule\s+(deinit|rm)\b'; then
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  if echo "$COMMAND" | grep -qE 'git\s+tag\s+(-a\s+|-d\s+)?v'; then
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  echo "$COMMAND" | grep -qE '^\s*git\s+add' || exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
3
4
  [ -z "$CONTENT" ] && exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
3
4
  [ -z "$FILE" ] && exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  if echo "$COMMAND" | grep -qE "^\s*rm\b"; then COUNT=$(echo "$COMMAND" | tr " " "\n" | grep -cvE "^-" | head -1); [ "$COUNT" -gt 5 ] && echo "WARNING: Deleting $COUNT files at once" >&2; fi
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  LINES=$(echo "$CONTENT" | wc -l); [ "$LINES" -gt 100 ] && echo "NOTE: Edit adds 100+ lines — consider splitting" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  IMPORTS=$(echo "$CONTENT" | grep -cE "^(import|from|require)" || echo 0); [ "$IMPORTS" -gt 20 ] && echo "NOTE: $IMPORTS imports — consider splitting module" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
3
  [ -z "$COMMAND" ] && exit 0
3
4
  STATE="/tmp/cc-subagent-count"; C=$(cat "$STATE" 2>/dev/null || echo 0); echo $((C+1)) > "$STATE"; [ "$C" -gt 5 ] && echo "WARNING: $C subagents spawned this session" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
3
4
  echo "$TOOL" | grep -q '^mcp__' || exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  if echo "$CONTENT" | grep -qE "from ['\"]/|require\(['\"]/"; then echo "NOTE: Absolute import path detected" >&2; fi
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "\balert\(|\bconfirm\(|\bprompt\(" && echo "WARNING: alert/confirm/prompt in code" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  # TRIGGER: PostToolUse MATCHER: "Edit|Write"
2
3
  CONTENT=$(cat | jq -r ".tool_input.new_string // empty" 2>/dev/null)
3
4
  [ -z "$CONTENT" ] && exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE ": any\b|<any>" && echo "NOTE: TypeScript any detected — add proper types" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "if\s*\([^=]*=[^=]" && echo "NOTE: Assignment in condition (use ===)" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  DEPTH=$(echo "$CONTENT" | grep -c "function\s*("); [ "$DEPTH" -gt 3 ] && echo "NOTE: Possible callback hell ($DEPTH levels)" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  FILE=$(cat | jq -r '.tool_input.file_path // empty' 2>/dev/null)
2
3
  case "$FILE" in *package.json) ;; *) exit 0;; esac
3
4
  CONTENT=$(cat | jq -r '.tool_input.new_string // empty' 2>/dev/null)
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -qE "localStorage\.setItem.*password|sessionStorage.*token" && echo "WARNING: Storing secrets in browser storage" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  COMMENTED=$(echo "$CONTENT" | grep -cE "^\s*(//|#)\s*(if|for|while|function|const|let|var|import|class)" || echo 0); [ "$COMMENTED" -gt 5 ] && echo "NOTE: Large block of commented code — delete or uncomment" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  INPUT=$(cat)
2
3
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
4
  [ -z "$COMMAND" ] && exit 0
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  echo "$CONTENT" | grep -q "console.assert" && echo "NOTE: console.assert in code" >&2
@@ -1,3 +1,4 @@
1
+ #!/bin/bash
1
2
  CONTENT=$(cat | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
2
3
  [ -z "$CONTENT" ] && exit 0
3
4
  if echo "$CONTENT" | grep -qE "catch\s*\([^)]*\)\s*\{[\s\n]*\}|except:[\s\n]*pass"; then echo "WARNING: Empty catch/except block swallows errors" >&2; fi