cc-safe-setup 8.7.0 → 8.9.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/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 + 100 examples = **108 hooks**. 33 CLI commands. 457 tests. 4 languages. [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) · [Examples](https://yurukusa.github.io/cc-safe-setup/by-example.html) · [Migration](https://yurukusa.github.io/cc-safe-setup/migration-guide.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
9
+ 8 built-in + 102 examples = **110 hooks**. 34 CLI commands. 457 tests. 4 languages. [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) · [Examples](https://yurukusa.github.io/cc-safe-setup/by-example.html) · [Migration](https://yurukusa.github.io/cc-safe-setup/migration-guide.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
10
10
 
11
11
  ```bash
12
12
  npx cc-safe-setup
@@ -0,0 +1,317 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Claude Code settings.json Complete Reference</title>
7
+ <meta name="description" content="Every settings.json field explained with examples. Hooks, permissions, env vars — the definitive reference.">
8
+ <style>
9
+ *{box-sizing:border-box;margin:0;padding:0}
10
+ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#c9d1d9;padding:1.5rem;line-height:1.6}
11
+ .c{max-width:820px;margin:0 auto}
12
+ h1{color:#f0f6fc;font-size:1.4rem;margin-bottom:.3rem}
13
+ h2{color:#f0f6fc;font-size:1.05rem;margin:1.3rem 0 .4rem;padding-bottom:.3rem;border-bottom:1px solid #21262d}
14
+ h3{color:#c9d1d9;font-size:.88rem;margin:.7rem 0 .2rem}
15
+ .sub{color:#8b949e;font-size:.83rem;margin-bottom:1rem}
16
+ a{color:#58a6ff;text-decoration:none}
17
+ code{background:#161b22;padding:.12rem .25rem;border-radius:3px;font-size:.82rem}
18
+ pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.6rem;font-size:.78rem;color:#e6edf3;overflow-x:auto;margin:.3rem 0;position:relative}
19
+ .copy{position:absolute;top:.3rem;right:.3rem;background:#21262d;border:1px solid #30363d;color:#8b949e;padding:.15rem .4rem;border-radius:3px;cursor:pointer;font-size:.65rem}
20
+ .copy:hover{color:#f0f6fc}
21
+ table{width:100%;border-collapse:collapse;margin:.4rem 0;font-size:.8rem}
22
+ th,td{padding:.35rem .5rem;border:1px solid #21262d;text-align:left}
23
+ th{background:#161b22;color:#f0f6fc}
24
+ .note{background:#161b22;border-left:3px solid #58a6ff;padding:.4rem .7rem;margin:.4rem 0;font-size:.8rem;color:#8b949e}
25
+ .warn{border-left-color:#d29922}
26
+ .field{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.6rem;margin:.4rem 0}
27
+ .field-name{font-weight:700;color:#f0f6fc;font-family:monospace;font-size:.85rem}
28
+ .field-type{color:#d29922;font-size:.7rem;margin-left:.4rem}
29
+ .field-desc{color:#8b949e;font-size:.8rem;margin:.2rem 0}
30
+ .footer{text-align:center;color:#484f58;font-size:.7rem;margin-top:2rem;padding-top:1rem;border-top:1px solid #21262d}
31
+ .toc{columns:2;font-size:.8rem;margin:.5rem 0}
32
+ .toc a{display:block;padding:.15rem 0}
33
+ </style>
34
+ </head>
35
+ <body>
36
+ <div class="c">
37
+
38
+ <h1>settings.json Reference</h1>
39
+ <p class="sub">Every field, every option, with practical examples. <a href="https://docs.anthropic.com/en/docs/claude-code/settings">Official docs</a></p>
40
+
41
+ <div class="toc">
42
+ <a href="#locations">File Locations</a>
43
+ <a href="#hooks">hooks</a>
44
+ <a href="#permissions">permissions</a>
45
+ <a href="#env">env</a>
46
+ <a href="#model">model</a>
47
+ <a href="#projects">projects</a>
48
+ <a href="#examples">Full Examples</a>
49
+ </div>
50
+
51
+ <h2 id="locations">File Locations</h2>
52
+
53
+ <table>
54
+ <tr><th>File</th><th>Scope</th><th>Priority</th></tr>
55
+ <tr><td><code>~/.claude/settings.json</code></td><td>Global (all projects)</td><td>Lowest</td></tr>
56
+ <tr><td><code>.claude/settings.json</code></td><td>Project (in repo)</td><td>Medium</td></tr>
57
+ <tr><td><code>.claude/settings.local.json</code></td><td>Project local (gitignored)</td><td>Highest</td></tr>
58
+ </table>
59
+
60
+ <div class="note">
61
+ Project settings merge with global. Higher priority files override lower ones for the same key. For hooks, they are <strong>combined</strong>, not replaced.
62
+ </div>
63
+
64
+ <h2 id="hooks">hooks</h2>
65
+
66
+ <div class="field">
67
+ <span class="field-name">hooks</span> <span class="field-type">object</span>
68
+ <div class="field-desc">Shell scripts that run at lifecycle points. The core of safety configuration.</div>
69
+ </div>
70
+
71
+ <h3>Structure</h3>
72
+ <pre><code>{
73
+ "hooks": {
74
+ "EVENT": [
75
+ {
76
+ "matcher": "REGEX",
77
+ "hooks": [
78
+ {
79
+ "type": "command",
80
+ "command": "bash path/to/hook.sh"
81
+ }
82
+ ]
83
+ }
84
+ ]
85
+ }
86
+ }</code><button class="copy" onclick="cp(this)">Copy</button></pre>
87
+
88
+ <h3>Events</h3>
89
+ <table>
90
+ <tr><th>Event</th><th>When</th><th>stdin JSON</th></tr>
91
+ <tr><td><code>PreToolUse</code></td><td>Before tool executes</td><td><code>tool_name</code>, <code>tool_input</code></td></tr>
92
+ <tr><td><code>PostToolUse</code></td><td>After tool completes</td><td><code>tool_name</code>, <code>tool_input</code>, <code>tool_result</code></td></tr>
93
+ <tr><td><code>Stop</code></td><td>Claude finishes responding</td><td><code>stop_reason</code></td></tr>
94
+ <tr><td><code>SubagentStop</code></td><td>Subagent finishes</td><td>Subagent context</td></tr>
95
+ <tr><td><code>UserPromptSubmit</code></td><td>User sends a message</td><td>Prompt content</td></tr>
96
+ </table>
97
+
98
+ <h3>Matcher</h3>
99
+ <table>
100
+ <tr><th>Value</th><th>Matches</th></tr>
101
+ <tr><td><code>""</code></td><td>All tools</td></tr>
102
+ <tr><td><code>"Bash"</code></td><td>Shell commands only</td></tr>
103
+ <tr><td><code>"Edit"</code></td><td>File edits only</td></tr>
104
+ <tr><td><code>"Write"</code></td><td>File writes only</td></tr>
105
+ <tr><td><code>"Read"</code></td><td>File reads only</td></tr>
106
+ <tr><td><code>"Edit|Write"</code></td><td>Any file modification</td></tr>
107
+ <tr><td><code>"Bash|Edit|Write"</code></td><td>Multiple tools (regex OR)</td></tr>
108
+ </table>
109
+
110
+ <h3>Exit Codes</h3>
111
+ <table>
112
+ <tr><th>Code</th><th>Effect</th></tr>
113
+ <tr><td><code>0</code></td><td>Allow (default)</td></tr>
114
+ <tr><td><code>2</code></td><td>Block the action</td></tr>
115
+ <tr><td><code>1</code></td><td>Hook error (ignored, action proceeds)</td></tr>
116
+ </table>
117
+
118
+ <h3>stdout Override</h3>
119
+ <pre><code>// Auto-approve
120
+ echo '{"decision":"approve","reason":"Safe command"}'
121
+
122
+ // Block with reason
123
+ echo '{"decision":"block","reason":"Too dangerous"}'</code></pre>
124
+
125
+ <h3>Practical Example</h3>
126
+ <pre><code>{
127
+ "hooks": {
128
+ "PreToolUse": [
129
+ {
130
+ "matcher": "Bash",
131
+ "hooks": [
132
+ {"type": "command", "command": "bash ~/.claude/hooks/destructive-guard.sh"},
133
+ {"type": "command", "command": "bash ~/.claude/hooks/branch-guard.sh"},
134
+ {"type": "command", "command": "bash ~/.claude/hooks/secret-guard.sh"}
135
+ ]
136
+ },
137
+ {
138
+ "matcher": "Edit|Write",
139
+ "hooks": [
140
+ {"type": "command", "command": "bash ~/.claude/hooks/scope-guard.sh"}
141
+ ]
142
+ }
143
+ ],
144
+ "PostToolUse": [
145
+ {
146
+ "matcher": "Edit|Write",
147
+ "hooks": [
148
+ {"type": "command", "command": "bash ~/.claude/hooks/syntax-check.sh"}
149
+ ]
150
+ },
151
+ {
152
+ "matcher": "",
153
+ "hooks": [
154
+ {"type": "command", "command": "bash ~/.claude/hooks/context-monitor.sh"}
155
+ ]
156
+ }
157
+ ],
158
+ "Stop": [
159
+ {
160
+ "matcher": "",
161
+ "hooks": [
162
+ {"type": "command", "command": "bash ~/.claude/hooks/api-error-alert.sh"}
163
+ ]
164
+ }
165
+ ]
166
+ }
167
+ }</code><button class="copy" onclick="cp(this)">Copy</button></pre>
168
+
169
+ <h2 id="permissions">permissions</h2>
170
+
171
+ <div class="field">
172
+ <span class="field-name">permissions</span> <span class="field-type">object</span>
173
+ <div class="field-desc">Allow/deny rules for tool usage without prompting.</div>
174
+ </div>
175
+
176
+ <pre><code>{
177
+ "permissions": {
178
+ "allow": [
179
+ "Bash(npm test)",
180
+ "Bash(git status)",
181
+ "Bash(git log *)",
182
+ "Read(*)",
183
+ "Bash(ls *)"
184
+ ],
185
+ "deny": [
186
+ "Bash(rm -rf *)",
187
+ "Bash(sudo *)",
188
+ "Bash(git push * --force)"
189
+ ]
190
+ }
191
+ }</code><button class="copy" onclick="cp(this)">Copy</button></pre>
192
+
193
+ <div class="note warn">
194
+ <strong>Gotcha:</strong> Permissions use glob patterns, not regex. <code>*</code> matches anything. Claude adds flags like <code>-C /path</code> that can break exact matches — use hooks for reliable blocking.
195
+ </div>
196
+
197
+ <h2 id="env">env</h2>
198
+
199
+ <div class="field">
200
+ <span class="field-name">env</span> <span class="field-type">object</span>
201
+ <div class="field-desc">Environment variables passed to Claude Code and hooks.</div>
202
+ </div>
203
+
204
+ <pre><code>{
205
+ "env": {
206
+ "CC_TOKEN_BUDGET": "20",
207
+ "CC_MAX_LINE_LENGTH": "120",
208
+ "CC_DISK_WARN_PCT": "90",
209
+ "CC_ALLOWLIST_FILE": "~/.claude/allowlist.txt"
210
+ }
211
+ }</code><button class="copy" onclick="cp(this)">Copy</button></pre>
212
+
213
+ <h2 id="model">model</h2>
214
+
215
+ <div class="field">
216
+ <span class="field-name">model</span> <span class="field-type">string</span>
217
+ <div class="field-desc">Default model for Claude Code sessions.</div>
218
+ </div>
219
+
220
+ <pre><code>{
221
+ "model": "claude-sonnet-4-6"
222
+ }</code></pre>
223
+
224
+ <table>
225
+ <tr><th>Model</th><th>Best for</th></tr>
226
+ <tr><td><code>claude-opus-4-6</code></td><td>Complex reasoning, architecture</td></tr>
227
+ <tr><td><code>claude-sonnet-4-6</code></td><td>Fast daily coding (default)</td></tr>
228
+ <tr><td><code>claude-haiku-4-5</code></td><td>Quick tasks, high volume</td></tr>
229
+ </table>
230
+
231
+ <h2 id="projects">Project-Level Settings</h2>
232
+
233
+ <pre><code>// .claude/settings.json (commit to repo)
234
+ {
235
+ "hooks": {
236
+ "PreToolUse": [{
237
+ "matcher": "Bash",
238
+ "hooks": [{
239
+ "type": "command",
240
+ "command": "bash .claude/hooks/guard.sh"
241
+ }]
242
+ }]
243
+ }
244
+ }
245
+
246
+ // .claude/settings.local.json (gitignored, personal)
247
+ {
248
+ "permissions": {
249
+ "allow": ["Bash(npm test)"]
250
+ }
251
+ }</code><button class="copy" onclick="cp(this)">Copy</button></pre>
252
+
253
+ <div class="note">
254
+ Use relative paths in project settings (<code>.claude/hooks/guard.sh</code>) so they work for all team members. Use <code>npx cc-safe-setup --team</code> to set this up automatically.
255
+ </div>
256
+
257
+ <h2 id="examples">Full Working Examples</h2>
258
+
259
+ <h3>Minimal Safe Setup</h3>
260
+ <pre><code>{
261
+ "hooks": {
262
+ "PreToolUse": [{
263
+ "matcher": "Bash",
264
+ "hooks": [
265
+ {"type": "command", "command": "bash ~/.claude/hooks/destructive-guard.sh"},
266
+ {"type": "command", "command": "bash ~/.claude/hooks/branch-guard.sh"},
267
+ {"type": "command", "command": "bash ~/.claude/hooks/secret-guard.sh"}
268
+ ]
269
+ }]
270
+ }
271
+ }</code><button class="copy" onclick="cp(this)">Copy</button></pre>
272
+
273
+ <h3>Node.js Project</h3>
274
+ <pre><code>{
275
+ "hooks": {
276
+ "PreToolUse": [
277
+ {"matcher": "Bash", "hooks": [
278
+ {"type": "command", "command": "bash ~/.claude/hooks/destructive-guard.sh"},
279
+ {"type": "command", "command": "bash ~/.claude/hooks/branch-guard.sh"},
280
+ {"type": "command", "command": "bash ~/.claude/hooks/secret-guard.sh"},
281
+ {"type": "command", "command": "bash ~/.claude/hooks/auto-approve-build.sh"}
282
+ ]},
283
+ {"matcher": "Edit|Write", "hooks": [
284
+ {"type": "command", "command": "bash ~/.claude/hooks/syntax-check.sh"}
285
+ ]}
286
+ ]
287
+ },
288
+ "permissions": {
289
+ "allow": ["Bash(npm test)", "Bash(npm run lint)", "Read(*)"]
290
+ }
291
+ }</code><button class="copy" onclick="cp(this)">Copy</button></pre>
292
+
293
+ <h3>Maximum Safety (Autonomous)</h3>
294
+ <pre><code>// Generated by: npx cc-safe-setup --profile strict
295
+ // 33 hooks for autonomous/production use
296
+ // Run: npx cc-safe-setup --shield</code></pre>
297
+
298
+ <div class="footer">
299
+ <a href="hooks-cheatsheet.html">Cheat Sheet</a> ·
300
+ <a href="builder.html">Builder</a> ·
301
+ <a href="faq.html">FAQ</a> ·
302
+ <a href="by-example.html">Examples</a> ·
303
+ <a href="migration-guide.html">Migration</a> ·
304
+ <a href="https://github.com/yurukusa/cc-safe-setup">GitHub</a>
305
+ </div>
306
+ </div>
307
+
308
+ <script>
309
+ function cp(btn) {
310
+ const code = btn.parentElement.querySelector('code').textContent;
311
+ navigator.clipboard.writeText(code);
312
+ btn.textContent = 'Copied!';
313
+ setTimeout(() => btn.textContent = 'Copy', 1500);
314
+ }
315
+ </script>
316
+ </body>
317
+ </html>
@@ -0,0 +1,35 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # git-blame-context.sh — Show file ownership before major edits
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Before Claude rewrites a file, show who wrote most of it.
7
+ # Helps prevent accidentally breaking code you don't understand
8
+ # the history of. Especially useful in team repositories.
9
+ #
10
+ # TRIGGER: PreToolUse MATCHER: "Edit|Write"
11
+ # ================================================================
12
+
13
+ INPUT=$(cat)
14
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
15
+ [ -z "$FILE" ] && exit 0
16
+ [ ! -f "$FILE" ] && exit 0
17
+
18
+ # Only warn on substantial edits (old_string > 10 lines)
19
+ OLD=$(echo "$INPUT" | jq -r '.tool_input.old_string // empty' 2>/dev/null)
20
+ [ -z "$OLD" ] && exit 0
21
+ OLD_LINES=$(echo "$OLD" | wc -l)
22
+ [ "$OLD_LINES" -lt 10 ] && exit 0
23
+
24
+ # Get top contributors for this file
25
+ CONTRIBUTORS=$(git log --format='%an' -- "$FILE" 2>/dev/null | sort | uniq -c | sort -rn | head -3)
26
+ if [ -n "$CONTRIBUTORS" ]; then
27
+ TOTAL_COMMITS=$(git log --oneline -- "$FILE" 2>/dev/null | wc -l)
28
+ LAST_AUTHOR=$(git log -1 --format='%an' -- "$FILE" 2>/dev/null)
29
+ echo "NOTE: Editing $OLD_LINES+ lines in $FILE" >&2
30
+ echo " Last edited by: $LAST_AUTHOR" >&2
31
+ echo " Top contributors ($TOTAL_COMMITS commits):" >&2
32
+ echo "$CONTRIBUTORS" | head -3 | sed 's/^/ /' >&2
33
+ fi
34
+
35
+ exit 0
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # import-cycle-warn.sh — Detect potential circular imports
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude adds imports without considering dependency cycles.
7
+ # After an edit that adds an import/require, check if the
8
+ # target file imports back from the edited file.
9
+ #
10
+ # TRIGGER: PostToolUse MATCHER: "Edit"
11
+ # ================================================================
12
+
13
+ INPUT=$(cat)
14
+ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
15
+ [ -z "$FILE" ] && exit 0
16
+
17
+ NEW=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty' 2>/dev/null)
18
+ [ -z "$NEW" ] && exit 0
19
+
20
+ # Extract newly added imports
21
+ BASENAME=$(basename "$FILE" | sed 's/\.[^.]*$//')
22
+
23
+ # JS/TS: import ... from './target' or require('./target')
24
+ IMPORTS=$(echo "$NEW" | grep -oE "(from\s+['\"]\.\/[^'\"]+|require\(['\"]\.\/[^'\"]+)" | grep -oE '\./[^"'"'"']+' | sed 's/^\.\///')
25
+
26
+ # Python: from .target import or import target
27
+ if [ -z "$IMPORTS" ]; then
28
+ IMPORTS=$(echo "$NEW" | grep -oE "from\s+\.\w+" | awk '{print $2}' | sed 's/^\.//')
29
+ fi
30
+
31
+ [ -z "$IMPORTS" ] && exit 0
32
+
33
+ DIR=$(dirname "$FILE")
34
+ for imp in $IMPORTS; do
35
+ # Check if target imports back
36
+ for ext in .js .ts .jsx .tsx .py .mjs; do
37
+ TARGET="$DIR/$imp$ext"
38
+ [ -f "$TARGET" ] || continue
39
+ if grep -qE "(from\s+['\"].*${BASENAME}|import\s+.*${BASENAME}|require.*${BASENAME})" "$TARGET" 2>/dev/null; then
40
+ echo "WARNING: Potential circular import detected." >&2
41
+ echo " $FILE imports $imp" >&2
42
+ echo " $TARGET imports back from $BASENAME" >&2
43
+ break
44
+ fi
45
+ done
46
+ done
47
+
48
+ exit 0
package/index.mjs CHANGED
@@ -95,6 +95,7 @@ const ANALYZE = process.argv.includes('--analyze');
95
95
  const TEAM = process.argv.includes('--team');
96
96
  const MIGRATE_FROM_IDX = process.argv.findIndex(a => a === '--migrate-from');
97
97
  const MIGRATE_FROM = MIGRATE_FROM_IDX !== -1 ? process.argv[MIGRATE_FROM_IDX + 1] : null;
98
+ const HEALTH = process.argv.includes('--health');
98
99
  const PROFILE_IDX = process.argv.findIndex(a => a === '--profile');
99
100
  const PROFILE = PROFILE_IDX !== -1 ? process.argv[PROFILE_IDX + 1] : null;
100
101
  const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
@@ -132,6 +133,7 @@ if (HELP) {
132
133
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
133
134
  npx cc-safe-setup --watch Live dashboard of blocked commands
134
135
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
136
+ npx cc-safe-setup --health Hook health dashboard (size, permissions, age)
135
137
  npx cc-safe-setup --migrate-from <tool> Migrate from safety-net/hooks-mastery/etc.
136
138
  npx cc-safe-setup --team Set up project-level hooks (commit to repo for team)
137
139
  npx cc-safe-setup --profile <level> Switch safety profile (strict/standard/minimal)
@@ -843,6 +845,92 @@ async function fullSetup() {
843
845
  console.log();
844
846
  }
845
847
 
848
+ async function health() {
849
+ const { readdirSync, statSync } = await import('fs');
850
+ console.log();
851
+ console.log(c.bold + ' Hook Health Dashboard' + c.reset);
852
+ console.log();
853
+
854
+ if (!existsSync(HOOKS_DIR)) {
855
+ console.log(c.red + ' No hooks directory found.' + c.reset);
856
+ console.log(c.dim + ' Run: npx cc-safe-setup --shield' + c.reset);
857
+ return;
858
+ }
859
+
860
+ const hooks = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh') || f.endsWith('.py'));
861
+ if (hooks.length === 0) {
862
+ console.log(c.yellow + ' No hooks installed.' + c.reset);
863
+ return;
864
+ }
865
+
866
+ // Table header
867
+ const pad = (s, n) => s.substring(0, n).padEnd(n);
868
+ console.log(c.dim + ' ' + pad('Hook', 30) + pad('Size', 8) + pad('Perms', 7) + pad('Age', 12) + 'Shebang' + c.reset);
869
+ console.log(c.dim + ' ' + '─'.repeat(70) + c.reset);
870
+
871
+ let healthy = 0, issues = 0;
872
+ const now = Date.now();
873
+
874
+ for (const h of hooks.sort()) {
875
+ const fullPath = join(HOOKS_DIR, h);
876
+ const st = statSync(fullPath);
877
+ const content = readFileSync(fullPath, 'utf-8');
878
+ const firstLine = content.split('\n')[0];
879
+
880
+ // Size
881
+ const size = st.size < 1024 ? st.size + 'B' : Math.round(st.size / 1024) + 'KB';
882
+
883
+ // Permissions
884
+ const isExec = !!(st.mode & 0o111);
885
+ const perms = isExec ? c.green + '✓ exec' + c.reset : c.red + '✗ exec' + c.reset;
886
+
887
+ // Age
888
+ const ageDays = Math.floor((now - st.mtimeMs) / 86400000);
889
+ const age = ageDays === 0 ? 'today' : ageDays === 1 ? '1 day' : ageDays + ' days';
890
+
891
+ // Shebang
892
+ const hasShebang = firstLine.startsWith('#!');
893
+ const shebang = hasShebang ? c.green + '✓' + c.reset : c.red + '✗' + c.reset;
894
+
895
+ // Status icon
896
+ const ok = isExec && hasShebang;
897
+ const icon = ok ? c.green + '●' + c.reset : c.red + '●' + c.reset;
898
+ if (ok) healthy++; else issues++;
899
+
900
+ console.log(' ' + icon + ' ' + pad(h, 29) + pad(size, 8) + pad(isExec ? '✓ exec' : '✗ exec', 7) + pad(age, 12) + (hasShebang ? '✓' : '✗'));
901
+ }
902
+
903
+ console.log(c.dim + ' ' + '─'.repeat(70) + c.reset);
904
+ console.log(` ${c.green}${healthy} healthy${c.reset} · ${issues > 0 ? c.red + issues + ' issues' + c.reset : c.dim + '0 issues' + c.reset} · ${hooks.length} total`);
905
+
906
+ if (issues > 0) {
907
+ console.log();
908
+ console.log(c.yellow + ' Fix issues: npx cc-safe-setup --quickfix' + c.reset);
909
+ }
910
+
911
+ // Settings.json hook count
912
+ console.log();
913
+ if (existsSync(SETTINGS_PATH)) {
914
+ try {
915
+ const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
916
+ let configured = 0;
917
+ for (const groups of Object.values(s.hooks || {})) {
918
+ for (const g of groups) {
919
+ configured += (g.hooks || []).length;
920
+ }
921
+ }
922
+ console.log(c.dim + ` ${configured} hooks configured in settings.json` + c.reset);
923
+ if (configured < hooks.length) {
924
+ console.log(c.yellow + ` ${hooks.length - configured} hooks installed but not configured.` + c.reset);
925
+ console.log(c.dim + ' Run --shield to auto-configure all hooks.' + c.reset);
926
+ }
927
+ } catch {
928
+ console.log(c.red + ' settings.json has syntax errors.' + c.reset);
929
+ }
930
+ }
931
+ console.log();
932
+ }
933
+
846
934
  async function migrateFrom(tool) {
847
935
  console.log();
848
936
  console.log(c.bold + ' cc-safe-setup --migrate-from ' + (tool || '?') + c.reset);
@@ -3519,6 +3607,7 @@ async function main() {
3519
3607
  if (FULL) return fullSetup();
3520
3608
  if (DOCTOR) return doctor();
3521
3609
  if (WATCH) return watch();
3610
+ if (HEALTH) return health();
3522
3611
  if (MIGRATE_FROM_IDX !== -1) return migrateFrom(MIGRATE_FROM);
3523
3612
  if (TEAM) return team();
3524
3613
  if (PROFILE_IDX !== -1) return profile(PROFILE);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "8.7.0",
3
+ "version": "8.9.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": {