@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/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
- pythonProjectExists: {
1622
- id: 'CL-PY01',
1623
- name: 'Python project detected (pyproject.toml / setup.py / requirements.txt)',
1624
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return true; },
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: 'Ensure pyproject.toml, setup.py, or requirements.txt exists for Python projects.',
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
- pythonVersionSpecified: {
1633
- id: 'CL-PY02',
1634
- name: 'Python version specified (.python-version or requires-python)',
1635
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /\.python-version$/.test(f)) || /requires-python/i.test(ctx.fileContent('pyproject.toml') || ''); },
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: 'Create .python-version or add requires-python to pyproject.toml.',
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
- pythonVenvMentioned: {
1644
- id: 'CL-PY03',
1645
- name: 'Virtual environment mentioned in instructions',
1646
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /venv|virtualenv|conda|poetry shell|uv venv/i.test(docs); },
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: 'Document virtual environment setup in project instructions.',
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
- pythonLockfileExists: {
1655
- id: 'CL-PY04',
1656
- name: 'Python lockfile exists (poetry.lock / uv.lock / Pipfile.lock / pinned requirements)',
1657
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /poetry\.lock$|uv\.lock$|Pipfile\.lock$/.test(f)) || /==/m.test(ctx.fileContent('requirements.txt') || ''); },
1658
- impact: 'high',
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: 'Add a lockfile (poetry.lock, uv.lock, Pipfile.lock) or pin versions with == in requirements.txt.',
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
- pythonPytestConfigured: {
1666
- id: 'CL-PY05',
1667
- name: 'pytest configured (pyproject.toml [tool.pytest] / pytest.ini / conftest.py)',
1668
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return /\[tool\.pytest/i.test(ctx.fileContent('pyproject.toml') || '') || ctx.files.some(f => /pytest\.ini$|conftest\.py$/.test(f)); },
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: 'Configure pytest in pyproject.toml [tool.pytest.ini_options] or create pytest.ini.',
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
- pythonLinterConfigured: {
1677
- id: 'CL-PY06',
1678
- name: 'Python linter configured (ruff / flake8 / pylint)',
1679
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const pp = ctx.fileContent('pyproject.toml') || ''; return /\[tool\.ruff|\[tool\.flake8|\[tool\.pylint/i.test(pp) || ctx.files.some(f => /\.flake8$|pylintrc$|\.pylintrc$|ruff\.toml$/.test(f)); },
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: 'Configure ruff, flake8, or pylint in pyproject.toml or dedicated config file.',
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
- pythonTypeCheckerConfigured: {
1688
- id: 'CL-PY07',
1689
- name: 'Type checker configured (mypy / pyright)',
1690
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const pp = ctx.fileContent('pyproject.toml') || ''; return /\[tool\.mypy|\[tool\.pyright/i.test(pp) || ctx.files.some(f => /mypy\.ini$|pyrightconfig\.json$/.test(f)); },
1691
- impact: 'medium',
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: 'Configure mypy or pyright in pyproject.toml or dedicated config file.',
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
- pythonFormatterConfigured: {
1699
- id: 'CL-PY08',
1700
- name: 'Formatter configured (black / isort / ruff format)',
1701
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const pp = ctx.fileContent('pyproject.toml') || ''; return /\[tool\.black|\[tool\.isort|\[tool\.ruff\.format/i.test(pp); },
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 black, isort, or ruff format in pyproject.toml.',
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
- pythonDjangoSettingsDocumented: {
1710
- id: 'CL-PY09',
1711
- name: 'Django settings documented if Django project',
1712
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; if (!ctx.files.some(f => /manage\.py$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /django|settings\.py|DJANGO_SETTINGS_MODULE/i.test(docs); },
1713
- impact: 'high',
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: 'Document Django settings module and configuration in project instructions.',
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
- pythonFastapiEntryDocumented: {
1721
- id: 'CL-PY10',
1722
- name: 'FastAPI entry point documented if FastAPI project',
1723
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/fastapi/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /fastapi|uvicorn|app\.py|main\.py/i.test(docs); },
1724
- impact: 'high',
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: 'Document FastAPI entry point and how to run the development server.',
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
- pythonMigrationsDocumented: {
1732
- id: 'CL-PY11',
1733
- name: 'Database migrations mentioned (alembic / Django migrations)',
1734
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /alembic|migrate|makemigrations|django.{0,10}migration/i.test(docs) || ctx.files.some(f => /alembic[.]ini$|alembic[/]/.test(f)); },
1735
- impact: 'medium',
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: 'Document database migration workflow (alembic or Django migrations).',
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
- pythonEnvHandlingDocumented: {
1743
- id: 'CL-PY12',
1744
- name: '.env handling documented (python-dotenv)',
1745
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/dotenv|python-dotenv|environs/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /\.env|dotenv|environment.{0,10}variable/i.test(docs); },
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: 'Document .env file usage and python-dotenv configuration.',
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
- pythonPreCommitConfigured: {
1754
- id: 'CL-PY13',
1755
- name: 'pre-commit hooks configured (.pre-commit-config.yaml)',
1756
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /\.pre-commit-config\.yaml$/.test(f)); },
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: 'Add .pre-commit-config.yaml with Python-specific hooks (ruff, mypy, etc.).',
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
- pythonDockerBaseImage: {
1765
- id: 'CL-PY14',
1766
- name: 'Docker uses Python base image correctly',
1767
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const df = ctx.fileContent('Dockerfile') || ''; if (!df) return null; return /FROM.*python:/i.test(df); },
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: 'Use official Python base image in Dockerfile (e.g., FROM python:3.12-slim).',
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
- pythonTestMatrixConfigured: {
1776
- id: 'CL-PY15',
1777
- name: 'Test matrix configured (tox.ini / noxfile.py)',
1778
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /tox\.ini$|noxfile\.py$/.test(f)); },
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: 'Configure tox or nox for multi-environment testing.',
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
- pythonValidationUsed: {
1787
- id: 'CL-PY16',
1788
- name: 'Pydantic or dataclass validation used',
1789
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); return /pydantic|dataclass/i.test(deps); },
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: 'Use pydantic or dataclasses for data validation and type safety.',
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
- pythonAsyncDocumented: {
1798
- id: 'CL-PY17',
1799
- name: 'Async patterns documented if async project',
1800
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/asyncio|aiohttp|fastapi|starlette|httpx/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /async|await|asyncio|event.{0,5}loop/i.test(docs); },
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: 'Document async patterns and conventions used in the project.',
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
- pythonPinnedVersions: {
1809
- id: 'CL-PY18',
1810
- name: 'Requirements have pinned versions (== in requirements.txt)',
1811
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const req = ctx.fileContent('requirements.txt') || ''; if (!req.trim()) return null; const lines = req.split('\n').filter(l => l.trim() && !l.startsWith('#')); return lines.length > 0 && lines.every(l => /==/.test(l) || /^-/.test(l.trim())); },
1812
- impact: 'high',
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: 'Pin all dependency versions with == in requirements.txt for reproducible builds.',
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
- pythonPackageStructure: {
1820
- id: 'CL-PY19',
1821
- name: 'Python package has proper structure (src/ layout or __init__.py)',
1822
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /src[/].*[/]__init__\.py$|^[^/]+[/]__init__\.py$/.test(f)); },
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: 'Use src/ layout or ensure packages have __init__.py files.',
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
- pythonDocsToolConfigured: {
1831
- id: 'CL-PY20',
1832
- name: 'Documentation tool configured (sphinx / mkdocs)',
1833
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; return ctx.files.some(f => /mkdocs\.yml$|conf\.py$|docs[/]/.test(f)) || /sphinx|mkdocs/i.test(ctx.fileContent('pyproject.toml') || ''); },
1834
- impact: 'low',
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: 'Configure sphinx or mkdocs for project documentation.',
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
- pythonCoverageConfigured: {
1842
- id: 'CL-PY21',
1843
- name: 'Coverage configured (coverage / pytest-cov)',
1844
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const pp = ctx.fileContent('pyproject.toml') || ''; return /\[tool\.coverage|pytest-cov|coverage/i.test(pp) || ctx.files.some(f => /\.coveragerc$/.test(f)); },
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: 'Configure coverage reporting with pytest-cov or coverage.py.',
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
- pythonNoSecretsInSettings: {
1853
- id: 'CL-PY22',
1854
- name: 'No secrets in Django settings.py',
1855
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const settings = ctx.fileContent('settings.py') || ctx.files.filter(f => /settings\.py$/.test(f)).map(f => ctx.fileContent(f) || '').join(''); if (!settings) return null; return !/SECRET_KEY\s*=\s*['"][^'"]{10,}/i.test(settings); },
1856
- impact: 'critical',
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: 'Move SECRET_KEY and other secrets to environment variables, not hardcoded in settings.py.',
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
- pythonWsgiAsgiDocumented: {
1864
- id: 'CL-PY23',
1865
- name: 'WSGI/ASGI server documented (gunicorn / uvicorn)',
1866
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/gunicorn|uvicorn|daphne|hypercorn/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /gunicorn|uvicorn|daphne|hypercorn|wsgi|asgi/i.test(docs); },
1867
- impact: 'medium',
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: 'Document WSGI/ASGI server configuration (gunicorn, uvicorn).',
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
- pythonTaskQueueDocumented: {
1875
- id: 'CL-PY24',
1876
- name: 'Task queue documented if used (celery / rq)',
1877
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const deps = (ctx.fileContent('pyproject.toml') || '') + (ctx.fileContent('requirements.txt') || ''); if (!/celery|rq|dramatiq|huey/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /celery|rq|dramatiq|huey|task.{0,10}queue|worker/i.test(docs); },
1878
- impact: 'medium',
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: 'Document task queue configuration and worker setup.',
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
- pythonGitignore: {
1886
- id: 'CL-PY25',
1887
- name: 'Python-specific .gitignore (__pycache__, *.pyc, .venv)',
1888
- check: (ctx) => { const hasPy = ctx.files.some(f => /pyproject\.toml$|requirements\.txt$|setup\.py$|manage\.py$/.test(f)); if (!hasPy) return null; const gi = ctx.fileContent('.gitignore') || ''; return /__pycache__|\*\.pyc|\.venv/i.test(gi); },
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: 'Add Python-specific entries to .gitignore (__pycache__, *.pyc, .venv, *.egg-info).',
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: 'CL-GO01',
1902
- name: 'go.mod exists',
1903
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return true; },
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
- 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)); },
1915
- impact: 'high',
1916
- category: 'go',
1917
- fix: 'Commit go.sum to version control for reproducible builds.',
1918
- // sourceUrl assigned by attachSourceUrls via category mapping
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: 'Add .golangci.yml to configure linting rules.',
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
- goTestDocumented: {
1934
- id: 'CL-GO04',
1935
- name: 'go test documented in instructions',
1936
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go test/i.test(docs); },
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: 'Document go test command in project instructions.',
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
- goBuildDocumented: {
1945
- id: 'CL-GO05',
1946
- name: 'go build documented in instructions',
1947
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go build|go install/i.test(docs); },
1948
- impact: 'high',
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: 'Document go build command in project instructions.',
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
- goStandardLayout: {
1956
- id: 'CL-GO06',
1957
- name: 'Standard Go layout (cmd/ / internal/ / pkg/)',
1958
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return ctx.files.some(f => /^cmd[/]|^internal[/]|^pkg[/]/.test(f)); },
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: 'Use standard Go project layout with cmd/, internal/, and/or pkg/ directories.',
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
- goErrorHandlingDocumented: {
1967
- id: 'CL-GO07',
1968
- name: 'Error handling patterns documented',
1969
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /error handling|errors?\.(?:New|Wrap|Is|As)|fmt\.Errorf/i.test(docs); },
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: 'Document error handling conventions (error wrapping, sentinel errors, etc.).',
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
- goContextUsageDocumented: {
1978
- id: 'CL-GO08',
1979
- name: 'Context usage documented',
1980
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /context\.Context|ctx\.Done|context\.WithCancel|context\.WithTimeout/i.test(docs); },
1981
- impact: 'medium',
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: 'Document context.Context usage patterns for cancellation and timeouts.',
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
- goroutineSafetyDocumented: {
1989
- id: 'CL-GO09',
1990
- name: 'Goroutine safety documented',
1991
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /goroutine|sync\.Mutex|sync\.WaitGroup|channel|concurren/i.test(docs); },
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: 'Document goroutine safety patterns, mutex usage, and channel conventions.',
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
- goModTidyMentioned: {
2000
- id: 'CL-GO10',
2001
- name: 'go mod tidy mentioned in instructions',
2002
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go mod tidy/i.test(docs); },
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: 'Document go mod tidy in project workflow instructions.',
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
- goVetConfigured: {
2011
- id: 'CL-GO11',
2012
- name: 'go vet or staticcheck configured',
2013
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/go.yml') || ''; return /go vet|staticcheck/i.test(docs + ci); },
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: 'Configure go vet and/or staticcheck in CI or project instructions.',
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
- goMakefileExists: {
2022
- id: 'CL-GO12',
2023
- name: 'Makefile or Taskfile exists for Go project',
2024
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; return ctx.files.some(f => /^Makefile$|^Taskfile\.ya?ml$/.test(f)); },
2025
- impact: 'medium',
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 Makefile or Taskfile.yml with common Go targets (build, test, lint).',
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
- goDockerMultiStage: {
2033
- id: 'CL-GO13',
2034
- name: 'Docker multi-stage build for Go',
2035
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const df = ctx.fileContent('Dockerfile') || ''; if (!df) return null; return /FROM.*golang.*AS/i.test(df) && /FROM.*(?:alpine|scratch|distroless|gcr\.io)/i.test(df); },
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: 'Use multi-stage Docker build: build in golang image, run in minimal image (alpine/scratch/distroless).',
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
- goCgoDocumented: {
2044
- id: 'CL-GO14',
2045
- name: 'CGO documented if used',
2046
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const goMod = ctx.fileContent('go.mod') || ''; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; if (!/CGO_ENABLED|import "C"/i.test(goMod + docs)) return null; return /CGO|cgo/i.test(docs); },
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: 'Document CGO usage, dependencies, and build requirements.',
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
- goWorkForMonorepo: {
2055
- id: 'CL-GO15',
2056
- name: 'go.work for monorepo',
2057
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const multiMod = ctx.files.filter(f => /go\.mod$/.test(f)).length > 1; if (!multiMod) return null; return ctx.files.some(f => /go\.work$/.test(f)); },
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: 'Use go.work for Go workspace in monorepo with multiple modules.',
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
- goBenchmarkTests: {
2066
- id: 'CL-GO16',
2067
- name: 'Benchmark tests mentioned',
2068
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go test.*-bench|Benchmark/i.test(docs); },
2069
- impact: 'low',
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: 'Document benchmark testing with go test -bench.',
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
- goRaceDetector: {
2077
- id: 'CL-GO17',
2078
- name: 'Race detector (-race) documented',
2079
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/go.yml') || ''; return /-race/i.test(docs + ci); },
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: 'Document and enable race detector with go test -race.',
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
- goGenerateDocumented: {
2088
- id: 'CL-GO18',
2089
- name: 'go generate documented',
2090
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /go generate/i.test(docs) || ctx.files.some(f => /generate\.go$/.test(f)); },
2091
- impact: 'low',
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: 'Document go generate usage and generated files.',
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
- goInterfaceDesignDocumented: {
2099
- id: 'CL-GO19',
2100
- name: 'Interface-based design documented',
2101
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /interface|mock|stub|dependency injection/i.test(docs); },
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: 'Document interface-based design patterns for testability and dependency injection.',
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
- goGitignore: {
2110
- id: 'CL-GO20',
2111
- name: 'Go-specific .gitignore entries',
2112
- check: (ctx) => { if (!ctx.files.some(f => /go\.mod$/.test(f))) return null; const gi = ctx.fileContent('.gitignore') || ''; return /vendor[/]|\*\.exe|\*\.test|\*\.out|[/]bin[/]/i.test(gi); },
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: 'Add Go-specific entries to .gitignore (vendor/, *.exe, *.test, /bin/).',
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
- rustCargoTomlExists: {
2124
- id: 'CL-RS01',
2125
- name: 'Cargo.toml exists with edition field',
2126
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; return /edition\s*=/.test(cargo); },
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: 'Ensure Cargo.toml exists and specifies the edition field (e.g., edition = "2021").',
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
- rustCargoLockCommitted: {
2135
- id: 'CL-RS02',
2136
- name: 'Cargo.lock committed (for binary crates)',
2137
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; return ctx.files.some(f => /Cargo\.lock$/.test(f)); },
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: 'Commit Cargo.lock for binary crates to ensure reproducible builds.',
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
- rustClippyConfigured: {
2146
- id: 'CL-RS03',
2147
- name: 'Clippy configured (CI or .cargo/config.toml)',
2148
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/rust.yml') || ''; const cargoConfig = ctx.fileContent('.cargo/config.toml') || ''; return /clippy/i.test(ci + cargoConfig); },
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 .cargo/config.toml for lint enforcement.',
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
- rustFmtConfigured: {
2157
- id: 'CL-RS04',
2158
- name: 'rustfmt configured (rustfmt.toml or .rustfmt.toml)',
2159
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; return ctx.files.some(f => /rustfmt\.toml$|\.rustfmt\.toml$/.test(f)); },
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: 'Create rustfmt.toml or .rustfmt.toml to configure code formatting.',
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
- rustCargoTestDocumented: {
2168
- id: 'CL-RS05',
2169
- name: 'cargo test documented in instructions',
2170
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cargo test/i.test(docs); },
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: 'Document cargo test command in project instructions.',
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
- rustCargoBuildDocumented: {
2179
- id: 'CL-RS06',
2180
- name: 'cargo build/check documented in instructions',
2181
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cargo (?:build|check)/i.test(docs); },
2182
- impact: 'high',
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: 'Document cargo build or cargo check command in project instructions.',
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
- rustUnsafePolicyDocumented: {
2190
- id: 'CL-RS07',
2191
- name: 'Unsafe code policy documented',
2192
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /unsafe|#!?\[forbid\(unsafe|#!?\[deny\(unsafe/i.test(docs); },
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: 'Document unsafe code policy (forbidden, minimized, or where allowed).',
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
- rustErrorHandlingStrategy: {
2201
- id: 'CL-RS08',
2202
- name: 'Error handling strategy (anyhow/thiserror in deps)',
2203
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; return /anyhow|thiserror|eyre|color-eyre/i.test(cargo); },
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: 'Use anyhow (applications) or thiserror (libraries) for structured error handling.',
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
- rustFeatureFlagsDocumented: {
2212
- id: 'CL-RS09',
2213
- name: 'Feature flags documented (Cargo.toml [features])',
2214
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/\[features\]/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /feature|--features|--all-features/i.test(docs); },
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 feature flags and their purpose in project instructions.',
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
- rustWorkspaceConfig: {
2223
- id: 'CL-RS10',
2224
- name: 'Workspace config if multi-crate (Cargo.toml [workspace])',
2225
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (ctx.files.filter(f => /Cargo\.toml$/.test(f)).length <= 1) return null; return /\[workspace\]/i.test(cargo); },
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: 'Configure [workspace] in root Cargo.toml for multi-crate projects.',
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
- rustMsrvSpecified: {
2234
- id: 'CL-RS11',
2235
- name: 'MSRV specified (rust-version field)',
2236
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; return /rust-version\s*=/.test(cargo); },
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: 'Specify rust-version (MSRV) in Cargo.toml for compatibility guarantees.',
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
- rustDocCommentsEncouraged: {
2245
- id: 'CL-RS12',
2246
- name: 'Doc comments (///) encouraged in instructions',
2247
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /doc comment|\/{3}|rustdoc|cargo doc/i.test(docs); },
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: 'Encourage /// doc comments and cargo doc in project instructions.',
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
- rustBenchmarksConfigured: {
2256
- id: 'CL-RS13',
2257
- name: 'Criterion benchmarks mentioned (benches/ dir)',
2258
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; return ctx.files.some(f => /benches[/]/.test(f)) || /criterion/i.test(ctx.fileContent('Cargo.toml') || ''); },
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: 'Set up criterion benchmarks in benches/ directory.',
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
- rustCrossCompilationDocumented: {
2267
- id: 'CL-RS14',
2268
- name: 'Cross-compilation documented',
2269
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /cross.?compil|--target|rustup target|cargo build.*--target/i.test(docs); },
2270
- impact: 'low',
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: 'Document cross-compilation targets and setup instructions.',
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
- rustMemorySafetyDocumented: {
2278
- id: 'CL-RS15',
2279
- name: 'Memory safety patterns documented',
2280
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /ownership|borrow|lifetime|memory.?safe|Arc|Rc|RefCell/i.test(docs); },
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: 'Document memory safety patterns (ownership, borrowing, lifetime conventions).',
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
- rustAsyncRuntimeDocumented: {
2289
- id: 'CL-RS16',
2290
- name: 'Async runtime documented (tokio/async-std in deps)',
2291
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/tokio|async-std|smol/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /tokio|async-std|async|await|runtime/i.test(docs); },
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: 'Document async runtime choice and patterns (tokio, async-std).',
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
- rustSerdeDocumented: {
2300
- id: 'CL-RS17',
2301
- name: 'Serde patterns documented',
2302
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/serde/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /serde|Serialize|Deserialize|serde_json|serde_yaml/i.test(docs); },
2303
- impact: 'medium',
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: 'Document serde serialization/deserialization patterns and conventions.',
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
- rustCargoAuditConfigured: {
2311
- id: 'CL-RS18',
2312
- name: 'cargo-audit configured in CI',
2313
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const ci = ctx.fileContent('.github/workflows/ci.yml') || ctx.fileContent('.github/workflows/rust.yml') || ctx.fileContent('.github/workflows/audit.yml') || ''; return /cargo.?audit|advisory/i.test(ci); },
2314
- impact: 'medium',
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: 'Configure cargo-audit in CI for vulnerability scanning.',
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
- rustWasmTargetDocumented: {
2322
- id: 'CL-RS19',
2323
- name: 'WASM target documented if applicable',
2324
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const cargo = ctx.fileContent('Cargo.toml') || ''; if (!/wasm|wasm-bindgen|wasm-pack/i.test(cargo)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /wasm|WebAssembly|wasm-pack|wasm-bindgen/i.test(docs); },
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: 'Document WASM target configuration and build process.',
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
- rustGitignore: {
2333
- id: 'CL-RS20',
2334
- name: 'Rust .gitignore includes target/',
2335
- check: (ctx) => { if (!ctx.files.some(f => /Cargo\.toml$/.test(f))) return null; const gi = ctx.fileContent('.gitignore') || ''; return /target[/]|[/]target/i.test(gi); },
2336
- impact: 'medium',
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: 'Add target/ to .gitignore for Rust build artifacts.',
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
- javaBuildFileExists: {
2348
- id: 'CL-JV01',
2349
- name: 'pom.xml or build.gradle exists',
2350
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return true; },
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: 'Ensure pom.xml or build.gradle exists for Java projects.',
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
- javaVersionSpecified: {
2359
- id: 'CL-JV02',
2710
+ javaVersion: {
2711
+ id: 120302,
2360
2712
  name: 'Java version specified',
2361
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const pom = ctx.fileContent('pom.xml') || ''; const gradle = ctx.fileContent('build.gradle') || ctx.fileContent('build.gradle.kts') || ''; return /java\.version|maven\.compiler\.source|sourceCompatibility|JavaVersion/i.test(pom + gradle) || ctx.files.some(f => /\.java-version$/.test(f)); },
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 pom.xml properties, build.gradle, or .java-version file.',
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
- javaWrapperCommitted: {
2370
- id: 'CL-JV03',
2371
- name: 'Maven/Gradle wrapper committed (mvnw or gradlew)',
2372
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /mvnw$|gradlew$/.test(f)); },
2373
- impact: 'high',
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: 'Commit mvnw (Maven) or gradlew (Gradle) wrapper for reproducible builds.',
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
- javaSpringBootVersion: {
2381
- id: 'CL-JV04',
2382
- name: 'Spring Boot version documented if Spring project',
2383
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const pom = ctx.fileContent('pom.xml') || ''; const gradle = ctx.fileContent('build.gradle') || ctx.fileContent('build.gradle.kts') || ''; if (!/spring-boot/i.test(pom + gradle)) return null; return /spring-boot.*\d+\.\d+/i.test(pom + gradle); },
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: 'Document Spring Boot version in build configuration.',
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
- javaApplicationConfig: {
2392
- id: 'CL-JV05',
2393
- name: 'application.yml or application.properties exists',
2394
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /application\.ya?ml$|application\.properties$/.test(f)); },
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: 'Create application.yml or application.properties for Spring configuration.',
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
- javaTestFramework: {
2403
- id: 'CL-JV06',
2404
- name: 'Test framework configured (JUnit/TestNG in deps)',
2405
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const pom = ctx.fileContent('pom.xml') || ''; const gradle = ctx.fileContent('build.gradle') || ctx.fileContent('build.gradle.kts') || ''; return /junit|testng|spring-boot-starter-test/i.test(pom + gradle); },
2406
- impact: 'high',
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 JUnit or TestNG test framework in project dependencies.',
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
- javaCodeStyleConfigured: {
2414
- id: 'CL-JV07',
2415
- name: 'Code style configured (checkstyle.xml, spotbugs)',
2416
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /checkstyle\.xml$|spotbugs.*\.xml$/.test(f)) || /checkstyle|spotbugs|google-java-format/i.test((ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || '')); },
2417
- impact: 'medium',
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: 'Configure checkstyle or spotbugs for code quality enforcement.',
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
- javaSpringProfilesDocumented: {
2425
- id: 'CL-JV08',
2426
- name: 'Spring profiles documented',
2427
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/spring/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /spring[.]profiles|@Profile|SPRING_PROFILES_ACTIVE/i.test(docs); },
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: 'Document Spring profiles and their configuration in project instructions.',
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
- javaDatabaseMigration: {
2436
- id: 'CL-JV09',
2437
- name: 'Database migration configured (flyway/liquibase)',
2438
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); return /flyway|liquibase/i.test(deps) || ctx.files.some(f => /db[/]migration|flyway|liquibase/i.test(f)); },
2439
- impact: 'medium',
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: 'Configure database migration tool (Flyway or Liquibase) for schema management.',
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
- javaLombokDocumented: {
2447
- id: 'CL-JV10',
2448
- name: 'Lombok/MapStruct documented if used',
2449
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/lombok|mapstruct/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /lombok|mapstruct/i.test(docs); },
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: 'Document Lombok/MapStruct usage and IDE setup requirements.',
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
- javaApiDocsConfigured: {
2458
- id: 'CL-JV11',
2459
- name: 'API docs configured (springdoc/swagger deps)',
2460
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); return /springdoc|swagger|openapi/i.test(deps); },
2461
- impact: 'medium',
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: 'Configure API documentation with springdoc-openapi or Swagger.',
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
- javaSecurityConfigured: {
2469
- id: 'CL-JV12',
2470
- name: 'Security configuration documented',
2471
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); if (!/spring-security|spring-boot-starter-security/i.test(deps)) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /security|authentication|authorization|SecurityConfig|@EnableWebSecurity/i.test(docs); },
2472
- impact: 'high',
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: 'Document Spring Security configuration and authentication setup.',
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
- javaActuatorConfigured: {
2480
- id: 'CL-JV13',
2481
- name: 'Actuator/health checks configured',
2482
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); return /actuator|spring-boot-starter-actuator/i.test(deps); },
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: 'Configure Spring Boot Actuator for health checks and monitoring.',
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
- javaLoggingConfigured: {
2491
- id: 'CL-JV14',
2492
- name: 'Logging configured (logback.xml or log4j2.xml)',
2493
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /logback.*\.xml$|log4j2?.*\.xml$|logging\.properties$/.test(f)); },
2494
- impact: 'medium',
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: 'Configure logging with logback.xml or log4j2.xml.',
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
- javaMultiModuleProject: {
2502
- id: 'CL-JV15',
2503
- name: 'Multi-module project configured if applicable',
2504
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const buildFiles = ctx.files.filter(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f)); if (buildFiles.length <= 1) return null; const rootPom = ctx.fileContent('pom.xml') || ''; const rootGradle = ctx.fileContent('settings.gradle') || ctx.fileContent('settings.gradle.kts') || ''; return /<modules>|include\s/i.test(rootPom + rootGradle); },
2505
- impact: 'medium',
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 multi-module project structure in root build file.',
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
- javaDockerConfigured: {
2513
- id: 'CL-JV16',
2514
- name: 'Docker build configured (Dockerfile or Jib plugin)',
2515
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const df = ctx.fileContent('Dockerfile') || ''; const deps = (ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || ''); return /FROM.*(?:openjdk|eclipse-temurin|amazoncorretto)/i.test(df) || /jib/i.test(deps); },
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: 'Configure Docker build with Dockerfile or Jib plugin.',
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
- javaEnvConfigsSeparated: {
2524
- id: 'CL-JV17',
2525
- name: 'Environment-specific configs separated',
2526
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /application-(?:dev|prod|staging|test|local)\.(?:ya?ml|properties)$/.test(f)); },
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: 'Separate environment configs (application-dev.yml, application-prod.yml, etc.).',
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
- javaNoSecretsInConfig: {
2535
- id: 'CL-JV18',
2536
- name: 'No secrets in application.yml/properties',
2537
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const appYml = ctx.files.filter(f => /application.*\.ya?ml$|application.*\.properties$/.test(f)).map(f => ctx.fileContent(f) || '').join(''); if (!appYml) return null; return !/password\s*[:=]\s*[^$\{\s][^\s]{8,}|secret\s*[:=]\s*[^$\{\s][^\s]{8,}/i.test(appYml); },
2538
- impact: 'critical',
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: 'Move secrets to environment variables or external secret management, not application config files.',
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
- javaIntegrationTestsSeparate: {
2546
- id: 'CL-JV19',
2547
- name: 'Integration tests separate from unit tests',
2548
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; return ctx.files.some(f => /src[/](?:integration-?test|it)[/]|IT\.java$|Integration(?:Test)?\.java$/.test(f)) || /failsafe|integration-test/i.test((ctx.fileContent('pom.xml') || '') + (ctx.fileContent('build.gradle') || '') + (ctx.fileContent('build.gradle.kts') || '')); },
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: 'Separate integration tests from unit tests using Maven Failsafe or dedicated source set.',
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
- javaBuildCommandDocumented: {
2557
- id: 'CL-JV20',
2558
- name: 'Build command documented in instructions',
2559
- check: (ctx) => { if (!ctx.files.some(f => /pom\.xml$|build\.gradle$|build\.gradle\.kts$/.test(f))) return null; const docs = (ctx.claudeMdContent ? ctx.claudeMdContent() : ctx.fileContent('CLAUDE.md')) || ctx.fileContent('README.md') || ''; return /mvn|gradle|mvnw|gradlew|maven|./i.test(docs) && /build|compile|package|install/i.test(docs); },
2560
- impact: 'high',
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: 'Document build command (mvnw package, gradlew build) in project instructions.',
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
  },