@nerviq/cli 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +170 -73
- package/package.json +1 -1
- package/src/activity.js +20 -0
- package/src/aider/domain-packs.js +27 -2
- package/src/aider/mcp-packs.js +231 -0
- package/src/aider/techniques.js +3210 -1397
- package/src/audit.js +257 -2
- package/src/catalog.js +18 -2
- package/src/codex/domain-packs.js +23 -1
- package/src/codex/mcp-packs.js +254 -0
- package/src/codex/techniques.js +4738 -3257
- package/src/copilot/domain-packs.js +23 -1
- package/src/copilot/mcp-packs.js +254 -0
- package/src/copilot/techniques.js +3433 -1936
- package/src/cursor/domain-packs.js +23 -1
- package/src/cursor/mcp-packs.js +257 -0
- package/src/cursor/techniques.js +3697 -1869
- package/src/deprecation.js +98 -0
- package/src/domain-pack-expansion.js +571 -0
- package/src/domain-packs.js +25 -2
- package/src/formatters/otel.js +151 -0
- package/src/gemini/domain-packs.js +23 -1
- package/src/gemini/mcp-packs.js +257 -0
- package/src/gemini/techniques.js +3734 -2238
- package/src/integrations.js +194 -0
- package/src/mcp-packs.js +233 -0
- package/src/opencode/domain-packs.js +23 -1
- package/src/opencode/mcp-packs.js +231 -0
- package/src/opencode/techniques.js +3500 -1687
- package/src/org.js +68 -0
- package/src/source-urls.js +410 -260
- package/src/stack-checks.js +565 -0
- package/src/supplemental-checks.js +767 -0
- package/src/techniques.js +2929 -1449
- package/src/telemetry.js +160 -0
- package/src/windsurf/domain-packs.js +23 -1
- package/src/windsurf/mcp-packs.js +257 -0
- package/src/windsurf/techniques.js +3647 -1834
- package/src/workspace.js +233 -0
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
const
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
},
|
|
1105
|
-
impact: 'low', rating: 3, category: '
|
|
1106
|
-
fix: '
|
|
1107
|
-
template: null
|
|
1108
|
-
},
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
id:
|
|
1138
|
-
name: 'CLAUDE.md includes
|
|
1139
|
-
check: (ctx) => {
|
|
1140
|
-
const md = ctx.claudeMdContent() || '';
|
|
1141
|
-
return /
|
|
1142
|
-
},
|
|
1143
|
-
impact: 'medium', rating:
|
|
1144
|
-
fix: 'Add
|
|
1145
|
-
template: null
|
|
1146
|
-
},
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
id:
|
|
1177
|
-
name: 'CLAUDE.md
|
|
1178
|
-
check: (ctx) => {
|
|
1179
|
-
const md = ctx.claudeMdContent() || '';
|
|
1180
|
-
return /
|
|
1181
|
-
},
|
|
1182
|
-
impact: '
|
|
1183
|
-
fix: '
|
|
1184
|
-
template: null
|
|
1185
|
-
},
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
id:
|
|
1189
|
-
name: '
|
|
1190
|
-
check: (ctx) => {
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
return
|
|
1258
|
-
},
|
|
1259
|
-
impact: '
|
|
1260
|
-
fix: '
|
|
1261
|
-
template: null
|
|
1262
|
-
},
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
id:
|
|
1266
|
-
name: '.
|
|
1267
|
-
check: (ctx) => {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
},
|
|
1297
|
-
impact: 'medium', rating: 3, category: '
|
|
1298
|
-
fix: 'Add
|
|
1299
|
-
template: null
|
|
1300
|
-
},
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
id:
|
|
1304
|
-
name: '
|
|
1305
|
-
check: (ctx) => {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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 };
|