@nerviq/cli 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +216 -124
  2. package/bin/cli.js +620 -183
  3. package/package.json +3 -2
  4. package/src/activity.js +49 -9
  5. package/src/adoption-advisor.js +299 -0
  6. package/src/aider/freshness.js +65 -20
  7. package/src/aider/techniques.js +16 -11
  8. package/src/analyze.js +128 -0
  9. package/src/anti-patterns.js +13 -0
  10. package/src/audit/instruction-files.js +180 -0
  11. package/src/audit/recommendations.js +531 -0
  12. package/src/audit.js +53 -681
  13. package/src/behavioral-drift.js +801 -0
  14. package/src/codex/freshness.js +84 -25
  15. package/src/continuous-ops.js +681 -0
  16. package/src/copilot/freshness.js +57 -20
  17. package/src/cost-tracking.js +61 -0
  18. package/src/cursor/freshness.js +65 -20
  19. package/src/cursor/techniques.js +17 -12
  20. package/src/deep-review.js +83 -0
  21. package/src/diff-only.js +280 -0
  22. package/src/doctor.js +118 -55
  23. package/src/freshness.js +74 -21
  24. package/src/gemini/freshness.js +66 -21
  25. package/src/governance.js +59 -43
  26. package/src/hook-validation.js +342 -0
  27. package/src/index.js +5 -0
  28. package/src/integrations.js +42 -5
  29. package/src/mcp-server.js +95 -59
  30. package/src/mcp-validation.js +337 -0
  31. package/src/opencode/freshness.js +66 -21
  32. package/src/opencode/techniques.js +12 -7
  33. package/src/operating-profile.js +574 -0
  34. package/src/org.js +97 -13
  35. package/src/plans.js +192 -8
  36. package/src/platform-change-manifest.js +86 -0
  37. package/src/policy-layers.js +210 -0
  38. package/src/profiles.js +4 -1
  39. package/src/prompt-injection.js +74 -0
  40. package/src/repo-archetype.js +386 -0
  41. package/src/setup/analysis.js +619 -0
  42. package/src/setup/runtime.js +172 -0
  43. package/src/setup.js +62 -748
  44. package/src/source-urls.js +132 -132
  45. package/src/supplemental-checks.js +13 -12
  46. package/src/techniques/api.js +407 -0
  47. package/src/techniques/automation.js +316 -0
  48. package/src/techniques/compliance.js +257 -0
  49. package/src/techniques/hygiene.js +294 -0
  50. package/src/techniques/instructions.js +243 -0
  51. package/src/techniques/observability.js +226 -0
  52. package/src/techniques/optimization.js +142 -0
  53. package/src/techniques/quality.js +317 -0
  54. package/src/techniques/security.js +237 -0
  55. package/src/techniques/shared.js +443 -0
  56. package/src/techniques/stacks.js +2294 -0
  57. package/src/techniques/tools.js +106 -0
  58. package/src/techniques/workflow.js +413 -0
  59. package/src/techniques.js +78 -5607
  60. package/src/watch.js +18 -0
  61. package/src/windsurf/freshness.js +36 -21
  62. package/src/windsurf/techniques.js +17 -12
@@ -0,0 +1,2294 @@
1
+ /**
2
+ * Stacks technique fragments.
3
+ * Generated mechanically from the legacy techniques.js monolith during HR-09.
4
+ */
5
+
6
+ const {
7
+ path,
8
+ findProjectFiles,
9
+ hasProjectFile,
10
+ readProjectFiles,
11
+ isPythonProject,
12
+ isGoProject,
13
+ isRustProject,
14
+ isJavaProject,
15
+ isFlutterProject,
16
+ isSwiftProject,
17
+ isKotlinProject,
18
+ getMainPythonFiles,
19
+ getPythonProjectText,
20
+ getGoFiles,
21
+ getRustFiles,
22
+ getMainRustFiles,
23
+ getMainJavaFiles,
24
+ getMainGoFiles,
25
+ getWorkflowContent,
26
+ getPreCommitContent,
27
+ getGoProjectText,
28
+ getRustProjectText,
29
+ getJavaBuildText,
30
+ getJavaProjectText,
31
+ getGoInterfaceBlocks,
32
+ countGoInterfaceMethods,
33
+ attachSourceUrls,
34
+ } = require('./shared');
35
+
36
+ module.exports = {
37
+ pyprojectTomlExists: {
38
+ id: 120001,
39
+ name: 'pyproject.toml exists for Python packaging',
40
+ check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)pyproject\.toml$/i); },
41
+ impact: 'high',
42
+ category: 'python',
43
+ fix: 'Add pyproject.toml to declare modern Python packaging, tooling, and metadata.',
44
+ // sourceUrl assigned by attachSourceUrls via category mapping
45
+ confidence: 0.7,
46
+ },
47
+
48
+ pythonTypeHints: {
49
+ id: 120002,
50
+ name: 'Type hints used in Python code',
51
+ check: (ctx) => {
52
+ if (!isPythonProject(ctx)) return null;
53
+ if (hasProjectFile(ctx, /(^|\/)(mypy\.ini|py\.typed|pyrightconfig\.json)$/i)) return true;
54
+ const pyproject = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i);
55
+ if (/\[tool\.(mypy|pyright)\]/i.test(pyproject)) return true;
56
+ const files = getMainPythonFiles(ctx);
57
+ if (files.length === 0) return null;
58
+ return files.some(file => /from typing import|import typing|from __future__ import annotations|->\s*[\w\[\]., ]+|:\s*[\w\[\]., ]+\s*=/.test(ctx.fileContent(file) || ''));
59
+ },
60
+ impact: 'medium',
61
+ category: 'python',
62
+ fix: 'Add type hints in main Python modules or configure mypy/pyright with py.typed support.',
63
+ // sourceUrl assigned by attachSourceUrls via category mapping
64
+ confidence: 0.7,
65
+ },
66
+
67
+ pythonLinter: {
68
+ id: 120003,
69
+ name: 'Python linter configured',
70
+ check: (ctx) => {
71
+ if (!isPythonProject(ctx)) return null;
72
+ const config = `${getPythonProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)(\.flake8|\.pylintrc|pylintrc|ruff\.toml|\.ruff\.toml)$/i)}`;
73
+ return /\[tool\.ruff\]|\[flake8\]|\[tool\.flake8\]|\[tool\.pylint\]|ruff|flake8|pylint/i.test(config);
74
+ },
75
+ impact: 'medium',
76
+ category: 'python',
77
+ fix: 'Configure a Python linter such as ruff, flake8, or pylint in pyproject.toml or a dedicated config file.',
78
+ // sourceUrl assigned by attachSourceUrls via category mapping
79
+ confidence: 0.7,
80
+ },
81
+
82
+ pythonFormatter: {
83
+ id: 120004,
84
+ name: 'Python formatter configured',
85
+ check: (ctx) => {
86
+ if (!isPythonProject(ctx)) return null;
87
+ const pyproject = getPythonProjectText(ctx);
88
+ const prettier = readProjectFiles(ctx, /(^|\/)\.prettierrc(\.(json|ya?ml|toml))?$/i);
89
+ return /\[tool\.black\]|\[tool\.ruff\.format\]|\[tool\.isort\]/i.test(pyproject) ||
90
+ /python|\.py\b/i.test(prettier);
91
+ },
92
+ impact: 'medium',
93
+ category: 'python',
94
+ fix: 'Configure formatting with black, ruff format, isort, or a Prettier override that explicitly covers Python files.',
95
+ // sourceUrl assigned by attachSourceUrls via category mapping
96
+ confidence: 0.7,
97
+ },
98
+
99
+ pythonTestFramework: {
100
+ id: 120005,
101
+ name: 'Python test framework present',
102
+ check: (ctx) => {
103
+ if (!isPythonProject(ctx)) return null;
104
+ return /\[tool\.pytest/i.test(getPythonProjectText(ctx)) ||
105
+ hasProjectFile(ctx, /(^|\/)(pytest\.ini|tox\.ini|conftest\.py)$/i);
106
+ },
107
+ impact: 'high',
108
+ category: 'python',
109
+ fix: 'Add pytest.ini, conftest.py, tox.ini, or pyproject.toml pytest configuration so the test framework is explicit.',
110
+ // sourceUrl assigned by attachSourceUrls via category mapping
111
+ confidence: 0.7,
112
+ },
113
+
114
+ pythonVenvIgnored: {
115
+ id: 120006,
116
+ name: 'Virtual environment directories ignored in git',
117
+ check: (ctx) => {
118
+ if (!isPythonProject(ctx)) return null;
119
+ const gitignore = ctx.fileContent('.gitignore') || '';
120
+ return /(^|\n)\s*\.venv\/?\s*($|\n)|(^|\n)\s*venv\/?\s*($|\n)|(^|\n)\s*env\/?\s*($|\n)/i.test(gitignore);
121
+ },
122
+ impact: 'medium',
123
+ category: 'python',
124
+ fix: 'Ignore `.venv/`, `venv/`, or `env/` in .gitignore so local environments do not get committed.',
125
+ // sourceUrl assigned by attachSourceUrls via category mapping
126
+ confidence: 0.7,
127
+ },
128
+
129
+ pythonRequirementsPinned: {
130
+ id: 120007,
131
+ name: 'Requirements files use pinned versions',
132
+ check: (ctx) => {
133
+ if (!isPythonProject(ctx)) return null;
134
+ const files = findProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
135
+ if (files.length === 0) return null;
136
+ const lines = files
137
+ .flatMap(file => (ctx.fileContent(file) || '').split(/\r?\n/))
138
+ .map(line => line.trim())
139
+ .filter(line => line && !line.startsWith('#'));
140
+ if (lines.length === 0) return null;
141
+ return lines.every(line => /^(-r|-c|--)/.test(line) || /==| @ /.test(line));
142
+ },
143
+ impact: 'high',
144
+ category: 'python',
145
+ fix: 'Pin Python requirements with `==` or direct references so installs stay reproducible.',
146
+ // sourceUrl assigned by attachSourceUrls via category mapping
147
+ confidence: 0.7,
148
+ },
149
+
150
+ pythonSecurityScanner: {
151
+ id: 120008,
152
+ name: 'Python security scanner configured',
153
+ check: (ctx) => {
154
+ if (!isPythonProject(ctx)) return null;
155
+ const content = `${getPythonProjectText(ctx)}\n${getWorkflowContent(ctx)}\n${getPreCommitContent(ctx)}`;
156
+ return /bandit|pip-audit|safety/i.test(content);
157
+ },
158
+ impact: 'medium',
159
+ category: 'python',
160
+ fix: 'Configure bandit, safety, or pip-audit in dependencies, pre-commit, or CI.',
161
+ // sourceUrl assigned by attachSourceUrls via category mapping
162
+ confidence: 0.7,
163
+ },
164
+
165
+ pythonPreCommitHooks: {
166
+ id: 120009,
167
+ name: 'pre-commit configured with Python hooks',
168
+ check: (ctx) => {
169
+ if (!isPythonProject(ctx)) return null;
170
+ const preCommit = getPreCommitContent(ctx);
171
+ if (!preCommit) return false;
172
+ return /ruff|black|mypy|pyupgrade|pytest|bandit|isort|flake8|pylint/i.test(preCommit);
173
+ },
174
+ impact: 'medium',
175
+ category: 'python',
176
+ fix: 'Add `.pre-commit-config.yaml` with Python-focused hooks such as ruff, black, mypy, or bandit.',
177
+ // sourceUrl assigned by attachSourceUrls via category mapping
178
+ confidence: 0.7,
179
+ },
180
+
181
+ pythonDocstrings: {
182
+ id: 120010,
183
+ name: 'Docstrings present in main Python files',
184
+ check: (ctx) => {
185
+ if (!isPythonProject(ctx)) return null;
186
+ const files = getMainPythonFiles(ctx);
187
+ if (files.length === 0) return null;
188
+ return files.some(file => /(^|\n)\s*(def|class)\s+\w+.*:\s*\n\s*("""|''')|^\s*("""|''')/m.test(ctx.fileContent(file) || ''));
189
+ },
190
+ impact: 'low',
191
+ category: 'python',
192
+ fix: 'Add module, class, or function docstrings in the main Python source files.',
193
+ // sourceUrl assigned by attachSourceUrls via category mapping
194
+ confidence: 0.7,
195
+ },
196
+
197
+ pythonCIConfigured: {
198
+ id: 120011,
199
+ name: 'CI runs Python tests',
200
+ check: (ctx) => {
201
+ if (!isPythonProject(ctx)) return null;
202
+ return /pytest|python -m pytest|python -m unittest|tox\b|nox\b/i.test(getWorkflowContent(ctx));
203
+ },
204
+ impact: 'high',
205
+ category: 'python',
206
+ fix: 'Run Python tests in CI with pytest, unittest, tox, or nox.',
207
+ // sourceUrl assigned by attachSourceUrls via category mapping
208
+ confidence: 0.7,
209
+ },
210
+
211
+ pythonCoverage: {
212
+ id: 120012,
213
+ name: 'Python coverage configured',
214
+ check: (ctx) => {
215
+ if (!isPythonProject(ctx)) return null;
216
+ const content = `${getPythonProjectText(ctx)}\n${getWorkflowContent(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.coveragerc$/i)}`;
217
+ return /\[tool\.coverage|pytest-cov|coverage\b|--cov\b/i.test(content);
218
+ },
219
+ impact: 'medium',
220
+ category: 'python',
221
+ fix: 'Configure coverage.py or pytest-cov in project config or CI.',
222
+ // sourceUrl assigned by attachSourceUrls via category mapping
223
+ confidence: 0.7,
224
+ },
225
+
226
+ pythonPackageManager: {
227
+ id: 120013,
228
+ name: 'Modern Python package manager lockfile present',
229
+ check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)(poetry\.lock|pdm\.lock|uv\.lock|Pipfile\.lock)$/); },
230
+ impact: 'medium',
231
+ category: 'python',
232
+ fix: 'Commit a Poetry, PDM, uv, or Pipenv lockfile for reproducible dependency resolution.',
233
+ // sourceUrl assigned by attachSourceUrls via category mapping
234
+ confidence: 0.7,
235
+ },
236
+
237
+ pythonMinVersionSpecified: {
238
+ id: 120014,
239
+ name: 'Minimum Python version specified',
240
+ check: (ctx) => {
241
+ if (!isPythonProject(ctx)) return null;
242
+ const content = `${getPythonProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.python-version$/i)}`;
243
+ return /requires-python|python_requires|(^|\n)\s*python\s*=|^\s*\d+\.\d+(\.\d+)?\s*$/im.test(content);
244
+ },
245
+ impact: 'medium',
246
+ category: 'python',
247
+ fix: 'Specify the supported Python version with `.python-version`, `requires-python`, or `python_requires`.',
248
+ // sourceUrl assigned by attachSourceUrls via category mapping
249
+ confidence: 0.7,
250
+ },
251
+
252
+ pythonAsyncPatterns: {
253
+ id: 120015,
254
+ name: 'Async Python patterns used',
255
+ check: (ctx) => {
256
+ if (!isPythonProject(ctx)) return null;
257
+ const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
258
+ return /asyncio|aiohttp|fastapi|starlette|trio|anyio|async def|await /i.test(content);
259
+ },
260
+ impact: 'low',
261
+ category: 'python',
262
+ fix: 'Adopt explicit async patterns such as asyncio, aiohttp, FastAPI, or `async def` where concurrent workflows matter.',
263
+ // sourceUrl assigned by attachSourceUrls via category mapping
264
+ confidence: 0.7,
265
+ },
266
+
267
+ pythonEnvExample: {
268
+ id: 120016,
269
+ name: 'Python project includes an environment example file',
270
+ check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)\.env(\.example|\.sample)$/i); },
271
+ impact: 'medium',
272
+ category: 'python',
273
+ fix: 'Add `.env.example` or `.env.sample` so required Python environment variables are documented.',
274
+ // sourceUrl assigned by attachSourceUrls via category mapping
275
+ confidence: 0.7,
276
+ },
277
+
278
+ pythonMigrations: {
279
+ id: 120017,
280
+ name: 'Python database migration tooling present',
281
+ check: (ctx) => {
282
+ if (!isPythonProject(ctx)) return null;
283
+ const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 20).map(file => ctx.fileContent(file) || '').join('\n')}`;
284
+ return /alembic|django\.db\.migrations|makemigrations|migrate/i.test(content) ||
285
+ hasProjectFile(ctx, /(^|\/)alembic\.ini$/i) ||
286
+ hasProjectFile(ctx, /(^|\/)(alembic|migrations)\//i);
287
+ },
288
+ impact: 'medium',
289
+ category: 'python',
290
+ fix: 'Use Alembic or Django migrations and keep the migration surface committed in the repo.',
291
+ // sourceUrl assigned by attachSourceUrls via category mapping
292
+ confidence: 0.7,
293
+ },
294
+
295
+ pythonLogging: {
296
+ id: 120018,
297
+ name: 'Python structured logging configured',
298
+ check: (ctx) => {
299
+ if (!isPythonProject(ctx)) return null;
300
+ const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
301
+ return /structlog|loguru|logging\.config|dictConfig|getLogger|basicConfig/i.test(content);
302
+ },
303
+ impact: 'medium',
304
+ category: 'python',
305
+ fix: 'Configure logging with Python logging config, structlog, or loguru for consistent operational signals.',
306
+ // sourceUrl assigned by attachSourceUrls via category mapping
307
+ confidence: 0.7,
308
+ },
309
+
310
+ pythonAPISchema: {
311
+ id: 120019,
312
+ name: 'Python API schema or model definitions present',
313
+ check: (ctx) => {
314
+ if (!isPythonProject(ctx)) return null;
315
+ const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
316
+ return /openapi|swagger|BaseModel|pydantic|Schema\)|marshmallow|TypedDict/i.test(content);
317
+ },
318
+ impact: 'medium',
319
+ category: 'python',
320
+ fix: 'Define API schemas with OpenAPI, Pydantic, Marshmallow, or typed request/response models.',
321
+ // sourceUrl assigned by attachSourceUrls via category mapping
322
+ confidence: 0.7,
323
+ },
324
+
325
+ pythonContainerized: {
326
+ id: 120020,
327
+ name: 'Python container image uses a Python base',
328
+ check: (ctx) => {
329
+ if (!isPythonProject(ctx)) return null;
330
+ const dockerfile = ctx.fileContent('Dockerfile') || '';
331
+ if (!dockerfile) return null;
332
+ return /FROM\s+python[:\d.-]/i.test(dockerfile);
333
+ },
334
+ impact: 'medium',
335
+ category: 'python',
336
+ fix: 'Use an official Python image such as `python:3.12-slim` when containerizing Python services.',
337
+ // sourceUrl assigned by attachSourceUrls via category mapping
338
+ confidence: 0.7,
339
+ },
340
+
341
+ pythonDependencyGroups: {
342
+ id: 120021,
343
+ name: 'Python dev and test dependency groups separated',
344
+ check: (ctx) => {
345
+ if (!isPythonProject(ctx)) return null;
346
+ const content = getPythonProjectText(ctx);
347
+ return /\[tool\.poetry\.group\.[^\]]+\]|\[project\.optional-dependencies\]|extras_require|dependency-groups/i.test(content);
348
+ },
349
+ impact: 'medium',
350
+ category: 'python',
351
+ fix: 'Separate Python dev and test dependencies with Poetry groups, optional-dependencies, or extras_require.',
352
+ // sourceUrl assigned by attachSourceUrls via category mapping
353
+ confidence: 0.7,
354
+ },
355
+
356
+ pythonPathConfig: {
357
+ id: 120022,
358
+ name: 'Python tool path configuration present',
359
+ check: (ctx) => {
360
+ if (!isPythonProject(ctx)) return null;
361
+ if (hasProjectFile(ctx, /(^|\/)pyrightconfig\.json$/i)) return true;
362
+ const vscodeSettings = findProjectFiles(ctx, /(^|\/)\.vscode\/settings\.json$/i)
363
+ .map(file => ctx.jsonFile(file) || {})
364
+ .find(settings => Object.keys(settings).some(key => key.toLowerCase().includes('python')));
365
+ return !!vscodeSettings;
366
+ },
367
+ impact: 'low',
368
+ category: 'python',
369
+ fix: 'Add `pyrightconfig.json` or VS Code Python settings so tooling resolves imports and environments consistently.',
370
+ // sourceUrl assigned by attachSourceUrls via category mapping
371
+ confidence: 0.7,
372
+ },
373
+
374
+ pythonMonorepo: {
375
+ id: 120023,
376
+ name: 'Python monorepo-friendly package layout present',
377
+ check: (ctx) => {
378
+ if (!isPythonProject(ctx)) return null;
379
+ const content = getPythonProjectText(ctx);
380
+ return ctx.hasDir('src') ||
381
+ /namespace_packages|find_namespace:|from\s*=\s*["']src["']|package-dir/i.test(content);
382
+ },
383
+ impact: 'low',
384
+ category: 'python',
385
+ fix: 'Use a `src/` layout or namespace package configuration for larger multi-package Python repos.',
386
+ // sourceUrl assigned by attachSourceUrls via category mapping
387
+ confidence: 0.7,
388
+ },
389
+
390
+ pythonErrorHandling: {
391
+ id: 120024,
392
+ name: 'Custom Python exception classes defined',
393
+ check: (ctx) => {
394
+ if (!isPythonProject(ctx)) return null;
395
+ const files = getMainPythonFiles(ctx);
396
+ if (files.length === 0) return null;
397
+ return files.some(file => /class\s+\w+(Error|Exception)\s*\((?:[\w.]*Exception|[\w.]*Error)\)\s*:/i.test(ctx.fileContent(file) || ''));
398
+ },
399
+ impact: 'low',
400
+ category: 'python',
401
+ fix: 'Define custom exception classes for domain-specific Python error handling instead of only raising generic exceptions.',
402
+ // sourceUrl assigned by attachSourceUrls via category mapping
403
+ confidence: 0.7,
404
+ },
405
+
406
+ pythonDataValidation: {
407
+ id: 120025,
408
+ name: 'Python data validation library used',
409
+ check: (ctx) => {
410
+ if (!isPythonProject(ctx)) return null;
411
+ const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
412
+ return /pydantic|marshmallow|attrs|attr\.s|BaseModel|Schema\)/i.test(content);
413
+ },
414
+ impact: 'medium',
415
+ category: 'python',
416
+ fix: 'Use Pydantic, Marshmallow, attrs, or similar validation libraries for structured Python inputs and models.',
417
+ // sourceUrl assigned by attachSourceUrls via category mapping
418
+ confidence: 0.7,
419
+ },
420
+
421
+ goModExists: {
422
+ id: 120101,
423
+ name: 'go.mod exists for Go module management',
424
+ check: (ctx) => { if (!isGoProject(ctx)) return null; return true; },
425
+ impact: 'high',
426
+ category: 'go',
427
+ fix: 'Initialize the repository as a Go module with `go mod init` and commit `go.mod`.',
428
+ // sourceUrl assigned by attachSourceUrls via category mapping
429
+ confidence: 0.7,
430
+ },
431
+
432
+ goLinter: {
433
+ id: 120102,
434
+ name: 'Go linter configured',
435
+ check: (ctx) => {
436
+ if (!isGoProject(ctx)) return null;
437
+ const content = `${getGoProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.golangci\.(ya?ml|toml)$/i)}`;
438
+ return /\.golangci\.|golangci-lint/i.test(content);
439
+ },
440
+ impact: 'medium',
441
+ category: 'go',
442
+ fix: 'Configure golangci-lint in the repo or CI for consistent Go lint enforcement.',
443
+ // sourceUrl assigned by attachSourceUrls via category mapping
444
+ confidence: 0.7,
445
+ },
446
+
447
+ goTestFiles: {
448
+ id: 120103,
449
+ name: 'Go test files present',
450
+ check: (ctx) => { if (!isGoProject(ctx)) return null; return hasProjectFile(ctx, /_test\.go$/i); },
451
+ impact: 'high',
452
+ category: 'go',
453
+ fix: 'Add `_test.go` files so Go packages have executable unit or integration tests.',
454
+ // sourceUrl assigned by attachSourceUrls via category mapping
455
+ confidence: 0.7,
456
+ },
457
+
458
+ goVet: {
459
+ id: 120104,
460
+ name: 'go vet runs in automation',
461
+ check: (ctx) => {
462
+ if (!isGoProject(ctx)) return null;
463
+ const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
464
+ return /go vet/i.test(content);
465
+ },
466
+ impact: 'medium',
467
+ category: 'go',
468
+ fix: 'Run `go vet` in CI or the project Makefile to catch common Go mistakes.',
469
+ // sourceUrl assigned by attachSourceUrls via category mapping
470
+ confidence: 0.7,
471
+ },
472
+
473
+ goFmt: {
474
+ id: 120105,
475
+ name: 'gofmt or goimports enforced',
476
+ check: (ctx) => {
477
+ if (!isGoProject(ctx)) return null;
478
+ const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}\n${getPreCommitContent(ctx)}`;
479
+ return /gofmt|goimports/i.test(content);
480
+ },
481
+ impact: 'medium',
482
+ category: 'go',
483
+ fix: 'Run `gofmt` or `goimports` in CI, pre-commit, or developer tooling.',
484
+ // sourceUrl assigned by attachSourceUrls via category mapping
485
+ confidence: 0.7,
486
+ },
487
+
488
+ goModTidy: {
489
+ id: 120106,
490
+ name: 'go mod tidy runs in automation',
491
+ check: (ctx) => {
492
+ if (!isGoProject(ctx)) return null;
493
+ const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
494
+ return /go mod tidy/i.test(content);
495
+ },
496
+ impact: 'medium',
497
+ category: 'go',
498
+ fix: 'Run `go mod tidy` in CI or the Makefile so module metadata stays clean.',
499
+ // sourceUrl assigned by attachSourceUrls via category mapping
500
+ confidence: 0.7,
501
+ },
502
+
503
+ goBuildTags: {
504
+ id: 120107,
505
+ name: 'Go build tags or constraints used',
506
+ check: (ctx) => {
507
+ if (!isGoProject(ctx)) return null;
508
+ const files = getGoFiles(ctx);
509
+ if (files.length === 0) return null;
510
+ return files.some(file => /\/\/go:build|\/\/ \+build/.test(ctx.fileContent(file) || ''));
511
+ },
512
+ impact: 'low',
513
+ category: 'go',
514
+ fix: 'Use `//go:build` constraints when a Go package depends on build tags or platform-specific variants.',
515
+ // sourceUrl assigned by attachSourceUrls via category mapping
516
+ confidence: 0.7,
517
+ },
518
+
519
+ goErrorWrapping: {
520
+ id: 120108,
521
+ name: 'Go errors use wrapping patterns',
522
+ check: (ctx) => {
523
+ if (!isGoProject(ctx)) return null;
524
+ const files = getMainGoFiles(ctx);
525
+ if (files.length === 0) return null;
526
+ return files.some(file => /fmt\.Errorf\([^)]*%w|errors\.Join\(/.test(ctx.fileContent(file) || ''));
527
+ },
528
+ impact: 'medium',
529
+ category: 'go',
530
+ fix: 'Wrap Go errors with `fmt.Errorf(... %w ...)` or similar patterns to preserve context.',
531
+ // sourceUrl assigned by attachSourceUrls via category mapping
532
+ confidence: 0.7,
533
+ },
534
+
535
+ goInterfaceSegregation: {
536
+ id: 120109,
537
+ name: 'Go interfaces stay small',
538
+ check: (ctx) => {
539
+ if (!isGoProject(ctx)) return null;
540
+ const interfaces = getGoInterfaceBlocks(ctx);
541
+ if (interfaces.length === 0) return null;
542
+ return interfaces.every(block => countGoInterfaceMethods(block) <= 5);
543
+ },
544
+ impact: 'low',
545
+ category: 'go',
546
+ fix: 'Keep Go interfaces small and focused; split interfaces that define more than five methods.',
547
+ // sourceUrl assigned by attachSourceUrls via category mapping
548
+ confidence: 0.7,
549
+ },
550
+
551
+ goContextUsage: {
552
+ id: 120110,
553
+ name: 'Go services use context.Context',
554
+ check: (ctx) => {
555
+ if (!isGoProject(ctx)) return null;
556
+ const files = getMainGoFiles(ctx);
557
+ if (files.length === 0) return null;
558
+ return files.some(file => /context\.Context|context\.With(Cancel|Timeout|Deadline)|ctx\s+context\.Context/.test(ctx.fileContent(file) || ''));
559
+ },
560
+ impact: 'medium',
561
+ category: 'go',
562
+ fix: 'Pass `context.Context` through handlers and services so cancellation and deadlines are propagated correctly.',
563
+ // sourceUrl assigned by attachSourceUrls via category mapping
564
+ confidence: 0.7,
565
+ },
566
+
567
+ goStructTags: {
568
+ id: 120111,
569
+ name: 'Exported Go structs include tags',
570
+ check: (ctx) => {
571
+ if (!isGoProject(ctx)) return null;
572
+ const structBlocks = [];
573
+ for (const file of getMainGoFiles(ctx)) {
574
+ const content = ctx.fileContent(file) || '';
575
+ for (const match of content.matchAll(/type\s+([A-Z]\w*)\s+struct\s*\{([\s\S]*?)\}/g)) {
576
+ structBlocks.push(match[2]);
577
+ }
578
+ }
579
+ if (structBlocks.length === 0) return null;
580
+ return structBlocks.some(block => /`[^`]*(json|yaml|db):"/.test(block));
581
+ },
582
+ impact: 'low',
583
+ category: 'go',
584
+ fix: 'Add struct tags such as `json`, `yaml`, or `db` on exported Go types that cross boundaries.',
585
+ // sourceUrl assigned by attachSourceUrls via category mapping
586
+ confidence: 0.7,
587
+ },
588
+
589
+ goMakefile: {
590
+ id: 120112,
591
+ name: 'Go Makefile includes build, test, and lint targets',
592
+ check: (ctx) => {
593
+ if (!isGoProject(ctx)) return null;
594
+ const makefile = ctx.fileContent('Makefile') || '';
595
+ if (!makefile) return false;
596
+ return /^\s*build:/m.test(makefile) && /^\s*test:/m.test(makefile) && /^\s*lint:/m.test(makefile);
597
+ },
598
+ impact: 'medium',
599
+ category: 'go',
600
+ fix: 'Add a Makefile with `build`, `test`, and `lint` targets for common Go workflows.',
601
+ // sourceUrl assigned by attachSourceUrls via category mapping
602
+ confidence: 0.7,
603
+ },
604
+
605
+ goDocComments: {
606
+ id: 120113,
607
+ name: 'Exported Go functions have doc comments',
608
+ check: (ctx) => {
609
+ if (!isGoProject(ctx)) return null;
610
+ const files = getMainGoFiles(ctx);
611
+ if (files.length === 0) return null;
612
+ const documented = files.some(file => /\/\/\s*[A-Z]\w+.*\nfunc\s+(?:\([^)]+\)\s*)?[A-Z]\w+\s*\(/.test(ctx.fileContent(file) || ''));
613
+ const exported = files.some(file => /func\s+(?:\([^)]+\)\s*)?[A-Z]\w+\s*\(/.test(ctx.fileContent(file) || ''));
614
+ if (!exported) return null;
615
+ return documented;
616
+ },
617
+ impact: 'low',
618
+ category: 'go',
619
+ fix: 'Add Go doc comments above exported functions so package APIs remain self-describing.',
620
+ // sourceUrl assigned by attachSourceUrls via category mapping
621
+ confidence: 0.7,
622
+ },
623
+
624
+ goSecurityScanner: {
625
+ id: 120114,
626
+ name: 'Go security scanner configured',
627
+ check: (ctx) => {
628
+ if (!isGoProject(ctx)) return null;
629
+ return /gosec|staticcheck/i.test(getGoProjectText(ctx));
630
+ },
631
+ impact: 'medium',
632
+ category: 'go',
633
+ fix: 'Configure `gosec` or `staticcheck` in CI or the Makefile for Go security and static analysis checks.',
634
+ // sourceUrl assigned by attachSourceUrls via category mapping
635
+ confidence: 0.7,
636
+ },
637
+
638
+ goCIConfigured: {
639
+ id: 120115,
640
+ name: 'CI runs Go tests',
641
+ check: (ctx) => {
642
+ if (!isGoProject(ctx)) return null;
643
+ return /go test(\s|$)|go test \.\/\.\.\./i.test(getWorkflowContent(ctx));
644
+ },
645
+ impact: 'high',
646
+ category: 'go',
647
+ fix: 'Run `go test ./...` in CI so Go packages are verified on every change.',
648
+ // sourceUrl assigned by attachSourceUrls via category mapping
649
+ confidence: 0.7,
650
+ },
651
+
652
+ goContainerized: {
653
+ id: 120116,
654
+ name: 'Go Dockerfile uses multi-stage build',
655
+ check: (ctx) => {
656
+ if (!isGoProject(ctx)) return null;
657
+ const dockerfile = ctx.fileContent('Dockerfile') || '';
658
+ if (!dockerfile) return null;
659
+ return /FROM\s+golang[:\d.-].*\bAS\b/i.test(dockerfile) &&
660
+ /FROM\s+(alpine|scratch|distroless|gcr\.io|cgr\.dev)/i.test(dockerfile);
661
+ },
662
+ impact: 'medium',
663
+ category: 'go',
664
+ fix: 'Use a multi-stage Go Dockerfile: compile in a `golang` image and run from a minimal final image.',
665
+ // sourceUrl assigned by attachSourceUrls via category mapping
666
+ confidence: 0.7,
667
+ },
668
+
669
+ goCoverageConfigured: {
670
+ id: 120117,
671
+ name: 'Go coverage reporting configured',
672
+ check: (ctx) => {
673
+ if (!isGoProject(ctx)) return null;
674
+ const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
675
+ return /go test[^\n]*-cover/i.test(content);
676
+ },
677
+ impact: 'medium',
678
+ category: 'go',
679
+ fix: 'Add `go test -cover` to CI or developer commands so Go coverage is tracked explicitly.',
680
+ // sourceUrl assigned by attachSourceUrls via category mapping
681
+ confidence: 0.7,
682
+ },
683
+
684
+ goAPIFramework: {
685
+ id: 120118,
686
+ name: 'Go HTTP framework detected',
687
+ check: (ctx) => {
688
+ if (!isGoProject(ctx)) return null;
689
+ return /gin-gonic\/gin|labstack\/echo|gofiber\/fiber|go-chi\/chi|gin\.Default\(|echo\.New\(|fiber\.New\(|chi\.NewRouter\(/i.test(getGoProjectText(ctx));
690
+ },
691
+ impact: 'low',
692
+ category: 'go',
693
+ fix: 'Use a well-supported Go HTTP framework such as Gin, Echo, Fiber, or Chi when building API services.',
694
+ // sourceUrl assigned by attachSourceUrls via category mapping
695
+ confidence: 0.7,
696
+ },
697
+
698
+ goMigrationTool: {
699
+ id: 120119,
700
+ name: 'Go database migration tooling present',
701
+ check: (ctx) => {
702
+ if (!isGoProject(ctx)) return null;
703
+ return /golang-migrate|pressly\/goose|atlasgo|atlas\s/i.test(getGoProjectText(ctx)) ||
704
+ hasProjectFile(ctx, /(^|\/)(migrations|db\/migrations)\//i);
705
+ },
706
+ impact: 'medium',
707
+ category: 'go',
708
+ fix: 'Add a Go migration tool such as golang-migrate, goose, or Atlas and keep migration files in the repo.',
709
+ // sourceUrl assigned by attachSourceUrls via category mapping
710
+ confidence: 0.7,
711
+ },
712
+
713
+ goDependencyInjection: {
714
+ id: 120120,
715
+ name: 'Go dependency injection pattern present',
716
+ check: (ctx) => {
717
+ if (!isGoProject(ctx)) return null;
718
+ return /google\/wire|uber-go\/fx|uber-go\/dig|wire\.Build\(|fx\.New\(|dig\.New\(/i.test(getGoProjectText(ctx));
719
+ },
720
+ impact: 'low',
721
+ category: 'go',
722
+ fix: 'Use Wire, Fx, Dig, or an equivalent composition pattern when Go dependency graphs become complex.',
723
+ // sourceUrl assigned by attachSourceUrls via category mapping
724
+ confidence: 0.7,
725
+ },
726
+
727
+ cargoTomlExists: {
728
+ id: 120201,
729
+ name: 'Cargo.toml exists',
730
+ check: (ctx) => {
731
+ if (!isRustProject(ctx)) return null;
732
+ return true;
733
+ },
734
+ impact: 'high',
735
+ category: 'rust',
736
+ fix: 'Add a `Cargo.toml` manifest so Rust dependencies, metadata, and build settings are tracked explicitly.',
737
+ // sourceUrl assigned by attachSourceUrls via category mapping
738
+ confidence: 0.7,
739
+ },
740
+
741
+ rustEdition: {
742
+ id: 120202,
743
+ name: 'Rust edition specified in Cargo.toml',
744
+ check: (ctx) => {
745
+ if (!isRustProject(ctx)) return null;
746
+ return /edition\s*=\s*"20(18|21|24)"/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
747
+ },
748
+ impact: 'high',
749
+ category: 'rust',
750
+ fix: 'Specify a Rust edition such as `edition = "2021"` in `Cargo.toml` so tooling and language semantics are pinned.',
751
+ // sourceUrl assigned by attachSourceUrls via category mapping
752
+ confidence: 0.7,
753
+ },
754
+
755
+ rustClippy: {
756
+ id: 120203,
757
+ name: 'Clippy configured',
758
+ check: (ctx) => {
759
+ if (!isRustProject(ctx)) return null;
760
+ return hasProjectFile(ctx, /(^|\/)(clippy\.toml|\.clippy\.toml)$/i) ||
761
+ /clippy/i.test(`${readProjectFiles(ctx, /(^|\/)\.cargo\/config\.toml$/i)}\n${getWorkflowContent(ctx)}\n${getPreCommitContent(ctx)}`);
762
+ },
763
+ impact: 'medium',
764
+ category: 'rust',
765
+ fix: 'Configure `cargo clippy` in CI, pre-commit, or `.cargo/config.toml` so linting is enforced consistently.',
766
+ // sourceUrl assigned by attachSourceUrls via category mapping
767
+ confidence: 0.7,
768
+ },
769
+
770
+ rustFmt: {
771
+ id: 120204,
772
+ name: 'rustfmt configured',
773
+ check: (ctx) => {
774
+ if (!isRustProject(ctx)) return null;
775
+ return hasProjectFile(ctx, /(^|\/)(rustfmt\.toml|\.rustfmt\.toml)$/i);
776
+ },
777
+ impact: 'medium',
778
+ category: 'rust',
779
+ fix: 'Add `rustfmt.toml` or `.rustfmt.toml` to capture Rust formatting expectations in version control.',
780
+ // sourceUrl assigned by attachSourceUrls via category mapping
781
+ confidence: 0.7,
782
+ },
783
+
784
+ rustTestsExist: {
785
+ id: 120205,
786
+ name: 'Rust tests exist',
787
+ check: (ctx) => {
788
+ if (!isRustProject(ctx)) return null;
789
+ const files = getRustFiles(ctx);
790
+ if (files.length === 0) return null;
791
+ return hasProjectFile(ctx, /(^|\/)tests\//i) ||
792
+ files.some(file => /#\s*\[\s*test\s*\]/.test(ctx.fileContent(file) || ''));
793
+ },
794
+ impact: 'high',
795
+ category: 'rust',
796
+ fix: 'Add Rust unit or integration tests using `#[test]` functions or a `tests/` directory.',
797
+ // sourceUrl assigned by attachSourceUrls via category mapping
798
+ confidence: 0.7,
799
+ },
800
+
801
+ rustBenchmarks: {
802
+ id: 120206,
803
+ name: 'Rust benchmarks present',
804
+ check: (ctx) => {
805
+ if (!isRustProject(ctx)) return null;
806
+ return hasProjectFile(ctx, /(^|\/)benches\//i) ||
807
+ /#\s*\[\s*bench\s*\]|criterion/i.test(getRustProjectText(ctx));
808
+ },
809
+ impact: 'low',
810
+ category: 'rust',
811
+ fix: 'Add Rust benchmarks through `benches/`, `criterion`, or benchmark annotations when performance-sensitive code matters.',
812
+ // sourceUrl assigned by attachSourceUrls via category mapping
813
+ confidence: 0.7,
814
+ },
815
+
816
+ rustCIConfigured: {
817
+ id: 120207,
818
+ name: 'CI runs cargo test',
819
+ check: (ctx) => {
820
+ if (!isRustProject(ctx)) return null;
821
+ return /cargo test(\s|$)/i.test(getWorkflowContent(ctx));
822
+ },
823
+ impact: 'high',
824
+ category: 'rust',
825
+ fix: 'Run `cargo test` in CI so Rust correctness is verified automatically on every change.',
826
+ // sourceUrl assigned by attachSourceUrls via category mapping
827
+ confidence: 0.7,
828
+ },
829
+
830
+ rustCargoLock: {
831
+ id: 120208,
832
+ name: 'Cargo.lock handling is appropriate',
833
+ check: (ctx) => {
834
+ if (!isRustProject(ctx)) return null;
835
+ const cargoText = readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i);
836
+ const hasLock = hasProjectFile(ctx, /(^|\/)Cargo\.lock$/i);
837
+ const gitignore = ctx.fileContent('.gitignore') || '';
838
+ const libraryOnly = /\[lib\]/i.test(cargoText) && !/\[\[bin\]\]|src\/main\.rs/i.test(getRustProjectText(ctx));
839
+ if (libraryOnly) return hasLock || /(^|\r?\n)\s*Cargo\.lock\s*$/m.test(gitignore);
840
+ return hasLock;
841
+ },
842
+ impact: 'medium',
843
+ category: 'rust',
844
+ fix: 'Commit `Cargo.lock` for binaries, or explicitly ignore it for library-only crates when that is your chosen policy.',
845
+ // sourceUrl assigned by attachSourceUrls via category mapping
846
+ confidence: 0.7,
847
+ },
848
+
849
+ rustUnsafeBlocks: {
850
+ id: 120209,
851
+ name: 'Unsafe blocks are documented',
852
+ check: (ctx) => {
853
+ if (!isRustProject(ctx)) return null;
854
+ const files = getRustFiles(ctx);
855
+ const unsafeFiles = files.filter(file => /\bunsafe\b/.test(ctx.fileContent(file) || ''));
856
+ if (unsafeFiles.length === 0) return true;
857
+ return unsafeFiles.every(file => /SAFETY:|\/\/\s*SAFETY|\/\*\s*SAFETY/i.test(ctx.fileContent(file) || ''));
858
+ },
859
+ impact: 'medium',
860
+ category: 'rust',
861
+ fix: 'Document each `unsafe` block with a nearby `SAFETY:` comment explaining the invariants being upheld.',
862
+ // sourceUrl assigned by attachSourceUrls via category mapping
863
+ confidence: 0.7,
864
+ },
865
+
866
+ rustErrorHandling: {
867
+ id: 120210,
868
+ name: 'Rust error handling strategy present',
869
+ check: (ctx) => {
870
+ if (!isRustProject(ctx)) return null;
871
+ return /thiserror|anyhow|eyre|impl\s+std::error::Error|enum\s+\w+Error|struct\s+\w+Error/i.test(getRustProjectText(ctx));
872
+ },
873
+ impact: 'medium',
874
+ category: 'rust',
875
+ fix: 'Use `thiserror`, `anyhow`, or explicit error types so Rust errors remain structured and descriptive.',
876
+ // sourceUrl assigned by attachSourceUrls via category mapping
877
+ confidence: 0.7,
878
+ },
879
+
880
+ rustAsync: {
881
+ id: 120211,
882
+ name: 'Rust async runtime configured',
883
+ check: (ctx) => {
884
+ if (!isRustProject(ctx)) return null;
885
+ return /tokio|async-std|smol/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
886
+ },
887
+ impact: 'medium',
888
+ category: 'rust',
889
+ fix: 'Declare an async runtime such as Tokio or async-std when the Rust project uses asynchronous workflows.',
890
+ // sourceUrl assigned by attachSourceUrls via category mapping
891
+ confidence: 0.7,
892
+ },
893
+
894
+ rustSerde: {
895
+ id: 120212,
896
+ name: 'Serde serialization configured',
897
+ check: (ctx) => {
898
+ if (!isRustProject(ctx)) return null;
899
+ return /\bserde(_json|_yaml)?\b/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
900
+ },
901
+ impact: 'low',
902
+ category: 'rust',
903
+ fix: 'Add `serde` and related crates when Rust data crosses process, storage, or network boundaries.',
904
+ // sourceUrl assigned by attachSourceUrls via category mapping
905
+ confidence: 0.7,
906
+ },
907
+
908
+ rustDocComments: {
909
+ id: 120213,
910
+ name: 'Public Rust items have doc comments',
911
+ check: (ctx) => {
912
+ if (!isRustProject(ctx)) return null;
913
+ const files = getMainRustFiles(ctx);
914
+ const exported = files.some(file => /\bpub\s+(fn|struct|enum|trait|mod|const|type)\b/.test(ctx.fileContent(file) || ''));
915
+ if (!exported) return null;
916
+ return files.some(file => /\/\/\/[^\n]*\n\s*pub\s+(fn|struct|enum|trait|mod|const|type)\b/.test(ctx.fileContent(file) || ''));
917
+ },
918
+ impact: 'low',
919
+ category: 'rust',
920
+ fix: 'Add `///` doc comments above public Rust APIs so crates are easier to consume and maintain.',
921
+ // sourceUrl assigned by attachSourceUrls via category mapping
922
+ confidence: 0.7,
923
+ },
924
+
925
+ rustSecurityAudit: {
926
+ id: 120214,
927
+ name: 'Rust security audit tooling configured',
928
+ check: (ctx) => {
929
+ if (!isRustProject(ctx)) return null;
930
+ return /cargo-audit|cargo deny|cargo-deny/i.test(getRustProjectText(ctx));
931
+ },
932
+ impact: 'medium',
933
+ category: 'rust',
934
+ fix: 'Configure `cargo-audit` or `cargo-deny` in CI or project automation to scan Rust dependencies for risk.',
935
+ // sourceUrl assigned by attachSourceUrls via category mapping
936
+ confidence: 0.7,
937
+ },
938
+
939
+ rustMSRV: {
940
+ id: 120215,
941
+ name: 'Minimum supported Rust version specified',
942
+ check: (ctx) => {
943
+ if (!isRustProject(ctx)) return null;
944
+ return /rust-version\s*=/.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
945
+ },
946
+ impact: 'medium',
947
+ category: 'rust',
948
+ fix: 'Set `rust-version` in `Cargo.toml` so the project’s MSRV is explicit for contributors and CI.',
949
+ // sourceUrl assigned by attachSourceUrls via category mapping
950
+ confidence: 0.7,
951
+ },
952
+
953
+ rustWorkspace: {
954
+ id: 120216,
955
+ name: 'Cargo workspace configured for multi-crate projects',
956
+ check: (ctx) => {
957
+ if (!isRustProject(ctx)) return null;
958
+ const cargoFiles = findProjectFiles(ctx, /(^|\/)Cargo\.toml$/i);
959
+ if (cargoFiles.length <= 1) return null;
960
+ return /\[workspace\]/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
961
+ },
962
+ impact: 'medium',
963
+ category: 'rust',
964
+ fix: 'Add a root Cargo workspace when the Rust repository contains multiple crates.',
965
+ // sourceUrl assigned by attachSourceUrls via category mapping
966
+ confidence: 0.7,
967
+ },
968
+
969
+ rustBuildScript: {
970
+ id: 120217,
971
+ name: 'Rust build script present when needed',
972
+ check: (ctx) => {
973
+ if (!isRustProject(ctx)) return null;
974
+ return hasProjectFile(ctx, /(^|\/)build\.rs$/i);
975
+ },
976
+ impact: 'low',
977
+ category: 'rust',
978
+ fix: 'Use `build.rs` when the project needs generated bindings, codegen, or compile-time environment setup.',
979
+ // sourceUrl assigned by attachSourceUrls via category mapping
980
+ confidence: 0.7,
981
+ },
982
+
983
+ rustFeatureFlags: {
984
+ id: 120218,
985
+ name: 'Rust feature flags defined',
986
+ check: (ctx) => {
987
+ if (!isRustProject(ctx)) return null;
988
+ return /\[features\]/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
989
+ },
990
+ impact: 'low',
991
+ category: 'rust',
992
+ fix: 'Define Cargo feature flags when Rust functionality needs optional capabilities or slimmed dependency sets.',
993
+ // sourceUrl assigned by attachSourceUrls via category mapping
994
+ confidence: 0.7,
995
+ },
996
+
997
+ rustCrossCompilation: {
998
+ id: 120219,
999
+ name: 'Rust cross-compilation targets configured',
1000
+ check: (ctx) => {
1001
+ if (!isRustProject(ctx)) return null;
1002
+ return /--target|rustup target add|target\.[\w.-]+|cross build|cross test|cargo zigbuild/i.test(getRustProjectText(ctx));
1003
+ },
1004
+ impact: 'low',
1005
+ category: 'rust',
1006
+ fix: 'Configure Rust cross-compilation targets in CI or `.cargo/config.toml` when builds must run across architectures or platforms.',
1007
+ // sourceUrl assigned by attachSourceUrls via category mapping
1008
+ confidence: 0.7,
1009
+ },
1010
+
1011
+ rustContainerized: {
1012
+ id: 120220,
1013
+ name: 'Rust Dockerfile present',
1014
+ check: (ctx) => {
1015
+ if (!isRustProject(ctx)) return null;
1016
+ return /FROM\s+rust|cargo\s+(build|chef|install|test)/i.test(ctx.fileContent('Dockerfile') || '');
1017
+ },
1018
+ impact: 'low',
1019
+ category: 'rust',
1020
+ fix: 'Use a Dockerfile that references Rust or Cargo when the project’s build and release flow is containerized.',
1021
+ // sourceUrl assigned by attachSourceUrls via category mapping
1022
+ confidence: 0.7,
1023
+ },
1024
+
1025
+ mavenOrGradle: {
1026
+ id: 120301,
1027
+ name: 'Maven or Gradle build file exists',
1028
+ check: (ctx) => {
1029
+ if (!isJavaProject(ctx)) return null;
1030
+ return true;
1031
+ },
1032
+ impact: 'high',
1033
+ category: 'java',
1034
+ fix: 'Add `pom.xml`, `build.gradle`, or `build.gradle.kts` so the Java build is defined in version control.',
1035
+ // sourceUrl assigned by attachSourceUrls via category mapping
1036
+ confidence: 0.7,
1037
+ },
1038
+
1039
+ javaVersion: {
1040
+ id: 120302,
1041
+ name: 'Java version specified',
1042
+ check: (ctx) => {
1043
+ if (!isJavaProject(ctx)) return null;
1044
+ return /java\.version|maven\.compiler\.(source|target|release)|sourceCompatibility|targetCompatibility|JavaLanguageVersion|toolchain/i.test(getJavaBuildText(ctx)) ||
1045
+ hasProjectFile(ctx, /(^|\/)\.java-version$/i);
1046
+ },
1047
+ impact: 'high',
1048
+ category: 'java',
1049
+ fix: 'Specify the Java version in Maven or Gradle so compilation and runtime expectations stay explicit.',
1050
+ // sourceUrl assigned by attachSourceUrls via category mapping
1051
+ confidence: 0.7,
1052
+ },
1053
+
1054
+ springBootDetected: {
1055
+ id: 120303,
1056
+ name: 'Spring Boot detected',
1057
+ check: (ctx) => {
1058
+ if (!isJavaProject(ctx)) return null;
1059
+ return /spring-boot/i.test(getJavaBuildText(ctx));
1060
+ },
1061
+ impact: 'medium',
1062
+ category: 'java',
1063
+ fix: 'Use Spring Boot dependencies when the Java service relies on Spring auto-configuration and conventions.',
1064
+ // sourceUrl assigned by attachSourceUrls via category mapping
1065
+ confidence: 0.7,
1066
+ },
1067
+
1068
+ javaTestFramework: {
1069
+ id: 120304,
1070
+ name: 'Java test framework configured',
1071
+ check: (ctx) => {
1072
+ if (!isJavaProject(ctx)) return null;
1073
+ return /junit|testng|spring-boot-starter-test/i.test(getJavaBuildText(ctx));
1074
+ },
1075
+ impact: 'high',
1076
+ category: 'java',
1077
+ fix: 'Add JUnit, TestNG, or Spring Boot test dependencies so Java tests have a standard runner.',
1078
+ // sourceUrl assigned by attachSourceUrls via category mapping
1079
+ confidence: 0.7,
1080
+ },
1081
+
1082
+ javaLinter: {
1083
+ id: 120305,
1084
+ name: 'Java linter configured',
1085
+ check: (ctx) => {
1086
+ if (!isJavaProject(ctx)) return null;
1087
+ return /checkstyle|spotbugs|pmd/i.test(getJavaProjectText(ctx)) ||
1088
+ hasProjectFile(ctx, /(^|\/)(checkstyle\.xml|spotbugs.*\.xml|pmd\.xml)$/i);
1089
+ },
1090
+ impact: 'medium',
1091
+ category: 'java',
1092
+ fix: 'Configure Checkstyle, SpotBugs, or PMD so Java code quality rules run consistently.',
1093
+ // sourceUrl assigned by attachSourceUrls via category mapping
1094
+ confidence: 0.7,
1095
+ },
1096
+
1097
+ javaFormatter: {
1098
+ id: 120306,
1099
+ name: 'Java formatter configured',
1100
+ check: (ctx) => {
1101
+ if (!isJavaProject(ctx)) return null;
1102
+ return /google-java-format|spotless/i.test(getJavaBuildText(ctx)) ||
1103
+ hasProjectFile(ctx, /(^|\/)\.editorconfig$/i);
1104
+ },
1105
+ impact: 'medium',
1106
+ category: 'java',
1107
+ fix: 'Configure Spotless, google-java-format, or an `.editorconfig` so Java formatting stays consistent.',
1108
+ // sourceUrl assigned by attachSourceUrls via category mapping
1109
+ confidence: 0.7,
1110
+ },
1111
+
1112
+ javaCIConfigured: {
1113
+ id: 120307,
1114
+ name: 'CI runs Java tests',
1115
+ check: (ctx) => {
1116
+ if (!isJavaProject(ctx)) return null;
1117
+ return /(?:mvn|mvnw)\s+test|(?:gradle|gradlew)\s+test/i.test(getWorkflowContent(ctx));
1118
+ },
1119
+ impact: 'high',
1120
+ category: 'java',
1121
+ fix: 'Run `mvn test`, `mvnw test`, `gradle test`, or `gradlew test` in CI so Java changes are validated automatically.',
1122
+ // sourceUrl assigned by attachSourceUrls via category mapping
1123
+ confidence: 0.7,
1124
+ },
1125
+
1126
+ javaSecurityScanner: {
1127
+ id: 120308,
1128
+ name: 'Java security scanner configured',
1129
+ check: (ctx) => {
1130
+ if (!isJavaProject(ctx)) return null;
1131
+ return /dependency-check|snyk|spotbugs-security|findsecbugs/i.test(getJavaProjectText(ctx));
1132
+ },
1133
+ impact: 'medium',
1134
+ category: 'java',
1135
+ fix: 'Configure OWASP Dependency-Check, Snyk, or SpotBugs security rules for Java dependency and code scanning.',
1136
+ // sourceUrl assigned by attachSourceUrls via category mapping
1137
+ confidence: 0.7,
1138
+ },
1139
+
1140
+ javaDocumentation: {
1141
+ id: 120309,
1142
+ name: 'Java documentation configured',
1143
+ check: (ctx) => {
1144
+ if (!isJavaProject(ctx)) return null;
1145
+ if (/javadoc/i.test(getJavaBuildText(ctx))) return true;
1146
+ const files = getMainJavaFiles(ctx);
1147
+ const publicTypes = files.some(file => /\bpublic\s+(class|interface|enum|record)\s+[A-Z]\w*/.test(ctx.fileContent(file) || ''));
1148
+ if (!publicTypes) return null;
1149
+ return files.some(file => /\/\*\*[\s\S]*?\*\/\s*public\s+(class|interface|enum|record)\s+[A-Z]\w*/.test(ctx.fileContent(file) || ''));
1150
+ },
1151
+ impact: 'low',
1152
+ category: 'java',
1153
+ fix: 'Generate Javadocs or add doc comments on public Java types so the API remains understandable to contributors.',
1154
+ // sourceUrl assigned by attachSourceUrls via category mapping
1155
+ confidence: 0.7,
1156
+ },
1157
+
1158
+ javaProfiles: {
1159
+ id: 120310,
1160
+ name: 'Java profiles or build variants configured',
1161
+ check: (ctx) => {
1162
+ if (!isJavaProject(ctx)) return null;
1163
+ return /<profiles>|spring\.profiles|@Profile|profiles\s*\{|buildTypes\s*\{|productFlavors\s*\{/i.test(getJavaProjectText(ctx));
1164
+ },
1165
+ impact: 'low',
1166
+ category: 'java',
1167
+ fix: 'Use Maven profiles, Spring profiles, or Gradle build variants when Java environments need explicit separation.',
1168
+ // sourceUrl assigned by attachSourceUrls via category mapping
1169
+ confidence: 0.7,
1170
+ },
1171
+
1172
+ javaContainerized: {
1173
+ id: 120311,
1174
+ name: 'Java Dockerfile references Java build/runtime',
1175
+ check: (ctx) => {
1176
+ if (!isJavaProject(ctx)) return null;
1177
+ return /FROM\s+(?:maven|gradle|openjdk|eclipse-temurin|amazoncorretto)|\bjava\b|\bmvn\b|\bgradle\b/i.test(ctx.fileContent('Dockerfile') || '');
1178
+ },
1179
+ impact: 'low',
1180
+ category: 'java',
1181
+ fix: 'Use a Dockerfile or build image that references Java, Maven, or Gradle when the application is containerized.',
1182
+ // sourceUrl assigned by attachSourceUrls via category mapping
1183
+ confidence: 0.7,
1184
+ },
1185
+
1186
+ javaAPIFramework: {
1187
+ id: 120312,
1188
+ name: 'Java API framework detected',
1189
+ check: (ctx) => {
1190
+ if (!isJavaProject(ctx)) return null;
1191
+ return /spring-web|spring-boot-starter-web|@RestController|@Controller|javax\.ws\.rs|jakarta\.ws\.rs|micronaut-http|io\.micronaut/i.test(getJavaProjectText(ctx));
1192
+ },
1193
+ impact: 'low',
1194
+ category: 'java',
1195
+ fix: 'Use Spring MVC, JAX-RS, or Micronaut conventions explicitly when the Java project exposes an API.',
1196
+ // sourceUrl assigned by attachSourceUrls via category mapping
1197
+ confidence: 0.7,
1198
+ },
1199
+
1200
+ javaMigrations: {
1201
+ id: 120313,
1202
+ name: 'Java database migration tooling present',
1203
+ check: (ctx) => {
1204
+ if (!isJavaProject(ctx)) return null;
1205
+ return /flyway|liquibase/i.test(getJavaProjectText(ctx)) ||
1206
+ hasProjectFile(ctx, /(^|\/)(db\/migration|db\/migrations|migrations)\//i) ||
1207
+ hasProjectFile(ctx, /(^|\/)(schema|data)\.sql$/i);
1208
+ },
1209
+ impact: 'medium',
1210
+ category: 'java',
1211
+ fix: 'Add Flyway, Liquibase, or repo-managed migration files so Java schema changes are repeatable and reviewable.',
1212
+ // sourceUrl assigned by attachSourceUrls via category mapping
1213
+ confidence: 0.7,
1214
+ },
1215
+
1216
+ javaMessageQueue: {
1217
+ id: 120314,
1218
+ name: 'Java message queue integration detected',
1219
+ check: (ctx) => {
1220
+ if (!isJavaProject(ctx)) return null;
1221
+ return /kafka|rabbitmq|amqp|jms|spring-kafka|spring-rabbit/i.test(getJavaProjectText(ctx));
1222
+ },
1223
+ impact: 'low',
1224
+ category: 'java',
1225
+ fix: 'Use explicit Kafka, RabbitMQ, or JMS integrations when the Java service relies on asynchronous messaging.',
1226
+ // sourceUrl assigned by attachSourceUrls via category mapping
1227
+ confidence: 0.7,
1228
+ },
1229
+
1230
+ javaCaching: {
1231
+ id: 120315,
1232
+ name: 'Java caching configured',
1233
+ check: (ctx) => {
1234
+ if (!isJavaProject(ctx)) return null;
1235
+ return /redis|ehcache|spring-cache|@Cacheable|caffeine/i.test(getJavaProjectText(ctx));
1236
+ },
1237
+ impact: 'low',
1238
+ category: 'java',
1239
+ fix: 'Configure Redis, Ehcache, Caffeine, or Spring Cache when Java services benefit from explicit caching layers.',
1240
+ // sourceUrl assigned by attachSourceUrls via category mapping
1241
+ confidence: 0.7,
1242
+ },
1243
+
1244
+ javaMonitoring: {
1245
+ id: 120316,
1246
+ name: 'Java monitoring dependencies detected',
1247
+ check: (ctx) => {
1248
+ if (!isJavaProject(ctx)) return null;
1249
+ return /actuator|micrometer|prometheus/i.test(getJavaProjectText(ctx));
1250
+ },
1251
+ impact: 'medium',
1252
+ category: 'java',
1253
+ fix: 'Add Actuator, Micrometer, or Prometheus integrations so Java services expose health and metrics data.',
1254
+ // sourceUrl assigned by attachSourceUrls via category mapping
1255
+ confidence: 0.7,
1256
+ },
1257
+
1258
+ javaLogging: {
1259
+ id: 120317,
1260
+ name: 'Java logging configured',
1261
+ check: (ctx) => {
1262
+ if (!isJavaProject(ctx)) return null;
1263
+ return /slf4j|logback|log4j/i.test(getJavaProjectText(ctx)) ||
1264
+ hasProjectFile(ctx, /(^|\/)(logback.*\.xml|log4j2?.*\.xml)$/i);
1265
+ },
1266
+ impact: 'medium',
1267
+ category: 'java',
1268
+ fix: 'Use SLF4J, Logback, or Log4j so Java application logging is explicit and configurable.',
1269
+ // sourceUrl assigned by attachSourceUrls via category mapping
1270
+ confidence: 0.7,
1271
+ },
1272
+
1273
+ javaMultiModule: {
1274
+ id: 120318,
1275
+ name: 'Java multi-module structure configured',
1276
+ check: (ctx) => {
1277
+ if (!isJavaProject(ctx)) return null;
1278
+ const buildFiles = findProjectFiles(ctx, /(^|\/)(pom\.xml|build\.gradle|build\.gradle\.kts)$/i);
1279
+ if (buildFiles.length <= 1) return null;
1280
+ return /<modules>|include\s*\(|include\s+['":]/i.test(getJavaBuildText(ctx));
1281
+ },
1282
+ impact: 'medium',
1283
+ category: 'java',
1284
+ fix: 'Configure a root Maven or Gradle multi-module definition when the Java repository contains multiple modules.',
1285
+ // sourceUrl assigned by attachSourceUrls via category mapping
1286
+ confidence: 0.7,
1287
+ },
1288
+
1289
+ javaDependencyInjection: {
1290
+ id: 120319,
1291
+ name: 'Java dependency injection pattern present',
1292
+ check: (ctx) => {
1293
+ if (!isJavaProject(ctx)) return null;
1294
+ return /spring-context|guice|dagger|@Autowired|@Inject|@Bean|@Component|@Service/i.test(getJavaProjectText(ctx));
1295
+ },
1296
+ impact: 'medium',
1297
+ category: 'java',
1298
+ fix: 'Use Spring DI, Guice, or Dagger patterns so Java object graphs stay explicit and testable.',
1299
+ // sourceUrl assigned by attachSourceUrls via category mapping
1300
+ confidence: 0.7,
1301
+ },
1302
+
1303
+ javaPropertyFiles: {
1304
+ id: 120320,
1305
+ name: 'Java application property files exist',
1306
+ check: (ctx) => {
1307
+ if (!isJavaProject(ctx)) return null;
1308
+ return hasProjectFile(ctx, /(^|\/)application\.(properties|ya?ml)$/i);
1309
+ },
1310
+ impact: 'low',
1311
+ category: 'java',
1312
+ fix: 'Add `application.properties` or `application.yml` when the Java service relies on conventional runtime configuration files.',
1313
+ // sourceUrl assigned by attachSourceUrls via category mapping
1314
+ confidence: 0.7,
1315
+ },
1316
+
1317
+ rubyGemfileExists: {
1318
+ id: 'CL-RB01',
1319
+ name: 'Gemfile exists',
1320
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return true; },
1321
+ impact: 'high',
1322
+ category: 'ruby',
1323
+ fix: 'Create a Gemfile to manage Ruby dependencies.',
1324
+ // sourceUrl assigned by attachSourceUrls via category mapping
1325
+ confidence: 0.7,
1326
+ },
1327
+
1328
+ rubyGemfileLockCommitted: {
1329
+ id: 'CL-RB02',
1330
+ name: 'Gemfile.lock committed',
1331
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /Gemfile\.lock$/.test(f)); },
1332
+ impact: 'high',
1333
+ category: 'ruby',
1334
+ fix: 'Commit Gemfile.lock to version control for reproducible builds.',
1335
+ // sourceUrl assigned by attachSourceUrls via category mapping
1336
+ confidence: 0.7,
1337
+ },
1338
+
1339
+ rubyVersionSpecified: {
1340
+ id: 'CL-RB03',
1341
+ name: 'Ruby version specified (.ruby-version)',
1342
+ 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') || ''); },
1343
+ impact: 'medium',
1344
+ category: 'ruby',
1345
+ fix: 'Create .ruby-version or specify ruby version in Gemfile.',
1346
+ // sourceUrl assigned by attachSourceUrls via category mapping
1347
+ confidence: 0.7,
1348
+ },
1349
+
1350
+ rubyRubocopConfigured: {
1351
+ id: 'CL-RB04',
1352
+ name: 'RuboCop configured (.rubocop.yml)',
1353
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /\.rubocop\.ya?ml$/.test(f)); },
1354
+ impact: 'medium',
1355
+ category: 'ruby',
1356
+ fix: 'Add .rubocop.yml to configure Ruby style checking.',
1357
+ // sourceUrl assigned by attachSourceUrls via category mapping
1358
+ confidence: 0.7,
1359
+ },
1360
+
1361
+ rubyTestFrameworkConfigured: {
1362
+ id: 'CL-RB05',
1363
+ name: 'RSpec or Minitest configured (spec/ or test/)',
1364
+ 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)); },
1365
+ impact: 'high',
1366
+ category: 'ruby',
1367
+ fix: 'Configure RSpec (spec/) or Minitest (test/) for testing.',
1368
+ // sourceUrl assigned by attachSourceUrls via category mapping
1369
+ confidence: 0.7,
1370
+ },
1371
+
1372
+ rubyRailsCredentialsDocumented: {
1373
+ id: 'CL-RB06',
1374
+ name: 'Rails credentials documented in instructions',
1375
+ 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); },
1376
+ impact: 'high',
1377
+ category: 'ruby',
1378
+ fix: 'Document Rails credentials management (rails credentials:edit) in project instructions.',
1379
+ // sourceUrl assigned by attachSourceUrls via category mapping
1380
+ confidence: 0.7,
1381
+ },
1382
+
1383
+ rubyMigrationsDocumented: {
1384
+ id: 'CL-RB07',
1385
+ name: 'Database migrations documented (db/migrate/)',
1386
+ 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); },
1387
+ impact: 'medium',
1388
+ category: 'ruby',
1389
+ fix: 'Document database migration workflow (rails db:migrate) in project instructions.',
1390
+ // sourceUrl assigned by attachSourceUrls via category mapping
1391
+ confidence: 0.7,
1392
+ },
1393
+
1394
+ rubyBundlerAuditConfigured: {
1395
+ id: 'CL-RB08',
1396
+ name: 'Bundler audit configured',
1397
+ 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)); },
1398
+ impact: 'medium',
1399
+ category: 'ruby',
1400
+ fix: 'Add bundler-audit gem for dependency vulnerability scanning.',
1401
+ // sourceUrl assigned by attachSourceUrls via category mapping
1402
+ confidence: 0.7,
1403
+ },
1404
+
1405
+ rubyTypeCheckingConfigured: {
1406
+ id: 'CL-RB09',
1407
+ name: 'Sorbet/RBS type checking configured (sorbet/ or sig/)',
1408
+ 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') || ''); },
1409
+ impact: 'low',
1410
+ category: 'ruby',
1411
+ fix: 'Configure Sorbet or RBS for type checking.',
1412
+ // sourceUrl assigned by attachSourceUrls via category mapping
1413
+ confidence: 0.7,
1414
+ },
1415
+
1416
+ rubyRailsRoutesDocumented: {
1417
+ id: 'CL-RB10',
1418
+ name: 'Rails routes documented',
1419
+ 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); },
1420
+ impact: 'medium',
1421
+ category: 'ruby',
1422
+ fix: 'Document key routes and API endpoints in project instructions.',
1423
+ // sourceUrl assigned by attachSourceUrls via category mapping
1424
+ confidence: 0.7,
1425
+ },
1426
+
1427
+ rubyBackgroundJobsDocumented: {
1428
+ id: 'CL-RB11',
1429
+ name: 'Background jobs documented (Sidekiq/GoodJob)',
1430
+ 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); },
1431
+ impact: 'medium',
1432
+ category: 'ruby',
1433
+ fix: 'Document background job framework and worker configuration.',
1434
+ // sourceUrl assigned by attachSourceUrls via category mapping
1435
+ confidence: 0.7,
1436
+ },
1437
+
1438
+ rubyRailsEnvConfigsSeparated: {
1439
+ id: 'CL-RB12',
1440
+ name: 'Rails environment configs separated (config/environments/)',
1441
+ check: (ctx) => { if (!ctx.files.some(f => /Gemfile$/.test(f))) return null; return ctx.files.some(f => /config\/environments\//.test(f)); },
1442
+ impact: 'medium',
1443
+ category: 'ruby',
1444
+ fix: 'Ensure config/environments/ has separate files for development, test, and production.',
1445
+ // sourceUrl assigned by attachSourceUrls via category mapping
1446
+ confidence: 0.7,
1447
+ },
1448
+
1449
+ rubyAssetPipelineDocumented: {
1450
+ id: 'CL-RB13',
1451
+ name: 'Asset pipeline documented',
1452
+ 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); },
1453
+ impact: 'low',
1454
+ category: 'ruby',
1455
+ fix: 'Document asset pipeline configuration (Sprockets, Propshaft, or JS/CSS bundling).',
1456
+ // sourceUrl assigned by attachSourceUrls via category mapping
1457
+ confidence: 0.7,
1458
+ },
1459
+
1460
+ rubyMasterKeyInGitignore: {
1461
+ id: 'CL-RB14',
1462
+ name: 'Rails master.key in .gitignore',
1463
+ 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); },
1464
+ impact: 'critical',
1465
+ category: 'ruby',
1466
+ fix: 'Add config/master.key to .gitignore to prevent secret leakage.',
1467
+ // sourceUrl assigned by attachSourceUrls via category mapping
1468
+ confidence: 0.7,
1469
+ },
1470
+
1471
+ rubyTestDataFactories: {
1472
+ id: 'CL-RB15',
1473
+ name: 'Factory Bot/fixtures for test data (spec/factories/)',
1474
+ 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') || ''); },
1475
+ impact: 'medium',
1476
+ category: 'ruby',
1477
+ fix: 'Configure Factory Bot (spec/factories/) or fixtures (test/fixtures/) for test data.',
1478
+ // sourceUrl assigned by attachSourceUrls via category mapping
1479
+ confidence: 0.7,
1480
+ },
1481
+
1482
+ dotnetProjectExists: {
1483
+ id: 'CL-DN01',
1484
+ name: '.csproj or .sln exists',
1485
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return true; },
1486
+ impact: 'high',
1487
+ category: 'dotnet',
1488
+ fix: 'Ensure .csproj or .sln file exists for .NET projects.',
1489
+ // sourceUrl assigned by attachSourceUrls via category mapping
1490
+ confidence: 0.7,
1491
+ },
1492
+
1493
+ dotnetVersionSpecified: {
1494
+ id: 'CL-DN02',
1495
+ name: '.NET version specified (global.json or TargetFramework)',
1496
+ 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); }); },
1497
+ impact: 'medium',
1498
+ category: 'dotnet',
1499
+ fix: 'Create global.json or ensure TargetFramework is set in .csproj.',
1500
+ // sourceUrl assigned by attachSourceUrls via category mapping
1501
+ confidence: 0.7,
1502
+ },
1503
+
1504
+ dotnetPackagesLock: {
1505
+ id: 'CL-DN03',
1506
+ name: 'NuGet packages lock (packages.lock.json)',
1507
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /packages\.lock\.json$/.test(f)); },
1508
+ impact: 'medium',
1509
+ category: 'dotnet',
1510
+ fix: 'Enable NuGet lock file (packages.lock.json) for reproducible restores.',
1511
+ // sourceUrl assigned by attachSourceUrls via category mapping
1512
+ confidence: 0.7,
1513
+ },
1514
+
1515
+ dotnetTestDocumented: {
1516
+ id: 'CL-DN04',
1517
+ name: 'dotnet test documented',
1518
+ 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); },
1519
+ impact: 'high',
1520
+ category: 'dotnet',
1521
+ fix: 'Document how to run tests with dotnet test in project instructions.',
1522
+ // sourceUrl assigned by attachSourceUrls via category mapping
1523
+ confidence: 0.7,
1524
+ },
1525
+
1526
+ dotnetEditorConfigExists: {
1527
+ id: 'CL-DN05',
1528
+ name: 'EditorConfig configured (.editorconfig)',
1529
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /\.editorconfig$/.test(f)); },
1530
+ impact: 'medium',
1531
+ category: 'dotnet',
1532
+ fix: 'Add .editorconfig for consistent code style across the team.',
1533
+ // sourceUrl assigned by attachSourceUrls via category mapping
1534
+ confidence: 0.7,
1535
+ },
1536
+
1537
+ dotnetRoslynAnalyzers: {
1538
+ id: 'CL-DN06',
1539
+ name: 'Roslyn analyzers configured',
1540
+ 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); }); },
1541
+ impact: 'medium',
1542
+ category: 'dotnet',
1543
+ fix: 'Add Roslyn analyzers (StyleCop.Analyzers, Microsoft.CodeAnalysis) to the project.',
1544
+ // sourceUrl assigned by attachSourceUrls via category mapping
1545
+ confidence: 0.7,
1546
+ },
1547
+
1548
+ dotnetAppsettingsExists: {
1549
+ id: 'CL-DN07',
1550
+ name: 'appsettings.json exists',
1551
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /appsettings\.json$/.test(f)); },
1552
+ impact: 'medium',
1553
+ category: 'dotnet',
1554
+ fix: 'Create appsettings.json for application configuration.',
1555
+ // sourceUrl assigned by attachSourceUrls via category mapping
1556
+ confidence: 0.7,
1557
+ },
1558
+
1559
+ dotnetUserSecretsDocumented: {
1560
+ id: 'CL-DN08',
1561
+ name: 'User secrets configured in instructions',
1562
+ 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); },
1563
+ impact: 'high',
1564
+ category: 'dotnet',
1565
+ fix: 'Document user secrets management (dotnet user-secrets) in project instructions.',
1566
+ // sourceUrl assigned by attachSourceUrls via category mapping
1567
+ confidence: 0.7,
1568
+ },
1569
+
1570
+ dotnetEfMigrations: {
1571
+ id: 'CL-DN09',
1572
+ name: 'Entity Framework migrations (Migrations/ directory)',
1573
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /Migrations\//.test(f)); },
1574
+ impact: 'medium',
1575
+ category: 'dotnet',
1576
+ fix: 'Document Entity Framework migration workflow (dotnet ef migrations).',
1577
+ // sourceUrl assigned by attachSourceUrls via category mapping
1578
+ confidence: 0.7,
1579
+ },
1580
+
1581
+ dotnetHealthChecks: {
1582
+ id: 'CL-DN10',
1583
+ name: 'Health checks configured',
1584
+ 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); }); },
1585
+ impact: 'medium',
1586
+ category: 'dotnet',
1587
+ fix: 'Configure health checks with AddHealthChecks() and MapHealthChecks().',
1588
+ // sourceUrl assigned by attachSourceUrls via category mapping
1589
+ confidence: 0.7,
1590
+ },
1591
+
1592
+ dotnetSwaggerConfigured: {
1593
+ id: 'CL-DN11',
1594
+ name: 'Swagger/OpenAPI configured',
1595
+ 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); }); },
1596
+ impact: 'medium',
1597
+ category: 'dotnet',
1598
+ fix: 'Configure Swagger/OpenAPI with Swashbuckle or NSwag.',
1599
+ // sourceUrl assigned by attachSourceUrls via category mapping
1600
+ confidence: 0.7,
1601
+ },
1602
+
1603
+ dotnetNoConnectionStringsInConfig: {
1604
+ id: 'CL-DN12',
1605
+ name: 'No connection strings in appsettings.json',
1606
+ 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); },
1607
+ impact: 'critical',
1608
+ category: 'dotnet',
1609
+ fix: 'Move connection strings with passwords to user secrets or environment variables.',
1610
+ // sourceUrl assigned by attachSourceUrls via category mapping
1611
+ confidence: 0.7,
1612
+ },
1613
+
1614
+ dotnetDockerSupport: {
1615
+ id: 'CL-DN13',
1616
+ name: 'Docker support configured',
1617
+ 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); },
1618
+ impact: 'medium',
1619
+ category: 'dotnet',
1620
+ fix: 'Add Dockerfile with official .NET SDK/ASP.NET base images.',
1621
+ // sourceUrl assigned by attachSourceUrls via category mapping
1622
+ confidence: 0.7,
1623
+ },
1624
+
1625
+ dotnetTestProjectSeparate: {
1626
+ id: 'CL-DN14',
1627
+ name: 'Unit test project separate (.Tests.csproj)',
1628
+ check: (ctx) => { if (!ctx.files.some(f => /\.csproj$|\.sln$/.test(f))) return null; return ctx.files.some(f => /\.Tests?\.csproj$|Tests?\/.*\.csproj$/.test(f)); },
1629
+ impact: 'high',
1630
+ category: 'dotnet',
1631
+ fix: 'Create separate test project (e.g., MyApp.Tests.csproj) for unit tests.',
1632
+ // sourceUrl assigned by attachSourceUrls via category mapping
1633
+ confidence: 0.7,
1634
+ },
1635
+
1636
+ dotnetGlobalUsingsDocumented: {
1637
+ id: 'CL-DN15',
1638
+ name: 'GlobalUsings documented',
1639
+ 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); }); },
1640
+ impact: 'low',
1641
+ category: 'dotnet',
1642
+ fix: 'Document global using directives in GlobalUsings.cs or enable ImplicitUsings in .csproj.',
1643
+ // sourceUrl assigned by attachSourceUrls via category mapping
1644
+ confidence: 0.7,
1645
+ },
1646
+
1647
+ phpComposerJsonExists: {
1648
+ id: 'CL-PHP01',
1649
+ name: 'composer.json exists',
1650
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; return true; },
1651
+ impact: 'high',
1652
+ category: 'php',
1653
+ fix: 'Create composer.json to manage PHP dependencies.',
1654
+ // sourceUrl assigned by attachSourceUrls via category mapping
1655
+ confidence: 0.7,
1656
+ },
1657
+
1658
+ phpComposerLockCommitted: {
1659
+ id: 'CL-PHP02',
1660
+ name: 'composer.lock committed',
1661
+ check: (ctx) => { if (!ctx.files.some(f => /composer\.json$/.test(f))) return null; return ctx.files.some(f => /composer\.lock$/.test(f)); },
1662
+ impact: 'high',
1663
+ category: 'php',
1664
+ fix: 'Commit composer.lock to version control for reproducible installs.',
1665
+ // sourceUrl assigned by attachSourceUrls via category mapping
1666
+ confidence: 0.7,
1667
+ },
1668
+
1669
+ phpVersionSpecified: {
1670
+ id: 'CL-PHP03',
1671
+ name: 'PHP version specified (composer.json require.php)',
1672
+ 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); },
1673
+ impact: 'medium',
1674
+ category: 'php',
1675
+ fix: 'Specify PHP version requirement in composer.json require section.',
1676
+ // sourceUrl assigned by attachSourceUrls via category mapping
1677
+ confidence: 0.7,
1678
+ },
1679
+
1680
+ phpStaticAnalysisConfigured: {
1681
+ id: 'CL-PHP04',
1682
+ name: 'PHPStan/Psalm configured (phpstan.neon)',
1683
+ 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)); },
1684
+ impact: 'medium',
1685
+ category: 'php',
1686
+ fix: 'Configure PHPStan (phpstan.neon) or Psalm (psalm.xml) for static analysis.',
1687
+ // sourceUrl assigned by attachSourceUrls via category mapping
1688
+ confidence: 0.7,
1689
+ },
1690
+
1691
+ phpCsFixerConfigured: {
1692
+ id: 'CL-PHP05',
1693
+ name: 'PHP CS Fixer configured (.php-cs-fixer.php)',
1694
+ 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)); },
1695
+ impact: 'medium',
1696
+ category: 'php',
1697
+ fix: 'Add .php-cs-fixer.php for consistent code formatting.',
1698
+ // sourceUrl assigned by attachSourceUrls via category mapping
1699
+ confidence: 0.7,
1700
+ },
1701
+
1702
+ phpUnitConfigured: {
1703
+ id: 'CL-PHP06',
1704
+ name: 'PHPUnit configured (phpunit.xml)',
1705
+ 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)); },
1706
+ impact: 'high',
1707
+ category: 'php',
1708
+ fix: 'Configure PHPUnit with phpunit.xml for testing.',
1709
+ // sourceUrl assigned by attachSourceUrls via category mapping
1710
+ confidence: 0.7,
1711
+ },
1712
+
1713
+ phpLaravelEnvExample: {
1714
+ id: 'CL-PHP07',
1715
+ name: 'Laravel .env.example exists',
1716
+ 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)); },
1717
+ impact: 'high',
1718
+ category: 'php',
1719
+ fix: 'Create .env.example with all required environment variables documented.',
1720
+ // sourceUrl assigned by attachSourceUrls via category mapping
1721
+ confidence: 0.7,
1722
+ },
1723
+
1724
+ phpLaravelAppKeyNotCommitted: {
1725
+ id: 'CL-PHP08',
1726
+ name: 'Laravel APP_KEY not committed',
1727
+ 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); },
1728
+ impact: 'critical',
1729
+ category: 'php',
1730
+ fix: 'Ensure .env with APP_KEY is in .gitignore — never commit application keys.',
1731
+ // sourceUrl assigned by attachSourceUrls via category mapping
1732
+ confidence: 0.7,
1733
+ },
1734
+
1735
+ phpLaravelMigrationsExist: {
1736
+ id: 'CL-PHP09',
1737
+ name: 'Laravel migrations exist (database/migrations/)',
1738
+ 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)); },
1739
+ impact: 'medium',
1740
+ category: 'php',
1741
+ fix: 'Create database migrations in database/migrations/ directory.',
1742
+ // sourceUrl assigned by attachSourceUrls via category mapping
1743
+ confidence: 0.7,
1744
+ },
1745
+
1746
+ phpArtisanCommandsDocumented: {
1747
+ id: 'CL-PHP10',
1748
+ name: 'Artisan commands documented',
1749
+ 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); },
1750
+ impact: 'medium',
1751
+ category: 'php',
1752
+ fix: 'Document key Artisan commands (migrate, seed, make:*) in project instructions.',
1753
+ // sourceUrl assigned by attachSourceUrls via category mapping
1754
+ confidence: 0.7,
1755
+ },
1756
+
1757
+ phpQueueWorkerDocumented: {
1758
+ id: 'CL-PHP11',
1759
+ name: 'Queue worker documented',
1760
+ 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); },
1761
+ impact: 'medium',
1762
+ category: 'php',
1763
+ fix: 'Document queue worker setup (php artisan queue:work, Horizon).',
1764
+ // sourceUrl assigned by attachSourceUrls via category mapping
1765
+ confidence: 0.7,
1766
+ },
1767
+
1768
+ phpLaravelPintConfigured: {
1769
+ id: 'CL-PHP12',
1770
+ name: 'Laravel Pint configured (pint.json)',
1771
+ 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') || ''); },
1772
+ impact: 'low',
1773
+ category: 'php',
1774
+ fix: 'Configure Laravel Pint (pint.json) for code style enforcement.',
1775
+ // sourceUrl assigned by attachSourceUrls via category mapping
1776
+ confidence: 0.7,
1777
+ },
1778
+
1779
+ phpAssetBundlingDocumented: {
1780
+ id: 'CL-PHP13',
1781
+ name: 'Vite/Mix asset bundling documented',
1782
+ 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); },
1783
+ impact: 'low',
1784
+ category: 'php',
1785
+ fix: 'Document asset bundling setup (Vite or Mix) in project instructions.',
1786
+ // sourceUrl assigned by attachSourceUrls via category mapping
1787
+ confidence: 0.7,
1788
+ },
1789
+
1790
+ phpConfigCachingDocumented: {
1791
+ id: 'CL-PHP14',
1792
+ name: 'Laravel config caching documented',
1793
+ 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); },
1794
+ impact: 'low',
1795
+ category: 'php',
1796
+ fix: 'Document config/route caching strategy (php artisan config:cache) for production.',
1797
+ // sourceUrl assigned by attachSourceUrls via category mapping
1798
+ confidence: 0.7,
1799
+ },
1800
+
1801
+ phpComposerScriptsDefined: {
1802
+ id: 'CL-PHP15',
1803
+ name: 'Composer scripts defined',
1804
+ 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); },
1805
+ impact: 'medium',
1806
+ category: 'php',
1807
+ fix: 'Define composer scripts for common tasks (test, lint, analyze) in composer.json.',
1808
+ // sourceUrl assigned by attachSourceUrls via category mapping
1809
+ confidence: 0.7,
1810
+ },
1811
+
1812
+ pubspecExists: {
1813
+ id: 120401,
1814
+ name: 'pubspec.yaml exists',
1815
+ check: (ctx) => {
1816
+ if (!isFlutterProject(ctx)) return null;
1817
+ return true;
1818
+ },
1819
+ impact: 'high',
1820
+ category: 'flutter',
1821
+ fix: 'Add a `pubspec.yaml` manifest so Flutter/Dart dependencies and project metadata are tracked.',
1822
+ confidence: 0.7,
1823
+ },
1824
+
1825
+ flutterAnalysis: {
1826
+ id: 120402,
1827
+ name: 'Flutter analysis options configured',
1828
+ check: (ctx) => {
1829
+ if (!isFlutterProject(ctx)) return null;
1830
+ return ctx.files.some(f => /analysis_options\.yaml$/i.test(f));
1831
+ },
1832
+ impact: 'medium',
1833
+ category: 'flutter',
1834
+ fix: 'Add analysis_options.yaml for Dart/Flutter linting rules.',
1835
+ confidence: 0.8,
1836
+ },
1837
+
1838
+ flutterTestDir: {
1839
+ id: 120403,
1840
+ name: 'Flutter tests exist',
1841
+ check: (ctx) => {
1842
+ if (!isFlutterProject(ctx)) return null;
1843
+ return hasProjectFile(ctx, /(^|\/)test\/.*_test\.dart$/i);
1844
+ },
1845
+ impact: 'high',
1846
+ category: 'flutter',
1847
+ fix: 'Add Flutter widget or unit tests in a `test/` directory with `_test.dart` suffix.',
1848
+ confidence: 0.8,
1849
+ },
1850
+
1851
+ flutterLintRules: {
1852
+ id: 120404,
1853
+ name: 'Flutter lint package configured',
1854
+ check: (ctx) => {
1855
+ if (!isFlutterProject(ctx)) return null;
1856
+ const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
1857
+ return /flutter_lints|very_good_analysis/i.test(pubspec);
1858
+ },
1859
+ impact: 'medium',
1860
+ category: 'flutter',
1861
+ fix: 'Add `flutter_lints` or `very_good_analysis` to pubspec.yaml dev_dependencies for consistent linting.',
1862
+ confidence: 0.8,
1863
+ },
1864
+
1865
+ flutterPlatformDirs: {
1866
+ id: 120405,
1867
+ name: 'Flutter platform directories present',
1868
+ check: (ctx) => {
1869
+ if (!isFlutterProject(ctx)) return null;
1870
+ return hasProjectFile(ctx, /^android\//i) && hasProjectFile(ctx, /^ios\//i);
1871
+ },
1872
+ impact: 'medium',
1873
+ category: 'flutter',
1874
+ fix: 'Run `flutter create .` to generate `android/` and `ios/` platform directories.',
1875
+ confidence: 0.7,
1876
+ },
1877
+
1878
+ flutterWebSupport: {
1879
+ id: 120406,
1880
+ name: 'Flutter web support enabled',
1881
+ check: (ctx) => {
1882
+ if (!isFlutterProject(ctx)) return null;
1883
+ return hasProjectFile(ctx, /^web\//i);
1884
+ },
1885
+ impact: 'low',
1886
+ category: 'flutter',
1887
+ fix: 'Run `flutter create --platforms=web .` to add web support.',
1888
+ confidence: 0.7,
1889
+ },
1890
+
1891
+ flutterL10n: {
1892
+ id: 120407,
1893
+ name: 'Flutter localization configured',
1894
+ check: (ctx) => {
1895
+ if (!isFlutterProject(ctx)) return null;
1896
+ const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
1897
+ return hasProjectFile(ctx, /(^|\/)l10n\.yaml$/i) || /\bintl\b/i.test(pubspec);
1898
+ },
1899
+ impact: 'medium',
1900
+ category: 'flutter',
1901
+ fix: 'Add `l10n.yaml` or the `intl` package to support localization and internationalization.',
1902
+ confidence: 0.7,
1903
+ },
1904
+
1905
+ flutterStateManagement: {
1906
+ id: 120408,
1907
+ name: 'Flutter state management configured',
1908
+ check: (ctx) => {
1909
+ if (!isFlutterProject(ctx)) return null;
1910
+ const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
1911
+ return /riverpod|flutter_bloc|\bbloc\b|\bprovider\b/i.test(pubspec);
1912
+ },
1913
+ impact: 'medium',
1914
+ category: 'flutter',
1915
+ fix: 'Add a state management solution such as `riverpod`, `bloc`, or `provider` to pubspec.yaml.',
1916
+ confidence: 0.7,
1917
+ },
1918
+
1919
+ flutterNavigation: {
1920
+ id: 120409,
1921
+ name: 'Flutter routing configured',
1922
+ check: (ctx) => {
1923
+ if (!isFlutterProject(ctx)) return null;
1924
+ const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
1925
+ return /go_router|auto_route/i.test(pubspec);
1926
+ },
1927
+ impact: 'medium',
1928
+ category: 'flutter',
1929
+ fix: 'Add `go_router` or `auto_route` for declarative, type-safe Flutter routing.',
1930
+ confidence: 0.7,
1931
+ },
1932
+
1933
+ flutterCIConfigured: {
1934
+ id: 120410,
1935
+ name: 'CI runs flutter test',
1936
+ check: (ctx) => {
1937
+ if (!isFlutterProject(ctx)) return null;
1938
+ return /flutter test(\s|$)/i.test(getWorkflowContent(ctx));
1939
+ },
1940
+ impact: 'high',
1941
+ category: 'flutter',
1942
+ fix: 'Add `flutter test` to your CI workflow so tests run automatically on every change.',
1943
+ confidence: 0.8,
1944
+ },
1945
+
1946
+ flutterCodeGen: {
1947
+ id: 120411,
1948
+ name: 'Flutter code generation configured',
1949
+ check: (ctx) => {
1950
+ if (!isFlutterProject(ctx)) return null;
1951
+ const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
1952
+ return /build_runner|freezed/i.test(pubspec);
1953
+ },
1954
+ impact: 'medium',
1955
+ category: 'flutter',
1956
+ fix: 'Add `build_runner` and/or `freezed` to pubspec.yaml for code generation support.',
1957
+ confidence: 0.7,
1958
+ },
1959
+
1960
+ flutterFirebase: {
1961
+ id: 120412,
1962
+ name: 'Flutter Firebase integration',
1963
+ check: (ctx) => {
1964
+ if (!isFlutterProject(ctx)) return null;
1965
+ const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
1966
+ return /firebase/i.test(pubspec) || hasProjectFile(ctx, /(^|\/)firebase_options\.dart$/i);
1967
+ },
1968
+ impact: 'medium',
1969
+ category: 'flutter',
1970
+ fix: 'Add Firebase packages to pubspec.yaml and run `flutterfire configure` to generate firebase_options.dart.',
1971
+ confidence: 0.7,
1972
+ },
1973
+
1974
+ flutterAssets: {
1975
+ id: 120413,
1976
+ name: 'Flutter assets configured',
1977
+ check: (ctx) => {
1978
+ if (!isFlutterProject(ctx)) return null;
1979
+ const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
1980
+ return /\bassets\s*:/i.test(pubspec);
1981
+ },
1982
+ impact: 'low',
1983
+ category: 'flutter',
1984
+ fix: 'Add an `assets:` section in pubspec.yaml to declare images, fonts, and other bundled resources.',
1985
+ confidence: 0.7,
1986
+ },
1987
+
1988
+ flutterFlavors: {
1989
+ id: 120414,
1990
+ name: 'Flutter flavors configured',
1991
+ check: (ctx) => {
1992
+ if (!isFlutterProject(ctx)) return null;
1993
+ const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
1994
+ return /\bflavors?\b/i.test(pubspec) || /--flavor/i.test(getWorkflowContent(ctx));
1995
+ },
1996
+ impact: 'low',
1997
+ category: 'flutter',
1998
+ fix: 'Configure Flutter flavors for environment-specific builds (dev, staging, production).',
1999
+ confidence: 0.7,
2000
+ },
2001
+
2002
+ flutterContainerized: {
2003
+ id: 120415,
2004
+ name: 'Flutter Dockerfile present',
2005
+ check: (ctx) => {
2006
+ if (!isFlutterProject(ctx)) return null;
2007
+ const dockerfiles = readProjectFiles(ctx, /(^|\/)Dockerfile/i);
2008
+ return /flutter|dart/i.test(dockerfiles);
2009
+ },
2010
+ impact: 'low',
2011
+ category: 'flutter',
2012
+ fix: 'Add a Dockerfile that includes the Flutter or Dart SDK for containerized builds.',
2013
+ confidence: 0.7,
2014
+ },
2015
+
2016
+ swiftPackageExists: {
2017
+ id: 120501,
2018
+ name: 'Swift package or Xcode project exists',
2019
+ check: (ctx) => {
2020
+ if (!isSwiftProject(ctx)) return null;
2021
+ return true;
2022
+ },
2023
+ impact: 'high',
2024
+ category: 'swift',
2025
+ fix: 'Add a `Package.swift` or `.xcodeproj` to define your Swift project structure.',
2026
+ confidence: 0.7,
2027
+ },
2028
+
2029
+ swiftLinter: {
2030
+ id: 120502,
2031
+ name: 'SwiftLint configured',
2032
+ check: (ctx) => {
2033
+ if (!isSwiftProject(ctx)) return null;
2034
+ return hasProjectFile(ctx, /(^|\/)(\.swiftlint\.yml|\.swiftlint\.yaml)$/i);
2035
+ },
2036
+ impact: 'medium',
2037
+ category: 'swift',
2038
+ fix: 'Add `.swiftlint.yml` to enforce Swift coding conventions.',
2039
+ confidence: 0.8,
2040
+ },
2041
+
2042
+ swiftTests: {
2043
+ id: 120503,
2044
+ name: 'Swift tests exist',
2045
+ check: (ctx) => {
2046
+ if (!isSwiftProject(ctx)) return null;
2047
+ return hasProjectFile(ctx, /(^|\/)Tests\//i) ||
2048
+ findProjectFiles(ctx, /\.swift$/i).some(f => /XCTest/i.test(ctx.fileContent(f) || ''));
2049
+ },
2050
+ impact: 'high',
2051
+ category: 'swift',
2052
+ fix: 'Add Swift tests in a `Tests/` directory using XCTest.',
2053
+ confidence: 0.8,
2054
+ },
2055
+
2056
+ swiftFormatter: {
2057
+ id: 120504,
2058
+ name: 'SwiftFormat configured',
2059
+ check: (ctx) => {
2060
+ if (!isSwiftProject(ctx)) return null;
2061
+ return hasProjectFile(ctx, /(^|\/)\.swiftformat$/i);
2062
+ },
2063
+ impact: 'medium',
2064
+ category: 'swift',
2065
+ fix: 'Add `.swiftformat` to enforce consistent Swift formatting.',
2066
+ confidence: 0.7,
2067
+ },
2068
+
2069
+ swiftCIConfigured: {
2070
+ id: 120505,
2071
+ name: 'CI runs swift test or xcodebuild',
2072
+ check: (ctx) => {
2073
+ if (!isSwiftProject(ctx)) return null;
2074
+ return /swift test|xcodebuild(\s|$)/i.test(getWorkflowContent(ctx));
2075
+ },
2076
+ impact: 'high',
2077
+ category: 'swift',
2078
+ fix: 'Add `swift test` or `xcodebuild test` to your CI workflow.',
2079
+ confidence: 0.8,
2080
+ },
2081
+
2082
+ swiftDocComments: {
2083
+ id: 120506,
2084
+ name: 'Swift doc comments present',
2085
+ check: (ctx) => {
2086
+ if (!isSwiftProject(ctx)) return null;
2087
+ const swiftFiles = findProjectFiles(ctx, /\.swift$/i);
2088
+ if (swiftFiles.length === 0) return null;
2089
+ return swiftFiles.some(f => /\/\/\//.test(ctx.fileContent(f) || ''));
2090
+ },
2091
+ impact: 'low',
2092
+ category: 'swift',
2093
+ fix: 'Add `///` documentation comments to public Swift APIs.',
2094
+ confidence: 0.7,
2095
+ },
2096
+
2097
+ swiftSPM: {
2098
+ id: 120507,
2099
+ name: 'Swift Package Manager used',
2100
+ check: (ctx) => {
2101
+ if (!isSwiftProject(ctx)) return null;
2102
+ return hasProjectFile(ctx, /(^|\/)Package\.swift$/i);
2103
+ },
2104
+ impact: 'medium',
2105
+ category: 'swift',
2106
+ fix: 'Add `Package.swift` to use Swift Package Manager for dependency management.',
2107
+ confidence: 0.7,
2108
+ },
2109
+
2110
+ swiftMinVersion: {
2111
+ id: 120508,
2112
+ name: 'Swift tools version specified',
2113
+ check: (ctx) => {
2114
+ if (!isSwiftProject(ctx)) return null;
2115
+ const pkg = readProjectFiles(ctx, /(^|\/)Package\.swift$/i);
2116
+ return /swift-tools-version/i.test(pkg);
2117
+ },
2118
+ impact: 'medium',
2119
+ category: 'swift',
2120
+ fix: 'Add `// swift-tools-version:5.9` (or appropriate version) at the top of Package.swift.',
2121
+ confidence: 0.8,
2122
+ },
2123
+
2124
+ swiftAccessControl: {
2125
+ id: 120509,
2126
+ name: 'Swift access control used',
2127
+ check: (ctx) => {
2128
+ if (!isSwiftProject(ctx)) return null;
2129
+ const swiftFiles = findProjectFiles(ctx, /\.swift$/i);
2130
+ if (swiftFiles.length === 0) return null;
2131
+ return swiftFiles.some(f => /\b(public|internal)\b/.test(ctx.fileContent(f) || ''));
2132
+ },
2133
+ impact: 'low',
2134
+ category: 'swift',
2135
+ fix: 'Use `public`/`internal` access control in Swift files to define clear API boundaries.',
2136
+ confidence: 0.7,
2137
+ },
2138
+
2139
+ swiftConcurrency: {
2140
+ id: 120510,
2141
+ name: 'Swift concurrency used',
2142
+ check: (ctx) => {
2143
+ if (!isSwiftProject(ctx)) return null;
2144
+ const swiftFiles = findProjectFiles(ctx, /\.swift$/i);
2145
+ if (swiftFiles.length === 0) return null;
2146
+ return swiftFiles.some(f => /\basync\b.*\bawait\b|\bawait\b/s.test(ctx.fileContent(f) || ''));
2147
+ },
2148
+ impact: 'low',
2149
+ category: 'swift',
2150
+ fix: 'Adopt Swift structured concurrency with `async`/`await` for modern asynchronous code.',
2151
+ confidence: 0.7,
2152
+ },
2153
+
2154
+ kotlinGradlePlugin: {
2155
+ id: 120601,
2156
+ name: 'Kotlin Gradle plugin configured',
2157
+ check: (ctx) => {
2158
+ if (!isKotlinProject(ctx)) return null;
2159
+ const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
2160
+ return /kotlin\(|org\.jetbrains\.kotlin/i.test(gradle);
2161
+ },
2162
+ impact: 'high',
2163
+ category: 'kotlin',
2164
+ fix: 'Apply the Kotlin Gradle plugin in build.gradle.kts to enable Kotlin compilation.',
2165
+ confidence: 0.8,
2166
+ },
2167
+
2168
+ kotlinVersion: {
2169
+ id: 120602,
2170
+ name: 'Kotlin version specified',
2171
+ check: (ctx) => {
2172
+ if (!isKotlinProject(ctx)) return null;
2173
+ const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle|gradle\.properties)$/i);
2174
+ return /kotlinVersion|kotlin_version|org\.jetbrains\.kotlin.*\d+\.\d+/i.test(gradle);
2175
+ },
2176
+ impact: 'high',
2177
+ category: 'kotlin',
2178
+ fix: 'Pin the Kotlin version in gradle.properties or build.gradle.kts for reproducible builds.',
2179
+ confidence: 0.8,
2180
+ },
2181
+
2182
+ kotlinLinter: {
2183
+ id: 120603,
2184
+ name: 'Kotlin linter configured',
2185
+ check: (ctx) => {
2186
+ if (!isKotlinProject(ctx)) return null;
2187
+ const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
2188
+ return /ktlint|detekt/i.test(gradle) ||
2189
+ hasProjectFile(ctx, /(^|\/)(\.editorconfig|detekt\.yml|detekt\.yaml)$/i);
2190
+ },
2191
+ impact: 'medium',
2192
+ category: 'kotlin',
2193
+ fix: 'Add `ktlint` or `detekt` to enforce Kotlin code style and static analysis.',
2194
+ confidence: 0.8,
2195
+ },
2196
+
2197
+ kotlinTests: {
2198
+ id: 120604,
2199
+ name: 'Kotlin tests exist',
2200
+ check: (ctx) => {
2201
+ if (!isKotlinProject(ctx)) return null;
2202
+ return hasProjectFile(ctx, /(^|\/)src\/test\/.*\.kt$/i) ||
2203
+ hasProjectFile(ctx, /(^|\/)test\/.*\.kt$/i);
2204
+ },
2205
+ impact: 'high',
2206
+ category: 'kotlin',
2207
+ fix: 'Add Kotlin tests in `src/test/` using JUnit or KotlinTest.',
2208
+ confidence: 0.8,
2209
+ },
2210
+
2211
+ kotlinCoroutines: {
2212
+ id: 120605,
2213
+ name: 'Kotlin Coroutines in dependencies',
2214
+ check: (ctx) => {
2215
+ if (!isKotlinProject(ctx)) return null;
2216
+ const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
2217
+ return /kotlinx[.-]coroutines/i.test(gradle);
2218
+ },
2219
+ impact: 'medium',
2220
+ category: 'kotlin',
2221
+ fix: 'Add `kotlinx-coroutines-core` to dependencies for structured concurrency.',
2222
+ confidence: 0.7,
2223
+ },
2224
+
2225
+ kotlinSerialization: {
2226
+ id: 120606,
2227
+ name: 'Kotlin serialization configured',
2228
+ check: (ctx) => {
2229
+ if (!isKotlinProject(ctx)) return null;
2230
+ const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
2231
+ return /kotlinx[.-]serialization/i.test(gradle);
2232
+ },
2233
+ impact: 'medium',
2234
+ category: 'kotlin',
2235
+ fix: 'Add `kotlinx.serialization` for type-safe, multiplatform serialization.',
2236
+ confidence: 0.7,
2237
+ },
2238
+
2239
+ kotlinCompose: {
2240
+ id: 120607,
2241
+ name: 'Jetpack Compose configured',
2242
+ check: (ctx) => {
2243
+ if (!isKotlinProject(ctx)) return null;
2244
+ const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
2245
+ return /compose/i.test(gradle);
2246
+ },
2247
+ impact: 'medium',
2248
+ category: 'kotlin',
2249
+ fix: 'Enable Jetpack Compose in build.gradle.kts for modern declarative Android UI.',
2250
+ confidence: 0.7,
2251
+ },
2252
+
2253
+ kotlinCIConfigured: {
2254
+ id: 120608,
2255
+ name: 'CI runs Kotlin tests',
2256
+ check: (ctx) => {
2257
+ if (!isKotlinProject(ctx)) return null;
2258
+ return /gradle.*test|gradlew.*test/i.test(getWorkflowContent(ctx));
2259
+ },
2260
+ impact: 'high',
2261
+ category: 'kotlin',
2262
+ fix: 'Add `./gradlew test` to your CI workflow so Kotlin tests run automatically.',
2263
+ confidence: 0.8,
2264
+ },
2265
+
2266
+ kotlinMultiplatform: {
2267
+ id: 120609,
2268
+ name: 'Kotlin Multiplatform configured',
2269
+ check: (ctx) => {
2270
+ if (!isKotlinProject(ctx)) return null;
2271
+ const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
2272
+ return /multiplatform/i.test(gradle);
2273
+ },
2274
+ impact: 'medium',
2275
+ category: 'kotlin',
2276
+ fix: 'Apply the `kotlin-multiplatform` Gradle plugin to share code across JVM, iOS, JS, and Native targets.',
2277
+ confidence: 0.7,
2278
+ },
2279
+
2280
+ kotlinDocComments: {
2281
+ id: 120610,
2282
+ name: 'KDoc comments present',
2283
+ check: (ctx) => {
2284
+ if (!isKotlinProject(ctx)) return null;
2285
+ const ktFiles = findProjectFiles(ctx, /\.kt$/i);
2286
+ if (ktFiles.length === 0) return null;
2287
+ return ktFiles.some(f => /\/\*\*/.test(ctx.fileContent(f) || ''));
2288
+ },
2289
+ impact: 'low',
2290
+ category: 'kotlin',
2291
+ fix: 'Add KDoc comments (`/** ... */`) to public Kotlin APIs for documentation generation.',
2292
+ confidence: 0.7,
2293
+ },
2294
+ };