cc-safe-setup 7.8.0 → 8.0.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 + 75 examples = **83 hooks**. 28 CLI commands. 420 tests. [Web Tool](https://yurukusa.github.io/cc-safe-setup/) · [Hooks Cheat Sheet](https://yurukusa.github.io/cc-safe-setup/hooks-cheatsheet.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html) · [Troubleshooting](TROUBLESHOOTING.md)
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)
10
10
 
11
11
  ```bash
12
12
  npx cc-safe-setup
@@ -0,0 +1,283 @@
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>Hook Builder — Claude Code</title>
7
+ <meta name="description" content="Generate Claude Code hooks from plain English. No coding required.">
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;min-height:100vh;padding:1.5rem}
11
+ .c{max-width:800px;margin:0 auto}
12
+ h1{color:#f0f6fc;font-size:1.4rem;margin-bottom:.3rem}
13
+ .sub{color:#8b949e;font-size:.85rem;margin-bottom:1.2rem}
14
+ a{color:#58a6ff;text-decoration:none}
15
+ input,select{width:100%;padding:.6rem;background:#161b22;border:1px solid #30363d;border-radius:6px;color:#c9d1d9;font-size:.9rem;margin-bottom:.6rem}
16
+ input:focus,select:focus{outline:none;border-color:#58a6ff}
17
+ input::placeholder{color:#484f58}
18
+ label{display:block;color:#8b949e;font-size:.78rem;margin-bottom:.2rem}
19
+ pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.8rem;font-size:.8rem;color:#e6edf3;overflow-x:auto;white-space:pre-wrap;position:relative;margin:.5rem 0}
20
+ .copy-btn{position:absolute;top:.4rem;right:.4rem;background:#21262d;border:1px solid #30363d;color:#8b949e;padding:.2rem .5rem;border-radius:4px;cursor:pointer;font-size:.7rem}
21
+ .copy-btn:hover{color:#f0f6fc;border-color:#58a6ff}
22
+ .btn{background:#238636;color:#fff;border:none;padding:.6rem 1.2rem;border-radius:6px;cursor:pointer;font-size:.9rem;width:100%;margin:.5rem 0}
23
+ .btn:hover{background:#2ea043}
24
+ .templates{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.4rem;margin:.8rem 0}
25
+ .tmpl{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.5rem;cursor:pointer;font-size:.78rem;color:#8b949e;transition:border-color .15s}
26
+ .tmpl:hover{border-color:#58a6ff;color:#c9d1d9}
27
+ .tmpl-title{font-weight:600;color:#c9d1d9;font-size:.82rem}
28
+ .section{margin:1.2rem 0}
29
+ .footer{text-align:center;color:#484f58;font-size:.7rem;margin-top:2rem}
30
+ .note{background:#161b22;border-left:3px solid #58a6ff;padding:.5rem .8rem;font-size:.78rem;color:#8b949e;margin:.5rem 0}
31
+ .hidden{display:none}
32
+ </style>
33
+ </head>
34
+ <body>
35
+ <div class="c">
36
+
37
+ <h1>Hook Builder</h1>
38
+ <p class="sub">Describe what you want in plain English. Get a working hook.</p>
39
+
40
+ <div class="section">
41
+ <label>What should the hook do?</label>
42
+ <input id="desc" placeholder="e.g., Block npm publish without running tests first" oninput="clearOutput()">
43
+ </div>
44
+
45
+ <div class="section">
46
+ <label>Trigger event</label>
47
+ <select id="trigger">
48
+ <option value="PreToolUse">PreToolUse (before tool runs — block/approve)</option>
49
+ <option value="PostToolUse">PostToolUse (after tool runs — check results)</option>
50
+ <option value="Stop">Stop (when Claude finishes — notify/cleanup)</option>
51
+ </select>
52
+ </div>
53
+
54
+ <div class="section">
55
+ <label>Tool matcher (regex)</label>
56
+ <input id="matcher" placeholder='e.g., Bash, Edit|Write, or empty for all' value="Bash">
57
+ </div>
58
+
59
+ <button class="btn" onclick="generate()">Generate Hook</button>
60
+
61
+ <div id="output" class="hidden">
62
+ <h2 style="color:#f0f6fc;font-size:1rem;margin:1rem 0 .5rem">Generated Hook</h2>
63
+ <pre id="hook-code"><code></code><button class="copy-btn" onclick="copyCode()">Copy</button></pre>
64
+
65
+ <h2 style="color:#f0f6fc;font-size:1rem;margin:1rem 0 .5rem">settings.json entry</h2>
66
+ <pre id="settings-code"><code></code><button class="copy-btn" onclick="copySettings()">Copy Settings</button></pre>
67
+
68
+ <div class="note">
69
+ <strong>Install:</strong> Save the hook to <code>~/.claude/hooks/your-hook.sh</code>, run <code>chmod +x</code>, and add the settings.json entry.
70
+ <br>Or use the CLI: <code id="cli-cmd"></code>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="section">
75
+ <h2 style="color:#8b949e;font-size:.9rem;margin-bottom:.5rem">Templates</h2>
76
+ <div class="templates" id="templates"></div>
77
+ </div>
78
+
79
+ <div class="footer">
80
+ <a href="hooks-cheatsheet.html">Cheat Sheet</a> ·
81
+ <a href="https://yurukusa.github.io/cc-hook-registry/playground.html">Playground</a> ·
82
+ <a href="https://github.com/yurukusa/cc-safe-setup">GitHub</a>
83
+ </div>
84
+ </div>
85
+
86
+ <script>
87
+ const TEMPLATES = [
88
+ { title: 'Block rm -rf /', desc: 'Block rm -rf on root directory', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'rm\\s+.*-rf\\s+/', action: 'block', msg: 'rm -rf on root directory' },
89
+ { title: 'Block force push', desc: 'Block git push --force', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'git\\s+push\\s+.*--force', action: 'block', msg: 'Force push' },
90
+ { title: 'Block .env commit', desc: 'Block git add .env files', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'git\\s+add\\s+.*\\.env', action: 'block', msg: 'Adding .env to git' },
91
+ { title: 'Block sudo', desc: 'Block all sudo commands', trigger: 'PreToolUse', matcher: 'Bash', pattern: '^\\s*sudo\\b', action: 'block', msg: 'sudo command' },
92
+ { title: 'Block DB drop', desc: 'Block DROP DATABASE/TABLE', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'DROP\\s+(DATABASE|TABLE)', action: 'block', msg: 'Database drop' },
93
+ { title: 'Auto-approve tests', desc: 'Auto-approve npm test, pytest, cargo test', trigger: 'PreToolUse', matcher: 'Bash', pattern: '^\\s*(npm\\s+test|pytest|cargo\\s+test)', action: 'approve', msg: 'Safe test command' },
94
+ { title: 'Warn large output', desc: 'Warn when output exceeds 50KB', trigger: 'PostToolUse', matcher: '', pattern: null, action: 'warn-output', msg: 'Large output consuming context' },
95
+ { title: 'Session end notify', desc: 'Desktop notification on session end', trigger: 'Stop', matcher: '', pattern: null, action: 'notify', msg: 'Session completed' },
96
+ { title: 'Block npm -g', desc: 'Block global npm install', trigger: 'PreToolUse', matcher: 'Bash', pattern: 'npm\\s+install\\s+(-g|--global)', action: 'block', msg: 'Global npm install' },
97
+ { title: 'Block deploy Friday', desc: 'Block deploy commands on Fridays', trigger: 'PreToolUse', matcher: 'Bash', pattern: null, action: 'friday', msg: 'No deploys on Friday' },
98
+ ];
99
+
100
+ document.getElementById('templates').innerHTML = TEMPLATES.map((t,i) => `
101
+ <div class="tmpl" onclick="useTemplate(${i})">
102
+ <div class="tmpl-title">${t.title}</div>
103
+ <div>${t.desc}</div>
104
+ </div>
105
+ `).join('');
106
+
107
+ function useTemplate(i) {
108
+ const t = TEMPLATES[i];
109
+ document.getElementById('desc').value = t.desc;
110
+ document.getElementById('trigger').value = t.trigger;
111
+ document.getElementById('matcher').value = t.matcher;
112
+ generate(t);
113
+ }
114
+
115
+ function clearOutput() {
116
+ document.getElementById('output').classList.add('hidden');
117
+ }
118
+
119
+ function generate(template) {
120
+ const desc = document.getElementById('desc').value.trim();
121
+ const trigger = document.getElementById('trigger').value;
122
+ const matcher = document.getElementById('matcher').value.trim();
123
+
124
+ if (!desc && !template) return;
125
+
126
+ let hookCode, hookName;
127
+
128
+ if (template) {
129
+ hookName = template.title.toLowerCase().replace(/[^a-z0-9]+/g, '-');
130
+ hookCode = generateFromTemplate(template);
131
+ } else {
132
+ hookName = desc.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 30);
133
+ hookCode = generateFromDesc(desc, trigger, matcher);
134
+ }
135
+
136
+ const settingsCode = JSON.stringify({
137
+ hooks: {
138
+ [trigger]: [{
139
+ matcher: matcher,
140
+ hooks: [{ type: "command", command: `bash ~/.claude/hooks/${hookName}.sh` }]
141
+ }]
142
+ }
143
+ }, null, 2);
144
+
145
+ document.getElementById('hook-code').querySelector('code').textContent = hookCode;
146
+ document.getElementById('settings-code').querySelector('code').textContent = settingsCode;
147
+ document.getElementById('cli-cmd').textContent = `npx cc-safe-setup --create "${desc}"`;
148
+ document.getElementById('output').classList.remove('hidden');
149
+ }
150
+
151
+ function generateFromTemplate(t) {
152
+ if (t.action === 'block') {
153
+ return `#!/bin/bash
154
+ # ${t.desc}
155
+ # TRIGGER: ${t.trigger} MATCHER: "${t.matcher}"
156
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
157
+ [ -z "$COMMAND" ] && exit 0
158
+ if echo "$COMMAND" | grep -qE '${t.pattern}'; then
159
+ echo "BLOCKED: ${t.msg}" >&2
160
+ exit 2
161
+ fi
162
+ exit 0`;
163
+ }
164
+ if (t.action === 'approve') {
165
+ return `#!/bin/bash
166
+ # ${t.desc}
167
+ # TRIGGER: ${t.trigger} MATCHER: "${t.matcher}"
168
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
169
+ [ -z "$COMMAND" ] && exit 0
170
+ if echo "$COMMAND" | grep -qE '${t.pattern}'; then
171
+ echo '{"decision":"approve","reason":"${t.msg}"}'
172
+ fi
173
+ exit 0`;
174
+ }
175
+ if (t.action === 'warn-output') {
176
+ return `#!/bin/bash
177
+ # ${t.desc}
178
+ # TRIGGER: PostToolUse MATCHER: ""
179
+ OUTPUT=$(cat | jq -r '.tool_result // empty' 2>/dev/null)
180
+ LEN=\${#OUTPUT}
181
+ if [ "$LEN" -gt 50000 ]; then
182
+ echo "WARNING: Output is \${LEN} chars — consuming context fast" >&2
183
+ fi
184
+ exit 0`;
185
+ }
186
+ if (t.action === 'notify') {
187
+ return `#!/bin/bash
188
+ # ${t.desc}
189
+ # TRIGGER: Stop MATCHER: ""
190
+ notify-send "Claude Code" "Session completed" 2>/dev/null || \\
191
+ osascript -e 'display notification "Session completed"' 2>/dev/null
192
+ exit 0`;
193
+ }
194
+ if (t.action === 'friday') {
195
+ return `#!/bin/bash
196
+ # ${t.desc}
197
+ # TRIGGER: PreToolUse MATCHER: "Bash"
198
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
199
+ [ -z "$COMMAND" ] && exit 0
200
+ if [ "$(date +%u)" = "5" ]; then
201
+ if echo "$COMMAND" | grep -qE '(deploy|publish|release)'; then
202
+ echo "BLOCKED: No deploys on Friday" >&2
203
+ exit 2
204
+ fi
205
+ fi
206
+ exit 0`;
207
+ }
208
+ return '#!/bin/bash\n# Custom hook\nexit 0';
209
+ }
210
+
211
+ function generateFromDesc(desc, trigger, matcher) {
212
+ const d = desc.toLowerCase();
213
+ let pattern = '', action = 'block', msg = desc;
214
+
215
+ if (d.includes('block') || d.includes('prevent') || d.includes('stop')) {
216
+ action = 'block';
217
+ if (d.includes('rm') || d.includes('delete')) pattern = 'rm\\s+.*-rf';
218
+ else if (d.includes('push') && d.includes('main')) pattern = 'git\\s+push\\s+.*\\b(main|master)\\b';
219
+ else if (d.includes('force')) pattern = '--force';
220
+ else if (d.includes('sudo')) pattern = '^\\s*sudo\\b';
221
+ else if (d.includes('drop')) pattern = 'DROP\\s+(DATABASE|TABLE)';
222
+ else if (d.includes('publish')) pattern = 'npm\\s+publish';
223
+ else if (d.includes('deploy')) pattern = '(deploy|vercel|netlify|firebase)';
224
+ else pattern = d.split(' ').pop();
225
+ } else if (d.includes('approve') || d.includes('allow') || d.includes('auto')) {
226
+ action = 'approve';
227
+ if (d.includes('test')) pattern = '(npm\\s+test|pytest|cargo\\s+test)';
228
+ else if (d.includes('build')) pattern = '(npm\\s+run\\s+build|cargo\\s+build|go\\s+build)';
229
+ else if (d.includes('git')) pattern = 'git\\s+(status|log|diff)';
230
+ else pattern = d.split(' ').pop();
231
+ } else if (d.includes('warn') || d.includes('alert') || d.includes('notify')) {
232
+ action = 'warn';
233
+ pattern = d.split(' ').pop();
234
+ }
235
+
236
+ if (action === 'block') {
237
+ return `#!/bin/bash
238
+ # ${desc}
239
+ # TRIGGER: ${trigger} MATCHER: "${matcher}"
240
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
241
+ [ -z "$COMMAND" ] && exit 0
242
+ if echo "$COMMAND" | grep -qiE '${pattern}'; then
243
+ echo "BLOCKED: ${msg}" >&2
244
+ exit 2
245
+ fi
246
+ exit 0`;
247
+ }
248
+ if (action === 'approve') {
249
+ return `#!/bin/bash
250
+ # ${desc}
251
+ # TRIGGER: ${trigger} MATCHER: "${matcher}"
252
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
253
+ [ -z "$COMMAND" ] && exit 0
254
+ if echo "$COMMAND" | grep -qiE '${pattern}'; then
255
+ echo '{"decision":"approve","reason":"${msg}"}'
256
+ fi
257
+ exit 0`;
258
+ }
259
+ return `#!/bin/bash
260
+ # ${desc}
261
+ # TRIGGER: ${trigger} MATCHER: "${matcher}"
262
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
263
+ [ -z "$COMMAND" ] && exit 0
264
+ if echo "$COMMAND" | grep -qiE '${pattern}'; then
265
+ echo "WARNING: ${msg}" >&2
266
+ fi
267
+ exit 0`;
268
+ }
269
+
270
+ function copyCode() {
271
+ navigator.clipboard.writeText(document.getElementById('hook-code').querySelector('code').textContent);
272
+ document.getElementById('hook-code').querySelector('.copy-btn').textContent = 'Copied!';
273
+ setTimeout(() => document.getElementById('hook-code').querySelector('.copy-btn').textContent = 'Copy', 1500);
274
+ }
275
+
276
+ function copySettings() {
277
+ navigator.clipboard.writeText(document.getElementById('settings-code').querySelector('code').textContent);
278
+ document.getElementById('settings-code').querySelector('.copy-btn').textContent = 'Copied!';
279
+ setTimeout(() => document.getElementById('settings-code').querySelector('.copy-btn').textContent = 'Copy Settings', 1500);
280
+ }
281
+ </script>
282
+ </body>
283
+ </html>
package/docs/faq.html ADDED
@@ -0,0 +1,244 @@
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 Hooks FAQ — Every Question Answered</title>
7
+ <meta name="description" content="Answers to every common question about Claude Code hooks. Why hooks? How do they work? What about performance?">
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:800px;margin:0 auto}
12
+ h1{color:#f0f6fc;font-size:1.5rem;margin-bottom:.3rem}
13
+ .sub{color:#8b949e;font-size:.85rem;margin-bottom:1.5rem}
14
+ a{color:#58a6ff;text-decoration:none}
15
+ code{background:#161b22;padding:.15rem .3rem;border-radius:3px;font-size:.85rem}
16
+ pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.7rem;font-size:.8rem;color:#e6edf3;overflow-x:auto;margin:.4rem 0}
17
+ details{background:#161b22;border:1px solid #30363d;border-radius:6px;margin:.5rem 0}
18
+ summary{padding:.7rem .9rem;cursor:pointer;font-weight:600;color:#f0f6fc;font-size:.9rem;list-style:none}
19
+ summary::before{content:"▸ ";color:#58a6ff}
20
+ details[open] summary::before{content:"▾ "}
21
+ details[open] summary{border-bottom:1px solid #21262d}
22
+ .answer{padding:.7rem .9rem;font-size:.85rem}
23
+ .answer p{margin:.4rem 0}
24
+ .cat{color:#58a6ff;font-size:.75rem;font-weight:bold;text-transform:uppercase;margin:1.5rem 0 .5rem;letter-spacing:.05em}
25
+ .footer{text-align:center;color:#484f58;font-size:.7rem;margin-top:2rem;padding-top:1rem;border-top:1px solid #21262d}
26
+ .quick{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:.8rem;margin:.8rem 0;font-size:.82rem}
27
+ </style>
28
+ </head>
29
+ <body>
30
+ <div class="c">
31
+
32
+ <h1>Claude Code Hooks FAQ</h1>
33
+ <p class="sub">Every question we've been asked, answered. <a href="https://docs.anthropic.com/en/docs/claude-code/hooks">Official docs</a></p>
34
+
35
+ <div class="quick">
36
+ <strong>TL;DR:</strong> Hooks are shell scripts that run before/after Claude Code tools. <code>exit 2</code> = block. <code>exit 0</code> = allow. Install 8 safety hooks: <code>npx cc-safe-setup</code>
37
+ </div>
38
+
39
+ <div class="cat">Basics</div>
40
+
41
+ <details>
42
+ <summary>What are Claude Code hooks?</summary>
43
+ <div class="answer">
44
+ <p>Shell scripts that run at specific points in Claude Code's lifecycle:</p>
45
+ <ul>
46
+ <li><strong>PreToolUse</strong> — before any tool (Bash, Edit, Write) executes</li>
47
+ <li><strong>PostToolUse</strong> — after a tool completes</li>
48
+ <li><strong>Stop</strong> — when Claude finishes responding</li>
49
+ </ul>
50
+ <p>They're configured in <code>~/.claude/settings.json</code> and enforced at the process level — the model cannot bypass them.</p>
51
+ </div>
52
+ </details>
53
+
54
+ <details>
55
+ <summary>How is this different from CLAUDE.md?</summary>
56
+ <div class="answer">
57
+ <p><strong>CLAUDE.md</strong> is a prompt-level instruction. It works well at the start of a session but degrades as context fills up. After 100+ tool calls, Claude may "forget" rules.</p>
58
+ <p><strong>Hooks</strong> run on every single tool call, enforced by the runtime. Even if Claude wants to ignore them, <code>exit 2</code> physically prevents the action.</p>
59
+ <p>Use both: CLAUDE.md for guidelines, hooks for hard constraints.</p>
60
+ </div>
61
+ </details>
62
+
63
+ <details>
64
+ <summary>Do I need to know bash to use hooks?</summary>
65
+ <div class="answer">
66
+ <p>Not really. You can:</p>
67
+ <ol>
68
+ <li>Use <code>npx cc-safe-setup</code> to install pre-built hooks (zero coding)</li>
69
+ <li>Use <code>npx cc-safe-setup --create "your description"</code> to generate hooks from English</li>
70
+ <li>Use the <a href="builder.html">Hook Builder</a> web tool</li>
71
+ <li>Copy-paste from the <a href="hooks-cheatsheet.html">Cheat Sheet</a></li>
72
+ </ol>
73
+ </div>
74
+ </details>
75
+
76
+ <div class="cat">How It Works</div>
77
+
78
+ <details>
79
+ <summary>What does exit code 2 do?</summary>
80
+ <div class="answer">
81
+ <p><code>exit 2</code> blocks the tool call. The model receives a message that the operation was blocked and must try a different approach.</p>
82
+ <p><code>exit 0</code> allows the operation (default).</p>
83
+ <p><code>exit 1</code> is treated as a hook error and is silently ignored — don't use it for blocking.</p>
84
+ </div>
85
+ </details>
86
+
87
+ <details>
88
+ <summary>What JSON does the hook receive on stdin?</summary>
89
+ <div class="answer">
90
+ <p>For <strong>PreToolUse</strong> (Bash):</p>
91
+ <pre><code>{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}</code></pre>
92
+ <p>For <strong>PreToolUse</strong> (Edit):</p>
93
+ <pre><code>{"tool_name":"Edit","tool_input":{"file_path":"app.js","old_string":"...","new_string":"..."}}</code></pre>
94
+ <p>For <strong>PostToolUse</strong>:</p>
95
+ <pre><code>{"tool_name":"Bash","tool_input":{"command":"ls"},"tool_result":"file1.txt\nfile2.txt"}</code></pre>
96
+ <p>Extract with: <code>cat | jq -r '.tool_input.command // empty'</code></p>
97
+ </div>
98
+ </details>
99
+
100
+ <details>
101
+ <summary>Can I auto-approve commands via hooks?</summary>
102
+ <div class="answer">
103
+ <p>Yes. Output JSON to stdout:</p>
104
+ <pre><code>echo '{"decision":"approve","reason":"Safe test command"}'
105
+ exit 0</code></pre>
106
+ <p>This skips the permission prompt for that specific tool call.</p>
107
+ </div>
108
+ </details>
109
+
110
+ <details>
111
+ <summary>What does the "matcher" field do?</summary>
112
+ <div class="answer">
113
+ <p>It's a regex matched against the tool name:</p>
114
+ <ul>
115
+ <li><code>"Bash"</code> — only Bash commands</li>
116
+ <li><code>"Edit|Write"</code> — file modifications</li>
117
+ <li><code>""</code> (empty) — matches ALL tools</li>
118
+ <li><code>"Read"</code> — file reads</li>
119
+ </ul>
120
+ </div>
121
+ </details>
122
+
123
+ <div class="cat">Performance & Safety</div>
124
+
125
+ <details>
126
+ <summary>Do hooks slow down Claude Code?</summary>
127
+ <div class="answer">
128
+ <p>No. Each hook runs in ~5ms (bash + jq). Even with 10 hooks chained, total overhead is <50ms per tool call — imperceptible compared to the API latency.</p>
129
+ <p>You can measure: <code>npx cc-safe-setup --benchmark</code></p>
130
+ </div>
131
+ </details>
132
+
133
+ <details>
134
+ <summary>Can Claude disable or modify hooks?</summary>
135
+ <div class="answer">
136
+ <p>Claude can edit <code>settings.json</code> via the Edit tool. To prevent this:</p>
137
+ <pre><code>npx cc-safe-setup --install-example protect-claudemd</code></pre>
138
+ <p>This hook blocks edits to CLAUDE.md and settings files.</p>
139
+ </div>
140
+ </details>
141
+
142
+ <details>
143
+ <summary>What if my hook has a bug?</summary>
144
+ <div class="answer">
145
+ <p>If a hook exits with code 1 (error), Claude Code treats it as a hook failure and <strong>ignores it</strong> — the tool call proceeds. This is by design: buggy hooks don't block all operations.</p>
146
+ <p>Only <code>exit 2</code> blocks. If your hook accidentally exits 2 for everything, use <code>npx cc-safe-setup --doctor</code> to diagnose.</p>
147
+ </div>
148
+ </details>
149
+
150
+ <details>
151
+ <summary>Can I use Python/TypeScript instead of bash?</summary>
152
+ <div class="answer">
153
+ <p>Yes. The <code>command</code> field can run anything:</p>
154
+ <pre><code>{"type": "command", "command": "python3 ~/.claude/hooks/my-guard.py"}</code></pre>
155
+ <p>cc-safe-setup includes Python examples: <code>examples/python/destructive_guard.py</code></p>
156
+ <p>For TypeScript, see <a href="https://www.npmjs.com/package/@anthropic-ai/claude-code-safety-net">safety-net</a> (1,185★).</p>
157
+ </div>
158
+ </details>
159
+
160
+ <div class="cat">Common Issues</div>
161
+
162
+ <details>
163
+ <summary>My hook doesn't fire — what's wrong?</summary>
164
+ <div class="answer">
165
+ <p>Run diagnostics: <code>npx cc-safe-setup --doctor</code></p>
166
+ <p>Common causes:</p>
167
+ <ol>
168
+ <li><strong>Not executable:</strong> <code>chmod +x ~/.claude/hooks/your-hook.sh</code></li>
169
+ <li><strong>Missing shebang:</strong> First line must be <code>#!/bin/bash</code></li>
170
+ <li><strong>Wrong matcher:</strong> <code>"Bash"</code> won't fire for Edit/Write tools</li>
171
+ <li><strong>settings.json syntax error:</strong> Validate with <code>python3 -c "import json; json.load(open('$HOME/.claude/settings.json'))"</code></li>
172
+ <li><strong>jq not installed:</strong> <code>which jq</code> — install if missing</li>
173
+ </ol>
174
+ <p>Quick fix: <code>npx cc-safe-setup --quickfix</code></p>
175
+ </div>
176
+ </details>
177
+
178
+ <details>
179
+ <summary>My hook blocks everything</summary>
180
+ <div class="answer">
181
+ <p>Your hook is returning <code>exit 2</code> for all inputs. Debug by testing manually:</p>
182
+ <pre><code>echo '{"tool_input":{"command":"ls"}}' | bash ~/.claude/hooks/your-hook.sh
183
+ echo "Exit: $?"</code></pre>
184
+ <p>If it exits 2 for <code>ls</code>, your regex is too broad. Check the <code>grep -qE</code> pattern.</p>
185
+ </div>
186
+ </details>
187
+
188
+ <details>
189
+ <summary>Multiple hooks — does order matter?</summary>
190
+ <div class="answer">
191
+ <p>Hooks in the same group run sequentially. If any hook exits 2, the tool call is blocked — remaining hooks don't run.</p>
192
+ <p><strong>Important:</strong> Each hook consumes stdin. If you chain multiple hooks in the same group, the second hook gets empty stdin. Put each hook in a separate matcher group, or use <code>tee</code>.</p>
193
+ </div>
194
+ </details>
195
+
196
+ <details>
197
+ <summary>Can hooks access the internet?</summary>
198
+ <div class="answer">
199
+ <p>Yes. Hooks are regular shell scripts — they can make HTTP requests, send notifications, write to databases, etc.</p>
200
+ <p>Example: send a Slack notification when a dangerous command is blocked:</p>
201
+ <pre><code>curl -s -X POST "$SLACK_WEBHOOK" -d "{\"text\":\"Blocked: $COMMAND\"}"</code></pre>
202
+ </div>
203
+ </details>
204
+
205
+ <div class="cat">Installation</div>
206
+
207
+ <details>
208
+ <summary>How do I install hooks?</summary>
209
+ <div class="answer">
210
+ <p>Three ways:</p>
211
+ <ol>
212
+ <li><strong>One command:</strong> <code>npx cc-safe-setup</code> (installs 8 safety hooks)</li>
213
+ <li><strong>Individual:</strong> <code>npx cc-safe-setup --install-example block-database-wipe</code></li>
214
+ <li><strong>Manual:</strong> Save script to <code>~/.claude/hooks/</code>, <code>chmod +x</code>, add to <code>settings.json</code></li>
215
+ </ol>
216
+ </div>
217
+ </details>
218
+
219
+ <details>
220
+ <summary>How do I uninstall hooks?</summary>
221
+ <div class="answer">
222
+ <pre><code>npx cc-safe-setup --uninstall</code></pre>
223
+ <p>Or manually: remove the hook entries from <code>~/.claude/settings.json</code> and delete the script files.</p>
224
+ </div>
225
+ </details>
226
+
227
+ <details>
228
+ <summary>Where are hooks stored?</summary>
229
+ <div class="answer">
230
+ <p>Scripts: <code>~/.claude/hooks/</code></p>
231
+ <p>Configuration: <code>~/.claude/settings.json</code> (global) or <code>.claude/settings.json</code> (project)</p>
232
+ <p>Project-level hooks override global hooks for the same matcher.</p>
233
+ </div>
234
+ </details>
235
+
236
+ <div class="footer">
237
+ <a href="hooks-cheatsheet.html">Cheat Sheet</a> ·
238
+ <a href="builder.html">Hook Builder</a> ·
239
+ <a href="https://yurukusa.github.io/cc-hook-registry/playground.html">Playground</a> ·
240
+ <a href="https://github.com/yurukusa/cc-safe-setup">GitHub</a>
241
+ </div>
242
+ </div>
243
+ </body>
244
+ </html>
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # auto-stash-before-pull.sh — Suggest stash before git pull/merge
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude runs git pull/merge with uncommitted changes, causing
7
+ # merge conflicts or lost work. This hook warns and suggests
8
+ # git stash before pull/merge/rebase operations.
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 pull/merge/rebase
17
+ echo "$COMMAND" | grep -qE '\bgit\s+(pull|merge|rebase)\b' || exit 0
18
+
19
+ # Check for uncommitted changes
20
+ DIRTY=$(git status --porcelain 2>/dev/null)
21
+ if [ -n "$DIRTY" ]; then
22
+ COUNT=$(echo "$DIRTY" | wc -l)
23
+ echo "WARNING: git pull/merge/rebase with $COUNT uncommitted change(s)." >&2
24
+ echo "Consider running: git stash && git pull && git stash pop" >&2
25
+ fi
26
+
27
+ 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,36 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # compact-reminder.sh — Remind to /compact when context is low
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # After Claude responds (Stop event), check how many tool calls
7
+ # have been made in the session. If the count exceeds a threshold,
8
+ # suggest running /compact to free up context space.
9
+ #
10
+ # TRIGGER: Stop MATCHER: ""
11
+ #
12
+ # CONFIG:
13
+ # CC_COMPACT_THRESHOLD=100 (suggest after 100 tool calls)
14
+ # ================================================================
15
+
16
+ THRESHOLD="${CC_COMPACT_THRESHOLD:-100}"
17
+ STATE="/tmp/cc-tool-count-$(echo "$PWD" | md5sum | cut -c1-8)"
18
+
19
+ # Increment counter
20
+ COUNT=1
21
+ [ -f "$STATE" ] && COUNT=$(($(cat "$STATE") + 1))
22
+ echo "$COUNT" > "$STATE"
23
+
24
+ if [ "$COUNT" -eq "$THRESHOLD" ]; then
25
+ echo "" >&2
26
+ echo "NOTE: $COUNT tool calls in this session." >&2
27
+ echo "Consider running /compact to free context space." >&2
28
+ echo "Reset counter: rm $STATE" >&2
29
+ fi
30
+
31
+ # Repeat reminder every 50 calls after threshold
32
+ if [ "$COUNT" -gt "$THRESHOLD" ] && [ $(( (COUNT - THRESHOLD) % 50 )) -eq 0 ]; then
33
+ echo "REMINDER: $COUNT tool calls. /compact recommended." >&2
34
+ fi
35
+
36
+ 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.8.0",
3
+ "version": "8.0.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": {