crapkit 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. {crapkit-0.3.0/src/crapkit.egg-info → crapkit-0.4.0}/PKG-INFO +22 -13
  2. {crapkit-0.3.0 → crapkit-0.4.0}/README.md +21 -12
  3. {crapkit-0.3.0 → crapkit-0.4.0}/pyproject.toml +1 -1
  4. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/__init__.py +1 -1
  5. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/_pygdefer.py +6 -3
  6. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/analyze.py +162 -3
  7. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/__init__.py +32 -0
  8. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/admin.py +70 -0
  9. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/analyses.py +21 -8
  10. crapkit-0.4.0/src/crapkit/cli/claude_hook.py +307 -0
  11. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/parser.py +46 -5
  12. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/scoring.py +19 -3
  13. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/verifying.py +49 -1
  14. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/config.py +7 -1
  15. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/discover.py +22 -7
  16. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/doctor.py +55 -0
  17. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/hook.py +3 -26
  18. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/lizardcognitive.py +63 -8
  19. crapkit-0.4.0/src/crapkit/lizardpowershell.py +288 -0
  20. crapkit-0.4.0/src/crapkit/lizardrust.py +137 -0
  21. crapkit-0.4.0/src/crapkit/lizardshell.py +389 -0
  22. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/mutate.py +60 -1
  23. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/scaffold.py +6 -0
  24. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/universe.py +14 -0
  25. {crapkit-0.3.0 → crapkit-0.4.0/src/crapkit.egg-info}/PKG-INFO +22 -13
  26. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit.egg-info/SOURCES.txt +4 -0
  27. {crapkit-0.3.0 → crapkit-0.4.0}/LICENSE +0 -0
  28. {crapkit-0.3.0 → crapkit-0.4.0}/setup.cfg +0 -0
  29. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/__main__.py +0 -0
  30. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cache.py +0 -0
  31. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/churn.py +0 -0
  32. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/churn_cache.py +0 -0
  33. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/churn_log.py +0 -0
  34. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/_shared.py +0 -0
  35. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/queue.py +0 -0
  36. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/ratchet_cmds.py +0 -0
  37. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/cli/reports.py +0 -0
  38. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/coupling.py +0 -0
  39. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/coverage_istanbul.py +0 -0
  40. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/coverage_py.py +0 -0
  41. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/covstream.py +0 -0
  42. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/diffparse.py +0 -0
  43. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/digest.py +0 -0
  44. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/dup.py +0 -0
  45. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/errors.py +0 -0
  46. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/gitio.py +0 -0
  47. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/junitparse.py +0 -0
  48. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/lanes.py +0 -0
  49. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/mcp_server.py +0 -0
  50. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/merge.py +0 -0
  51. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/mutate_pool.py +0 -0
  52. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/override.py +0 -0
  53. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/packet.py +0 -0
  54. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/ratchet.py +0 -0
  55. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/ratchet_report.py +0 -0
  56. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/report.py +0 -0
  57. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/sarif.py +0 -0
  58. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/sarifio.py +0 -0
  59. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/score.py +0 -0
  60. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/snapshot.py +0 -0
  61. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/store.py +0 -0
  62. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/uncovered.py +0 -0
  63. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/verify.py +0 -0
  64. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/watch.py +0 -0
  65. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit/worklist.py +0 -0
  66. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit.egg-info/dependency_links.txt +0 -0
  67. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit.egg-info/entry_points.txt +0 -0
  68. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit.egg-info/requires.txt +0 -0
  69. {crapkit-0.3.0 → crapkit-0.4.0}/src/crapkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crapkit
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Deterministic CRAP-score framework: per-function complexity x coverage risk, worklists, ratchets, refactor verification
5
5
  Author: Jean-Francois Gagne
6
6
  License: MIT
@@ -28,11 +28,14 @@ Dynamic: license-file
28
28
 
29
29
  crapkit scores every function in your repo on complexity times uncovered risk, ranks the
30
30
  worst ones by how often the file changes, and blocks commits that add more. It reads
31
- TypeScript, TSX, JavaScript, Python, Swift and Go through [lizard](https://github.com/terryyin/lizard),
31
+ TypeScript, TSX, JavaScript, Python, Swift, Go, Rust, shell, PowerShell, C and C++,
32
+ Objective-C, Vue, Java and Zig through [lizard](https://github.com/terryyin/lizard),
32
33
  and joins per-function branch coverage from istanbul or coverage.py artifacts your own test
33
- command already produces. Swift and Go have no coverage parser: declare those scopes
34
- `coverage_optional` and they score on complexity alone. Every read-side command speaks JSON with a pinned schema, because
35
- half the callers are coding agents.
34
+ command already produces. Those two parsers are the whole list, so Swift, Go, Rust, shell,
35
+ PowerShell, C and C++, Objective-C, Java and Zig have no coverage to join: declare those
36
+ scopes `coverage_optional` and they score on complexity alone. Vue joins istanbul coverage
37
+ when your own vitest run reports on `.vue` files. Every read-side command speaks JSON with a
38
+ pinned schema, because half the callers are coding agents.
36
39
 
37
40
  ```
38
41
  CRAP = ccn^2 * (1 - cov)^3 + ccn
@@ -77,7 +80,7 @@ Check the install:
77
80
 
78
81
  ```
79
82
  $ crapkit --version
80
- crapkit 0.3.0
83
+ crapkit 0.4.0
81
84
  ```
82
85
 
83
86
  `python -m crapkit` works identically to the `crapkit` console script, and is what to use
@@ -473,11 +476,15 @@ without touching the repo-wide cache, and refuses the commit when a staged funct
473
476
  its scope ceiling. It needs no coverage data and no snapshot, so it costs the size of the
474
477
  commit, not the size of the repo.
475
478
 
476
- Two limits to know. The gate judges files a `[[scope]]` claims; a staged source file no
479
+ Three limits to know. The gate judges files a `[[scope]]` claims; a staged source file no
477
480
  scope claims is not gated, and the hook says so on stderr (`N staged file(s) belong to no
478
481
  scope and were not gated`) so the hole is visible the moment a new top-level directory
479
- appears. And git runs hooks outside your shell's activated venv: bare `python` must resolve
480
- to an interpreter that has crapkit installed, or use the absolute form
482
+ appears. A function the committed ratchet already carries a mark for is not gated either,
483
+ so touching signed debt does not refuse the commit; the hook reports the count on stderr
484
+ (`N staged function(s) carry a ratchet mark and were not gated`) and `crapkit verify` is
485
+ what fails a mark that rose. And git runs hooks outside your shell's activated venv: bare
486
+ `python` must resolve to an interpreter that has crapkit installed, or use the absolute
487
+ form
481
488
  (`exec /path/to/venv/Scripts/python -m crapkit hook-precommit`).
482
489
 
483
490
  ### Route 1: `.git/hooks/pre-commit` (local, not committed)
@@ -726,7 +733,8 @@ advances the baseline nor tightens the ratchet, exit 9 included.
726
733
  ## Subcommands
727
734
 
728
735
  Every subcommand takes `--repo PATH` (default `.`), and the flag goes **after** the
729
- subcommand:
736
+ subcommand. `claude-hook` is the one exception: it has no `--repo`, because it takes its
737
+ root from the file named in the hook payload it reads.
730
738
 
731
739
  ```
732
740
  $ crapkit worklist --repo /path/to/repo --scope util --top 1
@@ -747,7 +755,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
747
755
  | Command | What it does |
748
756
  |---|---|
749
757
  | `init` | Sniffs tracked source into per-directory scopes, writes a self-validated starter `crapkit.toml` whose lanes report into `.crapkit/cov/`, and appends `.crapkit/` plus each runner's own droppings to `.gitignore`. Writes a live `[[lane]]` when it can detect the test runner, otherwise a commented template. Refuses to clobber an existing config. |
750
- | `doctor [--show-files] [--json] [--tune]` | 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, lanes writing their artifacts at the repo root instead of under `.crapkit/` (WARN), committed hooks under `core.hooksPath` that are not executable in the index (WARN), directories whose functions are all `untested` while their tests exist (WARN), and scopes a lane measures with no `[crapkit.scoped_tests]` template behind them (WARN), which is the loop's step 4 with nothing to run. `--tune` prints suggested parallelism knobs and writes nothing. See [docs/agent-json.md](docs/agent-json.md#doctor---json). |
758
+ | `doctor [--show-files] [--json] [--tune] [--plugin-root PATH]` | Checks the config still describes the repo: unknown keys (with the accepted spellings), zero-file scopes, tracked source no scope claims, scopes no lane covers, lane cwds and commands that no longer resolve, lizard importable, oversized files, lanes writing their artifacts at the repo root instead of under `.crapkit/` (WARN), committed hooks under `core.hooksPath` that are not executable in the index (WARN), directories whose functions are all `untested` while their tests exist (WARN), and scopes a lane measures with no `[crapkit.scoped_tests]` template behind them (WARN), which is the loop's step 4 with nothing to run. `--tune` prints suggested parallelism knobs and writes nothing. `--plugin-root PATH` reads no repo at all: it checks an installed [plugin](plugin/) against this CLI, comparing `.claude-plugin/plugin.json`'s version against the running crapkit and every `--protocol` in `hooks/hooks.json` against the protocol `claude-hook` answers, one line per disagreement and silence when they agree. See [docs/agent-json.md](docs/agent-json.md#doctor---json). |
751
759
  | `inventory [--db PATH] [--export PATH] [--json]` | Two lizard passes over every in-scope file into a SQLite snapshot run, cached by content hash. `--db` is the only way to point crapkit at a store outside `.crapkit/`, and only this command accepts it. |
752
760
  | `coverage [--lane NAME] [--reuse-artifacts] [--reuse-unchanged] [--export PATH] [--sarif PATH] [--github] [--json]` | Runs the lanes, joins branch coverage onto a fresh inventory, writes a scored run. A failed lane is recorded, not fatal: its scopes fall back to `no-lane` and the run is typed `partial`, so it can never serve as a baseline. See [docs/lanes.md](docs/lanes.md). |
753
761
  | `verify [--baseline ID \| --base REF \| --baseline-tsv PATH] [--emit-baseline PATH] [--override REASON] [--reuse-artifacts] [--reuse-unchanged] [--sarif PATH] [--github] [--json]` | The full verdict against the trusted baseline: gate on touched functions, ratchet, no new test failures, optional diff-coverage ceiling. The three baseline selectors are mutually exclusive; `--baseline ID` also bypasses the taint rule ([The trusted baseline](#the-trusted-baseline)), and `--baseline-tsv` reads a commit-stamped file so a fresh clone verifies with no store. Findings a dirty tree produced are tagged `dirty` and counted apart. |
@@ -765,9 +773,10 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
765
773
  | `report [--out PATH]` | One self-contained HTML page written to `.crapkit/report.html` (or `--out PATH`, repo-relative), with the path printed on stdout. It renders what `worklist --json` and `trend --json` already answer at their defaults: the ranked worklist capped at `worklist_top`, the per-scope grades off the newest run, the trend series, and a banner naming every stale lane. It measures nothing, opens no network connection, and carries no per-function CRAP or coverage, because no repo-wide payload has them; each row prints the `crapkit explain` call that does. |
766
774
  | `duplication [--min-lines N] [--similarity F] [--top N] [--json]` | Near-duplicate functions by normalized line shingles with containment scoring. Defaults: `--min-lines 8`, `--similarity 0.8`, `--top 50`. `--top` truncates the list. |
767
775
  | `coupling [--min-support N] [--min-confidence F] [--top N] [--json]` | File pairs that keep landing in the same commits. Defaults: `--min-support 5` shared commits, `--min-confidence 0.5` max-direction ratio, `--top 50`. Bulk commits never couple pairs, and a young repo returns nothing at the default support. |
768
- | `mutate [--files F ...] [--max-mutants N] [--json]` | Diff-scoped mutation testing: flips comparisons, boundary shifts, boolean connectives and boolean literals on changed lines, runs `mutation_command` per mutant, lists survivors. `--files` replaces diff scope with the whole file. `--max-mutants` (default 100) caps the run and the cap warning goes to stderr only, so `mutants` in `--json` is the capped count. |
776
+ | `mutate [--files F ...] [--max-mutants N] [--json]` | Diff-scoped mutation testing: flips comparisons, boundary shifts, boolean connectives and boolean literals on changed lines, runs `mutation_command` per mutant, lists survivors. `--files` replaces diff scope with the whole file. `--max-mutants` (default 100) caps the run and the cap warning goes to stderr only, so `mutants` in `--json` is the capped count. Shell and PowerShell files are refused by name on stderr rather than mutated: `<` and `>` are redirections there, not comparisons. |
769
777
  | `test-scoped FILE ...` | Runs each owning scope's `[crapkit.scoped_tests]` template on the files (quoted, longest-prefix scope wins). A template with no `{files}` runs as written, which is how a scope whose tests live outside its own paths runs its whole suite. Exit code only; a nonzero runner exits 1. |
770
778
  | `hook-precommit` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
779
+ | `claude-hook [--protocol N]` | Reads one Claude Code PostToolUse payload from stdin and judges the file it edited: ccn against the scope ceiling, on functions the edit changed, minus functions a ratchet mark already covers. Advisory only: the edit has landed and nothing is blocked, and `hook-precommit` stays the enforcement point. Exit 2 with three lines on stderr is the only thing it ever says. No `crapkit.toml` above the edited file, an unscoped file, mid-rebase or mid-merge, a `--protocol` other than 1, source that parses to no functions, or any internal failure: exit 0, silence, empty stderr. Takes no `--repo` — the root is the first `crapkit.toml` above the edited file and the upward walk stops at a `.git` entry, so a worktree never borrows its parent's config. It opens no snapshot, writes nothing, and leaves stdout empty. |
771
780
  | `watch [--interval SECONDS] [--cycles N]` | Rescores tracked files as they change (mtime polling, default 2s, subprocess-isolated so a half-saved syntax error never kills the watcher). `--cycles N` polls exactly N times and exits 0; without it the loop runs until ctrl-c. |
772
781
  | `mcp` | A dependency-free stdio MCP server (newline JSON-RPC 2.0) exposing nine read-only tools. See [docs/agent-json.md](docs/agent-json.md#mcp-server). |
773
782
 
@@ -781,7 +790,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
781
790
  | [docs/ratchet.md](docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
782
791
  | [docs/agent-json.md](docs/agent-json.md) | The machine surface: `schema`, every payload field, real captured examples. |
783
792
  | [docs/adoption.md](docs/adoption.md) | The judgment layer over the quickstarts: scope granularity, exclude vs lane, scoped_tests wiring, the first-verify taint hazard. |
784
- | [skills/](skills/) | Agent skills shipped with the repo (`crapkit`, `crapkit-recover`, `crapkit-onboard`) — install by copying to your agent runtime's skills directory. |
793
+ | [plugin/](plugin/) | The Claude Code plugin: three skills (`crapkit`, `crapkit-recover`, `crapkit-onboard`), the read-side MCP server, and the advisory PostToolUse hook. Install with `claude plugin marketplace add JeanFrancoisGagne/crapkit` then `claude plugin install crapkit@crapkit`; other runtimes copy `plugin/skills/*` into their skills directory. |
785
794
 
786
795
  [crapkit.schema.json](crapkit.schema.json) is the authority on the config file shape.
787
796
 
@@ -2,11 +2,14 @@
2
2
 
3
3
  crapkit scores every function in your repo on complexity times uncovered risk, ranks the
4
4
  worst ones by how often the file changes, and blocks commits that add more. It reads
5
- TypeScript, TSX, JavaScript, Python, Swift and Go through [lizard](https://github.com/terryyin/lizard),
5
+ TypeScript, TSX, JavaScript, Python, Swift, Go, Rust, shell, PowerShell, C and C++,
6
+ Objective-C, Vue, Java and Zig through [lizard](https://github.com/terryyin/lizard),
6
7
  and joins per-function branch coverage from istanbul or coverage.py artifacts your own test
7
- command already produces. Swift and Go have no coverage parser: declare those scopes
8
- `coverage_optional` and they score on complexity alone. Every read-side command speaks JSON with a pinned schema, because
9
- half the callers are coding agents.
8
+ command already produces. Those two parsers are the whole list, so Swift, Go, Rust, shell,
9
+ PowerShell, C and C++, Objective-C, Java and Zig have no coverage to join: declare those
10
+ scopes `coverage_optional` and they score on complexity alone. Vue joins istanbul coverage
11
+ when your own vitest run reports on `.vue` files. Every read-side command speaks JSON with a
12
+ pinned schema, because half the callers are coding agents.
10
13
 
11
14
  ```
12
15
  CRAP = ccn^2 * (1 - cov)^3 + ccn
@@ -51,7 +54,7 @@ Check the install:
51
54
 
52
55
  ```
53
56
  $ crapkit --version
54
- crapkit 0.3.0
57
+ crapkit 0.4.0
55
58
  ```
56
59
 
57
60
  `python -m crapkit` works identically to the `crapkit` console script, and is what to use
@@ -447,11 +450,15 @@ without touching the repo-wide cache, and refuses the commit when a staged funct
447
450
  its scope ceiling. It needs no coverage data and no snapshot, so it costs the size of the
448
451
  commit, not the size of the repo.
449
452
 
450
- Two limits to know. The gate judges files a `[[scope]]` claims; a staged source file no
453
+ Three limits to know. The gate judges files a `[[scope]]` claims; a staged source file no
451
454
  scope claims is not gated, and the hook says so on stderr (`N staged file(s) belong to no
452
455
  scope and were not gated`) so the hole is visible the moment a new top-level directory
453
- appears. And git runs hooks outside your shell's activated venv: bare `python` must resolve
454
- to an interpreter that has crapkit installed, or use the absolute form
456
+ appears. A function the committed ratchet already carries a mark for is not gated either,
457
+ so touching signed debt does not refuse the commit; the hook reports the count on stderr
458
+ (`N staged function(s) carry a ratchet mark and were not gated`) and `crapkit verify` is
459
+ what fails a mark that rose. And git runs hooks outside your shell's activated venv: bare
460
+ `python` must resolve to an interpreter that has crapkit installed, or use the absolute
461
+ form
455
462
  (`exec /path/to/venv/Scripts/python -m crapkit hook-precommit`).
456
463
 
457
464
  ### Route 1: `.git/hooks/pre-commit` (local, not committed)
@@ -700,7 +707,8 @@ advances the baseline nor tightens the ratchet, exit 9 included.
700
707
  ## Subcommands
701
708
 
702
709
  Every subcommand takes `--repo PATH` (default `.`), and the flag goes **after** the
703
- subcommand:
710
+ subcommand. `claude-hook` is the one exception: it has no `--repo`, because it takes its
711
+ root from the file named in the hook payload it reads.
704
712
 
705
713
  ```
706
714
  $ crapkit worklist --repo /path/to/repo --scope util --top 1
@@ -721,7 +729,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
721
729
  | Command | What it does |
722
730
  |---|---|
723
731
  | `init` | Sniffs tracked source into per-directory scopes, writes a self-validated starter `crapkit.toml` whose lanes report into `.crapkit/cov/`, and appends `.crapkit/` plus each runner's own droppings to `.gitignore`. Writes a live `[[lane]]` when it can detect the test runner, otherwise a commented template. Refuses to clobber an existing config. |
724
- | `doctor [--show-files] [--json] [--tune]` | 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, lanes writing their artifacts at the repo root instead of under `.crapkit/` (WARN), committed hooks under `core.hooksPath` that are not executable in the index (WARN), directories whose functions are all `untested` while their tests exist (WARN), and scopes a lane measures with no `[crapkit.scoped_tests]` template behind them (WARN), which is the loop's step 4 with nothing to run. `--tune` prints suggested parallelism knobs and writes nothing. See [docs/agent-json.md](docs/agent-json.md#doctor---json). |
732
+ | `doctor [--show-files] [--json] [--tune] [--plugin-root PATH]` | Checks the config still describes the repo: unknown keys (with the accepted spellings), zero-file scopes, tracked source no scope claims, scopes no lane covers, lane cwds and commands that no longer resolve, lizard importable, oversized files, lanes writing their artifacts at the repo root instead of under `.crapkit/` (WARN), committed hooks under `core.hooksPath` that are not executable in the index (WARN), directories whose functions are all `untested` while their tests exist (WARN), and scopes a lane measures with no `[crapkit.scoped_tests]` template behind them (WARN), which is the loop's step 4 with nothing to run. `--tune` prints suggested parallelism knobs and writes nothing. `--plugin-root PATH` reads no repo at all: it checks an installed [plugin](plugin/) against this CLI, comparing `.claude-plugin/plugin.json`'s version against the running crapkit and every `--protocol` in `hooks/hooks.json` against the protocol `claude-hook` answers, one line per disagreement and silence when they agree. See [docs/agent-json.md](docs/agent-json.md#doctor---json). |
725
733
  | `inventory [--db PATH] [--export PATH] [--json]` | Two lizard passes over every in-scope file into a SQLite snapshot run, cached by content hash. `--db` is the only way to point crapkit at a store outside `.crapkit/`, and only this command accepts it. |
726
734
  | `coverage [--lane NAME] [--reuse-artifacts] [--reuse-unchanged] [--export PATH] [--sarif PATH] [--github] [--json]` | Runs the lanes, joins branch coverage onto a fresh inventory, writes a scored run. A failed lane is recorded, not fatal: its scopes fall back to `no-lane` and the run is typed `partial`, so it can never serve as a baseline. See [docs/lanes.md](docs/lanes.md). |
727
735
  | `verify [--baseline ID \| --base REF \| --baseline-tsv PATH] [--emit-baseline PATH] [--override REASON] [--reuse-artifacts] [--reuse-unchanged] [--sarif PATH] [--github] [--json]` | The full verdict against the trusted baseline: gate on touched functions, ratchet, no new test failures, optional diff-coverage ceiling. The three baseline selectors are mutually exclusive; `--baseline ID` also bypasses the taint rule ([The trusted baseline](#the-trusted-baseline)), and `--baseline-tsv` reads a commit-stamped file so a fresh clone verifies with no store. Findings a dirty tree produced are tagged `dirty` and counted apart. |
@@ -739,9 +747,10 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
739
747
  | `report [--out PATH]` | One self-contained HTML page written to `.crapkit/report.html` (or `--out PATH`, repo-relative), with the path printed on stdout. It renders what `worklist --json` and `trend --json` already answer at their defaults: the ranked worklist capped at `worklist_top`, the per-scope grades off the newest run, the trend series, and a banner naming every stale lane. It measures nothing, opens no network connection, and carries no per-function CRAP or coverage, because no repo-wide payload has them; each row prints the `crapkit explain` call that does. |
740
748
  | `duplication [--min-lines N] [--similarity F] [--top N] [--json]` | Near-duplicate functions by normalized line shingles with containment scoring. Defaults: `--min-lines 8`, `--similarity 0.8`, `--top 50`. `--top` truncates the list. |
741
749
  | `coupling [--min-support N] [--min-confidence F] [--top N] [--json]` | File pairs that keep landing in the same commits. Defaults: `--min-support 5` shared commits, `--min-confidence 0.5` max-direction ratio, `--top 50`. Bulk commits never couple pairs, and a young repo returns nothing at the default support. |
742
- | `mutate [--files F ...] [--max-mutants N] [--json]` | Diff-scoped mutation testing: flips comparisons, boundary shifts, boolean connectives and boolean literals on changed lines, runs `mutation_command` per mutant, lists survivors. `--files` replaces diff scope with the whole file. `--max-mutants` (default 100) caps the run and the cap warning goes to stderr only, so `mutants` in `--json` is the capped count. |
750
+ | `mutate [--files F ...] [--max-mutants N] [--json]` | Diff-scoped mutation testing: flips comparisons, boundary shifts, boolean connectives and boolean literals on changed lines, runs `mutation_command` per mutant, lists survivors. `--files` replaces diff scope with the whole file. `--max-mutants` (default 100) caps the run and the cap warning goes to stderr only, so `mutants` in `--json` is the capped count. Shell and PowerShell files are refused by name on stderr rather than mutated: `<` and `>` are redirections there, not comparisons. |
743
751
  | `test-scoped FILE ...` | Runs each owning scope's `[crapkit.scoped_tests]` template on the files (quoted, longest-prefix scope wins). A template with no `{files}` runs as written, which is how a scope whose tests live outside its own paths runs its whole suite. Exit code only; a nonzero runner exits 1. |
744
752
  | `hook-precommit` | The cc-only gate on staged blobs. No coverage, no snapshot, no repo-wide cache. Exit 6 on a violation. |
753
+ | `claude-hook [--protocol N]` | Reads one Claude Code PostToolUse payload from stdin and judges the file it edited: ccn against the scope ceiling, on functions the edit changed, minus functions a ratchet mark already covers. Advisory only: the edit has landed and nothing is blocked, and `hook-precommit` stays the enforcement point. Exit 2 with three lines on stderr is the only thing it ever says. No `crapkit.toml` above the edited file, an unscoped file, mid-rebase or mid-merge, a `--protocol` other than 1, source that parses to no functions, or any internal failure: exit 0, silence, empty stderr. Takes no `--repo` — the root is the first `crapkit.toml` above the edited file and the upward walk stops at a `.git` entry, so a worktree never borrows its parent's config. It opens no snapshot, writes nothing, and leaves stdout empty. |
745
754
  | `watch [--interval SECONDS] [--cycles N]` | Rescores tracked files as they change (mtime polling, default 2s, subprocess-isolated so a half-saved syntax error never kills the watcher). `--cycles N` polls exactly N times and exits 0; without it the loop runs until ctrl-c. |
746
755
  | `mcp` | A dependency-free stdio MCP server (newline JSON-RPC 2.0) exposing nine read-only tools. See [docs/agent-json.md](docs/agent-json.md#mcp-server). |
747
756
 
@@ -755,7 +764,7 @@ crapkit: error: argument command: invalid choice: '/path/to/repo' (choose from '
755
764
  | [docs/ratchet.md](docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
756
765
  | [docs/agent-json.md](docs/agent-json.md) | The machine surface: `schema`, every payload field, real captured examples. |
757
766
  | [docs/adoption.md](docs/adoption.md) | The judgment layer over the quickstarts: scope granularity, exclude vs lane, scoped_tests wiring, the first-verify taint hazard. |
758
- | [skills/](skills/) | Agent skills shipped with the repo (`crapkit`, `crapkit-recover`, `crapkit-onboard`) — install by copying to your agent runtime's skills directory. |
767
+ | [plugin/](plugin/) | The Claude Code plugin: three skills (`crapkit`, `crapkit-recover`, `crapkit-onboard`), the read-side MCP server, and the advisory PostToolUse hook. Install with `claude plugin marketplace add JeanFrancoisGagne/crapkit` then `claude plugin install crapkit@crapkit`; other runtimes copy `plugin/skills/*` into their skills directory. |
759
768
 
760
769
  [crapkit.schema.json](crapkit.schema.json) is the authority on the config file shape.
761
770
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "crapkit"
7
- version = "0.3.0"
7
+ version = "0.4.0"
8
8
  description = "Deterministic CRAP-score framework: per-function complexity x coverage risk, worklists, ratchets, refactor verification"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,2 +1,2 @@
1
1
  """crapkit: deterministic CRAP-score framework."""
2
- __version__ = "0.3.0"
2
+ __version__ = "0.4.0"
@@ -6,9 +6,12 @@ importlib.metadata, email, zipfile and socket. Measured on this box: 42ms with
6
6
  pygments, 16ms without, paid by every process that touches the analysis stack,
7
7
  the pre-commit hook included.
8
8
 
9
- crapkit analyzes six languages (typescript, tsx, javascript, python, swift, go).
10
- Erlang is not one of them and no scope can name it. The readers that need
11
- pygments are still SHIPPED, not removed: `deferred_pygments()` puts proxies in
9
+ crapkit analyzes fourteen languages (typescript, tsx, javascript, python, swift,
10
+ go, rust, shell, powershell, cpp, objectivec, vue, java, zig). Erlang is not one
11
+ of them and no scope can name it, which is the whole argument for stubbing its
12
+ reader, so a test pins this list to `config.SUPPORTED_LANGUAGES`. The readers
13
+ that need pygments are still SHIPPED, not removed: `deferred_pygments()` puts
14
+ proxies in
12
15
  sys.modules for the duration of the lizard import, so the readers bind stand-ins
13
16
  and the real package loads the first time anything reads or calls one. An .erl
14
17
  file analyzed through lizard directly gets the same answer; it just pays the
@@ -5,9 +5,11 @@ nested node_modules (measured hang on the first consumer repo).
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
+ import codecs
8
9
  import hashlib
9
10
  import json
10
11
  import os
12
+ import sys
11
13
  import time
12
14
  from concurrent.futures import ProcessPoolExecutor
13
15
  from pathlib import Path
@@ -17,17 +19,40 @@ from ._pygdefer import deferred_pygments
17
19
  with deferred_pygments(): # lizard's Erlang reader would load pygments here
18
20
  import lizard
19
21
 
22
+ from .lizardpowershell import register as _register_powershell
23
+ from .lizardrust import register as _register_rust
24
+ from .lizardshell import register as _register_shell
25
+
20
26
  from .cache import partition_by_cache, updated_cache
21
27
  from .errors import ToolError
22
28
  from .lizardcognitive import LizardExtension as _Cognitive
23
29
  from .merge import FunctionRecord
30
+ from .packet import bare_name
31
+
32
+ # lizard picks a reader by extension off a hardcoded list, and none of these is
33
+ # on it: `.rs` resolves to a reader that counts no `match` arm (lizard #494),
34
+ # and `.sh` and `.ps1` resolve to nothing at all, which lizard answers with
35
+ # CLikeReader rather than a failure. All three belong HERE, at the module scope
36
+ # of the module a ProcessPoolExecutor child imports, or spawned workers measure
37
+ # with the readers lizard shipped and report plausible wrong numbers.
38
+ #
39
+ # lizardshell and lizardpowershell already register themselves on import and
40
+ # lizardrust deliberately does not (rebinding a name in another package's
41
+ # namespace is not something an import should do quietly). Calling all three
42
+ # keeps the wiring readable in one place and costs nothing: each is idempotent.
43
+ _register_rust()
44
+ _register_shell()
45
+ _register_powershell()
24
46
 
25
47
  _POOL_THRESHOLD = 16
26
48
 
27
49
  # Bump whenever analysis semantics change (merge rules, extension set, record
28
50
  # extraction): the fingerprint must invalidate cached records produced by older
29
51
  # logic even when file content and tool versions are identical.
30
- ANALYSIS_VERSION = 4 # 4: cognitive stopped reading 0 for every Swift and Kotlin function
52
+ ANALYSIS_VERSION = 6 # 6: five more C-family extension sets and .ps1/.psm1 are
53
+ # admitted, a C++ rvalue reference is no longer a
54
+ # cognitive condition, and source bytes decode utf-8
55
+ # then cp1252 instead of by machine locale
31
56
 
32
57
  # The three tokens lizard's modified rule reacts to. Membership is checked before
33
58
  # anything else runs, so the common token pays one frozenset lookup.
@@ -122,11 +147,145 @@ def _record(rel_path: str, fn) -> FunctionRecord:
122
147
  )
123
148
 
124
149
 
150
+ # How many colliding names one warning prints before it stops. A generated file
151
+ # can hold hundreds; the point is that the file needs looking at, not the list.
152
+ _NAMES_SHOWN = 5
153
+
154
+
155
+ def _colliding_names(records: list[FunctionRecord]) -> list[str]:
156
+ """Names this file gives to more than one function, in first-seen order.
157
+
158
+ Anonymous functions are exempt. lizard calls every one of them
159
+ `(anonymous)`, so a file with two arrow callbacks collides by construction
160
+ and a warning would name nothing anyone could act on; `packet.handles`
161
+ answers that collision with the `(anonymous)#N` ordinal instead.
162
+ """
163
+ seen: set[str] = set()
164
+ colliding: dict[str, None] = {}
165
+ for record in records:
166
+ if bare_name(record.long_name) and record.long_name in seen:
167
+ colliding[record.long_name] = None
168
+ seen.add(record.long_name)
169
+ return list(colliding)
170
+
171
+
172
+ def _warn_on_collisions(rel_path: str, records: list[FunctionRecord]) -> None:
173
+ """One stderr line for a file whose records cannot all reach the ratchet.
174
+
175
+ A mark is keyed on (path, long_name), and so is the row a run writes, so the
176
+ last function under a colliding name is the only one marked and the only one
177
+ gated. C makes this ordinary: both arms of an `#ifdef` fork are textually
178
+ present, so a platform shim defines the same function twice in one file.
179
+ Python makes it ordinary too, because a method's long_name carries no class.
180
+ Neither is fixable here — the ratchet cannot key on a span, which drifts with
181
+ every edit — so the loss is announced rather than silent.
182
+ """
183
+ names = _colliding_names(records)
184
+ if not names:
185
+ return
186
+ print(f"crapkit: {rel_path} defines {_listed(names)} more than once; the ratchet keys "
187
+ f"on (path, long_name) and keeps the last, so the earlier ones are neither "
188
+ f"marked nor gated", file=sys.stderr)
189
+
190
+
191
+ def _listed(names: list[str]) -> str:
192
+ if len(names) <= _NAMES_SHOWN:
193
+ return ", ".join(names)
194
+ return f"{', '.join(names[:_NAMES_SHOWN])} and {len(names) - _NAMES_SHOWN} more name(s)"
195
+
196
+
197
+ def _file_records(rel_path: str, functions) -> list[FunctionRecord]:
198
+ records = [_record(rel_path, fn) for fn in functions]
199
+ _warn_on_collisions(rel_path, records)
200
+ return records
201
+
202
+
203
+ # --- how a source file's bytes become text -------------------------------------
204
+ #
205
+ # lizard opens a source file with `io.open(path, 'r')` and no encoding, so the
206
+ # MACHINE'S LOCALE decides what a repo scores. Measured on one function,
207
+ # `function Write-Café`, written once as UTF-8 and once as cp1252, read on a
208
+ # cp1252 interpreter and on a UTF-8 one:
209
+ #
210
+ # source cp1252 reader utf-8 reader
211
+ # utf-8 no function at all Write-Café
212
+ # cp1252 Write-Café Write-Caf
213
+ #
214
+ # The empty cell is not a rounding error: `é` arrives as `Ã` plus `©`, the `©`
215
+ # is no word character, and the declaration stops being one. The ratchet keys on
216
+ # path::long_name, so those are three different rows for one commit, and a
217
+ # Windows developer and a Linux CI cannot see each other's baseline.
218
+ #
219
+ # utf-8 first, cp1252 second, replacement for the five bytes cp1252 leaves
220
+ # undefined. That is the whole rule and it is fixed rather than environmental:
221
+ # all four cells above read `Write-Café`. The fallback is cp1252 rather than
222
+ # latin-1 because Windows PowerShell 5.1 writes cp1252, and because latin-1
223
+ # decodes every byte and so can never say it was wrong.
224
+ #
225
+ # UTF-16 is NOT handled. `Out-File` and the ISE write it, and such a file
226
+ # decodes here as NUL-separated cp1252 text that reports no function; it
227
+ # reported none before this change either, so nothing regressed and the narrow
228
+ # rule stays narrow.
229
+
230
+
231
+ def _characters(raw: bytes) -> str:
232
+ if raw.startswith(codecs.BOM_UTF8):
233
+ raw = raw[len(codecs.BOM_UTF8):]
234
+ try:
235
+ return raw.decode("utf-8")
236
+ except UnicodeDecodeError:
237
+ return raw.decode("cp1252", "replace")
238
+
239
+
240
+ def decode_source(raw: bytes) -> str:
241
+ """Source bytes as text, decoded by content rather than by machine locale.
242
+
243
+ The single rule for every path into lizard: a file on disk, and a staged
244
+ blob the pre-commit gate never writes down. Those two must agree or the
245
+ gate judges different content than the inventory scores.
246
+
247
+ Line endings are normalized the way `io.open(path, 'r')` normalized them,
248
+ because that is what lizard did and every recorded line number and NLOC in
249
+ every cache depends on it: a lone `\\r` left in the stream is one more
250
+ whitespace token, not one more line.
251
+ """
252
+ return _characters(raw).replace("\r\n", "\n").replace("\r", "\n")
253
+
254
+
255
+ def read_source(path: str) -> str:
256
+ return decode_source(Path(path).read_bytes())
257
+
258
+
259
+ def _install_decoder() -> None:
260
+ """Point lizard's own file read at `read_source`. Idempotent.
261
+
262
+ A rebind rather than a replacement for `FileAnalyzer.__call__`, so lizard
263
+ keeps the IOError branch that answers a file deleted mid-run with an empty
264
+ result instead of a traceback, and keeps reading each file exactly once.
265
+ `lizard.py` binds `auto_read` into its own module namespace at import, and
266
+ `FileAnalyzer.__call__` resolves it there on every call.
267
+
268
+ Raises when that name is gone, which is what a lizard release that reads
269
+ source some other way would look like. Loud beats an attribute nobody
270
+ reads and a decode that silently went back to the locale's.
271
+ """
272
+ if not hasattr(lizard, "auto_read"):
273
+ raise RuntimeError(
274
+ f"crapkit.analyze._install_decoder() found no lizard.auto_read to "
275
+ f"rebind: lizard {lizard.version} reads source some other way. "
276
+ f"Rewrite this against the new mechanism; leaving it undone makes "
277
+ f"every non-ASCII file score by the machine's locale.")
278
+ lizard.auto_read = read_source
279
+
280
+
281
+ _install_decoder()
282
+
283
+
125
284
  def analyze_one(args: tuple[str, str]) -> tuple[str, list[FunctionRecord]]:
126
285
  abs_path, rel_path = args
127
286
  try:
128
287
  analysis = lizard.FileAnalyzer(_extensions_for(rel_path))(abs_path)
129
- return rel_path, [_record(rel_path, fn) for fn in analysis.function_list]
288
+ return rel_path, _file_records(rel_path, analysis.function_list)
130
289
  except Exception as exc: # loud, with the file named
131
290
  raise ToolError(f"lizard failed on {rel_path}: {exc}") from exc
132
291
 
@@ -142,7 +301,7 @@ def analyze_source(rel_path: str, code: str) -> list[FunctionRecord]:
142
301
  try:
143
302
  analyzer = lizard.FileAnalyzer(_extensions_for(rel_path))
144
303
  analysis = analyzer.analyze_source_code(rel_path, code)
145
- return [_record(rel_path, fn) for fn in analysis.function_list]
304
+ return _file_records(rel_path, analysis.function_list)
146
305
  except Exception as exc: # loud, with the file named
147
306
  raise ToolError(f"lizard failed on {rel_path}: {exc}") from exc
148
307
 
@@ -19,16 +19,21 @@ _OWNER = {
19
19
  "_BriefLoader": "queue",
20
20
  "_Corpus": "scoring",
21
21
  "_Handles": "queue",
22
+ "_CLAUDE_SUBCOMMANDS": "parser",
22
23
  "_SCRIPT_SUFFIXES": "admin",
23
24
  "_VersionAction": "parser",
25
+ "_advise": "claude_hook",
26
+ "_advisory_lines": "claude_hook",
24
27
  "_actionable": "queue",
25
28
  "_analysis_tools": "_shared",
26
29
  "_analysis_workers": "scoring",
30
+ "_answerable": "claude_hook",
27
31
  "_analyzed_corpus": "scoring",
28
32
  "_apply_verify_override": "verifying",
29
33
  "_at_level": "admin",
30
34
  "_baseline_behind": "verifying",
31
35
  "_baseline_failures": "verifying",
36
+ "_baseline_rows": "scoring",
32
37
  "_batch_json": "queue",
33
38
  "_batch_rows": "queue",
34
39
  "_brief_batch": "queue",
@@ -42,8 +47,10 @@ _OWNER = {
42
47
  "_brief_twins": "queue",
43
48
  "_brief_versions": "queue",
44
49
  "_build_inventory": "scoring",
50
+ "_breaches": "claude_hook",
45
51
  "_by_scope": "scoring",
46
52
  "_ceiling_breaches": "scoring",
53
+ "_changed": "claude_hook",
47
54
  "_churn_text": "queue",
48
55
  "_claim_matches": "queue",
49
56
  "_claimable": "queue",
@@ -51,10 +58,12 @@ _OWNER = {
51
58
  "_claims_to_release": "queue",
52
59
  "_collect_lanes": "scoring",
53
60
  "_collect_mutants": "analyses",
61
+ "_config": "claude_hook",
54
62
  "_contexts_for_span": "reports",
55
63
  "_coverage_summary": "scoring",
56
64
  "_covered_scope_names": "admin",
57
65
  "_diff_cover_breach": "verifying",
66
+ "_diff_proc": "claude_hook",
58
67
  "_digest_pair": "reports",
59
68
  "_dirty_tag": "_shared",
60
69
  "_doctor_findings": "admin",
@@ -63,6 +72,7 @@ _OWNER = {
63
72
  "_doctor_lane_summary": "admin",
64
73
  "_doctor_lanes": "admin",
65
74
  "_doctor_oversized": "admin",
75
+ "_doctor_plugin": "admin",
66
76
  "_doctor_report": "admin",
67
77
  "_doctor_scope_files": "admin",
68
78
  "_doctor_scopes": "admin",
@@ -71,6 +81,8 @@ _OWNER = {
71
81
  "_doctor_unclaimed": "admin",
72
82
  "_doctor_uncovered": "admin",
73
83
  "_doctor_unmeasured": "admin",
84
+ "_edited_file": "claude_hook",
85
+ "_edited_path": "claude_hook",
74
86
  "_emit_baseline": "verifying",
75
87
  "_emit_coverage_findings": "scoring",
76
88
  "_emit_doctor": "admin",
@@ -92,13 +104,17 @@ _OWNER = {
92
104
  "_flake_retry": "verifying",
93
105
  "_gate_candidates": "scoring",
94
106
  "_gate_line": "_shared",
107
+ "_gated_violations": "verifying",
95
108
  "_grant_env_override": "verifying",
96
109
  "_group_files_by_scope": "verifying",
97
110
  "_guard_ratchet_stamp": "verifying",
98
111
  "_handle": "queue",
112
+ "_hook_handlers": "admin",
99
113
  "_hook_modes": "admin",
114
+ "_hook_protocols": "admin",
100
115
  "_interpreter": "admin",
101
116
  "_is_test_path": "verifying",
117
+ "_judge": "claude_hook",
102
118
  "_junit_seconds": "admin",
103
119
  "_lane_command_problems": "admin",
104
120
  "_lane_durations": "admin",
@@ -116,9 +132,11 @@ _OWNER = {
116
132
  "_load_ratchet_or_die": "_shared",
117
133
  "_load_repo_config": "_shared",
118
134
  "_load_sources": "_shared",
135
+ "_manifest_version": "admin",
119
136
  "_mark_text": "queue",
120
137
  "_matching_rows": "queue",
121
138
  "_maybe_claim": "queue",
139
+ "_marks_for": "claude_hook",
122
140
  "_maybe_flake_retry": "verifying",
123
141
  "_merge_stamp": "ratchet_cmds",
124
142
  "_missing_named_script": "admin",
@@ -127,6 +145,7 @@ _OWNER = {
127
145
  "_name_prefix": "queue",
128
146
  "_named_baseline": "verifying",
129
147
  "_named_claims": "queue",
148
+ "_named_protocol": "admin",
130
149
  "_newest_coverage_run": "admin",
131
150
  "_newest_run_report": "admin",
132
151
  "_next_head": "queue",
@@ -140,6 +159,7 @@ _OWNER = {
140
159
  "_no_line_message": "queue",
141
160
  "_no_match_message": "queue",
142
161
  "_no_scopes_reason": "admin",
162
+ "_note_marked_staged": "verifying",
143
163
  "_note_stale_staged": "verifying",
144
164
  "_open_store": "_shared",
145
165
  "_owning_scope": "verifying",
@@ -148,8 +168,10 @@ _OWNER = {
148
168
  "_packet_context": "queue",
149
169
  "_packet_gate": "queue",
150
170
  "_packet_scope": "queue",
171
+ "_payload": "claude_hook",
151
172
  "_pick_baseline": "verifying",
152
173
  "_pick_function": "queue",
174
+ "_plugin_json": "admin",
153
175
  "_policy_findings": "ratchet_cmds",
154
176
  "_present_markers": "admin",
155
177
  "_present_on_disk": "scoring",
@@ -185,9 +207,12 @@ _OWNER = {
185
207
  "_ratchet_move": "ratchet_cmds",
186
208
  "_ratchet_report": "ratchet_cmds",
187
209
  "_ratchet_sha256": "verifying",
210
+ "_reconfigure_streams": "parser",
211
+ "_records": "claude_hook",
188
212
  "_records_by_scope": "scoring",
189
213
  "_release_claims": "verifying",
190
214
  "_release_target": "queue",
215
+ "_report": "claude_hook",
191
216
  "_report_lanes": "reports",
192
217
  "_report_payload": "reports",
193
218
  "_report_verify": "verifying",
@@ -214,13 +239,16 @@ _OWNER = {
214
239
  "_runs_prune": "reports",
215
240
  "_scope_config": "queue",
216
241
  "_scope_rollup_from_agg": "reports",
242
+ "_scoped": "claude_hook",
217
243
  "_scoped_command": "verifying",
218
244
  "_scored_run": "scoring",
219
245
  "_scored_store": "queue",
220
246
  "_select_lanes": "scoring",
221
247
  "_send_digest_alert": "reports",
248
+ "_sequencing": "claude_hook",
222
249
  "_settle_verify": "verifying",
223
250
  "_skip_reason": "queue",
251
+ "_split_marked": "verifying",
224
252
  "_split_worklist": "queue",
225
253
  "_stale_warning": "queue",
226
254
  "_store_if_any": "admin",
@@ -228,6 +256,7 @@ _OWNER = {
228
256
  "_store_report": "admin",
229
257
  "_taint_note": "verifying",
230
258
  "_traced_lane": "scoring",
259
+ "_tracked": "claude_hook",
231
260
  "_tracked_files": "scoring",
232
261
  "_trend_payload": "reports",
233
262
  "_trend_row": "reports",
@@ -239,7 +268,9 @@ _OWNER = {
239
268
  "_under_scope_path": "verifying",
240
269
  "_unknown_key_text": "admin",
241
270
  "_unmarked_breaches": "scoring",
271
+ "_unknown_claude_command": "parser",
242
272
  "_untracked_of": "scoring",
273
+ "_verdict": "claude_hook",
243
274
  "_verify_attribution": "verifying",
244
275
  "_verify_baseline": "verifying",
245
276
  "_verify_basis": "verifying",
@@ -266,6 +297,7 @@ _OWNER = {
266
297
  "build_parser": "parser",
267
298
  "cmd_brief": "queue",
268
299
  "cmd_claims": "queue",
300
+ "cmd_claude_hook": "claude_hook",
269
301
  "cmd_coupling": "analyses",
270
302
  "cmd_coverage": "scoring",
271
303
  "cmd_digest": "reports",