cc-safe-setup 7.2.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ # output-length-guard.sh — Warn when tool output is very large
3
+ # TRIGGER: PostToolUse MATCHER: ""
4
+ # Checks tool output size and warns when it's consuming too much context
5
+ INPUT=$(cat)
6
+ OUTPUT=$(echo "$INPUT" | jq -r '.tool_result // empty' 2>/dev/null)
7
+ if [ -n "$OUTPUT" ]; then
8
+ LEN=${#OUTPUT}
9
+ if [ "$LEN" -gt 50000 ]; then
10
+ echo "WARNING: Tool output is ${LEN} chars. Large outputs consume context rapidly." >&2
11
+ echo "Consider using head/tail/grep to limit output, or redirect to a file." >&2
12
+ fi
13
+ fi
14
+ exit 0
package/index.mjs CHANGED
@@ -89,6 +89,7 @@ const ISSUES = process.argv.includes('--issues');
89
89
  const MIGRATE = process.argv.includes('--migrate');
90
90
  const GENERATE_CI = process.argv.includes('--generate-ci');
91
91
  const REPORT = process.argv.includes('--report');
92
+ const QUICKFIX = process.argv.includes('--quickfix');
92
93
  const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
93
94
  const COMPARE = COMPARE_IDX !== -1 ? { a: process.argv[COMPARE_IDX + 1], b: process.argv[COMPARE_IDX + 2] } : null;
94
95
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
@@ -124,6 +125,7 @@ if (HELP) {
124
125
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
125
126
  npx cc-safe-setup --watch Live dashboard of blocked commands
126
127
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
128
+ npx cc-safe-setup --quickfix Auto-detect and fix common Claude Code problems
127
129
  npx cc-safe-setup --stats Block statistics and patterns report
128
130
  npx cc-safe-setup --export Export hooks config for team sharing
129
131
  npx cc-safe-setup --import <file> Import hooks from exported config
@@ -829,6 +831,200 @@ async function fullSetup() {
829
831
  console.log();
830
832
  }
831
833
 
834
+ async function quickfix() {
835
+ const { execSync } = await import('child_process');
836
+ console.log();
837
+ console.log(c.bold + ' cc-safe-setup --quickfix' + c.reset);
838
+ console.log(c.dim + ' Auto-detect and fix common Claude Code problems' + c.reset);
839
+ console.log();
840
+
841
+ let fixed = 0, warnings = 0, ok = 0;
842
+
843
+ // Check 1: jq installed
844
+ try {
845
+ execSync('which jq', { stdio: 'pipe' });
846
+ console.log(c.green + ' ✓' + c.reset + ' jq is installed');
847
+ ok++;
848
+ } catch {
849
+ console.log(c.red + ' ✗' + c.reset + ' jq is not installed — hooks cannot parse JSON');
850
+ console.log(c.dim + ' Fix: brew install jq (macOS) | sudo apt install jq (Linux)' + c.reset);
851
+ warnings++;
852
+ }
853
+
854
+ // Check 2: ~/.claude directory exists
855
+ const claudeDir = join(HOME, '.claude');
856
+ if (existsSync(claudeDir)) {
857
+ console.log(c.green + ' ✓' + c.reset + ' ~/.claude directory exists');
858
+ ok++;
859
+ } else {
860
+ mkdirSync(claudeDir, { recursive: true });
861
+ console.log(c.yellow + ' ⚡' + c.reset + ' Created ~/.claude directory');
862
+ fixed++;
863
+ }
864
+
865
+ // Check 3: hooks directory exists
866
+ if (existsSync(HOOKS_DIR)) {
867
+ console.log(c.green + ' ✓' + c.reset + ' ~/.claude/hooks directory exists');
868
+ ok++;
869
+ } else {
870
+ mkdirSync(HOOKS_DIR, { recursive: true });
871
+ console.log(c.yellow + ' ⚡' + c.reset + ' Created ~/.claude/hooks directory');
872
+ fixed++;
873
+ }
874
+
875
+ // Check 4: settings.json exists and is valid JSON
876
+ if (existsSync(SETTINGS_PATH)) {
877
+ try {
878
+ JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
879
+ console.log(c.green + ' ✓' + c.reset + ' settings.json is valid JSON');
880
+ ok++;
881
+ } catch (e) {
882
+ console.log(c.red + ' ✗' + c.reset + ' settings.json has invalid JSON: ' + e.message);
883
+ console.log(c.dim + ' This is the #1 cause of hooks not working.' + c.reset);
884
+ console.log(c.dim + ' Common fix: remove trailing commas, check for comments (JSONC not supported in all contexts)' + c.reset);
885
+ warnings++;
886
+ }
887
+ } else {
888
+ writeFileSync(SETTINGS_PATH, '{}');
889
+ console.log(c.yellow + ' ⚡' + c.reset + ' Created empty settings.json');
890
+ fixed++;
891
+ }
892
+
893
+ // Check 5: hooks have executable permission
894
+ if (existsSync(HOOKS_DIR)) {
895
+ const { readdirSync, statSync } = await import('fs');
896
+ const hooks = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh'));
897
+ let nonExec = 0;
898
+ for (const h of hooks) {
899
+ const p = join(HOOKS_DIR, h);
900
+ const st = statSync(p);
901
+ if (!(st.mode & 0o111)) {
902
+ chmodSync(p, 0o755);
903
+ nonExec++;
904
+ }
905
+ }
906
+ if (nonExec > 0) {
907
+ console.log(c.yellow + ' ⚡' + c.reset + ` Fixed ${nonExec} hook(s) missing executable permission`);
908
+ fixed += nonExec;
909
+ } else if (hooks.length > 0) {
910
+ console.log(c.green + ' ✓' + c.reset + ` All ${hooks.length} hooks have executable permission`);
911
+ ok++;
912
+ }
913
+ }
914
+
915
+ // Check 6: hooks have correct shebang
916
+ if (existsSync(HOOKS_DIR)) {
917
+ const { readdirSync } = await import('fs');
918
+ const hooks = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh'));
919
+ let badShebang = 0;
920
+ for (const h of hooks) {
921
+ const content = readFileSync(join(HOOKS_DIR, h), 'utf-8');
922
+ const firstLine = content.split('\n')[0];
923
+ if (!firstLine.startsWith('#!')) {
924
+ badShebang++;
925
+ console.log(c.red + ' ✗' + c.reset + ` ${h} missing shebang (#!/bin/bash)`);
926
+ }
927
+ }
928
+ if (badShebang === 0 && hooks.length > 0) {
929
+ console.log(c.green + ' ✓' + c.reset + ' All hooks have valid shebang lines');
930
+ ok++;
931
+ }
932
+ warnings += badShebang;
933
+ }
934
+
935
+ // Check 7: settings.json hooks reference existing files
936
+ if (existsSync(SETTINGS_PATH)) {
937
+ try {
938
+ const settings = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
939
+ let broken = 0;
940
+ for (const [trigger, groups] of Object.entries(settings.hooks || {})) {
941
+ for (const group of groups) {
942
+ for (const hook of (group.hooks || [])) {
943
+ const cmd = hook.command || '';
944
+ // Extract script path from command
945
+ const match = cmd.match(/bash\s+"?([^"\s]+\.sh)/);
946
+ if (match && !existsSync(match[1])) {
947
+ console.log(c.red + ' ✗' + c.reset + ` Hook references missing file: ${match[1]}`);
948
+ broken++;
949
+ }
950
+ }
951
+ }
952
+ }
953
+ if (broken === 0) {
954
+ console.log(c.green + ' ✓' + c.reset + ' All hook file references are valid');
955
+ ok++;
956
+ }
957
+ warnings += broken;
958
+ } catch {}
959
+ }
960
+
961
+ // Check 8: No .env in git staging
962
+ try {
963
+ const staged = execSync('git diff --cached --name-only 2>/dev/null', { encoding: 'utf-8' });
964
+ if (/\.env/i.test(staged)) {
965
+ console.log(c.red + ' ✗' + c.reset + ' .env file is staged in git! Run: git reset HEAD .env');
966
+ warnings++;
967
+ } else {
968
+ console.log(c.green + ' ✓' + c.reset + ' No secret files in git staging area');
969
+ ok++;
970
+ }
971
+ } catch {
972
+ console.log(c.dim + ' · Not in a git repository (skipping git checks)' + c.reset);
973
+ }
974
+
975
+ // Check 9: CLAUDE.md exists in project
976
+ if (existsSync('CLAUDE.md')) {
977
+ console.log(c.green + ' ✓' + c.reset + ' CLAUDE.md found in project');
978
+ ok++;
979
+ } else {
980
+ console.log(c.yellow + ' △' + c.reset + ' No CLAUDE.md — consider creating one for project-specific rules');
981
+ warnings++;
982
+ }
983
+
984
+ // Check 10: Safety hooks installed
985
+ let safetyHooks = 0;
986
+ if (existsSync(SETTINGS_PATH)) {
987
+ try {
988
+ const s = JSON.parse(readFileSync(SETTINGS_PATH, 'utf-8'));
989
+ const allHookCmds = [];
990
+ for (const groups of Object.values(s.hooks || {})) {
991
+ for (const g of groups) {
992
+ for (const h of (g.hooks || [])) allHookCmds.push(h.command || '');
993
+ }
994
+ }
995
+ const critical = ['destructive-guard', 'branch-guard', 'secret-guard'];
996
+ for (const name of critical) {
997
+ if (allHookCmds.some(c => c.includes(name))) {
998
+ safetyHooks++;
999
+ } else {
1000
+ console.log(c.yellow + ' △' + c.reset + ` Missing critical hook: ${name}`);
1001
+ console.log(c.dim + ' Fix: npx cc-safe-setup' + c.reset);
1002
+ warnings++;
1003
+ }
1004
+ }
1005
+ if (safetyHooks === 3) {
1006
+ console.log(c.green + ' ✓' + c.reset + ' All 3 critical safety hooks installed');
1007
+ ok++;
1008
+ }
1009
+ } catch {}
1010
+ }
1011
+
1012
+ console.log();
1013
+ console.log(c.bold + ' Summary' + c.reset);
1014
+ console.log(c.green + ` ${ok} OK` + c.reset + c.yellow + ` · ${fixed} fixed` + c.reset + c.red + ` · ${warnings} warnings` + c.reset);
1015
+
1016
+ if (fixed > 0) {
1017
+ console.log();
1018
+ console.log(c.green + ` ⚡ Auto-fixed ${fixed} issue(s)` + c.reset);
1019
+ }
1020
+ if (warnings > 0) {
1021
+ console.log();
1022
+ console.log(c.yellow + ' Run npx cc-safe-setup to install missing safety hooks' + c.reset);
1023
+ console.log(c.yellow + ' Run npx cc-safe-setup --doctor for detailed diagnosis' + c.reset);
1024
+ }
1025
+ console.log();
1026
+ }
1027
+
832
1028
  async function report() {
833
1029
  // Generate markdown safety report
834
1030
  let hookCount = 0;
@@ -2640,6 +2836,7 @@ async function main() {
2640
2836
  if (FULL) return fullSetup();
2641
2837
  if (DOCTOR) return doctor();
2642
2838
  if (WATCH) return watch();
2839
+ if (QUICKFIX) return quickfix();
2643
2840
  if (REPORT) return report();
2644
2841
  if (GENERATE_CI) return generateCI();
2645
2842
  if (MIGRATE) return migrate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "7.2.0",
3
+ "version": "7.3.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": {