cc-safe-setup 28.4.1 → 28.4.2

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,13 +6,13 @@
6
6
 
7
7
  **One command to make Claude Code safe for autonomous operation.** [日本語](docs/README.ja.md)
8
8
 
9
- 8 built-in + 319 examples = **327 hooks**. 45 CLI commands. 955 tests. 5 languages. [**Getting Started**](https://yurukusa.github.io/cc-safe-setup/getting-started.html) · [**Hub**](https://yurukusa.github.io/cc-safe-setup/hub.html) · [**Recipes**](https://yurukusa.github.io/cc-safe-setup/recipes.html) · [Wizard](https://yurukusa.github.io/cc-safe-setup/wizard.html) · [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) · [Matrix](https://yurukusa.github.io/cc-safe-setup/matrix.html) · [Playground](https://yurukusa.github.io/cc-hook-registry/playground.html)
10
-
11
9
  ```bash
12
10
  npx cc-safe-setup
13
11
  ```
14
12
 
15
- Installs 8 production-tested safety hooks in ~10 seconds. Zero dependencies. No manual configuration.
13
+ Installs 8 safety hooks in ~10 seconds. Blocks `rm -rf /`, prevents pushes to main, catches secret leaks, validates syntax after every edit. Zero dependencies.
14
+
15
+ [**Getting Started**](https://yurukusa.github.io/cc-safe-setup/getting-started.html) · [**All Tools**](https://yurukusa.github.io/cc-safe-setup/hub.html) · [**Recipes**](https://yurukusa.github.io/cc-safe-setup/recipes.html) · [Validate your settings.json](https://yurukusa.github.io/cc-safe-setup/validator.html)
16
16
 
17
17
  ```
18
18
  cc-safe-setup
@@ -0,0 +1,42 @@
1
+ INPUT=$(cat)
2
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
3
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
4
+ RESULT=$(echo "$INPUT" | jq -r '.tool_result // empty' 2>/dev/null)
5
+ [ -z "$COMMAND" ] && exit 0
6
+ if [ "${CC_REQUIRE_TESTS:-0}" = "1" ]; then
7
+ if echo "$COMMAND" | grep -qE '^\s*git\s+commit'; then
8
+ LAST_TEST=$(stat -c %Y coverage/.last-run.json 2>/dev/null || stat -c %Y test-results 2>/dev/null || echo 0)
9
+ NOW=$(date +%s)
10
+ if [ "$LAST_TEST" -eq 0 ] || [ $((NOW - LAST_TEST)) -gt 600 ]; then
11
+ echo "⚠ CLAUDE.md VIOLATION: Committed without running tests first" >&2
12
+ fi
13
+ fi
14
+ fi
15
+ if [ -n "${CC_ENFORCED_BRANCH}" ]; then
16
+ if echo "$COMMAND" | grep -qE "git\s+push.*${CC_ENFORCED_BRANCH}"; then
17
+ echo "⚠ CLAUDE.md VIOLATION: Pushed to protected branch '${CC_ENFORCED_BRANCH}'" >&2
18
+ fi
19
+ fi
20
+ if [ "${CC_NO_FORCE_PUSH:-1}" = "1" ]; then
21
+ if echo "$COMMAND" | grep -qE 'git\s+push.*--force'; then
22
+ echo "⚠ CLAUDE.md VIOLATION: Force push detected" >&2
23
+ fi
24
+ fi
25
+ MAX_FILES=${CC_MAX_FILES_PER_COMMIT:-20}
26
+ if echo "$COMMAND" | grep -qE '^\s*git\s+commit'; then
27
+ STAGED=$(git diff --cached --name-only 2>/dev/null | wc -l)
28
+ if [ "$STAGED" -gt "$MAX_FILES" ]; then
29
+ echo "⚠ CLAUDE.md VIOLATION: Committing $STAGED files (max: $MAX_FILES)" >&2
30
+ fi
31
+ fi
32
+ if echo "$COMMAND" | grep -qE '^\s*git\s+add'; then
33
+ STAGED_FILES=$(git diff --cached --name-only 2>/dev/null)
34
+ for f in $STAGED_FILES; do
35
+ [ -f "$f" ] || continue
36
+ if grep -qE 'console\.log\(|debugger;|print\(' "$f" 2>/dev/null; then
37
+ echo "⚠ CLAUDE.md REMINDER: Debug statements found in staged file: $f" >&2
38
+ break
39
+ fi
40
+ done
41
+ fi
42
+ exit 0
@@ -0,0 +1,17 @@
1
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
2
+ [ -z "$COMMAND" ] && exit 0
3
+ echo "$COMMAND" | grep -qE '^\s*git\s+commit' || exit 0
4
+ RECENT=0
5
+ TIMEOUT=${CC_TEST_TIMEOUT:-600}
6
+ NOW=$(date +%s)
7
+ for marker in coverage/.last-run.json test-results .nyc_output junit.xml; do
8
+ [ -e "$marker" ] || continue
9
+ MTIME=$(stat -c %Y "$marker" 2>/dev/null || echo 0)
10
+ [ $((NOW - MTIME)) -lt "$TIMEOUT" ] && RECENT=1 && break
11
+ done
12
+ if [ "$RECENT" -eq 0 ]; then
13
+ echo "BLOCKED: No recent test results (within $((TIMEOUT/60)) min)" >&2
14
+ echo "Run your test suite first, then commit." >&2
15
+ exit 2
16
+ fi
17
+ exit 0
@@ -0,0 +1,13 @@
1
+ COUNTER="${HOME}/.claude/session-tool-count"
2
+ COUNT=$(cat "$COUNTER" 2>/dev/null || echo 0)
3
+ COUNT=$((COUNT + 1))
4
+ echo "$COUNT" > "$COUNTER"
5
+ WARN1=${CC_USAGE_WARN1:-100}
6
+ WARN2=${CC_USAGE_WARN2:-200}
7
+ WARN3=${CC_USAGE_WARN3:-300}
8
+ case "$COUNT" in
9
+ "$WARN1") echo "NOTE: $COUNT tool calls this session" >&2 ;;
10
+ "$WARN2") echo "WARNING: $COUNT tool calls — consider wrapping up" >&2 ;;
11
+ "$WARN3") echo "ALERT: $COUNT tool calls — approaching typical session limit" >&2 ;;
12
+ esac
13
+ exit 0
package/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, copyFileSync } from 'fs';
3
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, copyFileSync, unlinkSync } from 'fs';
4
4
  import { join, dirname } from 'path';
5
5
  import { homedir } from 'os';
6
6
  import { createInterface } from 'readline';
@@ -11,6 +11,9 @@ const HOME = homedir();
11
11
  const HOOKS_DIR = join(HOME, '.claude', 'hooks');
12
12
  const SETTINGS_PATH = join(HOME, '.claude', 'settings.json');
13
13
 
14
+ // Convert Windows backslash paths to bash-compatible forward slashes
15
+ const toBashPath = (p) => p.replace(/\\/g, '/');
16
+
14
17
  const c = {
15
18
  reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
16
19
  red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
@@ -132,52 +135,43 @@ if (HELP) {
132
135
  console.log(`
133
136
  cc-safe-setup — Make Claude Code safe for autonomous operation
134
137
 
135
- Usage:
136
- npx cc-safe-setup Install 8 safety hooks
137
- npx cc-safe-setup --status Check installed hooks
138
- npx cc-safe-setup --verify Test each hook with sample inputs
139
- npx cc-safe-setup --dry-run Preview without installing
140
- npx cc-safe-setup --uninstall Remove all installed hooks
141
- npx cc-safe-setup --examples List 30 example hooks (5 categories)
142
- npx cc-safe-setup --install-example <name> Install a specific example
143
- npx cc-safe-setup --full Complete setup: hooks + scan + audit + badge
144
- npx cc-safe-setup --audit Safety score (0-100) with fixes
145
- npx cc-safe-setup --audit --fix Auto-fix missing protections
146
- npx cc-safe-setup --audit --json Machine-readable output for CI/CD
147
- npx cc-safe-setup --scan Detect tech stack, recommend hooks
148
- npx cc-safe-setup --learn Learn from your block history
149
- npx cc-safe-setup --generate-ci Generate GitHub Actions workflow for safety checks
150
- npx cc-safe-setup --migrate Detect hooks from other projects, suggest replacements
151
- npx cc-safe-setup --compare <a> <b> Compare two hooks side-by-side
152
- npx cc-safe-setup --issues Show GitHub Issues each hook addresses
153
- npx cc-safe-setup --dashboard Real-time status dashboard
154
- npx cc-safe-setup --benchmark Measure hook execution time
155
- npx cc-safe-setup --share Generate shareable URL for your setup
156
- npx cc-safe-setup --diff <file> Compare your settings with another file
157
- npx cc-safe-setup --lint Static analysis of hook configuration
158
- npx cc-safe-setup --doctor Diagnose why hooks aren't working
159
- npx cc-safe-setup --simulate "rm -rf /" See how hooks react to a command
160
- npx cc-safe-setup --protect .env Block edits to a specific file/dir
161
- npx cc-safe-setup --rules [file] Compile YAML rules into a single hook
162
- npx cc-safe-setup --watch Live dashboard of blocked commands
163
- npx cc-safe-setup --create "<desc>" Generate a custom hook from description
164
- npx cc-safe-setup --test-hook <name> Test a specific hook with sample inputs
165
- npx cc-safe-setup --save-profile <name> Save current hooks as a named profile
166
- npx cc-safe-setup --changelog Show what changed in recent versions
167
- npx cc-safe-setup --score Print safety score (0-100) and exit
168
- npx cc-safe-setup --init-project Complete project setup (CLAUDE.md + hooks + CI + .gitignore)
169
- npx cc-safe-setup --suggest Analyze project and predict risks → suggest hooks
170
- npx cc-safe-setup --why <hook> Why this hook exists (real incident + issue link)
171
- npx cc-safe-setup --replay Replay blocked commands timeline (demo/review)
172
- npx cc-safe-setup --guard "<rule>" Instantly enforce a rule (generate + install + activate)
173
- npx cc-safe-setup --diff-hooks <path> Compare hooks between two settings files
174
- npx cc-safe-setup --from-claudemd Convert CLAUDE.md rules into hooks
175
- npx cc-safe-setup --health Hook health dashboard (size, permissions, age)
176
- npx cc-safe-setup --migrate-from <tool> Migrate from safety-net/hooks-mastery/etc.
177
- npx cc-safe-setup --team Set up project-level hooks (commit to repo for team)
178
- npx cc-safe-setup --profile <level> Switch safety profile (strict/standard/minimal)
179
- npx cc-safe-setup --analyze Analyze what Claude did in your last session
180
- npx cc-safe-setup --shield Maximum safety in one command (fix + scan + install + CLAUDE.md)
138
+ Quick Start:
139
+ npx cc-safe-setup Install 8 safety hooks (30 sec)
140
+ npx cc-safe-setup --shield Maximum safety — one command
141
+ npx cc-safe-setup --doctor Diagnose hook problems
142
+
143
+ Protect:
144
+ --protect .env Block edits to a specific file
145
+ --guard "never delete prod" Enforce a rule from plain English
146
+ --simulate "rm -rf /" Preview how hooks react to a command
147
+ --validate Check all hooks for errors (auto-fix)
148
+ --safe-mode Emergency: disable all hooks
149
+
150
+ Install & Configure:
151
+ --install-example <name> Install from 300+ example hooks
152
+ --examples Browse examples by category
153
+ --from-claudemd Convert CLAUDE.md rules into hooks
154
+ --rules [file] Compile YAML rules into hooks
155
+ --team Set up project-level hooks for git
156
+ --profile <level> Switch profile (strict/standard/minimal)
157
+ --shield Full setup: hooks + scan + CLAUDE.md
158
+
159
+ Diagnose & Monitor:
160
+ --status / --verify Check installed hooks / test them
161
+ --doctor 13-point diagnostic
162
+ --audit [--fix] [--json] Safety score 0-100
163
+ --watch Live blocked command feed
164
+ --health Hook health dashboard
165
+ --suggest Predict risks from project analysis
166
+
167
+ Manage:
168
+ --dry-run / --uninstall Preview / remove hooks
169
+ --export / --import Share configuration
170
+ --diff <file> Compare settings
171
+ --lint Static analysis of config
172
+
173
+ More: --create, --why, --replay, --score, --changelog, --analyze,
174
+ --benchmark, --dashboard, --migrate-from, --init-project
181
175
  npx cc-safe-setup --quickfix Auto-detect and fix common Claude Code problems
182
176
  npx cc-safe-setup --stats Block statistics and patterns report
183
177
  npx cc-safe-setup --export Export hooks config for team sharing
@@ -311,6 +305,8 @@ function status() {
311
305
  console.log(' ' + c.dim + '+ ' + installedExamples.length + ' example hooks' + c.reset);
312
306
  }
313
307
  console.log();
308
+ console.log(c.dim + ' Tip: --validate to check health · --simulate "cmd" to test · --shield for max safety' + c.reset);
309
+ console.log();
314
310
 
315
311
  // Exit code for CI: 0 = all installed, 1 = missing hooks
316
312
  if (missing > 0) process.exit(1);
@@ -621,7 +617,7 @@ async function installExample(name) {
621
617
 
622
618
  const hookEntry = {
623
619
  matcher: matcher,
624
- hooks: [{ type: 'command', command: destPath }],
620
+ hooks: [{ type: 'command', command: toBashPath(destPath) }],
625
621
  };
626
622
 
627
623
  // Check if already installed
@@ -2220,7 +2216,7 @@ async function profile(level) {
2220
2216
 
2221
2217
  // Register all hooks in settings
2222
2218
  const hookFiles = prof.hooks.filter(h => existsSync(join(HOOKS_DIR, `${h}.sh`)));
2223
- const bashHooks = hookFiles.map(h => ({ type: 'command', command: `bash ${join(HOOKS_DIR, h + '.sh')}` }));
2219
+ const bashHooks = hookFiles.map(h => ({ type: 'command', command: `bash ${toBashPath(join(HOOKS_DIR, h + '.sh'))}` }));
2224
2220
 
2225
2221
  // Simplified: put all under PreToolUse Bash for now
2226
2222
  if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
@@ -3887,7 +3883,7 @@ exit 0`,
3887
3883
  if (!existing.some(cmd => cmd.includes(matched.name))) {
3888
3884
  settings.hooks[matched.trigger].push({
3889
3885
  matcher: matched.matcher,
3890
- hooks: [{ type: 'command', command: hookPath }],
3886
+ hooks: [{ type: 'command', command: toBashPath(hookPath) }],
3891
3887
  });
3892
3888
  writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2));
3893
3889
  console.log(c.green + ' ✓ Registered in settings.json' + c.reset);
@@ -4401,26 +4397,26 @@ async function compileRules(rulesFile) {
4401
4397
 
4402
4398
  # Block dangerous commands
4403
4399
  - block: "rm -rf on root or home"
4404
- pattern: "rm\\\\s+-rf\\\\s+(\\\\/$|~)"
4400
+ pattern: "rm\\s+-rf\\s+(\\/$|~)"
4405
4401
 
4406
4402
  - block: "git push to main"
4407
- pattern: "git\\\\s+push.*(main|master)"
4403
+ pattern: "git\\s+push.*(main|master)"
4408
4404
 
4409
4405
  - block: "git force push"
4410
- pattern: "git\\\\s+push.*--force"
4406
+ pattern: "git\\s+push.*--force"
4411
4407
 
4412
4408
  - block: "git add .env"
4413
- pattern: "git\\\\s+add.*\\\\.env"
4409
+ pattern: "git\\s+add.*\\.env"
4414
4410
 
4415
4411
  # Auto-approve safe commands
4416
4412
  - approve: "read-only commands"
4417
4413
  commands: [cat, head, tail, ls, grep, find, which, pwd, date]
4418
4414
 
4419
4415
  - approve: "git read commands"
4420
- pattern: "^\\\\s*git\\\\s+(status|log|diff|show|branch)"
4416
+ pattern: "^\\s*git\\s+(status|log|diff|show|branch)"
4421
4417
 
4422
4418
  - approve: "test runners"
4423
- pattern: "^\\\\s*(npm\\\\s+test|pytest|go\\\\s+test|cargo\\\\s+test)"
4419
+ pattern: "^\\s*(npm\\s+test|pytest|go\\s+test|cargo\\s+test)"
4424
4420
 
4425
4421
  # Protect files from edits
4426
4422
  - protect: ".env"
@@ -4444,8 +4440,8 @@ async function compileRules(rulesFile) {
4444
4440
  const blockMatch = trimmed.match(/^-\s+block:\s+"(.+)"/);
4445
4441
  const approveMatch = trimmed.match(/^-\s+approve:\s+"(.+)"/);
4446
4442
  const protectMatch = trimmed.match(/^-\s+protect:\s+"(.+)"/);
4447
- const patternMatch = trimmed.match(/^\s+pattern:\s+"(.+)"/);
4448
- const commandsMatch = trimmed.match(/^\s+commands:\s+\[(.+)\]/);
4443
+ const patternMatch = trimmed.match(/^pattern:\s+"(.+)"/);
4444
+ const commandsMatch = trimmed.match(/^commands:\s+\[(.+)\]/);
4449
4445
 
4450
4446
  if (blockMatch) { current = { type: 'block', name: blockMatch[1] }; rules.push(current); }
4451
4447
  else if (approveMatch) { current = { type: 'approve', name: approveMatch[1] }; rules.push(current); }
@@ -4539,6 +4535,20 @@ FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
4539
4535
  writeFileSync(hookPath, script);
4540
4536
  chmodSync(hookPath, 0o755);
4541
4537
 
4538
+ // Syntax check — a broken hook returns exit 2 which blocks ALL tools
4539
+ const { execSync } = await import('child_process');
4540
+ try {
4541
+ execSync(`bash -n "${hookPath}"`, { stdio: 'pipe' });
4542
+ } catch (e) {
4543
+ const stderr = (e.stderr || '').toString().trim();
4544
+ unlinkSync(hookPath);
4545
+ console.log(c.red + ' ✗ Syntax error in generated hook — aborted' + c.reset);
4546
+ if (stderr) console.log(c.dim + ' ' + stderr + c.reset);
4547
+ console.log(c.dim + ' Check your rules file for unescaped special characters.' + c.reset);
4548
+ console.log();
4549
+ return;
4550
+ }
4551
+
4542
4552
  // Register in settings.json
4543
4553
  let settings = {};
4544
4554
  if (existsSync(SETTINGS_PATH)) {
@@ -5285,7 +5295,7 @@ async function main() {
5285
5295
  if (!exists) {
5286
5296
  settings.hooks[trigger].push({
5287
5297
  matcher: hook.matcher,
5288
- hooks: [{ type: 'command', command: hookPath }]
5298
+ hooks: [{ type: 'command', command: toBashPath(hookPath) }]
5289
5299
  });
5290
5300
  }
5291
5301
  }
@@ -5297,10 +5307,13 @@ async function main() {
5297
5307
  console.log();
5298
5308
  console.log(c.bold + ' Done.' + c.reset + ' ' + Object.keys(HOOKS).length + ' safety hooks installed.');
5299
5309
  console.log(' ' + c.dim + 'Restart Claude Code to activate.' + c.reset);
5300
- console.log(' ' + c.dim + 'Verify:' + c.reset + ' ' + c.blue + 'npx cc-health-check' + c.reset);
5301
5310
  console.log();
5302
- console.log(' ' + c.dim + 'Need more? 16 hooks + templates for autonomous teams:' + c.reset);
5303
- console.log(' https://yurukusa.github.io/cc-ops-kit-landing/?utm_source=npm&utm_medium=cli&utm_campaign=safe-setup');
5311
+ console.log(' ' + c.dim + 'Next steps:' + c.reset);
5312
+ console.log(' ' + c.blue + ' --doctor' + c.reset + ' Verify hooks work');
5313
+ console.log(' ' + c.blue + ' --simulate "cmd"' + c.reset + ' Test how hooks react');
5314
+ console.log(' ' + c.blue + ' --shield' + c.reset + ' Maximum safety (recommended)');
5315
+ console.log();
5316
+ console.log(' ' + c.dim + '22 web tools: https://yurukusa.github.io/cc-safe-setup/hub.html' + c.reset);
5304
5317
  console.log();
5305
5318
  }
5306
5319
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "28.4.1",
4
- "description": "One command to make Claude Code safe. 327 hooks (8 built-in + 319 examples). 45 CLI commands. 941 tests. 5 languages.",
3
+ "version": "28.4.2",
4
+ "description": "One command to make Claude Code safe. 336 hooks (8 built-in + 328 examples). 49 CLI commands. 978 tests. 5 languages.",
5
5
  "main": "index.mjs",
6
6
  "bin": {
7
7
  "cc-safe-setup": "index.mjs"