cc-safe-setup 10.0.0 → 10.1.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.
Files changed (2) hide show
  1. package/index.mjs +93 -0
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -97,6 +97,8 @@ const MIGRATE_FROM_IDX = process.argv.findIndex(a => a === '--migrate-from');
97
97
  const MIGRATE_FROM = MIGRATE_FROM_IDX !== -1 ? process.argv[MIGRATE_FROM_IDX + 1] : null;
98
98
  const HEALTH = process.argv.includes('--health');
99
99
  const FROM_CLAUDEMD = process.argv.includes('--from-claudemd');
100
+ const DIFF_HOOKS_IDX = process.argv.findIndex(a => a === '--diff-hooks');
101
+ const DIFF_HOOKS = DIFF_HOOKS_IDX !== -1 ? process.argv[DIFF_HOOKS_IDX + 1] : null;
100
102
  const PROFILE_IDX = process.argv.findIndex(a => a === '--profile');
101
103
  const PROFILE = PROFILE_IDX !== -1 ? process.argv[PROFILE_IDX + 1] : null;
102
104
  const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
@@ -134,6 +136,7 @@ if (HELP) {
134
136
  npx cc-safe-setup --doctor Diagnose why hooks aren't working
135
137
  npx cc-safe-setup --watch Live dashboard of blocked commands
136
138
  npx cc-safe-setup --create "<desc>" Generate a custom hook from description
139
+ npx cc-safe-setup --diff-hooks <path> Compare hooks between two settings files
137
140
  npx cc-safe-setup --from-claudemd Convert CLAUDE.md rules into hooks
138
141
  npx cc-safe-setup --health Hook health dashboard (size, permissions, age)
139
142
  npx cc-safe-setup --migrate-from <tool> Migrate from safety-net/hooks-mastery/etc.
@@ -847,6 +850,95 @@ async function fullSetup() {
847
850
  console.log();
848
851
  }
849
852
 
853
+ async function diffHooks(otherPath) {
854
+ console.log();
855
+ console.log(c.bold + ' cc-safe-setup --diff-hooks' + c.reset);
856
+ console.log(c.dim + ' Compare hook configurations' + c.reset);
857
+ console.log();
858
+
859
+ // Parse hooks from a settings file
860
+ function getHooks(path) {
861
+ if (!existsSync(path)) return new Set();
862
+ try {
863
+ const s = JSON.parse(readFileSync(path, 'utf-8'));
864
+ const hooks = new Set();
865
+ for (const [trigger, groups] of Object.entries(s.hooks || {})) {
866
+ for (const group of groups) {
867
+ for (const h of (group.hooks || [])) {
868
+ const cmd = h.command || '';
869
+ const name = cmd.split('/').pop().replace('.sh', '').replace('.py', '');
870
+ if (name) hooks.add(name);
871
+ }
872
+ }
873
+ }
874
+ return hooks;
875
+ } catch { return new Set(); }
876
+ }
877
+
878
+ // Get current settings
879
+ const myHooks = getHooks(SETTINGS_PATH);
880
+
881
+ if (!otherPath) {
882
+ // Compare global vs project settings
883
+ const projectSettings = join(process.cwd(), '.claude', 'settings.json');
884
+ const projectLocal = join(process.cwd(), '.claude', 'settings.local.json');
885
+
886
+ if (existsSync(projectSettings) || existsSync(projectLocal)) {
887
+ const projectHooks = new Set([
888
+ ...getHooks(projectSettings),
889
+ ...getHooks(projectLocal)
890
+ ]);
891
+
892
+ console.log(c.bold + ' Global vs Project comparison' + c.reset);
893
+ console.log(` Global: ${myHooks.size} hooks (${SETTINGS_PATH})`);
894
+ console.log(` Project: ${projectHooks.size} hooks (.claude/settings*.json)`);
895
+ console.log();
896
+
897
+ const onlyGlobal = [...myHooks].filter(h => !projectHooks.has(h));
898
+ const onlyProject = [...projectHooks].filter(h => !myHooks.has(h));
899
+ const both = [...myHooks].filter(h => projectHooks.has(h));
900
+
901
+ if (both.length > 0) {
902
+ console.log(c.green + ` ✓ ${both.length} hooks in both:` + c.reset);
903
+ both.slice(0, 10).forEach(h => console.log(c.dim + ` ${h}` + c.reset));
904
+ if (both.length > 10) console.log(c.dim + ` ... and ${both.length - 10} more` + c.reset);
905
+ console.log();
906
+ }
907
+ if (onlyGlobal.length > 0) {
908
+ console.log(c.yellow + ` △ ${onlyGlobal.length} only in global:` + c.reset);
909
+ onlyGlobal.forEach(h => console.log(` ${h}`));
910
+ console.log();
911
+ }
912
+ if (onlyProject.length > 0) {
913
+ console.log(c.blue + ` ○ ${onlyProject.length} only in project:` + c.reset);
914
+ onlyProject.forEach(h => console.log(` ${h}`));
915
+ console.log();
916
+ }
917
+ } else {
918
+ console.log(c.dim + ' No project-level settings found.' + c.reset);
919
+ console.log(c.dim + ' Create with: npx cc-safe-setup --team' + c.reset);
920
+ console.log();
921
+ console.log(c.bold + ' Global hooks (' + myHooks.size + '):' + c.reset);
922
+ [...myHooks].sort().forEach(h => console.log(` ${h}`));
923
+ }
924
+ } else {
925
+ // Compare with specified file
926
+ const otherHooks = getHooks(otherPath);
927
+ console.log(` File A: ${SETTINGS_PATH} (${myHooks.size} hooks)`);
928
+ console.log(` File B: ${otherPath} (${otherHooks.size} hooks)`);
929
+ console.log();
930
+
931
+ const onlyA = [...myHooks].filter(h => !otherHooks.has(h));
932
+ const onlyB = [...otherHooks].filter(h => !myHooks.has(h));
933
+ const both = [...myHooks].filter(h => otherHooks.has(h));
934
+
935
+ console.log(c.green + ` ${both.length} shared` + c.reset);
936
+ if (onlyA.length > 0) console.log(c.yellow + ` ${onlyA.length} only in A: ${onlyA.join(', ')}` + c.reset);
937
+ if (onlyB.length > 0) console.log(c.blue + ` ${onlyB.length} only in B: ${onlyB.join(', ')}` + c.reset);
938
+ }
939
+ console.log();
940
+ }
941
+
850
942
  async function fromClaudeMd() {
851
943
  console.log();
852
944
  console.log(c.bold + ' cc-safe-setup --from-claudemd' + c.reset);
@@ -3723,6 +3815,7 @@ async function main() {
3723
3815
  if (FULL) return fullSetup();
3724
3816
  if (DOCTOR) return doctor();
3725
3817
  if (WATCH) return watch();
3818
+ if (DIFF_HOOKS_IDX !== -1) return diffHooks(DIFF_HOOKS);
3726
3819
  if (FROM_CLAUDEMD) return fromClaudeMd();
3727
3820
  if (HEALTH) return health();
3728
3821
  if (MIGRATE_FROM_IDX !== -1) return migrateFrom(MIGRATE_FROM);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "10.0.0",
3
+ "version": "10.1.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": {