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.
- package/COOKBOOK.md +70 -0
- package/README.md +6 -2
- package/examples/absolute-rule-enforcer.sh +42 -0
- package/examples/allow-claude-settings.sh +2 -0
- package/examples/allow-git-hooks-dir.sh +2 -0
- package/examples/allow-protected-dirs.sh +2 -0
- package/examples/allowlist.sh +2 -0
- package/examples/ansible-vault-guard.sh +2 -0
- package/examples/auto-approve-build.sh +2 -0
- package/examples/auto-approve-compound-git.sh +2 -0
- package/examples/auto-approve-docker.sh +2 -0
- package/examples/auto-approve-git-read.sh +2 -0
- package/examples/auto-approve-python.sh +2 -0
- package/examples/auto-approve-readonly-tools.sh +2 -0
- package/examples/auto-approve-ssh.sh +2 -0
- package/examples/auto-approve-test.sh +2 -0
- package/examples/auto-checkpoint.sh +2 -0
- package/examples/auto-git-checkpoint.sh +2 -0
- package/examples/auto-mode-safe-commands.sh +2 -0
- package/examples/auto-snapshot.sh +2 -0
- package/examples/backup-before-refactor.sh +2 -0
- package/examples/banned-command-guard.sh +3 -3
- package/examples/bash-domain-allowlist.sh +72 -0
- package/examples/bash-safety-auto-deny.sh +56 -0
- package/examples/bash-secret-output-detector.sh +68 -0
- package/examples/bash-timeout-guard.sh +2 -0
- package/examples/bashrc-safety-check.sh +59 -0
- package/examples/bg-task-cooldown-guard.sh +46 -0
- package/examples/block-database-wipe.sh +2 -0
- package/examples/branch-name-check.sh +2 -0
- package/examples/branch-naming-convention.sh +2 -0
- package/examples/cargo-publish-guard.sh +2 -0
- package/examples/check-abort-controller.sh +2 -0
- package/examples/check-accessibility.sh +2 -0
- package/examples/check-aria-labels.sh +2 -0
- package/examples/check-async-await-consistency.sh +2 -0
- package/examples/check-before-act-enforcer.sh +47 -0
- package/examples/check-charset-meta.sh +2 -0
- package/examples/check-cleanup-effect.sh +2 -0
- package/examples/check-content-type.sh +2 -0
- package/examples/check-controlled-input.sh +2 -0
- package/examples/check-cookie-flags.sh +2 -0
- package/examples/check-cors-config.sh +2 -0
- package/examples/check-csp-headers.sh +2 -0
- package/examples/check-csrf-protection.sh +2 -0
- package/examples/check-debounce.sh +2 -0
- package/examples/check-dependency-age.sh +2 -0
- package/examples/check-dependency-license.sh +2 -0
- package/examples/check-dockerfile-best-practice.sh +2 -0
- package/examples/check-error-boundaries.sh +2 -0
- package/examples/check-error-class.sh +2 -0
- package/examples/check-error-handling.sh +2 -0
- package/examples/check-error-logging.sh +2 -0
- package/examples/check-error-message.sh +2 -0
- package/examples/check-error-page.sh +2 -0
- package/examples/check-error-stack.sh +2 -0
- package/examples/check-favicon.sh +2 -0
- package/examples/check-form-validation.sh +2 -0
- package/examples/check-git-hooks-compat.sh +2 -0
- package/examples/check-https-redirect.sh +2 -0
- package/examples/check-image-optimization.sh +2 -0
- package/examples/check-input-validation.sh +2 -0
- package/examples/check-key-prop.sh +2 -0
- package/examples/check-lang-attribute.sh +2 -0
- package/examples/check-lazy-loading.sh +2 -0
- package/examples/check-loading-state.sh +2 -0
- package/examples/check-memo-deps.sh +2 -0
- package/examples/check-meta-description.sh +2 -0
- package/examples/check-npm-scripts-exist.sh +2 -0
- package/examples/check-null-check.sh +2 -0
- package/examples/check-package-size.sh +2 -0
- package/examples/check-pagination.sh +2 -0
- package/examples/check-port-availability.sh +2 -0
- package/examples/check-promise-all.sh +2 -0
- package/examples/check-prop-types.sh +2 -0
- package/examples/check-rate-limiting.sh +2 -0
- package/examples/check-responsive-design.sh +2 -0
- package/examples/check-retry-logic.sh +2 -0
- package/examples/check-return-types.sh +2 -0
- package/examples/check-semantic-html.sh +2 -0
- package/examples/check-semantic-versioning.sh +2 -0
- package/examples/check-suspense-fallback.sh +2 -0
- package/examples/check-test-naming.sh +2 -0
- package/examples/check-timeout-cleanup.sh +2 -0
- package/examples/check-tls-version.sh +2 -0
- package/examples/check-type-coercion.sh +2 -0
- package/examples/check-unsubscribe.sh +2 -0
- package/examples/check-viewport-meta.sh +2 -0
- package/examples/check-worker-terminate.sh +2 -0
- package/examples/checkpoint-tamper-guard.sh +2 -0
- package/examples/chmod-guard.sh +2 -0
- package/examples/chown-guard.sh +2 -0
- package/examples/ci-workflow-guard.sh +59 -0
- package/examples/classifier-fallback-allow.sh +2 -0
- package/examples/claude-cache-gc.sh +15 -0
- package/examples/claudeignore-enforce-guard.sh +60 -0
- package/examples/claudemd-enforcer.sh +2 -0
- package/examples/claudemd-violation-detector.sh +36 -0
- package/examples/clear-command-confirm-guard.sh +21 -0
- package/examples/commit-message-check.sh +2 -0
- package/examples/compact-blocker.sh +25 -0
- package/examples/composer-guard.sh +2 -0
- package/examples/compound-command-allow.sh +2 -0
- package/examples/consecutive-failure-circuit-breaker.sh +49 -0
- package/examples/console-log-count.sh +2 -0
- package/examples/context-compact-advisor.sh +16 -0
- package/examples/core-file-protect-guard.sh +91 -0
- package/examples/cors-star-warn.sh +2 -0
- package/examples/credential-exfil-guard.sh +2 -0
- package/examples/credential-file-cat-guard.sh +2 -0
- package/examples/cron-modification-guard.sh +40 -0
- package/examples/cwd-drift-detector.sh +47 -0
- package/examples/cwd-project-boundary-guard.sh +50 -0
- package/examples/denied-action-retry-guard.sh +41 -0
- package/examples/dependency-install-guard.sh +2 -0
- package/examples/deploy-guard.sh +2 -0
- package/examples/deploy-path-verify-guard.sh +62 -0
- package/examples/deployment-verify-guard.sh +81 -0
- package/examples/django-migrate-guard.sh +2 -0
- package/examples/docker-volume-guard.sh +2 -0
- package/examples/dockerfile-latest-guard.sh +2 -0
- package/examples/dotenv-commit-guard.sh +44 -0
- package/examples/dotenv-example-sync.sh +55 -0
- package/examples/dotnet-build-on-edit.sh +2 -0
- package/examples/drizzle-migrate-guard.sh +2 -0
- package/examples/edit-counter-test-gate.sh +44 -0
- package/examples/edit-error-counter.sh +2 -0
- package/examples/edit-guard.sh +2 -0
- package/examples/edit-old-string-validator.sh +37 -0
- package/examples/edit-retry-loop-guard.sh +2 -0
- package/examples/edit-verify.sh +2 -0
- package/examples/encoding-preserve-guard.sh +34 -0
- package/examples/enforce-tests.sh +2 -0
- package/examples/env-inherit-guard.sh +2 -0
- package/examples/env-inline-secret-guard.sh +36 -0
- package/examples/env-prod-guard.sh +2 -0
- package/examples/env-required-check.sh +2 -0
- package/examples/env-var-check.sh +2 -0
- package/examples/expo-eject-guard.sh +2 -0
- package/examples/export-overwrite-guard.sh +29 -0
- package/examples/file-change-tracker.sh +2 -0
- package/examples/file-change-undo-tracker.sh +46 -0
- package/examples/file-recycle-bin.sh +48 -0
- package/examples/file-size-limit.sh +2 -0
- package/examples/five-hundred-milestone.sh +2 -0
- package/examples/flask-debug-guard.sh +2 -0
- package/examples/gem-push-guard.sh +2 -0
- package/examples/git-checkout-safety-guard.sh +2 -0
- package/examples/git-config-guard.sh +2 -0
- package/examples/git-crypt-worktree-guard.sh +36 -0
- package/examples/git-hook-bypass-guard.sh +2 -0
- package/examples/git-merge-conflict-prevent.sh +2 -0
- package/examples/git-message-length.sh +2 -0
- package/examples/git-operations-require-approval.sh +99 -0
- package/examples/git-show-flag-sanitizer.sh +41 -0
- package/examples/git-stash-before-danger.sh +2 -0
- package/examples/git-submodule-guard.sh +2 -0
- package/examples/git-tag-guard.sh +2 -0
- package/examples/github-actions-secret-guard.sh +59 -0
- package/examples/gitignore-check.sh +2 -0
- package/examples/gitops-drift-guard.sh +53 -0
- package/examples/go-mod-tidy-warn.sh +2 -0
- package/examples/hallucination-url-check.sh +2 -0
- package/examples/hardcoded-ip-guard.sh +2 -0
- package/examples/headless-empty-result-guard.sh +46 -0
- package/examples/headless-stop-guard.sh +43 -0
- package/examples/helm-install-guard.sh +2 -0
- package/examples/issue-draft-redact-guard.sh +45 -0
- package/examples/java-compile-on-edit.sh +2 -0
- package/examples/k8s-production-guard.sh +77 -0
- package/examples/laravel-artisan-guard.sh +2 -0
- package/examples/large-file-guard.sh +2 -0
- package/examples/line-ending-guard.sh +30 -0
- package/examples/log-level-guard.sh +2 -0
- package/examples/magic-number-warn.sh +2 -0
- package/examples/max-edit-size-guard.sh +2 -0
- package/examples/max-file-count-guard.sh +2 -0
- package/examples/max-file-delete-count.sh +2 -0
- package/examples/max-function-length.sh +2 -0
- package/examples/max-import-count.sh +2 -0
- package/examples/max-subagent-count.sh +2 -0
- package/examples/mcp-orphan-process-guard.sh +39 -0
- package/examples/mcp-server-allowlist.sh +45 -0
- package/examples/mcp-tool-audit-log.sh +41 -0
- package/examples/mcp-tool-guard.sh +2 -0
- package/examples/migration-verify-guard.sh +44 -0
- package/examples/monorepo-scope-guard.sh +2 -0
- package/examples/network-exfil-guard.sh +61 -0
- package/examples/network-guard.sh +2 -0
- package/examples/nextjs-env-guard.sh +2 -0
- package/examples/no-absolute-import.sh +2 -0
- package/examples/no-alert-confirm-prompt.sh +2 -0
- package/examples/no-any-type.sh +2 -0
- package/examples/no-any-typescript.sh +2 -0
- package/examples/no-assignment-in-condition.sh +2 -0
- package/examples/no-callback-hell.sh +2 -0
- package/examples/no-catch-all-route.sh +2 -0
- package/examples/no-circular-dependency.sh +2 -0
- package/examples/no-class-in-functional.sh +2 -0
- package/examples/no-cleartext-storage.sh +2 -0
- package/examples/no-commented-code.sh +2 -0
- package/examples/no-commit-fixup.sh +2 -0
- package/examples/no-console-assert.sh +2 -0
- package/examples/no-console-error-swallow.sh +2 -0
- package/examples/no-console-in-prod.sh +2 -0
- package/examples/no-console-log.sh +2 -0
- package/examples/no-console-time.sh +2 -0
- package/examples/no-cors-wildcard.sh +2 -0
- package/examples/no-curl-upload.sh +2 -0
- package/examples/no-dangerouslySetInnerHTML.sh +2 -0
- package/examples/no-dangling-await.sh +2 -0
- package/examples/no-debug-in-commit.sh +2 -0
- package/examples/no-deep-nesting.sh +2 -0
- package/examples/no-deep-relative-import.sh +2 -0
- package/examples/no-default-credentials.sh +2 -0
- package/examples/no-deprecated-api.sh +2 -0
- package/examples/no-direct-dom-manipulation.sh +2 -0
- package/examples/no-disabled-test.sh +2 -0
- package/examples/no-document-cookie.sh +2 -0
- package/examples/no-document-write.sh +2 -0
- package/examples/no-empty-function.sh +2 -0
- package/examples/no-eval-in-template.sh +2 -0
- package/examples/no-eval-template.sh +2 -0
- package/examples/no-eval.sh +2 -0
- package/examples/no-exec-user-input.sh +2 -0
- package/examples/no-expose-internal-ids.sh +2 -0
- package/examples/no-floating-promises.sh +2 -0
- package/examples/no-force-install.sh +2 -0
- package/examples/no-git-rebase-public.sh +2 -0
- package/examples/no-global-state.sh +2 -0
- package/examples/no-hardcoded-port.sh +2 -0
- package/examples/no-hardcoded-url.sh +2 -0
- package/examples/no-helmet-missing.sh +2 -0
- package/examples/no-http-url.sh +2 -0
- package/examples/no-http-without-https.sh +2 -0
- package/examples/no-index-as-key.sh +2 -0
- package/examples/no-infinite-scroll-mem.sh +2 -0
- package/examples/no-inline-event-handler.sh +2 -0
- package/examples/no-inline-handler.sh +2 -0
- package/examples/no-inline-style.sh +2 -0
- package/examples/no-inline-styles.sh +2 -0
- package/examples/no-innerhtml.sh +2 -0
- package/examples/no-install-global.sh +2 -0
- package/examples/no-jwt-in-url.sh +2 -0
- package/examples/no-large-commit.sh +2 -0
- package/examples/no-localhost-expose.sh +2 -0
- package/examples/no-long-switch.sh +2 -0
- package/examples/no-magic-number.sh +2 -0
- package/examples/no-md5-sha1.sh +2 -0
- package/examples/no-memory-leak-interval.sh +2 -0
- package/examples/no-mixed-line-endings.sh +2 -0
- package/examples/no-mutation-in-reducer.sh +2 -0
- package/examples/no-mutation-observer-leak.sh +2 -0
- package/examples/no-nested-subscribe.sh +2 -0
- package/examples/no-nested-ternary.sh +2 -0
- package/examples/no-network-exfil.sh +2 -0
- package/examples/no-new-array-fill.sh +2 -0
- package/examples/no-object-freeze-mutation.sh +2 -0
- package/examples/no-open-redirect.sh +2 -0
- package/examples/no-output-truncation.sh +44 -0
- package/examples/no-package-downgrade.sh +2 -0
- package/examples/no-package-lock-edit.sh +2 -0
- package/examples/no-path-join-user-input.sh +2 -0
- package/examples/no-port-bind.sh +2 -0
- package/examples/no-process-exit.sh +2 -0
- package/examples/no-prototype-pollution.sh +2 -0
- package/examples/no-push-without-ci.sh +2 -0
- package/examples/no-raw-ref.sh +2 -0
- package/examples/no-redundant-fragment.sh +2 -0
- package/examples/no-render-in-loop.sh +2 -0
- package/examples/no-root-user-docker.sh +2 -0
- package/examples/no-root-write.sh +2 -0
- package/examples/no-secrets-in-args.sh +2 -0
- package/examples/no-secrets-in-logs.sh +2 -0
- package/examples/no-sensitive-log.sh +2 -0
- package/examples/no-side-effects-in-render.sh +2 -0
- package/examples/no-sleep-in-hooks.sh +2 -0
- package/examples/no-star-import-python.sh +2 -0
- package/examples/no-string-concat-sql.sh +2 -0
- package/examples/no-sudo-guard.sh +2 -0
- package/examples/no-sync-external-call.sh +2 -0
- package/examples/no-sync-fs.sh +2 -0
- package/examples/no-table-layout.sh +2 -0
- package/examples/no-throw-string.sh +2 -0
- package/examples/no-todo-in-merge.sh +2 -0
- package/examples/no-todo-in-production.sh +2 -0
- package/examples/no-todo-without-issue.sh +2 -0
- package/examples/no-triple-slash-ref.sh +2 -0
- package/examples/no-unreachable-code.sh +2 -0
- package/examples/no-unused-import.sh +2 -0
- package/examples/no-unused-state.sh +2 -0
- package/examples/no-var-keyword.sh +2 -0
- package/examples/no-wildcard-cors.sh +2 -0
- package/examples/no-wildcard-import.sh +2 -0
- package/examples/no-window-location.sh +2 -0
- package/examples/no-with-statement.sh +2 -0
- package/examples/no-write-outside-src.sh +2 -0
- package/examples/no-xml-external-entity.sh +2 -0
- package/examples/notify-waiting.sh +2 -0
- package/examples/npm-audit-warn.sh +2 -0
- package/examples/npm-publish-guard.sh +2 -0
- package/examples/npm-script-injection.sh +2 -0
- package/examples/npm-supply-chain-guard.sh +92 -0
- package/examples/nuxt-config-guard.sh +2 -0
- package/examples/output-secret-mask.sh +2 -0
- package/examples/package-json-guard.sh +2 -0
- package/examples/parallel-session-guard.sh +2 -0
- package/examples/path-traversal-guard.sh +2 -0
- package/examples/permission-audit-log.sh +2 -0
- package/examples/permission-entry-validator.sh +48 -0
- package/examples/permission-pattern-auto-allow.sh +50 -0
- package/examples/php-lint-on-edit.sh +2 -0
- package/examples/pip-publish-guard.sh +2 -0
- package/examples/plain-language-danger-warn.sh +37 -0
- package/examples/plan-mode-enforcer.sh +2 -0
- package/examples/plugin-process-cleanup.sh +50 -0
- package/examples/polyglot-rm-guard.sh +59 -0
- package/examples/pr-description-check.sh +2 -0
- package/examples/pre-compact-knowledge-save.sh +53 -0
- package/examples/pre-compact-transcript-export.sh +85 -0
- package/examples/prefer-builtin-tools.sh +2 -0
- package/examples/prefer-const.sh +2 -0
- package/examples/prefer-dedicated-tools.sh +55 -0
- package/examples/prefer-optional-chaining.sh +2 -0
- package/examples/prisma-migrate-guard.sh +2 -0
- package/examples/prompt-injection-detector.sh +2 -0
- package/examples/prompt-length-guard.sh +2 -0
- package/examples/protect-dotfiles.sh +2 -0
- package/examples/public-repo-push-guard.sh +58 -0
- package/examples/push-requires-test-pass-record.sh +2 -0
- package/examples/push-requires-test-pass.sh +2 -0
- package/examples/rails-migration-guard.sh +2 -0
- package/examples/rate-limit-guard.sh +2 -0
- package/examples/read-all-files-enforcer.sh +51 -0
- package/examples/read-audit-log.sh +34 -0
- package/examples/readme-exists-check.sh +2 -0
- package/examples/redis-flushall-guard.sh +2 -0
- package/examples/rm-safety-net.sh +2 -0
- package/examples/role-tool-guard.sh +69 -0
- package/examples/ruby-lint-on-edit.sh +2 -0
- package/examples/schema-migration-guard.sh +57 -0
- package/examples/scope-guard.sh +2 -0
- package/examples/secret-file-read-guard.sh +74 -0
- package/examples/self-modify-bypass-guard.sh +54 -0
- package/examples/sensitive-log-guard.sh +2 -0
- package/examples/session-checkpoint.sh +2 -0
- package/examples/session-duration-guard.sh +51 -0
- package/examples/session-end-logger.sh +57 -0
- package/examples/session-error-rate-monitor.sh +65 -0
- package/examples/session-health-monitor.sh +61 -0
- package/examples/session-memory-watchdog.sh +17 -0
- package/examples/session-permission-reset-guard.sh +39 -0
- package/examples/session-resume-env-fix.sh +49 -0
- package/examples/session-state-saver.sh +2 -0
- package/examples/session-summary-stop.sh +2 -0
- package/examples/session-summary.sh +2 -0
- package/examples/session-token-counter.sh +2 -0
- package/examples/settings-auto-backup.sh +53 -0
- package/examples/settings-mutation-detector.sh +45 -0
- package/examples/shell-wrapper-guard.sh +2 -0
- package/examples/skill-gate.sh +2 -0
- package/examples/skill-injection-detector.sh +41 -0
- package/examples/spec-file-scope-guard.sh +69 -0
- package/examples/spring-profile-guard.sh +2 -0
- package/examples/sql-injection-detect.sh +2 -0
- package/examples/subagent-budget-guard.sh +2 -0
- package/examples/subagent-claudemd-inject.sh +45 -0
- package/examples/subagent-context-size-guard.sh +26 -0
- package/examples/subagent-tool-call-limiter.sh +48 -0
- package/examples/svelte-lint-on-edit.sh +2 -0
- package/examples/swift-build-on-edit.sh +2 -0
- package/examples/symlink-protect.sh +12 -0
- package/examples/system-message-workaround.sh +44 -0
- package/examples/system-package-guard.sh +2 -0
- package/examples/temp-file-cleanup-stop.sh +28 -0
- package/examples/temp-file-cleanup.sh +2 -0
- package/examples/terminal-state-restore.sh +23 -0
- package/examples/test-after-edit.sh +2 -0
- package/examples/test-before-commit.sh +13 -14
- package/examples/test-before-push.sh +2 -0
- package/examples/test-exit-code-verify.sh +2 -0
- package/examples/timeout-guard.sh +2 -0
- package/examples/timezone-guard.sh +2 -0
- package/examples/tmp-output-size-guard.sh +46 -0
- package/examples/todo-check.sh +2 -0
- package/examples/todo-deadline-warn.sh +48 -0
- package/examples/token-budget-per-task.sh +55 -0
- package/examples/token-spike-alert.sh +51 -0
- package/examples/token-usage-tracker.sh +14 -0
- package/examples/turbo-cache-guard.sh +2 -0
- package/examples/uncommitted-changes-stop.sh +2 -0
- package/examples/uncommitted-work-shield.sh +37 -0
- package/examples/usage-warn.sh +2 -0
- package/examples/verify-before-commit.sh +2 -0
- package/examples/virtual-cwd-helper.sh +40 -0
- package/examples/vue-lint-on-edit.sh +2 -0
- package/examples/webfetch-domain-allow.sh +96 -0
- package/examples/worktree-delete-guard.sh +43 -0
- package/examples/worktree-memory-guard.sh +47 -0
- package/examples/worktree-path-validator.sh +42 -0
- package/examples/worktree-project-unify.sh +19 -0
- package/examples/worktree-unmerged-guard.sh +2 -0
- package/examples/write-overwrite-confirm.sh +40 -0
- package/examples/write-secret-guard.sh +2 -0
- package/examples/write-shrink-guard.sh +46 -0
- package/examples/write-test-ratio.sh +2 -0
- package/index.mjs +631 -138
- package/package.json +2 -2
- package/scripts/generate-categories.mjs +206 -0
- package/scripts.json +4 -1
- package/test.sh.new_tests +0 -0
- package/test.sh.patch +0 -0
- package/tests/test-core-file-protect-guard.sh +73 -0
- package/tests/test-deployment-verify-guard.sh +74 -0
- package/tests/test-git-operations-require-approval.sh +65 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "29.6.
|
|
4
|
-
"description": "One command to make Claude Code safe.
|
|
3
|
+
"version": "29.6.36",
|
|
4
|
+
"description": "One command to make Claude Code safe. 634 example hooks + 8 built-in. 56 CLI commands. 11933 tests. Works with Auto Mode.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cc-safe-setup": "index.mjs"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* generate-categories.mjs
|
|
4
|
+
*
|
|
5
|
+
* Scans examples/*.sh to generate the CATEGORIES object for index.mjs --examples.
|
|
6
|
+
*
|
|
7
|
+
* Why: The manual CATEGORIES list falls behind (147 of 634 hooks registered).
|
|
8
|
+
* This script auto-generates from the actual files so --examples always shows all hooks.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node scripts/generate-categories.mjs # preview JSON
|
|
12
|
+
* node scripts/generate-categories.mjs --apply # update index.mjs in-place
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
16
|
+
import { join, dirname } from 'path';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const ROOT = join(__dirname, '..');
|
|
21
|
+
const EXAMPLES_DIR = join(ROOT, 'examples');
|
|
22
|
+
const INDEX_PATH = join(ROOT, 'index.mjs');
|
|
23
|
+
|
|
24
|
+
// ─── Category rules ───
|
|
25
|
+
// Order matters: first match wins. Patterns are tested against the filename (without .sh).
|
|
26
|
+
const CATEGORY_RULES = [
|
|
27
|
+
// Auto-Approve / Permissions (must be first — specific prefixes)
|
|
28
|
+
{ pattern: /^auto-approve-/, category: 'Auto-Approve' },
|
|
29
|
+
{ pattern: /^allow-/, category: 'Auto-Approve' },
|
|
30
|
+
{ pattern: /^permission-/, category: 'Auto-Approve' },
|
|
31
|
+
{ pattern: /^edit-always-allow|^classifier-fallback-allow|^webfetch-domain-allow/, category: 'Auto-Approve' },
|
|
32
|
+
|
|
33
|
+
// Recovery / Session State
|
|
34
|
+
{ pattern: /checkpoint|snapshot|backup|restore|recover|rollback|revert|stash-before|session-handoff|recycle-bin|session-state-saver|session-resume|session-summary|pre-compact-knowledge|pre-compact-transcript|file-change-undo|context-compact-advisor/, category: 'Recovery' },
|
|
35
|
+
|
|
36
|
+
// Monitoring / Observability
|
|
37
|
+
{ pattern: /tracker|counter|monitor|logger|log$|budget-alert|health-monitor|watchdog|quota|usage-cache|token-counter|token-usage|cost-tracker|daily-usage|session-end-logger|session-error-rate|cross-session-error|tool-file-logger|file-change-monitor|file-change-tracker|read-audit|tool-call-rate-limiter|mcp-tool-audit/, category: 'Monitoring' },
|
|
38
|
+
|
|
39
|
+
// Quality / Linting / Code Standards
|
|
40
|
+
{ pattern: /^enforce-|^require-|lint-on-edit|^branch-name|^commit-message|^commit-quality|^commit-scope|^pr-description|^no-console|^no-eval|^no-wildcard|^no-todo-ship|^test-coverage|^ci-skip|^debug-leftover|^typescript-strict|^sensitive-regex|^git-author|^git-blame|^import-cycle|^env-drift|^package-script|^lockfile|^git-lfs|^python-ruff|^typescript-lint|^fact-check|^conflict-marker|^test-deletion|^license-check|^changelog-reminder|^branch-naming|^verify-before-commit|^prefer-const|^prefer-optional|^prefer-builtin|^prefer-dedicated|^max-function-length|^max-import-count|^dockerfile-lint|^dotenv-example|^dotenv-validate|^dotenv-watch|^env-naming|^readme-update|^write-test-ratio|^edit-counter-test|^test-after-edit|^test-before-commit|^test-before-push|^push-requires-test|^git-message-length|^console-log-count|^go-vet|^java-compile|^swift-build|^dotnet-build|^rust-clippy|^edit-old-string-validator/, category: 'Quality' },
|
|
41
|
+
|
|
42
|
+
// UX / Developer Experience
|
|
43
|
+
{ pattern: /^prompt-length|^prompt-injection-detect|^auto-answer|^notify-|^tmp-cleanup|^hook-debug|^loop-detector|^diff-size|^dependency-audit|^binary-file|^stale-branch|^read-before-edit|^compact-reminder|^context-snapshot|^post-compact-restore|^reinject-claudemd|^output-length|^error-memory|^parallel-edit|^large-read|^max-session|^dangling-process|^encoding-guard|^disk-space|^rate-limit-guard|^stale-env|^node-version|^max-file-count|^file-size-limit|^token-budget|^long-session|^cwd-reminder|^virtual-cwd|^hook-stdout|^fish-shell|^system-message|^plan-mode|^plan-repo|^skill-gate|^git-show-flag|^consecutive-error|^consecutive-failure|^session-time-limit|^temp-file-cleanup|^plugin-process|^context-threshold|^five-hundred|^session-budget/, category: 'UX' },
|
|
44
|
+
|
|
45
|
+
// Subagent / MCP Controls
|
|
46
|
+
{ pattern: /^subagent-|^mcp-|^max-concurrent-agents|^max-subagent/, category: 'Agent Controls' },
|
|
47
|
+
|
|
48
|
+
// Safety Guards (broad catch — must be last among specifics)
|
|
49
|
+
{ pattern: /guard|block|protect|^no-|^strip-|^scope-|^allowlist|^strict-allowlist|^secret|^staged-secret|^output-credential|^output-secret|^network|^path-traversal|^timeout|^session-drift|^post-compact-safety|^read-budget|^hook-permission|^response-budget|^deploy|^env-var-check|^env-source|^overwrite|^write-overwrite|^memory-write|^worktree|^docker-prune|^pip-venv|^variable-expansion|^bash-trace|^work-hours|^case-sensitive|^compound-command|^uncommitted|^rm-safety|^absolute-rule|^claudemd-enforcer|^read-all-files|^concurrent-edit|^git-operations-require|^core-file-protect|^api-overload|^api-rate-limit|^api-retry|^npm-script-injection|^dependency-version|^package-lock-frozen|^migration-safety|^git-merge-conflict|^git-index-lock|^bash-domain-allowlist|^claude-cache-gc|^deployment-verify|^max-file-delete/, category: 'Safety Guards' },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// ─── Description extraction ───
|
|
53
|
+
function extractDescription(filepath, filename) {
|
|
54
|
+
const content = readFileSync(filepath, 'utf8');
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
|
|
57
|
+
// Strategy 1: "# filename — description" on line 2 or nearby
|
|
58
|
+
for (let i = 1; i < Math.min(lines.length, 8); i++) {
|
|
59
|
+
const m = lines[i].match(/^#\s*\S+\.sh\s*[—–-]+\s*(.+)/);
|
|
60
|
+
if (m) return m[1].trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Strategy 2: "# ===...=== \n # filename — description"
|
|
64
|
+
for (let i = 1; i < Math.min(lines.length, 10); i++) {
|
|
65
|
+
const m = lines[i].match(/^#\s*\S+\.sh\s*[—–-]+\s*(.+)/);
|
|
66
|
+
if (m) return m[1].trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Strategy 3: First non-empty, non-shebang, non-separator comment line
|
|
70
|
+
for (let i = 1; i < Math.min(lines.length, 15); i++) {
|
|
71
|
+
const line = lines[i];
|
|
72
|
+
if (!line.startsWith('#')) continue;
|
|
73
|
+
const text = line.replace(/^#\s*/, '').trim();
|
|
74
|
+
if (!text) continue;
|
|
75
|
+
if (/^=+$/.test(text)) continue; // separator line
|
|
76
|
+
if (/^TRIGGER:/i.test(text)) continue;
|
|
77
|
+
if (/^MATCHER:/i.test(text)) continue;
|
|
78
|
+
if (/^Usage:/i.test(text)) continue;
|
|
79
|
+
if (/^\{/.test(text)) continue; // JSON
|
|
80
|
+
if (/^"hooks"/.test(text)) continue;
|
|
81
|
+
// If it looks like "PURPOSE:" grab what follows
|
|
82
|
+
const purposeMatch = text.match(/^PURPOSE:\s*(.+)/i);
|
|
83
|
+
if (purposeMatch) return purposeMatch[1].trim();
|
|
84
|
+
// Skip the filename echo
|
|
85
|
+
if (text.startsWith(filename)) continue;
|
|
86
|
+
// Use this line
|
|
87
|
+
return text;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Fallback: derive from filename
|
|
91
|
+
const base = filename.replace('.sh', '');
|
|
92
|
+
return base.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Categorize ───
|
|
96
|
+
function categorize(filename) {
|
|
97
|
+
const base = filename.replace('.sh', '');
|
|
98
|
+
for (const rule of CATEGORY_RULES) {
|
|
99
|
+
if (rule.pattern.test(base)) return rule.category;
|
|
100
|
+
}
|
|
101
|
+
// Fallback heuristics
|
|
102
|
+
if (base.includes('warn') || base.includes('check') || base.includes('detect') || base.includes('verify')) return 'Quality';
|
|
103
|
+
if (base.includes('auto-') || base.includes('approve')) return 'Auto-Approve';
|
|
104
|
+
// Default
|
|
105
|
+
return 'Other';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── Main ───
|
|
109
|
+
const files = readdirSync(EXAMPLES_DIR)
|
|
110
|
+
.filter(f => f.endsWith('.sh'))
|
|
111
|
+
.sort();
|
|
112
|
+
|
|
113
|
+
const categories = {};
|
|
114
|
+
|
|
115
|
+
for (const file of files) {
|
|
116
|
+
const cat = categorize(file);
|
|
117
|
+
const desc = extractDescription(join(EXAMPLES_DIR, file), file);
|
|
118
|
+
if (!categories[cat]) categories[cat] = {};
|
|
119
|
+
categories[cat][file] = desc;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Desired category order
|
|
123
|
+
const ORDER = ['Safety Guards', 'Auto-Approve', 'Quality', 'Monitoring', 'Recovery', 'UX', 'Agent Controls', 'Other'];
|
|
124
|
+
const ordered = {};
|
|
125
|
+
for (const cat of ORDER) {
|
|
126
|
+
if (categories[cat] && Object.keys(categories[cat]).length > 0) {
|
|
127
|
+
ordered[cat] = categories[cat];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Add any categories not in ORDER
|
|
131
|
+
for (const cat of Object.keys(categories)) {
|
|
132
|
+
if (!ordered[cat]) ordered[cat] = categories[cat];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── Generate code ───
|
|
136
|
+
function generateCode(cats) {
|
|
137
|
+
let code = ' const CATEGORIES = {\n';
|
|
138
|
+
const catEntries = Object.entries(cats);
|
|
139
|
+
for (let ci = 0; ci < catEntries.length; ci++) {
|
|
140
|
+
const [cat, hooks] = catEntries[ci];
|
|
141
|
+
code += ` '${cat}': {\n`;
|
|
142
|
+
const hookEntries = Object.entries(hooks);
|
|
143
|
+
for (let hi = 0; hi < hookEntries.length; hi++) {
|
|
144
|
+
const [file, desc] = hookEntries[hi];
|
|
145
|
+
// Escape single quotes in description
|
|
146
|
+
const safeDesc = desc.replace(/'/g, "\\'");
|
|
147
|
+
code += ` '${file}': '${safeDesc}',\n`;
|
|
148
|
+
}
|
|
149
|
+
code += ' },\n';
|
|
150
|
+
}
|
|
151
|
+
code += ' };';
|
|
152
|
+
return code;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const newCode = generateCode(ordered);
|
|
156
|
+
|
|
157
|
+
// Stats
|
|
158
|
+
const totalHooks = Object.values(ordered).reduce((s, c) => s + Object.keys(c).length, 0);
|
|
159
|
+
console.log(`Categories: ${Object.keys(ordered).length}`);
|
|
160
|
+
for (const [cat, hooks] of Object.entries(ordered)) {
|
|
161
|
+
console.log(` ${cat}: ${Object.keys(hooks).length} hooks`);
|
|
162
|
+
}
|
|
163
|
+
console.log(`Total: ${totalHooks} hooks from ${files.length} .sh files`);
|
|
164
|
+
|
|
165
|
+
if (process.argv.includes('--apply')) {
|
|
166
|
+
const src = readFileSync(INDEX_PATH, 'utf8');
|
|
167
|
+
|
|
168
|
+
// Match the CATEGORIES block: " const CATEGORIES = {" ... " };"
|
|
169
|
+
const startMarker = ' const CATEGORIES = {';
|
|
170
|
+
const startIdx = src.indexOf(startMarker);
|
|
171
|
+
if (startIdx === -1) {
|
|
172
|
+
console.error('ERROR: Could not find "const CATEGORIES = {" in index.mjs');
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Find the closing "};" that matches - it's " };" at the same indentation
|
|
177
|
+
let endIdx = -1;
|
|
178
|
+
let depth = 0;
|
|
179
|
+
for (let i = startIdx + startMarker.length; i < src.length; i++) {
|
|
180
|
+
if (src[i] === '{') depth++;
|
|
181
|
+
if (src[i] === '}') {
|
|
182
|
+
if (depth === 0) {
|
|
183
|
+
// Check this is " };"
|
|
184
|
+
endIdx = i;
|
|
185
|
+
// Include the semicolon
|
|
186
|
+
if (src[i + 1] === ';') endIdx = i + 1;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
depth--;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (endIdx === -1) {
|
|
194
|
+
console.error('ERROR: Could not find closing of CATEGORIES block');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const before = src.substring(0, startIdx);
|
|
199
|
+
const after = src.substring(endIdx + 1);
|
|
200
|
+
const updated = before + newCode + after;
|
|
201
|
+
|
|
202
|
+
writeFileSync(INDEX_PATH, updated, 'utf8');
|
|
203
|
+
console.log(`\nUpdated index.mjs: ${totalHooks} hooks in CATEGORIES`);
|
|
204
|
+
} else {
|
|
205
|
+
console.log('\nRun with --apply to update index.mjs');
|
|
206
|
+
}
|
package/scripts.json
CHANGED
|
@@ -6,5 +6,8 @@
|
|
|
6
6
|
"comment-strip": "#!/bin/bash\n# ================================================================\n# comment-strip.sh — Strip bash comments that break permissions\n# ================================================================\n# PURPOSE:\n# Claude Code sometimes adds comments to bash commands like:\n# # Check the diff\n# git diff HEAD~1\n# This breaks permission allowlists (e.g. Bash(git:*)) because\n# the matcher sees \"# Check the diff\" instead of \"git diff\".\n#\n# This hook strips leading comment lines and returns the clean\n# command via updatedInput, so permissions match correctly.\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Bash\"\n#\n# INCIDENT: GitHub Issue #29582 (18 reactions)\n# Users on linux/vscode report that bash comments added by Claude\n# cause permission prompts even when the command is allowlisted.\n#\n# HOW IT WORKS:\n# - Reads the command from tool_input\n# - Strips leading lines that start with #\n# - Strips trailing comments (everything after # on command lines)\n# - Returns updatedInput with the cleaned command\n# - Uses hookSpecificOutput.permissionDecision = \"allow\" only if\n# the command was modified (so it doesn't override other hooks)\n# ================================================================\n\nINPUT=$(cat)\nCOMMAND=$(echo \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null)\n\nif [[ -z \"$COMMAND\" ]]; then\n exit 0\nfi\n\n# Strip leading comment lines and empty lines\nCLEAN=$(echo \"$COMMAND\" | sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d')\n\n# If nothing changed, pass through\nif [[ \"$CLEAN\" == \"$COMMAND\" ]]; then\n exit 0\nfi\n\n# If command is empty after stripping, don't modify\nif [[ -z \"$CLEAN\" ]]; then\n exit 0\nfi\n\n# Return cleaned command via hookSpecificOutput\n# permissionDecision is not set — let the normal permission flow handle it\n# We only modify the input so the permission matcher sees the real command\njq -n --arg cmd \"$CLEAN\" '{\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n updatedInput: {\n command: $cmd\n }\n }\n}'\n",
|
|
7
7
|
"cd-git-allow": "#!/bin/bash\n# ================================================================\n# cd-git-allow.sh — Auto-approve cd+git compound commands\n# ================================================================\n# PURPOSE:\n# Claude Code shows \"Compound commands with cd and git require\n# approval\" for commands like: cd /path && git log\n# This is safe in trusted project directories but causes\n# constant permission prompts.\n#\n# This hook auto-approves cd+git compounds when the git operation\n# is read-only (log, diff, status, branch, show, etc.)\n# Destructive git operations (push, reset, clean) are NOT\n# auto-approved — they still require manual approval.\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Bash\"\n#\n# INCIDENT: GitHub Issue #32985 (9 reactions)\n#\n# WHAT IT AUTO-APPROVES:\n# - cd /path && git log\n# - cd /path && git diff\n# - cd /path && git status\n# - cd /path && git branch\n# - cd /path && git show\n# - cd /path && git rev-parse\n#\n# WHAT IT DOES NOT APPROVE (still prompts):\n# - cd /path && git push\n# - cd /path && git reset --hard\n# - cd /path && git clean\n# - cd /path && git checkout (could discard changes)\n# ================================================================\n\nINPUT=$(cat)\nCOMMAND=$(echo \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null)\n\nif [[ -z \"$COMMAND\" ]]; then\n exit 0\nfi\n\n# Only handle cd + git compounds\nif ! echo \"$COMMAND\" | grep -qE '^\\s*cd\\s+.*&&\\s*git\\s'; then\n exit 0\nfi\n\n# Extract the git subcommand\nGIT_CMD=$(echo \"$COMMAND\" | grep -oP '&&\\s*git\\s+\\K\\S+')\n\n# Read-only git operations — safe to auto-approve\nSAFE_GIT=\"log diff status branch show rev-parse tag remote stash-list describe name-rev\"\n\nfor safe in $SAFE_GIT; do\n if [[ \"$GIT_CMD\" == \"$safe\" ]]; then\n jq -n '{\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"allow\",\n permissionDecisionReason: \"cd+git compound auto-approved (read-only git operation)\"\n }\n }'\n exit 0\n fi\ndone\n\n# Not a read-only git op — let normal permission flow handle it\nexit 0\n",
|
|
8
8
|
"secret-guard": "#!/bin/bash\n# ================================================================\n# secret-guard.sh — Secret/Credential Leak Prevention\n# ================================================================\n# PURPOSE:\n# Prevents accidental exposure of secrets, API keys, and\n# credentials through git commits or shell output.\n#\n# Catches the most common ways secrets leak:\n# - git add .env (committing env files)\n# - git add credentials.json / *.pem / *.key\n# - echo $API_KEY or printenv (exposing secrets in output)\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Bash\"\n#\n# WHAT IT BLOCKS (exit 2):\n# - git add .env / .env.local / .env.production\n# - git add *credentials* / *secret* / *.pem / *.key\n# - git add -A or git add . when .env exists (warns)\n#\n# WHAT IT ALLOWS (exit 0):\n# - git add specific safe files\n# - Reading .env for application use (not committing)\n# - All non-git-add commands\n#\n# CONFIGURATION:\n# CC_SECRET_PATTERNS — colon-separated additional patterns to block\n# default: \".env:.env.local:.env.production:credentials:secret:*.pem:*.key:*.p12\"\n# ================================================================\n\nINPUT=$(cat)\nCOMMAND=$(echo \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null)\n\nif [[ -z \"$COMMAND\" ]]; then\n exit 0\nfi\n\n# --- Check 1: git add of secret files ---\nif echo \"$COMMAND\" | grep -qE '^\\s*git\\s+add'; then\n # Direct .env file staging\n if echo \"$COMMAND\" | grep -qiE 'git\\s+add\\s+.*\\.env(\\s|$|\\.|/)'; then\n echo \"BLOCKED: Attempted to stage .env file.\" >&2\n echo \"\" >&2\n echo \"Command: $COMMAND\" >&2\n echo \"\" >&2\n echo \".env files contain secrets and should never be committed.\" >&2\n echo \"Add .env to .gitignore instead.\" >&2\n exit 2\n fi\n\n # Credential/key files\n if echo \"$COMMAND\" | grep -qiE 'git\\s+add\\s+.*(credentials|\\.pem|\\.key|\\.p12|\\.pfx|id_rsa|id_ed25519)'; then\n echo \"BLOCKED: Attempted to stage credential/key file.\" >&2\n echo \"\" >&2\n echo \"Command: $COMMAND\" >&2\n echo \"\" >&2\n echo \"Key and credential files should never be committed to git.\" >&2\n echo \"Add them to .gitignore instead.\" >&2\n exit 2\n fi\n\n # git add -A or git add . when .env exists — warn but check\n if echo \"$COMMAND\" | grep -qE 'git\\s+add\\s+(-A|--all|\\.)(\\s|$)'; then\n # Check if .env exists in the current or project directory\n if [ -f \".env\" ] || [ -f \".env.local\" ] || [ -f \".env.production\" ]; then\n echo \"BLOCKED: 'git add .' with .env file present.\" >&2\n echo \"\" >&2\n echo \"Command: $COMMAND\" >&2\n echo \"\" >&2\n echo \"An .env file exists in this directory. 'git add .' would stage it.\" >&2\n echo \"Add specific files instead: git add src/ lib/ package.json\" >&2\n echo \"Or add .env to .gitignore first.\" >&2\n exit 2\n fi\n fi\nfi\n\nexit 0\n",
|
|
9
|
-
"api-error-alert": "#!/bin/bash\nINPUT=$(cat)\nREASON=$(echo \"$INPUT\" | jq -r '.stop_reason // \"unknown\"' 2>/dev/null)\nHOOK_EVENT=$(echo \"$INPUT\" | jq -r '.hook_event_name // \"\"' 2>/dev/null)\nif [[ \"$REASON\" == \"user\" || \"$REASON\" == \"normal\" || -z \"$REASON\" ]]; then\n exit 0\nfi\nLOG=\"${CC_ERROR_ALERT_LOG:-$HOME/.claude/session-errors.log}\"\nMISSION=\"${CC_CONTEXT_MISSION_FILE:-$HOME/mission.md}\"\nTS=$(date -Iseconds)\nmkdir -p \"$(dirname \"$LOG\")\" 2>/dev/null\necho \"[$TS] Session stopped: reason=$REASON event=$HOOK_EVENT\" >> \"$LOG\"\nif [ -z \"$WSL_DISTRO_NAME\" ]; then\n notify-send \"Claude Code\" \"Session stopped: $REASON\" 2>/dev/null || true\n osascript -e \"display notification \\\"Session stopped: $REASON\\\" with title \\\"Claude Code\\\"\" 2>/dev/null || true\nelse\n powershell.exe -Command \"Write-Host 'Claude Code: Session stopped - $REASON'\" 2>/dev/null || true\nfi\nexit 0\n"
|
|
9
|
+
"api-error-alert": "#!/bin/bash\nINPUT=$(cat)\nREASON=$(echo \"$INPUT\" | jq -r '.stop_reason // \"unknown\"' 2>/dev/null)\nHOOK_EVENT=$(echo \"$INPUT\" | jq -r '.hook_event_name // \"\"' 2>/dev/null)\nif [[ \"$REASON\" == \"user\" || \"$REASON\" == \"normal\" || -z \"$REASON\" ]]; then\n exit 0\nfi\nLOG=\"${CC_ERROR_ALERT_LOG:-$HOME/.claude/session-errors.log}\"\nMISSION=\"${CC_CONTEXT_MISSION_FILE:-$HOME/mission.md}\"\nTS=$(date -Iseconds)\nmkdir -p \"$(dirname \"$LOG\")\" 2>/dev/null\necho \"[$TS] Session stopped: reason=$REASON event=$HOOK_EVENT\" >> \"$LOG\"\nif [ -z \"$WSL_DISTRO_NAME\" ]; then\n notify-send \"Claude Code\" \"Session stopped: $REASON\" 2>/dev/null || true\n osascript -e \"display notification \\\"Session stopped: $REASON\\\" with title \\\"Claude Code\\\"\" 2>/dev/null || true\nelse\n powershell.exe -Command \"Write-Host 'Claude Code: Session stopped - $REASON'\" 2>/dev/null || true\nfi\nexit 0\n",
|
|
10
|
+
"clear-command-confirm-guard": "#!/bin/bash\n# clear-command-confirm-guard.sh — Block accidental /clear command\n#\n# Solves: /clear destroys all conversation context with zero\n# confirmation. Prefix matching means /c + Enter can\n# accidentally trigger /clear instead of /commit or /compact (#40931).\n#\n# How it works: Blocks /clear entirely. Use /compact to reduce\n# context without losing it.\n#\n# TRIGGER: UserPromptSubmit\n# MATCHER: \"^/clear$\"\n\nINPUT=$(cat)\nPROMPT=$(echo \"$INPUT\" | jq -r '.prompt // empty' 2>/dev/null)\n\nif echo \"$PROMPT\" | grep -qE '^/clear$'; then\n echo \"BLOCKED: /clear permanently destroys all context. Use /compact instead to reduce context safely.\" >&2\n exit 2\nfi\nexit 0\n",
|
|
11
|
+
"claudemd-violation-detector": "#!/bin/bash\n# claudemd-violation-detector.sh — Remind critical CLAUDE.md rules after tool use\n#\n# Solves: Claude ignores CLAUDE.md instructions, especially after\n# context compaction or in long sessions (#40930).\n#\n# How it works: After each tool use, extracts and prints\n# critical rules (ABSOLUTE/MUST NEVER/NEVER/禁止) from CLAUDE.md\n# as a reminder. Runs every N tool calls to avoid noise.\n#\n# TRIGGER: PostToolUse\n# MATCHER: \"\"\n\nset -euo pipefail\n\n# Rate limit: only remind every 20 tool calls\nCOUNTER_FILE=\"/tmp/claudemd-reminder-counter\"\nCOUNT=$(cat \"$COUNTER_FILE\" 2>/dev/null || echo \"0\")\nCOUNT=$((COUNT + 1))\necho \"$COUNT\" > \"$COUNTER_FILE\"\n[ $((COUNT % 20)) -ne 0 ] && exit 0\n\n# Find CLAUDE.md\nCLAUDEMD=\"\"\nfor candidate in \"CLAUDE.md\" \".claude/CLAUDE.md\" \"../CLAUDE.md\"; do\n [ -f \"$candidate\" ] && CLAUDEMD=\"$candidate\" && break\ndone\n[ -z \"$CLAUDEMD\" ] && exit 0\n\n# Extract critical rules\nRULES=$(grep -iE '(ABSOLUTE|MUST NEVER|NEVER DO|禁止|絶対)' \"$CLAUDEMD\" 2>/dev/null | head -5 || true)\n[ -z \"$RULES\" ] && exit 0\n\necho \"📋 CLAUDE.md critical rules reminder:\" >&2\necho \"$RULES\" >&2\nexit 0\n",
|
|
12
|
+
"subagent-context-size-guard": "#!/bin/bash\n# subagent-context-size-guard.sh — Warn on thin subagent prompts\n#\n# Solves: Subagents get spawned with minimal context, leading to\n# poor results because they lack necessary background (#40929).\n# The parent agent assumes shared context, but each subagent\n# starts fresh.\n#\n# How it works: Checks Agent tool's prompt parameter length.\n# If under 100 characters, warns that the prompt may be too thin\n# for a standalone agent to work effectively.\n#\n# TRIGGER: PreToolUse\n# MATCHER: \"Agent\"\n\nset -euo pipefail\nINPUT=$(cat)\nPROMPT=$(echo \"$INPUT\" | jq -r '.tool_input.prompt // empty' 2>/dev/null)\n\n[ -z \"$PROMPT\" ] && exit 0\n\nLEN=${#PROMPT}\nif [ \"$LEN\" -lt 100 ]; then\n echo \"WARNING: Agent prompt is only ${LEN} chars. Subagents start with zero context — include enough background for them to work independently.\" >&2\nfi\nexit 0\n"
|
|
10
13
|
}
|
|
File without changes
|
package/test.sh.patch
ADDED
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for core-file-protect-guard.sh
|
|
3
|
+
# Run: bash tests/test-core-file-protect-guard.sh
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
PASS=0
|
|
7
|
+
FAIL=0
|
|
8
|
+
HOOK="$(dirname "$0")/../examples/core-file-protect-guard.sh"
|
|
9
|
+
|
|
10
|
+
test_hook() {
|
|
11
|
+
local input="$1" expected_exit="$2" desc="$3"
|
|
12
|
+
local actual_exit=0
|
|
13
|
+
echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
|
|
14
|
+
if [ "$actual_exit" -eq "$expected_exit" ]; then
|
|
15
|
+
echo " PASS: $desc"
|
|
16
|
+
PASS=$((PASS + 1))
|
|
17
|
+
else
|
|
18
|
+
echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
|
|
19
|
+
FAIL=$((FAIL + 1))
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test_hook_env() {
|
|
24
|
+
local env_var="$1" input="$2" expected_exit="$3" desc="$4"
|
|
25
|
+
local actual_exit=0
|
|
26
|
+
echo "$input" | env "$env_var" bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
|
|
27
|
+
if [ "$actual_exit" -eq "$expected_exit" ]; then
|
|
28
|
+
echo " PASS: $desc"
|
|
29
|
+
PASS=$((PASS + 1))
|
|
30
|
+
else
|
|
31
|
+
echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
|
|
32
|
+
FAIL=$((FAIL + 1))
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
echo "core-file-protect-guard.sh tests"
|
|
37
|
+
echo ""
|
|
38
|
+
|
|
39
|
+
# --- Edit/Write on protected files (exit 2) ---
|
|
40
|
+
test_hook '{"tool_name":"Edit","tool_input":{"file_path":"src/game_rules.py"}}' 2 "Edit rules file blocked"
|
|
41
|
+
test_hook '{"tool_name":"Write","tool_input":{"file_path":"app/config.yaml"}}' 2 "Write config file blocked"
|
|
42
|
+
test_hook '{"tool_name":"Edit","tool_input":{"file_path":"lib/core_engine.py"}}' 2 "Edit core file blocked"
|
|
43
|
+
test_hook '{"tool_name":"Write","tool_input":{"file_path":"settings/core_settings.json"}}' 2 "Write core settings blocked"
|
|
44
|
+
test_hook '{"tool_name":"Edit","tool_input":{"file_path":"data/combat_rules.json"}}' 2 "Edit combat_rules blocked"
|
|
45
|
+
|
|
46
|
+
# --- Edit/Write on non-protected files (exit 0) ---
|
|
47
|
+
test_hook '{"tool_name":"Edit","tool_input":{"file_path":"src/main.py"}}' 0 "Edit non-protected file allowed"
|
|
48
|
+
test_hook '{"tool_name":"Write","tool_input":{"file_path":"src/utils.ts"}}' 0 "Write non-protected file allowed"
|
|
49
|
+
test_hook '{"tool_name":"Edit","tool_input":{"file_path":"README.md"}}' 0 "Edit README allowed"
|
|
50
|
+
|
|
51
|
+
# --- Bash sed/awk on protected files (exit 2) ---
|
|
52
|
+
test_hook '{"tool_name":"Bash","tool_input":{"command":"sed -i s/old/new/ game_rules.txt"}}' 2 "sed -i on rules file blocked"
|
|
53
|
+
test_hook '{"tool_name":"Bash","tool_input":{"command":"awk -i inplace {print} core_logic.py"}}' 2 "awk -i on core file blocked"
|
|
54
|
+
|
|
55
|
+
# --- Bash on non-protected files (exit 0) ---
|
|
56
|
+
test_hook '{"tool_name":"Bash","tool_input":{"command":"sed -i s/x/y/ main.py"}}' 0 "sed -i on non-protected allowed"
|
|
57
|
+
test_hook '{"tool_name":"Bash","tool_input":{"command":"cat config.yaml"}}' 0 "cat (read) on protected name allowed"
|
|
58
|
+
|
|
59
|
+
# --- Custom CC_PROTECTED_FILES ---
|
|
60
|
+
test_hook_env "CC_PROTECTED_FILES=*schema*:*migration*" '{"tool_name":"Edit","tool_input":{"file_path":"db/schema.rb"}}' 2 "custom env: schema file blocked"
|
|
61
|
+
test_hook_env "CC_PROTECTED_FILES=*schema*:*migration*" '{"tool_name":"Edit","tool_input":{"file_path":"src/game_rules.py"}}' 0 "custom env: rules file allowed (not in custom patterns)"
|
|
62
|
+
|
|
63
|
+
# --- Edge cases ---
|
|
64
|
+
test_hook '{"tool_name":"Edit","tool_input":{"file_path":""}}' 0 "empty file path passes"
|
|
65
|
+
test_hook '{"tool_name":"Bash","tool_input":{"command":""}}' 0 "empty command passes"
|
|
66
|
+
test_hook '{"tool_name":"Bash","tool_input":{"command":"echo hello"}}' 0 "echo command allowed"
|
|
67
|
+
test_hook '{"tool_input":{"command":"ls"}}' 0 "no tool_name passes"
|
|
68
|
+
test_hook '{}' 0 "empty input passes"
|
|
69
|
+
|
|
70
|
+
echo ""
|
|
71
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
72
|
+
[ "$FAIL" -gt 0 ] && exit 1
|
|
73
|
+
echo "All tests passed!"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for deployment-verify-guard.sh
|
|
3
|
+
# Run: bash tests/test-deployment-verify-guard.sh
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
PASS=0
|
|
7
|
+
FAIL=0
|
|
8
|
+
HOOK="$(dirname "$0")/../examples/deployment-verify-guard.sh"
|
|
9
|
+
|
|
10
|
+
test_hook() {
|
|
11
|
+
local input="$1" expected_exit="$2" desc="$3"
|
|
12
|
+
local actual_exit=0
|
|
13
|
+
echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
|
|
14
|
+
if [ "$actual_exit" -eq "$expected_exit" ]; then
|
|
15
|
+
echo " PASS: $desc"
|
|
16
|
+
PASS=$((PASS + 1))
|
|
17
|
+
else
|
|
18
|
+
echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
|
|
19
|
+
FAIL=$((FAIL + 1))
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Test that deploy command produces "Deploy detected" on stderr
|
|
24
|
+
test_hook_stderr() {
|
|
25
|
+
local input="$1" pattern="$2" desc="$3"
|
|
26
|
+
local stderr
|
|
27
|
+
stderr=$(echo "$input" | bash "$HOOK" 2>&1 >/dev/null) || true
|
|
28
|
+
if echo "$stderr" | grep -qi "$pattern"; then
|
|
29
|
+
echo " PASS: $desc"
|
|
30
|
+
PASS=$((PASS + 1))
|
|
31
|
+
else
|
|
32
|
+
echo " FAIL: $desc (expected '$pattern' on stderr, got: $stderr)"
|
|
33
|
+
FAIL=$((FAIL + 1))
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
echo "deployment-verify-guard.sh tests"
|
|
38
|
+
echo ""
|
|
39
|
+
|
|
40
|
+
# --- All commands exit 0 (non-blocking) ---
|
|
41
|
+
test_hook '{"tool_input":{"command":"systemctl restart nginx"}}' 0 "deploy: systemctl restart exits 0"
|
|
42
|
+
test_hook '{"tool_input":{"command":"docker restart myapp"}}' 0 "deploy: docker restart exits 0"
|
|
43
|
+
test_hook '{"tool_input":{"command":"docker compose up -d"}}' 0 "deploy: docker compose up exits 0"
|
|
44
|
+
test_hook '{"tool_input":{"command":"kubectl apply -f deploy.yaml"}}' 0 "deploy: kubectl apply exits 0"
|
|
45
|
+
test_hook '{"tool_input":{"command":"terraform apply"}}' 0 "deploy: terraform apply exits 0"
|
|
46
|
+
test_hook '{"tool_input":{"command":"fly deploy"}}' 0 "deploy: fly deploy exits 0"
|
|
47
|
+
test_hook '{"tool_input":{"command":"heroku container:push web"}}' 0 "deploy: heroku push exits 0"
|
|
48
|
+
|
|
49
|
+
# --- Verification commands exit 0 ---
|
|
50
|
+
test_hook '{"tool_input":{"command":"curl http://localhost:3000/health"}}' 0 "verify: curl exits 0"
|
|
51
|
+
test_hook '{"tool_input":{"command":"npm test"}}' 0 "verify: npm test exits 0"
|
|
52
|
+
test_hook '{"tool_input":{"command":"pytest tests/"}}' 0 "verify: pytest exits 0"
|
|
53
|
+
test_hook '{"tool_input":{"command":"docker logs myapp | tail"}}' 0 "verify: docker logs exits 0"
|
|
54
|
+
test_hook '{"tool_input":{"command":"journalctl -u nginx --since 5min"}}' 0 "verify: journalctl exits 0"
|
|
55
|
+
|
|
56
|
+
# --- Non-deploy non-verify commands ---
|
|
57
|
+
test_hook '{"tool_input":{"command":"ls -la"}}' 0 "unrelated command exits 0"
|
|
58
|
+
test_hook '{"tool_input":{"command":"git commit -m fix"}}' 0 "commit without deploy exits 0"
|
|
59
|
+
|
|
60
|
+
# --- Edge cases ---
|
|
61
|
+
test_hook '{"tool_input":{"command":"echo deploy"}}' 0 "echo deploy not tracked (echo skipped)"
|
|
62
|
+
test_hook '{"tool_input":{"command":""}}' 0 "empty command passes"
|
|
63
|
+
test_hook '{}' 0 "empty input passes"
|
|
64
|
+
|
|
65
|
+
# --- Deploy detection stderr message ---
|
|
66
|
+
test_hook_stderr \
|
|
67
|
+
'{"tool_input":{"command":"systemctl restart nginx"}}' \
|
|
68
|
+
"deploy detected" \
|
|
69
|
+
"deploy command emits detection message on stderr"
|
|
70
|
+
|
|
71
|
+
echo ""
|
|
72
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
73
|
+
[ "$FAIL" -gt 0 ] && exit 1
|
|
74
|
+
echo "All tests passed!"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for git-operations-require-approval.sh
|
|
3
|
+
# Run: bash tests/test-git-operations-require-approval.sh
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
PASS=0
|
|
7
|
+
FAIL=0
|
|
8
|
+
HOOK="$(dirname "$0")/../examples/git-operations-require-approval.sh"
|
|
9
|
+
|
|
10
|
+
test_hook() {
|
|
11
|
+
local input="$1" expected_exit="$2" desc="$3"
|
|
12
|
+
local actual_exit=0
|
|
13
|
+
echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
|
|
14
|
+
if [ "$actual_exit" -eq "$expected_exit" ]; then
|
|
15
|
+
echo " PASS: $desc"
|
|
16
|
+
PASS=$((PASS + 1))
|
|
17
|
+
else
|
|
18
|
+
echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
|
|
19
|
+
FAIL=$((FAIL + 1))
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
echo "git-operations-require-approval.sh tests"
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
# --- Safe commands (exit 0) ---
|
|
27
|
+
test_hook '{"tool_input":{"command":"git status"}}' 0 "git status allowed"
|
|
28
|
+
test_hook '{"tool_input":{"command":"git log --oneline"}}' 0 "git log allowed"
|
|
29
|
+
test_hook '{"tool_input":{"command":"git diff HEAD"}}' 0 "git diff allowed"
|
|
30
|
+
test_hook '{"tool_input":{"command":"git show HEAD"}}' 0 "git show allowed"
|
|
31
|
+
test_hook '{"tool_input":{"command":"git add src/main.ts"}}' 0 "git add allowed"
|
|
32
|
+
test_hook '{"tool_input":{"command":"git stash"}}' 0 "git stash allowed"
|
|
33
|
+
test_hook '{"tool_input":{"command":"git fetch origin"}}' 0 "git fetch allowed"
|
|
34
|
+
test_hook '{"tool_input":{"command":"git branch -a"}}' 0 "git branch -a (list) allowed"
|
|
35
|
+
test_hook '{"tool_input":{"command":"git branch --list"}}' 0 "git branch --list allowed"
|
|
36
|
+
test_hook '{"tool_input":{"command":"git branch -d old-branch"}}' 0 "git branch -d (delete) allowed"
|
|
37
|
+
test_hook '{"tool_input":{"command":"npm install"}}' 0 "non-git command allowed"
|
|
38
|
+
|
|
39
|
+
# --- Blocked commands (exit 2) ---
|
|
40
|
+
test_hook '{"tool_input":{"command":"git commit -m \"fix bug\""}}' 2 "git commit blocked"
|
|
41
|
+
test_hook '{"tool_input":{"command":"git commit --amend"}}' 2 "git commit --amend blocked"
|
|
42
|
+
test_hook '{"tool_input":{"command":"git push origin main"}}' 2 "git push blocked"
|
|
43
|
+
test_hook '{"tool_input":{"command":"git push --force origin main"}}' 2 "git push --force blocked"
|
|
44
|
+
test_hook '{"tool_input":{"command":"git push -f origin feature"}}' 2 "git push -f blocked"
|
|
45
|
+
test_hook '{"tool_input":{"command":"git checkout -b new-branch"}}' 2 "git checkout -b blocked"
|
|
46
|
+
test_hook '{"tool_input":{"command":"git switch -c feature"}}' 2 "git switch -c blocked"
|
|
47
|
+
test_hook '{"tool_input":{"command":"git switch --create feature"}}' 2 "git switch --create blocked"
|
|
48
|
+
test_hook '{"tool_input":{"command":"git branch feature-x"}}' 2 "git branch <name> blocked"
|
|
49
|
+
|
|
50
|
+
# --- Compound commands ---
|
|
51
|
+
test_hook '{"tool_input":{"command":"ls && git commit -m test"}}' 2 "compound && with commit blocked"
|
|
52
|
+
test_hook '{"tool_input":{"command":"git add . ; git push origin main"}}' 2 "compound ; with push blocked"
|
|
53
|
+
test_hook '{"tool_input":{"command":"test -f x || git commit -m y"}}' 2 "compound || with commit blocked"
|
|
54
|
+
test_hook '{"tool_input":{"command":"git status && git log"}}' 0 "compound safe commands allowed"
|
|
55
|
+
|
|
56
|
+
# --- Edge cases ---
|
|
57
|
+
test_hook '{"tool_input":{"command":"echo git commit -m test"}}' 0 "echo git commit not blocked"
|
|
58
|
+
test_hook '{"tool_input":{"command":"printf git push"}}' 0 "printf git push not blocked"
|
|
59
|
+
test_hook '{"tool_input":{"command":""}}' 0 "empty command passes"
|
|
60
|
+
test_hook '{}' 0 "empty input passes"
|
|
61
|
+
|
|
62
|
+
echo ""
|
|
63
|
+
echo "Results: $PASS passed, $FAIL failed"
|
|
64
|
+
[ "$FAIL" -gt 0 ] && exit 1
|
|
65
|
+
echo "All tests passed!"
|