craftsman-gate 4.1.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 (133) hide show
  1. craftsman_gate-4.1.0/.gitattributes +12 -0
  2. craftsman_gate-4.1.0/LICENSE +21 -0
  3. craftsman_gate-4.1.0/MANIFEST.in +6 -0
  4. craftsman_gate-4.1.0/NOTICE +12 -0
  5. craftsman_gate-4.1.0/PKG-INFO +165 -0
  6. craftsman_gate-4.1.0/README.md +131 -0
  7. craftsman_gate-4.1.0/craftsman/__init__.py +7 -0
  8. craftsman_gate-4.1.0/craftsman/antigravity.py +242 -0
  9. craftsman_gate-4.1.0/craftsman/assets.py +204 -0
  10. craftsman_gate-4.1.0/craftsman/cli.py +456 -0
  11. craftsman_gate-4.1.0/craftsman/diffscan.py +1361 -0
  12. craftsman_gate-4.1.0/craftsman/diffscan_signals.py +228 -0
  13. craftsman_gate-4.1.0/craftsman/doctor.py +886 -0
  14. craftsman_gate-4.1.0/craftsman/evidence.py +547 -0
  15. craftsman_gate-4.1.0/craftsman/export.py +228 -0
  16. craftsman_gate-4.1.0/craftsman/files.py +141 -0
  17. craftsman_gate-4.1.0/craftsman/gate.py +681 -0
  18. craftsman_gate-4.1.0/craftsman/gatecheck.py +754 -0
  19. craftsman_gate-4.1.0/craftsman/generated.py +147 -0
  20. craftsman_gate-4.1.0/craftsman/hook_command.py +83 -0
  21. craftsman_gate-4.1.0/craftsman/hooks.py +727 -0
  22. craftsman_gate-4.1.0/craftsman/install.py +378 -0
  23. craftsman_gate-4.1.0/craftsman/ledger.py +1295 -0
  24. craftsman_gate-4.1.0/craftsman/legacy_githooks.py +147 -0
  25. craftsman_gate-4.1.0/craftsman/lenses.py +223 -0
  26. craftsman_gate-4.1.0/craftsman/manifest.py +254 -0
  27. craftsman_gate-4.1.0/craftsman/packet.py +751 -0
  28. craftsman_gate-4.1.0/craftsman/paths.py +99 -0
  29. craftsman_gate-4.1.0/craftsman/platforms/__init__.py +1 -0
  30. craftsman_gate-4.1.0/craftsman/platforms/agents_md.py +77 -0
  31. craftsman_gate-4.1.0/craftsman/platforms/antigravity.py +151 -0
  32. craftsman_gate-4.1.0/craftsman/platforms/claude.py +414 -0
  33. craftsman_gate-4.1.0/craftsman/platforms/copilot.py +459 -0
  34. craftsman_gate-4.1.0/craftsman/playbooks.py +113 -0
  35. craftsman_gate-4.1.0/craftsman/quality.py +368 -0
  36. craftsman_gate-4.1.0/craftsman/records.py +1216 -0
  37. craftsman_gate-4.1.0/craftsman/review.py +2121 -0
  38. craftsman_gate-4.1.0/craftsman/runner.py +313 -0
  39. craftsman_gate-4.1.0/craftsman/scope.py +559 -0
  40. craftsman_gate-4.1.0/craftsman/shell.py +710 -0
  41. craftsman_gate-4.1.0/craftsman/staging.py +365 -0
  42. craftsman_gate-4.1.0/craftsman/streams.py +24 -0
  43. craftsman_gate-4.1.0/craftsman/text.py +112 -0
  44. craftsman_gate-4.1.0/craftsman/transcript.py +234 -0
  45. craftsman_gate-4.1.0/craftsman/trigger.py +88 -0
  46. craftsman_gate-4.1.0/craftsman/triviality.py +193 -0
  47. craftsman_gate-4.1.0/craftsman/uninstall.py +240 -0
  48. craftsman_gate-4.1.0/craftsman/workspace.py +52 -0
  49. craftsman_gate-4.1.0/craftsman/worktree_guard.py +35 -0
  50. craftsman_gate-4.1.0/craftsman_gate.egg-info/PKG-INFO +165 -0
  51. craftsman_gate-4.1.0/craftsman_gate.egg-info/SOURCES.txt +131 -0
  52. craftsman_gate-4.1.0/craftsman_gate.egg-info/dependency_links.txt +1 -0
  53. craftsman_gate-4.1.0/craftsman_gate.egg-info/entry_points.txt +2 -0
  54. craftsman_gate-4.1.0/craftsman_gate.egg-info/requires.txt +5 -0
  55. craftsman_gate-4.1.0/craftsman_gate.egg-info/top_level.txt +1 -0
  56. craftsman_gate-4.1.0/hooks.json +27 -0
  57. craftsman_gate-4.1.0/plugin.json +25 -0
  58. craftsman_gate-4.1.0/pyproject.toml +84 -0
  59. craftsman_gate-4.1.0/rules/ultra_mode.md +36 -0
  60. craftsman_gate-4.1.0/scripts/copilot-export.py +16 -0
  61. craftsman_gate-4.1.0/scripts/craftsman-cli.py +16 -0
  62. craftsman_gate-4.1.0/scripts/craftsman-scope-guard.py +16 -0
  63. craftsman_gate-4.1.0/scripts/craftsman-stop-gate.py +16 -0
  64. craftsman_gate-4.1.0/setup.cfg +4 -0
  65. craftsman_gate-4.1.0/setup.py +36 -0
  66. craftsman_gate-4.1.0/skills/audit/SKILL.md +51 -0
  67. craftsman_gate-4.1.0/skills/audit-comments/SKILL.md +50 -0
  68. craftsman_gate-4.1.0/skills/audit-errors/SKILL.md +64 -0
  69. craftsman_gate-4.1.0/skills/audit-security/SKILL.md +66 -0
  70. craftsman_gate-4.1.0/skills/audit-tests/SKILL.md +60 -0
  71. craftsman_gate-4.1.0/skills/audit-types/SKILL.md +59 -0
  72. craftsman_gate-4.1.0/skills/batch/SKILL.md +52 -0
  73. craftsman_gate-4.1.0/skills/code-quality/SKILL.md +202 -0
  74. craftsman_gate-4.1.0/skills/code-review/SKILL.md +516 -0
  75. craftsman_gate-4.1.0/skills/code-review/lenses.md +345 -0
  76. craftsman_gate-4.1.0/skills/craftsman/SKILL.md +186 -0
  77. craftsman_gate-4.1.0/skills/craftsman/scope.md +8 -0
  78. craftsman_gate-4.1.0/skills/extreme-optimizer/SKILL.md +128 -0
  79. craftsman_gate-4.1.0/skills/feature-dev/SKILL.md +63 -0
  80. craftsman_gate-4.1.0/skills/high-level-commit-message/SKILL.md +145 -0
  81. craftsman_gate-4.1.0/skills/leetcode-style-code-reviewer/SKILL.md +379 -0
  82. craftsman_gate-4.1.0/skills/modernize/SKILL.md +62 -0
  83. craftsman_gate-4.1.0/skills/simplify/SKILL.md +68 -0
  84. craftsman_gate-4.1.0/skills/verify/SKILL.md +92 -0
  85. craftsman_gate-4.1.0/templates/AGENTS.md +22 -0
  86. craftsman_gate-4.1.0/templates/agents/craftsman-reviewer.md +145 -0
  87. craftsman_gate-4.1.0/templates/agents/craftsman-verifier.md +163 -0
  88. craftsman_gate-4.1.0/templates/agents/craftsman.md +27 -0
  89. craftsman_gate-4.1.0/tests/test_antigravity.py +294 -0
  90. craftsman_gate-4.1.0/tests/test_assets.py +179 -0
  91. craftsman_gate-4.1.0/tests/test_cli.py +178 -0
  92. craftsman_gate-4.1.0/tests/test_diffscan.py +1224 -0
  93. craftsman_gate-4.1.0/tests/test_diffscan_signals.py +191 -0
  94. craftsman_gate-4.1.0/tests/test_docs_links.py +144 -0
  95. craftsman_gate-4.1.0/tests/test_doctor.py +534 -0
  96. craftsman_gate-4.1.0/tests/test_evidence.py +89 -0
  97. craftsman_gate-4.1.0/tests/test_export_cli.py +190 -0
  98. craftsman_gate-4.1.0/tests/test_files.py +140 -0
  99. craftsman_gate-4.1.0/tests/test_gate.py +515 -0
  100. craftsman_gate-4.1.0/tests/test_gatecheck.py +676 -0
  101. craftsman_gate-4.1.0/tests/test_generated.py +104 -0
  102. craftsman_gate-4.1.0/tests/test_hooks.py +902 -0
  103. craftsman_gate-4.1.0/tests/test_install.py +819 -0
  104. craftsman_gate-4.1.0/tests/test_ledger.py +934 -0
  105. craftsman_gate-4.1.0/tests/test_lenses.py +152 -0
  106. craftsman_gate-4.1.0/tests/test_manifest.py +278 -0
  107. craftsman_gate-4.1.0/tests/test_packet.py +406 -0
  108. craftsman_gate-4.1.0/tests/test_packet_shards.py +412 -0
  109. craftsman_gate-4.1.0/tests/test_partial_report.py +272 -0
  110. craftsman_gate-4.1.0/tests/test_platform_agents_md.py +59 -0
  111. craftsman_gate-4.1.0/tests/test_platform_claude.py +367 -0
  112. craftsman_gate-4.1.0/tests/test_platform_copilot.py +282 -0
  113. craftsman_gate-4.1.0/tests/test_playbooks.py +178 -0
  114. craftsman_gate-4.1.0/tests/test_pruning.py +765 -0
  115. craftsman_gate-4.1.0/tests/test_quality.py +224 -0
  116. craftsman_gate-4.1.0/tests/test_quick_cover.py +549 -0
  117. craftsman_gate-4.1.0/tests/test_records.py +232 -0
  118. craftsman_gate-4.1.0/tests/test_review.py +1587 -0
  119. craftsman_gate-4.1.0/tests/test_review_scope.py +607 -0
  120. craftsman_gate-4.1.0/tests/test_runner.py +181 -0
  121. craftsman_gate-4.1.0/tests/test_scope.py +1110 -0
  122. craftsman_gate-4.1.0/tests/test_shell.py +139 -0
  123. craftsman_gate-4.1.0/tests/test_staging.py +130 -0
  124. craftsman_gate-4.1.0/tests/test_streams.py +59 -0
  125. craftsman_gate-4.1.0/tests/test_text.py +429 -0
  126. craftsman_gate-4.1.0/tests/test_transcript.py +134 -0
  127. craftsman_gate-4.1.0/tests/test_trigger.py +182 -0
  128. craftsman_gate-4.1.0/tests/test_triviality.py +89 -0
  129. craftsman_gate-4.1.0/tests/test_uninstall.py +279 -0
  130. craftsman_gate-4.1.0/tests/test_version.py +82 -0
  131. craftsman_gate-4.1.0/tests/test_working_tree_hygiene.py +346 -0
  132. craftsman_gate-4.1.0/tests/test_workspace.py +67 -0
  133. craftsman_gate-4.1.0/tests/test_worktree_guard.py +354 -0
@@ -0,0 +1,12 @@
1
+ * text=auto eol=lf
2
+ *.sh text eol=lf
3
+ *.py text eol=lf
4
+ *.md text eol=lf
5
+ *.json text eol=lf
6
+ *.excalidraw text eol=lf
7
+ *.png binary
8
+ *.toml text eol=lf
9
+ *.yml text eol=lf
10
+ *.yaml text eol=lf
11
+ Makefile text eol=lf
12
+ *.cmd text eol=crlf
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rafał Buczyński
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ graft skills
2
+ graft rules
3
+ graft scripts
4
+ graft templates
5
+ include plugin.json hooks.json LICENSE NOTICE README.md setup.py pyproject.toml .gitattributes
6
+ global-exclude __pycache__ *.py[cod] .DS_Store
@@ -0,0 +1,12 @@
1
+ Craftsman
2
+ Copyright (c) 2026 Rafał Buczyński
3
+
4
+ Make AI coding agents prove they reviewed and tested their change.
5
+
6
+ This software includes:
7
+ - Quality gate and review verification runtime (`craftsman/`).
8
+ - Lifecycle hook scripts and adapters for Claude Code, GitHub Copilot, and Google Antigravity.
9
+ - Curated software engineering review skills and agent templates.
10
+ - Test suites and benchmarks.
11
+
12
+ All components are licensed under the MIT License (see LICENSE).
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: craftsman-gate
3
+ Version: 4.1.0
4
+ Summary: Make AI coding agents prove they reviewed and tested their change
5
+ Author-email: Rafał Buczyński <buczynski.rafal2@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/BuczynskiRafal/craftsman
8
+ Project-URL: Repository, https://github.com/BuczynskiRafal/craftsman
9
+ Project-URL: Issues, https://github.com/BuczynskiRafal/craftsman/issues
10
+ Keywords: ai-agents,claude-code,github-copilot,codex,cursor,antigravity,quality-gate,developer-tools,code-review,ai-code-review,llm,coding-agents,agents-md
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ License-File: NOTICE
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.8.0; extra == "dev"
31
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # Craftsman
36
+
37
+ [![CI](https://github.com/BuczynskiRafal/craftsman/actions/workflows/ci.yml/badge.svg)](https://github.com/BuczynskiRafal/craftsman/actions/workflows/ci.yml)
38
+ [![Version](https://img.shields.io/badge/version-4.1.0-emerald.svg)](https://github.com/BuczynskiRafal/craftsman/blob/main/plugin.json)
39
+ [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10--3.13-blue.svg)](https://github.com/BuczynskiRafal/craftsman/blob/main/pyproject.toml)
40
+ [![Dependencies: stdlib only](https://img.shields.io/badge/dependencies-stdlib%20only-brightgreen.svg)](https://github.com/BuczynskiRafal/craftsman/blob/main/pyproject.toml)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/BuczynskiRafal/craftsman/blob/main/LICENSE)
42
+
43
+ Craftsman adds a layer to an AI coding agent that checks the quality and simplicity of the code it
44
+ delivers. The agent cannot finish its work until an independent reviewer has examined the change.
45
+ Craftsman enforces a review loop in which every reported defect is verified and resolved, and at the
46
+ end the tests must pass on the final version of the code.
47
+
48
+ Craftsman keeps a ledger in the `.git` directory, where it collects evidence from review,
49
+ verification, and tests. When the agent tries to finish, a platform hook checks the ledger and, if
50
+ anything is missing, sends the agent back with a list of what is missing.
51
+
52
+ It works with Claude Code, GitHub Copilot, and Google Antigravity. In Codex and Cursor, the same
53
+ protocol runs without hooks.
54
+
55
+ ## How It Works
56
+
57
+ ![Craftsman engineering loop](https://raw.githubusercontent.com/BuczynskiRafal/craftsman/main/docs/diagrams/harness-overview.png)
58
+
59
+ 1. `/craftsman <task>` records the base commit that the diff is computed from.
60
+ 2. The agent implements the change and cleans it up with `/simplify`.
61
+ 3. Craftsman splits the diff into hunks and attaches to each one the context the diff does not show:
62
+ the rest of the function, its call sites, and risk signals. Defects usually sit where the change
63
+ meets that code.
64
+ 4. Reviewer subagents that did not write the change examine every hunk through several lenses (for
65
+ example contracts, error handling, tests, security) and record suspected defects.
66
+ 5. A separate verifier tries to disprove or reproduce each suspected defect. Searching and judging
67
+ are kept apart so that the reviewer does not drop leads too early.
68
+ 6. The agent fixes confirmed defects. Every piece of evidence in the ledger is tied to a diff
69
+ fingerprint, so a code change invalidates the old review; the next round covers only the changed
70
+ parts.
71
+ 7. The agent runs the tests through `craftsman run`, which records the exit code.
72
+ 8. The stop hook lets the agent finish once the ledger is complete for the current version of the
73
+ code. The agent prints a report: what was reviewed, what was found, what was fixed, what was
74
+ dismissed, and what the gate did not assess.
75
+
76
+ The level of rigor depends on the gear: `quick` for small changes (review in the current context),
77
+ `standard` by default (subagents and a verifier), `deep` for risky changes (all lenses, tests, and
78
+ e2e).
79
+
80
+ `/code-review` runs only the review of an existing change, without fixes.
81
+
82
+ ## Platforms
83
+
84
+ | Platform | What blocks completion |
85
+ | :--- | :--- |
86
+ | Claude Code | `Stop` hook |
87
+ | GitHub Copilot in VS Code | `Stop` hook |
88
+ | GitHub Copilot CLI | `agentStop` hook |
89
+ | Google Antigravity | `Stop` hook |
90
+ | Codex, Cursor, and other tools that read `AGENTS.md` | `craftsman gate --complete` |
91
+
92
+ Where the platform cannot confirm that a separate subagent performed the review, the report marks it
93
+ as `SELF-REVIEWED`.
94
+
95
+ ## Measurements
96
+
97
+ Pilot on [SWR-Bench](https://doi.org/10.1145/3808144): 5 PRs from `pytest`, `flask`, and `requests`,
98
+ 9 confirmed defects, 1 repetition per configuration. The sample is too small for statistical
99
+ conclusions. Details and Claude Code runs: [docs/benchmark.md](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/benchmark.md).
100
+
101
+ | Configuration | Defects: without → with Craftsman | Avg. time per PR |
102
+ | :--- | :---: | :---: |
103
+ | Antigravity · Gemini 4 Argon `low` | 2/9 → 4/9 | 1.5 → 4.7 min |
104
+ | Antigravity · Gemini 4 Argon `medium` | 2/9 → 5/9 | 2.0 → 6.7 min |
105
+ | Antigravity · Gemini 4 Argon `high` | 3/9 → 7/9 | 15.6 → 26.3 min |
106
+
107
+ ## Installation
108
+
109
+ Requires Python 3.10+, with no external dependencies.
110
+
111
+ ```bash
112
+ pipx install craftsman-gate # or: uv tool install craftsman-gate
113
+ craftsman install # detects Claude Code, Copilot, and Antigravity
114
+ ```
115
+
116
+ Or as a plugin:
117
+
118
+ ```bash
119
+ /plugin marketplace add BuczynskiRafal/craftsman # Claude Code
120
+ /plugin install craftsman@craftsman
121
+ agy plugin install https://github.com/BuczynskiRafal/craftsman # Antigravity
122
+ ```
123
+
124
+ ## Usage
125
+
126
+ ```
127
+ /craftsman add pagination to the catalog search
128
+ /craftsman --deep migrate the session store to Redis
129
+ /code-review
130
+ ```
131
+
132
+ `craftsman status` shows the current gear, round, and missing evidence.
133
+
134
+ ## Documentation
135
+
136
+ - [How it works](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/how-it-works.md): ledger, lenses, gears, platform enforcement
137
+ - [Installation](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/installation.md): flags, platform setup, uninstall, `craftsman doctor`
138
+ - [Usage](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/usage.md): commands, review-only mode
139
+ - [Guards](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/guards.md): gate state machine, search scope guard
140
+ - [Skills](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/skills.md): catalog of bundled skills
141
+ - [Benchmark](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/benchmark.md): methodology, results, replication
142
+ - [Architecture](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/harness-architecture.md): project internals for contributors
143
+
144
+ ## Contributing
145
+
146
+ See [CONTRIBUTING.md](https://github.com/BuczynskiRafal/craftsman/blob/main/CONTRIBUTING.md).
147
+
148
+ ## Citation
149
+
150
+ ```bibtex
151
+ @article{zeng2026swrbench,
152
+ author = {Zeng, Zhengran and Shi, Ruikai and Han, Keke and Li, Yixin and Sun, Kaicheng and Wang, Yidong and Yu, Zhuohao and Xie, Rui and Ye, Wei and Zhang, Shikun},
153
+ title = {{SWR-Bench}: Assessing {LLM} Performance in Real-World Code Review Comment Generation},
154
+ journal = {Proceedings of the ACM on Software Engineering},
155
+ volume = {3},
156
+ number = {FSE},
157
+ articleno = {FSE137},
158
+ year = {2026},
159
+ doi = {10.1145/3808144}
160
+ }
161
+ ```
162
+
163
+ ## License
164
+
165
+ MIT, see [LICENSE](https://github.com/BuczynskiRafal/craftsman/blob/main/LICENSE) and attribution in [NOTICE](https://github.com/BuczynskiRafal/craftsman/blob/main/NOTICE).
@@ -0,0 +1,131 @@
1
+ # Craftsman
2
+
3
+ [![CI](https://github.com/BuczynskiRafal/craftsman/actions/workflows/ci.yml/badge.svg)](https://github.com/BuczynskiRafal/craftsman/actions/workflows/ci.yml)
4
+ [![Version](https://img.shields.io/badge/version-4.1.0-emerald.svg)](https://github.com/BuczynskiRafal/craftsman/blob/main/plugin.json)
5
+ [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10--3.13-blue.svg)](https://github.com/BuczynskiRafal/craftsman/blob/main/pyproject.toml)
6
+ [![Dependencies: stdlib only](https://img.shields.io/badge/dependencies-stdlib%20only-brightgreen.svg)](https://github.com/BuczynskiRafal/craftsman/blob/main/pyproject.toml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/BuczynskiRafal/craftsman/blob/main/LICENSE)
8
+
9
+ Craftsman adds a layer to an AI coding agent that checks the quality and simplicity of the code it
10
+ delivers. The agent cannot finish its work until an independent reviewer has examined the change.
11
+ Craftsman enforces a review loop in which every reported defect is verified and resolved, and at the
12
+ end the tests must pass on the final version of the code.
13
+
14
+ Craftsman keeps a ledger in the `.git` directory, where it collects evidence from review,
15
+ verification, and tests. When the agent tries to finish, a platform hook checks the ledger and, if
16
+ anything is missing, sends the agent back with a list of what is missing.
17
+
18
+ It works with Claude Code, GitHub Copilot, and Google Antigravity. In Codex and Cursor, the same
19
+ protocol runs without hooks.
20
+
21
+ ## How It Works
22
+
23
+ ![Craftsman engineering loop](https://raw.githubusercontent.com/BuczynskiRafal/craftsman/main/docs/diagrams/harness-overview.png)
24
+
25
+ 1. `/craftsman <task>` records the base commit that the diff is computed from.
26
+ 2. The agent implements the change and cleans it up with `/simplify`.
27
+ 3. Craftsman splits the diff into hunks and attaches to each one the context the diff does not show:
28
+ the rest of the function, its call sites, and risk signals. Defects usually sit where the change
29
+ meets that code.
30
+ 4. Reviewer subagents that did not write the change examine every hunk through several lenses (for
31
+ example contracts, error handling, tests, security) and record suspected defects.
32
+ 5. A separate verifier tries to disprove or reproduce each suspected defect. Searching and judging
33
+ are kept apart so that the reviewer does not drop leads too early.
34
+ 6. The agent fixes confirmed defects. Every piece of evidence in the ledger is tied to a diff
35
+ fingerprint, so a code change invalidates the old review; the next round covers only the changed
36
+ parts.
37
+ 7. The agent runs the tests through `craftsman run`, which records the exit code.
38
+ 8. The stop hook lets the agent finish once the ledger is complete for the current version of the
39
+ code. The agent prints a report: what was reviewed, what was found, what was fixed, what was
40
+ dismissed, and what the gate did not assess.
41
+
42
+ The level of rigor depends on the gear: `quick` for small changes (review in the current context),
43
+ `standard` by default (subagents and a verifier), `deep` for risky changes (all lenses, tests, and
44
+ e2e).
45
+
46
+ `/code-review` runs only the review of an existing change, without fixes.
47
+
48
+ ## Platforms
49
+
50
+ | Platform | What blocks completion |
51
+ | :--- | :--- |
52
+ | Claude Code | `Stop` hook |
53
+ | GitHub Copilot in VS Code | `Stop` hook |
54
+ | GitHub Copilot CLI | `agentStop` hook |
55
+ | Google Antigravity | `Stop` hook |
56
+ | Codex, Cursor, and other tools that read `AGENTS.md` | `craftsman gate --complete` |
57
+
58
+ Where the platform cannot confirm that a separate subagent performed the review, the report marks it
59
+ as `SELF-REVIEWED`.
60
+
61
+ ## Measurements
62
+
63
+ Pilot on [SWR-Bench](https://doi.org/10.1145/3808144): 5 PRs from `pytest`, `flask`, and `requests`,
64
+ 9 confirmed defects, 1 repetition per configuration. The sample is too small for statistical
65
+ conclusions. Details and Claude Code runs: [docs/benchmark.md](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/benchmark.md).
66
+
67
+ | Configuration | Defects: without → with Craftsman | Avg. time per PR |
68
+ | :--- | :---: | :---: |
69
+ | Antigravity · Gemini 4 Argon `low` | 2/9 → 4/9 | 1.5 → 4.7 min |
70
+ | Antigravity · Gemini 4 Argon `medium` | 2/9 → 5/9 | 2.0 → 6.7 min |
71
+ | Antigravity · Gemini 4 Argon `high` | 3/9 → 7/9 | 15.6 → 26.3 min |
72
+
73
+ ## Installation
74
+
75
+ Requires Python 3.10+, with no external dependencies.
76
+
77
+ ```bash
78
+ pipx install craftsman-gate # or: uv tool install craftsman-gate
79
+ craftsman install # detects Claude Code, Copilot, and Antigravity
80
+ ```
81
+
82
+ Or as a plugin:
83
+
84
+ ```bash
85
+ /plugin marketplace add BuczynskiRafal/craftsman # Claude Code
86
+ /plugin install craftsman@craftsman
87
+ agy plugin install https://github.com/BuczynskiRafal/craftsman # Antigravity
88
+ ```
89
+
90
+ ## Usage
91
+
92
+ ```
93
+ /craftsman add pagination to the catalog search
94
+ /craftsman --deep migrate the session store to Redis
95
+ /code-review
96
+ ```
97
+
98
+ `craftsman status` shows the current gear, round, and missing evidence.
99
+
100
+ ## Documentation
101
+
102
+ - [How it works](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/how-it-works.md): ledger, lenses, gears, platform enforcement
103
+ - [Installation](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/installation.md): flags, platform setup, uninstall, `craftsman doctor`
104
+ - [Usage](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/usage.md): commands, review-only mode
105
+ - [Guards](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/guards.md): gate state machine, search scope guard
106
+ - [Skills](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/skills.md): catalog of bundled skills
107
+ - [Benchmark](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/benchmark.md): methodology, results, replication
108
+ - [Architecture](https://github.com/BuczynskiRafal/craftsman/blob/main/docs/harness-architecture.md): project internals for contributors
109
+
110
+ ## Contributing
111
+
112
+ See [CONTRIBUTING.md](https://github.com/BuczynskiRafal/craftsman/blob/main/CONTRIBUTING.md).
113
+
114
+ ## Citation
115
+
116
+ ```bibtex
117
+ @article{zeng2026swrbench,
118
+ author = {Zeng, Zhengran and Shi, Ruikai and Han, Keke and Li, Yixin and Sun, Kaicheng and Wang, Yidong and Yu, Zhuohao and Xie, Rui and Ye, Wei and Zhang, Shikun},
119
+ title = {{SWR-Bench}: Assessing {LLM} Performance in Real-World Code Review Comment Generation},
120
+ journal = {Proceedings of the ACM on Software Engineering},
121
+ volume = {3},
122
+ number = {FSE},
123
+ articleno = {FSE137},
124
+ year = {2026},
125
+ doi = {10.1145/3808144}
126
+ }
127
+ ```
128
+
129
+ ## License
130
+
131
+ MIT, see [LICENSE](https://github.com/BuczynskiRafal/craftsman/blob/main/LICENSE) and attribution in [NOTICE](https://github.com/BuczynskiRafal/craftsman/blob/main/NOTICE).
@@ -0,0 +1,7 @@
1
+ """Make AI coding agents prove they reviewed and tested their change."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = ["__version__"]
6
+
7
+ __version__ = "4.1.0"
@@ -0,0 +1,242 @@
1
+ """The Antigravity Stop hook: `scripts/craftsman-stop-gate.py`, wired by `hooks.json`.
2
+
3
+ Antigravity has no prompt or subagent hooks, so this hook reads the session transcript instead:
4
+ - a `/craftsman` or `ultracraft` request opens a gate when the agent did not open one itself;
5
+ - `craftsman-reviewer` subagents that finished since the request are recorded in the evidence
6
+ ledger, which is the proof of reviewer isolation for Antigravity sessions;
7
+ - an unanswered `ask_question` lets the agent stop to wait for the user.
8
+ Then the gate's shared stop decision applies (`craftsman.gate.decide_stop`).
9
+
10
+ The hook reads a JSON object on stdin with `terminationReason`, `fullyIdle`,
11
+ `artifactDirectoryPath` (the session directory, `<appDataDir>/brain/<conversation id>`) and
12
+ `workspacePaths`, and answers `{"decision": "continue" | "allow", "reason": ...}`. When the gate
13
+ cannot be evaluated, it lets the agent stop and says why on stderr.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import sys
20
+ from collections.abc import Mapping, Sequence
21
+ from dataclasses import dataclass, replace
22
+ from datetime import datetime, timezone
23
+ from pathlib import Path
24
+ from typing import Any, Literal
25
+
26
+ from craftsman.diffscan import DiffScanError, resolve_base_sha, snapshot, worktree_root
27
+ from craftsman.gate import (
28
+ GateOwner,
29
+ GateState,
30
+ GateStateError,
31
+ GateTier,
32
+ archive_ledger_for_new_base,
33
+ decide_stop,
34
+ state_path_for,
35
+ update_state,
36
+ )
37
+ from craftsman.ledger import add_agent_event, ledger_path_for, mutate
38
+ from craftsman.records import REVIEWER_AGENT, AgentEvent, Ledger, LedgerError, format_timestamp
39
+ from craftsman.streams import configure_std_streams
40
+ from craftsman.transcript import (
41
+ Entry,
42
+ TranscriptError,
43
+ finished_reviewers,
44
+ is_waiting_on_question,
45
+ latest_user_input,
46
+ read_transcript_entries,
47
+ step_of,
48
+ )
49
+ from craftsman.trigger import detect_trigger, task_from_prompt
50
+
51
+ PLATFORM: Literal["antigravity"] = "antigravity"
52
+ TRANSCRIPT = Path(".system_generated") / "logs" / "transcript.jsonl"
53
+
54
+
55
+ class PayloadError(ValueError):
56
+ """The Stop payload lacks what the hook needs to find the session and its work tree."""
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class StopPayload:
61
+ session_dir: Path
62
+ workspace: Path
63
+
64
+ @classmethod
65
+ def parse(cls, payload: Mapping[str, Any]) -> StopPayload:
66
+ session_dir = payload.get("artifactDirectoryPath")
67
+ workspaces = payload.get("workspacePaths")
68
+ if not isinstance(session_dir, str) or not session_dir.strip():
69
+ raise PayloadError("the Stop payload has no artifactDirectoryPath")
70
+ if not isinstance(workspaces, list) or not workspaces or not isinstance(workspaces[0], str):
71
+ raise PayloadError("the Stop payload has no workspacePaths")
72
+ return cls(Path(session_dir).expanduser(), Path(workspaces[0]).expanduser())
73
+
74
+ @property
75
+ def caller(self) -> GateOwner:
76
+ return GateOwner(PLATFORM, self.session_dir.name)
77
+
78
+
79
+ def session_id(payload: Mapping[str, Any]) -> str | None:
80
+ """The conversation id of an Antigravity hook payload: the name of its session directory."""
81
+ session_dir = payload.get("artifactDirectoryPath")
82
+ if isinstance(session_dir, str) and session_dir.strip():
83
+ return Path(session_dir).expanduser().name
84
+ return None
85
+
86
+
87
+ def is_final_stop(payload: Mapping[str, Any]) -> bool:
88
+ """True when the agent ends its turn, rather than pausing between tool calls."""
89
+ reason, idle = payload.get("terminationReason"), payload.get("fullyIdle")
90
+ if not isinstance(reason, str) or not isinstance(idle, bool):
91
+ raise PayloadError("the Stop payload needs `terminationReason` and `fullyIdle`")
92
+ return reason == "NO_TOOL_CALL" and idle
93
+
94
+
95
+ def on_stop(stop: StopPayload) -> str | None:
96
+ """The reason to keep the agent working, or None to let it stop."""
97
+ root = worktree_root(stop.workspace)
98
+ entries = read_transcript_entries(stop.session_dir / TRANSCRIPT)
99
+
100
+ def decide(state: GateState | None) -> tuple[GateState | None, str | None]:
101
+ state = _opened_by_request(state, root, entries, stop.caller)
102
+ if state is None or not state.active or not state.owner.admits(stop.caller):
103
+ return state, None
104
+ state = _claimed(state, entries, stop.caller)
105
+ _record_finished_reviewers(root, stop.session_dir, entries, state.request_step or 0)
106
+ if is_waiting_on_question(entries):
107
+ return state, None
108
+ decision = decide_stop(state, root)
109
+ if decision.notice is not None:
110
+ sys.stderr.write(decision.notice + "\n")
111
+ return decision.state, decision.block_reason
112
+
113
+ return update_state(state_path_for(root), decide)
114
+
115
+
116
+ def _opened_by_request(
117
+ state: GateState | None, root: Path, entries: Sequence[Entry], caller: GateOwner
118
+ ) -> GateState | None:
119
+ """The gate, opened for a `/craftsman` request the agent did not open a gate for."""
120
+ if state is not None and state.active:
121
+ return state
122
+ if state is not None and state.owner.admits(caller):
123
+ if state.request_step is None:
124
+ # Closed with `craftsman gate --complete` before any stop: the request was handled.
125
+ latest = latest_user_input(entries)
126
+ return state if latest is None else replace(state, request_step=latest[0])
127
+ handled_step = state.request_step
128
+ else:
129
+ handled_step = 0
130
+ request = _latest_trigger(entries, handled_step)
131
+ if request is None:
132
+ return state
133
+ step, tier, prompt = request
134
+ base_sha = resolve_base_sha(root, None)
135
+ if snapshot(root, base_sha).is_empty:
136
+ return state # nothing changed yet, so there is nothing to gate
137
+ archive_ledger_for_new_base(root, base_sha)
138
+ return GateState.open(caller, base_sha, tier, task_from_prompt(prompt), request_step=step)
139
+
140
+
141
+ def _latest_trigger(entries: Sequence[Entry], after_step: int) -> tuple[int, GateTier, str] | None:
142
+ """The step, tier and text of the latest user input after `after_step` that invokes Craftsman."""
143
+ for position in range(len(entries), 0, -1):
144
+ entry = entries[position - 1]
145
+ step = step_of(entry, position)
146
+ if step <= after_step:
147
+ return None
148
+ if entry.get("type") != "USER_INPUT":
149
+ continue
150
+ prompt = str(entry.get("content") or "")
151
+ tier = detect_trigger(prompt)
152
+ if tier is not None:
153
+ return step, tier, prompt
154
+ return None
155
+
156
+
157
+ def _claimed(state: GateState, entries: Sequence[Entry], caller: GateOwner) -> GateState:
158
+ """The gate as this session's stop hook serves it: owned, transcript-isolated, anchored."""
159
+ request_step = state.request_step
160
+ if request_step is None:
161
+ latest = latest_user_input(entries)
162
+ request_step = latest[0] if latest is not None else 0
163
+ return replace(
164
+ state,
165
+ owner=state.owner.claimed_by(caller),
166
+ isolation_evidence="transcript",
167
+ request_step=request_step,
168
+ )
169
+
170
+
171
+ def _record_finished_reviewers(
172
+ root: Path, session_dir: Path, entries: Sequence[Entry], after_step: int
173
+ ) -> None:
174
+ """Record each finished `craftsman-reviewer` subagent once, at the time its own run ended.
175
+
176
+ A subagent's session directory is a sibling of its parent's, named by its conversation id;
177
+ a subagent without one left no trace of having run, so it is not recorded.
178
+ """
179
+ events = [
180
+ event
181
+ for sub_id in finished_reviewers(entries, after_step)
182
+ if (event := _reviewer_stop(session_dir.parent / sub_id, sub_id)) is not None
183
+ ]
184
+ if events:
185
+ mutate(ledger_path_for(root), lambda ledger: (_with_new_events(ledger, events), None))
186
+
187
+
188
+ def _reviewer_stop(subagent_dir: Path, sub_id: str) -> AgentEvent | None:
189
+ try:
190
+ finished_at = (subagent_dir / TRANSCRIPT).stat().st_mtime
191
+ except FileNotFoundError:
192
+ return None
193
+ return AgentEvent(
194
+ platform=PLATFORM,
195
+ event="stop",
196
+ agent=REVIEWER_AGENT,
197
+ agent_id=sub_id,
198
+ at=format_timestamp(datetime.fromtimestamp(finished_at, timezone.utc)),
199
+ )
200
+
201
+
202
+ def _with_new_events(ledger: Ledger, events: Sequence[AgentEvent]) -> Ledger:
203
+ recorded = {(event.platform, event.agent_id) for event in ledger.agents if event.event == "stop"}
204
+ for event in events:
205
+ if (event.platform, event.agent_id) not in recorded:
206
+ ledger = add_agent_event(ledger, event)
207
+ return ledger
208
+
209
+
210
+ def answer(payload: object) -> dict[str, str]:
211
+ """The hook's answer to one Stop payload."""
212
+ if not isinstance(payload, dict):
213
+ return _allow_after_error("the Stop payload is not a JSON object")
214
+ try:
215
+ if not is_final_stop(payload):
216
+ return {"decision": "allow"}
217
+ reason = on_stop(StopPayload.parse(payload))
218
+ except (PayloadError, DiffScanError, LedgerError, GateStateError, TranscriptError, OSError) as exc:
219
+ return _allow_after_error(str(exc))
220
+ return {"decision": "continue", "reason": reason} if reason is not None else {"decision": "allow"}
221
+
222
+
223
+ def _allow_after_error(problem: str) -> dict[str, str]:
224
+ sys.stderr.write(
225
+ f"[craftsman-stop-gate] The quality gate was not checked: {problem}. "
226
+ "Run `craftsman review check` yourself.\n"
227
+ )
228
+ return {"decision": "allow"}
229
+
230
+
231
+ def main() -> int:
232
+ configure_std_streams()
233
+ try:
234
+ out = answer(json.loads(sys.stdin.read()))
235
+ except (OSError, ValueError) as exc:
236
+ out = _allow_after_error(f"unreadable hook input ({exc})")
237
+ sys.stdout.write(json.dumps(out, ensure_ascii=False) + "\n")
238
+ return 0
239
+
240
+
241
+ if __name__ == "__main__":
242
+ raise SystemExit(main())