@nerviq/cli 1.2.7 → 1.3.1

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