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 +1 -1
- package/src/audit.js +27 -6
- package/src/setup.js +210 -22
- package/src/techniques.js +30 -43
package/package.json
CHANGED
package/src/audit.js
CHANGED
|
@@ -61,17 +61,35 @@ async function audit(options) {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
const
|
|
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 =
|
|
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}/${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
500
|
-
4.
|
|
501
|
-
5.
|
|
502
|
-
|
|
503
|
-
## Deploy
|
|
504
|
-
1. Confirm target environment
|
|
505
|
-
2.
|
|
506
|
-
3.
|
|
507
|
-
4.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
861
|
+
if (!ctx.hasDir('.claude/commands')) return null; // not applicable
|
|
875
862
|
const files = ctx.dirFiles('.claude/commands');
|
|
876
|
-
if (files.length === 0) return
|
|
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
|
|
882
|
+
if (!ctx.hasDir('.claude/agents')) return null;
|
|
896
883
|
const files = ctx.dirFiles('.claude/agents');
|
|
897
|
-
if (files.length === 0) return
|
|
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;
|