vibecodedshit 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +95 -0
- package/README.md +73 -2
- package/assets/fonts/NotoEmoji-OFL.txt +93 -0
- package/assets/fonts/NotoEmoji-Poop.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-Bold.ttf +0 -0
- package/assets/fonts/SpaceGrotesk-OFL.txt +93 -0
- package/dist/bin.js +11 -0
- package/dist/card/previous.js +38 -0
- package/dist/card/render.js +71 -0
- package/dist/cli.js +138 -0
- package/dist/commands/card.js +69 -0
- package/dist/commands/common.js +1 -0
- package/dist/commands/diff.js +54 -0
- package/dist/commands/footer.js +28 -0
- package/dist/commands/report.js +49 -0
- package/dist/commands/route.js +62 -0
- package/dist/commands/submit.js +125 -0
- package/dist/commands/summary.js +77 -0
- package/dist/diff/compare.js +73 -0
- package/dist/diff/conditions.js +87 -0
- package/dist/diff/format.js +23 -0
- package/dist/exec/docker.js +158 -0
- package/dist/exec/findings.js +73 -0
- package/dist/exec/plan.js +50 -0
- package/dist/exec/reports.js +90 -0
- package/dist/exec/roots.js +49 -0
- package/dist/exec/run.js +182 -0
- package/dist/exec/steps/build.js +45 -0
- package/dist/exec/steps/coverage-install.js +12 -0
- package/dist/exec/steps/install.js +41 -0
- package/dist/exec/steps/knip.js +28 -0
- package/dist/exec/steps/lint.js +25 -0
- package/dist/exec/steps/lockfiles.js +25 -0
- package/dist/exec/steps/test.js +17 -0
- package/dist/exec/steps/typecheck.js +40 -0
- package/dist/exec/test-reports/coverage-py.js +23 -0
- package/dist/exec/test-reports/coverage-ranges.js +65 -0
- package/dist/exec/test-reports/coverage-summary.js +23 -0
- package/dist/exec/test-reports/jest-json.js +31 -0
- package/dist/exec/test-reports/junit.js +24 -0
- package/dist/exec/test-reports/mocha-json.js +18 -0
- package/dist/exec/test-reports/vitest-json.js +5 -0
- package/dist/exec/test-runners/detect.js +99 -0
- package/dist/exec/test-runners/index.js +6 -0
- package/dist/exec/test-runners/jest.js +27 -0
- package/dist/exec/test-runners/mocha.js +28 -0
- package/dist/exec/test-runners/node-test.js +24 -0
- package/dist/exec/test-runners/pytest.js +18 -0
- package/dist/exec/test-runners/script.js +48 -0
- package/dist/exec/test-runners/spec.js +1 -0
- package/dist/exec/test-runners/vitest.js +28 -0
- package/dist/exec/tests.js +120 -0
- package/dist/exec/types.js +1 -0
- package/dist/interactive/after-scan.js +121 -0
- package/dist/interactive/docker-setup.js +97 -0
- package/dist/interactive/prompt.js +17 -0
- package/dist/interactive/system.js +10 -0
- package/dist/manifest/package-json.js +36 -0
- package/dist/manifest/python.js +106 -0
- package/dist/measure/d3/index.js +45 -0
- package/dist/measure/d3/presets.js +100 -0
- package/dist/measure/d3/py/adapter.js +67 -0
- package/dist/measure/d3/py/config.js +80 -0
- package/dist/measure/d3/py/contracts.js +57 -0
- package/dist/measure/d3/py/domain.js +57 -0
- package/dist/measure/d3/py/graph.js +214 -0
- package/dist/measure/d3/py/interpreter.js +31 -0
- package/dist/measure/d3/py/layers.js +120 -0
- package/dist/measure/d3/py/roots.js +58 -0
- package/dist/measure/d3/py/types.js +1 -0
- package/dist/measure/d3/scc.js +64 -0
- package/dist/measure/d3/ts/adapter.js +371 -0
- package/dist/measure/d3/ts/classify.js +107 -0
- package/dist/measure/d3/ts/config.js +318 -0
- package/dist/measure/d3/ts/domain.js +23 -0
- package/dist/measure/d3/ts/rules.js +65 -0
- package/dist/measure/d3/types.js +5 -0
- package/dist/measure/deps/cvss.js +66 -0
- package/dist/measure/deps/declared.js +195 -0
- package/dist/measure/deps/files.js +40 -0
- package/dist/measure/deps/lockfiles.js +308 -0
- package/dist/measure/deps/osv.js +122 -0
- package/dist/measure/deps/registry.js +142 -0
- package/dist/measure/deps/resolved.js +78 -0
- package/dist/measure/deps/units.js +5 -0
- package/dist/measure/eslint/health-plugin.js +19 -0
- package/dist/measure/ops/docs-commands.js +73 -0
- package/dist/measure/ops/docs-resolve.js +217 -0
- package/dist/measure/ops/entry-points.js +49 -0
- package/dist/measure/ops/env-example.js +20 -0
- package/dist/measure/ops/env-reads.js +46 -0
- package/dist/measure/ops/runtime.js +73 -0
- package/dist/measure/p1-1.js +8 -0
- package/dist/measure/p1-2.js +8 -0
- package/dist/measure/p1-3.js +8 -0
- package/dist/measure/p1-4.js +41 -0
- package/dist/measure/p1-5.js +59 -0
- package/dist/measure/p2-1.js +8 -0
- package/dist/measure/p2-2.js +44 -0
- package/dist/measure/p2-3.js +81 -0
- package/dist/measure/p2-4.js +288 -0
- package/dist/measure/p2-5.js +8 -0
- package/dist/measure/p3-1.js +16 -0
- package/dist/measure/p3-2.js +26 -0
- package/dist/measure/p3-3.js +37 -0
- package/dist/measure/p3-4.js +26 -0
- package/dist/measure/p3-5.js +26 -0
- package/dist/measure/p4-1.js +184 -0
- package/dist/measure/p4-2.js +27 -0
- package/dist/measure/p4-3.js +106 -0
- package/dist/measure/p4-4.js +309 -0
- package/dist/measure/p5-1.js +117 -0
- package/dist/measure/p5-2.js +80 -0
- package/dist/measure/p5-3.js +21 -0
- package/dist/measure/p5-4.js +161 -0
- package/dist/measure/p5-5.js +158 -0
- package/dist/measure/p6-1.js +20 -0
- package/dist/measure/p6-2.js +20 -0
- package/dist/measure/p6-3.js +40 -0
- package/dist/measure/p6-4.js +31 -0
- package/dist/measure/p6-5.js +60 -0
- package/dist/measure/p6-6.js +54 -0
- package/dist/measure/py/typecheck-config.js +95 -0
- package/dist/measure/registry.js +31 -0
- package/dist/measure/safety/py-tests.js +124 -0
- package/dist/measure/safety/ts-barrels.js +68 -0
- package/dist/measure/safety/ts-cases.js +111 -0
- package/dist/measure/safety/ts-credits.js +101 -0
- package/dist/measure/safety/ts-imports.js +63 -0
- package/dist/measure/safety/ts-resolve.js +43 -0
- package/dist/measure/ts/directives.js +37 -0
- package/dist/measure/ts/exports.js +60 -0
- package/dist/measure/ts/tsconfig.js +179 -0
- package/dist/measure/types.js +1 -0
- package/dist/measure/units.js +41 -0
- package/dist/methodology/load.js +159 -0
- package/dist/methodology/schema.js +105 -0
- package/dist/python/deadcode.js +27 -0
- package/dist/python/env.js +13 -0
- package/dist/python/helper.js +24 -0
- package/dist/python/imports.js +40 -0
- package/dist/python/nox.js +16 -0
- package/dist/python/scope.js +35 -0
- package/dist/python/tests.js +19 -0
- package/dist/python/typing.js +27 -0
- package/dist/report/escape.js +5 -0
- package/dist/report/format.js +75 -0
- package/dist/report/render.js +213 -0
- package/dist/report/roadmap.js +58 -0
- package/dist/report/scares.js +83 -0
- package/dist/report/style.js +27 -0
- package/dist/report-only/hotspots.js +48 -0
- package/dist/route/check.js +37 -0
- package/dist/route/files.js +74 -0
- package/dist/route/order.js +22 -0
- package/dist/route/select.js +114 -0
- package/dist/route/write.js +13 -0
- package/dist/run/read.js +89 -0
- package/dist/run/recompute.js +19 -0
- package/dist/run/scan.js +196 -0
- package/dist/run/schema.js +1 -0
- package/dist/run/score-run.js +42 -0
- package/dist/run/write.js +4 -0
- package/dist/scope/build.js +173 -0
- package/dist/scope/packages.js +87 -0
- package/dist/scope/rules.js +21 -0
- package/dist/scope/tracked.js +34 -0
- package/dist/scope/ts-lines.js +63 -0
- package/dist/scope/types.js +7 -0
- package/dist/scoring/aggregate.js +38 -0
- package/dist/scoring/pool.js +65 -0
- package/dist/scoring/score.js +81 -0
- package/dist/scoring/what-if.js +38 -0
- package/dist/scripts/checks.js +53 -0
- package/dist/scripts/ci.js +139 -0
- package/dist/scripts/entry-points/find.js +31 -0
- package/dist/scripts/entry-points/index.js +18 -0
- package/dist/scripts/entry-points/justfile.js +62 -0
- package/dist/scripts/entry-points/makefile.js +66 -0
- package/dist/scripts/entry-points/nox.js +29 -0
- package/dist/scripts/entry-points/package-json.js +21 -0
- package/dist/scripts/entry-points/pyproject.js +73 -0
- package/dist/scripts/entry-points/tox.js +130 -0
- package/dist/scripts/expand.js +300 -0
- package/dist/scripts/triggers.js +151 -0
- package/dist/scripts/types.js +1 -0
- package/dist/submit/constants.js +10 -0
- package/dist/submit/payload.js +42 -0
- package/dist/submit/repo-identity.js +46 -0
- package/dist/submit/send.js +29 -0
- package/dist/tools/catalog.js +46 -0
- package/dist/tools/image.js +180 -0
- package/dist/tools/kit.js +71 -0
- package/dist/tools/knip.js +169 -0
- package/dist/tools/outcome.js +21 -0
- package/dist/tools/resolve.js +66 -0
- package/dist/tools/workcopy.js +27 -0
- package/dist/util/ini.js +37 -0
- package/dist/util/pep440.js +163 -0
- package/dist/util/toml.js +27 -0
- package/docker/Dockerfile +142 -0
- package/docker/bin/health-capture +5 -0
- package/docker/bin/health-coverage-install +5 -0
- package/docker/bin/health-eslint +66 -0
- package/docker/bin/health-pytest +10 -0
- package/docker/bin/health-tsc +10 -0
- package/docker/bin/health-venv +11 -0
- package/docker/config/eslint.config.mjs +4 -0
- package/docker/config/eslint.rules.mjs +65 -0
- package/docker/config/eslint.untyped.config.mjs +4 -0
- package/docker/config/knip.json +1 -0
- package/docker/config/ruff.toml +7 -0
- package/methodology/LICENSE +396 -0
- package/methodology/v1.0/METHODOLOGY.md +1176 -0
- package/methodology/v1.0/dimensions/1-safety-net.yaml +240 -0
- package/methodology/v1.0/dimensions/2-type-static-rigor.yaml +289 -0
- package/methodology/v1.0/dimensions/3-architecture-boundaries.yaml +266 -0
- package/methodology/v1.0/dimensions/4-complexity-maintainability.yaml +192 -0
- package/methodology/v1.0/dimensions/5-dependency-security-hygiene.yaml +223 -0
- package/methodology/v1.0/dimensions/6-operability-feedback-loop.yaml +279 -0
- package/methodology/v1.0/methodology.yaml +242 -0
- package/methodology/v1.0/presets/django.yaml +43 -0
- package/methodology/v1.0/presets/express.yaml +47 -0
- package/methodology/v1.0/presets/fastapi.yaml +52 -0
- package/methodology/v1.0/presets/generic.yaml +41 -0
- package/methodology/v1.0/presets/nextjs.yaml +50 -0
- package/methodology/v1.0/scope.yaml +115 -0
- package/package.json +61 -4
- package/python/dead_code_helper.py +265 -0
- package/python/env_helper.py +71 -0
- package/python/graph_helper.py +277 -0
- package/python/imports_helper.py +132 -0
- package/python/nox_helper.py +79 -0
- package/python/scope_helper.py +96 -0
- package/python/tests_helper.py +171 -0
- package/python/typing_helper.py +235 -0
|
@@ -0,0 +1,1176 @@
|
|
|
1
|
+
# Codebase Health Methodology, version 1.0
|
|
2
|
+
|
|
3
|
+
This document explains how a codebase health score is computed and why each rule exists. The YAML files in this folder are the source of truth; section 6 is generated from them. If this document and the YAML disagree, the YAML is correct.
|
|
4
|
+
|
|
5
|
+
| File | Contents |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `methodology.yaml` | Aggregation, bands, execution rules, delta rules, regression flags |
|
|
8
|
+
| `scope.yaml` | Which files count and how lines are counted |
|
|
9
|
+
| `dimensions/*.yaml` | The 30 parameters: weights, anchors, measurement, rationale, sources |
|
|
10
|
+
| `presets/*.yaml` | Default import rules for Next.js, Express, Django, FastAPI and generic projects |
|
|
11
|
+
|
|
12
|
+
## 1. Purpose
|
|
13
|
+
|
|
14
|
+
**Health is how safely and cheaply someone with no memory of this codebase, whether a contractor or an agent, can make a correct change.**
|
|
15
|
+
|
|
16
|
+
The methodology is built for three uses:
|
|
17
|
+
|
|
18
|
+
1. **Audits.** A run gives a baseline score, six dimension scores and the 30 parameter values behind them.
|
|
19
|
+
2. **Before and after.** A second run after the work shows what the fixes changed, parameter by parameter.
|
|
20
|
+
3. **Sprint-loop gate.** Every automated change is scored against its base commit. Regressions are flagged and never block.
|
|
21
|
+
|
|
22
|
+
It covers TypeScript, JavaScript and Python. Every parameter is computed from the repository by a tool. There are no surveys, no manual judgements and no AI grading. One parameter (6.3, feedback loop time) depends on wall-clock time and can vary slightly between identical runs; every other parameter gives the same result for the same commit, tool versions and as-of date.
|
|
23
|
+
|
|
24
|
+
## 2. How a score is computed
|
|
25
|
+
|
|
26
|
+
### Parameter score
|
|
27
|
+
|
|
28
|
+
Each parameter has a **raw value** measured from the repository, such as "62% line coverage" or "3.1 escape hatches per 1,000 lines". A fixed rubric turns the raw value into a score from 0 to 100.
|
|
29
|
+
|
|
30
|
+
Most rubrics are **piecewise-linear**: 3 or 4 anchors, each with a raw value, a score and a written meaning. Between two anchors the score is interpolated linearly. Below the first anchor and above the last, the score is clamped. Yes/no facts and configuration choices use **levels** instead, where each named level has a fixed score.
|
|
31
|
+
|
|
32
|
+
Example, 1.2 line coverage at 52%:
|
|
33
|
+
|
|
34
|
+
| Raw | Score |
|
|
35
|
+
|---|---|
|
|
36
|
+
| 40% | 50 |
|
|
37
|
+
| 60% | 75 |
|
|
38
|
+
|
|
39
|
+
52% is 12/20 of the way from 40% to 60%, so the score is 50 + 0.6 × 25 = **65**.
|
|
40
|
+
|
|
41
|
+
Anchors are fixed. They do not depend on other repositories, so a score means the same thing in every audit and every year until the methodology version changes.
|
|
42
|
+
|
|
43
|
+
### Dimension score
|
|
44
|
+
|
|
45
|
+
A dimension score is the weighted mean of its parameter scores. Weights within each dimension add up to 100.
|
|
46
|
+
|
|
47
|
+
Example, D1 Safety net:
|
|
48
|
+
|
|
49
|
+
| Parameter | Raw | Score | Weight | Contribution |
|
|
50
|
+
|---|---|---|---|---|
|
|
51
|
+
| 1.1 Suite pass rate | 97% | 70 | 20% | 14.0 |
|
|
52
|
+
| 1.2 Line coverage | 52% | 65 | 25% | 16.3 |
|
|
53
|
+
| 1.3 Branch coverage | 38% | 60 | 15% | 9.0 |
|
|
54
|
+
| 1.4 Modules with tests | 55% | 62.5 | 25% | 15.6 |
|
|
55
|
+
| 1.5 Tests that assert something | 92% | 70 | 15% | 10.5 |
|
|
56
|
+
| **D1** | | | | **65** |
|
|
57
|
+
|
|
58
|
+
### Not applicable
|
|
59
|
+
|
|
60
|
+
A parameter can be not applicable only when its precondition is a design choice the methodology does not score. In v1.0 there are two such cases:
|
|
61
|
+
|
|
62
|
+
- **3.4 Public-entry discipline**, when the repository has fewer than two modules.
|
|
63
|
+
- **3.5 Domain purity**, when the repository declares no domain layer.
|
|
64
|
+
|
|
65
|
+
The weight of a not-applicable parameter is spread over the other parameters in the same dimension, in proportion to their weights. With 3.5 not applicable, D3's weights become 17.6 / 35.3 / 29.4 / 17.6.
|
|
66
|
+
|
|
67
|
+
Missing tests, a failed install or a failed test run are never "not applicable". They score 0.
|
|
68
|
+
|
|
69
|
+
### Health score
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
health = min( mean(D1..D6), min(D1..D6) + 30 )
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The six dimensions have equal weight. The cap means one very weak dimension pulls the headline down, because a codebase with no tests is not made safe by clean dependencies.
|
|
76
|
+
|
|
77
|
+
| Example | D1 | D2 | D3 | D4 | D5 | D6 | Mean | Weakest + 30 | Health |
|
|
78
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
79
|
+
| Balanced | 72 | 65 | 58 | 81 | 90 | 60 | 71.0 | 88 | **71** Stable |
|
|
80
|
+
| No tests | 5 | 80 | 80 | 80 | 80 | 80 | 67.5 | 35 | **35** Vibe-coded |
|
|
81
|
+
|
|
82
|
+
### Bands
|
|
83
|
+
|
|
84
|
+
| Health | Band | Meaning |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| 0–39 | Vibe-coded | Changes break things without anyone noticing. Nobody, human or agent, can work here safely. |
|
|
87
|
+
| 40–59 | Fragile | It works, but every change is a gamble. |
|
|
88
|
+
| 60–79 | Stable | Changes are safe with care and review. |
|
|
89
|
+
| 80–100 | Agent-ready | An agent with no memory of the codebase can make a correct change and verify it alone. |
|
|
90
|
+
|
|
91
|
+
Because of the cap, Agent-ready requires every dimension to score at least 49.5.
|
|
92
|
+
|
|
93
|
+
### Precision
|
|
94
|
+
|
|
95
|
+
Scores are stored at full precision in `run.json`. The report shows health and dimension scores as integers (rounded half up) and the health delta with one decimal. The band comes from the displayed health score.
|
|
96
|
+
|
|
97
|
+
## 3. Layers and execution
|
|
98
|
+
|
|
99
|
+
Every run has two layers.
|
|
100
|
+
|
|
101
|
+
| Layer | What it does | Parameters |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| Static | Reads tracked files, parses ASTs, builds import graphs. Never runs repository code. | 23 |
|
|
104
|
+
| Execution | Installs dependencies, then runs the build, the typechecker, a pinned lint ruleset and the test suite with coverage. | 7 (1.1, 1.2, 1.3, 2.1, 2.5, 6.1, 6.3) |
|
|
105
|
+
|
|
106
|
+
Both layers run on every scan. The execution layer runs in a pinned Docker image, `health:1.0`, with 4 CPUs and 8 GB of memory. The same image runs the client's code, so nothing from the repository runs on the host.
|
|
107
|
+
|
|
108
|
+
- **Network** is allowed while installing dependencies and while fetching the advisory and registry snapshots. It is off for the build, typecheck, lint and tests.
|
|
109
|
+
- **Environment.** Variables listed in the repository's env example file are loaded with their example values. No other variables or secrets are provided. External services such as databases, queues and browsers are not provided.
|
|
110
|
+
- **Timeouts.** Install 15 minutes, build 15 minutes, typecheck 10 minutes, lint 10 minutes, tests 10 minutes. The test suite runs once.
|
|
111
|
+
|
|
112
|
+
There are two kinds of failure:
|
|
113
|
+
|
|
114
|
+
| Failure | Cause | Result |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| Repository failure | Install, build, typecheck or tests fail or time out because of the repository | Every parameter that needs that step scores 0. An install that fails in one install root zeroes only the units beneath that root; the other roots are still measured. The run is valid. |
|
|
117
|
+
| Tool error | The scanner itself fails, for example a crash or coverage data missing after a test run that produced results | The run is invalid and produces no score. |
|
|
118
|
+
|
|
119
|
+
## 4. Scope
|
|
120
|
+
|
|
121
|
+
Full rules are in `scope.yaml`.
|
|
122
|
+
|
|
123
|
+
- **Files.** The scan starts from `git ls-files` and keeps TypeScript, JavaScript and Python files.
|
|
124
|
+
- **Fixed exclusions.** Vendored code, build output, generated files, minified files, migrations, `.d.ts` files and test data (fixtures, snapshots, testdata) are excluded.
|
|
125
|
+
- **File roles.** Each remaining file is a test file, a config file or a logic file. Test directories (`test`, `tests`, `e2e`) are recognised at any depth. A logic file defines at least one function, method or class. Barrels, type-only files and constants-only files are not logic files.
|
|
126
|
+
- **Tests.** Test files are scored in D1, and also in 2.1 (typecheck errors) and 2.5 (lint findings), because broken tests are a real problem. They are excluded from every other static metric.
|
|
127
|
+
- **Line counts.** *Logic lines* are non-blank, non-comment lines in logic files; Python docstrings count as comments. *Source lines* add test files. Per-1,000-line parameters use one or the other, as each parameter states.
|
|
128
|
+
- **`.healthignore`.** A repository can exclude more paths with gitignore syntax. The report shows the percentage of lines excluded, and `health diff` refuses to compare runs with different ignore files. `.healthignore` does not apply to the secret scan (5.4).
|
|
129
|
+
|
|
130
|
+
### Monorepos and mixed languages
|
|
131
|
+
|
|
132
|
+
A repository always gets one headline score.
|
|
133
|
+
|
|
134
|
+
- **Ratio parameters** (percentages, counts per 1,000 lines, pass rates) sum numerators and denominators across packages and languages, then score the pooled ratio once. A unit under an install root whose install failed takes no part in the sums; the pooled score is multiplied by the share of the logic lines that is not failed, so a broken package costs its share.
|
|
135
|
+
- **Configuration and level parameters** are scored per language or package and combined by logic-line weight. Example: 2.2 scores 100 for a TypeScript frontend with 8,000 logic lines and 0 for a Python backend with 2,000 logic lines. The pooled 2.2 score is 80.
|
|
136
|
+
|
|
137
|
+
The report breaks every dimension down per package and per language.
|
|
138
|
+
|
|
139
|
+
## 5. Comparing runs and regression flags
|
|
140
|
+
|
|
141
|
+
### The as-of date
|
|
142
|
+
|
|
143
|
+
Two parameters depend on outside data that changes over time: 5.1 (known vulnerabilities) and 5.2 (dependencies a major version behind). Every run has an **as-of date**. Advisories published on or before that date count, and "latest version" means the latest version published on or before that date.
|
|
144
|
+
|
|
145
|
+
Without this, a repository's score would drop when a new advisory is published, even if nobody changed the code.
|
|
146
|
+
|
|
147
|
+
### Delta rule
|
|
148
|
+
|
|
149
|
+
`health diff before.json after.json` compares two runs only when all five conditions match. Otherwise it refuses and names the conditions that differ.
|
|
150
|
+
|
|
151
|
+
1. **Layers.** Both runs attempted the same layers.
|
|
152
|
+
2. **Tool versions.** Both runs used the same image digest.
|
|
153
|
+
3. **Methodology version.** Both runs used the same methodology version.
|
|
154
|
+
4. **As-of date.** Both runs were scored at the same as-of date. In a contract, this is the baseline's as-of date. The report also shows the newer run's score as of today.
|
|
155
|
+
5. **Ignore set.** Both runs used the same `.healthignore` and `.gitleaksignore` contents.
|
|
156
|
+
|
|
157
|
+
`run.json` stores raw values and the resolved dependency list, so the newer run can be rescored at the baseline's as-of date without rescanning.
|
|
158
|
+
|
|
159
|
+
### Sprint-loop regression flags
|
|
160
|
+
|
|
161
|
+
Each change is scored against its base commit, at the same as-of date. Base commit runs are cached by tree hash, methodology version and as-of date.
|
|
162
|
+
|
|
163
|
+
A change is flagged when **any dimension drops by 3.0 points or more** (unrounded), or when **the band drops**. A flagged pull request gets the `health-regression` label. Flags never block a merge.
|
|
164
|
+
|
|
165
|
+
Telegram summary format:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
Health 62 Stable (+1.4) · Safety net +6 · Complexity −1 · ⚠ none
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 6. The six dimensions
|
|
172
|
+
|
|
173
|
+
| # | Dimension | Question |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| D1 | Safety net | Will a change break something without anyone noticing? |
|
|
176
|
+
| D2 | Type & static rigor | Does the code tell you what it expects before you run it? |
|
|
177
|
+
| D3 | Architecture & boundaries | Can you change one part without reading the rest? |
|
|
178
|
+
| D4 | Complexity & maintainability | How much does someone have to read to make one change correctly? |
|
|
179
|
+
| D5 | Dependency & security hygiene | Is the code built on parts that are known, current and safe? |
|
|
180
|
+
| D6 | Operability & feedback loop | Can someone new set it up, check a change and know they are done? |
|
|
181
|
+
|
|
182
|
+
### D1. Safety net
|
|
183
|
+
|
|
184
|
+
**Question.** Will a change break something without anyone noticing?
|
|
185
|
+
|
|
186
|
+
**Why it matters for agents.** An agent cannot remember how the system is supposed to behave. The test suite is the only record of that behaviour it can check its change against.
|
|
187
|
+
|
|
188
|
+
| # | Parameter | Layer | Weight |
|
|
189
|
+
|---|---|---|---|
|
|
190
|
+
| 1.1 | Suite pass rate | Execution | 20% |
|
|
191
|
+
| 1.2 | Line coverage | Execution | 25% |
|
|
192
|
+
| 1.3 | Branch coverage | Execution | 15% |
|
|
193
|
+
| 1.4 | Modules with tests | Static | 25% |
|
|
194
|
+
| 1.5 | Tests that assert something | Static | 15% |
|
|
195
|
+
|
|
196
|
+
#### 1.1 Suite pass rate
|
|
197
|
+
|
|
198
|
+
Weight 20%. Execution layer. Scores 0 if any of these steps fail: install, test run. Pooled as a ratio across languages and packages.
|
|
199
|
+
|
|
200
|
+
**Raw value** (percent). passed / (passed + failed + errored) test cases. Skipped and expected-failure tests are excluded from both numerator and denominator. A test file that fails to load or collect counts as one failed test case.
|
|
201
|
+
|
|
202
|
+
| Raw | Score | Meaning |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| 80 | 0 | The suite is red. Nobody can use it to check a change. |
|
|
205
|
+
| 95 | 50 | Mostly green, with known failures people have learned to ignore. |
|
|
206
|
+
| 100 | 100 | Green. A new failure means the change broke something. |
|
|
207
|
+
|
|
208
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 0.
|
|
209
|
+
|
|
210
|
+
**How it is measured.**
|
|
211
|
+
|
|
212
|
+
- TypeScript / JavaScript: vitest, jest, mocha or node:test, detected from package.json dependencies and the test script. Run once with a JSON reporter. Playwright and Cypress suites are not run.
|
|
213
|
+
- Python: pytest (also runs unittest suites), with --continue-on-collection-errors and a JSON or JUnit report.
|
|
214
|
+
|
|
215
|
+
**Why.** A suite with known failures stops working as a check: a new failure looks the same as the old ones. The curve is steep on purpose. 95% of a 200-test suite is 10 failing tests, which is already enough for people to stop reading the output.
|
|
216
|
+
|
|
217
|
+
#### 1.2 Line coverage
|
|
218
|
+
|
|
219
|
+
Weight 25%. Execution layer. Scores 0 if any of these steps fail: install, test run. Pooled as a ratio across languages and packages.
|
|
220
|
+
|
|
221
|
+
**Raw value** (percent). Covered lines / coverable lines across all logic files, including logic files no test loads. A file no test loads counts as 0% covered. For TypeScript and JavaScript the coverable lines are health's code lines (lines with at least one token). A line counts as covered when the innermost statement or function containing it ran.
|
|
222
|
+
|
|
223
|
+
| Raw | Score | Meaning |
|
|
224
|
+
|---|---|---|
|
|
225
|
+
| 10 | 0 | No safety net. |
|
|
226
|
+
| 40 | 50 | Happy paths only. |
|
|
227
|
+
| 60 | 75 | Acceptable. |
|
|
228
|
+
| 80 | 100 | Well covered. No extra credit above this. |
|
|
229
|
+
|
|
230
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 0.
|
|
231
|
+
|
|
232
|
+
**How it is measured.**
|
|
233
|
+
|
|
234
|
+
- TypeScript / JavaScript: v8 or istanbul coverage with the json-summary and json reporters (vitest coverage.include, jest collectCoverageFrom over logic files, c8 for mocha and node:test). Lines are counted by health from the json report's statement and function ranges, not taken from the tool, so the same code gives the same count under every runner. Repository coverage thresholds are disabled so they cannot fail the run.
|
|
235
|
+
- Python: coverage.py through pytest-cov, with --cov set to every first-party package so files no test imports are included.
|
|
236
|
+
|
|
237
|
+
**Why.** Coverage shows which code no test executes. It does not show that the executed code is checked, which is why 1.5 exists. The 60% anchor matches Google's "acceptable" level. The 100 anchor is 80%, which is the coverage SonarQube's default quality gate requires on new code and sits between Google's "commendable" (75%) and "exemplary" (90%). Above 80% the methodology gives no extra credit, because the remaining code is often glue that costs more to test than it returns.
|
|
238
|
+
|
|
239
|
+
**Sources.**
|
|
240
|
+
|
|
241
|
+
- Arguelles, Ivanković, Bender. Code Coverage Best Practices. Google Testing Blog, 2020. https://testing.googleblog.com/2020/08/code-coverage-best-practices.html
|
|
242
|
+
- SonarQube default quality gate (Sonar way): coverage on new code at least 80%.
|
|
243
|
+
|
|
244
|
+
#### 1.3 Branch coverage
|
|
245
|
+
|
|
246
|
+
Weight 15%. Execution layer. Scores 0 if any of these steps fail: install, test run. Pooled as a ratio across languages and packages.
|
|
247
|
+
|
|
248
|
+
**Raw value** (percent). Covered branches / total branches across all logic files, including files no test loads.
|
|
249
|
+
|
|
250
|
+
| Raw | Score | Meaning |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| 5 | 0 | Only one path through each decision is ever run. |
|
|
253
|
+
| 30 | 50 | Error and edge paths are mostly untested. |
|
|
254
|
+
| 50 | 75 | Main error paths are tested. |
|
|
255
|
+
| 70 | 100 | Most decisions are tested both ways. |
|
|
256
|
+
|
|
257
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 0.
|
|
258
|
+
|
|
259
|
+
**How it is measured.**
|
|
260
|
+
|
|
261
|
+
- TypeScript / JavaScript: Same coverage run as 1.2; branch totals from json-summary. They come from the runner's coverage tool and can differ slightly between runners on the same code.
|
|
262
|
+
- Python: Same run as 1.2 with --cov-branch.
|
|
263
|
+
|
|
264
|
+
**Why.** Tests written quickly cover the happy path. Line coverage can be high while every error branch is untested. Branch coverage shows that gap. Branch percentages are lower than line percentages on the same code, so the anchors sit about 10 points lower than 1.2's.
|
|
265
|
+
|
|
266
|
+
#### 1.4 Modules with tests
|
|
267
|
+
|
|
268
|
+
Weight 25%. Static layer. Pooled as a ratio across languages and packages.
|
|
269
|
+
|
|
270
|
+
**Raw value** (percent). Logic files directly imported by at least one test file / all logic files. Imports are followed through barrel files and Python __init__.py re-exports to the file that defines the imported name. Transitive imports do not count.
|
|
271
|
+
|
|
272
|
+
| Raw | Score | Meaning |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| 10 | 0 | Almost nothing has a test. |
|
|
275
|
+
| 40 | 50 | Some modules have tests; most do not. |
|
|
276
|
+
| 70 | 75 | Most modules have a test. |
|
|
277
|
+
| 90 | 100 | Nearly every module has a test. |
|
|
278
|
+
|
|
279
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 0.
|
|
280
|
+
|
|
281
|
+
**How it is measured.**
|
|
282
|
+
|
|
283
|
+
- TypeScript / JavaScript: AST import graph from test files (static imports, require and dynamic import with a literal path).
|
|
284
|
+
- Python: AST import graph from test files (import and from-import statements).
|
|
285
|
+
|
|
286
|
+
**Why.** This is the static counterpart to coverage. It still works when the suite does not run, so a repository with tests that are broken gets partial credit, and "make the existing tests run" shows up as a gain in 1.1 to 1.3. Only direct imports count; otherwise one test that imports the app entry point would mark every file as tested.
|
|
287
|
+
|
|
288
|
+
#### 1.5 Tests that assert something
|
|
289
|
+
|
|
290
|
+
Weight 15%. Static layer. Pooled as a ratio across languages and packages.
|
|
291
|
+
|
|
292
|
+
**Raw value** (percent). Test cases containing at least one assertion / all test cases. An assertion inside a helper function defined in the same file and called by the test counts (one level deep).
|
|
293
|
+
|
|
294
|
+
| Raw | Score | Meaning |
|
|
295
|
+
|---|---|---|
|
|
296
|
+
| 50 | 0 | Many tests run code without checking anything. |
|
|
297
|
+
| 80 | 50 | A noticeable share of tests check nothing. |
|
|
298
|
+
| 95 | 75 | A few tests check nothing. |
|
|
299
|
+
| 100 | 100 | Every test checks something. |
|
|
300
|
+
|
|
301
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 0.
|
|
302
|
+
|
|
303
|
+
**How it is measured.**
|
|
304
|
+
|
|
305
|
+
- TypeScript / JavaScript: Test case: a call to it, test or their .each/.only/.concurrent variants. Assertion: expect(...), assert(...), assert.*, expectTypeOf(...), t.assert* / t.is / t.deepEqual (node:test and ava contexts).
|
|
306
|
+
- Python: Test case: a test_* function in a test module, or a test_* method of a unittest.TestCase subclass. Assertion: an assert statement, self.assert*, pytest.raises, pytest.warns, or a mock method call starting with assert_.
|
|
307
|
+
|
|
308
|
+
**Why.** A test without an assertion raises coverage and checks nothing. This is the cheapest way to game 1.2 and 1.3, and it is common in generated tests. The anchors are strict because writing a test without an assertion is almost never intentional.
|
|
309
|
+
|
|
310
|
+
### D2. Type & static rigor
|
|
311
|
+
|
|
312
|
+
**Question.** Does the code tell you what it expects before you run it?
|
|
313
|
+
|
|
314
|
+
**Why it matters for agents.** Type errors and lint findings are the fastest feedback an agent gets. A strict typechecker catches a wrong change in seconds, before any test runs, and the types document what each function accepts and returns.
|
|
315
|
+
|
|
316
|
+
| # | Parameter | Layer | Weight |
|
|
317
|
+
|---|---|---|---|
|
|
318
|
+
| 2.1 | Typecheck errors | Execution | 25% |
|
|
319
|
+
| 2.2 | Declared strictness | Static | 20% |
|
|
320
|
+
| 2.3 | Escape hatches | Static | 20% |
|
|
321
|
+
| 2.4 | Typed public surface | Static | 15% |
|
|
322
|
+
| 2.5 | Baseline lint findings | Execution | 20% |
|
|
323
|
+
|
|
324
|
+
#### 2.1 Typecheck errors
|
|
325
|
+
|
|
326
|
+
Weight 25%. Execution layer. Scores 0 if any of these steps fail: install, typecheck run. Pooled as a ratio across languages and packages.
|
|
327
|
+
|
|
328
|
+
**Raw value** (errors per 1,000 source lines). Typechecker errors located in in-scope files (logic and test), divided by source lines / 1,000. Errors in excluded files and in dependencies are ignored. Warnings are ignored.
|
|
329
|
+
|
|
330
|
+
| Raw | Score | Meaning |
|
|
331
|
+
|---|---|---|
|
|
332
|
+
| 0 | 100 | Clean. A new error means the change introduced it. |
|
|
333
|
+
| 1 | 75 | A few known errors. |
|
|
334
|
+
| 5 | 50 | Errors are routine and people work around them. |
|
|
335
|
+
| 20 | 0 | The typechecker is ignored. |
|
|
336
|
+
|
|
337
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
338
|
+
|
|
339
|
+
**How it is measured.**
|
|
340
|
+
|
|
341
|
+
- TypeScript / JavaScript: tsc --noEmit with the repository's tsconfig.json (each tsconfig that a workspace package declares). JavaScript-only repositories without a tsconfig or jsconfig are checked with a default config: allowJs, checkJs, strict false.
|
|
342
|
+
- Python: pyright with the repository's configuration (pyrightconfig.json or [tool.pyright]). If only mypy is configured, mypy with its configuration. If both are configured, pyright. If neither, pyright in basic mode.
|
|
343
|
+
|
|
344
|
+
**Why.** The repository's own config is used so the count reflects what its developers see. A config error, or a checker that cannot start, is a repository failure and scores 0. Untyped code is checked with a default config on purpose, so "add types" is visible as a score change.
|
|
345
|
+
|
|
346
|
+
**Sources.**
|
|
347
|
+
|
|
348
|
+
- TypeScript: Type checking JavaScript files (checkJs). https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html
|
|
349
|
+
- Pyright configuration: typeCheckingMode. https://microsoft.github.io/pyright/#/configuration
|
|
350
|
+
|
|
351
|
+
#### 2.2 Declared strictness
|
|
352
|
+
|
|
353
|
+
Weight 20%. Static layer. Scored per language and package, then combined by logic-line weight.
|
|
354
|
+
|
|
355
|
+
**Raw value** (level). The strictness the repository's typechecker configuration declares, read from the config files without running anything.
|
|
356
|
+
|
|
357
|
+
TypeScript / JavaScript:
|
|
358
|
+
|
|
359
|
+
| Level | Score | Meaning |
|
|
360
|
+
|---|---|---|
|
|
361
|
+
| none | 0 | No strict-family flag is enabled, or JavaScript without checkJs. |
|
|
362
|
+
| partial | 40 | Some strict-family flags are enabled, or strict is true with one or more strict-family flags turned back off. |
|
|
363
|
+
| strict | 75 | strict is true and no strict-family flag is turned off. |
|
|
364
|
+
| strict_plus | 100 | strict plus noUncheckedIndexedAccess. |
|
|
365
|
+
|
|
366
|
+
Python:
|
|
367
|
+
|
|
368
|
+
| Level | Score | Meaning |
|
|
369
|
+
|---|---|---|
|
|
370
|
+
| none | 0 | No pyright or mypy configuration. |
|
|
371
|
+
| basic | 40 | pyright typeCheckingMode basic, or mypy with default settings. |
|
|
372
|
+
| standard | 75 | pyright typeCheckingMode standard (pyright's default when a config exists), or mypy with disallow_untyped_defs and check_untyped_defs. |
|
|
373
|
+
| strict | 100 | pyright typeCheckingMode strict, or mypy strict = true. |
|
|
374
|
+
|
|
375
|
+
**How it is measured.**
|
|
376
|
+
|
|
377
|
+
- TypeScript / JavaScript: tsconfig.json with extends resolved. Strict-family flags: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, alwaysStrict.
|
|
378
|
+
- Python: pyrightconfig.json, [tool.pyright] or [tool.basedpyright] in pyproject.toml; mypy.ini, .mypy.ini, setup.cfg [mypy] or [tool.mypy] in pyproject.toml. When both are configured, pyright's level is used.
|
|
379
|
+
|
|
380
|
+
**Why.** 2.1 counts errors under whatever config the repository chose. Without 2.2, turning strictness off would raise the score. Strict null checks catch access to null and undefined values, a common runtime error in both languages. noUncheckedIndexedAccess makes array and record lookups return possibly undefined values, which is where strict TypeScript still lets runtime errors through.
|
|
381
|
+
|
|
382
|
+
**Sources.**
|
|
383
|
+
|
|
384
|
+
- TypeScript TSConfig reference: strict. https://www.typescriptlang.org/tsconfig#strict
|
|
385
|
+
- TypeScript TSConfig reference: noUncheckedIndexedAccess. https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess
|
|
386
|
+
- Pyright configuration: typeCheckingMode. https://microsoft.github.io/pyright/#/configuration
|
|
387
|
+
- mypy command line: --strict. https://mypy.readthedocs.io/en/stable/command_line.html
|
|
388
|
+
|
|
389
|
+
#### 2.3 Escape hatches
|
|
390
|
+
|
|
391
|
+
Weight 20%. Static layer. Pooled as a ratio across languages and packages.
|
|
392
|
+
|
|
393
|
+
**Raw value** (escape hatches per 1,000 logic lines). Points where code opts out of the typechecker, counted in logic files, divided by logic lines / 1,000. A file-level opt-out counts as 10.
|
|
394
|
+
|
|
395
|
+
| Raw | Score | Meaning |
|
|
396
|
+
|---|---|---|
|
|
397
|
+
| 0 | 100 | The types are trusted everywhere. |
|
|
398
|
+
| 2 | 75 | A few documented exceptions. |
|
|
399
|
+
| 5 | 50 | Casts are a common way to make errors go away. |
|
|
400
|
+
| 15 | 0 | The types say little about what the code does. |
|
|
401
|
+
|
|
402
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
403
|
+
|
|
404
|
+
**How it is measured.**
|
|
405
|
+
|
|
406
|
+
- TypeScript / JavaScript: Each counts 1: an explicit any type; an as assertion (as const is excluded; each as in "as unknown as T" counts separately); an angle-bracket assertion <T>x; a non-null assertion x!; a definite assignment assertion x!: T; @ts-ignore; @ts-expect-error. Counts 10: @ts-nocheck.
|
|
407
|
+
- Python: Each counts 1: Any in an annotation; typing.cast(...); # type: ignore on a line; # pyright: ignore on a line. Counts 10: # type: ignore before the first statement of a module; # mypy: ignore-errors.
|
|
408
|
+
|
|
409
|
+
**Why.** Each escape hatch is a place where the typechecker cannot help the next person. A change that breaks a contract passes the typechecker silently at that point. The typescript-eslint no-unsafe-* rules are not used in 2.5, so the same problem is not counted twice.
|
|
410
|
+
|
|
411
|
+
#### 2.4 Typed public surface
|
|
412
|
+
|
|
413
|
+
Weight 15%. Static layer. Pooled as a ratio across languages and packages.
|
|
414
|
+
|
|
415
|
+
**Raw value** (percent). Typed slots / all slots on the public surface of logic files. In TypeScript and JavaScript a slot is a parameter. In Python a slot is a parameter or a return type.
|
|
416
|
+
|
|
417
|
+
| Raw | Score | Meaning |
|
|
418
|
+
|---|---|---|
|
|
419
|
+
| 20 | 0 | The public surface is untyped. |
|
|
420
|
+
| 50 | 50 | Half of what callers use is typed. |
|
|
421
|
+
| 80 | 75 | Most of what callers use is typed. |
|
|
422
|
+
| 100 | 100 | Every public function says what it accepts. |
|
|
423
|
+
|
|
424
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
425
|
+
|
|
426
|
+
**How it is measured.**
|
|
427
|
+
|
|
428
|
+
- TypeScript / JavaScript: Public surface: exported functions, exported arrow functions and function expressions, default exports, and public methods of exported classes. A parameter is typed if it has a type annotation, a default value, a contextual type (for example a callback passed to a typed function), or a JSDoc @param type in a file checked with checkJs.
|
|
429
|
+
- Python: Public surface: module-level functions and classes whose names do not start with _, and their methods whose names do not start with _ (dunder methods are included). If __all__ is defined, it decides what is public. Modules whose names start with _ are excluded. self and cls are not slots. __init__ needs no return annotation.
|
|
430
|
+
|
|
431
|
+
**Why.** The public surface is what the next person calls without reading the body. In TypeScript, inferred return types are reliable, so only parameters are counted. In Python, an unannotated return is Any, so both are counted. Untyped JavaScript and untyped Python score low on purpose.
|
|
432
|
+
|
|
433
|
+
**Sources.**
|
|
434
|
+
|
|
435
|
+
- PEP 484: Type Hints. https://peps.python.org/pep-0484/
|
|
436
|
+
|
|
437
|
+
#### 2.5 Baseline lint findings
|
|
438
|
+
|
|
439
|
+
Weight 20%. Execution layer. Scores 0 if any of these steps fail: install, lint run. Pooled as a ratio across languages and packages.
|
|
440
|
+
|
|
441
|
+
**Raw value** (findings per 1,000 source lines). Findings from the methodology's pinned correctness ruleset in in-scope files (logic and test), divided by source lines / 1,000. The repository's own lint config is not used. A file that cannot be parsed counts as one finding.
|
|
442
|
+
|
|
443
|
+
| Raw | Score | Meaning |
|
|
444
|
+
|---|---|---|
|
|
445
|
+
| 0 | 100 | No known bug patterns. |
|
|
446
|
+
| 2 | 75 | A few. |
|
|
447
|
+
| 5 | 50 | Bug patterns are common. |
|
|
448
|
+
| 20 | 0 | The code has never been linted for correctness. |
|
|
449
|
+
|
|
450
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
451
|
+
|
|
452
|
+
**How it is measured.**
|
|
453
|
+
|
|
454
|
+
- TypeScript / JavaScript: ESLint with the pinned ruleset. Core rules: no-unreachable, no-dupe-keys, no-self-compare, no-unsafe-finally, no-fallthrough, no-cond-assign, use-isnan, valid-typeof, no-unsafe-optional-chaining, no-dupe-else-if, no-duplicate-case, no-self-assign, getter-return, no-async-promise-executor, no-constant-binary-expression, no-unused-vars (or @typescript-eslint/no-unused-vars on TypeScript files). Type-aware rules on TypeScript files only: @typescript-eslint/no-floating-promises, no-misused-promises, await-thenable. TypeScript files outside every tsconfig are linted without the type-aware rules. react-hooks/rules-of-hooks when react is a dependency.
|
|
455
|
+
- Python: ruff with the rule sets F (Pyflakes), B (flake8-bugbear) and E9 (syntax and IO errors).
|
|
456
|
+
|
|
457
|
+
**Why.** Each rule in the set catches a real bug pattern, such as an unawaited promise or an unreachable branch. Style rules are excluded, so formatting choices do not affect the score. The ruleset is pinned so every repository is judged by the same rules, and so turning rules off in the repository's own config does not raise the score.
|
|
458
|
+
|
|
459
|
+
**Sources.**
|
|
460
|
+
|
|
461
|
+
- typescript-eslint rules. https://typescript-eslint.io/rules/
|
|
462
|
+
- Ruff rules. https://docs.astral.sh/ruff/rules/
|
|
463
|
+
|
|
464
|
+
### D3. Architecture & boundaries
|
|
465
|
+
|
|
466
|
+
**Question.** Can you change one part without reading the rest?
|
|
467
|
+
|
|
468
|
+
**Why it matters for agents.** An agent works inside a limited context window. Enforced boundaries limit how much of the codebase one change can affect, so the agent only needs to read the module it is changing and that module's public entry points.
|
|
469
|
+
|
|
470
|
+
| # | Parameter | Layer | Weight |
|
|
471
|
+
|---|---|---|---|
|
|
472
|
+
| 3.1 | Boundaries declared and enforced | Static | 15% |
|
|
473
|
+
| 3.2 | Boundary violations | Static | 30% |
|
|
474
|
+
| 3.3 | Import cycles | Static | 25% |
|
|
475
|
+
| 3.4 | Public-entry discipline | Static | 15% |
|
|
476
|
+
| 3.5 | Domain purity | Static | 15% |
|
|
477
|
+
|
|
478
|
+
**Presets.** When a repository declares no import rules, the scan uses the preset for the detected framework (presets/*.yaml). When it declares rules (see 3.1), its rules replace the preset. Detection order: nextjs, express, django, fastapi, generic. Each workspace package gets its own preset.
|
|
479
|
+
|
|
480
|
+
#### 3.1 Boundaries declared and enforced
|
|
481
|
+
|
|
482
|
+
Weight 15%. Static layer. Scored per language and package, then combined by logic-line weight.
|
|
483
|
+
|
|
484
|
+
**Raw value** (level). Whether the repository declares import rules in a supported rules file, and whether CI runs them.
|
|
485
|
+
|
|
486
|
+
| Level | Score | Meaning |
|
|
487
|
+
|---|---|---|
|
|
488
|
+
| none | 0 | No import rules are declared. |
|
|
489
|
+
| declared | 50 | A rules file declares at least one direction rule, but CI does not run it. |
|
|
490
|
+
| enforced | 100 | The rules are declared and CI runs them on pull requests. |
|
|
491
|
+
|
|
492
|
+
**How it is measured.**
|
|
493
|
+
|
|
494
|
+
- TypeScript / JavaScript: Rules file: .dependency-cruiser.{js,cjs,mjs,json}. Declared means at least one forbidden or allowed rule whose from and to paths name different directories. Enforced means a CI file invokes depcruise, directly or through a package script or make target (up to 3 levels).
|
|
495
|
+
- Python: Rules file: .importlinter, [importlinter] in setup.cfg, or [tool.importlinter] in pyproject.toml. Declared means at least one layers, forbidden or independence contract. Enforced means a CI file invokes lint-imports, directly or through a script or make target.
|
|
496
|
+
- Ci files: .github/workflows/*, .gitlab-ci.yml, .circleci/config.yml, bitbucket-pipelines.yml, azure-pipelines.yml.
|
|
497
|
+
|
|
498
|
+
**Why.** An architecture that exists only in people's heads is not visible to an agent and erodes with each change. A rules file states the architecture in a form a tool can check. Running it in CI is what stops new violations.
|
|
499
|
+
|
|
500
|
+
**Sources.**
|
|
501
|
+
|
|
502
|
+
- dependency-cruiser rules reference. https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md
|
|
503
|
+
- import-linter contract types. https://import-linter.readthedocs.io/en/stable/contract_types.html
|
|
504
|
+
|
|
505
|
+
#### 3.2 Boundary violations
|
|
506
|
+
|
|
507
|
+
Weight 30%. Static layer. Pooled as a ratio across languages and packages.
|
|
508
|
+
|
|
509
|
+
**Raw value** (violations per 1,000 imports). Import edges that break a rule, divided by all internal import edges / 1,000. Rules come from the repository's declared rules file, or from the preset when none is declared. Rules with severity error or warn count; info is ignored. Each violating edge counts once, even when it breaks several rules. Imports from test files are excluded. Type-only imports are included, except where a preset rule says otherwise.
|
|
510
|
+
|
|
511
|
+
| Raw | Score | Meaning |
|
|
512
|
+
|---|---|---|
|
|
513
|
+
| 0 | 100 | Every import respects the architecture. |
|
|
514
|
+
| 2 | 75 | A few known shortcuts. |
|
|
515
|
+
| 10 | 50 | The layers exist in name only in places. |
|
|
516
|
+
| 30 | 0 | Anything imports anything. |
|
|
517
|
+
|
|
518
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
519
|
+
|
|
520
|
+
**How it is measured.**
|
|
521
|
+
|
|
522
|
+
- TypeScript / JavaScript: dependency-cruiser with the declared rules or the preset converted to dependency-cruiser rules.
|
|
523
|
+
- Python: grimp builds the import graph; import-linter checks the declared contracts or the preset converted to contracts.
|
|
524
|
+
|
|
525
|
+
**Why.** Each violation is a dependency a reader would not expect from the directory structure. The count is normalized per 1,000 imports so repositories of different sizes are comparable.
|
|
526
|
+
|
|
527
|
+
**Sources.**
|
|
528
|
+
|
|
529
|
+
- Martin, R. C. Clean Architecture. Prentice Hall, 2017 (the Dependency Rule).
|
|
530
|
+
- Cockburn, A. Hexagonal Architecture (Ports and Adapters), 2005. https://alistair.cockburn.us/hexagonal-architecture/
|
|
531
|
+
|
|
532
|
+
#### 3.3 Import cycles
|
|
533
|
+
|
|
534
|
+
Weight 25%. Static layer. Pooled as a ratio across languages and packages.
|
|
535
|
+
|
|
536
|
+
**Raw value** (percent). Logic files that belong to an import cycle / all logic files. A cycle is a strongly connected component of two or more files in the graph of module-level static imports. Type-only imports, dynamic imports, imports inside functions and imports under "if TYPE_CHECKING:" are excluded.
|
|
537
|
+
|
|
538
|
+
| Raw | Score | Meaning |
|
|
539
|
+
|---|---|---|
|
|
540
|
+
| 0 | 100 | The import graph has no cycles. |
|
|
541
|
+
| 2 | 75 | One or two small cycles. |
|
|
542
|
+
| 5 | 50 | Cycles connect several areas. |
|
|
543
|
+
| 15 | 0 | Large parts of the code depend on each other. |
|
|
544
|
+
|
|
545
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
546
|
+
|
|
547
|
+
**How it is measured.**
|
|
548
|
+
|
|
549
|
+
- TypeScript / JavaScript: dependency-cruiser cycle detection on the filtered graph.
|
|
550
|
+
- Python: grimp import graph, filtered as defined, then strongly connected components.
|
|
551
|
+
|
|
552
|
+
**Why.** Files in a cycle cannot be understood, tested or changed one at a time. Cycles also cause import-order bugs at runtime, especially in Python. Type-only imports are excluded because they have no runtime effect.
|
|
553
|
+
|
|
554
|
+
**Sources.**
|
|
555
|
+
|
|
556
|
+
- Martin, R. C. Agile Software Development: Principles, Patterns, and Practices. Prentice Hall, 2002 (the Acyclic Dependencies Principle).
|
|
557
|
+
|
|
558
|
+
#### 3.4 Public-entry discipline
|
|
559
|
+
|
|
560
|
+
Weight 15%. Static layer. Pooled as a ratio across languages and packages.
|
|
561
|
+
|
|
562
|
+
**Raw value** (percent). Imports that cross from one module into another and land on that module's public entry / all imports that cross between modules. Modules are defined by the preset (or by the independence or layer contracts in the declared rules). The public entry is index.* in TypeScript and JavaScript, and __init__.py in Python.
|
|
563
|
+
|
|
564
|
+
| Raw | Score | Meaning |
|
|
565
|
+
|---|---|---|
|
|
566
|
+
| 50 | 0 | Other modules reach into internals as often as not. |
|
|
567
|
+
| 75 | 50 | Entry points exist and are often bypassed. |
|
|
568
|
+
| 90 | 75 | Entry points are mostly used. |
|
|
569
|
+
| 100 | 100 | Every cross-module import goes through the entry point. |
|
|
570
|
+
|
|
571
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
572
|
+
|
|
573
|
+
**How it is measured.**
|
|
574
|
+
|
|
575
|
+
- TypeScript / JavaScript: Import graph from dependency-cruiser; module roots from the preset.
|
|
576
|
+
- Python: grimp import graph; module roots from the preset. In Django apps, models, services and selectors are also public entries.
|
|
577
|
+
|
|
578
|
+
**Not applicable when.** The repository has fewer than two modules as defined by the preset or the declared rules. Splitting code into modules is a design choice the methodology does not score.
|
|
579
|
+
|
|
580
|
+
**Why.** A public entry point is a small interface the next person can read instead of the whole module. When other modules import internals directly, every internal file becomes part of the interface and cannot be changed safely.
|
|
581
|
+
|
|
582
|
+
**Sources.**
|
|
583
|
+
|
|
584
|
+
- HackSoft Django Styleguide. https://github.com/HackSoftware/Django-Styleguide
|
|
585
|
+
|
|
586
|
+
#### 3.5 Domain purity
|
|
587
|
+
|
|
588
|
+
Weight 15%. Static layer. Pooled as a ratio across languages and packages.
|
|
589
|
+
|
|
590
|
+
**Raw value** (percent). Files in the domain layer that import a framework, database, network or filesystem package, or an infrastructure directory / all files in the domain layer.
|
|
591
|
+
|
|
592
|
+
| Raw | Score | Meaning |
|
|
593
|
+
|---|---|---|
|
|
594
|
+
| 0 | 100 | Domain logic is plain code that runs and tests without infrastructure. |
|
|
595
|
+
| 5 | 75 | A few domain files touch infrastructure. |
|
|
596
|
+
| 15 | 50 | Domain logic regularly reaches for the database or the network. |
|
|
597
|
+
| 40 | 0 | The domain layer is a directory name. |
|
|
598
|
+
|
|
599
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
600
|
+
|
|
601
|
+
**How it is measured.**
|
|
602
|
+
|
|
603
|
+
- Domain layer: Files under a directory named domain, or files in a layer named domain in the declared rules.
|
|
604
|
+
- TypeScript / JavaScript: Forbidden imports: next, react, react-dom, express, @prisma/client, drizzle-orm, pg, mysql2, mongoose, redis, ioredis, axios, node-fetch, fs, node:fs, http, node:http, https, node:https. A call to the global fetch also counts.
|
|
605
|
+
- Python: Forbidden imports: django, fastapi, flask, starlette, sqlalchemy, requests, httpx, aiohttp, boto3, psycopg, psycopg2, redis, pymongo. A call to the builtin open also counts.
|
|
606
|
+
- Infrastructure directories: infrastructure, infra, db, adapters.
|
|
607
|
+
|
|
608
|
+
**Not applicable when.** The repository declares no domain layer. Choosing a layered or domain-driven design is a design choice the methodology does not score.
|
|
609
|
+
|
|
610
|
+
**Why.** This is the deterministic part of domain-driven design alignment. Pure domain code can be tested without a database or network and changed without reading infrastructure code. The methodology does not judge whether the domain model is good; it checks that the repository keeps the boundary it declared.
|
|
611
|
+
|
|
612
|
+
**Sources.**
|
|
613
|
+
|
|
614
|
+
- Evans, E. Domain-Driven Design. Addison-Wesley, 2003.
|
|
615
|
+
- Cockburn, A. Hexagonal Architecture (Ports and Adapters), 2005. https://alistair.cockburn.us/hexagonal-architecture/
|
|
616
|
+
|
|
617
|
+
### D4. Complexity & maintainability
|
|
618
|
+
|
|
619
|
+
**Question.** How much does someone have to read to make one change correctly?
|
|
620
|
+
|
|
621
|
+
**Why it matters for agents.** Every line an agent reads uses context. Long files, complex functions, duplicated logic and dead code all increase how much it must read, and duplicated logic is where an agent fixes one copy and misses the other.
|
|
622
|
+
|
|
623
|
+
| # | Parameter | Layer | Weight |
|
|
624
|
+
|---|---|---|---|
|
|
625
|
+
| 4.1 | Complex functions | Static | 30% |
|
|
626
|
+
| 4.2 | Lines in large files | Static | 20% |
|
|
627
|
+
| 4.3 | Duplication | Static | 25% |
|
|
628
|
+
| 4.4 | Unused exports and files | Static | 25% |
|
|
629
|
+
|
|
630
|
+
#### 4.1 Complex functions
|
|
631
|
+
|
|
632
|
+
Weight 30%. Static layer. Pooled as a ratio across languages and packages.
|
|
633
|
+
|
|
634
|
+
**Raw value** (percent). Functions with cognitive complexity strictly greater than 15 / all functions in logic files, as enumerated by the tool.
|
|
635
|
+
|
|
636
|
+
| Raw | Score | Meaning |
|
|
637
|
+
|---|---|---|
|
|
638
|
+
| 0 | 100 | Every function can be read in one pass. |
|
|
639
|
+
| 3 | 75 | A few hard functions. |
|
|
640
|
+
| 8 | 50 | Hard functions in most areas. |
|
|
641
|
+
| 20 | 0 | Logic is concentrated in functions nobody wants to touch. |
|
|
642
|
+
|
|
643
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
644
|
+
|
|
645
|
+
**How it is measured.**
|
|
646
|
+
|
|
647
|
+
- TypeScript / JavaScript: eslint-plugin-sonarjs cognitive-complexity rule, threshold 15.
|
|
648
|
+
- Python: complexipy, threshold 15.
|
|
649
|
+
|
|
650
|
+
**Why.** Cognitive complexity counts nesting and breaks in linear flow, which is closer to how hard a function is to read than cyclomatic complexity. 15 is the default threshold in SonarQube, eslint-plugin-sonarjs and complexipy. Counting the share of functions over the threshold, instead of the mean, keeps many small functions from hiding a few large ones.
|
|
651
|
+
|
|
652
|
+
**Sources.**
|
|
653
|
+
|
|
654
|
+
- Campbell, G. A. Cognitive Complexity: A new way of measuring understandability. SonarSource, 2018. https://www.sonarsource.com/docs/CognitiveComplexity.pdf
|
|
655
|
+
- SonarQube rule S3776 (Cognitive Complexity of functions should not be too high), default threshold 15.
|
|
656
|
+
- McCabe, T. J. A Complexity Measure. IEEE Transactions on Software Engineering, 1976 (cyclomatic complexity, threshold 10).
|
|
657
|
+
|
|
658
|
+
#### 4.2 Lines in large files
|
|
659
|
+
|
|
660
|
+
Weight 20%. Static layer. Pooled as a ratio across languages and packages.
|
|
661
|
+
|
|
662
|
+
**Raw value** (percent). Logic lines in logic files with more than 500 logic lines / all logic lines.
|
|
663
|
+
|
|
664
|
+
| Raw | Score | Meaning |
|
|
665
|
+
|---|---|---|
|
|
666
|
+
| 0 | 100 | Every file fits in one read. |
|
|
667
|
+
| 10 | 75 | A few large files. |
|
|
668
|
+
| 25 | 50 | A quarter of the code lives in large files. |
|
|
669
|
+
| 50 | 0 | Most of the code lives in files too large to read whole. |
|
|
670
|
+
|
|
671
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
672
|
+
|
|
673
|
+
**How it is measured.**
|
|
674
|
+
|
|
675
|
+
- TypeScript / JavaScript: Logic line count per file (scope.yaml).
|
|
676
|
+
- Python: Logic line count per file (scope.yaml); docstrings do not count.
|
|
677
|
+
|
|
678
|
+
**Why.** A large file has to be read in parts, so changes to it are made with partial context. The threshold of 500 logic lines is more lenient than ESLint's max-lines default (300 physical lines) and stricter than Pylint's max-module-lines default (1,000). The raw value is a share of lines, not of files, so one 5,000-line file weighs more than one 501-line file.
|
|
679
|
+
|
|
680
|
+
**Sources.**
|
|
681
|
+
|
|
682
|
+
- ESLint max-lines rule, default 300. https://eslint.org/docs/latest/rules/max-lines
|
|
683
|
+
- Pylint too-many-lines (C0302), max-module-lines default 1000.
|
|
684
|
+
|
|
685
|
+
#### 4.3 Duplication
|
|
686
|
+
|
|
687
|
+
Weight 25%. Static layer. Pooled as a ratio across languages and packages.
|
|
688
|
+
|
|
689
|
+
**Raw value** (percent). Duplicated lines / all lines in logic files, as reported by jscpd.
|
|
690
|
+
|
|
691
|
+
| Raw | Score | Meaning |
|
|
692
|
+
|---|---|---|
|
|
693
|
+
| 3 | 100 | Duplication is incidental. |
|
|
694
|
+
| 5 | 75 | Some copied blocks. |
|
|
695
|
+
| 10 | 50 | Copy and paste is a common way to add features. |
|
|
696
|
+
| 20 | 0 | Large parts of the logic exist in several copies. |
|
|
697
|
+
|
|
698
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
699
|
+
|
|
700
|
+
**How it is measured.**
|
|
701
|
+
|
|
702
|
+
- TypeScript / JavaScript: jscpd, min-tokens 50, min-lines 5, logic files only.
|
|
703
|
+
- Python: jscpd, same settings.
|
|
704
|
+
|
|
705
|
+
**Why.** A bug fixed in one copy stays in the others. This is a common agent mistake: the agent finds one copy, fixes it, and its tests pass. 3% is the default duplication limit in SonarQube's quality gate.
|
|
706
|
+
|
|
707
|
+
**Sources.**
|
|
708
|
+
|
|
709
|
+
- SonarQube default quality gate (Sonar way): duplicated lines on new code at most 3%.
|
|
710
|
+
- jscpd. https://github.com/kucherenko/jscpd
|
|
711
|
+
|
|
712
|
+
#### 4.4 Unused exports and files
|
|
713
|
+
|
|
714
|
+
Weight 25%. Static layer. Pooled as a ratio across languages and packages.
|
|
715
|
+
|
|
716
|
+
**Raw value** (percent). (Unused exported symbols + exported symbols in unused files) / all exported symbols in logic files. Exported types count. Library entry points (package.json exports and main, Python __all__ of a distributed package) count as used.
|
|
717
|
+
|
|
718
|
+
| Raw | Score | Meaning |
|
|
719
|
+
|---|---|---|
|
|
720
|
+
| 0 | 100 | Everything exported is used. |
|
|
721
|
+
| 3 | 75 | A little dead code. |
|
|
722
|
+
| 8 | 50 | Dead code is common. |
|
|
723
|
+
| 20 | 0 | A large share of the code is never called. |
|
|
724
|
+
|
|
725
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
726
|
+
|
|
727
|
+
**How it is measured.**
|
|
728
|
+
|
|
729
|
+
- TypeScript / JavaScript: knip (unused files, exports and types), with framework plugins enabled.
|
|
730
|
+
- Python: vulture with min-confidence 60 for unused functions, classes and methods; decorated definitions are treated as used. A method is also treated as used when its class, directly or through base classes defined in the repository, has a base class defined outside the repository (standard library or third party), or when a base class defined in the repository has a method of the same name; object and Generic do not count as bases. Unused modules: modules no other module imports, excluding entry modules (__main__, manage.py, wsgi, asgi, settings, conftest, targets of [project.scripts], Django convention modules such as models, admin, apps, urls, views, and FastAPI main.py).
|
|
731
|
+
|
|
732
|
+
**Why.** Dead code costs reading time and looks like a place to make a change. An agent that edits an unused function gets no test failure and no effect. Decorated Python definitions are treated as used because frameworks register them by decorator (routes, fixtures, signals). Methods that override a base class are treated as used for the same reason: frameworks and the standard library call hooks such as do_GET, setUp or emit by name, which vulture cannot see.
|
|
733
|
+
|
|
734
|
+
**Sources.**
|
|
735
|
+
|
|
736
|
+
- knip. https://knip.dev
|
|
737
|
+
- vulture. https://github.com/jendrikseipp/vulture
|
|
738
|
+
|
|
739
|
+
### D5. Dependency & security hygiene
|
|
740
|
+
|
|
741
|
+
**Question.** Is the code built on parts that are known, current and safe?
|
|
742
|
+
|
|
743
|
+
**Why it matters for agents.** An agent adds dependencies and copies patterns from what it finds. An undeclared dependency, a stale major version or a committed secret gets copied into new code the same way good patterns do.
|
|
744
|
+
|
|
745
|
+
| # | Parameter | Layer | Weight |
|
|
746
|
+
|---|---|---|---|
|
|
747
|
+
| 5.1 | Known vulnerabilities in production dependencies | Static | 30% |
|
|
748
|
+
| 5.2 | Dependencies a major version behind | Static | 20% |
|
|
749
|
+
| 5.3 | Lockfile | Static | 15% |
|
|
750
|
+
| 5.4 | Secrets in the working tree | Static | 20% |
|
|
751
|
+
| 5.5 | Dependency declaration errors | Static | 15% |
|
|
752
|
+
|
|
753
|
+
**As of date.** Parameters 5.1 and 5.2 are evaluated at the run's as-of date: advisories published on or before that date count, and the latest version of a package is the latest published on or before that date.
|
|
754
|
+
|
|
755
|
+
#### 5.1 Known vulnerabilities in production dependencies
|
|
756
|
+
|
|
757
|
+
Weight 30%. Static layer. Pooled as a ratio across languages and packages.
|
|
758
|
+
|
|
759
|
+
**Raw value** (severity points). Sum of severity points over advisories affecting resolved production dependencies (direct and transitive). Critical 10, high 5, moderate 2, low 0.5. Each advisory counts once per package; aliases are deduplicated. Withdrawn advisories are excluded.
|
|
760
|
+
|
|
761
|
+
| Raw | Score | Meaning |
|
|
762
|
+
|---|---|---|
|
|
763
|
+
| 0 | 100 | No known vulnerabilities. |
|
|
764
|
+
| 2 | 75 | One moderate, or a few low. |
|
|
765
|
+
| 10 | 50 | A critical, or several highs. |
|
|
766
|
+
| 30 | 0 | Several criticals. Nobody is watching. |
|
|
767
|
+
|
|
768
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
769
|
+
|
|
770
|
+
**How it is measured.**
|
|
771
|
+
|
|
772
|
+
- TypeScript / JavaScript: osv-scanner on the lockfile, against the OSV snapshot for the as-of date. Production dependencies only.
|
|
773
|
+
- Python: osv-scanner on the lockfile, against the OSV snapshot for the as-of date. Production dependencies only.
|
|
774
|
+
- Severity: The GitHub Security Advisory severity label when present. Otherwise the CVSS v3.1 base score band (critical 9.0-10.0, high 7.0-8.9, moderate 4.0-6.9, low 0.1-3.9). Otherwise moderate.
|
|
775
|
+
|
|
776
|
+
**Why.** Severity points stop one critical advisory from scoring the same as one low one. The count is not normalized by repository size, because a vulnerability's risk does not shrink as the codebase grows. Development-only dependencies are reported separately and not scored.
|
|
777
|
+
|
|
778
|
+
**Sources.**
|
|
779
|
+
|
|
780
|
+
- OSV schema (published, withdrawn, aliases). https://ossf.github.io/osv-schema/
|
|
781
|
+
- FIRST. CVSS v3.1 Specification, qualitative severity rating scale. https://www.first.org/cvss/v3.1/specification-document
|
|
782
|
+
|
|
783
|
+
#### 5.2 Dependencies a major version behind
|
|
784
|
+
|
|
785
|
+
Weight 20%. Static layer. Pooled as a ratio across languages and packages.
|
|
786
|
+
|
|
787
|
+
**Raw value** (percent). Direct dependencies (production and development) whose resolved version is at least one major version behind the highest stable, non-yanked version published on or before the as-of date / all direct dependencies. For 0.x versions, a minor version bump counts as major.
|
|
788
|
+
|
|
789
|
+
| Raw | Score | Meaning |
|
|
790
|
+
|---|---|---|
|
|
791
|
+
| 0 | 100 | Every direct dependency is on its current major. |
|
|
792
|
+
| 10 | 75 | A few dependencies are behind. |
|
|
793
|
+
| 25 | 50 | A quarter of dependencies are behind. |
|
|
794
|
+
| 50 | 0 | Upgrades have stopped. |
|
|
795
|
+
|
|
796
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
797
|
+
|
|
798
|
+
**How it is measured.**
|
|
799
|
+
|
|
800
|
+
- TypeScript / JavaScript: npm registry publish times from the registry snapshot.
|
|
801
|
+
- Python: PyPI release upload times from the registry snapshot.
|
|
802
|
+
|
|
803
|
+
**Why.** Each major version behind is a migration someone will have to do, and an agent writing code against current documentation will use APIs the installed version does not have. Development dependencies are included because outdated tooling affects every change.
|
|
804
|
+
|
|
805
|
+
**Sources.**
|
|
806
|
+
|
|
807
|
+
- Semantic Versioning 2.0.0, item 4 (major version zero). https://semver.org
|
|
808
|
+
|
|
809
|
+
#### 5.3 Lockfile
|
|
810
|
+
|
|
811
|
+
Weight 15%. Static layer. Scored per language and package, then combined by logic-line weight.
|
|
812
|
+
|
|
813
|
+
**Raw value** (level). Whether a lockfile exists and matches the manifest.
|
|
814
|
+
|
|
815
|
+
| Level | Score | Meaning |
|
|
816
|
+
|---|---|---|
|
|
817
|
+
| none | 0 | No lockfile. Every install can resolve different versions. |
|
|
818
|
+
| out_of_sync | 40 | A lockfile exists but does not match the manifest. |
|
|
819
|
+
| in_sync | 100 | The lockfile matches the manifest. |
|
|
820
|
+
|
|
821
|
+
**How it is measured.**
|
|
822
|
+
|
|
823
|
+
- TypeScript / JavaScript: package-lock.json, npm-shrinkwrap.json, pnpm-lock.yaml, yarn.lock or bun.lock. In sync means every dependency declared in package.json is present in the lockfile with a version that satisfies its range.
|
|
824
|
+
- Python: uv.lock, poetry.lock, pdm.lock, Pipfile.lock, or requirements files with every requirement pinned with == and --hash. In sync means every dependency declared in pyproject.toml (or Pipfile) is in the lockfile with a version that satisfies its specifier, or for uv.lock and poetry.lock, the lockfile's recorded manifest hash matches.
|
|
825
|
+
|
|
826
|
+
**Why.** Without a lockfile, two installs of the same commit can produce different code. Results stop being reproducible, including this methodology's results.
|
|
827
|
+
|
|
828
|
+
**Sources.**
|
|
829
|
+
|
|
830
|
+
- npm ci documentation. https://docs.npmjs.com/cli/commands/npm-ci
|
|
831
|
+
|
|
832
|
+
#### 5.4 Secrets in the working tree
|
|
833
|
+
|
|
834
|
+
Weight 20%. Static layer. Scored per language and package, then combined by logic-line weight.
|
|
835
|
+
|
|
836
|
+
**Raw value** (count). Secrets found by the pinned gitleaks default ruleset in every tracked file at the scanned commit. .healthignore does not apply. .gitleaksignore applies and is part of the ignore set.
|
|
837
|
+
|
|
838
|
+
| Raw | Score | Meaning |
|
|
839
|
+
|---|---|---|
|
|
840
|
+
| 0 | 100 | No secrets in the code. |
|
|
841
|
+
| 1 | 0 | At least one secret is in the code. |
|
|
842
|
+
|
|
843
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
844
|
+
|
|
845
|
+
**How it is measured.**
|
|
846
|
+
|
|
847
|
+
- All languages: gitleaks dir on the checkout, default ruleset of the pinned version.
|
|
848
|
+
|
|
849
|
+
**Why.** One committed secret is enough to cause a breach, so the parameter is binary. Secrets in git history are reported but not scored, because the fix is rotating the key, which cannot be seen in the code.
|
|
850
|
+
|
|
851
|
+
**Sources.**
|
|
852
|
+
|
|
853
|
+
- CWE-798: Use of Hard-coded Credentials. https://cwe.mitre.org/data/definitions/798.html
|
|
854
|
+
- gitleaks. https://github.com/gitleaks/gitleaks
|
|
855
|
+
|
|
856
|
+
#### 5.5 Dependency declaration errors
|
|
857
|
+
|
|
858
|
+
Weight 15%. Static layer. Pooled as a ratio across languages and packages.
|
|
859
|
+
|
|
860
|
+
**Raw value** (percent). (Declared dependencies that are never imported + imported packages that are not declared) / declared direct dependencies.
|
|
861
|
+
|
|
862
|
+
| Raw | Score | Meaning |
|
|
863
|
+
|---|---|---|
|
|
864
|
+
| 0 | 100 | The manifest matches the code. |
|
|
865
|
+
| 5 | 75 | A few stale or missing entries. |
|
|
866
|
+
| 15 | 50 | The manifest and the code have drifted apart. |
|
|
867
|
+
| 30 | 0 | Nobody maintains the manifest. |
|
|
868
|
+
|
|
869
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 100.
|
|
870
|
+
|
|
871
|
+
**How it is measured.**
|
|
872
|
+
|
|
873
|
+
- TypeScript / JavaScript: knip unused dependencies plus unlisted dependencies.
|
|
874
|
+
- Python: deptry DEP001 (missing), DEP002 (unused) and DEP003 (transitive imported directly). DEP004 (development dependency used in production code) is not counted.
|
|
875
|
+
|
|
876
|
+
**Why.** An undeclared import works until the transitive dependency that provides it changes. An unused declaration adds install time and attack surface. Both mislead a reader about what the code depends on.
|
|
877
|
+
|
|
878
|
+
**Sources.**
|
|
879
|
+
|
|
880
|
+
- deptry rules. https://deptry.com/rules-violations/
|
|
881
|
+
|
|
882
|
+
### D6. Operability & feedback loop
|
|
883
|
+
|
|
884
|
+
**Question.** Can someone new set it up, check a change and know they are done?
|
|
885
|
+
|
|
886
|
+
**Why it matters for agents.** An agent needs a command that tells it whether its change is correct, and it needs to find that command without asking. A slow or missing check means the agent stops early or guesses.
|
|
887
|
+
|
|
888
|
+
| # | Parameter | Layer | Weight |
|
|
889
|
+
|---|---|---|---|
|
|
890
|
+
| 6.1 | Clean install and build | Execution | 25% |
|
|
891
|
+
| 6.2 | One-command verify | Static | 15% |
|
|
892
|
+
| 6.3 | Feedback loop time | Execution | 10% |
|
|
893
|
+
| 6.4 | CI runs the checks on pull requests | Static | 20% |
|
|
894
|
+
| 6.5 | Reproducible environment | Static | 15% |
|
|
895
|
+
| 6.6 | Commands documented for agents | Static | 15% |
|
|
896
|
+
|
|
897
|
+
**Script resolution.** Parameters 6.2, 6.4 and 6.6 follow package.json scripts, Makefile targets, justfile recipes, tox and nox sessions, and pyproject.toml script tables up to 3 levels deep. Recognised tools: typecheckers tsc, vue-tsc, pyright, basedpyright, mypy; linters eslint, biome, oxlint, ruff, flake8, pylint; test runners vitest, jest, mocha, node --test, pytest, python -m unittest, tox, nox. The prefixes npx, bunx, pnpm exec, yarn, uv run, poetry run, pdm run and python -m are removed before a command is matched.
|
|
898
|
+
|
|
899
|
+
#### 6.1 Clean install and build
|
|
900
|
+
|
|
901
|
+
Weight 25%. Execution layer. Scored per language and package, then combined by logic-line weight.
|
|
902
|
+
|
|
903
|
+
**Raw value** (level). Result of a clean install from the lockfile, followed by the build step, in the pinned runner.
|
|
904
|
+
|
|
905
|
+
| Level | Score | Meaning |
|
|
906
|
+
|---|---|---|
|
|
907
|
+
| install_fails | 0 | The project does not install from a clean checkout. |
|
|
908
|
+
| build_fails | 40 | It installs, but the build fails. |
|
|
909
|
+
| works | 100 | It installs and builds from a clean checkout. A project with no build step scores 100 when the install succeeds. |
|
|
910
|
+
|
|
911
|
+
**How it is measured.**
|
|
912
|
+
|
|
913
|
+
- TypeScript / JavaScript: Install: npm ci, pnpm install --frozen-lockfile, yarn install --immutable or bun install --frozen-lockfile, chosen by lockfile; npm install when there is no lockfile. Build: the build script in package.json, when one exists.
|
|
914
|
+
- Python: Install: uv sync --frozen, poetry install --sync, pdm sync, pipenv sync, or pip install -r on the requirements files, chosen by lockfile; pip install . when there is only a pyproject.toml. Build: python manage.py check for Django projects; otherwise importing each first-party top-level package.
|
|
915
|
+
|
|
916
|
+
**Why.** Everything in the execution layer depends on this step. A project that does not install from a clean checkout depends on someone's machine.
|
|
917
|
+
|
|
918
|
+
#### 6.2 One-command verify
|
|
919
|
+
|
|
920
|
+
Weight 15%. Static layer. Scored per language and package, then combined by logic-line weight.
|
|
921
|
+
|
|
922
|
+
**Raw value** (count of checks (0-3)). The largest number of the three checks (typecheck, lint, tests) that a single entry point runs: one package.json script, Makefile target, justfile recipe, tox or nox session, or pyproject script.
|
|
923
|
+
|
|
924
|
+
| Level | Score | Meaning |
|
|
925
|
+
|---|---|---|
|
|
926
|
+
| 0 | 0 | No single command runs any check. |
|
|
927
|
+
| 1 | 33 | One command runs one check. |
|
|
928
|
+
| 2 | 66 | One command runs two checks. |
|
|
929
|
+
| 3 | 100 | One command runs typecheck, lint and tests. |
|
|
930
|
+
|
|
931
|
+
**How it is measured.**
|
|
932
|
+
|
|
933
|
+
- All languages: Static resolution of entry points as described in script_resolution.
|
|
934
|
+
|
|
935
|
+
**Why.** "Run this and it tells you if you are done" is the most useful fact an agent can learn about a repository. Without it, the agent has to find and run each check separately, and often skips one.
|
|
936
|
+
|
|
937
|
+
#### 6.3 Feedback loop time
|
|
938
|
+
|
|
939
|
+
Weight 10%. Execution layer. Scores 0 if any of these steps fail: install, typecheck run, lint run, test run. Scored per language and package, then combined by logic-line weight.
|
|
940
|
+
|
|
941
|
+
**Raw value** (seconds). Summed wall-clock time of the methodology's own typecheck (2.1), lint (2.5) and test (1.1) runs on the pinned runner.
|
|
942
|
+
|
|
943
|
+
| Raw | Score | Meaning |
|
|
944
|
+
|---|---|---|
|
|
945
|
+
| 60 | 100 | A full check takes about a minute. |
|
|
946
|
+
| 180 | 75 | A full check takes a few minutes. |
|
|
947
|
+
| 600 | 50 | The ten-minute build. |
|
|
948
|
+
| 1200 | 0 | Nobody runs the full check locally. |
|
|
949
|
+
|
|
950
|
+
Scores are interpolated between anchors and clamped outside them. If there is nothing to count, the score is 0.
|
|
951
|
+
|
|
952
|
+
**How it is measured.**
|
|
953
|
+
|
|
954
|
+
- All languages: Wall-clock time recorded by the orchestrator for the three steps.
|
|
955
|
+
|
|
956
|
+
**Why.** A slow check gets run less often, by people and by agents. This is the only parameter whose raw value varies between identical runs, so its weight is low and the runner hardware is fixed.
|
|
957
|
+
|
|
958
|
+
**Sources.**
|
|
959
|
+
|
|
960
|
+
- Beck, K. Extreme Programming Explained, 2nd ed. Addison-Wesley, 2004 (Ten-Minute Build).
|
|
961
|
+
- Fowler, M. Continuous Integration. https://martinfowler.com/articles/continuousIntegration.html
|
|
962
|
+
|
|
963
|
+
#### 6.4 CI runs the checks on pull requests
|
|
964
|
+
|
|
965
|
+
Weight 20%. Static layer. Scored per language and package, then combined by logic-line weight.
|
|
966
|
+
|
|
967
|
+
**Raw value** (count of checks (0-3)). How many of the three checks (typecheck, lint, tests) CI runs on pull requests or on every push. Checks may be spread across jobs and workflows.
|
|
968
|
+
|
|
969
|
+
| Level | Score | Meaning |
|
|
970
|
+
|---|---|---|
|
|
971
|
+
| 0 | 0 | CI runs no checks, or there is no CI. |
|
|
972
|
+
| 1 | 33 | CI runs one check. |
|
|
973
|
+
| 2 | 66 | CI runs two checks. |
|
|
974
|
+
| 3 | 100 | CI runs typecheck, lint and tests on every pull request. |
|
|
975
|
+
|
|
976
|
+
**How it is measured.**
|
|
977
|
+
|
|
978
|
+
- All languages: CI files as listed in 3.1. A workflow counts if it is triggered on pull_request, pull_request_target, merge_request events, or on push without a branch filter that limits it to the default branch. Commands are resolved as described in script_resolution.
|
|
979
|
+
|
|
980
|
+
**Why.** Local checks are optional; CI checks are not. CI is what keeps the score from dropping after the audit ends.
|
|
981
|
+
|
|
982
|
+
**Sources.**
|
|
983
|
+
|
|
984
|
+
- Fowler, M. Continuous Integration. https://martinfowler.com/articles/continuousIntegration.html
|
|
985
|
+
|
|
986
|
+
#### 6.5 Reproducible environment
|
|
987
|
+
|
|
988
|
+
Weight 15%. Static layer. Scored per language and package, then combined by logic-line weight.
|
|
989
|
+
|
|
990
|
+
**Raw value** (score). Mean of two components.
|
|
991
|
+
|
|
992
|
+
The score is the mean of two components, each 0-100:
|
|
993
|
+
|
|
994
|
+
- **Runtime version pinned**: 100 if pinned, 0 if not. Counts: Any of .nvmrc, .node-version, .tool-versions, mise.toml, engines.node in package.json, volta in package.json, .python-version, requires-python in pyproject.toml, or runtime.txt.
|
|
995
|
+
- **Environment variables documented**: Variable names read in logic files that appear in an env example file / all variable names read in logic files, times 100. No variables read scores 100. Example files: .env.example, .env.sample, .env.template, .env.dist, example.env. Reads: process.env.X, process.env["X"], destructuring from process.env, import.meta.env.X, os.environ["X"], os.environ.get("X"), os.getenv("X"). Excluded names: NODE_ENV, CI, HOME, PATH, PWD, TZ, NEXT_RUNTIME, and names starting with VERCEL_, NETLIFY, RENDER_, RAILWAY_ or GITHUB_.
|
|
996
|
+
|
|
997
|
+
**How it is measured.**
|
|
998
|
+
|
|
999
|
+
- All languages: Static reads of the files and patterns listed in the components.
|
|
1000
|
+
|
|
1001
|
+
**Why.** A missing environment variable is a common reason a fresh checkout does not start, and an agent cannot guess the names. Config read from the environment, with the names documented, is the Twelve-Factor approach.
|
|
1002
|
+
|
|
1003
|
+
**Sources.**
|
|
1004
|
+
|
|
1005
|
+
- The Twelve-Factor App, III. Config. https://12factor.net/config
|
|
1006
|
+
|
|
1007
|
+
#### 6.6 Commands documented for agents
|
|
1008
|
+
|
|
1009
|
+
Weight 15%. Static layer. Scored per language and package, then combined by logic-line weight.
|
|
1010
|
+
|
|
1011
|
+
**Raw value** (level). Whether the repository documents how to install, run and check it, and whether the documented commands exist.
|
|
1012
|
+
|
|
1013
|
+
| Level | Score | Meaning |
|
|
1014
|
+
|---|---|---|
|
|
1015
|
+
| none | 0 | No documented commands. |
|
|
1016
|
+
| readme | 50 | README documents commands, or an agent instructions file exists but at least one command in it does not resolve. |
|
|
1017
|
+
| agent_file | 100 | AGENTS.md or CLAUDE.md at the repository root documents commands, and every command in it resolves. |
|
|
1018
|
+
|
|
1019
|
+
**How it is measured.**
|
|
1020
|
+
|
|
1021
|
+
- All languages: A command is an inline code span or a line in a fenced code block that starts with a package manager (npm, pnpm, yarn, bun, npx, uv, poetry, pdm, pip, pipenv, python, make, just) or a recognised tool. It resolves when the script, target, recipe, session or tool it names exists in the repository or in its declared dependencies.
|
|
1022
|
+
|
|
1023
|
+
**Why.** An agent reads AGENTS.md or CLAUDE.md before anything else. Commands in that file that do not exist send it down the wrong path, which is worse than no file, so they drop the level to README.
|
|
1024
|
+
|
|
1025
|
+
**Sources.**
|
|
1026
|
+
|
|
1027
|
+
- AGENTS.md. https://agents.md
|
|
1028
|
+
|
|
1029
|
+
## 7. Report-only signals
|
|
1030
|
+
|
|
1031
|
+
These appear in the report and never change the score.
|
|
1032
|
+
|
|
1033
|
+
| Signal | What it shows | Why it is not scored |
|
|
1034
|
+
|---|---|---|
|
|
1035
|
+
| Hotspots | Files ranked by churn (commits in the last 12 months) × cognitive complexity | The "fix these first" list. Churn describes how the team works, not the code's state, and would make two runs of the same tree differ. |
|
|
1036
|
+
| Secrets in git history | Secrets in past commits | The fix is rotating the key, which the code cannot show. |
|
|
1037
|
+
| Development dependency vulnerabilities | Advisories in packages reachable only from development dependencies | They do not ship to production. |
|
|
1038
|
+
| Top scares | The first three of a fixed list of consequences the findings trigger, such as a Supabase service-role key in the tree, secrets in the tree or history, known production advisories, or an install that fails | A consequence in plain words, not a measurement. The scan cannot see access rules such as row-level security. |
|
|
1039
|
+
| Excluded lines | Percentage of lines excluded by `.healthignore` | Makes exclusions visible to the reader. |
|
|
1040
|
+
| Per-package breakdown | Every dimension per workspace package and per language | Shows where the pooled score comes from. |
|
|
1041
|
+
|
|
1042
|
+
## 8. What a 100 looks like
|
|
1043
|
+
|
|
1044
|
+
Concrete descriptions of a repository that scores 100 on every parameter.
|
|
1045
|
+
|
|
1046
|
+
### TypeScript
|
|
1047
|
+
|
|
1048
|
+
- **Tests.** vitest with `coverage.all`, at least 80% line and 70% branch coverage. Every module has a test file next to it. Every test asserts something. The suite is green.
|
|
1049
|
+
- **Types.** `tsconfig.json` with `"strict": true` and `"noUncheckedIndexedAccess": true`. `tsc --noEmit` reports 0 errors. No `any`, no `as` casts other than `as const`, no `!` non-null assertions, no `@ts-ignore`. Every exported function has typed parameters.
|
|
1050
|
+
- **Lint.** 0 findings from the pinned correctness rules, including `no-floating-promises` and `no-misused-promises`.
|
|
1051
|
+
- **Architecture.** A `.dependency-cruiser.cjs` file with direction rules, run by `depcruise` in CI. No import cycles. Each feature folder has an `index.ts`, and other features import only from it. If there is a `domain/` folder, it imports no framework, database or network packages.
|
|
1052
|
+
- **Size.** No function above cognitive complexity 15. No file above 500 logic lines. Under 3% duplication. `knip` reports no unused exports, files or dependencies.
|
|
1053
|
+
- **Dependencies.** A lockfile in sync with `package.json`. No known vulnerabilities in production dependencies. Every direct dependency on its current major version. No secrets in tracked files.
|
|
1054
|
+
- **Operability.** `npm ci && npm run build` works on a clean checkout. One script runs everything:
|
|
1055
|
+
|
|
1056
|
+
```json
|
|
1057
|
+
"scripts": {
|
|
1058
|
+
"typecheck": "tsc --noEmit",
|
|
1059
|
+
"lint": "eslint .",
|
|
1060
|
+
"test": "vitest run --coverage",
|
|
1061
|
+
"check": "npm run typecheck && npm run lint && npm run test"
|
|
1062
|
+
}
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
`npm run check` takes under a minute. CI runs it on every pull request. `.nvmrc` pins Node. Every `process.env.X` the code reads is listed in `.env.example`. `AGENTS.md` lists the commands, and every command in it exists.
|
|
1066
|
+
|
|
1067
|
+
### Python
|
|
1068
|
+
|
|
1069
|
+
A Python project at 100 uses a small set of modern tools. Most of them are configured in `pyproject.toml`.
|
|
1070
|
+
|
|
1071
|
+
- **Project and lockfile.** `uv` manages the environment. `uv.lock` is committed. `.python-version` pins the interpreter, and `requires-python` is set.
|
|
1072
|
+
- **Tests.** pytest with pytest-cov, run with `--cov=<package> --cov-branch` so files no test imports are counted. At least 80% line and 70% branch coverage. Every module has a `test_<module>.py`. Every test has an `assert` or uses `pytest.raises`.
|
|
1073
|
+
- **Types.** pyright in strict mode (or mypy with `strict = true`), with 0 errors. Every public function annotates its parameters and its return type. No `Any`, no `cast()`, no `# type: ignore`. Python does not enforce annotations at runtime; only the typechecker reads them.
|
|
1074
|
+
- **Lint.** ruff reports 0 findings for rule sets F (Pyflakes: undefined names, unused imports), B (bugbear: likely bugs such as mutable default arguments) and E9 (syntax errors).
|
|
1075
|
+
- **Architecture.** import-linter contracts declare the layers, and CI runs `lint-imports`. No import cycles (Python cycles also cause import-order errors at runtime). Code that is only needed for type annotations is imported under `if TYPE_CHECKING:`. A `domain/` package imports no framework, ORM or HTTP client.
|
|
1076
|
+
- **Size.** No function above cognitive complexity 15 (complexipy). No module above 500 logic lines. Under 3% duplication. vulture finds no unused code, and deptry finds no unused, missing or transitive-only dependencies.
|
|
1077
|
+
- **Operability.** `uv sync --frozen` works on a clean checkout, and the package imports. One command runs all checks, CI runs it on pull requests, and `AGENTS.md` lists it.
|
|
1078
|
+
|
|
1079
|
+
Example `pyproject.toml`:
|
|
1080
|
+
|
|
1081
|
+
```toml
|
|
1082
|
+
[project]
|
|
1083
|
+
name = "billing"
|
|
1084
|
+
requires-python = ">=3.12"
|
|
1085
|
+
dependencies = ["fastapi>=0.115", "sqlalchemy>=2.0"]
|
|
1086
|
+
|
|
1087
|
+
[dependency-groups]
|
|
1088
|
+
dev = ["pytest>=8", "pytest-cov>=5", "pyright>=1.1.380", "ruff>=0.6", "import-linter>=2"]
|
|
1089
|
+
|
|
1090
|
+
[tool.pyright]
|
|
1091
|
+
typeCheckingMode = "strict"
|
|
1092
|
+
|
|
1093
|
+
[tool.ruff.lint]
|
|
1094
|
+
select = ["E", "F", "B", "I", "UP"]
|
|
1095
|
+
|
|
1096
|
+
[tool.pytest.ini_options]
|
|
1097
|
+
addopts = "--cov=billing --cov-branch"
|
|
1098
|
+
|
|
1099
|
+
[tool.importlinter]
|
|
1100
|
+
root_package = "billing"
|
|
1101
|
+
|
|
1102
|
+
[[tool.importlinter.contracts]]
|
|
1103
|
+
name = "Layers"
|
|
1104
|
+
type = "layers"
|
|
1105
|
+
layers = ["billing.api", "billing.services", "billing.domain"]
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
Example `Makefile` target:
|
|
1109
|
+
|
|
1110
|
+
```make
|
|
1111
|
+
check:
|
|
1112
|
+
uv run pyright
|
|
1113
|
+
uv run ruff check
|
|
1114
|
+
uv run pytest
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
## 9. Known limits of v1.0
|
|
1118
|
+
|
|
1119
|
+
1. **Anchors are not calibrated against a corpus.** They come from the cited sources and from judgement. The next step is to run v1.0 on a set of repositories and check that the bands match how those repositories feel to work in. Any anchor change will be a new version.
|
|
1120
|
+
2. **Test quality is measured only by assertions.** A test can assert something trivial. Mutation testing would measure this directly but is too slow for every run, so it is out of v1.0.
|
|
1121
|
+
3. **Tests that need external services fail.** No database, queue or browser is provided. Integration tests that need one count as failed in 1.1, and the code they cover counts as uncovered.
|
|
1122
|
+
4. **Declared import rules replace the preset.** A repository can declare a permissive rules file and avoid the preset's rules. 3.1 still rewards the declaration with 50 or 100.
|
|
1123
|
+
5. **Domain-driven design is checked only at the boundary.** 3.5 checks that domain code does not import infrastructure. It does not judge whether the domain model is good.
|
|
1124
|
+
6. **Static analysis misses dynamic behaviour.** Dependency injection, plugin registration and string-based imports can make used code look unused (4.4) or hide imports (D3).
|
|
1125
|
+
7. **Security covers known advisories and committed secrets only.** There is no static application security testing (SAST).
|
|
1126
|
+
8. **6.3 varies between runs.** It is the only parameter that does, and it has a weight of 10%.
|
|
1127
|
+
9. **Other languages are ignored.** Files in other languages do not count toward any parameter.
|
|
1128
|
+
|
|
1129
|
+
## 10. Versioning
|
|
1130
|
+
|
|
1131
|
+
- The methodology version is in `methodology.yaml` and in every `run.json`.
|
|
1132
|
+
- Any change that can change a score creates a new version: a weight, an anchor, a band, a threshold, a rule, a preset, or the scope. Wording changes that cannot change a score do not.
|
|
1133
|
+
- Runs from different methodology versions are never compared.
|
|
1134
|
+
- Tool upgrades produce a new image digest. Runs with different digests are not compared either.
|
|
1135
|
+
|
|
1136
|
+
## 11. Sources
|
|
1137
|
+
|
|
1138
|
+
- AGENTS.md. https://agents.md
|
|
1139
|
+
- Arguelles, Ivanković, Bender. Code Coverage Best Practices. Google Testing Blog, 2020. https://testing.googleblog.com/2020/08/code-coverage-best-practices.html
|
|
1140
|
+
- Beck, K. Extreme Programming Explained, 2nd ed. Addison-Wesley, 2004 (Ten-Minute Build).
|
|
1141
|
+
- Campbell, G. A. Cognitive Complexity: A new way of measuring understandability. SonarSource, 2018. https://www.sonarsource.com/docs/CognitiveComplexity.pdf
|
|
1142
|
+
- Cockburn, A. Hexagonal Architecture (Ports and Adapters), 2005. https://alistair.cockburn.us/hexagonal-architecture/
|
|
1143
|
+
- CWE-798: Use of Hard-coded Credentials. https://cwe.mitre.org/data/definitions/798.html
|
|
1144
|
+
- dependency-cruiser rules reference. https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md
|
|
1145
|
+
- deptry rules. https://deptry.com/rules-violations/
|
|
1146
|
+
- ESLint max-lines rule, default 300. https://eslint.org/docs/latest/rules/max-lines
|
|
1147
|
+
- Evans, E. Domain-Driven Design. Addison-Wesley, 2003.
|
|
1148
|
+
- FastAPI: Bigger Applications - Multiple Files. https://fastapi.tiangolo.com/tutorial/bigger-applications/
|
|
1149
|
+
- FIRST. CVSS v3.1 Specification, qualitative severity rating scale. https://www.first.org/cvss/v3.1/specification-document
|
|
1150
|
+
- Fowler, M. Continuous Integration. https://martinfowler.com/articles/continuousIntegration.html
|
|
1151
|
+
- gitleaks. https://github.com/gitleaks/gitleaks
|
|
1152
|
+
- HackSoft Django Styleguide. https://github.com/HackSoftware/Django-Styleguide
|
|
1153
|
+
- import-linter contract types. https://import-linter.readthedocs.io/en/stable/contract_types.html
|
|
1154
|
+
- jscpd. https://github.com/kucherenko/jscpd
|
|
1155
|
+
- knip. https://knip.dev
|
|
1156
|
+
- Martin, R. C. Agile Software Development: Principles, Patterns, and Practices. Prentice Hall, 2002 (the Acyclic Dependencies Principle).
|
|
1157
|
+
- Martin, R. C. Clean Architecture. Prentice Hall, 2017 (the Dependency Rule).
|
|
1158
|
+
- McCabe, T. J. A Complexity Measure. IEEE Transactions on Software Engineering, 1976 (cyclomatic complexity, threshold 10).
|
|
1159
|
+
- mypy command line: --strict. https://mypy.readthedocs.io/en/stable/command_line.html
|
|
1160
|
+
- Next.js: Server and Client Components, keeping server-only code out of the client environment. https://nextjs.org/docs/app/getting-started/server-and-client-components
|
|
1161
|
+
- npm ci documentation. https://docs.npmjs.com/cli/commands/npm-ci
|
|
1162
|
+
- OSV schema (published, withdrawn, aliases). https://ossf.github.io/osv-schema/
|
|
1163
|
+
- PEP 484: Type Hints. https://peps.python.org/pep-0484/
|
|
1164
|
+
- Pylint too-many-lines (C0302), max-module-lines default 1000.
|
|
1165
|
+
- Pyright configuration: typeCheckingMode. https://microsoft.github.io/pyright/#/configuration
|
|
1166
|
+
- Ruff rules. https://docs.astral.sh/ruff/rules/
|
|
1167
|
+
- Semantic Versioning 2.0.0, item 4 (major version zero). https://semver.org
|
|
1168
|
+
- SonarQube default quality gate (Sonar way): coverage on new code at least 80%.
|
|
1169
|
+
- SonarQube default quality gate (Sonar way): duplicated lines on new code at most 3%.
|
|
1170
|
+
- SonarQube rule S3776 (Cognitive Complexity of functions should not be too high), default threshold 15.
|
|
1171
|
+
- The Twelve-Factor App, III. Config. https://12factor.net/config
|
|
1172
|
+
- TypeScript TSConfig reference: noUncheckedIndexedAccess. https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess
|
|
1173
|
+
- TypeScript TSConfig reference: strict. https://www.typescriptlang.org/tsconfig#strict
|
|
1174
|
+
- typescript-eslint rules. https://typescript-eslint.io/rules/
|
|
1175
|
+
- TypeScript: Type checking JavaScript files (checkJs). https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html
|
|
1176
|
+
- vulture. https://github.com/jendrikseipp/vulture
|