crapkit 0.7.5__tar.gz → 0.7.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {crapkit-0.7.5/src/crapkit.egg-info → crapkit-0.7.6}/PKG-INFO +46 -14
- {crapkit-0.7.5 → crapkit-0.7.6}/README.md +44 -12
- {crapkit-0.7.5 → crapkit-0.7.6}/pyproject.toml +2 -2
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/__init__.py +1 -1
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/scoring.py +2 -2
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/mcp_server.py +20 -30
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/score.py +35 -19
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/store.py +1 -1
- {crapkit-0.7.5 → crapkit-0.7.6/src/crapkit.egg-info}/PKG-INFO +46 -14
- {crapkit-0.7.5 → crapkit-0.7.6}/LICENSE +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/setup.cfg +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/__main__.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/_analysis_pool.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/_mcp_stdio.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/_process_family.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/_process_owner.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/_pygdefer.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/_windows_job.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/analyze.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cache.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/churn.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/churn_cache.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/churn_log.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/__init__.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/_shared.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/admin.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/analyses.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/claude_hook.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/maintenance.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/parser.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/queue.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/ratchet_cmds.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/reports.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/cli/verifying.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/config.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/config_contract.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/coupling.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/coupling_cache.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/coverage_istanbul.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/coverage_py.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/covstream.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/diffparse.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/digest.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/doctor.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/dup.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/errors.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/gitio.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/gitpaths.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/hook.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/invocation.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/junitparse.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/keys.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/lanes.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/lizardcognitive.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/lizardpowershell.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/lizardpython.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/lizardrust.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/lizardshell.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/lizardtypescript.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/locks.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/logs.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/merge.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/mutate.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/mutate_pool.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/override.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/packet.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/procs.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/ratchet.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/ratchet_report.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/ratchetfile.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/records.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/report.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/repotext.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/resources.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/retention.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/rootfind.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/sarif.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/sarifio.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/scaffold.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/snapshot.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/uncovered.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/universe.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/verify.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/watch.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit/worklist.py +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit.egg-info/SOURCES.txt +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit.egg-info/dependency_links.txt +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit.egg-info/entry_points.txt +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit.egg-info/requires.txt +0 -0
- {crapkit-0.7.5 → crapkit-0.7.6}/src/crapkit.egg-info/top_level.txt +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crapkit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.6
|
|
4
4
|
Summary: Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more.
|
|
5
5
|
Author: Jean-Francois Gagne
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/JeanFrancoisGagne/crapkit
|
|
8
|
-
Project-URL: Documentation, https://
|
|
8
|
+
Project-URL: Documentation, https://www.jfgagne.com/crapkit/handbook.html
|
|
9
9
|
Project-URL: Changelog, https://github.com/JeanFrancoisGagne/crapkit/blob/main/CHANGELOG.md
|
|
10
10
|
Project-URL: Issues, https://github.com/JeanFrancoisGagne/crapkit/issues
|
|
11
11
|
Project-URL: Source, https://github.com/JeanFrancoisGagne/crapkit
|
|
@@ -111,6 +111,9 @@ crapkit ratchet seed
|
|
|
111
111
|
git add crapkit.toml crapkit-ratchet.tsv .gitignore
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
Not a Python repo? `uvx crapkit init` runs the same commands and adds nothing to your
|
|
115
|
+
manifest: see [A repo that is not Python](#a-repo-that-is-not-python).
|
|
116
|
+
|
|
114
117
|
`init` detects pytest, Vitest and Jest from the repository's own files. Review the
|
|
115
118
|
generated config before running its commands. When detection leaves a commented
|
|
116
119
|
lane, fill it in using the [lane recipes](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md).
|
|
@@ -182,6 +185,28 @@ pip install git+https://github.com/JeanFrancoisGagne/crapkit.git
|
|
|
182
185
|
pip install .
|
|
183
186
|
```
|
|
184
187
|
|
|
188
|
+
### A repo that is not Python
|
|
189
|
+
|
|
190
|
+
crapkit is a command-line tool, never a dependency of the code it scores. A TypeScript,
|
|
191
|
+
Go or Rust repo adds nothing to its own manifest. With [uv](https://docs.astral.sh/uv/)
|
|
192
|
+
on the machine, `uvx` fetches crapkit into a cache of its own and runs it:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
$ uvx crapkit init
|
|
196
|
+
wrote crapkit.toml with 1 scope(s): src
|
|
197
|
+
detected 1 lane(s) from this repo's own files: js - next: run `crapkit coverage`
|
|
198
|
+
added to .gitignore: .crapkit/
|
|
199
|
+
|
|
200
|
+
$ uvx crapkit coverage
|
|
201
|
+
$ uvx crapkit worklist
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The lane still runs your own test runner, so Vitest or Jest and its coverage package come
|
|
205
|
+
from the repo's `node_modules` as they do today. `uv tool install crapkit` or
|
|
206
|
+
`pipx install crapkit` puts a `crapkit` command on PATH once, which is what the
|
|
207
|
+
[commit gate](#the-gate) and the Claude Code plugin call. uv brings its own Python when
|
|
208
|
+
the machine has none.
|
|
209
|
+
|
|
185
210
|
Requires Python 3.11 or newer and Git on PATH. The CLI has one runtime dependency,
|
|
186
211
|
`lizard>=1.24.0`; a package mirror needs both distributions. Install into the environment
|
|
187
212
|
you intend to use, then check `crapkit --version`. The `pip install -e ".[dev]"` under
|
|
@@ -199,7 +224,7 @@ files. Review those commands before running Crapkit in a repository you do not t
|
|
|
199
224
|
|
|
200
225
|
```
|
|
201
226
|
$ crapkit --version
|
|
202
|
-
crapkit 0.7.
|
|
227
|
+
crapkit 0.7.6
|
|
203
228
|
```
|
|
204
229
|
|
|
205
230
|
`python -m crapkit` works identically to the console script and is what to use from a
|
|
@@ -455,7 +480,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
|
|
|
455
480
|
repos:
|
|
456
481
|
- repo: https://github.com/JeanFrancoisGagne/crapkit
|
|
457
482
|
# crapkit's release step rewrites this line to the tag it just cut
|
|
458
|
-
rev: v0.7.
|
|
483
|
+
rev: v0.7.6
|
|
459
484
|
hooks:
|
|
460
485
|
- id: crapkit-gate
|
|
461
486
|
```
|
|
@@ -563,7 +588,7 @@ to a workflow, and every input has a default:
|
|
|
563
588
|
- uses: actions/checkout@v4
|
|
564
589
|
with:
|
|
565
590
|
fetch-depth: 0
|
|
566
|
-
- uses: JeanFrancoisGagne/crapkit@v0.7.
|
|
591
|
+
- uses: JeanFrancoisGagne/crapkit@v0.7.6
|
|
567
592
|
```
|
|
568
593
|
|
|
569
594
|
The whole job those four lines sit in:
|
|
@@ -583,7 +608,7 @@ jobs:
|
|
|
583
608
|
with:
|
|
584
609
|
python-version: "3.12" # the interpreter the install below lands in
|
|
585
610
|
- run: pip install -e ".[dev]" # whatever your lanes need to run
|
|
586
|
-
- uses: JeanFrancoisGagne/crapkit@v0.7.
|
|
611
|
+
- uses: JeanFrancoisGagne/crapkit@v0.7.6
|
|
587
612
|
with:
|
|
588
613
|
gate: "false"
|
|
589
614
|
```
|
|
@@ -654,7 +679,7 @@ closes it. A verify that passed is one line: `**verify passed.** Run 2 against b
|
|
|
654
679
|
The rows are the ranked worklist for the files the pull request changed, worst first,
|
|
655
680
|
`top` of them, with the rows a finding names listed first. `risk` is ccn times churn
|
|
656
681
|
weight, the number `crapkit worklist` ranks on, and `remedy` is the run's own verdict for
|
|
657
|
-
that function: `decompose`, `add-tests` or `ok`. `(accepted debt)` marks a function the
|
|
682
|
+
that function: `decompose`, `split-lines`, `add-tests` or `ok`. `(accepted debt)` marks a function the
|
|
658
683
|
committed ratchet carries a mark for, so an untouched `legacy_router` does not read like
|
|
659
684
|
the pull request's own new function. A pull request that touches no ranked function gets
|
|
660
685
|
the heading and no table.
|
|
@@ -830,6 +855,7 @@ The coverage summary counts all four as `measured` / `untested` / `no_lane` / `c
|
|
|
830
855
|
| Remedy | Condition | Action |
|
|
831
856
|
|---|---|---|
|
|
832
857
|
| `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. |
|
|
833
859
|
| `add-tests` | `ccn <= ceiling` and `crap > ceiling` | Cover the branches. |
|
|
834
860
|
| `ok` | `crap <= ceiling` | Nothing. |
|
|
835
861
|
|
|
@@ -1268,12 +1294,12 @@ export function classify(row: Row): string {
|
|
|
1268
1294
|
```
|
|
1269
1295
|
$ crapkit rescore src/grade.ts --gate
|
|
1270
1296
|
rescore vs run 1 @ 8bfbe613fcd (coverage STALE, complexity fresh)
|
|
1271
|
-
ccn cov crap remedy
|
|
1272
|
-
5 0% 30.0 add-tests
|
|
1273
|
-
5 0% 30.0 add-tests
|
|
1274
|
-
4 0% 20.0 add-tests
|
|
1275
|
-
4 45% 6.7 add-tests
|
|
1276
|
-
4 75% 4.2 ok
|
|
1297
|
+
ccn cov crap remedy function
|
|
1298
|
+
5 0% 30.0 add-tests src/grade.ts:22 band ( score )
|
|
1299
|
+
5 0% 30.0 add-tests src/grade.ts:38 demote ( letter , row Row )
|
|
1300
|
+
4 0% 20.0 add-tests src/grade.ts:8 penalty ( attempts , late )
|
|
1301
|
+
4 45% 6.7 add-tests src/grade.ts:48 classify ( row Row )
|
|
1302
|
+
4 75% 4.2 ok src/grade.ts:59 average ( scores Array )
|
|
1277
1303
|
```
|
|
1278
1304
|
|
|
1279
1305
|
Exit 0: every piece is at or under 6. The `crap` column is loud because its coverage half
|
|
@@ -1340,7 +1366,7 @@ with no debt.
|
|
|
1340
1366
|
|
|
1341
1367
|
| Page | Covers |
|
|
1342
1368
|
|---|---|
|
|
1343
|
-
| [The handbook](https://
|
|
1369
|
+
| [The handbook](https://www.jfgagne.com/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://www.jfgagne.com/crapkit/handbook.html), self-contained, so it opens straight from a clone. |
|
|
1344
1370
|
| [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. |
|
|
1345
1371
|
| [docs/configuration.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md) | Every `crapkit.toml` key: type, default, and what it does. |
|
|
1346
1372
|
| [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. |
|
|
@@ -1371,6 +1397,12 @@ line arms the complexity gate. See
|
|
|
1371
1397
|
for development and [the verified implementation report](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/architecture/2026-09-07-implementation/REPORT.md)
|
|
1372
1398
|
for complete Windows source and Linux wheel results, focused benchmarks and their limits.
|
|
1373
1399
|
|
|
1400
|
+
## Maintainer and project background
|
|
1401
|
+
|
|
1402
|
+
crapkit is created and maintained by [Jean-François Gagné](https://www.jfgagne.com/).
|
|
1403
|
+
Read the [project background](https://www.jfgagne.com/projects/crapkit/) for the
|
|
1404
|
+
problem it addresses and how it fits into his work on software and AI.
|
|
1405
|
+
|
|
1374
1406
|
## License
|
|
1375
1407
|
|
|
1376
1408
|
MIT. See [LICENSE](https://github.com/JeanFrancoisGagne/crapkit/blob/main/LICENSE).
|
|
@@ -70,6 +70,9 @@ crapkit ratchet seed
|
|
|
70
70
|
git add crapkit.toml crapkit-ratchet.tsv .gitignore
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
Not a Python repo? `uvx crapkit init` runs the same commands and adds nothing to your
|
|
74
|
+
manifest: see [A repo that is not Python](#a-repo-that-is-not-python).
|
|
75
|
+
|
|
73
76
|
`init` detects pytest, Vitest and Jest from the repository's own files. Review the
|
|
74
77
|
generated config before running its commands. When detection leaves a commented
|
|
75
78
|
lane, fill it in using the [lane recipes](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md).
|
|
@@ -141,6 +144,28 @@ pip install git+https://github.com/JeanFrancoisGagne/crapkit.git
|
|
|
141
144
|
pip install .
|
|
142
145
|
```
|
|
143
146
|
|
|
147
|
+
### A repo that is not Python
|
|
148
|
+
|
|
149
|
+
crapkit is a command-line tool, never a dependency of the code it scores. A TypeScript,
|
|
150
|
+
Go or Rust repo adds nothing to its own manifest. With [uv](https://docs.astral.sh/uv/)
|
|
151
|
+
on the machine, `uvx` fetches crapkit into a cache of its own and runs it:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
$ uvx crapkit init
|
|
155
|
+
wrote crapkit.toml with 1 scope(s): src
|
|
156
|
+
detected 1 lane(s) from this repo's own files: js - next: run `crapkit coverage`
|
|
157
|
+
added to .gitignore: .crapkit/
|
|
158
|
+
|
|
159
|
+
$ uvx crapkit coverage
|
|
160
|
+
$ uvx crapkit worklist
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The lane still runs your own test runner, so Vitest or Jest and its coverage package come
|
|
164
|
+
from the repo's `node_modules` as they do today. `uv tool install crapkit` or
|
|
165
|
+
`pipx install crapkit` puts a `crapkit` command on PATH once, which is what the
|
|
166
|
+
[commit gate](#the-gate) and the Claude Code plugin call. uv brings its own Python when
|
|
167
|
+
the machine has none.
|
|
168
|
+
|
|
144
169
|
Requires Python 3.11 or newer and Git on PATH. The CLI has one runtime dependency,
|
|
145
170
|
`lizard>=1.24.0`; a package mirror needs both distributions. Install into the environment
|
|
146
171
|
you intend to use, then check `crapkit --version`. The `pip install -e ".[dev]"` under
|
|
@@ -158,7 +183,7 @@ files. Review those commands before running Crapkit in a repository you do not t
|
|
|
158
183
|
|
|
159
184
|
```
|
|
160
185
|
$ crapkit --version
|
|
161
|
-
crapkit 0.7.
|
|
186
|
+
crapkit 0.7.6
|
|
162
187
|
```
|
|
163
188
|
|
|
164
189
|
`python -m crapkit` works identically to the console script and is what to use from a
|
|
@@ -414,7 +439,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
|
|
|
414
439
|
repos:
|
|
415
440
|
- repo: https://github.com/JeanFrancoisGagne/crapkit
|
|
416
441
|
# crapkit's release step rewrites this line to the tag it just cut
|
|
417
|
-
rev: v0.7.
|
|
442
|
+
rev: v0.7.6
|
|
418
443
|
hooks:
|
|
419
444
|
- id: crapkit-gate
|
|
420
445
|
```
|
|
@@ -522,7 +547,7 @@ to a workflow, and every input has a default:
|
|
|
522
547
|
- uses: actions/checkout@v4
|
|
523
548
|
with:
|
|
524
549
|
fetch-depth: 0
|
|
525
|
-
- uses: JeanFrancoisGagne/crapkit@v0.7.
|
|
550
|
+
- uses: JeanFrancoisGagne/crapkit@v0.7.6
|
|
526
551
|
```
|
|
527
552
|
|
|
528
553
|
The whole job those four lines sit in:
|
|
@@ -542,7 +567,7 @@ jobs:
|
|
|
542
567
|
with:
|
|
543
568
|
python-version: "3.12" # the interpreter the install below lands in
|
|
544
569
|
- run: pip install -e ".[dev]" # whatever your lanes need to run
|
|
545
|
-
- uses: JeanFrancoisGagne/crapkit@v0.7.
|
|
570
|
+
- uses: JeanFrancoisGagne/crapkit@v0.7.6
|
|
546
571
|
with:
|
|
547
572
|
gate: "false"
|
|
548
573
|
```
|
|
@@ -613,7 +638,7 @@ closes it. A verify that passed is one line: `**verify passed.** Run 2 against b
|
|
|
613
638
|
The rows are the ranked worklist for the files the pull request changed, worst first,
|
|
614
639
|
`top` of them, with the rows a finding names listed first. `risk` is ccn times churn
|
|
615
640
|
weight, the number `crapkit worklist` ranks on, and `remedy` is the run's own verdict for
|
|
616
|
-
that function: `decompose`, `add-tests` or `ok`. `(accepted debt)` marks a function the
|
|
641
|
+
that function: `decompose`, `split-lines`, `add-tests` or `ok`. `(accepted debt)` marks a function the
|
|
617
642
|
committed ratchet carries a mark for, so an untouched `legacy_router` does not read like
|
|
618
643
|
the pull request's own new function. A pull request that touches no ranked function gets
|
|
619
644
|
the heading and no table.
|
|
@@ -789,6 +814,7 @@ The coverage summary counts all four as `measured` / `untested` / `no_lane` / `c
|
|
|
789
814
|
| Remedy | Condition | Action |
|
|
790
815
|
|---|---|---|
|
|
791
816
|
| `decompose` | `ccn > ceiling` | Split it. No amount of coverage clears this. |
|
|
817
|
+
| `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. |
|
|
792
818
|
| `add-tests` | `ccn <= ceiling` and `crap > ceiling` | Cover the branches. |
|
|
793
819
|
| `ok` | `crap <= ceiling` | Nothing. |
|
|
794
820
|
|
|
@@ -1227,12 +1253,12 @@ export function classify(row: Row): string {
|
|
|
1227
1253
|
```
|
|
1228
1254
|
$ crapkit rescore src/grade.ts --gate
|
|
1229
1255
|
rescore vs run 1 @ 8bfbe613fcd (coverage STALE, complexity fresh)
|
|
1230
|
-
ccn cov crap remedy
|
|
1231
|
-
5 0% 30.0 add-tests
|
|
1232
|
-
5 0% 30.0 add-tests
|
|
1233
|
-
4 0% 20.0 add-tests
|
|
1234
|
-
4 45% 6.7 add-tests
|
|
1235
|
-
4 75% 4.2 ok
|
|
1256
|
+
ccn cov crap remedy function
|
|
1257
|
+
5 0% 30.0 add-tests src/grade.ts:22 band ( score )
|
|
1258
|
+
5 0% 30.0 add-tests src/grade.ts:38 demote ( letter , row Row )
|
|
1259
|
+
4 0% 20.0 add-tests src/grade.ts:8 penalty ( attempts , late )
|
|
1260
|
+
4 45% 6.7 add-tests src/grade.ts:48 classify ( row Row )
|
|
1261
|
+
4 75% 4.2 ok src/grade.ts:59 average ( scores Array )
|
|
1236
1262
|
```
|
|
1237
1263
|
|
|
1238
1264
|
Exit 0: every piece is at or under 6. The `crap` column is loud because its coverage half
|
|
@@ -1299,7 +1325,7 @@ with no debt.
|
|
|
1299
1325
|
|
|
1300
1326
|
| Page | Covers |
|
|
1301
1327
|
|---|---|
|
|
1302
|
-
| [The handbook](https://
|
|
1328
|
+
| [The handbook](https://www.jfgagne.com/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://www.jfgagne.com/crapkit/handbook.html), self-contained, so it opens straight from a clone. |
|
|
1303
1329
|
| [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. |
|
|
1304
1330
|
| [docs/configuration.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md) | Every `crapkit.toml` key: type, default, and what it does. |
|
|
1305
1331
|
| [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. |
|
|
@@ -1330,6 +1356,12 @@ line arms the complexity gate. See
|
|
|
1330
1356
|
for development and [the verified implementation report](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/architecture/2026-09-07-implementation/REPORT.md)
|
|
1331
1357
|
for complete Windows source and Linux wheel results, focused benchmarks and their limits.
|
|
1332
1358
|
|
|
1359
|
+
## Maintainer and project background
|
|
1360
|
+
|
|
1361
|
+
crapkit is created and maintained by [Jean-François Gagné](https://www.jfgagne.com/).
|
|
1362
|
+
Read the [project background](https://www.jfgagne.com/projects/crapkit/) for the
|
|
1363
|
+
problem it addresses and how it fits into his work on software and AI.
|
|
1364
|
+
|
|
1333
1365
|
## License
|
|
1334
1366
|
|
|
1335
1367
|
MIT. See [LICENSE](https://github.com/JeanFrancoisGagne/crapkit/blob/main/LICENSE).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "crapkit"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.6"
|
|
8
8
|
description = "Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -49,7 +49,7 @@ dependencies = [
|
|
|
49
49
|
|
|
50
50
|
[project.urls]
|
|
51
51
|
Homepage = "https://github.com/JeanFrancoisGagne/crapkit"
|
|
52
|
-
Documentation = "https://
|
|
52
|
+
Documentation = "https://www.jfgagne.com/crapkit/handbook.html"
|
|
53
53
|
Changelog = "https://github.com/JeanFrancoisGagne/crapkit/blob/main/CHANGELOG.md"
|
|
54
54
|
Issues = "https://github.com/JeanFrancoisGagne/crapkit/issues"
|
|
55
55
|
Source = "https://github.com/JeanFrancoisGagne/crapkit"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""crapkit: deterministic CRAP-score framework."""
|
|
2
|
-
__version__ = "0.7.
|
|
2
|
+
__version__ = "0.7.6"
|
|
@@ -817,6 +817,6 @@ def cmd_rescore(args: argparse.Namespace) -> int:
|
|
|
817
817
|
def _print_rescore_table(overlay, latest: dict) -> None:
|
|
818
818
|
"""The refactor loop's view: fresh ccn, worst first, stale cov labeled."""
|
|
819
819
|
print(f"rescore vs run {latest['id']} @ {latest['commit'][:11]} (coverage STALE, complexity fresh)")
|
|
820
|
-
print(f" {'ccn':>4} {'cov':>5} {'crap':>8} {'remedy':
|
|
820
|
+
print(f" {'ccn':>4} {'cov':>5} {'crap':>8} {'remedy':11} function")
|
|
821
821
|
for r in sorted(overlay, key=lambda x: (-x.ccn, x.path, x.start)):
|
|
822
|
-
print(f" {r.ccn:>4} {r.cov:>5.0%} {r.crap:>8.1f} {r.remedy:
|
|
822
|
+
print(f" {r.ccn:>4} {r.cov:>5.0%} {r.crap:>8.1f} {r.remedy:11} {r.path}:{r.start} {r.long_name}")
|
|
@@ -30,6 +30,15 @@ _NAME_DESCRIPTION = ("the bare identifier (classify, or route for a Rust "
|
|
|
30
30
|
"printed (classify( score , late )); both resolve, exact "
|
|
31
31
|
"match first")
|
|
32
32
|
|
|
33
|
+
# The scorer's remedy vocabulary, said once: a structured result carrying a value
|
|
34
|
+
# its outputSchema does not list is rejected whole by a validating client.
|
|
35
|
+
_REMEDIES = ("decompose", "split-lines", "add-tests", "ok")
|
|
36
|
+
_REMEDY_DESCRIPTION = ("decompose (ccn over ceiling), split-lines (another function shares its "
|
|
37
|
+
"source lines, so coverage cannot tell them apart and no test lowers the "
|
|
38
|
+
"score until the definitions sit on separate lines), add-tests (coverage "
|
|
39
|
+
"short) or ok (nothing left to do)")
|
|
40
|
+
_REMEDY = {"type": "string", "description": _REMEDY_DESCRIPTION, "enum": _REMEDIES}
|
|
41
|
+
|
|
33
42
|
# The partition a large repo needs before `top` means anything: one --scope
|
|
34
43
|
# per element, exact names as declared in crapkit.toml.
|
|
35
44
|
_SCOPE = {
|
|
@@ -63,10 +72,7 @@ _PACKET_PROPERTIES = {'scope': {'type': 'string', 'description': 'the declared s
|
|
|
63
72
|
'measure this span',
|
|
64
73
|
'enum': ('measured', 'untested', 'no-lane', 'cc-only')},
|
|
65
74
|
'crap': {'type': 'number', 'description': 'the score: ccn^2 x (1 - cov)^3 + ccn'},
|
|
66
|
-
'remedy':
|
|
67
|
-
'description': 'decompose (ccn over ceiling), add-tests (coverage short) or ok '
|
|
68
|
-
'(nothing left to do)',
|
|
69
|
-
'enum': ('decompose', 'add-tests', 'ok')},
|
|
75
|
+
'remedy': _REMEDY,
|
|
70
76
|
'target': {'type': 'integer', 'description': "this scope's effective ccn ceiling"},
|
|
71
77
|
'commits': {'type': 'integer', 'description': 'commits touching the file in the churn window'},
|
|
72
78
|
'authors': {'type': 'integer', 'description': 'distinct authors of those commits'},
|
|
@@ -112,9 +118,9 @@ _WORKLIST_ITEM = {'type': 'object',
|
|
|
112
118
|
'inventory-only run',
|
|
113
119
|
'enum': ('measured', 'untested', 'no-lane', 'cc-only', None)},
|
|
114
120
|
'remedy': {'type': 'string',
|
|
115
|
-
'description': 'decompose, add-tests or ok;
|
|
116
|
-
'
|
|
117
|
-
'enum':
|
|
121
|
+
'description': 'decompose, split-lines, add-tests or ok; every row '
|
|
122
|
+
'but ok reaches get_next_item when a lane measures it',
|
|
123
|
+
'enum': _REMEDIES},
|
|
118
124
|
'crap': {'type': ('number', 'null'),
|
|
119
125
|
'description': 'the score from the ranked run; null on an inventory-only '
|
|
120
126
|
'run'},
|
|
@@ -469,9 +475,9 @@ TOOLS: tuple[dict, ...] = (
|
|
|
469
475
|
"value get_next_item prints")},
|
|
470
476
|
"remedy": {
|
|
471
477
|
"type": "string",
|
|
472
|
-
"description": ("decompose, add-tests or ok: the branch the
|
|
473
|
-
"scored.remedy carries the same value"),
|
|
474
|
-
"enum":
|
|
478
|
+
"description": ("decompose, split-lines, add-tests or ok: the branch the "
|
|
479
|
+
"session takes; scored.remedy carries the same value"),
|
|
480
|
+
"enum": _REMEDIES},
|
|
475
481
|
"target": {
|
|
476
482
|
"type": "integer",
|
|
477
483
|
"description": ("this scope's effective ccn ceiling, the same value as "
|
|
@@ -527,11 +533,7 @@ TOOLS: tuple[dict, ...] = (
|
|
|
527
533
|
"crap": {
|
|
528
534
|
"type": "number",
|
|
529
535
|
"description": "the score: ccn^2 x (1 - cov)^3 + ccn"},
|
|
530
|
-
"remedy":
|
|
531
|
-
"type": "string",
|
|
532
|
-
"description": ("decompose (ccn over ceiling), add-tests (coverage short) or "
|
|
533
|
-
"ok (nothing left to do)"),
|
|
534
|
-
"enum": ("decompose", "add-tests", "ok")}}},
|
|
536
|
+
"remedy": _REMEDY}},
|
|
535
537
|
"source": {
|
|
536
538
|
"type": "string",
|
|
537
539
|
"description": ("the function's own text, start to end inclusive, newlines intact: "
|
|
@@ -590,11 +592,7 @@ TOOLS: tuple[dict, ...] = (
|
|
|
590
592
|
"crap": {
|
|
591
593
|
"type": "number",
|
|
592
594
|
"description": "score"},
|
|
593
|
-
"remedy":
|
|
594
|
-
"type": "string",
|
|
595
|
-
"description": ("decompose (ccn over ceiling), add-tests (coverage short) "
|
|
596
|
-
"or ok (nothing left to do)"),
|
|
597
|
-
"enum": ("decompose", "add-tests", "ok")}}}},
|
|
595
|
+
"remedy": _REMEDY}}},
|
|
598
596
|
"file_totals": {
|
|
599
597
|
"type": "object",
|
|
600
598
|
"description": "the file rolled up",
|
|
@@ -1342,11 +1340,7 @@ TOOLS: tuple[dict, ...] = (
|
|
|
1342
1340
|
"crap": {
|
|
1343
1341
|
"type": "number",
|
|
1344
1342
|
"description": "score from fresh ccn and baseline cov"},
|
|
1345
|
-
"remedy":
|
|
1346
|
-
"type": "string",
|
|
1347
|
-
"description": ("decompose (ccn over ceiling), add-tests (coverage short) "
|
|
1348
|
-
"or ok (nothing left to do)"),
|
|
1349
|
-
"enum": ("decompose", "add-tests", "ok")},
|
|
1343
|
+
"remedy": _REMEDY,
|
|
1350
1344
|
"stale_coverage": {
|
|
1351
1345
|
"type": "boolean",
|
|
1352
1346
|
"description": ("always true: complexity is the working tree's, coverage "
|
|
@@ -1393,11 +1387,7 @@ TOOLS: tuple[dict, ...] = (
|
|
|
1393
1387
|
"crap": {
|
|
1394
1388
|
"type": "number",
|
|
1395
1389
|
"description": "score"},
|
|
1396
|
-
"remedy":
|
|
1397
|
-
"type": "string",
|
|
1398
|
-
"description": ("decompose (ccn over ceiling), add-tests "
|
|
1399
|
-
"(coverage short) or ok (nothing left to do)"),
|
|
1400
|
-
"enum": ("decompose", "add-tests", "ok")},
|
|
1390
|
+
"remedy": _REMEDY,
|
|
1401
1391
|
"key_name": {
|
|
1402
1392
|
"type": "string",
|
|
1403
1393
|
"description": "the ratchet key form of the name"},
|
|
@@ -128,13 +128,20 @@ def _best_match(row: InventoryRow, candidates: list[FnCoverage]) -> FnCoverage |
|
|
|
128
128
|
return best
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
def _remedy(ccn: int, score: float, ceiling: int) -> str:
|
|
131
|
+
def _remedy(ccn: int, score: float, ceiling: int, shared_span: bool = False) -> str:
|
|
132
|
+
"""The first thing that can lower this score. A function sharing its source
|
|
133
|
+
line span with another scores as uncovered whatever its tests do, so
|
|
134
|
+
add-tests there is advice nobody can follow: splitting the definitions is,
|
|
135
|
+
and the run after the split says whether tests are still owed."""
|
|
132
136
|
if ccn > ceiling:
|
|
133
137
|
return "decompose"
|
|
134
|
-
|
|
138
|
+
if score <= ceiling:
|
|
139
|
+
return "ok"
|
|
140
|
+
return "split-lines" if shared_span else "add-tests"
|
|
135
141
|
|
|
136
142
|
|
|
137
|
-
def _finish(row, cov: float, flag: str, *, target: int, scope_targets
|
|
143
|
+
def _finish(row, cov: float, flag: str, *, target: int, scope_targets,
|
|
144
|
+
shared_span: bool = False) -> ScoredRow:
|
|
138
145
|
# cc-only is the pre-commit hook's rule: crap IS ccn, so _remedy can only
|
|
139
146
|
# answer ok or decompose. Feeding it cov=0 through the formula would say
|
|
140
147
|
# add-tests about code no test can reach.
|
|
@@ -146,7 +153,7 @@ def _finish(row, cov: float, flag: str, *, target: int, scope_targets) -> Scored
|
|
|
146
153
|
# built a throwaway dict per row and looked every field up by name.
|
|
147
154
|
return ScoredRow(row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7],
|
|
148
155
|
row[8], row[9], row[10],
|
|
149
|
-
cov, flag, score, _remedy(row[7], score, ceiling), row[11], row[12])
|
|
156
|
+
cov, flag, score, _remedy(row[7], score, ceiling, shared_span), row[11], row[12])
|
|
150
157
|
|
|
151
158
|
|
|
152
159
|
def _nearest_overlay(row, candidates) -> list:
|
|
@@ -277,11 +284,13 @@ def overlay_stale_coverage(
|
|
|
277
284
|
if r.flag == "measured":
|
|
278
285
|
by_key.setdefault((r.path, r.long_name), []).append(r)
|
|
279
286
|
|
|
287
|
+
shared = _shared_source_spans(rows, lane_scopes, cc_only_scopes)
|
|
280
288
|
scored = []
|
|
281
289
|
for row in rows:
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
scored.append(_finish(row, cov, flag, target=target, scope_targets=scope_targets
|
|
290
|
+
verdict = _cov_without_join(row, lane_scopes, cc_only_scopes)
|
|
291
|
+
cov, flag = verdict or _named_overlay_cov(row, by_key, positions)
|
|
292
|
+
scored.append(_finish(row, cov, flag, target=target, scope_targets=scope_targets,
|
|
293
|
+
shared_span=_on_shared_span(row, verdict, shared)))
|
|
285
294
|
return scored
|
|
286
295
|
|
|
287
296
|
|
|
@@ -326,8 +335,7 @@ def _shared_source_spans(rows, lane_scopes: set, cc_only_scopes) -> dict:
|
|
|
326
335
|
return collisions
|
|
327
336
|
|
|
328
337
|
|
|
329
|
-
def _ambiguous_spans(
|
|
330
|
-
lane_scopes: set, cc_only_scopes) -> dict:
|
|
338
|
+
def _ambiguous_spans(shared: dict, coverage_by_path: dict, start_index: dict) -> dict:
|
|
331
339
|
"""The shared spans a measurement would speak about, so the join would hand
|
|
332
340
|
every function on the span one function's number.
|
|
333
341
|
|
|
@@ -338,18 +346,25 @@ def _ambiguous_spans(rows, coverage_by_path: dict, start_index: dict,
|
|
|
338
346
|
shape _note_unanalyzable settled for unreadable files.
|
|
339
347
|
"""
|
|
340
348
|
ambiguous = {}
|
|
341
|
-
for span, members in
|
|
349
|
+
for span, members in shared.items():
|
|
342
350
|
if _span_join_cov(members[0], coverage_by_path, start_index)[1] == "measured":
|
|
343
351
|
ambiguous[span] = members
|
|
344
352
|
return ambiguous
|
|
345
353
|
|
|
346
354
|
|
|
347
|
-
def
|
|
348
|
-
|
|
349
|
-
|
|
355
|
+
def _joined_cov(row, ambiguous: dict, coverage_by_path: dict,
|
|
356
|
+
start_index: dict) -> tuple[float, str]:
|
|
357
|
+
"""Uncovered on an ambiguous span, never the neighbour's number: the honest
|
|
358
|
+
floor for a function whose measurement cannot be told from another's."""
|
|
350
359
|
if (row.path, row.start, row.end) in ambiguous:
|
|
351
360
|
return 0.0, "untested"
|
|
352
|
-
return
|
|
361
|
+
return _span_join_cov(row, coverage_by_path, start_index)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _on_shared_span(row, verdict, shared: dict) -> bool:
|
|
365
|
+
"""A row some lane measures, on a span it shares. Measured yet or not: tests
|
|
366
|
+
would only make the span measured, and then it scores as uncovered."""
|
|
367
|
+
return verdict is None and (row.path, row.start, row.end) in shared
|
|
353
368
|
|
|
354
369
|
|
|
355
370
|
def score_rows(
|
|
@@ -363,14 +378,15 @@ def score_rows(
|
|
|
363
378
|
shared_spans: SharedSpanFold | None = None,
|
|
364
379
|
) -> list[ScoredRow]:
|
|
365
380
|
start_index = _start_index(coverage_by_path)
|
|
366
|
-
|
|
381
|
+
shared = _shared_source_spans(rows, lane_scopes, cc_only_scopes)
|
|
382
|
+
ambiguous = _ambiguous_spans(shared, coverage_by_path, start_index)
|
|
367
383
|
for members in ambiguous.values():
|
|
368
384
|
if shared_spans is not None:
|
|
369
385
|
shared_spans.add(members)
|
|
370
386
|
scored = []
|
|
371
387
|
for r in rows:
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
388
|
+
verdict = _cov_without_join(r, lane_scopes, cc_only_scopes)
|
|
389
|
+
cov, flag = verdict or _joined_cov(r, ambiguous, coverage_by_path, start_index)
|
|
390
|
+
scored.append(_finish(r, cov, flag, target=target, scope_targets=scope_targets,
|
|
391
|
+
shared_span=_on_shared_span(r, verdict, shared)))
|
|
376
392
|
return scored
|
|
@@ -70,7 +70,7 @@ _CODE_DDL = """CREATE TABLE IF NOT EXISTS {table} (
|
|
|
70
70
|
# and a store is a file people copy between machines. A name from outside this
|
|
71
71
|
# list is still stored, at a code minted after these.
|
|
72
72
|
_CODE_SEEDS = {"flags": ("measured", "untested", "no-lane", "cc-only"),
|
|
73
|
-
"remedies": ("ok", "add-tests", "decompose")}
|
|
73
|
+
"remedies": ("ok", "add-tests", "decompose", "split-lines")}
|
|
74
74
|
|
|
75
75
|
_SCHEMA = f"""
|
|
76
76
|
CREATE TABLE IF NOT EXISTS runs (
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: crapkit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.6
|
|
4
4
|
Summary: Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more.
|
|
5
5
|
Author: Jean-Francois Gagne
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/JeanFrancoisGagne/crapkit
|
|
8
|
-
Project-URL: Documentation, https://
|
|
8
|
+
Project-URL: Documentation, https://www.jfgagne.com/crapkit/handbook.html
|
|
9
9
|
Project-URL: Changelog, https://github.com/JeanFrancoisGagne/crapkit/blob/main/CHANGELOG.md
|
|
10
10
|
Project-URL: Issues, https://github.com/JeanFrancoisGagne/crapkit/issues
|
|
11
11
|
Project-URL: Source, https://github.com/JeanFrancoisGagne/crapkit
|
|
@@ -111,6 +111,9 @@ crapkit ratchet seed
|
|
|
111
111
|
git add crapkit.toml crapkit-ratchet.tsv .gitignore
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
Not a Python repo? `uvx crapkit init` runs the same commands and adds nothing to your
|
|
115
|
+
manifest: see [A repo that is not Python](#a-repo-that-is-not-python).
|
|
116
|
+
|
|
114
117
|
`init` detects pytest, Vitest and Jest from the repository's own files. Review the
|
|
115
118
|
generated config before running its commands. When detection leaves a commented
|
|
116
119
|
lane, fill it in using the [lane recipes](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md).
|
|
@@ -182,6 +185,28 @@ pip install git+https://github.com/JeanFrancoisGagne/crapkit.git
|
|
|
182
185
|
pip install .
|
|
183
186
|
```
|
|
184
187
|
|
|
188
|
+
### A repo that is not Python
|
|
189
|
+
|
|
190
|
+
crapkit is a command-line tool, never a dependency of the code it scores. A TypeScript,
|
|
191
|
+
Go or Rust repo adds nothing to its own manifest. With [uv](https://docs.astral.sh/uv/)
|
|
192
|
+
on the machine, `uvx` fetches crapkit into a cache of its own and runs it:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
$ uvx crapkit init
|
|
196
|
+
wrote crapkit.toml with 1 scope(s): src
|
|
197
|
+
detected 1 lane(s) from this repo's own files: js - next: run `crapkit coverage`
|
|
198
|
+
added to .gitignore: .crapkit/
|
|
199
|
+
|
|
200
|
+
$ uvx crapkit coverage
|
|
201
|
+
$ uvx crapkit worklist
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The lane still runs your own test runner, so Vitest or Jest and its coverage package come
|
|
205
|
+
from the repo's `node_modules` as they do today. `uv tool install crapkit` or
|
|
206
|
+
`pipx install crapkit` puts a `crapkit` command on PATH once, which is what the
|
|
207
|
+
[commit gate](#the-gate) and the Claude Code plugin call. uv brings its own Python when
|
|
208
|
+
the machine has none.
|
|
209
|
+
|
|
185
210
|
Requires Python 3.11 or newer and Git on PATH. The CLI has one runtime dependency,
|
|
186
211
|
`lizard>=1.24.0`; a package mirror needs both distributions. Install into the environment
|
|
187
212
|
you intend to use, then check `crapkit --version`. The `pip install -e ".[dev]"` under
|
|
@@ -199,7 +224,7 @@ files. Review those commands before running Crapkit in a repository you do not t
|
|
|
199
224
|
|
|
200
225
|
```
|
|
201
226
|
$ crapkit --version
|
|
202
|
-
crapkit 0.7.
|
|
227
|
+
crapkit 0.7.6
|
|
203
228
|
```
|
|
204
229
|
|
|
205
230
|
`python -m crapkit` works identically to the console script and is what to use from a
|
|
@@ -455,7 +480,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
|
|
|
455
480
|
repos:
|
|
456
481
|
- repo: https://github.com/JeanFrancoisGagne/crapkit
|
|
457
482
|
# crapkit's release step rewrites this line to the tag it just cut
|
|
458
|
-
rev: v0.7.
|
|
483
|
+
rev: v0.7.6
|
|
459
484
|
hooks:
|
|
460
485
|
- id: crapkit-gate
|
|
461
486
|
```
|
|
@@ -563,7 +588,7 @@ to a workflow, and every input has a default:
|
|
|
563
588
|
- uses: actions/checkout@v4
|
|
564
589
|
with:
|
|
565
590
|
fetch-depth: 0
|
|
566
|
-
- uses: JeanFrancoisGagne/crapkit@v0.7.
|
|
591
|
+
- uses: JeanFrancoisGagne/crapkit@v0.7.6
|
|
567
592
|
```
|
|
568
593
|
|
|
569
594
|
The whole job those four lines sit in:
|
|
@@ -583,7 +608,7 @@ jobs:
|
|
|
583
608
|
with:
|
|
584
609
|
python-version: "3.12" # the interpreter the install below lands in
|
|
585
610
|
- run: pip install -e ".[dev]" # whatever your lanes need to run
|
|
586
|
-
- uses: JeanFrancoisGagne/crapkit@v0.7.
|
|
611
|
+
- uses: JeanFrancoisGagne/crapkit@v0.7.6
|
|
587
612
|
with:
|
|
588
613
|
gate: "false"
|
|
589
614
|
```
|
|
@@ -654,7 +679,7 @@ closes it. A verify that passed is one line: `**verify passed.** Run 2 against b
|
|
|
654
679
|
The rows are the ranked worklist for the files the pull request changed, worst first,
|
|
655
680
|
`top` of them, with the rows a finding names listed first. `risk` is ccn times churn
|
|
656
681
|
weight, the number `crapkit worklist` ranks on, and `remedy` is the run's own verdict for
|
|
657
|
-
that function: `decompose`, `add-tests` or `ok`. `(accepted debt)` marks a function the
|
|
682
|
+
that function: `decompose`, `split-lines`, `add-tests` or `ok`. `(accepted debt)` marks a function the
|
|
658
683
|
committed ratchet carries a mark for, so an untouched `legacy_router` does not read like
|
|
659
684
|
the pull request's own new function. A pull request that touches no ranked function gets
|
|
660
685
|
the heading and no table.
|
|
@@ -830,6 +855,7 @@ The coverage summary counts all four as `measured` / `untested` / `no_lane` / `c
|
|
|
830
855
|
| Remedy | Condition | Action |
|
|
831
856
|
|---|---|---|
|
|
832
857
|
| `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. |
|
|
833
859
|
| `add-tests` | `ccn <= ceiling` and `crap > ceiling` | Cover the branches. |
|
|
834
860
|
| `ok` | `crap <= ceiling` | Nothing. |
|
|
835
861
|
|
|
@@ -1268,12 +1294,12 @@ export function classify(row: Row): string {
|
|
|
1268
1294
|
```
|
|
1269
1295
|
$ crapkit rescore src/grade.ts --gate
|
|
1270
1296
|
rescore vs run 1 @ 8bfbe613fcd (coverage STALE, complexity fresh)
|
|
1271
|
-
ccn cov crap remedy
|
|
1272
|
-
5 0% 30.0 add-tests
|
|
1273
|
-
5 0% 30.0 add-tests
|
|
1274
|
-
4 0% 20.0 add-tests
|
|
1275
|
-
4 45% 6.7 add-tests
|
|
1276
|
-
4 75% 4.2 ok
|
|
1297
|
+
ccn cov crap remedy function
|
|
1298
|
+
5 0% 30.0 add-tests src/grade.ts:22 band ( score )
|
|
1299
|
+
5 0% 30.0 add-tests src/grade.ts:38 demote ( letter , row Row )
|
|
1300
|
+
4 0% 20.0 add-tests src/grade.ts:8 penalty ( attempts , late )
|
|
1301
|
+
4 45% 6.7 add-tests src/grade.ts:48 classify ( row Row )
|
|
1302
|
+
4 75% 4.2 ok src/grade.ts:59 average ( scores Array )
|
|
1277
1303
|
```
|
|
1278
1304
|
|
|
1279
1305
|
Exit 0: every piece is at or under 6. The `crap` column is loud because its coverage half
|
|
@@ -1340,7 +1366,7 @@ with no debt.
|
|
|
1340
1366
|
|
|
1341
1367
|
| Page | Covers |
|
|
1342
1368
|
|---|---|
|
|
1343
|
-
| [The handbook](https://
|
|
1369
|
+
| [The handbook](https://www.jfgagne.com/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://www.jfgagne.com/crapkit/handbook.html), self-contained, so it opens straight from a clone. |
|
|
1344
1370
|
| [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. |
|
|
1345
1371
|
| [docs/configuration.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/configuration.md) | Every `crapkit.toml` key: type, default, and what it does. |
|
|
1346
1372
|
| [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. |
|
|
@@ -1371,6 +1397,12 @@ line arms the complexity gate. See
|
|
|
1371
1397
|
for development and [the verified implementation report](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/architecture/2026-09-07-implementation/REPORT.md)
|
|
1372
1398
|
for complete Windows source and Linux wheel results, focused benchmarks and their limits.
|
|
1373
1399
|
|
|
1400
|
+
## Maintainer and project background
|
|
1401
|
+
|
|
1402
|
+
crapkit is created and maintained by [Jean-François Gagné](https://www.jfgagne.com/).
|
|
1403
|
+
Read the [project background](https://www.jfgagne.com/projects/crapkit/) for the
|
|
1404
|
+
problem it addresses and how it fits into his work on software and AI.
|
|
1405
|
+
|
|
1374
1406
|
## License
|
|
1375
1407
|
|
|
1376
1408
|
MIT. See [LICENSE](https://github.com/JeanFrancoisGagne/crapkit/blob/main/LICENSE).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|