overkill 0.8.2__tar.gz → 0.9.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 (93) hide show
  1. {overkill-0.8.2 → overkill-0.9.0}/.overkillrc.example +3 -0
  2. {overkill-0.8.2 → overkill-0.9.0}/PKG-INFO +122 -5
  3. {overkill-0.8.2 → overkill-0.9.0}/README.md +120 -3
  4. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-review.prompt.md +1 -1
  5. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/codex-review.prompt.md +1 -1
  6. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/gemini-review.prompt.md +1 -1
  7. {overkill-0.8.2 → overkill-0.9.0}/pyproject.toml +1 -1
  8. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/agents.py +220 -27
  9. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/cli.py +206 -3
  10. overkill-0.9.0/src/mr_overkill/commit_scope.py +125 -0
  11. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/git_ops.py +18 -2
  12. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/loop_engine.py +106 -11
  13. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/models.py +22 -0
  14. overkill-0.9.0/src/mr_overkill/review_loop.py +439 -0
  15. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/self_review.py +15 -3
  16. overkill-0.9.0/src/mr_overkill/wip_scope.py +439 -0
  17. {overkill-0.8.2 → overkill-0.9.0}/tests/test_agents.py +318 -0
  18. {overkill-0.8.2 → overkill-0.9.0}/tests/test_cli.py +319 -0
  19. overkill-0.9.0/tests/test_commit_scope.py +197 -0
  20. {overkill-0.8.2 → overkill-0.9.0}/tests/test_git_ops.py +96 -0
  21. {overkill-0.8.2 → overkill-0.9.0}/tests/test_loop_engine.py +313 -2
  22. overkill-0.9.0/tests/test_review_loop.py +1116 -0
  23. {overkill-0.8.2 → overkill-0.9.0}/tests/test_self_review.py +117 -0
  24. overkill-0.9.0/tests/test_wip_scope.py +693 -0
  25. {overkill-0.8.2 → overkill-0.9.0}/uv.lock +1 -1
  26. overkill-0.8.2/src/mr_overkill/review_loop.py +0 -65
  27. overkill-0.8.2/tests/test_review_loop.py +0 -225
  28. {overkill-0.8.2 → overkill-0.9.0}/.github/workflows/publish.yml +0 -0
  29. {overkill-0.8.2 → overkill-0.9.0}/.github/workflows/test.yml +0 -0
  30. {overkill-0.8.2 → overkill-0.9.0}/.gitignore +0 -0
  31. {overkill-0.8.2 → overkill-0.9.0}/.python-version +0 -0
  32. {overkill-0.8.2 → overkill-0.9.0}/.refactorsuggestrc.example +0 -0
  33. {overkill-0.8.2 → overkill-0.9.0}/AGENTS.md +0 -0
  34. {overkill-0.8.2 → overkill-0.9.0}/CLAUDE.md +0 -0
  35. {overkill-0.8.2 → overkill-0.9.0}/GEMINI.md +0 -0
  36. {overkill-0.8.2 → overkill-0.9.0}/LICENSE +0 -0
  37. {overkill-0.8.2 → overkill-0.9.0}/install.sh +0 -0
  38. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-fix-execute.prompt.md +0 -0
  39. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-fix.prompt.md +0 -0
  40. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-refactor-fix-execute.prompt.md +0 -0
  41. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-refactor-fix.prompt.md +0 -0
  42. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-refactor-full.prompt.md +0 -0
  43. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-refactor-layer.prompt.md +0 -0
  44. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-refactor-micro.prompt.md +0 -0
  45. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-refactor-module.prompt.md +0 -0
  46. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/claude-self-review.prompt.md +0 -0
  47. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/codex-refactor-full.prompt.md +0 -0
  48. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/codex-refactor-layer.prompt.md +0 -0
  49. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/codex-refactor-micro.prompt.md +0 -0
  50. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/codex-refactor-module.prompt.md +0 -0
  51. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/gemini-refactor-full.prompt.md +0 -0
  52. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/gemini-refactor-layer.prompt.md +0 -0
  53. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/gemini-refactor-micro.prompt.md +0 -0
  54. {overkill-0.8.2 → overkill-0.9.0}/prompts/active/gemini-refactor-module.prompt.md +0 -0
  55. {overkill-0.8.2 → overkill-0.9.0}/prompts/reference/claude-code-review-plugin.md +0 -0
  56. {overkill-0.8.2 → overkill-0.9.0}/prompts/reference/claude-security-review.md +0 -0
  57. {overkill-0.8.2 → overkill-0.9.0}/prompts/reference/codex-review-original.md +0 -0
  58. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/__init__.py +0 -0
  59. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/__main__.py +0 -0
  60. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/budget/__init__.py +0 -0
  61. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/budget/claude.py +0 -0
  62. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/budget/codex.py +0 -0
  63. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/budget/gemini.py +0 -0
  64. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/budget_report.py +0 -0
  65. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/classify.py +0 -0
  66. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/data/__init__.py +0 -0
  67. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/data/review.schema.json +0 -0
  68. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/init.py +0 -0
  69. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/json_extract.py +0 -0
  70. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/refactor_suggest.py +0 -0
  71. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/reporting.py +0 -0
  72. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/resume.py +0 -0
  73. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/retry.py +0 -0
  74. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/time_utils.py +0 -0
  75. {overkill-0.8.2 → overkill-0.9.0}/src/mr_overkill/two_step_fix.py +0 -0
  76. {overkill-0.8.2 → overkill-0.9.0}/tests/__init__.py +0 -0
  77. {overkill-0.8.2 → overkill-0.9.0}/tests/conftest.py +0 -0
  78. {overkill-0.8.2 → overkill-0.9.0}/tests/test_budget_claude.py +0 -0
  79. {overkill-0.8.2 → overkill-0.9.0}/tests/test_budget_codex.py +0 -0
  80. {overkill-0.8.2 → overkill-0.9.0}/tests/test_budget_gemini.py +0 -0
  81. {overkill-0.8.2 → overkill-0.9.0}/tests/test_budget_policy.py +0 -0
  82. {overkill-0.8.2 → overkill-0.9.0}/tests/test_budget_report.py +0 -0
  83. {overkill-0.8.2 → overkill-0.9.0}/tests/test_classify.py +0 -0
  84. {overkill-0.8.2 → overkill-0.9.0}/tests/test_init.py +0 -0
  85. {overkill-0.8.2 → overkill-0.9.0}/tests/test_integration.py +0 -0
  86. {overkill-0.8.2 → overkill-0.9.0}/tests/test_json_extract.py +0 -0
  87. {overkill-0.8.2 → overkill-0.9.0}/tests/test_refactor_suggest.py +0 -0
  88. {overkill-0.8.2 → overkill-0.9.0}/tests/test_reporting.py +0 -0
  89. {overkill-0.8.2 → overkill-0.9.0}/tests/test_resume.py +0 -0
  90. {overkill-0.8.2 → overkill-0.9.0}/tests/test_retry.py +0 -0
  91. {overkill-0.8.2 → overkill-0.9.0}/tests/test_time_utils.py +0 -0
  92. {overkill-0.8.2 → overkill-0.9.0}/tests/test_two_step_fix.py +0 -0
  93. {overkill-0.8.2 → overkill-0.9.0}/uninstall.sh +0 -0
@@ -17,6 +17,9 @@
17
17
  # Enable auto-commit of fixes (default: true)
18
18
  # AUTO_COMMIT=true
19
19
 
20
+ # Push the review/* branch created by --commit (default: false — stays local)
21
+ # COMMIT_SCOPE_PUSH=false
22
+
20
23
  # CI trigger policy for iteration commits (default: last-only)
21
24
  # last-only — append "[skip ci]" to each iteration commit; push a single
22
25
  # empty "chore: trigger CI" commit only when the loop ends
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: overkill
3
- Version: 0.8.2
3
+ Version: 0.9.0
4
4
  Summary: AI-powered code review loop — automates Codex/Gemini review + Claude fix cycles
5
5
  Project-URL: Repository, https://github.com/modocai/mr-overkill
6
6
  Project-URL: Issues, https://github.com/modocai/mr-overkill/issues
@@ -117,14 +117,31 @@ overkill refactor-suggest -n 1 --dry-run
117
117
  overkill review-loop [OPTIONS]
118
118
 
119
119
  Options:
120
- -t, --target <branch> Target branch to diff against (default: develop)
120
+ -t, --target <rev> Target to diff against (default: develop). Accepts any
121
+ git revision, not just a branch name: a SHA, a tag, or
122
+ HEAD~5 all work, so you can review "everything since
123
+ commit X" without opening a PR. A revision that moves
124
+ with HEAD is pinned to a SHA when the run starts, so
125
+ the loop's own fix commits cannot shrink the range.
126
+ --commit <rev> Review an already-merged commit instead of the branch
127
+ diff. Creates a review/<sha>-<ts> branch off HEAD and
128
+ applies fixes there; no PR is created. <rev> is a single
129
+ commit — ranges are not supported. Excludes -t.
130
+ --push Push the auto-created review branch (default: local only)
131
+ --wip Include uncommitted working-tree changes in the review.
132
+ With commits enabled they are parked in a scaffolding
133
+ commit that is unwound when the run finishes, so no
134
+ commit is left behind either way. Excludes --commit.
121
135
  -n, --max-loop <N> Maximum review-fix iterations (required, unless --resume)
122
136
  --max-subloop <N> Maximum self-review sub-iterations per fix (default: 4)
123
137
  --no-self-review Disable self-review (equivalent to --max-subloop 0)
124
138
  --dry-run Run review only, do not fix
125
139
  --no-auto-commit Fix but do not commit/push (single iteration)
126
140
  --resume Resume from a previously interrupted run (reuses existing logs)
127
- --reviewer-backend <be> Reviewer backend: claude|codex (default: codex)
141
+ --fix-nits Also flag nits and style issues during self-review
142
+ --context <text> Additional context for the reviewer (design intent,
143
+ constraints)
144
+ --reviewer-backend <be> Reviewer backend: claude|codex|gemini (default: codex)
128
145
  --ci-trigger-mode <m> CI trigger policy: every|last-only|none (default: last-only).
129
146
  'last-only' tags each iteration commit with [skip ci]
130
147
  and pushes a single empty trigger commit on PASS —
@@ -142,8 +159,108 @@ Examples:
142
159
  overkill review-loop --resume # resume an interrupted run
143
160
  overkill review-loop -n 2 --reviewer-backend claude # use Claude as reviewer
144
161
  overkill review-loop -n 10 --ci-trigger-mode last-only # CI fires once on PASS
162
+
163
+ # Review only what landed after a given commit, before opening a PR
164
+ overkill review-loop -t abc123 -n 3
165
+ overkill review-loop -t "$(git merge-base origin/develop HEAD)" -n 3
166
+
167
+ # Improve a commit that is already merged
168
+ overkill review-loop --commit abc123 -n 1 --dry-run # report only, no branch
169
+ overkill review-loop --commit abc123 -n 3 # fix on a review/* branch
170
+
171
+ # Review work you have not committed yet
172
+ overkill review-loop --wip -n 1 --dry-run # report only, nothing touched
173
+ overkill review-loop --wip -n 3 # fix it, still uncommitted at the end
174
+ ```
175
+
176
+ ### Reviewing an already-merged commit
177
+
178
+ `--commit` exists for the case the branch diff cannot express: a change that
179
+ already landed, which you now want to improve.
180
+
181
+ 1. The commit's diff is written to `.overkill/logs/scope.diff`. It is computed
182
+ against the commit's **first parent**, so merge commits produce a real patch
183
+ — `git show` prints nothing for those.
184
+ 2. A `review/<sha>-<timestamp>` branch is created off your current HEAD and the
185
+ fixes are committed there. The branch stays local unless you pass `--push`,
186
+ and no PR is created or commented on.
187
+ 3. The reviewer treats `scope.diff` as *scope only*. Since other commits may
188
+ have landed since, it must confirm each finding against the file's current
189
+ contents and cite current line numbers.
190
+
191
+ Run it from a clean, up-to-date checkout of the branch the commit lives on:
192
+
193
+ ```bash
194
+ git switch main && git pull
195
+ overkill review-loop --commit abc123 -n 3
145
196
  ```
146
197
 
198
+ **After upgrading, re-run `overkill init`** in each repo — `--commit` and
199
+ `--wip` need the `${REVIEW_SCOPE_NOTE}` marker that the refreshed review prompts
200
+ carry, and the run aborts with an explanatory error if it is missing. Note that
201
+ `init` overwrites `.overkill/prompts/active/`, so back up any customised prompts
202
+ first.
203
+
204
+ ### Reviewing work you have not committed yet
205
+
206
+ Without `--wip` the review scope is `git diff <target>...<current>` — committed
207
+ work only. A dirty tree is rejected outright, and under `--dry-run` it is
208
+ silently left out of scope. `--wip` pulls it in.
209
+
210
+ How it gets there depends on whether the run is allowed to commit:
211
+
212
+ | Command | Mechanism | Iterations | Commits left behind |
213
+ |---|---|---|---|
214
+ | `--wip --dry-run` | worktree diff written to `.overkill/logs/wip.diff` | 1 (review only) | none |
215
+ | `--wip --no-auto-commit` | same | 1 | none |
216
+ | `--wip` | scaffolding commit, unwound at the end | up to `-n` | none |
217
+
218
+ Both paths end the same way: your working tree is dirty again, with the fixes
219
+ applied on top of your own edits. Only the iteration count differs. Multiple
220
+ iterations need commits because the loop detects convergence from the commit
221
+ graph, so `--wip` parks your work in a throwaway commit, lets the loop run
222
+ against it unchanged, and then removes the scaffolding with
223
+ `git reset --mixed`.
224
+
225
+ ```bash
226
+ overkill review-loop --wip -n 3
227
+ git diff # your work plus the fixes, uncommitted
228
+ cat .overkill/logs/wip-fixes.diff # just what the loop changed
229
+ ```
230
+
231
+ Worth knowing before you use it:
232
+
233
+ - **Nothing is ever pushed** in `--wip` mode, and no PR is commented on. This is
234
+ not configurable — the scaffolding commit holds unfinished work.
235
+ - **`git add -A` sweeps in anything `.gitignore` does not cover.** The file list
236
+ is printed before the scaffolding commit is made. Nothing is pushed and the
237
+ commit is unwound, but check the list if you keep untracked secrets around.
238
+ - **A staged/unstaged split does not survive.** `git reset --mixed` leaves
239
+ everything unstaged.
240
+ - **If the run is interrupted the scaffolding stays.** The command to undo it is
241
+ printed at the start and the base commit is saved to
242
+ `.overkill/logs/wip-base.txt`; `--wip --resume` picks an interrupted run back
243
+ up, parking the work again if the scaffolding is already gone. A run that
244
+ already finished is left alone. Resume needs commits enabled — the other two
245
+ modes are a single pass with nothing to resume. A *fresh* `--wip` run refuses
246
+ to start on top of leftover scaffolding rather than nest a second commit on
247
+ it, which would strand the earlier draft on the branch — including when the
248
+ scaffolding sits behind fix commits the interrupted run already made. A
249
+ resume refuses too if you have committed normally on top of it, because
250
+ unwinding would rewind that commit into uncommitted changes.
251
+ - **A resumed run's `wip-fixes.diff` only covers the iterations after the
252
+ resume.** Re-parking folds the earlier attempt's fixes in with your own work,
253
+ so they cannot be told apart again; that attempt's diff is kept beside it as
254
+ `wip-fixes-<sha>.diff`.
255
+ - **Commit hooks are skipped** for the run's own commits. Work in progress
256
+ routinely fails hooks it will pass once finished, and every commit `--wip`
257
+ makes is torn down again.
258
+ - **An unfinished merge, rebase, cherry-pick or revert blocks the mode.** The
259
+ scaffolding commit would conclude the operation, and unwinding would then
260
+ reset past it.
261
+ - **New files are included.** They are staged as intent-to-add so the reviewer
262
+ can see them, then unstaged again.
263
+
147
264
  ## Usage: overkill refactor-suggest
148
265
 
149
266
  Unlike `review-loop` which reviews diffs, `refactor-suggest` analyzes the **entire codebase** for refactoring opportunities at a chosen scope level.
@@ -164,7 +281,7 @@ Options:
164
281
  --resume Resume from a previously interrupted run (reuses existing logs)
165
282
  --with-review Run review-loop after PR creation (default: 4 iterations)
166
283
  --with-review-loops <N> Set review-loop iteration count (implies --with-review)
167
- --reviewer-backend <be> Reviewer backend: claude|codex (default: codex)
284
+ --reviewer-backend <be> Reviewer backend: claude|codex|gemini (default: codex)
168
285
  --diagnostic-log Save full Claude event stream to sidecar files
169
286
  --no-budget-gate Skip token-budget checks and run regardless
170
287
  (same as OVERKILL_SKIP_BUDGET=1)
@@ -94,14 +94,31 @@ overkill refactor-suggest -n 1 --dry-run
94
94
  overkill review-loop [OPTIONS]
95
95
 
96
96
  Options:
97
- -t, --target <branch> Target branch to diff against (default: develop)
97
+ -t, --target <rev> Target to diff against (default: develop). Accepts any
98
+ git revision, not just a branch name: a SHA, a tag, or
99
+ HEAD~5 all work, so you can review "everything since
100
+ commit X" without opening a PR. A revision that moves
101
+ with HEAD is pinned to a SHA when the run starts, so
102
+ the loop's own fix commits cannot shrink the range.
103
+ --commit <rev> Review an already-merged commit instead of the branch
104
+ diff. Creates a review/<sha>-<ts> branch off HEAD and
105
+ applies fixes there; no PR is created. <rev> is a single
106
+ commit — ranges are not supported. Excludes -t.
107
+ --push Push the auto-created review branch (default: local only)
108
+ --wip Include uncommitted working-tree changes in the review.
109
+ With commits enabled they are parked in a scaffolding
110
+ commit that is unwound when the run finishes, so no
111
+ commit is left behind either way. Excludes --commit.
98
112
  -n, --max-loop <N> Maximum review-fix iterations (required, unless --resume)
99
113
  --max-subloop <N> Maximum self-review sub-iterations per fix (default: 4)
100
114
  --no-self-review Disable self-review (equivalent to --max-subloop 0)
101
115
  --dry-run Run review only, do not fix
102
116
  --no-auto-commit Fix but do not commit/push (single iteration)
103
117
  --resume Resume from a previously interrupted run (reuses existing logs)
104
- --reviewer-backend <be> Reviewer backend: claude|codex (default: codex)
118
+ --fix-nits Also flag nits and style issues during self-review
119
+ --context <text> Additional context for the reviewer (design intent,
120
+ constraints)
121
+ --reviewer-backend <be> Reviewer backend: claude|codex|gemini (default: codex)
105
122
  --ci-trigger-mode <m> CI trigger policy: every|last-only|none (default: last-only).
106
123
  'last-only' tags each iteration commit with [skip ci]
107
124
  and pushes a single empty trigger commit on PASS —
@@ -119,8 +136,108 @@ Examples:
119
136
  overkill review-loop --resume # resume an interrupted run
120
137
  overkill review-loop -n 2 --reviewer-backend claude # use Claude as reviewer
121
138
  overkill review-loop -n 10 --ci-trigger-mode last-only # CI fires once on PASS
139
+
140
+ # Review only what landed after a given commit, before opening a PR
141
+ overkill review-loop -t abc123 -n 3
142
+ overkill review-loop -t "$(git merge-base origin/develop HEAD)" -n 3
143
+
144
+ # Improve a commit that is already merged
145
+ overkill review-loop --commit abc123 -n 1 --dry-run # report only, no branch
146
+ overkill review-loop --commit abc123 -n 3 # fix on a review/* branch
147
+
148
+ # Review work you have not committed yet
149
+ overkill review-loop --wip -n 1 --dry-run # report only, nothing touched
150
+ overkill review-loop --wip -n 3 # fix it, still uncommitted at the end
151
+ ```
152
+
153
+ ### Reviewing an already-merged commit
154
+
155
+ `--commit` exists for the case the branch diff cannot express: a change that
156
+ already landed, which you now want to improve.
157
+
158
+ 1. The commit's diff is written to `.overkill/logs/scope.diff`. It is computed
159
+ against the commit's **first parent**, so merge commits produce a real patch
160
+ — `git show` prints nothing for those.
161
+ 2. A `review/<sha>-<timestamp>` branch is created off your current HEAD and the
162
+ fixes are committed there. The branch stays local unless you pass `--push`,
163
+ and no PR is created or commented on.
164
+ 3. The reviewer treats `scope.diff` as *scope only*. Since other commits may
165
+ have landed since, it must confirm each finding against the file's current
166
+ contents and cite current line numbers.
167
+
168
+ Run it from a clean, up-to-date checkout of the branch the commit lives on:
169
+
170
+ ```bash
171
+ git switch main && git pull
172
+ overkill review-loop --commit abc123 -n 3
122
173
  ```
123
174
 
175
+ **After upgrading, re-run `overkill init`** in each repo — `--commit` and
176
+ `--wip` need the `${REVIEW_SCOPE_NOTE}` marker that the refreshed review prompts
177
+ carry, and the run aborts with an explanatory error if it is missing. Note that
178
+ `init` overwrites `.overkill/prompts/active/`, so back up any customised prompts
179
+ first.
180
+
181
+ ### Reviewing work you have not committed yet
182
+
183
+ Without `--wip` the review scope is `git diff <target>...<current>` — committed
184
+ work only. A dirty tree is rejected outright, and under `--dry-run` it is
185
+ silently left out of scope. `--wip` pulls it in.
186
+
187
+ How it gets there depends on whether the run is allowed to commit:
188
+
189
+ | Command | Mechanism | Iterations | Commits left behind |
190
+ |---|---|---|---|
191
+ | `--wip --dry-run` | worktree diff written to `.overkill/logs/wip.diff` | 1 (review only) | none |
192
+ | `--wip --no-auto-commit` | same | 1 | none |
193
+ | `--wip` | scaffolding commit, unwound at the end | up to `-n` | none |
194
+
195
+ Both paths end the same way: your working tree is dirty again, with the fixes
196
+ applied on top of your own edits. Only the iteration count differs. Multiple
197
+ iterations need commits because the loop detects convergence from the commit
198
+ graph, so `--wip` parks your work in a throwaway commit, lets the loop run
199
+ against it unchanged, and then removes the scaffolding with
200
+ `git reset --mixed`.
201
+
202
+ ```bash
203
+ overkill review-loop --wip -n 3
204
+ git diff # your work plus the fixes, uncommitted
205
+ cat .overkill/logs/wip-fixes.diff # just what the loop changed
206
+ ```
207
+
208
+ Worth knowing before you use it:
209
+
210
+ - **Nothing is ever pushed** in `--wip` mode, and no PR is commented on. This is
211
+ not configurable — the scaffolding commit holds unfinished work.
212
+ - **`git add -A` sweeps in anything `.gitignore` does not cover.** The file list
213
+ is printed before the scaffolding commit is made. Nothing is pushed and the
214
+ commit is unwound, but check the list if you keep untracked secrets around.
215
+ - **A staged/unstaged split does not survive.** `git reset --mixed` leaves
216
+ everything unstaged.
217
+ - **If the run is interrupted the scaffolding stays.** The command to undo it is
218
+ printed at the start and the base commit is saved to
219
+ `.overkill/logs/wip-base.txt`; `--wip --resume` picks an interrupted run back
220
+ up, parking the work again if the scaffolding is already gone. A run that
221
+ already finished is left alone. Resume needs commits enabled — the other two
222
+ modes are a single pass with nothing to resume. A *fresh* `--wip` run refuses
223
+ to start on top of leftover scaffolding rather than nest a second commit on
224
+ it, which would strand the earlier draft on the branch — including when the
225
+ scaffolding sits behind fix commits the interrupted run already made. A
226
+ resume refuses too if you have committed normally on top of it, because
227
+ unwinding would rewind that commit into uncommitted changes.
228
+ - **A resumed run's `wip-fixes.diff` only covers the iterations after the
229
+ resume.** Re-parking folds the earlier attempt's fixes in with your own work,
230
+ so they cannot be told apart again; that attempt's diff is kept beside it as
231
+ `wip-fixes-<sha>.diff`.
232
+ - **Commit hooks are skipped** for the run's own commits. Work in progress
233
+ routinely fails hooks it will pass once finished, and every commit `--wip`
234
+ makes is torn down again.
235
+ - **An unfinished merge, rebase, cherry-pick or revert blocks the mode.** The
236
+ scaffolding commit would conclude the operation, and unwinding would then
237
+ reset past it.
238
+ - **New files are included.** They are staged as intent-to-add so the reviewer
239
+ can see them, then unstaged again.
240
+
124
241
  ## Usage: overkill refactor-suggest
125
242
 
126
243
  Unlike `review-loop` which reviews diffs, `refactor-suggest` analyzes the **entire codebase** for refactoring opportunities at a chosen scope level.
@@ -141,7 +258,7 @@ Options:
141
258
  --resume Resume from a previously interrupted run (reuses existing logs)
142
259
  --with-review Run review-loop after PR creation (default: 4 iterations)
143
260
  --with-review-loops <N> Set review-loop iteration count (implies --with-review)
144
- --reviewer-backend <be> Reviewer backend: claude|codex (default: codex)
261
+ --reviewer-backend <be> Reviewer backend: claude|codex|gemini (default: codex)
145
262
  --diagnostic-log Save full Claude event stream to sidecar files
146
263
  --no-budget-gate Skip token-budget checks and run regardless
147
264
  (same as OVERKILL_SKIP_BUDGET=1)
@@ -1,5 +1,5 @@
1
1
  You are a code reviewer analyzing a proposed change.
2
-
2
+ ${REVIEW_SCOPE_NOTE}
3
3
  ## Context
4
4
 
5
5
  - **Current branch**: ${CURRENT_BRANCH}
@@ -1,5 +1,5 @@
1
1
  You are a code reviewer analyzing a proposed change.
2
-
2
+ ${REVIEW_SCOPE_NOTE}
3
3
  ## Context
4
4
 
5
5
  - **Current branch**: ${CURRENT_BRANCH}
@@ -1,5 +1,5 @@
1
1
  You are a code reviewer analyzing a proposed change.
2
-
2
+ ${REVIEW_SCOPE_NOTE}
3
3
  ## Context
4
4
 
5
5
  - **Current branch**: ${CURRENT_BRANCH}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "overkill"
3
- version = "0.8.2"
3
+ version = "0.9.0"
4
4
  description = "AI-powered code review loop — automates Codex/Gemini review + Claude fix cycles"
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -16,6 +16,7 @@ from abc import ABC, abstractmethod
16
16
  from importlib.resources import as_file, files
17
17
  from pathlib import Path
18
18
 
19
+ from mr_overkill import commit_scope, wip_scope
19
20
  from mr_overkill.budget import SKIP_BUDGET_ENV_VAR, budget_gate_disabled
20
21
  from mr_overkill.budget.claude import claude_budget_sufficient
21
22
  from mr_overkill.budget.codex import codex_budget_sufficient
@@ -47,6 +48,211 @@ def _format_reviewer_context(raw: str) -> str:
47
48
  return f"## Author Context\n\n{raw}"
48
49
 
49
50
 
51
+ _SCOPE_NOTE_MARKER = "${REVIEW_SCOPE_NOTE}"
52
+
53
+
54
+ # Shared by both WIP mechanisms: the code under review is a draft, and the
55
+ # reviewer has to be told so or it will report the draft-ness as the defect.
56
+ # Neither mechanism hands over the draft alone: both artefacts also carry the
57
+ # author's own committed work on this branch, and that is finished code. So the
58
+ # subject names the draft portion and each caller carves the rest out.
59
+ def _wip_draft_calibration(subject: str) -> str:
60
+ """Draft-vs-defect calibration, scoped to *subject*."""
61
+ return f"""
62
+ ### This work is unfinished
63
+
64
+ {subject} has not been committed by the author, so judge it as a **draft**:
65
+
66
+ - Do flag defects in what is actually written — bugs, unsafe assumptions, \
67
+ resource leaks, debug statements or credentials left behind.
68
+ - Do **not** flag incompleteness itself. A half-implemented feature, a missing \
69
+ test for code still being written, or a function that is clearly the next thing \
70
+ the author will fill in is not a finding.
71
+ """
72
+
73
+
74
+ # The no-commit WIP mechanism leaves the author's draft uncommitted, so the
75
+ # self-review diff — ``git diff HEAD`` over the files the fixer touched — carries
76
+ # the draft alongside the fix. Unsaid, the self-reviewer reads the draft as the
77
+ # fixer's work: it reports the incompleteness, or calls the draft scope creep and
78
+ # the re-fix reverts work that exists nowhere but the working tree.
79
+ def _wip_self_review_note(scope_diff: Path) -> str:
80
+ """Draft-vs-fix separation for the no-commit self-reviewer."""
81
+ return f"""
82
+
83
+ ### The diff also contains the author's uncommitted draft
84
+
85
+ Nothing was committed before the fix, so the diff file shows the author's own work \
86
+ in progress as well as the fix. The draft as it stood **before any fix ran** was \
87
+ captured at `{scope_diff}`, so anything already written there is the author's rather \
88
+ than the fix. That capture was taken against a different base — match it by content, \
89
+ not by line number — and where a hunk mixes the two, the findings above say which \
90
+ part is the fix.
91
+
92
+ - Judge **only the fix**. Hunks that belong to the author's draft are out of scope.
93
+ - Do **not** report the draft's incompleteness, and do **not** report it as scope \
94
+ creep or ask for it to be reverted — it is not the fixer's work, and reverting it \
95
+ would destroy uncommitted work.
96
+ """
97
+
98
+
99
+ def _format_wip_scope(config: LoopConfig) -> str:
100
+ """Build the WIP-scope override block for whichever mechanism is in play."""
101
+ if config.scope_diff_file is not None:
102
+ # No-commit run: the branch diff shows only committed work, so it is
103
+ # actively misleading here — the scope artefact is the whole truth.
104
+ return f"""
105
+ > **REVIEW MODE: UNCOMMITTED WORK — read this first. It overrides the framing \
106
+ in the line above and re-scopes the Instructions below.**
107
+
108
+ The change under review is the author's **uncommitted working tree**, captured \
109
+ in full at `{config.scope_diff_file}`. Read that file first.
110
+
111
+ **Ignore the `git diff` command in the Instructions section.** It compares two \
112
+ commits, so it shows only the part of the work that happens to be committed \
113
+ already — reviewing it would silently skip everything the author is actually \
114
+ working on.
115
+
116
+ The scope diff was taken from the working tree as it exists right now, so its \
117
+ paths and line numbers are **current** and you may cite them directly.
118
+
119
+ It runs from this branch's fork point with `{config.target_branch}`, so it also \
120
+ contains commits the author already made here. `git diff \
121
+ {config.target_branch}...HEAD` prints exactly that committed portion — it is in \
122
+ scope too, but it is finished code, so the draft allowance below does not apply \
123
+ to it.
124
+ {_wip_draft_calibration("The uncommitted part of the change under review")}"""
125
+
126
+ # Scaffolding-commit run: the branch diff is exactly right, but the
127
+ # scaffolding commit's message would otherwise read as the change under
128
+ # review, and it sits on top of whatever the author has already committed
129
+ # on this branch rather than at the bottom of the diff.
130
+ return f"""
131
+ > **REVIEW MODE: UNCOMMITTED WORK — read this first.**
132
+
133
+ The commit on this branch whose message is `{wip_scope.SCAFFOLD_MESSAGE}` is not a \
134
+ change the author wrote a commit for. It is their **uncommitted work**, parked in \
135
+ a throwaway commit so that it can be reviewed and so that fixes have somewhere to \
136
+ land. It will be unwound when this run finishes. Review it exactly as if the \
137
+ author had committed it deliberately — it is the change under review.
138
+
139
+ Commits **before** it are the author's own committed work on this branch. They are \
140
+ in scope too, but they are finished code, so the draft allowance below does not \
141
+ apply to them. Commits **after** it are fixes an earlier iteration of this loop \
142
+ already applied.
143
+ {_wip_draft_calibration("The work parked in that commit")}"""
144
+
145
+
146
+ def _format_review_scope(config: LoopConfig, iteration: int) -> str:
147
+ """Build the scope override block, or "" in normal branch-diff mode.
148
+
149
+ Empty-string-means-no-section, like ``EXTRA_REVIEW_GUIDELINES`` in the
150
+ self-review prompt. Note ``string.Template`` does not substitute
151
+ recursively, so every value here is interpolated in Python.
152
+ """
153
+ sha = config.scope_commit
154
+ if not sha:
155
+ return _format_wip_scope(config) if config.wip else ""
156
+
157
+ diff_path = config.scope_diff_file
158
+ ancestry = ""
159
+ if not commit_scope.is_ancestor_of_head(sha):
160
+ ancestry = (
161
+ f"\n> WARNING: `{sha[:7]}` is **not an ancestor of HEAD**. Much of the "
162
+ "code it touched may not exist in the working tree at all. Report only "
163
+ "defects you can locate in a file that exists right now.\n"
164
+ )
165
+
166
+ if iteration <= 1:
167
+ fixes = (
168
+ "None yet — this is the first pass. The `git diff` command in the "
169
+ "Instructions section will print **nothing** on this iteration. That is "
170
+ "expected, it is not an error, and it is **not** the change under review."
171
+ )
172
+ else:
173
+ fixes = (
174
+ f"This is iteration {iteration}. Earlier iterations already produced fix "
175
+ "commits on this branch, and the `git diff` command in the Instructions "
176
+ "section prints **those fixes** — not the change under review. Use it to "
177
+ "(a) confirm which of your earlier findings are now resolved — **never "
178
+ "re-report a resolved finding** — and (b) look for new defects the fixes "
179
+ "themselves introduced.\n\nIf every earlier finding is resolved and you "
180
+ 'find nothing new, return zero findings and `"patch is correct"`.'
181
+ )
182
+
183
+ return f"""
184
+ > **REVIEW MODE: COMMIT SCOPE — read this first. It overrides the framing in the \
185
+ line above and re-scopes the Instructions and Review Guidelines below.**
186
+
187
+ You are **not** reviewing a proposed change. You are reviewing a change that was \
188
+ **already merged**:
189
+
190
+ - **Commit under review**: {commit_scope.commit_headline(sha)}
191
+ - **Its diff**: `{diff_path}`
192
+ {ancestry}
193
+ Read that diff file first. Do **not** try to reconstruct it with `git show` or \
194
+ `git diff` — it has already been generated correctly for you, including for merge \
195
+ commits (where `git show` prints nothing at all).
196
+
197
+ ### The scope diff is historical — the code has moved on
198
+
199
+ Other commits have landed since. A line number, a function, or a whole file in the \
200
+ scope diff may no longer exist, may have been renamed, or may already have been fixed.
201
+
202
+ 1. Use the scope diff **only** to decide *what is in scope*: which files and which \
203
+ behaviour the commit touched.
204
+ 2. **The current contents of the working tree are the sole authority on whether a \
205
+ defect exists.** Before reporting anything, open the current file and confirm the \
206
+ defect is still there. If the current code already handles it, drop the finding.
207
+ 3. Every `code_location` must be a **current** path with **current** line numbers, \
208
+ verified by reading the file. Line numbers copied from the scope diff will be wrong.
209
+ 4. If a file in the scope diff no longer exists, skip it.
210
+
211
+ ### Reading the guidelines below
212
+
213
+ Wherever a guideline says "this diff", read it as "the change made by \
214
+ `{sha[:7]}`, as it manifests in the code as it exists right now". The requirement \
215
+ that the issue be **introduced by this diff** still holds: do not flag pre-existing \
216
+ problems, and do not flag problems introduced by *later* commits.
217
+
218
+ ### Fixes already applied on this branch
219
+
220
+ {fixes}
221
+ """
222
+
223
+
224
+ def _review_prompt_vars(config: LoopConfig, iteration: int) -> dict[str, str]:
225
+ """Template variables shared by all three review prompts."""
226
+ return {
227
+ "CURRENT_BRANCH": config.current_branch,
228
+ "TARGET_BRANCH": config.target_branch,
229
+ "ITERATION": str(iteration),
230
+ "REVIEWER_CONTEXT": _format_reviewer_context(config.reviewer_context),
231
+ "REVIEW_SCOPE_NOTE": _format_review_scope(config, iteration),
232
+ }
233
+
234
+
235
+ def _render_review_prompt(
236
+ prompt_file: Path, config: LoopConfig, iteration: int
237
+ ) -> str | None:
238
+ """Render a review prompt, or ``None`` if it cannot be used.
239
+
240
+ In a scoped mode a prompt that predates the feature would silently drop the
241
+ scope note and have the reviewer inspect the wrong diff — which reads as
242
+ "no findings" rather than as a failure. Refuse instead.
243
+ """
244
+ raw = prompt_file.read_text(encoding="utf-8")
245
+ if (config.scope_commit or config.wip) and _SCOPE_NOTE_MARKER not in raw:
246
+ logger.error(
247
+ "Prompt %s predates scoped review support (no %s marker). "
248
+ "Run 'overkill init' to refresh the prompt templates.",
249
+ prompt_file,
250
+ _SCOPE_NOTE_MARKER,
251
+ )
252
+ return None
253
+ return string.Template(raw).safe_substitute(_review_prompt_vars(config, iteration))
254
+
255
+
50
256
  # ── Review schema (single source of truth for structured output) ─────
51
257
 
52
258
 
@@ -217,15 +423,9 @@ class CodexReviewAgent(ReviewAgent):
217
423
  logger.error("Review prompt not found: %s", prompt_file)
218
424
  return False
219
425
 
220
- tmpl = string.Template(
221
- prompt_file.read_text(encoding="utf-8")
222
- )
223
- prompt_text = tmpl.safe_substitute({
224
- "CURRENT_BRANCH": config.current_branch,
225
- "TARGET_BRANCH": config.target_branch,
226
- "ITERATION": str(iteration),
227
- "REVIEWER_CONTEXT": _format_reviewer_context(config.reviewer_context),
228
- })
426
+ prompt_text = _render_review_prompt(prompt_file, config, iteration)
427
+ if prompt_text is None:
428
+ return False
229
429
 
230
430
  if not self._budget_fn("codex", config.budget_scope, 0):
231
431
  raise BudgetTimeoutError(
@@ -323,15 +523,9 @@ class ClaudeReviewAgent(ReviewAgent):
323
523
  logger.error("Review prompt not found: %s", prompt_file)
324
524
  return False
325
525
 
326
- tmpl = string.Template(
327
- prompt_file.read_text(encoding="utf-8")
328
- )
329
- prompt_text = tmpl.safe_substitute({
330
- "CURRENT_BRANCH": config.current_branch,
331
- "TARGET_BRANCH": config.target_branch,
332
- "ITERATION": str(iteration),
333
- "REVIEWER_CONTEXT": _format_reviewer_context(config.reviewer_context),
334
- })
526
+ prompt_text = _render_review_prompt(prompt_file, config, iteration)
527
+ if prompt_text is None:
528
+ return False
335
529
 
336
530
  if not self._budget_fn("claude", config.budget_scope, 0):
337
531
  raise BudgetTimeoutError(
@@ -426,15 +620,9 @@ class GeminiReviewAgent(ReviewAgent):
426
620
  logger.error("Review prompt not found: %s", prompt_file)
427
621
  return False
428
622
 
429
- tmpl = string.Template(
430
- prompt_file.read_text(encoding="utf-8")
431
- )
432
- prompt_text = tmpl.safe_substitute({
433
- "CURRENT_BRANCH": config.current_branch,
434
- "TARGET_BRANCH": config.target_branch,
435
- "ITERATION": str(iteration),
436
- "REVIEWER_CONTEXT": _format_reviewer_context(config.reviewer_context),
437
- })
623
+ prompt_text = _render_review_prompt(prompt_file, config, iteration)
624
+ if prompt_text is None:
625
+ return False
438
626
 
439
627
  if not self._budget_fn("gemini", config.budget_scope, 0):
440
628
  raise BudgetTimeoutError(
@@ -591,6 +779,11 @@ class ClaudeSelfReviewAgent(SelfReviewAgent):
591
779
  budget_scope=config.budget_scope,
592
780
  dry_run=config.dry_run,
593
781
  fix_nits=config.fix_nits,
782
+ scope_note=(
783
+ _wip_self_review_note(config.scope_diff_file)
784
+ if config.wip and config.scope_diff_file is not None
785
+ else ""
786
+ ),
594
787
  original_review_json=json.loads(review_json_str),
595
788
  )
596
789