crapkit 0.4.9__tar.gz → 0.4.11__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.9 → crapkit-0.4.11}/PKG-INFO +70 -48
- crapkit-0.4.9/src/crapkit.egg-info/PKG-INFO → crapkit-0.4.11/README.md +1169 -1186
- {crapkit-0.4.9 → crapkit-0.4.11}/pyproject.toml +1 -1
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/__init__.py +1 -1
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/admin.py +4 -1
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/scoring.py +7 -1
- crapkit-0.4.9/README.md → crapkit-0.4.11/src/crapkit.egg-info/PKG-INFO +1208 -1147
- {crapkit-0.4.9 → crapkit-0.4.11}/LICENSE +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/setup.cfg +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/__main__.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/_pygdefer.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/analyze.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cache.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/churn.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/churn_cache.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/churn_log.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/__init__.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/_shared.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/analyses.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/claude_hook.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/parser.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/queue.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/ratchet_cmds.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/reports.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/cli/verifying.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/config.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/coupling.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/coupling_cache.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/coverage_istanbul.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/coverage_py.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/covstream.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/diffparse.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/digest.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/discover.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/doctor.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/dup.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/errors.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/gitio.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/hook.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/junitparse.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/keys.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/lanes.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/lizardcognitive.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/lizardpowershell.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/lizardrust.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/lizardshell.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/mcp_server.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/merge.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/mutate.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/mutate_pool.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/override.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/packet.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/procs.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/ratchet.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/ratchet_report.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/report.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/sarif.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/sarifio.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/scaffold.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/score.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/snapshot.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/store.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/uncovered.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/universe.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/verify.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/watch.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit/worklist.py +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit.egg-info/SOURCES.txt +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit.egg-info/dependency_links.txt +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit.egg-info/entry_points.txt +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/src/crapkit.egg-info/requires.txt +0 -0
- {crapkit-0.4.9 → crapkit-0.4.11}/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.11
|
|
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
|
|
@@ -42,7 +42,7 @@ Dynamic: license-file
|
|
|
42
42
|
[](https://github.com/JeanFrancoisGagne/crapkit/actions/workflows/ci.yml)
|
|
43
43
|
[](https://pypi.org/project/crapkit/)
|
|
44
44
|
[](https://pypi.org/project/crapkit/)
|
|
45
|
-
[](LICENSE)
|
|
45
|
+
[](https://github.com/JeanFrancoisGagne/crapkit/blob/main/LICENSE)
|
|
46
46
|
|
|
47
47
|

|
|
48
48
|
|
|
@@ -58,6 +58,9 @@ because half the callers are coding agents.
|
|
|
58
58
|
CRAP = ccn^2 * (1 - cov)^3 + ccn
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
The name is not ours: C.R.A.P. (Change Risk Anti-Patterns) was coined for crap4j by
|
|
62
|
+
Alberto Savoia and Bob Evans in 2007.
|
|
63
|
+
|
|
61
64
|
`ccn` is the smaller of standard and modified cyclomatic complexity, both read off one
|
|
62
65
|
lizard pass. `cov` is branch coverage inside the function's span; with no branches it
|
|
63
66
|
falls back to statement coverage, and with no statements to invoked-or-not, so a
|
|
@@ -77,7 +80,12 @@ it.
|
|
|
77
80
|
```
|
|
78
81
|
pip install crapkit
|
|
79
82
|
cd your-repo
|
|
80
|
-
crapkit init #
|
|
83
|
+
crapkit init # crapkit.toml and .gitignore lines, plus a live coverage lane when it
|
|
84
|
+
# recognizes the runner and a scope speaks its language: pyproject.toml,
|
|
85
|
+
# pytest.ini or setup.cfg for pytest; a test script or vitest/jest in
|
|
86
|
+
# package.json for the JS side
|
|
87
|
+
# without one: the lane comes commented out, init says to declare one,
|
|
88
|
+
# and docs/lanes.md is how to fill it in
|
|
81
89
|
crapkit coverage # runs the lane, joins coverage, stores a scored run
|
|
82
90
|
crapkit worklist # the ranked risk map
|
|
83
91
|
crapkit ratchet seed && git add crapkit.toml crapkit-ratchet.tsv .gitignore
|
|
@@ -94,6 +102,9 @@ worklist @ fae4db93108 (run 1, floor ccn>=5, churn 12mo) — 1 active, 0 dormant
|
|
|
94
102
|
risk 0.0 ccn 14 ( 14 std) 1c/1a w 0.00 calc/grade.py:7 classify( score , attempts , late , bonus )
|
|
95
103
|
```
|
|
96
104
|
|
|
105
|
+
`risk 0.0` is what a one-commit repo scores, because churn needs a spread of commits to
|
|
106
|
+
rank and ccn order stands in until then ([Risk](#risk-what-ranks-the-worklist)).
|
|
107
|
+
|
|
97
108
|
`ratchet seed` signs today's debt at today's score. From then on marks only ever fall, so
|
|
98
109
|
the repo can get better and never worse while you burn it down.
|
|
99
110
|
|
|
@@ -145,9 +156,14 @@ mirror installs fine. Requires Python 3.11 or newer. The `pip install -e ".[dev]
|
|
|
145
156
|
[Development](#development) is a different thing: it adds the test extra, for people
|
|
146
157
|
changing crapkit.
|
|
147
158
|
|
|
159
|
+
Scoring runs your own test command on your own machine and reads the artifact it writes.
|
|
160
|
+
There is no network call anywhere in crapkit, so no source, no score and no telemetry
|
|
161
|
+
leaves the box
|
|
162
|
+
([SECURITY.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/SECURITY.md)).
|
|
163
|
+
|
|
148
164
|
```
|
|
149
165
|
$ crapkit --version
|
|
150
|
-
crapkit 0.4.
|
|
166
|
+
crapkit 0.4.11
|
|
151
167
|
```
|
|
152
168
|
|
|
153
169
|
`python -m crapkit` works identically to the console script and is what to use from a
|
|
@@ -222,9 +238,11 @@ claude plugin install crapkit@crapkit
|
|
|
222
238
|
```
|
|
223
239
|
|
|
224
240
|
Two commands, installed once per user, and every repo on the machine gets it. The plugin
|
|
225
|
-
ships three skills
|
|
226
|
-
|
|
227
|
-
|
|
241
|
+
ships three skills, the read-only MCP server, and one advisory PostToolUse hook that names
|
|
242
|
+
any function an edit pushed over its ceiling. Claude reaches two of the skills by itself,
|
|
243
|
+
`crapkit` and `crapkit-recover`; the third you type, as `/crapkit:crapkit-onboard`, because
|
|
244
|
+
wiring a repo up happens once and its description has no business in every turn's window.
|
|
245
|
+
It adds no files to your repo, and it needs the crapkit CLI on PATH.
|
|
228
246
|
|
|
229
247
|
A repo with no `crapkit.toml` costs a silent sub-50 ms no-op per edit. Other agent
|
|
230
248
|
runtimes have no marketplace: copy `plugin/skills/*` into their skills directory instead.
|
|
@@ -375,7 +393,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
|
|
|
375
393
|
repos:
|
|
376
394
|
- repo: https://github.com/JeanFrancoisGagne/crapkit
|
|
377
395
|
# crapkit's release step rewrites this line to the tag it just cut
|
|
378
|
-
rev: v0.4.
|
|
396
|
+
rev: v0.4.11
|
|
379
397
|
hooks:
|
|
380
398
|
- id: crapkit-gate
|
|
381
399
|
```
|
|
@@ -426,7 +444,7 @@ crapkit: baseline commit a74260f321f is not an ancestor of HEAD (rebase or amend
|
|
|
426
444
|
|
|
427
445
|
That is exit 4 on a `git clone --depth 1` of a repo whose baseline verifies at full depth.
|
|
428
446
|
Set `fetch-depth: 0` on the checkout step, which is what crapkit's own
|
|
429
|
-
[.github/workflows/ci.yml](.github/workflows/ci.yml) does.
|
|
447
|
+
[.github/workflows/ci.yml](https://github.com/JeanFrancoisGagne/crapkit/blob/main/.github/workflows/ci.yml) does.
|
|
430
448
|
|
|
431
449
|
The whole PR job, on GitHub Actions:
|
|
432
450
|
|
|
@@ -469,11 +487,11 @@ violation and 0 otherwise. The stderr block above is the same either way.
|
|
|
469
487
|
three-record audit: an alert line through `alert_command`, a ratchet entry staged into the
|
|
470
488
|
commit, and a row in the override log. All three land or nothing does, and an unset
|
|
471
489
|
`alert_command` refuses the override outright. See
|
|
472
|
-
[docs/ratchet.md](docs/ratchet.md#overrides-and-the-audit-trail).
|
|
490
|
+
[docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md#overrides-and-the-audit-trail).
|
|
473
491
|
|
|
474
492
|
## The GitHub Action
|
|
475
493
|
|
|
476
|
-
[action.yml](action.yml) at this repository's root is a composite action, so a reviewer
|
|
494
|
+
[action.yml](https://github.com/JeanFrancoisGagne/crapkit/blob/main/action.yml) at this repository's root is a composite action, so a reviewer
|
|
477
495
|
sees crapkit's numbers on the pull request without installing anything. Four lines add it
|
|
478
496
|
to a workflow, and every input has a default:
|
|
479
497
|
|
|
@@ -481,7 +499,7 @@ to a workflow, and every input has a default:
|
|
|
481
499
|
- uses: actions/checkout@v4
|
|
482
500
|
with:
|
|
483
501
|
fetch-depth: 0
|
|
484
|
-
- uses: JeanFrancoisGagne/crapkit@v0.4.
|
|
502
|
+
- uses: JeanFrancoisGagne/crapkit@v0.4.11
|
|
485
503
|
```
|
|
486
504
|
|
|
487
505
|
The whole job those four lines sit in:
|
|
@@ -497,8 +515,11 @@ jobs:
|
|
|
497
515
|
- uses: actions/checkout@v4
|
|
498
516
|
with:
|
|
499
517
|
fetch-depth: 0 # the diff, and verify's baseline commit
|
|
518
|
+
- uses: actions/setup-python@v5
|
|
519
|
+
with:
|
|
520
|
+
python-version: "3.12" # the interpreter the install below lands in
|
|
500
521
|
- run: pip install -e ".[dev]" # whatever your lanes need to run
|
|
501
|
-
- uses: JeanFrancoisGagne/crapkit@v0.4.
|
|
522
|
+
- uses: JeanFrancoisGagne/crapkit@v0.4.11
|
|
502
523
|
with:
|
|
503
524
|
gate: "false"
|
|
504
525
|
```
|
|
@@ -513,8 +534,9 @@ baseline's commit. With a shallow clone the file list comes back empty and the c
|
|
|
513
534
|
ranks the whole repository instead of the diff.
|
|
514
535
|
|
|
515
536
|
The action installs crapkit from `$GITHUB_ACTION_PATH`, which is its own checkout of the
|
|
516
|
-
ref you pinned in `uses:`. So
|
|
517
|
-
with whatever released
|
|
537
|
+
ref you pinned in `uses:`. So a pin left at last month's tag scores your tree with last
|
|
538
|
+
month's crapkit rather than with whatever released since, and pinning a tag is the whole
|
|
539
|
+
version policy; the snippets above name the current release.
|
|
518
540
|
|
|
519
541
|
### What the comment looks like
|
|
520
542
|
|
|
@@ -564,7 +586,7 @@ behind the checkout to measure from.
|
|
|
564
586
|
| `gate` | `"false"` | `"true"` exits with `crapkit verify`'s own code, so a finding fails the check. Anything else exits 0 and the comment is the whole output |
|
|
565
587
|
| `delta` | `"true"` | scores the pull request's base commit first, so the verdict covers the commits the pull request adds. Costs a second lane run; `"false"` scores the checkout alone |
|
|
566
588
|
| `top` | `"5"` | worklist rows rendered in the table |
|
|
567
|
-
| `python-version` | `"3.12"` | the interpreter `actions/setup-python` installs crapkit into |
|
|
589
|
+
| `python-version` | `"3.12"` | the interpreter `actions/setup-python` installs crapkit into. Match it to the version your own setup-python step named, or the lanes run on an interpreter your dependencies never reached |
|
|
568
590
|
|
|
569
591
|
`gate: "false"` is the default on purpose. A team adopts the action before it has decided
|
|
570
592
|
which findings should stop a merge, and a check that fails on day one gets turned off on
|
|
@@ -641,17 +663,17 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
|
|
|
641
663
|
| Command | What it does |
|
|
642
664
|
|---|---|
|
|
643
665
|
| `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. |
|
|
644
|
-
| `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
|
|
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). |
|
|
645
667
|
| `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. |
|
|
646
|
-
| `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). |
|
|
668
|
+
| `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). |
|
|
647
669
|
| `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. |
|
|
648
670
|
| `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. |
|
|
649
671
|
| `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. |
|
|
650
672
|
| `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. |
|
|
651
673
|
| `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). |
|
|
652
|
-
| `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. |
|
|
674
|
+
| `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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#test-attribution-for-explain---tests)). `--json` emits the same content as one `schema` 1 object. |
|
|
653
675
|
| `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. |
|
|
654
|
-
| `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). |
|
|
676
|
+
| `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](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md). |
|
|
655
677
|
| `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. |
|
|
656
678
|
| `overrides [--json]` | The override audit trail: who granted what, when, and why. |
|
|
657
679
|
| `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). |
|
|
@@ -664,7 +686,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
|
|
|
664
686
|
| `hook-precommit` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
|
|
665
687
|
| `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. |
|
|
666
688
|
| `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. |
|
|
667
|
-
| `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). |
|
|
689
|
+
| `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). |
|
|
668
690
|
|
|
669
691
|
## Reading the output
|
|
670
692
|
|
|
@@ -826,7 +848,7 @@ pip install pytest-cov
|
|
|
826
848
|
If your suite drives its own CLI through `subprocess.run`, add `[tool.coverage.run]
|
|
827
849
|
patch = ["subprocess"]` to `pyproject.toml` and keep `coverage>=7.10.6`: pytest-cov 7.0.0
|
|
828
850
|
dropped subprocess measurement, so without that key every entry point scores 0% and nothing
|
|
829
|
-
warns. [docs/lanes.md](docs/lanes.md) has the whole rule.
|
|
851
|
+
warns. [docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md) has the whole rule.
|
|
830
852
|
|
|
831
853
|
### 1. Scaffold the config
|
|
832
854
|
|
|
@@ -844,11 +866,11 @@ coverage lane from what the repo already has: a pytest marker file (`pyproject.t
|
|
|
844
866
|
`poetry.lock`, `pdm.lock` or `Pipfile.lock` makes the lane `uv run python -m pytest …` (and
|
|
845
867
|
the matching `run` for the rest), because a bare `python` binds to whichever venv the shell
|
|
846
868
|
has active rather than the one the repo pins — see
|
|
847
|
-
[The interpreter a lane binds to](docs/lanes.md#the-interpreter-a-lane-binds-to). Whatever
|
|
869
|
+
[The interpreter a lane binds to](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#the-interpreter-a-lane-binds-to). Whatever
|
|
848
870
|
it detects, it also leaves commented templates for the runners it did not find, and those
|
|
849
871
|
carry the same launcher, so uncommenting one cannot hand the bare `python` back. Every lane
|
|
850
872
|
it writes reports into `.crapkit/cov/`, which is why the `.gitignore` list is so short: see
|
|
851
|
-
[Where artifacts live](docs/lanes.md#where-artifacts-live).
|
|
873
|
+
[Where artifacts live](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#where-artifacts-live).
|
|
852
874
|
|
|
853
875
|
```toml
|
|
854
876
|
[crapkit]
|
|
@@ -886,16 +908,16 @@ calc = "python -m pytest {files} -q -p no:cacheprovider"
|
|
|
886
908
|
```
|
|
887
909
|
|
|
888
910
|
The last block is the one an agent loop needs. `crapkit test-scoped` exits 3 for a file
|
|
889
|
-
whose scope declares no template, and [AGENTS.md](AGENTS.md#4-run-the-owning-scopes-tests)
|
|
911
|
+
whose scope declares no template, and [AGENTS.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/AGENTS.md#4-run-the-owning-scopes-tests)
|
|
890
912
|
makes it step 4 of the burn-down loop. Every key is in
|
|
891
|
-
[docs/configuration.md](docs/configuration.md).
|
|
913
|
+
[docs/configuration.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md).
|
|
892
914
|
|
|
893
915
|
### 2. Check the config against the repo
|
|
894
916
|
|
|
895
917
|
```
|
|
896
918
|
$ crapkit doctor
|
|
897
919
|
ok config keys all recognized
|
|
898
|
-
ok scope 'calc': 1
|
|
920
|
+
ok scope 'calc': 1 file
|
|
899
921
|
ok every tracked source file belongs to a scope
|
|
900
922
|
ok 1 lane(s) declared
|
|
901
923
|
ok lizard 1.24.0
|
|
@@ -934,7 +956,7 @@ $ crapkit next-item
|
|
|
934
956
|
`remedy: "decompose"`, `est_splits: 3` (this needs roughly three pieces to fit under 6),
|
|
935
957
|
and `uncovered_lines` naming the ten lines no test walks. `handle` is the name form to
|
|
936
958
|
pass back, and `stale: false` says the run still describes HEAD. Every field is in
|
|
937
|
-
[docs/agent-json.md](docs/agent-json.md).
|
|
959
|
+
[docs/agent-json.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md).
|
|
938
960
|
|
|
939
961
|
### 5. Seed the ratchet
|
|
940
962
|
|
|
@@ -967,11 +989,11 @@ against the trusted baseline: every function the diff touched sits at or under i
|
|
|
967
989
|
ceiling, no marked function got worse, and no test that passed in the baseline fails now.
|
|
968
990
|
Exit 0 advances the baseline and tightens `crapkit-ratchet.tsv` in place, so the repaid
|
|
969
991
|
mark leaves the file: follow up with `git commit -am "ratchet: classify repaid"`. The full
|
|
970
|
-
mark lifecycle is in [docs/ratchet.md](docs/ratchet.md).
|
|
992
|
+
mark lifecycle is in [docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md).
|
|
971
993
|
|
|
972
994
|
`crapkit next-item` now comes back `empty: true` with a `reasons` object saying which
|
|
973
995
|
ending you got. That is most of the stop condition, not all of it:
|
|
974
|
-
[AGENTS.md](AGENTS.md#the-termination-rule) states the whole rule and reads the rest of
|
|
996
|
+
[AGENTS.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/AGENTS.md#the-termination-rule) states the whole rule and reads the rest of
|
|
975
997
|
`reasons`.
|
|
976
998
|
|
|
977
999
|
## Quickstart: TypeScript
|
|
@@ -994,10 +1016,10 @@ It reads vitest's `json` reporter from `.crapkit/cov/js/coverage-final.json`; th
|
|
|
994
1016
|
lane's `results_artifact`, which the crashed-worker and no-new-failures checks read; both
|
|
995
1017
|
reporters are named because `--reporter=junit` alone would replace the console output you
|
|
996
1018
|
watch the suite through. Anything that produces
|
|
997
|
-
an istanbul `coverage-final.json` works; see [docs/lanes.md](docs/lanes.md) for the
|
|
998
|
-
[jest](docs/lanes.md#jest) and [pytest](docs/lanes.md#pytest) recipes, a package
|
|
999
|
-
[one directory down](docs/lanes.md#running-from-a-subdirectory), and a
|
|
1000
|
-
[crapkit root below the repo top](docs/lanes.md#a-crapkit-root-below-the-repo-top).
|
|
1019
|
+
an istanbul `coverage-final.json` works; see [docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md) for the
|
|
1020
|
+
[jest](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#jest) and [pytest](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#pytest) recipes, a package
|
|
1021
|
+
[one directory down](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#running-from-a-subdirectory), and a
|
|
1022
|
+
[crapkit root below the repo top](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#a-crapkit-root-below-the-repo-top).
|
|
1001
1023
|
|
|
1002
1024
|
### 2. Install a coverage provider
|
|
1003
1025
|
|
|
@@ -1012,7 +1034,7 @@ crapkit: lane 'js' FAILED: lane 'js' produced no artifact at .crapkit/cov/js/cov
|
|
|
1012
1034
|
MISSING DEPENDENCY Cannot find dependency '@vitest/coverage-v8'
|
|
1013
1035
|
|
|
1014
1036
|
(exit 1)
|
|
1015
|
-
crapkit: every lane failed
|
|
1037
|
+
crapkit: every lane failed (1 of 1); the errors are above
|
|
1016
1038
|
```
|
|
1017
1039
|
|
|
1018
1040
|
That failure **writes no run**. Every lane failed, so `coverage` exits before it opens a
|
|
@@ -1038,7 +1060,7 @@ explicitly, keep `"json"` in the list.
|
|
|
1038
1060
|
One more vitest default worth flipping now: it writes **no coverage report at all when the
|
|
1039
1061
|
run fails**, so a single red test becomes a missing artifact and a lane failure. Set
|
|
1040
1062
|
`coverage.reportOnFailure = true`. The full config block is in
|
|
1041
|
-
[docs/lanes.md](docs/lanes.md#reportonfailure).
|
|
1063
|
+
[docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#reportonfailure).
|
|
1042
1064
|
|
|
1043
1065
|
### 3. Score the repo
|
|
1044
1066
|
|
|
@@ -1150,22 +1172,22 @@ dropped it once `classify` scored under the ceiling, rewriting the tracked
|
|
|
1150
1172
|
|
|
1151
1173
|
A verify may also print `warning: N changed line(s) have no coverage` above its verdict;
|
|
1152
1174
|
that block is advisory unless `diff_uncovered_max` is set
|
|
1153
|
-
([docs/configuration.md](docs/configuration.md)).
|
|
1175
|
+
([docs/configuration.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md)).
|
|
1154
1176
|
|
|
1155
1177
|
## Documentation
|
|
1156
1178
|
|
|
1157
1179
|
| Page | Covers |
|
|
1158
1180
|
|---|---|
|
|
1159
|
-
| [The handbook](https://jeanfrancoisgagne.github.io/crapkit/handbook.html) | **Start here for anything deeper.** The illustrated handbook: what crapkit is, how every piece works, and where each command earns its keep. Also at [docs/handbook.html](
|
|
1160
|
-
| [docs/adoption.md](docs/adoption.md) | The judgment layer over the quickstarts: scope granularity, exclude vs lane, scoped_tests wiring, the first-verify taint hazard. |
|
|
1161
|
-
| [docs/configuration.md](docs/configuration.md) | Every `crapkit.toml` key: type, default, and what it does. |
|
|
1162
|
-
| [docs/lanes.md](docs/lanes.md) | The lane model, vitest and jest and pytest recipes, artifact reuse, flake retest, containers. |
|
|
1163
|
-
| [docs/ratchet.md](docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
|
|
1164
|
-
| [docs/agent-json.md](docs/agent-json.md) | The machine surface: `schema`, every payload field, real captured examples. |
|
|
1165
|
-
| [AGENTS.md](AGENTS.md) | The burn-down loop an agent runs, and the rules for changing crapkit itself. |
|
|
1166
|
-
| [plugin/](plugin
|
|
1181
|
+
| [The handbook](https://jeanfrancoisgagne.github.io/crapkit/handbook.html) | **Start here for anything deeper.** The illustrated handbook: what crapkit is, how every piece works, and where each command earns its keep. Also at [docs/handbook.html](https://jeanfrancoisgagne.github.io/crapkit/handbook.html), self-contained, so it opens straight from a clone. |
|
|
1182
|
+
| [docs/adoption.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/adoption.md) | The judgment layer over the quickstarts: scope granularity, exclude vs lane, scoped_tests wiring, the first-verify taint hazard. |
|
|
1183
|
+
| [docs/configuration.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md) | Every `crapkit.toml` key: type, default, and what it does. |
|
|
1184
|
+
| [docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md) | The lane model, vitest and jest and pytest recipes, artifact reuse, flake retest, containers. |
|
|
1185
|
+
| [docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
|
|
1186
|
+
| [docs/agent-json.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md) | The machine surface: `schema`, every payload field, real captured examples. |
|
|
1187
|
+
| [AGENTS.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/AGENTS.md) | The burn-down loop an agent runs, and the rules for changing crapkit itself. |
|
|
1188
|
+
| [plugin/](https://github.com/JeanFrancoisGagne/crapkit/tree/main/plugin) | The Claude Code plugin: three skills, the read-side MCP server, and the advisory PostToolUse hook. |
|
|
1167
1189
|
|
|
1168
|
-
[crapkit.schema.json](crapkit.schema.json) is the authority on the config file shape.
|
|
1190
|
+
[crapkit.schema.json](https://github.com/JeanFrancoisGagne/crapkit/blob/main/crapkit.schema.json) is the authority on the config file shape.
|
|
1169
1191
|
|
|
1170
1192
|
## Development
|
|
1171
1193
|
|
|
@@ -1179,8 +1201,8 @@ python -m pytest -q
|
|
|
1179
1201
|
`pytest-xdist` is not optional: `tests/fixtures/mini_repo` declares a lane that shells out
|
|
1180
1202
|
to `pytest ... -n 2`, and without it that subprocess dies on an unrecognized `-n`. The
|
|
1181
1203
|
`git config` line arms the complexity gate on your own commits. Same steps, with what each
|
|
1182
|
-
one buys, in [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
1204
|
+
one buys, in [CONTRIBUTING.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/CONTRIBUTING.md).
|
|
1183
1205
|
|
|
1184
1206
|
## License
|
|
1185
1207
|
|
|
1186
|
-
MIT. See [LICENSE](LICENSE).
|
|
1208
|
+
MIT. See [LICENSE](https://github.com/JeanFrancoisGagne/crapkit/blob/main/LICENSE).
|