crapkit 0.4.11__tar.gz → 0.4.12__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.
Files changed (76) hide show
  1. {crapkit-0.4.11/src/crapkit.egg-info → crapkit-0.4.12}/PKG-INFO +22 -17
  2. {crapkit-0.4.11 → crapkit-0.4.12}/README.md +21 -16
  3. {crapkit-0.4.11 → crapkit-0.4.12}/pyproject.toml +1 -1
  4. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/__init__.py +1 -1
  5. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/__init__.py +26 -0
  6. crapkit-0.4.12/src/crapkit/cli/_shared.py +228 -0
  7. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/admin.py +269 -33
  8. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/analyses.py +7 -3
  9. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/parser.py +78 -4
  10. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/queue.py +11 -9
  11. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/ratchet_cmds.py +8 -6
  12. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/reports.py +12 -10
  13. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/scoring.py +7 -5
  14. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/verifying.py +55 -16
  15. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/config.py +56 -3
  16. crapkit-0.4.12/src/crapkit/coverage_py.py +164 -0
  17. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/covstream.py +34 -30
  18. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/doctor.py +16 -8
  19. crapkit-0.4.12/src/crapkit/invocation.py +45 -0
  20. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/lanes.py +265 -26
  21. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/mcp_server.py +3 -1
  22. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/mutate_pool.py +57 -3
  23. crapkit-0.4.12/src/crapkit/procs.py +137 -0
  24. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/ratchet.py +32 -5
  25. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/report.py +7 -2
  26. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/scaffold.py +288 -24
  27. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/uncovered.py +3 -1
  28. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/universe.py +10 -1
  29. {crapkit-0.4.11 → crapkit-0.4.12/src/crapkit.egg-info}/PKG-INFO +22 -17
  30. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit.egg-info/SOURCES.txt +1 -0
  31. crapkit-0.4.11/src/crapkit/cli/_shared.py +0 -130
  32. crapkit-0.4.11/src/crapkit/coverage_py.py +0 -87
  33. crapkit-0.4.11/src/crapkit/procs.py +0 -66
  34. {crapkit-0.4.11 → crapkit-0.4.12}/LICENSE +0 -0
  35. {crapkit-0.4.11 → crapkit-0.4.12}/setup.cfg +0 -0
  36. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/__main__.py +0 -0
  37. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/_pygdefer.py +0 -0
  38. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/analyze.py +0 -0
  39. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cache.py +0 -0
  40. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/churn.py +0 -0
  41. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/churn_cache.py +0 -0
  42. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/churn_log.py +0 -0
  43. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/cli/claude_hook.py +0 -0
  44. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/coupling.py +0 -0
  45. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/coupling_cache.py +0 -0
  46. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/coverage_istanbul.py +0 -0
  47. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/diffparse.py +0 -0
  48. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/digest.py +0 -0
  49. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/discover.py +0 -0
  50. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/dup.py +0 -0
  51. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/errors.py +0 -0
  52. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/gitio.py +0 -0
  53. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/hook.py +0 -0
  54. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/junitparse.py +0 -0
  55. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/keys.py +0 -0
  56. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/lizardcognitive.py +0 -0
  57. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/lizardpowershell.py +0 -0
  58. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/lizardrust.py +0 -0
  59. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/lizardshell.py +0 -0
  60. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/merge.py +0 -0
  61. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/mutate.py +0 -0
  62. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/override.py +0 -0
  63. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/packet.py +0 -0
  64. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/ratchet_report.py +0 -0
  65. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/sarif.py +0 -0
  66. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/sarifio.py +0 -0
  67. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/score.py +0 -0
  68. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/snapshot.py +0 -0
  69. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/store.py +0 -0
  70. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/verify.py +0 -0
  71. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/watch.py +0 -0
  72. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit/worklist.py +0 -0
  73. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit.egg-info/dependency_links.txt +0 -0
  74. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit.egg-info/entry_points.txt +0 -0
  75. {crapkit-0.4.11 → crapkit-0.4.12}/src/crapkit.egg-info/requires.txt +0 -0
  76. {crapkit-0.4.11 → crapkit-0.4.12}/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.11
3
+ Version: 0.4.12
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
@@ -117,7 +117,9 @@ command when `pytest_cov` is missing; `pip install "crapkit[py]"` pulls the plug
117
117
  alongside crapkit when the two share a venv. On a Windows PATH holding only the `py`
118
118
  launcher it writes `py`, not a `python3` the lane could never run, and when cmd.exe cannot
119
119
  start the interpreter at all (exit 9009, the Store alias) it names that instead of guessing
120
- at pytest-cov. The two quickstarts below walk a real repo end to end.
120
+ at pytest-cov. A repo that pins no lockfile and carries its own `.venv` gets that venv's
121
+ interpreter in the lane, when that interpreter can import pytest, rather than whichever
122
+ python the shell answers with. The two quickstarts below walk a real repo end to end.
121
123
 
122
124
  **On Windows a lane command is read by cmd.exe**, the shell that will run it, not by sh.
123
125
  Double quotes are the portable quoting. A single-quoted value is refused at config load
@@ -129,7 +131,7 @@ artifact:
129
131
  command = "python -m pytest -m 'not live and not perf' --cov=calc --cov-branch --cov-report=json:.crapkit/cov/py.json"
130
132
 
131
133
  $ crapkit doctor
132
- 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)
134
+ 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); a suite whose testpaths cannot be collected in one process needs one lane per testpath, each with full_suite = false and its own artifact
133
135
  ```
134
136
 
135
137
  Write it `-m "not live and not perf"`. Carets, `&&` and `|` segments, redirections and
@@ -163,7 +165,7 @@ leaves the box
163
165
 
164
166
  ```
165
167
  $ crapkit --version
166
- crapkit 0.4.11
168
+ crapkit 0.4.12
167
169
  ```
168
170
 
169
171
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -393,7 +395,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
393
395
  repos:
394
396
  - repo: https://github.com/JeanFrancoisGagne/crapkit
395
397
  # crapkit's release step rewrites this line to the tag it just cut
396
- rev: v0.4.11
398
+ rev: v0.4.12
397
399
  hooks:
398
400
  - id: crapkit-gate
399
401
  ```
@@ -499,7 +501,7 @@ to a workflow, and every input has a default:
499
501
  - uses: actions/checkout@v4
500
502
  with:
501
503
  fetch-depth: 0
502
- - uses: JeanFrancoisGagne/crapkit@v0.4.11
504
+ - uses: JeanFrancoisGagne/crapkit@v0.4.12
503
505
  ```
504
506
 
505
507
  The whole job those four lines sit in:
@@ -519,7 +521,7 @@ jobs:
519
521
  with:
520
522
  python-version: "3.12" # the interpreter the install below lands in
521
523
  - run: pip install -e ".[dev]" # whatever your lanes need to run
522
- - uses: JeanFrancoisGagne/crapkit@v0.4.11
524
+ - uses: JeanFrancoisGagne/crapkit@v0.4.12
523
525
  with:
524
526
  gate: "false"
525
527
  ```
@@ -663,7 +665,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
663
665
  | Command | What it does |
664
666
  |---|---|
665
667
  | `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. |
666
- | `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](https://github.com/JeanFrancoisGagne/crapkit/tree/main/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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md#doctor---json). |
668
+ | `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 or that the shell cannot start, naming the word to change; a bare name is looked for on PATH and a runner spelled as a path is looked for under the directory the lane runs in, so `.venv/bin/python` answers the same from any directory you run `doctor` in; 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](https://github.com/JeanFrancoisGagne/crapkit/tree/main/plugin) against the `crapkit` on PATH (the bare name its hooks and MCP server spawn) on both version and hook `--protocol`, and FAILs when PATH carries no `crapkit` at all, 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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md#doctor---json). |
667
669
  | `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. |
668
670
  | `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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md). |
669
671
  | `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. |
@@ -678,7 +680,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
678
680
  | `overrides [--json]` | The override audit trail: who granted what, when, and why. |
679
681
  | `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). |
680
682
  | `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. |
681
- | `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. |
683
+ | `report [--out PATH]` | One self-contained HTML page written to `.crapkit/report.html` (or `--out PATH`, repo-relative, or an absolute path you name), 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. |
682
684
  | `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. |
683
685
  | `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. |
684
686
  | `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. |
@@ -686,6 +688,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
686
688
  | `hook-precommit` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
687
689
  | `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, and `hook-precommit` stays the enforcement point. Exit 2 and an advisory on stderr is the only thing it ever says, one block per judged file (a head line, one line per breaching function, a closing line): 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. The root is the first `crapkit.toml` above the edited file; the walk stops at a `.git` entry, so a worktree never borrows its parent's config. A `Bash` event names no file, so it judges the working tree instead: the dirty or untracked `*.py` files touched in the last 12 seconds, 25 at most, each through the same ladder, and silence for a clean tree or a cwd outside any repo. That half fires only where you register a `Bash` matcher ([The Claude Code plugin](#the-claude-code-plugin)). It opens no snapshot and writes nothing. |
688
690
  | `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. |
691
+ | `help [TOPIC]` | The help git, npm and docker answer to. With no TOPIC it prints the command list; with one it prints that subcommand's own help, the same page as `crapkit TOPIC --help`. A TOPIC that names no subcommand exits 3. |
689
692
  | `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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md#mcp-server). |
690
693
 
691
694
  ## Reading the output
@@ -882,11 +885,11 @@ paths = ["calc"]
882
885
  languages = ["python"]
883
886
 
884
887
  [exclude]
885
- globs = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/vendor/**", "**/*.test.*", "**/*.spec.*", "**/test_*.py", "**/*_test.py", "**/conftest.py", "**/*_test.go", "*.config.ts", "*.config.js", "*.config.mts", "**/*.config.ts", "**/*.config.js", "**/*.config.mts"]
888
+ globs = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/vendor/**", "**/*.test.*", "**/*.spec.*", "**/test_*.py", "**/*_test.py", "**/conftest.py", "node_modules/**", "dist/**", "build/**", "vendor/**", "*.test.*", "*.spec.*", "test_*.py", "*_test.py", "conftest.py", "*_test.go", "**/*_test.go", "*.config.ts", "*.config.js", "*.config.mts", "**/*.config.ts", "**/*.config.js", "**/*.config.mts"]
886
889
 
887
890
  [[lane]]
888
891
  name = "py"
889
- command = "python -m pytest --cov --cov-branch --cov-report=json:.crapkit/cov/py.json --junitxml=.crapkit/cov/junit-py.xml"
892
+ command = "python -m pytest --cov --cov-branch --cov-report=json:.crapkit/cov/py.json --junitxml=.crapkit/cov/junit-py.xml --continue-on-collection-errors"
890
893
  artifact = ".crapkit/cov/py.json"
891
894
  results_artifact = ".crapkit/cov/junit-py.xml"
892
895
  parser = "coveragepy"
@@ -895,7 +898,7 @@ scopes = ["calc"]
895
898
  # Declare one [[lane]] per coverage command, then run `crapkit coverage`.
896
899
  # [[lane]]
897
900
  # name = "js"
898
- # command = "npx vitest run --coverage --coverage.reportsDirectory=.crapkit/cov/js --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml"
901
+ # command = "npx vitest run --coverage --coverage.reportsDirectory=.crapkit/cov/js --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml"
899
902
  # artifact = ".crapkit/cov/js/coverage-final.json"
900
903
  # results_artifact = ".crapkit/cov/js/junit.xml"
901
904
  # parser = "istanbul"
@@ -1010,7 +1013,7 @@ added to .gitignore: .crapkit/
1010
1013
  ```
1011
1014
 
1012
1015
  The lane `init` wrote is
1013
- `npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml`.
1016
+ `npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml`.
1014
1017
  It reads vitest's `json` reporter from `.crapkit/cov/js/coverage-final.json`; the
1015
1018
  `reportsDirectory` flag is what keeps that report out of your root. The junit half is the
1016
1019
  lane's `results_artifact`, which the crashed-worker and no-new-failures checks read; both
@@ -1029,7 +1032,7 @@ exit 5:
1029
1032
 
1030
1033
  ```
1031
1034
  $ crapkit coverage
1032
- 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
1035
+ 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 --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml
1033
1036
 
1034
1037
  MISSING DEPENDENCY Cannot find dependency '@vitest/coverage-v8'
1035
1038
 
@@ -1057,9 +1060,11 @@ The artifact crapkit wants is `coverage-final.json`, written by vitest's `json`
1057
1060
  reporter, which is on by default. If your vitest config sets `coverage.reporter`
1058
1061
  explicitly, keep `"json"` in the list.
1059
1062
 
1060
- One more vitest default worth flipping now: it writes **no coverage report at all when the
1061
- run fails**, so a single red test becomes a missing artifact and a lane failure. Set
1062
- `coverage.reportOnFailure = true`. The full config block is in
1063
+ vitest writes **no coverage report at all when the run fails**. The lane `init` wrote
1064
+ already carries `--coverage.reportOnFailure`, so a red test still produces the artifact.
1065
+ If you write the lane by hand, or you would rather keep the switch beside your other
1066
+ coverage settings, `coverage.reportOnFailure = true` in the vitest config does the same
1067
+ job; either one is enough. The full block is in
1063
1068
  [docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#reportonfailure).
1064
1069
 
1065
1070
  ### 3. Score the repo
@@ -78,7 +78,9 @@ command when `pytest_cov` is missing; `pip install "crapkit[py]"` pulls the plug
78
78
  alongside crapkit when the two share a venv. On a Windows PATH holding only the `py`
79
79
  launcher it writes `py`, not a `python3` the lane could never run, and when cmd.exe cannot
80
80
  start the interpreter at all (exit 9009, the Store alias) it names that instead of guessing
81
- at pytest-cov. The two quickstarts below walk a real repo end to end.
81
+ at pytest-cov. A repo that pins no lockfile and carries its own `.venv` gets that venv's
82
+ interpreter in the lane, when that interpreter can import pytest, rather than whichever
83
+ python the shell answers with. The two quickstarts below walk a real repo end to end.
82
84
 
83
85
  **On Windows a lane command is read by cmd.exe**, the shell that will run it, not by sh.
84
86
  Double quotes are the portable quoting. A single-quoted value is refused at config load
@@ -90,7 +92,7 @@ artifact:
90
92
  command = "python -m pytest -m 'not live and not perf' --cov=calc --cov-branch --cov-report=json:.crapkit/cov/py.json"
91
93
 
92
94
  $ crapkit doctor
93
- 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)
95
+ 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); a suite whose testpaths cannot be collected in one process needs one lane per testpath, each with full_suite = false and its own artifact
94
96
  ```
95
97
 
96
98
  Write it `-m "not live and not perf"`. Carets, `&&` and `|` segments, redirections and
@@ -124,7 +126,7 @@ leaves the box
124
126
 
125
127
  ```
126
128
  $ crapkit --version
127
- crapkit 0.4.11
129
+ crapkit 0.4.12
128
130
  ```
129
131
 
130
132
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -354,7 +356,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
354
356
  repos:
355
357
  - repo: https://github.com/JeanFrancoisGagne/crapkit
356
358
  # crapkit's release step rewrites this line to the tag it just cut
357
- rev: v0.4.11
359
+ rev: v0.4.12
358
360
  hooks:
359
361
  - id: crapkit-gate
360
362
  ```
@@ -460,7 +462,7 @@ to a workflow, and every input has a default:
460
462
  - uses: actions/checkout@v4
461
463
  with:
462
464
  fetch-depth: 0
463
- - uses: JeanFrancoisGagne/crapkit@v0.4.11
465
+ - uses: JeanFrancoisGagne/crapkit@v0.4.12
464
466
  ```
465
467
 
466
468
  The whole job those four lines sit in:
@@ -480,7 +482,7 @@ jobs:
480
482
  with:
481
483
  python-version: "3.12" # the interpreter the install below lands in
482
484
  - run: pip install -e ".[dev]" # whatever your lanes need to run
483
- - uses: JeanFrancoisGagne/crapkit@v0.4.11
485
+ - uses: JeanFrancoisGagne/crapkit@v0.4.12
484
486
  with:
485
487
  gate: "false"
486
488
  ```
@@ -624,7 +626,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
624
626
  | Command | What it does |
625
627
  |---|---|
626
628
  | `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. |
627
- | `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](https://github.com/JeanFrancoisGagne/crapkit/tree/main/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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md#doctor---json). |
629
+ | `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 or that the shell cannot start, naming the word to change; a bare name is looked for on PATH and a runner spelled as a path is looked for under the directory the lane runs in, so `.venv/bin/python` answers the same from any directory you run `doctor` in; 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](https://github.com/JeanFrancoisGagne/crapkit/tree/main/plugin) against the `crapkit` on PATH (the bare name its hooks and MCP server spawn) on both version and hook `--protocol`, and FAILs when PATH carries no `crapkit` at all, 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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md#doctor---json). |
628
630
  | `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. |
629
631
  | `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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md). |
630
632
  | `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. |
@@ -639,7 +641,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
639
641
  | `overrides [--json]` | The override audit trail: who granted what, when, and why. |
640
642
  | `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). |
641
643
  | `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. |
642
- | `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. |
644
+ | `report [--out PATH]` | One self-contained HTML page written to `.crapkit/report.html` (or `--out PATH`, repo-relative, or an absolute path you name), 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. |
643
645
  | `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. |
644
646
  | `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. |
645
647
  | `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. |
@@ -647,6 +649,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
647
649
  | `hook-precommit` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
648
650
  | `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, and `hook-precommit` stays the enforcement point. Exit 2 and an advisory on stderr is the only thing it ever says, one block per judged file (a head line, one line per breaching function, a closing line): 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. The root is the first `crapkit.toml` above the edited file; the walk stops at a `.git` entry, so a worktree never borrows its parent's config. A `Bash` event names no file, so it judges the working tree instead: the dirty or untracked `*.py` files touched in the last 12 seconds, 25 at most, each through the same ladder, and silence for a clean tree or a cwd outside any repo. That half fires only where you register a `Bash` matcher ([The Claude Code plugin](#the-claude-code-plugin)). It opens no snapshot and writes nothing. |
649
651
  | `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. |
652
+ | `help [TOPIC]` | The help git, npm and docker answer to. With no TOPIC it prints the command list; with one it prints that subcommand's own help, the same page as `crapkit TOPIC --help`. A TOPIC that names no subcommand exits 3. |
650
653
  | `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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md#mcp-server). |
651
654
 
652
655
  ## Reading the output
@@ -843,11 +846,11 @@ paths = ["calc"]
843
846
  languages = ["python"]
844
847
 
845
848
  [exclude]
846
- globs = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/vendor/**", "**/*.test.*", "**/*.spec.*", "**/test_*.py", "**/*_test.py", "**/conftest.py", "**/*_test.go", "*.config.ts", "*.config.js", "*.config.mts", "**/*.config.ts", "**/*.config.js", "**/*.config.mts"]
849
+ globs = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/vendor/**", "**/*.test.*", "**/*.spec.*", "**/test_*.py", "**/*_test.py", "**/conftest.py", "node_modules/**", "dist/**", "build/**", "vendor/**", "*.test.*", "*.spec.*", "test_*.py", "*_test.py", "conftest.py", "*_test.go", "**/*_test.go", "*.config.ts", "*.config.js", "*.config.mts", "**/*.config.ts", "**/*.config.js", "**/*.config.mts"]
847
850
 
848
851
  [[lane]]
849
852
  name = "py"
850
- command = "python -m pytest --cov --cov-branch --cov-report=json:.crapkit/cov/py.json --junitxml=.crapkit/cov/junit-py.xml"
853
+ command = "python -m pytest --cov --cov-branch --cov-report=json:.crapkit/cov/py.json --junitxml=.crapkit/cov/junit-py.xml --continue-on-collection-errors"
851
854
  artifact = ".crapkit/cov/py.json"
852
855
  results_artifact = ".crapkit/cov/junit-py.xml"
853
856
  parser = "coveragepy"
@@ -856,7 +859,7 @@ scopes = ["calc"]
856
859
  # Declare one [[lane]] per coverage command, then run `crapkit coverage`.
857
860
  # [[lane]]
858
861
  # name = "js"
859
- # command = "npx vitest run --coverage --coverage.reportsDirectory=.crapkit/cov/js --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml"
862
+ # command = "npx vitest run --coverage --coverage.reportsDirectory=.crapkit/cov/js --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml"
860
863
  # artifact = ".crapkit/cov/js/coverage-final.json"
861
864
  # results_artifact = ".crapkit/cov/js/junit.xml"
862
865
  # parser = "istanbul"
@@ -971,7 +974,7 @@ added to .gitignore: .crapkit/
971
974
  ```
972
975
 
973
976
  The lane `init` wrote is
974
- `npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml`.
977
+ `npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml`.
975
978
  It reads vitest's `json` reporter from `.crapkit/cov/js/coverage-final.json`; the
976
979
  `reportsDirectory` flag is what keeps that report out of your root. The junit half is the
977
980
  lane's `results_artifact`, which the crashed-worker and no-new-failures checks read; both
@@ -990,7 +993,7 @@ exit 5:
990
993
 
991
994
  ```
992
995
  $ crapkit coverage
993
- 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
996
+ 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 --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml
994
997
 
995
998
  MISSING DEPENDENCY Cannot find dependency '@vitest/coverage-v8'
996
999
 
@@ -1018,9 +1021,11 @@ The artifact crapkit wants is `coverage-final.json`, written by vitest's `json`
1018
1021
  reporter, which is on by default. If your vitest config sets `coverage.reporter`
1019
1022
  explicitly, keep `"json"` in the list.
1020
1023
 
1021
- One more vitest default worth flipping now: it writes **no coverage report at all when the
1022
- run fails**, so a single red test becomes a missing artifact and a lane failure. Set
1023
- `coverage.reportOnFailure = true`. The full config block is in
1024
+ vitest writes **no coverage report at all when the run fails**. The lane `init` wrote
1025
+ already carries `--coverage.reportOnFailure`, so a red test still produces the artifact.
1026
+ If you write the lane by hand, or you would rather keep the switch beside your other
1027
+ coverage settings, `coverage.reportOnFailure = true` in the vitest config does the same
1028
+ job; either one is enough. The full block is in
1024
1029
  [docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#reportonfailure).
1025
1030
 
1026
1031
  ### 3. Score the repo
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "crapkit"
7
- version = "0.4.11"
7
+ version = "0.4.12"
8
8
  description = "Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { text = "MIT" }
@@ -1,2 +1,2 @@
1
1
  """crapkit: deterministic CRAP-score framework."""
2
- __version__ = "0.4.11"
2
+ __version__ = "0.4.12"
@@ -59,8 +59,10 @@ _OWNER = {
59
59
  "_claimable": "queue",
60
60
  "_claims_summary": "queue",
61
61
  "_claims_to_release": "queue",
62
+ "_clearing_spellings": "verifying",
62
63
  "_collect_lanes": "scoring",
63
64
  "_collect_mutants": "analyses",
65
+ "_committed_launcher": "admin",
64
66
  "_config": "claude_hook",
65
67
  "_contexts_for_span": "reports",
66
68
  "_coupling_pairs": "analyses",
@@ -118,16 +120,21 @@ _OWNER = {
118
120
  "_guard_ratchet_stamp": "verifying",
119
121
  "_handle": "queue",
120
122
  "_held_marks": "verifying",
123
+ "_help_topic": "parser",
124
+ "_help_topics": "parser",
121
125
  "_hook_handlers": "admin",
122
126
  "_hook_modes": "admin",
123
127
  "_hook_protocols": "admin",
128
+ "_imports_pytest": "admin",
124
129
  "_installed_crapkit_roots": "admin",
125
130
  "_interpreter": "admin",
131
+ "_is_rooted": "_shared",
126
132
  "_is_test_path": "verifying",
127
133
  "_judge": "claude_hook",
128
134
  "_junit_seconds": "admin",
129
135
  "_lane_command_problems": "admin",
130
136
  "_lane_durations": "admin",
137
+ "_lane_path": "admin",
131
138
  "_lane_problem": "admin",
132
139
  "_lane_problems": "admin",
133
140
  "_lane_report": "admin",
@@ -142,18 +149,22 @@ _OWNER = {
142
149
  "_load_ratchet_or_die": "_shared",
143
150
  "_load_repo_config": "_shared",
144
151
  "_load_sources": "_shared",
152
+ "_looks_like_a_path": "parser",
145
153
  "_manifest_field": "admin",
146
154
  "_manifest_roots": "admin",
147
155
  "_manifest_version": "admin",
148
156
  "_mark_text": "queue",
157
+ "_marker_texts": "admin",
149
158
  "_matching_rows": "queue",
150
159
  "_maybe_claim": "queue",
151
160
  "_marks_for": "claude_hook",
152
161
  "_maybe_flake_retry": "verifying",
153
162
  "_merge_stamp": "ratchet_cmds",
154
163
  "_missing_named_script": "admin",
164
+ "_missing_runner": "admin",
155
165
  "_more_skips_line": "verifying",
156
166
  "_mutation_targets": "analyses",
167
+ "_name_found_root": "admin",
157
168
  "_name_matches": "queue",
158
169
  "_name_prefix": "queue",
159
170
  "_named_baseline": "verifying",
@@ -168,6 +179,7 @@ _OWNER = {
168
179
  "_next_reasons": "queue",
169
180
  "_next_step": "admin",
170
181
  "_no_baseline": "verifying",
182
+ "_no_crapkit_on_path": "admin",
171
183
  "_no_handle_message": "queue",
172
184
  "_no_lane_debt": "queue",
173
185
  "_no_lane_gap": "queue",
@@ -185,12 +197,14 @@ _OWNER = {
185
197
  "_packet_context": "queue",
186
198
  "_packet_gate": "queue",
187
199
  "_packet_scope": "queue",
200
+ "_path_first_arg": "parser",
188
201
  "_payload": "claude_hook",
189
202
  "_pick_baseline": "verifying",
190
203
  "_pick_function": "queue",
191
204
  "_plugin_json": "admin",
192
205
  "_plugins_dir": "admin",
193
206
  "_policy_findings": "ratchet_cmds",
207
+ "_positive_top": "_shared",
194
208
  "_present_lockfiles": "admin",
195
209
  "_present_markers": "admin",
196
210
  "_present_on_disk": "scoring",
@@ -200,6 +214,7 @@ _OWNER = {
200
214
  "_print_brief_context": "queue",
201
215
  "_print_brief_neighbours": "queue",
202
216
  "_print_claims": "queue",
217
+ "_print_clear_the_reason": "verifying",
203
218
  "_print_coverage": "scoring",
204
219
  "_print_duplication": "analyses",
205
220
  "_print_finding_split": "verifying",
@@ -215,6 +230,7 @@ _OWNER = {
215
230
  "_print_trend": "reports",
216
231
  "_print_uncovered": "reports",
217
232
  "_print_verify_findings": "verifying",
233
+ "_probed_cli_version": "admin",
218
234
  "_progress": "scoring",
219
235
  "_prune_renames": "ratchet_cmds",
220
236
  "_pruned": "ratchet_cmds",
@@ -227,6 +243,7 @@ _OWNER = {
227
243
  "_ratchet_mark": "reports",
228
244
  "_ratchet_merge": "ratchet_cmds",
229
245
  "_ratchet_move": "ratchet_cmds",
246
+ "_ratchet_or_die": "_shared",
230
247
  "_ratchet_report": "ratchet_cmds",
231
248
  "_ratchet_sha256": "verifying",
232
249
  "_reconfigure_streams": "parser",
@@ -235,8 +252,12 @@ _OWNER = {
235
252
  "_records_by_scope": "scoring",
236
253
  "_refuse_empty_lane_run": "scoring",
237
254
  "_refuse_lane_less_verify": "verifying",
255
+ "_refuse_path_argument": "parser",
238
256
  "_release_claims": "verifying",
239
257
  "_release_target": "queue",
258
+ "_repo_out_path": "_shared",
259
+ "_repo_relative": "_shared",
260
+ "_repo_venv_python": "admin",
240
261
  "_report": "claude_hook",
241
262
  "_report_lanes": "reports",
242
263
  "_report_payload": "reports",
@@ -274,6 +295,7 @@ _OWNER = {
274
295
  "_sequencing": "claude_hook",
275
296
  "_settle_verify": "verifying",
276
297
  "_skip_reason": "queue",
298
+ "_spawned_cli": "admin",
277
299
  "_split_marked": "verifying",
278
300
  "_split_worklist": "queue",
279
301
  "_stale_warning": "queue",
@@ -292,12 +314,15 @@ _OWNER = {
292
314
  "_uncovered_fields": "queue",
293
315
  "_uncovered_scopes": "admin",
294
316
  "_uncovered_text": "reports",
317
+ "_under_root": "_shared",
295
318
  "_under_scope_path": "verifying",
296
319
  "_unknown_key_text": "admin",
297
320
  "_unmarked_breaches": "scoring",
298
321
  "_unknown_claude_command": "parser",
299
322
  "_untracked_of": "scoring",
300
323
  "_untrusted_reason": "verifying",
324
+ "_venv_dirs": "admin",
325
+ "_venv_launcher": "admin",
301
326
  "_verdict": "claude_hook",
302
327
  "_verify_attribution": "verifying",
303
328
  "_verify_baseline": "verifying",
@@ -335,6 +360,7 @@ _OWNER = {
335
360
  "cmd_doctor": "admin",
336
361
  "cmd_duplication": "analyses",
337
362
  "cmd_explain": "reports",
363
+ "cmd_help": "parser",
338
364
  "cmd_hook_precommit": "verifying",
339
365
  "cmd_init": "admin",
340
366
  "cmd_inventory": "scoring",