crapkit
This server provides read-only insights from crapkit's scored runs to help you find and act on risky code.
next_item: get the highest-risk function to refactor, with score, uncovered lines, and effort estimate.
worklist: survey the full risk ranking (complexity times churn) across the repo.
runs: view run history (id, kind, verdict, commit, lanes) to evidence the store's staleness.
brief: deep context on one function—score, ratchet mark, uncovered lines, duplication twins, churn, and coupling partners.
explain: see a function's score trajectory over runs and its ratchet mark.
doctor: check whether the config still matches the repo (typo keys, empty scopes, missing runners).
coupling: find file pairs that change together—dependencies imports don't reveal.
duplication: find near-duplicate functions by normalized line shingles.
ratchet_report: review the debt burn-down—open marks, age, and repayment velocity.
crapkit

crapkit scores every function in your repo on complexity times uncovered risk, ranks the worst ones by how often the file changes, and blocks commits that add more. It reads Python, TypeScript, TSX, JavaScript, Swift, Go, Rust, shell, PowerShell, C and C++, Objective-C, Vue, Java and Zig through lizard, and joins per-function branch coverage from the istanbul or coverage.py artifact your own test command already writes. Every read command speaks sorted-keys JSON on a pinned schema, because half the callers are coding agents.
CRAP = ccn^2 * (1 - cov)^3 + ccnThe name is not ours: C.R.A.P. (Change Risk Anti-Patterns) was coined for crap4j by Alberto Savoia and Bob Evans in 2007.
ccn is the smaller of standard and modified cyclomatic complexity, both read off one
lizard pass. cov is branch coverage inside the function's span; with no branches it
falls back to statement coverage, and with no statements to invoked-or-not, so a
half-executed straight-line function never reads as fully covered.
Above the ceiling, coverage cannot save you. Decompose. At the default target of 6, a function at ccn 7 with 100% coverage still scores 7 and still fails the gate. The only move that clears it is splitting the function.
crapkit scores git-tracked files only. Source you have not git added is invisible to
it.
The 60-second start
pip install crapkit
cd your-repo
crapkit init # crapkit.toml and .gitignore lines, plus a live coverage lane when it
# recognizes the runner and a scope speaks its language: pyproject.toml,
# pytest.ini or setup.cfg for pytest; a test script or vitest/jest in
# package.json for the JS side
# without one: the lane comes commented out, init says to declare one,
# and docs/lanes.md is how to fill it in
crapkit coverage # runs the lane, joins coverage, stores a scored run
crapkit worklist # the ranked risk map
crapkit ratchet seed && git add crapkit.toml crapkit-ratchet.tsv .gitignorecoverage scores, worklist ranks:
$ crapkit coverage
run 1 @ fae4db93108: 2 functions scored — 2 measured / 0 untested / 0 no-lane / 0 cc-only, 1 over target 6, CRAP load 41.0, grade F
$ crapkit worklist
worklist @ fae4db93108 (run 1, floor ccn>=5, churn 12mo) — 1 active, 0 dormant
risk 0.0 ccn 14 ( 14 std) 1c/1a w 0.00 calc/grade.py:7 classify( score , attempts , late , bonus )risk 0.0 is what a one-commit repo scores, because churn needs a spread of commits to
rank and ccn order stands in until then (Risk).
ratchet seed signs today's debt at today's score. From then on marks only ever fall, so
the repo can get better and never worse while you burn it down.
One thing stops most first runs: the coverage plugin. init writes a lane that shells
out to your own test runner, and the runner needs its coverage package installed:
pytest-cov for pytest, @vitest/coverage-v8 (pinned to your vitest major) for vitest.
Without it the lane produces no artifact and coverage exits 5 quoting the runner's own
error. For pytest, init probes the python its lane will run and prints the install
command when pytest_cov is missing; pip install "crapkit[py]" pulls the plugin
alongside crapkit when the two share a venv. On a Windows PATH holding only the py
launcher it writes py, not a python3 the lane could never run, and when cmd.exe cannot
start the interpreter at all (exit 9009, the Store alias) it names that instead of guessing
at pytest-cov. A repo that pins no lockfile and carries its own .venv gets that venv's
interpreter in the lane, when that interpreter can import pytest, rather than whichever
python the shell answers with. The two quickstarts below walk a real repo end to end.
On Windows a lane command is read by cmd.exe, the shell that will run it, not by sh. Double quotes are the portable quoting. A single-quoted value is refused at config load with exit 3, because cmd.exe would hand pytest five words and the lane would write no artifact:
# the lane in crapkit.toml
command = "python -m pytest -m 'not live and not perf' --cov=calc --cov-branch --cov-report=json:.crapkit/cov/py.json"
$ crapkit doctor
crapkit: lane 'py': positional argument 'live' narrows a full-suite coverage run; drop it, attach it to the flag it belongs to (-n8, --numprocesses=8), or set full_suite = false deliberately (cmd.exe does not treat ' as a quote: write the value in double quotes); a suite whose testpaths cannot be collected in one process needs one lane per testpath, each with full_suite = false and its own artifactWrite it -m "not live and not perf". Carets, && and | segments, redirections and
empty quoted arguments all read the way the shell reads them, so a chained lane
(cd tests && python -m pytest --cov ...) is checked one segment at a time. doctor reads
a lane the same way, and FAILs one whose runner will not start.
Related MCP server: PhpCodeArcheology
Install
pip install crapkitThat is the release on PyPI. For the unreleased tip
of main, or from a local clone (run at the clone root):
pip install git+https://github.com/JeanFrancoisGagne/crapkit.git
pip install .Every route pulls one dependency, lizard>=1.24.0, a normal PyPI wheel, so an offline
mirror installs fine. Requires Python 3.11 or newer. The pip install -e ".[dev]" under
Development is a different thing: it adds the test extra, for people
changing crapkit.
Scoring runs your own test command on your own machine and reads the artifact it writes. There is no network call anywhere in crapkit, so no source, no score and no telemetry leaves the box (SECURITY.md).
$ crapkit --version
crapkit 0.4.15python -m crapkit works identically to the console script and is what to use from a
source checkout. Every subcommand accepts --repo PATH (default: the current directory),
so you never have to cd into the repo you are scoring; Subcommands shows
where the flag goes.
Upgrading from 0.4.4
Run crapkit ratchet seed first. Shell cognitive complexity now nests, which is
analysis version 8, and marks measured under version 7 are not comparable. Until you
re-seed, verify refuses at exit 3:
$ crapkit verify
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`Only shell and PowerShell cognitive numbers move. ccn does not, so a re-seed re-stamps
the file and leaves the marks where they were.
Five more things change under you. Three of them need nothing from you:
New cache files.
.crapkit/coupling-cache-v1.jsonjoinschurn-cache-v2.jsonandchurn-log-v2.z. A warm 0.4.4 churn cache is adopted once and its file removed, and.crapkit/is already gitignored, so nothing new reaches your index.trendandreportwrite. Both read a per-run rollup table, filled once per run and pruned with its run, instead of rescanning every scored row. A read-only.crapkit/costs the speedup, never the command.Nested scopes may move files. One predicate decides scope ownership now, and the deepest declared path wins, so a repo whose
[[scope]]paths nest inside each other can see files change scope, rollup and ceiling on the next scan. Scopes that do not nest see no change.
The other two put something in front of you:
mutatekeeps a worktree pool. Withmutation_workers > 1the worker worktrees now live under.crapkit/mutate-pool/between runs and are re-prepared each run, which is the setup cost gone (30.6 s to build four on a 31,459-file repo, 0.46 s to re-prepare them). The pool is not size-bounded and nothing sweeps it:crapkit mutate --drop-poolremoves it and exits. Single-worker runs are untouched.doctorWARNs on a lane with noresults_artifact. Everycoveragepyoristanbullane written before 0.4.5 gets one, with the two lines that fix it. Coverage is unaffected. What the lane cannot feed without a results file is the crashed-worker check and the no-new-failures check (exit 8).
The exe lock on Windows
uv tool upgrade crapkit, and pip install -U into a tool venv, fail with os error 32
("The process cannot access the file because it is being used by another process") while a
crapkit MCP server is live: an agent session spawns crapkit.exe mcp, which holds the
launcher, and Windows will not overwrite a running executable. The venv upgrades before
that copy fails, so crapkit --version already reports the new version and only the
launcher is stale. Quit the agent session and rerun the upgrade, or rename the locked exe
aside (Windows allows renaming a running one) and copy the new one in. Two lines in
cmd.exe, where both % variables expand:
move %USERPROFILE%\.local\bin\crapkit.exe %USERPROFILE%\.local\bin\crapkit.exe.old
copy %APPDATA%\uv\tools\crapkit\Scripts\crapkit.exe %USERPROFILE%\.local\bin\crapkit.exeGit Bash has no move and passes %APPDATA% through as literal text, so that block
fails there on its first line. Its form is mv and cp over "$USERPROFILE" and
"$APPDATA", which Git Bash sets to the same two directories.
The Claude Code plugin
claude plugin marketplace add JeanFrancoisGagne/crapkit
claude plugin install crapkit@crapkitTwo commands, installed once per user, and every repo on the machine gets it. The plugin
ships three skills, the read-only MCP server, and one advisory PostToolUse hook that names
any function an edit pushed over its ceiling. Claude reaches two of the skills by itself,
crapkit and crapkit-recover; the third you type, as /crapkit:crapkit-onboard, because
wiring a repo up happens once and its description has no business in every turn's window.
It adds no files to your repo, and it needs the crapkit CLI on PATH.
A repo with no crapkit.toml costs a silent sub-50 ms no-op per edit. Other agent
runtimes have no marketplace: copy plugin/skills/* into their skills directory instead.
The hook registers on Edit|Write, which is every write that names a file. An agent that
writes its source through a shell heredoc names none, so a Bash event is judged off the
working tree instead. That half is yours to register, because it costs two
git spawns per shell call. Add a second PostToolUse entry to your own settings, same
command, matcher Bash:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "crapkit claude-hook --protocol 1", "timeout": 20 }
]
}
]
}
}The cost is one git rev-parse --show-toplevel and one git status --porcelain -z -uall
per shell call in any git repo, whether or not crapkit measures it: about 30 ms together
on crapkit's own checkout, and more on a bigger tree. What comes back is the dirty or
untracked *.py files written in the last 12 seconds, 25 at most, each judged the way an
edit is. Python only, so a TypeScript or Go repo pays the two spawns and hears nothing.
Languages
14 languages, two coverage parsers. Coverage joins where a parser exists; everything else scores on complexity alone.
Language | Files | Coverage |
Python |
| coverage.py |
TypeScript |
| istanbul |
TSX |
| istanbul |
JavaScript |
| istanbul |
Vue |
| istanbul, when your vitest run reports on |
Swift |
| none: cc-only |
Go |
| none: cc-only |
Rust |
| none: cc-only |
shell |
| none: cc-only |
PowerShell |
| none: cc-only |
C and C++ |
| none: cc-only |
Objective-C |
| none: cc-only |
Java |
| none: cc-only |
Zig |
| none: cc-only |
A cc-only scope declares coverage_optional = true, scores crap = ccn, and needs no
lane. Nothing about it is provisional: the ceiling still binds and the gate still refuses
a function over it. Add a coverage lane the day a parser exists and the same scope starts
joining coverage.
crapkit init writes that key itself, on every scope whose languages all lack a parser,
and leaves it off any scope a lane could still measure. So the 60-second start above runs
unchanged on a Go, Rust or shell repo: crapkit coverage scores it with no lane at all,
and that run is the baseline worklist, next-item, ratchet seed and verify read.
Three readers are crapkit's own. lizard ships none for shell or PowerShell, so crapkit
counts their functions itself. Its Rust reader scores a 7-arm match as ccn 2 (filed as
lizard #494), so crapkit counts each non-wildcard arm like a C case and retires the
override the day upstream fixes it. The cognitive column charges that same block once,
the way Sonar charges a switch.
The gate
Four surfaces ask the same question, ccn against the scope's ceiling, with four different powers:
Surface | Fires | Power |
| after an agent's edit lands | advisory. Names the breach on stderr. Blocks nothing, because PostToolUse runs after the write |
| 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 |
|
| blocks. The hook exits 6; git reports 1. Staged blobs only, so it costs the size of the commit and needs no coverage |
| before you push, and in CI | the verdict. Gate, ratchet, new test failures, diff coverage, against the trusted baseline |
Both hooks exempt a function the committed ratchet already carries a mark for, so touching
signed debt never refuses a commit. verify is what fails a mark that rises. Since 0.4.5
its gate exempts a touched function whose fresh CRAP sits at or under its mark, the
rule rescore --gate already applied; push it past the mark and the gate fires again. The
pre-commit hook still exempts on the mark's existence alone, on purpose: a staged blob has
no coverage, so there is no fresh CRAP to compare against. It reports each exemption count
on stderr (staged function(s) carry a ratchet mark and were not gated), and says the same
about a staged file no [[scope]] claims, so a new top-level directory cannot go ungated
in silence.
The crapkit root does not have to be the git top. Since 0.4.5 every git spawn runs with
diff.relative=true and core.quotePath=false, so a crapkit.toml in packages/api
gates that package's own staged files and names them app/m.py, not
packages/api/app/m.py, and a dirty non-ASCII path is a real row rather than an invisible
one. Before that a nested root matched staged paths against no scope, and a function at
twice the ceiling committed with a warning.
Git runs hooks outside your shell's activated venv. Bare python must resolve to an
interpreter that has crapkit installed, or spell it out
(exec /path/to/venv/Scripts/python -m crapkit hook-precommit).
Route 1: .git/hooks/pre-commit (local, not committed)
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
exec python -m crapkit hook-precommit
EOF
chmod +x .git/hooks/pre-commitRoute 2: a committed hooks directory
The whole route, from a repo that has no githooks/ yet:
mkdir -p githooks
cat > githooks/pre-commit <<'EOF'
#!/bin/sh
exec python -m crapkit hook-precommit
EOF
chmod +x githooks/pre-commit
printf 'githooks/pre-commit text eol=lf\n' >> .gitattributes
git add .gitattributes githooks/pre-commit
git update-index --chmod=+x githooks/pre-commit
git commit -m "add crapkit gate hook"
git config core.hooksPath githooksThe --chmod goes between the add and the commit. It writes the executable bit to
the index, so a commit that already happened does not carry it: run it after and git ls-tree HEAD still says 100644, which is a hook Unix checkouts silently skip. The
.gitattributes line is the harder half of the same failure: under Windows' default
core.autocrlf the hook checks out CRLF and #!/bin/sh\r dies on Linux and macOS with a
bad-interpreter error. crapkit doctor warns when a file under core.hooksPath is not
100755 in the index and prints the update-index line for it.
Git will not read a hooks path out of a committed file, so that git config line belongs
in your CONTRIBUTING setup steps. Every clone arms the gate with it.
Route 3: the pre-commit framework
crapkit ships a .pre-commit-hooks.yaml declaring id: crapkit-gate. In your
.pre-commit-config.yaml:
repos:
- repo: https://github.com/JeanFrancoisGagne/crapkit
# crapkit's release step rewrites this line to the tag it just cut
rev: v0.4.15
hooks:
- id: crapkit-gateThat file arms nothing on its own. The framework writes .git/hooks/pre-commit when you
tell it to, and until then git commit runs no gate and says nothing:
pip install pre-commit
pre-commit installpre-commit install is the line every clone needs, the way Route 2 needs its
git config core.hooksPath line.
rev is a git ref pre-commit resolves against that remote. Pin a release tag, not a
branch: pre-commit autoupdate only moves between tags, and a moving main would change
your gate under you.
Route 4: CI
A CI job runs on a fresh clone, which has no .crapkit/ store, so bare crapkit verify
exits 1. Running coverage first would make the PR's own tree the baseline, a gate that
can never fail. The portable baseline is the mechanism:
# on the default branch, after a passing verify: commit this file
crapkit verify --emit-baseline crapkit-baseline.tsv
# in the PR job, against the committed baseline
crapkit verify --baseline-tsv crapkit-baseline.tsv --github--github emits ::error file=... annotations that land on the PR diff; --sarif PATH
writes SARIF 2.1.0 for code-scanning upload. Refresh the committed baseline whenever the
default branch's verify passes.
Two things the job has to do before those lines run. Install crapkit, pip install crapkit, and pin the version the way Route 3 pins rev: an unpinned install moves your
gate on whatever day a release lands. Fetch the whole history. actions/checkout
clones one commit by default, verify reads the diff against the baseline's commit out of
git, and a shallow clone does not have that commit:
$ crapkit verify --baseline-tsv crapkit-baseline.tsv
crapkit: baseline commit a74260f321f is not an ancestor of HEAD (rebase or amend rewrote history) — run `crapkit coverage` for a fresh baselineThat is exit 4 on a git clone --depth 1 of a repo whose baseline verifies at full depth.
Set fetch-depth: 0 on the checkout step, which is what crapkit's own
.github/workflows/ci.yml does.
The whole PR job, on GitHub Actions:
on: pull_request
jobs:
crapkit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # verify needs the baseline's commit
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install crapkit
- run: pip install -e ".[dev]" # your own test dependencies
- run: crapkit verify --baseline-tsv crapkit-baseline.tsv --githubThe second install is the one people leave out. verify reruns your lanes, so the job
needs whatever your test command needs: the coverage plugin, npm ci, a database, all of
it. Without them the lane writes no artifact and verify exits 5 quoting the runner's own
error, which is a broken job and not a verdict.
What a refusal looks like
$ git commit -m "add route"
crapkit gate: 1 staged function(s) exceed the complexity ceiling of 6:
ccn 7 app/m.py:9 route( a , b , c , d )
decompose before committing (coverage cannot save a function above the target).That commit exited 1, not 6. Git collapses any failed hook to 1, so 6 is a code you
only ever see by running the hook yourself: crapkit hook-precommit exits 6 on a
violation and 0 otherwise. The stderr block above is the same either way.
CRAPKIT_OVERRIDE_REASON is not a bypass. Setting it routes the commit through the full
three-record audit: an alert line through alert_command, a ratchet entry staged into the
commit, and a row in the override log. All three land or nothing does, and an unset
alert_command refuses the override outright. See
docs/ratchet.md.
The GitHub Action
action.yml at this repository's root is a composite action, so a reviewer sees crapkit's numbers on the pull request without installing anything. Four lines add it to a workflow, and every input has a default:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: JeanFrancoisGagne/crapkit@v0.4.15The whole job those four lines sit in:
on: pull_request
jobs:
crapkit:
runs-on: ubuntu-latest
permissions:
pull-requests: write # the comment, and nothing else
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # the diff, and verify's baseline commit
- uses: actions/setup-python@v5
with:
python-version: "3.12" # the interpreter the install below lands in
- run: pip install -e ".[dev]" # whatever your lanes need to run
- uses: JeanFrancoisGagne/crapkit@v0.4.15
with:
gate: "false"That pip install step is the one people leave out, and it is the same one Route 4 above
names: the action installs crapkit and nothing else, so your lanes still need whatever
your test command needs. Without it the lane writes no artifact and the comment says so.
fetch-depth: 0 is the other one. actions/checkout clones a single commit; the action
reads the pull request's changed files out of git and verify reads the diff against the
baseline's commit. With a shallow clone the file list comes back empty and the comment
ranks the whole repository instead of the diff.
The action installs crapkit from $GITHUB_ACTION_PATH, which is its own checkout of the
ref you pinned in uses:. So a pin left at last month's tag scores your tree with last
month's crapkit rather than with whatever released since, and pinning a tag is the whole
version policy; the snippets above name the current release.
What the comment looks like
One comment per pull request, edited in place on every push. A hidden
<!-- crapkit-action --> line is how the next run finds it, so a fifteen-push branch
carries one comment and not fifteen. On a push event there is no pull request to carry
it, and the same text goes to the job log instead.
Rendered against this repository's own store, with a base commit fifteen back standing in for a pull request:
<!-- crapkit-action -->
## crapkit
1333 functions in 61 files, 0 over target, CRAP load 3713.89, grade A+.
**verify passed.** Run 2 against baseline 1, 7 changed files.
### Worklist: 39 changed files
| File | Function | ccn | risk | remedy |
|---|---|---:|---:|---|
| `src/crapkit/cli/admin.py:1026` | `_recorded_roots( recorded )` | 6 | 10.9908 | ok |
| `src/crapkit/lanes.py:646` | `build_retest_command( template : str , tests : set [ str ] )` | 6 | 10.8234 | ok |
| `src/crapkit/cli/admin.py:80` | `_next_step( scopes : dict , lanes : tuple )` | 5 | 9.159 | ok |
| `src/crapkit/cli/admin.py:239` | `_pytest_cov_probe( command : str )` | 5 | 9.159 | ok |
| `src/crapkit/cli/admin.py:505` | `_segment_problems( name : str , cwd : Path , tokens : list [ str ] )` | 5 | 9.159 | ok |The rows are the ranked worklist for the files the pull request changed, worst first,
top of them. risk is ccn times churn weight, the number crapkit worklist ranks on,
and remedy is the run's own verdict for that function: decompose, add-tests or ok.
A pull request that touches no ranked function gets the heading and no table.
The two file counts describe the same diff, counted twice. 39 changed files is
git diff --name-only base.sha...HEAD, the branch's own commits, and it is what the
table is filtered to. The count on the verdict line is what verify measured from the
same fork point. With delta: "false" the second one is 0, because there is nothing
behind the checkout to measure from.
The inputs
Input | Default | What it does |
|
|
|
|
| scores the pull request's base commit first, so the verdict covers the commits the pull request adds. Costs a second lane run; |
|
| worklist rows rendered in the table |
|
| the interpreter |
gate: "false" is the default on purpose. A team adopts the action before it has decided
which findings should stop a merge, and a check that fails on day one gets turned off on
day two.
What the verdict line covers
On a pull request, the commits the pull request adds. The action scores the fork point
first, then the checkout, then runs crapkit verify --base <fork>, which measures the
diff from there and takes the fork point's run as its baseline. So the gate judges the
functions in the diff a reviewer is reading, and a repository that was already over its
ceiling before the branch started does not fail every pull request that touches it.
The fork point is git merge-base of base.sha and HEAD, not base.sha itself.
base.sha is the base branch's tip when the event fired, so a base branch that moved
after the branch forked carries commits HEAD never saw, and a run there would be neither
the baseline verify wants nor a diff anyone is reviewing.
The base run happens in a detached worktree under RUNNER_TEMP, and its store is copied
over the checkout's so both runs sit in one place. The cost is two lane runs on a pull
request: your suite runs once at the fork point and once on the checkout. Set delta: "false" to skip the base run, and the verdict falls back to the checkout against its own
run, which reports the tree's own health and judges no changed function.
Three things leave the base run unmade, and none of them fails the job: a shallow clone
that does not hold the fork point, a fork point older than your crapkit.toml, and a
lane that will not run against that tree. The step logs crapkit base scoring exited N
and the verdict falls back the same way delta: "false" does. A push event never makes
one, because there is no base commit and no pull request to comment on.
One requirement the base run adds: the lane has to measure the tree it runs in. A lane
that reaches an installed copy of your package instead of the checkout will measure the
pull request's code while standing on the base commit, and the two runs then describe the
same tree. crapkit verify refuses a run whose artifact names files outside the tree
(exit 5), which catches the loud version of this; a lane pinned to a path outside the
worktree is the quiet one. Point the lane at the tree, or set delta: "false".
--reuse-artifacts is what keeps each of those runs to one pass of your suite. coverage
ran the lanes moments earlier on that tree, and verify parses those artifacts rather than
running the whole suite a second time for the same numbers.
The other gate that judges a delta is the portable baseline in Route 4:
commit crapkit-baseline.tsv on the default branch and run crapkit verify --baseline-tsv crapkit-baseline.tsv in a step of your own. It needs no second lane run, and it needs
someone to keep that file current.
The comment is posted with gh api and the job's own GITHUB_TOKEN, which needs
pull-requests: write. Two things it cannot do: a pull request from a fork gets a
read-only token, so the POST is a 403 there, and a self-hosted runner without the gh CLI
on PATH fails that step. Both leave the rendered text in the job log.
Subcommands
Every subcommand takes --repo PATH (default .), and the flag goes after the
subcommand. claude-hook is the one exception: it has no --repo, because it takes its
root from the file named in the hook payload it reads.
$ crapkit worklist --repo /path/to/repo --scope util --top 1
worklist @ a7c5c85ac37 (run 1, floor ccn>=5, churn 12mo) — 1 active, 0 dormant
risk 5.4 ccn 5 ( 5 std) 5c/1a w 1.08 util/stats.py:1 bucket( value , low , high )Before it, argparse reads the path as the subcommand name and exits 2 without ever
mentioning --repo:
$ crapkit --repo /path/to/repo worklist --top 1
crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from 'inventory', 'coverage', ...)--json prints one sorted-keys JSON object on stdout, always carrying a schema field.
Command | What it does |
| Sniffs tracked source into per-directory scopes, writes a self-validated starter |
| 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 |
| One lizard pass over every in-scope file into a SQLite snapshot run, cached by content hash. |
| Runs the lanes, joins branch coverage onto a fresh inventory, writes a scored run. A failed lane is recorded, not fatal: its scopes fall back to |
| The full verdict against the trusted baseline: gate on touched functions, ratchet, no new test failures, optional diff-coverage ceiling. The three baseline selectors are mutually exclusive; |
| The risk map: every admitted function ranked by |
| The actionable queue as JSON, with churn, budget estimates and uncovered lines. Same run and same admission floor as |
| The open claims, and the way to hand one back without waiting for a verify. |
| The start-editing packet for one function: its own |
| A function's score across runs plus its mark. |
| Fresh complexity for named files over the latest run's stale coverage, joined by name. Advisory: it writes no run. |
| 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. |
| Run history, and retention. |
| The override audit trail: who granted what, when, and why. |
| 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 |
| The delta between the two newest runs with identical lane sets. Silent when nothing changed. |
| One self-contained HTML page written to |
| Near-duplicate functions by normalized line shingles with containment scoring. Defaults: |
| File pairs that keep landing in the same commits. Defaults: |
| Diff-scoped mutation testing: flips comparisons, boundary shifts, boolean connectives and boolean literals on changed lines, runs |
| Runs each owning scope's |
| The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
| 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 |
| Rescores tracked files as they change (mtime polling, default 2s, subprocess-isolated so a half-saved syntax error never kills the watcher). |
| 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 |
| A dependency-free stdio MCP server (newline JSON-RPC 2.0) exposing nine read-only tools. Every tool shells to the CLI's own |
Reading the output
Flags: why a coverage number is missing
Flag | Meaning | Scored |
| A lane artifact spoke about this function. | Real |
| A lane covers the scope, but its artifact is silent on this function, which normally means no test imports the file. |
|
| No lane's |
|
| The scope sets |
|
The coverage summary counts all four as measured / untested / no_lane / cc_only.
Remedy: what to do about it
Remedy | Condition | Action |
|
| Split it. No amount of coverage clears this. |
|
| Cover the branches. |
|
| Nothing. |
Grade and CRAP load
The grade is the share of functions over their ceiling: A+ at exactly zero, A under
2%, B under 5%, C under 10%, D under 20%, F at 20% or more. crap_load beside it
is the plain sum of every function's CRAP score, so it moves when a function gets better
even if the letter does not.
Risk: what ranks the worklist
risk = ccn * churn weight. The weight is a time-weighted sum over the file's commits in
the churn window: each commit contributes a logistic weight rising to 0.5 for the newest
commit in the log and falling to near zero for the oldest, so five edits last month
outrank fifty from two years ago. The window anchors on the newest commit, never on the
wall clock, so a fixed tree ranks identically forever.
Age is not the input, position in the log is. A log whose commits all share one timestamp
reads 0.0 everywhere, which is why a one-commit repo shows risk 0.0 on every row and
falls back to ccn order. Commits minutes apart already rank. This repo was eight commits
old, all made the same day:
$ crapkit worklist --scope util
worklist @ a7c5c85ac37 (run 1, floor ccn>=5, churn 12mo) — 3 active, 0 dormant
risk 5.4 ccn 5 ( 5 std) 5c/1a w 1.08 util/stats.py:1 bucket( value , low , high )
risk 4.5 ccn 9 ( 9 std) 1c/1a w 0.50 util/curve.py:1 curve( scores , mode , floor , ceiling , skip_none )
risk 4.3 ccn 4 ( 4 std) 5c/1a w 1.08 util/stats.py:13 spread( values , cap ) okbucket at ccn 5 outranks curve at ccn 9 because five commits touched it and one
touched curve. That is the whole point of weighting by churn. spread carries the ok
marker: already at or under its ceiling, listed anyway, and next-item would not hand it
out.
The list splits in two: active (files with commits in the window) and dormant
(zero churn, kept out of the queue but counted). Two rules reach under the
worklist_floor. A file whose churn weight sits in the top 10% is promoted down to ccn 3,
which is why spread appears above at ccn 4. And a function over its ceiling is admitted
whatever its ccn, so the floor can never hold back debt.
The trusted baseline
Every verify measures the working tree against one earlier run, the trusted
baseline. crapkit runs list marks which one that is today.
Which runs qualify. A coverage run, or a verify that passed. A failed verify
never qualifies, and neither does a partial run (a lane failed, so some scope fell back
to no-lane) nor a hook override record, which carries no scored rows at all. In runs list, verdict=- marks a run that produces no verdict rather than one that failed: only
verify renders a verdict. Four readers ask this one question and get this one answer: the
baseline pick here, ratchet seed, prune, and the tighten damping that compares a mark
against the same commit's previous run. A mark can no longer be signed off a run verify
refused.
What advances it. Any qualifying run. coverage writes one wherever HEAD is, so a
dashboard cron advances the baseline exactly as CI does. A passing verify advances it
and tightens the ratchet on the way.
The taint rule. A failed verify recorded findings against a tree. Until some
verify passes, runs made after that failure do not become the baseline: choosing one
would move the comparison point past the findings, the flagged function would stop
counting as touched, and nothing would look at it again. verify says which run it
refused and falls back to the newest run in front of the failure.
$ crapkit runs list
run 1 @ 88012a148f6 2026-08-23T09:27:46Z coverage verdict=- lanes=py baseline
run 2 @ 803bdde8556 2026-08-23T09:27:53Z verify verdict=FAILED lanes=py
run 3 @ 803bdde8556 2026-08-23T09:28:02Z coverage verdict=- lanes=py
$ crapkit verify
warning: run 3 is not the baseline: verify run 2 FAILED with 1 finding(s) and no passing verify has cleared it since — measuring against run 1 @ 88012a148f6 instead, so those findings stay visible. Fix them, or pass `--baseline 3` to accept the newer run deliberately.
verify FAILED @ d89068de7f3 vs baseline 88012a148f6 (2 changed files)
GATE crap 72.0 ccn 8 cov 0% calc/legacy.py:7 legacy_router( a , b , c , d , e ) -> decompose
findings: 1 committed / 0 dirty (uncommitted edits and untracked files)Run 3 is a coverage run somebody took on the tree run 2 refused, and it scores the same
ccn-8 function. Without the rule it would have become the baseline, legacy_router would
have stopped being a touched function, and that gate line would never print again.
The escape, twice. Fix the findings and let a verify pass, which clears the taint
for good. Or accept the newer run on purpose with verify --baseline 3: an explicit id
bypasses the rule, and the run history records which run the verdict used. Nothing here
touches a repo that has never run verify: with no failure to protect, coverage alone
always advances the baseline.
When the id you pass cannot serve. A --baseline ID naming a real run that is not a
candidate says which run it is, why, and which ones can:
$ crapkit verify --baseline 3
crapkit: run 3 is an inventory run (no coverage was measured) and cannot serve as a baseline; trusted runs: 1, 2; pass `--baseline 2` for the newestExit codes
Code | Meaning |
0 | OK. For |
1 | Overloaded. Three unrelated things, listed below the table. |
2 | Usage error from argparse: unknown flag, missing positional. Raised before crapkit's own error handling. |
3 | Config error: |
4 | Git error: not a repository, a baseline commit rewritten out of the history. |
5 | Tool error: lizard not importable, a lane that produced no artifact, one that measured a different tree, one that measured this tree and reported it in absolute paths (the join is root-relative, so those match nothing either; the refusal names the runner's own switch, |
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 |
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. |
8 | New test failures against the baseline run. Failures the baseline already had do not count. |
9 | Diff-coverage ceiling breached: |
Exit 1 means one of three things
CI cannot tell a crash from a clean policy verdict on the code alone. Which one you got depends on the command:
Command | What exit 1 means |
| A |
| The debt policy was breached. Also a verdict. |
anything else | An unexpected error: "no snapshot yet, run |
verify reports the first of 6, 7, 8, 9 that fires, in that order. A gate violation
and a ratchet regression together report 6. A run that takes any of them fails, so it
neither advances the baseline nor tightens the ratchet, exit 9 included.
Quickstart: Python
A repo with calc/grade.py, tests/test_grade.py, and a pyproject.toml. Commit first;
crapkit reads git ls-files. Install the coverage plugin first, because the lane init
writes runs pytest --cov and those flags come from pytest-cov:
pip install pytest-cov(pip install "crapkit[py]" pulls both at once when crapkit shares the suite's venv.)
If your suite drives its own CLI through subprocess.run, add [tool.coverage.run] patch = ["subprocess"] to pyproject.toml and keep coverage>=7.10.6: pytest-cov 7.0.0
dropped subprocess measurement, so without that key every entry point scores 0% and nothing
warns. docs/lanes.md has the whole rule.
1. Scaffold the config
$ crapkit init
wrote crapkit.toml with 1 scope(s): calc
detected 1 lane(s) from this repo's own files: py — next: run `crapkit coverage`
added to .gitignore: .crapkit/, .coverage, __pycache__/init sniffs tracked source into one scope per top-level source directory, and detects a
coverage lane from what the repo already has: a pytest marker file (pyproject.toml,
pytest.ini, setup.cfg) writes a live [[lane]], and so does a test script or
vitest/jest in package.json. A lockfile beside them names the environment: uv.lock,
poetry.lock, pdm.lock or Pipfile.lock makes the lane uv run python -m pytest … (and
the matching run for the rest), because a bare python binds to whichever venv the shell
has active rather than the one the repo pins — see
The interpreter a lane binds to. Whatever
it detects, it also leaves commented templates for the runners it did not find, and those
carry the same launcher, so uncommenting one cannot hand the bare python back. Every lane
it writes reports into .crapkit/cov/, which is why the .gitignore list is so short: see
Where artifacts live.
[crapkit]
target = 6
[[scope]]
name = "calc"
paths = ["calc"]
languages = ["python"]
[exclude]
globs = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/vendor/**", "**/*.test.*", "**/*.spec.*", "**/test_*.py", "**/*_test.py", "**/conftest.py", "node_modules/**", "dist/**", "build/**", "vendor/**", "*.test.*", "*.spec.*", "test_*.py", "*_test.py", "conftest.py", "*_test.go", "**/*_test.go", "*.config.ts", "*.config.js", "*.config.mts", "**/*.config.ts", "**/*.config.js", "**/*.config.mts"]
[[lane]]
name = "py"
command = "python -m pytest --cov --cov-branch --cov-report=json:.crapkit/cov/py.json --junitxml=.crapkit/cov/junit-py.xml --continue-on-collection-errors"
artifact = ".crapkit/cov/py.json"
results_artifact = ".crapkit/cov/junit-py.xml"
parser = "coveragepy"
scopes = ["calc"]
# Declare one [[lane]] per coverage command, then run `crapkit coverage`.
# [[lane]]
# name = "js"
# command = "npx vitest run --coverage --coverage.reportsDirectory=.crapkit/cov/js --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml"
# artifact = ".crapkit/cov/js/coverage-final.json"
# results_artifact = ".crapkit/cov/js/junit.xml"
# parser = "istanbul"
# scopes = ["<your-scope>"]
# `crapkit test-scoped FILES` runs one command per scope, with {files}
# replaced by that scope's files, each quoted.
[crapkit.scoped_tests]
calc = "python -m pytest {files} -q -p no:cacheprovider"The last block is the one an agent loop needs. crapkit test-scoped exits 3 for a file
whose scope declares no template, and AGENTS.md
makes it step 4 of the burn-down loop. Every key is in
docs/configuration.md.
2. Check the config against the repo
$ crapkit doctor
ok config keys all recognized
ok scope 'calc': 1 file
ok every tracked source file belongs to a scope
ok 1 lane(s) declared
ok lizard 1.24.0
doctor: no problems founddoctor prints one line per check and exits 1 only on a FAIL. WARN and note report
and exit 0.
3. Score the repo, and read the queue
$ crapkit coverage
run 1 @ fae4db93108: 2 functions scored — 2 measured / 0 untested / 0 no-lane / 0 cc-only, 1 over target 6, CRAP load 41.0, grade F
$ crapkit worklist
worklist @ fae4db93108 (run 1, floor ccn>=5, churn 12mo) — 1 active, 0 dormant
risk 0.0 ccn 14 ( 14 std) 1c/1a w 0.00 calc/grade.py:7 classify( score , attempts , late , bonus )Columns: risk, ccn with the standard-only ccn in parentheses,
<commits>c/<authors>a in the churn window with w<weight>, path:line, the function's
long name, then a marker on rows the burn-down queue will not hand out (ok, no-lane).
worklist is the risk map, not a to-do list. It ranks finished rows too, so it does
not empty when the burn-down does. next-item is the other view of that run: it drops the
no-lane rows, ranks by crap, and its empty: true is the stop condition.
4. Take the top item
$ crapkit next-item
{"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": 8, "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}remedy: "decompose", est_splits: 3 (this needs roughly three pieces to fit under 6),
and uncovered_lines naming the ten lines no test walks. handle is the name form to
pass back, and stale: false says the run still describes HEAD. Every field is in
docs/agent-json.md.
5. Seed the ratchet
Arm the debt gate before fixing anything. ratchet seed records every over-target
function at its current score, and from then on nothing may get worse.
$ crapkit ratchet seed
crapkit-ratchet.tsv: added 1, tightened 0 — 1 mark(s) vs run 1 (fae4db93108)
$ git add crapkit.toml crapkit-ratchet.tsv .gitignore && git commit -m "adopt crapkit"6. Fix it and verify
Extract until every piece sits at or under the ceiling. Here classify became
_validate, _adjusted, _band and a classify that only sequences them, with the
table of cases pushed into parametrized tests. Commit the fix, then:
$ crapkit verify
verify OK @ 8d10c13303d vs baseline fae4db93108 (5 changed files)
$ crapkit coverage
run 3 @ 8d10c13303d: 5 functions scored — 5 measured / 0 untested / 0 no-lane / 0 cc-only, 0 over target 6, CRAP load 19.0, grade A+CRAP load 41.0 to 19.0, grade F to A+. verify reruns the lanes and checks three things
against the trusted baseline: every function the diff touched sits at or under its
ceiling, no marked function got worse, and no test that passed in the baseline fails now.
Exit 0 advances the baseline and tightens crapkit-ratchet.tsv in place, so the repaid
mark leaves the file: follow up with git commit -am "ratchet: classify repaid". The full
mark lifecycle is in docs/ratchet.md.
crapkit next-item now comes back empty: true with a reasons object saying which
ending you got. That is most of the stop condition, not all of it:
AGENTS.md states the whole rule and reads the rest of
reasons.
Quickstart: TypeScript
A vitest repo with src/grade.ts and test/grade.test.ts.
1. Scaffold the config
$ crapkit init
wrote crapkit.toml with 1 scope(s): src
detected 1 lane(s) from this repo's own files: js — next: run `crapkit coverage`
added to .gitignore: .crapkit/The lane init wrote is
npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml.
It reads vitest's json reporter from .crapkit/cov/js/coverage-final.json; the
reportsDirectory flag is what keeps that report out of your root. The junit half is the
lane's results_artifact, which the crashed-worker and no-new-failures checks read; both
reporters are named because --reporter=junit alone would replace the console output you
watch the suite through. Anything that produces
an istanbul coverage-final.json works; see docs/lanes.md for the
jest and pytest recipes, a package
one directory down, and a
crapkit root below the repo top.
2. Install a coverage provider
This is the step that stops most TypeScript users. vitest ships no coverage provider
by default. Without one, init and doctor are both happy and coverage dies with
exit 5:
$ crapkit coverage
crapkit: lane 'js' FAILED: lane 'js' produced no artifact at .crapkit/cov/js/coverage-final.json (command exit 1); full log: /repo/.crapkit/lane-js.log; last output: $ npm run test -- --coverage --coverage.reportsDirectory=.crapkit/cov/js --coverage.reportOnFailure --reporter=default --reporter=junit --outputFile=.crapkit/cov/js/junit.xml
MISSING DEPENDENCY Cannot find dependency '@vitest/coverage-v8'
(exit 1)
crapkit: every lane failed (1 of 1); the errors are aboveThat failure writes no run. Every lane failed, so coverage exits before it opens a
store: there is no .crapkit/crap.sqlite yet and the run ids below still start at 1.
Install the provider, and pin the major yourself. Unpinned, npm resolves the newest provider against your older vitest and refuses the tree:
npm i -D "@vitest/coverage-v8@<your vitest major>"Question | Answer |
Which provider? | Either works. |
Which crapkit parser? | Both feed |
Which version? | The provider's major has to match vitest's. On vitest 2 that is |
The artifact crapkit wants is coverage-final.json, written by vitest's json coverage
reporter, which is on by default. If your vitest config sets coverage.reporter
explicitly, keep "json" in the list.
vitest writes no coverage report at all when the run fails. The lane init wrote
already carries --coverage.reportOnFailure, so a red test still produces the artifact.
If you write the lane by hand, or you would rather keep the switch beside your other
coverage settings, coverage.reportOnFailure = true in the vitest config does the same
job; either one is enough. The full block is in
docs/lanes.md.
3. Score the repo
$ crapkit coverage
run 1 @ 8bfbe613fcd: 2 functions scored — 2 measured / 0 untested / 0 no-lane / 0 cc-only, 1 over target 6, CRAP load 56.68, grade F
$ crapkit worklist
worklist @ 8bfbe613fcd (run 1, floor ccn>=5, churn 12mo) — 1 active, 0 dormant
risk 0.0 ccn 15 ( 15 std) 1c/1a w 0.00 src/grade.ts:8 classify ( row Row )classify is ccn 15 against a ceiling of 6: one function holding the late-and-retry
penalty, the letter bands, the demotion rule and the null case.
4. Seed the ratchet and commit
ratchet seed records every over-target function at the score it has today, so nothing
can get worse while you burn this one down.
$ crapkit ratchet seed
crapkit-ratchet.tsv: added 1, tightened 0 — 1 mark(s) vs run 1 (8bfbe613fcd)
$ git add crapkit.toml crapkit-ratchet.tsv .gitignore && git commit -m "adopt crapkit"5. Fix it
Above the ceiling, coverage cannot help, so classify gets split rather than tested.
penalty, band and demote come out as their own exported functions, and classify
keeps the null case and the bonus:
export function classify(row: Row): string {
if (row.score === null) {
return "N/A";
}
let score = row.score - penalty(row.attempts, row.late);
if (row.bonus && score < 90) {
score += 3;
}
return demote(band(score), row);
}rescore --gate judges that edit on complexity alone, before the slow step:
$ crapkit rescore src/grade.ts --gate
rescore vs run 1 @ 8bfbe613fcd (coverage STALE, complexity fresh)
ccn cov crap remedy function
5 0% 30.0 add-tests src/grade.ts:22 band ( score )
5 0% 30.0 add-tests src/grade.ts:38 demote ( letter , row Row )
4 0% 20.0 add-tests src/grade.ts:8 penalty ( attempts , late )
4 45% 6.7 add-tests src/grade.ts:48 classify ( row Row )
4 75% 4.2 ok src/grade.ts:59 average ( scores Array )Exit 0: every piece is at or under 6. The crap column is loud because its coverage half
is still run 1's, from before three of those functions existed, and add-tests is the
literal instruction for step 6.
6. Cover the new pieces
rescore --gate passed on complexity, not on coverage. penalty, band and demote are
three functions no test has ever called, so each gets a table test:
describe("band", () => {
it.each([[95, "A"], [85, "B"], [75, "C"], [65, "D"], [10, "F"]])(
"scores %i as %s", (score, expected) => expect(band(score)).toBe(expected));
});Run the suite once before the slow step:
$ npx vitest run
Test Files 1 passed (1)
Tests 21 passed (21)Skip this step and step 7 fails rather than passes. Run on a copy of this repo with step 6
left out, verify reruns the lanes against the real tree and three functions the old
suite never called come back over the ceiling:
$ crapkit verify
verify FAILED @ 0296156ff21 vs baseline 0e646697946 (1 changed files)
GATE crap 17.8 ccn 5 cov 20% src/grade.ts:38 demote ( letter , row Row ) -> add-tests
GATE crap 12.4 ccn 5 cov 33% src/grade.ts:22 band ( score ) -> add-tests
GATE crap 10.8 ccn 4 cov 25% src/grade.ts:8 penalty ( attempts , late ) -> add-tests7. Verify
$ crapkit verify
verify OK @ 2af3433d979 vs baseline 8bfbe613fcd (3 changed files)
$ crapkit coverage
run 3 @ 2af3433d979: 5 functions scored — 5 measured / 0 untested / 0 no-lane / 0 cc-only, 0 over target 6, CRAP load 22.0, grade A+CRAP load 56.68 to 22.0, grade F to A+, and the mark seeded in step 4 is gone: verify
dropped it once classify scored under the ceiling, rewriting the tracked
crapkit-ratchet.tsv in place. Commit it with your change. Marks only ever fall.
A verify may also print warning: N changed line(s) have no coverage above its verdict;
that block is advisory unless diff_uncovered_max is set
(docs/configuration.md).
Documentation
Page | Covers |
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, self-contained, so it opens straight from a clone. | |
The judgment layer over the quickstarts: scope granularity, exclude vs lane, scoped_tests wiring, the first-verify taint hazard. | |
Every | |
The lane model, vitest and jest and pytest recipes, artifact reuse, flake retest, containers. | |
Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. | |
The machine surface: | |
Where crapkit sits next to radon, xenon, wily, coverage.py and SonarQube, and how they run together. | |
The burn-down loop an agent runs, and the rules for changing crapkit itself. | |
The Claude Code plugin: three skills, the read-side MCP server, and the advisory PostToolUse hook. |
crapkit.schema.json is the authority on the config file shape.
Development
pip install -e ".[dev]"
pip install pytest-xdist
git config core.hooksPath git-hooks
python -m pytest -qpytest-xdist is not optional: tests/fixtures/mini_repo declares a lane that shells out
to pytest ... -n 2, and without it that subprocess dies on an unrecognized -n. The
git config line arms the complexity gate on your own commits. Same steps, with what each
one buys, in CONTRIBUTING.md.
License
MIT. See LICENSE.
Available Tools
9 toolsbriefARead-onlyIdempotent
One function's whole context in one call: scored row, ratchet mark, uncovered lines, duplication twins, churn and change-coupling partners. The deepest single read; explain is the slimmer history-only view.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | the bare identifier (classify, or route for a Rust `route cmd : & Cmd`) or the whole long_name next_item printed (classify( score , late )); both resolve, exact match first | |
| path | No | repo-relative source file | |
| repo | No | path to the scored repo's root (default: the repo the server was started in) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so the safety profile is covered. The description adds genuine behavioral context by stating the tool is a composite read and enumerating its returned facets. This goes beyond the annotations without contradicting them, though it does not discuss cost, pagination, or response structure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, both dense and purposeful. The first sentence front-loads the core value proposition and the second gives a precise sibling contrast. There is no filler or repetition of schema content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the burden of explaining what the call returns, and it does so by listing the major data categories. The terms 'ratchet mark' and 'change-coupling partners' rely on domain context that sibling tools help establish, but the overall picture is sufficiently complete for selection and invocation. Optional parameters are not mentioned, though the schema already documents them.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3 and the schema already explains all three parameters clearly. The description adds no parameter-specific details, which is acceptable because the parameter descriptions are already strong. It does not need to compensate for any schema gaps.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool as a single-call read that gathers a function's full context, enumerating the included data: scored row, ratchet mark, uncovered lines, duplication twins, churn, and change-coupling partners. It lacks an explicit imperative verb like 'retrieve' or 'get', but 'the deepest single read' communicates the action and scope effectively. It also distinguishes itself from the sibling tool 'explain', helping an agent understand its unique role.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear usage context by calling this 'the deepest single read' and positioning 'explain' as 'the slimmer history-only view.' This implies an agent should choose this tool when comprehensive context is needed and 'explain' when only history matters. It does not explicitly mention exclusions relative to other siblings like 'duplication' or 'coupling', but the selected contrast is useful and concrete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
couplingARead-onlyIdempotent
File pairs that keep landing in the same commits: dependencies no import statement reveals. Use it to learn what else an edit usually drags along before touching a file.
| Name | Required | Description | Default |
|---|---|---|---|
| repo | No | path to the scored repo's root (default: the repo the server was started in) | |
| min_support | No | minimum shared commits before a pair counts (default 5) | |
| min_confidence | No | minimum P(pair changes together), 0 to 1 (default 0.5) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as read-only and idempotent, and the description goes beyond this by explaining the behavioral basis: the tool relies on commit co-occurrence rather than static import statements. This adds meaningful context about how the analysis works.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: the first sentence explains the core concept, and the second gives the practical use case. It is economical, though the phrase 'dependencies no import statement reveals' is slightly awkward and could be clearer.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only tool with three optional, fully documented parameters and no output schema, the description provides enough context for an agent to understand what it does, when to use it, and what kind of results to expect (file pairs). It does not detail the output format, but the concept is straightforward.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema fully documents repo, min_support, and min_confidence. The description does not add parameter-specific guidance, but the baseline of 3 is appropriate since the schema carries the load.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's core function: identifying file pairs that frequently co-occur in the same commits, revealing implicit dependencies not visible via imports. It is clear enough to distinguish it from a generic 'list files' tool, though it does not explicitly name sibling alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides a concrete usage context: 'Use it to learn what else an edit usually drags along before touching a file.' This tells an agent when to invoke it, though it does not mention when not to use it or point to alternative tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
doctorARead-onlyIdempotent
Config and repo agreement check: typo keys, empty scopes, missing lane cwds, unresolvable runners. Run it first when any other tool answers strangely or an expected file is missing from the ranking.
| Name | Required | Description | Default |
|---|---|---|---|
| repo | No | path to the scored repo's root (default: the repo the server was started in) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so the description does not need to restate safety. It adds value beyond annotations by describing what the check covers and framing it as a first-line diagnostic. No contradiction with the read-only annotation; the tool plainly inspects configuration rather than mutating it.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences deliver the core function, the specific checks performed, and the precise usage context. There is no filler or redundant restatement of the tool name. The description is front-loaded and every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only diagnostic with one optional parameter, the description is complete enough: it names the exact problems it detects and when to run it. It does not describe the return format, but the absence of an output schema and the nature of the tool make this a minor gap rather than a critical omission.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single parameter 'repo' has full schema description coverage ('path to the scored repo's root (default: the repo the server was started in)'). The tool description does not add parameter-level detail, but with 100% schema coverage, the schema already carries the necessary meaning. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool as a diagnostic check for config and repo agreement, enumerating specific failure modes it detects (typo keys, empty scopes, missing lane cwds, unresolvable runners). This is far more specific than a generic tool name and gives an agent a concrete model of the tool's function, distinguishing it from analysis-oriented siblings like coupling or duplication.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The second sentence gives explicit trigger conditions: 'Run it first when any other tool answers strangely or an expected file is missing from the ranking.' This tells the agent exactly when to invoke doctor versus other tools, which is ideal usage guidance for a diagnostic tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
duplicationARead-onlyIdempotent
Near-duplicate function pairs by normalized line shingles, with the containment percent. Use it before a refactor so twins get folded together instead of one copy getting fixed alone.
| Name | Required | Description | Default |
|---|---|---|---|
| repo | No | path to the scored repo's root (default: the repo the server was started in) | |
| similarity | No | containment threshold, shared over smaller, 0 to 1 (default 0.8) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, covering the safety profile. The description adds useful behavioral detail about how it works: normalized line shingles and a containment percentage. There is no contradiction with the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two focused sentences with no filler. The first sentence states the output and method; the second provides a concrete use case. It is easy to scan and every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only analysis tool with two optional parameters and no output schema, the description is complete enough: it says what the tool finds, how it computes similarity, what metric is returned, and when to use it. No critical information is missing for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, with both parameters fully documented including the default and range for 'similarity.' The description reinforces the 'containment' concept but adds no new parameter-level detail beyond the schema, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool's subject ('near-duplicate function pairs') and its method ('normalized line shingles'), plus the metric it returns ('containment percent'). It lacks an explicit verb like 'find' or 'list,' but the intent is unambiguous. It does not explicitly differentiate from sibling tools, but the narrowly defined purpose makes confusion unlikely.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit guidance on when to use the tool: 'Use it before a refactor.' It also explains the benefit, folding twins together instead of fixing one copy alone. It does not name alternatives or exclusions, but the use case is clearly stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
explainARead-onlyIdempotent
One function's score trajectory across runs plus its ratchet mark. Use it to see whether a function is improving or decaying; brief adds the full context around the newest score.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | the bare identifier (classify, or route for a Rust `route cmd : & Cmd`) or the whole long_name next_item printed (classify( score , late )); both resolve, exact match first | |
| path | No | repo-relative source file | |
| repo | No | path to the scored repo's root (default: the repo the server was started in) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so the safety profile is established. The description adds useful output context (trajectory, ratchet mark, and that brief adds fuller context), but it does not go deeper into behavioral details such as how runs are ordered or what exactly the ratchet mark represents.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no filler. The first sentence defines the output scope, and the second provides usage guidance plus the sibling alternative. Every sentence earns its place and the key information is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only lookup tool, the description covers what the tool returns, why to use it, and how it differs from a related sibling. The params are fully documented in the schema and the annotations cover side effects. It could name the return format more explicitly, but an agent has enough to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the parameter descriptions already document name, path, and repo well, including matching behavior for 'name' and defaults for 'repo'. The tool description itself adds no parameter-specific semantics, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific resource ('one function's score trajectory across runs plus its ratchet mark') and a clear use ('see whether a function is improving or decaying'). It also distinguishes itself from the sibling 'brief' by noting that brief adds full context around the newest score, so an agent can tell them apart.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit guidance on when to use the tool ('Use it to see whether a function is improving or decaying') and points to 'brief' as the alternative for full context. It does not discuss other siblings, but the main decision point is covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
next_itemARead-onlyIdempotent
The highest-risk function to refactor next, as one work packet: score, uncovered lines, effort estimate and the commands that verify the fix. Reach for it when you want one item to act on; worklist is the same ranking as a survey.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | return the next N packets instead of one (>= 1, default 1) | |
| repo | No | path to the scored repo's root (default: the repo the server was started in) | |
| exclude | No | skip items whose path or function name contains this fragment (repeatable) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, covering safety. The description adds selection logic (highest-risk) and return contents (score, uncovered lines, effort estimate, verify commands), which is useful context beyond annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two concise sentences with no filler, front-loading the core purpose and the routing to the alternative. Efficient and well-structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
All three parameters are optional and fully documented in the schema. There is no output schema, but the description explains the return contents, making the tool fully invocable without missing information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with clear descriptions for top, repo, and exclude. The description adds no parameter-specific guidance, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific action (return next item) and resource (highest-risk function to refactor), and describes the packet contents. Explicitly contrasts with sibling worklist, so it is clearly distinguishable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit guidance: 'Reach for it when you want one item to act on' and names the alternative 'worklist is the same ranking as a survey.' This tells an agent when to use this versus siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ratchet_reportARead-onlyIdempotent
The debt burn-down: every open ratchet mark with its age, plus repayment velocity from git history. Use it to see whether marked debt is being paid down or piling up.
| Name | Required | Description | Default |
|---|---|---|---|
| repo | No | path to the scored repo's root (default: the repo the server was started in) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With readOnlyHint and idempotentHint already declared, the description adds useful behavioral context by specifying the data source (git history) and the report contents (open ratchet marks, ages, repayment velocity). This is especially valuable because there is no output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences deliver the report's substance and its intended use without any filler. The key concept, 'debt burn-down,' is front-loaded, and every phrase earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-optional-parameter read-only tool, the description and schema together provide output contents, data source, and usage purpose. Nothing essential is missing for an agent to decide when to call this tool and what it will receive.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the single repo parameter is already fully documented with its default behavior. The description adds no additional parameter semantics, so the baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly identifies the tool as a debt burn-down report covering open ratchet marks with their age and repayment velocity from git history. The final sentence states its purpose explicitly: 'Use it to see whether marked debt is being paid down or piling up.' This subject matter distinguishes it from siblings like coupling, duplication, and worklist.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives a clear intended use case: determining whether marked debt is being paid down or accumulating. It does not name alternatives or provide when-not-to-use guidance, so it misses the explicit exclusion that would earn a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
runsARead-onlyIdempotent
Run history, newest first: id, kind, verdict, commit and lane set. Use it to date the store or to check which commit the other tools answer from.
| Name | Required | Description | Default |
|---|---|---|---|
| repo | No | path to the scored repo's root (default: the repo the server was started in) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint, so the safety profile is covered. The description adds behavioral context beyond annotations by specifying ordering ('newest first') and the exact fields returned, which helps the agent know what to expect. It does not contradict the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two compact sentences contain the essential information: what the tool returns and when to use it. There is no filler or repetition of schema/annotation data. The field list is front-loaded, and the usage guidance is immediately actionable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read-only list tool, the description is nearly complete: it lists the output fields, states ordering, and explains the tool's purpose in relation to the other tools. There is no output schema, but the description supplies the key return information. Minor ambiguity remains around the meaning of 'kind' and 'verdict' values, but it does not block correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The only parameter, repo, is fully described in the schema with its default behavior. Schema description coverage is 100%, so the description does not need to add parameter semantics. The tool description adds no extra parameter detail, but the baseline of 3 applies because the schema already carries the full burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool as a run-history listing tool and enumerates the returned fields: id, kind, verdict, commit, and lane set. It does not use an explicit verb like 'list', but the resource and scope are unmistakable. It also gives a purpose ('date the store', 'check which commit the other tools answer from') that differentiates it from the sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use it to date the store or to check which commit the other tools answer from' provides explicit, concrete use cases. It does not mention alternatives or when not to use it, but the context is clear enough for an agent to select it appropriately among siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
worklistARead-onlyIdempotent
The run's whole risk ranking: every function over its ceiling, then the queue under it ordered by ccn times recency-weighted churn. Use it to survey the repo; next_item hands out one packet from the top.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | cap the active list (default: the config's worklist_top) | |
| repo | No | path to the scored repo's root (default: the repo the server was started in) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so the description doesn't need to repeat those. The description adds useful behavioral context: the tool returns the entire risk ranking with a specific ordering rule and a queue structure. This is beyond what annotations convey, and there is no contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with no filler. The primary purpose is front-loaded, and the sibling reference is efficiently included in the second sentence. Every word contributes value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only, idempotent tool with two optional parameters and no output schema, the description covers the essential purpose, ordering, and usage context. It does not describe return format or limits, but given the annotations and schema, there are no critical gaps for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with both top and repo parameters fully described. The description itself does not add parameter-specific semantics beyond what the schema provides, so the baseline of 3 applies. It doesn't repeat or enhance parameter details.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states what the tool does: it provides the run's whole risk ranking, with a specific ordering (ccn times recency-weighted churn). It explicitly differentiates from the sibling next_item (
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description tells the agent to use it for surveying the repo and explicitly contrasts it with next_item. This gives concrete guidance on when to select this tool over at least one sibling, and the context (whole ranking vs one packet) is clear. It doesn't address all other siblings, but the primary alternative is covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
Most tools have clearly distinct purposes: worklist/next_item separate the survey view from the actionable packet, and brief/explain are differentiated by context depth versus trajectory. The two closely related pairs could cause minor confusion, but the descriptions resolve the boundaries well.
The tool names mix bare nouns (runs, brief, doctor), verbs (explain), compound nouns (worklist, coupling, duplication), and snake_case compounds (next_item, ratchet_report) with no consistent pattern. They are evocative and readable, but the naming conventions are not systematic enough to predict tool names from their function.
Nine tools is a well-scoped set for a code-risk and refactoring workflow. Each tool covers a distinct operational need without feeling padded or redundant.
The set covers the core workflow well: prioritized work items, full risk rankings, run history, per-function context and trajectory, config diagnostics, coupling, duplication, and debt reporting. Minor gaps exist around directly manipulating thresholds or updating state, but the surface is complete for survey-and-act refactoring.
Maintenance
Related MCP Connectors
AI-native git hosting — repos, PRs, issues, CI gates, and AI code review over MCP (60 tools).
Repo intel for AI coding agents: overview, PRs, contributors, hot files, CI, deps. Remote MCP.
GitHub repo maintainability verdicts—maintained, slowing, at-risk, abandoned—via MCP.
Repository evidence for agents before they adopt dependencies, enter codebases, compare, or merge.
Related MCP Servers
AlicenseNot gradedqualityCmaintenanceEnables access to Codacy's code quality platform through natural language, providing repository management, security analysis, pull request reviews, and local CLI-based code analysis. Supports comprehensive code quality monitoring including issues, coverage, security vulnerabilities, and technical debt assessment across organizations and repositories.47462MIT- AlicenseAqualityAmaintenancePHP static analysis MCP server with 11 tools for querying 60+ code quality metrics, detecting problems (God Class, dependency cycles, SOLID violations), analyzing dependencies, identifying refactoring priorities, and mapping test coverage — all from live analysis data.11289MIT
- AlicenseAqualityAmaintenanceDocGuard, an official, zero-dependency Node.js MCP server (npm: docguard-cli, docguard mcp) exposing 5 read-only doc-governance tools: guard, score, explain, verify-claims, diagnose.632927MIT
- AlicenseAqualityAmaintenanceDiffgate MCP server acts as a code review engine, enabling AI coding agents to analyze and validate code diffs before application. It enhances AI workflows by providing self-checking capabilities to optimise and secure code changes.71085Apache 2.0
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/JeanFrancoisGagne/crapkit'
If you have feedback or need assistance with the MCP directory API, please join our Discord server