cc-safe-setup 29.6.32 → 29.6.36

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 (415) hide show
  1. package/COOKBOOK.md +70 -0
  2. package/README.md +6 -2
  3. package/examples/absolute-rule-enforcer.sh +42 -0
  4. package/examples/allow-claude-settings.sh +2 -0
  5. package/examples/allow-git-hooks-dir.sh +2 -0
  6. package/examples/allow-protected-dirs.sh +2 -0
  7. package/examples/allowlist.sh +2 -0
  8. package/examples/ansible-vault-guard.sh +2 -0
  9. package/examples/auto-approve-build.sh +2 -0
  10. package/examples/auto-approve-compound-git.sh +2 -0
  11. package/examples/auto-approve-docker.sh +2 -0
  12. package/examples/auto-approve-git-read.sh +2 -0
  13. package/examples/auto-approve-python.sh +2 -0
  14. package/examples/auto-approve-readonly-tools.sh +2 -0
  15. package/examples/auto-approve-ssh.sh +2 -0
  16. package/examples/auto-approve-test.sh +2 -0
  17. package/examples/auto-checkpoint.sh +2 -0
  18. package/examples/auto-git-checkpoint.sh +2 -0
  19. package/examples/auto-mode-safe-commands.sh +2 -0
  20. package/examples/auto-snapshot.sh +2 -0
  21. package/examples/backup-before-refactor.sh +2 -0
  22. package/examples/banned-command-guard.sh +3 -3
  23. package/examples/bash-domain-allowlist.sh +72 -0
  24. package/examples/bash-safety-auto-deny.sh +56 -0
  25. package/examples/bash-secret-output-detector.sh +68 -0
  26. package/examples/bash-timeout-guard.sh +2 -0
  27. package/examples/bashrc-safety-check.sh +59 -0
  28. package/examples/bg-task-cooldown-guard.sh +46 -0
  29. package/examples/block-database-wipe.sh +2 -0
  30. package/examples/branch-name-check.sh +2 -0
  31. package/examples/branch-naming-convention.sh +2 -0
  32. package/examples/cargo-publish-guard.sh +2 -0
  33. package/examples/check-abort-controller.sh +2 -0
  34. package/examples/check-accessibility.sh +2 -0
  35. package/examples/check-aria-labels.sh +2 -0
  36. package/examples/check-async-await-consistency.sh +2 -0
  37. package/examples/check-before-act-enforcer.sh +47 -0
  38. package/examples/check-charset-meta.sh +2 -0
  39. package/examples/check-cleanup-effect.sh +2 -0
  40. package/examples/check-content-type.sh +2 -0
  41. package/examples/check-controlled-input.sh +2 -0
  42. package/examples/check-cookie-flags.sh +2 -0
  43. package/examples/check-cors-config.sh +2 -0
  44. package/examples/check-csp-headers.sh +2 -0
  45. package/examples/check-csrf-protection.sh +2 -0
  46. package/examples/check-debounce.sh +2 -0
  47. package/examples/check-dependency-age.sh +2 -0
  48. package/examples/check-dependency-license.sh +2 -0
  49. package/examples/check-dockerfile-best-practice.sh +2 -0
  50. package/examples/check-error-boundaries.sh +2 -0
  51. package/examples/check-error-class.sh +2 -0
  52. package/examples/check-error-handling.sh +2 -0
  53. package/examples/check-error-logging.sh +2 -0
  54. package/examples/check-error-message.sh +2 -0
  55. package/examples/check-error-page.sh +2 -0
  56. package/examples/check-error-stack.sh +2 -0
  57. package/examples/check-favicon.sh +2 -0
  58. package/examples/check-form-validation.sh +2 -0
  59. package/examples/check-git-hooks-compat.sh +2 -0
  60. package/examples/check-https-redirect.sh +2 -0
  61. package/examples/check-image-optimization.sh +2 -0
  62. package/examples/check-input-validation.sh +2 -0
  63. package/examples/check-key-prop.sh +2 -0
  64. package/examples/check-lang-attribute.sh +2 -0
  65. package/examples/check-lazy-loading.sh +2 -0
  66. package/examples/check-loading-state.sh +2 -0
  67. package/examples/check-memo-deps.sh +2 -0
  68. package/examples/check-meta-description.sh +2 -0
  69. package/examples/check-npm-scripts-exist.sh +2 -0
  70. package/examples/check-null-check.sh +2 -0
  71. package/examples/check-package-size.sh +2 -0
  72. package/examples/check-pagination.sh +2 -0
  73. package/examples/check-port-availability.sh +2 -0
  74. package/examples/check-promise-all.sh +2 -0
  75. package/examples/check-prop-types.sh +2 -0
  76. package/examples/check-rate-limiting.sh +2 -0
  77. package/examples/check-responsive-design.sh +2 -0
  78. package/examples/check-retry-logic.sh +2 -0
  79. package/examples/check-return-types.sh +2 -0
  80. package/examples/check-semantic-html.sh +2 -0
  81. package/examples/check-semantic-versioning.sh +2 -0
  82. package/examples/check-suspense-fallback.sh +2 -0
  83. package/examples/check-test-naming.sh +2 -0
  84. package/examples/check-timeout-cleanup.sh +2 -0
  85. package/examples/check-tls-version.sh +2 -0
  86. package/examples/check-type-coercion.sh +2 -0
  87. package/examples/check-unsubscribe.sh +2 -0
  88. package/examples/check-viewport-meta.sh +2 -0
  89. package/examples/check-worker-terminate.sh +2 -0
  90. package/examples/checkpoint-tamper-guard.sh +2 -0
  91. package/examples/chmod-guard.sh +2 -0
  92. package/examples/chown-guard.sh +2 -0
  93. package/examples/ci-workflow-guard.sh +59 -0
  94. package/examples/classifier-fallback-allow.sh +2 -0
  95. package/examples/claude-cache-gc.sh +15 -0
  96. package/examples/claudeignore-enforce-guard.sh +60 -0
  97. package/examples/claudemd-enforcer.sh +2 -0
  98. package/examples/claudemd-violation-detector.sh +36 -0
  99. package/examples/clear-command-confirm-guard.sh +21 -0
  100. package/examples/commit-message-check.sh +2 -0
  101. package/examples/compact-blocker.sh +25 -0
  102. package/examples/composer-guard.sh +2 -0
  103. package/examples/compound-command-allow.sh +2 -0
  104. package/examples/consecutive-failure-circuit-breaker.sh +49 -0
  105. package/examples/console-log-count.sh +2 -0
  106. package/examples/context-compact-advisor.sh +16 -0
  107. package/examples/core-file-protect-guard.sh +91 -0
  108. package/examples/cors-star-warn.sh +2 -0
  109. package/examples/credential-exfil-guard.sh +2 -0
  110. package/examples/credential-file-cat-guard.sh +2 -0
  111. package/examples/cron-modification-guard.sh +40 -0
  112. package/examples/cwd-drift-detector.sh +47 -0
  113. package/examples/cwd-project-boundary-guard.sh +50 -0
  114. package/examples/denied-action-retry-guard.sh +41 -0
  115. package/examples/dependency-install-guard.sh +2 -0
  116. package/examples/deploy-guard.sh +2 -0
  117. package/examples/deploy-path-verify-guard.sh +62 -0
  118. package/examples/deployment-verify-guard.sh +81 -0
  119. package/examples/django-migrate-guard.sh +2 -0
  120. package/examples/docker-volume-guard.sh +2 -0
  121. package/examples/dockerfile-latest-guard.sh +2 -0
  122. package/examples/dotenv-commit-guard.sh +44 -0
  123. package/examples/dotenv-example-sync.sh +55 -0
  124. package/examples/dotnet-build-on-edit.sh +2 -0
  125. package/examples/drizzle-migrate-guard.sh +2 -0
  126. package/examples/edit-counter-test-gate.sh +44 -0
  127. package/examples/edit-error-counter.sh +2 -0
  128. package/examples/edit-guard.sh +2 -0
  129. package/examples/edit-old-string-validator.sh +37 -0
  130. package/examples/edit-retry-loop-guard.sh +2 -0
  131. package/examples/edit-verify.sh +2 -0
  132. package/examples/encoding-preserve-guard.sh +34 -0
  133. package/examples/enforce-tests.sh +2 -0
  134. package/examples/env-inherit-guard.sh +2 -0
  135. package/examples/env-inline-secret-guard.sh +36 -0
  136. package/examples/env-prod-guard.sh +2 -0
  137. package/examples/env-required-check.sh +2 -0
  138. package/examples/env-var-check.sh +2 -0
  139. package/examples/expo-eject-guard.sh +2 -0
  140. package/examples/export-overwrite-guard.sh +29 -0
  141. package/examples/file-change-tracker.sh +2 -0
  142. package/examples/file-change-undo-tracker.sh +46 -0
  143. package/examples/file-recycle-bin.sh +48 -0
  144. package/examples/file-size-limit.sh +2 -0
  145. package/examples/five-hundred-milestone.sh +2 -0
  146. package/examples/flask-debug-guard.sh +2 -0
  147. package/examples/gem-push-guard.sh +2 -0
  148. package/examples/git-checkout-safety-guard.sh +2 -0
  149. package/examples/git-config-guard.sh +2 -0
  150. package/examples/git-crypt-worktree-guard.sh +36 -0
  151. package/examples/git-hook-bypass-guard.sh +2 -0
  152. package/examples/git-merge-conflict-prevent.sh +2 -0
  153. package/examples/git-message-length.sh +2 -0
  154. package/examples/git-operations-require-approval.sh +99 -0
  155. package/examples/git-show-flag-sanitizer.sh +41 -0
  156. package/examples/git-stash-before-danger.sh +2 -0
  157. package/examples/git-submodule-guard.sh +2 -0
  158. package/examples/git-tag-guard.sh +2 -0
  159. package/examples/github-actions-secret-guard.sh +59 -0
  160. package/examples/gitignore-check.sh +2 -0
  161. package/examples/gitops-drift-guard.sh +53 -0
  162. package/examples/go-mod-tidy-warn.sh +2 -0
  163. package/examples/hallucination-url-check.sh +2 -0
  164. package/examples/hardcoded-ip-guard.sh +2 -0
  165. package/examples/headless-empty-result-guard.sh +46 -0
  166. package/examples/headless-stop-guard.sh +43 -0
  167. package/examples/helm-install-guard.sh +2 -0
  168. package/examples/issue-draft-redact-guard.sh +45 -0
  169. package/examples/java-compile-on-edit.sh +2 -0
  170. package/examples/k8s-production-guard.sh +77 -0
  171. package/examples/laravel-artisan-guard.sh +2 -0
  172. package/examples/large-file-guard.sh +2 -0
  173. package/examples/line-ending-guard.sh +30 -0
  174. package/examples/log-level-guard.sh +2 -0
  175. package/examples/magic-number-warn.sh +2 -0
  176. package/examples/max-edit-size-guard.sh +2 -0
  177. package/examples/max-file-count-guard.sh +2 -0
  178. package/examples/max-file-delete-count.sh +2 -0
  179. package/examples/max-function-length.sh +2 -0
  180. package/examples/max-import-count.sh +2 -0
  181. package/examples/max-subagent-count.sh +2 -0
  182. package/examples/mcp-orphan-process-guard.sh +39 -0
  183. package/examples/mcp-server-allowlist.sh +45 -0
  184. package/examples/mcp-tool-audit-log.sh +41 -0
  185. package/examples/mcp-tool-guard.sh +2 -0
  186. package/examples/migration-verify-guard.sh +44 -0
  187. package/examples/monorepo-scope-guard.sh +2 -0
  188. package/examples/network-exfil-guard.sh +61 -0
  189. package/examples/network-guard.sh +2 -0
  190. package/examples/nextjs-env-guard.sh +2 -0
  191. package/examples/no-absolute-import.sh +2 -0
  192. package/examples/no-alert-confirm-prompt.sh +2 -0
  193. package/examples/no-any-type.sh +2 -0
  194. package/examples/no-any-typescript.sh +2 -0
  195. package/examples/no-assignment-in-condition.sh +2 -0
  196. package/examples/no-callback-hell.sh +2 -0
  197. package/examples/no-catch-all-route.sh +2 -0
  198. package/examples/no-circular-dependency.sh +2 -0
  199. package/examples/no-class-in-functional.sh +2 -0
  200. package/examples/no-cleartext-storage.sh +2 -0
  201. package/examples/no-commented-code.sh +2 -0
  202. package/examples/no-commit-fixup.sh +2 -0
  203. package/examples/no-console-assert.sh +2 -0
  204. package/examples/no-console-error-swallow.sh +2 -0
  205. package/examples/no-console-in-prod.sh +2 -0
  206. package/examples/no-console-log.sh +2 -0
  207. package/examples/no-console-time.sh +2 -0
  208. package/examples/no-cors-wildcard.sh +2 -0
  209. package/examples/no-curl-upload.sh +2 -0
  210. package/examples/no-dangerouslySetInnerHTML.sh +2 -0
  211. package/examples/no-dangling-await.sh +2 -0
  212. package/examples/no-debug-in-commit.sh +2 -0
  213. package/examples/no-deep-nesting.sh +2 -0
  214. package/examples/no-deep-relative-import.sh +2 -0
  215. package/examples/no-default-credentials.sh +2 -0
  216. package/examples/no-deprecated-api.sh +2 -0
  217. package/examples/no-direct-dom-manipulation.sh +2 -0
  218. package/examples/no-disabled-test.sh +2 -0
  219. package/examples/no-document-cookie.sh +2 -0
  220. package/examples/no-document-write.sh +2 -0
  221. package/examples/no-empty-function.sh +2 -0
  222. package/examples/no-eval-in-template.sh +2 -0
  223. package/examples/no-eval-template.sh +2 -0
  224. package/examples/no-eval.sh +2 -0
  225. package/examples/no-exec-user-input.sh +2 -0
  226. package/examples/no-expose-internal-ids.sh +2 -0
  227. package/examples/no-floating-promises.sh +2 -0
  228. package/examples/no-force-install.sh +2 -0
  229. package/examples/no-git-rebase-public.sh +2 -0
  230. package/examples/no-global-state.sh +2 -0
  231. package/examples/no-hardcoded-port.sh +2 -0
  232. package/examples/no-hardcoded-url.sh +2 -0
  233. package/examples/no-helmet-missing.sh +2 -0
  234. package/examples/no-http-url.sh +2 -0
  235. package/examples/no-http-without-https.sh +2 -0
  236. package/examples/no-index-as-key.sh +2 -0
  237. package/examples/no-infinite-scroll-mem.sh +2 -0
  238. package/examples/no-inline-event-handler.sh +2 -0
  239. package/examples/no-inline-handler.sh +2 -0
  240. package/examples/no-inline-style.sh +2 -0
  241. package/examples/no-inline-styles.sh +2 -0
  242. package/examples/no-innerhtml.sh +2 -0
  243. package/examples/no-install-global.sh +2 -0
  244. package/examples/no-jwt-in-url.sh +2 -0
  245. package/examples/no-large-commit.sh +2 -0
  246. package/examples/no-localhost-expose.sh +2 -0
  247. package/examples/no-long-switch.sh +2 -0
  248. package/examples/no-magic-number.sh +2 -0
  249. package/examples/no-md5-sha1.sh +2 -0
  250. package/examples/no-memory-leak-interval.sh +2 -0
  251. package/examples/no-mixed-line-endings.sh +2 -0
  252. package/examples/no-mutation-in-reducer.sh +2 -0
  253. package/examples/no-mutation-observer-leak.sh +2 -0
  254. package/examples/no-nested-subscribe.sh +2 -0
  255. package/examples/no-nested-ternary.sh +2 -0
  256. package/examples/no-network-exfil.sh +2 -0
  257. package/examples/no-new-array-fill.sh +2 -0
  258. package/examples/no-object-freeze-mutation.sh +2 -0
  259. package/examples/no-open-redirect.sh +2 -0
  260. package/examples/no-output-truncation.sh +44 -0
  261. package/examples/no-package-downgrade.sh +2 -0
  262. package/examples/no-package-lock-edit.sh +2 -0
  263. package/examples/no-path-join-user-input.sh +2 -0
  264. package/examples/no-port-bind.sh +2 -0
  265. package/examples/no-process-exit.sh +2 -0
  266. package/examples/no-prototype-pollution.sh +2 -0
  267. package/examples/no-push-without-ci.sh +2 -0
  268. package/examples/no-raw-ref.sh +2 -0
  269. package/examples/no-redundant-fragment.sh +2 -0
  270. package/examples/no-render-in-loop.sh +2 -0
  271. package/examples/no-root-user-docker.sh +2 -0
  272. package/examples/no-root-write.sh +2 -0
  273. package/examples/no-secrets-in-args.sh +2 -0
  274. package/examples/no-secrets-in-logs.sh +2 -0
  275. package/examples/no-sensitive-log.sh +2 -0
  276. package/examples/no-side-effects-in-render.sh +2 -0
  277. package/examples/no-sleep-in-hooks.sh +2 -0
  278. package/examples/no-star-import-python.sh +2 -0
  279. package/examples/no-string-concat-sql.sh +2 -0
  280. package/examples/no-sudo-guard.sh +2 -0
  281. package/examples/no-sync-external-call.sh +2 -0
  282. package/examples/no-sync-fs.sh +2 -0
  283. package/examples/no-table-layout.sh +2 -0
  284. package/examples/no-throw-string.sh +2 -0
  285. package/examples/no-todo-in-merge.sh +2 -0
  286. package/examples/no-todo-in-production.sh +2 -0
  287. package/examples/no-todo-without-issue.sh +2 -0
  288. package/examples/no-triple-slash-ref.sh +2 -0
  289. package/examples/no-unreachable-code.sh +2 -0
  290. package/examples/no-unused-import.sh +2 -0
  291. package/examples/no-unused-state.sh +2 -0
  292. package/examples/no-var-keyword.sh +2 -0
  293. package/examples/no-wildcard-cors.sh +2 -0
  294. package/examples/no-wildcard-import.sh +2 -0
  295. package/examples/no-window-location.sh +2 -0
  296. package/examples/no-with-statement.sh +2 -0
  297. package/examples/no-write-outside-src.sh +2 -0
  298. package/examples/no-xml-external-entity.sh +2 -0
  299. package/examples/notify-waiting.sh +2 -0
  300. package/examples/npm-audit-warn.sh +2 -0
  301. package/examples/npm-publish-guard.sh +2 -0
  302. package/examples/npm-script-injection.sh +2 -0
  303. package/examples/npm-supply-chain-guard.sh +92 -0
  304. package/examples/nuxt-config-guard.sh +2 -0
  305. package/examples/output-secret-mask.sh +2 -0
  306. package/examples/package-json-guard.sh +2 -0
  307. package/examples/parallel-session-guard.sh +2 -0
  308. package/examples/path-traversal-guard.sh +2 -0
  309. package/examples/permission-audit-log.sh +2 -0
  310. package/examples/permission-entry-validator.sh +48 -0
  311. package/examples/permission-pattern-auto-allow.sh +50 -0
  312. package/examples/php-lint-on-edit.sh +2 -0
  313. package/examples/pip-publish-guard.sh +2 -0
  314. package/examples/plain-language-danger-warn.sh +37 -0
  315. package/examples/plan-mode-enforcer.sh +2 -0
  316. package/examples/plugin-process-cleanup.sh +50 -0
  317. package/examples/polyglot-rm-guard.sh +59 -0
  318. package/examples/pr-description-check.sh +2 -0
  319. package/examples/pre-compact-knowledge-save.sh +53 -0
  320. package/examples/pre-compact-transcript-export.sh +85 -0
  321. package/examples/prefer-builtin-tools.sh +2 -0
  322. package/examples/prefer-const.sh +2 -0
  323. package/examples/prefer-dedicated-tools.sh +55 -0
  324. package/examples/prefer-optional-chaining.sh +2 -0
  325. package/examples/prisma-migrate-guard.sh +2 -0
  326. package/examples/prompt-injection-detector.sh +2 -0
  327. package/examples/prompt-length-guard.sh +2 -0
  328. package/examples/protect-dotfiles.sh +2 -0
  329. package/examples/public-repo-push-guard.sh +58 -0
  330. package/examples/push-requires-test-pass-record.sh +2 -0
  331. package/examples/push-requires-test-pass.sh +2 -0
  332. package/examples/rails-migration-guard.sh +2 -0
  333. package/examples/rate-limit-guard.sh +2 -0
  334. package/examples/read-all-files-enforcer.sh +51 -0
  335. package/examples/read-audit-log.sh +34 -0
  336. package/examples/readme-exists-check.sh +2 -0
  337. package/examples/redis-flushall-guard.sh +2 -0
  338. package/examples/rm-safety-net.sh +2 -0
  339. package/examples/role-tool-guard.sh +69 -0
  340. package/examples/ruby-lint-on-edit.sh +2 -0
  341. package/examples/schema-migration-guard.sh +57 -0
  342. package/examples/scope-guard.sh +2 -0
  343. package/examples/secret-file-read-guard.sh +74 -0
  344. package/examples/self-modify-bypass-guard.sh +54 -0
  345. package/examples/sensitive-log-guard.sh +2 -0
  346. package/examples/session-checkpoint.sh +2 -0
  347. package/examples/session-duration-guard.sh +51 -0
  348. package/examples/session-end-logger.sh +57 -0
  349. package/examples/session-error-rate-monitor.sh +65 -0
  350. package/examples/session-health-monitor.sh +61 -0
  351. package/examples/session-memory-watchdog.sh +17 -0
  352. package/examples/session-permission-reset-guard.sh +39 -0
  353. package/examples/session-resume-env-fix.sh +49 -0
  354. package/examples/session-state-saver.sh +2 -0
  355. package/examples/session-summary-stop.sh +2 -0
  356. package/examples/session-summary.sh +2 -0
  357. package/examples/session-token-counter.sh +2 -0
  358. package/examples/settings-auto-backup.sh +53 -0
  359. package/examples/settings-mutation-detector.sh +45 -0
  360. package/examples/shell-wrapper-guard.sh +2 -0
  361. package/examples/skill-gate.sh +2 -0
  362. package/examples/skill-injection-detector.sh +41 -0
  363. package/examples/spec-file-scope-guard.sh +69 -0
  364. package/examples/spring-profile-guard.sh +2 -0
  365. package/examples/sql-injection-detect.sh +2 -0
  366. package/examples/subagent-budget-guard.sh +2 -0
  367. package/examples/subagent-claudemd-inject.sh +45 -0
  368. package/examples/subagent-context-size-guard.sh +26 -0
  369. package/examples/subagent-tool-call-limiter.sh +48 -0
  370. package/examples/svelte-lint-on-edit.sh +2 -0
  371. package/examples/swift-build-on-edit.sh +2 -0
  372. package/examples/symlink-protect.sh +12 -0
  373. package/examples/system-message-workaround.sh +44 -0
  374. package/examples/system-package-guard.sh +2 -0
  375. package/examples/temp-file-cleanup-stop.sh +28 -0
  376. package/examples/temp-file-cleanup.sh +2 -0
  377. package/examples/terminal-state-restore.sh +23 -0
  378. package/examples/test-after-edit.sh +2 -0
  379. package/examples/test-before-commit.sh +13 -14
  380. package/examples/test-before-push.sh +2 -0
  381. package/examples/test-exit-code-verify.sh +2 -0
  382. package/examples/timeout-guard.sh +2 -0
  383. package/examples/timezone-guard.sh +2 -0
  384. package/examples/tmp-output-size-guard.sh +46 -0
  385. package/examples/todo-check.sh +2 -0
  386. package/examples/todo-deadline-warn.sh +48 -0
  387. package/examples/token-budget-per-task.sh +55 -0
  388. package/examples/token-spike-alert.sh +51 -0
  389. package/examples/token-usage-tracker.sh +14 -0
  390. package/examples/turbo-cache-guard.sh +2 -0
  391. package/examples/uncommitted-changes-stop.sh +2 -0
  392. package/examples/uncommitted-work-shield.sh +37 -0
  393. package/examples/usage-warn.sh +2 -0
  394. package/examples/verify-before-commit.sh +2 -0
  395. package/examples/virtual-cwd-helper.sh +40 -0
  396. package/examples/vue-lint-on-edit.sh +2 -0
  397. package/examples/webfetch-domain-allow.sh +96 -0
  398. package/examples/worktree-delete-guard.sh +43 -0
  399. package/examples/worktree-memory-guard.sh +47 -0
  400. package/examples/worktree-path-validator.sh +42 -0
  401. package/examples/worktree-project-unify.sh +19 -0
  402. package/examples/worktree-unmerged-guard.sh +2 -0
  403. package/examples/write-overwrite-confirm.sh +40 -0
  404. package/examples/write-secret-guard.sh +2 -0
  405. package/examples/write-shrink-guard.sh +46 -0
  406. package/examples/write-test-ratio.sh +2 -0
  407. package/index.mjs +631 -138
  408. package/package.json +2 -2
  409. package/scripts/generate-categories.mjs +206 -0
  410. package/scripts.json +4 -1
  411. package/test.sh.new_tests +0 -0
  412. package/test.sh.patch +0 -0
  413. package/tests/test-core-file-protect-guard.sh +73 -0
  414. package/tests/test-deployment-verify-guard.sh +74 -0
  415. package/tests/test-git-operations-require-approval.sh +65 -0
@@ -0,0 +1,14 @@
1
+ INPUT=$(cat)
2
+ TRANSCRIPT=$(ls -t ~/.claude/projects/*/sessions/*/transcript.jsonl 2>/dev/null | head -1)
3
+ [ -f "$TRANSCRIPT" ] || exit 0
4
+ USAGE=$(tail -20 "$TRANSCRIPT" | grep -o '"usage":{[^}]*}' | tail -1)
5
+ if [ -n "$USAGE" ]; then
6
+ IN=$(echo "$USAGE" | grep -o '"input_tokens":[0-9]*' | grep -o '[0-9]*')
7
+ OUT=$(echo "$USAGE" | grep -o '"output_tokens":[0-9]*' | grep -o '[0-9]*')
8
+ TOTAL=$((${IN:-0} + ${OUT:-0}))
9
+ echo "$(date -Iseconds) in=${IN:-0} out=${OUT:-0} total=$TOTAL" >> ~/.claude/token-usage.log
10
+ BUDGET="${CC_TOKEN_BUDGET:-500000}"
11
+ SUM=$(awk -F'total=' '{sum+=$2}END{print sum+0}' ~/.claude/token-usage.log 2>/dev/null)
12
+ [ "${SUM:-0}" -gt "$BUDGET" ] && echo "Token usage: ~$SUM (budget: $BUDGET)" >&2
13
+ fi
14
+ exit 0
@@ -16,6 +16,8 @@
16
16
  # }
17
17
  # }
18
18
  # ================================================================
19
+ #
20
+ # TRIGGER: PreToolUse MATCHER: "Bash"
19
21
 
20
22
  INPUT=$(cat)
21
23
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -1,3 +1,5 @@
1
+ #
2
+ # TRIGGER: Stop MATCHER: ""
1
3
  INPUT=$(cat)
2
4
  if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
3
5
  exit 0
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # uncommitted-work-shield.sh — Auto-stash before destructive git operations
3
+ #
4
+ # Solves: Git operations destroying uncommitted work (#34327, #33850, #37150).
5
+ # Users lost days of work from git reset/checkout/clean.
6
+ # Unlike uncommitted-work-guard (which blocks), this hook SAVES work.
7
+ #
8
+ # How it works: PreToolUse hook on Bash that detects destructive git
9
+ # commands and auto-stashes uncommitted changes before allowing them.
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "Bash"
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT=$(cat)
17
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
18
+ [ -z "$COMMAND" ] && exit 0
19
+
20
+ # Only check destructive git commands
21
+ DESTRUCTIVE_GIT='git\s+(reset\s+--hard|checkout\s+--|clean\s+-[fd]|stash\s+drop|stash\s+clear)'
22
+
23
+ if ! echo "$COMMAND" | grep -qE "$DESTRUCTIVE_GIT"; then
24
+ exit 0
25
+ fi
26
+
27
+ # Check if there are uncommitted changes
28
+ if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
29
+ # Auto-stash with timestamp
30
+ STASH_MSG="auto-shield-$(date +%Y%m%d-%H%M%S)"
31
+ git stash push -m "$STASH_MSG" 2>/dev/null || true
32
+ echo "SHIELDED: Uncommitted changes saved to stash '$STASH_MSG'." >&2
33
+ echo "Restore with: git stash pop" >&2
34
+ fi
35
+
36
+ # Allow the command to proceed (changes are saved)
37
+ exit 0
@@ -1,4 +1,6 @@
1
1
  #!/bin/bash
2
+ #
3
+ # TRIGGER: PreToolUse MATCHER: "Bash"
2
4
  COUNTER="${HOME}/.claude/session-tool-count"
3
5
  COUNT=$(cat "$COUNTER" 2>/dev/null || echo 0)
4
6
  COUNT=$((COUNT + 1))
@@ -22,6 +22,8 @@
22
22
  # }]
23
23
  # }
24
24
  # }
25
+ #
26
+ # TRIGGER: PreToolUse MATCHER: "Bash"
25
27
 
26
28
  INPUT=$(cat)
27
29
  COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # virtual-cwd-helper.sh — Remind about virtual working directory
3
+ #
4
+ # Solves: Claude Code is bound to the directory where it was
5
+ # spawned. Users can't switch projects mid-session (#3473).
6
+ #
7
+ # How it works: Reads ~/.claude/virtual-cwd file. If set,
8
+ # warns that commands should be prefixed with cd to the
9
+ # virtual CWD. Users can switch directories by updating
10
+ # the file.
11
+ #
12
+ # Setup: echo "/path/to/project" > ~/.claude/virtual-cwd
13
+ #
14
+ # TRIGGER: PreToolUse
15
+ # MATCHER: "Bash"
16
+
17
+ set -euo pipefail
18
+ INPUT=$(cat)
19
+
20
+ VCWD_FILE="${HOME}/.claude/virtual-cwd"
21
+ [ ! -f "$VCWD_FILE" ] && exit 0
22
+
23
+ VCWD=$(cat "$VCWD_FILE" 2>/dev/null)
24
+ [ -z "$VCWD" ] && exit 0
25
+
26
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
27
+ [ -z "$COMMAND" ] && exit 0
28
+
29
+ # Skip if command already starts with cd to the virtual CWD
30
+ if echo "$COMMAND" | grep -q "^cd $VCWD"; then
31
+ exit 0
32
+ fi
33
+
34
+ # Skip cd commands (user is navigating)
35
+ if echo "$COMMAND" | grep -q "^cd "; then
36
+ exit 0
37
+ fi
38
+
39
+ echo "NOTE: Virtual CWD is $VCWD — prefix with: cd $VCWD &&" >&2
40
+ exit 0
@@ -1,3 +1,5 @@
1
+ #
2
+ # TRIGGER: PostToolUse MATCHER: "Edit|Write"
1
3
  INPUT=$(cat)
2
4
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
3
5
  [[ -z "$FILE" ]] && exit 0
@@ -0,0 +1,96 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # webfetch-domain-allow.sh — Auto-approve WebFetch for allowed domains
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # WebFetch(domain:*) in settings.json silently fails to match in
7
+ # many configurations (especially sandbox mode). This hook reads
8
+ # the requested URL, extracts the domain, and auto-approves if
9
+ # it matches a configurable allowlist.
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "WebFetch"
13
+ #
14
+ # DECISION: exit 0 with permissionDecision "allow" = auto-approve
15
+ # exit 0 with empty JSON = passthrough (ask user)
16
+ #
17
+ # CONFIG: Set CC_WEBFETCH_ALLOW_DOMAINS env var (comma-separated)
18
+ # or edit the ALLOWED_DOMAINS array below.
19
+ # Use "*" to allow all domains.
20
+ #
21
+ # See: https://github.com/anthropics/claude-code/issues/9329
22
+ # ================================================================
23
+
24
+ INPUT=$(cat)
25
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
26
+
27
+ # Only handle WebFetch
28
+ [[ "$TOOL" != "WebFetch" ]] && exit 0
29
+
30
+ # Extract URL from tool input
31
+ URL=$(echo "$INPUT" | jq -r '.tool_input.url // empty' 2>/dev/null)
32
+ [ -z "$URL" ] && exit 0
33
+
34
+ # Extract domain from URL
35
+ DOMAIN=$(echo "$URL" | sed -E 's|^https?://||' | sed 's|/.*||' | sed 's|:.*||')
36
+ [ -z "$DOMAIN" ] && exit 0
37
+
38
+ # ========================================
39
+ # DOMAIN ALLOWLIST — edit to your needs
40
+ # ========================================
41
+ # Option 1: Environment variable (comma-separated)
42
+ # export CC_WEBFETCH_ALLOW_DOMAINS="docs.anthropic.com,github.com,*.example.com"
43
+ # Option 2: Edit this array directly
44
+ ALLOWED_DOMAINS=(
45
+ "*" # Allow all domains — change to specific domains for tighter control
46
+ # "docs.anthropic.com"
47
+ # "github.com"
48
+ # "*.github.io"
49
+ # "developer.mozilla.org"
50
+ )
51
+
52
+ # Override from env if set
53
+ if [ -n "$CC_WEBFETCH_ALLOW_DOMAINS" ]; then
54
+ IFS=',' read -ra ALLOWED_DOMAINS <<< "$CC_WEBFETCH_ALLOW_DOMAINS"
55
+ fi
56
+
57
+ # Check domain against allowlist
58
+ for pattern in "${ALLOWED_DOMAINS[@]}"; do
59
+ pattern=$(echo "$pattern" | xargs) # trim whitespace
60
+ # Wildcard: allow everything
61
+ if [ "$pattern" = "*" ]; then
62
+ jq -n '{
63
+ hookSpecificOutput: {
64
+ hookEventName: "PreToolUse",
65
+ permissionDecision: "allow"
66
+ }
67
+ }'
68
+ exit 0
69
+ fi
70
+ # Glob pattern: *.example.com matches sub.example.com
71
+ if [[ "$pattern" == \** ]]; then
72
+ suffix="${pattern#\*}"
73
+ if [[ "$DOMAIN" == *"$suffix" ]]; then
74
+ jq -n '{
75
+ hookSpecificOutput: {
76
+ hookEventName: "PreToolUse",
77
+ permissionDecision: "allow"
78
+ }
79
+ }'
80
+ exit 0
81
+ fi
82
+ fi
83
+ # Exact match
84
+ if [ "$DOMAIN" = "$pattern" ]; then
85
+ jq -n '{
86
+ hookSpecificOutput: {
87
+ hookEventName: "PreToolUse",
88
+ permissionDecision: "allow"
89
+ }
90
+ }'
91
+ exit 0
92
+ fi
93
+ done
94
+
95
+ # Not in allowlist — passthrough to normal permission flow
96
+ exit 0
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # worktree-delete-guard.sh — Block git worktree removal
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Prevents one Claude session from deleting a worktree that
7
+ # another session is actively using. Opus 4.6 has been observed
8
+ # removing worktrees during cleanup without checking for
9
+ # concurrent sessions. (#40850)
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "Bash"
13
+ #
14
+ # WHAT IT BLOCKS:
15
+ # - git worktree remove <path>
16
+ # - git worktree prune
17
+ # - rm -rf on worktree directories
18
+ # ================================================================
19
+
20
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
21
+ [ -z "$COMMAND" ] && exit 0
22
+
23
+ # Block explicit worktree removal
24
+ if echo "$COMMAND" | grep -qE 'git\s+worktree\s+(remove|prune)'; then
25
+ echo "BLOCKED: Cannot remove git worktrees — other sessions may depend on them." >&2
26
+ echo "Command: $COMMAND" >&2
27
+ echo "List worktrees first: git worktree list" >&2
28
+ exit 2
29
+ fi
30
+
31
+ # Block rm on worktree paths (if we're in a git repo)
32
+ if git rev-parse --git-dir &>/dev/null; then
33
+ COMMON_DIR=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
34
+ if [ -n "$COMMON_DIR" ]; then
35
+ WORKTREES_DIR="${COMMON_DIR}/worktrees"
36
+ if echo "$COMMAND" | grep -qE "(rm|rmdir)\s+.*worktrees"; then
37
+ echo "BLOCKED: Cannot delete worktree storage directory." >&2
38
+ exit 2
39
+ fi
40
+ fi
41
+ fi
42
+
43
+ exit 0
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ # worktree-memory-guard.sh — Warn when memory path resolves to main worktree
3
+ #
4
+ # Solves: Git worktrees resolving to main worktree's memory directory (#39920).
5
+ # In worktree isolation mode, Claude writes memory to the main repo's
6
+ # .claude/projects/ instead of the worktree's, causing cross-contamination.
7
+ #
8
+ # How it works: PreToolUse hook on Write/Edit that checks if the target
9
+ # path is a memory file (.claude/projects/*/memory/) and warns if
10
+ # the current working directory differs from the resolved path's repo root.
11
+ #
12
+ # TRIGGER: PreToolUse
13
+ # MATCHER: "Write|Edit"
14
+
15
+ set -euo pipefail
16
+
17
+ INPUT=$(cat)
18
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
19
+ [ -z "$FILE" ] && exit 0
20
+
21
+ # Only check memory files
22
+ if ! echo "$FILE" | grep -q '.claude/projects/.*/memory/'; then
23
+ exit 0
24
+ fi
25
+
26
+ # Check if we're in a worktree
27
+ GIT_DIR=$(git rev-parse --git-dir 2>/dev/null || echo "")
28
+ if [ -z "$GIT_DIR" ]; then
29
+ exit 0
30
+ fi
31
+
32
+ # Detect worktree by checking if .git is a file (worktree) vs directory (main)
33
+ if [ -f ".git" ]; then
34
+ # We're in a worktree — check if memory path points to main repo
35
+ MAIN_WORKTREE=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null | sed 's|/.git$||')
36
+ CWD=$(pwd)
37
+
38
+ if [ "$MAIN_WORKTREE" != "$CWD" ] && echo "$FILE" | grep -q "$MAIN_WORKTREE"; then
39
+ echo "WARNING: Memory file resolves to main worktree, not current worktree." >&2
40
+ echo " File: $FILE" >&2
41
+ echo " Main: $MAIN_WORKTREE" >&2
42
+ echo " CWD: $CWD" >&2
43
+ echo " Consider writing to worktree-local memory instead." >&2
44
+ fi
45
+ fi
46
+
47
+ exit 0
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ # worktree-path-validator.sh — Warn when file operations target main workspace instead of worktree
3
+ #
4
+ # Solves: In worktree sessions, Edit/Read/Write tools target
5
+ # files in the main workspace instead of the worktree
6
+ # directory (#36182). This causes edits to the wrong
7
+ # copy of files.
8
+ #
9
+ # How it works: Detects if running in a worktree (git rev-parse
10
+ # --git-common-dir differs from --git-dir). If so, checks
11
+ # that file_path targets the worktree, not the main workspace.
12
+ #
13
+ # TRIGGER: PreToolUse
14
+ # MATCHER: "Edit|Write|Read"
15
+
16
+ set -euo pipefail
17
+ INPUT=$(cat)
18
+
19
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
20
+ [ -z "$FILE_PATH" ] && exit 0
21
+
22
+ # Check if we're in a worktree
23
+ GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || exit 0
24
+ GIT_COMMON=$(git rev-parse --git-common-dir 2>/dev/null) || exit 0
25
+
26
+ # If git-dir == git-common-dir, we're in the main repo (not a worktree)
27
+ [ "$GIT_DIR" = "$GIT_COMMON" ] && exit 0
28
+
29
+ # We're in a worktree — check that file_path is within the worktree
30
+ WORKTREE_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
31
+ MAIN_ROOT=$(cd "$GIT_COMMON/.." && pwd 2>/dev/null) || exit 0
32
+
33
+ # If file_path starts with the main workspace path instead of worktree
34
+ if echo "$FILE_PATH" | grep -q "^$MAIN_ROOT" && ! echo "$FILE_PATH" | grep -q "^$WORKTREE_ROOT"; then
35
+ echo "WARNING: File path targets main workspace, not this worktree." >&2
36
+ echo " File: $FILE_PATH" >&2
37
+ echo " Worktree: $WORKTREE_ROOT" >&2
38
+ echo " Main repo: $MAIN_ROOT" >&2
39
+ echo " Consider using: ${FILE_PATH/$MAIN_ROOT/$WORKTREE_ROOT}" >&2
40
+ fi
41
+
42
+ exit 0
@@ -0,0 +1,19 @@
1
+ MAIN_TREE=$(git worktree list --porcelain 2>/dev/null | head -1 | sed 's/worktree //')
2
+ [ -z "$MAIN_TREE" ] && exit 0
3
+ CUR_DIR=$(pwd)
4
+ [ "$CUR_DIR" = "$MAIN_TREE" ] && exit 0
5
+ to_project_dir() {
6
+ echo "$HOME/.claude/projects/$(echo "$1" | sed 's|/|-|g; s|^-||')"
7
+ }
8
+ MAIN_PROJECT=$(to_project_dir "$MAIN_TREE")
9
+ CUR_PROJECT=$(to_project_dir "$CUR_DIR")
10
+ if [ -d "$MAIN_PROJECT" ] && [ ! -L "$CUR_PROJECT" ]; then
11
+ if [ -d "$CUR_PROJECT" ] && [ -z "$(ls -A "$CUR_PROJECT" 2>/dev/null)" ]; then
12
+ rmdir "$CUR_PROJECT"
13
+ fi
14
+ if [ ! -e "$CUR_PROJECT" ]; then
15
+ ln -s "$MAIN_PROJECT" "$CUR_PROJECT"
16
+ echo "Linked worktree project dir → main repo" >&2
17
+ fi
18
+ fi
19
+ exit 0
@@ -17,6 +17,8 @@
17
17
  # }]
18
18
  # }
19
19
  # }
20
+ #
21
+ # TRIGGER: PreToolUse MATCHER: "Bash"
20
22
 
21
23
  INPUT=$(cat)
22
24
  # jq with python3 fallback (macOS may not have jq)
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # write-overwrite-confirm.sh — Warn when Write tool overwrites large files
3
+ #
4
+ # Solves: Write tool silently replacing large files with new content (#34597).
5
+ # A 500-line file can be overwritten with 10 lines without warning.
6
+ #
7
+ # How it works: PreToolUse hook on Write that compares the new content
8
+ # size against the existing file. If the new content is significantly
9
+ # smaller, warns about potential data loss.
10
+ #
11
+ # TRIGGER: PreToolUse
12
+ # MATCHER: "Write"
13
+
14
+ set -euo pipefail
15
+
16
+ INPUT=$(cat)
17
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
18
+ [ -z "$FILE" ] && exit 0
19
+
20
+ # Skip if file doesn't exist (new file creation)
21
+ [ ! -f "$FILE" ] && exit 0
22
+
23
+ # Get current file size (lines)
24
+ CURRENT_LINES=$(wc -l < "$FILE" 2>/dev/null || echo 0)
25
+
26
+ # Get new content size (approximate from JSON length)
27
+ NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty' 2>/dev/null)
28
+ NEW_LINES=$(echo "$NEW_CONTENT" | wc -l 2>/dev/null || echo 0)
29
+
30
+ # Warn if shrinking by more than 50% and file is > 50 lines
31
+ if [ "$CURRENT_LINES" -gt 50 ] && [ "$NEW_LINES" -gt 0 ]; then
32
+ RATIO=$((NEW_LINES * 100 / CURRENT_LINES))
33
+ if [ "$RATIO" -lt 50 ]; then
34
+ echo "WARNING: File shrinking from $CURRENT_LINES to ~$NEW_LINES lines ($RATIO%)." >&2
35
+ echo "File: $FILE" >&2
36
+ echo "Consider using Edit tool for targeted changes instead of full rewrite." >&2
37
+ fi
38
+ fi
39
+
40
+ exit 0
@@ -24,6 +24,8 @@
24
24
  # }]
25
25
  # }
26
26
  # }
27
+ #
28
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write"
27
29
 
28
30
  INPUT=$(cat)
29
31
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # write-shrink-guard.sh — Block writes that drastically shrink files
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Prevents accidental file truncation. When Claude uses the Write
7
+ # tool, if the new content is <10% of the original file size,
8
+ # it's likely a truncation bug, not an intentional edit.
9
+ #
10
+ # Real case: 31,699-line file truncated to 16 lines, destroying
11
+ # 5 hours of work. (#40807)
12
+ #
13
+ # TRIGGER: PreToolUse
14
+ # MATCHER: "Write"
15
+ #
16
+ # DECISION: exit 2 = block, exit 0 = allow
17
+ # ================================================================
18
+
19
+ INPUT=$(cat)
20
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
21
+ [ "$TOOL" != "Write" ] && exit 0
22
+
23
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
24
+ [ -z "$FILE" ] || [ ! -f "$FILE" ] && exit 0
25
+
26
+ # Get original file size
27
+ OLD_SIZE=$(wc -c < "$FILE" 2>/dev/null || echo 0)
28
+ [ "$OLD_SIZE" -lt 1000 ] && exit 0 # Skip small files
29
+
30
+ # Get new content size
31
+ NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // empty' 2>/dev/null)
32
+ NEW_SIZE=${#NEW_CONTENT}
33
+
34
+ # Calculate ratio
35
+ if [ "$NEW_SIZE" -gt 0 ] && [ "$OLD_SIZE" -gt 0 ]; then
36
+ RATIO=$((NEW_SIZE * 100 / OLD_SIZE))
37
+ if [ "$RATIO" -lt 10 ]; then
38
+ echo "BLOCKED: Write would shrink $(basename "$FILE") from $OLD_SIZE to $NEW_SIZE bytes (${RATIO}% of original)." >&2
39
+ echo "This looks like accidental truncation. Use Edit for targeted changes instead." >&2
40
+ exit 2
41
+ elif [ "$RATIO" -lt 25 ]; then
42
+ echo "WARNING: Write would significantly reduce $(basename "$FILE") from $OLD_SIZE to $NEW_SIZE bytes (${RATIO}%)." >&2
43
+ fi
44
+ fi
45
+
46
+ exit 0
@@ -1,4 +1,6 @@
1
1
  #!/bin/bash
2
+ #
3
+ # TRIGGER: PreToolUse MATCHER: "Bash"
2
4
  COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
3
5
  [ -z "$COMMAND" ] && exit 0
4
6
  echo "$COMMAND" | grep -qE "^\s*git\s+commit" || exit 0; S=$(git diff --cached --name-only 2>/dev/null | grep -cvE "test|spec" || echo 0); T=$(git diff --cached --name-only 2>/dev/null | grep -cE "test|spec" || echo 0); [ "$S" -gt 5 ] && [ "$T" -eq 0 ] && echo "WARNING: $S source files, 0 test files" >&2