@nerviq/cli 1.2.6 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +27 -2
- package/package.json +1 -1
- package/src/harmony/add.js +68 -0
- package/src/harmony/cli.js +1 -0
- package/src/harmony/watch.js +35 -1
- package/src/techniques.js +880 -459
package/src/techniques.js
CHANGED
|
@@ -117,6 +117,18 @@ 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
|
+
|
|
120
132
|
function getPythonFiles(ctx) {
|
|
121
133
|
if (ctx.__nerviqPythonFiles) return ctx.__nerviqPythonFiles;
|
|
122
134
|
ctx.__nerviqPythonFiles = findProjectFiles(ctx, /\.py$/i);
|
|
@@ -150,6 +162,34 @@ function getGoFiles(ctx) {
|
|
|
150
162
|
return ctx.__nerviqGoFiles;
|
|
151
163
|
}
|
|
152
164
|
|
|
165
|
+
function getRustFiles(ctx) {
|
|
166
|
+
if (ctx.__nerviqRustFiles) return ctx.__nerviqRustFiles;
|
|
167
|
+
ctx.__nerviqRustFiles = findProjectFiles(ctx, /\.rs$/i);
|
|
168
|
+
return ctx.__nerviqRustFiles;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getMainRustFiles(ctx) {
|
|
172
|
+
if (ctx.__nerviqMainRustFiles) return ctx.__nerviqMainRustFiles;
|
|
173
|
+
ctx.__nerviqMainRustFiles = getRustFiles(ctx)
|
|
174
|
+
.filter(file => !/(^|\/)(tests|target)\//i.test(file))
|
|
175
|
+
.slice(0, 60);
|
|
176
|
+
return ctx.__nerviqMainRustFiles;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getJavaFiles(ctx) {
|
|
180
|
+
if (ctx.__nerviqJavaFiles) return ctx.__nerviqJavaFiles;
|
|
181
|
+
ctx.__nerviqJavaFiles = findProjectFiles(ctx, /\.java$/i);
|
|
182
|
+
return ctx.__nerviqJavaFiles;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getMainJavaFiles(ctx) {
|
|
186
|
+
if (ctx.__nerviqMainJavaFiles) return ctx.__nerviqMainJavaFiles;
|
|
187
|
+
ctx.__nerviqMainJavaFiles = getJavaFiles(ctx)
|
|
188
|
+
.filter(file => !/(^|\/)(test|tests|src\/test)\//i.test(file))
|
|
189
|
+
.slice(0, 60);
|
|
190
|
+
return ctx.__nerviqMainJavaFiles;
|
|
191
|
+
}
|
|
192
|
+
|
|
153
193
|
function getMainGoFiles(ctx) {
|
|
154
194
|
if (ctx.__nerviqMainGoFiles) return ctx.__nerviqMainGoFiles;
|
|
155
195
|
ctx.__nerviqMainGoFiles = getGoFiles(ctx).filter(file => !/_test\.go$/i.test(file)).slice(0, 60);
|
|
@@ -180,6 +220,42 @@ function getGoProjectText(ctx) {
|
|
|
180
220
|
return ctx.__nerviqGoProjectText;
|
|
181
221
|
}
|
|
182
222
|
|
|
223
|
+
function getRustProjectText(ctx) {
|
|
224
|
+
if (ctx.__nerviqRustProjectText) return ctx.__nerviqRustProjectText;
|
|
225
|
+
ctx.__nerviqRustProjectText = [
|
|
226
|
+
readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i),
|
|
227
|
+
readProjectFiles(ctx, /(^|\/)(clippy\.toml|\.clippy\.toml|rustfmt\.toml|\.rustfmt\.toml|build\.rs)$/i),
|
|
228
|
+
readProjectFiles(ctx, /(^|\/)\.cargo\/config\.toml$/i),
|
|
229
|
+
getWorkflowContent(ctx),
|
|
230
|
+
getMainRustFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').filter(Boolean).join('\n'),
|
|
231
|
+
].filter(Boolean).join('\n');
|
|
232
|
+
return ctx.__nerviqRustProjectText;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getJavaBuildText(ctx) {
|
|
236
|
+
if (ctx.__nerviqJavaBuildText) return ctx.__nerviqJavaBuildText;
|
|
237
|
+
ctx.__nerviqJavaBuildText = [
|
|
238
|
+
readProjectFiles(ctx, /(^|\/)pom\.xml$/i),
|
|
239
|
+
readProjectFiles(ctx, /(^|\/)build\.gradle$/i),
|
|
240
|
+
readProjectFiles(ctx, /(^|\/)build\.gradle\.kts$/i),
|
|
241
|
+
readProjectFiles(ctx, /(^|\/)settings\.gradle$/i),
|
|
242
|
+
readProjectFiles(ctx, /(^|\/)settings\.gradle\.kts$/i),
|
|
243
|
+
].filter(Boolean).join('\n');
|
|
244
|
+
return ctx.__nerviqJavaBuildText;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function getJavaProjectText(ctx) {
|
|
248
|
+
if (ctx.__nerviqJavaProjectText) return ctx.__nerviqJavaProjectText;
|
|
249
|
+
ctx.__nerviqJavaProjectText = [
|
|
250
|
+
getJavaBuildText(ctx),
|
|
251
|
+
getWorkflowContent(ctx),
|
|
252
|
+
readProjectFiles(ctx, /(^|\/)\.editorconfig$/i),
|
|
253
|
+
readProjectFiles(ctx, /(^|\/)(application\.properties|application\.ya?ml|logback.*\.xml|log4j2?.*\.xml)$/i),
|
|
254
|
+
getMainJavaFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').filter(Boolean).join('\n'),
|
|
255
|
+
].filter(Boolean).join('\n');
|
|
256
|
+
return ctx.__nerviqJavaProjectText;
|
|
257
|
+
}
|
|
258
|
+
|
|
183
259
|
function getGoInterfaceBlocks(ctx) {
|
|
184
260
|
if (ctx.__nerviqGoInterfaces) return ctx.__nerviqGoInterfaces;
|
|
185
261
|
const blocks = [];
|
|
@@ -1618,277 +1694,386 @@ const TECHNIQUES = {
|
|
|
1618
1694
|
// === PYTHON STACK CHECKS (category: 'python') ===============
|
|
1619
1695
|
// ============================================================
|
|
1620
1696
|
|
|
1621
|
-
|
|
1622
|
-
id:
|
|
1623
|
-
name: '
|
|
1624
|
-
check: (ctx) => {
|
|
1697
|
+
pyprojectTomlExists: {
|
|
1698
|
+
id: 120001,
|
|
1699
|
+
name: 'pyproject.toml exists for Python packaging',
|
|
1700
|
+
check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)pyproject\.toml$/i); },
|
|
1625
1701
|
impact: 'high',
|
|
1626
1702
|
category: 'python',
|
|
1627
|
-
fix: '
|
|
1703
|
+
fix: 'Add pyproject.toml to declare modern Python packaging, tooling, and metadata.',
|
|
1628
1704
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1629
1705
|
confidence: 0.7,
|
|
1630
1706
|
},
|
|
1631
1707
|
|
|
1632
|
-
|
|
1633
|
-
id:
|
|
1634
|
-
name: '
|
|
1635
|
-
check: (ctx) => {
|
|
1708
|
+
pythonTypeHints: {
|
|
1709
|
+
id: 120002,
|
|
1710
|
+
name: 'Type hints used in Python code',
|
|
1711
|
+
check: (ctx) => {
|
|
1712
|
+
if (!isPythonProject(ctx)) return null;
|
|
1713
|
+
if (hasProjectFile(ctx, /(^|\/)(mypy\.ini|py\.typed|pyrightconfig\.json)$/i)) return true;
|
|
1714
|
+
const pyproject = readProjectFiles(ctx, /(^|\/)pyproject\.toml$/i);
|
|
1715
|
+
if (/\[tool\.(mypy|pyright)\]/i.test(pyproject)) return true;
|
|
1716
|
+
const files = getMainPythonFiles(ctx);
|
|
1717
|
+
if (files.length === 0) return null;
|
|
1718
|
+
return files.some(file => /from typing import|import typing|from __future__ import annotations|->\s*[\w\[\]., ]+|:\s*[\w\[\]., ]+\s*=/.test(ctx.fileContent(file) || ''));
|
|
1719
|
+
},
|
|
1636
1720
|
impact: 'medium',
|
|
1637
1721
|
category: 'python',
|
|
1638
|
-
fix: '
|
|
1722
|
+
fix: 'Add type hints in main Python modules or configure mypy/pyright with py.typed support.',
|
|
1639
1723
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1640
1724
|
confidence: 0.7,
|
|
1641
1725
|
},
|
|
1642
1726
|
|
|
1643
|
-
|
|
1644
|
-
id:
|
|
1645
|
-
name: '
|
|
1646
|
-
check: (ctx) => {
|
|
1727
|
+
pythonLinter: {
|
|
1728
|
+
id: 120003,
|
|
1729
|
+
name: 'Python linter configured',
|
|
1730
|
+
check: (ctx) => {
|
|
1731
|
+
if (!isPythonProject(ctx)) return null;
|
|
1732
|
+
const config = `${getPythonProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)(\.flake8|\.pylintrc|pylintrc|ruff\.toml|\.ruff\.toml)$/i)}`;
|
|
1733
|
+
return /\[tool\.ruff\]|\[flake8\]|\[tool\.flake8\]|\[tool\.pylint\]|ruff|flake8|pylint/i.test(config);
|
|
1734
|
+
},
|
|
1647
1735
|
impact: 'medium',
|
|
1648
1736
|
category: 'python',
|
|
1649
|
-
fix: '
|
|
1737
|
+
fix: 'Configure a Python linter such as ruff, flake8, or pylint in pyproject.toml or a dedicated config file.',
|
|
1650
1738
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1651
1739
|
confidence: 0.7,
|
|
1652
1740
|
},
|
|
1653
1741
|
|
|
1654
|
-
|
|
1655
|
-
id:
|
|
1656
|
-
name: 'Python
|
|
1657
|
-
check: (ctx) => {
|
|
1658
|
-
|
|
1742
|
+
pythonFormatter: {
|
|
1743
|
+
id: 120004,
|
|
1744
|
+
name: 'Python formatter configured',
|
|
1745
|
+
check: (ctx) => {
|
|
1746
|
+
if (!isPythonProject(ctx)) return null;
|
|
1747
|
+
const pyproject = getPythonProjectText(ctx);
|
|
1748
|
+
const prettier = readProjectFiles(ctx, /(^|\/)\.prettierrc(\.(json|ya?ml|toml))?$/i);
|
|
1749
|
+
return /\[tool\.black\]|\[tool\.ruff\.format\]|\[tool\.isort\]/i.test(pyproject) ||
|
|
1750
|
+
/python|\.py\b/i.test(prettier);
|
|
1751
|
+
},
|
|
1752
|
+
impact: 'medium',
|
|
1659
1753
|
category: 'python',
|
|
1660
|
-
fix: '
|
|
1754
|
+
fix: 'Configure formatting with black, ruff format, isort, or a Prettier override that explicitly covers Python files.',
|
|
1661
1755
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1662
1756
|
confidence: 0.7,
|
|
1663
1757
|
},
|
|
1664
1758
|
|
|
1665
|
-
|
|
1666
|
-
id:
|
|
1667
|
-
name: '
|
|
1668
|
-
check: (ctx) => {
|
|
1759
|
+
pythonTestFramework: {
|
|
1760
|
+
id: 120005,
|
|
1761
|
+
name: 'Python test framework present',
|
|
1762
|
+
check: (ctx) => {
|
|
1763
|
+
if (!isPythonProject(ctx)) return null;
|
|
1764
|
+
return /\[tool\.pytest/i.test(getPythonProjectText(ctx)) ||
|
|
1765
|
+
hasProjectFile(ctx, /(^|\/)(pytest\.ini|tox\.ini|conftest\.py)$/i);
|
|
1766
|
+
},
|
|
1669
1767
|
impact: 'high',
|
|
1670
1768
|
category: 'python',
|
|
1671
|
-
fix: '
|
|
1769
|
+
fix: 'Add pytest.ini, conftest.py, tox.ini, or pyproject.toml pytest configuration so the test framework is explicit.',
|
|
1672
1770
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1673
1771
|
confidence: 0.7,
|
|
1674
1772
|
},
|
|
1675
1773
|
|
|
1676
|
-
|
|
1677
|
-
id:
|
|
1678
|
-
name: '
|
|
1679
|
-
check: (ctx) => {
|
|
1774
|
+
pythonVenvIgnored: {
|
|
1775
|
+
id: 120006,
|
|
1776
|
+
name: 'Virtual environment directories ignored in git',
|
|
1777
|
+
check: (ctx) => {
|
|
1778
|
+
if (!isPythonProject(ctx)) return null;
|
|
1779
|
+
const gitignore = ctx.fileContent('.gitignore') || '';
|
|
1780
|
+
return /(^|\n)\s*\.venv\/?\s*($|\n)|(^|\n)\s*venv\/?\s*($|\n)|(^|\n)\s*env\/?\s*($|\n)/i.test(gitignore);
|
|
1781
|
+
},
|
|
1680
1782
|
impact: 'medium',
|
|
1681
1783
|
category: 'python',
|
|
1682
|
-
fix: '
|
|
1784
|
+
fix: 'Ignore `.venv/`, `venv/`, or `env/` in .gitignore so local environments do not get committed.',
|
|
1683
1785
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1684
1786
|
confidence: 0.7,
|
|
1685
1787
|
},
|
|
1686
1788
|
|
|
1687
|
-
|
|
1688
|
-
id:
|
|
1689
|
-
name: '
|
|
1690
|
-
check: (ctx) => {
|
|
1691
|
-
|
|
1789
|
+
pythonRequirementsPinned: {
|
|
1790
|
+
id: 120007,
|
|
1791
|
+
name: 'Requirements files use pinned versions',
|
|
1792
|
+
check: (ctx) => {
|
|
1793
|
+
if (!isPythonProject(ctx)) return null;
|
|
1794
|
+
const files = findProjectFiles(ctx, /(^|\/)requirements[^/]*\.txt$/i);
|
|
1795
|
+
if (files.length === 0) return null;
|
|
1796
|
+
const lines = files
|
|
1797
|
+
.flatMap(file => (ctx.fileContent(file) || '').split(/\r?\n/))
|
|
1798
|
+
.map(line => line.trim())
|
|
1799
|
+
.filter(line => line && !line.startsWith('#'));
|
|
1800
|
+
if (lines.length === 0) return null;
|
|
1801
|
+
return lines.every(line => /^(-r|-c|--)/.test(line) || /==| @ /.test(line));
|
|
1802
|
+
},
|
|
1803
|
+
impact: 'high',
|
|
1692
1804
|
category: 'python',
|
|
1693
|
-
fix: '
|
|
1805
|
+
fix: 'Pin Python requirements with `==` or direct references so installs stay reproducible.',
|
|
1694
1806
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1695
1807
|
confidence: 0.7,
|
|
1696
1808
|
},
|
|
1697
1809
|
|
|
1698
|
-
|
|
1699
|
-
id:
|
|
1700
|
-
name: '
|
|
1701
|
-
check: (ctx) => {
|
|
1810
|
+
pythonSecurityScanner: {
|
|
1811
|
+
id: 120008,
|
|
1812
|
+
name: 'Python security scanner configured',
|
|
1813
|
+
check: (ctx) => {
|
|
1814
|
+
if (!isPythonProject(ctx)) return null;
|
|
1815
|
+
const content = `${getPythonProjectText(ctx)}\n${getWorkflowContent(ctx)}\n${getPreCommitContent(ctx)}`;
|
|
1816
|
+
return /bandit|pip-audit|safety/i.test(content);
|
|
1817
|
+
},
|
|
1702
1818
|
impact: 'medium',
|
|
1703
1819
|
category: 'python',
|
|
1704
|
-
fix: 'Configure
|
|
1820
|
+
fix: 'Configure bandit, safety, or pip-audit in dependencies, pre-commit, or CI.',
|
|
1705
1821
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1706
1822
|
confidence: 0.7,
|
|
1707
1823
|
},
|
|
1708
1824
|
|
|
1709
|
-
|
|
1710
|
-
id:
|
|
1711
|
-
name: '
|
|
1712
|
-
check: (ctx) => {
|
|
1713
|
-
|
|
1825
|
+
pythonPreCommitHooks: {
|
|
1826
|
+
id: 120009,
|
|
1827
|
+
name: 'pre-commit configured with Python hooks',
|
|
1828
|
+
check: (ctx) => {
|
|
1829
|
+
if (!isPythonProject(ctx)) return null;
|
|
1830
|
+
const preCommit = getPreCommitContent(ctx);
|
|
1831
|
+
if (!preCommit) return false;
|
|
1832
|
+
return /ruff|black|mypy|pyupgrade|pytest|bandit|isort|flake8|pylint/i.test(preCommit);
|
|
1833
|
+
},
|
|
1834
|
+
impact: 'medium',
|
|
1714
1835
|
category: 'python',
|
|
1715
|
-
fix: '
|
|
1836
|
+
fix: 'Add `.pre-commit-config.yaml` with Python-focused hooks such as ruff, black, mypy, or bandit.',
|
|
1716
1837
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1717
1838
|
confidence: 0.7,
|
|
1718
1839
|
},
|
|
1719
1840
|
|
|
1720
|
-
|
|
1721
|
-
id:
|
|
1722
|
-
name: '
|
|
1723
|
-
check: (ctx) => {
|
|
1724
|
-
|
|
1841
|
+
pythonDocstrings: {
|
|
1842
|
+
id: 120010,
|
|
1843
|
+
name: 'Docstrings present in main Python files',
|
|
1844
|
+
check: (ctx) => {
|
|
1845
|
+
if (!isPythonProject(ctx)) return null;
|
|
1846
|
+
const files = getMainPythonFiles(ctx);
|
|
1847
|
+
if (files.length === 0) return null;
|
|
1848
|
+
return files.some(file => /(^|\n)\s*(def|class)\s+\w+.*:\s*\n\s*("""|''')|^\s*("""|''')/m.test(ctx.fileContent(file) || ''));
|
|
1849
|
+
},
|
|
1850
|
+
impact: 'low',
|
|
1725
1851
|
category: 'python',
|
|
1726
|
-
fix: '
|
|
1852
|
+
fix: 'Add module, class, or function docstrings in the main Python source files.',
|
|
1727
1853
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1728
1854
|
confidence: 0.7,
|
|
1729
1855
|
},
|
|
1730
1856
|
|
|
1731
|
-
|
|
1732
|
-
id:
|
|
1733
|
-
name: '
|
|
1734
|
-
check: (ctx) => {
|
|
1735
|
-
|
|
1857
|
+
pythonCIConfigured: {
|
|
1858
|
+
id: 120011,
|
|
1859
|
+
name: 'CI runs Python tests',
|
|
1860
|
+
check: (ctx) => {
|
|
1861
|
+
if (!isPythonProject(ctx)) return null;
|
|
1862
|
+
return /pytest|python -m pytest|python -m unittest|tox\b|nox\b/i.test(getWorkflowContent(ctx));
|
|
1863
|
+
},
|
|
1864
|
+
impact: 'high',
|
|
1736
1865
|
category: 'python',
|
|
1737
|
-
fix: '
|
|
1866
|
+
fix: 'Run Python tests in CI with pytest, unittest, tox, or nox.',
|
|
1738
1867
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1739
1868
|
confidence: 0.7,
|
|
1740
1869
|
},
|
|
1741
1870
|
|
|
1742
|
-
|
|
1743
|
-
id:
|
|
1744
|
-
name: '
|
|
1745
|
-
check: (ctx) => {
|
|
1871
|
+
pythonCoverage: {
|
|
1872
|
+
id: 120012,
|
|
1873
|
+
name: 'Python coverage configured',
|
|
1874
|
+
check: (ctx) => {
|
|
1875
|
+
if (!isPythonProject(ctx)) return null;
|
|
1876
|
+
const content = `${getPythonProjectText(ctx)}\n${getWorkflowContent(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.coveragerc$/i)}`;
|
|
1877
|
+
return /\[tool\.coverage|pytest-cov|coverage\b|--cov\b/i.test(content);
|
|
1878
|
+
},
|
|
1746
1879
|
impact: 'medium',
|
|
1747
1880
|
category: 'python',
|
|
1748
|
-
fix: '
|
|
1881
|
+
fix: 'Configure coverage.py or pytest-cov in project config or CI.',
|
|
1749
1882
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1750
1883
|
confidence: 0.7,
|
|
1751
1884
|
},
|
|
1752
1885
|
|
|
1753
|
-
|
|
1754
|
-
id:
|
|
1755
|
-
name: '
|
|
1756
|
-
check: (ctx) => {
|
|
1886
|
+
pythonPackageManager: {
|
|
1887
|
+
id: 120013,
|
|
1888
|
+
name: 'Modern Python package manager lockfile present',
|
|
1889
|
+
check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)(poetry\.lock|pdm\.lock|uv\.lock|Pipfile\.lock)$/); },
|
|
1757
1890
|
impact: 'medium',
|
|
1758
1891
|
category: 'python',
|
|
1759
|
-
fix: '
|
|
1892
|
+
fix: 'Commit a Poetry, PDM, uv, or Pipenv lockfile for reproducible dependency resolution.',
|
|
1760
1893
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1761
1894
|
confidence: 0.7,
|
|
1762
1895
|
},
|
|
1763
1896
|
|
|
1764
|
-
|
|
1765
|
-
id:
|
|
1766
|
-
name: '
|
|
1767
|
-
check: (ctx) => {
|
|
1897
|
+
pythonMinVersionSpecified: {
|
|
1898
|
+
id: 120014,
|
|
1899
|
+
name: 'Minimum Python version specified',
|
|
1900
|
+
check: (ctx) => {
|
|
1901
|
+
if (!isPythonProject(ctx)) return null;
|
|
1902
|
+
const content = `${getPythonProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.python-version$/i)}`;
|
|
1903
|
+
return /requires-python|python_requires|(^|\n)\s*python\s*=|^\s*\d+\.\d+(\.\d+)?\s*$/im.test(content);
|
|
1904
|
+
},
|
|
1768
1905
|
impact: 'medium',
|
|
1769
1906
|
category: 'python',
|
|
1770
|
-
fix: '
|
|
1907
|
+
fix: 'Specify the supported Python version with `.python-version`, `requires-python`, or `python_requires`.',
|
|
1771
1908
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1772
1909
|
confidence: 0.7,
|
|
1773
1910
|
},
|
|
1774
1911
|
|
|
1775
|
-
|
|
1776
|
-
id:
|
|
1777
|
-
name: '
|
|
1778
|
-
check: (ctx) => {
|
|
1912
|
+
pythonAsyncPatterns: {
|
|
1913
|
+
id: 120015,
|
|
1914
|
+
name: 'Async Python patterns used',
|
|
1915
|
+
check: (ctx) => {
|
|
1916
|
+
if (!isPythonProject(ctx)) return null;
|
|
1917
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
1918
|
+
return /asyncio|aiohttp|fastapi|starlette|trio|anyio|async def|await /i.test(content);
|
|
1919
|
+
},
|
|
1779
1920
|
impact: 'low',
|
|
1780
1921
|
category: 'python',
|
|
1781
|
-
fix: '
|
|
1922
|
+
fix: 'Adopt explicit async patterns such as asyncio, aiohttp, FastAPI, or `async def` where concurrent workflows matter.',
|
|
1782
1923
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1783
1924
|
confidence: 0.7,
|
|
1784
1925
|
},
|
|
1785
1926
|
|
|
1786
|
-
|
|
1787
|
-
id:
|
|
1788
|
-
name: '
|
|
1789
|
-
check: (ctx) => {
|
|
1927
|
+
pythonEnvExample: {
|
|
1928
|
+
id: 120016,
|
|
1929
|
+
name: 'Python project includes an environment example file',
|
|
1930
|
+
check: (ctx) => { if (!isPythonProject(ctx)) return null; return hasProjectFile(ctx, /(^|\/)\.env(\.example|\.sample)$/i); },
|
|
1790
1931
|
impact: 'medium',
|
|
1791
1932
|
category: 'python',
|
|
1792
|
-
fix: '
|
|
1933
|
+
fix: 'Add `.env.example` or `.env.sample` so required Python environment variables are documented.',
|
|
1793
1934
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1794
1935
|
confidence: 0.7,
|
|
1795
1936
|
},
|
|
1796
1937
|
|
|
1797
|
-
|
|
1798
|
-
id:
|
|
1799
|
-
name: '
|
|
1800
|
-
check: (ctx) => {
|
|
1938
|
+
pythonMigrations: {
|
|
1939
|
+
id: 120017,
|
|
1940
|
+
name: 'Python database migration tooling present',
|
|
1941
|
+
check: (ctx) => {
|
|
1942
|
+
if (!isPythonProject(ctx)) return null;
|
|
1943
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 20).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
1944
|
+
return /alembic|django\.db\.migrations|makemigrations|migrate/i.test(content) ||
|
|
1945
|
+
hasProjectFile(ctx, /(^|\/)alembic\.ini$/i) ||
|
|
1946
|
+
hasProjectFile(ctx, /(^|\/)(alembic|migrations)\//i);
|
|
1947
|
+
},
|
|
1801
1948
|
impact: 'medium',
|
|
1802
1949
|
category: 'python',
|
|
1803
|
-
fix: '
|
|
1950
|
+
fix: 'Use Alembic or Django migrations and keep the migration surface committed in the repo.',
|
|
1804
1951
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1805
1952
|
confidence: 0.7,
|
|
1806
1953
|
},
|
|
1807
1954
|
|
|
1808
|
-
|
|
1809
|
-
id:
|
|
1810
|
-
name: '
|
|
1811
|
-
check: (ctx) => {
|
|
1812
|
-
|
|
1955
|
+
pythonLogging: {
|
|
1956
|
+
id: 120018,
|
|
1957
|
+
name: 'Python structured logging configured',
|
|
1958
|
+
check: (ctx) => {
|
|
1959
|
+
if (!isPythonProject(ctx)) return null;
|
|
1960
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
1961
|
+
return /structlog|loguru|logging\.config|dictConfig|getLogger|basicConfig/i.test(content);
|
|
1962
|
+
},
|
|
1963
|
+
impact: 'medium',
|
|
1813
1964
|
category: 'python',
|
|
1814
|
-
fix: '
|
|
1965
|
+
fix: 'Configure logging with Python logging config, structlog, or loguru for consistent operational signals.',
|
|
1815
1966
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1816
1967
|
confidence: 0.7,
|
|
1817
1968
|
},
|
|
1818
1969
|
|
|
1819
|
-
|
|
1820
|
-
id:
|
|
1821
|
-
name: 'Python
|
|
1822
|
-
check: (ctx) => {
|
|
1970
|
+
pythonAPISchema: {
|
|
1971
|
+
id: 120019,
|
|
1972
|
+
name: 'Python API schema or model definitions present',
|
|
1973
|
+
check: (ctx) => {
|
|
1974
|
+
if (!isPythonProject(ctx)) return null;
|
|
1975
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
1976
|
+
return /openapi|swagger|BaseModel|pydantic|Schema\)|marshmallow|TypedDict/i.test(content);
|
|
1977
|
+
},
|
|
1823
1978
|
impact: 'medium',
|
|
1824
1979
|
category: 'python',
|
|
1825
|
-
fix: '
|
|
1980
|
+
fix: 'Define API schemas with OpenAPI, Pydantic, Marshmallow, or typed request/response models.',
|
|
1826
1981
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1827
1982
|
confidence: 0.7,
|
|
1828
1983
|
},
|
|
1829
1984
|
|
|
1830
|
-
|
|
1831
|
-
id:
|
|
1832
|
-
name: '
|
|
1833
|
-
check: (ctx) => {
|
|
1834
|
-
|
|
1985
|
+
pythonContainerized: {
|
|
1986
|
+
id: 120020,
|
|
1987
|
+
name: 'Python container image uses a Python base',
|
|
1988
|
+
check: (ctx) => {
|
|
1989
|
+
if (!isPythonProject(ctx)) return null;
|
|
1990
|
+
const dockerfile = ctx.fileContent('Dockerfile') || '';
|
|
1991
|
+
if (!dockerfile) return null;
|
|
1992
|
+
return /FROM\s+python[:\d.-]/i.test(dockerfile);
|
|
1993
|
+
},
|
|
1994
|
+
impact: 'medium',
|
|
1835
1995
|
category: 'python',
|
|
1836
|
-
fix: '
|
|
1996
|
+
fix: 'Use an official Python image such as `python:3.12-slim` when containerizing Python services.',
|
|
1837
1997
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1838
1998
|
confidence: 0.7,
|
|
1839
1999
|
},
|
|
1840
2000
|
|
|
1841
|
-
|
|
1842
|
-
id:
|
|
1843
|
-
name: '
|
|
1844
|
-
check: (ctx) => {
|
|
2001
|
+
pythonDependencyGroups: {
|
|
2002
|
+
id: 120021,
|
|
2003
|
+
name: 'Python dev and test dependency groups separated',
|
|
2004
|
+
check: (ctx) => {
|
|
2005
|
+
if (!isPythonProject(ctx)) return null;
|
|
2006
|
+
const content = getPythonProjectText(ctx);
|
|
2007
|
+
return /\[tool\.poetry\.group\.[^\]]+\]|\[project\.optional-dependencies\]|extras_require|dependency-groups/i.test(content);
|
|
2008
|
+
},
|
|
1845
2009
|
impact: 'medium',
|
|
1846
2010
|
category: 'python',
|
|
1847
|
-
fix: '
|
|
2011
|
+
fix: 'Separate Python dev and test dependencies with Poetry groups, optional-dependencies, or extras_require.',
|
|
1848
2012
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1849
2013
|
confidence: 0.7,
|
|
1850
2014
|
},
|
|
1851
2015
|
|
|
1852
|
-
|
|
1853
|
-
id:
|
|
1854
|
-
name: '
|
|
1855
|
-
check: (ctx) => {
|
|
1856
|
-
|
|
2016
|
+
pythonPathConfig: {
|
|
2017
|
+
id: 120022,
|
|
2018
|
+
name: 'Python tool path configuration present',
|
|
2019
|
+
check: (ctx) => {
|
|
2020
|
+
if (!isPythonProject(ctx)) return null;
|
|
2021
|
+
if (hasProjectFile(ctx, /(^|\/)pyrightconfig\.json$/i)) return true;
|
|
2022
|
+
const vscodeSettings = findProjectFiles(ctx, /(^|\/)\.vscode\/settings\.json$/i)
|
|
2023
|
+
.map(file => ctx.jsonFile(file) || {})
|
|
2024
|
+
.find(settings => Object.keys(settings).some(key => key.toLowerCase().includes('python')));
|
|
2025
|
+
return !!vscodeSettings;
|
|
2026
|
+
},
|
|
2027
|
+
impact: 'low',
|
|
1857
2028
|
category: 'python',
|
|
1858
|
-
fix: '
|
|
2029
|
+
fix: 'Add `pyrightconfig.json` or VS Code Python settings so tooling resolves imports and environments consistently.',
|
|
1859
2030
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1860
2031
|
confidence: 0.7,
|
|
1861
2032
|
},
|
|
1862
2033
|
|
|
1863
|
-
|
|
1864
|
-
id:
|
|
1865
|
-
name: '
|
|
1866
|
-
check: (ctx) => {
|
|
1867
|
-
|
|
2034
|
+
pythonMonorepo: {
|
|
2035
|
+
id: 120023,
|
|
2036
|
+
name: 'Python monorepo-friendly package layout present',
|
|
2037
|
+
check: (ctx) => {
|
|
2038
|
+
if (!isPythonProject(ctx)) return null;
|
|
2039
|
+
const content = getPythonProjectText(ctx);
|
|
2040
|
+
return ctx.hasDir('src') ||
|
|
2041
|
+
/namespace_packages|find_namespace:|from\s*=\s*["']src["']|package-dir/i.test(content);
|
|
2042
|
+
},
|
|
2043
|
+
impact: 'low',
|
|
1868
2044
|
category: 'python',
|
|
1869
|
-
fix: '
|
|
2045
|
+
fix: 'Use a `src/` layout or namespace package configuration for larger multi-package Python repos.',
|
|
1870
2046
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1871
2047
|
confidence: 0.7,
|
|
1872
2048
|
},
|
|
1873
2049
|
|
|
1874
|
-
|
|
1875
|
-
id:
|
|
1876
|
-
name: '
|
|
1877
|
-
check: (ctx) => {
|
|
1878
|
-
|
|
2050
|
+
pythonErrorHandling: {
|
|
2051
|
+
id: 120024,
|
|
2052
|
+
name: 'Custom Python exception classes defined',
|
|
2053
|
+
check: (ctx) => {
|
|
2054
|
+
if (!isPythonProject(ctx)) return null;
|
|
2055
|
+
const files = getMainPythonFiles(ctx);
|
|
2056
|
+
if (files.length === 0) return null;
|
|
2057
|
+
return files.some(file => /class\s+\w+(Error|Exception)\s*\((?:[\w.]*Exception|[\w.]*Error)\)\s*:/i.test(ctx.fileContent(file) || ''));
|
|
2058
|
+
},
|
|
2059
|
+
impact: 'low',
|
|
1879
2060
|
category: 'python',
|
|
1880
|
-
fix: '
|
|
2061
|
+
fix: 'Define custom exception classes for domain-specific Python error handling instead of only raising generic exceptions.',
|
|
1881
2062
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1882
2063
|
confidence: 0.7,
|
|
1883
2064
|
},
|
|
1884
2065
|
|
|
1885
|
-
|
|
1886
|
-
id:
|
|
1887
|
-
name: 'Python
|
|
1888
|
-
check: (ctx) => {
|
|
2066
|
+
pythonDataValidation: {
|
|
2067
|
+
id: 120025,
|
|
2068
|
+
name: 'Python data validation library used',
|
|
2069
|
+
check: (ctx) => {
|
|
2070
|
+
if (!isPythonProject(ctx)) return null;
|
|
2071
|
+
const content = `${getPythonProjectText(ctx)}\n${getMainPythonFiles(ctx).slice(0, 30).map(file => ctx.fileContent(file) || '').join('\n')}`;
|
|
2072
|
+
return /pydantic|marshmallow|attrs|attr\.s|BaseModel|Schema\)/i.test(content);
|
|
2073
|
+
},
|
|
1889
2074
|
impact: 'medium',
|
|
1890
2075
|
category: 'python',
|
|
1891
|
-
fix: '
|
|
2076
|
+
fix: 'Use Pydantic, Marshmallow, attrs, or similar validation libraries for structured Python inputs and models.',
|
|
1892
2077
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1893
2078
|
confidence: 0.7,
|
|
1894
2079
|
},
|
|
@@ -1898,221 +2083,307 @@ const TECHNIQUES = {
|
|
|
1898
2083
|
// ============================================================
|
|
1899
2084
|
|
|
1900
2085
|
goModExists: {
|
|
1901
|
-
id:
|
|
1902
|
-
name: 'go.mod exists',
|
|
1903
|
-
check: (ctx) => { if (!ctx
|
|
2086
|
+
id: 120101,
|
|
2087
|
+
name: 'go.mod exists for Go module management',
|
|
2088
|
+
check: (ctx) => { if (!isGoProject(ctx)) return null; return true; },
|
|
1904
2089
|
impact: 'high',
|
|
1905
2090
|
category: 'go',
|
|
1906
|
-
fix: 'Initialize Go module with go mod init.',
|
|
2091
|
+
fix: 'Initialize the repository as a Go module with `go mod init` and commit `go.mod`.',
|
|
1907
2092
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1908
2093
|
confidence: 0.7,
|
|
1909
2094
|
},
|
|
1910
2095
|
|
|
1911
|
-
|
|
1912
|
-
id:
|
|
1913
|
-
name: '
|
|
1914
|
-
check: (ctx) => {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
confidence: 0.7,
|
|
1920
|
-
},
|
|
1921
|
-
|
|
1922
|
-
golangciLintConfigured: {
|
|
1923
|
-
id: 'CL-GO03',
|
|
1924
|
-
name: 'golangci-lint configured (.golangci.yml)',
|
|
1925
|
-
check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return ctx.files.some(f => /\.golangci\.ya?ml$|\.golangci\.toml$/.test(f)); },
|
|
2096
|
+
goLinter: {
|
|
2097
|
+
id: 120102,
|
|
2098
|
+
name: 'Go linter configured',
|
|
2099
|
+
check: (ctx) => {
|
|
2100
|
+
if (!isGoProject(ctx)) return null;
|
|
2101
|
+
const content = `${getGoProjectText(ctx)}\n${readProjectFiles(ctx, /(^|\/)\.golangci\.(ya?ml|toml)$/i)}`;
|
|
2102
|
+
return /\.golangci\.|golangci-lint/i.test(content);
|
|
2103
|
+
},
|
|
1926
2104
|
impact: 'medium',
|
|
1927
2105
|
category: 'go',
|
|
1928
|
-
fix: '
|
|
2106
|
+
fix: 'Configure golangci-lint in the repo or CI for consistent Go lint enforcement.',
|
|
1929
2107
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1930
2108
|
confidence: 0.7,
|
|
1931
2109
|
},
|
|
1932
2110
|
|
|
1933
|
-
|
|
1934
|
-
id:
|
|
1935
|
-
name: '
|
|
1936
|
-
check: (ctx) => { if (!ctx
|
|
2111
|
+
goTestFiles: {
|
|
2112
|
+
id: 120103,
|
|
2113
|
+
name: 'Go test files present',
|
|
2114
|
+
check: (ctx) => { if (!isGoProject(ctx)) return null; return hasProjectFile(ctx, /_test\.go$/i); },
|
|
1937
2115
|
impact: 'high',
|
|
1938
2116
|
category: 'go',
|
|
1939
|
-
fix: '
|
|
2117
|
+
fix: 'Add `_test.go` files so Go packages have executable unit or integration tests.',
|
|
1940
2118
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1941
2119
|
confidence: 0.7,
|
|
1942
2120
|
},
|
|
1943
2121
|
|
|
1944
|
-
|
|
1945
|
-
id:
|
|
1946
|
-
name: 'go
|
|
1947
|
-
check: (ctx) => {
|
|
1948
|
-
|
|
2122
|
+
goVet: {
|
|
2123
|
+
id: 120104,
|
|
2124
|
+
name: 'go vet runs in automation',
|
|
2125
|
+
check: (ctx) => {
|
|
2126
|
+
if (!isGoProject(ctx)) return null;
|
|
2127
|
+
const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
|
|
2128
|
+
return /go vet/i.test(content);
|
|
2129
|
+
},
|
|
2130
|
+
impact: 'medium',
|
|
1949
2131
|
category: 'go',
|
|
1950
|
-
fix: '
|
|
2132
|
+
fix: 'Run `go vet` in CI or the project Makefile to catch common Go mistakes.',
|
|
1951
2133
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1952
2134
|
confidence: 0.7,
|
|
1953
2135
|
},
|
|
1954
2136
|
|
|
1955
|
-
|
|
1956
|
-
id:
|
|
1957
|
-
name: '
|
|
1958
|
-
check: (ctx) => {
|
|
2137
|
+
goFmt: {
|
|
2138
|
+
id: 120105,
|
|
2139
|
+
name: 'gofmt or goimports enforced',
|
|
2140
|
+
check: (ctx) => {
|
|
2141
|
+
if (!isGoProject(ctx)) return null;
|
|
2142
|
+
const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}\n${getPreCommitContent(ctx)}`;
|
|
2143
|
+
return /gofmt|goimports/i.test(content);
|
|
2144
|
+
},
|
|
1959
2145
|
impact: 'medium',
|
|
1960
2146
|
category: 'go',
|
|
1961
|
-
fix: '
|
|
2147
|
+
fix: 'Run `gofmt` or `goimports` in CI, pre-commit, or developer tooling.',
|
|
1962
2148
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1963
2149
|
confidence: 0.7,
|
|
1964
2150
|
},
|
|
1965
2151
|
|
|
1966
|
-
|
|
1967
|
-
id:
|
|
1968
|
-
name: '
|
|
1969
|
-
check: (ctx) => {
|
|
2152
|
+
goModTidy: {
|
|
2153
|
+
id: 120106,
|
|
2154
|
+
name: 'go mod tidy runs in automation',
|
|
2155
|
+
check: (ctx) => {
|
|
2156
|
+
if (!isGoProject(ctx)) return null;
|
|
2157
|
+
const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
|
|
2158
|
+
return /go mod tidy/i.test(content);
|
|
2159
|
+
},
|
|
1970
2160
|
impact: 'medium',
|
|
1971
2161
|
category: 'go',
|
|
1972
|
-
fix: '
|
|
2162
|
+
fix: 'Run `go mod tidy` in CI or the Makefile so module metadata stays clean.',
|
|
1973
2163
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1974
2164
|
confidence: 0.7,
|
|
1975
2165
|
},
|
|
1976
2166
|
|
|
1977
|
-
|
|
1978
|
-
id:
|
|
1979
|
-
name: '
|
|
1980
|
-
check: (ctx) => {
|
|
1981
|
-
|
|
2167
|
+
goBuildTags: {
|
|
2168
|
+
id: 120107,
|
|
2169
|
+
name: 'Go build tags or constraints used',
|
|
2170
|
+
check: (ctx) => {
|
|
2171
|
+
if (!isGoProject(ctx)) return null;
|
|
2172
|
+
const files = getGoFiles(ctx);
|
|
2173
|
+
if (files.length === 0) return null;
|
|
2174
|
+
return files.some(file => /\/\/go:build|\/\/ \+build/.test(ctx.fileContent(file) || ''));
|
|
2175
|
+
},
|
|
2176
|
+
impact: 'low',
|
|
1982
2177
|
category: 'go',
|
|
1983
|
-
fix: '
|
|
2178
|
+
fix: 'Use `//go:build` constraints when a Go package depends on build tags or platform-specific variants.',
|
|
1984
2179
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1985
2180
|
confidence: 0.7,
|
|
1986
2181
|
},
|
|
1987
2182
|
|
|
1988
|
-
|
|
1989
|
-
id:
|
|
1990
|
-
name: '
|
|
1991
|
-
check: (ctx) => {
|
|
2183
|
+
goErrorWrapping: {
|
|
2184
|
+
id: 120108,
|
|
2185
|
+
name: 'Go errors use wrapping patterns',
|
|
2186
|
+
check: (ctx) => {
|
|
2187
|
+
if (!isGoProject(ctx)) return null;
|
|
2188
|
+
const files = getMainGoFiles(ctx);
|
|
2189
|
+
if (files.length === 0) return null;
|
|
2190
|
+
return files.some(file => /fmt\.Errorf\([^)]*%w|errors\.Join\(/.test(ctx.fileContent(file) || ''));
|
|
2191
|
+
},
|
|
1992
2192
|
impact: 'medium',
|
|
1993
2193
|
category: 'go',
|
|
1994
|
-
fix: '
|
|
2194
|
+
fix: 'Wrap Go errors with `fmt.Errorf(... %w ...)` or similar patterns to preserve context.',
|
|
1995
2195
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
1996
2196
|
confidence: 0.7,
|
|
1997
2197
|
},
|
|
1998
2198
|
|
|
1999
|
-
|
|
2000
|
-
id:
|
|
2001
|
-
name: '
|
|
2002
|
-
check: (ctx) => {
|
|
2199
|
+
goInterfaceSegregation: {
|
|
2200
|
+
id: 120109,
|
|
2201
|
+
name: 'Go interfaces stay small',
|
|
2202
|
+
check: (ctx) => {
|
|
2203
|
+
if (!isGoProject(ctx)) return null;
|
|
2204
|
+
const interfaces = getGoInterfaceBlocks(ctx);
|
|
2205
|
+
if (interfaces.length === 0) return null;
|
|
2206
|
+
return interfaces.every(block => countGoInterfaceMethods(block) <= 5);
|
|
2207
|
+
},
|
|
2003
2208
|
impact: 'low',
|
|
2004
2209
|
category: 'go',
|
|
2005
|
-
fix: '
|
|
2210
|
+
fix: 'Keep Go interfaces small and focused; split interfaces that define more than five methods.',
|
|
2006
2211
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2007
2212
|
confidence: 0.7,
|
|
2008
2213
|
},
|
|
2009
2214
|
|
|
2010
|
-
|
|
2011
|
-
id:
|
|
2012
|
-
name: '
|
|
2013
|
-
check: (ctx) => {
|
|
2215
|
+
goContextUsage: {
|
|
2216
|
+
id: 120110,
|
|
2217
|
+
name: 'Go services use context.Context',
|
|
2218
|
+
check: (ctx) => {
|
|
2219
|
+
if (!isGoProject(ctx)) return null;
|
|
2220
|
+
const files = getMainGoFiles(ctx);
|
|
2221
|
+
if (files.length === 0) return null;
|
|
2222
|
+
return files.some(file => /context\.Context|context\.With(Cancel|Timeout|Deadline)|ctx\s+context\.Context/.test(ctx.fileContent(file) || ''));
|
|
2223
|
+
},
|
|
2014
2224
|
impact: 'medium',
|
|
2015
2225
|
category: 'go',
|
|
2016
|
-
fix: '
|
|
2226
|
+
fix: 'Pass `context.Context` through handlers and services so cancellation and deadlines are propagated correctly.',
|
|
2017
2227
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2018
2228
|
confidence: 0.7,
|
|
2019
2229
|
},
|
|
2020
2230
|
|
|
2021
|
-
|
|
2022
|
-
id:
|
|
2023
|
-
name: '
|
|
2024
|
-
check: (ctx) => {
|
|
2025
|
-
|
|
2231
|
+
goStructTags: {
|
|
2232
|
+
id: 120111,
|
|
2233
|
+
name: 'Exported Go structs include tags',
|
|
2234
|
+
check: (ctx) => {
|
|
2235
|
+
if (!isGoProject(ctx)) return null;
|
|
2236
|
+
const structBlocks = [];
|
|
2237
|
+
for (const file of getMainGoFiles(ctx)) {
|
|
2238
|
+
const content = ctx.fileContent(file) || '';
|
|
2239
|
+
for (const match of content.matchAll(/type\s+([A-Z]\w*)\s+struct\s*\{([\s\S]*?)\}/g)) {
|
|
2240
|
+
structBlocks.push(match[2]);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
if (structBlocks.length === 0) return null;
|
|
2244
|
+
return structBlocks.some(block => /`[^`]*(json|yaml|db):"/.test(block));
|
|
2245
|
+
},
|
|
2246
|
+
impact: 'low',
|
|
2026
2247
|
category: 'go',
|
|
2027
|
-
fix: 'Add
|
|
2248
|
+
fix: 'Add struct tags such as `json`, `yaml`, or `db` on exported Go types that cross boundaries.',
|
|
2028
2249
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2029
2250
|
confidence: 0.7,
|
|
2030
2251
|
},
|
|
2031
2252
|
|
|
2032
|
-
|
|
2033
|
-
id:
|
|
2034
|
-
name: '
|
|
2035
|
-
check: (ctx) => {
|
|
2253
|
+
goMakefile: {
|
|
2254
|
+
id: 120112,
|
|
2255
|
+
name: 'Go Makefile includes build, test, and lint targets',
|
|
2256
|
+
check: (ctx) => {
|
|
2257
|
+
if (!isGoProject(ctx)) return null;
|
|
2258
|
+
const makefile = ctx.fileContent('Makefile') || '';
|
|
2259
|
+
if (!makefile) return false;
|
|
2260
|
+
return /^\s*build:/m.test(makefile) && /^\s*test:/m.test(makefile) && /^\s*lint:/m.test(makefile);
|
|
2261
|
+
},
|
|
2036
2262
|
impact: 'medium',
|
|
2037
2263
|
category: 'go',
|
|
2038
|
-
fix: '
|
|
2264
|
+
fix: 'Add a Makefile with `build`, `test`, and `lint` targets for common Go workflows.',
|
|
2039
2265
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2040
2266
|
confidence: 0.7,
|
|
2041
2267
|
},
|
|
2042
2268
|
|
|
2043
|
-
|
|
2044
|
-
id:
|
|
2045
|
-
name: '
|
|
2046
|
-
check: (ctx) => {
|
|
2269
|
+
goDocComments: {
|
|
2270
|
+
id: 120113,
|
|
2271
|
+
name: 'Exported Go functions have doc comments',
|
|
2272
|
+
check: (ctx) => {
|
|
2273
|
+
if (!isGoProject(ctx)) return null;
|
|
2274
|
+
const files = getMainGoFiles(ctx);
|
|
2275
|
+
if (files.length === 0) return null;
|
|
2276
|
+
const documented = files.some(file => /\/\/\s*[A-Z]\w+.*\nfunc\s+(?:\([^)]+\)\s*)?[A-Z]\w+\s*\(/.test(ctx.fileContent(file) || ''));
|
|
2277
|
+
const exported = files.some(file => /func\s+(?:\([^)]+\)\s*)?[A-Z]\w+\s*\(/.test(ctx.fileContent(file) || ''));
|
|
2278
|
+
if (!exported) return null;
|
|
2279
|
+
return documented;
|
|
2280
|
+
},
|
|
2047
2281
|
impact: 'low',
|
|
2048
2282
|
category: 'go',
|
|
2049
|
-
fix: '
|
|
2283
|
+
fix: 'Add Go doc comments above exported functions so package APIs remain self-describing.',
|
|
2050
2284
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2051
2285
|
confidence: 0.7,
|
|
2052
2286
|
},
|
|
2053
2287
|
|
|
2054
|
-
|
|
2055
|
-
id:
|
|
2056
|
-
name: '
|
|
2057
|
-
check: (ctx) => {
|
|
2288
|
+
goSecurityScanner: {
|
|
2289
|
+
id: 120114,
|
|
2290
|
+
name: 'Go security scanner configured',
|
|
2291
|
+
check: (ctx) => {
|
|
2292
|
+
if (!isGoProject(ctx)) return null;
|
|
2293
|
+
return /gosec|staticcheck/i.test(getGoProjectText(ctx));
|
|
2294
|
+
},
|
|
2058
2295
|
impact: 'medium',
|
|
2059
2296
|
category: 'go',
|
|
2060
|
-
fix: '
|
|
2297
|
+
fix: 'Configure `gosec` or `staticcheck` in CI or the Makefile for Go security and static analysis checks.',
|
|
2061
2298
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2062
2299
|
confidence: 0.7,
|
|
2063
2300
|
},
|
|
2064
2301
|
|
|
2065
|
-
|
|
2066
|
-
id:
|
|
2067
|
-
name: '
|
|
2068
|
-
check: (ctx) => {
|
|
2069
|
-
|
|
2302
|
+
goCIConfigured: {
|
|
2303
|
+
id: 120115,
|
|
2304
|
+
name: 'CI runs Go tests',
|
|
2305
|
+
check: (ctx) => {
|
|
2306
|
+
if (!isGoProject(ctx)) return null;
|
|
2307
|
+
return /go test(\s|$)|go test \.\/\.\.\./i.test(getWorkflowContent(ctx));
|
|
2308
|
+
},
|
|
2309
|
+
impact: 'high',
|
|
2070
2310
|
category: 'go',
|
|
2071
|
-
fix: '
|
|
2311
|
+
fix: 'Run `go test ./...` in CI so Go packages are verified on every change.',
|
|
2072
2312
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2073
2313
|
confidence: 0.7,
|
|
2074
2314
|
},
|
|
2075
2315
|
|
|
2076
|
-
|
|
2077
|
-
id:
|
|
2078
|
-
name: '
|
|
2079
|
-
check: (ctx) => {
|
|
2316
|
+
goContainerized: {
|
|
2317
|
+
id: 120116,
|
|
2318
|
+
name: 'Go Dockerfile uses multi-stage build',
|
|
2319
|
+
check: (ctx) => {
|
|
2320
|
+
if (!isGoProject(ctx)) return null;
|
|
2321
|
+
const dockerfile = ctx.fileContent('Dockerfile') || '';
|
|
2322
|
+
if (!dockerfile) return null;
|
|
2323
|
+
return /FROM\s+golang[:\d.-].*\bAS\b/i.test(dockerfile) &&
|
|
2324
|
+
/FROM\s+(alpine|scratch|distroless|gcr\.io|cgr\.dev)/i.test(dockerfile);
|
|
2325
|
+
},
|
|
2080
2326
|
impact: 'medium',
|
|
2081
2327
|
category: 'go',
|
|
2082
|
-
fix: '
|
|
2328
|
+
fix: 'Use a multi-stage Go Dockerfile: compile in a `golang` image and run from a minimal final image.',
|
|
2083
2329
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2084
2330
|
confidence: 0.7,
|
|
2085
2331
|
},
|
|
2086
2332
|
|
|
2087
|
-
|
|
2088
|
-
id:
|
|
2089
|
-
name: '
|
|
2090
|
-
check: (ctx) => {
|
|
2091
|
-
|
|
2333
|
+
goCoverageConfigured: {
|
|
2334
|
+
id: 120117,
|
|
2335
|
+
name: 'Go coverage reporting configured',
|
|
2336
|
+
check: (ctx) => {
|
|
2337
|
+
if (!isGoProject(ctx)) return null;
|
|
2338
|
+
const content = `${getWorkflowContent(ctx)}\n${ctx.fileContent('Makefile') || ''}`;
|
|
2339
|
+
return /go test[^\n]*-cover/i.test(content);
|
|
2340
|
+
},
|
|
2341
|
+
impact: 'medium',
|
|
2092
2342
|
category: 'go',
|
|
2093
|
-
fix: '
|
|
2343
|
+
fix: 'Add `go test -cover` to CI or developer commands so Go coverage is tracked explicitly.',
|
|
2094
2344
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2095
2345
|
confidence: 0.7,
|
|
2096
2346
|
},
|
|
2097
2347
|
|
|
2098
|
-
|
|
2099
|
-
id:
|
|
2100
|
-
name: '
|
|
2101
|
-
check: (ctx) => {
|
|
2348
|
+
goAPIFramework: {
|
|
2349
|
+
id: 120118,
|
|
2350
|
+
name: 'Go HTTP framework detected',
|
|
2351
|
+
check: (ctx) => {
|
|
2352
|
+
if (!isGoProject(ctx)) return null;
|
|
2353
|
+
return /gin-gonic\/gin|labstack\/echo|gofiber\/fiber|go-chi\/chi|gin\.Default\(|echo\.New\(|fiber\.New\(|chi\.NewRouter\(/i.test(getGoProjectText(ctx));
|
|
2354
|
+
},
|
|
2102
2355
|
impact: 'low',
|
|
2103
2356
|
category: 'go',
|
|
2104
|
-
fix: '
|
|
2357
|
+
fix: 'Use a well-supported Go HTTP framework such as Gin, Echo, Fiber, or Chi when building API services.',
|
|
2105
2358
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2106
2359
|
confidence: 0.7,
|
|
2107
2360
|
},
|
|
2108
2361
|
|
|
2109
|
-
|
|
2110
|
-
id:
|
|
2111
|
-
name: 'Go
|
|
2112
|
-
check: (ctx) => {
|
|
2362
|
+
goMigrationTool: {
|
|
2363
|
+
id: 120119,
|
|
2364
|
+
name: 'Go database migration tooling present',
|
|
2365
|
+
check: (ctx) => {
|
|
2366
|
+
if (!isGoProject(ctx)) return null;
|
|
2367
|
+
return /golang-migrate|pressly\/goose|atlasgo|atlas\s/i.test(getGoProjectText(ctx)) ||
|
|
2368
|
+
hasProjectFile(ctx, /(^|\/)(migrations|db\/migrations)\//i);
|
|
2369
|
+
},
|
|
2370
|
+
impact: 'medium',
|
|
2371
|
+
category: 'go',
|
|
2372
|
+
fix: 'Add a Go migration tool such as golang-migrate, goose, or Atlas and keep migration files in the repo.',
|
|
2373
|
+
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2374
|
+
confidence: 0.7,
|
|
2375
|
+
},
|
|
2376
|
+
|
|
2377
|
+
goDependencyInjection: {
|
|
2378
|
+
id: 120120,
|
|
2379
|
+
name: 'Go dependency injection pattern present',
|
|
2380
|
+
check: (ctx) => {
|
|
2381
|
+
if (!isGoProject(ctx)) return null;
|
|
2382
|
+
return /google\/wire|uber-go\/fx|uber-go\/dig|wire\.Build\(|fx\.New\(|dig\.New\(/i.test(getGoProjectText(ctx));
|
|
2383
|
+
},
|
|
2113
2384
|
impact: 'low',
|
|
2114
2385
|
category: 'go',
|
|
2115
|
-
fix: '
|
|
2386
|
+
fix: 'Use Wire, Fx, Dig, or an equivalent composition pattern when Go dependency graphs become complex.',
|
|
2116
2387
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2117
2388
|
confidence: 0.7,
|
|
2118
2389
|
},
|
|
@@ -2120,222 +2391,300 @@ const TECHNIQUES = {
|
|
|
2120
2391
|
// === RUST STACK CHECKS (category: 'rust') ===================
|
|
2121
2392
|
// ============================================================
|
|
2122
2393
|
|
|
2123
|
-
|
|
2124
|
-
id:
|
|
2125
|
-
name: 'Cargo.toml exists
|
|
2126
|
-
check: (ctx) => {
|
|
2394
|
+
cargoTomlExists: {
|
|
2395
|
+
id: 120201,
|
|
2396
|
+
name: 'Cargo.toml exists',
|
|
2397
|
+
check: (ctx) => {
|
|
2398
|
+
if (!isRustProject(ctx)) return null;
|
|
2399
|
+
return true;
|
|
2400
|
+
},
|
|
2127
2401
|
impact: 'high',
|
|
2128
2402
|
category: 'rust',
|
|
2129
|
-
fix: '
|
|
2403
|
+
fix: 'Add a `Cargo.toml` manifest so Rust dependencies, metadata, and build settings are tracked explicitly.',
|
|
2130
2404
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2131
2405
|
confidence: 0.7,
|
|
2132
2406
|
},
|
|
2133
2407
|
|
|
2134
|
-
|
|
2135
|
-
id:
|
|
2136
|
-
name: '
|
|
2137
|
-
check: (ctx) => {
|
|
2408
|
+
rustEdition: {
|
|
2409
|
+
id: 120202,
|
|
2410
|
+
name: 'Rust edition specified in Cargo.toml',
|
|
2411
|
+
check: (ctx) => {
|
|
2412
|
+
if (!isRustProject(ctx)) return null;
|
|
2413
|
+
return /edition\s*=\s*"20(18|21|24)"/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2414
|
+
},
|
|
2138
2415
|
impact: 'high',
|
|
2139
2416
|
category: 'rust',
|
|
2140
|
-
fix: '
|
|
2417
|
+
fix: 'Specify a Rust edition such as `edition = "2021"` in `Cargo.toml` so tooling and language semantics are pinned.',
|
|
2141
2418
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2142
2419
|
confidence: 0.7,
|
|
2143
2420
|
},
|
|
2144
2421
|
|
|
2145
|
-
|
|
2146
|
-
id:
|
|
2147
|
-
name: 'Clippy configured
|
|
2148
|
-
check: (ctx) => {
|
|
2422
|
+
rustClippy: {
|
|
2423
|
+
id: 120203,
|
|
2424
|
+
name: 'Clippy configured',
|
|
2425
|
+
check: (ctx) => {
|
|
2426
|
+
if (!isRustProject(ctx)) return null;
|
|
2427
|
+
return hasProjectFile(ctx, /(^|\/)(clippy\.toml|\.clippy\.toml)$/i) ||
|
|
2428
|
+
/clippy/i.test(`${readProjectFiles(ctx, /(^|\/)\.cargo\/config\.toml$/i)}\n${getWorkflowContent(ctx)}\n${getPreCommitContent(ctx)}`);
|
|
2429
|
+
},
|
|
2149
2430
|
impact: 'medium',
|
|
2150
2431
|
category: 'rust',
|
|
2151
|
-
fix: 'Configure clippy in CI or
|
|
2432
|
+
fix: 'Configure `cargo clippy` in CI, pre-commit, or `.cargo/config.toml` so linting is enforced consistently.',
|
|
2152
2433
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2153
2434
|
confidence: 0.7,
|
|
2154
2435
|
},
|
|
2155
2436
|
|
|
2156
|
-
|
|
2157
|
-
id:
|
|
2158
|
-
name: 'rustfmt configured
|
|
2159
|
-
check: (ctx) => {
|
|
2437
|
+
rustFmt: {
|
|
2438
|
+
id: 120204,
|
|
2439
|
+
name: 'rustfmt configured',
|
|
2440
|
+
check: (ctx) => {
|
|
2441
|
+
if (!isRustProject(ctx)) return null;
|
|
2442
|
+
return hasProjectFile(ctx, /(^|\/)(rustfmt\.toml|\.rustfmt\.toml)$/i);
|
|
2443
|
+
},
|
|
2160
2444
|
impact: 'medium',
|
|
2161
2445
|
category: 'rust',
|
|
2162
|
-
fix: '
|
|
2446
|
+
fix: 'Add `rustfmt.toml` or `.rustfmt.toml` to capture Rust formatting expectations in version control.',
|
|
2163
2447
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2164
2448
|
confidence: 0.7,
|
|
2165
2449
|
},
|
|
2166
2450
|
|
|
2167
|
-
|
|
2168
|
-
id:
|
|
2169
|
-
name: '
|
|
2170
|
-
check: (ctx) => {
|
|
2451
|
+
rustTestsExist: {
|
|
2452
|
+
id: 120205,
|
|
2453
|
+
name: 'Rust tests exist',
|
|
2454
|
+
check: (ctx) => {
|
|
2455
|
+
if (!isRustProject(ctx)) return null;
|
|
2456
|
+
const files = getRustFiles(ctx);
|
|
2457
|
+
if (files.length === 0) return null;
|
|
2458
|
+
return hasProjectFile(ctx, /(^|\/)tests\//i) ||
|
|
2459
|
+
files.some(file => /#\s*\[\s*test\s*\]/.test(ctx.fileContent(file) || ''));
|
|
2460
|
+
},
|
|
2171
2461
|
impact: 'high',
|
|
2172
2462
|
category: 'rust',
|
|
2173
|
-
fix: '
|
|
2463
|
+
fix: 'Add Rust unit or integration tests using `#[test]` functions or a `tests/` directory.',
|
|
2174
2464
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2175
2465
|
confidence: 0.7,
|
|
2176
2466
|
},
|
|
2177
2467
|
|
|
2178
|
-
|
|
2179
|
-
id:
|
|
2180
|
-
name: '
|
|
2181
|
-
check: (ctx) => {
|
|
2182
|
-
|
|
2468
|
+
rustBenchmarks: {
|
|
2469
|
+
id: 120206,
|
|
2470
|
+
name: 'Rust benchmarks present',
|
|
2471
|
+
check: (ctx) => {
|
|
2472
|
+
if (!isRustProject(ctx)) return null;
|
|
2473
|
+
return hasProjectFile(ctx, /(^|\/)benches\//i) ||
|
|
2474
|
+
/#\s*\[\s*bench\s*\]|criterion/i.test(getRustProjectText(ctx));
|
|
2475
|
+
},
|
|
2476
|
+
impact: 'low',
|
|
2183
2477
|
category: 'rust',
|
|
2184
|
-
fix: '
|
|
2478
|
+
fix: 'Add Rust benchmarks through `benches/`, `criterion`, or benchmark annotations when performance-sensitive code matters.',
|
|
2185
2479
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2186
2480
|
confidence: 0.7,
|
|
2187
2481
|
},
|
|
2188
2482
|
|
|
2189
|
-
|
|
2190
|
-
id:
|
|
2191
|
-
name: '
|
|
2192
|
-
check: (ctx) => {
|
|
2483
|
+
rustCIConfigured: {
|
|
2484
|
+
id: 120207,
|
|
2485
|
+
name: 'CI runs cargo test',
|
|
2486
|
+
check: (ctx) => {
|
|
2487
|
+
if (!isRustProject(ctx)) return null;
|
|
2488
|
+
return /cargo test(\s|$)/i.test(getWorkflowContent(ctx));
|
|
2489
|
+
},
|
|
2193
2490
|
impact: 'high',
|
|
2194
2491
|
category: 'rust',
|
|
2195
|
-
fix: '
|
|
2492
|
+
fix: 'Run `cargo test` in CI so Rust correctness is verified automatically on every change.',
|
|
2196
2493
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2197
2494
|
confidence: 0.7,
|
|
2198
2495
|
},
|
|
2199
2496
|
|
|
2200
|
-
|
|
2201
|
-
id:
|
|
2202
|
-
name: '
|
|
2203
|
-
check: (ctx) => {
|
|
2497
|
+
rustCargoLock: {
|
|
2498
|
+
id: 120208,
|
|
2499
|
+
name: 'Cargo.lock handling is appropriate',
|
|
2500
|
+
check: (ctx) => {
|
|
2501
|
+
if (!isRustProject(ctx)) return null;
|
|
2502
|
+
const cargoText = readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i);
|
|
2503
|
+
const hasLock = hasProjectFile(ctx, /(^|\/)Cargo\.lock$/i);
|
|
2504
|
+
const gitignore = ctx.fileContent('.gitignore') || '';
|
|
2505
|
+
const libraryOnly = /\[lib\]/i.test(cargoText) && !/\[\[bin\]\]|src\/main\.rs/i.test(getRustProjectText(ctx));
|
|
2506
|
+
if (libraryOnly) return hasLock || /(^|\r?\n)\s*Cargo\.lock\s*$/m.test(gitignore);
|
|
2507
|
+
return hasLock;
|
|
2508
|
+
},
|
|
2204
2509
|
impact: 'medium',
|
|
2205
2510
|
category: 'rust',
|
|
2206
|
-
fix: '
|
|
2511
|
+
fix: 'Commit `Cargo.lock` for binaries, or explicitly ignore it for library-only crates when that is your chosen policy.',
|
|
2207
2512
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2208
2513
|
confidence: 0.7,
|
|
2209
2514
|
},
|
|
2210
2515
|
|
|
2211
|
-
|
|
2212
|
-
id:
|
|
2213
|
-
name: '
|
|
2214
|
-
check: (ctx) => {
|
|
2516
|
+
rustUnsafeBlocks: {
|
|
2517
|
+
id: 120209,
|
|
2518
|
+
name: 'Unsafe blocks are documented',
|
|
2519
|
+
check: (ctx) => {
|
|
2520
|
+
if (!isRustProject(ctx)) return null;
|
|
2521
|
+
const files = getRustFiles(ctx);
|
|
2522
|
+
const unsafeFiles = files.filter(file => /\bunsafe\b/.test(ctx.fileContent(file) || ''));
|
|
2523
|
+
if (unsafeFiles.length === 0) return true;
|
|
2524
|
+
return unsafeFiles.every(file => /SAFETY:|\/\/\s*SAFETY|\/\*\s*SAFETY/i.test(ctx.fileContent(file) || ''));
|
|
2525
|
+
},
|
|
2215
2526
|
impact: 'medium',
|
|
2216
2527
|
category: 'rust',
|
|
2217
|
-
fix: 'Document
|
|
2528
|
+
fix: 'Document each `unsafe` block with a nearby `SAFETY:` comment explaining the invariants being upheld.',
|
|
2218
2529
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2219
2530
|
confidence: 0.7,
|
|
2220
2531
|
},
|
|
2221
2532
|
|
|
2222
|
-
|
|
2223
|
-
id:
|
|
2224
|
-
name: '
|
|
2225
|
-
check: (ctx) => {
|
|
2533
|
+
rustErrorHandling: {
|
|
2534
|
+
id: 120210,
|
|
2535
|
+
name: 'Rust error handling strategy present',
|
|
2536
|
+
check: (ctx) => {
|
|
2537
|
+
if (!isRustProject(ctx)) return null;
|
|
2538
|
+
return /thiserror|anyhow|eyre|impl\s+std::error::Error|enum\s+\w+Error|struct\s+\w+Error/i.test(getRustProjectText(ctx));
|
|
2539
|
+
},
|
|
2226
2540
|
impact: 'medium',
|
|
2227
2541
|
category: 'rust',
|
|
2228
|
-
fix: '
|
|
2542
|
+
fix: 'Use `thiserror`, `anyhow`, or explicit error types so Rust errors remain structured and descriptive.',
|
|
2229
2543
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2230
2544
|
confidence: 0.7,
|
|
2231
2545
|
},
|
|
2232
2546
|
|
|
2233
|
-
|
|
2234
|
-
id:
|
|
2235
|
-
name: '
|
|
2236
|
-
check: (ctx) => {
|
|
2547
|
+
rustAsync: {
|
|
2548
|
+
id: 120211,
|
|
2549
|
+
name: 'Rust async runtime configured',
|
|
2550
|
+
check: (ctx) => {
|
|
2551
|
+
if (!isRustProject(ctx)) return null;
|
|
2552
|
+
return /tokio|async-std|smol/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2553
|
+
},
|
|
2237
2554
|
impact: 'medium',
|
|
2238
2555
|
category: 'rust',
|
|
2239
|
-
fix: '
|
|
2556
|
+
fix: 'Declare an async runtime such as Tokio or async-std when the Rust project uses asynchronous workflows.',
|
|
2240
2557
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2241
2558
|
confidence: 0.7,
|
|
2242
2559
|
},
|
|
2243
2560
|
|
|
2244
|
-
|
|
2245
|
-
id:
|
|
2246
|
-
name: '
|
|
2247
|
-
check: (ctx) => {
|
|
2561
|
+
rustSerde: {
|
|
2562
|
+
id: 120212,
|
|
2563
|
+
name: 'Serde serialization configured',
|
|
2564
|
+
check: (ctx) => {
|
|
2565
|
+
if (!isRustProject(ctx)) return null;
|
|
2566
|
+
return /\bserde(_json|_yaml)?\b/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2567
|
+
},
|
|
2248
2568
|
impact: 'low',
|
|
2249
2569
|
category: 'rust',
|
|
2250
|
-
fix: '
|
|
2570
|
+
fix: 'Add `serde` and related crates when Rust data crosses process, storage, or network boundaries.',
|
|
2251
2571
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2252
2572
|
confidence: 0.7,
|
|
2253
2573
|
},
|
|
2254
2574
|
|
|
2255
|
-
|
|
2256
|
-
id:
|
|
2257
|
-
name: '
|
|
2258
|
-
check: (ctx) => {
|
|
2575
|
+
rustDocComments: {
|
|
2576
|
+
id: 120213,
|
|
2577
|
+
name: 'Public Rust items have doc comments',
|
|
2578
|
+
check: (ctx) => {
|
|
2579
|
+
if (!isRustProject(ctx)) return null;
|
|
2580
|
+
const files = getMainRustFiles(ctx);
|
|
2581
|
+
const exported = files.some(file => /\bpub\s+(fn|struct|enum|trait|mod|const|type)\b/.test(ctx.fileContent(file) || ''));
|
|
2582
|
+
if (!exported) return null;
|
|
2583
|
+
return files.some(file => /\/\/\/[^\n]*\n\s*pub\s+(fn|struct|enum|trait|mod|const|type)\b/.test(ctx.fileContent(file) || ''));
|
|
2584
|
+
},
|
|
2259
2585
|
impact: 'low',
|
|
2260
2586
|
category: 'rust',
|
|
2261
|
-
fix: '
|
|
2587
|
+
fix: 'Add `///` doc comments above public Rust APIs so crates are easier to consume and maintain.',
|
|
2262
2588
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2263
2589
|
confidence: 0.7,
|
|
2264
2590
|
},
|
|
2265
2591
|
|
|
2266
|
-
|
|
2267
|
-
id:
|
|
2268
|
-
name: '
|
|
2269
|
-
check: (ctx) => {
|
|
2270
|
-
|
|
2592
|
+
rustSecurityAudit: {
|
|
2593
|
+
id: 120214,
|
|
2594
|
+
name: 'Rust security audit tooling configured',
|
|
2595
|
+
check: (ctx) => {
|
|
2596
|
+
if (!isRustProject(ctx)) return null;
|
|
2597
|
+
return /cargo-audit|cargo deny|cargo-deny/i.test(getRustProjectText(ctx));
|
|
2598
|
+
},
|
|
2599
|
+
impact: 'medium',
|
|
2271
2600
|
category: 'rust',
|
|
2272
|
-
fix: '
|
|
2601
|
+
fix: 'Configure `cargo-audit` or `cargo-deny` in CI or project automation to scan Rust dependencies for risk.',
|
|
2273
2602
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2274
2603
|
confidence: 0.7,
|
|
2275
2604
|
},
|
|
2276
2605
|
|
|
2277
|
-
|
|
2278
|
-
id:
|
|
2279
|
-
name: '
|
|
2280
|
-
check: (ctx) => {
|
|
2606
|
+
rustMSRV: {
|
|
2607
|
+
id: 120215,
|
|
2608
|
+
name: 'Minimum supported Rust version specified',
|
|
2609
|
+
check: (ctx) => {
|
|
2610
|
+
if (!isRustProject(ctx)) return null;
|
|
2611
|
+
return /rust-version\s*=/.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2612
|
+
},
|
|
2281
2613
|
impact: 'medium',
|
|
2282
2614
|
category: 'rust',
|
|
2283
|
-
fix: '
|
|
2615
|
+
fix: 'Set `rust-version` in `Cargo.toml` so the project’s MSRV is explicit for contributors and CI.',
|
|
2284
2616
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2285
2617
|
confidence: 0.7,
|
|
2286
2618
|
},
|
|
2287
2619
|
|
|
2288
|
-
|
|
2289
|
-
id:
|
|
2290
|
-
name: '
|
|
2291
|
-
check: (ctx) => {
|
|
2620
|
+
rustWorkspace: {
|
|
2621
|
+
id: 120216,
|
|
2622
|
+
name: 'Cargo workspace configured for multi-crate projects',
|
|
2623
|
+
check: (ctx) => {
|
|
2624
|
+
if (!isRustProject(ctx)) return null;
|
|
2625
|
+
const cargoFiles = findProjectFiles(ctx, /(^|\/)Cargo\.toml$/i);
|
|
2626
|
+
if (cargoFiles.length <= 1) return null;
|
|
2627
|
+
return /\[workspace\]/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2628
|
+
},
|
|
2292
2629
|
impact: 'medium',
|
|
2293
2630
|
category: 'rust',
|
|
2294
|
-
fix: '
|
|
2631
|
+
fix: 'Add a root Cargo workspace when the Rust repository contains multiple crates.',
|
|
2295
2632
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2296
2633
|
confidence: 0.7,
|
|
2297
2634
|
},
|
|
2298
2635
|
|
|
2299
|
-
|
|
2300
|
-
id:
|
|
2301
|
-
name: '
|
|
2302
|
-
check: (ctx) => {
|
|
2303
|
-
|
|
2636
|
+
rustBuildScript: {
|
|
2637
|
+
id: 120217,
|
|
2638
|
+
name: 'Rust build script present when needed',
|
|
2639
|
+
check: (ctx) => {
|
|
2640
|
+
if (!isRustProject(ctx)) return null;
|
|
2641
|
+
return hasProjectFile(ctx, /(^|\/)build\.rs$/i);
|
|
2642
|
+
},
|
|
2643
|
+
impact: 'low',
|
|
2304
2644
|
category: 'rust',
|
|
2305
|
-
fix: '
|
|
2645
|
+
fix: 'Use `build.rs` when the project needs generated bindings, codegen, or compile-time environment setup.',
|
|
2306
2646
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2307
2647
|
confidence: 0.7,
|
|
2308
2648
|
},
|
|
2309
2649
|
|
|
2310
|
-
|
|
2311
|
-
id:
|
|
2312
|
-
name: '
|
|
2313
|
-
check: (ctx) => {
|
|
2314
|
-
|
|
2650
|
+
rustFeatureFlags: {
|
|
2651
|
+
id: 120218,
|
|
2652
|
+
name: 'Rust feature flags defined',
|
|
2653
|
+
check: (ctx) => {
|
|
2654
|
+
if (!isRustProject(ctx)) return null;
|
|
2655
|
+
return /\[features\]/i.test(readProjectFiles(ctx, /(^|\/)Cargo\.toml$/i));
|
|
2656
|
+
},
|
|
2657
|
+
impact: 'low',
|
|
2315
2658
|
category: 'rust',
|
|
2316
|
-
fix: '
|
|
2659
|
+
fix: 'Define Cargo feature flags when Rust functionality needs optional capabilities or slimmed dependency sets.',
|
|
2317
2660
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2318
2661
|
confidence: 0.7,
|
|
2319
2662
|
},
|
|
2320
2663
|
|
|
2321
|
-
|
|
2322
|
-
id:
|
|
2323
|
-
name: '
|
|
2324
|
-
check: (ctx) => {
|
|
2664
|
+
rustCrossCompilation: {
|
|
2665
|
+
id: 120219,
|
|
2666
|
+
name: 'Rust cross-compilation targets configured',
|
|
2667
|
+
check: (ctx) => {
|
|
2668
|
+
if (!isRustProject(ctx)) return null;
|
|
2669
|
+
return /--target|rustup target add|target\.[\w.-]+|cross build|cross test|cargo zigbuild/i.test(getRustProjectText(ctx));
|
|
2670
|
+
},
|
|
2325
2671
|
impact: 'low',
|
|
2326
2672
|
category: 'rust',
|
|
2327
|
-
fix: '
|
|
2673
|
+
fix: 'Configure Rust cross-compilation targets in CI or `.cargo/config.toml` when builds must run across architectures or platforms.',
|
|
2328
2674
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2329
2675
|
confidence: 0.7,
|
|
2330
2676
|
},
|
|
2331
2677
|
|
|
2332
|
-
|
|
2333
|
-
id:
|
|
2334
|
-
name: 'Rust
|
|
2335
|
-
check: (ctx) => {
|
|
2336
|
-
|
|
2678
|
+
rustContainerized: {
|
|
2679
|
+
id: 120220,
|
|
2680
|
+
name: 'Rust Dockerfile present',
|
|
2681
|
+
check: (ctx) => {
|
|
2682
|
+
if (!isRustProject(ctx)) return null;
|
|
2683
|
+
return /FROM\s+rust|cargo\s+(build|chef|install|test)/i.test(ctx.fileContent('Dockerfile') || '');
|
|
2684
|
+
},
|
|
2685
|
+
impact: 'low',
|
|
2337
2686
|
category: 'rust',
|
|
2338
|
-
fix: '
|
|
2687
|
+
fix: 'Use a Dockerfile that references Rust or Cargo when the project’s build and release flow is containerized.',
|
|
2339
2688
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2340
2689
|
confidence: 0.7,
|
|
2341
2690
|
},
|
|
@@ -2344,222 +2693,294 @@ const TECHNIQUES = {
|
|
|
2344
2693
|
// === JAVA/SPRING STACK CHECKS (category: 'java') ============
|
|
2345
2694
|
// ============================================================
|
|
2346
2695
|
|
|
2347
|
-
|
|
2348
|
-
id:
|
|
2349
|
-
name: '
|
|
2350
|
-
check: (ctx) => {
|
|
2696
|
+
mavenOrGradle: {
|
|
2697
|
+
id: 120301,
|
|
2698
|
+
name: 'Maven or Gradle build file exists',
|
|
2699
|
+
check: (ctx) => {
|
|
2700
|
+
if (!isJavaProject(ctx)) return null;
|
|
2701
|
+
return true;
|
|
2702
|
+
},
|
|
2351
2703
|
impact: 'high',
|
|
2352
2704
|
category: 'java',
|
|
2353
|
-
fix: '
|
|
2705
|
+
fix: 'Add `pom.xml`, `build.gradle`, or `build.gradle.kts` so the Java build is defined in version control.',
|
|
2354
2706
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2355
2707
|
confidence: 0.7,
|
|
2356
2708
|
},
|
|
2357
2709
|
|
|
2358
|
-
|
|
2359
|
-
id:
|
|
2710
|
+
javaVersion: {
|
|
2711
|
+
id: 120302,
|
|
2360
2712
|
name: 'Java version specified',
|
|
2361
|
-
check: (ctx) => {
|
|
2713
|
+
check: (ctx) => {
|
|
2714
|
+
if (!isJavaProject(ctx)) return null;
|
|
2715
|
+
return /java\.version|maven\.compiler\.(source|target|release)|sourceCompatibility|targetCompatibility|JavaLanguageVersion|toolchain/i.test(getJavaBuildText(ctx)) ||
|
|
2716
|
+
hasProjectFile(ctx, /(^|\/)\.java-version$/i);
|
|
2717
|
+
},
|
|
2362
2718
|
impact: 'high',
|
|
2363
2719
|
category: 'java',
|
|
2364
|
-
fix: 'Specify Java version in
|
|
2720
|
+
fix: 'Specify the Java version in Maven or Gradle so compilation and runtime expectations stay explicit.',
|
|
2365
2721
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2366
2722
|
confidence: 0.7,
|
|
2367
2723
|
},
|
|
2368
2724
|
|
|
2369
|
-
|
|
2370
|
-
id:
|
|
2371
|
-
name: '
|
|
2372
|
-
check: (ctx) => {
|
|
2373
|
-
|
|
2725
|
+
springBootDetected: {
|
|
2726
|
+
id: 120303,
|
|
2727
|
+
name: 'Spring Boot detected',
|
|
2728
|
+
check: (ctx) => {
|
|
2729
|
+
if (!isJavaProject(ctx)) return null;
|
|
2730
|
+
return /spring-boot/i.test(getJavaBuildText(ctx));
|
|
2731
|
+
},
|
|
2732
|
+
impact: 'medium',
|
|
2374
2733
|
category: 'java',
|
|
2375
|
-
fix: '
|
|
2734
|
+
fix: 'Use Spring Boot dependencies when the Java service relies on Spring auto-configuration and conventions.',
|
|
2376
2735
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2377
2736
|
confidence: 0.7,
|
|
2378
2737
|
},
|
|
2379
2738
|
|
|
2380
|
-
|
|
2381
|
-
id:
|
|
2382
|
-
name: '
|
|
2383
|
-
check: (ctx) => {
|
|
2739
|
+
javaTestFramework: {
|
|
2740
|
+
id: 120304,
|
|
2741
|
+
name: 'Java test framework configured',
|
|
2742
|
+
check: (ctx) => {
|
|
2743
|
+
if (!isJavaProject(ctx)) return null;
|
|
2744
|
+
return /junit|testng|spring-boot-starter-test/i.test(getJavaBuildText(ctx));
|
|
2745
|
+
},
|
|
2384
2746
|
impact: 'high',
|
|
2385
2747
|
category: 'java',
|
|
2386
|
-
fix: '
|
|
2748
|
+
fix: 'Add JUnit, TestNG, or Spring Boot test dependencies so Java tests have a standard runner.',
|
|
2387
2749
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2388
2750
|
confidence: 0.7,
|
|
2389
2751
|
},
|
|
2390
2752
|
|
|
2391
|
-
|
|
2392
|
-
id:
|
|
2393
|
-
name: '
|
|
2394
|
-
check: (ctx) => {
|
|
2753
|
+
javaLinter: {
|
|
2754
|
+
id: 120305,
|
|
2755
|
+
name: 'Java linter configured',
|
|
2756
|
+
check: (ctx) => {
|
|
2757
|
+
if (!isJavaProject(ctx)) return null;
|
|
2758
|
+
return /checkstyle|spotbugs|pmd/i.test(getJavaProjectText(ctx)) ||
|
|
2759
|
+
hasProjectFile(ctx, /(^|\/)(checkstyle\.xml|spotbugs.*\.xml|pmd\.xml)$/i);
|
|
2760
|
+
},
|
|
2395
2761
|
impact: 'medium',
|
|
2396
2762
|
category: 'java',
|
|
2397
|
-
fix: '
|
|
2763
|
+
fix: 'Configure Checkstyle, SpotBugs, or PMD so Java code quality rules run consistently.',
|
|
2398
2764
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2399
2765
|
confidence: 0.7,
|
|
2400
2766
|
},
|
|
2401
2767
|
|
|
2402
|
-
|
|
2403
|
-
id:
|
|
2404
|
-
name: '
|
|
2405
|
-
check: (ctx) => {
|
|
2406
|
-
|
|
2768
|
+
javaFormatter: {
|
|
2769
|
+
id: 120306,
|
|
2770
|
+
name: 'Java formatter configured',
|
|
2771
|
+
check: (ctx) => {
|
|
2772
|
+
if (!isJavaProject(ctx)) return null;
|
|
2773
|
+
return /google-java-format|spotless/i.test(getJavaBuildText(ctx)) ||
|
|
2774
|
+
hasProjectFile(ctx, /(^|\/)\.editorconfig$/i);
|
|
2775
|
+
},
|
|
2776
|
+
impact: 'medium',
|
|
2407
2777
|
category: 'java',
|
|
2408
|
-
fix: 'Configure
|
|
2778
|
+
fix: 'Configure Spotless, google-java-format, or an `.editorconfig` so Java formatting stays consistent.',
|
|
2409
2779
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2410
2780
|
confidence: 0.7,
|
|
2411
2781
|
},
|
|
2412
2782
|
|
|
2413
|
-
|
|
2414
|
-
id:
|
|
2415
|
-
name: '
|
|
2416
|
-
check: (ctx) => {
|
|
2417
|
-
|
|
2783
|
+
javaCIConfigured: {
|
|
2784
|
+
id: 120307,
|
|
2785
|
+
name: 'CI runs Java tests',
|
|
2786
|
+
check: (ctx) => {
|
|
2787
|
+
if (!isJavaProject(ctx)) return null;
|
|
2788
|
+
return /(?:mvn|mvnw)\s+test|(?:gradle|gradlew)\s+test/i.test(getWorkflowContent(ctx));
|
|
2789
|
+
},
|
|
2790
|
+
impact: 'high',
|
|
2418
2791
|
category: 'java',
|
|
2419
|
-
fix: '
|
|
2792
|
+
fix: 'Run `mvn test`, `mvnw test`, `gradle test`, or `gradlew test` in CI so Java changes are validated automatically.',
|
|
2420
2793
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2421
2794
|
confidence: 0.7,
|
|
2422
2795
|
},
|
|
2423
2796
|
|
|
2424
|
-
|
|
2425
|
-
id:
|
|
2426
|
-
name: '
|
|
2427
|
-
check: (ctx) => {
|
|
2797
|
+
javaSecurityScanner: {
|
|
2798
|
+
id: 120308,
|
|
2799
|
+
name: 'Java security scanner configured',
|
|
2800
|
+
check: (ctx) => {
|
|
2801
|
+
if (!isJavaProject(ctx)) return null;
|
|
2802
|
+
return /dependency-check|snyk|spotbugs-security|findsecbugs/i.test(getJavaProjectText(ctx));
|
|
2803
|
+
},
|
|
2428
2804
|
impact: 'medium',
|
|
2429
2805
|
category: 'java',
|
|
2430
|
-
fix: '
|
|
2806
|
+
fix: 'Configure OWASP Dependency-Check, Snyk, or SpotBugs security rules for Java dependency and code scanning.',
|
|
2431
2807
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2432
2808
|
confidence: 0.7,
|
|
2433
2809
|
},
|
|
2434
2810
|
|
|
2435
|
-
|
|
2436
|
-
id:
|
|
2437
|
-
name: '
|
|
2438
|
-
check: (ctx) => {
|
|
2439
|
-
|
|
2811
|
+
javaDocumentation: {
|
|
2812
|
+
id: 120309,
|
|
2813
|
+
name: 'Java documentation configured',
|
|
2814
|
+
check: (ctx) => {
|
|
2815
|
+
if (!isJavaProject(ctx)) return null;
|
|
2816
|
+
if (/javadoc/i.test(getJavaBuildText(ctx))) return true;
|
|
2817
|
+
const files = getMainJavaFiles(ctx);
|
|
2818
|
+
const publicTypes = files.some(file => /\bpublic\s+(class|interface|enum|record)\s+[A-Z]\w*/.test(ctx.fileContent(file) || ''));
|
|
2819
|
+
if (!publicTypes) return null;
|
|
2820
|
+
return files.some(file => /\/\*\*[\s\S]*?\*\/\s*public\s+(class|interface|enum|record)\s+[A-Z]\w*/.test(ctx.fileContent(file) || ''));
|
|
2821
|
+
},
|
|
2822
|
+
impact: 'low',
|
|
2440
2823
|
category: 'java',
|
|
2441
|
-
fix: '
|
|
2824
|
+
fix: 'Generate Javadocs or add doc comments on public Java types so the API remains understandable to contributors.',
|
|
2442
2825
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2443
2826
|
confidence: 0.7,
|
|
2444
2827
|
},
|
|
2445
2828
|
|
|
2446
|
-
|
|
2447
|
-
id:
|
|
2448
|
-
name: '
|
|
2449
|
-
check: (ctx) => {
|
|
2829
|
+
javaProfiles: {
|
|
2830
|
+
id: 120310,
|
|
2831
|
+
name: 'Java profiles or build variants configured',
|
|
2832
|
+
check: (ctx) => {
|
|
2833
|
+
if (!isJavaProject(ctx)) return null;
|
|
2834
|
+
return /<profiles>|spring\.profiles|@Profile|profiles\s*\{|buildTypes\s*\{|productFlavors\s*\{/i.test(getJavaProjectText(ctx));
|
|
2835
|
+
},
|
|
2450
2836
|
impact: 'low',
|
|
2451
2837
|
category: 'java',
|
|
2452
|
-
fix: '
|
|
2838
|
+
fix: 'Use Maven profiles, Spring profiles, or Gradle build variants when Java environments need explicit separation.',
|
|
2453
2839
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2454
2840
|
confidence: 0.7,
|
|
2455
2841
|
},
|
|
2456
2842
|
|
|
2457
|
-
|
|
2458
|
-
id:
|
|
2459
|
-
name: '
|
|
2460
|
-
check: (ctx) => {
|
|
2461
|
-
|
|
2843
|
+
javaContainerized: {
|
|
2844
|
+
id: 120311,
|
|
2845
|
+
name: 'Java Dockerfile references Java build/runtime',
|
|
2846
|
+
check: (ctx) => {
|
|
2847
|
+
if (!isJavaProject(ctx)) return null;
|
|
2848
|
+
return /FROM\s+(?:maven|gradle|openjdk|eclipse-temurin|amazoncorretto)|\bjava\b|\bmvn\b|\bgradle\b/i.test(ctx.fileContent('Dockerfile') || '');
|
|
2849
|
+
},
|
|
2850
|
+
impact: 'low',
|
|
2462
2851
|
category: 'java',
|
|
2463
|
-
fix: '
|
|
2852
|
+
fix: 'Use a Dockerfile or build image that references Java, Maven, or Gradle when the application is containerized.',
|
|
2464
2853
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2465
2854
|
confidence: 0.7,
|
|
2466
2855
|
},
|
|
2467
2856
|
|
|
2468
|
-
|
|
2469
|
-
id:
|
|
2470
|
-
name: '
|
|
2471
|
-
check: (ctx) => {
|
|
2472
|
-
|
|
2857
|
+
javaAPIFramework: {
|
|
2858
|
+
id: 120312,
|
|
2859
|
+
name: 'Java API framework detected',
|
|
2860
|
+
check: (ctx) => {
|
|
2861
|
+
if (!isJavaProject(ctx)) return null;
|
|
2862
|
+
return /spring-web|spring-boot-starter-web|@RestController|@Controller|javax\.ws\.rs|jakarta\.ws\.rs|micronaut-http|io\.micronaut/i.test(getJavaProjectText(ctx));
|
|
2863
|
+
},
|
|
2864
|
+
impact: 'low',
|
|
2473
2865
|
category: 'java',
|
|
2474
|
-
fix: '
|
|
2866
|
+
fix: 'Use Spring MVC, JAX-RS, or Micronaut conventions explicitly when the Java project exposes an API.',
|
|
2475
2867
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2476
2868
|
confidence: 0.7,
|
|
2477
2869
|
},
|
|
2478
2870
|
|
|
2479
|
-
|
|
2480
|
-
id:
|
|
2481
|
-
name: '
|
|
2482
|
-
check: (ctx) => {
|
|
2871
|
+
javaMigrations: {
|
|
2872
|
+
id: 120313,
|
|
2873
|
+
name: 'Java database migration tooling present',
|
|
2874
|
+
check: (ctx) => {
|
|
2875
|
+
if (!isJavaProject(ctx)) return null;
|
|
2876
|
+
return /flyway|liquibase/i.test(getJavaProjectText(ctx)) ||
|
|
2877
|
+
hasProjectFile(ctx, /(^|\/)(db\/migration|db\/migrations|migrations)\//i) ||
|
|
2878
|
+
hasProjectFile(ctx, /(^|\/)(schema|data)\.sql$/i);
|
|
2879
|
+
},
|
|
2483
2880
|
impact: 'medium',
|
|
2484
2881
|
category: 'java',
|
|
2485
|
-
fix: '
|
|
2882
|
+
fix: 'Add Flyway, Liquibase, or repo-managed migration files so Java schema changes are repeatable and reviewable.',
|
|
2486
2883
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2487
2884
|
confidence: 0.7,
|
|
2488
2885
|
},
|
|
2489
2886
|
|
|
2490
|
-
|
|
2491
|
-
id:
|
|
2492
|
-
name: '
|
|
2493
|
-
check: (ctx) => {
|
|
2494
|
-
|
|
2887
|
+
javaMessageQueue: {
|
|
2888
|
+
id: 120314,
|
|
2889
|
+
name: 'Java message queue integration detected',
|
|
2890
|
+
check: (ctx) => {
|
|
2891
|
+
if (!isJavaProject(ctx)) return null;
|
|
2892
|
+
return /kafka|rabbitmq|amqp|jms|spring-kafka|spring-rabbit/i.test(getJavaProjectText(ctx));
|
|
2893
|
+
},
|
|
2894
|
+
impact: 'low',
|
|
2495
2895
|
category: 'java',
|
|
2496
|
-
fix: '
|
|
2896
|
+
fix: 'Use explicit Kafka, RabbitMQ, or JMS integrations when the Java service relies on asynchronous messaging.',
|
|
2497
2897
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2498
2898
|
confidence: 0.7,
|
|
2499
2899
|
},
|
|
2500
2900
|
|
|
2501
|
-
|
|
2502
|
-
id:
|
|
2503
|
-
name: '
|
|
2504
|
-
check: (ctx) => {
|
|
2505
|
-
|
|
2901
|
+
javaCaching: {
|
|
2902
|
+
id: 120315,
|
|
2903
|
+
name: 'Java caching configured',
|
|
2904
|
+
check: (ctx) => {
|
|
2905
|
+
if (!isJavaProject(ctx)) return null;
|
|
2906
|
+
return /redis|ehcache|spring-cache|@Cacheable|caffeine/i.test(getJavaProjectText(ctx));
|
|
2907
|
+
},
|
|
2908
|
+
impact: 'low',
|
|
2506
2909
|
category: 'java',
|
|
2507
|
-
fix: 'Configure
|
|
2910
|
+
fix: 'Configure Redis, Ehcache, Caffeine, or Spring Cache when Java services benefit from explicit caching layers.',
|
|
2508
2911
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2509
2912
|
confidence: 0.7,
|
|
2510
2913
|
},
|
|
2511
2914
|
|
|
2512
|
-
|
|
2513
|
-
id:
|
|
2514
|
-
name: '
|
|
2515
|
-
check: (ctx) => {
|
|
2915
|
+
javaMonitoring: {
|
|
2916
|
+
id: 120316,
|
|
2917
|
+
name: 'Java monitoring dependencies detected',
|
|
2918
|
+
check: (ctx) => {
|
|
2919
|
+
if (!isJavaProject(ctx)) return null;
|
|
2920
|
+
return /actuator|micrometer|prometheus/i.test(getJavaProjectText(ctx));
|
|
2921
|
+
},
|
|
2516
2922
|
impact: 'medium',
|
|
2517
2923
|
category: 'java',
|
|
2518
|
-
fix: '
|
|
2924
|
+
fix: 'Add Actuator, Micrometer, or Prometheus integrations so Java services expose health and metrics data.',
|
|
2519
2925
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2520
2926
|
confidence: 0.7,
|
|
2521
2927
|
},
|
|
2522
2928
|
|
|
2523
|
-
|
|
2524
|
-
id:
|
|
2525
|
-
name: '
|
|
2526
|
-
check: (ctx) => {
|
|
2929
|
+
javaLogging: {
|
|
2930
|
+
id: 120317,
|
|
2931
|
+
name: 'Java logging configured',
|
|
2932
|
+
check: (ctx) => {
|
|
2933
|
+
if (!isJavaProject(ctx)) return null;
|
|
2934
|
+
return /slf4j|logback|log4j/i.test(getJavaProjectText(ctx)) ||
|
|
2935
|
+
hasProjectFile(ctx, /(^|\/)(logback.*\.xml|log4j2?.*\.xml)$/i);
|
|
2936
|
+
},
|
|
2527
2937
|
impact: 'medium',
|
|
2528
2938
|
category: 'java',
|
|
2529
|
-
fix: '
|
|
2939
|
+
fix: 'Use SLF4J, Logback, or Log4j so Java application logging is explicit and configurable.',
|
|
2530
2940
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2531
2941
|
confidence: 0.7,
|
|
2532
2942
|
},
|
|
2533
2943
|
|
|
2534
|
-
|
|
2535
|
-
id:
|
|
2536
|
-
name: '
|
|
2537
|
-
check: (ctx) => {
|
|
2538
|
-
|
|
2944
|
+
javaMultiModule: {
|
|
2945
|
+
id: 120318,
|
|
2946
|
+
name: 'Java multi-module structure configured',
|
|
2947
|
+
check: (ctx) => {
|
|
2948
|
+
if (!isJavaProject(ctx)) return null;
|
|
2949
|
+
const buildFiles = findProjectFiles(ctx, /(^|\/)(pom\.xml|build\.gradle|build\.gradle\.kts)$/i);
|
|
2950
|
+
if (buildFiles.length <= 1) return null;
|
|
2951
|
+
return /<modules>|include\s*\(|include\s+['":]/i.test(getJavaBuildText(ctx));
|
|
2952
|
+
},
|
|
2953
|
+
impact: 'medium',
|
|
2539
2954
|
category: 'java',
|
|
2540
|
-
fix: '
|
|
2955
|
+
fix: 'Configure a root Maven or Gradle multi-module definition when the Java repository contains multiple modules.',
|
|
2541
2956
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2542
2957
|
confidence: 0.7,
|
|
2543
2958
|
},
|
|
2544
2959
|
|
|
2545
|
-
|
|
2546
|
-
id:
|
|
2547
|
-
name: '
|
|
2548
|
-
check: (ctx) => {
|
|
2960
|
+
javaDependencyInjection: {
|
|
2961
|
+
id: 120319,
|
|
2962
|
+
name: 'Java dependency injection pattern present',
|
|
2963
|
+
check: (ctx) => {
|
|
2964
|
+
if (!isJavaProject(ctx)) return null;
|
|
2965
|
+
return /spring-context|guice|dagger|@Autowired|@Inject|@Bean|@Component|@Service/i.test(getJavaProjectText(ctx));
|
|
2966
|
+
},
|
|
2549
2967
|
impact: 'medium',
|
|
2550
2968
|
category: 'java',
|
|
2551
|
-
fix: '
|
|
2969
|
+
fix: 'Use Spring DI, Guice, or Dagger patterns so Java object graphs stay explicit and testable.',
|
|
2552
2970
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2553
2971
|
confidence: 0.7,
|
|
2554
2972
|
},
|
|
2555
2973
|
|
|
2556
|
-
|
|
2557
|
-
id:
|
|
2558
|
-
name: '
|
|
2559
|
-
check: (ctx) => {
|
|
2560
|
-
|
|
2974
|
+
javaPropertyFiles: {
|
|
2975
|
+
id: 120320,
|
|
2976
|
+
name: 'Java application property files exist',
|
|
2977
|
+
check: (ctx) => {
|
|
2978
|
+
if (!isJavaProject(ctx)) return null;
|
|
2979
|
+
return hasProjectFile(ctx, /(^|\/)application\.(properties|ya?ml)$/i);
|
|
2980
|
+
},
|
|
2981
|
+
impact: 'low',
|
|
2561
2982
|
category: 'java',
|
|
2562
|
-
fix: '
|
|
2983
|
+
fix: 'Add `application.properties` or `application.yml` when the Java service relies on conventional runtime configuration files.',
|
|
2563
2984
|
// sourceUrl assigned by attachSourceUrls via category mapping
|
|
2564
2985
|
confidence: 0.7,
|
|
2565
2986
|
},
|