@nerviq/cli 1.2.7 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/domain-pack-expansion.js +462 -0
- package/src/techniques.js +1393 -459
package/src/techniques.js
CHANGED
|
@@ -117,6 +117,38 @@ function isGoProject(ctx) {
|
|
|
117
117
|
return ctx.__nerviqIsGo;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
function isRustProject(ctx) {
|
|
121
|
+
if (ctx.__nerviqIsRust !== undefined) return ctx.__nerviqIsRust;
|
|
122
|
+
ctx.__nerviqIsRust = hasProjectFile(ctx, /(^|\/)Cargo\.toml$/i);
|
|
123
|
+
return ctx.__nerviqIsRust;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isJavaProject(ctx) {
|
|
127
|
+
if (ctx.__nerviqIsJava !== undefined) return ctx.__nerviqIsJava;
|
|
128
|
+
ctx.__nerviqIsJava = hasProjectFile(ctx, /(^|\/)(pom\.xml|build\.gradle|build\.gradle\.kts)$/i);
|
|
129
|
+
return ctx.__nerviqIsJava;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isFlutterProject(ctx) {
|
|
133
|
+
if (ctx.__nerviqIsFlutter !== undefined) return ctx.__nerviqIsFlutter;
|
|
134
|
+
ctx.__nerviqIsFlutter = hasProjectFile(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
135
|
+
return ctx.__nerviqIsFlutter;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isSwiftProject(ctx) {
|
|
139
|
+
if (ctx.__nerviqIsSwift !== undefined) return ctx.__nerviqIsSwift;
|
|
140
|
+
ctx.__nerviqIsSwift = hasProjectFile(ctx, /(^|\/)Package\.swift$/i) ||
|
|
141
|
+
hasProjectFile(ctx, /\.xcodeproj/i);
|
|
142
|
+
return ctx.__nerviqIsSwift;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isKotlinProject(ctx) {
|
|
146
|
+
if (ctx.__nerviqIsKotlin !== undefined) return ctx.__nerviqIsKotlin;
|
|
147
|
+
const gradle = (ctx.fileContent('build.gradle.kts') || '') + (ctx.fileContent('build.gradle') || '');
|
|
148
|
+
ctx.__nerviqIsKotlin = /kotlin/i.test(gradle);
|
|
149
|
+
return ctx.__nerviqIsKotlin;
|
|
150
|
+
}
|
|
151
|
+
|
|
120
152
|
function getPythonFiles(ctx) {
|
|
121
153
|
if (ctx.__nerviqPythonFiles) return ctx.__nerviqPythonFiles;
|
|
122
154
|
ctx.__nerviqPythonFiles = findProjectFiles(ctx, /\.py$/i);
|
|
@@ -150,6 +182,34 @@ function getGoFiles(ctx) {
|
|
|
150
182
|
return ctx.__nerviqGoFiles;
|
|
151
183
|
}
|
|
152
184
|
|
|
185
|
+
function getRustFiles(ctx) {
|
|
186
|
+
if (ctx.__nerviqRustFiles) return ctx.__nerviqRustFiles;
|
|
187
|
+
ctx.__nerviqRustFiles = findProjectFiles(ctx, /\.rs$/i);
|
|
188
|
+
return ctx.__nerviqRustFiles;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getMainRustFiles(ctx) {
|
|
192
|
+
if (ctx.__nerviqMainRustFiles) return ctx.__nerviqMainRustFiles;
|
|
193
|
+
ctx.__nerviqMainRustFiles = getRustFiles(ctx)
|
|
194
|
+
.filter(file => !/(^|\/)(tests|target)\//i.test(file))
|
|
195
|
+
.slice(0, 60);
|
|
196
|
+
return ctx.__nerviqMainRustFiles;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getJavaFiles(ctx) {
|
|
200
|
+
if (ctx.__nerviqJavaFiles) return ctx.__nerviqJavaFiles;
|
|
201
|
+
ctx.__nerviqJavaFiles = findProjectFiles(ctx, /\.java$/i);
|
|
202
|
+
return ctx.__nerviqJavaFiles;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getMainJavaFiles(ctx) {
|
|
206
|
+
if (ctx.__nerviqMainJavaFiles) return ctx.__nerviqMainJavaFiles;
|
|
207
|
+
ctx.__nerviqMainJavaFiles = getJavaFiles(ctx)
|
|
208
|
+
.filter(file => !/(^|\/)(test|tests|src\/test)\//i.test(file))
|
|
209
|
+
.slice(0, 60);
|
|
210
|
+
return ctx.__nerviqMainJavaFiles;
|
|
211
|
+
}
|
|
212
|
+
|
|
153
213
|
function getMainGoFiles(ctx) {
|
|
154
214
|
if (ctx.__nerviqMainGoFiles) return ctx.__nerviqMainGoFiles;
|
|
155
215
|
ctx.__nerviqMainGoFiles = getGoFiles(ctx).filter(file => !/_test\.go$/i.test(file)).slice(0, 60);
|
|
@@ -180,6 +240,42 @@ function getGoProjectText(ctx) {
|
|
|
180
240
|
return ctx.__nerviqGoProjectText;
|
|
181
241
|
}
|
|
182
242
|
|
|
243
|
+
function getRustProjectText(ctx) {
|
|
244
|
+
if (ctx.__nerviqRustProjectText) return ctx.__nerviqRustProjectText;
|
|
245
|
+
ctx.__nerviqRustProjectText = [
|
|
246
|
+
readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i),
|
|
247
|
+
readProjectFiles(ctx, /(^|\/)(clippy\.toml|\.clippy\.toml|rustfmt\.toml|\.rustfmt\.toml|build\.rs)$/i),
|
|
248
|
+
readProjectFiles(ctx, /(^|\/)\.cargo\/config\.toml$/i),
|
|
249
|
+
getWorkflowContent(ctx),
|
|
250
|
+
getMainRustFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').filter(Boolean).join('\n'),
|
|
251
|
+
].filter(Boolean).join('\n');
|
|
252
|
+
return ctx.__nerviqRustProjectText;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getJavaBuildText(ctx) {
|
|
256
|
+
if (ctx.__nerviqJavaBuildText) return ctx.__nerviqJavaBuildText;
|
|
257
|
+
ctx.__nerviqJavaBuildText = [
|
|
258
|
+
readProjectFiles(ctx, /(^|\/)pom\.xml$/i),
|
|
259
|
+
readProjectFiles(ctx, /(^|\/)build\.gradle$/i),
|
|
260
|
+
readProjectFiles(ctx, /(^|\/)build\.gradle\.kts$/i),
|
|
261
|
+
readProjectFiles(ctx, /(^|\/)settings\.gradle$/i),
|
|
262
|
+
readProjectFiles(ctx, /(^|\/)settings\.gradle\.kts$/i),
|
|
263
|
+
].filter(Boolean).join('\n');
|
|
264
|
+
return ctx.__nerviqJavaBuildText;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function getJavaProjectText(ctx) {
|
|
268
|
+
if (ctx.__nerviqJavaProjectText) return ctx.__nerviqJavaProjectText;
|
|
269
|
+
ctx.__nerviqJavaProjectText = [
|
|
270
|
+
getJavaBuildText(ctx),
|
|
271
|
+
getWorkflowContent(ctx),
|
|
272
|
+
readProjectFiles(ctx, /(^|\/)\.editorconfig$/i),
|
|
273
|
+
readProjectFiles(ctx, /(^|\/)(application\.properties|application\.ya?ml|logback.*\.xml|log4j2?.*\.xml)$/i),
|
|
274
|
+
getMainJavaFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').filter(Boolean).join('\n'),
|
|
275
|
+
].filter(Boolean).join('\n');
|
|
276
|
+
return ctx.__nerviqJavaProjectText;
|
|
277
|
+
}
|
|
278
|
+
|
|
183
279
|
function getGoInterfaceBlocks(ctx) {
|
|
184
280
|
if (ctx.__nerviqGoInterfaces) return ctx.__nerviqGoInterfaces;
|
|
185
281
|
const blocks = [];
|
|
@@ -1618,277 +1714,386 @@ const TECHNIQUES = {
|
|
|
1618
1714
|
// === PYTHON STACK CHECKS (category: 'python') ===============
|
|
1619
1715
|
// ============================================================
|
|
1620
1716
|
|
|
1621
|
-
|
|
1622
|
-
id:
|
|
1623
|
-
name: '
|
|
1624
|
-
check: (ctx) => {
|
|
1717
|
+
pyprojectTomlExists: {
|
|
1718
|
+
id: 120001,
|
|
1719
|
+
name: 'pyproject.toml exists for Python packaging',
|
|
1720
|
+
check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)pyproject\.toml$/i); },
|
|
1625
1721
|
impact: 'high',
|
|
1626
1722
|
category: 'python',
|
|
1627
|
-
fix: '
|
|
1723
|
+
fix: 'Add pyproject.toml to declare modern Python packaging, tooling, and metadata.',
|
|
1628
1724
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1629
1725
|
confidence: 0.7,
|
|
1630
1726
|
},
|
|
1631
1727
|
|
|
1632
|
-
|
|
1633
|
-
id:
|
|
1634
|
-
name: '
|
|
1635
|
-
check: (ctx) => {
|
|
1728
|
+
pythonTypeHints: {
|
|
1729
|
+
id: 120002,
|
|
1730
|
+
name: 'Type hints used in Python code',
|
|
1731
|
+
check: (ctx) => {
|
|
1732
|
+
if (!isPythonProject(ctx)) return null;
|
|
1733
|
+
if (hasProjectFile(ctx, /(^|\/)(mypy\.ini|py\.typed|pyrightconfig\.json)$/i)) return true;
|
|
1734
|
+
const pyproject = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i);
|
|
1735
|
+
if (/\[tool\.(mypy|pyright)\]/i.test(pyproject)) return true;
|
|
1736
|
+
const files = getMainPythonFiles(ctx);
|
|
1737
|
+
if (files.length === 0) return null;
|
|
1738
|
+
return files.some(file => /from typing import|import typing|from __future__ import annotations|->\s*[\w\[\]., ]+|:\s*[\w\[\]., ]+\s*=/.test(ctx.fileContent(file) || ''));
|
|
1739
|
+
},
|
|
1636
1740
|
impact: 'medium',
|
|
1637
1741
|
category: 'python',
|
|
1638
|
-
fix: '
|
|
1742
|
+
fix: 'Add type hints in main Python modules or configure mypy/pyright with py.typed support.',
|
|
1639
1743
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1640
1744
|
confidence: 0.7,
|
|
1641
1745
|
},
|
|
1642
1746
|
|
|
1643
|
-
|
|
1644
|
-
id:
|
|
1645
|
-
name: '
|
|
1646
|
-
check: (ctx) => {
|
|
1747
|
+
pythonLinter: {
|
|
1748
|
+
id: 120003,
|
|
1749
|
+
name: 'Python linter configured',
|
|
1750
|
+
check: (ctx) => {
|
|
1751
|
+
if (!isPythonProject(ctx)) return null;
|
|
1752
|
+
const config = `${getPythonProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)(\.flake8|\.pylintrc|pylintrc|ruff\.toml|\.ruff\.toml)$/i)}`;
|
|
1753
|
+
return /\[tool\.ruff\]|\[flake8\]|\[tool\.flake8\]|\[tool\.pylint\]|ruff|flake8|pylint/i.test(config);
|
|
1754
|
+
},
|
|
1647
1755
|
impact: 'medium',
|
|
1648
1756
|
category: 'python',
|
|
1649
|
-
fix: '
|
|
1757
|
+
fix: 'Configure a Python linter such as ruff, flake8, or pylint in pyproject.toml or a dedicated config file.',
|
|
1650
1758
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1651
1759
|
confidence: 0.7,
|
|
1652
1760
|
},
|
|
1653
1761
|
|
|
1654
|
-
|
|
1655
|
-
id:
|
|
1656
|
-
name: 'Python
|
|
1657
|
-
check: (ctx) => {
|
|
1658
|
-
|
|
1762
|
+
pythonFormatter: {
|
|
1763
|
+
id: 120004,
|
|
1764
|
+
name: 'Python formatter configured',
|
|
1765
|
+
check: (ctx) => {
|
|
1766
|
+
if (!isPythonProject(ctx)) return null;
|
|
1767
|
+
const pyproject = getPythonProjectText(ctx);
|
|
1768
|
+
const prettier = readProjectFiles(ctx, /(^|\/)\.prettierrc(\.(json|ya?ml|toml))?$/i);
|
|
1769
|
+
return /\[tool\.black\]|\[tool\.ruff\.format\]|\[tool\.isort\]/i.test(pyproject) ||
|
|
1770
|
+
/python|\.py\b/i.test(prettier);
|
|
1771
|
+
},
|
|
1772
|
+
impact: 'medium',
|
|
1659
1773
|
category: 'python',
|
|
1660
|
-
fix: '
|
|
1774
|
+
fix: 'Configure formatting with black, ruff format, isort, or a Prettier override that explicitly covers Python files.',
|
|
1661
1775
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1662
1776
|
confidence: 0.7,
|
|
1663
1777
|
},
|
|
1664
1778
|
|
|
1665
|
-
|
|
1666
|
-
id:
|
|
1667
|
-
name: '
|
|
1668
|
-
check: (ctx) => {
|
|
1779
|
+
pythonTestFramework: {
|
|
1780
|
+
id: 120005,
|
|
1781
|
+
name: 'Python test framework present',
|
|
1782
|
+
check: (ctx) => {
|
|
1783
|
+
if (!isPythonProject(ctx)) return null;
|
|
1784
|
+
return /\[tool\.pytest/i.test(getPythonProjectText(ctx)) ||
|
|
1785
|
+
hasProjectFile(ctx, /(^|\/)(pytest\.ini|tox\.ini|conftest\.py)$/i);
|
|
1786
|
+
},
|
|
1669
1787
|
impact: 'high',
|
|
1670
1788
|
category: 'python',
|
|
1671
|
-
fix: '
|
|
1789
|
+
fix: 'Add pytest.ini, conftest.py, tox.ini, or pyproject.toml pytest configuration so the test framework is explicit.',
|
|
1672
1790
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1673
1791
|
confidence: 0.7,
|
|
1674
1792
|
},
|
|
1675
1793
|
|
|
1676
|
-
|
|
1677
|
-
id:
|
|
1678
|
-
name: '
|
|
1679
|
-
check: (ctx) => {
|
|
1794
|
+
pythonVenvIgnored: {
|
|
1795
|
+
id: 120006,
|
|
1796
|
+
name: 'Virtual environment directories ignored in git',
|
|
1797
|
+
check: (ctx) => {
|
|
1798
|
+
if (!isPythonProject(ctx)) return null;
|
|
1799
|
+
const gitignore = ctx.fileContent('.gitignore') || '';
|
|
1800
|
+
return /(^|\n)\s*\.venv\/?\s*($|\n)|(^|\n)\s*venv\/?\s*($|\n)|(^|\n)\s*env\/?\s*($|\n)/i.test(gitignore);
|
|
1801
|
+
},
|
|
1680
1802
|
impact: 'medium',
|
|
1681
1803
|
category: 'python',
|
|
1682
|
-
fix: '
|
|
1804
|
+
fix: 'Ignore `.venv/`, `venv/`, or `env/` in .gitignore so local environments do not get committed.',
|
|
1683
1805
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1684
1806
|
confidence: 0.7,
|
|
1685
1807
|
},
|
|
1686
1808
|
|
|
1687
|
-
|
|
1688
|
-
id:
|
|
1689
|
-
name: '
|
|
1690
|
-
check: (ctx) => {
|
|
1691
|
-
|
|
1809
|
+
pythonRequirementsPinned: {
|
|
1810
|
+
id: 120007,
|
|
1811
|
+
name: 'Requirements files use pinned versions',
|
|
1812
|
+
check: (ctx) => {
|
|
1813
|
+
if (!isPythonProject(ctx)) return null;
|
|
1814
|
+
const files = findProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
|
|
1815
|
+
if (files.length === 0) return null;
|
|
1816
|
+
const lines = files
|
|
1817
|
+
.flatMap(file => (ctx.fileContent(file) || '').split(/\r?\n/))
|
|
1818
|
+
.map(line => line.trim())
|
|
1819
|
+
.filter(line => line && !line.startsWith('#'));
|
|
1820
|
+
if (lines.length === 0) return null;
|
|
1821
|
+
return lines.every(line => /^(-r|-c|--)/.test(line) || /==| @ /.test(line));
|
|
1822
|
+
},
|
|
1823
|
+
impact: 'high',
|
|
1692
1824
|
category: 'python',
|
|
1693
|
-
fix: '
|
|
1825
|
+
fix: 'Pin Python requirements with `==` or direct references so installs stay reproducible.',
|
|
1694
1826
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1695
1827
|
confidence: 0.7,
|
|
1696
1828
|
},
|
|
1697
1829
|
|
|
1698
|
-
|
|
1699
|
-
id:
|
|
1700
|
-
name: '
|
|
1701
|
-
check: (ctx) => {
|
|
1830
|
+
pythonSecurityScanner: {
|
|
1831
|
+
id: 120008,
|
|
1832
|
+
name: 'Python security scanner configured',
|
|
1833
|
+
check: (ctx) => {
|
|
1834
|
+
if (!isPythonProject(ctx)) return null;
|
|
1835
|
+
const content = `${getPythonProjectText(ctx)}\n${getWorkflowContent(ctx)}\n${getPreCommitContent(ctx)}`;
|
|
1836
|
+
return /bandit|pip-audit|safety/i.test(content);
|
|
1837
|
+
},
|
|
1702
1838
|
impact: 'medium',
|
|
1703
1839
|
category: 'python',
|
|
1704
|
-
fix: 'Configure
|
|
1840
|
+
fix: 'Configure bandit, safety, or pip-audit in dependencies, pre-commit, or CI.',
|
|
1705
1841
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1706
1842
|
confidence: 0.7,
|
|
1707
1843
|
},
|
|
1708
1844
|
|
|
1709
|
-
|
|
1710
|
-
id:
|
|
1711
|
-
name: '
|
|
1712
|
-
check: (ctx) => {
|
|
1713
|
-
|
|
1845
|
+
pythonPreCommitHooks: {
|
|
1846
|
+
id: 120009,
|
|
1847
|
+
name: 'pre-commit configured with Python hooks',
|
|
1848
|
+
check: (ctx) => {
|
|
1849
|
+
if (!isPythonProject(ctx)) return null;
|
|
1850
|
+
const preCommit = getPreCommitContent(ctx);
|
|
1851
|
+
if (!preCommit) return false;
|
|
1852
|
+
return /ruff|black|mypy|pyupgrade|pytest|bandit|isort|flake8|pylint/i.test(preCommit);
|
|
1853
|
+
},
|
|
1854
|
+
impact: 'medium',
|
|
1714
1855
|
category: 'python',
|
|
1715
|
-
fix: '
|
|
1856
|
+
fix: 'Add `.pre-commit-config.yaml` with Python-focused hooks such as ruff, black, mypy, or bandit.',
|
|
1716
1857
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1717
1858
|
confidence: 0.7,
|
|
1718
1859
|
},
|
|
1719
1860
|
|
|
1720
|
-
|
|
1721
|
-
id:
|
|
1722
|
-
name: '
|
|
1723
|
-
check: (ctx) => {
|
|
1724
|
-
|
|
1861
|
+
pythonDocstrings: {
|
|
1862
|
+
id: 120010,
|
|
1863
|
+
name: 'Docstrings present in main Python files',
|
|
1864
|
+
check: (ctx) => {
|
|
1865
|
+
if (!isPythonProject(ctx)) return null;
|
|
1866
|
+
const files = getMainPythonFiles(ctx);
|
|
1867
|
+
if (files.length === 0) return null;
|
|
1868
|
+
return files.some(file => /(^|\n)\s*(def|class)\s+\w+.*:\s*\n\s*("""|''')|^\s*("""|''')/m.test(ctx.fileContent(file) || ''));
|
|
1869
|
+
},
|
|
1870
|
+
impact: 'low',
|
|
1725
1871
|
category: 'python',
|
|
1726
|
-
fix: '
|
|
1872
|
+
fix: 'Add module, class, or function docstrings in the main Python source files.',
|
|
1727
1873
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1728
1874
|
confidence: 0.7,
|
|
1729
1875
|
},
|
|
1730
1876
|
|
|
1731
|
-
|
|
1732
|
-
id:
|
|
1733
|
-
name: '
|
|
1734
|
-
check: (ctx) => {
|
|
1735
|
-
|
|
1877
|
+
pythonCIConfigured: {
|
|
1878
|
+
id: 120011,
|
|
1879
|
+
name: 'CI runs Python tests',
|
|
1880
|
+
check: (ctx) => {
|
|
1881
|
+
if (!isPythonProject(ctx)) return null;
|
|
1882
|
+
return /pytest|python -m pytest|python -m unittest|tox\b|nox\b/i.test(getWorkflowContent(ctx));
|
|
1883
|
+
},
|
|
1884
|
+
impact: 'high',
|
|
1736
1885
|
category: 'python',
|
|
1737
|
-
fix: '
|
|
1886
|
+
fix: 'Run Python tests in CI with pytest, unittest, tox, or nox.',
|
|
1738
1887
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1739
1888
|
confidence: 0.7,
|
|
1740
1889
|
},
|
|
1741
1890
|
|
|
1742
|
-
|
|
1743
|
-
id:
|
|
1744
|
-
name: '
|
|
1745
|
-
check: (ctx) => {
|
|
1891
|
+
pythonCoverage: {
|
|
1892
|
+
id: 120012,
|
|
1893
|
+
name: 'Python coverage configured',
|
|
1894
|
+
check: (ctx) => {
|
|
1895
|
+
if (!isPythonProject(ctx)) return null;
|
|
1896
|
+
const content = `${getPythonProjectText(ctx)}\n${getWorkflowContent(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.coveragerc$/i)}`;
|
|
1897
|
+
return /\[tool\.coverage|pytest-cov|coverage\b|--cov\b/i.test(content);
|
|
1898
|
+
},
|
|
1746
1899
|
impact: 'medium',
|
|
1747
1900
|
category: 'python',
|
|
1748
|
-
fix: '
|
|
1901
|
+
fix: 'Configure coverage.py or pytest-cov in project config or CI.',
|
|
1749
1902
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1750
1903
|
confidence: 0.7,
|
|
1751
1904
|
},
|
|
1752
1905
|
|
|
1753
|
-
|
|
1754
|
-
id:
|
|
1755
|
-
name: '
|
|
1756
|
-
check: (ctx) => {
|
|
1906
|
+
pythonPackageManager: {
|
|
1907
|
+
id: 120013,
|
|
1908
|
+
name: 'Modern Python package manager lockfile present',
|
|
1909
|
+
check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)(poetry\.lock|pdm\.lock|uv\.lock|Pipfile\.lock)$/); },
|
|
1757
1910
|
impact: 'medium',
|
|
1758
1911
|
category: 'python',
|
|
1759
|
-
fix: '
|
|
1912
|
+
fix: 'Commit a Poetry, PDM, uv, or Pipenv lockfile for reproducible dependency resolution.',
|
|
1760
1913
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1761
1914
|
confidence: 0.7,
|
|
1762
1915
|
},
|
|
1763
1916
|
|
|
1764
|
-
|
|
1765
|
-
id:
|
|
1766
|
-
name: '
|
|
1767
|
-
check: (ctx) => {
|
|
1917
|
+
pythonMinVersionSpecified: {
|
|
1918
|
+
id: 120014,
|
|
1919
|
+
name: 'Minimum Python version specified',
|
|
1920
|
+
check: (ctx) => {
|
|
1921
|
+
if (!isPythonProject(ctx)) return null;
|
|
1922
|
+
const content = `${getPythonProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.python-version$/i)}`;
|
|
1923
|
+
return /requires-python|python_requires|(^|\n)\s*python\s*=|^\s*\d+\.\d+(\.\d+)?\s*$/im.test(content);
|
|
1924
|
+
},
|
|
1768
1925
|
impact: 'medium',
|
|
1769
1926
|
category: 'python',
|
|
1770
|
-
fix: '
|
|
1927
|
+
fix: 'Specify the supported Python version with `.python-version`, `requires-python`, or `python_requires`.',
|
|
1771
1928
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1772
1929
|
confidence: 0.7,
|
|
1773
1930
|
},
|
|
1774
1931
|
|
|
1775
|
-
|
|
1776
|
-
id:
|
|
1777
|
-
name: '
|
|
1778
|
-
check: (ctx) => {
|
|
1932
|
+
pythonAsyncPatterns: {
|
|
1933
|
+
id: 120015,
|
|
1934
|
+
name: 'Async Python patterns used',
|
|
1935
|
+
check: (ctx) => {
|
|
1936
|
+
if (!isPythonProject(ctx)) return null;
|
|
1937
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
1938
|
+
return /asyncio|aiohttp|fastapi|starlette|trio|anyio|async def|await /i.test(content);
|
|
1939
|
+
},
|
|
1779
1940
|
impact: 'low',
|
|
1780
1941
|
category: 'python',
|
|
1781
|
-
fix: '
|
|
1942
|
+
fix: 'Adopt explicit async patterns such as asyncio, aiohttp, FastAPI, or `async def` where concurrent workflows matter.',
|
|
1782
1943
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1783
1944
|
confidence: 0.7,
|
|
1784
1945
|
},
|
|
1785
1946
|
|
|
1786
|
-
|
|
1787
|
-
id:
|
|
1788
|
-
name: '
|
|
1789
|
-
check: (ctx) => {
|
|
1947
|
+
pythonEnvExample: {
|
|
1948
|
+
id: 120016,
|
|
1949
|
+
name: 'Python project includes an environment example file',
|
|
1950
|
+
check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)\.env(\.example|\.sample)$/i); },
|
|
1790
1951
|
impact: 'medium',
|
|
1791
1952
|
category: 'python',
|
|
1792
|
-
fix: '
|
|
1953
|
+
fix: 'Add `.env.example` or `.env.sample` so required Python environment variables are documented.',
|
|
1793
1954
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1794
1955
|
confidence: 0.7,
|
|
1795
1956
|
},
|
|
1796
1957
|
|
|
1797
|
-
|
|
1798
|
-
id:
|
|
1799
|
-
name: '
|
|
1800
|
-
check: (ctx) => {
|
|
1958
|
+
pythonMigrations: {
|
|
1959
|
+
id: 120017,
|
|
1960
|
+
name: 'Python database migration tooling present',
|
|
1961
|
+
check: (ctx) => {
|
|
1962
|
+
if (!isPythonProject(ctx)) return null;
|
|
1963
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 20).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
1964
|
+
return /alembic|django\.db\.migrations|makemigrations|migrate/i.test(content) ||
|
|
1965
|
+
hasProjectFile(ctx, /(^|\/)alembic\.ini$/i) ||
|
|
1966
|
+
hasProjectFile(ctx, /(^|\/)(alembic|migrations)\//i);
|
|
1967
|
+
},
|
|
1801
1968
|
impact: 'medium',
|
|
1802
1969
|
category: 'python',
|
|
1803
|
-
fix: '
|
|
1970
|
+
fix: 'Use Alembic or Django migrations and keep the migration surface committed in the repo.',
|
|
1804
1971
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1805
1972
|
confidence: 0.7,
|
|
1806
1973
|
},
|
|
1807
1974
|
|
|
1808
|
-
|
|
1809
|
-
id:
|
|
1810
|
-
name: '
|
|
1811
|
-
check: (ctx) => {
|
|
1812
|
-
|
|
1975
|
+
pythonLogging: {
|
|
1976
|
+
id: 120018,
|
|
1977
|
+
name: 'Python structured logging configured',
|
|
1978
|
+
check: (ctx) => {
|
|
1979
|
+
if (!isPythonProject(ctx)) return null;
|
|
1980
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
1981
|
+
return /structlog|loguru|logging\.config|dictConfig|getLogger|basicConfig/i.test(content);
|
|
1982
|
+
},
|
|
1983
|
+
impact: 'medium',
|
|
1813
1984
|
category: 'python',
|
|
1814
|
-
fix: '
|
|
1985
|
+
fix: 'Configure logging with Python logging config, structlog, or loguru for consistent operational signals.',
|
|
1815
1986
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1816
1987
|
confidence: 0.7,
|
|
1817
1988
|
},
|
|
1818
1989
|
|
|
1819
|
-
|
|
1820
|
-
id:
|
|
1821
|
-
name: 'Python
|
|
1822
|
-
check: (ctx) => {
|
|
1990
|
+
pythonAPISchema: {
|
|
1991
|
+
id: 120019,
|
|
1992
|
+
name: 'Python API schema or model definitions present',
|
|
1993
|
+
check: (ctx) => {
|
|
1994
|
+
if (!isPythonProject(ctx)) return null;
|
|
1995
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
1996
|
+
return /openapi|swagger|BaseModel|pydantic|Schema\)|marshmallow|TypedDict/i.test(content);
|
|
1997
|
+
},
|
|
1823
1998
|
impact: 'medium',
|
|
1824
1999
|
category: 'python',
|
|
1825
|
-
fix: '
|
|
2000
|
+
fix: 'Define API schemas with OpenAPI, Pydantic, Marshmallow, or typed request/response models.',
|
|
1826
2001
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1827
2002
|
confidence: 0.7,
|
|
1828
2003
|
},
|
|
1829
2004
|
|
|
1830
|
-
|
|
1831
|
-
id:
|
|
1832
|
-
name: '
|
|
1833
|
-
check: (ctx) => {
|
|
1834
|
-
|
|
2005
|
+
pythonContainerized: {
|
|
2006
|
+
id: 120020,
|
|
2007
|
+
name: 'Python container image uses a Python base',
|
|
2008
|
+
check: (ctx) => {
|
|
2009
|
+
if (!isPythonProject(ctx)) return null;
|
|
2010
|
+
const dockerfile = ctx.fileContent('Dockerfile') || '';
|
|
2011
|
+
if (!dockerfile) return null;
|
|
2012
|
+
return /FROM\s+python[:\d.-]/i.test(dockerfile);
|
|
2013
|
+
},
|
|
2014
|
+
impact: 'medium',
|
|
1835
2015
|
category: 'python',
|
|
1836
|
-
fix: '
|
|
2016
|
+
fix: 'Use an official Python image such as `python:3.12-slim` when containerizing Python services.',
|
|
1837
2017
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1838
2018
|
confidence: 0.7,
|
|
1839
2019
|
},
|
|
1840
2020
|
|
|
1841
|
-
|
|
1842
|
-
id:
|
|
1843
|
-
name: '
|
|
1844
|
-
check: (ctx) => {
|
|
2021
|
+
pythonDependencyGroups: {
|
|
2022
|
+
id: 120021,
|
|
2023
|
+
name: 'Python dev and test dependency groups separated',
|
|
2024
|
+
check: (ctx) => {
|
|
2025
|
+
if (!isPythonProject(ctx)) return null;
|
|
2026
|
+
const content = getPythonProjectText(ctx);
|
|
2027
|
+
return /\[tool\.poetry\.group\.[^\]]+\]|\[project\.optional-dependencies\]|extras_require|dependency-groups/i.test(content);
|
|
2028
|
+
},
|
|
1845
2029
|
impact: 'medium',
|
|
1846
2030
|
category: 'python',
|
|
1847
|
-
fix: '
|
|
2031
|
+
fix: 'Separate Python dev and test dependencies with Poetry groups, optional-dependencies, or extras_require.',
|
|
1848
2032
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1849
2033
|
confidence: 0.7,
|
|
1850
2034
|
},
|
|
1851
2035
|
|
|
1852
|
-
|
|
1853
|
-
id:
|
|
1854
|
-
name: '
|
|
1855
|
-
check: (ctx) => {
|
|
1856
|
-
|
|
2036
|
+
pythonPathConfig: {
|
|
2037
|
+
id: 120022,
|
|
2038
|
+
name: 'Python tool path configuration present',
|
|
2039
|
+
check: (ctx) => {
|
|
2040
|
+
if (!isPythonProject(ctx)) return null;
|
|
2041
|
+
if (hasProjectFile(ctx, /(^|\/)pyrightconfig\.json$/i)) return true;
|
|
2042
|
+
const vscodeSettings = findProjectFiles(ctx, /(^|\/)\.vscode\/settings\.json$/i)
|
|
2043
|
+
.map(file => ctx.jsonFile(file) || {})
|
|
2044
|
+
.find(settings => Object.keys(settings).some(key => key.toLowerCase().includes('python')));
|
|
2045
|
+
return !!vscodeSettings;
|
|
2046
|
+
},
|
|
2047
|
+
impact: 'low',
|
|
1857
2048
|
category: 'python',
|
|
1858
|
-
fix: '
|
|
2049
|
+
fix: 'Add `pyrightconfig.json` or VS Code Python settings so tooling resolves imports and environments consistently.',
|
|
1859
2050
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1860
2051
|
confidence: 0.7,
|
|
1861
2052
|
},
|
|
1862
2053
|
|
|
1863
|
-
|
|
1864
|
-
id:
|
|
1865
|
-
name: '
|
|
1866
|
-
check: (ctx) => {
|
|
1867
|
-
|
|
2054
|
+
pythonMonorepo: {
|
|
2055
|
+
id: 120023,
|
|
2056
|
+
name: 'Python monorepo-friendly package layout present',
|
|
2057
|
+
check: (ctx) => {
|
|
2058
|
+
if (!isPythonProject(ctx)) return null;
|
|
2059
|
+
const content = getPythonProjectText(ctx);
|
|
2060
|
+
return ctx.hasDir('src') ||
|
|
2061
|
+
/namespace_packages|find_namespace:|from\s*=\s*["']src["']|package-dir/i.test(content);
|
|
2062
|
+
},
|
|
2063
|
+
impact: 'low',
|
|
1868
2064
|
category: 'python',
|
|
1869
|
-
fix: '
|
|
2065
|
+
fix: 'Use a `src/` layout or namespace package configuration for larger multi-package Python repos.',
|
|
1870
2066
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1871
2067
|
confidence: 0.7,
|
|
1872
2068
|
},
|
|
1873
2069
|
|
|
1874
|
-
|
|
1875
|
-
id:
|
|
1876
|
-
name: '
|
|
1877
|
-
check: (ctx) => {
|
|
1878
|
-
|
|
2070
|
+
pythonErrorHandling: {
|
|
2071
|
+
id: 120024,
|
|
2072
|
+
name: 'Custom Python exception classes defined',
|
|
2073
|
+
check: (ctx) => {
|
|
2074
|
+
if (!isPythonProject(ctx)) return null;
|
|
2075
|
+
const files = getMainPythonFiles(ctx);
|
|
2076
|
+
if (files.length === 0) return null;
|
|
2077
|
+
return files.some(file => /class\s+\w+(Error|Exception)\s*\((?:[\w.]*Exception|[\w.]*Error)\)\s*:/i.test(ctx.fileContent(file) || ''));
|
|
2078
|
+
},
|
|
2079
|
+
impact: 'low',
|
|
1879
2080
|
category: 'python',
|
|
1880
|
-
fix: '
|
|
2081
|
+
fix: 'Define custom exception classes for domain-specific Python error handling instead of only raising generic exceptions.',
|
|
1881
2082
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1882
2083
|
confidence: 0.7,
|
|
1883
2084
|
},
|
|
1884
2085
|
|
|
1885
|
-
|
|
1886
|
-
id:
|
|
1887
|
-
name: 'Python
|
|
1888
|
-
check: (ctx) => {
|
|
2086
|
+
pythonDataValidation: {
|
|
2087
|
+
id: 120025,
|
|
2088
|
+
name: 'Python data validation library used',
|
|
2089
|
+
check: (ctx) => {
|
|
2090
|
+
if (!isPythonProject(ctx)) return null;
|
|
2091
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
2092
|
+
return /pydantic|marshmallow|attrs|attr\.s|BaseModel|Schema\)/i.test(content);
|
|
2093
|
+
},
|
|
1889
2094
|
impact: 'medium',
|
|
1890
2095
|
category: 'python',
|
|
1891
|
-
fix: '
|
|
2096
|
+
fix: 'Use Pydantic, Marshmallow, attrs, or similar validation libraries for structured Python inputs and models.',
|
|
1892
2097
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1893
2098
|
confidence: 0.7,
|
|
1894
2099
|
},
|
|
@@ -1898,221 +2103,307 @@ const TECHNIQUES = {
|
|
|
1898
2103
|
// ============================================================
|
|
1899
2104
|
|
|
1900
2105
|
goModExists: {
|
|
1901
|
-
id:
|
|
1902
|
-
name: 'go.mod exists',
|
|
1903
|
-
check: (ctx) => { if (!ctx
|
|
1904
|
-
impact: 'high',
|
|
1905
|
-
category: 'go',
|
|
1906
|
-
fix: 'Initialize Go module with go mod init.',
|
|
1907
|
-
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1908
|
-
confidence: 0.7,
|
|
1909
|
-
},
|
|
1910
|
-
|
|
1911
|
-
goSumCommitted: {
|
|
1912
|
-
id: 'CL-GO02',
|
|
1913
|
-
name: 'go.sum committed',
|
|
1914
|
-
check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return ctx.files.some(f => /go\.sum$/.test(f)); },
|
|
2106
|
+
id: 120101,
|
|
2107
|
+
name: 'go.mod exists for Go module management',
|
|
2108
|
+
check: (ctx) => { if (!isGoProject(ctx)) return null; return true; },
|
|
1915
2109
|
impact: 'high',
|
|
1916
2110
|
category: 'go',
|
|
1917
|
-
fix: '
|
|
2111
|
+
fix: 'Initialize the repository as a Go module with `go mod init` and commit `go.mod`.',
|
|
1918
2112
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1919
2113
|
confidence: 0.7,
|
|
1920
2114
|
},
|
|
1921
2115
|
|
|
1922
|
-
|
|
1923
|
-
id:
|
|
1924
|
-
name: '
|
|
1925
|
-
check: (ctx) => {
|
|
2116
|
+
goLinter: {
|
|
2117
|
+
id: 120102,
|
|
2118
|
+
name: 'Go linter configured',
|
|
2119
|
+
check: (ctx) => {
|
|
2120
|
+
if (!isGoProject(ctx)) return null;
|
|
2121
|
+
const content = `${getGoProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.golangci\.(ya?ml|toml)$/i)}`;
|
|
2122
|
+
return /\.golangci\.|golangci-lint/i.test(content);
|
|
2123
|
+
},
|
|
1926
2124
|
impact: 'medium',
|
|
1927
2125
|
category: 'go',
|
|
1928
|
-
fix: '
|
|
2126
|
+
fix: 'Configure golangci-lint in the repo or CI for consistent Go lint enforcement.',
|
|
1929
2127
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1930
2128
|
confidence: 0.7,
|
|
1931
2129
|
},
|
|
1932
2130
|
|
|
1933
|
-
|
|
1934
|
-
id:
|
|
1935
|
-
name: '
|
|
1936
|
-
check: (ctx) => { if (!ctx
|
|
2131
|
+
goTestFiles: {
|
|
2132
|
+
id: 120103,
|
|
2133
|
+
name: 'Go test files present',
|
|
2134
|
+
check: (ctx) => { if (!isGoProject(ctx)) return null; return hasProjectFile(ctx, /_test\.go$/i); },
|
|
1937
2135
|
impact: 'high',
|
|
1938
2136
|
category: 'go',
|
|
1939
|
-
fix: '
|
|
2137
|
+
fix: 'Add `_test.go` files so Go packages have executable unit or integration tests.',
|
|
1940
2138
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1941
2139
|
confidence: 0.7,
|
|
1942
2140
|
},
|
|
1943
2141
|
|
|
1944
|
-
|
|
1945
|
-
id:
|
|
1946
|
-
name: 'go
|
|
1947
|
-
check: (ctx) => {
|
|
1948
|
-
|
|
2142
|
+
goVet: {
|
|
2143
|
+
id: 120104,
|
|
2144
|
+
name: 'go vet runs in automation',
|
|
2145
|
+
check: (ctx) => {
|
|
2146
|
+
if (!isGoProject(ctx)) return null;
|
|
2147
|
+
const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
|
|
2148
|
+
return /go vet/i.test(content);
|
|
2149
|
+
},
|
|
2150
|
+
impact: 'medium',
|
|
1949
2151
|
category: 'go',
|
|
1950
|
-
fix: '
|
|
2152
|
+
fix: 'Run `go vet` in CI or the project Makefile to catch common Go mistakes.',
|
|
1951
2153
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1952
2154
|
confidence: 0.7,
|
|
1953
2155
|
},
|
|
1954
2156
|
|
|
1955
|
-
|
|
1956
|
-
id:
|
|
1957
|
-
name: '
|
|
1958
|
-
check: (ctx) => {
|
|
2157
|
+
goFmt: {
|
|
2158
|
+
id: 120105,
|
|
2159
|
+
name: 'gofmt or goimports enforced',
|
|
2160
|
+
check: (ctx) => {
|
|
2161
|
+
if (!isGoProject(ctx)) return null;
|
|
2162
|
+
const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}\n${getPreCommitContent(ctx)}`;
|
|
2163
|
+
return /gofmt|goimports/i.test(content);
|
|
2164
|
+
},
|
|
1959
2165
|
impact: 'medium',
|
|
1960
2166
|
category: 'go',
|
|
1961
|
-
fix: '
|
|
2167
|
+
fix: 'Run `gofmt` or `goimports` in CI, pre-commit, or developer tooling.',
|
|
1962
2168
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1963
2169
|
confidence: 0.7,
|
|
1964
2170
|
},
|
|
1965
2171
|
|
|
1966
|
-
|
|
1967
|
-
id:
|
|
1968
|
-
name: '
|
|
1969
|
-
check: (ctx) => {
|
|
2172
|
+
goModTidy: {
|
|
2173
|
+
id: 120106,
|
|
2174
|
+
name: 'go mod tidy runs in automation',
|
|
2175
|
+
check: (ctx) => {
|
|
2176
|
+
if (!isGoProject(ctx)) return null;
|
|
2177
|
+
const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
|
|
2178
|
+
return /go mod tidy/i.test(content);
|
|
2179
|
+
},
|
|
1970
2180
|
impact: 'medium',
|
|
1971
2181
|
category: 'go',
|
|
1972
|
-
fix: '
|
|
2182
|
+
fix: 'Run `go mod tidy` in CI or the Makefile so module metadata stays clean.',
|
|
1973
2183
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1974
2184
|
confidence: 0.7,
|
|
1975
2185
|
},
|
|
1976
2186
|
|
|
1977
|
-
|
|
1978
|
-
id:
|
|
1979
|
-
name: '
|
|
1980
|
-
check: (ctx) => {
|
|
1981
|
-
|
|
2187
|
+
goBuildTags: {
|
|
2188
|
+
id: 120107,
|
|
2189
|
+
name: 'Go build tags or constraints used',
|
|
2190
|
+
check: (ctx) => {
|
|
2191
|
+
if (!isGoProject(ctx)) return null;
|
|
2192
|
+
const files = getGoFiles(ctx);
|
|
2193
|
+
if (files.length === 0) return null;
|
|
2194
|
+
return files.some(file => /\/\/go:build|\/\/ \+build/.test(ctx.fileContent(file) || ''));
|
|
2195
|
+
},
|
|
2196
|
+
impact: 'low',
|
|
1982
2197
|
category: 'go',
|
|
1983
|
-
fix: '
|
|
2198
|
+
fix: 'Use `//go:build` constraints when a Go package depends on build tags or platform-specific variants.',
|
|
1984
2199
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1985
2200
|
confidence: 0.7,
|
|
1986
2201
|
},
|
|
1987
2202
|
|
|
1988
|
-
|
|
1989
|
-
id:
|
|
1990
|
-
name: '
|
|
1991
|
-
check: (ctx) => {
|
|
2203
|
+
goErrorWrapping: {
|
|
2204
|
+
id: 120108,
|
|
2205
|
+
name: 'Go errors use wrapping patterns',
|
|
2206
|
+
check: (ctx) => {
|
|
2207
|
+
if (!isGoProject(ctx)) return null;
|
|
2208
|
+
const files = getMainGoFiles(ctx);
|
|
2209
|
+
if (files.length === 0) return null;
|
|
2210
|
+
return files.some(file => /fmt\.Errorf\([^)]*%w|errors\.Join\(/.test(ctx.fileContent(file) || ''));
|
|
2211
|
+
},
|
|
1992
2212
|
impact: 'medium',
|
|
1993
2213
|
category: 'go',
|
|
1994
|
-
fix: '
|
|
2214
|
+
fix: 'Wrap Go errors with `fmt.Errorf(... %w ...)` or similar patterns to preserve context.',
|
|
1995
2215
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1996
2216
|
confidence: 0.7,
|
|
1997
2217
|
},
|
|
1998
2218
|
|
|
1999
|
-
|
|
2000
|
-
id:
|
|
2001
|
-
name: '
|
|
2002
|
-
check: (ctx) => {
|
|
2219
|
+
goInterfaceSegregation: {
|
|
2220
|
+
id: 120109,
|
|
2221
|
+
name: 'Go interfaces stay small',
|
|
2222
|
+
check: (ctx) => {
|
|
2223
|
+
if (!isGoProject(ctx)) return null;
|
|
2224
|
+
const interfaces = getGoInterfaceBlocks(ctx);
|
|
2225
|
+
if (interfaces.length === 0) return null;
|
|
2226
|
+
return interfaces.every(block => countGoInterfaceMethods(block) <= 5);
|
|
2227
|
+
},
|
|
2003
2228
|
impact: 'low',
|
|
2004
2229
|
category: 'go',
|
|
2005
|
-
fix: '
|
|
2230
|
+
fix: 'Keep Go interfaces small and focused; split interfaces that define more than five methods.',
|
|
2006
2231
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2007
2232
|
confidence: 0.7,
|
|
2008
2233
|
},
|
|
2009
2234
|
|
|
2010
|
-
|
|
2011
|
-
id:
|
|
2012
|
-
name: '
|
|
2013
|
-
check: (ctx) => {
|
|
2235
|
+
goContextUsage: {
|
|
2236
|
+
id: 120110,
|
|
2237
|
+
name: 'Go services use context.Context',
|
|
2238
|
+
check: (ctx) => {
|
|
2239
|
+
if (!isGoProject(ctx)) return null;
|
|
2240
|
+
const files = getMainGoFiles(ctx);
|
|
2241
|
+
if (files.length === 0) return null;
|
|
2242
|
+
return files.some(file => /context\.Context|context\.With(Cancel|Timeout|Deadline)|ctx\s+context\.Context/.test(ctx.fileContent(file) || ''));
|
|
2243
|
+
},
|
|
2014
2244
|
impact: 'medium',
|
|
2015
2245
|
category: 'go',
|
|
2016
|
-
fix: '
|
|
2246
|
+
fix: 'Pass `context.Context` through handlers and services so cancellation and deadlines are propagated correctly.',
|
|
2017
2247
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2018
2248
|
confidence: 0.7,
|
|
2019
2249
|
},
|
|
2020
2250
|
|
|
2021
|
-
|
|
2022
|
-
id:
|
|
2023
|
-
name: '
|
|
2024
|
-
check: (ctx) => {
|
|
2025
|
-
|
|
2251
|
+
goStructTags: {
|
|
2252
|
+
id: 120111,
|
|
2253
|
+
name: 'Exported Go structs include tags',
|
|
2254
|
+
check: (ctx) => {
|
|
2255
|
+
if (!isGoProject(ctx)) return null;
|
|
2256
|
+
const structBlocks = [];
|
|
2257
|
+
for (const file of getMainGoFiles(ctx)) {
|
|
2258
|
+
const content = ctx.fileContent(file) || '';
|
|
2259
|
+
for (const match of content.matchAll(/type\s+([A-Z]\w*)\s+struct\s*\{([\s\S]*?)\}/g)) {
|
|
2260
|
+
structBlocks.push(match[2]);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
if (structBlocks.length === 0) return null;
|
|
2264
|
+
return structBlocks.some(block => /`[^`]*(json|yaml|db):"/.test(block));
|
|
2265
|
+
},
|
|
2266
|
+
impact: 'low',
|
|
2026
2267
|
category: 'go',
|
|
2027
|
-
fix: 'Add
|
|
2268
|
+
fix: 'Add struct tags such as `json`, `yaml`, or `db` on exported Go types that cross boundaries.',
|
|
2028
2269
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2029
2270
|
confidence: 0.7,
|
|
2030
2271
|
},
|
|
2031
2272
|
|
|
2032
|
-
|
|
2033
|
-
id:
|
|
2034
|
-
name: '
|
|
2035
|
-
check: (ctx) => {
|
|
2273
|
+
goMakefile: {
|
|
2274
|
+
id: 120112,
|
|
2275
|
+
name: 'Go Makefile includes build, test, and lint targets',
|
|
2276
|
+
check: (ctx) => {
|
|
2277
|
+
if (!isGoProject(ctx)) return null;
|
|
2278
|
+
const makefile = ctx.fileContent('Makefile') || '';
|
|
2279
|
+
if (!makefile) return false;
|
|
2280
|
+
return /^\s*build:/m.test(makefile) && /^\s*test:/m.test(makefile) && /^\s*lint:/m.test(makefile);
|
|
2281
|
+
},
|
|
2036
2282
|
impact: 'medium',
|
|
2037
2283
|
category: 'go',
|
|
2038
|
-
fix: '
|
|
2284
|
+
fix: 'Add a Makefile with `build`, `test`, and `lint` targets for common Go workflows.',
|
|
2039
2285
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2040
2286
|
confidence: 0.7,
|
|
2041
2287
|
},
|
|
2042
2288
|
|
|
2043
|
-
|
|
2044
|
-
id:
|
|
2045
|
-
name: '
|
|
2046
|
-
check: (ctx) => {
|
|
2289
|
+
goDocComments: {
|
|
2290
|
+
id: 120113,
|
|
2291
|
+
name: 'Exported Go functions have doc comments',
|
|
2292
|
+
check: (ctx) => {
|
|
2293
|
+
if (!isGoProject(ctx)) return null;
|
|
2294
|
+
const files = getMainGoFiles(ctx);
|
|
2295
|
+
if (files.length === 0) return null;
|
|
2296
|
+
const documented = files.some(file => /\/\/\s*[A-Z]\w+.*\nfunc\s+(?:\([^)]+\)\s*)?[A-Z]\w+\s*\(/.test(ctx.fileContent(file) || ''));
|
|
2297
|
+
const exported = files.some(file => /func\s+(?:\([^)]+\)\s*)?[A-Z]\w+\s*\(/.test(ctx.fileContent(file) || ''));
|
|
2298
|
+
if (!exported) return null;
|
|
2299
|
+
return documented;
|
|
2300
|
+
},
|
|
2047
2301
|
impact: 'low',
|
|
2048
2302
|
category: 'go',
|
|
2049
|
-
fix: '
|
|
2303
|
+
fix: 'Add Go doc comments above exported functions so package APIs remain self-describing.',
|
|
2050
2304
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2051
2305
|
confidence: 0.7,
|
|
2052
2306
|
},
|
|
2053
2307
|
|
|
2054
|
-
|
|
2055
|
-
id:
|
|
2056
|
-
name: '
|
|
2057
|
-
check: (ctx) => {
|
|
2308
|
+
goSecurityScanner: {
|
|
2309
|
+
id: 120114,
|
|
2310
|
+
name: 'Go security scanner configured',
|
|
2311
|
+
check: (ctx) => {
|
|
2312
|
+
if (!isGoProject(ctx)) return null;
|
|
2313
|
+
return /gosec|staticcheck/i.test(getGoProjectText(ctx));
|
|
2314
|
+
},
|
|
2058
2315
|
impact: 'medium',
|
|
2059
2316
|
category: 'go',
|
|
2060
|
-
fix: '
|
|
2317
|
+
fix: 'Configure `gosec` or `staticcheck` in CI or the Makefile for Go security and static analysis checks.',
|
|
2061
2318
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2062
2319
|
confidence: 0.7,
|
|
2063
2320
|
},
|
|
2064
2321
|
|
|
2065
|
-
|
|
2066
|
-
id:
|
|
2067
|
-
name: '
|
|
2068
|
-
check: (ctx) => {
|
|
2069
|
-
|
|
2322
|
+
goCIConfigured: {
|
|
2323
|
+
id: 120115,
|
|
2324
|
+
name: 'CI runs Go tests',
|
|
2325
|
+
check: (ctx) => {
|
|
2326
|
+
if (!isGoProject(ctx)) return null;
|
|
2327
|
+
return /go test(\s|$)|go test \.\/\.\.\./i.test(getWorkflowContent(ctx));
|
|
2328
|
+
},
|
|
2329
|
+
impact: 'high',
|
|
2070
2330
|
category: 'go',
|
|
2071
|
-
fix: '
|
|
2331
|
+
fix: 'Run `go test ./...` in CI so Go packages are verified on every change.',
|
|
2072
2332
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2073
2333
|
confidence: 0.7,
|
|
2074
2334
|
},
|
|
2075
2335
|
|
|
2076
|
-
|
|
2077
|
-
id:
|
|
2078
|
-
name: '
|
|
2079
|
-
check: (ctx) => {
|
|
2336
|
+
goContainerized: {
|
|
2337
|
+
id: 120116,
|
|
2338
|
+
name: 'Go Dockerfile uses multi-stage build',
|
|
2339
|
+
check: (ctx) => {
|
|
2340
|
+
if (!isGoProject(ctx)) return null;
|
|
2341
|
+
const dockerfile = ctx.fileContent('Dockerfile') || '';
|
|
2342
|
+
if (!dockerfile) return null;
|
|
2343
|
+
return /FROM\s+golang[:\d.-].*\bAS\b/i.test(dockerfile) &&
|
|
2344
|
+
/FROM\s+(alpine|scratch|distroless|gcr\.io|cgr\.dev)/i.test(dockerfile);
|
|
2345
|
+
},
|
|
2080
2346
|
impact: 'medium',
|
|
2081
2347
|
category: 'go',
|
|
2082
|
-
fix: '
|
|
2348
|
+
fix: 'Use a multi-stage Go Dockerfile: compile in a `golang` image and run from a minimal final image.',
|
|
2083
2349
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2084
2350
|
confidence: 0.7,
|
|
2085
2351
|
},
|
|
2086
2352
|
|
|
2087
|
-
|
|
2088
|
-
id:
|
|
2089
|
-
name: '
|
|
2090
|
-
check: (ctx) => {
|
|
2091
|
-
|
|
2353
|
+
goCoverageConfigured: {
|
|
2354
|
+
id: 120117,
|
|
2355
|
+
name: 'Go coverage reporting configured',
|
|
2356
|
+
check: (ctx) => {
|
|
2357
|
+
if (!isGoProject(ctx)) return null;
|
|
2358
|
+
const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
|
|
2359
|
+
return /go test[^\n]*-cover/i.test(content);
|
|
2360
|
+
},
|
|
2361
|
+
impact: 'medium',
|
|
2092
2362
|
category: 'go',
|
|
2093
|
-
fix: '
|
|
2363
|
+
fix: 'Add `go test -cover` to CI or developer commands so Go coverage is tracked explicitly.',
|
|
2094
2364
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2095
2365
|
confidence: 0.7,
|
|
2096
2366
|
},
|
|
2097
2367
|
|
|
2098
|
-
|
|
2099
|
-
id:
|
|
2100
|
-
name: '
|
|
2101
|
-
check: (ctx) => {
|
|
2368
|
+
goAPIFramework: {
|
|
2369
|
+
id: 120118,
|
|
2370
|
+
name: 'Go HTTP framework detected',
|
|
2371
|
+
check: (ctx) => {
|
|
2372
|
+
if (!isGoProject(ctx)) return null;
|
|
2373
|
+
return /gin-gonic\/gin|labstack\/echo|gofiber\/fiber|go-chi\/chi|gin\.Default\(|echo\.New\(|fiber\.New\(|chi\.NewRouter\(/i.test(getGoProjectText(ctx));
|
|
2374
|
+
},
|
|
2102
2375
|
impact: 'low',
|
|
2103
2376
|
category: 'go',
|
|
2104
|
-
fix: '
|
|
2377
|
+
fix: 'Use a well-supported Go HTTP framework such as Gin, Echo, Fiber, or Chi when building API services.',
|
|
2105
2378
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2106
2379
|
confidence: 0.7,
|
|
2107
2380
|
},
|
|
2108
2381
|
|
|
2109
|
-
|
|
2110
|
-
id:
|
|
2111
|
-
name: 'Go
|
|
2112
|
-
check: (ctx) => {
|
|
2382
|
+
goMigrationTool: {
|
|
2383
|
+
id: 120119,
|
|
2384
|
+
name: 'Go database migration tooling present',
|
|
2385
|
+
check: (ctx) => {
|
|
2386
|
+
if (!isGoProject(ctx)) return null;
|
|
2387
|
+
return /golang-migrate|pressly\/goose|atlasgo|atlas\s/i.test(getGoProjectText(ctx)) ||
|
|
2388
|
+
hasProjectFile(ctx, /(^|\/)(migrations|db\/migrations)\//i);
|
|
2389
|
+
},
|
|
2390
|
+
impact: 'medium',
|
|
2391
|
+
category: 'go',
|
|
2392
|
+
fix: 'Add a Go migration tool such as golang-migrate, goose, or Atlas and keep migration files in the repo.',
|
|
2393
|
+
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2394
|
+
confidence: 0.7,
|
|
2395
|
+
},
|
|
2396
|
+
|
|
2397
|
+
goDependencyInjection: {
|
|
2398
|
+
id: 120120,
|
|
2399
|
+
name: 'Go dependency injection pattern present',
|
|
2400
|
+
check: (ctx) => {
|
|
2401
|
+
if (!isGoProject(ctx)) return null;
|
|
2402
|
+
return /google\/wire|uber-go\/fx|uber-go\/dig|wire\.Build\(|fx\.New\(|dig\.New\(/i.test(getGoProjectText(ctx));
|
|
2403
|
+
},
|
|
2113
2404
|
impact: 'low',
|
|
2114
2405
|
category: 'go',
|
|
2115
|
-
fix: '
|
|
2406
|
+
fix: 'Use Wire, Fx, Dig, or an equivalent composition pattern when Go dependency graphs become complex.',
|
|
2116
2407
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2117
2408
|
confidence: 0.7,
|
|
2118
2409
|
},
|
|
@@ -2120,222 +2411,300 @@ const TECHNIQUES = {
|
|
|
2120
2411
|
// === RUST STACK CHECKS (category: 'rust') ===================
|
|
2121
2412
|
// ============================================================
|
|
2122
2413
|
|
|
2123
|
-
|
|
2124
|
-
id:
|
|
2125
|
-
name: 'Cargo.toml exists
|
|
2126
|
-
check: (ctx) => {
|
|
2414
|
+
cargoTomlExists: {
|
|
2415
|
+
id: 120201,
|
|
2416
|
+
name: 'Cargo.toml exists',
|
|
2417
|
+
check: (ctx) => {
|
|
2418
|
+
if (!isRustProject(ctx)) return null;
|
|
2419
|
+
return true;
|
|
2420
|
+
},
|
|
2127
2421
|
impact: 'high',
|
|
2128
2422
|
category: 'rust',
|
|
2129
|
-
fix: '
|
|
2423
|
+
fix: 'Add a `Cargo.toml` manifest so Rust dependencies, metadata, and build settings are tracked explicitly.',
|
|
2130
2424
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2131
2425
|
confidence: 0.7,
|
|
2132
2426
|
},
|
|
2133
2427
|
|
|
2134
|
-
|
|
2135
|
-
id:
|
|
2136
|
-
name: '
|
|
2137
|
-
check: (ctx) => {
|
|
2428
|
+
rustEdition: {
|
|
2429
|
+
id: 120202,
|
|
2430
|
+
name: 'Rust edition specified in Cargo.toml',
|
|
2431
|
+
check: (ctx) => {
|
|
2432
|
+
if (!isRustProject(ctx)) return null;
|
|
2433
|
+
return /edition\s*=\s*"20(18|21|24)"/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2434
|
+
},
|
|
2138
2435
|
impact: 'high',
|
|
2139
2436
|
category: 'rust',
|
|
2140
|
-
fix: '
|
|
2437
|
+
fix: 'Specify a Rust edition such as `edition = "2021"` in `Cargo.toml` so tooling and language semantics are pinned.',
|
|
2141
2438
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2142
2439
|
confidence: 0.7,
|
|
2143
2440
|
},
|
|
2144
2441
|
|
|
2145
|
-
|
|
2146
|
-
id:
|
|
2147
|
-
name: 'Clippy configured
|
|
2148
|
-
check: (ctx) => {
|
|
2442
|
+
rustClippy: {
|
|
2443
|
+
id: 120203,
|
|
2444
|
+
name: 'Clippy configured',
|
|
2445
|
+
check: (ctx) => {
|
|
2446
|
+
if (!isRustProject(ctx)) return null;
|
|
2447
|
+
return hasProjectFile(ctx, /(^|\/)(clippy\.toml|\.clippy\.toml)$/i) ||
|
|
2448
|
+
/clippy/i.test(`${readProjectFiles(ctx, /(^|\/)\.cargo\/config\.toml$/i)}\n${getWorkflowContent(ctx)}\n${getPreCommitContent(ctx)}`);
|
|
2449
|
+
},
|
|
2149
2450
|
impact: 'medium',
|
|
2150
2451
|
category: 'rust',
|
|
2151
|
-
fix: 'Configure clippy in CI or
|
|
2452
|
+
fix: 'Configure `cargo clippy` in CI, pre-commit, or `.cargo/config.toml` so linting is enforced consistently.',
|
|
2152
2453
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2153
2454
|
confidence: 0.7,
|
|
2154
2455
|
},
|
|
2155
2456
|
|
|
2156
|
-
|
|
2157
|
-
id:
|
|
2158
|
-
name: 'rustfmt configured
|
|
2159
|
-
check: (ctx) => {
|
|
2457
|
+
rustFmt: {
|
|
2458
|
+
id: 120204,
|
|
2459
|
+
name: 'rustfmt configured',
|
|
2460
|
+
check: (ctx) => {
|
|
2461
|
+
if (!isRustProject(ctx)) return null;
|
|
2462
|
+
return hasProjectFile(ctx, /(^|\/)(rustfmt\.toml|\.rustfmt\.toml)$/i);
|
|
2463
|
+
},
|
|
2160
2464
|
impact: 'medium',
|
|
2161
2465
|
category: 'rust',
|
|
2162
|
-
fix: '
|
|
2466
|
+
fix: 'Add `rustfmt.toml` or `.rustfmt.toml` to capture Rust formatting expectations in version control.',
|
|
2163
2467
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2164
2468
|
confidence: 0.7,
|
|
2165
2469
|
},
|
|
2166
2470
|
|
|
2167
|
-
|
|
2168
|
-
id:
|
|
2169
|
-
name: '
|
|
2170
|
-
check: (ctx) => {
|
|
2471
|
+
rustTestsExist: {
|
|
2472
|
+
id: 120205,
|
|
2473
|
+
name: 'Rust tests exist',
|
|
2474
|
+
check: (ctx) => {
|
|
2475
|
+
if (!isRustProject(ctx)) return null;
|
|
2476
|
+
const files = getRustFiles(ctx);
|
|
2477
|
+
if (files.length === 0) return null;
|
|
2478
|
+
return hasProjectFile(ctx, /(^|\/)tests\//i) ||
|
|
2479
|
+
files.some(file => /#\s*\[\s*test\s*\]/.test(ctx.fileContent(file) || ''));
|
|
2480
|
+
},
|
|
2171
2481
|
impact: 'high',
|
|
2172
2482
|
category: 'rust',
|
|
2173
|
-
fix: '
|
|
2483
|
+
fix: 'Add Rust unit or integration tests using `#[test]` functions or a `tests/` directory.',
|
|
2174
2484
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2175
2485
|
confidence: 0.7,
|
|
2176
2486
|
},
|
|
2177
2487
|
|
|
2178
|
-
|
|
2179
|
-
id:
|
|
2180
|
-
name: '
|
|
2181
|
-
check: (ctx) => {
|
|
2182
|
-
|
|
2488
|
+
rustBenchmarks: {
|
|
2489
|
+
id: 120206,
|
|
2490
|
+
name: 'Rust benchmarks present',
|
|
2491
|
+
check: (ctx) => {
|
|
2492
|
+
if (!isRustProject(ctx)) return null;
|
|
2493
|
+
return hasProjectFile(ctx, /(^|\/)benches\//i) ||
|
|
2494
|
+
/#\s*\[\s*bench\s*\]|criterion/i.test(getRustProjectText(ctx));
|
|
2495
|
+
},
|
|
2496
|
+
impact: 'low',
|
|
2183
2497
|
category: 'rust',
|
|
2184
|
-
fix: '
|
|
2498
|
+
fix: 'Add Rust benchmarks through `benches/`, `criterion`, or benchmark annotations when performance-sensitive code matters.',
|
|
2185
2499
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2186
2500
|
confidence: 0.7,
|
|
2187
2501
|
},
|
|
2188
2502
|
|
|
2189
|
-
|
|
2190
|
-
id:
|
|
2191
|
-
name: '
|
|
2192
|
-
check: (ctx) => {
|
|
2503
|
+
rustCIConfigured: {
|
|
2504
|
+
id: 120207,
|
|
2505
|
+
name: 'CI runs cargo test',
|
|
2506
|
+
check: (ctx) => {
|
|
2507
|
+
if (!isRustProject(ctx)) return null;
|
|
2508
|
+
return /cargo test(\s|$)/i.test(getWorkflowContent(ctx));
|
|
2509
|
+
},
|
|
2193
2510
|
impact: 'high',
|
|
2194
2511
|
category: 'rust',
|
|
2195
|
-
fix: '
|
|
2512
|
+
fix: 'Run `cargo test` in CI so Rust correctness is verified automatically on every change.',
|
|
2196
2513
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2197
2514
|
confidence: 0.7,
|
|
2198
2515
|
},
|
|
2199
2516
|
|
|
2200
|
-
|
|
2201
|
-
id:
|
|
2202
|
-
name: '
|
|
2203
|
-
check: (ctx) => {
|
|
2517
|
+
rustCargoLock: {
|
|
2518
|
+
id: 120208,
|
|
2519
|
+
name: 'Cargo.lock handling is appropriate',
|
|
2520
|
+
check: (ctx) => {
|
|
2521
|
+
if (!isRustProject(ctx)) return null;
|
|
2522
|
+
const cargoText = readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i);
|
|
2523
|
+
const hasLock = hasProjectFile(ctx, /(^|\/)Cargo\.lock$/i);
|
|
2524
|
+
const gitignore = ctx.fileContent('.gitignore') || '';
|
|
2525
|
+
const libraryOnly = /\[lib\]/i.test(cargoText) && !/\[\[bin\]\]|src\/main\.rs/i.test(getRustProjectText(ctx));
|
|
2526
|
+
if (libraryOnly) return hasLock || /(^|\r?\n)\s*Cargo\.lock\s*$/m.test(gitignore);
|
|
2527
|
+
return hasLock;
|
|
2528
|
+
},
|
|
2204
2529
|
impact: 'medium',
|
|
2205
2530
|
category: 'rust',
|
|
2206
|
-
fix: '
|
|
2531
|
+
fix: 'Commit `Cargo.lock` for binaries, or explicitly ignore it for library-only crates when that is your chosen policy.',
|
|
2207
2532
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2208
2533
|
confidence: 0.7,
|
|
2209
2534
|
},
|
|
2210
2535
|
|
|
2211
|
-
|
|
2212
|
-
id:
|
|
2213
|
-
name: '
|
|
2214
|
-
check: (ctx) => {
|
|
2536
|
+
rustUnsafeBlocks: {
|
|
2537
|
+
id: 120209,
|
|
2538
|
+
name: 'Unsafe blocks are documented',
|
|
2539
|
+
check: (ctx) => {
|
|
2540
|
+
if (!isRustProject(ctx)) return null;
|
|
2541
|
+
const files = getRustFiles(ctx);
|
|
2542
|
+
const unsafeFiles = files.filter(file => /\bunsafe\b/.test(ctx.fileContent(file) || ''));
|
|
2543
|
+
if (unsafeFiles.length === 0) return true;
|
|
2544
|
+
return unsafeFiles.every(file => /SAFETY:|\/\/\s*SAFETY|\/\*\s*SAFETY/i.test(ctx.fileContent(file) || ''));
|
|
2545
|
+
},
|
|
2215
2546
|
impact: 'medium',
|
|
2216
2547
|
category: 'rust',
|
|
2217
|
-
fix: 'Document
|
|
2548
|
+
fix: 'Document each `unsafe` block with a nearby `SAFETY:` comment explaining the invariants being upheld.',
|
|
2218
2549
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2219
2550
|
confidence: 0.7,
|
|
2220
2551
|
},
|
|
2221
2552
|
|
|
2222
|
-
|
|
2223
|
-
id:
|
|
2224
|
-
name: '
|
|
2225
|
-
check: (ctx) => {
|
|
2553
|
+
rustErrorHandling: {
|
|
2554
|
+
id: 120210,
|
|
2555
|
+
name: 'Rust error handling strategy present',
|
|
2556
|
+
check: (ctx) => {
|
|
2557
|
+
if (!isRustProject(ctx)) return null;
|
|
2558
|
+
return /thiserror|anyhow|eyre|impl\s+std::error::Error|enum\s+\w+Error|struct\s+\w+Error/i.test(getRustProjectText(ctx));
|
|
2559
|
+
},
|
|
2226
2560
|
impact: 'medium',
|
|
2227
2561
|
category: 'rust',
|
|
2228
|
-
fix: '
|
|
2562
|
+
fix: 'Use `thiserror`, `anyhow`, or explicit error types so Rust errors remain structured and descriptive.',
|
|
2229
2563
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2230
2564
|
confidence: 0.7,
|
|
2231
2565
|
},
|
|
2232
2566
|
|
|
2233
|
-
|
|
2234
|
-
id:
|
|
2235
|
-
name: '
|
|
2236
|
-
check: (ctx) => {
|
|
2567
|
+
rustAsync: {
|
|
2568
|
+
id: 120211,
|
|
2569
|
+
name: 'Rust async runtime configured',
|
|
2570
|
+
check: (ctx) => {
|
|
2571
|
+
if (!isRustProject(ctx)) return null;
|
|
2572
|
+
return /tokio|async-std|smol/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2573
|
+
},
|
|
2237
2574
|
impact: 'medium',
|
|
2238
2575
|
category: 'rust',
|
|
2239
|
-
fix: '
|
|
2576
|
+
fix: 'Declare an async runtime such as Tokio or async-std when the Rust project uses asynchronous workflows.',
|
|
2240
2577
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2241
2578
|
confidence: 0.7,
|
|
2242
2579
|
},
|
|
2243
2580
|
|
|
2244
|
-
|
|
2245
|
-
id:
|
|
2246
|
-
name: '
|
|
2247
|
-
check: (ctx) => {
|
|
2581
|
+
rustSerde: {
|
|
2582
|
+
id: 120212,
|
|
2583
|
+
name: 'Serde serialization configured',
|
|
2584
|
+
check: (ctx) => {
|
|
2585
|
+
if (!isRustProject(ctx)) return null;
|
|
2586
|
+
return /\bserde(_json|_yaml)?\b/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2587
|
+
},
|
|
2248
2588
|
impact: 'low',
|
|
2249
2589
|
category: 'rust',
|
|
2250
|
-
fix: '
|
|
2590
|
+
fix: 'Add `serde` and related crates when Rust data crosses process, storage, or network boundaries.',
|
|
2251
2591
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2252
2592
|
confidence: 0.7,
|
|
2253
2593
|
},
|
|
2254
2594
|
|
|
2255
|
-
|
|
2256
|
-
id:
|
|
2257
|
-
name: '
|
|
2258
|
-
check: (ctx) => {
|
|
2595
|
+
rustDocComments: {
|
|
2596
|
+
id: 120213,
|
|
2597
|
+
name: 'Public Rust items have doc comments',
|
|
2598
|
+
check: (ctx) => {
|
|
2599
|
+
if (!isRustProject(ctx)) return null;
|
|
2600
|
+
const files = getMainRustFiles(ctx);
|
|
2601
|
+
const exported = files.some(file => /\bpub\s+(fn|struct|enum|trait|mod|const|type)\b/.test(ctx.fileContent(file) || ''));
|
|
2602
|
+
if (!exported) return null;
|
|
2603
|
+
return files.some(file => /\/\/\/[^\n]*\n\s*pub\s+(fn|struct|enum|trait|mod|const|type)\b/.test(ctx.fileContent(file) || ''));
|
|
2604
|
+
},
|
|
2259
2605
|
impact: 'low',
|
|
2260
2606
|
category: 'rust',
|
|
2261
|
-
fix: '
|
|
2607
|
+
fix: 'Add `///` doc comments above public Rust APIs so crates are easier to consume and maintain.',
|
|
2262
2608
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2263
2609
|
confidence: 0.7,
|
|
2264
2610
|
},
|
|
2265
2611
|
|
|
2266
|
-
|
|
2267
|
-
id:
|
|
2268
|
-
name: '
|
|
2269
|
-
check: (ctx) => {
|
|
2270
|
-
|
|
2612
|
+
rustSecurityAudit: {
|
|
2613
|
+
id: 120214,
|
|
2614
|
+
name: 'Rust security audit tooling configured',
|
|
2615
|
+
check: (ctx) => {
|
|
2616
|
+
if (!isRustProject(ctx)) return null;
|
|
2617
|
+
return /cargo-audit|cargo deny|cargo-deny/i.test(getRustProjectText(ctx));
|
|
2618
|
+
},
|
|
2619
|
+
impact: 'medium',
|
|
2271
2620
|
category: 'rust',
|
|
2272
|
-
fix: '
|
|
2621
|
+
fix: 'Configure `cargo-audit` or `cargo-deny` in CI or project automation to scan Rust dependencies for risk.',
|
|
2273
2622
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2274
2623
|
confidence: 0.7,
|
|
2275
2624
|
},
|
|
2276
2625
|
|
|
2277
|
-
|
|
2278
|
-
id:
|
|
2279
|
-
name: '
|
|
2280
|
-
check: (ctx) => {
|
|
2626
|
+
rustMSRV: {
|
|
2627
|
+
id: 120215,
|
|
2628
|
+
name: 'Minimum supported Rust version specified',
|
|
2629
|
+
check: (ctx) => {
|
|
2630
|
+
if (!isRustProject(ctx)) return null;
|
|
2631
|
+
return /rust-version\s*=/.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2632
|
+
},
|
|
2281
2633
|
impact: 'medium',
|
|
2282
2634
|
category: 'rust',
|
|
2283
|
-
fix: '
|
|
2635
|
+
fix: 'Set `rust-version` in `Cargo.toml` so the project’s MSRV is explicit for contributors and CI.',
|
|
2284
2636
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2285
2637
|
confidence: 0.7,
|
|
2286
2638
|
},
|
|
2287
2639
|
|
|
2288
|
-
|
|
2289
|
-
id:
|
|
2290
|
-
name: '
|
|
2291
|
-
check: (ctx) => {
|
|
2640
|
+
rustWorkspace: {
|
|
2641
|
+
id: 120216,
|
|
2642
|
+
name: 'Cargo workspace configured for multi-crate projects',
|
|
2643
|
+
check: (ctx) => {
|
|
2644
|
+
if (!isRustProject(ctx)) return null;
|
|
2645
|
+
const cargoFiles = findProjectFiles(ctx, /(^|\/)Cargo\.toml$/i);
|
|
2646
|
+
if (cargoFiles.length <= 1) return null;
|
|
2647
|
+
return /\[workspace\]/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2648
|
+
},
|
|
2292
2649
|
impact: 'medium',
|
|
2293
2650
|
category: 'rust',
|
|
2294
|
-
fix: '
|
|
2651
|
+
fix: 'Add a root Cargo workspace when the Rust repository contains multiple crates.',
|
|
2295
2652
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2296
2653
|
confidence: 0.7,
|
|
2297
2654
|
},
|
|
2298
2655
|
|
|
2299
|
-
|
|
2300
|
-
id:
|
|
2301
|
-
name: '
|
|
2302
|
-
check: (ctx) => {
|
|
2303
|
-
|
|
2656
|
+
rustBuildScript: {
|
|
2657
|
+
id: 120217,
|
|
2658
|
+
name: 'Rust build script present when needed',
|
|
2659
|
+
check: (ctx) => {
|
|
2660
|
+
if (!isRustProject(ctx)) return null;
|
|
2661
|
+
return hasProjectFile(ctx, /(^|\/)build\.rs$/i);
|
|
2662
|
+
},
|
|
2663
|
+
impact: 'low',
|
|
2304
2664
|
category: 'rust',
|
|
2305
|
-
fix: '
|
|
2665
|
+
fix: 'Use `build.rs` when the project needs generated bindings, codegen, or compile-time environment setup.',
|
|
2306
2666
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2307
2667
|
confidence: 0.7,
|
|
2308
2668
|
},
|
|
2309
2669
|
|
|
2310
|
-
|
|
2311
|
-
id:
|
|
2312
|
-
name: '
|
|
2313
|
-
check: (ctx) => {
|
|
2314
|
-
|
|
2670
|
+
rustFeatureFlags: {
|
|
2671
|
+
id: 120218,
|
|
2672
|
+
name: 'Rust feature flags defined',
|
|
2673
|
+
check: (ctx) => {
|
|
2674
|
+
if (!isRustProject(ctx)) return null;
|
|
2675
|
+
return /\[features\]/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2676
|
+
},
|
|
2677
|
+
impact: 'low',
|
|
2315
2678
|
category: 'rust',
|
|
2316
|
-
fix: '
|
|
2679
|
+
fix: 'Define Cargo feature flags when Rust functionality needs optional capabilities or slimmed dependency sets.',
|
|
2317
2680
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2318
2681
|
confidence: 0.7,
|
|
2319
2682
|
},
|
|
2320
2683
|
|
|
2321
|
-
|
|
2322
|
-
id:
|
|
2323
|
-
name: '
|
|
2324
|
-
check: (ctx) => {
|
|
2684
|
+
rustCrossCompilation: {
|
|
2685
|
+
id: 120219,
|
|
2686
|
+
name: 'Rust cross-compilation targets configured',
|
|
2687
|
+
check: (ctx) => {
|
|
2688
|
+
if (!isRustProject(ctx)) return null;
|
|
2689
|
+
return /--target|rustup target add|target\.[\w.-]+|cross build|cross test|cargo zigbuild/i.test(getRustProjectText(ctx));
|
|
2690
|
+
},
|
|
2325
2691
|
impact: 'low',
|
|
2326
2692
|
category: 'rust',
|
|
2327
|
-
fix: '
|
|
2693
|
+
fix: 'Configure Rust cross-compilation targets in CI or `.cargo/config.toml` when builds must run across architectures or platforms.',
|
|
2328
2694
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2329
2695
|
confidence: 0.7,
|
|
2330
2696
|
},
|
|
2331
2697
|
|
|
2332
|
-
|
|
2333
|
-
id:
|
|
2334
|
-
name: 'Rust
|
|
2335
|
-
check: (ctx) => {
|
|
2336
|
-
|
|
2698
|
+
rustContainerized: {
|
|
2699
|
+
id: 120220,
|
|
2700
|
+
name: 'Rust Dockerfile present',
|
|
2701
|
+
check: (ctx) => {
|
|
2702
|
+
if (!isRustProject(ctx)) return null;
|
|
2703
|
+
return /FROM\s+rust|cargo\s+(build|chef|install|test)/i.test(ctx.fileContent('Dockerfile') || '');
|
|
2704
|
+
},
|
|
2705
|
+
impact: 'low',
|
|
2337
2706
|
category: 'rust',
|
|
2338
|
-
fix: '
|
|
2707
|
+
fix: 'Use a Dockerfile that references Rust or Cargo when the project’s build and release flow is containerized.',
|
|
2339
2708
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2340
2709
|
confidence: 0.7,
|
|
2341
2710
|
},
|
|
@@ -2344,222 +2713,294 @@ const TECHNIQUES = {
|
|
|
2344
2713
|
// === JAVA/SPRING STACK CHECKS (category: 'java') ============
|
|
2345
2714
|
// ============================================================
|
|
2346
2715
|
|
|
2347
|
-
|
|
2348
|
-
id:
|
|
2349
|
-
name: '
|
|
2350
|
-
check: (ctx) => {
|
|
2716
|
+
mavenOrGradle: {
|
|
2717
|
+
id: 120301,
|
|
2718
|
+
name: 'Maven or Gradle build file exists',
|
|
2719
|
+
check: (ctx) => {
|
|
2720
|
+
if (!isJavaProject(ctx)) return null;
|
|
2721
|
+
return true;
|
|
2722
|
+
},
|
|
2351
2723
|
impact: 'high',
|
|
2352
2724
|
category: 'java',
|
|
2353
|
-
fix: '
|
|
2725
|
+
fix: 'Add `pom.xml`, `build.gradle`, or `build.gradle.kts` so the Java build is defined in version control.',
|
|
2354
2726
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2355
2727
|
confidence: 0.7,
|
|
2356
2728
|
},
|
|
2357
2729
|
|
|
2358
|
-
|
|
2359
|
-
id:
|
|
2730
|
+
javaVersion: {
|
|
2731
|
+
id: 120302,
|
|
2360
2732
|
name: 'Java version specified',
|
|
2361
|
-
check: (ctx) => {
|
|
2733
|
+
check: (ctx) => {
|
|
2734
|
+
if (!isJavaProject(ctx)) return null;
|
|
2735
|
+
return /java\.version|maven\.compiler\.(source|target|release)|sourceCompatibility|targetCompatibility|JavaLanguageVersion|toolchain/i.test(getJavaBuildText(ctx)) ||
|
|
2736
|
+
hasProjectFile(ctx, /(^|\/)\.java-version$/i);
|
|
2737
|
+
},
|
|
2362
2738
|
impact: 'high',
|
|
2363
2739
|
category: 'java',
|
|
2364
|
-
fix: 'Specify Java version in
|
|
2740
|
+
fix: 'Specify the Java version in Maven or Gradle so compilation and runtime expectations stay explicit.',
|
|
2365
2741
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2366
2742
|
confidence: 0.7,
|
|
2367
2743
|
},
|
|
2368
2744
|
|
|
2369
|
-
|
|
2370
|
-
id:
|
|
2371
|
-
name: '
|
|
2372
|
-
check: (ctx) => {
|
|
2373
|
-
|
|
2745
|
+
springBootDetected: {
|
|
2746
|
+
id: 120303,
|
|
2747
|
+
name: 'Spring Boot detected',
|
|
2748
|
+
check: (ctx) => {
|
|
2749
|
+
if (!isJavaProject(ctx)) return null;
|
|
2750
|
+
return /spring-boot/i.test(getJavaBuildText(ctx));
|
|
2751
|
+
},
|
|
2752
|
+
impact: 'medium',
|
|
2374
2753
|
category: 'java',
|
|
2375
|
-
fix: '
|
|
2754
|
+
fix: 'Use Spring Boot dependencies when the Java service relies on Spring auto-configuration and conventions.',
|
|
2376
2755
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2377
2756
|
confidence: 0.7,
|
|
2378
2757
|
},
|
|
2379
2758
|
|
|
2380
|
-
|
|
2381
|
-
id:
|
|
2382
|
-
name: '
|
|
2383
|
-
check: (ctx) => {
|
|
2759
|
+
javaTestFramework: {
|
|
2760
|
+
id: 120304,
|
|
2761
|
+
name: 'Java test framework configured',
|
|
2762
|
+
check: (ctx) => {
|
|
2763
|
+
if (!isJavaProject(ctx)) return null;
|
|
2764
|
+
return /junit|testng|spring-boot-starter-test/i.test(getJavaBuildText(ctx));
|
|
2765
|
+
},
|
|
2384
2766
|
impact: 'high',
|
|
2385
2767
|
category: 'java',
|
|
2386
|
-
fix: '
|
|
2768
|
+
fix: 'Add JUnit, TestNG, or Spring Boot test dependencies so Java tests have a standard runner.',
|
|
2387
2769
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2388
2770
|
confidence: 0.7,
|
|
2389
2771
|
},
|
|
2390
2772
|
|
|
2391
|
-
|
|
2392
|
-
id:
|
|
2393
|
-
name: '
|
|
2394
|
-
check: (ctx) => {
|
|
2773
|
+
javaLinter: {
|
|
2774
|
+
id: 120305,
|
|
2775
|
+
name: 'Java linter configured',
|
|
2776
|
+
check: (ctx) => {
|
|
2777
|
+
if (!isJavaProject(ctx)) return null;
|
|
2778
|
+
return /checkstyle|spotbugs|pmd/i.test(getJavaProjectText(ctx)) ||
|
|
2779
|
+
hasProjectFile(ctx, /(^|\/)(checkstyle\.xml|spotbugs.*\.xml|pmd\.xml)$/i);
|
|
2780
|
+
},
|
|
2395
2781
|
impact: 'medium',
|
|
2396
2782
|
category: 'java',
|
|
2397
|
-
fix: '
|
|
2783
|
+
fix: 'Configure Checkstyle, SpotBugs, or PMD so Java code quality rules run consistently.',
|
|
2398
2784
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2399
2785
|
confidence: 0.7,
|
|
2400
2786
|
},
|
|
2401
2787
|
|
|
2402
|
-
|
|
2403
|
-
id:
|
|
2404
|
-
name: '
|
|
2405
|
-
check: (ctx) => {
|
|
2406
|
-
|
|
2788
|
+
javaFormatter: {
|
|
2789
|
+
id: 120306,
|
|
2790
|
+
name: 'Java formatter configured',
|
|
2791
|
+
check: (ctx) => {
|
|
2792
|
+
if (!isJavaProject(ctx)) return null;
|
|
2793
|
+
return /google-java-format|spotless/i.test(getJavaBuildText(ctx)) ||
|
|
2794
|
+
hasProjectFile(ctx, /(^|\/)\.editorconfig$/i);
|
|
2795
|
+
},
|
|
2796
|
+
impact: 'medium',
|
|
2407
2797
|
category: 'java',
|
|
2408
|
-
fix: 'Configure
|
|
2798
|
+
fix: 'Configure Spotless, google-java-format, or an `.editorconfig` so Java formatting stays consistent.',
|
|
2409
2799
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2410
2800
|
confidence: 0.7,
|
|
2411
2801
|
},
|
|
2412
2802
|
|
|
2413
|
-
|
|
2414
|
-
id:
|
|
2415
|
-
name: '
|
|
2416
|
-
check: (ctx) => {
|
|
2417
|
-
|
|
2803
|
+
javaCIConfigured: {
|
|
2804
|
+
id: 120307,
|
|
2805
|
+
name: 'CI runs Java tests',
|
|
2806
|
+
check: (ctx) => {
|
|
2807
|
+
if (!isJavaProject(ctx)) return null;
|
|
2808
|
+
return /(?:mvn|mvnw)\s+test|(?:gradle|gradlew)\s+test/i.test(getWorkflowContent(ctx));
|
|
2809
|
+
},
|
|
2810
|
+
impact: 'high',
|
|
2418
2811
|
category: 'java',
|
|
2419
|
-
fix: '
|
|
2812
|
+
fix: 'Run `mvn test`, `mvnw test`, `gradle test`, or `gradlew test` in CI so Java changes are validated automatically.',
|
|
2420
2813
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2421
2814
|
confidence: 0.7,
|
|
2422
2815
|
},
|
|
2423
2816
|
|
|
2424
|
-
|
|
2425
|
-
id:
|
|
2426
|
-
name: '
|
|
2427
|
-
check: (ctx) => {
|
|
2817
|
+
javaSecurityScanner: {
|
|
2818
|
+
id: 120308,
|
|
2819
|
+
name: 'Java security scanner configured',
|
|
2820
|
+
check: (ctx) => {
|
|
2821
|
+
if (!isJavaProject(ctx)) return null;
|
|
2822
|
+
return /dependency-check|snyk|spotbugs-security|findsecbugs/i.test(getJavaProjectText(ctx));
|
|
2823
|
+
},
|
|
2428
2824
|
impact: 'medium',
|
|
2429
2825
|
category: 'java',
|
|
2430
|
-
fix: '
|
|
2826
|
+
fix: 'Configure OWASP Dependency-Check, Snyk, or SpotBugs security rules for Java dependency and code scanning.',
|
|
2431
2827
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2432
2828
|
confidence: 0.7,
|
|
2433
2829
|
},
|
|
2434
2830
|
|
|
2435
|
-
|
|
2436
|
-
id:
|
|
2437
|
-
name: '
|
|
2438
|
-
check: (ctx) => {
|
|
2439
|
-
|
|
2831
|
+
javaDocumentation: {
|
|
2832
|
+
id: 120309,
|
|
2833
|
+
name: 'Java documentation configured',
|
|
2834
|
+
check: (ctx) => {
|
|
2835
|
+
if (!isJavaProject(ctx)) return null;
|
|
2836
|
+
if (/javadoc/i.test(getJavaBuildText(ctx))) return true;
|
|
2837
|
+
const files = getMainJavaFiles(ctx);
|
|
2838
|
+
const publicTypes = files.some(file => /\bpublic\s+(class|interface|enum|record)\s+[A-Z]\w*/.test(ctx.fileContent(file) || ''));
|
|
2839
|
+
if (!publicTypes) return null;
|
|
2840
|
+
return files.some(file => /\/\*\*[\s\S]*?\*\/\s*public\s+(class|interface|enum|record)\s+[A-Z]\w*/.test(ctx.fileContent(file) || ''));
|
|
2841
|
+
},
|
|
2842
|
+
impact: 'low',
|
|
2440
2843
|
category: 'java',
|
|
2441
|
-
fix: '
|
|
2844
|
+
fix: 'Generate Javadocs or add doc comments on public Java types so the API remains understandable to contributors.',
|
|
2442
2845
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2443
2846
|
confidence: 0.7,
|
|
2444
2847
|
},
|
|
2445
2848
|
|
|
2446
|
-
|
|
2447
|
-
id:
|
|
2448
|
-
name: '
|
|
2449
|
-
check: (ctx) => {
|
|
2849
|
+
javaProfiles: {
|
|
2850
|
+
id: 120310,
|
|
2851
|
+
name: 'Java profiles or build variants configured',
|
|
2852
|
+
check: (ctx) => {
|
|
2853
|
+
if (!isJavaProject(ctx)) return null;
|
|
2854
|
+
return /<profiles>|spring\.profiles|@Profile|profiles\s*\{|buildTypes\s*\{|productFlavors\s*\{/i.test(getJavaProjectText(ctx));
|
|
2855
|
+
},
|
|
2450
2856
|
impact: 'low',
|
|
2451
2857
|
category: 'java',
|
|
2452
|
-
fix: '
|
|
2858
|
+
fix: 'Use Maven profiles, Spring profiles, or Gradle build variants when Java environments need explicit separation.',
|
|
2453
2859
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2454
2860
|
confidence: 0.7,
|
|
2455
2861
|
},
|
|
2456
2862
|
|
|
2457
|
-
|
|
2458
|
-
id:
|
|
2459
|
-
name: '
|
|
2460
|
-
check: (ctx) => {
|
|
2461
|
-
|
|
2863
|
+
javaContainerized: {
|
|
2864
|
+
id: 120311,
|
|
2865
|
+
name: 'Java Dockerfile references Java build/runtime',
|
|
2866
|
+
check: (ctx) => {
|
|
2867
|
+
if (!isJavaProject(ctx)) return null;
|
|
2868
|
+
return /FROM\s+(?:maven|gradle|openjdk|eclipse-temurin|amazoncorretto)|\bjava\b|\bmvn\b|\bgradle\b/i.test(ctx.fileContent('Dockerfile') || '');
|
|
2869
|
+
},
|
|
2870
|
+
impact: 'low',
|
|
2462
2871
|
category: 'java',
|
|
2463
|
-
fix: '
|
|
2872
|
+
fix: 'Use a Dockerfile or build image that references Java, Maven, or Gradle when the application is containerized.',
|
|
2464
2873
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2465
2874
|
confidence: 0.7,
|
|
2466
2875
|
},
|
|
2467
2876
|
|
|
2468
|
-
|
|
2469
|
-
id:
|
|
2470
|
-
name: '
|
|
2471
|
-
check: (ctx) => {
|
|
2472
|
-
|
|
2877
|
+
javaAPIFramework: {
|
|
2878
|
+
id: 120312,
|
|
2879
|
+
name: 'Java API framework detected',
|
|
2880
|
+
check: (ctx) => {
|
|
2881
|
+
if (!isJavaProject(ctx)) return null;
|
|
2882
|
+
return /spring-web|spring-boot-starter-web|@RestController|@Controller|javax\.ws\.rs|jakarta\.ws\.rs|micronaut-http|io\.micronaut/i.test(getJavaProjectText(ctx));
|
|
2883
|
+
},
|
|
2884
|
+
impact: 'low',
|
|
2473
2885
|
category: 'java',
|
|
2474
|
-
fix: '
|
|
2886
|
+
fix: 'Use Spring MVC, JAX-RS, or Micronaut conventions explicitly when the Java project exposes an API.',
|
|
2475
2887
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2476
2888
|
confidence: 0.7,
|
|
2477
2889
|
},
|
|
2478
2890
|
|
|
2479
|
-
|
|
2480
|
-
id:
|
|
2481
|
-
name: '
|
|
2482
|
-
check: (ctx) => {
|
|
2891
|
+
javaMigrations: {
|
|
2892
|
+
id: 120313,
|
|
2893
|
+
name: 'Java database migration tooling present',
|
|
2894
|
+
check: (ctx) => {
|
|
2895
|
+
if (!isJavaProject(ctx)) return null;
|
|
2896
|
+
return /flyway|liquibase/i.test(getJavaProjectText(ctx)) ||
|
|
2897
|
+
hasProjectFile(ctx, /(^|\/)(db\/migration|db\/migrations|migrations)\//i) ||
|
|
2898
|
+
hasProjectFile(ctx, /(^|\/)(schema|data)\.sql$/i);
|
|
2899
|
+
},
|
|
2483
2900
|
impact: 'medium',
|
|
2484
2901
|
category: 'java',
|
|
2485
|
-
fix: '
|
|
2902
|
+
fix: 'Add Flyway, Liquibase, or repo-managed migration files so Java schema changes are repeatable and reviewable.',
|
|
2486
2903
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2487
2904
|
confidence: 0.7,
|
|
2488
2905
|
},
|
|
2489
2906
|
|
|
2490
|
-
|
|
2491
|
-
id:
|
|
2492
|
-
name: '
|
|
2493
|
-
check: (ctx) => {
|
|
2494
|
-
|
|
2907
|
+
javaMessageQueue: {
|
|
2908
|
+
id: 120314,
|
|
2909
|
+
name: 'Java message queue integration detected',
|
|
2910
|
+
check: (ctx) => {
|
|
2911
|
+
if (!isJavaProject(ctx)) return null;
|
|
2912
|
+
return /kafka|rabbitmq|amqp|jms|spring-kafka|spring-rabbit/i.test(getJavaProjectText(ctx));
|
|
2913
|
+
},
|
|
2914
|
+
impact: 'low',
|
|
2495
2915
|
category: 'java',
|
|
2496
|
-
fix: '
|
|
2916
|
+
fix: 'Use explicit Kafka, RabbitMQ, or JMS integrations when the Java service relies on asynchronous messaging.',
|
|
2497
2917
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2498
2918
|
confidence: 0.7,
|
|
2499
2919
|
},
|
|
2500
2920
|
|
|
2501
|
-
|
|
2502
|
-
id:
|
|
2503
|
-
name: '
|
|
2504
|
-
check: (ctx) => {
|
|
2505
|
-
|
|
2921
|
+
javaCaching: {
|
|
2922
|
+
id: 120315,
|
|
2923
|
+
name: 'Java caching configured',
|
|
2924
|
+
check: (ctx) => {
|
|
2925
|
+
if (!isJavaProject(ctx)) return null;
|
|
2926
|
+
return /redis|ehcache|spring-cache|@Cacheable|caffeine/i.test(getJavaProjectText(ctx));
|
|
2927
|
+
},
|
|
2928
|
+
impact: 'low',
|
|
2506
2929
|
category: 'java',
|
|
2507
|
-
fix: 'Configure
|
|
2930
|
+
fix: 'Configure Redis, Ehcache, Caffeine, or Spring Cache when Java services benefit from explicit caching layers.',
|
|
2508
2931
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2509
2932
|
confidence: 0.7,
|
|
2510
2933
|
},
|
|
2511
2934
|
|
|
2512
|
-
|
|
2513
|
-
id:
|
|
2514
|
-
name: '
|
|
2515
|
-
check: (ctx) => {
|
|
2935
|
+
javaMonitoring: {
|
|
2936
|
+
id: 120316,
|
|
2937
|
+
name: 'Java monitoring dependencies detected',
|
|
2938
|
+
check: (ctx) => {
|
|
2939
|
+
if (!isJavaProject(ctx)) return null;
|
|
2940
|
+
return /actuator|micrometer|prometheus/i.test(getJavaProjectText(ctx));
|
|
2941
|
+
},
|
|
2516
2942
|
impact: 'medium',
|
|
2517
2943
|
category: 'java',
|
|
2518
|
-
fix: '
|
|
2944
|
+
fix: 'Add Actuator, Micrometer, or Prometheus integrations so Java services expose health and metrics data.',
|
|
2519
2945
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2520
2946
|
confidence: 0.7,
|
|
2521
2947
|
},
|
|
2522
2948
|
|
|
2523
|
-
|
|
2524
|
-
id:
|
|
2525
|
-
name: '
|
|
2526
|
-
check: (ctx) => {
|
|
2949
|
+
javaLogging: {
|
|
2950
|
+
id: 120317,
|
|
2951
|
+
name: 'Java logging configured',
|
|
2952
|
+
check: (ctx) => {
|
|
2953
|
+
if (!isJavaProject(ctx)) return null;
|
|
2954
|
+
return /slf4j|logback|log4j/i.test(getJavaProjectText(ctx)) ||
|
|
2955
|
+
hasProjectFile(ctx, /(^|\/)(logback.*\.xml|log4j2?.*\.xml)$/i);
|
|
2956
|
+
},
|
|
2527
2957
|
impact: 'medium',
|
|
2528
2958
|
category: 'java',
|
|
2529
|
-
fix: '
|
|
2959
|
+
fix: 'Use SLF4J, Logback, or Log4j so Java application logging is explicit and configurable.',
|
|
2530
2960
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2531
2961
|
confidence: 0.7,
|
|
2532
2962
|
},
|
|
2533
2963
|
|
|
2534
|
-
|
|
2535
|
-
id:
|
|
2536
|
-
name: '
|
|
2537
|
-
check: (ctx) => {
|
|
2538
|
-
|
|
2964
|
+
javaMultiModule: {
|
|
2965
|
+
id: 120318,
|
|
2966
|
+
name: 'Java multi-module structure configured',
|
|
2967
|
+
check: (ctx) => {
|
|
2968
|
+
if (!isJavaProject(ctx)) return null;
|
|
2969
|
+
const buildFiles = findProjectFiles(ctx, /(^|\/)(pom\.xml|build\.gradle|build\.gradle\.kts)$/i);
|
|
2970
|
+
if (buildFiles.length <= 1) return null;
|
|
2971
|
+
return /<modules>|include\s*\(|include\s+['":]/i.test(getJavaBuildText(ctx));
|
|
2972
|
+
},
|
|
2973
|
+
impact: 'medium',
|
|
2539
2974
|
category: 'java',
|
|
2540
|
-
fix: '
|
|
2975
|
+
fix: 'Configure a root Maven or Gradle multi-module definition when the Java repository contains multiple modules.',
|
|
2541
2976
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2542
2977
|
confidence: 0.7,
|
|
2543
2978
|
},
|
|
2544
2979
|
|
|
2545
|
-
|
|
2546
|
-
id:
|
|
2547
|
-
name: '
|
|
2548
|
-
check: (ctx) => {
|
|
2980
|
+
javaDependencyInjection: {
|
|
2981
|
+
id: 120319,
|
|
2982
|
+
name: 'Java dependency injection pattern present',
|
|
2983
|
+
check: (ctx) => {
|
|
2984
|
+
if (!isJavaProject(ctx)) return null;
|
|
2985
|
+
return /spring-context|guice|dagger|@Autowired|@Inject|@Bean|@Component|@Service/i.test(getJavaProjectText(ctx));
|
|
2986
|
+
},
|
|
2549
2987
|
impact: 'medium',
|
|
2550
2988
|
category: 'java',
|
|
2551
|
-
fix: '
|
|
2989
|
+
fix: 'Use Spring DI, Guice, or Dagger patterns so Java object graphs stay explicit and testable.',
|
|
2552
2990
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2553
2991
|
confidence: 0.7,
|
|
2554
2992
|
},
|
|
2555
2993
|
|
|
2556
|
-
|
|
2557
|
-
id:
|
|
2558
|
-
name: '
|
|
2559
|
-
check: (ctx) => {
|
|
2560
|
-
|
|
2994
|
+
javaPropertyFiles: {
|
|
2995
|
+
id: 120320,
|
|
2996
|
+
name: 'Java application property files exist',
|
|
2997
|
+
check: (ctx) => {
|
|
2998
|
+
if (!isJavaProject(ctx)) return null;
|
|
2999
|
+
return hasProjectFile(ctx, /(^|\/)application\.(properties|ya?ml)$/i);
|
|
3000
|
+
},
|
|
3001
|
+
impact: 'low',
|
|
2561
3002
|
category: 'java',
|
|
2562
|
-
fix: '
|
|
3003
|
+
fix: 'Add `application.properties` or `application.yml` when the Java service relies on conventional runtime configuration files.',
|
|
2563
3004
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2564
3005
|
confidence: 0.7,
|
|
2565
3006
|
},
|
|
@@ -3181,6 +3622,499 @@ const TECHNIQUES = {
|
|
|
3181
3622
|
},
|
|
3182
3623
|
|
|
3183
3624
|
|
|
3625
|
+
// === FLUTTER STACK CHECKS (category: 'flutter') ===============
|
|
3626
|
+
// ============================================================
|
|
3627
|
+
|
|
3628
|
+
pubspecExists: {
|
|
3629
|
+
id: 120401,
|
|
3630
|
+
name: 'pubspec.yaml exists',
|
|
3631
|
+
check: (ctx) => {
|
|
3632
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3633
|
+
return true;
|
|
3634
|
+
},
|
|
3635
|
+
impact: 'high',
|
|
3636
|
+
category: 'flutter',
|
|
3637
|
+
fix: 'Add a `pubspec.yaml` manifest so Flutter/Dart dependencies and project metadata are tracked.',
|
|
3638
|
+
confidence: 0.7,
|
|
3639
|
+
},
|
|
3640
|
+
|
|
3641
|
+
flutterAnalysis: {
|
|
3642
|
+
id: 120402,
|
|
3643
|
+
name: 'Flutter analysis options configured',
|
|
3644
|
+
check: (ctx) => {
|
|
3645
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3646
|
+
return ctx.files.some(f => /analysis_options\.yaml$/i.test(f));
|
|
3647
|
+
},
|
|
3648
|
+
impact: 'medium',
|
|
3649
|
+
category: 'flutter',
|
|
3650
|
+
fix: 'Add analysis_options.yaml for Dart/Flutter linting rules.',
|
|
3651
|
+
confidence: 0.8,
|
|
3652
|
+
},
|
|
3653
|
+
|
|
3654
|
+
flutterTestDir: {
|
|
3655
|
+
id: 120403,
|
|
3656
|
+
name: 'Flutter tests exist',
|
|
3657
|
+
check: (ctx) => {
|
|
3658
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3659
|
+
return hasProjectFile(ctx, /(^|\/)test\/.*_test\.dart$/i);
|
|
3660
|
+
},
|
|
3661
|
+
impact: 'high',
|
|
3662
|
+
category: 'flutter',
|
|
3663
|
+
fix: 'Add Flutter widget or unit tests in a `test/` directory with `_test.dart` suffix.',
|
|
3664
|
+
confidence: 0.8,
|
|
3665
|
+
},
|
|
3666
|
+
|
|
3667
|
+
flutterLintRules: {
|
|
3668
|
+
id: 120404,
|
|
3669
|
+
name: 'Flutter lint package configured',
|
|
3670
|
+
check: (ctx) => {
|
|
3671
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3672
|
+
const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
3673
|
+
return /flutter_lints|very_good_analysis/i.test(pubspec);
|
|
3674
|
+
},
|
|
3675
|
+
impact: 'medium',
|
|
3676
|
+
category: 'flutter',
|
|
3677
|
+
fix: 'Add `flutter_lints` or `very_good_analysis` to pubspec.yaml dev_dependencies for consistent linting.',
|
|
3678
|
+
confidence: 0.8,
|
|
3679
|
+
},
|
|
3680
|
+
|
|
3681
|
+
flutterPlatformDirs: {
|
|
3682
|
+
id: 120405,
|
|
3683
|
+
name: 'Flutter platform directories present',
|
|
3684
|
+
check: (ctx) => {
|
|
3685
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3686
|
+
return hasProjectFile(ctx, /^android\//i) && hasProjectFile(ctx, /^ios\//i);
|
|
3687
|
+
},
|
|
3688
|
+
impact: 'medium',
|
|
3689
|
+
category: 'flutter',
|
|
3690
|
+
fix: 'Run `flutter create .` to generate `android/` and `ios/` platform directories.',
|
|
3691
|
+
confidence: 0.7,
|
|
3692
|
+
},
|
|
3693
|
+
|
|
3694
|
+
flutterWebSupport: {
|
|
3695
|
+
id: 120406,
|
|
3696
|
+
name: 'Flutter web support enabled',
|
|
3697
|
+
check: (ctx) => {
|
|
3698
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3699
|
+
return hasProjectFile(ctx, /^web\//i);
|
|
3700
|
+
},
|
|
3701
|
+
impact: 'low',
|
|
3702
|
+
category: 'flutter',
|
|
3703
|
+
fix: 'Run `flutter create --platforms=web .` to add web support.',
|
|
3704
|
+
confidence: 0.7,
|
|
3705
|
+
},
|
|
3706
|
+
|
|
3707
|
+
flutterL10n: {
|
|
3708
|
+
id: 120407,
|
|
3709
|
+
name: 'Flutter localization configured',
|
|
3710
|
+
check: (ctx) => {
|
|
3711
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3712
|
+
const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
3713
|
+
return hasProjectFile(ctx, /(^|\/)l10n\.yaml$/i) || /\bintl\b/i.test(pubspec);
|
|
3714
|
+
},
|
|
3715
|
+
impact: 'medium',
|
|
3716
|
+
category: 'flutter',
|
|
3717
|
+
fix: 'Add `l10n.yaml` or the `intl` package to support localization and internationalization.',
|
|
3718
|
+
confidence: 0.7,
|
|
3719
|
+
},
|
|
3720
|
+
|
|
3721
|
+
flutterStateManagement: {
|
|
3722
|
+
id: 120408,
|
|
3723
|
+
name: 'Flutter state management configured',
|
|
3724
|
+
check: (ctx) => {
|
|
3725
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3726
|
+
const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
3727
|
+
return /riverpod|flutter_bloc|\bbloc\b|\bprovider\b/i.test(pubspec);
|
|
3728
|
+
},
|
|
3729
|
+
impact: 'medium',
|
|
3730
|
+
category: 'flutter',
|
|
3731
|
+
fix: 'Add a state management solution such as `riverpod`, `bloc`, or `provider` to pubspec.yaml.',
|
|
3732
|
+
confidence: 0.7,
|
|
3733
|
+
},
|
|
3734
|
+
|
|
3735
|
+
flutterNavigation: {
|
|
3736
|
+
id: 120409,
|
|
3737
|
+
name: 'Flutter routing configured',
|
|
3738
|
+
check: (ctx) => {
|
|
3739
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3740
|
+
const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
3741
|
+
return /go_router|auto_route/i.test(pubspec);
|
|
3742
|
+
},
|
|
3743
|
+
impact: 'medium',
|
|
3744
|
+
category: 'flutter',
|
|
3745
|
+
fix: 'Add `go_router` or `auto_route` for declarative, type-safe Flutter routing.',
|
|
3746
|
+
confidence: 0.7,
|
|
3747
|
+
},
|
|
3748
|
+
|
|
3749
|
+
flutterCIConfigured: {
|
|
3750
|
+
id: 120410,
|
|
3751
|
+
name: 'CI runs flutter test',
|
|
3752
|
+
check: (ctx) => {
|
|
3753
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3754
|
+
return /flutter test(\s|$)/i.test(getWorkflowContent(ctx));
|
|
3755
|
+
},
|
|
3756
|
+
impact: 'high',
|
|
3757
|
+
category: 'flutter',
|
|
3758
|
+
fix: 'Add `flutter test` to your CI workflow so tests run automatically on every change.',
|
|
3759
|
+
confidence: 0.8,
|
|
3760
|
+
},
|
|
3761
|
+
|
|
3762
|
+
flutterCodeGen: {
|
|
3763
|
+
id: 120411,
|
|
3764
|
+
name: 'Flutter code generation configured',
|
|
3765
|
+
check: (ctx) => {
|
|
3766
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3767
|
+
const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
3768
|
+
return /build_runner|freezed/i.test(pubspec);
|
|
3769
|
+
},
|
|
3770
|
+
impact: 'medium',
|
|
3771
|
+
category: 'flutter',
|
|
3772
|
+
fix: 'Add `build_runner` and/or `freezed` to pubspec.yaml for code generation support.',
|
|
3773
|
+
confidence: 0.7,
|
|
3774
|
+
},
|
|
3775
|
+
|
|
3776
|
+
flutterFirebase: {
|
|
3777
|
+
id: 120412,
|
|
3778
|
+
name: 'Flutter Firebase integration',
|
|
3779
|
+
check: (ctx) => {
|
|
3780
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3781
|
+
const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
3782
|
+
return /firebase/i.test(pubspec) || hasProjectFile(ctx, /(^|\/)firebase_options\.dart$/i);
|
|
3783
|
+
},
|
|
3784
|
+
impact: 'medium',
|
|
3785
|
+
category: 'flutter',
|
|
3786
|
+
fix: 'Add Firebase packages to pubspec.yaml and run `flutterfire configure` to generate firebase_options.dart.',
|
|
3787
|
+
confidence: 0.7,
|
|
3788
|
+
},
|
|
3789
|
+
|
|
3790
|
+
flutterAssets: {
|
|
3791
|
+
id: 120413,
|
|
3792
|
+
name: 'Flutter assets configured',
|
|
3793
|
+
check: (ctx) => {
|
|
3794
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3795
|
+
const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
3796
|
+
return /\bassets\s*:/i.test(pubspec);
|
|
3797
|
+
},
|
|
3798
|
+
impact: 'low',
|
|
3799
|
+
category: 'flutter',
|
|
3800
|
+
fix: 'Add an `assets:` section in pubspec.yaml to declare images, fonts, and other bundled resources.',
|
|
3801
|
+
confidence: 0.7,
|
|
3802
|
+
},
|
|
3803
|
+
|
|
3804
|
+
flutterFlavors: {
|
|
3805
|
+
id: 120414,
|
|
3806
|
+
name: 'Flutter flavors configured',
|
|
3807
|
+
check: (ctx) => {
|
|
3808
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3809
|
+
const pubspec = readProjectFiles(ctx, /(^|\/)pubspec\.yaml$/i);
|
|
3810
|
+
return /\bflavors?\b/i.test(pubspec) || /--flavor/i.test(getWorkflowContent(ctx));
|
|
3811
|
+
},
|
|
3812
|
+
impact: 'low',
|
|
3813
|
+
category: 'flutter',
|
|
3814
|
+
fix: 'Configure Flutter flavors for environment-specific builds (dev, staging, production).',
|
|
3815
|
+
confidence: 0.7,
|
|
3816
|
+
},
|
|
3817
|
+
|
|
3818
|
+
flutterContainerized: {
|
|
3819
|
+
id: 120415,
|
|
3820
|
+
name: 'Flutter Dockerfile present',
|
|
3821
|
+
check: (ctx) => {
|
|
3822
|
+
if (!isFlutterProject(ctx)) return null;
|
|
3823
|
+
const dockerfiles = readProjectFiles(ctx, /(^|\/)Dockerfile/i);
|
|
3824
|
+
return /flutter|dart/i.test(dockerfiles);
|
|
3825
|
+
},
|
|
3826
|
+
impact: 'low',
|
|
3827
|
+
category: 'flutter',
|
|
3828
|
+
fix: 'Add a Dockerfile that includes the Flutter or Dart SDK for containerized builds.',
|
|
3829
|
+
confidence: 0.7,
|
|
3830
|
+
},
|
|
3831
|
+
|
|
3832
|
+
// === SWIFT STACK CHECKS (category: 'swift') ==================
|
|
3833
|
+
// ============================================================
|
|
3834
|
+
|
|
3835
|
+
swiftPackageExists: {
|
|
3836
|
+
id: 120501,
|
|
3837
|
+
name: 'Swift package or Xcode project exists',
|
|
3838
|
+
check: (ctx) => {
|
|
3839
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3840
|
+
return true;
|
|
3841
|
+
},
|
|
3842
|
+
impact: 'high',
|
|
3843
|
+
category: 'swift',
|
|
3844
|
+
fix: 'Add a `Package.swift` or `.xcodeproj` to define your Swift project structure.',
|
|
3845
|
+
confidence: 0.7,
|
|
3846
|
+
},
|
|
3847
|
+
|
|
3848
|
+
swiftLinter: {
|
|
3849
|
+
id: 120502,
|
|
3850
|
+
name: 'SwiftLint configured',
|
|
3851
|
+
check: (ctx) => {
|
|
3852
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3853
|
+
return hasProjectFile(ctx, /(^|\/)(\.swiftlint\.yml|\.swiftlint\.yaml)$/i);
|
|
3854
|
+
},
|
|
3855
|
+
impact: 'medium',
|
|
3856
|
+
category: 'swift',
|
|
3857
|
+
fix: 'Add `.swiftlint.yml` to enforce Swift coding conventions.',
|
|
3858
|
+
confidence: 0.8,
|
|
3859
|
+
},
|
|
3860
|
+
|
|
3861
|
+
swiftTests: {
|
|
3862
|
+
id: 120503,
|
|
3863
|
+
name: 'Swift tests exist',
|
|
3864
|
+
check: (ctx) => {
|
|
3865
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3866
|
+
return hasProjectFile(ctx, /(^|\/)Tests\//i) ||
|
|
3867
|
+
findProjectFiles(ctx, /\.swift$/i).some(f => /XCTest/i.test(ctx.fileContent(f) || ''));
|
|
3868
|
+
},
|
|
3869
|
+
impact: 'high',
|
|
3870
|
+
category: 'swift',
|
|
3871
|
+
fix: 'Add Swift tests in a `Tests/` directory using XCTest.',
|
|
3872
|
+
confidence: 0.8,
|
|
3873
|
+
},
|
|
3874
|
+
|
|
3875
|
+
swiftFormatter: {
|
|
3876
|
+
id: 120504,
|
|
3877
|
+
name: 'SwiftFormat configured',
|
|
3878
|
+
check: (ctx) => {
|
|
3879
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3880
|
+
return hasProjectFile(ctx, /(^|\/)\.swiftformat$/i);
|
|
3881
|
+
},
|
|
3882
|
+
impact: 'medium',
|
|
3883
|
+
category: 'swift',
|
|
3884
|
+
fix: 'Add `.swiftformat` to enforce consistent Swift formatting.',
|
|
3885
|
+
confidence: 0.7,
|
|
3886
|
+
},
|
|
3887
|
+
|
|
3888
|
+
swiftCIConfigured: {
|
|
3889
|
+
id: 120505,
|
|
3890
|
+
name: 'CI runs swift test or xcodebuild',
|
|
3891
|
+
check: (ctx) => {
|
|
3892
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3893
|
+
return /swift test|xcodebuild(\s|$)/i.test(getWorkflowContent(ctx));
|
|
3894
|
+
},
|
|
3895
|
+
impact: 'high',
|
|
3896
|
+
category: 'swift',
|
|
3897
|
+
fix: 'Add `swift test` or `xcodebuild test` to your CI workflow.',
|
|
3898
|
+
confidence: 0.8,
|
|
3899
|
+
},
|
|
3900
|
+
|
|
3901
|
+
swiftDocComments: {
|
|
3902
|
+
id: 120506,
|
|
3903
|
+
name: 'Swift doc comments present',
|
|
3904
|
+
check: (ctx) => {
|
|
3905
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3906
|
+
const swiftFiles = findProjectFiles(ctx, /\.swift$/i);
|
|
3907
|
+
if (swiftFiles.length === 0) return null;
|
|
3908
|
+
return swiftFiles.some(f => /\/\/\//.test(ctx.fileContent(f) || ''));
|
|
3909
|
+
},
|
|
3910
|
+
impact: 'low',
|
|
3911
|
+
category: 'swift',
|
|
3912
|
+
fix: 'Add `///` documentation comments to public Swift APIs.',
|
|
3913
|
+
confidence: 0.7,
|
|
3914
|
+
},
|
|
3915
|
+
|
|
3916
|
+
swiftSPM: {
|
|
3917
|
+
id: 120507,
|
|
3918
|
+
name: 'Swift Package Manager used',
|
|
3919
|
+
check: (ctx) => {
|
|
3920
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3921
|
+
return hasProjectFile(ctx, /(^|\/)Package\.swift$/i);
|
|
3922
|
+
},
|
|
3923
|
+
impact: 'medium',
|
|
3924
|
+
category: 'swift',
|
|
3925
|
+
fix: 'Add `Package.swift` to use Swift Package Manager for dependency management.',
|
|
3926
|
+
confidence: 0.7,
|
|
3927
|
+
},
|
|
3928
|
+
|
|
3929
|
+
swiftMinVersion: {
|
|
3930
|
+
id: 120508,
|
|
3931
|
+
name: 'Swift tools version specified',
|
|
3932
|
+
check: (ctx) => {
|
|
3933
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3934
|
+
const pkg = readProjectFiles(ctx, /(^|\/)Package\.swift$/i);
|
|
3935
|
+
return /swift-tools-version/i.test(pkg);
|
|
3936
|
+
},
|
|
3937
|
+
impact: 'medium',
|
|
3938
|
+
category: 'swift',
|
|
3939
|
+
fix: 'Add `// swift-tools-version:5.9` (or appropriate version) at the top of Package.swift.',
|
|
3940
|
+
confidence: 0.8,
|
|
3941
|
+
},
|
|
3942
|
+
|
|
3943
|
+
swiftAccessControl: {
|
|
3944
|
+
id: 120509,
|
|
3945
|
+
name: 'Swift access control used',
|
|
3946
|
+
check: (ctx) => {
|
|
3947
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3948
|
+
const swiftFiles = findProjectFiles(ctx, /\.swift$/i);
|
|
3949
|
+
if (swiftFiles.length === 0) return null;
|
|
3950
|
+
return swiftFiles.some(f => /\b(public|internal)\b/.test(ctx.fileContent(f) || ''));
|
|
3951
|
+
},
|
|
3952
|
+
impact: 'low',
|
|
3953
|
+
category: 'swift',
|
|
3954
|
+
fix: 'Use `public`/`internal` access control in Swift files to define clear API boundaries.',
|
|
3955
|
+
confidence: 0.7,
|
|
3956
|
+
},
|
|
3957
|
+
|
|
3958
|
+
swiftConcurrency: {
|
|
3959
|
+
id: 120510,
|
|
3960
|
+
name: 'Swift concurrency used',
|
|
3961
|
+
check: (ctx) => {
|
|
3962
|
+
if (!isSwiftProject(ctx)) return null;
|
|
3963
|
+
const swiftFiles = findProjectFiles(ctx, /\.swift$/i);
|
|
3964
|
+
if (swiftFiles.length === 0) return null;
|
|
3965
|
+
return swiftFiles.some(f => /\basync\b.*\bawait\b|\bawait\b/s.test(ctx.fileContent(f) || ''));
|
|
3966
|
+
},
|
|
3967
|
+
impact: 'low',
|
|
3968
|
+
category: 'swift',
|
|
3969
|
+
fix: 'Adopt Swift structured concurrency with `async`/`await` for modern asynchronous code.',
|
|
3970
|
+
confidence: 0.7,
|
|
3971
|
+
},
|
|
3972
|
+
|
|
3973
|
+
// === KOTLIN STACK CHECKS (category: 'kotlin') ================
|
|
3974
|
+
// ============================================================
|
|
3975
|
+
|
|
3976
|
+
kotlinGradlePlugin: {
|
|
3977
|
+
id: 120601,
|
|
3978
|
+
name: 'Kotlin Gradle plugin configured',
|
|
3979
|
+
check: (ctx) => {
|
|
3980
|
+
if (!isKotlinProject(ctx)) return null;
|
|
3981
|
+
const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
|
|
3982
|
+
return /kotlin\(|org\.jetbrains\.kotlin/i.test(gradle);
|
|
3983
|
+
},
|
|
3984
|
+
impact: 'high',
|
|
3985
|
+
category: 'kotlin',
|
|
3986
|
+
fix: 'Apply the Kotlin Gradle plugin in build.gradle.kts to enable Kotlin compilation.',
|
|
3987
|
+
confidence: 0.8,
|
|
3988
|
+
},
|
|
3989
|
+
|
|
3990
|
+
kotlinVersion: {
|
|
3991
|
+
id: 120602,
|
|
3992
|
+
name: 'Kotlin version specified',
|
|
3993
|
+
check: (ctx) => {
|
|
3994
|
+
if (!isKotlinProject(ctx)) return null;
|
|
3995
|
+
const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle|gradle\.properties)$/i);
|
|
3996
|
+
return /kotlinVersion|kotlin_version|org\.jetbrains\.kotlin.*\d+\.\d+/i.test(gradle);
|
|
3997
|
+
},
|
|
3998
|
+
impact: 'high',
|
|
3999
|
+
category: 'kotlin',
|
|
4000
|
+
fix: 'Pin the Kotlin version in gradle.properties or build.gradle.kts for reproducible builds.',
|
|
4001
|
+
confidence: 0.8,
|
|
4002
|
+
},
|
|
4003
|
+
|
|
4004
|
+
kotlinLinter: {
|
|
4005
|
+
id: 120603,
|
|
4006
|
+
name: 'Kotlin linter configured',
|
|
4007
|
+
check: (ctx) => {
|
|
4008
|
+
if (!isKotlinProject(ctx)) return null;
|
|
4009
|
+
const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
|
|
4010
|
+
return /ktlint|detekt/i.test(gradle) ||
|
|
4011
|
+
hasProjectFile(ctx, /(^|\/)(\.editorconfig|detekt\.yml|detekt\.yaml)$/i);
|
|
4012
|
+
},
|
|
4013
|
+
impact: 'medium',
|
|
4014
|
+
category: 'kotlin',
|
|
4015
|
+
fix: 'Add `ktlint` or `detekt` to enforce Kotlin code style and static analysis.',
|
|
4016
|
+
confidence: 0.8,
|
|
4017
|
+
},
|
|
4018
|
+
|
|
4019
|
+
kotlinTests: {
|
|
4020
|
+
id: 120604,
|
|
4021
|
+
name: 'Kotlin tests exist',
|
|
4022
|
+
check: (ctx) => {
|
|
4023
|
+
if (!isKotlinProject(ctx)) return null;
|
|
4024
|
+
return hasProjectFile(ctx, /(^|\/)src\/test\/.*\.kt$/i) ||
|
|
4025
|
+
hasProjectFile(ctx, /(^|\/)test\/.*\.kt$/i);
|
|
4026
|
+
},
|
|
4027
|
+
impact: 'high',
|
|
4028
|
+
category: 'kotlin',
|
|
4029
|
+
fix: 'Add Kotlin tests in `src/test/` using JUnit or KotlinTest.',
|
|
4030
|
+
confidence: 0.8,
|
|
4031
|
+
},
|
|
4032
|
+
|
|
4033
|
+
kotlinCoroutines: {
|
|
4034
|
+
id: 120605,
|
|
4035
|
+
name: 'Kotlin Coroutines in dependencies',
|
|
4036
|
+
check: (ctx) => {
|
|
4037
|
+
if (!isKotlinProject(ctx)) return null;
|
|
4038
|
+
const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
|
|
4039
|
+
return /kotlinx[.-]coroutines/i.test(gradle);
|
|
4040
|
+
},
|
|
4041
|
+
impact: 'medium',
|
|
4042
|
+
category: 'kotlin',
|
|
4043
|
+
fix: 'Add `kotlinx-coroutines-core` to dependencies for structured concurrency.',
|
|
4044
|
+
confidence: 0.7,
|
|
4045
|
+
},
|
|
4046
|
+
|
|
4047
|
+
kotlinSerialization: {
|
|
4048
|
+
id: 120606,
|
|
4049
|
+
name: 'Kotlin serialization configured',
|
|
4050
|
+
check: (ctx) => {
|
|
4051
|
+
if (!isKotlinProject(ctx)) return null;
|
|
4052
|
+
const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
|
|
4053
|
+
return /kotlinx[.-]serialization/i.test(gradle);
|
|
4054
|
+
},
|
|
4055
|
+
impact: 'medium',
|
|
4056
|
+
category: 'kotlin',
|
|
4057
|
+
fix: 'Add `kotlinx.serialization` for type-safe, multiplatform serialization.',
|
|
4058
|
+
confidence: 0.7,
|
|
4059
|
+
},
|
|
4060
|
+
|
|
4061
|
+
kotlinCompose: {
|
|
4062
|
+
id: 120607,
|
|
4063
|
+
name: 'Jetpack Compose configured',
|
|
4064
|
+
check: (ctx) => {
|
|
4065
|
+
if (!isKotlinProject(ctx)) return null;
|
|
4066
|
+
const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
|
|
4067
|
+
return /compose/i.test(gradle);
|
|
4068
|
+
},
|
|
4069
|
+
impact: 'medium',
|
|
4070
|
+
category: 'kotlin',
|
|
4071
|
+
fix: 'Enable Jetpack Compose in build.gradle.kts for modern declarative Android UI.',
|
|
4072
|
+
confidence: 0.7,
|
|
4073
|
+
},
|
|
4074
|
+
|
|
4075
|
+
kotlinCIConfigured: {
|
|
4076
|
+
id: 120608,
|
|
4077
|
+
name: 'CI runs Kotlin tests',
|
|
4078
|
+
check: (ctx) => {
|
|
4079
|
+
if (!isKotlinProject(ctx)) return null;
|
|
4080
|
+
return /gradle.*test|gradlew.*test/i.test(getWorkflowContent(ctx));
|
|
4081
|
+
},
|
|
4082
|
+
impact: 'high',
|
|
4083
|
+
category: 'kotlin',
|
|
4084
|
+
fix: 'Add `./gradlew test` to your CI workflow so Kotlin tests run automatically.',
|
|
4085
|
+
confidence: 0.8,
|
|
4086
|
+
},
|
|
4087
|
+
|
|
4088
|
+
kotlinMultiplatform: {
|
|
4089
|
+
id: 120609,
|
|
4090
|
+
name: 'Kotlin Multiplatform configured',
|
|
4091
|
+
check: (ctx) => {
|
|
4092
|
+
if (!isKotlinProject(ctx)) return null;
|
|
4093
|
+
const gradle = readProjectFiles(ctx, /(^|\/)(build\.gradle\.kts|build\.gradle)$/i);
|
|
4094
|
+
return /multiplatform/i.test(gradle);
|
|
4095
|
+
},
|
|
4096
|
+
impact: 'medium',
|
|
4097
|
+
category: 'kotlin',
|
|
4098
|
+
fix: 'Apply the `kotlin-multiplatform` Gradle plugin to share code across JVM, iOS, JS, and Native targets.',
|
|
4099
|
+
confidence: 0.7,
|
|
4100
|
+
},
|
|
4101
|
+
|
|
4102
|
+
kotlinDocComments: {
|
|
4103
|
+
id: 120610,
|
|
4104
|
+
name: 'KDoc comments present',
|
|
4105
|
+
check: (ctx) => {
|
|
4106
|
+
if (!isKotlinProject(ctx)) return null;
|
|
4107
|
+
const ktFiles = findProjectFiles(ctx, /\.kt$/i);
|
|
4108
|
+
if (ktFiles.length === 0) return null;
|
|
4109
|
+
return ktFiles.some(f => /\/\*\*/.test(ctx.fileContent(f) || ''));
|
|
4110
|
+
},
|
|
4111
|
+
impact: 'low',
|
|
4112
|
+
category: 'kotlin',
|
|
4113
|
+
fix: 'Add KDoc comments (`/** ... */`) to public Kotlin APIs for documentation generation.',
|
|
4114
|
+
confidence: 0.7,
|
|
4115
|
+
},
|
|
4116
|
+
|
|
4117
|
+
|
|
3184
4118
|
};
|
|
3185
4119
|
|
|
3186
4120
|
Object.assign(TECHNIQUES, buildSupplementalChecks({
|