crapkit 0.7.6__tar.gz → 0.8.0__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 (103) hide show
  1. {crapkit-0.7.6 → crapkit-0.8.0}/PKG-INFO +62 -31
  2. crapkit-0.7.6/src/crapkit.egg-info/PKG-INFO → crapkit-0.8.0/README.md +1398 -1408
  3. {crapkit-0.7.6 → crapkit-0.8.0}/pyproject.toml +10 -5
  4. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/__init__.py +1 -1
  5. crapkit-0.8.0/src/crapkit/_process_owner.py +335 -0
  6. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/_windows_job.py +2 -2
  7. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/analyze.py +177 -49
  8. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cache.py +4 -3
  9. crapkit-0.8.0/src/crapkit/churn.py +224 -0
  10. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/churn_cache.py +30 -10
  11. crapkit-0.8.0/src/crapkit/churn_commits.py +184 -0
  12. crapkit-0.8.0/src/crapkit/churn_log.py +458 -0
  13. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/_shared.py +66 -18
  14. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/admin.py +173 -244
  15. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/analyses.py +11 -5
  16. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/claude_hook.py +32 -5
  17. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/maintenance.py +11 -10
  18. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/parser.py +14 -7
  19. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/queue.py +154 -156
  20. crapkit-0.8.0/src/crapkit/cli/ratchet_cmds.py +524 -0
  21. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/reports.py +62 -18
  22. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/scoring.py +143 -28
  23. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/verifying.py +189 -108
  24. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/config.py +38 -5
  25. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/config_contract.py +24 -6
  26. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/coupling.py +8 -1
  27. crapkit-0.8.0/src/crapkit/coverage_format.py +57 -0
  28. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/coverage_istanbul.py +138 -3
  29. crapkit-0.8.0/src/crapkit/coverage_py.py +287 -0
  30. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/covstream.py +17 -220
  31. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/doctor.py +113 -18
  32. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/dup.py +143 -48
  33. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/gitio.py +58 -31
  34. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/hook.py +9 -1
  35. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/junitparse.py +50 -8
  36. crapkit-0.8.0/src/crapkit/keys.py +402 -0
  37. crapkit-0.8.0/src/crapkit/lane_changes.py +145 -0
  38. crapkit-0.8.0/src/crapkit/lane_command.py +187 -0
  39. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/lanes.py +375 -175
  40. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/lizardcognitive.py +43 -9
  41. crapkit-0.8.0/src/crapkit/lizardpython.py +389 -0
  42. crapkit-0.8.0/src/crapkit/lizardtypescript.py +299 -0
  43. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/mcp_server.py +70 -43
  44. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/merge.py +4 -0
  45. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/override.py +48 -22
  46. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/packet.py +91 -116
  47. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/procs.py +168 -257
  48. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/ratchet.py +74 -30
  49. crapkit-0.8.0/src/crapkit/ratchetfile.py +160 -0
  50. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/report.py +24 -12
  51. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/scaffold.py +14 -10
  52. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/score.py +68 -17
  53. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/snapshot.py +11 -3
  54. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/store.py +470 -139
  55. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/uncovered.py +14 -20
  56. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/universe.py +3 -3
  57. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/verify.py +49 -8
  58. crapkit-0.7.6/README.md → crapkit-0.8.0/src/crapkit.egg-info/PKG-INFO +1439 -1367
  59. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit.egg-info/SOURCES.txt +4 -1
  60. crapkit-0.7.6/src/crapkit/_process_owner.py +0 -129
  61. crapkit-0.7.6/src/crapkit/churn.py +0 -86
  62. crapkit-0.7.6/src/crapkit/churn_log.py +0 -340
  63. crapkit-0.7.6/src/crapkit/cli/ratchet_cmds.py +0 -258
  64. crapkit-0.7.6/src/crapkit/coverage_py.py +0 -119
  65. crapkit-0.7.6/src/crapkit/keys.py +0 -171
  66. crapkit-0.7.6/src/crapkit/lizardpython.py +0 -262
  67. crapkit-0.7.6/src/crapkit/lizardtypescript.py +0 -142
  68. crapkit-0.7.6/src/crapkit/ratchetfile.py +0 -84
  69. crapkit-0.7.6/src/crapkit/retention.py +0 -204
  70. {crapkit-0.7.6 → crapkit-0.8.0}/LICENSE +0 -0
  71. {crapkit-0.7.6 → crapkit-0.8.0}/setup.cfg +0 -0
  72. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/__main__.py +0 -0
  73. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/_analysis_pool.py +0 -0
  74. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/_mcp_stdio.py +0 -0
  75. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/_process_family.py +0 -0
  76. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/_pygdefer.py +0 -0
  77. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/cli/__init__.py +0 -0
  78. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/coupling_cache.py +0 -0
  79. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/diffparse.py +0 -0
  80. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/digest.py +0 -0
  81. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/errors.py +0 -0
  82. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/gitpaths.py +0 -0
  83. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/invocation.py +0 -0
  84. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/lizardpowershell.py +0 -0
  85. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/lizardrust.py +0 -0
  86. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/lizardshell.py +0 -0
  87. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/locks.py +0 -0
  88. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/logs.py +0 -0
  89. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/mutate.py +0 -0
  90. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/mutate_pool.py +0 -0
  91. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/ratchet_report.py +0 -0
  92. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/records.py +0 -0
  93. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/repotext.py +0 -0
  94. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/resources.py +0 -0
  95. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/rootfind.py +0 -0
  96. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/sarif.py +0 -0
  97. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/sarifio.py +0 -0
  98. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/watch.py +0 -0
  99. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit/worklist.py +0 -0
  100. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit.egg-info/dependency_links.txt +0 -0
  101. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit.egg-info/entry_points.txt +0 -0
  102. {crapkit-0.7.6 → crapkit-0.8.0}/src/crapkit.egg-info/requires.txt +0 -0
  103. {crapkit-0.7.6 → crapkit-0.8.0}/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.7.6
3
+ Version: 0.8.0
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
@@ -224,7 +224,7 @@ files. Review those commands before running Crapkit in a repository you do not t
224
224
 
225
225
  ```
226
226
  $ crapkit --version
227
- crapkit 0.7.6
227
+ crapkit 0.8.0
228
228
  ```
229
229
 
230
230
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -237,18 +237,22 @@ where the flag goes.
237
237
 
238
238
  Keep the CLI and plugin versions aligned, measure fresh coverage after upgrading,
239
239
  and review any ratchet identity refusal before reseeding. The current reader is
240
- analysis version 10; older JavaScript and TypeScript callback marks can require a
241
- reviewed mapping. Follow the [upgrade guide](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/upgrading.md)
240
+ analysis version 11. It renames once each Python def with a PEP 695 type parameter
241
+ list and each def nested three or more deep, and it lists a def whose body sits on
242
+ its colon line; `crapkit ratchet prune` drops the marks left under the old names.
243
+ Older JavaScript and TypeScript callback marks can require a reviewed mapping.
244
+ Follow the [upgrade guide](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/upgrading.md)
242
245
  for saved state, portable records and Windows launcher locks.
243
246
 
244
247
  ### Upgrading from 0.4.4
245
248
 
246
249
  This historical example describes the 0.4.4 to 0.4.5 transition, from analysis
247
- version 7 to 8. It is retained to explain older refusal messages:
250
+ version 7 to 8. It is retained to explain the refusal, quoted as crapkit prints it
251
+ today:
248
252
 
249
253
  ```
250
254
  $ crapkit verify
251
- crapkit: ratchet marks were recorded under [crapkit-analysis=7 lizard=1.24.0] but this run measures [crapkit-analysis=8 lizard=1.24.0] — CRAP scores are not comparable across metric versions; re-baseline with `crapkit ratchet seed`
255
+ crapkit: ratchet marks were recorded under [crapkit-analysis=7 lizard=1.24.0] but this run measures [crapkit-analysis=8 lizard=1.24.0] — CRAP scores are not comparable across metric versions; run `crapkit coverage`, then re-baseline with `crapkit ratchet seed`
252
256
  ```
253
257
 
254
258
  That transition changed cognitive complexity, not `ccn` or the CRAP formula.
@@ -276,8 +280,10 @@ any function an edit pushed over its ceiling. Claude reaches two of the skills b
276
280
  wiring a repo up happens once and its description has no business in every turn's window.
277
281
  It adds no files to your repo, and it needs the crapkit CLI on PATH.
278
282
 
279
- A repo with no `crapkit.toml` costs a silent sub-50 ms no-op per edit. After upgrading
280
- the CLI, refresh the marketplace before updating the installed plugin:
283
+ A repo with no `crapkit.toml` costs a silent no-op per edit: 68 ms on Windows through the
284
+ `crapkit.exe` launcher Claude Code starts, where a bare `python -c pass` took 32 ms on the
285
+ same machine. After upgrading the CLI, refresh the marketplace before updating the
286
+ installed plugin:
281
287
 
282
288
  ```
283
289
  claude plugin marketplace update crapkit
@@ -310,8 +316,8 @@ command, matcher `Bash`:
310
316
  ```
311
317
 
312
318
  The cost is one `git rev-parse --show-toplevel` and one `git status --porcelain -z -uall`
313
- per shell call in any git repo, whether or not crapkit measures it: about 30 ms together
314
- on crapkit's own checkout, and more on a bigger tree. What comes back is the dirty or
319
+ per shell call in any git repo, whether or not crapkit measures it: about 50 ms together
320
+ on crapkit's own checkout on Windows, and more on a bigger tree. What comes back is the dirty or
315
321
  untracked `*.py` files written in the last 12 seconds, 25 at most, each judged the way an
316
322
  edit is. Python only, so a TypeScript or Go repo pays the two spawns and hears nothing.
317
323
 
@@ -396,7 +402,7 @@ full verdict. The preview and hooks differ in what their available evidence can
396
402
  | Surface | Fires | Power |
397
403
  |---|---|---|
398
404
  | `crapkit claude-hook` | after an agent's edit lands | **advisory.** Names the breach on stderr. Blocks nothing, because PostToolUse runs after the write |
399
- | `crapkit rescore FILE --gate` | when you ask, after the first coverage run | **preview.** The commit gate's verdict on demand, sub-second, before you stage. With no run behind it, exit 1 and `no snapshot` |
405
+ | `crapkit rescore FILE --gate` | when you ask, after the first coverage run | **preview.** A stricter preview of the commit gate, sub-second, before you stage: a ratchet mark pardons a function only while its CRAP is at or under the mark. With no run behind it, exit 1 and `no snapshot` |
400
406
  | `crapkit hook-precommit` | `git commit` | **blocks.** The hook exits 6; git reports 1. Staged blobs only, so it costs the size of the commit and needs no coverage |
401
407
  | `crapkit verify` | before you push, and in CI | **the verdict.** Gate, ratchet, new test failures, diff coverage, against the trusted baseline |
402
408
 
@@ -430,8 +436,9 @@ chmod +x .git/hooks/pre-commit
430
436
  ```
431
437
 
432
438
  The same file from PowerShell. `Out-File` and `>` write a byte-order mark (UTF-16 on
433
- 5.1) in front of the shebang, and git then answers every commit with `cannot spawn
434
- .git/hooks/pre-commit` and lets it through; `Set-Content -Encoding ascii` does not. Git
439
+ 5.1) in front of the shebang, and git then refuses every commit with `error: cannot spawn
440
+ .git/hooks/pre-commit: No such file or directory` (measured on git 2.43 for Windows)
441
+ without ever running the gate; `Set-Content -Encoding ascii` writes no mark. Git
435
442
  runs the hook with its own `sh`, so the interpreter is spelled with forward slashes and
436
443
  quoted, and no `chmod` is needed on Windows:
437
444
 
@@ -480,7 +487,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
480
487
  repos:
481
488
  - repo: https://github.com/JeanFrancoisGagne/crapkit
482
489
  # crapkit's release step rewrites this line to the tag it just cut
483
- rev: v0.7.6
490
+ rev: v0.8.0
484
491
  hooks:
485
492
  - id: crapkit-gate
486
493
  ```
@@ -532,6 +539,16 @@ crapkit: baseline commit a74260f321f is not an ancestor of HEAD in this shallow
532
539
  That is exit 4 on a `git clone --depth 1` of a repo whose baseline verifies at full depth.
533
540
  On a full clone the same exit blames what it used to, a rebase or an amend that rewrote
534
541
  history, and asks for a fresh baseline instead.
542
+ `verify --base` and `hook-precommit --base` look up the fork point with `git merge-base`,
543
+ and in the same clone they refuse with exit 4 and the same fix:
544
+
545
+ ```
546
+ $ crapkit hook-precommit --base c47a37b1df69c434ba42eec5979ddad03d2bf1e4
547
+ crapkit: git merge-base c47a37b1df69c434ba42eec5979ddad03d2bf1e4 HEAD failed in /home/runner/work/app/app: fatal: Not a valid commit name c47a37b1df69c434ba42eec5979ddad03d2bf1e4; this shallow clone does not hold every commit: set fetch-depth: 0 on the checkout or run git fetch --unshallow
548
+ ```
549
+
550
+ When the clone holds both commits but not the one they fork from, the line reads
551
+ `no merge base between REF and HEAD in ROOT`, followed by the same fix.
535
552
  Set `fetch-depth: 0` on the checkout step, which is what crapkit's own
536
553
  [.github/workflows/ci.yml](https://github.com/JeanFrancoisGagne/crapkit/blob/main/.github/workflows/ci.yml) does.
537
554
 
@@ -588,7 +605,7 @@ to a workflow, and every input has a default:
588
605
  - uses: actions/checkout@v4
589
606
  with:
590
607
  fetch-depth: 0
591
- - uses: JeanFrancoisGagne/crapkit@v0.7.6
608
+ - uses: JeanFrancoisGagne/crapkit@v0.8.0
592
609
  ```
593
610
 
594
611
  The whole job those four lines sit in:
@@ -608,7 +625,7 @@ jobs:
608
625
  with:
609
626
  python-version: "3.12" # the interpreter the install below lands in
610
627
  - run: pip install -e ".[dev]" # whatever your lanes need to run
611
- - uses: JeanFrancoisGagne/crapkit@v0.7.6
628
+ - uses: JeanFrancoisGagne/crapkit@v0.8.0
612
629
  with:
613
630
  gate: "false"
614
631
  ```
@@ -764,8 +781,9 @@ comment then carries coverage's failure in place of the verdict:
764
781
  ```
765
782
 
766
783
  The parenthesis is the first line of the lane failure the summary carries. When every
767
- lane failed, `coverage` prints no summary at all and the lane errors are only in the job
768
- log, and the line says so. With `gate: "true"` the job exits with coverage's code.
784
+ lane failed, `coverage` prints an error object instead of a summary and the lane errors
785
+ are only in the job log, and the line says so: `(every lane failed (1 of 1); the lane
786
+ errors are in the job log)`. With `gate: "true"` the job exits with coverage's code.
769
787
 
770
788
  The other gate that judges a delta is the portable baseline in [Route 4](#route-4-ci):
771
789
  commit `crapkit-baseline.tsv` on the default branch and run `crapkit verify --baseline-tsv
@@ -779,9 +797,9 @@ on PATH fails that step. Both leave the rendered text in the job log.
779
797
 
780
798
  ## Subcommands
781
799
 
782
- `crapkit clean --dry-run --json` previews policy-based cleanup. See
800
+ `crapkit clean --dry-run --json` previews abandoned temporary mutation recovery. See
783
801
  [resource policies](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/resources.md)
784
- for shared analysis workers, process lifetime, bounded logs and retained evidence.
802
+ for shared analysis workers, process lifetime and bounded logs.
785
803
 
786
804
  Every subcommand takes `--repo PATH`, and the flag goes **after** the subcommand. Without
787
805
  it the root is the nearest `crapkit.toml` at or above the current directory
@@ -809,7 +827,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
809
827
 
810
828
  | Command | What it does |
811
829
  |---|---|
812
- | `clean [--dry-run] [--json]` | Applies configured retention to recognized idle default test evidence and recovers abandoned temporary mutation worktrees. Preserves active runs, caller-managed output and intentional mutation pools. `--dry-run` reports planned removals. |
830
+ | `clean [--dry-run] [--json]` | Recovers abandoned temporary mutation worktrees. Preserves active runs and intentional mutation pools. `--dry-run` reports planned recoveries. It leaves test evidence alone: crapkit's own development runner, `tools/testing/run.py`, prunes `.crapkit/test-runs/` with `--retention-days` and `--retention-count`. `--json` keeps `test_runs` with every array empty. |
813
831
  | `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. |
814
832
  | `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). |
815
833
  | `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. |
@@ -820,8 +838,8 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
820
838
  | `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. |
821
839
  | `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). |
822
840
  | `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. |
823
- | `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. |
824
- | `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). |
841
+ | `rescore FILE ... [--gate] [--json]` | Fresh complexity for named files over the latest run's stale coverage, joined by name. A function on a line span another one shares, and a Python def whose body starts on the line its signature ends, scores untested, as the coverage run scores it. 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. |
842
+ | `ratchet seed \| prune \| merge \| move \| report [--baseline ID] [--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. `seed` and `prune` take `--baseline ID` to read a named run instead of verify's pick, refused for the reasons `verify --baseline` refuses one. See [docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md). |
825
843
  | `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. |
826
844
  | `overrides [--json]` | The override audit trail: who granted what, when, and why. |
827
845
  | `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). |
@@ -831,7 +849,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
831
849
  | `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. |
832
850
  | `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. Both lists pass through the scored corpus first, the same predicate `coverage` uses (scopes, excludes, the test-file cut, `max_file_bytes`): a test file, an excluded path, a file over `max_file_bytes` or a file no scope claims is named on stderr and never mutated, `--json` lists it under `outside_corpus`, and when nothing is left stdout says `nothing to mutate` at exit 0 without starting the suite. `--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. Every worker uses a kept worktree, including one; see [mutation worktrees](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md#mutation-worktrees). `--drop-pool` removes them and exits. |
833
851
  | `test-scoped FILE ...` | Runs each owning scope's `[crapkit.scoped_tests]` template on the files (quoted, longest-prefix scope wins). A template with no `{files}` runs as written, which is how a scope whose tests live outside its own paths runs its whole suite. Exit code only; a nonzero runner exits 1. |
834
- | `hook-precommit` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
852
+ | `hook-precommit [--base REF]` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. `--base REF` compares the index with the merge base of REF and HEAD, the form a CI checkout runs. |
835
853
  | `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. |
836
854
  | `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. |
837
855
  | `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. |
@@ -855,7 +873,7 @@ The coverage summary counts all four as `measured` / `untested` / `no_lane` / `c
855
873
  | Remedy | Condition | Action |
856
874
  |---|---|---|
857
875
  | `decompose` | `ccn > ceiling` | Split it. No amount of coverage clears this. |
858
- | `split-lines` | `ccn <= ceiling`, `crap > ceiling`, and another function shares its source lines | Put each definition on its own lines, then measure again. Coverage cannot tell functions on one line apart, so the score stays at uncovered whatever the tests do. |
876
+ | `split-lines` | `ccn <= ceiling`, `crap > ceiling`, and another function shares its source lines, or a Python def's body starts on the line its signature ends | Put each definition on its own lines, then measure again. Coverage cannot tell functions on one line apart, so the score stays at uncovered whatever the tests do. A one-line Python def shares its line with its `def` statement, which runs at import, so coverage.py cannot show a call. So does a body on the last line of a signature that spans several lines, or one that goes on from the colon's line inside brackets or after a backslash: move the body to its own line after the signature. |
859
877
  | `add-tests` | `ccn <= ceiling` and `crap > ceiling` | Cover the branches. |
860
878
  | `ok` | `crap <= ceiling` | Nothing. |
861
879
 
@@ -946,6 +964,11 @@ bypasses the rule, and the run history records which run the verdict used. Nothi
946
964
  touches a repo that has never run `verify`: with no failure to protect, `coverage` alone
947
965
  always advances the baseline.
948
966
 
967
+ `ratchet seed --baseline ID` and `ratchet prune --baseline ID` take the same name as
968
+ `verify --baseline ID`, admitted by the same rule. When a failed verify pins seed to a run
969
+ it cannot read or sign, that is the way out
970
+ ([docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md#naming-the-run-to-seed-from)).
971
+
949
972
  **When the id you pass cannot serve.** A `--baseline ID` naming a real run that is not a
950
973
  candidate says which run it is, why, and which ones can:
951
974
 
@@ -967,7 +990,7 @@ crapkit: run 3 is an inventory run (no coverage was measured) and cannot serve a
967
990
  | 6 | Gate violation. A function the diff touched is over its ceiling and past any ratchet mark it carries: an edit that leaves a marked function at or under its mark is the debt the repo signed for and is exempt. Also `rescore --gate`, which applies the same rule, and `hook-precommit`, which exempts on the mark's existence instead. |
968
991
  | 7 | Ratchet regression the diff never touched. A marked function scores worse than its recorded high-water mark; a touched one past its mark reports 6. |
969
992
  | 8 | New test failures against the baseline run. Failures the baseline already had do not count. |
970
- | 9 | Diff-coverage ceiling breached: `diff_uncovered_max` is set and more changed lines than that never ran. |
993
+ | 9 | Diff-coverage ceiling breached: `diff_uncovered_max` is set and more changed lines than that never ran. A changed file no lane artifact mentions counts every line of its functions. |
971
994
 
972
995
  ### Exit 1 means one of three things
973
996
 
@@ -1089,6 +1112,7 @@ makes it step 4 of the burn-down loop. Every key is in
1089
1112
 
1090
1113
  ```
1091
1114
  $ crapkit doctor
1115
+ resources: up to 8 analysis worker(s) per pool, 8 shared slot(s); lane log limit 16777216 bytes per file
1092
1116
  ok config keys all recognized
1093
1117
  ok scope 'calc': 1 file
1094
1118
  ok every tracked source file belongs to a scope
@@ -1128,7 +1152,7 @@ not empty when the burn-down does. `next-item` is the other view of that run: it
1128
1152
 
1129
1153
  ```
1130
1154
  $ crapkit next-item
1131
- {"commit": "fae4db93108b4841a00959f9117430679e7250ca", "empty": false, "item": {"authors": 1, "ccn": 14, "ccn_std": 14, "cognitive": 13, "commits": 1, "cov": 0.5, "crap": 38.5, "end": 28, "est_splits": 3, "est_uncovered_paths": 7, "flag": "measured", "function": "classify( score , attempts , late , bonus )", "handle": "classify", "nesting": 3, "nloc": 22, "path": "calc/grade.py", "remedy": "decompose", "scope": "calc", "start": 7, "target": 6, "uncovered_lines": [9, 11, 15, 17, 19, 24, 25, 26, 27, 28]}, "run_id": 1, "schema": 1, "skipped_no_lane": 0, "stale": false}
1155
+ {"commit": "fae4db93108b4841a00959f9117430679e7250ca", "empty": false, "item": {"authors": 1, "ccn": 14, "ccn_std": 14, "cognitive": 13, "commits": 1, "cov": 0.5, "crap": 38.5, "end": 28, "est_splits": 3, "est_uncovered_paths": 7, "flag": "measured", "function": "classify( score , attempts , late , bonus )", "handle": "classify", "nesting": 3, "nloc": 22, "occurrence": 1, "path": "calc/grade.py", "remedy": "decompose", "scope": "calc", "start": 7, "target": 6, "uncovered_lines": [9, 11, 15, 17, 19, 24, 25, 26, 27, 28]}, "run_id": 1, "schema": 1, "skipped_no_lane": 0, "stale": false}
1132
1156
  ```
1133
1157
 
1134
1158
  `remedy: "decompose"`, `est_splits: 3` (this needs roughly three pieces to fit under 6),
@@ -1156,7 +1180,7 @@ table of cases pushed into parametrized tests. Commit the fix, then:
1156
1180
 
1157
1181
  ```
1158
1182
  $ crapkit verify
1159
- verify OK @ 8d10c13303d vs baseline fae4db93108 (5 changed files)
1183
+ verify OK @ 8d10c13303d vs baseline fae4db93108 (5 changed files) ratchet: 1 dropped, 0 tightened -> git add crapkit-ratchet.tsv
1160
1184
 
1161
1185
  $ crapkit coverage
1162
1186
  run 3 @ 8d10c13303d: 5 functions scored: 5 measured, 0 over ceiling 6, CRAP load 19.0, grade A+
@@ -1200,6 +1224,12 @@ an istanbul `coverage-final.json` works; see [docs/lanes.md](https://github.com/
1200
1224
  [one directory down](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#running-from-a-subdirectory), and a
1201
1225
  [crapkit root below the repo top](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md#a-crapkit-root-below-the-repo-top).
1202
1226
 
1227
+ `init` writes the vitest line under `[crapkit.scoped_tests]` commented out, because it
1228
+ cannot see which vitest config a file-scoped run needs. Until you uncomment that line or
1229
+ write your own, `crapkit doctor` WARNs `scope 'src' has a lane but no
1230
+ [crapkit.scoped_tests] template`. The WARN leaves doctor's exit at 0; it means `crapkit
1231
+ test-scoped` exits 3 on files under `src/`.
1232
+
1203
1233
  ### 2. Install a coverage provider
1204
1234
 
1205
1235
  **This is the step that stops most TypeScript users.** vitest ships no coverage provider
@@ -1300,9 +1330,10 @@ rescore vs run 1 @ 8bfbe613fcd (coverage STALE, complexity fresh)
1300
1330
  4 0% 20.0 add-tests src/grade.ts:8 penalty ( attempts , late )
1301
1331
  4 45% 6.7 add-tests src/grade.ts:48 classify ( row Row )
1302
1332
  4 75% 4.2 ok src/grade.ts:59 average ( scores Array )
1333
+ gate: 4 changed function(s) judged, 0 over ceiling 6
1303
1334
  ```
1304
1335
 
1305
- Exit 0: every piece is at or under 6. The `crap` column is loud because its coverage half
1336
+ Exit 0 and the `gate:` line: every changed piece is at or under 6. The `crap` column is loud because its coverage half
1306
1337
  is still run 1's, from before three of those functions existed, and `add-tests` is the
1307
1338
  literal instruction for step 6.
1308
1339
 
@@ -1342,7 +1373,7 @@ verify FAILED @ 0296156ff21 vs baseline 0e646697946 (1 changed files)
1342
1373
 
1343
1374
  ```
1344
1375
  $ crapkit verify
1345
- verify OK @ 2af3433d979 vs baseline 8bfbe613fcd (3 changed files)
1376
+ verify OK @ 2af3433d979 vs baseline 8bfbe613fcd (3 changed files) ratchet: 1 dropped, 0 tightened -> git add crapkit-ratchet.tsv
1346
1377
 
1347
1378
  $ crapkit coverage
1348
1379
  run 3 @ 2af3433d979: 5 functions scored: 5 measured, 0 over ceiling 6, CRAP load 22.0, grade A+
@@ -1370,7 +1401,7 @@ with no debt.
1370
1401
  | [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. |
1371
1402
  | [docs/configuration.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md) | Every `crapkit.toml` key: type, default, and what it does. |
1372
1403
  | [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. |
1373
- | [docs/resources.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/resources.md) | Worker budgets, command cleanup, log rotation, test evidence retention and safe cleanup. |
1404
+ | [docs/resources.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/resources.md) | Worker budgets, command cleanup, log rotation and safe cleanup. |
1374
1405
  | [docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
1375
1406
  | [docs/upgrading.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/upgrading.md) | Existing installations: analysis and key versions, saved state, plugin alignment and Windows upgrades. |
1376
1407
  | [docs/portable-records.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/portable-records.md) | Lossless exports, portable baselines and ratchets, including filenames with delimiters. |