@nerviq/cli 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -23
- package/README.md +2 -2
- package/bin/cli.js +131 -130
- package/package.json +2 -1
- package/src/activity.js +1039 -1039
- package/src/adoption-advisor.js +299 -299
- package/src/aider/config-parser.js +166 -166
- package/src/aider/context.js +158 -158
- package/src/aider/deep-review.js +316 -316
- package/src/aider/domain-packs.js +303 -303
- package/src/aider/freshness.js +93 -93
- package/src/aider/governance.js +253 -253
- package/src/aider/interactive.js +334 -334
- package/src/aider/mcp-packs.js +329 -329
- package/src/aider/patch.js +214 -214
- package/src/aider/plans.js +186 -186
- package/src/aider/premium.js +360 -360
- package/src/aider/setup.js +404 -404
- package/src/aider/techniques.js +16 -16
- package/src/analyze.js +951 -951
- package/src/anti-patterns.js +485 -485
- package/src/audit/instruction-files.js +180 -180
- package/src/audit/recommendations.js +577 -577
- package/src/auto-suggest.js +154 -154
- package/src/badge.js +13 -13
- package/src/behavioral-drift.js +801 -801
- package/src/benchmark.js +67 -67
- package/src/catalog.js +103 -103
- package/src/certification.js +128 -128
- package/src/codex/config-parser.js +183 -183
- package/src/codex/context.js +223 -223
- package/src/codex/deep-review.js +493 -493
- package/src/codex/domain-packs.js +394 -394
- package/src/codex/freshness.js +84 -84
- package/src/codex/governance.js +192 -192
- package/src/codex/interactive.js +618 -618
- package/src/codex/mcp-packs.js +914 -914
- package/src/codex/patch.js +209 -209
- package/src/codex/plans.js +251 -251
- package/src/codex/premium.js +614 -614
- package/src/codex/setup.js +591 -591
- package/src/context.js +320 -320
- package/src/continuous-ops.js +681 -681
- package/src/copilot/activity.js +309 -309
- package/src/copilot/deep-review.js +346 -346
- package/src/copilot/domain-packs.js +372 -372
- package/src/copilot/freshness.js +57 -57
- package/src/copilot/governance.js +222 -222
- package/src/copilot/interactive.js +406 -406
- package/src/copilot/mcp-packs.js +826 -826
- package/src/copilot/plans.js +253 -253
- package/src/copilot/premium.js +451 -451
- package/src/copilot/setup.js +488 -488
- package/src/cost-tracking.js +61 -61
- package/src/cursor/activity.js +301 -301
- package/src/cursor/config-parser.js +265 -265
- package/src/cursor/context.js +256 -256
- package/src/cursor/deep-review.js +334 -334
- package/src/cursor/domain-packs.js +368 -368
- package/src/cursor/freshness.js +65 -65
- package/src/cursor/governance.js +229 -229
- package/src/cursor/interactive.js +391 -391
- package/src/cursor/mcp-packs.js +828 -828
- package/src/cursor/plans.js +254 -254
- package/src/cursor/premium.js +469 -469
- package/src/cursor/setup.js +488 -488
- package/src/dashboard.js +493 -493
- package/src/deep-review.js +428 -428
- package/src/deprecation.js +98 -98
- package/src/diff-only.js +280 -280
- package/src/doctor.js +119 -119
- package/src/domain-pack-expansion.js +1033 -1033
- package/src/domain-packs.js +387 -387
- package/src/feedback.js +178 -178
- package/src/fix-engine.js +783 -783
- package/src/fix-prompts.js +122 -122
- package/src/formatters/sarif.js +115 -115
- package/src/freshness.js +74 -74
- package/src/gemini/config-parser.js +275 -275
- package/src/gemini/context.js +290 -221
- package/src/gemini/deep-review.js +559 -559
- package/src/gemini/domain-packs.js +393 -393
- package/src/gemini/freshness.js +66 -66
- package/src/gemini/governance.js +201 -201
- package/src/gemini/interactive.js +860 -860
- package/src/gemini/mcp-packs.js +915 -915
- package/src/gemini/plans.js +269 -269
- package/src/gemini/premium.js +760 -760
- package/src/gemini/setup.js +692 -692
- package/src/gemini/techniques.js +105 -33
- package/src/governance.js +72 -72
- package/src/harmony/add.js +68 -68
- package/src/harmony/advisor.js +333 -333
- package/src/harmony/canon.js +565 -565
- package/src/harmony/cli.js +591 -591
- package/src/harmony/drift.js +401 -401
- package/src/harmony/governance.js +313 -313
- package/src/harmony/memory.js +239 -239
- package/src/harmony/sync.js +475 -475
- package/src/harmony/watch.js +370 -370
- package/src/hook-validation.js +342 -342
- package/src/index.js +271 -271
- package/src/init.js +184 -184
- package/src/instruction-surfaces.js +185 -185
- package/src/integrations.js +144 -144
- package/src/interactive.js +118 -118
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/mcp-packs.js +830 -830
- package/src/mcp-server.js +726 -726
- package/src/mcp-validation.js +337 -337
- package/src/nerviq-sync.json +7 -7
- package/src/opencode/config-parser.js +109 -109
- package/src/opencode/context.js +247 -247
- package/src/opencode/deep-review.js +313 -313
- package/src/opencode/domain-packs.js +262 -262
- package/src/opencode/freshness.js +66 -66
- package/src/opencode/governance.js +159 -159
- package/src/opencode/interactive.js +392 -392
- package/src/opencode/mcp-packs.js +705 -705
- package/src/opencode/patch.js +184 -184
- package/src/opencode/plans.js +231 -231
- package/src/opencode/premium.js +413 -413
- package/src/opencode/setup.js +449 -449
- package/src/opencode/techniques.js +27 -27
- package/src/operating-profile.js +574 -574
- package/src/org.js +152 -152
- package/src/permission-rules.js +218 -218
- package/src/plans.js +839 -839
- package/src/platform-change-manifest.js +86 -86
- package/src/plugins.js +110 -110
- package/src/policy-layers.js +210 -210
- package/src/profiles.js +124 -124
- package/src/prompt-injection.js +74 -74
- package/src/public-api.js +173 -173
- package/src/recommendation-rules.js +84 -84
- package/src/repo-archetype.js +386 -386
- package/src/secret-patterns.js +39 -39
- package/src/server.js +527 -527
- package/src/setup/analysis.js +607 -607
- package/src/setup/runtime.js +172 -172
- package/src/setup.js +677 -677
- package/src/shared/capabilities.js +194 -194
- package/src/source-urls.js +132 -132
- package/src/stack-checks.js +565 -565
- package/src/supplemental-checks.js +13 -13
- package/src/synergy/adaptive.js +261 -261
- package/src/synergy/compensation.js +137 -137
- package/src/synergy/evidence.js +193 -193
- package/src/synergy/learning.js +199 -199
- package/src/synergy/patterns.js +227 -227
- package/src/synergy/ranking.js +83 -83
- package/src/synergy/report.js +165 -165
- package/src/synergy/routing.js +146 -146
- package/src/techniques/api.js +407 -407
- package/src/techniques/automation.js +316 -316
- package/src/techniques/compliance.js +257 -257
- package/src/techniques/hygiene.js +294 -294
- package/src/techniques/instructions.js +243 -243
- package/src/techniques/observability.js +226 -226
- package/src/techniques/optimization.js +142 -142
- package/src/techniques/quality.js +318 -318
- package/src/techniques/security.js +237 -237
- package/src/techniques/shared.js +443 -443
- package/src/techniques/stacks.js +2294 -2294
- package/src/techniques/tools.js +106 -106
- package/src/techniques/workflow.js +413 -413
- package/src/techniques.js +81 -81
- package/src/terminology.js +73 -73
- package/src/token-estimate.js +35 -35
- package/src/usage-patterns.js +99 -99
- package/src/verification-metadata.js +145 -145
- package/src/watch.js +247 -247
- package/src/windsurf/activity.js +302 -302
- package/src/windsurf/config-parser.js +267 -267
- package/src/windsurf/context.js +249 -249
- package/src/windsurf/deep-review.js +337 -337
- package/src/windsurf/domain-packs.js +370 -370
- package/src/windsurf/freshness.js +36 -36
- package/src/windsurf/governance.js +231 -231
- package/src/windsurf/interactive.js +388 -388
- package/src/windsurf/mcp-packs.js +792 -792
- package/src/windsurf/plans.js +247 -247
- package/src/windsurf/premium.js +468 -468
- package/src/windsurf/setup.js +471 -471
- package/src/windsurf/techniques.js +17 -17
- package/src/workspace.js +375 -375
package/src/techniques/stacks.js
CHANGED
|
@@ -1,2294 +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
|
-
};
|
|
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
|
+
};
|