cc-safe-setup 7.9.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md ADDED
@@ -0,0 +1,18 @@
1
+ # Project Rules
2
+
3
+ ## Safety
4
+ - Do not push to main/master directly
5
+ - Do not force-push
6
+ - Do not delete files outside this project
7
+ - Do not commit .env or credential files
8
+ - Run tests before committing
9
+
10
+ ## Code Style
11
+ - Follow existing conventions
12
+ - Keep functions small and focused
13
+ - Add comments only when the logic isn't obvious
14
+
15
+ ## Git
16
+ - Use descriptive commit messages
17
+ - One logical change per commit
18
+ - Create feature branches for new work
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  **One command to make Claude Code safe for autonomous operation.** [日本語](docs/README.ja.md)
8
8
 
9
- 8 built-in + 77 examples = **85 hooks**. 28 CLI commands. 423 tests. [Web Tool](https://yurukusa.github.io/cc-safe-setup/) · [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/hooks-cheatsheet.html) · [Builder](https://yurukusa.github.io/cc-safe-setup/builder.html) · [FAQ](https://yurukusa.github.io/cc-safe-setup/faq.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
9
+ 8 built-in + 92 examples = **100 hooks**. 29 CLI commands. 433 tests. [Web Tool](https://yurukusa.github.io/cc-safe-setup/) · [Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/hooks-cheatsheet.html) · [Builder](https://yurukusa.github.io/cc-safe-setup/builder.html) · [FAQ](https://yurukusa.github.io/cc-safe-setup/faq.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
10
10
 
11
11
  ```bash
12
12
  npx cc-safe-setup
@@ -103,6 +103,8 @@ Each hook exists because a real incident happened without it.
103
103
 
104
104
  Safe to run multiple times. Existing settings are preserved. A backup is created if settings.json can't be parsed.
105
105
 
106
+ **Maximum safety:** `npx cc-safe-setup --shield` — one command: fix environment, install hooks, detect stack, configure settings, generate CLAUDE.md.
107
+
106
108
  **Preview first:** `npx cc-safe-setup --dry-run`
107
109
 
108
110
  **Check status:** `npx cc-safe-setup --status` — see which hooks are installed (exit code 1 if missing).
@@ -0,0 +1,10 @@
1
+ INPUT=$(cat)
2
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
4
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ [ -z "$COMMAND" ] && exit 0
6
+ if echo "$COMMAND" | grep -qE '\bgit\s+mv\b.*\b(src|lib|app)\b'; then
7
+ git stash push -m "pre-refactor-backup-$(date +%s)" 2>/dev/null
8
+ echo "NOTE: Stashed changes as pre-refactor backup." >&2
9
+ fi
10
+ exit 0
@@ -0,0 +1,13 @@
1
+ INPUT=$(cat)
2
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
4
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ [ -z "$COMMAND" ] && exit 0
6
+ if echo "$COMMAND" | grep -qE '\bgit\s+(checkout|switch)\s+-b\s+'; then
7
+ BRANCH=$(echo "$COMMAND" | grep -oE '(-b|--create)\s+(\S+)' | awk '{print $2}')
8
+ if [ -n "$BRANCH" ] && ! echo "$BRANCH" | grep -qE '^(feat|fix|chore|docs|test|refactor)/'; then
9
+ echo "WARNING: Branch '$BRANCH' doesn't follow convention." >&2
10
+ echo "Use: feat/, fix/, chore/, docs/, test/, refactor/" >&2
11
+ fi
12
+ fi
13
+ exit 0
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # changelog-reminder.sh — Remind to update CHANGELOG on version bump
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # When Claude bumps a version number (npm version, cargo set-version,
7
+ # etc.), this hook reminds to update CHANGELOG.md with the changes.
8
+ #
9
+ # TRIGGER: PostToolUse MATCHER: "Bash"
10
+ # ================================================================
11
+
12
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
13
+ [ -z "$COMMAND" ] && exit 0
14
+
15
+ # Detect version bump commands
16
+ if echo "$COMMAND" | grep -qE '(npm\s+version|cargo\s+set-version|bump2version|poetry\s+version)'; then
17
+ if [ -f "CHANGELOG.md" ]; then
18
+ echo "REMINDER: Update CHANGELOG.md with the new version's changes." >&2
19
+ fi
20
+ fi
21
+
22
+ exit 0
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # commit-scope-guard.sh — Warn when committing too many files
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code can modify dozens of files and commit them all at
7
+ # once, making the commit hard to review and revert. This hook
8
+ # warns when staging more than a configurable number of files.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Bash"
11
+ #
12
+ # CONFIG:
13
+ # CC_MAX_COMMIT_FILES=15 (warn above 15 files)
14
+ # ================================================================
15
+
16
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
17
+ [ -z "$COMMAND" ] && exit 0
18
+
19
+ echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
20
+
21
+ MAX="${CC_MAX_COMMIT_FILES:-15}"
22
+ STAGED=$(git diff --cached --name-only 2>/dev/null | wc -l)
23
+
24
+ if [ "$STAGED" -gt "$MAX" ]; then
25
+ echo "WARNING: Committing $STAGED files (threshold: $MAX)." >&2
26
+ echo "Consider splitting into smaller, focused commits." >&2
27
+ echo "Files:" >&2
28
+ git diff --cached --name-only 2>/dev/null | head -10 | sed 's/^/ /' >&2
29
+ [ "$STAGED" -gt 10 ] && echo " ... and $((STAGED-10)) more" >&2
30
+ fi
31
+
32
+ exit 0
@@ -0,0 +1,12 @@
1
+ INPUT=$(cat)
2
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
4
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ [ -z "$CONTENT" ] && exit 0
6
+ LEN=${#CONTENT}
7
+ MAX="${CC_MAX_FILE_SIZE:-1048576}"
8
+ if [ "$LEN" -gt "$MAX" ]; then
9
+ echo "BLOCKED: File content is ${LEN} bytes (limit: ${MAX})." >&2
10
+ exit 2
11
+ fi
12
+ exit 0
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # hardcoded-secret-detector.sh — Detect hardcoded secrets in edits
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude sometimes hardcodes API keys, passwords, or tokens
7
+ # directly into source files instead of using environment
8
+ # variables. This hook checks edited content for secret patterns.
9
+ #
10
+ # TRIGGER: PostToolUse MATCHER: "Edit|Write"
11
+ # ================================================================
12
+
13
+ INPUT=$(cat)
14
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
15
+ [ -z "$CONTENT" ] && exit 0
16
+
17
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
18
+
19
+ # Skip config/env files (secrets are expected there)
20
+ case "$FILE" in
21
+ *.env*|*credentials*|*secret*|*.key|*.pem) exit 0 ;;
22
+ esac
23
+
24
+ FOUND=0
25
+
26
+ # AWS keys (AKIA...)
27
+ if echo "$CONTENT" | grep -qE 'AKIA[0-9A-Z]{16}'; then
28
+ echo "WARNING: Possible AWS access key in $FILE" >&2
29
+ FOUND=1
30
+ fi
31
+
32
+ # Generic API key patterns
33
+ if echo "$CONTENT" | grep -qE "(api_key|apikey|api-key|secret_key|access_token)\s*[=:]\s*['\"][a-zA-Z0-9]{20,}['\"]"; then
34
+ echo "WARNING: Possible hardcoded API key in $FILE" >&2
35
+ FOUND=1
36
+ fi
37
+
38
+ # Password patterns
39
+ if echo "$CONTENT" | grep -qiE "(password|passwd|pwd)\s*[=:]\s*['\"][^'\"]{8,}['\"]"; then
40
+ echo "WARNING: Possible hardcoded password in $FILE" >&2
41
+ FOUND=1
42
+ fi
43
+
44
+ # JWT tokens
45
+ if echo "$CONTENT" | grep -qE 'eyJ[a-zA-Z0-9_-]{20,}\.eyJ[a-zA-Z0-9_-]{20,}'; then
46
+ echo "WARNING: Possible JWT token in $FILE" >&2
47
+ FOUND=1
48
+ fi
49
+
50
+ # Private keys
51
+ if echo "$CONTENT" | grep -qE 'BEGIN (RSA |EC |DSA )?PRIVATE KEY'; then
52
+ echo "WARNING: Private key detected in $FILE" >&2
53
+ FOUND=1
54
+ fi
55
+
56
+ if [ "$FOUND" -eq 1 ]; then
57
+ echo "Use environment variables instead of hardcoding secrets." >&2
58
+ fi
59
+
60
+ exit 0
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # license-check.sh — Warn when creating files without a license header
3
+ # TRIGGER: PostToolUse MATCHER: "Write"
4
+ FILE=$(cat | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ [ -z "$FILE" ] && exit 0
6
+ case "$FILE" in *.js|*.ts|*.py|*.go|*.rs|*.java|*.rb|*.sh) ;; *) exit 0 ;; esac
7
+ [ ! -f "$FILE" ] && exit 0
8
+ if ! head -5 "$FILE" | grep -qiE '(license|copyright|MIT|Apache|GPL)'; then
9
+ if [ -f "LICENSE" ] || [ -f "LICENSE.md" ]; then
10
+ echo "NOTE: New source file $FILE has no license header." >&2
11
+ fi
12
+ fi
13
+ exit 0
@@ -0,0 +1,10 @@
1
+ INPUT=$(cat)
2
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
4
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ [ -z "$CONTENT" ] && exit 0
6
+ case "$FILE" in *.test.*|*.spec.*|*debug*) exit 0 ;; esac
7
+ if echo "$CONTENT" | grep -qE '\bconsole\.(log|debug)\b'; then
8
+ echo "WARNING: console.log detected in $FILE. Use proper logging." >&2
9
+ fi
10
+ exit 0
@@ -0,0 +1,9 @@
1
+ INPUT=$(cat)
2
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
4
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ [ -z "$CONTENT" ] && exit 0
6
+ if echo "$CONTENT" | grep -qE '\beval\s*\('; then
7
+ echo "WARNING: eval() detected in $FILE. Avoid eval for security." >&2
8
+ fi
9
+ exit 0
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ # no-todo-ship.sh — Block commits with TODO/FIXME/HACK markers
3
+ # TRIGGER: PreToolUse MATCHER: "Bash"
4
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
5
+ [ -z "$COMMAND" ] && exit 0
6
+ echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
7
+ TODOS=$(git diff --cached 2>/dev/null | grep -cE '^\+.*\b(TODO|FIXME|HACK|XXX)\b' || echo 0)
8
+ if [ "$TODOS" -gt 0 ]; then
9
+ echo "WARNING: $TODOS TODO/FIXME/HACK markers in staged changes." >&2
10
+ echo "Resolve them before shipping, or document why they're needed." >&2
11
+ fi
12
+ exit 0
@@ -0,0 +1,9 @@
1
+ INPUT=$(cat)
2
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
4
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ [ -z "$CONTENT" ] && exit 0
6
+ if echo "$CONTENT" | grep -qE '(from\s+\S+\s+import\s+\*|import\s+\*\s+from)'; then
7
+ echo "WARNING: Wildcard import detected. Import specific names." >&2
8
+ fi
9
+ exit 0
@@ -0,0 +1,11 @@
1
+ INPUT=$(cat)
2
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
4
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ [ -z "$COMMAND" ] && exit 0
6
+ if echo "$COMMAND" | grep -qE '\bgh\s+pr\s+create\b'; then
7
+ if ! echo "$COMMAND" | grep -qE '\-\-body|\-b\s'; then
8
+ echo "WARNING: PR created without --body description." >&2
9
+ fi
10
+ fi
11
+ exit 0
@@ -0,0 +1,15 @@
1
+ INPUT=$(cat)
2
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
3
+ CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // .tool_input.content // empty' 2>/dev/null)
4
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
5
+ STATE="/tmp/cc-rate-limit-$(echo "$PWD" | md5sum | cut -c1-8)"
6
+ NOW=$(date +%s)
7
+ if [ -f "$STATE" ]; then
8
+ LAST=$(cat "$STATE")
9
+ DIFF=$((NOW - LAST))
10
+ if [ "$DIFF" -lt 1 ]; then
11
+ echo "WARNING: Rapid tool calls (${DIFF}s apart). Slow down." >&2
12
+ fi
13
+ fi
14
+ echo "$NOW" > "$STATE"
15
+ exit 0
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # worktree-guard.sh — Warn when operating in a git worktree
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Git worktrees share the same .git directory. Destructive operations
7
+ # in one worktree (git clean, reset) can affect the main working tree.
8
+ # This hook warns when Claude is operating inside a worktree.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Bash"
11
+ # ================================================================
12
+
13
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
14
+ [ -z "$COMMAND" ] && exit 0
15
+
16
+ # Only check destructive git commands
17
+ echo "$COMMAND" | grep -qE '\bgit\s+(clean|reset|checkout\s+--|stash\s+drop)' || exit 0
18
+
19
+ # Check if we're in a worktree
20
+ GITDIR=$(git rev-parse --git-dir 2>/dev/null)
21
+ if echo "$GITDIR" | grep -q "worktrees"; then
22
+ MAIN_DIR=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null | sed 's|/.git$||')
23
+ echo "WARNING: You are in a git worktree." >&2
24
+ echo "Main working tree: $MAIN_DIR" >&2
25
+ echo "Destructive git operations may affect the main tree." >&2
26
+ fi
27
+
28
+ exit 0
package/index.mjs CHANGED
@@ -90,6 +90,7 @@ const MIGRATE = process.argv.includes('--migrate');
90
90
  const GENERATE_CI = process.argv.includes('--generate-ci');
91
91
  const REPORT = process.argv.includes('--report');
92
92
  const QUICKFIX = process.argv.includes('--quickfix');
93
+ const SHIELD = process.argv.includes('--shield');
93
94
  const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
94
95
  const COMPARE = COMPARE_IDX !== -1 ? { a: process.argv[COMPARE_IDX + 1], b: process.argv[COMPARE_IDX + 2] } : null;
95
96
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
@@ -125,6 +126,7 @@ if (HELP) {
125
126
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
126
127
  npx cc-safe-setup --watch Live dashboard of blocked commands
127
128
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
129
+ npx cc-safe-setup --shield Maximum safety in one command (fix + scan + install + CLAUDE.md)
128
130
  npx cc-safe-setup --quickfix Auto-detect and fix common Claude Code problems
129
131
  npx cc-safe-setup --stats Block statistics and patterns report
130
132
  npx cc-safe-setup --export Export hooks config for team sharing
@@ -831,6 +833,210 @@ async function fullSetup() {
831
833
  console.log();
832
834
  }
833
835
 
836
+ async function shield() {
837
+ const { execSync } = await import('child_process');
838
+ const { readdirSync } = await import('fs');
839
+ console.log();
840
+ console.log(c.bold + ' 🛡️ cc-safe-setup --shield' + c.reset);
841
+ console.log(c.dim + ' Maximum safety in one command' + c.reset);
842
+ console.log();
843
+
844
+ // Step 1: Fix environment issues
845
+ console.log(c.bold + ' Step 1: Fix environment' + c.reset);
846
+ await quickfix();
847
+
848
+ // Step 2: Install core safety hooks
849
+ console.log();
850
+ console.log(c.bold + ' Step 2: Install safety hooks' + c.reset);
851
+ // Run the default install
852
+ mkdirSync(HOOKS_DIR, { recursive: true });
853
+ let installed = 0;
854
+ for (const [hookId, hookMeta] of Object.entries(HOOKS)) {
855
+ const hookPath = join(HOOKS_DIR, `${hookId}.sh`);
856
+ if (!existsSync(hookPath)) {
857
+ writeFileSync(hookPath, SCRIPTS[hookId]);
858
+ chmodSync(hookPath, 0o755);
859
+ installed++;
860
+ console.log(c.green + ' +' + c.reset + ` ${hookMeta.name}`);
861
+ } else {
862
+ console.log(c.dim + ' ✓' + c.reset + ` ${hookMeta.name} (already installed)`);
863
+ }
864
+ }
865
+
866
+ // Step 3: Detect project stack and install recommended examples
867
+ console.log();
868
+ console.log(c.bold + ' Step 3: Project-aware hooks' + c.reset);
869
+ const cwd = process.cwd();
870
+ const extras = [];
871
+ if (existsSync(join(cwd, 'package.json'))) {
872
+ extras.push('auto-approve-build');
873
+ try {
874
+ const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
875
+ if (pkg.dependencies?.prisma || pkg.devDependencies?.prisma) extras.push('block-database-wipe');
876
+ if (pkg.scripts?.deploy) extras.push('deploy-guard');
877
+ } catch {}
878
+ }
879
+ if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) extras.push('auto-approve-python');
880
+ if (existsSync(join(cwd, 'Dockerfile'))) extras.push('auto-approve-docker');
881
+ if (existsSync(join(cwd, 'go.mod'))) extras.push('auto-approve-go');
882
+ if (existsSync(join(cwd, 'Cargo.toml'))) extras.push('auto-approve-cargo');
883
+ if (existsSync(join(cwd, 'Makefile'))) extras.push('auto-approve-make');
884
+ if (existsSync(join(cwd, '.env'))) extras.push('env-source-guard');
885
+
886
+ // Always include these for maximum safety
887
+ extras.push('scope-guard', 'no-sudo-guard', 'protect-claudemd');
888
+
889
+ for (const ex of extras) {
890
+ const exPath = join(__dirname, 'examples', `${ex}.sh`);
891
+ const hookPath = join(HOOKS_DIR, `${ex}.sh`);
892
+ if (existsSync(exPath) && !existsSync(hookPath)) {
893
+ copyFileSync(exPath, hookPath);
894
+ chmodSync(hookPath, 0o755);
895
+ console.log(c.green + ' +' + c.reset + ` ${ex}`);
896
+ installed++;
897
+ } else if (existsSync(hookPath)) {
898
+ console.log(c.dim + ' ✓' + c.reset + ` ${ex} (already installed)`);
899
+ }
900
+ }
901
+
902
+ // Step 4: Update settings.json
903
+ console.log();
904
+ console.log(c.bold + ' Step 4: Configure settings.json' + c.reset);
905
+ let settings = {};
906
+ if (existsSync(SETTINGS_PATH)) {
907
+ try { settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8')); } catch {}
908
+ }
909
+ if (!settings.hooks) settings.hooks = {};
910
+
911
+ // Collect all installed hooks
912
+ const hookFiles = existsSync(HOOKS_DIR)
913
+ ? readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh'))
914
+ : [];
915
+
916
+ // Build hook entries by trigger type
917
+ const preToolHooks = [];
918
+ const postToolHooks = [];
919
+ const stopHooks = [];
920
+
921
+ for (const f of hookFiles) {
922
+ const content = readFileSync(join(HOOKS_DIR, f), 'utf-8');
923
+ const cmd = `bash ${join(HOOKS_DIR, f)}`;
924
+
925
+ // Check if already in settings
926
+ const alreadyConfigured = JSON.stringify(settings.hooks).includes(f);
927
+ if (alreadyConfigured) continue;
928
+
929
+ // Determine trigger from file content
930
+ if (content.includes('TRIGGER: Stop') || f.includes('api-error') || f.includes('revert-helper') || f.includes('session-handoff') || f.includes('compact-reminder') || f.includes('notify') || f.includes('tmp-cleanup')) {
931
+ stopHooks.push({ type: 'command', command: cmd });
932
+ } else if (content.includes('TRIGGER: PostToolUse') || f.includes('syntax-check') || f.includes('context-monitor') || f.includes('output-length') || f.includes('error-memory') || f.includes('cost-tracker')) {
933
+ postToolHooks.push({ type: 'command', command: cmd });
934
+ } else {
935
+ // Default: PreToolUse
936
+ const matcher = (f.includes('edit-guard') || f.includes('protect-dotfiles') || f.includes('overwrite-guard') || f.includes('binary-file') || f.includes('parallel-edit') || f.includes('test-deletion') || f.includes('memory-write'))
937
+ ? 'Edit|Write'
938
+ : 'Bash';
939
+ preToolHooks.push({ type: 'command', command: cmd, _matcher: matcher });
940
+ }
941
+ }
942
+
943
+ // Group PreToolUse hooks by matcher
944
+ if (preToolHooks.length > 0) {
945
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
946
+ const bashHooks = preToolHooks.filter(h => h._matcher === 'Bash').map(({ _matcher, ...h }) => h);
947
+ const editHooks = preToolHooks.filter(h => h._matcher === 'Edit|Write').map(({ _matcher, ...h }) => h);
948
+ if (bashHooks.length > 0) {
949
+ const existing = settings.hooks.PreToolUse.find(e => e.matcher === 'Bash');
950
+ if (existing) {
951
+ const existingCmds = new Set(existing.hooks.map(h => h.command));
952
+ for (const h of bashHooks) {
953
+ if (!existingCmds.has(h.command)) existing.hooks.push(h);
954
+ }
955
+ } else {
956
+ settings.hooks.PreToolUse.push({ matcher: 'Bash', hooks: bashHooks });
957
+ }
958
+ }
959
+ if (editHooks.length > 0) {
960
+ const existing = settings.hooks.PreToolUse.find(e => e.matcher === 'Edit|Write');
961
+ if (existing) {
962
+ const existingCmds = new Set(existing.hooks.map(h => h.command));
963
+ for (const h of editHooks) {
964
+ if (!existingCmds.has(h.command)) existing.hooks.push(h);
965
+ }
966
+ } else {
967
+ settings.hooks.PreToolUse.push({ matcher: 'Edit|Write', hooks: editHooks });
968
+ }
969
+ }
970
+ }
971
+ if (postToolHooks.length > 0) {
972
+ if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
973
+ const existing = settings.hooks.PostToolUse.find(e => e.matcher === '');
974
+ if (existing) {
975
+ const existingCmds = new Set(existing.hooks.map(h => h.command));
976
+ for (const h of postToolHooks) {
977
+ if (!existingCmds.has(h.command)) existing.hooks.push(h);
978
+ }
979
+ } else {
980
+ settings.hooks.PostToolUse.push({ matcher: '', hooks: postToolHooks });
981
+ }
982
+ }
983
+ if (stopHooks.length > 0) {
984
+ if (!settings.hooks.Stop) settings.hooks.Stop = [];
985
+ const existing = settings.hooks.Stop.find(e => e.matcher === '');
986
+ if (existing) {
987
+ const existingCmds = new Set(existing.hooks.map(h => h.command));
988
+ for (const h of stopHooks) {
989
+ if (!existingCmds.has(h.command)) existing.hooks.push(h);
990
+ }
991
+ } else {
992
+ settings.hooks.Stop.push({ matcher: '', hooks: stopHooks });
993
+ }
994
+ }
995
+
996
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
997
+ console.log(c.green + ' ✓' + c.reset + ' settings.json updated');
998
+
999
+ // Step 5: Generate CLAUDE.md template if none exists
1000
+ console.log();
1001
+ console.log(c.bold + ' Step 5: CLAUDE.md' + c.reset);
1002
+ if (!existsSync(join(cwd, 'CLAUDE.md'))) {
1003
+ const template = `# Project Rules
1004
+
1005
+ ## Safety
1006
+ - Do not push to main/master directly
1007
+ - Do not force-push
1008
+ - Do not delete files outside this project
1009
+ - Do not commit .env or credential files
1010
+ - Run tests before committing
1011
+
1012
+ ## Code Style
1013
+ - Follow existing conventions
1014
+ - Keep functions small and focused
1015
+ - Add comments only when the logic isn't obvious
1016
+
1017
+ ## Git
1018
+ - Use descriptive commit messages
1019
+ - One logical change per commit
1020
+ - Create feature branches for new work
1021
+ `;
1022
+ writeFileSync(join(cwd, 'CLAUDE.md'), template);
1023
+ console.log(c.green + ' +' + c.reset + ' Created CLAUDE.md with safety rules template');
1024
+ } else {
1025
+ console.log(c.dim + ' ✓' + c.reset + ' CLAUDE.md already exists');
1026
+ }
1027
+
1028
+ // Summary
1029
+ console.log();
1030
+ const totalHooks = hookFiles.length;
1031
+ console.log(c.bold + c.green + ' 🛡️ Shield activated!' + c.reset);
1032
+ console.log(c.dim + ` ${totalHooks} hooks installed and configured.` + c.reset);
1033
+ console.log(c.dim + ' Your Claude Code sessions are now protected.' + c.reset);
1034
+ console.log();
1035
+ console.log(c.dim + ' Verify: npx cc-safe-setup --verify' + c.reset);
1036
+ console.log(c.dim + ' Status: npx cc-safe-setup --status' + c.reset);
1037
+ console.log();
1038
+ }
1039
+
834
1040
  async function quickfix() {
835
1041
  const { execSync } = await import('child_process');
836
1042
  console.log();
@@ -2836,6 +3042,7 @@ async function main() {
2836
3042
  if (FULL) return fullSetup();
2837
3043
  if (DOCTOR) return doctor();
2838
3044
  if (WATCH) return watch();
3045
+ if (SHIELD) return shield();
2839
3046
  if (QUICKFIX) return quickfix();
2840
3047
  if (REPORT) return report();
2841
3048
  if (GENERATE_CI) return generateCI();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "7.9.0",
3
+ "version": "8.1.0",
4
4
  "description": "One command to make Claude Code safe. 59 hooks (8 built-in + 51 examples). 26 CLI commands: dashboard, create, audit, lint, diff, migrate, compare, generate-ci. 284 tests.",
5
5
  "main": "index.mjs",
6
6
  "bin": {