cc-safe-setup 12.0.0 → 12.2.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 + 104 examples = **118 hooks**. 41 CLI commands. 561 tests. 5 languages. [**Hub**](https://yurukusa.github.io/cc-safe-setup/hub.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)
9
+ 8 built-in + 104 examples = **118 hooks**. 42 CLI commands. 561 tests. 5 languages. [**Hub**](https://yurukusa.github.io/cc-safe-setup/hub.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
10
 
11
11
  ```bash
12
12
  npx cc-safe-setup
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # kubernetes-guard.sh — Block destructive kubectl commands
3
+ # TRIGGER: PreToolUse MATCHER: "Bash"
4
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
5
+ [ -z "$COMMAND" ] && exit 0
6
+ if echo "$COMMAND" | grep -qE '\bkubectl\s+delete\s+(namespace|ns|node)\b'; then
7
+ echo "BLOCKED: kubectl delete namespace/node is highly destructive" >&2
8
+ exit 2
9
+ fi
10
+ if echo "$COMMAND" | grep -qE '\bkubectl\s+delete\s+.*--all\b'; then
11
+ echo "WARNING: kubectl delete --all affects all resources in scope" >&2
12
+ fi
13
+ exit 0
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # terraform-guard.sh — Warn before terraform destroy/apply
3
+ # TRIGGER: PreToolUse MATCHER: "Bash"
4
+ COMMAND=$(cat | jq -r '.tool_input.command // empty' 2>/dev/null)
5
+ [ -z "$COMMAND" ] && exit 0
6
+ if echo "$COMMAND" | grep -qE '\bterraform\s+destroy\b'; then
7
+ echo "BLOCKED: terraform destroy is irreversible" >&2
8
+ exit 2
9
+ fi
10
+ if echo "$COMMAND" | grep -qE '\bterraform\s+apply\b' && ! echo "$COMMAND" | grep -q '\-auto-approve'; then
11
+ echo "NOTE: terraform apply detected. Review the plan carefully." >&2
12
+ fi
13
+ exit 0
package/index.mjs CHANGED
@@ -111,6 +111,8 @@ const SAVE_PROFILE = SAVE_PROFILE_IDX !== -1 ? process.argv[SAVE_PROFILE_IDX + 1
111
111
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
112
112
  const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
113
113
  const SUGGEST = process.argv.includes('--suggest');
114
+ const TEST_HOOK_IDX = process.argv.findIndex(a => a === '--test-hook');
115
+ const TEST_HOOK = TEST_HOOK_IDX !== -1 ? process.argv[TEST_HOOK_IDX + 1] : null;
114
116
  const WHY_IDX = process.argv.findIndex(a => a === '--why');
115
117
  const WHY_HOOK = WHY_IDX !== -1 ? process.argv[WHY_IDX + 1] : null;
116
118
 
@@ -144,6 +146,7 @@ if (HELP) {
144
146
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
145
147
  npx cc-safe-setup --watch Live dashboard of blocked commands
146
148
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
149
+ npx cc-safe-setup --test-hook <name> Test a specific hook with sample inputs
147
150
  npx cc-safe-setup --save-profile <name> Save current hooks as a named profile
148
151
  npx cc-safe-setup --suggest Analyze project and predict risks → suggest hooks
149
152
  npx cc-safe-setup --why <hook> Why this hook exists (real incident + issue link)
@@ -931,6 +934,81 @@ async function fullSetup() {
931
934
  console.log();
932
935
  }
933
936
 
937
+ async function testHook(hookName) {
938
+ const { execSync } = await import('child_process');
939
+ console.log();
940
+
941
+ if (!hookName) {
942
+ console.log(c.bold + ' cc-safe-setup --test-hook <name>' + c.reset);
943
+ console.log(c.dim + ' Test any hook with sample inputs.' + c.reset);
944
+ console.log();
945
+ console.log(' Example: npx cc-safe-setup --test-hook destructive-guard');
946
+ return;
947
+ }
948
+
949
+ const name = hookName.replace('.sh', '');
950
+ // Find the hook
951
+ let hookPath = join(HOOKS_DIR, `${name}.sh`);
952
+ if (!existsSync(hookPath)) hookPath = join(__dirname, 'examples', `${name}.sh`);
953
+ if (!existsSync(hookPath)) {
954
+ console.log(c.red + ` Hook "${name}" not found.` + c.reset);
955
+ return;
956
+ }
957
+
958
+ console.log(c.bold + ` Testing: ${name}` + c.reset);
959
+ console.log(c.dim + ` File: ${hookPath}` + c.reset);
960
+ console.log();
961
+
962
+ // Sample inputs per hook type
963
+ const SAMPLES = {
964
+ 'should-block': [
965
+ { desc: 'dangerous rm', input: '{"tool_input":{"command":"rm -rf /"}}' },
966
+ { desc: 'git reset hard', input: '{"tool_input":{"command":"git reset --hard"}}' },
967
+ { desc: 'force push', input: '{"tool_input":{"command":"git push origin main --force"}}' },
968
+ { desc: 'git add .env', input: '{"tool_input":{"command":"git add .env"}}' },
969
+ { desc: 'sudo command', input: '{"tool_input":{"command":"sudo rm -rf /home"}}' },
970
+ { desc: 'drop database', input: '{"tool_input":{"command":"DROP DATABASE production"}}' },
971
+ ],
972
+ 'should-allow': [
973
+ { desc: 'safe ls', input: '{"tool_input":{"command":"ls -la"}}' },
974
+ { desc: 'git status', input: '{"tool_input":{"command":"git status"}}' },
975
+ { desc: 'npm test', input: '{"tool_input":{"command":"npm test"}}' },
976
+ { desc: 'cat file', input: '{"tool_input":{"command":"cat README.md"}}' },
977
+ { desc: 'empty input', input: '{}' },
978
+ ],
979
+ };
980
+
981
+ let pass = 0, total = 0;
982
+
983
+ for (const [category, samples] of Object.entries(SAMPLES)) {
984
+ console.log(c.dim + ` ${category}:` + c.reset);
985
+ for (const sample of samples) {
986
+ total++;
987
+ try {
988
+ execSync(`echo '${sample.input}' | bash "${hookPath}"`, { stdio: 'pipe', timeout: 5000 });
989
+ // Exit 0 = allowed
990
+ const icon = category === 'should-allow' ? c.green + '✓' : c.yellow + '·';
991
+ console.log(` ${icon}${c.reset} ${sample.desc} → allowed (exit 0)`);
992
+ if (category === 'should-allow') pass++;
993
+ } catch (e) {
994
+ const code = e.status;
995
+ if (code === 2) {
996
+ // Blocked
997
+ const icon = category === 'should-block' ? c.green + '✓' : c.red + '✗';
998
+ console.log(` ${icon}${c.reset} ${sample.desc} → ${c.red}BLOCKED${c.reset} (exit 2)`);
999
+ if (category === 'should-block') pass++;
1000
+ } else {
1001
+ console.log(` ${c.yellow}?${c.reset} ${sample.desc} → exit ${code}`);
1002
+ }
1003
+ }
1004
+ }
1005
+ }
1006
+
1007
+ console.log();
1008
+ console.log(` ${pass}/${total} samples matched expected behavior`);
1009
+ console.log();
1010
+ }
1011
+
934
1012
  async function saveProfile(name) {
935
1013
  const { readdirSync } = await import('fs');
936
1014
  const profilesDir = join(HOME, '.claude', 'profiles');
@@ -4340,6 +4418,7 @@ async function main() {
4340
4418
  if (FULL) return fullSetup();
4341
4419
  if (DOCTOR) return doctor();
4342
4420
  if (WATCH) return watch();
4421
+ if (TEST_HOOK_IDX !== -1) return testHook(TEST_HOOK);
4343
4422
  if (SAVE_PROFILE_IDX !== -1) return saveProfile(SAVE_PROFILE);
4344
4423
  if (SUGGEST) return suggest();
4345
4424
  if (WHY_IDX !== -1) return why(WHY_HOOK);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "12.0.0",
3
+ "version": "12.2.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": {