crapkit 0.4.4__tar.gz → 0.4.6__tar.gz
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.
- {crapkit-0.4.4 → crapkit-0.4.6}/PKG-INFO +208 -59
- crapkit-0.4.4/src/crapkit.egg-info/PKG-INFO → crapkit-0.4.6/README.md +972 -862
- crapkit-0.4.6/pyproject.toml +104 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/__init__.py +1 -1
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/analyze.py +26 -9
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/churn_cache.py +42 -5
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/churn_log.py +45 -1
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/__init__.py +22 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/admin.py +428 -57
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/analyses.py +49 -5
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/claude_hook.py +8 -1
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/parser.py +9 -3
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/queue.py +62 -29
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/ratchet_cmds.py +29 -13
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/reports.py +2 -1
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/scoring.py +41 -15
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/verifying.py +112 -23
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/config.py +209 -32
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/coupling.py +45 -10
- crapkit-0.4.6/src/crapkit/coupling_cache.py +147 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/covstream.py +48 -6
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/dup.py +53 -5
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/gitio.py +179 -23
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/lanes.py +268 -28
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/lizardcognitive.py +90 -6
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/lizardshell.py +3 -3
- crapkit-0.4.6/src/crapkit/mutate_pool.py +308 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/packet.py +10 -3
- crapkit-0.4.6/src/crapkit/procs.py +66 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/scaffold.py +140 -31
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/store.py +173 -40
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/uncovered.py +96 -8
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/universe.py +61 -23
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/verify.py +28 -4
- crapkit-0.4.4/README.md → crapkit-0.4.6/src/crapkit.egg-info/PKG-INFO +1011 -827
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit.egg-info/SOURCES.txt +2 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit.egg-info/requires.txt +2 -0
- crapkit-0.4.4/pyproject.toml +0 -70
- crapkit-0.4.4/src/crapkit/mutate_pool.py +0 -152
- {crapkit-0.4.4 → crapkit-0.4.6}/LICENSE +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/setup.cfg +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/__main__.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/_pygdefer.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cache.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/churn.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/cli/_shared.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/coverage_istanbul.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/coverage_py.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/diffparse.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/digest.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/discover.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/doctor.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/errors.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/hook.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/junitparse.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/keys.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/lizardpowershell.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/lizardrust.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/mcp_server.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/merge.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/mutate.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/override.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/ratchet.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/ratchet_report.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/report.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/sarif.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/sarifio.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/score.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/snapshot.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/watch.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit/worklist.py +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit.egg-info/dependency_links.txt +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit.egg-info/entry_points.txt +0 -0
- {crapkit-0.4.4 → crapkit-0.4.6}/src/crapkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crapkit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more.
|
|
5
5
|
Author: Jean-Francois Gagne
|
|
6
6
|
License: MIT
|
|
@@ -9,18 +9,20 @@ Project-URL: Documentation, https://jeanfrancoisgagne.github.io/crapkit/handbook
|
|
|
9
9
|
Project-URL: Changelog, https://github.com/JeanFrancoisGagne/crapkit/blob/main/CHANGELOG.md
|
|
10
10
|
Project-URL: Issues, https://github.com/JeanFrancoisGagne/crapkit/issues
|
|
11
11
|
Project-URL: Source, https://github.com/JeanFrancoisGagne/crapkit
|
|
12
|
-
Keywords: complexity,coverage,crap,code-quality,technical-debt,cyclomatic,ratchet,pre-commit,claude-code
|
|
12
|
+
Keywords: complexity,coverage,crap,code-quality,technical-debt,cyclomatic,ratchet,pre-commit,claude-code,mcp,mutation-testing,static-analysis
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Environment :: Console
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
23
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
24
|
Classifier: Topic :: Software Development :: Testing
|
|
25
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
24
26
|
Requires-Python: >=3.11
|
|
25
27
|
Description-Content-Type: text/markdown
|
|
26
28
|
License-File: LICENSE
|
|
@@ -29,8 +31,10 @@ Provides-Extra: dev
|
|
|
29
31
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
30
32
|
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
31
33
|
Requires-Dist: pytest-xdist>=3; extra == "dev"
|
|
34
|
+
Requires-Dist: coverage>=7.10.6; extra == "dev"
|
|
32
35
|
Provides-Extra: py
|
|
33
36
|
Requires-Dist: pytest-cov>=5; extra == "py"
|
|
37
|
+
Requires-Dist: coverage>=7.10.6; extra == "py"
|
|
34
38
|
Dynamic: license-file
|
|
35
39
|
|
|
36
40
|
# crapkit
|
|
@@ -96,9 +100,29 @@ out to your own test runner, and the runner needs its coverage package installed
|
|
|
96
100
|
`pytest-cov` for pytest, `@vitest/coverage-v8` (pinned to your vitest major) for vitest.
|
|
97
101
|
Without it the lane produces no artifact and `coverage` exits 5 quoting the runner's own
|
|
98
102
|
error. For pytest, `init` probes the python its lane will run and prints the install
|
|
99
|
-
command when `pytest_cov` is missing; `pip install
|
|
100
|
-
alongside crapkit when the two share a venv.
|
|
101
|
-
|
|
103
|
+
command when `pytest_cov` is missing; `pip install "crapkit[py]"` pulls the plugin
|
|
104
|
+
alongside crapkit when the two share a venv. On a Windows PATH holding only the `py`
|
|
105
|
+
launcher it writes `py`, not a `python3` the lane could never run, and when cmd.exe cannot
|
|
106
|
+
start the interpreter at all (exit 9009, the Store alias) it names that instead of guessing
|
|
107
|
+
at pytest-cov. The two quickstarts below walk a real repo end to end.
|
|
108
|
+
|
|
109
|
+
**On Windows a lane command is read by cmd.exe**, the shell that will run it, not by sh.
|
|
110
|
+
Double quotes are the portable quoting. A single-quoted value is refused at config load
|
|
111
|
+
with exit 3, because cmd.exe would hand pytest five words and the lane would write no
|
|
112
|
+
artifact:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
# the lane in crapkit.toml
|
|
116
|
+
command = "python -m pytest -m 'not live and not perf' --cov=calc --cov-branch --cov-report=json:.crapkit/cov/py.json"
|
|
117
|
+
|
|
118
|
+
$ crapkit doctor
|
|
119
|
+
crapkit: lane 'py': positional argument 'live' narrows a full-suite coverage run; drop it, attach it to the flag it belongs to (-n8, --numprocesses=8), or set full_suite = false deliberately (cmd.exe does not treat ' as a quote: write the value in double quotes)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Write it `-m "not live and not perf"`. Carets, `&&` and `|` segments, redirections and
|
|
123
|
+
empty quoted arguments all read the way the shell reads them, so a chained lane
|
|
124
|
+
(`cd tests && python -m pytest --cov ...`) is checked one segment at a time. `doctor` reads
|
|
125
|
+
a lane the same way, and FAILs one whose runner will not start.
|
|
102
126
|
|
|
103
127
|
## Install
|
|
104
128
|
|
|
@@ -121,30 +145,73 @@ changing crapkit.
|
|
|
121
145
|
|
|
122
146
|
```
|
|
123
147
|
$ crapkit --version
|
|
124
|
-
crapkit 0.4.
|
|
148
|
+
crapkit 0.4.6
|
|
125
149
|
```
|
|
126
150
|
|
|
127
151
|
`python -m crapkit` works identically to the console script and is what to use from a
|
|
128
152
|
source checkout. Every subcommand accepts `--repo PATH` (default: the current directory),
|
|
129
|
-
so you never have to `cd` into the repo you are scoring
|
|
130
|
-
|
|
153
|
+
so you never have to `cd` into the repo you are scoring; [Subcommands](#subcommands) shows
|
|
154
|
+
where the flag goes.
|
|
155
|
+
|
|
156
|
+
## Upgrading from 0.4.4
|
|
131
157
|
|
|
132
|
-
|
|
158
|
+
**Run `crapkit ratchet seed` first.** Shell cognitive complexity now nests, which is
|
|
159
|
+
analysis version 8, and marks measured under version 7 are not comparable. Until you
|
|
160
|
+
re-seed, `verify` refuses at exit 3:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
$ crapkit verify
|
|
164
|
+
crapkit: ratchet marks were recorded under [crapkit-analysis=7 lizard=1.24.0] but this run measures [crapkit-analysis=8 lizard=1.24.0] — CRAP scores are not comparable across metric versions; re-baseline with `crapkit ratchet seed`
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Only shell and PowerShell cognitive numbers move. `ccn` does not, so a re-seed re-stamps
|
|
168
|
+
the file and leaves the marks where they were.
|
|
169
|
+
|
|
170
|
+
Five more things change under you. Three of them need nothing from you:
|
|
171
|
+
|
|
172
|
+
- **New cache files.** `.crapkit/coupling-cache-v1.json` joins `churn-cache-v2.json` and
|
|
173
|
+
`churn-log-v2.z`. A warm 0.4.4 churn cache is adopted once and its file removed, and
|
|
174
|
+
`.crapkit/` is already gitignored, so nothing new reaches your index.
|
|
175
|
+
- **`trend` and `report` write.** Both read a per-run rollup table, filled once per run and
|
|
176
|
+
pruned with its run, instead of rescanning every scored row. A read-only `.crapkit/`
|
|
177
|
+
costs the speedup, never the command.
|
|
178
|
+
- **Nested scopes may move files.** One predicate decides scope ownership now, and the
|
|
179
|
+
deepest declared path wins, so a repo whose `[[scope]]` paths nest inside each other can
|
|
180
|
+
see files change scope, rollup and ceiling on the next scan. Scopes that do not nest see
|
|
181
|
+
no change.
|
|
182
|
+
|
|
183
|
+
The other two put something in front of you:
|
|
184
|
+
|
|
185
|
+
- **`mutate` keeps a worktree pool.** With `mutation_workers > 1` the worker worktrees now
|
|
186
|
+
live under `.crapkit/mutate-pool/` between runs and are re-prepared each run, which is
|
|
187
|
+
the setup cost gone (30.6 s to build four on a 31,459-file repo, 0.46 s to re-prepare
|
|
188
|
+
them). The pool is not size-bounded and nothing sweeps it: `crapkit mutate --drop-pool`
|
|
189
|
+
removes it and exits. Single-worker runs are untouched.
|
|
190
|
+
- **`doctor` WARNs on a lane with no `results_artifact`.** Every `coveragepy` or `istanbul`
|
|
191
|
+
lane written before 0.4.5 gets one, with the two lines that fix it. Coverage is
|
|
192
|
+
unaffected. What the lane cannot feed without a results file is the crashed-worker check
|
|
193
|
+
and the no-new-failures check (exit 8).
|
|
194
|
+
|
|
195
|
+
### The exe lock on Windows
|
|
133
196
|
|
|
134
197
|
`uv tool upgrade crapkit`, and `pip install -U` into a tool venv, fail with `os error 32`
|
|
135
198
|
("The process cannot access the file because it is being used by another process") while a
|
|
136
199
|
crapkit MCP server is live: an agent session spawns `crapkit.exe mcp`, which holds the
|
|
137
200
|
launcher, and Windows will not overwrite a running executable. The venv upgrades before
|
|
138
|
-
that copy fails, so `crapkit --version` already reports the new version and the
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
201
|
+
that copy fails, so `crapkit --version` already reports the new version and only the
|
|
202
|
+
launcher is stale. Quit the agent session and rerun the upgrade, or rename the locked exe
|
|
203
|
+
aside (Windows allows renaming a running one) and copy the new one in. Two lines in
|
|
204
|
+
cmd.exe, where both `%` variables expand:
|
|
142
205
|
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
|
|
206
|
+
```bat
|
|
207
|
+
move %USERPROFILE%\.local\bin\crapkit.exe %USERPROFILE%\.local\bin\crapkit.exe.old
|
|
208
|
+
copy %APPDATA%\uv\tools\crapkit\Scripts\crapkit.exe %USERPROFILE%\.local\bin\crapkit.exe
|
|
146
209
|
```
|
|
147
210
|
|
|
211
|
+
Git Bash has no `move` and passes `%APPDATA%` through as literal text, so that block
|
|
212
|
+
fails there on its first line. Its form is `mv` and `cp` over `"$USERPROFILE"` and
|
|
213
|
+
`"$APPDATA"`, which Git Bash sets to the same two directories.
|
|
214
|
+
|
|
148
215
|
## The Claude Code plugin
|
|
149
216
|
|
|
150
217
|
```
|
|
@@ -211,10 +278,21 @@ different powers:
|
|
|
211
278
|
| `crapkit verify` | before you push, and in CI | **the verdict.** Gate, ratchet, new test failures, diff coverage, against the trusted baseline |
|
|
212
279
|
|
|
213
280
|
Both hooks exempt a function the committed ratchet already carries a mark for, so touching
|
|
214
|
-
signed debt never refuses a commit. `verify` is what fails a mark that rises.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
281
|
+
signed debt never refuses a commit. `verify` is what fails a mark that rises. Since 0.4.5
|
|
282
|
+
its gate exempts a touched function whose fresh CRAP sits **at or under** its mark, the
|
|
283
|
+
rule `rescore --gate` already applied; push it past the mark and the gate fires again. The
|
|
284
|
+
pre-commit hook still exempts on the mark's existence alone, on purpose: a staged blob has
|
|
285
|
+
no coverage, so there is no fresh CRAP to compare against. It reports each exemption count
|
|
286
|
+
on stderr (`staged function(s) carry a ratchet mark and were not gated`), and says the same
|
|
287
|
+
about a staged file no `[[scope]]` claims, so a new top-level directory cannot go ungated
|
|
288
|
+
in silence.
|
|
289
|
+
|
|
290
|
+
**The crapkit root does not have to be the git top.** Since 0.4.5 every git spawn runs with
|
|
291
|
+
`diff.relative=true` and `core.quotePath=false`, so a `crapkit.toml` in `packages/api`
|
|
292
|
+
gates that package's own staged files and names them `app/m.py`, not
|
|
293
|
+
`packages/api/app/m.py`, and a dirty non-ASCII path is a real row rather than an invisible
|
|
294
|
+
one. Before that a nested root matched staged paths against no scope, and a function at
|
|
295
|
+
twice the ceiling committed with a warning.
|
|
218
296
|
|
|
219
297
|
Git runs hooks outside your shell's activated venv. Bare `python` must resolve to an
|
|
220
298
|
interpreter that has crapkit installed, or spell it out
|
|
@@ -268,11 +346,22 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
|
|
|
268
346
|
repos:
|
|
269
347
|
- repo: https://github.com/JeanFrancoisGagne/crapkit
|
|
270
348
|
# crapkit's release step rewrites this line to the tag it just cut
|
|
271
|
-
rev: v0.4.
|
|
349
|
+
rev: v0.4.6
|
|
272
350
|
hooks:
|
|
273
351
|
- id: crapkit-gate
|
|
274
352
|
```
|
|
275
353
|
|
|
354
|
+
That file arms nothing on its own. The framework writes `.git/hooks/pre-commit` when you
|
|
355
|
+
tell it to, and until then `git commit` runs no gate and says nothing:
|
|
356
|
+
|
|
357
|
+
```sh
|
|
358
|
+
pip install pre-commit
|
|
359
|
+
pre-commit install
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
`pre-commit install` is the line every clone needs, the way Route 2 needs its
|
|
363
|
+
`git config core.hooksPath` line.
|
|
364
|
+
|
|
276
365
|
`rev` is a git ref pre-commit resolves against that remote. Pin a release tag, not a
|
|
277
366
|
branch: `pre-commit autoupdate` only moves between tags, and a moving `main` would change
|
|
278
367
|
your gate under you.
|
|
@@ -295,6 +384,45 @@ crapkit verify --baseline-tsv crapkit-baseline.tsv --github
|
|
|
295
384
|
writes SARIF 2.1.0 for code-scanning upload. Refresh the committed baseline whenever the
|
|
296
385
|
default branch's verify passes.
|
|
297
386
|
|
|
387
|
+
Two things the job has to do before those lines run. **Install crapkit**, `pip install
|
|
388
|
+
crapkit`, and pin the version the way Route 3 pins `rev`: an unpinned install moves your
|
|
389
|
+
gate on whatever day a release lands. **Fetch the whole history.** `actions/checkout`
|
|
390
|
+
clones one commit by default, `verify` reads the diff against the baseline's commit out of
|
|
391
|
+
git, and a shallow clone does not have that commit:
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
$ crapkit verify --baseline-tsv crapkit-baseline.tsv
|
|
395
|
+
crapkit: baseline commit a74260f321f is not an ancestor of HEAD (rebase or amend rewrote history) — run `crapkit coverage` for a fresh baseline
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
That is exit 4 on a `git clone --depth 1` of a repo whose baseline verifies at full depth.
|
|
399
|
+
Set `fetch-depth: 0` on the checkout step, which is what crapkit's own
|
|
400
|
+
[.github/workflows/ci.yml](.github/workflows/ci.yml) does.
|
|
401
|
+
|
|
402
|
+
The whole PR job, on GitHub Actions:
|
|
403
|
+
|
|
404
|
+
```yaml
|
|
405
|
+
on: pull_request
|
|
406
|
+
jobs:
|
|
407
|
+
crapkit:
|
|
408
|
+
runs-on: ubuntu-latest
|
|
409
|
+
steps:
|
|
410
|
+
- uses: actions/checkout@v4
|
|
411
|
+
with:
|
|
412
|
+
fetch-depth: 0 # verify needs the baseline's commit
|
|
413
|
+
- uses: actions/setup-python@v5
|
|
414
|
+
with:
|
|
415
|
+
python-version: "3.12"
|
|
416
|
+
- run: pip install crapkit
|
|
417
|
+
- run: pip install -e ".[dev]" # your own test dependencies
|
|
418
|
+
- run: crapkit verify --baseline-tsv crapkit-baseline.tsv --github
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The second install is the one people leave out. `verify` reruns your lanes, so the job
|
|
422
|
+
needs whatever your test command needs: the coverage plugin, `npm ci`, a database, all of
|
|
423
|
+
it. Without them the lane writes no artifact and `verify` exits 5 quoting the runner's own
|
|
424
|
+
error, which is a broken job and not a verdict.
|
|
425
|
+
|
|
298
426
|
### What a refusal looks like
|
|
299
427
|
|
|
300
428
|
```
|
|
@@ -339,30 +467,30 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
|
|
|
339
467
|
| Command | What it does |
|
|
340
468
|
|---|---|
|
|
341
469
|
| `init` | Sniffs tracked source into per-directory scopes, writes a self-validated starter `crapkit.toml` whose lanes report into `.crapkit/cov/`, and appends `.crapkit/` plus each runner's own droppings to `.gitignore`. Writes a live `[[lane]]` when it can detect the test runner, otherwise a commented template. Refuses to clobber an existing config. |
|
|
342
|
-
| `doctor [--show-files] [--json] [--tune] [--plugin-root PATH]` | Checks the config still describes the repo: unknown keys (with the accepted spellings), zero-file scopes, tracked source no scope claims, scopes no lane covers, lane cwds and commands that no longer resolve, lizard importable, oversized files. It WARNs on a lane writing its artifact at the repo root, a committed hook under `core.hooksPath` that is not executable in the index, a directory whose functions are all `untested` while its tests exist, and a scope a lane measures with no `[crapkit.scoped_tests]` template behind it, which is the loop's step 4 with nothing to run. `--tune` prints suggested parallelism knobs and writes nothing. `--plugin-root PATH` reads no repo at all: it checks an installed [plugin](plugin/) against this CLI on both version and hook `--protocol`, one line per disagreement and silence when they agree. See [docs/agent-json.md](docs/agent-json.md#doctor---json). |
|
|
470
|
+
| `doctor [--show-files] [--json] [--tune] [--plugin-root [PATH]]` | Checks the config still describes the repo: unknown keys (with the accepted spellings), zero-file scopes, tracked source no scope claims, scopes no lane covers, lane cwds and commands that no longer resolve, lizard importable, oversized files. It reads each lane command with the shell that will run it, so a quoted interpreter path is one word and a runner after `&&` is checked too, and it FAILs a lane whose runner does not resolve on PATH or that the shell cannot start, naming the word to change; each distinct runner is probed once, not once per lane. It WARNs on a lane writing its artifact at the repo root, a `coveragepy` or `istanbul` lane with no `results_artifact` (the crashed-worker and no-new-failures checks are off for it, whichever runner the lane spells), a committed hook under `core.hooksPath` that is not executable in the index, a directory whose functions are all `untested` while its tests exist, and a scope a lane measures with no `[crapkit.scoped_tests]` template behind it, which is the loop's step 4 with nothing to run. `--tune` prints suggested parallelism knobs and writes nothing. `--plugin-root PATH` reads no repo at all: it checks an installed [plugin](plugin/) against this CLI on both version and hook `--protocol`, one line per disagreement and silence when they agree; PATH is the plugin root or any directory above it, `~/.claude` included (only manifests named `crapkit` count, and the newest install wins), and with no PATH it looks in Claude Code's plugin cache. A root it found rather than one you typed is named first, as `crapkit doctor: checking PATH`. See [docs/agent-json.md](docs/agent-json.md#doctor---json). |
|
|
343
471
|
| `inventory [--db PATH] [--export PATH] [--json]` | One lizard pass over every in-scope file into a SQLite snapshot run, cached by content hash. `--db` is the only way to point crapkit at a store outside `.crapkit/`, and only this command accepts it. |
|
|
344
472
|
| `coverage [--lane NAME] [--reuse-artifacts] [--reuse-unchanged] [--export PATH] [--sarif PATH] [--github] [--json]` | Runs the lanes, joins branch coverage onto a fresh inventory, writes a scored run. A failed lane is recorded, not fatal: its scopes fall back to `no-lane` and the run is typed `partial`, so it can never serve as a baseline. See [docs/lanes.md](docs/lanes.md). |
|
|
345
|
-
| `verify [--baseline ID \| --base REF \| --baseline-tsv PATH] [--emit-baseline PATH] [--override REASON] [--reuse-artifacts] [--reuse-unchanged] [--no-tighten] [--sarif PATH] [--github] [--json]` | The full verdict against the trusted baseline: gate on touched functions, ratchet, no new test failures, optional diff-coverage ceiling. The three baseline selectors are mutually exclusive; `--baseline ID` also bypasses the taint rule ([The trusted baseline](#the-trusted-baseline)), and `--baseline-tsv` reads a commit-stamped file so a fresh clone verifies with no store. `--no-tighten` passes the verdict without rewriting the ratchet. Findings a dirty tree produced are tagged `dirty` and counted apart. |
|
|
346
|
-
| `worklist [--top N] [--scope NAME] [--batches N] [--json]` | The risk map: every admitted function ranked by `ccn * churn weight`, floored by `worklist_floor`, with hot simple code and anything over its ceiling admitted past that floor. It ranks finished rows and `no-lane` rows too, marked `ok` and `no-lane`, so it never empties; `next-item` carries the stop condition. `--scope NAME` (repeatable) is exact, not a substring. `--batches N` **adds** a `batches[]` view cutting the active list into at most N file-disjoint batches with co-changing files kept together; the normal keys stay. |
|
|
473
|
+
| `verify [--baseline ID \| --base REF \| --baseline-tsv PATH] [--emit-baseline PATH] [--override REASON] [--reuse-artifacts] [--reuse-unchanged] [--no-tighten] [--sarif PATH] [--github] [--json]` | The full verdict against the trusted baseline: gate on touched functions, ratchet, no new test failures, optional diff-coverage ceiling. The three baseline selectors are mutually exclusive; `--baseline ID` also bypasses the taint rule ([The trusted baseline](#the-trusted-baseline)), and `--baseline-tsv` reads a commit-stamped file so a fresh clone verifies with no store. `--no-tighten` passes the verdict without rewriting the ratchet. Findings a dirty tree produced are tagged `dirty` and counted apart. It reads each istanbul artifact once for coverage, dead lines and its digest, and skips the artifact walk on an empty diff; skipping the whole run on an unchanged tree was measured and rejected, because a key made of HEAD plus the dirty names cannot see a second edit to a file that was already dirty. |
|
|
474
|
+
| `worklist [--top N] [--scope NAME] [--batches N] [--json]` | The risk map: every admitted function ranked by `ccn * churn weight`, floored by `worklist_floor`, with hot simple code and anything over its ceiling admitted past that floor. It ranks finished rows and `no-lane` rows too, marked `ok` and `no-lane`, so it never empties; `next-item` carries the stop condition. `--scope NAME` (repeatable) is exact, not a substring. `--batches N` **adds** a `batches[]` view cutting the active list into at most N file-disjoint batches with co-changing files kept together, off the same cached pairs `coupling` reads; the normal keys stay. |
|
|
347
475
|
| `next-item [--top N] [--exclude FRAG] [--scope NAME] [--claim]` | The actionable queue as JSON, with churn, budget estimates and uncovered lines. Same run and same admission floor as `worklist`, a different view of it: `no-lane` rows are skipped and counted in `skipped_no_lane`, and what is left is ranked by `crap` descending rather than by risk, so the item it hands out is often not the worklist's first row. `--exclude FRAG` (repeatable) skips items whose path or function name contains FRAG; `--scope NAME` (repeatable) is exact, not a substring. `--claim` holds what it hands out so a second session skips it. `stale` is true when the ranked run's commit is not HEAD, the same field `worklist` carries. Every item carries a `handle`: the bare identifier, or `(anonymous)#N` for a function with no name, which is the name form that survives the edit the item asks for. |
|
|
348
476
|
| `claims [list \| release PATH NAME \| release --all] [--json]` | The open claims, and the way to hand one back without waiting for a verify. `release` takes the bare identifier, the whole long name, or the `handle` the claim was taken under, which is the only one that picks out a single `(anonymous)` claim. |
|
|
349
|
-
| `brief FILE NAME [--batch N] [--json]` | The start-editing packet for one function: its own `source` text, every function in the file, the scored row and the scope ceiling, the ratchet mark and what the gate will bind on, uncovered lines, duplication twins, file churn, coupling partners, the config's notes, and the literal commands for the rest of the loop. Plus `handle`, `remedy` and the same `est_splits` / `est_uncovered_paths` the queue prints, and a `commands.refresh` that writes a run (`refresh_writes_run`) rather than re-reading the stale one. `NAME` takes the bare identifier, the long name `next-item` printed, the function's start line, `(anonymous)#N` for a function printed `(anonymous)` counting the file's anonymous functions from the top, or `NAME#2` for the second of several functions a file gives one name to. `--batch N` drops the positionals and emits `packets[]` instead: the top N of the queue, built from one read of the store. |
|
|
350
|
-
| `explain FILE NAME [--history] [--tests] [--json]` | A function's score across runs plus its mark. `NAME` resolves exact first: a function whose bare identifier or long name is exactly `NAME` wins, and only when nothing matches exactly does it fall back to a prefix match, so `route` explains `route` rather than every `route_*` beside it. `--history` adds the commits that touched it (`git log -L`), each carrying its message `body`, `--tests` the tests that covered it, which needs coverage.py contexts turned on ([recipe](docs/lanes.md#test-attribution-for-explain---tests)). `--json` emits the same content as one `schema` 1 object. |
|
|
351
|
-
| `rescore FILE ... [--gate] [--json]` | Fresh complexity for named files over the latest run's stale coverage, joined by name. Advisory: it writes no run. `--gate` applies the pre-commit hook's policy to the same selection the hook uses (functions the tree changed since HEAD), minus functions
|
|
477
|
+
| `brief FILE NAME [--batch N] [--json]` | The start-editing packet for one function: its own `source` text, every function in the file, the scored row and the scope ceiling, the ratchet mark and what the gate will bind on, uncovered lines, duplication twins, file churn, coupling partners, the config's notes, and the literal commands for the rest of the loop. Plus `handle`, `remedy` and the same `est_splits` / `est_uncovered_paths` the queue prints, and a `commands.refresh` that writes a run (`refresh_writes_run`) rather than re-reading the stale one. `NAME` takes the bare identifier, the long name `next-item` printed, the function's start line, `(anonymous)#N` for a function printed `(anonymous)` counting the file's anonymous functions from the top, or `NAME#2` for the second of several functions a file gives one name to. `--batch N` drops the positionals and emits `packets[]` instead: the top N of the queue, built from one read of the store and one duplication pass over the snapshot for the whole batch (batch of 5: 11.8 s to 5.2 s, output byte-identical to five separate calls). |
|
|
478
|
+
| `explain FILE NAME [--history] [--tests] [--json]` | A function's score across runs plus its mark. `NAME` resolves exact first: a function whose bare identifier or long name is exactly `NAME` wins, and only when nothing matches exactly does it fall back to a prefix match, so `route` explains `route` rather than every `route_*` beside it. It also takes the function's start line, the form `brief` takes, which is how you open one printed `(anonymous)`. `--history` adds the commits that touched it (`git log -L`), each carrying its message `body`, `--tests` the tests that covered it, which needs coverage.py contexts turned on ([recipe](docs/lanes.md#test-attribution-for-explain---tests)). `--json` emits the same content as one `schema` 1 object. |
|
|
479
|
+
| `rescore FILE ... [--gate] [--json]` | Fresh complexity for named files over the latest run's stale coverage, joined by name. Advisory: it writes no run. `--gate` applies the pre-commit hook's policy to the same selection the hook uses (functions the tree changed since HEAD), minus functions whose CRAP sits at or under their ratchet mark, and exits 6. A marked function past its mark is gated; the pre-commit hook exempts on the mark's existence instead, because a staged blob has no coverage to score. |
|
|
352
480
|
| `ratchet seed \| prune \| merge \| move \| report [--enforce] [--json]` | The mark lifecycle: seed new debt, prune gone code (a mark whose file git renamed follows it), merge as a git driver, move re-paths marks, report reads burn-down from the file's own git history. See [docs/ratchet.md](docs/ratchet.md). |
|
|
353
481
|
| `runs [list \| prune [--keep N]] [--json]` | Run history, and retention. `list` marks the run `verify` compares against today `baseline`, and prints `verdict=-` for a run that produces no verdict rather than one that failed. See [The trusted baseline](#the-trusted-baseline). `--keep` (default 5) is a floor on the newest trusted runs, not a cap: the digest pair, every passing verify baseline, every run an override names, and the newest non-hook run are kept too. `prune` VACUUMs afterwards. |
|
|
354
482
|
| `overrides [--json]` | The override audit trail: who granted what, when, and why. |
|
|
355
|
-
| `trend [--json]` | Totals per trusted run: functions, over-target count, CRAP load, average, per-scope rollup. |
|
|
483
|
+
| `trend [--json]` | Totals per trusted run: functions, over-target count, CRAP load, average, per-scope rollup. It reads a per-run rollup table rather than rescanning every scored row, and fills that table for any run missing one, so it writes to the store (best effort: a read-only `.crapkit/` costs the speed, not the command). |
|
|
356
484
|
| `digest [--alert]` | The delta between the two newest runs with identical lane sets. Silent when nothing changed. `--alert` pipes the body to `alert_command` on stdin. Plain lines, never JSON. |
|
|
357
|
-
| `report [--out PATH]` | One self-contained HTML page written to `.crapkit/report.html` (or `--out PATH`, repo-relative), with the path printed on stdout. It renders what `worklist --json` and `trend --json` already answer at their defaults: the ranked worklist capped at `worklist_top`, the per-scope grades off the newest run, the trend series, and a banner naming every stale lane. It measures nothing and opens no network connection. Per-function CRAP and coverage are absent because no repo-wide payload carries them; each row prints the `crapkit explain` call that does. |
|
|
485
|
+
| `report [--out PATH]` | One self-contained HTML page written to `.crapkit/report.html` (or `--out PATH`, repo-relative), with the path printed on stdout. It renders what `worklist --json` and `trend --json` already answer at their defaults: the ranked worklist capped at `worklist_top`, the per-scope grades off the newest run, the trend series, and a banner naming every stale lane. It measures nothing and opens no network connection. Per-function CRAP and coverage are absent because no repo-wide payload carries them; each row prints the `crapkit explain` call that does. It reads the same per-run rollups `trend` does, and writes them on the same terms. |
|
|
358
486
|
| `duplication [--min-lines N] [--similarity F] [--top N] [--json]` | Near-duplicate functions by normalized line shingles with containment scoring. Defaults: `--min-lines 8`, `--similarity 0.8`, `--top 50`. `--top` truncates the list. A function and a function nested inside it never pair: their spans nest, they score 1.0 by construction, and nobody can deduplicate a factory from its own closure. |
|
|
359
|
-
| `coupling [--min-support N] [--min-confidence F] [--top N] [--json]` | File pairs that keep landing in the same commits. Defaults: `--min-support 5` shared commits, `--min-confidence 0.5` max-direction ratio, `--top 50`. Bulk commits never couple pairs, and a young repo returns nothing at the default support. |
|
|
360
|
-
| `mutate [--files F ...] [--max-mutants N] [--json]` | Diff-scoped mutation testing: flips comparisons, boundary shifts, boolean connectives and boolean literals on changed lines, runs `mutation_command` per mutant, lists survivors. `--files` replaces diff scope with the whole file. `--max-mutants` (default 100) caps the run and the cap warning goes to stderr only, so `mutants` in `--json` is the capped count. Shell and PowerShell files are refused by name on stderr rather than mutated: `<` and `>` are redirections there, not comparisons. |
|
|
487
|
+
| `coupling [--min-support N] [--min-confidence F] [--top N] [--json]` | File pairs that keep landing in the same commits. Defaults: `--min-support 5` shared commits, `--min-confidence 0.5` max-direction ratio, `--top 50`. Bulk commits never couple pairs, and a young repo returns nothing at the default support. The ranked pairs are cached in `.crapkit/coupling-cache-v1.json`, keyed on HEAD, the churn window, today's UTC date, the path format and a digest of the tracked set, and shared with `brief` and `worklist --batches` (warm: 1.05 s to 0.11 s on a 72k-commit repo). The date is part of that key, so the first run after midnight UTC rebuilds the pairs on an unchanged HEAD. `--top` reads the cache, because it truncates that same order; `--min-support` or `--min-confidence` off their defaults ask a wider question than the file answers, so they bypass it and recompute. |
|
|
488
|
+
| `mutate [--files F ...] [--max-mutants N] [--drop-pool] [--json]` | Diff-scoped mutation testing: flips comparisons, boundary shifts, boolean connectives and boolean literals on changed lines, runs `mutation_command` per mutant, lists survivors. `--files` replaces diff scope with the whole file. `--max-mutants` (default 100) caps the run and the cap warning goes to stderr only, so `mutants` in `--json` is the capped count. Shell and PowerShell files are refused by name on stderr rather than mutated: `<` and `>` are redirections there, not comparisons. With `mutation_workers > 1` the worker worktrees are kept at `.crapkit/mutate-pool/` and re-prepared per run (30.6 s to build four on a 31,459-file repo, 0.46 s to re-prepare them); `--drop-pool` removes them and exits. |
|
|
361
489
|
| `test-scoped FILE ...` | Runs each owning scope's `[crapkit.scoped_tests]` template on the files (quoted, longest-prefix scope wins). A template with no `{files}` runs as written, which is how a scope whose tests live outside its own paths runs its whole suite. Exit code only; a nonzero runner exits 1. |
|
|
362
490
|
| `hook-precommit` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
|
|
363
491
|
| `claude-hook [--protocol N]` | Reads one Claude Code PostToolUse payload from stdin and judges the file it edited: ccn against the scope ceiling, on functions the edit changed, minus functions a ratchet mark already covers. Advisory only. The edit has landed, nothing is blocked, and `hook-precommit` stays the enforcement point. Exit 2 with three lines on stderr is the only thing it ever says: no `crapkit.toml` above the edited file, an unscoped file, mid-rebase or mid-merge, a `--protocol` other than 1, source that parses to no functions, or any internal failure all exit 0 in silence. Takes no `--repo`, because the root is the first `crapkit.toml` above the edited file and the upward walk stops at a `.git` entry, so a worktree never borrows its parent's config. It opens no snapshot and writes nothing. |
|
|
364
492
|
| `watch [--interval SECONDS] [--cycles N]` | Rescores tracked files as they change (mtime polling, default 2s, subprocess-isolated so a half-saved syntax error never kills the watcher). `--cycles N` polls exactly N times and exits 0; without it the loop runs until ctrl-c. |
|
|
365
|
-
| `mcp` | A dependency-free stdio MCP server (newline JSON-RPC 2.0) exposing nine read-only tools. See [docs/agent-json.md](docs/agent-json.md#mcp-server). |
|
|
493
|
+
| `mcp` | A dependency-free stdio MCP server (newline JSON-RPC 2.0) exposing nine read-only tools. Every tool shells to the CLI's own `--json` surface, so the MCP view cannot drift from what the CLI reports. Answering from a kept in-process store was benchmarked and rejected: a packet's `source` would go stale behind the edit it describes. See [docs/agent-json.md](docs/agent-json.md#mcp-server). |
|
|
366
494
|
|
|
367
495
|
## Reading the output
|
|
368
496
|
|
|
@@ -433,9 +561,10 @@ baseline**. `crapkit runs list` marks which one that is today.
|
|
|
433
561
|
never qualifies, and neither does a `partial` run (a lane failed, so some scope fell back
|
|
434
562
|
to `no-lane`) nor a `hook` override record, which carries no scored rows at all. In `runs
|
|
435
563
|
list`, `verdict=-` marks a run that produces no verdict rather than one that failed: only
|
|
436
|
-
`verify` renders a verdict.
|
|
437
|
-
|
|
438
|
-
|
|
564
|
+
`verify` renders a verdict. Four readers ask this one question and get this one answer: the
|
|
565
|
+
baseline pick here, `ratchet seed`, `prune`, and the tighten damping that compares a mark
|
|
566
|
+
against the same commit's previous run. A mark can no longer be signed off a run `verify`
|
|
567
|
+
refused.
|
|
439
568
|
|
|
440
569
|
**What advances it.** Any qualifying run. `coverage` writes one wherever HEAD is, so a
|
|
441
570
|
dashboard cron advances the baseline exactly as CI does. A passing `verify` advances it
|
|
@@ -457,7 +586,7 @@ $ crapkit verify
|
|
|
457
586
|
warning: run 3 is not the baseline: verify run 2 FAILED with 1 finding(s) and no passing verify has cleared it since — measuring against run 1 @ 88012a148f6 instead, so those findings stay visible. Fix them, or pass `--baseline 3` to accept the newer run deliberately.
|
|
458
587
|
verify FAILED @ d89068de7f3 vs baseline 88012a148f6 (2 changed files)
|
|
459
588
|
GATE crap 72.0 ccn 8 cov 0% calc/legacy.py:7 legacy_router( a , b , c , d , e ) -> decompose
|
|
460
|
-
findings: 1 committed / 0 dirty (uncommitted
|
|
589
|
+
findings: 1 committed / 0 dirty (uncommitted edits and untracked files)
|
|
461
590
|
```
|
|
462
591
|
|
|
463
592
|
Run 3 is a `coverage` run somebody took on the tree run 2 refused, and it scores the same
|
|
@@ -470,6 +599,14 @@ bypasses the rule, and the run history records which run the verdict used. Nothi
|
|
|
470
599
|
touches a repo that has never run `verify`: with no failure to protect, `coverage` alone
|
|
471
600
|
always advances the baseline.
|
|
472
601
|
|
|
602
|
+
**When the id you pass cannot serve.** A `--baseline ID` naming a real run that is not a
|
|
603
|
+
candidate says which run it is, why, and which ones can:
|
|
604
|
+
|
|
605
|
+
```
|
|
606
|
+
$ crapkit verify --baseline 3
|
|
607
|
+
crapkit: run 3 is an inventory run (no coverage was measured) and cannot serve as a baseline; trusted runs: 1, 2; pass `--baseline 2` for the newest
|
|
608
|
+
```
|
|
609
|
+
|
|
473
610
|
## Exit codes
|
|
474
611
|
|
|
475
612
|
| Code | Meaning |
|
|
@@ -477,11 +614,11 @@ always advances the baseline.
|
|
|
477
614
|
| 0 | OK. For `verify` and `hook-precommit`: the gate passed. |
|
|
478
615
|
| 1 | **Overloaded.** Three unrelated things, listed below the table. |
|
|
479
616
|
| 2 | Usage error from argparse: unknown flag, missing positional. Raised before crapkit's own error handling. |
|
|
480
|
-
| 3 | Config error: `crapkit.toml` missing or unparseable, an unknown language or parser, a ratchet metric-stamp mismatch, a `test-scoped` file under no scope or under a scope with no template. |
|
|
617
|
+
| 3 | Config error: `crapkit.toml` missing or unparseable, an unknown language or parser, a lane command the shell that runs it reads as a narrowed suite, a ratchet metric-stamp mismatch ([Upgrading from 0.4.4](#upgrading-from-044)), a `test-scoped` file under no scope or under a scope with no template. |
|
|
481
618
|
| 4 | Git error: not a repository, a baseline commit rewritten out of the history. |
|
|
482
|
-
| 5 | Tool error: lizard not importable, a lane produced no artifact, a lane timed out past its retries, an override alert command failed. |
|
|
483
|
-
| 6 | Gate violation. A function the diff touched is over its ceiling
|
|
484
|
-
| 7 | Ratchet regression. A marked function scores worse than its recorded high-water mark
|
|
619
|
+
| 5 | Tool error: lizard not importable, a lane produced no artifact or one that measured a different tree, a lane timed out past its retries, an override alert command failed. A `timeout_seconds` kills the whole process tree, so no orphan suite keeps running behind the failure. |
|
|
620
|
+
| 6 | Gate violation. A function the diff touched is over its ceiling and past any ratchet mark it carries: an edit that leaves a marked function at or under its mark is the debt the repo signed for and is exempt. Also `rescore --gate`, which applies the same rule, and `hook-precommit`, which exempts on the mark's existence instead. |
|
|
621
|
+
| 7 | Ratchet regression the diff never touched. A marked function scores worse than its recorded high-water mark; a touched one past its mark reports 6. |
|
|
485
622
|
| 8 | New test failures against the baseline run. Failures the baseline already had do not count. |
|
|
486
623
|
| 9 | Diff-coverage ceiling breached: `diff_uncovered_max` is set and more changed lines than that never ran. |
|
|
487
624
|
|
|
@@ -510,9 +647,12 @@ writes runs `pytest --cov` and those flags come from `pytest-cov`:
|
|
|
510
647
|
pip install pytest-cov
|
|
511
648
|
```
|
|
512
649
|
|
|
513
|
-
(
|
|
514
|
-
|
|
515
|
-
|
|
650
|
+
(`pip install "crapkit[py]"` pulls both at once when crapkit shares the suite's venv.)
|
|
651
|
+
|
|
652
|
+
If your suite drives its own CLI through `subprocess.run`, add `[tool.coverage.run]
|
|
653
|
+
patch = ["subprocess"]` to `pyproject.toml` and keep `coverage>=7.10.6`: pytest-cov 7.0.0
|
|
654
|
+
dropped subprocess measurement, so without that key every entry point scores 0% and nothing
|
|
655
|
+
warns. [docs/lanes.md](docs/lanes.md) has the whole rule.
|
|
516
656
|
|
|
517
657
|
### 1. Scaffold the config
|
|
518
658
|
|
|
@@ -526,9 +666,13 @@ added to .gitignore: .crapkit/, .coverage, __pycache__/
|
|
|
526
666
|
`init` sniffs tracked source into one scope per top-level source directory, and detects a
|
|
527
667
|
coverage lane from what the repo already has: a pytest marker file (`pyproject.toml`,
|
|
528
668
|
`pytest.ini`, `setup.cfg`) writes a live `[[lane]]`, and so does a `test` script or
|
|
529
|
-
`vitest`/`jest` in `package.json`.
|
|
530
|
-
|
|
531
|
-
|
|
669
|
+
`vitest`/`jest` in `package.json`. A lockfile beside them names the environment: `uv.lock`,
|
|
670
|
+
`poetry.lock`, `pdm.lock` or `Pipfile.lock` makes the lane `uv run python -m pytest …` (and
|
|
671
|
+
the matching `run` for the rest), because a bare `python` binds to whichever venv the shell
|
|
672
|
+
has active rather than the one the repo pins — see
|
|
673
|
+
[The interpreter a lane binds to](docs/lanes.md#the-interpreter-a-lane-binds-to). Whatever
|
|
674
|
+
it detects, it also leaves commented templates for the runners it did not find. Every lane
|
|
675
|
+
it writes reports into `.crapkit/cov/`, which is why the `.gitignore` list is so short: see
|
|
532
676
|
[Where artifacts live](docs/lanes.md#where-artifacts-live).
|
|
533
677
|
|
|
534
678
|
```toml
|
|
@@ -545,16 +689,18 @@ globs = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/vendor/**", "**/
|
|
|
545
689
|
|
|
546
690
|
[[lane]]
|
|
547
691
|
name = "py"
|
|
548
|
-
command = "python -m pytest --cov --cov-branch --cov-report=json:.crapkit/cov/py.json"
|
|
692
|
+
command = "python -m pytest --cov --cov-branch --cov-report=json:.crapkit/cov/py.json --junitxml=.crapkit/cov/junit-py.xml"
|
|
549
693
|
artifact = ".crapkit/cov/py.json"
|
|
694
|
+
results_artifact = ".crapkit/cov/junit-py.xml"
|
|
550
695
|
parser = "coveragepy"
|
|
551
696
|
scopes = ["calc"]
|
|
552
697
|
|
|
553
698
|
# Declare one [[lane]] per coverage command, then run `crapkit coverage`.
|
|
554
699
|
# [[lane]]
|
|
555
700
|
# name = "js"
|
|
556
|
-
# command = "npx vitest run --coverage --coverage.reportsDirectory=.crapkit/cov/js"
|
|
701
|
+
# command = "npx vitest run --coverage --coverage.reportsDirectory=.crapkit/cov/js --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml"
|
|
557
702
|
# artifact = ".crapkit/cov/js/coverage-final.json"
|
|
703
|
+
# results_artifact = ".crapkit/cov/js/junit.xml"
|
|
558
704
|
# parser = "istanbul"
|
|
559
705
|
# scopes = ["<your-scope>"]
|
|
560
706
|
|
|
@@ -599,11 +745,9 @@ Columns: `risk`, `ccn` with the standard-only ccn in parentheses,
|
|
|
599
745
|
`<commits>c/<authors>a` in the churn window with `w<weight>`, `path:line`, the function's
|
|
600
746
|
long name, then a marker on rows the burn-down queue will not hand out (`ok`, `no-lane`).
|
|
601
747
|
|
|
602
|
-
**`worklist` is the risk map, not a to-do list.** It ranks
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
by `crap` descending instead of by risk. Its `empty: true` is the stop condition; the
|
|
606
|
-
worklist has none.
|
|
748
|
+
**`worklist` is the risk map, not a to-do list.** It ranks finished rows too, so it does
|
|
749
|
+
not empty when the burn-down does. `next-item` is the other view of that run: it drops the
|
|
750
|
+
`no-lane` rows, ranks by `crap`, and its `empty: true` is the stop condition.
|
|
607
751
|
|
|
608
752
|
### 4. Take the top item
|
|
609
753
|
|
|
@@ -669,11 +813,16 @@ added to .gitignore: .crapkit/
|
|
|
669
813
|
```
|
|
670
814
|
|
|
671
815
|
The lane `init` wrote is
|
|
672
|
-
`npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js
|
|
673
|
-
vitest's `json` reporter from `.crapkit/cov/js/coverage-final.json`; the
|
|
674
|
-
`reportsDirectory` flag is what keeps that report out of your root.
|
|
675
|
-
|
|
676
|
-
|
|
816
|
+
`npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml`.
|
|
817
|
+
It reads vitest's `json` reporter from `.crapkit/cov/js/coverage-final.json`; the
|
|
818
|
+
`reportsDirectory` flag is what keeps that report out of your root. The junit half is the
|
|
819
|
+
lane's `results_artifact`, which the crashed-worker and no-new-failures checks read; both
|
|
820
|
+
reporters are named because `--reporter=junit` alone would replace the console output you
|
|
821
|
+
watch the suite through. Anything that produces
|
|
822
|
+
an istanbul `coverage-final.json` works; see [docs/lanes.md](docs/lanes.md) for the
|
|
823
|
+
[jest](docs/lanes.md#jest) and [pytest](docs/lanes.md#pytest) recipes, a package
|
|
824
|
+
[one directory down](docs/lanes.md#running-from-a-subdirectory), and a
|
|
825
|
+
[crapkit root below the repo top](docs/lanes.md#a-crapkit-root-below-the-repo-top).
|
|
677
826
|
|
|
678
827
|
### 2. Install a coverage provider
|
|
679
828
|
|
|
@@ -683,7 +832,7 @@ exit 5:
|
|
|
683
832
|
|
|
684
833
|
```
|
|
685
834
|
$ crapkit coverage
|
|
686
|
-
crapkit: lane 'js' FAILED: lane 'js' produced no artifact at .crapkit/cov/js/coverage-final.json (command exit 1); last output: $ npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js
|
|
835
|
+
crapkit: lane 'js' FAILED: lane 'js' produced no artifact at .crapkit/cov/js/coverage-final.json (command exit 1); full log: /repo/.crapkit/lane-js.log; last output: $ npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml
|
|
687
836
|
|
|
688
837
|
MISSING DEPENDENCY Cannot find dependency '@vitest/coverage-v8'
|
|
689
838
|
|