cc-safe-setup 2.2.0 → 2.4.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
@@ -138,6 +138,29 @@ npx cc-safe-setup --audit
138
138
 
139
139
  Analyzes 9 safety dimensions and gives you a score (0-100) with one-command fixes for each risk.
140
140
 
141
+ ### CI Integration (GitHub Action)
142
+
143
+ ```yaml
144
+ # .github/workflows/safety.yml
145
+ - uses: yurukusa/cc-safe-setup@main
146
+ with:
147
+ threshold: 70 # CI fails if score drops below this
148
+ ```
149
+
150
+ ### Project Scanner
151
+
152
+ ```bash
153
+ npx cc-safe-setup --scan # detect tech stack, recommend hooks
154
+ npx cc-safe-setup --scan --apply # auto-create CLAUDE.md with project rules
155
+ ```
156
+
157
+ ### Self-Learning Safety
158
+
159
+ ```bash
160
+ npx cc-safe-setup --learn # analyze your block history for patterns
161
+ npx cc-safe-setup --learn --apply # auto-generate custom hooks from patterns
162
+ ```
163
+
141
164
  ## Examples
142
165
 
143
166
  Need custom hooks beyond the 8 built-in ones? Install any example with one command:
package/action.yml ADDED
@@ -0,0 +1,34 @@
1
+ name: 'Claude Code Safety Audit'
2
+ description: 'Check your Claude Code safety setup and fail CI if score is below threshold'
3
+ branding:
4
+ icon: 'shield'
5
+ color: 'green'
6
+
7
+ inputs:
8
+ threshold:
9
+ description: 'Minimum safety score (0-100). CI fails if below this.'
10
+ required: false
11
+ default: '70'
12
+
13
+ runs:
14
+ using: 'composite'
15
+ steps:
16
+ - name: Run safety audit
17
+ shell: bash
18
+ run: |
19
+ echo "::group::Claude Code Safety Audit"
20
+ npx cc-safe-setup@latest --audit 2>&1 | tee /tmp/audit-output.txt
21
+ echo "::endgroup::"
22
+
23
+ # Extract score
24
+ SCORE=$(grep -oP 'Safety Score: \K\d+' /tmp/audit-output.txt || echo "0")
25
+ THRESHOLD="${{ inputs.threshold }}"
26
+
27
+ echo "Safety Score: $SCORE / 100 (threshold: $THRESHOLD)"
28
+
29
+ if [ "$SCORE" -lt "$THRESHOLD" ]; then
30
+ echo "::error::Safety score $SCORE is below threshold $THRESHOLD. Run 'npx cc-safe-setup --audit --fix' to improve."
31
+ exit 1
32
+ else
33
+ echo "::notice::Safety score $SCORE meets threshold $THRESHOLD ✓"
34
+ fi
@@ -0,0 +1,54 @@
1
+ #!/bin/bash
2
+ # session-checkpoint.sh — Auto-save session state on every stop
3
+ #
4
+ # Solves: Session crashes/disconnects causing expensive re-analysis
5
+ # of the entire codebase on next start (#37866)
6
+ #
7
+ # Saves: git state, recent commits, modified files, working directory.
8
+ # Next session reads the checkpoint instead of re-analyzing everything.
9
+ #
10
+ # Usage: Add to settings.json as a Stop hook
11
+ #
12
+ # {
13
+ # "hooks": {
14
+ # "Stop": [{
15
+ # "hooks": [{ "type": "command", "command": "~/.claude/hooks/session-checkpoint.sh" }]
16
+ # }]
17
+ # }
18
+ # }
19
+ #
20
+ # Recovery: Add to CLAUDE.md:
21
+ # "If ~/.claude/checkpoints/ has a file for this project, read it first."
22
+
23
+ INPUT=$(cat)
24
+ REASON=$(echo "$INPUT" | jq -r '.stop_reason // empty' 2>/dev/null)
25
+
26
+ CHECKPOINT_DIR="$HOME/.claude/checkpoints"
27
+ mkdir -p "$CHECKPOINT_DIR"
28
+
29
+ PROJECT_NAME=$(basename "$(pwd)")
30
+ CHECKPOINT="$CHECKPOINT_DIR/${PROJECT_NAME}-latest.md"
31
+
32
+ {
33
+ echo "# Session Checkpoint"
34
+ echo "Saved: $(date -Iseconds)"
35
+ echo "Directory: $(pwd)"
36
+ echo "Stop reason: ${REASON:-unknown}"
37
+ echo ""
38
+ echo "## Recent Commits"
39
+ git log --oneline -10 2>/dev/null || echo "(not a git repo)"
40
+ echo ""
41
+ echo "## Uncommitted Changes"
42
+ git diff --stat 2>/dev/null || echo "(none)"
43
+ echo ""
44
+ echo "## Staged Files"
45
+ git diff --cached --name-only 2>/dev/null || echo "(none)"
46
+ echo ""
47
+ echo "## Current Branch"
48
+ git branch --show-current 2>/dev/null || echo "(unknown)"
49
+ } > "$CHECKPOINT" 2>/dev/null
50
+
51
+ # Cleanup: keep only last 10 checkpoints
52
+ ls -t "$CHECKPOINT_DIR"/*.md 2>/dev/null | tail -n +11 | xargs rm -f 2>/dev/null
53
+
54
+ exit 0
package/index.mjs CHANGED
@@ -70,6 +70,8 @@ const INSTALL_EXAMPLE_IDX = process.argv.findIndex(a => a === '--install-example
70
70
  const INSTALL_EXAMPLE = INSTALL_EXAMPLE_IDX !== -1 ? process.argv[INSTALL_EXAMPLE_IDX + 1] : null;
71
71
  const AUDIT = process.argv.includes('--audit');
72
72
  const LEARN = process.argv.includes('--learn');
73
+ const SCAN = process.argv.includes('--scan');
74
+ const FULL = process.argv.includes('--full');
73
75
 
74
76
  if (HELP) {
75
77
  console.log(`
@@ -567,6 +569,18 @@ async function audit() {
567
569
  console.log();
568
570
  console.log(c.dim + ' Run with --fix to auto-apply: npx cc-safe-setup --audit --fix' + c.reset);
569
571
  }
572
+
573
+ // Badge output
574
+ if (process.argv.includes('--badge')) {
575
+ const color = score >= 80 ? 'brightgreen' : score >= 50 ? 'yellow' : 'red';
576
+ const badge = `![Claude Code Safety](https://img.shields.io/badge/Claude_Code_Safety-${score}%2F100-${color})`;
577
+ console.log();
578
+ console.log(c.bold + ' README Badge:' + c.reset);
579
+ console.log(' ' + badge);
580
+ console.log();
581
+ console.log(c.dim + ' Paste this into your README.md' + c.reset);
582
+ }
583
+
570
584
  console.log();
571
585
  }
572
586
 
@@ -682,6 +696,184 @@ exit 0`;
682
696
  console.log();
683
697
  }
684
698
 
699
+ async function fullSetup() {
700
+ console.log();
701
+ console.log(c.bold + c.green + ' cc-safe-setup --full' + c.reset);
702
+ console.log(c.dim + ' Complete safety setup in one command' + c.reset);
703
+ console.log();
704
+
705
+ const { execSync } = await import('child_process');
706
+ const self = process.argv[1];
707
+
708
+ // Step 1: Install 8 built-in hooks
709
+ console.log(c.bold + ' Step 1: Installing 8 built-in safety hooks...' + c.reset);
710
+ try {
711
+ execSync('node ' + self + ' --yes', { stdio: 'inherit' });
712
+ } catch(e) {
713
+ // --yes doesn't exist, run normal install
714
+ execSync('node ' + self, { stdio: 'inherit', input: 'y\n' });
715
+ }
716
+
717
+ // Step 2: Scan project and create CLAUDE.md
718
+ console.log();
719
+ console.log(c.bold + ' Step 2: Scanning project and creating CLAUDE.md...' + c.reset);
720
+ scan();
721
+
722
+ // Step 3: Audit and show results
723
+ console.log();
724
+ console.log(c.bold + ' Step 3: Running safety audit...' + c.reset);
725
+ // Inject --badge into argv temporarily
726
+ process.argv.push('--badge');
727
+ await audit();
728
+
729
+ console.log(c.bold + c.green + ' ✓ Full setup complete!' + c.reset);
730
+ console.log(c.dim + ' Your project now has:' + c.reset);
731
+ console.log(c.dim + ' • 8 built-in safety hooks' + c.reset);
732
+ console.log(c.dim + ' • Project-specific hook recommendations' + c.reset);
733
+ console.log(c.dim + ' • Safety score and README badge' + c.reset);
734
+ console.log();
735
+ }
736
+
737
+ function scan() {
738
+ console.log();
739
+ console.log(c.bold + ' cc-safe-setup --scan' + c.reset);
740
+ console.log(c.dim + ' Scanning project to generate safety config...' + c.reset);
741
+ console.log();
742
+
743
+ const cwd = process.cwd();
744
+ const detected = { languages: [], frameworks: [], hasDb: false, hasDocker: false, isMonorepo: false };
745
+ const hooks = ['destructive-guard', 'branch-guard', 'secret-guard', 'syntax-check'];
746
+ const claudeMdRules = ['# Project Safety Rules\n', '## Generated by cc-safe-setup --scan\n'];
747
+
748
+ // Detect languages & frameworks
749
+ if (existsSync(join(cwd, 'package.json'))) {
750
+ detected.languages.push('JavaScript/TypeScript');
751
+ try {
752
+ const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf-8'));
753
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
754
+ if (allDeps.next) detected.frameworks.push('Next.js');
755
+ if (allDeps.react) detected.frameworks.push('React');
756
+ if (allDeps.express) detected.frameworks.push('Express');
757
+ if (allDeps.prisma || allDeps['@prisma/client']) { detected.frameworks.push('Prisma'); detected.hasDb = true; }
758
+ if (allDeps.sequelize) { detected.frameworks.push('Sequelize'); detected.hasDb = true; }
759
+ if (allDeps.typeorm) { detected.frameworks.push('TypeORM'); detected.hasDb = true; }
760
+ } catch(e) {}
761
+ }
762
+ if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) {
763
+ detected.languages.push('Python');
764
+ if (existsSync(join(cwd, 'manage.py'))) { detected.frameworks.push('Django'); detected.hasDb = true; }
765
+ if (existsSync(join(cwd, 'app.py')) || existsSync(join(cwd, 'wsgi.py'))) detected.frameworks.push('Flask');
766
+ }
767
+ if (existsSync(join(cwd, 'Gemfile'))) {
768
+ detected.languages.push('Ruby');
769
+ detected.frameworks.push('Rails');
770
+ detected.hasDb = true;
771
+ }
772
+ if (existsSync(join(cwd, 'composer.json'))) {
773
+ detected.languages.push('PHP');
774
+ try {
775
+ const composer = JSON.parse(readFileSync(join(cwd, 'composer.json'), 'utf-8'));
776
+ if (composer.require?.['laravel/framework']) { detected.frameworks.push('Laravel'); detected.hasDb = true; }
777
+ if (composer.require?.['symfony/framework-bundle']) { detected.frameworks.push('Symfony'); detected.hasDb = true; }
778
+ } catch(e) {}
779
+ }
780
+ if (existsSync(join(cwd, 'go.mod'))) detected.languages.push('Go');
781
+ if (existsSync(join(cwd, 'Cargo.toml'))) detected.languages.push('Rust');
782
+
783
+ // Docker
784
+ if (existsSync(join(cwd, 'Dockerfile')) || existsSync(join(cwd, 'docker-compose.yml')) || existsSync(join(cwd, 'compose.yaml'))) {
785
+ detected.hasDocker = true;
786
+ }
787
+
788
+ // Monorepo
789
+ if (existsSync(join(cwd, 'pnpm-workspace.yaml')) || existsSync(join(cwd, 'lerna.json')) || existsSync(join(cwd, 'nx.json'))) {
790
+ detected.isMonorepo = true;
791
+ }
792
+
793
+ // .env files
794
+ const hasEnv = existsSync(join(cwd, '.env')) || existsSync(join(cwd, '.env.local'));
795
+
796
+ // Display detection results
797
+ console.log(c.bold + ' Detected:' + c.reset);
798
+ if (detected.languages.length) console.log(' ' + c.green + '✓' + c.reset + ' Languages: ' + detected.languages.join(', '));
799
+ if (detected.frameworks.length) console.log(' ' + c.green + '✓' + c.reset + ' Frameworks: ' + detected.frameworks.join(', '));
800
+ if (detected.hasDb) console.log(' ' + c.green + '✓' + c.reset + ' Database detected');
801
+ if (detected.hasDocker) console.log(' ' + c.green + '✓' + c.reset + ' Docker detected');
802
+ if (detected.isMonorepo) console.log(' ' + c.green + '✓' + c.reset + ' Monorepo detected');
803
+ if (hasEnv) console.log(' ' + c.yellow + '⚠' + c.reset + ' .env file found (secret leak risk)');
804
+ console.log();
805
+
806
+ // Generate recommendations
807
+ const examples = [];
808
+
809
+ if (detected.hasDb) {
810
+ examples.push('block-database-wipe');
811
+ claudeMdRules.push('- Never run destructive database commands (migrate:fresh, DROP DATABASE, prisma migrate reset)');
812
+ claudeMdRules.push('- Always backup database before schema changes');
813
+ }
814
+
815
+ if (detected.hasDocker) {
816
+ examples.push('auto-approve-docker');
817
+ claudeMdRules.push('- Docker commands are auto-approved for build/compose/ps/logs');
818
+ }
819
+
820
+ if (hasEnv) {
821
+ claudeMdRules.push('- Never commit .env files. Use .env.example for templates');
822
+ claudeMdRules.push('- Never hardcode API keys or secrets in source files');
823
+ }
824
+
825
+ if (detected.languages.includes('Python')) {
826
+ examples.push('auto-approve-python');
827
+ claudeMdRules.push('- Run pytest after every code change');
828
+ }
829
+
830
+ if (detected.languages.includes('JavaScript/TypeScript')) {
831
+ examples.push('auto-approve-build');
832
+ claudeMdRules.push('- Run npm test after every code change');
833
+ }
834
+
835
+ // Always recommend
836
+ examples.push('scope-guard');
837
+ examples.push('protect-dotfiles');
838
+ claudeMdRules.push('- Always run tests before committing');
839
+ claudeMdRules.push('- Never force-push to main/master');
840
+ claudeMdRules.push('- Create a backup branch before large refactors');
841
+
842
+ // Display recommendations
843
+ console.log(c.bold + ' Recommended hooks:' + c.reset);
844
+ console.log(' ' + c.dim + 'npx cc-safe-setup' + c.reset + ' (8 built-in hooks)');
845
+ for (const ex of examples) {
846
+ console.log(' ' + c.dim + 'npx cc-safe-setup --install-example ' + ex + c.reset);
847
+ }
848
+
849
+ // Generate CLAUDE.md
850
+ console.log();
851
+ const claudeMdPath = join(cwd, 'CLAUDE.md');
852
+ if (existsSync(claudeMdPath)) {
853
+ console.log(c.yellow + ' CLAUDE.md already exists. Suggested rules to add:' + c.reset);
854
+ console.log();
855
+ for (const rule of claudeMdRules.slice(2)) {
856
+ console.log(' ' + c.dim + rule + c.reset);
857
+ }
858
+ } else {
859
+ const content = claudeMdRules.join('\n') + '\n';
860
+ if (process.argv.includes('--apply')) {
861
+ writeFileSync(claudeMdPath, content);
862
+ console.log(c.green + ' ✓ CLAUDE.md created with ' + (claudeMdRules.length - 2) + ' project-specific rules.' + c.reset);
863
+ } else {
864
+ console.log(c.bold + ' Suggested CLAUDE.md:' + c.reset);
865
+ console.log();
866
+ for (const rule of claudeMdRules) {
867
+ console.log(' ' + c.dim + rule + c.reset);
868
+ }
869
+ console.log();
870
+ console.log(c.dim + ' Run with --apply to create: npx cc-safe-setup --scan --apply' + c.reset);
871
+ }
872
+ }
873
+
874
+ console.log();
875
+ }
876
+
685
877
  async function main() {
686
878
  if (UNINSTALL) return uninstall();
687
879
  if (VERIFY) return verify();
@@ -690,6 +882,8 @@ async function main() {
690
882
  if (INSTALL_EXAMPLE) return installExample(INSTALL_EXAMPLE);
691
883
  if (AUDIT) return audit();
692
884
  if (LEARN) return learn();
885
+ if (SCAN) return scan();
886
+ if (FULL) return fullSetup();
693
887
 
694
888
  console.log();
695
889
  console.log(c.bold + ' cc-safe-setup' + c.reset);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "One command to make Claude Code safe for autonomous operation. 8 built-in hooks + 25 installable examples. Destructive blocker, branch guard, database wipe protection, dotfile guard, and more.",
5
5
  "main": "index.mjs",
6
6
  "bin": {