cc-safe-setup 7.2.0 → 7.4.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.
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # disk-space-guard.sh — Warn when disk space is running low
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Long Claude Code sessions can generate large files, logs, and
7
+ # build artifacts. This hook warns before writes when disk space
8
+ # is below a threshold.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Write|Bash"
11
+ #
12
+ # CONFIG:
13
+ # CC_DISK_WARN_PCT=90 (warn at this percentage used)
14
+ # ================================================================
15
+
16
+ WARN_PCT="${CC_DISK_WARN_PCT:-90}"
17
+
18
+ # Check disk usage (percentage used on the working directory's partition)
19
+ USAGE=$(df --output=pcent . 2>/dev/null | tail -1 | tr -d ' %')
20
+ [ -z "$USAGE" ] && exit 0
21
+
22
+ if [ "$USAGE" -ge "$WARN_PCT" ]; then
23
+ AVAIL=$(df -h --output=avail . 2>/dev/null | tail -1 | tr -d ' ')
24
+ echo "WARNING: Disk usage is ${USAGE}% (${AVAIL} available)." >&2
25
+ echo "Consider cleaning up build artifacts, logs, or /tmp files." >&2
26
+ fi
27
+
28
+ exit 0
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # memory-write-guard.sh — Log writes to ~/.claude/ directory
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude auto-writes to ~/.claude/projects/*/memory/ without
7
+ # user visibility. This hook logs all writes to ~/.claude/ paths
8
+ # so users know what's being stored.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Write|Edit"
11
+ #
12
+ # Born from: https://github.com/anthropics/claude-code/issues/38040
13
+ # "No way to enforce approval on all file modifications"
14
+ # ================================================================
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
+ # Check if targeting ~/.claude/
21
+ case "$FILE" in
22
+ */.claude/*|~/.claude/*)
23
+ # Log the write
24
+ LOG="$HOME/.claude/memory-writes.log"
25
+ echo "[$(date -Iseconds)] Write to: $FILE" >> "$LOG" 2>/dev/null
26
+
27
+ # Only warn (don't block) — memory writes are usually intentional
28
+ echo "NOTE: Writing to Claude config directory: $FILE" >&2
29
+
30
+ # Block writes to settings.json unless explicitly allowed
31
+ case "$FILE" in
32
+ */settings.json|*/settings.local.json)
33
+ echo "WARNING: Modifying Claude Code settings file." >&2
34
+ echo "Verify this change is intentional." >&2
35
+ ;;
36
+ esac
37
+ ;;
38
+ esac
39
+
40
+ exit 0
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ # output-length-guard.sh — Warn when tool output is very large
3
+ # TRIGGER: PostToolUse MATCHER: ""
4
+ # Checks tool output size and warns when it's consuming too much context
5
+ INPUT=$(cat)
6
+ OUTPUT=$(echo "$INPUT" | jq -r '.tool_result // empty' 2>/dev/null)
7
+ if [ -n "$OUTPUT" ]; then
8
+ LEN=${#OUTPUT}
9
+ if [ "$LEN" -gt 50000 ]; then
10
+ echo "WARNING: Tool output is ${LEN} chars. Large outputs consume context rapidly." >&2
11
+ echo "Consider using head/tail/grep to limit output, or redirect to a file." >&2
12
+ fi
13
+ fi
14
+ exit 0
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # overwrite-guard.sh — Warn before overwriting existing files
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude's Write tool can silently overwrite files without
7
+ # confirmation. This hook warns when a Write targets a file
8
+ # that already exists, giving visibility into potential data loss.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Write"
11
+ #
12
+ # Born from: https://github.com/anthropics/claude-code/issues/37595
13
+ # "/export overwrites existing files without warning"
14
+ # ================================================================
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
+ # Expand ~ to home directory
21
+ FILE="${FILE/#\~/$HOME}"
22
+
23
+ if [ -f "$FILE" ]; then
24
+ SIZE=$(wc -c < "$FILE" 2>/dev/null || echo 0)
25
+ if [ "$SIZE" -gt 0 ]; then
26
+ LINES=$(wc -l < "$FILE" 2>/dev/null || echo 0)
27
+ echo "WARNING: Overwriting existing file: $FILE ($LINES lines, $SIZE bytes)" >&2
28
+ echo "Use Edit tool instead to make targeted changes." >&2
29
+ fi
30
+ fi
31
+
32
+ exit 0
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # prompt-injection-guard.sh — Detect prompt injection in tool output
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # When Claude reads files or fetches web content, malicious
7
+ # instructions can be injected. This hook warns when tool output
8
+ # contains common prompt injection patterns.
9
+ #
10
+ # TRIGGER: PostToolUse MATCHER: ""
11
+ #
12
+ # Born from: https://github.com/anthropics/claude-code/issues/38046
13
+ # "Prompt Injection in /insights output"
14
+ # ================================================================
15
+
16
+ INPUT=$(cat)
17
+ OUTPUT=$(echo "$INPUT" | jq -r '.tool_result // empty' 2>/dev/null)
18
+ [ -z "$OUTPUT" ] && exit 0
19
+
20
+ # Check for common prompt injection patterns
21
+ SUSPICIOUS=0
22
+
23
+ # "Ignore previous instructions" pattern
24
+ if echo "$OUTPUT" | grep -qiE 'ignore\s+(all\s+)?previous\s+instructions'; then
25
+ echo "WARNING: Possible prompt injection detected: 'ignore previous instructions'" >&2
26
+ SUSPICIOUS=1
27
+ fi
28
+
29
+ # "You are now" role reassignment
30
+ if echo "$OUTPUT" | grep -qiE 'you\s+are\s+now\s+(a|an)\s+'; then
31
+ echo "WARNING: Possible prompt injection detected: role reassignment" >&2
32
+ SUSPICIOUS=1
33
+ fi
34
+
35
+ # "System prompt" manipulation
36
+ if echo "$OUTPUT" | grep -qiE '(new|updated|override)\s+system\s+prompt'; then
37
+ echo "WARNING: Possible prompt injection detected: system prompt override" >&2
38
+ SUSPICIOUS=1
39
+ fi
40
+
41
+ # Hidden instructions in HTML comments or zero-width chars
42
+ if echo "$OUTPUT" | grep -qP '<!--.*(?:execute|run|delete|remove).*-->'; then
43
+ echo "WARNING: Possible prompt injection in HTML comment" >&2
44
+ SUSPICIOUS=1
45
+ fi
46
+
47
+ if [ "$SUSPICIOUS" -eq 1 ]; then
48
+ echo "Review the output carefully before acting on it." >&2
49
+ fi
50
+
51
+ exit 0
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # test-deletion-guard.sh — Block deletion of test assertions
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude sometimes deletes or comments out failing tests instead
7
+ # of fixing the underlying code. This hook detects when an Edit
8
+ # to a test file removes test assertions.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Edit"
11
+ #
12
+ # Born from: https://github.com/anthropics/claude-code/issues/38050
13
+ # "Claude skips/deletes tests instead of fixing them"
14
+ # ================================================================
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
+ # Only check test files
21
+ case "$FILE" in
22
+ *test*|*spec*|*__tests__*|*_test.go|*_test.py|*Test.java|*Test.kt)
23
+ ;;
24
+ *)
25
+ exit 0
26
+ ;;
27
+ esac
28
+
29
+ OLD=$(echo "$INPUT" | jq -r '.tool_input.old_string // empty' 2>/dev/null)
30
+ NEW=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty' 2>/dev/null)
31
+ [ -z "$OLD" ] && exit 0
32
+
33
+ # Count test assertions in old vs new
34
+ count_tests() {
35
+ echo "$1" | grep -cE '(it\(|test\(|describe\(|def test_|#\[test\]|@Test|assert|expect\(|should\b)' 2>/dev/null || echo 0
36
+ }
37
+
38
+ OLD_COUNT=$(count_tests "$OLD")
39
+ NEW_COUNT=$(count_tests "$NEW")
40
+
41
+ if [ "$OLD_COUNT" -gt 0 ] && [ "$NEW_COUNT" -lt "$OLD_COUNT" ]; then
42
+ REMOVED=$((OLD_COUNT - NEW_COUNT))
43
+ echo "WARNING: This edit removes $REMOVED test assertion(s) from $FILE." >&2
44
+ echo "If tests are failing, fix the code instead of deleting tests." >&2
45
+ echo "Old assertions: $OLD_COUNT → New assertions: $NEW_COUNT" >&2
46
+ fi
47
+
48
+ exit 0
@@ -0,0 +1,45 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # uncommitted-work-guard.sh — Block destructive git when dirty
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude sometimes runs git checkout --, git reset --hard, or
7
+ # git stash drop when there are uncommitted changes, destroying
8
+ # hours of work. This hook checks git status before allowing
9
+ # destructive git commands.
10
+ #
11
+ # TRIGGER: PreToolUse MATCHER: "Bash"
12
+ #
13
+ # Born from: https://github.com/anthropics/claude-code/issues/37888
14
+ # "Claude runs forbidden destructive git commands, destroys work twice"
15
+ # ================================================================
16
+
17
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
18
+ [ -z "$COMMAND" ] && exit 0
19
+
20
+ # Only check destructive git commands
21
+ DESTRUCTIVE=0
22
+ echo "$COMMAND" | grep -qE '\bgit\s+checkout\s+--\s' && DESTRUCTIVE=1
23
+ echo "$COMMAND" | grep -qE '\bgit\s+checkout\s+\.\s*$' && DESTRUCTIVE=1
24
+ echo "$COMMAND" | grep -qE '\bgit\s+restore\s+--staged\s+\.' && DESTRUCTIVE=1
25
+ echo "$COMMAND" | grep -qE '\bgit\s+restore\s+\.\s*$' && DESTRUCTIVE=1
26
+ echo "$COMMAND" | grep -qE '\bgit\s+reset\s+--hard' && DESTRUCTIVE=1
27
+ echo "$COMMAND" | grep -qE '\bgit\s+clean\s+-[a-zA-Z]*f' && DESTRUCTIVE=1
28
+ echo "$COMMAND" | grep -qE '\bgit\s+stash\s+drop' && DESTRUCTIVE=1
29
+
30
+ [ "$DESTRUCTIVE" -eq 0 ] && exit 0
31
+
32
+ # Check for uncommitted changes
33
+ DIRTY=$(git status --porcelain 2>/dev/null | head -20)
34
+ if [ -n "$DIRTY" ]; then
35
+ COUNT=$(echo "$DIRTY" | wc -l)
36
+ echo "BLOCKED: Destructive git command with $COUNT uncommitted change(s)." >&2
37
+ echo "Changes that would be lost:" >&2
38
+ echo "$DIRTY" | head -10 | sed 's/^/ /' >&2
39
+ [ "$COUNT" -gt 10 ] && echo " ... and $((COUNT-10)) more" >&2
40
+ echo "" >&2
41
+ echo "Commit or stash your changes first, then retry." >&2
42
+ exit 2
43
+ fi
44
+
45
+ exit 0
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # verify-before-done.sh — Warn when committing without running tests
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code often declares fixes "done" and commits without
7
+ # verifying the fix actually works. This hook warns when a commit
8
+ # is made in a project that has tests, but no test command was
9
+ # run recently in the session.
10
+ #
11
+ # TRIGGER: PreToolUse MATCHER: "Bash"
12
+ #
13
+ # Born from: https://github.com/anthropics/claude-code/issues/37818
14
+ # "Claude repeatedly declares fixes done without verification"
15
+ # ================================================================
16
+
17
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
18
+ [ -z "$COMMAND" ] && exit 0
19
+
20
+ # Only check on git commit
21
+ echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
22
+
23
+ # Track test execution via state file
24
+ STATE="/tmp/cc-tests-ran-$(pwd | md5sum | cut -c1-8)"
25
+
26
+ # Check if tests were run in this session
27
+ if [ ! -f "$STATE" ]; then
28
+ # Detect if project has tests
29
+ HAS_TESTS=0
30
+ [ -f "package.json" ] && grep -q '"test"' package.json 2>/dev/null && HAS_TESTS=1
31
+ [ -f "pytest.ini" ] || [ -f "setup.cfg" ] || [ -f "pyproject.toml" ] && HAS_TESTS=1
32
+ [ -f "Cargo.toml" ] && HAS_TESTS=1
33
+ [ -f "go.mod" ] && HAS_TESTS=1
34
+ [ -f "Makefile" ] && grep -q 'test:' Makefile 2>/dev/null && HAS_TESTS=1
35
+
36
+ if [ "$HAS_TESTS" -eq 1 ]; then
37
+ echo "WARNING: Committing without running tests first." >&2
38
+ echo "Run your test suite before committing to verify changes work." >&2
39
+ echo "To suppress: touch $STATE" >&2
40
+ fi
41
+ fi
42
+
43
+ exit 0
package/index.mjs CHANGED
@@ -89,6 +89,7 @@ const ISSUES = process.argv.includes('--issues');
89
89
  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
+ const QUICKFIX = process.argv.includes('--quickfix');
92
93
  const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
93
94
  const COMPARE = COMPARE_IDX !== -1 ? { a: process.argv[COMPARE_IDX + 1], b: process.argv[COMPARE_IDX + 2] } : null;
94
95
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
@@ -124,6 +125,7 @@ if (HELP) {
124
125
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
125
126
  npx cc-safe-setup --watch Live dashboard of blocked commands
126
127
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
128
+ npx cc-safe-setup --quickfix Auto-detect and fix common Claude Code problems
127
129
  npx cc-safe-setup --stats Block statistics and patterns report
128
130
  npx cc-safe-setup --export Export hooks config for team sharing
129
131
  npx cc-safe-setup --import <file> Import hooks from exported config
@@ -829,6 +831,200 @@ async function fullSetup() {
829
831
  console.log();
830
832
  }
831
833
 
834
+ async function quickfix() {
835
+ const { execSync } = await import('child_process');
836
+ console.log();
837
+ console.log(c.bold + ' cc-safe-setup --quickfix' + c.reset);
838
+ console.log(c.dim + ' Auto-detect and fix common Claude Code problems' + c.reset);
839
+ console.log();
840
+
841
+ let fixed = 0, warnings = 0, ok = 0;
842
+
843
+ // Check 1: jq installed
844
+ try {
845
+ execSync('which jq', { stdio: 'pipe' });
846
+ console.log(c.green + ' ✓' + c.reset + ' jq is installed');
847
+ ok++;
848
+ } catch {
849
+ console.log(c.red + ' ✗' + c.reset + ' jq is not installed — hooks cannot parse JSON');
850
+ console.log(c.dim + ' Fix: brew install jq (macOS) | sudo apt install jq (Linux)' + c.reset);
851
+ warnings++;
852
+ }
853
+
854
+ // Check 2: ~/.claude directory exists
855
+ const claudeDir = join(HOME, '.claude');
856
+ if (existsSync(claudeDir)) {
857
+ console.log(c.green + ' ✓' + c.reset + ' ~/.claude directory exists');
858
+ ok++;
859
+ } else {
860
+ mkdirSync(claudeDir, { recursive: true });
861
+ console.log(c.yellow + ' ⚡' + c.reset + ' Created ~/.claude directory');
862
+ fixed++;
863
+ }
864
+
865
+ // Check 3: hooks directory exists
866
+ if (existsSync(HOOKS_DIR)) {
867
+ console.log(c.green + ' ✓' + c.reset + ' ~/.claude/hooks directory exists');
868
+ ok++;
869
+ } else {
870
+ mkdirSync(HOOKS_DIR, { recursive: true });
871
+ console.log(c.yellow + ' ⚡' + c.reset + ' Created ~/.claude/hooks directory');
872
+ fixed++;
873
+ }
874
+
875
+ // Check 4: settings.json exists and is valid JSON
876
+ if (existsSync(SETTINGS_PATH)) {
877
+ try {
878
+ JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
879
+ console.log(c.green + ' ✓' + c.reset + ' settings.json is valid JSON');
880
+ ok++;
881
+ } catch (e) {
882
+ console.log(c.red + ' ✗' + c.reset + ' settings.json has invalid JSON: ' + e.message);
883
+ console.log(c.dim + ' This is the #1 cause of hooks not working.' + c.reset);
884
+ console.log(c.dim + ' Common fix: remove trailing commas, check for comments (JSONC not supported in all contexts)' + c.reset);
885
+ warnings++;
886
+ }
887
+ } else {
888
+ writeFileSync(SETTINGS_PATH, '{}');
889
+ console.log(c.yellow + ' ⚡' + c.reset + ' Created empty settings.json');
890
+ fixed++;
891
+ }
892
+
893
+ // Check 5: hooks have executable permission
894
+ if (existsSync(HOOKS_DIR)) {
895
+ const { readdirSync, statSync } = await import('fs');
896
+ const hooks = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh'));
897
+ let nonExec = 0;
898
+ for (const h of hooks) {
899
+ const p = join(HOOKS_DIR, h);
900
+ const st = statSync(p);
901
+ if (!(st.mode & 0o111)) {
902
+ chmodSync(p, 0o755);
903
+ nonExec++;
904
+ }
905
+ }
906
+ if (nonExec > 0) {
907
+ console.log(c.yellow + ' ⚡' + c.reset + ` Fixed ${nonExec} hook(s) missing executable permission`);
908
+ fixed += nonExec;
909
+ } else if (hooks.length > 0) {
910
+ console.log(c.green + ' ✓' + c.reset + ` All ${hooks.length} hooks have executable permission`);
911
+ ok++;
912
+ }
913
+ }
914
+
915
+ // Check 6: hooks have correct shebang
916
+ if (existsSync(HOOKS_DIR)) {
917
+ const { readdirSync } = await import('fs');
918
+ const hooks = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh'));
919
+ let badShebang = 0;
920
+ for (const h of hooks) {
921
+ const content = readFileSync(join(HOOKS_DIR, h), 'utf-8');
922
+ const firstLine = content.split('\n')[0];
923
+ if (!firstLine.startsWith('#!')) {
924
+ badShebang++;
925
+ console.log(c.red + ' ✗' + c.reset + ` ${h} missing shebang (#!/bin/bash)`);
926
+ }
927
+ }
928
+ if (badShebang === 0 && hooks.length > 0) {
929
+ console.log(c.green + ' ✓' + c.reset + ' All hooks have valid shebang lines');
930
+ ok++;
931
+ }
932
+ warnings += badShebang;
933
+ }
934
+
935
+ // Check 7: settings.json hooks reference existing files
936
+ if (existsSync(SETTINGS_PATH)) {
937
+ try {
938
+ const settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
939
+ let broken = 0;
940
+ for (const [trigger, groups] of Object.entries(settings.hooks || {})) {
941
+ for (const group of groups) {
942
+ for (const hook of (group.hooks || [])) {
943
+ const cmd = hook.command || '';
944
+ // Extract script path from command
945
+ const match = cmd.match(/bash\s+"?([^"\s]+\.sh)/);
946
+ if (match && !existsSync(match[1])) {
947
+ console.log(c.red + ' ✗' + c.reset + ` Hook references missing file: ${match[1]}`);
948
+ broken++;
949
+ }
950
+ }
951
+ }
952
+ }
953
+ if (broken === 0) {
954
+ console.log(c.green + ' ✓' + c.reset + ' All hook file references are valid');
955
+ ok++;
956
+ }
957
+ warnings += broken;
958
+ } catch {}
959
+ }
960
+
961
+ // Check 8: No .env in git staging
962
+ try {
963
+ const staged = execSync('git diff --cached --name-only 2>/dev/null', { encoding: 'utf-8' });
964
+ if (/\.env/i.test(staged)) {
965
+ console.log(c.red + ' ✗' + c.reset + ' .env file is staged in git! Run: git reset HEAD .env');
966
+ warnings++;
967
+ } else {
968
+ console.log(c.green + ' ✓' + c.reset + ' No secret files in git staging area');
969
+ ok++;
970
+ }
971
+ } catch {
972
+ console.log(c.dim + ' · Not in a git repository (skipping git checks)' + c.reset);
973
+ }
974
+
975
+ // Check 9: CLAUDE.md exists in project
976
+ if (existsSync('CLAUDE.md')) {
977
+ console.log(c.green + ' ✓' + c.reset + ' CLAUDE.md found in project');
978
+ ok++;
979
+ } else {
980
+ console.log(c.yellow + ' △' + c.reset + ' No CLAUDE.md — consider creating one for project-specific rules');
981
+ warnings++;
982
+ }
983
+
984
+ // Check 10: Safety hooks installed
985
+ let safetyHooks = 0;
986
+ if (existsSync(SETTINGS_PATH)) {
987
+ try {
988
+ const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
989
+ const allHookCmds = [];
990
+ for (const groups of Object.values(s.hooks || {})) {
991
+ for (const g of groups) {
992
+ for (const h of (g.hooks || [])) allHookCmds.push(h.command || '');
993
+ }
994
+ }
995
+ const critical = ['destructive-guard', 'branch-guard', 'secret-guard'];
996
+ for (const name of critical) {
997
+ if (allHookCmds.some(c => c.includes(name))) {
998
+ safetyHooks++;
999
+ } else {
1000
+ console.log(c.yellow + ' △' + c.reset + ` Missing critical hook: ${name}`);
1001
+ console.log(c.dim + ' Fix: npx cc-safe-setup' + c.reset);
1002
+ warnings++;
1003
+ }
1004
+ }
1005
+ if (safetyHooks === 3) {
1006
+ console.log(c.green + ' ✓' + c.reset + ' All 3 critical safety hooks installed');
1007
+ ok++;
1008
+ }
1009
+ } catch {}
1010
+ }
1011
+
1012
+ console.log();
1013
+ console.log(c.bold + ' Summary' + c.reset);
1014
+ console.log(c.green + ` ${ok} OK` + c.reset + c.yellow + ` · ${fixed} fixed` + c.reset + c.red + ` · ${warnings} warnings` + c.reset);
1015
+
1016
+ if (fixed > 0) {
1017
+ console.log();
1018
+ console.log(c.green + ` ⚡ Auto-fixed ${fixed} issue(s)` + c.reset);
1019
+ }
1020
+ if (warnings > 0) {
1021
+ console.log();
1022
+ console.log(c.yellow + ' Run npx cc-safe-setup to install missing safety hooks' + c.reset);
1023
+ console.log(c.yellow + ' Run npx cc-safe-setup --doctor for detailed diagnosis' + c.reset);
1024
+ }
1025
+ console.log();
1026
+ }
1027
+
832
1028
  async function report() {
833
1029
  // Generate markdown safety report
834
1030
  let hookCount = 0;
@@ -2640,6 +2836,7 @@ async function main() {
2640
2836
  if (FULL) return fullSetup();
2641
2837
  if (DOCTOR) return doctor();
2642
2838
  if (WATCH) return watch();
2839
+ if (QUICKFIX) return quickfix();
2643
2840
  if (REPORT) return report();
2644
2841
  if (GENERATE_CI) return generateCI();
2645
2842
  if (MIGRATE) return migrate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "7.2.0",
3
+ "version": "7.4.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": {