claudex-setup 1.0.1 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudex-setup",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Audit and optimize any project for Claude Code. Powered by 1107 verified techniques.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/audit.js CHANGED
@@ -61,17 +61,35 @@ async function audit(options) {
61
61
  });
62
62
  }
63
63
 
64
- const passed = results.filter(r => r.passed);
65
- const failed = results.filter(r => !r.passed);
64
+ // null = not applicable (skip), true = pass, false = fail
65
+ const applicable = results.filter(r => r.passed !== null);
66
+ const skipped = results.filter(r => r.passed === null);
67
+ const passed = applicable.filter(r => r.passed);
68
+ const failed = applicable.filter(r => !r.passed);
66
69
  const critical = failed.filter(r => r.impact === 'critical');
67
70
  const high = failed.filter(r => r.impact === 'high');
68
71
  const medium = failed.filter(r => r.impact === 'medium');
69
72
 
70
- // Calculate score
73
+ // Calculate score only from applicable checks
71
74
  const weights = { critical: 15, high: 10, medium: 5 };
72
- const maxScore = results.reduce((sum, r) => sum + (weights[r.impact] || 5), 0);
75
+ const maxScore = applicable.reduce((sum, r) => sum + (weights[r.impact] || 5), 0);
73
76
  const earnedScore = passed.reduce((sum, r) => sum + (weights[r.impact] || 5), 0);
74
- const score = Math.round((earnedScore / maxScore) * 100);
77
+ const score = maxScore > 0 ? Math.round((earnedScore / maxScore) * 100) : 0;
78
+
79
+ // Detect scaffolded vs organic: if CLAUDE.md contains our version stamp, some checks
80
+ // are passing because WE generated them, not the user
81
+ const claudeMd = ctx.fileContent('CLAUDE.md') || '';
82
+ const isScaffolded = claudeMd.includes('Generated by claudex-setup') ||
83
+ claudeMd.includes('claudex-setup');
84
+ // Scaffolded checks: things our setup creates (CLAUDE.md, hooks, commands, agents, rules, skills)
85
+ const scaffoldedKeys = new Set(['claudeMd', 'mermaidArchitecture', 'verificationLoop',
86
+ 'hooks', 'customCommands', 'multipleCommands', 'agents', 'pathRules', 'multipleRules',
87
+ 'skills', 'hooksConfigured', 'preToolUseHook', 'postToolUseHook', 'fewShotExamples',
88
+ 'constraintBlocks', 'xmlTags']);
89
+ const organicPassed = passed.filter(r => !scaffoldedKeys.has(r.key));
90
+ const scaffoldedPassed = passed.filter(r => scaffoldedKeys.has(r.key));
91
+ const organicEarned = organicPassed.reduce((sum, r) => sum + (weights[r.impact] || 5), 0);
92
+ const organicScore = maxScore > 0 ? Math.round((organicEarned / maxScore) * 100) : 0;
75
93
 
76
94
  // Silent mode: skip all output, just return result
77
95
  if (silent) {
@@ -97,6 +115,9 @@ async function audit(options) {
97
115
 
98
116
  // Score
99
117
  console.log(` ${progressBar(score)} ${colorize(`${score}/100`, 'bold')}`);
118
+ if (isScaffolded && scaffoldedPassed.length > 0) {
119
+ console.log(colorize(` Organic: ${organicScore}/100 (without claudex-setup generated files)`, 'dim'));
120
+ }
100
121
  console.log('');
101
122
 
102
123
  // Passed
@@ -153,7 +174,7 @@ async function audit(options) {
153
174
 
154
175
  // Summary
155
176
  console.log(colorize(' ─────────────────────────────────────', 'dim'));
156
- console.log(` ${colorize(`${passed.length}/${results.length}`, 'bold')} checks passing`);
177
+ console.log(` ${colorize(`${passed.length}/${applicable.length}`, 'bold')} checks passing${skipped.length > 0 ? colorize(` (${skipped.length} not applicable)`, 'dim') : ''}`);
157
178
 
158
179
  if (failed.length > 0) {
159
180
  console.log(` Run ${colorize('npx claudex-setup setup', 'bold')} to fix automatically`);
package/src/setup.js CHANGED
@@ -25,6 +25,92 @@ function detectScripts(ctx) {
25
25
  return found;
26
26
  }
27
27
 
28
+ // ============================================================
29
+ // Helper: detect key dependencies and generate guidelines
30
+ // ============================================================
31
+ function detectDependencies(ctx) {
32
+ const pkg = ctx.jsonFile('package.json');
33
+ if (!pkg) return [];
34
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
35
+ const guidelines = [];
36
+
37
+ // Data fetching
38
+ if (allDeps['@tanstack/react-query']) {
39
+ guidelines.push('- Use React Query (TanStack Query) for all server data fetching — never raw useEffect + fetch');
40
+ guidelines.push('- Define query keys as constants. Invalidate related queries after mutations');
41
+ }
42
+ if (allDeps['swr']) {
43
+ guidelines.push('- Use SWR for data fetching with automatic revalidation');
44
+ }
45
+
46
+ // Validation
47
+ if (allDeps['zod']) {
48
+ guidelines.push('- Use Zod for all input validation and type inference (z.infer<typeof schema>)');
49
+ guidelines.push('- Define schemas in a shared location. Use .parse() at API boundaries');
50
+ }
51
+
52
+ // ORM / Database
53
+ if (allDeps['prisma'] || allDeps['@prisma/client']) {
54
+ guidelines.push('- Use Prisma for all database operations. Run `npx prisma generate` after schema changes');
55
+ guidelines.push('- Never write raw SQL unless Prisma cannot express the query');
56
+ }
57
+ if (allDeps['drizzle-orm']) {
58
+ guidelines.push('- Use Drizzle ORM for database operations. Schema-first approach');
59
+ }
60
+ if (allDeps['mongoose']) {
61
+ guidelines.push('- Use Mongoose for MongoDB operations. Define schemas with validation');
62
+ }
63
+
64
+ // Auth
65
+ if (allDeps['next-auth'] || allDeps['@auth/core']) {
66
+ guidelines.push('- Use NextAuth.js for authentication. Access session via auth() in Server Components');
67
+ }
68
+ if (allDeps['clerk'] || allDeps['@clerk/nextjs']) {
69
+ guidelines.push('- Use Clerk for authentication. Protect routes with middleware');
70
+ }
71
+
72
+ // State management
73
+ if (allDeps['zustand']) {
74
+ guidelines.push('- Use Zustand for client state. Keep stores small and focused');
75
+ }
76
+ if (allDeps['@reduxjs/toolkit']) {
77
+ guidelines.push('- Use Redux Toolkit for state management. Use createSlice and RTK Query');
78
+ }
79
+
80
+ // Styling
81
+ if (allDeps['tailwindcss']) {
82
+ guidelines.push('- Use Tailwind CSS for all styling. Avoid inline styles and CSS modules');
83
+ }
84
+ if (allDeps['styled-components'] || allDeps['@emotion/react']) {
85
+ guidelines.push('- Use CSS-in-JS for component styling. Colocate styles with components');
86
+ }
87
+
88
+ // Testing
89
+ if (allDeps['vitest']) {
90
+ guidelines.push('- Use Vitest for testing. Colocate test files with source (*.test.ts)');
91
+ }
92
+ if (allDeps['jest']) {
93
+ guidelines.push('- Use Jest for testing. Follow existing test patterns in the codebase');
94
+ }
95
+ if (allDeps['playwright'] || allDeps['@playwright/test']) {
96
+ guidelines.push('- Use Playwright for E2E tests. Keep tests in tests/ or e2e/');
97
+ }
98
+
99
+ // Python
100
+ const reqTxt = ctx.fileContent('requirements.txt') || '';
101
+ if (reqTxt.includes('sqlalchemy')) {
102
+ guidelines.push('- Use SQLAlchemy for all database operations');
103
+ }
104
+ if (reqTxt.includes('pydantic')) {
105
+ guidelines.push('- Use Pydantic for data validation and serialization');
106
+ }
107
+ if (reqTxt.includes('pytest')) {
108
+ guidelines.push('- Use pytest for testing. Run with `python -m pytest`');
109
+ }
110
+
111
+ return guidelines;
112
+ }
113
+
28
114
  // ============================================================
29
115
  // Helper: detect main directories
30
116
  // ============================================================
@@ -356,6 +442,13 @@ npm run lint # or: npx eslint .`;
356
442
  }
357
443
  }
358
444
 
445
+ // --- Dependency-specific guidelines ---
446
+ const depGuidelines = detectDependencies(ctx);
447
+ const depSection = depGuidelines.length > 0 ? `
448
+ ## Key Dependencies
449
+ ${depGuidelines.join('\n')}
450
+ ` : '';
451
+
359
452
  // --- Verification criteria based on detected commands ---
360
453
  const verificationSteps = [];
361
454
  verificationSteps.push('1. All existing tests still pass');
@@ -388,7 +481,7 @@ ${mermaid}
388
481
  ${dirDescription}
389
482
  ## Stack
390
483
  ${stackNames}
391
- ${stackSection}${tsSection}
484
+ ${stackSection}${tsSection}${depSection}
392
485
  ## Build & Test
393
486
  \`\`\`bash
394
487
  ${buildSection}
@@ -476,38 +569,109 @@ exit 0
476
569
  `,
477
570
  }),
478
571
 
479
- 'commands': () => ({
480
- 'test.md': `Run the test suite and report results.
572
+ 'commands': (stacks) => {
573
+ const stackKeys = stacks.map(s => s.key);
574
+ const isNext = stackKeys.includes('nextjs');
575
+ const isDjango = stackKeys.includes('django');
576
+ const isFastApi = stackKeys.includes('fastapi');
577
+ const isPython = stackKeys.includes('python') || isDjango || isFastApi;
578
+ const hasDocker = stackKeys.includes('docker');
579
+
580
+ const cmds = {};
581
+
582
+ // Test command - stack-specific
583
+ if (isNext) {
584
+ cmds['test.md'] = `Run the test suite for this Next.js project.
585
+
586
+ ## Steps:
587
+ 1. Run \`npm test\` (or \`npx vitest run\`)
588
+ 2. If tests fail, check for missing mocks or async issues
589
+ 3. For component tests, ensure React Testing Library patterns are used
590
+ 4. For API route tests, check request/response handling
591
+ 5. Report: total, passed, failed, coverage if available
592
+ `;
593
+ } else if (isPython) {
594
+ cmds['test.md'] = `Run the test suite for this Python project.
595
+
596
+ ## Steps:
597
+ 1. Run \`python -m pytest -v\` (or the project's test command)
598
+ 2. Check for fixture issues, missing test database, or import errors
599
+ 3. If using Django: \`python manage.py test\`
600
+ 4. Report: total, passed, failed, and any tracebacks
601
+ `;
602
+ } else {
603
+ cmds['test.md'] = `Run the test suite and report results.
481
604
 
482
605
  ## Steps:
483
606
  1. Run the project's test command
484
607
  2. If tests fail, analyze the failures
485
608
  3. Report: total, passed, failed, and any error details
486
- `,
487
- 'review.md': `Review the current changes for quality and correctness.
609
+ `;
610
+ }
611
+
612
+ // Review - always generic (works well as-is)
613
+ cmds['review.md'] = `Review the current changes for quality and correctness.
488
614
 
489
615
  ## Steps:
490
616
  1. Run \`git diff\` to see all changes
491
617
  2. Check for: bugs, security issues, missing tests, code style
492
618
  3. Provide actionable feedback
493
- `,
494
- 'deploy.md': `Pre-deployment checklist and deployment steps.
619
+ `;
620
+
621
+ // Deploy - stack-specific
622
+ if (isNext) {
623
+ cmds['deploy.md'] = `Pre-deployment checklist for Next.js.
624
+
625
+ ## Pre-deploy:
626
+ 1. Run \`git status\` — working tree must be clean
627
+ 2. Run \`npm run build\` — must succeed with no errors
628
+ 3. Run \`npm test\` — all tests pass
629
+ 4. Run \`npm run lint\` — no lint errors
630
+ 5. Check for \`console.log\` in production code
631
+ 6. Verify environment variables are set in deployment platform
632
+
633
+ ## Deploy:
634
+ 1. If Vercel: \`git push\` triggers auto-deploy
635
+ 2. If self-hosted: \`npm run build && npm start\`
636
+ 3. Verify: check /api/health or main page loads
637
+ 4. Tag: \`git tag -a vX.Y.Z -m "Release vX.Y.Z"\`
638
+ `;
639
+ } else if (hasDocker) {
640
+ cmds['deploy.md'] = `Pre-deployment checklist with Docker.
641
+
642
+ ## Pre-deploy:
643
+ 1. Run \`git status\` — working tree must be clean
644
+ 2. Run full test suite — all tests pass
645
+ 3. Run \`docker build -t app .\` — must succeed
646
+ 4. Run \`docker run app\` locally — smoke test
647
+
648
+ ## Deploy:
649
+ 1. Build: \`docker build -t registry/app:latest .\`
650
+ 2. Push: \`docker push registry/app:latest\`
651
+ 3. Deploy to target environment
652
+ 4. Verify health endpoint responds
653
+ 5. Tag: \`git tag -a vX.Y.Z -m "Release vX.Y.Z"\`
654
+ `;
655
+ } else {
656
+ cmds['deploy.md'] = `Pre-deployment checklist.
495
657
 
496
- ## Pre-deploy checks:
658
+ ## Pre-deploy:
497
659
  1. Run \`git status\` — working tree must be clean
498
660
  2. Run full test suite — all tests must pass
499
- 3. Run linter — no errors allowed
500
- 4. Check for TODO/FIXME/HACK comments in changed files
501
- 5. Verify no secrets in staged changes (\`git diff --cached\`)
502
-
503
- ## Deploy steps:
504
- 1. Confirm target environment (staging vs production)
505
- 2. Review the diff since last deploy tag
506
- 3. Run the deployment command
507
- 4. Verify deployment succeeded (health check / smoke test)
508
- 5. Tag the release: \`git tag -a vX.Y.Z -m "Release vX.Y.Z"\`
509
- `,
510
- 'fix.md': `Fix the issue described: $ARGUMENTS
661
+ 3. Run linter — no errors
662
+ 4. Verify no secrets in staged changes
663
+ 5. Review diff since last deploy
664
+
665
+ ## Deploy:
666
+ 1. Confirm target environment
667
+ 2. Run deployment command
668
+ 3. Verify deployment (health check)
669
+ 4. Tag: \`git tag -a vX.Y.Z -m "Release vX.Y.Z"\`
670
+ `;
671
+ }
672
+
673
+ // Fix - always generic with $ARGUMENTS
674
+ cmds['fix.md'] = `Fix the issue described: $ARGUMENTS
511
675
 
512
676
  ## Steps:
513
677
  1. Understand the issue — read relevant code and error messages
@@ -516,8 +680,32 @@ exit 0
516
680
  4. Write or update tests to cover the fix
517
681
  5. Run the full test suite to verify no regressions
518
682
  6. Summarize what was wrong and how the fix addresses it
519
- `,
520
- }),
683
+ `;
684
+
685
+ // Stack-specific bonus commands
686
+ if (isNext) {
687
+ cmds['check-build.md'] = `Run Next.js build check without deploying.
688
+
689
+ 1. Run \`npx next build\`
690
+ 2. Check for: TypeScript errors, missing pages, broken imports
691
+ 3. Verify no "Dynamic server usage" errors in static pages
692
+ 4. Report build output size and any warnings
693
+ `;
694
+ }
695
+
696
+ if (isPython && (isDjango || isFastApi)) {
697
+ cmds['migrate.md'] = `Run database migrations safely.
698
+
699
+ 1. Check current migration status${isDjango ? ': `python manage.py showmigrations`' : ''}
700
+ 2. Create new migration if schema changed${isDjango ? ': `python manage.py makemigrations`' : ''}
701
+ 3. Review the generated migration file
702
+ 4. Apply: ${isDjango ? '`python manage.py migrate`' : '`alembic upgrade head`'}
703
+ 5. Verify: check that the app starts and queries work
704
+ `;
705
+ }
706
+
707
+ return cmds;
708
+ },
521
709
 
522
710
  'skills': () => ({
523
711
  'fix-issue/SKILL.md': `---
package/src/techniques.js CHANGED
@@ -92,7 +92,7 @@ const TECHNIQUES = {
92
92
  },
93
93
 
94
94
  testCommand: {
95
- id: 93,
95
+ id: 93001,
96
96
  name: 'CLAUDE.md contains a test command',
97
97
  check: (ctx) => {
98
98
  const md = ctx.fileContent('CLAUDE.md') || '';
@@ -106,7 +106,7 @@ const TECHNIQUES = {
106
106
  },
107
107
 
108
108
  lintCommand: {
109
- id: 93,
109
+ id: 93002,
110
110
  name: 'CLAUDE.md contains a lint command',
111
111
  check: (ctx) => {
112
112
  const md = ctx.fileContent('CLAUDE.md') || '';
@@ -120,7 +120,7 @@ const TECHNIQUES = {
120
120
  },
121
121
 
122
122
  buildCommand: {
123
- id: 93,
123
+ id: 93003,
124
124
  name: 'CLAUDE.md contains a build command',
125
125
  check: (ctx) => {
126
126
  const md = ctx.fileContent('CLAUDE.md') || '';
@@ -167,7 +167,7 @@ const TECHNIQUES = {
167
167
  },
168
168
 
169
169
  gitIgnoreNodeModules: {
170
- id: 917,
170
+ id: 91701,
171
171
  name: '.gitignore blocks node_modules',
172
172
  check: (ctx) => {
173
173
  const gitignore = ctx.fileContent('.gitignore') || '';
@@ -210,7 +210,7 @@ const TECHNIQUES = {
210
210
  },
211
211
 
212
212
  multipleCommands: {
213
- id: 20,
213
+ id: 20001,
214
214
  name: '3+ slash commands for rich workflow',
215
215
  check: (ctx) => ctx.hasDir('.claude/commands') && ctx.dirFiles('.claude/commands').length >= 3,
216
216
  impact: 'medium',
@@ -221,7 +221,7 @@ const TECHNIQUES = {
221
221
  },
222
222
 
223
223
  deployCommand: {
224
- id: 20,
224
+ id: 20002,
225
225
  name: 'Has /deploy or /release command',
226
226
  check: (ctx) => {
227
227
  if (!ctx.hasDir('.claude/commands')) return false;
@@ -236,7 +236,7 @@ const TECHNIQUES = {
236
236
  },
237
237
 
238
238
  reviewCommand: {
239
- id: 20,
239
+ id: 20003,
240
240
  name: 'Has /review command',
241
241
  check: (ctx) => {
242
242
  if (!ctx.hasDir('.claude/commands')) return false;
@@ -262,7 +262,7 @@ const TECHNIQUES = {
262
262
  },
263
263
 
264
264
  multipleSkills: {
265
- id: 21,
265
+ id: 2101,
266
266
  name: '2+ skills for specialization',
267
267
  check: (ctx) => ctx.hasDir('.claude/skills') && ctx.dirFiles('.claude/skills').length >= 2,
268
268
  impact: 'medium',
@@ -284,7 +284,7 @@ const TECHNIQUES = {
284
284
  },
285
285
 
286
286
  multipleAgents: {
287
- id: 22,
287
+ id: 2201,
288
288
  name: '2+ agents for delegation',
289
289
  check: (ctx) => ctx.hasDir('.claude/agents') && ctx.dirFiles('.claude/agents').length >= 2,
290
290
  impact: 'medium',
@@ -295,7 +295,7 @@ const TECHNIQUES = {
295
295
  },
296
296
 
297
297
  multipleRules: {
298
- id: 3,
298
+ id: 301,
299
299
  name: '2+ rules files for granular control',
300
300
  check: (ctx) => ctx.hasDir('.claude/rules') && ctx.dirFiles('.claude/rules').length >= 2,
301
301
  impact: 'medium',
@@ -324,7 +324,7 @@ const TECHNIQUES = {
324
324
  },
325
325
 
326
326
  permissionDeny: {
327
- id: 24,
327
+ id: 2401,
328
328
  name: 'Deny rules configured in permissions',
329
329
  check: (ctx) => {
330
330
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
@@ -340,12 +340,12 @@ const TECHNIQUES = {
340
340
  },
341
341
 
342
342
  noBypassPermissions: {
343
- id: 24,
343
+ id: 2402,
344
344
  name: 'Default mode is not bypassPermissions',
345
345
  check: (ctx) => {
346
346
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
347
- if (!settings) return true; // no settings = not bypassed
348
- return settings.defaultMode !== 'bypassPermissions';
347
+ if (!settings || !settings.permissions) return null; // no settings = skip (not applicable)
348
+ return settings.permissions.defaultMode !== 'bypassPermissions';
349
349
  },
350
350
  impact: 'critical',
351
351
  rating: 5,
@@ -400,7 +400,7 @@ const TECHNIQUES = {
400
400
  },
401
401
 
402
402
  hooksInSettings: {
403
- id: 88,
403
+ id: 8801,
404
404
  name: 'Hooks configured in settings',
405
405
  check: (ctx) => {
406
406
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
@@ -415,7 +415,7 @@ const TECHNIQUES = {
415
415
  },
416
416
 
417
417
  preToolUseHook: {
418
- id: 88,
418
+ id: 8802,
419
419
  name: 'PreToolUse hook configured',
420
420
  check: (ctx) => {
421
421
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
@@ -430,7 +430,7 @@ const TECHNIQUES = {
430
430
  },
431
431
 
432
432
  postToolUseHook: {
433
- id: 88,
433
+ id: 8803,
434
434
  name: 'PostToolUse hook configured',
435
435
  check: (ctx) => {
436
436
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
@@ -445,7 +445,7 @@ const TECHNIQUES = {
445
445
  },
446
446
 
447
447
  sessionStartHook: {
448
- id: 88,
448
+ id: 8804,
449
449
  name: 'SessionStart hook configured',
450
450
  check: (ctx) => {
451
451
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
@@ -478,7 +478,7 @@ const TECHNIQUES = {
478
478
  },
479
479
 
480
480
  tailwindMention: {
481
- id: 1025,
481
+ id: 102501,
482
482
  name: 'Tailwind CSS configured',
483
483
  check: (ctx) => {
484
484
  const pkg = ctx.fileContent('package.json') || '';
@@ -508,7 +508,7 @@ const TECHNIQUES = {
508
508
  },
509
509
 
510
510
  dockerCompose: {
511
- id: 399,
511
+ id: 39901,
512
512
  name: 'Has docker-compose.yml',
513
513
  check: (ctx) => ctx.files.some(f => /^docker-compose\.(yml|yaml)$/i.test(f)),
514
514
  impact: 'medium',
@@ -589,7 +589,7 @@ const TECHNIQUES = {
589
589
  },
590
590
 
591
591
  editorconfig: {
592
- id: 0,
592
+ id: 5001,
593
593
  name: 'Has .editorconfig',
594
594
  check: (ctx) => ctx.files.includes('.editorconfig'),
595
595
  impact: 'low',
@@ -600,7 +600,7 @@ const TECHNIQUES = {
600
600
  },
601
601
 
602
602
  nvmrc: {
603
- id: 0,
603
+ id: 5002,
604
604
  name: 'Node version pinned',
605
605
  check: (ctx) => {
606
606
  if (ctx.files.includes('.nvmrc') || ctx.files.includes('.node-version')) return true;
@@ -665,7 +665,7 @@ const TECHNIQUES = {
665
665
  },
666
666
 
667
667
  multipleMcpServers: {
668
- id: 18,
668
+ id: 1801,
669
669
  name: '2+ MCP servers for rich tooling',
670
670
  check: (ctx) => {
671
671
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
@@ -746,7 +746,7 @@ const TECHNIQUES = {
746
746
  },
747
747
 
748
748
  constraintBlocks: {
749
- id: 96,
749
+ id: 9601,
750
750
  name: 'XML constraint blocks in CLAUDE.md',
751
751
  check: (ctx) => {
752
752
  const md = ctx.fileContent('CLAUDE.md') || '';
@@ -840,7 +840,7 @@ const TECHNIQUES = {
840
840
  name: 'Hooks use specific matchers (not catch-all)',
841
841
  check: (ctx) => {
842
842
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
843
- if (!settings || !settings.hooks) return true; // no hooks = skip
843
+ if (!settings || !settings.hooks) return null; // no hooks = not applicable
844
844
  const hookStr = JSON.stringify(settings.hooks);
845
845
  // Check that hooks have matchers, not just catch-all
846
846
  return hookStr.includes('matcher');
@@ -852,28 +852,15 @@ const TECHNIQUES = {
852
852
  template: null
853
853
  },
854
854
 
855
- permissionsNotBypassed: {
856
- id: 2005,
857
- name: 'Default mode is not bypassPermissions',
858
- check: (ctx) => {
859
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
860
- if (!settings || !settings.permissions) return true;
861
- return settings.permissions.defaultMode !== 'bypassPermissions';
862
- },
863
- impact: 'high',
864
- rating: 4,
865
- category: 'quality-deep',
866
- fix: 'bypassPermissions skips all safety checks. Use "default" or "auto" mode with targeted allow rules instead.',
867
- template: null
868
- },
855
+ // permissionsNotBypassed removed - duplicate of noBypassPermissions (#24)
869
856
 
870
857
  commandsUseArguments: {
871
858
  id: 2006,
872
859
  name: 'Commands use $ARGUMENTS for flexibility',
873
860
  check: (ctx) => {
874
- if (!ctx.hasDir('.claude/commands')) return true;
861
+ if (!ctx.hasDir('.claude/commands')) return null; // not applicable
875
862
  const files = ctx.dirFiles('.claude/commands');
876
- if (files.length === 0) return true;
863
+ if (files.length === 0) return null;
877
864
  // Check if at least one command uses $ARGUMENTS
878
865
  for (const f of files) {
879
866
  const content = ctx.fileContent(`.claude/commands/${f}`) || '';
@@ -892,9 +879,9 @@ const TECHNIQUES = {
892
879
  id: 2007,
893
880
  name: 'Agents have maxTurns limit',
894
881
  check: (ctx) => {
895
- if (!ctx.hasDir('.claude/agents')) return true;
882
+ if (!ctx.hasDir('.claude/agents')) return null;
896
883
  const files = ctx.dirFiles('.claude/agents');
897
- if (files.length === 0) return true;
884
+ if (files.length === 0) return null;
898
885
  for (const f of files) {
899
886
  const content = ctx.fileContent(`.claude/agents/${f}`) || '';
900
887
  if (!content.includes('maxTurns')) return false;