crapkit 0.6.0__tar.gz → 0.7.2__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.6.0/src/crapkit.egg-info → crapkit-0.7.2}/PKG-INFO +130 -90
- {crapkit-0.6.0 → crapkit-0.7.2}/README.md +126 -88
- {crapkit-0.6.0 → crapkit-0.7.2}/pyproject.toml +6 -6
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/__init__.py +1 -1
- crapkit-0.7.2/src/crapkit/_analysis_pool.py +190 -0
- crapkit-0.7.2/src/crapkit/_mcp_stdio.py +208 -0
- crapkit-0.7.2/src/crapkit/_process_family.py +83 -0
- crapkit-0.7.2/src/crapkit/_process_owner.py +129 -0
- crapkit-0.7.2/src/crapkit/_windows_job.py +98 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/analyze.py +228 -63
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cache.py +14 -5
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/churn.py +5 -43
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/churn_cache.py +10 -26
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/churn_log.py +13 -15
- crapkit-0.7.2/src/crapkit/cli/__init__.py +10 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/_shared.py +47 -6
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/admin.py +68 -63
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/analyses.py +1 -1
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/claude_hook.py +50 -34
- crapkit-0.7.2/src/crapkit/cli/maintenance.py +39 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/parser.py +9 -1
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/queue.py +111 -52
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/ratchet_cmds.py +46 -9
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/reports.py +21 -30
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/scoring.py +53 -39
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/cli/verifying.py +82 -98
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/config.py +86 -120
- crapkit-0.7.2/src/crapkit/config_contract.py +317 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/coupling.py +6 -24
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/coupling_cache.py +3 -2
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/coverage_istanbul.py +62 -87
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/coverage_py.py +14 -59
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/covstream.py +182 -36
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/diffparse.py +5 -6
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/digest.py +9 -2
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/doctor.py +6 -17
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/dup.py +80 -27
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/gitio.py +178 -49
- crapkit-0.7.2/src/crapkit/gitpaths.py +39 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/hook.py +7 -4
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/junitparse.py +36 -1
- crapkit-0.7.2/src/crapkit/keys.py +171 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/lanes.py +186 -77
- crapkit-0.7.2/src/crapkit/lizardtypescript.py +142 -0
- crapkit-0.7.2/src/crapkit/locks.py +36 -0
- crapkit-0.7.2/src/crapkit/logs.py +130 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/mcp_server.py +142 -336
- crapkit-0.7.2/src/crapkit/merge.py +35 -0
- crapkit-0.7.2/src/crapkit/mutate.py +298 -0
- crapkit-0.7.2/src/crapkit/mutate_pool.py +499 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/override.py +45 -14
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/packet.py +79 -45
- crapkit-0.7.2/src/crapkit/procs.py +533 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/ratchet.py +114 -14
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/ratchet_report.py +34 -22
- crapkit-0.7.2/src/crapkit/ratchetfile.py +84 -0
- crapkit-0.7.2/src/crapkit/records.py +55 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/report.py +19 -9
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/repotext.py +5 -1
- crapkit-0.7.2/src/crapkit/resources.py +141 -0
- crapkit-0.7.2/src/crapkit/retention.py +186 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/sarif.py +10 -3
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/scaffold.py +4 -79
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/score.py +101 -29
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/snapshot.py +5 -3
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/store.py +329 -134
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/uncovered.py +30 -39
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/universe.py +25 -5
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/verify.py +25 -1
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/worklist.py +37 -19
- {crapkit-0.6.0 → crapkit-0.7.2/src/crapkit.egg-info}/PKG-INFO +130 -90
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit.egg-info/SOURCES.txt +15 -1
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit.egg-info/requires.txt +3 -1
- crapkit-0.6.0/src/crapkit/cli/__init__.py +0 -412
- crapkit-0.6.0/src/crapkit/discover.py +0 -384
- crapkit-0.6.0/src/crapkit/keys.py +0 -86
- crapkit-0.6.0/src/crapkit/merge.py +0 -77
- crapkit-0.6.0/src/crapkit/mutate.py +0 -183
- crapkit-0.6.0/src/crapkit/mutate_pool.py +0 -362
- crapkit-0.6.0/src/crapkit/procs.py +0 -137
- {crapkit-0.6.0 → crapkit-0.7.2}/LICENSE +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/setup.cfg +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/__main__.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/_pygdefer.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/errors.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/invocation.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/lizardcognitive.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/lizardpowershell.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/lizardrust.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/lizardshell.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/rootfind.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/sarifio.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit/watch.py +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit.egg-info/dependency_links.txt +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/src/crapkit.egg-info/entry_points.txt +0 -0
- {crapkit-0.6.0 → crapkit-0.7.2}/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.
|
|
3
|
+
Version: 0.7.2
|
|
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
|
|
@@ -30,8 +30,10 @@ Requires-Dist: lizard>=1.24.0
|
|
|
30
30
|
Provides-Extra: dev
|
|
31
31
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
32
32
|
Requires-Dist: pytest-cov>=5; extra == "dev"
|
|
33
|
-
Requires-Dist: pytest-xdist>=3; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-xdist>=3.7; extra == "dev"
|
|
34
34
|
Requires-Dist: coverage>=7.10.6; extra == "dev"
|
|
35
|
+
Requires-Dist: PyYAML>=6; extra == "dev"
|
|
36
|
+
Requires-Dist: Pillow; extra == "dev"
|
|
35
37
|
Provides-Extra: py
|
|
36
38
|
Requires-Dist: pytest-cov>=5; extra == "py"
|
|
37
39
|
Requires-Dist: coverage>=7.10.6; extra == "py"
|
|
@@ -54,8 +56,8 @@ worst ones by how often the file changes, and blocks commits that add more. It r
|
|
|
54
56
|
Python, TypeScript, TSX, JavaScript, Swift, Go, Rust, shell, PowerShell, C and C++,
|
|
55
57
|
Objective-C, Vue, Java and Zig through [lizard](https://github.com/terryyin/lizard), and
|
|
56
58
|
joins per-function branch coverage from the istanbul or coverage.py artifact your own test
|
|
57
|
-
command already writes.
|
|
58
|
-
|
|
59
|
+
command already writes. JSON commands use sorted keys and a versioned schema for
|
|
60
|
+
scripts, coding agents and the optional MCP server.
|
|
59
61
|
|
|
60
62
|
```
|
|
61
63
|
CRAP = ccn^2 * (1 - cov)^3 + ccn
|
|
@@ -86,6 +88,14 @@ a wall of red. Next to crap4py, radon, xenon, wily and SonarQube:
|
|
|
86
88
|
crapkit scores **git-tracked files only**. Source you have not `git add`ed is invisible to
|
|
87
89
|
it.
|
|
88
90
|
|
|
91
|
+
| Start with | When |
|
|
92
|
+
|---|---|
|
|
93
|
+
| [Install](#install) and [the 60-second start](#the-60-second-start) | You want the first score in an existing Git repository. |
|
|
94
|
+
| [Python](#quickstart-python) or [TypeScript](#quickstart-typescript) quickstart | You want a worked example from setup through a passing verify. |
|
|
95
|
+
| [Adoption](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/adoption.md) | You need to choose scopes, wire tests or introduce a ratchet to existing debt. |
|
|
96
|
+
| [Upgrading](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/upgrading.md) | You already have saved runs, ratchet marks or an installed plugin. |
|
|
97
|
+
| [Subcommands](#subcommands) and [JSON/MCP reference](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md) | You are scripting commands or connecting a coding agent. |
|
|
98
|
+
|
|
89
99
|
---
|
|
90
100
|
|
|
91
101
|
## The 60-second start
|
|
@@ -93,17 +103,20 @@ it.
|
|
|
93
103
|
```
|
|
94
104
|
pip install crapkit
|
|
95
105
|
cd your-repo
|
|
96
|
-
crapkit init # crapkit.toml and
|
|
97
|
-
|
|
98
|
-
# pytest.ini or setup.cfg for pytest; a test script or vitest/jest in
|
|
99
|
-
# package.json for the JS side
|
|
100
|
-
# without one: the lane comes commented out, init says to declare one,
|
|
101
|
-
# and docs/lanes.md is how to fill it in
|
|
106
|
+
crapkit init # write crapkit.toml and ignore measurement output
|
|
107
|
+
crapkit doctor # check scopes, test commands and coverage dependencies
|
|
102
108
|
crapkit coverage # runs the lane, joins coverage, stores a scored run
|
|
103
109
|
crapkit worklist # the ranked risk map
|
|
104
|
-
crapkit ratchet seed
|
|
110
|
+
crapkit ratchet seed
|
|
111
|
+
git add crapkit.toml crapkit-ratchet.tsv .gitignore
|
|
105
112
|
```
|
|
106
113
|
|
|
114
|
+
`init` detects pytest, Vitest and Jest from the repository's own files. Review the
|
|
115
|
+
generated config before running its commands. When detection leaves a commented
|
|
116
|
+
lane, fill it in using the [lane recipes](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md).
|
|
117
|
+
Commit the adoption files, then run `crapkit verify` to establish a passing verdict.
|
|
118
|
+
Install the [commit gate](#the-gate) when the config and ratchet are ready.
|
|
119
|
+
|
|
107
120
|
`coverage` scores, `worklist` ranks:
|
|
108
121
|
|
|
109
122
|
```
|
|
@@ -169,19 +182,24 @@ pip install git+https://github.com/JeanFrancoisGagne/crapkit.git
|
|
|
169
182
|
pip install .
|
|
170
183
|
```
|
|
171
184
|
|
|
172
|
-
|
|
173
|
-
|
|
185
|
+
Requires Python 3.11 or newer and Git on PATH. The CLI has one runtime dependency,
|
|
186
|
+
`lizard>=1.24.0`; a package mirror needs both distributions. Install into the environment
|
|
187
|
+
you intend to use, then check `crapkit --version`. The `pip install -e ".[dev]"` under
|
|
174
188
|
[Development](#development) is a different thing: it adds the test extra, for people
|
|
175
189
|
changing crapkit.
|
|
176
190
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
191
|
+
Python projects can install `pip install "crapkit[py]"` in their test environment to
|
|
192
|
+
include pytest-cov and subprocess-capable coverage.py. A separate tool installation
|
|
193
|
+
still needs the coverage plugin in the environment that runs the suite.
|
|
194
|
+
|
|
195
|
+
Analysis and scoring run locally and send no telemetry. Configured lane, mutation
|
|
196
|
+
and alert commands run with your permissions and can contact services or change
|
|
197
|
+
files. Review those commands before running Crapkit in a repository you do not trust
|
|
180
198
|
([SECURITY.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/SECURITY.md)).
|
|
181
199
|
|
|
182
200
|
```
|
|
183
201
|
$ crapkit --version
|
|
184
|
-
crapkit 0.
|
|
202
|
+
crapkit 0.7.2
|
|
185
203
|
```
|
|
186
204
|
|
|
187
205
|
`python -m crapkit` works identically to the console script and is what to use from a
|
|
@@ -190,64 +208,34 @@ at or above the current directory, so a monorepo workspace finds the root's), an
|
|
|
190
208
|
you never have to `cd` into the repo you are scoring; [Subcommands](#subcommands) shows
|
|
191
209
|
where the flag goes.
|
|
192
210
|
|
|
193
|
-
## Upgrading
|
|
211
|
+
## Upgrading
|
|
194
212
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
213
|
+
Keep the CLI and plugin versions aligned, measure fresh coverage after upgrading,
|
|
214
|
+
and review any ratchet identity refusal before reseeding. The current reader is
|
|
215
|
+
analysis version 10; older JavaScript and TypeScript callback marks can require a
|
|
216
|
+
reviewed mapping. Follow the [upgrade guide](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/upgrading.md)
|
|
217
|
+
for saved state, portable records and Windows launcher locks.
|
|
218
|
+
|
|
219
|
+
### Upgrading from 0.4.4
|
|
220
|
+
|
|
221
|
+
This historical example describes the 0.4.4 to 0.4.5 transition, from analysis
|
|
222
|
+
version 7 to 8. It is retained to explain older refusal messages:
|
|
198
223
|
|
|
199
224
|
```
|
|
200
225
|
$ crapkit verify
|
|
201
226
|
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`
|
|
202
227
|
```
|
|
203
228
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
Five more things change under you. Three of them need nothing from you:
|
|
208
|
-
|
|
209
|
-
- **New cache files.** `.crapkit/coupling-cache-v1.json` joins `churn-cache-v2.json` and
|
|
210
|
-
`churn-log-v2.z`. A warm 0.4.4 churn cache is adopted once and its file removed, and
|
|
211
|
-
`.crapkit/` is already gitignored, so nothing new reaches your index.
|
|
212
|
-
- **`trend` and `report` write.** Both read a per-run rollup table, filled once per run and
|
|
213
|
-
pruned with its run, instead of rescanning every scored row. A read-only `.crapkit/`
|
|
214
|
-
costs the speedup, never the command.
|
|
215
|
-
- **Nested scopes may move files.** One predicate decides scope ownership now, and the
|
|
216
|
-
deepest declared path wins, so a repo whose `[[scope]]` paths nest inside each other can
|
|
217
|
-
see files change scope, rollup and ceiling on the next scan. Scopes that do not nest see
|
|
218
|
-
no change.
|
|
219
|
-
|
|
220
|
-
The other two put something in front of you:
|
|
221
|
-
|
|
222
|
-
- **`mutate` keeps a worktree pool.** With `mutation_workers > 1` the worker worktrees now
|
|
223
|
-
live under `.crapkit/mutate-pool/` between runs and are re-prepared each run, which is
|
|
224
|
-
the setup cost gone (30.6 s to build four on a 31,459-file repo, 0.46 s to re-prepare
|
|
225
|
-
them). The pool is not size-bounded and nothing sweeps it: `crapkit mutate --drop-pool`
|
|
226
|
-
removes it and exits. Single-worker runs are untouched.
|
|
227
|
-
- **`doctor` WARNs on a lane with no `results_artifact`.** Every `coveragepy` or `istanbul`
|
|
228
|
-
lane written before 0.4.5 gets one, with the two lines that fix it. Coverage is
|
|
229
|
-
unaffected. What the lane cannot feed without a results file is the crashed-worker check
|
|
230
|
-
and the no-new-failures check (exit 8).
|
|
229
|
+
That transition changed cognitive complexity, not `ccn` or the CRAP formula.
|
|
230
|
+
Later reader changes also affect function identity. Use the current upgrade guide
|
|
231
|
+
when moving from any older release to today's reader.
|
|
231
232
|
|
|
232
233
|
### The exe lock on Windows
|
|
233
234
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
that copy fails, so `crapkit --version` already reports the new version and only the
|
|
239
|
-
launcher is stale. Quit the agent session and rerun the upgrade, or rename the locked exe
|
|
240
|
-
aside (Windows allows renaming a running one) and copy the new one in. Two lines in
|
|
241
|
-
cmd.exe, where both `%` variables expand:
|
|
242
|
-
|
|
243
|
-
```bat
|
|
244
|
-
move %USERPROFILE%\.local\bin\crapkit.exe %USERPROFILE%\.local\bin\crapkit.exe.old
|
|
245
|
-
copy %APPDATA%\uv\tools\crapkit\Scripts\crapkit.exe %USERPROFILE%\.local\bin\crapkit.exe
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
Git Bash has no `move` and passes `%APPDATA%` through as literal text, so that block
|
|
249
|
-
fails there on its first line. Its form is `mv` and `cp` over `"$USERPROFILE"` and
|
|
250
|
-
`"$APPDATA"`, which Git Bash sets to the same two directories.
|
|
235
|
+
An active MCP server can hold `crapkit.exe` open and make an upgrade fail with
|
|
236
|
+
Windows error 32. Stop that server or its agent session, rerun the upgrade with
|
|
237
|
+
the same installer, then restart the client. See the
|
|
238
|
+
[Windows upgrade procedure](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/upgrading.md#windows-launcher-locks).
|
|
251
239
|
|
|
252
240
|
## The Claude Code plugin
|
|
253
241
|
|
|
@@ -257,14 +245,23 @@ claude plugin install crapkit@crapkit
|
|
|
257
245
|
```
|
|
258
246
|
|
|
259
247
|
Two commands, installed once per user, and every repo on the machine gets it. The plugin
|
|
260
|
-
ships three skills, the read-
|
|
248
|
+
ships three skills, the read-side MCP server, and one advisory PostToolUse hook that names
|
|
261
249
|
any function an edit pushed over its ceiling. Claude reaches two of the skills by itself,
|
|
262
250
|
`crapkit` and `crapkit-recover`; the third you type, as `/crapkit:crapkit-onboard`, because
|
|
263
251
|
wiring a repo up happens once and its description has no business in every turn's window.
|
|
264
252
|
It adds no files to your repo, and it needs the crapkit CLI on PATH.
|
|
265
253
|
|
|
266
|
-
A repo with no `crapkit.toml` costs a silent sub-50 ms no-op per edit.
|
|
267
|
-
|
|
254
|
+
A repo with no `crapkit.toml` costs a silent sub-50 ms no-op per edit. After upgrading
|
|
255
|
+
the CLI, refresh the marketplace before updating the installed plugin:
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
claude plugin marketplace update crapkit
|
|
259
|
+
claude plugin update crapkit@crapkit --scope user
|
|
260
|
+
crapkit doctor --plugin-root
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Restart existing Claude Code sessions to apply the plugin update. The check above
|
|
264
|
+
compares installed files with the CLI on PATH; it does not reload a running session.
|
|
268
265
|
|
|
269
266
|
The hook registers on `Edit|Write`, which is every write that names a file. An agent that
|
|
270
267
|
writes its source through a shell heredoc names none, so a `Bash` event is judged off the
|
|
@@ -293,6 +290,31 @@ on crapkit's own checkout, and more on a bigger tree. What comes back is the dir
|
|
|
293
290
|
untracked `*.py` files written in the last 12 seconds, 25 at most, each judged the way an
|
|
294
291
|
edit is. Python only, so a TypeScript or Go repo pays the two spawns and hears nothing.
|
|
295
292
|
|
|
293
|
+
### Codex
|
|
294
|
+
|
|
295
|
+
Codex can install the same marketplace's plugin through its own manager:
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
codex plugin marketplace add https://github.com/JeanFrancoisGagne/crapkit.git
|
|
299
|
+
codex plugin add crapkit@crapkit
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Use the three skills and MCP server in Codex. The advisory hook instructions above
|
|
303
|
+
configure Claude Code's PostToolUse event.
|
|
304
|
+
To refresh an existing Codex installation:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
codex plugin marketplace upgrade crapkit
|
|
308
|
+
codex plugin add crapkit@crapkit
|
|
309
|
+
codex plugin list --marketplace crapkit --json
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Check the installed Codex plugin with an explicit `crapkit doctor --plugin-root PATH`.
|
|
313
|
+
See [plugin upgrades](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/upgrading.md#plugin-and-mcp-clients)
|
|
314
|
+
for choosing that path and starting a fresh MCP session. A runtime with a skills
|
|
315
|
+
directory but no compatible marketplace can copy `plugin/skills/*` instead; other
|
|
316
|
+
MCP clients use the [stdio setup](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md#mcp-server).
|
|
317
|
+
|
|
296
318
|
## Languages
|
|
297
319
|
|
|
298
320
|
14 languages, two coverage parsers. Coverage joins where a parser exists; everything else
|
|
@@ -331,10 +353,20 @@ lizard #494), so crapkit counts each non-wildcard arm like a C `case` and retire
|
|
|
331
353
|
override the day upstream fixes it. The cognitive column charges that same block once,
|
|
332
354
|
the way Sonar charges a `switch`.
|
|
333
355
|
|
|
356
|
+
Expression arrows in arrays and argument lists are measured separately. In TypeScript,
|
|
357
|
+
wrap an arrow body in parentheses when it contains `<` before a comma, such as
|
|
358
|
+
`x => (pair<T,U>(x))` or `x => (x < 0)`. Without that delimiter, analysis refuses
|
|
359
|
+
the file because this reader cannot distinguish type arguments from an expression
|
|
360
|
+
separator. Generic arrow parameter declarations remain supported.
|
|
361
|
+
|
|
362
|
+
Functions on the same line have separate occurrence identifiers. Existing ratchet
|
|
363
|
+
marks with ambiguous old identities require a reviewed mapping; see
|
|
364
|
+
[same-line function identity](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md#same-line-function-identity).
|
|
365
|
+
|
|
334
366
|
## The gate
|
|
335
367
|
|
|
336
|
-
|
|
337
|
-
|
|
368
|
+
Use the advisory while editing, the gate when committing, and `verify` for the
|
|
369
|
+
full verdict. The preview and hooks differ in what their available evidence can prove:
|
|
338
370
|
|
|
339
371
|
| Surface | Fires | Power |
|
|
340
372
|
|---|---|---|
|
|
@@ -353,12 +385,10 @@ on stderr (`staged function(s) carry a ratchet mark and were not gated`), and sa
|
|
|
353
385
|
about a staged file no `[[scope]]` claims, so a new top-level directory cannot go ungated
|
|
354
386
|
in silence.
|
|
355
387
|
|
|
356
|
-
**The
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
one. Before that a nested root matched staged paths against no scope, and a function at
|
|
361
|
-
twice the ceiling committed with a warning.
|
|
388
|
+
**The Crapkit root can sit below the Git top.** A config in `packages/api` gates
|
|
389
|
+
that package's staged files as project-relative paths such as `app/m.py`.
|
|
390
|
+
[Path and root rules](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md#file-paths-and-root-discovery)
|
|
391
|
+
also cover absolute arguments, literal filenames and Git diff settings.
|
|
362
392
|
|
|
363
393
|
Git runs hooks outside your shell's activated venv. Bare `python` must resolve to an
|
|
364
394
|
interpreter that has crapkit installed, or spell it out
|
|
@@ -425,7 +455,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
|
|
|
425
455
|
repos:
|
|
426
456
|
- repo: https://github.com/JeanFrancoisGagne/crapkit
|
|
427
457
|
# crapkit's release step rewrites this line to the tag it just cut
|
|
428
|
-
rev: v0.
|
|
458
|
+
rev: v0.7.2
|
|
429
459
|
hooks:
|
|
430
460
|
- id: crapkit-gate
|
|
431
461
|
```
|
|
@@ -533,7 +563,7 @@ to a workflow, and every input has a default:
|
|
|
533
563
|
- uses: actions/checkout@v4
|
|
534
564
|
with:
|
|
535
565
|
fetch-depth: 0
|
|
536
|
-
- uses: JeanFrancoisGagne/crapkit@v0.
|
|
566
|
+
- uses: JeanFrancoisGagne/crapkit@v0.7.2
|
|
537
567
|
```
|
|
538
568
|
|
|
539
569
|
The whole job those four lines sit in:
|
|
@@ -553,7 +583,7 @@ jobs:
|
|
|
553
583
|
with:
|
|
554
584
|
python-version: "3.12" # the interpreter the install below lands in
|
|
555
585
|
- run: pip install -e ".[dev]" # whatever your lanes need to run
|
|
556
|
-
- uses: JeanFrancoisGagne/crapkit@v0.
|
|
586
|
+
- uses: JeanFrancoisGagne/crapkit@v0.7.2
|
|
557
587
|
with:
|
|
558
588
|
gate: "false"
|
|
559
589
|
```
|
|
@@ -724,6 +754,10 @@ on PATH fails that step. Both leave the rendered text in the job log.
|
|
|
724
754
|
|
|
725
755
|
## Subcommands
|
|
726
756
|
|
|
757
|
+
`crapkit clean --dry-run --json` previews policy-based cleanup. See
|
|
758
|
+
[resource policies](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/resources.md)
|
|
759
|
+
for shared analysis workers, process lifetime, bounded logs and retained evidence.
|
|
760
|
+
|
|
727
761
|
Every subcommand takes `--repo PATH`, and the flag goes **after** the subcommand. Without
|
|
728
762
|
it the root is the nearest `crapkit.toml` at or above the current directory
|
|
729
763
|
([ADR 0002](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/adr/0002-configuration-is-found-upward-nearest-wins.md)): from a
|
|
@@ -750,6 +784,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
|
|
|
750
784
|
|
|
751
785
|
| Command | What it does |
|
|
752
786
|
|---|---|
|
|
787
|
+
| `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. |
|
|
753
788
|
| `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. |
|
|
754
789
|
| `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). |
|
|
755
790
|
| `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. |
|
|
@@ -767,15 +802,15 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
|
|
|
767
802
|
| `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). |
|
|
768
803
|
| `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. |
|
|
769
804
|
| `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. Every row carries the function's CRAP and coverage, and prints the `crapkit explain` call for the rest: dark lines, history, the mark. It reads the same per-run rollups `trend` does, and writes them on the same terms. |
|
|
770
|
-
| `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`.
|
|
805
|
+
| `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`. Ties have a stable order across hash seeds. A positive `--top` bounds retained candidates and output; dense inputs still require pair comparisons. A function and its nested closure never pair. |
|
|
771
806
|
| `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. |
|
|
772
|
-
| `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.
|
|
807
|
+
| `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. |
|
|
773
808
|
| `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. |
|
|
774
809
|
| `hook-precommit` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
|
|
775
810
|
| `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. |
|
|
776
811
|
| `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. |
|
|
777
812
|
| `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. |
|
|
778
|
-
| `mcp` | A
|
|
813
|
+
| `mcp` | A stdio MCP server with no extra dependency, exposing twelve read-side tools named `verb_noun`, each with a title and output schema. Tools call the CLI to inspect current scores, source and edited-file gates. They take no claims and run no verification; calls can write caches or store metadata. See [the MCP contract and setup](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md#mcp-server). |
|
|
779
814
|
|
|
780
815
|
## Reading the output
|
|
781
816
|
|
|
@@ -1147,7 +1182,7 @@ exit 5:
|
|
|
1147
1182
|
|
|
1148
1183
|
```
|
|
1149
1184
|
$ crapkit coverage
|
|
1150
|
-
crapkit: lane 'js' FAILED: lane 'js' produced no artifact at .crapkit/cov/js/coverage-final.json (command exit 1);
|
|
1185
|
+
crapkit: lane 'js' FAILED: lane 'js' produced no artifact at .crapkit/cov/js/coverage-final.json (command exit 1); lane 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
|
|
1151
1186
|
|
|
1152
1187
|
MISSING DEPENDENCY Cannot find dependency '@vitest/coverage-v8'
|
|
1153
1188
|
|
|
@@ -1309,11 +1344,14 @@ with no debt.
|
|
|
1309
1344
|
| [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. |
|
|
1310
1345
|
| [docs/configuration.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md) | Every `crapkit.toml` key: type, default, and what it does. |
|
|
1311
1346
|
| [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. |
|
|
1347
|
+
| [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. |
|
|
1312
1348
|
| [docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
|
|
1349
|
+
| [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. |
|
|
1350
|
+
| [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. |
|
|
1313
1351
|
| [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. |
|
|
1314
1352
|
| [docs/comparison.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/comparison.md) | Where crapkit sits next to radon, xenon, wily, coverage.py and SonarQube, and how they run together. |
|
|
1315
1353
|
| [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. |
|
|
1316
|
-
| [plugin/](https://github.com/JeanFrancoisGagne/crapkit/tree/main/plugin) |
|
|
1354
|
+
| [plugin/](https://github.com/JeanFrancoisGagne/crapkit/tree/main/plugin) | Three skills and the MCP server for Claude Code and Codex, with advisory PostToolUse hook instructions for Claude Code. |
|
|
1317
1355
|
|
|
1318
1356
|
[crapkit.schema.json](https://github.com/JeanFrancoisGagne/crapkit/blob/main/crapkit.schema.json) is the authority on the config file shape.
|
|
1319
1357
|
|
|
@@ -1321,15 +1359,17 @@ with no debt.
|
|
|
1321
1359
|
|
|
1322
1360
|
```
|
|
1323
1361
|
pip install -e ".[dev]"
|
|
1324
|
-
pip install pytest-xdist
|
|
1325
1362
|
git config core.hooksPath git-hooks
|
|
1326
|
-
python
|
|
1363
|
+
python tools/testing/run.py
|
|
1327
1364
|
```
|
|
1328
1365
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1366
|
+
The dev extra includes pytest, pytest-cov, pytest-xdist and coverage.py. The shared
|
|
1367
|
+
runner owns the unit and E2E schedule; use `--unit-workers 1` for serial unit
|
|
1368
|
+
reproduction or `--coverage` for combined branch coverage and JUnit. The `git config`
|
|
1369
|
+
line arms the complexity gate. See
|
|
1370
|
+
[CONTRIBUTING.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/CONTRIBUTING.md)
|
|
1371
|
+
for development and [the verified implementation report](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/architecture/2026-09-07-implementation/REPORT.md)
|
|
1372
|
+
for complete Windows source and Linux wheel results, focused benchmarks and their limits.
|
|
1333
1373
|
|
|
1334
1374
|
## License
|
|
1335
1375
|
|