@nerviq/cli 1.0.0 → 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.
Files changed (48) hide show
  1. package/bin/cli.js +170 -73
  2. package/package.json +3 -5
  3. package/src/activity.js +20 -0
  4. package/src/aider/domain-packs.js +27 -2
  5. package/src/aider/mcp-packs.js +231 -0
  6. package/src/aider/techniques.js +3210 -1397
  7. package/src/audit.js +290 -9
  8. package/src/catalog.js +18 -2
  9. package/src/codex/domain-packs.js +23 -1
  10. package/src/codex/mcp-packs.js +254 -0
  11. package/src/codex/techniques.js +4738 -3257
  12. package/src/copilot/domain-packs.js +23 -1
  13. package/src/copilot/mcp-packs.js +254 -0
  14. package/src/copilot/techniques.js +3433 -1936
  15. package/src/cursor/domain-packs.js +23 -1
  16. package/src/cursor/mcp-packs.js +257 -0
  17. package/src/cursor/techniques.js +3697 -1869
  18. package/src/deprecation.js +98 -0
  19. package/src/domain-pack-expansion.js +571 -0
  20. package/src/domain-packs.js +25 -2
  21. package/src/formatters/otel.js +151 -0
  22. package/src/gemini/domain-packs.js +23 -1
  23. package/src/gemini/mcp-packs.js +257 -0
  24. package/src/gemini/techniques.js +3734 -2238
  25. package/src/integrations.js +194 -0
  26. package/src/mcp-packs.js +233 -0
  27. package/src/opencode/domain-packs.js +23 -1
  28. package/src/opencode/mcp-packs.js +231 -0
  29. package/src/opencode/techniques.js +3500 -1687
  30. package/src/org.js +68 -0
  31. package/src/source-urls.js +410 -260
  32. package/src/stack-checks.js +565 -0
  33. package/src/supplemental-checks.js +767 -0
  34. package/src/techniques.js +2929 -1449
  35. package/src/telemetry.js +160 -0
  36. package/src/windsurf/domain-packs.js +23 -1
  37. package/src/windsurf/mcp-packs.js +257 -0
  38. package/src/windsurf/techniques.js +3647 -1834
  39. package/src/workspace.js +233 -0
  40. package/CHANGELOG.md +0 -198
  41. package/content/case-study-template.md +0 -91
  42. package/content/claims-governance.md +0 -37
  43. package/content/claude-code/audit-repo/SKILL.md +0 -20
  44. package/content/claude-native-integration.md +0 -60
  45. package/content/devto-article.json +0 -9
  46. package/content/launch-posts.md +0 -226
  47. package/content/pilot-rollout-kit.md +0 -30
  48. package/content/release-checklist.md +0 -31
package/src/techniques.js CHANGED
@@ -1,1449 +1,2929 @@
1
- /**
2
- * CLAUDEX Technique Database
3
- * Curated from 1107 verified techniques, filtered to actionable setup recommendations.
4
- * Each technique includes: what to check, how to fix, impact level.
5
- */
6
-
7
- function hasFrontendSignals(ctx) {
8
- const pkg = ctx.fileContent('package.json') || '';
9
- return /react|vue|angular|next|svelte|tailwind|vite|astro/i.test(pkg) ||
10
- ctx.files.some(f => /tailwind\.config|vite\.config|next\.config|svelte\.config|nuxt\.config|pages\/|components\/|app\//i.test(f));
11
- }
12
-
13
- const { containsEmbeddedSecret } = require('./secret-patterns');
14
- const { attachSourceUrls } = require('./source-urls');
15
-
16
- const TECHNIQUES = {
17
- // ============================================================
18
- // === MEMORY & CONTEXT (category: 'memory') ==================
19
- // ============================================================
20
-
21
- claudeMd: {
22
- id: 1,
23
- name: 'CLAUDE.md project instructions',
24
- check: (ctx) => ctx.files.includes('CLAUDE.md') || ctx.files.includes('.claude/CLAUDE.md'),
25
- impact: 'critical',
26
- rating: 5,
27
- category: 'memory',
28
- fix: 'Create CLAUDE.md with project-specific instructions, build commands, and coding conventions.',
29
- template: 'claude-md'
30
- },
31
-
32
- mermaidArchitecture: {
33
- id: 51,
34
- name: 'Mermaid architecture diagram',
35
- check: (ctx) => {
36
- const md = ctx.claudeMdContent() || '';
37
- return md.includes('mermaid') || md.includes('graph ') || md.includes('flowchart ');
38
- },
39
- impact: 'high',
40
- rating: 5,
41
- category: 'memory',
42
- fix: 'Add a Mermaid diagram to CLAUDE.md showing project architecture. Saves 73% tokens vs prose.',
43
- template: 'mermaid'
44
- },
45
-
46
- pathRules: {
47
- id: 3,
48
- name: 'Path-specific rules',
49
- check: (ctx) => ctx.hasDir('.claude/rules') && ctx.dirFiles('.claude/rules').length > 0,
50
- impact: 'medium',
51
- rating: 4,
52
- category: 'memory',
53
- fix: 'Add rules for different file types (frontend vs backend conventions).',
54
- template: 'rules'
55
- },
56
-
57
- importSyntax: {
58
- id: 763,
59
- name: 'CLAUDE.md uses @path imports for modularity',
60
- check: (ctx) => {
61
- const md = ctx.claudeMdContent() || '';
62
- // Current syntax is @path/to/file (no "import" keyword)
63
- return /@\S+\.(md|txt|json|yml|yaml|toml)/i.test(md) || /@\w+\//.test(md);
64
- },
65
- impact: 'medium',
66
- rating: 4,
67
- category: 'memory',
68
- fix: 'Use @path syntax in CLAUDE.md to split instructions into focused modules (e.g. @docs/coding-style.md). You can also use .claude/rules/ for path-specific rules.',
69
- template: null
70
- },
71
-
72
- underlines200: {
73
- id: 681,
74
- name: 'CLAUDE.md under 200 lines (concise)',
75
- check: (ctx) => {
76
- const md = ctx.claudeMdContent() || '';
77
- return md.split('\n').length <= 200;
78
- },
79
- impact: 'medium',
80
- rating: 4,
81
- category: 'memory',
82
- fix: 'Keep CLAUDE.md under 200 lines. Use @import or .claude/rules/ to split large instructions.',
83
- template: null
84
- },
85
-
86
- // ============================================================
87
- // === QUALITY & TESTING (category: 'quality') ================
88
- // ============================================================
89
-
90
- verificationLoop: {
91
- id: 93,
92
- name: 'Verification criteria in CLAUDE.md',
93
- check: (ctx) => {
94
- const md = ctx.claudeMdContent() || '';
95
- return /\b(npm test|yarn test|pnpm test|pytest|go test|make test|npm run lint|yarn lint|npx |ruff |eslint)\b/i.test(md) ||
96
- /\b(test command|lint command|build command|verify|run tests|run lint)\b/i.test(md);
97
- },
98
- impact: 'critical',
99
- rating: 5,
100
- category: 'quality',
101
- fix: 'Add test/lint/build commands to CLAUDE.md so Claude can verify its own work.',
102
- template: null
103
- },
104
-
105
- testCommand: {
106
- id: 93001,
107
- name: 'CLAUDE.md contains a test command',
108
- check: (ctx) => {
109
- const md = ctx.claudeMdContent() || '';
110
- return /npm test|pytest|jest|vitest|cargo test|go test|mix test|rspec/.test(md);
111
- },
112
- impact: 'high',
113
- rating: 5,
114
- category: 'quality',
115
- fix: 'Add an explicit test command to CLAUDE.md (e.g. "Run `npm test` before committing").',
116
- template: null
117
- },
118
-
119
- lintCommand: {
120
- id: 93002,
121
- name: 'CLAUDE.md contains a lint command',
122
- check: (ctx) => {
123
- const md = ctx.claudeMdContent() || '';
124
- return /eslint|prettier|ruff|black|clippy|golangci-lint|rubocop|npm run lint|yarn lint|pnpm lint|bun lint/.test(md);
125
- },
126
- impact: 'high',
127
- rating: 4,
128
- category: 'quality',
129
- fix: 'Add a lint command to CLAUDE.md so Claude auto-formats and checks code style.',
130
- template: null
131
- },
132
-
133
- buildCommand: {
134
- id: 93003,
135
- name: 'CLAUDE.md contains a build command',
136
- check: (ctx) => {
137
- const md = ctx.claudeMdContent() || '';
138
- return /npm run build|cargo build|go build|make|tsc|gradle build|mvn compile/.test(md);
139
- },
140
- impact: 'medium',
141
- rating: 4,
142
- category: 'quality',
143
- fix: 'Add a build command to CLAUDE.md so Claude can verify compilation before committing.',
144
- template: null
145
- },
146
-
147
- // ============================================================
148
- // === GIT SAFETY (category: 'git') ===========================
149
- // ============================================================
150
-
151
- gitIgnoreClaudeTracked: {
152
- id: 976,
153
- name: '.claude/ tracked in git',
154
- check: (ctx) => {
155
- if (!ctx.fileContent('.gitignore')) return true; // no gitignore = ok
156
- const lines = ctx.fileContent('.gitignore')
157
- .split(/\r?\n/)
158
- .map(line => line.trim())
159
- .filter(line => line && !line.startsWith('#'));
160
- const ignoresClaudeDir = lines.some(line => /^(\/|\*\*\/)?\.claude\/?$/.test(line));
161
- const unignoresClaudeDir = lines.some(line => /^!(\/)?\.claude(\/|\*\*)?$/.test(line));
162
- return !ignoresClaudeDir || unignoresClaudeDir;
163
- },
164
- impact: 'high',
165
- rating: 4,
166
- category: 'git',
167
- fix: 'Remove .claude/ from .gitignore (keep .claude/settings.local.json ignored).',
168
- template: null
169
- },
170
-
171
- gitIgnoreEnv: {
172
- id: 917,
173
- name: '.gitignore blocks .env files',
174
- check: (ctx) => {
175
- const gitignore = ctx.fileContent('.gitignore') || '';
176
- return gitignore.includes('.env');
177
- },
178
- impact: 'critical',
179
- rating: 5,
180
- category: 'git',
181
- fix: 'Add .env to .gitignore to prevent leaking secrets.',
182
- template: null
183
- },
184
-
185
- gitIgnoreNodeModules: {
186
- id: 91701,
187
- name: '.gitignore blocks node_modules',
188
- check: (ctx) => {
189
- const hasNodeSignals = ctx.files.includes('package.json') ||
190
- ctx.files.includes('tsconfig.json') ||
191
- ctx.files.some(f => /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config/i.test(f));
192
- if (!hasNodeSignals) return null;
193
- const gitignore = ctx.fileContent('.gitignore') || '';
194
- return gitignore.includes('node_modules');
195
- },
196
- impact: 'high',
197
- rating: 4,
198
- category: 'git',
199
- fix: 'Add node_modules/ to .gitignore.',
200
- template: null
201
- },
202
-
203
- noSecretsInClaude: {
204
- id: 1039,
205
- name: 'CLAUDE.md has no embedded API keys',
206
- check: (ctx) => {
207
- const md = ctx.claudeMdContent() || '';
208
- return !containsEmbeddedSecret(md);
209
- },
210
- impact: 'critical',
211
- rating: 5,
212
- category: 'git',
213
- fix: 'Remove API keys from CLAUDE.md. Use environment variables or .env files instead.',
214
- template: null
215
- },
216
-
217
- // ============================================================
218
- // === WORKFLOW (category: 'workflow') =========================
219
- // ============================================================
220
-
221
- customCommands: {
222
- id: 20,
223
- name: 'Custom slash commands',
224
- check: (ctx) => ctx.hasDir('.claude/commands') && ctx.dirFiles('.claude/commands').length > 0,
225
- impact: 'high',
226
- rating: 4,
227
- category: 'workflow',
228
- fix: 'Create custom commands for repeated workflows (/test, /deploy, /review).',
229
- template: 'commands'
230
- },
231
-
232
- multipleCommands: {
233
- id: 20001,
234
- name: '3+ slash commands for rich workflow',
235
- check: (ctx) => ctx.hasDir('.claude/commands') && ctx.dirFiles('.claude/commands').length >= 3,
236
- impact: 'medium',
237
- rating: 4,
238
- category: 'workflow',
239
- fix: 'Add at least 3 slash commands to cover your main workflows (test, deploy, review, etc.).',
240
- template: 'commands'
241
- },
242
-
243
- deployCommand: {
244
- id: 20002,
245
- name: 'Has /deploy or /release command',
246
- check: (ctx) => {
247
- if (!ctx.hasDir('.claude/commands')) return false;
248
- const files = ctx.dirFiles('.claude/commands');
249
- return files.some(f => /deploy|release/i.test(f));
250
- },
251
- impact: 'medium',
252
- rating: 4,
253
- category: 'workflow',
254
- fix: 'Create a /deploy or /release command for one-click deployments.',
255
- template: null
256
- },
257
-
258
- reviewCommand: {
259
- id: 20003,
260
- name: 'Has /review command',
261
- check: (ctx) => {
262
- if (!ctx.hasDir('.claude/commands')) return false;
263
- const files = ctx.dirFiles('.claude/commands');
264
- return files.some(f => /review/i.test(f));
265
- },
266
- impact: 'medium',
267
- rating: 4,
268
- category: 'workflow',
269
- fix: 'Create a /review command for code review workflows.',
270
- template: null
271
- },
272
-
273
- skills: {
274
- id: 21,
275
- name: 'Custom skills',
276
- check: (ctx) => {
277
- // Skills use directory-per-skill structure: .claude/skills/<name>/SKILL.md
278
- if (!ctx.hasDir('.claude/skills')) return false;
279
- const dirs = ctx.dirFiles('.claude/skills');
280
- // Check for SKILL.md inside skill directories
281
- for (const d of dirs) {
282
- if (ctx.fileContent(`.claude/skills/${d}/SKILL.md`)) return true;
283
- }
284
- // Fallback: any files in skills dir (legacy .claude/commands/ also works)
285
- return dirs.length > 0;
286
- },
287
- impact: 'medium',
288
- rating: 4,
289
- category: 'workflow',
290
- fix: 'Create skills at .claude/skills/<name>/SKILL.md with YAML frontmatter (name, description). Each skill is a directory with a SKILL.md file.',
291
- template: 'skills'
292
- },
293
-
294
- multipleSkills: {
295
- id: 2101,
296
- name: '2+ skills for specialization',
297
- check: (ctx) => {
298
- if (!ctx.hasDir('.claude/skills')) return false;
299
- return ctx.dirFiles('.claude/skills').length >= 2;
300
- },
301
- impact: 'medium',
302
- rating: 4,
303
- category: 'workflow',
304
- fix: 'Add at least 2 skills covering different workflows (e.g. code-review, test-writer).',
305
- template: 'skills'
306
- },
307
-
308
- agents: {
309
- id: 22,
310
- name: 'Custom agents',
311
- check: (ctx) => ctx.hasDir('.claude/agents') && ctx.dirFiles('.claude/agents').length > 0,
312
- impact: 'medium',
313
- rating: 4,
314
- category: 'workflow',
315
- fix: 'Create specialized agents (security-reviewer, test-writer) in .claude/agents/.',
316
- template: 'agents'
317
- },
318
-
319
- multipleAgents: {
320
- id: 2201,
321
- name: '2+ agents for delegation',
322
- check: (ctx) => ctx.hasDir('.claude/agents') && ctx.dirFiles('.claude/agents').length >= 2,
323
- impact: 'medium',
324
- rating: 4,
325
- category: 'workflow',
326
- fix: 'Add at least 2 agents for specialized tasks (e.g. security-reviewer, test-writer).',
327
- template: 'agents'
328
- },
329
-
330
- multipleRules: {
331
- id: 301,
332
- name: '2+ rules files for granular control',
333
- check: (ctx) => ctx.hasDir('.claude/rules') && ctx.dirFiles('.claude/rules').length >= 2,
334
- impact: 'medium',
335
- rating: 4,
336
- category: 'workflow',
337
- fix: 'Add path-specific rules for different parts of the codebase (frontend, backend, tests).',
338
- template: 'rules'
339
- },
340
-
341
- // ============================================================
342
- // === SECURITY (category: 'security') ========================
343
- // ============================================================
344
-
345
- settingsPermissions: {
346
- id: 24,
347
- name: 'Permission configuration',
348
- check: (ctx) => {
349
- // Prefer local (effective config) any settings file with permissions passes
350
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
351
- return !!(settings && settings.permissions);
352
- },
353
- impact: 'medium',
354
- rating: 4,
355
- category: 'security',
356
- fix: 'Configure allow/deny permission lists for safe tool usage.',
357
- template: null
358
- },
359
-
360
- permissionDeny: {
361
- id: 2401,
362
- name: 'Deny rules configured in permissions',
363
- check: (ctx) => {
364
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
365
- if (!settings || !settings.permissions) return false;
366
- const deny = settings.permissions.deny;
367
- return Array.isArray(deny) && deny.length > 0;
368
- },
369
- impact: 'high',
370
- rating: 5,
371
- category: 'security',
372
- fix: 'Add permissions.deny rules to block dangerous operations (e.g. rm -rf, dropping databases).',
373
- template: null
374
- },
375
-
376
- noBypassPermissions: {
377
- id: 2402,
378
- name: 'Default mode is not bypassPermissions',
379
- check: (ctx) => {
380
- // Check shared settings first (committed to git) — if the shared baseline
381
- // is safe, a personal settings.local.json override should not fail the audit.
382
- const shared = ctx.jsonFile('.claude/settings.json');
383
- if (shared && shared.permissions) {
384
- return shared.permissions.defaultMode !== 'bypassPermissions';
385
- }
386
- const local = ctx.jsonFile('.claude/settings.local.json');
387
- if (!local || !local.permissions) return null;
388
- return local.permissions.defaultMode !== 'bypassPermissions';
389
- },
390
- impact: 'critical',
391
- rating: 5,
392
- category: 'security',
393
- fix: 'Do not set defaultMode to bypassPermissions. Use explicit allow rules instead.',
394
- template: null
395
- },
396
-
397
- secretsProtection: {
398
- id: 1096,
399
- name: 'Secrets protection configured',
400
- check: (ctx) => {
401
- // Prefer shared settings.json (committed) over local override
402
- const settings = ctx.jsonFile('.claude/settings.json') || ctx.jsonFile('.claude/settings.local.json');
403
- if (!settings || !settings.permissions) return false;
404
- const deny = JSON.stringify(settings.permissions.deny || []);
405
- return deny.includes('.env') || deny.includes('secrets');
406
- },
407
- impact: 'critical',
408
- rating: 5,
409
- category: 'security',
410
- fix: 'Add permissions.deny rules to block reading .env files and secrets directories.',
411
- template: null
412
- },
413
-
414
- securityReview: {
415
- id: 1031,
416
- name: 'Security review command awareness',
417
- check: (ctx) => {
418
- const md = ctx.claudeMdContent() || '';
419
- return md.includes('security') || md.includes('/security-review');
420
- },
421
- impact: 'high',
422
- rating: 5,
423
- category: 'security',
424
- fix: 'Add /security-review to your workflow. Claude Code has built-in OWASP Top 10 scanning.',
425
- template: null
426
- },
427
-
428
- // ============================================================
429
- // === AUTOMATION (category: 'automation') =====================
430
- // ============================================================
431
-
432
- hooks: {
433
- id: 19,
434
- name: 'Hooks for automation',
435
- check: (ctx) => {
436
- // Hooks are configured in settings.json (not .claude/hooks/ directory)
437
- const shared = ctx.jsonFile('.claude/settings.json') || {};
438
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
439
- return !!(shared.hooks && Object.keys(shared.hooks).length > 0) || !!(local.hooks && Object.keys(local.hooks).length > 0);
440
- },
441
- impact: 'high',
442
- rating: 4,
443
- category: 'automation',
444
- fix: 'Add hooks in .claude/settings.json under the "hooks" key. Supported events: PreToolUse, PostToolUse, Notification, Stop, StopFailure, SubagentStop, and more.',
445
- template: 'hooks'
446
- },
447
-
448
- hooksInSettings: {
449
- id: 8801,
450
- name: 'Hooks configured in settings',
451
- check: (ctx) => {
452
- const shared = ctx.jsonFile('.claude/settings.json');
453
- const local = ctx.jsonFile('.claude/settings.local.json');
454
- const hasSharedHooks = shared && shared.hooks && Object.keys(shared.hooks).length > 0;
455
- const hasLocalHooks = local && local.hooks && Object.keys(local.hooks).length > 0;
456
- return hasSharedHooks || hasLocalHooks;
457
- },
458
- impact: 'high',
459
- rating: 4,
460
- category: 'automation',
461
- fix: 'Add hooks in .claude/settings.json for automated enforcement (lint-on-save, test-on-commit).',
462
- template: 'hooks'
463
- },
464
-
465
- preToolUseHook: {
466
- id: 8802,
467
- name: 'PreToolUse hook configured',
468
- check: (ctx) => {
469
- const shared = ctx.jsonFile('.claude/settings.json');
470
- const local = ctx.jsonFile('.claude/settings.local.json');
471
- return !!(shared?.hooks?.PreToolUse || local?.hooks?.PreToolUse);
472
- },
473
- impact: 'high',
474
- rating: 4,
475
- category: 'automation',
476
- fix: 'Add PreToolUse hooks for validation before tool calls (e.g. block writes to protected files).',
477
- template: null
478
- },
479
-
480
- postToolUseHook: {
481
- id: 8803,
482
- name: 'PostToolUse hook configured',
483
- check: (ctx) => {
484
- const shared = ctx.jsonFile('.claude/settings.json');
485
- const local = ctx.jsonFile('.claude/settings.local.json');
486
- return !!(shared?.hooks?.PostToolUse || local?.hooks?.PostToolUse);
487
- },
488
- impact: 'high',
489
- rating: 4,
490
- category: 'automation',
491
- fix: 'Add PostToolUse hooks for auto-lint or auto-format after file writes.',
492
- template: null
493
- },
494
-
495
- sessionStartHook: {
496
- id: 8804,
497
- name: 'SessionStart hook configured',
498
- check: (ctx) => {
499
- const shared = ctx.jsonFile('.claude/settings.json');
500
- const local = ctx.jsonFile('.claude/settings.local.json');
501
- if (!(shared?.hooks || local?.hooks)) return false;
502
- return !!(shared?.hooks?.SessionStart || local?.hooks?.SessionStart);
503
- },
504
- impact: 'medium',
505
- rating: 4,
506
- category: 'automation',
507
- fix: 'Add a SessionStart hook for initialization tasks (log rotation, state loading, etc.).',
508
- template: null
509
- },
510
-
511
- // ============================================================
512
- // === DESIGN (category: 'design') ============================
513
- // ============================================================
514
-
515
- frontendDesignSkill: {
516
- id: 1025,
517
- name: 'Frontend design skill for anti-AI-slop',
518
- check: (ctx) => {
519
- if (!hasFrontendSignals(ctx)) return null;
520
- const md = ctx.claudeMdContent() || '';
521
- return md.includes('frontend_aesthetics') || md.includes('anti-AI-slop') || md.includes('frontend-design');
522
- },
523
- impact: 'medium',
524
- rating: 5,
525
- category: 'design',
526
- fix: 'Install the official frontend-design skill for better UI output quality.',
527
- template: null
528
- },
529
-
530
- tailwindMention: {
531
- id: 102501,
532
- name: 'Tailwind CSS configured',
533
- check: (ctx) => {
534
- if (!hasFrontendSignals(ctx)) return null;
535
- const pkg = ctx.fileContent('package.json') || '';
536
- return pkg.includes('tailwind') ||
537
- ctx.files.some(f => /tailwind\.config/.test(f));
538
- },
539
- impact: 'low',
540
- rating: 3,
541
- category: 'design',
542
- fix: 'Consider adding Tailwind CSS for rapid, consistent UI styling with Claude.',
543
- template: null
544
- },
545
-
546
- // ============================================================
547
- // === DEVOPS (category: 'devops') ============================
548
- // ============================================================
549
-
550
- dockerfile: {
551
- id: 399,
552
- name: 'Has Dockerfile',
553
- check: (ctx) => ctx.files.some(f => /^Dockerfile/i.test(f)),
554
- impact: 'medium',
555
- rating: 3,
556
- category: 'devops',
557
- fix: 'Add a Dockerfile for containerized builds and deployments.',
558
- template: null
559
- },
560
-
561
- dockerCompose: {
562
- id: 39901,
563
- name: 'Has docker-compose.yml',
564
- check: (ctx) => ctx.files.some(f => /^docker-compose\.(yml|yaml)$/i.test(f)),
565
- impact: 'medium',
566
- rating: 3,
567
- category: 'devops',
568
- fix: 'Add docker-compose.yml for multi-service local development.',
569
- template: null
570
- },
571
-
572
- ciPipeline: {
573
- id: 260,
574
- name: 'CI pipeline configured',
575
- check: (ctx) => ctx.hasDir('.github/workflows') || ctx.hasDir('.circleci') ||
576
- ctx.files.includes('.gitlab-ci.yml') || ctx.files.includes('Jenkinsfile') ||
577
- ctx.files.includes('.travis.yml') || ctx.files.includes('bitbucket-pipelines.yml'),
578
- impact: 'high',
579
- rating: 4,
580
- category: 'devops',
581
- fix: 'Add a CI pipeline (GitHub Actions, GitLab CI, CircleCI, etc.) for automated testing and deployment.',
582
- template: null
583
- },
584
-
585
- terraformFiles: {
586
- id: 397,
587
- name: 'Infrastructure as Code (Terraform)',
588
- check: (ctx) => ctx.files.some(f => /\.tf$/.test(f)) || ctx.files.includes('main.tf'),
589
- impact: 'medium',
590
- rating: 3,
591
- category: 'devops',
592
- fix: 'Add Terraform files for infrastructure-as-code management.',
593
- template: null
594
- },
595
-
596
- // ============================================================
597
- // === PROJECT HYGIENE (category: 'hygiene') ==================
598
- // ============================================================
599
-
600
- readme: {
601
- id: 416,
602
- name: 'Has README.md',
603
- check: (ctx) => ctx.files.some(f => /^readme\.md$/i.test(f)),
604
- impact: 'high',
605
- rating: 4,
606
- category: 'hygiene',
607
- fix: 'Add a README.md with project overview, setup instructions, and usage.',
608
- template: null
609
- },
610
-
611
- changelog: {
612
- id: 417,
613
- name: 'Has CHANGELOG.md',
614
- check: (ctx) => ctx.files.some(f => /^changelog\.md$/i.test(f)),
615
- impact: 'low',
616
- rating: 3,
617
- category: 'hygiene',
618
- fix: 'Add a CHANGELOG.md to track notable changes across versions.',
619
- template: null
620
- },
621
-
622
- contributing: {
623
- id: 418,
624
- name: 'Has CONTRIBUTING.md',
625
- check: (ctx) => ctx.files.some(f => /^contributing\.md$/i.test(f)),
626
- impact: 'low',
627
- rating: 3,
628
- category: 'hygiene',
629
- fix: 'Add a CONTRIBUTING.md with contribution guidelines and code standards.',
630
- template: null
631
- },
632
-
633
- license: {
634
- id: 434,
635
- name: 'Has LICENSE file',
636
- check: (ctx) => ctx.files.some(f => /^license/i.test(f)),
637
- impact: 'low',
638
- rating: 3,
639
- category: 'hygiene',
640
- fix: 'Add a LICENSE file to clarify usage rights.',
641
- template: null
642
- },
643
-
644
- editorconfig: {
645
- id: 5001,
646
- name: 'Has .editorconfig',
647
- check: (ctx) => ctx.files.includes('.editorconfig'),
648
- impact: 'low',
649
- rating: 3,
650
- category: 'hygiene',
651
- fix: 'Add .editorconfig for consistent formatting across editors and Claude.',
652
- template: null
653
- },
654
-
655
- nvmrc: {
656
- id: 5002,
657
- name: 'Node version pinned',
658
- check: (ctx) => {
659
- const hasNodeSignals = ctx.files.includes('package.json') ||
660
- ctx.files.includes('tsconfig.json') ||
661
- ctx.files.some(f => /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config/i.test(f));
662
- if (!hasNodeSignals) return null;
663
- if (ctx.files.includes('.nvmrc') || ctx.files.includes('.node-version')) return true;
664
- const pkg = ctx.jsonFile('package.json');
665
- return !!(pkg && pkg.engines && pkg.engines.node);
666
- },
667
- impact: 'low',
668
- rating: 3,
669
- category: 'hygiene',
670
- fix: 'Add .nvmrc, .node-version, or engines.node in package.json to pin Node version.',
671
- template: null
672
- },
673
-
674
- // ============================================================
675
- // === PERFORMANCE (category: 'performance') ==================
676
- // ============================================================
677
-
678
- compactionAwareness: {
679
- id: 568,
680
- name: 'CLAUDE.md mentions /compact or compaction',
681
- check: (ctx) => {
682
- const md = ctx.claudeMdContent() || '';
683
- return /\/compact|compaction|context.*(limit|manage|budget)/i.test(md);
684
- },
685
- impact: 'medium',
686
- rating: 4,
687
- category: 'performance',
688
- fix: 'Add compaction guidance to CLAUDE.md (e.g. "Run /compact when context is heavy").',
689
- template: null
690
- },
691
-
692
- contextManagement: {
693
- id: 45,
694
- name: 'Context management awareness',
695
- check: (ctx) => {
696
- const md = ctx.claudeMdContent() || '';
697
- return /context.*(manage|window|limit|budget|token)/i.test(md);
698
- },
699
- impact: 'medium',
700
- rating: 4,
701
- category: 'performance',
702
- fix: 'Add context management tips to CLAUDE.md to help Claude stay within token limits.',
703
- template: null
704
- },
705
-
706
- // ============================================================
707
- // === MCP / TOOLS (category: 'tools') ========================
708
- // ============================================================
709
-
710
- mcpServers: {
711
- id: 18,
712
- name: 'MCP servers configured',
713
- check: (ctx) => {
714
- // MCP now lives in .mcp.json (project) and ~/.claude.json (user), NOT settings.json
715
- const mcpJson = ctx.jsonFile('.mcp.json');
716
- if (mcpJson && mcpJson.mcpServers && Object.keys(mcpJson.mcpServers).length > 0) return true;
717
- // Fallback: check settings for legacy format
718
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
719
- return !!(settings && settings.mcpServers && Object.keys(settings.mcpServers).length > 0);
720
- },
721
- impact: 'medium',
722
- rating: 3,
723
- category: 'tools',
724
- fix: 'Configure MCP servers in .mcp.json at project root. Use `claude mcp add` to add servers. Project-level MCP is committed to git for team sharing.',
725
- template: null
726
- },
727
-
728
- multipleMcpServers: {
729
- id: 1801,
730
- name: '2+ MCP servers for rich tooling',
731
- check: (ctx) => {
732
- let count = 0;
733
- const mcpJson = ctx.jsonFile('.mcp.json');
734
- if (mcpJson && mcpJson.mcpServers) count += Object.keys(mcpJson.mcpServers).length;
735
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
736
- if (settings && settings.mcpServers) count += Object.keys(settings.mcpServers).length;
737
- return count >= 2;
738
- },
739
- impact: 'medium',
740
- rating: 4,
741
- category: 'tools',
742
- fix: 'Add at least 2 MCP servers for broader tool coverage (e.g. database + search).',
743
- template: null
744
- },
745
-
746
- context7Mcp: {
747
- id: 110,
748
- name: 'Context7 MCP for real-time docs',
749
- check: (ctx) => {
750
- const shared = ctx.jsonFile('.claude/settings.json') || {};
751
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
752
- const mcp = ctx.jsonFile('.mcp.json') || {};
753
- const all = { ...(shared.mcpServers || {}), ...(local.mcpServers || {}), ...(mcp.mcpServers || {}) };
754
- if (Object.keys(all).length === 0) return false;
755
- return Object.keys(all).some(k => /context7/i.test(k));
756
- },
757
- impact: 'medium',
758
- rating: 4,
759
- category: 'tools',
760
- fix: 'Add Context7 MCP server for real-time documentation lookup (always up-to-date library docs).',
761
- template: null
762
- },
763
-
764
- // ============================================================
765
- // === PROMPTING (category: 'prompting') ======================
766
- // ============================================================
767
-
768
- xmlTags: {
769
- id: 96,
770
- name: 'XML tags for structured prompts',
771
- check: (ctx) => {
772
- const md = ctx.claudeMdContent() || '';
773
- // Give credit for XML tags OR well-structured markdown with clear sections
774
- const hasXml = md.includes('<constraints') || md.includes('<rules') ||
775
- md.includes('<validation') || md.includes('<instructions');
776
- const hasStructuredMd = (md.includes('## Rules') || md.includes('## Constraints') ||
777
- md.includes('## Do not') || md.includes('## Never') || md.includes('## Important')) &&
778
- md.split('\n').length > 20;
779
- return hasXml || hasStructuredMd;
780
- },
781
- impact: 'medium',
782
- rating: 4,
783
- category: 'prompting',
784
- fix: 'Add clear rules sections to CLAUDE.md. XML tags (<constraints>) are optional but improve clarity.',
785
- template: null
786
- },
787
-
788
- fewShotExamples: {
789
- id: 9,
790
- name: 'CLAUDE.md contains code examples',
791
- check: (ctx) => {
792
- const md = ctx.claudeMdContent() || '';
793
- return (md.match(/```/g) || []).length >= 2;
794
- },
795
- impact: 'high',
796
- rating: 5,
797
- category: 'prompting',
798
- fix: 'Add code examples (few-shot) in CLAUDE.md to show preferred patterns and conventions.',
799
- template: null
800
- },
801
-
802
- roleDefinition: {
803
- id: 10,
804
- name: 'CLAUDE.md defines a role or persona',
805
- check: (ctx) => {
806
- const md = ctx.claudeMdContent() || '';
807
- return /^you are a |^your role is|^act as a |persona:|behave as a /im.test(md);
808
- },
809
- impact: 'medium',
810
- rating: 4,
811
- category: 'prompting',
812
- fix: 'Define a role or persona in CLAUDE.md (e.g. "You are a senior backend engineer...").',
813
- template: null
814
- },
815
-
816
- constraintBlocks: {
817
- id: 9601,
818
- name: 'XML constraint blocks in CLAUDE.md',
819
- check: (ctx) => {
820
- const md = ctx.claudeMdContent() || '';
821
- return /<constraints|<rules|<requirements|<boundaries/i.test(md);
822
- },
823
- impact: 'high',
824
- rating: 5,
825
- category: 'prompting',
826
- fix: 'Wrap critical rules in <constraints> XML blocks for 40% better adherence.',
827
- template: null
828
- },
829
-
830
- // ============================================================
831
- // === FEATURES (category: 'features') ========================
832
- // ============================================================
833
-
834
- channelsAwareness: {
835
- id: 1102,
836
- name: 'Claude Code Channels awareness',
837
- check: (ctx) => {
838
- const md = ctx.claudeMdContent() || '';
839
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
840
- const settingsStr = JSON.stringify(settings || {});
841
- return /\bchannels?\b.*\b(telegram|discord|imessage|slack|bridge)\b|\b(telegram|discord|imessage|slack|bridge)\b.*\bchannels?\b/i.test(md) || settingsStr.includes('channels');
842
- },
843
- impact: 'low',
844
- rating: 3,
845
- category: 'features',
846
- fix: 'Claude Code Channels (v2.1.80+) bridges Telegram/Discord/iMessage to your session.',
847
- template: null
848
- },
849
-
850
- // ============================================================
851
- // === QUALITY CHECKS FOR VETERANS (category: 'quality-deep')
852
- // These check HOW GOOD your config is, not just IF it exists.
853
- // ============================================================
854
-
855
- claudeMdFreshness: {
856
- id: 2001,
857
- name: 'CLAUDE.md mentions current Claude features',
858
- check: (ctx) => {
859
- const md = ctx.claudeMdContent() || '';
860
- if (md.length < 50) return false; // too short to evaluate
861
- // Check for awareness of features from 2025+
862
- const modernFeatures = ['hook', 'skill', 'agent', 'subagent', 'mcp', 'compact', '/clear', 'extended thinking', 'tool_use', 'worktree'];
863
- const found = modernFeatures.filter(f => md.toLowerCase().includes(f));
864
- return found.length >= 2; // knows at least 2 modern features
865
- },
866
- impact: 'medium',
867
- rating: 4,
868
- category: 'quality-deep',
869
- fix: 'Your CLAUDE.md may be outdated. Modern Claude Code supports hooks, skills, agents, MCP, worktrees, and extended thinking. Mention the ones you use.',
870
- template: null
871
- },
872
-
873
- // claudeMdNotOverlong removed duplicate of underlines200 (id 681)
874
-
875
- claudeLocalMd: {
876
- id: 2002,
877
- name: 'CLAUDE.local.md for personal overrides',
878
- check: (ctx) => {
879
- // CLAUDE.local.md is for personal, non-committed overrides
880
- return ctx.files.includes('CLAUDE.local.md') || ctx.files.includes('.claude/CLAUDE.local.md');
881
- },
882
- impact: 'low',
883
- rating: 2,
884
- category: 'memory',
885
- fix: 'Create CLAUDE.local.md for personal preferences that should not be committed (add to .gitignore).',
886
- template: null
887
- },
888
-
889
- claudeMdNoContradictions: {
890
- id: 2003,
891
- name: 'CLAUDE.md has no obvious contradictions',
892
- check: (ctx) => {
893
- const md = ctx.claudeMdContent();
894
- if (!md || md.length < 50) return false; // no CLAUDE.md or too short = not passing
895
- // Check for common contradictions
896
- // Check for contradictions on the SAME topic (same line or adjacent sentence)
897
- const lines = md.split('\n');
898
- let hasContradiction = false;
899
- for (const line of lines) {
900
- if (/\balways\b.*\bnever\b|\bnever\b.*\balways\b/i.test(line)) {
901
- hasContradiction = true;
902
- break;
903
- }
904
- }
905
- const hasBothStyles = /\buse tabs\b/i.test(md) && /\buse spaces\b/i.test(md);
906
- return !hasContradiction && !hasBothStyles;
907
- },
908
- impact: 'high',
909
- rating: 4,
910
- category: 'quality-deep',
911
- fix: 'CLAUDE.md may contain contradictory instructions. Review for conflicting rules (e.g., "always X" and "never X" about the same topic).',
912
- template: null
913
- },
914
-
915
- hooksAreSpecific: {
916
- id: 2004,
917
- name: 'Hooks use specific matchers (not catch-all)',
918
- check: (ctx) => {
919
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
920
- if (!settings || !settings.hooks) return null; // no hooks = not applicable
921
- const hookStr = JSON.stringify(settings.hooks);
922
- // Check that hooks have matchers, not just catch-all
923
- return hookStr.includes('matcher');
924
- },
925
- impact: 'medium',
926
- rating: 3,
927
- category: 'quality-deep',
928
- fix: 'Hooks without matchers run on every tool call. Use matchers like "Write|Edit" or "Bash" to target specific tools.',
929
- template: null
930
- },
931
-
932
- // permissionsNotBypassed removed - duplicate of noBypassPermissions (#24)
933
-
934
- commandsUseArguments: {
935
- id: 2006,
936
- name: 'Commands use $ARGUMENTS for flexibility',
937
- check: (ctx) => {
938
- if (!ctx.hasDir('.claude/commands')) return null; // not applicable
939
- const files = ctx.dirFiles('.claude/commands');
940
- if (files.length === 0) return null;
941
- // Check if at least one command uses $ARGUMENTS
942
- for (const f of files) {
943
- const content = ctx.fileContent(`.claude/commands/${f}`) || '';
944
- if (content.includes('$ARGUMENTS') || content.includes('$arguments')) return true;
945
- }
946
- return false;
947
- },
948
- impact: 'medium',
949
- rating: 3,
950
- category: 'quality-deep',
951
- fix: 'Commands without $ARGUMENTS are static. Use $ARGUMENTS to make them flexible: "Fix the issue: $ARGUMENTS"',
952
- template: null
953
- },
954
-
955
- agentsHaveMaxTurns: {
956
- id: 2007,
957
- name: 'Subagents have max-turns limit',
958
- check: (ctx) => {
959
- if (!ctx.hasDir('.claude/agents')) return null;
960
- const files = ctx.dirFiles('.claude/agents');
961
- if (files.length === 0) return null;
962
- for (const f of files) {
963
- const content = ctx.fileContent(`.claude/agents/${f}`) || '';
964
- // Current frontmatter uses kebab-case: max-turns (also accept legacy maxTurns)
965
- if (!content.includes('max-turns') && !content.includes('maxTurns')) return false;
966
- }
967
- return true;
968
- },
969
- impact: 'medium',
970
- rating: 3,
971
- category: 'quality-deep',
972
- fix: 'Subagents without max-turns can run indefinitely. Add "max-turns: 50" to subagent YAML frontmatter.',
973
- template: null
974
- },
975
-
976
- securityReviewInWorkflow: {
977
- id: 2008,
978
- name: '/security-review command or workflow',
979
- check: (ctx) => {
980
- const hasCommand = ctx.hasDir('.claude/commands') &&
981
- (ctx.dirFiles('.claude/commands') || []).some(f => f.includes('security') || f.includes('review'));
982
- const md = ctx.claudeMdContent() || '';
983
- const hasExplicitRef = /\/security-review|security review command|security workflow/i.test(md);
984
- return hasCommand || hasExplicitRef;
985
- },
986
- impact: 'medium',
987
- rating: 4,
988
- category: 'quality-deep',
989
- fix: 'Claude Code has built-in /security-review (OWASP Top 10). Add it to your workflow or create a /security command.',
990
- template: null
991
- },
992
-
993
- // --- New checks: testing depth ---
994
- testCoverage: {
995
- id: 2010,
996
- name: 'Test coverage or strategy mentioned',
997
- check: (ctx) => {
998
- const md = ctx.claudeMdContent() || '';
999
- return /coverage|test.*strateg|e2e|integration test|unit test/i.test(md);
1000
- },
1001
- impact: 'medium', rating: 3, category: 'quality',
1002
- fix: 'Mention your testing strategy in CLAUDE.md (unit, integration, E2E, coverage targets).',
1003
- template: null
1004
- },
1005
-
1006
- // --- New checks: agent depth ---
1007
- agentHasAllowedTools: {
1008
- id: 2011,
1009
- name: 'At least one subagent restricts tools',
1010
- check: (ctx) => {
1011
- if (!ctx.hasDir('.claude/agents')) return null;
1012
- const files = ctx.dirFiles('.claude/agents');
1013
- if (files.length === 0) return null;
1014
- for (const f of files) {
1015
- const content = ctx.fileContent(`.claude/agents/${f}`) || '';
1016
- // Current frontmatter uses allowed-tools (also accept legacy tools:)
1017
- if (/allowed-tools:/i.test(content) || /tools:\s*\[/.test(content)) return true;
1018
- }
1019
- return false;
1020
- },
1021
- impact: 'medium', rating: 3, category: 'workflow',
1022
- fix: 'Add allowed-tools to subagent frontmatter (e.g. allowed-tools: Read Grep Bash) for safer delegation.',
1023
- template: null
1024
- },
1025
-
1026
- // --- New checks: memory / auto-memory ---
1027
- autoMemoryAwareness: {
1028
- id: 2012,
1029
- name: 'Auto-memory or memory management mentioned',
1030
- check: (ctx) => {
1031
- const md = ctx.claudeMdContent() || '';
1032
- return /auto.?memory|memory.*manage|remember|persistent.*context/i.test(md);
1033
- },
1034
- impact: 'low', rating: 3, category: 'memory',
1035
- fix: 'Claude Code supports auto-memory for cross-session learning. Mention your memory strategy if relevant.',
1036
- template: null
1037
- },
1038
-
1039
- // --- New checks: sandbox / security depth ---
1040
- sandboxAwareness: {
1041
- id: 2013,
1042
- name: 'Sandbox or isolation mentioned',
1043
- check: (ctx) => {
1044
- const md = ctx.claudeMdContent() || '';
1045
- const settings = ctx.jsonFile('.claude/settings.json') || {};
1046
- return /sandbox|isolat/i.test(md) || !!settings.sandbox;
1047
- },
1048
- impact: 'medium', rating: 3, category: 'security',
1049
- fix: 'Claude Code supports sandboxed command execution. Consider enabling it for untrusted operations.',
1050
- template: null
1051
- },
1052
-
1053
- denyRulesDepth: {
1054
- id: 2014,
1055
- name: 'Deny rules cover 3+ patterns',
1056
- check: (ctx) => {
1057
- const shared = ctx.jsonFile('.claude/settings.json');
1058
- const local = ctx.jsonFile('.claude/settings.local.json');
1059
- const deny = (shared?.permissions?.deny || []).concat(local?.permissions?.deny || []);
1060
- return deny.length >= 3;
1061
- },
1062
- impact: 'high', rating: 4, category: 'security',
1063
- fix: 'Add at least 3 deny rules: rm -rf, force-push, and .env reads. More patterns = safer Claude.',
1064
- template: null
1065
- },
1066
-
1067
- // --- New checks: git depth ---
1068
- gitAttributionDecision: {
1069
- id: 2015,
1070
- name: 'Git attribution configured',
1071
- check: (ctx) => {
1072
- const shared = ctx.jsonFile('.claude/settings.json') || {};
1073
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
1074
- return shared.attribution !== undefined || local.attribution !== undefined ||
1075
- shared.includeCoAuthoredBy !== undefined || local.includeCoAuthoredBy !== undefined;
1076
- },
1077
- impact: 'low', rating: 3, category: 'git',
1078
- fix: 'Decide on git attribution: set attribution.commit or includeCoAuthoredBy in settings.',
1079
- template: null
1080
- },
1081
-
1082
- // --- New checks: performance ---
1083
- effortLevelConfigured: {
1084
- id: 2016,
1085
- name: 'Effort level or thinking configuration',
1086
- check: (ctx) => {
1087
- const md = ctx.claudeMdContent() || '';
1088
- const shared = ctx.jsonFile('.claude/settings.json') || {};
1089
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
1090
- return /effort|thinking/i.test(md) || shared.effortLevel || local.effortLevel ||
1091
- shared.alwaysThinkingEnabled !== undefined || local.alwaysThinkingEnabled !== undefined;
1092
- },
1093
- impact: 'low', rating: 3, category: 'performance',
1094
- fix: 'Configure effortLevel or mention thinking strategy in CLAUDE.md for task-appropriate reasoning depth.',
1095
- template: null
1096
- },
1097
-
1098
- // --- New checks: workflow depth ---
1099
- hasSnapshotHistory: {
1100
- id: 2017,
1101
- name: 'Audit snapshot history exists',
1102
- check: (ctx) => {
1103
- return !!ctx.fileContent('.claude/claudex-setup/snapshots/index.json');
1104
- },
1105
- impact: 'low', rating: 3, category: 'workflow',
1106
- fix: 'Run `npx nerviq --snapshot` to start tracking your setup score over time.',
1107
- template: null
1108
- },
1109
-
1110
- worktreeAwareness: {
1111
- id: 2018,
1112
- name: 'Worktree or parallel sessions mentioned',
1113
- check: (ctx) => {
1114
- const md = ctx.claudeMdContent() || '';
1115
- const shared = ctx.jsonFile('.claude/settings.json') || {};
1116
- return /worktree|parallel.*session/i.test(md) || !!shared.worktree;
1117
- },
1118
- impact: 'low', rating: 3, category: 'features',
1119
- fix: 'Claude Code supports git worktrees for parallel isolated sessions. Mention if relevant.',
1120
- template: null
1121
- },
1122
-
1123
- // --- New checks: prompting depth ---
1124
- negativeInstructions: {
1125
- id: 2019,
1126
- name: 'CLAUDE.md includes "do not" instructions',
1127
- check: (ctx) => {
1128
- const md = ctx.claudeMdContent() || '';
1129
- return /do not|don't|never|avoid|must not/i.test(md);
1130
- },
1131
- impact: 'medium', rating: 4, category: 'prompting',
1132
- fix: 'Add explicit "do not" rules to CLAUDE.md. Negative constraints reduce common mistakes.',
1133
- template: null
1134
- },
1135
-
1136
- outputStyleGuidance: {
1137
- id: 2020,
1138
- name: 'CLAUDE.md includes output or style guidance',
1139
- check: (ctx) => {
1140
- const md = ctx.claudeMdContent() || '';
1141
- return /coding style|naming convention|code style|style guide|formatting rules|\bprefer\b.*\b(single|double|tabs|spaces|camel|snake|kebab|named|default|const|let|arrow|function)\b/i.test(md);
1142
- },
1143
- impact: 'medium', rating: 3, category: 'prompting',
1144
- fix: 'Add coding style and naming conventions to CLAUDE.md so Claude matches your project patterns.',
1145
- template: null
1146
- },
1147
-
1148
- // --- New checks: devops depth ---
1149
- githubActionsOrCI: {
1150
- id: 2021,
1151
- name: 'GitHub Actions or CI configured',
1152
- check: (ctx) => {
1153
- return ctx.hasDir('.github/workflows') || !!ctx.fileContent('.circleci/config.yml') ||
1154
- !!ctx.fileContent('.gitlab-ci.yml') || !!ctx.fileContent('Jenkinsfile') ||
1155
- !!ctx.fileContent('.travis.yml') || !!ctx.fileContent('bitbucket-pipelines.yml');
1156
- },
1157
- impact: 'medium', rating: 3, category: 'devops',
1158
- fix: 'Add CI pipeline for automated testing. Claude Code has a GitHub Action for audit gates.',
1159
- template: null
1160
- },
1161
-
1162
- // --- New checks: depth round 2 ---
1163
- projectDescriptionInClaudeMd: {
1164
- id: 2022,
1165
- name: 'CLAUDE.md describes what the project does',
1166
- check: (ctx) => {
1167
- const md = ctx.claudeMdContent() || '';
1168
- return /what.*does|overview|purpose|about|description|project.*is/i.test(md) && md.length > 100;
1169
- },
1170
- impact: 'high', rating: 4, category: 'memory',
1171
- fix: 'Start CLAUDE.md with a clear project description. Claude needs to know what your project does.',
1172
- template: null
1173
- },
1174
-
1175
- directoryStructureInClaudeMd: {
1176
- id: 2023,
1177
- name: 'CLAUDE.md documents directory structure',
1178
- check: (ctx) => {
1179
- const md = ctx.claudeMdContent() || '';
1180
- return /src\/|app\/|lib\/|structure|director|folder/i.test(md);
1181
- },
1182
- impact: 'medium', rating: 4, category: 'memory',
1183
- fix: 'Document your directory structure in CLAUDE.md so Claude navigates your codebase efficiently.',
1184
- template: null
1185
- },
1186
-
1187
- multipleHookTypes: {
1188
- id: 2024,
1189
- name: '2+ hook event types configured',
1190
- check: (ctx) => {
1191
- const shared = ctx.jsonFile('.claude/settings.json') || {};
1192
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
1193
- const hooks = { ...(shared.hooks || {}), ...(local.hooks || {}) };
1194
- return Object.keys(hooks).length >= 2;
1195
- },
1196
- impact: 'medium', rating: 3, category: 'automation',
1197
- fix: 'Add at least 2 hook types (e.g. PostToolUse for linting + SessionStart for initialization).',
1198
- template: null
1199
- },
1200
-
1201
- stopFailureHook: {
1202
- id: 2025,
1203
- name: 'StopFailure hook for error tracking',
1204
- check: (ctx) => {
1205
- const shared = ctx.jsonFile('.claude/settings.json') || {};
1206
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
1207
- // StopFailure = error stop (API errors), Stop = normal completion — both useful but different
1208
- return !!(shared.hooks?.StopFailure || local.hooks?.StopFailure);
1209
- },
1210
- impact: 'low', rating: 3, category: 'automation',
1211
- fix: 'Add a StopFailure hook to log API errors and unexpected stops. Note: StopFailure (errors) is different from Stop (normal completion).',
1212
- template: null
1213
- },
1214
-
1215
- skillUsesPaths: {
1216
- id: 2026,
1217
- name: 'At least one skill uses paths for scoping',
1218
- check: (ctx) => {
1219
- if (!ctx.hasDir('.claude/skills')) return null;
1220
- const entries = ctx.dirFiles('.claude/skills');
1221
- if (entries.length === 0) return null;
1222
- for (const entry of entries) {
1223
- // Skills can be files or dirs with SKILL.md inside
1224
- const direct = ctx.fileContent(`.claude/skills/${entry}`) || '';
1225
- if (/paths:/i.test(direct)) return true;
1226
- const nested = ctx.fileContent(`.claude/skills/${entry}/SKILL.md`) || '';
1227
- if (/paths:/i.test(nested)) return true;
1228
- }
1229
- return false;
1230
- },
1231
- impact: 'low', rating: 3, category: 'workflow',
1232
- fix: 'Add paths to skill frontmatter to scope when skills activate (e.g. paths: ["src/**/*.ts"]).',
1233
- template: null
1234
- },
1235
-
1236
- mcpHasEnvConfig: {
1237
- id: 2027,
1238
- name: 'MCP servers have environment configuration',
1239
- check: (ctx) => {
1240
- const shared = ctx.jsonFile('.claude/settings.json') || {};
1241
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
1242
- const mcp = ctx.jsonFile('.mcp.json') || {};
1243
- const allServers = { ...(shared.mcpServers || {}), ...(local.mcpServers || {}), ...(mcp.mcpServers || {}) };
1244
- if (Object.keys(allServers).length === 0) return null;
1245
- return Object.values(allServers).some(s => s.env && Object.keys(s.env).length > 0);
1246
- },
1247
- impact: 'low', rating: 3, category: 'tools',
1248
- fix: 'Configure environment variables for MCP servers that need authentication (e.g. GITHUB_TOKEN).',
1249
- template: null
1250
- },
1251
-
1252
- gitIgnoreClaudeLocal: {
1253
- id: 2028,
1254
- name: '.gitignore excludes settings.local.json',
1255
- check: (ctx) => {
1256
- const gitignore = ctx.fileContent('.gitignore') || '';
1257
- return /settings\.local\.json|settings\.local/i.test(gitignore);
1258
- },
1259
- impact: 'medium', rating: 4, category: 'git',
1260
- fix: 'Add .claude/settings.local.json to .gitignore. Personal overrides should not be committed.',
1261
- template: null
1262
- },
1263
-
1264
- envExampleExists: {
1265
- id: 2029,
1266
- name: '.env.example or .env.template exists',
1267
- check: (ctx) => {
1268
- return !!(ctx.fileContent('.env.example') || ctx.fileContent('.env.template') || ctx.fileContent('.env.sample'));
1269
- },
1270
- impact: 'low', rating: 3, category: 'hygiene',
1271
- fix: 'Add .env.example so new developers know which environment variables are needed.',
1272
- template: null
1273
- },
1274
-
1275
- packageJsonHasScripts: {
1276
- id: 2030,
1277
- name: 'package.json has dev/test/build scripts',
1278
- check: (ctx) => {
1279
- const pkg = ctx.jsonFile('package.json');
1280
- if (!pkg) return null;
1281
- const scripts = pkg.scripts || {};
1282
- const has = (k) => !!scripts[k];
1283
- return has('test') || has('dev') || has('build') || has('start');
1284
- },
1285
- impact: 'medium', rating: 3, category: 'hygiene',
1286
- fix: 'Add scripts to package.json (test, dev, build). Claude uses these for verification.',
1287
- template: null
1288
- },
1289
-
1290
- typeCheckingConfigured: {
1291
- id: 2031,
1292
- name: 'Type checking configured (TypeScript or similar)',
1293
- check: (ctx) => {
1294
- return !!(ctx.fileContent('tsconfig.json') || ctx.fileContent('jsconfig.json') ||
1295
- ctx.fileContent('pyrightconfig.json') || ctx.fileContent('mypy.ini'));
1296
- },
1297
- impact: 'medium', rating: 3, category: 'quality',
1298
- fix: 'Add type checking configuration. Type-safe code produces fewer Claude errors.',
1299
- template: null
1300
- },
1301
-
1302
- noDeprecatedPatterns: {
1303
- id: 2009,
1304
- name: 'No deprecated patterns detected',
1305
- check: (ctx) => {
1306
- const md = ctx.claudeMdContent();
1307
- if (!md) return false;
1308
- // Only flag truly deprecated patterns, not valid aliases
1309
- const deprecatedPatterns = [
1310
- /\bhuman_prompt\b/i, /\bassistant_prompt\b/i, // old completions API format (not Messages API)
1311
- /\buse model claude-3-opus\b/i, // explicit recommendation to use old name as --model
1312
- /\buse model claude-3-sonnet\b/i,
1313
- ];
1314
- return !deprecatedPatterns.some(p => p.test(md));
1315
- },
1316
- impact: 'medium',
1317
- rating: 3,
1318
- category: 'quality-deep',
1319
- fix: 'CLAUDE.md references deprecated API patterns (human_prompt/assistant_prompt). Update to current Messages API conventions.',
1320
- template: null
1321
- },
1322
-
1323
- claudeMdQuality: {
1324
- id: 102502,
1325
- name: 'CLAUDE.md has substantive content',
1326
- check: (ctx) => {
1327
- const md = ctx.claudeMdContent();
1328
- if (!md) return null;
1329
- const lines = md.split('\n').filter(l => l.trim());
1330
- const sections = (md.match(/^##\s/gm) || []).length;
1331
- const hasCommand = /\b(npm|yarn|pnpm|pytest|go |make |ruff |cargo |dotnet )\b/i.test(md);
1332
- return lines.length >= 15 && sections >= 2 && hasCommand;
1333
- },
1334
- impact: 'medium',
1335
- rating: 4,
1336
- category: 'quality-deep',
1337
- fix: 'CLAUDE.md exists but lacks substance. Add at least 2 sections (## headings) and include your test/build/lint commands.',
1338
- template: null
1339
- },
1340
-
1341
- // ============================================================
1342
- // === NEW CHECKS: Uncovered features (2026-04-05) ============
1343
- // ============================================================
1344
-
1345
- mcpJsonProject: {
1346
- id: 2032,
1347
- name: 'Project-level .mcp.json exists',
1348
- check: (ctx) => ctx.files.includes('.mcp.json'),
1349
- impact: 'medium',
1350
- rating: 3,
1351
- category: 'tools',
1352
- fix: 'Create .mcp.json at project root for team-shared MCP servers. Use `claude mcp add --project` to add servers.',
1353
- template: null
1354
- },
1355
-
1356
- hooksNotificationEvent: {
1357
- id: 2033,
1358
- name: 'Notification hook for alerts',
1359
- check: (ctx) => {
1360
- const shared = ctx.jsonFile('.claude/settings.json') || {};
1361
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
1362
- return !!(shared.hooks?.Notification || local.hooks?.Notification);
1363
- },
1364
- impact: 'low',
1365
- rating: 2,
1366
- category: 'automation',
1367
- fix: 'Add a Notification hook to capture alerts and status updates from Claude during long tasks.',
1368
- template: null
1369
- },
1370
-
1371
- subagentStopHook: {
1372
- id: 2034,
1373
- name: 'SubagentStop hook for delegation tracking',
1374
- check: (ctx) => {
1375
- const shared = ctx.jsonFile('.claude/settings.json') || {};
1376
- const local = ctx.jsonFile('.claude/settings.local.json') || {};
1377
- return !!(shared.hooks?.SubagentStop || local.hooks?.SubagentStop);
1378
- },
1379
- impact: 'low',
1380
- rating: 2,
1381
- category: 'automation',
1382
- fix: 'Add a SubagentStop hook to track when delegated subagent tasks complete.',
1383
- template: null
1384
- },
1385
-
1386
- rulesDirectory: {
1387
- id: 2035,
1388
- name: 'Path-specific rules in .claude/rules/',
1389
- check: (ctx) => ctx.hasDir('.claude/rules') && ctx.dirFiles('.claude/rules').length > 0,
1390
- impact: 'medium',
1391
- rating: 3,
1392
- category: 'workflow',
1393
- fix: 'Create .claude/rules/ with path-specific rules for different parts of your codebase (e.g. frontend.md, backend.md).',
1394
- template: null
1395
- },
1396
-
1397
- gitignoreClaudeLocal: {
1398
- id: 2036,
1399
- name: 'CLAUDE.local.md in .gitignore',
1400
- check: (ctx) => {
1401
- const gitignore = ctx.fileContent('.gitignore') || '';
1402
- return /CLAUDE\.local\.md/i.test(gitignore);
1403
- },
1404
- impact: 'medium',
1405
- rating: 3,
1406
- category: 'git',
1407
- fix: 'Add CLAUDE.local.md to .gitignore — it contains personal overrides that should not be committed.',
1408
- template: null
1409
- },
1410
-
1411
- };
1412
-
1413
- // Stack detection
1414
- const STACKS = {
1415
- react: { files: ['package.json'], content: { 'package.json': 'react' }, label: 'React' },
1416
- vue: { files: ['package.json'], content: { 'package.json': 'vue' }, label: 'Vue' },
1417
- angular: { files: ['angular.json'], content: {}, label: 'Angular' },
1418
- nextjs: { files: ['next.config'], content: {}, label: 'Next.js' },
1419
- python: { files: ['requirements.txt', 'setup.py', 'pyproject.toml', 'Pipfile'], content: {}, label: 'Python' },
1420
- django: { files: ['manage.py'], content: {}, label: 'Django' },
1421
- fastapi: { files: ['requirements.txt'], content: { 'requirements.txt': 'fastapi' }, label: 'FastAPI' },
1422
- node: { files: ['package.json'], content: {}, label: 'Node.js' },
1423
- typescript: { files: ['tsconfig.json'], content: {}, label: 'TypeScript' },
1424
- rust: { files: ['Cargo.toml'], content: {}, label: 'Rust' },
1425
- go: { files: ['go.mod'], content: {}, label: 'Go' },
1426
- docker: { files: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml'], content: {}, label: 'Docker' },
1427
- svelte: { files: ['svelte.config.js'], content: {}, label: 'Svelte' },
1428
- flutter: { files: ['pubspec.yaml'], content: {}, label: 'Flutter' },
1429
- ruby: { files: ['Gemfile'], content: {}, label: 'Ruby' },
1430
- java: { files: ['pom.xml'], content: {}, label: 'Java' },
1431
- kotlin: { files: ['build.gradle.kts'], content: {}, label: 'Kotlin' },
1432
- swift: { files: ['Package.swift'], content: {}, label: 'Swift' },
1433
- terraform: { files: ['main.tf', 'terraform'], content: {}, label: 'Terraform' },
1434
- kubernetes: { files: ['k8s', 'kubernetes', 'helm'], content: {}, label: 'Kubernetes' },
1435
- cpp: { files: ['CMakeLists.txt', 'Makefile', '.clang-format'], content: {}, label: 'C++' },
1436
- bazel: { files: ['BUILD', 'WORKSPACE', 'BUILD.bazel', 'WORKSPACE.bazel'], content: {}, label: 'Bazel' },
1437
- deno: { files: ['deno.json', 'deno.jsonc', 'deno.lock'], content: {}, label: 'Deno' },
1438
- bun: { files: ['bun.lockb', 'bunfig.toml'], content: {}, label: 'Bun' },
1439
- elixir: { files: ['mix.exs'], content: {}, label: 'Elixir' },
1440
- astro: { files: ['astro.config.mjs', 'astro.config.ts'], content: {}, label: 'Astro' },
1441
- remix: { files: ['remix.config.js', 'remix.config.ts'], content: {}, label: 'Remix' },
1442
- nestjs: { files: ['nest-cli.json'], content: {}, label: 'NestJS' },
1443
- laravel: { files: ['artisan'], content: {}, label: 'Laravel' },
1444
- dotnet: { files: ['global.json', 'Directory.Build.props'], content: {}, label: '.NET' },
1445
- };
1446
-
1447
- attachSourceUrls('claude', TECHNIQUES);
1448
-
1449
- module.exports = { TECHNIQUES, STACKS, containsEmbeddedSecret };
1
+ /**
2
+ * CLAUDEX Technique Database
3
+ * Curated from 1107 verified techniques, filtered to actionable setup recommendations.
4
+ * Each technique includes: what to check, how to fix, impact level.
5
+ */
6
+
7
+ function hasFrontendSignals(ctx) {
8
+ const pkg = ctx.fileContent('package.json') || '';
9
+ return /react|vue|angular|next|svelte|tailwind|vite|astro/i.test(pkg) ||
10
+ ctx.files.some(f => /tailwind\.config|vite\.config|next\.config|svelte\.config|nuxt\.config|pages\/|components\/|app\//i.test(f));
11
+ }
12
+
13
+ const { containsEmbeddedSecret } = require('./secret-patterns');
14
+ const { attachSourceUrls } = require('./source-urls');
15
+ const { buildSupplementalChecks } = require('./supplemental-checks');
16
+
17
+ const CLAUDE_SUPPLEMENTAL_SOURCE_URLS = {
18
+ 'testing-strategy': 'https://code.claude.com/docs/en/common-workflows',
19
+ 'code-quality': 'https://code.claude.com/docs/en/best-practices',
20
+ 'api-design': 'https://code.claude.com/docs/en/best-practices',
21
+ database: 'https://code.claude.com/docs/en/common-workflows',
22
+ authentication: 'https://code.claude.com/docs/en/permissions',
23
+ monitoring: 'https://code.claude.com/docs/en/common-workflows',
24
+ 'dependency-management': 'https://code.claude.com/docs/en/best-practices',
25
+ 'cost-optimization': 'https://code.claude.com/docs/en/memory',
26
+ };
27
+
28
+ const TECHNIQUES = {
29
+ // ============================================================
30
+ // === MEMORY & CONTEXT (category: 'memory') ==================
31
+ // ============================================================
32
+
33
+ claudeMd: {
34
+ id: 1,
35
+ name: 'CLAUDE.md project instructions',
36
+ check: (ctx) => ctx.files.includes('CLAUDE.md') || ctx.files.includes('.claude/CLAUDE.md'),
37
+ impact: 'critical',
38
+ rating: 5,
39
+ category: 'memory',
40
+ fix: 'Create CLAUDE.md with project-specific instructions, build commands, and coding conventions.',
41
+ template: 'claude-md'
42
+ },
43
+
44
+ mermaidArchitecture: {
45
+ id: 51,
46
+ name: 'Mermaid architecture diagram',
47
+ check: (ctx) => {
48
+ const md = ctx.claudeMdContent() || '';
49
+ return md.includes('mermaid') || md.includes('graph ') || md.includes('flowchart ');
50
+ },
51
+ impact: 'high',
52
+ rating: 5,
53
+ category: 'memory',
54
+ fix: 'Add a Mermaid diagram to CLAUDE.md showing project architecture. Saves 73% tokens vs prose.',
55
+ template: 'mermaid'
56
+ },
57
+
58
+ pathRules: {
59
+ id: 3,
60
+ name: 'Path-specific rules',
61
+ check: (ctx) => ctx.hasDir('.claude/rules') && ctx.dirFiles('.claude/rules').length > 0,
62
+ impact: 'medium',
63
+ rating: 4,
64
+ category: 'memory',
65
+ fix: 'Add rules for different file types (frontend vs backend conventions).',
66
+ template: 'rules'
67
+ },
68
+
69
+ importSyntax: {
70
+ id: 763,
71
+ name: 'CLAUDE.md uses @path imports for modularity',
72
+ check: (ctx) => {
73
+ const md = ctx.claudeMdContent() || '';
74
+ // Current syntax is @path/to/file (no "import" keyword)
75
+ return /@\S+\.(md|txt|json|yml|yaml|toml)/i.test(md) || /@\w+\//.test(md);
76
+ },
77
+ impact: 'medium',
78
+ rating: 4,
79
+ category: 'memory',
80
+ fix: 'Use @path syntax in CLAUDE.md to split instructions into focused modules (e.g. @docs/coding-style.md). You can also use .claude/rules/ for path-specific rules.',
81
+ template: null
82
+ },
83
+
84
+ underlines200: {
85
+ id: 681,
86
+ name: 'CLAUDE.md under 200 lines (concise)',
87
+ check: (ctx) => {
88
+ const md = ctx.claudeMdContent() || '';
89
+ return md.split('\n').length <= 200;
90
+ },
91
+ impact: 'medium',
92
+ rating: 4,
93
+ category: 'memory',
94
+ fix: 'Keep CLAUDE.md under 200 lines. Use @import or .claude/rules/ to split large instructions.',
95
+ template: null
96
+ },
97
+
98
+ // ============================================================
99
+ // === QUALITY & TESTING (category: 'quality') ================
100
+ // ============================================================
101
+
102
+ verificationLoop: {
103
+ id: 93,
104
+ name: 'Verification criteria in CLAUDE.md',
105
+ check: (ctx) => {
106
+ const md = ctx.claudeMdContent() || '';
107
+ return /\b(npm test|yarn test|pnpm test|pytest|go test|make test|npm run lint|yarn lint|npx |ruff |eslint)\b/i.test(md) ||
108
+ /\b(test command|lint command|build command|verify|run tests|run lint)\b/i.test(md);
109
+ },
110
+ impact: 'critical',
111
+ rating: 5,
112
+ category: 'quality',
113
+ fix: 'Add test/lint/build commands to CLAUDE.md so Claude can verify its own work.',
114
+ template: null
115
+ },
116
+
117
+ testCommand: {
118
+ id: 93001,
119
+ name: 'CLAUDE.md contains a test command',
120
+ check: (ctx) => {
121
+ const md = ctx.claudeMdContent() || '';
122
+ return /npm test|pytest|jest|vitest|cargo test|go test|mix test|rspec/.test(md);
123
+ },
124
+ impact: 'high',
125
+ rating: 5,
126
+ category: 'quality',
127
+ fix: 'Add an explicit test command to CLAUDE.md (e.g. "Run `npm test` before committing").',
128
+ template: null
129
+ },
130
+
131
+ lintCommand: {
132
+ id: 93002,
133
+ name: 'CLAUDE.md contains a lint command',
134
+ check: (ctx) => {
135
+ const md = ctx.claudeMdContent() || '';
136
+ return /eslint|prettier|ruff|black|clippy|golangci-lint|rubocop|npm run lint|yarn lint|pnpm lint|bun lint/.test(md);
137
+ },
138
+ impact: 'high',
139
+ rating: 4,
140
+ category: 'quality',
141
+ fix: 'Add a lint command to CLAUDE.md so Claude auto-formats and checks code style.',
142
+ template: null
143
+ },
144
+
145
+ buildCommand: {
146
+ id: 93003,
147
+ name: 'CLAUDE.md contains a build command',
148
+ check: (ctx) => {
149
+ const md = ctx.claudeMdContent() || '';
150
+ return /npm run build|cargo build|go build|make|tsc|gradle build|mvn compile/.test(md);
151
+ },
152
+ impact: 'medium',
153
+ rating: 4,
154
+ category: 'quality',
155
+ fix: 'Add a build command to CLAUDE.md so Claude can verify compilation before committing.',
156
+ template: null
157
+ },
158
+
159
+ // ============================================================
160
+ // === GIT SAFETY (category: 'git') ===========================
161
+ // ============================================================
162
+
163
+ gitIgnoreClaudeTracked: {
164
+ id: 976,
165
+ name: '.claude/ tracked in git',
166
+ check: (ctx) => {
167
+ if (!ctx.fileContent('.gitignore')) return true; // no gitignore = ok
168
+ const lines = ctx.fileContent('.gitignore')
169
+ .split(/\r?\n/)
170
+ .map(line => line.trim())
171
+ .filter(line => line && !line.startsWith('#'));
172
+ const ignoresClaudeDir = lines.some(line => /^(\/|\*\*\/)?\.claude\/?$/.test(line));
173
+ const unignoresClaudeDir = lines.some(line => /^!(\/)?\.claude(\/|\*\*)?$/.test(line));
174
+ return !ignoresClaudeDir || unignoresClaudeDir;
175
+ },
176
+ impact: 'high',
177
+ rating: 4,
178
+ category: 'git',
179
+ fix: 'Remove .claude/ from .gitignore (keep .claude/settings.local.json ignored).',
180
+ template: null
181
+ },
182
+
183
+ gitIgnoreEnv: {
184
+ id: 917,
185
+ name: '.gitignore blocks .env files',
186
+ check: (ctx) => {
187
+ const gitignore = ctx.fileContent('.gitignore') || '';
188
+ return gitignore.includes('.env');
189
+ },
190
+ impact: 'critical',
191
+ rating: 5,
192
+ category: 'git',
193
+ fix: 'Add .env to .gitignore to prevent leaking secrets.',
194
+ template: null
195
+ },
196
+
197
+ gitIgnoreNodeModules: {
198
+ id: 91701,
199
+ name: '.gitignore blocks node_modules',
200
+ check: (ctx) => {
201
+ const hasNodeSignals = ctx.files.includes('package.json') ||
202
+ ctx.files.includes('tsconfig.json') ||
203
+ ctx.files.some(f => /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config/i.test(f));
204
+ if (!hasNodeSignals) return null;
205
+ const gitignore = ctx.fileContent('.gitignore') || '';
206
+ return gitignore.includes('node_modules');
207
+ },
208
+ impact: 'high',
209
+ rating: 4,
210
+ category: 'git',
211
+ fix: 'Add node_modules/ to .gitignore.',
212
+ template: null
213
+ },
214
+
215
+ noSecretsInClaude: {
216
+ id: 1039,
217
+ name: 'CLAUDE.md has no embedded API keys',
218
+ check: (ctx) => {
219
+ const md = ctx.claudeMdContent() || '';
220
+ return !containsEmbeddedSecret(md);
221
+ },
222
+ impact: 'critical',
223
+ rating: 5,
224
+ category: 'git',
225
+ fix: 'Remove API keys from CLAUDE.md. Use environment variables or .env files instead.',
226
+ template: null
227
+ },
228
+
229
+ // ============================================================
230
+ // === WORKFLOW (category: 'workflow') =========================
231
+ // ============================================================
232
+
233
+ customCommands: {
234
+ id: 20,
235
+ name: 'Custom slash commands',
236
+ check: (ctx) => ctx.hasDir('.claude/commands') && ctx.dirFiles('.claude/commands').length > 0,
237
+ impact: 'high',
238
+ rating: 4,
239
+ category: 'workflow',
240
+ fix: 'Create custom commands for repeated workflows (/test, /deploy, /review).',
241
+ template: 'commands'
242
+ },
243
+
244
+ multipleCommands: {
245
+ id: 20001,
246
+ name: '3+ slash commands for rich workflow',
247
+ check: (ctx) => ctx.hasDir('.claude/commands') && ctx.dirFiles('.claude/commands').length >= 3,
248
+ impact: 'medium',
249
+ rating: 4,
250
+ category: 'workflow',
251
+ fix: 'Add at least 3 slash commands to cover your main workflows (test, deploy, review, etc.).',
252
+ template: 'commands'
253
+ },
254
+
255
+ deployCommand: {
256
+ id: 20002,
257
+ name: 'Has /deploy or /release command',
258
+ check: (ctx) => {
259
+ if (!ctx.hasDir('.claude/commands')) return false;
260
+ const files = ctx.dirFiles('.claude/commands');
261
+ return files.some(f => /deploy|release/i.test(f));
262
+ },
263
+ impact: 'medium',
264
+ rating: 4,
265
+ category: 'workflow',
266
+ fix: 'Create a /deploy or /release command for one-click deployments.',
267
+ template: null
268
+ },
269
+
270
+ reviewCommand: {
271
+ id: 20003,
272
+ name: 'Has /review command',
273
+ check: (ctx) => {
274
+ if (!ctx.hasDir('.claude/commands')) return false;
275
+ const files = ctx.dirFiles('.claude/commands');
276
+ return files.some(f => /review/i.test(f));
277
+ },
278
+ impact: 'medium',
279
+ rating: 4,
280
+ category: 'workflow',
281
+ fix: 'Create a /review command for code review workflows.',
282
+ template: null
283
+ },
284
+
285
+ skills: {
286
+ id: 21,
287
+ name: 'Custom skills',
288
+ check: (ctx) => {
289
+ // Skills use directory-per-skill structure: .claude/skills/<name>/SKILL.md
290
+ if (!ctx.hasDir('.claude/skills')) return false;
291
+ const dirs = ctx.dirFiles('.claude/skills');
292
+ // Check for SKILL.md inside skill directories
293
+ for (const d of dirs) {
294
+ if (ctx.fileContent(`.claude/skills/${d}/SKILL.md`)) return true;
295
+ }
296
+ // Fallback: any files in skills dir (legacy .claude/commands/ also works)
297
+ return dirs.length > 0;
298
+ },
299
+ impact: 'medium',
300
+ rating: 4,
301
+ category: 'workflow',
302
+ fix: 'Create skills at .claude/skills/<name>/SKILL.md with YAML frontmatter (name, description). Each skill is a directory with a SKILL.md file.',
303
+ template: 'skills'
304
+ },
305
+
306
+ multipleSkills: {
307
+ id: 2101,
308
+ name: '2+ skills for specialization',
309
+ check: (ctx) => {
310
+ if (!ctx.hasDir('.claude/skills')) return false;
311
+ return ctx.dirFiles('.claude/skills').length >= 2;
312
+ },
313
+ impact: 'medium',
314
+ rating: 4,
315
+ category: 'workflow',
316
+ fix: 'Add at least 2 skills covering different workflows (e.g. code-review, test-writer).',
317
+ template: 'skills'
318
+ },
319
+
320
+ agents: {
321
+ id: 22,
322
+ name: 'Custom agents',
323
+ check: (ctx) => ctx.hasDir('.claude/agents') && ctx.dirFiles('.claude/agents').length > 0,
324
+ impact: 'medium',
325
+ rating: 4,
326
+ category: 'workflow',
327
+ fix: 'Create specialized agents (security-reviewer, test-writer) in .claude/agents/.',
328
+ template: 'agents'
329
+ },
330
+
331
+ multipleAgents: {
332
+ id: 2201,
333
+ name: '2+ agents for delegation',
334
+ check: (ctx) => ctx.hasDir('.claude/agents') && ctx.dirFiles('.claude/agents').length >= 2,
335
+ impact: 'medium',
336
+ rating: 4,
337
+ category: 'workflow',
338
+ fix: 'Add at least 2 agents for specialized tasks (e.g. security-reviewer, test-writer).',
339
+ template: 'agents'
340
+ },
341
+
342
+ multipleRules: {
343
+ id: 301,
344
+ name: '2+ rules files for granular control',
345
+ check: (ctx) => ctx.hasDir('.claude/rules') && ctx.dirFiles('.claude/rules').length >= 2,
346
+ impact: 'medium',
347
+ rating: 4,
348
+ category: 'workflow',
349
+ fix: 'Add path-specific rules for different parts of the codebase (frontend, backend, tests).',
350
+ template: 'rules'
351
+ },
352
+
353
+ // ============================================================
354
+ // === SECURITY (category: 'security') ========================
355
+ // ============================================================
356
+
357
+ settingsPermissions: {
358
+ id: 24,
359
+ name: 'Permission configuration',
360
+ check: (ctx) => {
361
+ // Prefer local (effective config) — any settings file with permissions passes
362
+ const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
363
+ return !!(settings && settings.permissions);
364
+ },
365
+ impact: 'medium',
366
+ rating: 4,
367
+ category: 'security',
368
+ fix: 'Configure allow/deny permission lists for safe tool usage.',
369
+ template: null
370
+ },
371
+
372
+ permissionDeny: {
373
+ id: 2401,
374
+ name: 'Deny rules configured in permissions',
375
+ check: (ctx) => {
376
+ const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
377
+ if (!settings || !settings.permissions) return false;
378
+ const deny = settings.permissions.deny;
379
+ return Array.isArray(deny) && deny.length > 0;
380
+ },
381
+ impact: 'high',
382
+ rating: 5,
383
+ category: 'security',
384
+ fix: 'Add permissions.deny rules to block dangerous operations (e.g. rm -rf, dropping databases).',
385
+ template: null
386
+ },
387
+
388
+ noBypassPermissions: {
389
+ id: 2402,
390
+ name: 'Default mode is not bypassPermissions',
391
+ check: (ctx) => {
392
+ // Check shared settings first (committed to git) — if the shared baseline
393
+ // is safe, a personal settings.local.json override should not fail the audit.
394
+ const shared = ctx.jsonFile('.claude/settings.json');
395
+ if (shared && shared.permissions) {
396
+ return shared.permissions.defaultMode !== 'bypassPermissions';
397
+ }
398
+ const local = ctx.jsonFile('.claude/settings.local.json');
399
+ if (!local || !local.permissions) return null;
400
+ return local.permissions.defaultMode !== 'bypassPermissions';
401
+ },
402
+ impact: 'critical',
403
+ rating: 5,
404
+ category: 'security',
405
+ fix: 'Do not set defaultMode to bypassPermissions. Use explicit allow rules instead.',
406
+ template: null
407
+ },
408
+
409
+ secretsProtection: {
410
+ id: 1096,
411
+ name: 'Secrets protection configured',
412
+ check: (ctx) => {
413
+ // Prefer shared settings.json (committed) over local override
414
+ const settings = ctx.jsonFile('.claude/settings.json') || ctx.jsonFile('.claude/settings.local.json');
415
+ if (!settings || !settings.permissions) return false;
416
+ const deny = JSON.stringify(settings.permissions.deny || []);
417
+ return deny.includes('.env') || deny.includes('secrets');
418
+ },
419
+ impact: 'critical',
420
+ rating: 5,
421
+ category: 'security',
422
+ fix: 'Add permissions.deny rules to block reading .env files and secrets directories.',
423
+ template: null
424
+ },
425
+
426
+ securityReview: {
427
+ id: 1031,
428
+ name: 'Security review command awareness',
429
+ check: (ctx) => {
430
+ const md = ctx.claudeMdContent() || '';
431
+ return md.includes('security') || md.includes('/security-review');
432
+ },
433
+ impact: 'high',
434
+ rating: 5,
435
+ category: 'security',
436
+ fix: 'Add /security-review to your workflow. Claude Code has built-in OWASP Top 10 scanning.',
437
+ template: null
438
+ },
439
+
440
+ // ============================================================
441
+ // === AUTOMATION (category: 'automation') =====================
442
+ // ============================================================
443
+
444
+ hooks: {
445
+ id: 19,
446
+ name: 'Hooks for automation',
447
+ check: (ctx) => {
448
+ // Hooks are configured in settings.json (not .claude/hooks/ directory)
449
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
450
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
451
+ return !!(shared.hooks && Object.keys(shared.hooks).length > 0) || !!(local.hooks && Object.keys(local.hooks).length > 0);
452
+ },
453
+ impact: 'high',
454
+ rating: 4,
455
+ category: 'automation',
456
+ fix: 'Add hooks in .claude/settings.json under the "hooks" key. Supported events: PreToolUse, PostToolUse, Notification, Stop, StopFailure, SubagentStop, and more.',
457
+ template: 'hooks'
458
+ },
459
+
460
+ hooksInSettings: {
461
+ id: 8801,
462
+ name: 'Hooks configured in settings',
463
+ check: (ctx) => {
464
+ const shared = ctx.jsonFile('.claude/settings.json');
465
+ const local = ctx.jsonFile('.claude/settings.local.json');
466
+ const hasSharedHooks = shared && shared.hooks && Object.keys(shared.hooks).length > 0;
467
+ const hasLocalHooks = local && local.hooks && Object.keys(local.hooks).length > 0;
468
+ return hasSharedHooks || hasLocalHooks;
469
+ },
470
+ impact: 'high',
471
+ rating: 4,
472
+ category: 'automation',
473
+ fix: 'Add hooks in .claude/settings.json for automated enforcement (lint-on-save, test-on-commit).',
474
+ template: 'hooks'
475
+ },
476
+
477
+ preToolUseHook: {
478
+ id: 8802,
479
+ name: 'PreToolUse hook configured',
480
+ check: (ctx) => {
481
+ const shared = ctx.jsonFile('.claude/settings.json');
482
+ const local = ctx.jsonFile('.claude/settings.local.json');
483
+ return !!(shared?.hooks?.PreToolUse || local?.hooks?.PreToolUse);
484
+ },
485
+ impact: 'high',
486
+ rating: 4,
487
+ category: 'automation',
488
+ fix: 'Add PreToolUse hooks for validation before tool calls (e.g. block writes to protected files).',
489
+ template: null
490
+ },
491
+
492
+ postToolUseHook: {
493
+ id: 8803,
494
+ name: 'PostToolUse hook configured',
495
+ check: (ctx) => {
496
+ const shared = ctx.jsonFile('.claude/settings.json');
497
+ const local = ctx.jsonFile('.claude/settings.local.json');
498
+ return !!(shared?.hooks?.PostToolUse || local?.hooks?.PostToolUse);
499
+ },
500
+ impact: 'high',
501
+ rating: 4,
502
+ category: 'automation',
503
+ fix: 'Add PostToolUse hooks for auto-lint or auto-format after file writes.',
504
+ template: null
505
+ },
506
+
507
+ sessionStartHook: {
508
+ id: 8804,
509
+ name: 'SessionStart hook configured',
510
+ check: (ctx) => {
511
+ const shared = ctx.jsonFile('.claude/settings.json');
512
+ const local = ctx.jsonFile('.claude/settings.local.json');
513
+ if (!(shared?.hooks || local?.hooks)) return false;
514
+ return !!(shared?.hooks?.SessionStart || local?.hooks?.SessionStart);
515
+ },
516
+ impact: 'medium',
517
+ rating: 4,
518
+ category: 'automation',
519
+ fix: 'Add a SessionStart hook for initialization tasks (log rotation, state loading, etc.).',
520
+ template: null
521
+ },
522
+
523
+ // ============================================================
524
+ // === DESIGN (category: 'design') ============================
525
+ // ============================================================
526
+
527
+ frontendDesignSkill: {
528
+ id: 1025,
529
+ name: 'Frontend design skill for anti-AI-slop',
530
+ check: (ctx) => {
531
+ if (!hasFrontendSignals(ctx)) return null;
532
+ const md = ctx.claudeMdContent() || '';
533
+ return md.includes('frontend_aesthetics') || md.includes('anti-AI-slop') || md.includes('frontend-design');
534
+ },
535
+ impact: 'medium',
536
+ rating: 5,
537
+ category: 'design',
538
+ fix: 'Install the official frontend-design skill for better UI output quality.',
539
+ template: null
540
+ },
541
+
542
+ tailwindMention: {
543
+ id: 102501,
544
+ name: 'Tailwind CSS configured',
545
+ check: (ctx) => {
546
+ if (!hasFrontendSignals(ctx)) return null;
547
+ const pkg = ctx.fileContent('package.json') || '';
548
+ return pkg.includes('tailwind') ||
549
+ ctx.files.some(f => /tailwind\.config/.test(f));
550
+ },
551
+ impact: 'low',
552
+ rating: 3,
553
+ category: 'design',
554
+ fix: 'Consider adding Tailwind CSS for rapid, consistent UI styling with Claude.',
555
+ template: null
556
+ },
557
+
558
+ // ============================================================
559
+ // === DEVOPS (category: 'devops') ============================
560
+ // ============================================================
561
+
562
+ dockerfile: {
563
+ id: 399,
564
+ name: 'Has Dockerfile',
565
+ check: (ctx) => ctx.files.some(f => /^Dockerfile/i.test(f)),
566
+ impact: 'medium',
567
+ rating: 3,
568
+ category: 'devops',
569
+ fix: 'Add a Dockerfile for containerized builds and deployments.',
570
+ template: null
571
+ },
572
+
573
+ dockerCompose: {
574
+ id: 39901,
575
+ name: 'Has docker-compose.yml',
576
+ check: (ctx) => ctx.files.some(f => /^docker-compose\.(yml|yaml)$/i.test(f)),
577
+ impact: 'medium',
578
+ rating: 3,
579
+ category: 'devops',
580
+ fix: 'Add docker-compose.yml for multi-service local development.',
581
+ template: null
582
+ },
583
+
584
+ ciPipeline: {
585
+ id: 260,
586
+ name: 'CI pipeline configured',
587
+ check: (ctx) => ctx.hasDir('.github/workflows') || ctx.hasDir('.circleci') ||
588
+ ctx.files.includes('.gitlab-ci.yml') || ctx.files.includes('Jenkinsfile') ||
589
+ ctx.files.includes('.travis.yml') || ctx.files.includes('bitbucket-pipelines.yml'),
590
+ impact: 'high',
591
+ rating: 4,
592
+ category: 'devops',
593
+ fix: 'Add a CI pipeline (GitHub Actions, GitLab CI, CircleCI, etc.) for automated testing and deployment.',
594
+ template: null
595
+ },
596
+
597
+ terraformFiles: {
598
+ id: 397,
599
+ name: 'Infrastructure as Code (Terraform)',
600
+ check: (ctx) => ctx.files.some(f => /\.tf$/.test(f)) || ctx.files.includes('main.tf'),
601
+ impact: 'medium',
602
+ rating: 3,
603
+ category: 'devops',
604
+ fix: 'Add Terraform files for infrastructure-as-code management.',
605
+ template: null
606
+ },
607
+
608
+ // ============================================================
609
+ // === PROJECT HYGIENE (category: 'hygiene') ==================
610
+ // ============================================================
611
+
612
+ readme: {
613
+ id: 416,
614
+ name: 'Has README.md',
615
+ check: (ctx) => ctx.files.some(f => /^readme\.md$/i.test(f)),
616
+ impact: 'high',
617
+ rating: 4,
618
+ category: 'hygiene',
619
+ fix: 'Add a README.md with project overview, setup instructions, and usage.',
620
+ template: null
621
+ },
622
+
623
+ changelog: {
624
+ id: 417,
625
+ name: 'Has CHANGELOG.md',
626
+ check: (ctx) => ctx.files.some(f => /^changelog\.md$/i.test(f)),
627
+ impact: 'low',
628
+ rating: 3,
629
+ category: 'hygiene',
630
+ fix: 'Add a CHANGELOG.md to track notable changes across versions.',
631
+ template: null
632
+ },
633
+
634
+ contributing: {
635
+ id: 418,
636
+ name: 'Has CONTRIBUTING.md',
637
+ check: (ctx) => ctx.files.some(f => /^contributing\.md$/i.test(f)),
638
+ impact: 'low',
639
+ rating: 3,
640
+ category: 'hygiene',
641
+ fix: 'Add a CONTRIBUTING.md with contribution guidelines and code standards.',
642
+ template: null
643
+ },
644
+
645
+ license: {
646
+ id: 434,
647
+ name: 'Has LICENSE file',
648
+ check: (ctx) => ctx.files.some(f => /^license/i.test(f)),
649
+ impact: 'low',
650
+ rating: 3,
651
+ category: 'hygiene',
652
+ fix: 'Add a LICENSE file to clarify usage rights.',
653
+ template: null
654
+ },
655
+
656
+ editorconfig: {
657
+ id: 5001,
658
+ name: 'Has .editorconfig',
659
+ check: (ctx) => ctx.files.includes('.editorconfig'),
660
+ impact: 'low',
661
+ rating: 3,
662
+ category: 'hygiene',
663
+ fix: 'Add .editorconfig for consistent formatting across editors and Claude.',
664
+ template: null
665
+ },
666
+
667
+ nvmrc: {
668
+ id: 5002,
669
+ name: 'Node version pinned',
670
+ check: (ctx) => {
671
+ const hasNodeSignals = ctx.files.includes('package.json') ||
672
+ ctx.files.includes('tsconfig.json') ||
673
+ ctx.files.some(f => /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config/i.test(f));
674
+ if (!hasNodeSignals) return null;
675
+ if (ctx.files.includes('.nvmrc') || ctx.files.includes('.node-version')) return true;
676
+ const pkg = ctx.jsonFile('package.json');
677
+ return !!(pkg && pkg.engines && pkg.engines.node);
678
+ },
679
+ impact: 'low',
680
+ rating: 3,
681
+ category: 'hygiene',
682
+ fix: 'Add .nvmrc, .node-version, or engines.node in package.json to pin Node version.',
683
+ template: null
684
+ },
685
+
686
+ // ============================================================
687
+ // === PERFORMANCE (category: 'performance') ==================
688
+ // ============================================================
689
+
690
+ compactionAwareness: {
691
+ id: 568,
692
+ name: 'CLAUDE.md mentions /compact or compaction',
693
+ check: (ctx) => {
694
+ const md = ctx.claudeMdContent() || '';
695
+ return /\/compact|compaction|context.*(limit|manage|budget)/i.test(md);
696
+ },
697
+ impact: 'medium',
698
+ rating: 4,
699
+ category: 'performance',
700
+ fix: 'Add compaction guidance to CLAUDE.md (e.g. "Run /compact when context is heavy").',
701
+ template: null
702
+ },
703
+
704
+ contextManagement: {
705
+ id: 45,
706
+ name: 'Context management awareness',
707
+ check: (ctx) => {
708
+ const md = ctx.claudeMdContent() || '';
709
+ return /context.*(manage|window|limit|budget|token)/i.test(md);
710
+ },
711
+ impact: 'medium',
712
+ rating: 4,
713
+ category: 'performance',
714
+ fix: 'Add context management tips to CLAUDE.md to help Claude stay within token limits.',
715
+ template: null
716
+ },
717
+
718
+ // ============================================================
719
+ // === MCP / TOOLS (category: 'tools') ========================
720
+ // ============================================================
721
+
722
+ mcpServers: {
723
+ id: 18,
724
+ name: 'MCP servers configured',
725
+ check: (ctx) => {
726
+ // MCP now lives in .mcp.json (project) and ~/.claude.json (user), NOT settings.json
727
+ const mcpJson = ctx.jsonFile('.mcp.json');
728
+ if (mcpJson && mcpJson.mcpServers && Object.keys(mcpJson.mcpServers).length > 0) return true;
729
+ // Fallback: check settings for legacy format
730
+ const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
731
+ return !!(settings && settings.mcpServers && Object.keys(settings.mcpServers).length > 0);
732
+ },
733
+ impact: 'medium',
734
+ rating: 3,
735
+ category: 'tools',
736
+ fix: 'Configure MCP servers in .mcp.json at project root. Use `claude mcp add` to add servers. Project-level MCP is committed to git for team sharing.',
737
+ template: null
738
+ },
739
+
740
+ multipleMcpServers: {
741
+ id: 1801,
742
+ name: '2+ MCP servers for rich tooling',
743
+ check: (ctx) => {
744
+ let count = 0;
745
+ const mcpJson = ctx.jsonFile('.mcp.json');
746
+ if (mcpJson && mcpJson.mcpServers) count += Object.keys(mcpJson.mcpServers).length;
747
+ const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
748
+ if (settings && settings.mcpServers) count += Object.keys(settings.mcpServers).length;
749
+ return count >= 2;
750
+ },
751
+ impact: 'medium',
752
+ rating: 4,
753
+ category: 'tools',
754
+ fix: 'Add at least 2 MCP servers for broader tool coverage (e.g. database + search).',
755
+ template: null
756
+ },
757
+
758
+ context7Mcp: {
759
+ id: 110,
760
+ name: 'Context7 MCP for real-time docs',
761
+ check: (ctx) => {
762
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
763
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
764
+ const mcp = ctx.jsonFile('.mcp.json') || {};
765
+ const all = { ...(shared.mcpServers || {}), ...(local.mcpServers || {}), ...(mcp.mcpServers || {}) };
766
+ if (Object.keys(all).length === 0) return false;
767
+ return Object.keys(all).some(k => /context7/i.test(k));
768
+ },
769
+ impact: 'medium',
770
+ rating: 4,
771
+ category: 'tools',
772
+ fix: 'Add Context7 MCP server for real-time documentation lookup (always up-to-date library docs).',
773
+ template: null
774
+ },
775
+
776
+ // ============================================================
777
+ // === PROMPTING (category: 'prompting') ======================
778
+ // ============================================================
779
+
780
+ xmlTags: {
781
+ id: 96,
782
+ name: 'XML tags for structured prompts',
783
+ check: (ctx) => {
784
+ const md = ctx.claudeMdContent() || '';
785
+ // Give credit for XML tags OR well-structured markdown with clear sections
786
+ const hasXml = md.includes('<constraints') || md.includes('<rules') ||
787
+ md.includes('<validation') || md.includes('<instructions');
788
+ const hasStructuredMd = (md.includes('## Rules') || md.includes('## Constraints') ||
789
+ md.includes('## Do not') || md.includes('## Never') || md.includes('## Important')) &&
790
+ md.split('\n').length > 20;
791
+ return hasXml || hasStructuredMd;
792
+ },
793
+ impact: 'medium',
794
+ rating: 4,
795
+ category: 'prompting',
796
+ fix: 'Add clear rules sections to CLAUDE.md. XML tags (<constraints>) are optional but improve clarity.',
797
+ template: null
798
+ },
799
+
800
+ fewShotExamples: {
801
+ id: 9,
802
+ name: 'CLAUDE.md contains code examples',
803
+ check: (ctx) => {
804
+ const md = ctx.claudeMdContent() || '';
805
+ return (md.match(/```/g) || []).length >= 2;
806
+ },
807
+ impact: 'high',
808
+ rating: 5,
809
+ category: 'prompting',
810
+ fix: 'Add code examples (few-shot) in CLAUDE.md to show preferred patterns and conventions.',
811
+ template: null
812
+ },
813
+
814
+ roleDefinition: {
815
+ id: 10,
816
+ name: 'CLAUDE.md defines a role or persona',
817
+ check: (ctx) => {
818
+ const md = ctx.claudeMdContent() || '';
819
+ return /^you are a |^your role is|^act as a |persona:|behave as a /im.test(md);
820
+ },
821
+ impact: 'medium',
822
+ rating: 4,
823
+ category: 'prompting',
824
+ fix: 'Define a role or persona in CLAUDE.md (e.g. "You are a senior backend engineer...").',
825
+ template: null
826
+ },
827
+
828
+ constraintBlocks: {
829
+ id: 9601,
830
+ name: 'XML constraint blocks in CLAUDE.md',
831
+ check: (ctx) => {
832
+ const md = ctx.claudeMdContent() || '';
833
+ return /<constraints|<rules|<requirements|<boundaries/i.test(md);
834
+ },
835
+ impact: 'high',
836
+ rating: 5,
837
+ category: 'prompting',
838
+ fix: 'Wrap critical rules in <constraints> XML blocks for 40% better adherence.',
839
+ template: null
840
+ },
841
+
842
+ // ============================================================
843
+ // === FEATURES (category: 'features') ========================
844
+ // ============================================================
845
+
846
+ channelsAwareness: {
847
+ id: 1102,
848
+ name: 'Claude Code Channels awareness',
849
+ check: (ctx) => {
850
+ const md = ctx.claudeMdContent() || '';
851
+ const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
852
+ const settingsStr = JSON.stringify(settings || {});
853
+ return /\bchannels?\b.*\b(telegram|discord|imessage|slack|bridge)\b|\b(telegram|discord|imessage|slack|bridge)\b.*\bchannels?\b/i.test(md) || settingsStr.includes('channels');
854
+ },
855
+ impact: 'low',
856
+ rating: 3,
857
+ category: 'features',
858
+ fix: 'Claude Code Channels (v2.1.80+) bridges Telegram/Discord/iMessage to your session.',
859
+ template: null
860
+ },
861
+
862
+ // ============================================================
863
+ // === QUALITY CHECKS FOR VETERANS (category: 'quality-deep')
864
+ // These check HOW GOOD your config is, not just IF it exists.
865
+ // ============================================================
866
+
867
+ claudeMdFreshness: {
868
+ id: 2001,
869
+ name: 'CLAUDE.md mentions current Claude features',
870
+ check: (ctx) => {
871
+ const md = ctx.claudeMdContent() || '';
872
+ if (md.length < 50) return false; // too short to evaluate
873
+ // Check for awareness of features from 2025+
874
+ const modernFeatures = ['hook', 'skill', 'agent', 'subagent', 'mcp', 'compact', '/clear', 'extended thinking', 'tool_use', 'worktree'];
875
+ const found = modernFeatures.filter(f => md.toLowerCase().includes(f));
876
+ return found.length >= 2; // knows at least 2 modern features
877
+ },
878
+ impact: 'medium',
879
+ rating: 4,
880
+ category: 'quality-deep',
881
+ fix: 'Your CLAUDE.md may be outdated. Modern Claude Code supports hooks, skills, agents, MCP, worktrees, and extended thinking. Mention the ones you use.',
882
+ template: null
883
+ },
884
+
885
+ // claudeMdNotOverlong removed duplicate of underlines200 (id 681)
886
+
887
+ claudeLocalMd: {
888
+ id: 2002,
889
+ name: 'CLAUDE.local.md for personal overrides',
890
+ check: (ctx) => {
891
+ // CLAUDE.local.md is for personal, non-committed overrides
892
+ return ctx.files.includes('CLAUDE.local.md') || ctx.files.includes('.claude/CLAUDE.local.md');
893
+ },
894
+ impact: 'low',
895
+ rating: 2,
896
+ category: 'memory',
897
+ fix: 'Create CLAUDE.local.md for personal preferences that should not be committed (add to .gitignore).',
898
+ template: null
899
+ },
900
+
901
+ claudeMdNoContradictions: {
902
+ id: 2003,
903
+ name: 'CLAUDE.md has no obvious contradictions',
904
+ check: (ctx) => {
905
+ const md = ctx.claudeMdContent();
906
+ if (!md || md.length < 50) return false; // no CLAUDE.md or too short = not passing
907
+ // Check for common contradictions
908
+ // Check for contradictions on the SAME topic (same line or adjacent sentence)
909
+ const lines = md.split('\n');
910
+ let hasContradiction = false;
911
+ for (const line of lines) {
912
+ if (/\balways\b.*\bnever\b|\bnever\b.*\balways\b/i.test(line)) {
913
+ hasContradiction = true;
914
+ break;
915
+ }
916
+ }
917
+ const hasBothStyles = /\buse tabs\b/i.test(md) && /\buse spaces\b/i.test(md);
918
+ return !hasContradiction && !hasBothStyles;
919
+ },
920
+ impact: 'high',
921
+ rating: 4,
922
+ category: 'quality-deep',
923
+ fix: 'CLAUDE.md may contain contradictory instructions. Review for conflicting rules (e.g., "always X" and "never X" about the same topic).',
924
+ template: null
925
+ },
926
+
927
+ hooksAreSpecific: {
928
+ id: 2004,
929
+ name: 'Hooks use specific matchers (not catch-all)',
930
+ check: (ctx) => {
931
+ const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
932
+ if (!settings || !settings.hooks) return null; // no hooks = not applicable
933
+ const hookStr = JSON.stringify(settings.hooks);
934
+ // Check that hooks have matchers, not just catch-all
935
+ return hookStr.includes('matcher');
936
+ },
937
+ impact: 'medium',
938
+ rating: 3,
939
+ category: 'quality-deep',
940
+ fix: 'Hooks without matchers run on every tool call. Use matchers like "Write|Edit" or "Bash" to target specific tools.',
941
+ template: null
942
+ },
943
+
944
+ // permissionsNotBypassed removed - duplicate of noBypassPermissions (#24)
945
+
946
+ commandsUseArguments: {
947
+ id: 2006,
948
+ name: 'Commands use $ARGUMENTS for flexibility',
949
+ check: (ctx) => {
950
+ if (!ctx.hasDir('.claude/commands')) return null; // not applicable
951
+ const files = ctx.dirFiles('.claude/commands');
952
+ if (files.length === 0) return null;
953
+ // Check if at least one command uses $ARGUMENTS
954
+ for (const f of files) {
955
+ const content = ctx.fileContent(`.claude/commands/${f}`) || '';
956
+ if (content.includes('$ARGUMENTS') || content.includes('$arguments')) return true;
957
+ }
958
+ return false;
959
+ },
960
+ impact: 'medium',
961
+ rating: 3,
962
+ category: 'quality-deep',
963
+ fix: 'Commands without $ARGUMENTS are static. Use $ARGUMENTS to make them flexible: "Fix the issue: $ARGUMENTS"',
964
+ template: null
965
+ },
966
+
967
+ agentsHaveMaxTurns: {
968
+ id: 2007,
969
+ name: 'Subagents have max-turns limit',
970
+ check: (ctx) => {
971
+ if (!ctx.hasDir('.claude/agents')) return null;
972
+ const files = ctx.dirFiles('.claude/agents');
973
+ if (files.length === 0) return null;
974
+ for (const f of files) {
975
+ const content = ctx.fileContent(`.claude/agents/${f}`) || '';
976
+ // Current frontmatter uses kebab-case: max-turns (also accept legacy maxTurns)
977
+ if (!content.includes('max-turns') && !content.includes('maxTurns')) return false;
978
+ }
979
+ return true;
980
+ },
981
+ impact: 'medium',
982
+ rating: 3,
983
+ category: 'quality-deep',
984
+ fix: 'Subagents without max-turns can run indefinitely. Add "max-turns: 50" to subagent YAML frontmatter.',
985
+ template: null
986
+ },
987
+
988
+ securityReviewInWorkflow: {
989
+ id: 2008,
990
+ name: '/security-review command or workflow',
991
+ check: (ctx) => {
992
+ const hasCommand = ctx.hasDir('.claude/commands') &&
993
+ (ctx.dirFiles('.claude/commands') || []).some(f => f.includes('security') || f.includes('review'));
994
+ const md = ctx.claudeMdContent() || '';
995
+ const hasExplicitRef = /\/security-review|security review command|security workflow/i.test(md);
996
+ return hasCommand || hasExplicitRef;
997
+ },
998
+ impact: 'medium',
999
+ rating: 4,
1000
+ category: 'quality-deep',
1001
+ fix: 'Claude Code has built-in /security-review (OWASP Top 10). Add it to your workflow or create a /security command.',
1002
+ template: null
1003
+ },
1004
+
1005
+ // --- New checks: testing depth ---
1006
+ testCoverage: {
1007
+ id: 2010,
1008
+ name: 'Test coverage or strategy mentioned',
1009
+ check: (ctx) => {
1010
+ const md = ctx.claudeMdContent() || '';
1011
+ return /coverage|test.*strateg|e2e|integration test|unit test/i.test(md);
1012
+ },
1013
+ impact: 'medium', rating: 3, category: 'quality',
1014
+ fix: 'Mention your testing strategy in CLAUDE.md (unit, integration, E2E, coverage targets).',
1015
+ template: null
1016
+ },
1017
+
1018
+ // --- New checks: agent depth ---
1019
+ agentHasAllowedTools: {
1020
+ id: 2011,
1021
+ name: 'At least one subagent restricts tools',
1022
+ check: (ctx) => {
1023
+ if (!ctx.hasDir('.claude/agents')) return null;
1024
+ const files = ctx.dirFiles('.claude/agents');
1025
+ if (files.length === 0) return null;
1026
+ for (const f of files) {
1027
+ const content = ctx.fileContent(`.claude/agents/${f}`) || '';
1028
+ // Current frontmatter uses allowed-tools (also accept legacy tools:)
1029
+ if (/allowed-tools:/i.test(content) || /tools:\s*\[/.test(content)) return true;
1030
+ }
1031
+ return false;
1032
+ },
1033
+ impact: 'medium', rating: 3, category: 'workflow',
1034
+ fix: 'Add allowed-tools to subagent frontmatter (e.g. allowed-tools: Read Grep Bash) for safer delegation.',
1035
+ template: null
1036
+ },
1037
+
1038
+ // --- New checks: memory / auto-memory ---
1039
+ autoMemoryAwareness: {
1040
+ id: 2012,
1041
+ name: 'Auto-memory or memory management mentioned',
1042
+ check: (ctx) => {
1043
+ const md = ctx.claudeMdContent() || '';
1044
+ return /auto.?memory|memory.*manage|remember|persistent.*context/i.test(md);
1045
+ },
1046
+ impact: 'low', rating: 3, category: 'memory',
1047
+ fix: 'Claude Code supports auto-memory for cross-session learning. Mention your memory strategy if relevant.',
1048
+ template: null
1049
+ },
1050
+
1051
+ // --- New checks: sandbox / security depth ---
1052
+ sandboxAwareness: {
1053
+ id: 2013,
1054
+ name: 'Sandbox or isolation mentioned',
1055
+ check: (ctx) => {
1056
+ const md = ctx.claudeMdContent() || '';
1057
+ const settings = ctx.jsonFile('.claude/settings.json') || {};
1058
+ return /sandbox|isolat/i.test(md) || !!settings.sandbox;
1059
+ },
1060
+ impact: 'medium', rating: 3, category: 'security',
1061
+ fix: 'Claude Code supports sandboxed command execution. Consider enabling it for untrusted operations.',
1062
+ template: null
1063
+ },
1064
+
1065
+ denyRulesDepth: {
1066
+ id: 2014,
1067
+ name: 'Deny rules cover 3+ patterns',
1068
+ check: (ctx) => {
1069
+ const shared = ctx.jsonFile('.claude/settings.json');
1070
+ const local = ctx.jsonFile('.claude/settings.local.json');
1071
+ const deny = (shared?.permissions?.deny || []).concat(local?.permissions?.deny || []);
1072
+ return deny.length >= 3;
1073
+ },
1074
+ impact: 'high', rating: 4, category: 'security',
1075
+ fix: 'Add at least 3 deny rules: rm -rf, force-push, and .env reads. More patterns = safer Claude.',
1076
+ template: null
1077
+ },
1078
+
1079
+ // --- New checks: git depth ---
1080
+ gitAttributionDecision: {
1081
+ id: 2015,
1082
+ name: 'Git attribution configured',
1083
+ check: (ctx) => {
1084
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1085
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1086
+ return shared.attribution !== undefined || local.attribution !== undefined ||
1087
+ shared.includeCoAuthoredBy !== undefined || local.includeCoAuthoredBy !== undefined;
1088
+ },
1089
+ impact: 'low', rating: 3, category: 'git',
1090
+ fix: 'Decide on git attribution: set attribution.commit or includeCoAuthoredBy in settings.',
1091
+ template: null
1092
+ },
1093
+
1094
+ // --- New checks: performance ---
1095
+ effortLevelConfigured: {
1096
+ id: 2016,
1097
+ name: 'Effort level or thinking configuration',
1098
+ check: (ctx) => {
1099
+ const md = ctx.claudeMdContent() || '';
1100
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1101
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1102
+ return /effort|thinking/i.test(md) || shared.effortLevel || local.effortLevel ||
1103
+ shared.alwaysThinkingEnabled !== undefined || local.alwaysThinkingEnabled !== undefined;
1104
+ },
1105
+ impact: 'low', rating: 3, category: 'performance',
1106
+ fix: 'Configure effortLevel or mention thinking strategy in CLAUDE.md for task-appropriate reasoning depth.',
1107
+ template: null
1108
+ },
1109
+
1110
+ // --- New checks: workflow depth ---
1111
+ hasSnapshotHistory: {
1112
+ id: 2017,
1113
+ name: 'Audit snapshot history exists',
1114
+ check: (ctx) => {
1115
+ return !!ctx.fileContent('.claude/claudex-setup/snapshots/index.json');
1116
+ },
1117
+ impact: 'low', rating: 3, category: 'workflow',
1118
+ fix: 'Run `npx nerviq --snapshot` to start tracking your setup score over time.',
1119
+ template: null
1120
+ },
1121
+
1122
+ worktreeAwareness: {
1123
+ id: 2018,
1124
+ name: 'Worktree or parallel sessions mentioned',
1125
+ check: (ctx) => {
1126
+ const md = ctx.claudeMdContent() || '';
1127
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1128
+ return /worktree|parallel.*session/i.test(md) || !!shared.worktree;
1129
+ },
1130
+ impact: 'low', rating: 3, category: 'features',
1131
+ fix: 'Claude Code supports git worktrees for parallel isolated sessions. Mention if relevant.',
1132
+ template: null
1133
+ },
1134
+
1135
+ // --- New checks: prompting depth ---
1136
+ negativeInstructions: {
1137
+ id: 2019,
1138
+ name: 'CLAUDE.md includes "do not" instructions',
1139
+ check: (ctx) => {
1140
+ const md = ctx.claudeMdContent() || '';
1141
+ return /do not|don't|never|avoid|must not/i.test(md);
1142
+ },
1143
+ impact: 'medium', rating: 4, category: 'prompting',
1144
+ fix: 'Add explicit "do not" rules to CLAUDE.md. Negative constraints reduce common mistakes.',
1145
+ template: null
1146
+ },
1147
+
1148
+ outputStyleGuidance: {
1149
+ id: 2020,
1150
+ name: 'CLAUDE.md includes output or style guidance',
1151
+ check: (ctx) => {
1152
+ const md = ctx.claudeMdContent() || '';
1153
+ return /coding style|naming convention|code style|style guide|formatting rules|\bprefer\b.*\b(single|double|tabs|spaces|camel|snake|kebab|named|default|const|let|arrow|function)\b/i.test(md);
1154
+ },
1155
+ impact: 'medium', rating: 3, category: 'prompting',
1156
+ fix: 'Add coding style and naming conventions to CLAUDE.md so Claude matches your project patterns.',
1157
+ template: null
1158
+ },
1159
+
1160
+ // --- New checks: devops depth ---
1161
+ githubActionsOrCI: {
1162
+ id: 2021,
1163
+ name: 'GitHub Actions or CI configured',
1164
+ check: (ctx) => {
1165
+ return ctx.hasDir('.github/workflows') || !!ctx.fileContent('.circleci/config.yml') ||
1166
+ !!ctx.fileContent('.gitlab-ci.yml') || !!ctx.fileContent('Jenkinsfile') ||
1167
+ !!ctx.fileContent('.travis.yml') || !!ctx.fileContent('bitbucket-pipelines.yml');
1168
+ },
1169
+ impact: 'medium', rating: 3, category: 'devops',
1170
+ fix: 'Add CI pipeline for automated testing. Claude Code has a GitHub Action for audit gates.',
1171
+ template: null
1172
+ },
1173
+
1174
+ // --- New checks: depth round 2 ---
1175
+ projectDescriptionInClaudeMd: {
1176
+ id: 2022,
1177
+ name: 'CLAUDE.md describes what the project does',
1178
+ check: (ctx) => {
1179
+ const md = ctx.claudeMdContent() || '';
1180
+ return /what.*does|overview|purpose|about|description|project.*is/i.test(md) && md.length > 100;
1181
+ },
1182
+ impact: 'high', rating: 4, category: 'memory',
1183
+ fix: 'Start CLAUDE.md with a clear project description. Claude needs to know what your project does.',
1184
+ template: null
1185
+ },
1186
+
1187
+ directoryStructureInClaudeMd: {
1188
+ id: 2023,
1189
+ name: 'CLAUDE.md documents directory structure',
1190
+ check: (ctx) => {
1191
+ const md = ctx.claudeMdContent() || '';
1192
+ return /src\/|app\/|lib\/|structure|director|folder/i.test(md);
1193
+ },
1194
+ impact: 'medium', rating: 4, category: 'memory',
1195
+ fix: 'Document your directory structure in CLAUDE.md so Claude navigates your codebase efficiently.',
1196
+ template: null
1197
+ },
1198
+
1199
+ multipleHookTypes: {
1200
+ id: 2024,
1201
+ name: '2+ hook event types configured',
1202
+ check: (ctx) => {
1203
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1204
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1205
+ const hooks = { ...(shared.hooks || {}), ...(local.hooks || {}) };
1206
+ return Object.keys(hooks).length >= 2;
1207
+ },
1208
+ impact: 'medium', rating: 3, category: 'automation',
1209
+ fix: 'Add at least 2 hook types (e.g. PostToolUse for linting + SessionStart for initialization).',
1210
+ template: null
1211
+ },
1212
+
1213
+ stopFailureHook: {
1214
+ id: 2025,
1215
+ name: 'StopFailure hook for error tracking',
1216
+ check: (ctx) => {
1217
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1218
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1219
+ // StopFailure = error stop (API errors), Stop = normal completion — both useful but different
1220
+ return !!(shared.hooks?.StopFailure || local.hooks?.StopFailure);
1221
+ },
1222
+ impact: 'low', rating: 3, category: 'automation',
1223
+ fix: 'Add a StopFailure hook to log API errors and unexpected stops. Note: StopFailure (errors) is different from Stop (normal completion).',
1224
+ template: null
1225
+ },
1226
+
1227
+ skillUsesPaths: {
1228
+ id: 2026,
1229
+ name: 'At least one skill uses paths for scoping',
1230
+ check: (ctx) => {
1231
+ if (!ctx.hasDir('.claude/skills')) return null;
1232
+ const entries = ctx.dirFiles('.claude/skills');
1233
+ if (entries.length === 0) return null;
1234
+ for (const entry of entries) {
1235
+ // Skills can be files or dirs with SKILL.md inside
1236
+ const direct = ctx.fileContent(`.claude/skills/${entry}`) || '';
1237
+ if (/paths:/i.test(direct)) return true;
1238
+ const nested = ctx.fileContent(`.claude/skills/${entry}/SKILL.md`) || '';
1239
+ if (/paths:/i.test(nested)) return true;
1240
+ }
1241
+ return false;
1242
+ },
1243
+ impact: 'low', rating: 3, category: 'workflow',
1244
+ fix: 'Add paths to skill frontmatter to scope when skills activate (e.g. paths: ["src/**/*.ts"]).',
1245
+ template: null
1246
+ },
1247
+
1248
+ mcpHasEnvConfig: {
1249
+ id: 2027,
1250
+ name: 'MCP servers have environment configuration',
1251
+ check: (ctx) => {
1252
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1253
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1254
+ const mcp = ctx.jsonFile('.mcp.json') || {};
1255
+ const allServers = { ...(shared.mcpServers || {}), ...(local.mcpServers || {}), ...(mcp.mcpServers || {}) };
1256
+ if (Object.keys(allServers).length === 0) return null;
1257
+ return Object.values(allServers).some(s => s.env && Object.keys(s.env).length > 0);
1258
+ },
1259
+ impact: 'low', rating: 3, category: 'tools',
1260
+ fix: 'Configure environment variables for MCP servers that need authentication (e.g. GITHUB_TOKEN).',
1261
+ template: null
1262
+ },
1263
+
1264
+ gitIgnoreClaudeLocal: {
1265
+ id: 2028,
1266
+ name: '.gitignore excludes settings.local.json',
1267
+ check: (ctx) => {
1268
+ const gitignore = ctx.fileContent('.gitignore') || '';
1269
+ return /settings\.local\.json|settings\.local/i.test(gitignore);
1270
+ },
1271
+ impact: 'medium', rating: 4, category: 'git',
1272
+ fix: 'Add .claude/settings.local.json to .gitignore. Personal overrides should not be committed.',
1273
+ template: null
1274
+ },
1275
+
1276
+ envExampleExists: {
1277
+ id: 2029,
1278
+ name: '.env.example or .env.template exists',
1279
+ check: (ctx) => {
1280
+ return !!(ctx.fileContent('.env.example') || ctx.fileContent('.env.template') || ctx.fileContent('.env.sample'));
1281
+ },
1282
+ impact: 'low', rating: 3, category: 'hygiene',
1283
+ fix: 'Add .env.example so new developers know which environment variables are needed.',
1284
+ template: null
1285
+ },
1286
+
1287
+ packageJsonHasScripts: {
1288
+ id: 2030,
1289
+ name: 'package.json has dev/test/build scripts',
1290
+ check: (ctx) => {
1291
+ const pkg = ctx.jsonFile('package.json');
1292
+ if (!pkg) return null;
1293
+ const scripts = pkg.scripts || {};
1294
+ const has = (k) => !!scripts[k];
1295
+ return has('test') || has('dev') || has('build') || has('start');
1296
+ },
1297
+ impact: 'medium', rating: 3, category: 'hygiene',
1298
+ fix: 'Add scripts to package.json (test, dev, build). Claude uses these for verification.',
1299
+ template: null
1300
+ },
1301
+
1302
+ typeCheckingConfigured: {
1303
+ id: 2031,
1304
+ name: 'Type checking configured (TypeScript or similar)',
1305
+ check: (ctx) => {
1306
+ return !!(ctx.fileContent('tsconfig.json') || ctx.fileContent('jsconfig.json') ||
1307
+ ctx.fileContent('pyrightconfig.json') || ctx.fileContent('mypy.ini'));
1308
+ },
1309
+ impact: 'medium', rating: 3, category: 'quality',
1310
+ fix: 'Add type checking configuration. Type-safe code produces fewer Claude errors.',
1311
+ template: null
1312
+ },
1313
+
1314
+ noDeprecatedPatterns: {
1315
+ id: 2009,
1316
+ name: 'No deprecated patterns detected',
1317
+ check: (ctx) => {
1318
+ const md = ctx.claudeMdContent();
1319
+ if (!md) return false;
1320
+ // Only flag truly deprecated patterns, not valid aliases
1321
+ const deprecatedPatterns = [
1322
+ /\bhuman_prompt\b/i, /\bassistant_prompt\b/i, // old completions API format (not Messages API)
1323
+ /\buse model claude-3-opus\b/i, // explicit recommendation to use old name as --model
1324
+ /\buse model claude-3-sonnet\b/i,
1325
+ ];
1326
+ return !deprecatedPatterns.some(p => p.test(md));
1327
+ },
1328
+ impact: 'medium',
1329
+ rating: 3,
1330
+ category: 'quality-deep',
1331
+ fix: 'CLAUDE.md references deprecated API patterns (human_prompt/assistant_prompt). Update to current Messages API conventions.',
1332
+ template: null
1333
+ },
1334
+
1335
+ claudeMdQuality: {
1336
+ id: 102502,
1337
+ name: 'CLAUDE.md has substantive content',
1338
+ check: (ctx) => {
1339
+ const md = ctx.claudeMdContent();
1340
+ if (!md) return null;
1341
+ const lines = md.split('\n').filter(l => l.trim());
1342
+ const sections = (md.match(/^##\s/gm) || []).length;
1343
+ const hasCommand = /\b(npm|yarn|pnpm|pytest|go |make |ruff |cargo |dotnet )\b/i.test(md);
1344
+ return lines.length >= 15 && sections >= 2 && hasCommand;
1345
+ },
1346
+ impact: 'medium',
1347
+ rating: 4,
1348
+ category: 'quality-deep',
1349
+ fix: 'CLAUDE.md exists but lacks substance. Add at least 2 sections (## headings) and include your test/build/lint commands.',
1350
+ template: null
1351
+ },
1352
+
1353
+ // ============================================================
1354
+ // === NEW CHECKS: Uncovered features (2026-04-05) ============
1355
+ // ============================================================
1356
+
1357
+ mcpJsonProject: {
1358
+ id: 2032,
1359
+ name: 'Project-level .mcp.json exists',
1360
+ check: (ctx) => ctx.files.includes('.mcp.json'),
1361
+ impact: 'medium',
1362
+ rating: 3,
1363
+ category: 'tools',
1364
+ fix: 'Create .mcp.json at project root for team-shared MCP servers. Use `claude mcp add --project` to add servers.',
1365
+ template: null
1366
+ },
1367
+
1368
+ hooksNotificationEvent: {
1369
+ id: 2033,
1370
+ name: 'Notification hook for alerts',
1371
+ check: (ctx) => {
1372
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1373
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1374
+ return !!(shared.hooks?.Notification || local.hooks?.Notification);
1375
+ },
1376
+ impact: 'low',
1377
+ rating: 2,
1378
+ category: 'automation',
1379
+ fix: 'Add a Notification hook to capture alerts and status updates from Claude during long tasks.',
1380
+ template: null
1381
+ },
1382
+
1383
+ subagentStopHook: {
1384
+ id: 2034,
1385
+ name: 'SubagentStop hook for delegation tracking',
1386
+ check: (ctx) => {
1387
+ const shared = ctx.jsonFile('.claude/settings.json') || {};
1388
+ const local = ctx.jsonFile('.claude/settings.local.json') || {};
1389
+ return !!(shared.hooks?.SubagentStop || local.hooks?.SubagentStop);
1390
+ },
1391
+ impact: 'low',
1392
+ rating: 2,
1393
+ category: 'automation',
1394
+ fix: 'Add a SubagentStop hook to track when delegated subagent tasks complete.',
1395
+ template: null
1396
+ },
1397
+
1398
+ rulesDirectory: {
1399
+ id: 2035,
1400
+ name: 'Path-specific rules in .claude/rules/',
1401
+ check: (ctx) => ctx.hasDir('.claude/rules') && ctx.dirFiles('.claude/rules').length > 0,
1402
+ impact: 'medium',
1403
+ rating: 3,
1404
+ category: 'workflow',
1405
+ fix: 'Create .claude/rules/ with path-specific rules for different parts of your codebase (e.g. frontend.md, backend.md).',
1406
+ template: null
1407
+ },
1408
+
1409
+ gitignoreClaudeLocal: {
1410
+ id: 2036,
1411
+ name: 'CLAUDE.local.md in .gitignore',
1412
+ check: (ctx) => {
1413
+ const gitignore = ctx.fileContent('.gitignore') || '';
1414
+ return /CLAUDE\.local\.md/i.test(gitignore);
1415
+ },
1416
+ impact: 'medium',
1417
+ rating: 3,
1418
+ category: 'git',
1419
+ fix: 'Add CLAUDE.local.md to .gitignore it contains personal overrides that should not be committed.',
1420
+ template: null
1421
+ },
1422
+
1423
+
1424
+ // ============================================================
1425
+ // === PYTHON STACK CHECKS (category: 'python') ===============
1426
+ // ============================================================
1427
+
1428
+ pythonProjectExists: {
1429
+ id: 'CL-PY01',
1430
+ name: 'Python project detected (pyproject.toml / setup.py / requirements.txt)',
1431
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return true; },
1432
+ impact: 'high',
1433
+ category: 'python',
1434
+ fix: 'Ensure pyproject.toml, setup.py, or requirements.txt exists for Python projects.',
1435
+ // sourceUrl assigned by attachSourceUrls via category mapping
1436
+ confidence: 0.7,
1437
+ },
1438
+
1439
+ pythonVersionSpecified: {
1440
+ id: 'CL-PY02',
1441
+ name: 'Python version specified (.python-version or requires-python)',
1442
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /\.python-version$/.test(f)) || /requires-python/i.test(ctx.fileContent('pyproject.toml') || ''); },
1443
+ impact: 'medium',
1444
+ category: 'python',
1445
+ fix: 'Create .python-version or add requires-python to pyproject.toml.',
1446
+ // sourceUrl assigned by attachSourceUrls via category mapping
1447
+ confidence: 0.7,
1448
+ },
1449
+
1450
+ pythonVenvMentioned: {
1451
+ id: 'CL-PY03',
1452
+ name: 'Virtual environment mentioned in instructions',
1453
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /venv|virtualenv|conda|poetry shell|uv venv/i.test(docs); },
1454
+ impact: 'medium',
1455
+ category: 'python',
1456
+ fix: 'Document virtual environment setup in project instructions.',
1457
+ // sourceUrl assigned by attachSourceUrls via category mapping
1458
+ confidence: 0.7,
1459
+ },
1460
+
1461
+ pythonLockfileExists: {
1462
+ id: 'CL-PY04',
1463
+ name: 'Python lockfile exists (poetry.lock / uv.lock / Pipfile.lock / pinned requirements)',
1464
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /poetry\.lock$|uv\.lock$|Pipfile\.lock$/.test(f)) || /==/m.test(ctx.fileContent('requirements.txt') || ''); },
1465
+ impact: 'high',
1466
+ category: 'python',
1467
+ fix: 'Add a lockfile (poetry.lock, uv.lock, Pipfile.lock) or pin versions with == in requirements.txt.',
1468
+ // sourceUrl assigned by attachSourceUrls via category mapping
1469
+ confidence: 0.7,
1470
+ },
1471
+
1472
+ pythonPytestConfigured: {
1473
+ id: 'CL-PY05',
1474
+ name: 'pytest configured (pyproject.toml [tool.pytest] / pytest.ini / conftest.py)',
1475
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return /\[tool\.pytest/i.test(ctx.fileContent('pyproject.toml') || '') || ctx.files.some(f => /pytest\.ini$|conftest\.py$/.test(f)); },
1476
+ impact: 'high',
1477
+ category: 'python',
1478
+ fix: 'Configure pytest in pyproject.toml [tool.pytest.ini_options] or create pytest.ini.',
1479
+ // sourceUrl assigned by attachSourceUrls via category mapping
1480
+ confidence: 0.7,
1481
+ },
1482
+
1483
+ pythonLinterConfigured: {
1484
+ id: 'CL-PY06',
1485
+ name: 'Python linter configured (ruff / flake8 / pylint)',
1486
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const pp = ctx.fileContent('pyproject.toml') || ''; return /\[tool\.ruff|\[tool\.flake8|\[tool\.pylint/i.test(pp) || ctx.files.some(f => /\.flake8$|pylintrc$|\.pylintrc$|ruff\.toml$/.test(f)); },
1487
+ impact: 'medium',
1488
+ category: 'python',
1489
+ fix: 'Configure ruff, flake8, or pylint in pyproject.toml or dedicated config file.',
1490
+ // sourceUrl assigned by attachSourceUrls via category mapping
1491
+ confidence: 0.7,
1492
+ },
1493
+
1494
+ pythonTypeCheckerConfigured: {
1495
+ id: 'CL-PY07',
1496
+ name: 'Type checker configured (mypy / pyright)',
1497
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const pp = ctx.fileContent('pyproject.toml') || ''; return /\[tool\.mypy|\[tool\.pyright/i.test(pp) || ctx.files.some(f => /mypy\.ini$|pyrightconfig\.json$/.test(f)); },
1498
+ impact: 'medium',
1499
+ category: 'python',
1500
+ fix: 'Configure mypy or pyright in pyproject.toml or dedicated config file.',
1501
+ // sourceUrl assigned by attachSourceUrls via category mapping
1502
+ confidence: 0.7,
1503
+ },
1504
+
1505
+ pythonFormatterConfigured: {
1506
+ id: 'CL-PY08',
1507
+ name: 'Formatter configured (black / isort / ruff format)',
1508
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const pp = ctx.fileContent('pyproject.toml') || ''; return /\[tool\.black|\[tool\.isort|\[tool\.ruff\.format/i.test(pp); },
1509
+ impact: 'medium',
1510
+ category: 'python',
1511
+ fix: 'Configure black, isort, or ruff format in pyproject.toml.',
1512
+ // sourceUrl assigned by attachSourceUrls via category mapping
1513
+ confidence: 0.7,
1514
+ },
1515
+
1516
+ pythonDjangoSettingsDocumented: {
1517
+ id: 'CL-PY09',
1518
+ name: 'Django settings documented if Django project',
1519
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; if (!ctx.files.some(f => /manage\.py$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /django|settings\.py|DJANGO_SETTINGS_MODULE/i.test(docs); },
1520
+ impact: 'high',
1521
+ category: 'python',
1522
+ fix: 'Document Django settings module and configuration in project instructions.',
1523
+ // sourceUrl assigned by attachSourceUrls via category mapping
1524
+ confidence: 0.7,
1525
+ },
1526
+
1527
+ pythonFastapiEntryDocumented: {
1528
+ id: 'CL-PY10',
1529
+ name: 'FastAPI entry point documented if FastAPI project',
1530
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/fastapi/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /fastapi|uvicorn|app\.py|main\.py/i.test(docs); },
1531
+ impact: 'high',
1532
+ category: 'python',
1533
+ fix: 'Document FastAPI entry point and how to run the development server.',
1534
+ // sourceUrl assigned by attachSourceUrls via category mapping
1535
+ confidence: 0.7,
1536
+ },
1537
+
1538
+ pythonMigrationsDocumented: {
1539
+ id: 'CL-PY11',
1540
+ name: 'Database migrations mentioned (alembic / Django migrations)',
1541
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /alembic|migrate|makemigrations|django.{0,10}migration/i.test(docs) || ctx.files.some(f => /alembic[.]ini$|alembic[/]/.test(f)); },
1542
+ impact: 'medium',
1543
+ category: 'python',
1544
+ fix: 'Document database migration workflow (alembic or Django migrations).',
1545
+ // sourceUrl assigned by attachSourceUrls via category mapping
1546
+ confidence: 0.7,
1547
+ },
1548
+
1549
+ pythonEnvHandlingDocumented: {
1550
+ id: 'CL-PY12',
1551
+ name: '.env handling documented (python-dotenv)',
1552
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/dotenv|python-dotenv|environs/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /\.env|dotenv|environment.{0,10}variable/i.test(docs); },
1553
+ impact: 'medium',
1554
+ category: 'python',
1555
+ fix: 'Document .env file usage and python-dotenv configuration.',
1556
+ // sourceUrl assigned by attachSourceUrls via category mapping
1557
+ confidence: 0.7,
1558
+ },
1559
+
1560
+ pythonPreCommitConfigured: {
1561
+ id: 'CL-PY13',
1562
+ name: 'pre-commit hooks configured (.pre-commit-config.yaml)',
1563
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /\.pre-commit-config\.yaml$/.test(f)); },
1564
+ impact: 'medium',
1565
+ category: 'python',
1566
+ fix: 'Add .pre-commit-config.yaml with Python-specific hooks (ruff, mypy, etc.).',
1567
+ // sourceUrl assigned by attachSourceUrls via category mapping
1568
+ confidence: 0.7,
1569
+ },
1570
+
1571
+ pythonDockerBaseImage: {
1572
+ id: 'CL-PY14',
1573
+ name: 'Docker uses Python base image correctly',
1574
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const df = ctx.fileContent('Dockerfile') || ''; if (!df) return null; return /FROM.*python:/i.test(df); },
1575
+ impact: 'medium',
1576
+ category: 'python',
1577
+ fix: 'Use official Python base image in Dockerfile (e.g., FROM python:3.12-slim).',
1578
+ // sourceUrl assigned by attachSourceUrls via category mapping
1579
+ confidence: 0.7,
1580
+ },
1581
+
1582
+ pythonTestMatrixConfigured: {
1583
+ id: 'CL-PY15',
1584
+ name: 'Test matrix configured (tox.ini / noxfile.py)',
1585
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /tox\.ini$|noxfile\.py$/.test(f)); },
1586
+ impact: 'low',
1587
+ category: 'python',
1588
+ fix: 'Configure tox or nox for multi-environment testing.',
1589
+ // sourceUrl assigned by attachSourceUrls via category mapping
1590
+ confidence: 0.7,
1591
+ },
1592
+
1593
+ pythonValidationUsed: {
1594
+ id: 'CL-PY16',
1595
+ name: 'Pydantic or dataclass validation used',
1596
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); return /pydantic|dataclass/i.test(deps); },
1597
+ impact: 'medium',
1598
+ category: 'python',
1599
+ fix: 'Use pydantic or dataclasses for data validation and type safety.',
1600
+ // sourceUrl assigned by attachSourceUrls via category mapping
1601
+ confidence: 0.7,
1602
+ },
1603
+
1604
+ pythonAsyncDocumented: {
1605
+ id: 'CL-PY17',
1606
+ name: 'Async patterns documented if async project',
1607
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/asyncio|aiohttp|fastapi|starlette|httpx/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /async|await|asyncio|event.{0,5}loop/i.test(docs); },
1608
+ impact: 'medium',
1609
+ category: 'python',
1610
+ fix: 'Document async patterns and conventions used in the project.',
1611
+ // sourceUrl assigned by attachSourceUrls via category mapping
1612
+ confidence: 0.7,
1613
+ },
1614
+
1615
+ pythonPinnedVersions: {
1616
+ id: 'CL-PY18',
1617
+ name: 'Requirements have pinned versions (== in requirements.txt)',
1618
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const req = ctx.fileContent('requirements.txt') || ''; if (!req.trim()) return null; const lines = req.split('\n').filter(l => l.trim() && !l.startsWith('#')); return lines.length > 0 && lines.every(l => /==/.test(l) || /^-/.test(l.trim())); },
1619
+ impact: 'high',
1620
+ category: 'python',
1621
+ fix: 'Pin all dependency versions with == in requirements.txt for reproducible builds.',
1622
+ // sourceUrl assigned by attachSourceUrls via category mapping
1623
+ confidence: 0.7,
1624
+ },
1625
+
1626
+ pythonPackageStructure: {
1627
+ id: 'CL-PY19',
1628
+ name: 'Python package has proper structure (src/ layout or __init__.py)',
1629
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /src[/].*[/]__init__\.py$|^[^/]+[/]__init__\.py$/.test(f)); },
1630
+ impact: 'medium',
1631
+ category: 'python',
1632
+ fix: 'Use src/ layout or ensure packages have __init__.py files.',
1633
+ // sourceUrl assigned by attachSourceUrls via category mapping
1634
+ confidence: 0.7,
1635
+ },
1636
+
1637
+ pythonDocsToolConfigured: {
1638
+ id: 'CL-PY20',
1639
+ name: 'Documentation tool configured (sphinx / mkdocs)',
1640
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /mkdocs\.yml$|conf\.py$|docs[/]/.test(f)) || /sphinx|mkdocs/i.test(ctx.fileContent('pyproject.toml') || ''); },
1641
+ impact: 'low',
1642
+ category: 'python',
1643
+ fix: 'Configure sphinx or mkdocs for project documentation.',
1644
+ // sourceUrl assigned by attachSourceUrls via category mapping
1645
+ confidence: 0.7,
1646
+ },
1647
+
1648
+ pythonCoverageConfigured: {
1649
+ id: 'CL-PY21',
1650
+ name: 'Coverage configured (coverage / pytest-cov)',
1651
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const pp = ctx.fileContent('pyproject.toml') || ''; return /\[tool\.coverage|pytest-cov|coverage/i.test(pp) || ctx.files.some(f => /\.coveragerc$/.test(f)); },
1652
+ impact: 'medium',
1653
+ category: 'python',
1654
+ fix: 'Configure coverage reporting with pytest-cov or coverage.py.',
1655
+ // sourceUrl assigned by attachSourceUrls via category mapping
1656
+ confidence: 0.7,
1657
+ },
1658
+
1659
+ pythonNoSecretsInSettings: {
1660
+ id: 'CL-PY22',
1661
+ name: 'No secrets in Django settings.py',
1662
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const settings = ctx.fileContent('settings.py') || ctx.files.filter(f => /settings\.py$/.test(f)).map(f => ctx.fileContent(f) || '').join(''); if (!settings) return null; return !/SECRET_KEY\s*=\s*['"][^'"]{10,}/i.test(settings); },
1663
+ impact: 'critical',
1664
+ category: 'python',
1665
+ fix: 'Move SECRET_KEY and other secrets to environment variables, not hardcoded in settings.py.',
1666
+ // sourceUrl assigned by attachSourceUrls via category mapping
1667
+ confidence: 0.7,
1668
+ },
1669
+
1670
+ pythonWsgiAsgiDocumented: {
1671
+ id: 'CL-PY23',
1672
+ name: 'WSGI/ASGI server documented (gunicorn / uvicorn)',
1673
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/gunicorn|uvicorn|daphne|hypercorn/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /gunicorn|uvicorn|daphne|hypercorn|wsgi|asgi/i.test(docs); },
1674
+ impact: 'medium',
1675
+ category: 'python',
1676
+ fix: 'Document WSGI/ASGI server configuration (gunicorn, uvicorn).',
1677
+ // sourceUrl assigned by attachSourceUrls via category mapping
1678
+ confidence: 0.7,
1679
+ },
1680
+
1681
+ pythonTaskQueueDocumented: {
1682
+ id: 'CL-PY24',
1683
+ name: 'Task queue documented if used (celery / rq)',
1684
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/celery|rq|dramatiq|huey/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /celery|rq|dramatiq|huey|task.{0,10}queue|worker/i.test(docs); },
1685
+ impact: 'medium',
1686
+ category: 'python',
1687
+ fix: 'Document task queue configuration and worker setup.',
1688
+ // sourceUrl assigned by attachSourceUrls via category mapping
1689
+ confidence: 0.7,
1690
+ },
1691
+
1692
+ pythonGitignore: {
1693
+ id: 'CL-PY25',
1694
+ name: 'Python-specific .gitignore (__pycache__, *.pyc, .venv)',
1695
+ check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const gi = ctx.fileContent('.gitignore') || ''; return /__pycache__|\*\.pyc|\.venv/i.test(gi); },
1696
+ impact: 'medium',
1697
+ category: 'python',
1698
+ fix: 'Add Python-specific entries to .gitignore (__pycache__, *.pyc, .venv, *.egg-info).',
1699
+ // sourceUrl assigned by attachSourceUrls via category mapping
1700
+ confidence: 0.7,
1701
+ },
1702
+
1703
+ // ============================================================
1704
+ // === GO STACK CHECKS (category: 'go') =======================
1705
+ // ============================================================
1706
+
1707
+ goModExists: {
1708
+ id: 'CL-GO01',
1709
+ name: 'go.mod exists',
1710
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return true; },
1711
+ impact: 'high',
1712
+ category: 'go',
1713
+ fix: 'Initialize Go module with go mod init.',
1714
+ // sourceUrl assigned by attachSourceUrls via category mapping
1715
+ confidence: 0.7,
1716
+ },
1717
+
1718
+ goSumCommitted: {
1719
+ id: 'CL-GO02',
1720
+ name: 'go.sum committed',
1721
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return ctx.files.some(f => /go\.sum$/.test(f)); },
1722
+ impact: 'high',
1723
+ category: 'go',
1724
+ fix: 'Commit go.sum to version control for reproducible builds.',
1725
+ // sourceUrl assigned by attachSourceUrls via category mapping
1726
+ confidence: 0.7,
1727
+ },
1728
+
1729
+ golangciLintConfigured: {
1730
+ id: 'CL-GO03',
1731
+ name: 'golangci-lint configured (.golangci.yml)',
1732
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return ctx.files.some(f => /\.golangci\.ya?ml$|\.golangci\.toml$/.test(f)); },
1733
+ impact: 'medium',
1734
+ category: 'go',
1735
+ fix: 'Add .golangci.yml to configure linting rules.',
1736
+ // sourceUrl assigned by attachSourceUrls via category mapping
1737
+ confidence: 0.7,
1738
+ },
1739
+
1740
+ goTestDocumented: {
1741
+ id: 'CL-GO04',
1742
+ name: 'go test documented in instructions',
1743
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go test/i.test(docs); },
1744
+ impact: 'high',
1745
+ category: 'go',
1746
+ fix: 'Document go test command in project instructions.',
1747
+ // sourceUrl assigned by attachSourceUrls via category mapping
1748
+ confidence: 0.7,
1749
+ },
1750
+
1751
+ goBuildDocumented: {
1752
+ id: 'CL-GO05',
1753
+ name: 'go build documented in instructions',
1754
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go build|go install/i.test(docs); },
1755
+ impact: 'high',
1756
+ category: 'go',
1757
+ fix: 'Document go build command in project instructions.',
1758
+ // sourceUrl assigned by attachSourceUrls via category mapping
1759
+ confidence: 0.7,
1760
+ },
1761
+
1762
+ goStandardLayout: {
1763
+ id: 'CL-GO06',
1764
+ name: 'Standard Go layout (cmd/ / internal/ / pkg/)',
1765
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return ctx.files.some(f => /^cmd[/]|^internal[/]|^pkg[/]/.test(f)); },
1766
+ impact: 'medium',
1767
+ category: 'go',
1768
+ fix: 'Use standard Go project layout with cmd/, internal/, and/or pkg/ directories.',
1769
+ // sourceUrl assigned by attachSourceUrls via category mapping
1770
+ confidence: 0.7,
1771
+ },
1772
+
1773
+ goErrorHandlingDocumented: {
1774
+ id: 'CL-GO07',
1775
+ name: 'Error handling patterns documented',
1776
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /error handling|errors?\.(?:New|Wrap|Is|As)|fmt\.Errorf/i.test(docs); },
1777
+ impact: 'medium',
1778
+ category: 'go',
1779
+ fix: 'Document error handling conventions (error wrapping, sentinel errors, etc.).',
1780
+ // sourceUrl assigned by attachSourceUrls via category mapping
1781
+ confidence: 0.7,
1782
+ },
1783
+
1784
+ goContextUsageDocumented: {
1785
+ id: 'CL-GO08',
1786
+ name: 'Context usage documented',
1787
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /context\.Context|ctx\.Done|context\.WithCancel|context\.WithTimeout/i.test(docs); },
1788
+ impact: 'medium',
1789
+ category: 'go',
1790
+ fix: 'Document context.Context usage patterns for cancellation and timeouts.',
1791
+ // sourceUrl assigned by attachSourceUrls via category mapping
1792
+ confidence: 0.7,
1793
+ },
1794
+
1795
+ goroutineSafetyDocumented: {
1796
+ id: 'CL-GO09',
1797
+ name: 'Goroutine safety documented',
1798
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /goroutine|sync\.Mutex|sync\.WaitGroup|channel|concurren/i.test(docs); },
1799
+ impact: 'medium',
1800
+ category: 'go',
1801
+ fix: 'Document goroutine safety patterns, mutex usage, and channel conventions.',
1802
+ // sourceUrl assigned by attachSourceUrls via category mapping
1803
+ confidence: 0.7,
1804
+ },
1805
+
1806
+ goModTidyMentioned: {
1807
+ id: 'CL-GO10',
1808
+ name: 'go mod tidy mentioned in instructions',
1809
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go mod tidy/i.test(docs); },
1810
+ impact: 'low',
1811
+ category: 'go',
1812
+ fix: 'Document go mod tidy in project workflow instructions.',
1813
+ // sourceUrl assigned by attachSourceUrls via category mapping
1814
+ confidence: 0.7,
1815
+ },
1816
+
1817
+ goVetConfigured: {
1818
+ id: 'CL-GO11',
1819
+ name: 'go vet or staticcheck configured',
1820
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/go.yml') || ''; return /go vet|staticcheck/i.test(docs + ci); },
1821
+ impact: 'medium',
1822
+ category: 'go',
1823
+ fix: 'Configure go vet and/or staticcheck in CI or project instructions.',
1824
+ // sourceUrl assigned by attachSourceUrls via category mapping
1825
+ confidence: 0.7,
1826
+ },
1827
+
1828
+ goMakefileExists: {
1829
+ id: 'CL-GO12',
1830
+ name: 'Makefile or Taskfile exists for Go project',
1831
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return ctx.files.some(f => /^Makefile$|^Taskfile\.ya?ml$/.test(f)); },
1832
+ impact: 'medium',
1833
+ category: 'go',
1834
+ fix: 'Add Makefile or Taskfile.yml with common Go targets (build, test, lint).',
1835
+ // sourceUrl assigned by attachSourceUrls via category mapping
1836
+ confidence: 0.7,
1837
+ },
1838
+
1839
+ goDockerMultiStage: {
1840
+ id: 'CL-GO13',
1841
+ name: 'Docker multi-stage build for Go',
1842
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const df = ctx.fileContent('Dockerfile') || ''; if (!df) return null; return /FROM.*golang.*AS/i.test(df) && /FROM.*(?:alpine|scratch|distroless|gcr\.io)/i.test(df); },
1843
+ impact: 'medium',
1844
+ category: 'go',
1845
+ fix: 'Use multi-stage Docker build: build in golang image, run in minimal image (alpine/scratch/distroless).',
1846
+ // sourceUrl assigned by attachSourceUrls via category mapping
1847
+ confidence: 0.7,
1848
+ },
1849
+
1850
+ goCgoDocumented: {
1851
+ id: 'CL-GO14',
1852
+ name: 'CGO documented if used',
1853
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const goMod = ctx.fileContent('go.mod') || ''; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; if (!/CGO_ENABLED|import "C"/i.test(goMod + docs)) return null; return /CGO|cgo/i.test(docs); },
1854
+ impact: 'low',
1855
+ category: 'go',
1856
+ fix: 'Document CGO usage, dependencies, and build requirements.',
1857
+ // sourceUrl assigned by attachSourceUrls via category mapping
1858
+ confidence: 0.7,
1859
+ },
1860
+
1861
+ goWorkForMonorepo: {
1862
+ id: 'CL-GO15',
1863
+ name: 'go.work for monorepo',
1864
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const multiMod = ctx.files.filter(f => /go\.mod$/.test(f)).length > 1; if (!multiMod) return null; return ctx.files.some(f => /go\.work$/.test(f)); },
1865
+ impact: 'medium',
1866
+ category: 'go',
1867
+ fix: 'Use go.work for Go workspace in monorepo with multiple modules.',
1868
+ // sourceUrl assigned by attachSourceUrls via category mapping
1869
+ confidence: 0.7,
1870
+ },
1871
+
1872
+ goBenchmarkTests: {
1873
+ id: 'CL-GO16',
1874
+ name: 'Benchmark tests mentioned',
1875
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go test.*-bench|Benchmark/i.test(docs); },
1876
+ impact: 'low',
1877
+ category: 'go',
1878
+ fix: 'Document benchmark testing with go test -bench.',
1879
+ // sourceUrl assigned by attachSourceUrls via category mapping
1880
+ confidence: 0.7,
1881
+ },
1882
+
1883
+ goRaceDetector: {
1884
+ id: 'CL-GO17',
1885
+ name: 'Race detector (-race) documented',
1886
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/go.yml') || ''; return /-race/i.test(docs + ci); },
1887
+ impact: 'medium',
1888
+ category: 'go',
1889
+ fix: 'Document and enable race detector with go test -race.',
1890
+ // sourceUrl assigned by attachSourceUrls via category mapping
1891
+ confidence: 0.7,
1892
+ },
1893
+
1894
+ goGenerateDocumented: {
1895
+ id: 'CL-GO18',
1896
+ name: 'go generate documented',
1897
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go generate/i.test(docs) || ctx.files.some(f => /generate\.go$/.test(f)); },
1898
+ impact: 'low',
1899
+ category: 'go',
1900
+ fix: 'Document go generate usage and generated files.',
1901
+ // sourceUrl assigned by attachSourceUrls via category mapping
1902
+ confidence: 0.7,
1903
+ },
1904
+
1905
+ goInterfaceDesignDocumented: {
1906
+ id: 'CL-GO19',
1907
+ name: 'Interface-based design documented',
1908
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /interface|mock|stub|dependency injection/i.test(docs); },
1909
+ impact: 'low',
1910
+ category: 'go',
1911
+ fix: 'Document interface-based design patterns for testability and dependency injection.',
1912
+ // sourceUrl assigned by attachSourceUrls via category mapping
1913
+ confidence: 0.7,
1914
+ },
1915
+
1916
+ goGitignore: {
1917
+ id: 'CL-GO20',
1918
+ name: 'Go-specific .gitignore entries',
1919
+ check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const gi = ctx.fileContent('.gitignore') || ''; return /vendor[/]|\*\.exe|\*\.test|\*\.out|[/]bin[/]/i.test(gi); },
1920
+ impact: 'low',
1921
+ category: 'go',
1922
+ fix: 'Add Go-specific entries to .gitignore (vendor/, *.exe, *.test, /bin/).',
1923
+ // sourceUrl assigned by attachSourceUrls via category mapping
1924
+ confidence: 0.7,
1925
+ },
1926
+ // ============================================================
1927
+ // === RUST STACK CHECKS (category: 'rust') ===================
1928
+ // ============================================================
1929
+
1930
+ rustCargoTomlExists: {
1931
+ id: 'CL-RS01',
1932
+ name: 'Cargo.toml exists with edition field',
1933
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; return /edition\s*=/.test(cargo); },
1934
+ impact: 'high',
1935
+ category: 'rust',
1936
+ fix: 'Ensure Cargo.toml exists and specifies the edition field (e.g., edition = "2021").',
1937
+ // sourceUrl assigned by attachSourceUrls via category mapping
1938
+ confidence: 0.7,
1939
+ },
1940
+
1941
+ rustCargoLockCommitted: {
1942
+ id: 'CL-RS02',
1943
+ name: 'Cargo.lock committed (for binary crates)',
1944
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; return ctx.files.some(f => /Cargo\.lock$/.test(f)); },
1945
+ impact: 'high',
1946
+ category: 'rust',
1947
+ fix: 'Commit Cargo.lock for binary crates to ensure reproducible builds.',
1948
+ // sourceUrl assigned by attachSourceUrls via category mapping
1949
+ confidence: 0.7,
1950
+ },
1951
+
1952
+ rustClippyConfigured: {
1953
+ id: 'CL-RS03',
1954
+ name: 'Clippy configured (CI or .cargo/config.toml)',
1955
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/rust.yml') || ''; const cargoConfig = ctx.fileContent('.cargo/config.toml') || ''; return /clippy/i.test(ci + cargoConfig); },
1956
+ impact: 'medium',
1957
+ category: 'rust',
1958
+ fix: 'Configure clippy in CI or .cargo/config.toml for lint enforcement.',
1959
+ // sourceUrl assigned by attachSourceUrls via category mapping
1960
+ confidence: 0.7,
1961
+ },
1962
+
1963
+ rustFmtConfigured: {
1964
+ id: 'CL-RS04',
1965
+ name: 'rustfmt configured (rustfmt.toml or .rustfmt.toml)',
1966
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; return ctx.files.some(f => /rustfmt\.toml$|\.rustfmt\.toml$/.test(f)); },
1967
+ impact: 'medium',
1968
+ category: 'rust',
1969
+ fix: 'Create rustfmt.toml or .rustfmt.toml to configure code formatting.',
1970
+ // sourceUrl assigned by attachSourceUrls via category mapping
1971
+ confidence: 0.7,
1972
+ },
1973
+
1974
+ rustCargoTestDocumented: {
1975
+ id: 'CL-RS05',
1976
+ name: 'cargo test documented in instructions',
1977
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cargo test/i.test(docs); },
1978
+ impact: 'high',
1979
+ category: 'rust',
1980
+ fix: 'Document cargo test command in project instructions.',
1981
+ // sourceUrl assigned by attachSourceUrls via category mapping
1982
+ confidence: 0.7,
1983
+ },
1984
+
1985
+ rustCargoBuildDocumented: {
1986
+ id: 'CL-RS06',
1987
+ name: 'cargo build/check documented in instructions',
1988
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cargo (?:build|check)/i.test(docs); },
1989
+ impact: 'high',
1990
+ category: 'rust',
1991
+ fix: 'Document cargo build or cargo check command in project instructions.',
1992
+ // sourceUrl assigned by attachSourceUrls via category mapping
1993
+ confidence: 0.7,
1994
+ },
1995
+
1996
+ rustUnsafePolicyDocumented: {
1997
+ id: 'CL-RS07',
1998
+ name: 'Unsafe code policy documented',
1999
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /unsafe|#!?\[forbid\(unsafe|#!?\[deny\(unsafe/i.test(docs); },
2000
+ impact: 'high',
2001
+ category: 'rust',
2002
+ fix: 'Document unsafe code policy (forbidden, minimized, or where allowed).',
2003
+ // sourceUrl assigned by attachSourceUrls via category mapping
2004
+ confidence: 0.7,
2005
+ },
2006
+
2007
+ rustErrorHandlingStrategy: {
2008
+ id: 'CL-RS08',
2009
+ name: 'Error handling strategy (anyhow/thiserror in deps)',
2010
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; return /anyhow|thiserror|eyre|color-eyre/i.test(cargo); },
2011
+ impact: 'medium',
2012
+ category: 'rust',
2013
+ fix: 'Use anyhow (applications) or thiserror (libraries) for structured error handling.',
2014
+ // sourceUrl assigned by attachSourceUrls via category mapping
2015
+ confidence: 0.7,
2016
+ },
2017
+
2018
+ rustFeatureFlagsDocumented: {
2019
+ id: 'CL-RS09',
2020
+ name: 'Feature flags documented (Cargo.toml [features])',
2021
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/\[features\]/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /feature|--features|--all-features/i.test(docs); },
2022
+ impact: 'medium',
2023
+ category: 'rust',
2024
+ fix: 'Document feature flags and their purpose in project instructions.',
2025
+ // sourceUrl assigned by attachSourceUrls via category mapping
2026
+ confidence: 0.7,
2027
+ },
2028
+
2029
+ rustWorkspaceConfig: {
2030
+ id: 'CL-RS10',
2031
+ name: 'Workspace config if multi-crate (Cargo.toml [workspace])',
2032
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (ctx.files.filter(f => /Cargo\.toml$/.test(f)).length <= 1) return null; return /\[workspace\]/i.test(cargo); },
2033
+ impact: 'medium',
2034
+ category: 'rust',
2035
+ fix: 'Configure [workspace] in root Cargo.toml for multi-crate projects.',
2036
+ // sourceUrl assigned by attachSourceUrls via category mapping
2037
+ confidence: 0.7,
2038
+ },
2039
+
2040
+ rustMsrvSpecified: {
2041
+ id: 'CL-RS11',
2042
+ name: 'MSRV specified (rust-version field)',
2043
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; return /rust-version\s*=/.test(cargo); },
2044
+ impact: 'medium',
2045
+ category: 'rust',
2046
+ fix: 'Specify rust-version (MSRV) in Cargo.toml for compatibility guarantees.',
2047
+ // sourceUrl assigned by attachSourceUrls via category mapping
2048
+ confidence: 0.7,
2049
+ },
2050
+
2051
+ rustDocCommentsEncouraged: {
2052
+ id: 'CL-RS12',
2053
+ name: 'Doc comments (///) encouraged in instructions',
2054
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /doc comment|\/{3}|rustdoc|cargo doc/i.test(docs); },
2055
+ impact: 'low',
2056
+ category: 'rust',
2057
+ fix: 'Encourage /// doc comments and cargo doc in project instructions.',
2058
+ // sourceUrl assigned by attachSourceUrls via category mapping
2059
+ confidence: 0.7,
2060
+ },
2061
+
2062
+ rustBenchmarksConfigured: {
2063
+ id: 'CL-RS13',
2064
+ name: 'Criterion benchmarks mentioned (benches/ dir)',
2065
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; return ctx.files.some(f => /benches[/]/.test(f)) || /criterion/i.test(ctx.fileContent('Cargo.toml') || ''); },
2066
+ impact: 'low',
2067
+ category: 'rust',
2068
+ fix: 'Set up criterion benchmarks in benches/ directory.',
2069
+ // sourceUrl assigned by attachSourceUrls via category mapping
2070
+ confidence: 0.7,
2071
+ },
2072
+
2073
+ rustCrossCompilationDocumented: {
2074
+ id: 'CL-RS14',
2075
+ name: 'Cross-compilation documented',
2076
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cross.?compil|--target|rustup target|cargo build.*--target/i.test(docs); },
2077
+ impact: 'low',
2078
+ category: 'rust',
2079
+ fix: 'Document cross-compilation targets and setup instructions.',
2080
+ // sourceUrl assigned by attachSourceUrls via category mapping
2081
+ confidence: 0.7,
2082
+ },
2083
+
2084
+ rustMemorySafetyDocumented: {
2085
+ id: 'CL-RS15',
2086
+ name: 'Memory safety patterns documented',
2087
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /ownership|borrow|lifetime|memory.?safe|Arc|Rc|RefCell/i.test(docs); },
2088
+ impact: 'medium',
2089
+ category: 'rust',
2090
+ fix: 'Document memory safety patterns (ownership, borrowing, lifetime conventions).',
2091
+ // sourceUrl assigned by attachSourceUrls via category mapping
2092
+ confidence: 0.7,
2093
+ },
2094
+
2095
+ rustAsyncRuntimeDocumented: {
2096
+ id: 'CL-RS16',
2097
+ name: 'Async runtime documented (tokio/async-std in deps)',
2098
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/tokio|async-std|smol/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /tokio|async-std|async|await|runtime/i.test(docs); },
2099
+ impact: 'medium',
2100
+ category: 'rust',
2101
+ fix: 'Document async runtime choice and patterns (tokio, async-std).',
2102
+ // sourceUrl assigned by attachSourceUrls via category mapping
2103
+ confidence: 0.7,
2104
+ },
2105
+
2106
+ rustSerdeDocumented: {
2107
+ id: 'CL-RS17',
2108
+ name: 'Serde patterns documented',
2109
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/serde/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /serde|Serialize|Deserialize|serde_json|serde_yaml/i.test(docs); },
2110
+ impact: 'medium',
2111
+ category: 'rust',
2112
+ fix: 'Document serde serialization/deserialization patterns and conventions.',
2113
+ // sourceUrl assigned by attachSourceUrls via category mapping
2114
+ confidence: 0.7,
2115
+ },
2116
+
2117
+ rustCargoAuditConfigured: {
2118
+ id: 'CL-RS18',
2119
+ name: 'cargo-audit configured in CI',
2120
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/rust.yml') || ctx.fileContent('.github/workflows/audit.yml') || ''; return /cargo.?audit|advisory/i.test(ci); },
2121
+ impact: 'medium',
2122
+ category: 'rust',
2123
+ fix: 'Configure cargo-audit in CI for vulnerability scanning.',
2124
+ // sourceUrl assigned by attachSourceUrls via category mapping
2125
+ confidence: 0.7,
2126
+ },
2127
+
2128
+ rustWasmTargetDocumented: {
2129
+ id: 'CL-RS19',
2130
+ name: 'WASM target documented if applicable',
2131
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/wasm|wasm-bindgen|wasm-pack/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /wasm|WebAssembly|wasm-pack|wasm-bindgen/i.test(docs); },
2132
+ impact: 'low',
2133
+ category: 'rust',
2134
+ fix: 'Document WASM target configuration and build process.',
2135
+ // sourceUrl assigned by attachSourceUrls via category mapping
2136
+ confidence: 0.7,
2137
+ },
2138
+
2139
+ rustGitignore: {
2140
+ id: 'CL-RS20',
2141
+ name: 'Rust .gitignore includes target/',
2142
+ check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const gi = ctx.fileContent('.gitignore') || ''; return /target[/]|[/]target/i.test(gi); },
2143
+ impact: 'medium',
2144
+ category: 'rust',
2145
+ fix: 'Add target/ to .gitignore for Rust build artifacts.',
2146
+ // sourceUrl assigned by attachSourceUrls via category mapping
2147
+ confidence: 0.7,
2148
+ },
2149
+
2150
+ // ============================================================
2151
+ // === JAVA/SPRING STACK CHECKS (category: 'java') ============
2152
+ // ============================================================
2153
+
2154
+ javaBuildFileExists: {
2155
+ id: 'CL-JV01',
2156
+ name: 'pom.xml or build.gradle exists',
2157
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return true; },
2158
+ impact: 'high',
2159
+ category: 'java',
2160
+ fix: 'Ensure pom.xml or build.gradle exists for Java projects.',
2161
+ // sourceUrl assigned by attachSourceUrls via category mapping
2162
+ confidence: 0.7,
2163
+ },
2164
+
2165
+ javaVersionSpecified: {
2166
+ id: 'CL-JV02',
2167
+ name: 'Java version specified',
2168
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const pom = ctx.fileContent('pom.xml') || ''; const gradle = ctx.fileContent('build.gradle') || ctx.fileContent('build.gradle.kts') || ''; return /java\.version|maven\.compiler\.source|sourceCompatibility|JavaVersion/i.test(pom + gradle) || ctx.files.some(f => /\.java-version$/.test(f)); },
2169
+ impact: 'high',
2170
+ category: 'java',
2171
+ fix: 'Specify Java version in pom.xml properties, build.gradle, or .java-version file.',
2172
+ // sourceUrl assigned by attachSourceUrls via category mapping
2173
+ confidence: 0.7,
2174
+ },
2175
+
2176
+ javaWrapperCommitted: {
2177
+ id: 'CL-JV03',
2178
+ name: 'Maven/Gradle wrapper committed (mvnw or gradlew)',
2179
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /mvnw$|gradlew$/.test(f)); },
2180
+ impact: 'high',
2181
+ category: 'java',
2182
+ fix: 'Commit mvnw (Maven) or gradlew (Gradle) wrapper for reproducible builds.',
2183
+ // sourceUrl assigned by attachSourceUrls via category mapping
2184
+ confidence: 0.7,
2185
+ },
2186
+
2187
+ javaSpringBootVersion: {
2188
+ id: 'CL-JV04',
2189
+ name: 'Spring Boot version documented if Spring project',
2190
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const pom = ctx.fileContent('pom.xml') || ''; const gradle = ctx.fileContent('build.gradle') || ctx.fileContent('build.gradle.kts') || ''; if (!/spring-boot/i.test(pom + gradle)) return null; return /spring-boot.*\d+\.\d+/i.test(pom + gradle); },
2191
+ impact: 'high',
2192
+ category: 'java',
2193
+ fix: 'Document Spring Boot version in build configuration.',
2194
+ // sourceUrl assigned by attachSourceUrls via category mapping
2195
+ confidence: 0.7,
2196
+ },
2197
+
2198
+ javaApplicationConfig: {
2199
+ id: 'CL-JV05',
2200
+ name: 'application.yml or application.properties exists',
2201
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /application\.ya?ml$|application\.properties$/.test(f)); },
2202
+ impact: 'medium',
2203
+ category: 'java',
2204
+ fix: 'Create application.yml or application.properties for Spring configuration.',
2205
+ // sourceUrl assigned by attachSourceUrls via category mapping
2206
+ confidence: 0.7,
2207
+ },
2208
+
2209
+ javaTestFramework: {
2210
+ id: 'CL-JV06',
2211
+ name: 'Test framework configured (JUnit/TestNG in deps)',
2212
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const pom = ctx.fileContent('pom.xml') || ''; const gradle = ctx.fileContent('build.gradle') || ctx.fileContent('build.gradle.kts') || ''; return /junit|testng|spring-boot-starter-test/i.test(pom + gradle); },
2213
+ impact: 'high',
2214
+ category: 'java',
2215
+ fix: 'Configure JUnit or TestNG test framework in project dependencies.',
2216
+ // sourceUrl assigned by attachSourceUrls via category mapping
2217
+ confidence: 0.7,
2218
+ },
2219
+
2220
+ javaCodeStyleConfigured: {
2221
+ id: 'CL-JV07',
2222
+ name: 'Code style configured (checkstyle.xml, spotbugs)',
2223
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /checkstyle\.xml$|spotbugs.*\.xml$/.test(f)) || /checkstyle|spotbugs|google-java-format/i.test((ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || '')); },
2224
+ impact: 'medium',
2225
+ category: 'java',
2226
+ fix: 'Configure checkstyle or spotbugs for code quality enforcement.',
2227
+ // sourceUrl assigned by attachSourceUrls via category mapping
2228
+ confidence: 0.7,
2229
+ },
2230
+
2231
+ javaSpringProfilesDocumented: {
2232
+ id: 'CL-JV08',
2233
+ name: 'Spring profiles documented',
2234
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/spring/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /spring[.]profiles|@Profile|SPRING_PROFILES_ACTIVE/i.test(docs); },
2235
+ impact: 'medium',
2236
+ category: 'java',
2237
+ fix: 'Document Spring profiles and their configuration in project instructions.',
2238
+ // sourceUrl assigned by attachSourceUrls via category mapping
2239
+ confidence: 0.7,
2240
+ },
2241
+
2242
+ javaDatabaseMigration: {
2243
+ id: 'CL-JV09',
2244
+ name: 'Database migration configured (flyway/liquibase)',
2245
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); return /flyway|liquibase/i.test(deps) || ctx.files.some(f => /db[/]migration|flyway|liquibase/i.test(f)); },
2246
+ impact: 'medium',
2247
+ category: 'java',
2248
+ fix: 'Configure database migration tool (Flyway or Liquibase) for schema management.',
2249
+ // sourceUrl assigned by attachSourceUrls via category mapping
2250
+ confidence: 0.7,
2251
+ },
2252
+
2253
+ javaLombokDocumented: {
2254
+ id: 'CL-JV10',
2255
+ name: 'Lombok/MapStruct documented if used',
2256
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/lombok|mapstruct/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /lombok|mapstruct/i.test(docs); },
2257
+ impact: 'low',
2258
+ category: 'java',
2259
+ fix: 'Document Lombok/MapStruct usage and IDE setup requirements.',
2260
+ // sourceUrl assigned by attachSourceUrls via category mapping
2261
+ confidence: 0.7,
2262
+ },
2263
+
2264
+ javaApiDocsConfigured: {
2265
+ id: 'CL-JV11',
2266
+ name: 'API docs configured (springdoc/swagger deps)',
2267
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); return /springdoc|swagger|openapi/i.test(deps); },
2268
+ impact: 'medium',
2269
+ category: 'java',
2270
+ fix: 'Configure API documentation with springdoc-openapi or Swagger.',
2271
+ // sourceUrl assigned by attachSourceUrls via category mapping
2272
+ confidence: 0.7,
2273
+ },
2274
+
2275
+ javaSecurityConfigured: {
2276
+ id: 'CL-JV12',
2277
+ name: 'Security configuration documented',
2278
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/spring-security|spring-boot-starter-security/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /security|authentication|authorization|SecurityConfig|@EnableWebSecurity/i.test(docs); },
2279
+ impact: 'high',
2280
+ category: 'java',
2281
+ fix: 'Document Spring Security configuration and authentication setup.',
2282
+ // sourceUrl assigned by attachSourceUrls via category mapping
2283
+ confidence: 0.7,
2284
+ },
2285
+
2286
+ javaActuatorConfigured: {
2287
+ id: 'CL-JV13',
2288
+ name: 'Actuator/health checks configured',
2289
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); return /actuator|spring-boot-starter-actuator/i.test(deps); },
2290
+ impact: 'medium',
2291
+ category: 'java',
2292
+ fix: 'Configure Spring Boot Actuator for health checks and monitoring.',
2293
+ // sourceUrl assigned by attachSourceUrls via category mapping
2294
+ confidence: 0.7,
2295
+ },
2296
+
2297
+ javaLoggingConfigured: {
2298
+ id: 'CL-JV14',
2299
+ name: 'Logging configured (logback.xml or log4j2.xml)',
2300
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /logback.*\.xml$|log4j2?.*\.xml$|logging\.properties$/.test(f)); },
2301
+ impact: 'medium',
2302
+ category: 'java',
2303
+ fix: 'Configure logging with logback.xml or log4j2.xml.',
2304
+ // sourceUrl assigned by attachSourceUrls via category mapping
2305
+ confidence: 0.7,
2306
+ },
2307
+
2308
+ javaMultiModuleProject: {
2309
+ id: 'CL-JV15',
2310
+ name: 'Multi-module project configured if applicable',
2311
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const buildFiles = ctx.files.filter(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f)); if (buildFiles.length <= 1) return null; const rootPom = ctx.fileContent('pom.xml') || ''; const rootGradle = ctx.fileContent('settings.gradle') || ctx.fileContent('settings.gradle.kts') || ''; return /<modules>|include\s/i.test(rootPom + rootGradle); },
2312
+ impact: 'medium',
2313
+ category: 'java',
2314
+ fix: 'Configure multi-module project structure in root build file.',
2315
+ // sourceUrl assigned by attachSourceUrls via category mapping
2316
+ confidence: 0.7,
2317
+ },
2318
+
2319
+ javaDockerConfigured: {
2320
+ id: 'CL-JV16',
2321
+ name: 'Docker build configured (Dockerfile or Jib plugin)',
2322
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const df = ctx.fileContent('Dockerfile') || ''; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); return /FROM.*(?:openjdk|eclipse-temurin|amazoncorretto)/i.test(df) || /jib/i.test(deps); },
2323
+ impact: 'medium',
2324
+ category: 'java',
2325
+ fix: 'Configure Docker build with Dockerfile or Jib plugin.',
2326
+ // sourceUrl assigned by attachSourceUrls via category mapping
2327
+ confidence: 0.7,
2328
+ },
2329
+
2330
+ javaEnvConfigsSeparated: {
2331
+ id: 'CL-JV17',
2332
+ name: 'Environment-specific configs separated',
2333
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /application-(?:dev|prod|staging|test|local)\.(?:ya?ml|properties)$/.test(f)); },
2334
+ impact: 'medium',
2335
+ category: 'java',
2336
+ fix: 'Separate environment configs (application-dev.yml, application-prod.yml, etc.).',
2337
+ // sourceUrl assigned by attachSourceUrls via category mapping
2338
+ confidence: 0.7,
2339
+ },
2340
+
2341
+ javaNoSecretsInConfig: {
2342
+ id: 'CL-JV18',
2343
+ name: 'No secrets in application.yml/properties',
2344
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const appYml = ctx.files.filter(f => /application.*\.ya?ml$|application.*\.properties$/.test(f)).map(f => ctx.fileContent(f) || '').join(''); if (!appYml) return null; return !/password\s*[:=]\s*[^$\{\s][^\s]{8,}|secret\s*[:=]\s*[^$\{\s][^\s]{8,}/i.test(appYml); },
2345
+ impact: 'critical',
2346
+ category: 'java',
2347
+ fix: 'Move secrets to environment variables or external secret management, not application config files.',
2348
+ // sourceUrl assigned by attachSourceUrls via category mapping
2349
+ confidence: 0.7,
2350
+ },
2351
+
2352
+ javaIntegrationTestsSeparate: {
2353
+ id: 'CL-JV19',
2354
+ name: 'Integration tests separate from unit tests',
2355
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /src[/](?:integration-?test|it)[/]|IT\.java$|Integration(?:Test)?\.java$/.test(f)) || /failsafe|integration-test/i.test((ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || '')); },
2356
+ impact: 'medium',
2357
+ category: 'java',
2358
+ fix: 'Separate integration tests from unit tests using Maven Failsafe or dedicated source set.',
2359
+ // sourceUrl assigned by attachSourceUrls via category mapping
2360
+ confidence: 0.7,
2361
+ },
2362
+
2363
+ javaBuildCommandDocumented: {
2364
+ id: 'CL-JV20',
2365
+ name: 'Build command documented in instructions',
2366
+ check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /mvn|gradle|mvnw|gradlew|maven|./i.test(docs) && /build|compile|package|install/i.test(docs); },
2367
+ impact: 'high',
2368
+ category: 'java',
2369
+ fix: 'Document build command (mvnw package, gradlew build) in project instructions.',
2370
+ // sourceUrl assigned by attachSourceUrls via category mapping
2371
+ confidence: 0.7,
2372
+ },
2373
+
2374
+ // ============================================================
2375
+ // === RUBY/RAILS STACK CHECKS (category: 'ruby') =============
2376
+ // ============================================================
2377
+
2378
+ rubyGemfileExists: {
2379
+ id: 'CL-RB01',
2380
+ name: 'Gemfile exists',
2381
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return true; },
2382
+ impact: 'high',
2383
+ category: 'ruby',
2384
+ fix: 'Create a Gemfile to manage Ruby dependencies.',
2385
+ // sourceUrl assigned by attachSourceUrls via category mapping
2386
+ confidence: 0.7,
2387
+ },
2388
+
2389
+ rubyGemfileLockCommitted: {
2390
+ id: 'CL-RB02',
2391
+ name: 'Gemfile.lock committed',
2392
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /Gemfile\.lock$/.test(f)); },
2393
+ impact: 'high',
2394
+ category: 'ruby',
2395
+ fix: 'Commit Gemfile.lock to version control for reproducible builds.',
2396
+ // sourceUrl assigned by attachSourceUrls via category mapping
2397
+ confidence: 0.7,
2398
+ },
2399
+
2400
+ rubyVersionSpecified: {
2401
+ id: 'CL-RB03',
2402
+ name: 'Ruby version specified (.ruby-version)',
2403
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /\.ruby-version$/.test(f)) || /ruby ['"]~?\d/i.test(ctx.fileContent('Gemfile') || ''); },
2404
+ impact: 'medium',
2405
+ category: 'ruby',
2406
+ fix: 'Create .ruby-version or specify ruby version in Gemfile.',
2407
+ // sourceUrl assigned by attachSourceUrls via category mapping
2408
+ confidence: 0.7,
2409
+ },
2410
+
2411
+ rubyRubocopConfigured: {
2412
+ id: 'CL-RB04',
2413
+ name: 'RuboCop configured (.rubocop.yml)',
2414
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /\.rubocop\.ya?ml$/.test(f)); },
2415
+ impact: 'medium',
2416
+ category: 'ruby',
2417
+ fix: 'Add .rubocop.yml to configure Ruby style checking.',
2418
+ // sourceUrl assigned by attachSourceUrls via category mapping
2419
+ confidence: 0.7,
2420
+ },
2421
+
2422
+ rubyTestFrameworkConfigured: {
2423
+ id: 'CL-RB05',
2424
+ name: 'RSpec or Minitest configured (spec/ or test/)',
2425
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /^spec\/|^test\/|spec_helper\.rb$|test_helper\.rb$/.test(f)); },
2426
+ impact: 'high',
2427
+ category: 'ruby',
2428
+ fix: 'Configure RSpec (spec/) or Minitest (test/) for testing.',
2429
+ // sourceUrl assigned by attachSourceUrls via category mapping
2430
+ confidence: 0.7,
2431
+ },
2432
+
2433
+ rubyRailsCredentialsDocumented: {
2434
+ id: 'CL-RB06',
2435
+ name: 'Rails credentials documented in instructions',
2436
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /config\/credentials/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /credentials|encrypted|master\.key|secret_key_base/i.test(docs); },
2437
+ impact: 'high',
2438
+ category: 'ruby',
2439
+ fix: 'Document Rails credentials management (rails credentials:edit) in project instructions.',
2440
+ // sourceUrl assigned by attachSourceUrls via category mapping
2441
+ confidence: 0.7,
2442
+ },
2443
+
2444
+ rubyMigrationsDocumented: {
2445
+ id: 'CL-RB07',
2446
+ name: 'Database migrations documented (db/migrate/)',
2447
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /db\/migrate\//.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /migration|migrate|db:migrate|rails db/i.test(docs); },
2448
+ impact: 'medium',
2449
+ category: 'ruby',
2450
+ fix: 'Document database migration workflow (rails db:migrate) in project instructions.',
2451
+ // sourceUrl assigned by attachSourceUrls via category mapping
2452
+ confidence: 0.7,
2453
+ },
2454
+
2455
+ rubyBundlerAuditConfigured: {
2456
+ id: 'CL-RB08',
2457
+ name: 'Bundler audit configured',
2458
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; const gf = ctx.fileContent('Gemfile') || ''; return /bundler-audit|bundle.audit/i.test(gf) || ctx.files.some(f => /\.bundler-audit/i.test(f)); },
2459
+ impact: 'medium',
2460
+ category: 'ruby',
2461
+ fix: 'Add bundler-audit gem for dependency vulnerability scanning.',
2462
+ // sourceUrl assigned by attachSourceUrls via category mapping
2463
+ confidence: 0.7,
2464
+ },
2465
+
2466
+ rubyTypeCheckingConfigured: {
2467
+ id: 'CL-RB09',
2468
+ name: 'Sorbet/RBS type checking configured (sorbet/ or sig/)',
2469
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /sorbet\/|sig\/|\.rbs$/.test(f)) || /sorbet|tapioca/i.test(ctx.fileContent('Gemfile') || ''); },
2470
+ impact: 'low',
2471
+ category: 'ruby',
2472
+ fix: 'Configure Sorbet or RBS for type checking.',
2473
+ // sourceUrl assigned by attachSourceUrls via category mapping
2474
+ confidence: 0.7,
2475
+ },
2476
+
2477
+ rubyRailsRoutesDocumented: {
2478
+ id: 'CL-RB10',
2479
+ name: 'Rails routes documented',
2480
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /config\/routes\.rb$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /routes|endpoints|api.*path|REST/i.test(docs); },
2481
+ impact: 'medium',
2482
+ category: 'ruby',
2483
+ fix: 'Document key routes and API endpoints in project instructions.',
2484
+ // sourceUrl assigned by attachSourceUrls via category mapping
2485
+ confidence: 0.7,
2486
+ },
2487
+
2488
+ rubyBackgroundJobsDocumented: {
2489
+ id: 'CL-RB11',
2490
+ name: 'Background jobs documented (Sidekiq/GoodJob)',
2491
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; const gf = ctx.fileContent('Gemfile') || ''; if (!/sidekiq|good_job|delayed_job|resque/i.test(gf)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /sidekiq|good_job|delayed_job|resque|background.*job|worker|queue/i.test(docs); },
2492
+ impact: 'medium',
2493
+ category: 'ruby',
2494
+ fix: 'Document background job framework and worker configuration.',
2495
+ // sourceUrl assigned by attachSourceUrls via category mapping
2496
+ confidence: 0.7,
2497
+ },
2498
+
2499
+ rubyRailsEnvConfigsSeparated: {
2500
+ id: 'CL-RB12',
2501
+ name: 'Rails environment configs separated (config/environments/)',
2502
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /config\/environments\//.test(f)); },
2503
+ impact: 'medium',
2504
+ category: 'ruby',
2505
+ fix: 'Ensure config/environments/ has separate files for development, test, and production.',
2506
+ // sourceUrl assigned by attachSourceUrls via category mapping
2507
+ confidence: 0.7,
2508
+ },
2509
+
2510
+ rubyAssetPipelineDocumented: {
2511
+ id: 'CL-RB13',
2512
+ name: 'Asset pipeline documented',
2513
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; const gf = ctx.fileContent('Gemfile') || ''; if (!/sprockets|propshaft|webpacker|jsbundling|cssbundling/i.test(gf)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /asset|sprockets|propshaft|webpacker|jsbundling|cssbundling|esbuild|vite/i.test(docs); },
2514
+ impact: 'low',
2515
+ category: 'ruby',
2516
+ fix: 'Document asset pipeline configuration (Sprockets, Propshaft, or JS/CSS bundling).',
2517
+ // sourceUrl assigned by attachSourceUrls via category mapping
2518
+ confidence: 0.7,
2519
+ },
2520
+
2521
+ rubyMasterKeyInGitignore: {
2522
+ id: 'CL-RB14',
2523
+ name: 'Rails master.key in .gitignore',
2524
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; if (!ctx.files.some(f => /config\/credentials/.test(f))) return null; const gi = ctx.fileContent('.gitignore') || ''; return /master\.key/i.test(gi); },
2525
+ impact: 'critical',
2526
+ category: 'ruby',
2527
+ fix: 'Add config/master.key to .gitignore to prevent secret leakage.',
2528
+ // sourceUrl assigned by attachSourceUrls via category mapping
2529
+ confidence: 0.7,
2530
+ },
2531
+
2532
+ rubyTestDataFactories: {
2533
+ id: 'CL-RB15',
2534
+ name: 'Factory Bot/fixtures for test data (spec/factories/)',
2535
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /spec\/factories\/|test\/fixtures\//.test(f)) || /factory_bot|fabrication/i.test(ctx.fileContent('Gemfile') || ''); },
2536
+ impact: 'medium',
2537
+ category: 'ruby',
2538
+ fix: 'Configure Factory Bot (spec/factories/) or fixtures (test/fixtures/) for test data.',
2539
+ // sourceUrl assigned by attachSourceUrls via category mapping
2540
+ confidence: 0.7,
2541
+ },
2542
+
2543
+ // ============================================================
2544
+ // === .NET/C# STACK CHECKS (category: 'dotnet') ==============
2545
+ // ============================================================
2546
+
2547
+ dotnetProjectExists: {
2548
+ id: 'CL-DN01',
2549
+ name: '.csproj or .sln exists',
2550
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return true; },
2551
+ impact: 'high',
2552
+ category: 'dotnet',
2553
+ fix: 'Ensure .csproj or .sln file exists for .NET projects.',
2554
+ // sourceUrl assigned by attachSourceUrls via category mapping
2555
+ confidence: 0.7,
2556
+ },
2557
+
2558
+ dotnetVersionSpecified: {
2559
+ id: 'CL-DN02',
2560
+ name: '.NET version specified (global.json or TargetFramework)',
2561
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /global\.json$/.test(f)) || ctx.files.some(f => { if (!/\.csproj$/.test(f)) return false; const c = ctx.fileContent(f) || ''; return /TargetFramework/i.test(c); }); },
2562
+ impact: 'medium',
2563
+ category: 'dotnet',
2564
+ fix: 'Create global.json or ensure TargetFramework is set in .csproj.',
2565
+ // sourceUrl assigned by attachSourceUrls via category mapping
2566
+ confidence: 0.7,
2567
+ },
2568
+
2569
+ dotnetPackagesLock: {
2570
+ id: 'CL-DN03',
2571
+ name: 'NuGet packages lock (packages.lock.json)',
2572
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /packages\.lock\.json$/.test(f)); },
2573
+ impact: 'medium',
2574
+ category: 'dotnet',
2575
+ fix: 'Enable NuGet lock file (packages.lock.json) for reproducible restores.',
2576
+ // sourceUrl assigned by attachSourceUrls via category mapping
2577
+ confidence: 0.7,
2578
+ },
2579
+
2580
+ dotnetTestDocumented: {
2581
+ id: 'CL-DN04',
2582
+ name: 'dotnet test documented',
2583
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /dotnet test|xunit|nunit|mstest/i.test(docs); },
2584
+ impact: 'high',
2585
+ category: 'dotnet',
2586
+ fix: 'Document how to run tests with dotnet test in project instructions.',
2587
+ // sourceUrl assigned by attachSourceUrls via category mapping
2588
+ confidence: 0.7,
2589
+ },
2590
+
2591
+ dotnetEditorConfigExists: {
2592
+ id: 'CL-DN05',
2593
+ name: 'EditorConfig configured (.editorconfig)',
2594
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /\.editorconfig$/.test(f)); },
2595
+ impact: 'medium',
2596
+ category: 'dotnet',
2597
+ fix: 'Add .editorconfig for consistent code style across the team.',
2598
+ // sourceUrl assigned by attachSourceUrls via category mapping
2599
+ confidence: 0.7,
2600
+ },
2601
+
2602
+ dotnetRoslynAnalyzers: {
2603
+ id: 'CL-DN06',
2604
+ name: 'Roslyn analyzers configured',
2605
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => { if (!/\.csproj$/.test(f)) return false; const c = ctx.fileContent(f) || ''; return /Analyzer|StyleCop|SonarAnalyzer|Microsoft\.CodeAnalysis/i.test(c); }); },
2606
+ impact: 'medium',
2607
+ category: 'dotnet',
2608
+ fix: 'Add Roslyn analyzers (StyleCop.Analyzers, Microsoft.CodeAnalysis) to the project.',
2609
+ // sourceUrl assigned by attachSourceUrls via category mapping
2610
+ confidence: 0.7,
2611
+ },
2612
+
2613
+ dotnetAppsettingsExists: {
2614
+ id: 'CL-DN07',
2615
+ name: 'appsettings.json exists',
2616
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /appsettings\.json$/.test(f)); },
2617
+ impact: 'medium',
2618
+ category: 'dotnet',
2619
+ fix: 'Create appsettings.json for application configuration.',
2620
+ // sourceUrl assigned by attachSourceUrls via category mapping
2621
+ confidence: 0.7,
2622
+ },
2623
+
2624
+ dotnetUserSecretsDocumented: {
2625
+ id: 'CL-DN08',
2626
+ name: 'User secrets configured in instructions',
2627
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /user.?secrets|dotnet secrets|Secret Manager/i.test(docs); },
2628
+ impact: 'high',
2629
+ category: 'dotnet',
2630
+ fix: 'Document user secrets management (dotnet user-secrets) in project instructions.',
2631
+ // sourceUrl assigned by attachSourceUrls via category mapping
2632
+ confidence: 0.7,
2633
+ },
2634
+
2635
+ dotnetEfMigrations: {
2636
+ id: 'CL-DN09',
2637
+ name: 'Entity Framework migrations (Migrations/ directory)',
2638
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /Migrations\//.test(f)); },
2639
+ impact: 'medium',
2640
+ category: 'dotnet',
2641
+ fix: 'Document Entity Framework migration workflow (dotnet ef migrations).',
2642
+ // sourceUrl assigned by attachSourceUrls via category mapping
2643
+ confidence: 0.7,
2644
+ },
2645
+
2646
+ dotnetHealthChecks: {
2647
+ id: 'CL-DN10',
2648
+ name: 'Health checks configured',
2649
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => { if (!/\.cs$/.test(f)) return false; const c = ctx.fileContent(f) || ''; return /AddHealthChecks|MapHealthChecks|IHealthCheck/i.test(c); }); },
2650
+ impact: 'medium',
2651
+ category: 'dotnet',
2652
+ fix: 'Configure health checks with AddHealthChecks() and MapHealthChecks().',
2653
+ // sourceUrl assigned by attachSourceUrls via category mapping
2654
+ confidence: 0.7,
2655
+ },
2656
+
2657
+ dotnetSwaggerConfigured: {
2658
+ id: 'CL-DN11',
2659
+ name: 'Swagger/OpenAPI configured',
2660
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => { if (!/\.cs$|.csproj$/.test(f)) return false; const c = ctx.fileContent(f) || ''; return /Swashbuckle|AddSwaggerGen|UseSwagger|NSwag|AddOpenApi/i.test(c); }); },
2661
+ impact: 'medium',
2662
+ category: 'dotnet',
2663
+ fix: 'Configure Swagger/OpenAPI with Swashbuckle or NSwag.',
2664
+ // sourceUrl assigned by attachSourceUrls via category mapping
2665
+ confidence: 0.7,
2666
+ },
2667
+
2668
+ dotnetNoConnectionStringsInConfig: {
2669
+ id: 'CL-DN12',
2670
+ name: 'No connection strings in appsettings.json',
2671
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; const settings = ctx.fileContent('appsettings.json') || ''; if (!settings) return null; return !/Server=.*Password=|Data Source=.*Password=/i.test(settings); },
2672
+ impact: 'critical',
2673
+ category: 'dotnet',
2674
+ fix: 'Move connection strings with passwords to user secrets or environment variables.',
2675
+ // sourceUrl assigned by attachSourceUrls via category mapping
2676
+ confidence: 0.7,
2677
+ },
2678
+
2679
+ dotnetDockerSupport: {
2680
+ id: 'CL-DN13',
2681
+ name: 'Docker support configured',
2682
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; const df = ctx.fileContent('Dockerfile') || ''; return /dotnet|aspnet|sdk/i.test(df); },
2683
+ impact: 'medium',
2684
+ category: 'dotnet',
2685
+ fix: 'Add Dockerfile with official .NET SDK/ASP.NET base images.',
2686
+ // sourceUrl assigned by attachSourceUrls via category mapping
2687
+ confidence: 0.7,
2688
+ },
2689
+
2690
+ dotnetTestProjectSeparate: {
2691
+ id: 'CL-DN14',
2692
+ name: 'Unit test project separate (.Tests.csproj)',
2693
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /\.Tests?\.csproj$|Tests?\/.*\.csproj$/.test(f)); },
2694
+ impact: 'high',
2695
+ category: 'dotnet',
2696
+ fix: 'Create separate test project (e.g., MyApp.Tests.csproj) for unit tests.',
2697
+ // sourceUrl assigned by attachSourceUrls via category mapping
2698
+ confidence: 0.7,
2699
+ },
2700
+
2701
+ dotnetGlobalUsingsDocumented: {
2702
+ id: 'CL-DN15',
2703
+ name: 'GlobalUsings documented',
2704
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /GlobalUsings\.cs$|Usings\.cs$/.test(f)) || ctx.files.some(f => { if (!/\.csproj$/.test(f)) return false; const c = ctx.fileContent(f) || ''; return /ImplicitUsings/i.test(c); }); },
2705
+ impact: 'low',
2706
+ category: 'dotnet',
2707
+ fix: 'Document global using directives in GlobalUsings.cs or enable ImplicitUsings in .csproj.',
2708
+ // sourceUrl assigned by attachSourceUrls via category mapping
2709
+ confidence: 0.7,
2710
+ },
2711
+
2712
+ // ============================================================
2713
+ // === PHP/LARAVEL STACK CHECKS (category: 'php') ==============
2714
+ // ============================================================
2715
+
2716
+ phpComposerJsonExists: {
2717
+ id: 'CL-PHP01',
2718
+ name: 'composer.json exists',
2719
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; return true; },
2720
+ impact: 'high',
2721
+ category: 'php',
2722
+ fix: 'Create composer.json to manage PHP dependencies.',
2723
+ // sourceUrl assigned by attachSourceUrls via category mapping
2724
+ confidence: 0.7,
2725
+ },
2726
+
2727
+ phpComposerLockCommitted: {
2728
+ id: 'CL-PHP02',
2729
+ name: 'composer.lock committed',
2730
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; return ctx.files.some(f => /composer\.lock$/.test(f)); },
2731
+ impact: 'high',
2732
+ category: 'php',
2733
+ fix: 'Commit composer.lock to version control for reproducible installs.',
2734
+ // sourceUrl assigned by attachSourceUrls via category mapping
2735
+ confidence: 0.7,
2736
+ },
2737
+
2738
+ phpVersionSpecified: {
2739
+ id: 'CL-PHP03',
2740
+ name: 'PHP version specified (composer.json require.php)',
2741
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; const cj = ctx.fileContent('composer.json') || ''; return /"php"s*:/i.test(cj); },
2742
+ impact: 'medium',
2743
+ category: 'php',
2744
+ fix: 'Specify PHP version requirement in composer.json require section.',
2745
+ // sourceUrl assigned by attachSourceUrls via category mapping
2746
+ confidence: 0.7,
2747
+ },
2748
+
2749
+ phpStaticAnalysisConfigured: {
2750
+ id: 'CL-PHP04',
2751
+ name: 'PHPStan/Psalm configured (phpstan.neon)',
2752
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; return ctx.files.some(f => /phpstan\.neon$|phpstan\.neon\.dist$|psalm\.xml$/.test(f)); },
2753
+ impact: 'medium',
2754
+ category: 'php',
2755
+ fix: 'Configure PHPStan (phpstan.neon) or Psalm (psalm.xml) for static analysis.',
2756
+ // sourceUrl assigned by attachSourceUrls via category mapping
2757
+ confidence: 0.7,
2758
+ },
2759
+
2760
+ phpCsFixerConfigured: {
2761
+ id: 'CL-PHP05',
2762
+ name: 'PHP CS Fixer configured (.php-cs-fixer.php)',
2763
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; return ctx.files.some(f => /\.php-cs-fixer\.php$|\.php-cs-fixer\.dist\.php$/.test(f)); },
2764
+ impact: 'medium',
2765
+ category: 'php',
2766
+ fix: 'Add .php-cs-fixer.php for consistent code formatting.',
2767
+ // sourceUrl assigned by attachSourceUrls via category mapping
2768
+ confidence: 0.7,
2769
+ },
2770
+
2771
+ phpUnitConfigured: {
2772
+ id: 'CL-PHP06',
2773
+ name: 'PHPUnit configured (phpunit.xml)',
2774
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; return ctx.files.some(f => /phpunit\.xml$|phpunit\.xml\.dist$/.test(f)); },
2775
+ impact: 'high',
2776
+ category: 'php',
2777
+ fix: 'Configure PHPUnit with phpunit.xml for testing.',
2778
+ // sourceUrl assigned by attachSourceUrls via category mapping
2779
+ confidence: 0.7,
2780
+ },
2781
+
2782
+ phpLaravelEnvExample: {
2783
+ id: 'CL-PHP07',
2784
+ name: 'Laravel .env.example exists',
2785
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; return ctx.files.some(f => /\.env\.example$/.test(f)); },
2786
+ impact: 'high',
2787
+ category: 'php',
2788
+ fix: 'Create .env.example with all required environment variables documented.',
2789
+ // sourceUrl assigned by attachSourceUrls via category mapping
2790
+ confidence: 0.7,
2791
+ },
2792
+
2793
+ phpLaravelAppKeyNotCommitted: {
2794
+ id: 'CL-PHP08',
2795
+ name: 'Laravel APP_KEY not committed',
2796
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; const env = ctx.fileContent('.env') || ''; if (!env) return null; return !/APP_KEY=base64:[A-Za-z0-9+/=]{30,}/i.test(env); },
2797
+ impact: 'critical',
2798
+ category: 'php',
2799
+ fix: 'Ensure .env with APP_KEY is in .gitignore — never commit application keys.',
2800
+ // sourceUrl assigned by attachSourceUrls via category mapping
2801
+ confidence: 0.7,
2802
+ },
2803
+
2804
+ phpLaravelMigrationsExist: {
2805
+ id: 'CL-PHP09',
2806
+ name: 'Laravel migrations exist (database/migrations/)',
2807
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; return ctx.files.some(f => /database\/migrations\//.test(f)); },
2808
+ impact: 'medium',
2809
+ category: 'php',
2810
+ fix: 'Create database migrations in database/migrations/ directory.',
2811
+ // sourceUrl assigned by attachSourceUrls via category mapping
2812
+ confidence: 0.7,
2813
+ },
2814
+
2815
+ phpArtisanCommandsDocumented: {
2816
+ id: 'CL-PHP10',
2817
+ name: 'Artisan commands documented',
2818
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /artisan|php artisan|make:model|make:controller|migrate/i.test(docs); },
2819
+ impact: 'medium',
2820
+ category: 'php',
2821
+ fix: 'Document key Artisan commands (migrate, seed, make:*) in project instructions.',
2822
+ // sourceUrl assigned by attachSourceUrls via category mapping
2823
+ confidence: 0.7,
2824
+ },
2825
+
2826
+ phpQueueWorkerDocumented: {
2827
+ id: 'CL-PHP11',
2828
+ name: 'Queue worker documented',
2829
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; const cj = ctx.fileContent('composer.json') || ''; if (!/horizon|queue/i.test(cj) && !ctx.files.some(f => /artisan$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /queue|horizon|worker|job|dispatch/i.test(docs); },
2830
+ impact: 'medium',
2831
+ category: 'php',
2832
+ fix: 'Document queue worker setup (php artisan queue:work, Horizon).',
2833
+ // sourceUrl assigned by attachSourceUrls via category mapping
2834
+ confidence: 0.7,
2835
+ },
2836
+
2837
+ phpLaravelPintConfigured: {
2838
+ id: 'CL-PHP12',
2839
+ name: 'Laravel Pint configured (pint.json)',
2840
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; return ctx.files.some(f => /pint\.json$/.test(f)) || /laravel\/pint/i.test(ctx.fileContent('composer.json') || ''); },
2841
+ impact: 'low',
2842
+ category: 'php',
2843
+ fix: 'Configure Laravel Pint (pint.json) for code style enforcement.',
2844
+ // sourceUrl assigned by attachSourceUrls via category mapping
2845
+ confidence: 0.7,
2846
+ },
2847
+
2848
+ phpAssetBundlingDocumented: {
2849
+ id: 'CL-PHP13',
2850
+ name: 'Vite/Mix asset bundling documented',
2851
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /vite\.config\.|webpack\.mix\.js$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /vite|mix|asset|npm run dev|npm run build/i.test(docs); },
2852
+ impact: 'low',
2853
+ category: 'php',
2854
+ fix: 'Document asset bundling setup (Vite or Mix) in project instructions.',
2855
+ // sourceUrl assigned by attachSourceUrls via category mapping
2856
+ confidence: 0.7,
2857
+ },
2858
+
2859
+ phpConfigCachingDocumented: {
2860
+ id: 'CL-PHP14',
2861
+ name: 'Laravel config caching documented',
2862
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; if (!ctx.files.some(f => /artisan$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /config:cache|config:clear|route:cache|optimize/i.test(docs); },
2863
+ impact: 'low',
2864
+ category: 'php',
2865
+ fix: 'Document config/route caching strategy (php artisan config:cache) for production.',
2866
+ // sourceUrl assigned by attachSourceUrls via category mapping
2867
+ confidence: 0.7,
2868
+ },
2869
+
2870
+ phpComposerScriptsDefined: {
2871
+ id: 'CL-PHP15',
2872
+ name: 'Composer scripts defined',
2873
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; const cj = ctx.fileContent('composer.json') || ''; return /"scripts"s*:/i.test(cj); },
2874
+ impact: 'medium',
2875
+ category: 'php',
2876
+ fix: 'Define composer scripts for common tasks (test, lint, analyze) in composer.json.',
2877
+ // sourceUrl assigned by attachSourceUrls via category mapping
2878
+ confidence: 0.7,
2879
+ },
2880
+
2881
+
2882
+ };
2883
+
2884
+ Object.assign(TECHNIQUES, buildSupplementalChecks({
2885
+ idPrefix: 'CL-T',
2886
+ urlMap: CLAUDE_SUPPLEMENTAL_SOURCE_URLS,
2887
+ docs: (ctx) => [
2888
+ ctx.claudeMdContent ? ctx.claudeMdContent() : (ctx.fileContent('CLAUDE.md') || ctx.fileContent('.claude/CLAUDE.md') || ''),
2889
+ ctx.fileContent('README.md') || '',
2890
+ ].filter(Boolean).join('\n'),
2891
+ }));
2892
+
2893
+ // Stack detection
2894
+ const STACKS = {
2895
+ react: { files: ['package.json'], content: { 'package.json': 'react' }, label: 'React' },
2896
+ vue: { files: ['package.json'], content: { 'package.json': 'vue' }, label: 'Vue' },
2897
+ angular: { files: ['angular.json'], content: {}, label: 'Angular' },
2898
+ nextjs: { files: ['next.config'], content: {}, label: 'Next.js' },
2899
+ python: { files: ['requirements.txt', 'setup.py', 'pyproject.toml', 'Pipfile'], content: {}, label: 'Python' },
2900
+ django: { files: ['manage.py'], content: {}, label: 'Django' },
2901
+ fastapi: { files: ['requirements.txt'], content: { 'requirements.txt': 'fastapi' }, label: 'FastAPI' },
2902
+ node: { files: ['package.json'], content: {}, label: 'Node.js' },
2903
+ typescript: { files: ['tsconfig.json'], content: {}, label: 'TypeScript' },
2904
+ rust: { files: ['Cargo.toml'], content: {}, label: 'Rust' },
2905
+ go: { files: ['go.mod'], content: {}, label: 'Go' },
2906
+ docker: { files: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml'], content: {}, label: 'Docker' },
2907
+ svelte: { files: ['svelte.config.js'], content: {}, label: 'Svelte' },
2908
+ flutter: { files: ['pubspec.yaml'], content: {}, label: 'Flutter' },
2909
+ ruby: { files: ['Gemfile'], content: {}, label: 'Ruby' },
2910
+ java: { files: ['pom.xml'], content: {}, label: 'Java' },
2911
+ kotlin: { files: ['build.gradle.kts'], content: {}, label: 'Kotlin' },
2912
+ swift: { files: ['Package.swift'], content: {}, label: 'Swift' },
2913
+ terraform: { files: ['main.tf', 'terraform'], content: {}, label: 'Terraform' },
2914
+ kubernetes: { files: ['k8s', 'kubernetes', 'helm'], content: {}, label: 'Kubernetes' },
2915
+ cpp: { files: ['CMakeLists.txt', 'Makefile', '.clang-format'], content: {}, label: 'C++' },
2916
+ bazel: { files: ['BUILD', 'WORKSPACE', 'BUILD.bazel', 'WORKSPACE.bazel'], content: {}, label: 'Bazel' },
2917
+ deno: { files: ['deno.json', 'deno.jsonc', 'deno.lock'], content: {}, label: 'Deno' },
2918
+ bun: { files: ['bun.lockb', 'bunfig.toml'], content: {}, label: 'Bun' },
2919
+ elixir: { files: ['mix.exs'], content: {}, label: 'Elixir' },
2920
+ astro: { files: ['astro.config.mjs', 'astro.config.ts'], content: {}, label: 'Astro' },
2921
+ remix: { files: ['remix.config.js', 'remix.config.ts'], content: {}, label: 'Remix' },
2922
+ nestjs: { files: ['nest-cli.json'], content: {}, label: 'NestJS' },
2923
+ laravel: { files: ['artisan'], content: {}, label: 'Laravel' },
2924
+ dotnet: { files: ['global.json', 'Directory.Build.props'], content: {}, label: '.NET' },
2925
+ };
2926
+
2927
+ attachSourceUrls('claude', TECHNIQUES);
2928
+
2929
+ module.exports = { TECHNIQUES, STACKS, containsEmbeddedSecret };