claudex-setup 0.5.1 → 1.0.1
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 +3 -3
- package/bin/cli.js +3 -3
- package/package.json +1 -1
- package/src/deep-review.js +16 -8
- package/src/insights.js +4 -8
- package/src/interactive.js +1 -1
- package/src/setup.js +131 -39
- package/src/techniques.js +14 -6
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ No install. No config. No dependencies.
|
|
|
67
67
|
|------|--------|
|
|
68
68
|
| `--verbose` | Show all recommendations (not just critical/high) |
|
|
69
69
|
| `--json` | Machine-readable JSON output (for CI) |
|
|
70
|
-
| `--
|
|
70
|
+
| `--insights` | Enable anonymous usage insights (off by default) |
|
|
71
71
|
|
|
72
72
|
## Smart CLAUDE.md Generation
|
|
73
73
|
|
|
@@ -76,7 +76,7 @@ Not a generic template. The `setup` command actually analyzes your project:
|
|
|
76
76
|
- **Reads package.json** - includes your actual test, build, lint, dev commands
|
|
77
77
|
- **Detects framework** - Next.js Server Components, Django models, FastAPI Pydantic, React hooks
|
|
78
78
|
- **TypeScript-aware** - detects strict mode, adds TS-specific rules
|
|
79
|
-
- **Auto Mermaid diagram** - scans directories and generates architecture visualization (
|
|
79
|
+
- **Auto Mermaid diagram** - scans directories and generates architecture visualization (Mermaid diagrams are more token-efficient than prose descriptions, per Anthropic docs)
|
|
80
80
|
- **XML constraint blocks** - adds `<constraints>` and `<verification>` with context-aware rules
|
|
81
81
|
- **Verification criteria** - auto-generates checklist from your actual commands
|
|
82
82
|
|
|
@@ -172,7 +172,7 @@ These checks evaluate **quality**, not just existence. A well-configured project
|
|
|
172
172
|
|
|
173
173
|
- **Zero dependencies** - nothing to audit
|
|
174
174
|
- **Runs 100% locally** - no cloud processing
|
|
175
|
-
- **Anonymous insights** - opt-in, no PII, no file contents (
|
|
175
|
+
- **Anonymous insights** - opt-in, no PII, no file contents (enable with `--insights`)
|
|
176
176
|
- **MIT Licensed** - use anywhere
|
|
177
177
|
|
|
178
178
|
## Backed by Research
|
package/bin/cli.js
CHANGED
|
@@ -11,14 +11,14 @@ const flags = args.filter(a => a.startsWith('--'));
|
|
|
11
11
|
const HELP = `
|
|
12
12
|
claudex-setup v${version}
|
|
13
13
|
Audit and optimize any project for Claude Code.
|
|
14
|
-
|
|
14
|
+
Backed by research from 1,107 cataloged Claude Code entries.
|
|
15
15
|
|
|
16
16
|
Usage:
|
|
17
17
|
npx claudex-setup Run audit on current directory
|
|
18
18
|
npx claudex-setup audit Same as above
|
|
19
19
|
npx claudex-setup setup Apply recommended configuration
|
|
20
20
|
npx claudex-setup setup --auto Apply all without prompts
|
|
21
|
-
npx claudex-setup deep-review AI-powered config
|
|
21
|
+
npx claudex-setup deep-review AI-powered config review (uses Claude Code or API key)
|
|
22
22
|
npx claudex-setup interactive Step-by-step guided wizard
|
|
23
23
|
npx claudex-setup watch Monitor changes and re-audit live
|
|
24
24
|
npx claudex-setup badge Generate shields.io badge markdown
|
|
@@ -26,7 +26,7 @@ const HELP = `
|
|
|
26
26
|
Options:
|
|
27
27
|
--verbose Show all recommendations (not just critical/high)
|
|
28
28
|
--json Output as JSON (for CI pipelines)
|
|
29
|
-
--
|
|
29
|
+
--insights Enable anonymous usage insights (off by default)
|
|
30
30
|
--help Show this help
|
|
31
31
|
--version Show version
|
|
32
32
|
`;
|
package/package.json
CHANGED
package/src/deep-review.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const https = require('https');
|
|
10
|
+
const path = require('path');
|
|
10
11
|
const { ProjectContext } = require('./context');
|
|
11
12
|
const { STACKS } = require('./techniques');
|
|
12
13
|
|
|
@@ -198,14 +199,21 @@ function hasClaudeCode() {
|
|
|
198
199
|
|
|
199
200
|
async function callClaudeCode(prompt) {
|
|
200
201
|
const { execSync } = require('child_process');
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
const os = require('os');
|
|
203
|
+
const fs = require('fs');
|
|
204
|
+
const tmpFile = path.join(os.tmpdir(), `claudex-review-${Date.now()}.txt`);
|
|
205
|
+
fs.writeFileSync(tmpFile, prompt, 'utf8');
|
|
206
|
+
try {
|
|
207
|
+
const result = execSync(`claude -p --output-format text < "${tmpFile}"`, {
|
|
208
|
+
encoding: 'utf8',
|
|
209
|
+
maxBuffer: 1024 * 1024,
|
|
210
|
+
timeout: 120000,
|
|
211
|
+
shell: true,
|
|
212
|
+
});
|
|
213
|
+
return result;
|
|
214
|
+
} finally {
|
|
215
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
216
|
+
}
|
|
209
217
|
}
|
|
210
218
|
|
|
211
219
|
async function deepReview(options) {
|
package/src/insights.js
CHANGED
|
@@ -25,14 +25,10 @@ const INSIGHTS_ENDPOINT = 'https://claudex-insights.claudex.workers.dev/v1/repor
|
|
|
25
25
|
const TIMEOUT_MS = 3000;
|
|
26
26
|
|
|
27
27
|
function shouldCollect() {
|
|
28
|
-
//
|
|
29
|
-
if (process.env.
|
|
30
|
-
if (process.argv.includes('--
|
|
31
|
-
|
|
32
|
-
// Don't collect in CI
|
|
33
|
-
if (process.env.CI || process.env.GITHUB_ACTIONS) return false;
|
|
34
|
-
|
|
35
|
-
return true;
|
|
28
|
+
// Opt-IN: only collect if user explicitly enables
|
|
29
|
+
if (process.env.CLAUDEX_INSIGHTS === '1') return true;
|
|
30
|
+
if (process.argv.includes('--insights')) return true;
|
|
31
|
+
return false;
|
|
36
32
|
}
|
|
37
33
|
|
|
38
34
|
function buildPayload(auditResult) {
|
package/src/interactive.js
CHANGED
|
@@ -102,7 +102,7 @@ async function interactive(options) {
|
|
|
102
102
|
console.log('');
|
|
103
103
|
|
|
104
104
|
// Run setup in auto mode
|
|
105
|
-
await setup({ ...options, auto: true });
|
|
105
|
+
await setup({ ...options, auto: true, only: toFix });
|
|
106
106
|
|
|
107
107
|
console.log('');
|
|
108
108
|
console.log(c(' Done! Run `npx claudex-setup` to see your new score.', 'green'));
|
package/src/setup.js
CHANGED
|
@@ -29,12 +29,20 @@ function detectScripts(ctx) {
|
|
|
29
29
|
// Helper: detect main directories
|
|
30
30
|
// ============================================================
|
|
31
31
|
function detectMainDirs(ctx) {
|
|
32
|
-
const candidates = ['src', 'lib', 'app', 'pages', 'components', 'api', 'routes', 'utils', 'helpers', 'services', 'models', 'controllers', 'views', 'public', 'assets', 'config', 'tests', 'test', '__tests__', 'spec', 'scripts', 'prisma', 'db', 'middleware'];
|
|
32
|
+
const candidates = ['src', 'lib', 'app', 'pages', 'components', 'api', 'routes', 'utils', 'helpers', 'services', 'models', 'controllers', 'views', 'public', 'assets', 'config', 'tests', 'test', '__tests__', 'spec', 'scripts', 'prisma', 'db', 'middleware', 'hooks'];
|
|
33
|
+
// Also check inside src/ for nested structure (common in Next.js, React)
|
|
34
|
+
const srcNested = ['src/components', 'src/app', 'src/pages', 'src/api', 'src/lib', 'src/hooks', 'src/utils', 'src/services', 'src/models', 'src/middleware', 'src/app/api', 'app/api'];
|
|
33
35
|
const found = [];
|
|
34
|
-
|
|
36
|
+
const seenNames = new Set();
|
|
37
|
+
|
|
38
|
+
for (const dir of [...candidates, ...srcNested]) {
|
|
35
39
|
if (ctx.hasDir(dir)) {
|
|
36
40
|
const files = ctx.dirFiles(dir);
|
|
37
|
-
|
|
41
|
+
const displayName = dir.includes('/') ? dir : dir;
|
|
42
|
+
if (!seenNames.has(displayName)) {
|
|
43
|
+
found.push({ name: displayName, fileCount: files.length, files: files.slice(0, 10) });
|
|
44
|
+
seenNames.add(displayName);
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
48
|
return found;
|
|
@@ -61,31 +69,63 @@ function generateMermaid(dirs, stacks) {
|
|
|
61
69
|
return ` ${id}[${label}]`;
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
//
|
|
65
|
-
|
|
72
|
+
// Detect Next.js App Router specifically
|
|
73
|
+
const hasAppRouter = dirNames.includes('app') || dirNames.includes('src/app');
|
|
74
|
+
const hasPages = dirNames.includes('pages') || dirNames.includes('src/pages');
|
|
75
|
+
const hasAppApi = dirNames.includes('app/api') || dirNames.includes('src/app/api');
|
|
76
|
+
const hasSrcComponents = dirNames.includes('src/components') || dirNames.includes('components');
|
|
77
|
+
const hasSrcHooks = dirNames.includes('src/hooks') || dirNames.includes('hooks');
|
|
78
|
+
const hasSrcLib = dirNames.includes('src/lib') || dirNames.includes('lib');
|
|
79
|
+
|
|
80
|
+
// Smart entry point based on framework
|
|
81
|
+
const isNextJs = stackKeys.includes('nextjs');
|
|
82
|
+
const isDjango = stackKeys.includes('django');
|
|
83
|
+
const isFastApi = stackKeys.includes('fastapi');
|
|
84
|
+
|
|
85
|
+
if (isNextJs) {
|
|
86
|
+
nodes.push(addNode('Next.js', 'round'));
|
|
87
|
+
} else if (isDjango) {
|
|
88
|
+
nodes.push(addNode('Django', 'round'));
|
|
89
|
+
} else if (isFastApi) {
|
|
90
|
+
nodes.push(addNode('FastAPI', 'round'));
|
|
91
|
+
} else {
|
|
92
|
+
nodes.push(addNode('Entry Point', 'round'));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const root = ids['Next.js'] || ids['Django'] || ids['FastAPI'] || ids['Entry Point'];
|
|
66
96
|
|
|
67
97
|
// Detect layers
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
if (hasAppRouter || hasPages) {
|
|
99
|
+
const label = hasAppRouter ? 'App Router' : 'Pages';
|
|
100
|
+
nodes.push(addNode(label, 'default'));
|
|
101
|
+
edges.push(` ${root} --> ${ids[label]}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (hasAppApi) {
|
|
105
|
+
nodes.push(addNode('API Routes', 'default'));
|
|
106
|
+
const parent = ids['App Router'] || ids['Pages'] || root;
|
|
107
|
+
edges.push(` ${parent} --> ${ids['API Routes']}`);
|
|
71
108
|
}
|
|
72
109
|
|
|
73
|
-
if (
|
|
110
|
+
if (hasSrcComponents) {
|
|
74
111
|
nodes.push(addNode('Components', 'default'));
|
|
75
|
-
const parent = ids['
|
|
112
|
+
const parent = ids['App Router'] || ids['Pages'] || root;
|
|
76
113
|
edges.push(` ${parent} --> ${ids['Components']}`);
|
|
77
114
|
}
|
|
78
115
|
|
|
79
|
-
if (
|
|
80
|
-
nodes.push(addNode('
|
|
81
|
-
const parent = ids['
|
|
82
|
-
edges.push(` ${parent} --> ${ids['
|
|
116
|
+
if (hasSrcHooks) {
|
|
117
|
+
nodes.push(addNode('Hooks', 'default'));
|
|
118
|
+
const parent = ids['Components'] || root;
|
|
119
|
+
edges.push(` ${parent} --> ${ids['Hooks']}`);
|
|
83
120
|
}
|
|
84
121
|
|
|
85
|
-
if (
|
|
122
|
+
if (hasSrcLib) {
|
|
86
123
|
nodes.push(addNode('lib/', 'default'));
|
|
87
|
-
const parent = ids['
|
|
124
|
+
const parent = ids['API Routes'] || ids['Hooks'] || ids['Components'] || root;
|
|
88
125
|
edges.push(` ${parent} --> ${ids['lib/']}`);
|
|
126
|
+
} else if (dirNames.includes('src') && !hasAppRouter && !hasPages) {
|
|
127
|
+
nodes.push(addNode('src/', 'default'));
|
|
128
|
+
edges.push(` ${root} --> ${ids['src/']}`);
|
|
89
129
|
}
|
|
90
130
|
|
|
91
131
|
if (dirNames.includes('api') || dirNames.includes('routes') || dirNames.includes('controllers')) {
|
|
@@ -379,45 +419,46 @@ ${verificationSteps.join('\n')}
|
|
|
379
419
|
- Use descriptive commit messages (why, not what)
|
|
380
420
|
- Create focused PRs — one concern per PR
|
|
381
421
|
- Document non-obvious decisions in code comments
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
*Generated by [claudex-setup](https://github.com/DnaFin/claudex-setup) v${require('../package.json').version} on ${new Date().toISOString().split('T')[0]}. Customize this file for your project — a hand-crafted CLAUDE.md will always be better than a generated one.*
|
|
382
425
|
`;
|
|
383
426
|
},
|
|
384
427
|
|
|
385
428
|
'hooks': () => ({
|
|
386
429
|
'on-edit-lint.sh': `#!/bin/bash
|
|
387
|
-
# PostToolUse hook -
|
|
388
|
-
#
|
|
389
|
-
|
|
390
|
-
|
|
430
|
+
# PostToolUse hook - runs linter after file edits
|
|
431
|
+
# Detects which linter is available and runs it
|
|
432
|
+
|
|
433
|
+
if command -v npx &>/dev/null; then
|
|
434
|
+
if [ -f "package.json" ] && grep -q '"lint"' package.json 2>/dev/null; then
|
|
435
|
+
npm run lint --silent 2>/dev/null
|
|
436
|
+
elif [ -f ".eslintrc" ] || [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ] || [ -f "eslint.config.js" ]; then
|
|
437
|
+
npx eslint --fix . --quiet 2>/dev/null
|
|
438
|
+
fi
|
|
439
|
+
elif command -v ruff &>/dev/null; then
|
|
440
|
+
ruff check --fix . 2>/dev/null
|
|
441
|
+
fi
|
|
391
442
|
`,
|
|
392
443
|
'protect-secrets.sh': `#!/bin/bash
|
|
393
|
-
# PreToolUse hook -
|
|
394
|
-
# Prevents accidental reads/writes to files containing secrets
|
|
395
|
-
|
|
444
|
+
# PreToolUse hook - blocks reads of secret files
|
|
396
445
|
INPUT=$(cat -)
|
|
397
|
-
|
|
446
|
+
FILE_PATH=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')
|
|
398
447
|
|
|
399
|
-
if
|
|
448
|
+
if echo "$FILE_PATH" | grep -qiE '\\.env$|\\.env\\.|secrets/|credentials|\\.pem$|\\.key$'; then
|
|
449
|
+
echo '{"decision": "block", "reason": "Blocked: accessing secret/credential files is not allowed."}'
|
|
400
450
|
exit 0
|
|
401
451
|
fi
|
|
402
|
-
|
|
403
|
-
BASENAME=$(basename "$FILE")
|
|
404
|
-
|
|
405
|
-
case "$BASENAME" in
|
|
406
|
-
.env|.env.*|*.pem|*.key|credentials.json|secrets.yaml|secrets.yml)
|
|
407
|
-
echo "WARN: Attempting to access sensitive file: $BASENAME"
|
|
408
|
-
echo "This file may contain secrets. Proceed with caution."
|
|
409
|
-
;;
|
|
410
|
-
esac
|
|
411
|
-
|
|
412
|
-
exit 0
|
|
452
|
+
echo '{"decision": "allow"}'
|
|
413
453
|
`,
|
|
414
454
|
'log-changes.sh': `#!/bin/bash
|
|
415
455
|
# PostToolUse hook - logs all file changes with timestamps
|
|
416
456
|
# Appends to .claude/logs/file-changes.log
|
|
417
457
|
|
|
418
458
|
INPUT=$(cat -)
|
|
419
|
-
TOOL_NAME=$(echo "$INPUT" |
|
|
420
|
-
|
|
459
|
+
TOOL_NAME=$(echo "$INPUT" | sed -n 's/.*"tool_name"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')
|
|
460
|
+
TOOL_NAME=\${TOOL_NAME:-unknown}
|
|
461
|
+
FILE_PATH=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\\([^"]*\\)".*/\\1/p')
|
|
421
462
|
|
|
422
463
|
if [ -z "$FILE_PATH" ]; then
|
|
423
464
|
exit 0
|
|
@@ -571,9 +612,19 @@ async function setup(options) {
|
|
|
571
612
|
let created = 0;
|
|
572
613
|
let skipped = 0;
|
|
573
614
|
|
|
615
|
+
let failedWithTemplates = [];
|
|
574
616
|
for (const [key, technique] of Object.entries(TECHNIQUES)) {
|
|
575
617
|
if (technique.passed || technique.check(ctx)) continue;
|
|
576
618
|
if (!technique.template) continue;
|
|
619
|
+
failedWithTemplates.push({ key, technique });
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Filter by 'only' list if provided (interactive wizard selections)
|
|
623
|
+
if (options.only && options.only.length > 0) {
|
|
624
|
+
failedWithTemplates = failedWithTemplates.filter(r => options.only.includes(r.key));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
for (const { key, technique } of failedWithTemplates) {
|
|
577
628
|
|
|
578
629
|
const template = TEMPLATES[technique.template];
|
|
579
630
|
if (!template) continue;
|
|
@@ -583,7 +634,13 @@ async function setup(options) {
|
|
|
583
634
|
|
|
584
635
|
if (typeof result === 'string') {
|
|
585
636
|
// Single file template (like CLAUDE.md)
|
|
586
|
-
|
|
637
|
+
// Map technique keys to actual file paths
|
|
638
|
+
const filePathMap = {
|
|
639
|
+
'claudeMd': 'CLAUDE.md',
|
|
640
|
+
'mermaidArchitecture': 'CLAUDE.md', // mermaid is part of CLAUDE.md, skip separate file
|
|
641
|
+
};
|
|
642
|
+
if (key === 'mermaidArchitecture') continue; // Mermaid is generated inside CLAUDE.md template
|
|
643
|
+
const filePath = filePathMap[key] || key;
|
|
587
644
|
const fullPath = path.join(options.dir, filePath);
|
|
588
645
|
|
|
589
646
|
if (!fs.existsSync(fullPath)) {
|
|
@@ -627,6 +684,41 @@ async function setup(options) {
|
|
|
627
684
|
}
|
|
628
685
|
}
|
|
629
686
|
|
|
687
|
+
// Auto-register hooks in settings if hooks were created but no settings exist
|
|
688
|
+
const hooksDir = path.join(options.dir, '.claude/hooks');
|
|
689
|
+
const settingsPath = path.join(options.dir, '.claude/settings.json');
|
|
690
|
+
if (fs.existsSync(hooksDir) && !fs.existsSync(settingsPath)) {
|
|
691
|
+
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
|
|
692
|
+
if (hookFiles.length > 0) {
|
|
693
|
+
const settings = {
|
|
694
|
+
hooks: {
|
|
695
|
+
PostToolUse: [{
|
|
696
|
+
matcher: "Write|Edit",
|
|
697
|
+
hooks: hookFiles.filter(f => f !== 'protect-secrets.sh').map(f => ({
|
|
698
|
+
type: "command",
|
|
699
|
+
command: `bash .claude/hooks/${f}`,
|
|
700
|
+
timeout: 10
|
|
701
|
+
}))
|
|
702
|
+
}]
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
// Add protect-secrets as PreToolUse if it exists
|
|
706
|
+
if (hookFiles.includes('protect-secrets.sh')) {
|
|
707
|
+
settings.hooks.PreToolUse = [{
|
|
708
|
+
matcher: "Read|Write|Edit",
|
|
709
|
+
hooks: [{
|
|
710
|
+
type: "command",
|
|
711
|
+
command: "bash .claude/hooks/protect-secrets.sh",
|
|
712
|
+
timeout: 5
|
|
713
|
+
}]
|
|
714
|
+
}];
|
|
715
|
+
}
|
|
716
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
717
|
+
console.log(` \x1b[32m✅\x1b[0m Created .claude/settings.json (hooks registered)`);
|
|
718
|
+
created++;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
630
722
|
console.log('');
|
|
631
723
|
if (created === 0 && skipped > 0) {
|
|
632
724
|
console.log(' \x1b[32m✅\x1b[0m Your project is already well configured!');
|
package/src/techniques.js
CHANGED
|
@@ -766,11 +766,16 @@ const TECHNIQUES = {
|
|
|
766
766
|
channelsAwareness: {
|
|
767
767
|
id: 1102,
|
|
768
768
|
name: 'Claude Code Channels awareness',
|
|
769
|
-
check: () =>
|
|
769
|
+
check: (ctx) => {
|
|
770
|
+
const md = ctx.fileContent('CLAUDE.md') || '';
|
|
771
|
+
const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
|
|
772
|
+
const settingsStr = JSON.stringify(settings || {});
|
|
773
|
+
return md.toLowerCase().includes('channel') || settingsStr.includes('channel');
|
|
774
|
+
},
|
|
770
775
|
impact: 'low',
|
|
771
|
-
rating:
|
|
776
|
+
rating: 3,
|
|
772
777
|
category: 'features',
|
|
773
|
-
fix: 'Claude Code Channels (v2.1.80+)
|
|
778
|
+
fix: 'Claude Code Channels (v2.1.80+) bridges Telegram/Discord/iMessage to your session.',
|
|
774
779
|
template: null
|
|
775
780
|
},
|
|
776
781
|
|
|
@@ -801,7 +806,8 @@ const TECHNIQUES = {
|
|
|
801
806
|
id: 2002,
|
|
802
807
|
name: 'CLAUDE.md is concise (under 200 lines)',
|
|
803
808
|
check: (ctx) => {
|
|
804
|
-
const md = ctx.fileContent('CLAUDE.md')
|
|
809
|
+
const md = ctx.fileContent('CLAUDE.md');
|
|
810
|
+
if (!md) return false; // no CLAUDE.md = not passing
|
|
805
811
|
return md.split('\n').length <= 200;
|
|
806
812
|
},
|
|
807
813
|
impact: 'medium',
|
|
@@ -815,7 +821,8 @@ const TECHNIQUES = {
|
|
|
815
821
|
id: 2003,
|
|
816
822
|
name: 'CLAUDE.md has no obvious contradictions',
|
|
817
823
|
check: (ctx) => {
|
|
818
|
-
const md = ctx.fileContent('CLAUDE.md')
|
|
824
|
+
const md = ctx.fileContent('CLAUDE.md');
|
|
825
|
+
if (!md || md.length < 50) return false; // no CLAUDE.md or too short = not passing
|
|
819
826
|
// Check for common contradictions
|
|
820
827
|
const hasNever = /never.*always|always.*never/i.test(md);
|
|
821
828
|
const hasBothStyles = /use tabs/i.test(md) && /use spaces/i.test(md);
|
|
@@ -921,7 +928,8 @@ const TECHNIQUES = {
|
|
|
921
928
|
id: 2009,
|
|
922
929
|
name: 'No deprecated patterns detected',
|
|
923
930
|
check: (ctx) => {
|
|
924
|
-
const md = ctx.fileContent('CLAUDE.md')
|
|
931
|
+
const md = ctx.fileContent('CLAUDE.md');
|
|
932
|
+
if (!md) return false; // no CLAUDE.md = not passing
|
|
925
933
|
// Check for patterns deprecated in Claude 4.x
|
|
926
934
|
const deprecated = [
|
|
927
935
|
'prefill', // deprecated in 4.6
|