crapkit 0.4.12__tar.gz → 0.4.14__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 (73) hide show
  1. {crapkit-0.4.12/src/crapkit.egg-info → crapkit-0.4.14}/PKG-INFO +9 -5
  2. {crapkit-0.4.12 → crapkit-0.4.14}/README.md +8 -4
  3. {crapkit-0.4.12 → crapkit-0.4.14}/pyproject.toml +1 -1
  4. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/__init__.py +1 -1
  5. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/mcp_server.py +97 -20
  6. {crapkit-0.4.12 → crapkit-0.4.14/src/crapkit.egg-info}/PKG-INFO +9 -5
  7. {crapkit-0.4.12 → crapkit-0.4.14}/LICENSE +0 -0
  8. {crapkit-0.4.12 → crapkit-0.4.14}/setup.cfg +0 -0
  9. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/__main__.py +0 -0
  10. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/_pygdefer.py +0 -0
  11. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/analyze.py +0 -0
  12. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cache.py +0 -0
  13. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/churn.py +0 -0
  14. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/churn_cache.py +0 -0
  15. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/churn_log.py +0 -0
  16. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/__init__.py +0 -0
  17. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/_shared.py +0 -0
  18. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/admin.py +0 -0
  19. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/analyses.py +0 -0
  20. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/claude_hook.py +0 -0
  21. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/parser.py +0 -0
  22. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/queue.py +0 -0
  23. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/ratchet_cmds.py +0 -0
  24. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/reports.py +0 -0
  25. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/scoring.py +0 -0
  26. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/cli/verifying.py +0 -0
  27. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/config.py +0 -0
  28. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/coupling.py +0 -0
  29. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/coupling_cache.py +0 -0
  30. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/coverage_istanbul.py +0 -0
  31. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/coverage_py.py +0 -0
  32. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/covstream.py +0 -0
  33. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/diffparse.py +0 -0
  34. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/digest.py +0 -0
  35. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/discover.py +0 -0
  36. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/doctor.py +0 -0
  37. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/dup.py +0 -0
  38. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/errors.py +0 -0
  39. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/gitio.py +0 -0
  40. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/hook.py +0 -0
  41. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/invocation.py +0 -0
  42. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/junitparse.py +0 -0
  43. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/keys.py +0 -0
  44. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/lanes.py +0 -0
  45. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/lizardcognitive.py +0 -0
  46. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/lizardpowershell.py +0 -0
  47. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/lizardrust.py +0 -0
  48. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/lizardshell.py +0 -0
  49. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/merge.py +0 -0
  50. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/mutate.py +0 -0
  51. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/mutate_pool.py +0 -0
  52. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/override.py +0 -0
  53. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/packet.py +0 -0
  54. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/procs.py +0 -0
  55. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/ratchet.py +0 -0
  56. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/ratchet_report.py +0 -0
  57. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/report.py +0 -0
  58. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/sarif.py +0 -0
  59. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/sarifio.py +0 -0
  60. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/scaffold.py +0 -0
  61. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/score.py +0 -0
  62. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/snapshot.py +0 -0
  63. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/store.py +0 -0
  64. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/uncovered.py +0 -0
  65. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/universe.py +0 -0
  66. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/verify.py +0 -0
  67. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/watch.py +0 -0
  68. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit/worklist.py +0 -0
  69. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit.egg-info/SOURCES.txt +0 -0
  70. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit.egg-info/dependency_links.txt +0 -0
  71. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit.egg-info/entry_points.txt +0 -0
  72. {crapkit-0.4.12 → crapkit-0.4.14}/src/crapkit.egg-info/requires.txt +0 -0
  73. {crapkit-0.4.12 → crapkit-0.4.14}/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.4.12
3
+ Version: 0.4.14
4
4
  Summary: Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more.
5
5
  Author: Jean-Francois Gagne
6
6
  License: MIT
@@ -39,10 +39,13 @@ Dynamic: license-file
39
39
 
40
40
  # crapkit
41
41
 
42
+ <!-- mcp-name: io.github.jeanfrancoisgagne/crapkit -->
43
+
42
44
  [![ci](https://github.com/JeanFrancoisGagne/crapkit/actions/workflows/ci.yml/badge.svg)](https://github.com/JeanFrancoisGagne/crapkit/actions/workflows/ci.yml)
43
45
  [![PyPI](https://img.shields.io/pypi/v/crapkit)](https://pypi.org/project/crapkit/)
44
46
  [![Python](https://img.shields.io/pypi/pyversions/crapkit)](https://pypi.org/project/crapkit/)
45
47
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/JeanFrancoisGagne/crapkit/blob/main/LICENSE)
48
+ [![crapkit MCP server](https://glama.ai/mcp/servers/JeanFrancoisGagne/crapkit/badges/score.svg)](https://glama.ai/mcp/servers/JeanFrancoisGagne/crapkit)
46
49
 
47
50
  ![crapkit init, coverage and worklist --top 5 on a small Python repo, then a shell heredoc adding a function at ccn 7: the per-edit advisory reports it and exits 2, and the commit gate refuses the staged file with exit 6](https://raw.githubusercontent.com/JeanFrancoisGagne/crapkit/main/docs/demo.gif)
48
51
 
@@ -165,7 +168,7 @@ leaves the box
165
168
 
166
169
  ```
167
170
  $ crapkit --version
168
- crapkit 0.4.12
171
+ crapkit 0.4.14
169
172
  ```
170
173
 
171
174
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -395,7 +398,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
395
398
  repos:
396
399
  - repo: https://github.com/JeanFrancoisGagne/crapkit
397
400
  # crapkit's release step rewrites this line to the tag it just cut
398
- rev: v0.4.12
401
+ rev: v0.4.14
399
402
  hooks:
400
403
  - id: crapkit-gate
401
404
  ```
@@ -501,7 +504,7 @@ to a workflow, and every input has a default:
501
504
  - uses: actions/checkout@v4
502
505
  with:
503
506
  fetch-depth: 0
504
- - uses: JeanFrancoisGagne/crapkit@v0.4.12
507
+ - uses: JeanFrancoisGagne/crapkit@v0.4.14
505
508
  ```
506
509
 
507
510
  The whole job those four lines sit in:
@@ -521,7 +524,7 @@ jobs:
521
524
  with:
522
525
  python-version: "3.12" # the interpreter the install below lands in
523
526
  - run: pip install -e ".[dev]" # whatever your lanes need to run
524
- - uses: JeanFrancoisGagne/crapkit@v0.4.12
527
+ - uses: JeanFrancoisGagne/crapkit@v0.4.14
525
528
  with:
526
529
  gate: "false"
527
530
  ```
@@ -1189,6 +1192,7 @@ that block is advisory unless `diff_uncovered_max` is set
1189
1192
  | [docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md) | The lane model, vitest and jest and pytest recipes, artifact reuse, flake retest, containers. |
1190
1193
  | [docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
1191
1194
  | [docs/agent-json.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md) | The machine surface: `schema`, every payload field, real captured examples. |
1195
+ | [docs/comparison.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/comparison.md) | Where crapkit sits next to radon, xenon, wily, coverage.py and SonarQube, and how they run together. |
1192
1196
  | [AGENTS.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/AGENTS.md) | The burn-down loop an agent runs, and the rules for changing crapkit itself. |
1193
1197
  | [plugin/](https://github.com/JeanFrancoisGagne/crapkit/tree/main/plugin) | The Claude Code plugin: three skills, the read-side MCP server, and the advisory PostToolUse hook. |
1194
1198
 
@@ -1,9 +1,12 @@
1
1
  # crapkit
2
2
 
3
+ <!-- mcp-name: io.github.jeanfrancoisgagne/crapkit -->
4
+
3
5
  [![ci](https://github.com/JeanFrancoisGagne/crapkit/actions/workflows/ci.yml/badge.svg)](https://github.com/JeanFrancoisGagne/crapkit/actions/workflows/ci.yml)
4
6
  [![PyPI](https://img.shields.io/pypi/v/crapkit)](https://pypi.org/project/crapkit/)
5
7
  [![Python](https://img.shields.io/pypi/pyversions/crapkit)](https://pypi.org/project/crapkit/)
6
8
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/JeanFrancoisGagne/crapkit/blob/main/LICENSE)
9
+ [![crapkit MCP server](https://glama.ai/mcp/servers/JeanFrancoisGagne/crapkit/badges/score.svg)](https://glama.ai/mcp/servers/JeanFrancoisGagne/crapkit)
7
10
 
8
11
  ![crapkit init, coverage and worklist --top 5 on a small Python repo, then a shell heredoc adding a function at ccn 7: the per-edit advisory reports it and exits 2, and the commit gate refuses the staged file with exit 6](https://raw.githubusercontent.com/JeanFrancoisGagne/crapkit/main/docs/demo.gif)
9
12
 
@@ -126,7 +129,7 @@ leaves the box
126
129
 
127
130
  ```
128
131
  $ crapkit --version
129
- crapkit 0.4.12
132
+ crapkit 0.4.14
130
133
  ```
131
134
 
132
135
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -356,7 +359,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
356
359
  repos:
357
360
  - repo: https://github.com/JeanFrancoisGagne/crapkit
358
361
  # crapkit's release step rewrites this line to the tag it just cut
359
- rev: v0.4.12
362
+ rev: v0.4.14
360
363
  hooks:
361
364
  - id: crapkit-gate
362
365
  ```
@@ -462,7 +465,7 @@ to a workflow, and every input has a default:
462
465
  - uses: actions/checkout@v4
463
466
  with:
464
467
  fetch-depth: 0
465
- - uses: JeanFrancoisGagne/crapkit@v0.4.12
468
+ - uses: JeanFrancoisGagne/crapkit@v0.4.14
466
469
  ```
467
470
 
468
471
  The whole job those four lines sit in:
@@ -482,7 +485,7 @@ jobs:
482
485
  with:
483
486
  python-version: "3.12" # the interpreter the install below lands in
484
487
  - run: pip install -e ".[dev]" # whatever your lanes need to run
485
- - uses: JeanFrancoisGagne/crapkit@v0.4.12
488
+ - uses: JeanFrancoisGagne/crapkit@v0.4.14
486
489
  with:
487
490
  gate: "false"
488
491
  ```
@@ -1150,6 +1153,7 @@ that block is advisory unless `diff_uncovered_max` is set
1150
1153
  | [docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md) | The lane model, vitest and jest and pytest recipes, artifact reuse, flake retest, containers. |
1151
1154
  | [docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
1152
1155
  | [docs/agent-json.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md) | The machine surface: `schema`, every payload field, real captured examples. |
1156
+ | [docs/comparison.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/comparison.md) | Where crapkit sits next to radon, xenon, wily, coverage.py and SonarQube, and how they run together. |
1153
1157
  | [AGENTS.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/AGENTS.md) | The burn-down loop an agent runs, and the rules for changing crapkit itself. |
1154
1158
  | [plugin/](https://github.com/JeanFrancoisGagne/crapkit/tree/main/plugin) | The Claude Code plugin: three skills, the read-side MCP server, and the advisory PostToolUse hook. |
1155
1159
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "crapkit"
7
- version = "0.4.12"
7
+ version = "0.4.14"
8
8
  description = "Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { text = "MIT" }
@@ -1,2 +1,2 @@
1
1
  """crapkit: deterministic CRAP-score framework."""
2
- __version__ = "0.4.12"
2
+ __version__ = "0.4.14"
@@ -13,9 +13,13 @@ from pathlib import Path
13
13
 
14
14
  from .invocation import _self
15
15
 
16
- PROTOCOL_VERSION = "2024-11-05"
16
+ # Newest first. Everything this server does — tools, annotations, structured
17
+ # results over stdio — is inside the 2025-06-18 revision, and nothing it does
18
+ # was removed from the older two, so any of the three can be spoken verbatim.
19
+ SUPPORTED_PROTOCOLS = ("2025-06-18", "2025-03-26", "2024-11-05")
17
20
 
18
- _REPO = {"repo": {"type": "string", "description": "repo root (default: the server's)"}}
21
+ _REPO = {"repo": {"type": "string", "description": (
22
+ "path to the scored repo's root (default: the repo the server was started in)")}}
19
23
 
20
24
  # brief and explain resolve NAME by one rule, so they describe it with one
21
25
  # string. The bare identifier is the long name's leading token, which is all
@@ -28,45 +32,80 @@ _NAME_DESCRIPTION = ("the bare identifier (classify, or route for a Rust "
28
32
  TOOLS: tuple[dict, ...] = (
29
33
  {"name": "next_item", "argv": ["next-item"], "json_flag": False, "positional": (),
30
34
  "flags": {"top": "--top", "exclude": "--exclude"},
31
- "description": "The highest-risk function(s) to refactor next, with scores and effort estimates",
32
- "properties": {"top": {"type": "integer"}, "exclude": {"type": "array", "items": {"type": "string"}}}},
35
+ "description": "The highest-risk function to refactor next, as one work packet: score, "
36
+ "uncovered lines, effort estimate and the commands that verify the fix. "
37
+ "Reach for it when you want one item to act on; worklist is the same "
38
+ "ranking as a survey.",
39
+ "properties": {"top": {"type": "integer",
40
+ "description": "return the next N packets instead of one (>= 1, default 1)"},
41
+ "exclude": {"type": "array", "items": {"type": "string"},
42
+ "description": "skip items whose path or function name contains "
43
+ "this fragment (repeatable)"}}},
33
44
  {"name": "worklist", "argv": ["worklist"], "json_flag": True, "positional": (),
34
45
  "flags": {"top": "--top"},
35
- "description": "Risk-ranked decomposition queue (ccn x recency-weighted churn)",
36
- "properties": {"top": {"type": "integer"}}},
46
+ "description": "The run's whole risk ranking: every function over its ceiling, then the "
47
+ "queue under it ordered by ccn times recency-weighted churn. Use it to "
48
+ "survey the repo; next_item hands out one packet from the top.",
49
+ "properties": {"top": {"type": "integer",
50
+ "description": "cap the active list (default: the config's worklist_top)"}}},
37
51
  {"name": "runs", "argv": ["runs"], "json_flag": True, "positional": (), "flags": {},
38
- "description": "Run history: id, kind, verdict, commit, lane set", "properties": {}},
52
+ "description": "Run history, newest first: id, kind, verdict, commit and lane set. Use it "
53
+ "to date the store or to check which commit the other tools answer from.",
54
+ "properties": {}},
39
55
  {"name": "brief", "argv": ["brief"], "json_flag": True, "positional": ("path", "name"),
40
56
  "flags": {},
41
- "description": "One function's whole context: scored row, ratchet mark, uncovered lines, "
42
- "duplication twins, churn and change-coupling partners",
57
+ "description": "One function's whole context in one call: scored row, ratchet mark, "
58
+ "uncovered lines, duplication twins, churn and change-coupling partners. "
59
+ "The deepest single read; explain is the slimmer history-only view.",
43
60
  "properties": {"path": {"type": "string", "description": "repo-relative source file"},
44
61
  "name": {"type": "string", "description": _NAME_DESCRIPTION}}},
45
62
  {"name": "explain", "argv": ["explain"], "json_flag": False, "positional": ("path", "name"),
46
63
  "flags": {},
47
- "description": "One function's score trajectory across runs plus its ratchet mark",
48
- "properties": {"path": {"type": "string"},
64
+ "description": "One function's score trajectory across runs plus its ratchet mark. Use it "
65
+ "to see whether a function is improving or decaying; brief adds the full "
66
+ "context around the newest score.",
67
+ "properties": {"path": {"type": "string", "description": "repo-relative source file"},
49
68
  "name": {"type": "string", "description": _NAME_DESCRIPTION}}},
50
69
  {"name": "doctor", "argv": ["doctor"], "json_flag": False, "positional": (), "flags": {},
51
- "description": "Config/repo agreement check: typo keys, empty scopes, missing lane cwds",
70
+ "description": "Config and repo agreement check: typo keys, empty scopes, missing lane "
71
+ "cwds, unresolvable runners. Run it first when any other tool answers "
72
+ "strangely or an expected file is missing from the ranking.",
52
73
  "properties": {}},
53
74
  {"name": "coupling", "argv": ["coupling"], "json_flag": True, "positional": (),
54
75
  "flags": {"min_support": "--min-support", "min_confidence": "--min-confidence"},
55
- "description": "File pairs that keep landing in the same commits (hidden dependencies)",
56
- "properties": {"min_support": {"type": "integer"}, "min_confidence": {"type": "number"}}},
76
+ "description": "File pairs that keep landing in the same commits: dependencies no import "
77
+ "statement reveals. Use it to learn what else an edit usually drags along "
78
+ "before touching a file.",
79
+ "properties": {"min_support": {"type": "integer",
80
+ "description": "minimum shared commits before a pair counts (default 5)"},
81
+ "min_confidence": {"type": "number",
82
+ "description": "minimum P(pair changes together), 0 to 1 (default 0.5)"}}},
57
83
  {"name": "duplication", "argv": ["duplication"], "json_flag": True, "positional": (),
58
84
  "flags": {"similarity": "--similarity"},
59
- "description": "Near-duplicate functions by normalized line shingles",
60
- "properties": {"similarity": {"type": "number"}}},
85
+ "description": "Near-duplicate function pairs by normalized line shingles, with the "
86
+ "containment percent. Use it before a refactor so twins get folded "
87
+ "together instead of one copy getting fixed alone.",
88
+ "properties": {"similarity": {"type": "number",
89
+ "description": "containment threshold, shared over smaller, "
90
+ "0 to 1 (default 0.8)"}}},
61
91
  {"name": "ratchet_report", "argv": ["ratchet", "report"], "json_flag": True, "positional": (),
62
92
  "flags": {},
63
- "description": "Debt burn-down: open mark ages and repayment velocity from git history",
93
+ "description": "The debt burn-down: every open ratchet mark with its age, plus repayment "
94
+ "velocity from git history. Use it to see whether marked debt is being "
95
+ "paid down or piling up.",
64
96
  "properties": {}},
65
97
  )
66
98
 
99
+ # Every tool shells to a read-only CLI command against a store on this machine:
100
+ # nothing writes, reruns answer the same, and no call leaves the repo. Declared
101
+ # once, in the field clients read, so the module docstring's promise reaches the
102
+ # model that plans with these tools.
103
+ _ANNOTATIONS = {"readOnlyHint": True, "idempotentHint": True, "openWorldHint": False}
104
+
67
105
 
68
106
  def tool_listing() -> list[dict]:
69
107
  return [{"name": t["name"], "description": t["description"],
108
+ "annotations": dict(_ANNOTATIONS),
70
109
  "inputSchema": {"type": "object", "properties": {**_REPO, **t["properties"]}}}
71
110
  for t in TOOLS]
72
111
 
@@ -116,7 +155,7 @@ def _run_cli(tool: dict, arguments: dict, repo: str) -> dict:
116
155
  proc = subprocess.run([sys.executable, "-m", "crapkit", *argv],
117
156
  capture_output=True, text=True, timeout=600)
118
157
  text = proc.stdout if proc.stdout.strip() else proc.stderr
119
- return _result(text, is_error=proc.returncode != 0)
158
+ return _structured(_result(text, is_error=proc.returncode != 0))
120
159
 
121
160
 
122
161
  def _call_tool(root: Path, name: str, arguments: dict) -> dict:
@@ -130,6 +169,43 @@ def _call_tool(root: Path, name: str, arguments: dict) -> dict:
130
169
  return _run_cli(tool, arguments, repo)
131
170
 
132
171
 
172
+ # What a connected model needs before its first call, in the one field the
173
+ # protocol reserves for it. The nine error results a model would otherwise
174
+ # collect from an unmeasured repo teach the same thing nine times, slower.
175
+ _INSTRUCTIONS = (
176
+ "crapkit scores every function as ccn^2 x (1 - coverage)^3 + ccn; these nine tools read "
177
+ "the newest scored run and every one is read-only: nothing here runs a test suite or "
178
+ "writes to the repo. They need a repo measured once (crapkit init, then crapkit "
179
+ "coverage); an unmeasured repo answers with a one-line pointer instead of data. Start "
180
+ "with next_item for one function to fix, worklist for the whole ranking, brief for "
181
+ "everything about one function.")
182
+
183
+
184
+ def _negotiated(params: dict) -> str:
185
+ """The client's revision when this server implements it, else the newest it
186
+ does; the spec leaves proceeding or disconnecting to the client from there."""
187
+ offered = params.get("protocolVersion")
188
+ return offered if offered in SUPPORTED_PROTOCOLS else SUPPORTED_PROTOCOLS[0]
189
+
190
+
191
+ def _structured(result: dict) -> dict:
192
+ """Attach the parsed object beside the text when the text is a JSON object.
193
+
194
+ The --json commands print for machines; a client on the 2025-06-18 revision
195
+ reads structuredContent directly. Prose, JSON arrays and error text stay
196
+ text-only rather than getting wrapped into shapes the tools never promised.
197
+ """
198
+ if result["isError"]:
199
+ return result
200
+ try:
201
+ parsed = json.loads(result["content"][0]["text"])
202
+ except ValueError:
203
+ return result
204
+ if isinstance(parsed, dict):
205
+ return {**result, "structuredContent": parsed}
206
+ return result
207
+
208
+
133
209
  def _respond(msg_id, result=None, error=None) -> dict:
134
210
  resp = {"jsonrpc": "2.0", "id": msg_id}
135
211
  resp["error" if error else "result"] = error if error else result
@@ -142,9 +218,10 @@ def _handle(root: Path, msg: dict) -> dict | None:
142
218
  return None # a notification (e.g. notifications/initialized) needs no reply
143
219
  if method == "initialize":
144
220
  return _respond(msg["id"], {
145
- "protocolVersion": PROTOCOL_VERSION,
221
+ "protocolVersion": _negotiated(msg.get("params", {})),
146
222
  "capabilities": {"tools": {}},
147
- "serverInfo": {"name": "crapkit", "version": _version()}})
223
+ "serverInfo": {"name": "crapkit", "version": _version()},
224
+ "instructions": _INSTRUCTIONS})
148
225
  if method == "tools/list":
149
226
  return _respond(msg["id"], {"tools": tool_listing()})
150
227
  if method == "tools/call":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crapkit
3
- Version: 0.4.12
3
+ Version: 0.4.14
4
4
  Summary: Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more.
5
5
  Author: Jean-Francois Gagne
6
6
  License: MIT
@@ -39,10 +39,13 @@ Dynamic: license-file
39
39
 
40
40
  # crapkit
41
41
 
42
+ <!-- mcp-name: io.github.jeanfrancoisgagne/crapkit -->
43
+
42
44
  [![ci](https://github.com/JeanFrancoisGagne/crapkit/actions/workflows/ci.yml/badge.svg)](https://github.com/JeanFrancoisGagne/crapkit/actions/workflows/ci.yml)
43
45
  [![PyPI](https://img.shields.io/pypi/v/crapkit)](https://pypi.org/project/crapkit/)
44
46
  [![Python](https://img.shields.io/pypi/pyversions/crapkit)](https://pypi.org/project/crapkit/)
45
47
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/JeanFrancoisGagne/crapkit/blob/main/LICENSE)
48
+ [![crapkit MCP server](https://glama.ai/mcp/servers/JeanFrancoisGagne/crapkit/badges/score.svg)](https://glama.ai/mcp/servers/JeanFrancoisGagne/crapkit)
46
49
 
47
50
  ![crapkit init, coverage and worklist --top 5 on a small Python repo, then a shell heredoc adding a function at ccn 7: the per-edit advisory reports it and exits 2, and the commit gate refuses the staged file with exit 6](https://raw.githubusercontent.com/JeanFrancoisGagne/crapkit/main/docs/demo.gif)
48
51
 
@@ -165,7 +168,7 @@ leaves the box
165
168
 
166
169
  ```
167
170
  $ crapkit --version
168
- crapkit 0.4.12
171
+ crapkit 0.4.14
169
172
  ```
170
173
 
171
174
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -395,7 +398,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
395
398
  repos:
396
399
  - repo: https://github.com/JeanFrancoisGagne/crapkit
397
400
  # crapkit's release step rewrites this line to the tag it just cut
398
- rev: v0.4.12
401
+ rev: v0.4.14
399
402
  hooks:
400
403
  - id: crapkit-gate
401
404
  ```
@@ -501,7 +504,7 @@ to a workflow, and every input has a default:
501
504
  - uses: actions/checkout@v4
502
505
  with:
503
506
  fetch-depth: 0
504
- - uses: JeanFrancoisGagne/crapkit@v0.4.12
507
+ - uses: JeanFrancoisGagne/crapkit@v0.4.14
505
508
  ```
506
509
 
507
510
  The whole job those four lines sit in:
@@ -521,7 +524,7 @@ jobs:
521
524
  with:
522
525
  python-version: "3.12" # the interpreter the install below lands in
523
526
  - run: pip install -e ".[dev]" # whatever your lanes need to run
524
- - uses: JeanFrancoisGagne/crapkit@v0.4.12
527
+ - uses: JeanFrancoisGagne/crapkit@v0.4.14
525
528
  with:
526
529
  gate: "false"
527
530
  ```
@@ -1189,6 +1192,7 @@ that block is advisory unless `diff_uncovered_max` is set
1189
1192
  | [docs/lanes.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/lanes.md) | The lane model, vitest and jest and pytest recipes, artifact reuse, flake retest, containers. |
1190
1193
  | [docs/ratchet.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/ratchet.md) | Seeding, pruning, the git merge driver, metric stamps, debt policy, overrides. |
1191
1194
  | [docs/agent-json.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/agent-json.md) | The machine surface: `schema`, every payload field, real captured examples. |
1195
+ | [docs/comparison.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/docs/comparison.md) | Where crapkit sits next to radon, xenon, wily, coverage.py and SonarQube, and how they run together. |
1192
1196
  | [AGENTS.md](https://github.com/JeanFrancoisGagne/crapkit/blob/main/AGENTS.md) | The burn-down loop an agent runs, and the rules for changing crapkit itself. |
1193
1197
  | [plugin/](https://github.com/JeanFrancoisGagne/crapkit/tree/main/plugin) | The Claude Code plugin: three skills, the read-side MCP server, and the advisory PostToolUse hook. |
1194
1198
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes