claudex-setup 1.8.0 → 1.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.9.0] - 2026-03-31
4
+
5
+ ### Added
6
+ - 3 new domain packs: `monorepo`, `mobile`, `regulated-lite` (7→10 total)
7
+ - 3 new MCP packs: `github-mcp`, `postgres-mcp`, `memory-mcp` (2→5 total)
8
+ - smart MCP pack recommendation based on detected domain packs
9
+ - `suggest-only --out report.md` exports full analysis as shareable markdown
10
+ - `why` explanations for all strengths preserved (20+ specific reasons)
11
+ - `why` explanations for all gap findings (12+ specific reasons)
12
+ - 5 new hooks in governance registry: duplicate-id-check, injection-defense, trust-drift-check, session-init, protect-catalog
13
+ - case study template in `content/case-study-template.md`
14
+ - hook risk level display in governance output (color-coded low/medium/high)
15
+
16
+ ### Fixed
17
+ - **Settings hierarchy bug**: `noBypassPermissions` and `secretsProtection` checks now correctly read `.claude/settings.json` before `.claude/settings.local.json`, so personal maintainer overrides no longer fail the shared audit
18
+ - domain pack detection now handles monorepo (nx.json, turbo.json, lerna.json, workspaces), mobile (React Native, Flutter, iOS/Android dirs), and regulated repos (SECURITY.md, compliance dirs)
19
+
20
+ ### Changed
21
+ - strengths preserved section now shows 8 items (was 6) with specific value explanations
22
+ - claudex-sync.json updated with domain pack, MCP pack, and anti-pattern counts
23
+
3
24
  ## [1.8.0] - 2026-03-31
4
25
 
5
26
  ### Added
package/README.md CHANGED
@@ -227,7 +227,7 @@ jobs:
227
227
  runs-on: ubuntu-latest
228
228
  steps:
229
229
  - uses: actions/checkout@v4
230
- - uses: DnaFin/claudex-setup@v1.8.0
230
+ - uses: DnaFin/claudex-setup@v1.9.0
231
231
  with:
232
232
  threshold: 50
233
233
  ```
package/bin/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { audit } = require('../src/audit');
4
4
  const { setup } = require('../src/setup');
5
- const { analyzeProject, printAnalysis } = require('../src/analyze');
5
+ const { analyzeProject, printAnalysis, exportMarkdown } = require('../src/analyze');
6
6
  const { buildProposalBundle, printProposalBundle, writePlanFile, applyProposalBundle, printApplyResult } = require('../src/plans');
7
7
  const { getGovernanceSummary, printGovernanceSummary, ensureWritableProfile } = require('../src/governance');
8
8
  const { runBenchmark, printBenchmark, writeBenchmarkReport } = require('../src/benchmark');
@@ -295,6 +295,12 @@ async function main() {
295
295
  return; // keep process alive for http
296
296
  } else if (normalizedCommand === 'augment' || normalizedCommand === 'suggest-only') {
297
297
  const report = await analyzeProject({ ...options, mode: normalizedCommand });
298
+ if (options.out && !options.json) {
299
+ const fs = require('fs');
300
+ const md = exportMarkdown(report);
301
+ fs.writeFileSync(options.out, md, 'utf8');
302
+ console.log(`\n Report exported to ${options.out}\n`);
303
+ }
298
304
  printAnalysis(report, options);
299
305
  } else if (normalizedCommand === 'plan') {
300
306
  const bundle = await buildProposalBundle(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudex-setup",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Audit and improve Claude Code readiness with discover, plan, apply, governance, and benchmark workflows.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/analyze.js CHANGED
@@ -170,6 +170,30 @@ function moduleFromCategory(category) {
170
170
  return map[category] || category;
171
171
  }
172
172
 
173
+ const STRENGTH_REASONS = {
174
+ claudeMd: 'Foundation of Claude workflow. Every session benefits from this.',
175
+ mermaidArchitecture: 'Architecture diagram saves 73% tokens vs prose — high-value asset.',
176
+ verificationLoop: 'Claude can self-verify, catching errors before human review.',
177
+ hooks: 'Automated enforcement (100% vs 80% from instructions alone).',
178
+ hooksInSettings: 'Hook registration in settings ensures consistent automation.',
179
+ preToolUseHook: 'Pre-execution validation adds a safety layer.',
180
+ postToolUseHook: 'Post-execution automation catches issues immediately.',
181
+ sessionStartHook: 'Session initialization ensures consistent starting state.',
182
+ customCommands: 'Reusable workflows encoded as one-liner commands.',
183
+ settingsPermissions: 'Explicit permissions prevent accidental dangerous operations.',
184
+ permissionDeny: 'Deny rules block risky operations at the system level.',
185
+ pathRules: 'Scoped rules ensure different code areas get appropriate guidance.',
186
+ fewShotExamples: 'Code examples guide Claude to match your conventions.',
187
+ constraintBlocks: 'XML constraint blocks improve rule adherence by 40%.',
188
+ xmlTags: 'Structured prompt sections improve consistency.',
189
+ context7Mcp: 'Real-time docs eliminate version-mismatch hallucinations.',
190
+ mcpServers: 'External tool integration extends Claude capabilities.',
191
+ compactionAwareness: 'Context management keeps sessions efficient.',
192
+ agents: 'Specialized agents delegate complex tasks effectively.',
193
+ noSecretsInClaude: 'No secrets in config — good security hygiene.',
194
+ gitIgnoreEnv: 'Environment files are properly excluded from git.',
195
+ };
196
+
173
197
  function toStrengths(results) {
174
198
  return results
175
199
  .filter(r => r.passed === true)
@@ -177,15 +201,30 @@ function toStrengths(results) {
177
201
  const order = { critical: 3, high: 2, medium: 1, low: 0 };
178
202
  return (order[b.impact] || 0) - (order[a.impact] || 0);
179
203
  })
180
- .slice(0, 6)
204
+ .slice(0, 8)
181
205
  .map(r => ({
182
206
  key: r.key,
183
207
  name: r.name,
184
208
  category: r.category,
185
- note: `Already present and worth preserving: ${r.name}.`,
209
+ why: STRENGTH_REASONS[r.key] || `Already configured and working: ${r.name}.`,
186
210
  }));
187
211
  }
188
212
 
213
+ const GAP_REASONS = {
214
+ noBypassPermissions: 'bypassPermissions skips all safety checks. Use explicit allow rules for control without risk.',
215
+ secretsProtection: 'Without deny rules for .env, Claude can read secrets and potentially expose them in outputs.',
216
+ testCommand: 'Without a test command, Claude cannot verify its changes work before you review them.',
217
+ lintCommand: 'Without a lint command, Claude may produce inconsistently formatted code.',
218
+ buildCommand: 'Without a build command, Claude cannot catch compilation errors early.',
219
+ ciPipeline: 'CI ensures every change is automatically tested. Without it, bugs reach main branch faster.',
220
+ securityReview: 'Claude Code has built-in OWASP Top 10 scanning. Not using it leaves vulnerabilities undetected.',
221
+ skills: 'Skills encode domain expertise as reusable components. Without them, you repeat context every session.',
222
+ multipleAgents: 'Multiple agents enable parallel specialized work (security review + code writing simultaneously).',
223
+ multipleMcpServers: 'More MCP servers give Claude access to more external context (docs, databases, APIs).',
224
+ roleDefinition: 'A role definition helps Claude calibrate response depth and technical level.',
225
+ importSyntax: '@import keeps CLAUDE.md lean while still providing deep instructions in focused modules.',
226
+ };
227
+
189
228
  function toGaps(results) {
190
229
  return results
191
230
  .filter(r => r.passed === false)
@@ -200,6 +239,7 @@ function toGaps(results) {
200
239
  impact: r.impact,
201
240
  category: r.category,
202
241
  fix: r.fix,
242
+ why: GAP_REASONS[r.key] || r.fix,
203
243
  }));
204
244
  }
205
245
 
@@ -356,7 +396,10 @@ function printAnalysis(report, options = {}) {
356
396
  if (report.strengthsPreserved.length > 0) {
357
397
  console.log(c(' Strengths Preserved', 'green'));
358
398
  for (const item of report.strengthsPreserved) {
359
- console.log(` - ${item.name}`);
399
+ console.log(` ${c('✓', 'green')} ${item.name}`);
400
+ if (item.why) {
401
+ console.log(c(` ${item.why}`, 'dim'));
402
+ }
360
403
  }
361
404
  console.log('');
362
405
  }
@@ -420,4 +463,87 @@ function printAnalysis(report, options = {}) {
420
463
  }
421
464
  }
422
465
 
423
- module.exports = { analyzeProject, printAnalysis };
466
+ function exportMarkdown(report) {
467
+ const lines = [];
468
+ lines.push(`# Claudex Setup Analysis Report`);
469
+ lines.push(`## ${report.mode === 'suggest-only' ? 'Suggest-Only' : 'Augment'} Mode`);
470
+ lines.push('');
471
+ lines.push(`**Project:** ${report.projectSummary.name}${report.projectSummary.description ? ` — ${report.projectSummary.description}` : ''}`);
472
+ lines.push(`**Date:** ${new Date().toISOString().split('T')[0]}`);
473
+ lines.push(`**Score:** ${report.projectSummary.score}/100 | **Organic:** ${report.projectSummary.organicScore}/100`);
474
+ lines.push(`**Stacks:** ${report.projectSummary.stacks.join(', ') || 'None detected'}`);
475
+ lines.push(`**Domain Packs:** ${report.projectSummary.domains.join(', ') || 'Baseline General'}`);
476
+ lines.push(`**Maturity:** ${report.projectSummary.maturity}`);
477
+ lines.push('');
478
+
479
+ if (report.strengthsPreserved.length > 0) {
480
+ lines.push('## Strengths Preserved');
481
+ lines.push('');
482
+ for (const item of report.strengthsPreserved) {
483
+ lines.push(`- **${item.name}** — ${item.why || 'Already configured.'}`);
484
+ }
485
+ lines.push('');
486
+ }
487
+
488
+ if (report.gapsIdentified.length > 0) {
489
+ lines.push('## Gaps Identified');
490
+ lines.push('');
491
+ lines.push('| Gap | Impact | Fix |');
492
+ lines.push('|-----|--------|-----|');
493
+ for (const item of report.gapsIdentified) {
494
+ lines.push(`| ${item.name} | ${item.impact} | ${item.fix} |`);
495
+ }
496
+ lines.push('');
497
+ }
498
+
499
+ if (report.topNextActions.length > 0) {
500
+ lines.push('## Top Next Actions');
501
+ lines.push('');
502
+ report.topNextActions.slice(0, 5).forEach((item, index) => {
503
+ lines.push(`${index + 1}. **${item.name}** — ${item.fix}`);
504
+ });
505
+ lines.push('');
506
+ }
507
+
508
+ if (report.recommendedDomainPacks.length > 0) {
509
+ lines.push('## Recommended Domain Packs');
510
+ lines.push('');
511
+ for (const pack of report.recommendedDomainPacks) {
512
+ lines.push(`- **${pack.label}**: ${pack.useWhen}`);
513
+ }
514
+ lines.push('');
515
+ }
516
+
517
+ if (report.recommendedMcpPacks.length > 0) {
518
+ lines.push('## Recommended MCP Packs');
519
+ lines.push('');
520
+ for (const pack of report.recommendedMcpPacks) {
521
+ lines.push(`- **${pack.label}**: ${pack.useWhen}`);
522
+ }
523
+ lines.push('');
524
+ }
525
+
526
+ if (report.riskNotes.length > 0) {
527
+ lines.push('## Risk Notes');
528
+ lines.push('');
529
+ for (const note of report.riskNotes) {
530
+ lines.push(`- ⚠️ ${note}`);
531
+ }
532
+ lines.push('');
533
+ }
534
+
535
+ if (report.suggestedRolloutOrder.length > 0) {
536
+ lines.push('## Suggested Rollout Order');
537
+ lines.push('');
538
+ report.suggestedRolloutOrder.forEach((item, index) => {
539
+ lines.push(`${index + 1}. ${item}`);
540
+ });
541
+ lines.push('');
542
+ }
543
+
544
+ lines.push('---');
545
+ lines.push(`*Generated by claudex-setup v${require('../package.json').version}*`);
546
+ return lines.join('\n');
547
+ }
548
+
549
+ module.exports = { analyzeProject, printAnalysis, exportMarkdown };
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "synced_from": "claudex",
3
- "synced_at": "2026-03-30T23:05:29Z",
3
+ "synced_at": "2026-03-31T19:30:00Z",
4
4
  "total_items": 1107,
5
5
  "tested": 948,
6
- "last_id": 1157
6
+ "last_id": 1157,
7
+ "domain_packs": 10,
8
+ "mcp_packs": 5,
9
+ "anti_patterns": 53,
10
+ "contract_version": "1.0.0"
7
11
  }
@@ -55,6 +55,30 @@ const DOMAIN_PACKS = [
55
55
  recommendedMcpPacks: ['context7-docs'],
56
56
  benchmarkFocus: ['policy-aware rollout', 'approval flow readiness', 'benchmark export quality'],
57
57
  },
58
+ {
59
+ key: 'monorepo',
60
+ label: 'Monorepo',
61
+ useWhen: 'Nx, Turborepo, Lerna, or workspace-based repos with multiple packages sharing a root.',
62
+ recommendedModules: ['path-specific rules', 'commands per package', 'governance', 'agents'],
63
+ recommendedMcpPacks: ['context7-docs'],
64
+ benchmarkFocus: ['package-scoped rule coverage', 'cross-package safety', 'workspace-aware starter output'],
65
+ },
66
+ {
67
+ key: 'mobile',
68
+ label: 'Mobile App',
69
+ useWhen: 'React Native, Flutter, Swift, or Kotlin repos with mobile-specific build and release workflows.',
70
+ recommendedModules: ['verification', 'commands', 'rules', 'agents'],
71
+ recommendedMcpPacks: ['context7-docs'],
72
+ benchmarkFocus: ['build verification', 'platform-specific rules', 'release workflow quality'],
73
+ },
74
+ {
75
+ key: 'regulated-lite',
76
+ label: 'Regulated Lite',
77
+ useWhen: 'Repos in regulated environments (fintech, health, legal) that need auditability without full enterprise governance overhead.',
78
+ recommendedModules: ['governance', 'activity artifacts', 'suggest-only profile', 'audit logging'],
79
+ recommendedMcpPacks: ['context7-docs'],
80
+ benchmarkFocus: ['audit trail completeness', 'change traceability', 'policy compliance readiness'],
81
+ },
58
82
  ];
59
83
 
60
84
  function uniqueByKey(items) {
@@ -144,6 +168,45 @@ function detectDomainPacks(ctx, stacks, assets = null) {
144
168
  ]);
145
169
  }
146
170
 
171
+ // Monorepo detection
172
+ const isMonorepo = ctx.files.includes('nx.json') || ctx.files.includes('turbo.json') ||
173
+ ctx.files.includes('lerna.json') || ctx.hasDir('packages') ||
174
+ (pkg.workspaces && (Array.isArray(pkg.workspaces) ? pkg.workspaces.length > 0 : true));
175
+ if (isMonorepo) {
176
+ addMatch('monorepo', [
177
+ 'Detected monorepo or workspace configuration.',
178
+ ctx.files.includes('nx.json') ? 'Nx workspace detected.' : null,
179
+ ctx.files.includes('turbo.json') ? 'Turborepo detected.' : null,
180
+ ctx.hasDir('packages') ? 'Packages directory detected.' : null,
181
+ ]);
182
+ }
183
+
184
+ // Mobile detection
185
+ const isMobile = deps['react-native'] || deps.expo || deps.flutter ||
186
+ ctx.files.includes('Podfile') || ctx.files.includes('build.gradle') ||
187
+ ctx.files.includes('build.gradle.kts') || ctx.hasDir('ios') || ctx.hasDir('android');
188
+ if (isMobile) {
189
+ addMatch('mobile', [
190
+ 'Detected mobile app structure or dependencies.',
191
+ deps['react-native'] ? 'React Native detected.' : null,
192
+ ctx.hasDir('ios') ? 'iOS directory detected.' : null,
193
+ ctx.hasDir('android') ? 'Android directory detected.' : null,
194
+ ]);
195
+ }
196
+
197
+ // Regulated-lite detection
198
+ const isRegulated = ctx.files.includes('SECURITY.md') ||
199
+ ctx.files.includes('COMPLIANCE.md') || ctx.hasDir('compliance') ||
200
+ ctx.hasDir('audit') || ctx.hasDir('policies') ||
201
+ (pkg.keywords && pkg.keywords.some(k => ['hipaa', 'fintech', 'compliance', 'regulated', 'sox', 'pci'].includes(k)));
202
+ if (isRegulated && !isEnterpriseGoverned) {
203
+ addMatch('regulated-lite', [
204
+ 'Detected compliance or regulatory signals without full enterprise governance.',
205
+ ctx.files.includes('SECURITY.md') ? 'SECURITY.md present.' : null,
206
+ ctx.hasDir('compliance') ? 'Compliance directory detected.' : null,
207
+ ]);
208
+ }
209
+
147
210
  const deduped = uniqueByKey(matches);
148
211
  if (deduped.length === 0) {
149
212
  return [{
package/src/governance.js CHANGED
@@ -86,6 +86,54 @@ const HOOK_REGISTRY = [
86
86
  dryRunExample: 'Edit one file and verify the log entry is appended.',
87
87
  rollbackPath: 'Remove the PostToolUse hook entry and delete the log file if desired.',
88
88
  },
89
+ {
90
+ key: 'duplicate-id-check',
91
+ file: '.claude/hooks/check-duplicate-ids.sh',
92
+ triggerPoint: 'PostToolUse',
93
+ matcher: 'Write|Edit',
94
+ purpose: 'Detects duplicate IDs in catalog or structured data files after edits.',
95
+ filesTouched: [],
96
+ sideEffects: ['Returns a systemMessage warning if duplicates are found.'],
97
+ risk: 'low',
98
+ dryRunExample: 'Edit a catalog file and verify duplicate check runs without blocking.',
99
+ rollbackPath: 'Remove the PostToolUse hook entry from settings.',
100
+ },
101
+ {
102
+ key: 'injection-defense',
103
+ file: '.claude/hooks/injection-defense.sh',
104
+ triggerPoint: 'PostToolUse',
105
+ matcher: 'WebFetch|WebSearch',
106
+ purpose: 'Scans web tool outputs for common prompt injection patterns.',
107
+ filesTouched: ['tools/failure-log.txt'],
108
+ sideEffects: ['Logs alerts to failure log.', 'Returns a systemMessage warning if patterns detected.'],
109
+ risk: 'low',
110
+ dryRunExample: 'Run a WebFetch and verify output is scanned for injection patterns.',
111
+ rollbackPath: 'Remove the PostToolUse hook entry from settings.',
112
+ },
113
+ {
114
+ key: 'trust-drift-check',
115
+ file: '.claude/hooks/trust-drift-check.sh',
116
+ triggerPoint: 'PostToolUse',
117
+ matcher: 'Write|Edit',
118
+ purpose: 'Runs trust drift validation after file changes to catch metric/docs inconsistencies.',
119
+ filesTouched: [],
120
+ sideEffects: ['Returns a systemMessage warning if drift is detected.'],
121
+ risk: 'low',
122
+ dryRunExample: 'Edit a product-facing file and verify drift check runs.',
123
+ rollbackPath: 'Remove the PostToolUse hook entry from settings.',
124
+ },
125
+ {
126
+ key: 'session-init',
127
+ file: '.claude/hooks/rotate-logs.sh',
128
+ triggerPoint: 'SessionStart',
129
+ matcher: null,
130
+ purpose: 'Rotates large log files and loads workspace context at session start.',
131
+ filesTouched: ['tools/change-log.txt', 'tools/failure-log.txt'],
132
+ sideEffects: ['Archives logs over 500KB.', 'Returns a systemMessage with workspace info.'],
133
+ risk: 'low',
134
+ dryRunExample: 'Start a new session and verify log rotation runs.',
135
+ rollbackPath: 'Remove the SessionStart hook entry from settings.',
136
+ },
89
137
  ];
90
138
 
91
139
  const POLICY_PACKS = [
@@ -307,8 +355,9 @@ function printGovernanceSummary(summary, options = {}) {
307
355
 
308
356
  console.log(' Hook Registry');
309
357
  for (const hook of summary.hookRegistry) {
310
- console.log(` - ${hook.file}`);
311
- console.log(` ${hook.triggerPoint} ${hook.matcher} -> ${hook.purpose}`);
358
+ const riskColor = hook.risk === 'low' ? '\x1b[32m' : hook.risk === 'medium' ? '\x1b[33m' : '\x1b[31m';
359
+ console.log(` - ${hook.file} ${riskColor}[${hook.risk} risk]\x1b[0m`);
360
+ console.log(` ${hook.triggerPoint}${hook.matcher ? ` ${hook.matcher}` : ''} -> ${hook.purpose}`);
312
361
  }
313
362
  console.log('');
314
363
 
package/src/mcp-packs.js CHANGED
@@ -23,6 +23,44 @@ const MCP_PACKS = [
23
23
  },
24
24
  },
25
25
  },
26
+ {
27
+ key: 'github-mcp',
28
+ label: 'GitHub',
29
+ useWhen: 'Repos hosted on GitHub that benefit from issue, PR, and repository context during Claude sessions.',
30
+ adoption: 'Recommended for any GitHub-hosted project. Requires GITHUB_PERSONAL_ACCESS_TOKEN env var.',
31
+ servers: {
32
+ github: {
33
+ command: 'npx',
34
+ args: ['-y', '@modelcontextprotocol/server-github'],
35
+ env: { GITHUB_PERSONAL_ACCESS_TOKEN: '${GITHUB_PERSONAL_ACCESS_TOKEN}' },
36
+ },
37
+ },
38
+ },
39
+ {
40
+ key: 'postgres-mcp',
41
+ label: 'PostgreSQL',
42
+ useWhen: 'Repos with PostgreSQL databases that benefit from schema inspection and query assistance.',
43
+ adoption: 'Useful for backend-api and data-pipeline repos. Requires DATABASE_URL env var.',
44
+ servers: {
45
+ postgres: {
46
+ command: 'npx',
47
+ args: ['-y', '@modelcontextprotocol/server-postgres'],
48
+ env: { DATABASE_URL: '${DATABASE_URL}' },
49
+ },
50
+ },
51
+ },
52
+ {
53
+ key: 'memory-mcp',
54
+ label: 'Memory / Knowledge Graph',
55
+ useWhen: 'Long-running projects that benefit from persistent entity and relationship tracking across sessions.',
56
+ adoption: 'Useful for complex projects with many interconnected concepts. Stores data locally.',
57
+ servers: {
58
+ memory: {
59
+ command: 'npx',
60
+ args: ['-y', '@modelcontextprotocol/server-memory'],
61
+ },
62
+ },
63
+ },
26
64
  ];
27
65
 
28
66
  function clone(value) {
@@ -71,6 +109,22 @@ function recommendMcpPacks(stacks = [], domainPacks = []) {
71
109
  recommended.add('context7-docs');
72
110
  }
73
111
 
112
+ // GitHub MCP for repos with .github directory
113
+ const domainKeys = new Set(domainPacks.map(p => p.key));
114
+ if (domainKeys.has('oss-library') || domainKeys.has('enterprise-governed')) {
115
+ recommended.add('github-mcp');
116
+ }
117
+
118
+ // Postgres MCP for data-heavy repos
119
+ if (domainKeys.has('data-pipeline') || domainKeys.has('backend-api')) {
120
+ recommended.add('postgres-mcp');
121
+ }
122
+
123
+ // Memory MCP for complex/monorepo projects
124
+ if (domainKeys.has('monorepo') || domainKeys.has('enterprise-governed')) {
125
+ recommended.add('memory-mcp');
126
+ }
127
+
74
128
  return MCP_PACKS
75
129
  .filter(pack => recommended.has(pack.key))
76
130
  .map(pack => clone(pack));
package/src/techniques.js CHANGED
@@ -358,9 +358,15 @@ const TECHNIQUES = {
358
358
  id: 2402,
359
359
  name: 'Default mode is not bypassPermissions',
360
360
  check: (ctx) => {
361
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
362
- if (!settings || !settings.permissions) return null; // no settings = skip (not applicable)
363
- return settings.permissions.defaultMode !== 'bypassPermissions';
361
+ // Check shared settings first (committed to git) if the shared baseline
362
+ // is safe, a personal settings.local.json override should not fail the audit.
363
+ const shared = ctx.jsonFile('.claude/settings.json');
364
+ if (shared && shared.permissions) {
365
+ return shared.permissions.defaultMode !== 'bypassPermissions';
366
+ }
367
+ const local = ctx.jsonFile('.claude/settings.local.json');
368
+ if (!local || !local.permissions) return null;
369
+ return local.permissions.defaultMode !== 'bypassPermissions';
364
370
  },
365
371
  impact: 'critical',
366
372
  rating: 5,
@@ -373,7 +379,8 @@ const TECHNIQUES = {
373
379
  id: 1096,
374
380
  name: 'Secrets protection configured',
375
381
  check: (ctx) => {
376
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
382
+ // Prefer shared settings.json (committed) over local override
383
+ const settings = ctx.jsonFile('.claude/settings.json') || ctx.jsonFile('.claude/settings.local.json');
377
384
  if (!settings || !settings.permissions) return false;
378
385
  const deny = JSON.stringify(settings.permissions.deny || []);
379
386
  return deny.includes('.env') || deny.includes('secrets');