continuous-refactoring 0.2.0__tar.gz → 0.3.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.
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/.gitignore +2 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/PKG-INFO +76 -21
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/README.md +75 -20
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/pyproject.toml +1 -1
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/cli.py +139 -30
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/commit_messages.py +6 -2
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/decisions.py +15 -20
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/effort.py +45 -8
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/failure_report.py +68 -2
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/git.py +1 -1
- continuous_refactoring-0.3.0/src/continuous_refactoring/log_mirroring.py +11 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/loop.py +351 -77
- continuous_refactoring-0.3.0/src/continuous_refactoring/migration_cli.py +701 -0
- continuous_refactoring-0.3.0/src/continuous_refactoring/migration_consistency.py +528 -0
- continuous_refactoring-0.3.0/src/continuous_refactoring/migration_tick.py +950 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/phases.py +13 -5
- continuous_refactoring-0.3.0/src/continuous_refactoring/planning.py +1223 -0
- continuous_refactoring-0.3.0/src/continuous_refactoring/planning_publish.py +688 -0
- continuous_refactoring-0.3.0/src/continuous_refactoring/planning_state.py +1037 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/prompts.py +44 -7
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/refactor_attempts.py +86 -18
- continuous_refactoring-0.3.0/src/continuous_refactoring/review_cli.py +296 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/routing.py +40 -21
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/routing_pipeline.py +170 -70
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/scope_candidates.py +27 -18
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/scope_expansion.py +51 -15
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/targeting.py +15 -18
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/conftest.py +76 -24
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_cli_init_taste.py +48 -37
- continuous_refactoring-0.3.0/tests/test_cli_migrations.py +1485 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_cli_review.py +113 -10
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_cli_taste_warning.py +33 -21
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_cli_upgrade.py +56 -62
- continuous_refactoring-0.3.0/tests/test_cli_version.py +39 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_commit_messages.py +10 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_config.py +6 -20
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_continuous_refactoring.py +8 -1
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_decisions.py +85 -0
- continuous_refactoring-0.3.0/tests/test_effort.py +143 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_failure_report.py +216 -7
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_focus_on_live_migrations.py +448 -30
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_git.py +60 -29
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_loop_migration_tick.py +872 -38
- continuous_refactoring-0.3.0/tests/test_migration_consistency.py +305 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_no_driver_branching.py +8 -18
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_phases.py +133 -6
- continuous_refactoring-0.3.0/tests/test_planning.py +1498 -0
- continuous_refactoring-0.3.0/tests/test_planning_publish.py +611 -0
- continuous_refactoring-0.3.0/tests/test_planning_state.py +873 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_prompts.py +222 -0
- continuous_refactoring-0.3.0/tests/test_refactor_attempts.py +166 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_routing.py +90 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_run.py +829 -128
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_run_once.py +128 -18
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_scope_candidates.py +29 -10
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_scope_expansion.py +136 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_scope_loop_integration.py +211 -6
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_scope_selection.py +28 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_targeting.py +14 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_taste_interview.py +7 -6
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_taste_refine.py +3 -5
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_taste_upgrade.py +16 -10
- continuous_refactoring-0.2.0/src/continuous_refactoring/migration_tick.py +0 -468
- continuous_refactoring-0.2.0/src/continuous_refactoring/planning.py +0 -588
- continuous_refactoring-0.2.0/src/continuous_refactoring/review_cli.py +0 -136
- continuous_refactoring-0.2.0/tests/test_effort.py +0 -54
- continuous_refactoring-0.2.0/tests/test_planning.py +0 -579
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/LICENSE +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/__init__.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/__main__.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/agent.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/artifacts.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/config.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/migration_manifest_codec.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/migrations.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/fixtures/claude_stream_json/selection.stdout.log +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_claude_stream_json.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_e2e.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_main_entrypoint.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_migrations.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_prompts_scope_selection.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_run_once_regression.py +0 -0
- {continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/tests/test_wake_up.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: continuous-refactoring
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Continuous refactoring loop for AI coding agents
|
|
5
5
|
Project-URL: Repository, https://github.com/bigH/continuous-refactoring
|
|
6
6
|
Project-URL: Issues, https://github.com/bigH/continuous-refactoring/issues
|
|
@@ -32,6 +32,8 @@ Small, test-gated cleanup commits by an AI coding agent.
|
|
|
32
32
|
|
|
33
33
|
Think of it as a supervised janitor loop: the agent proposes a cleanup, your tests decide if it stays.
|
|
34
34
|
|
|
35
|
+
Here's [an article](https://artisincode.com/essays/how-i-use-unspent-tokens/) I wrote about it.
|
|
36
|
+
|
|
35
37
|
## Install
|
|
36
38
|
|
|
37
39
|
Try it without installing:
|
|
@@ -92,15 +94,18 @@ continuous-refactoring run \
|
|
|
92
94
|
--max-attempts 2
|
|
93
95
|
```
|
|
94
96
|
|
|
95
|
-
That
|
|
97
|
+
That runs up to 10 refactor actions, then stops sooner if the finite target file
|
|
98
|
+
runs out or the loop starts failing. Use `run --focus-on-live-migrations` when
|
|
99
|
+
you want the loop to work only on eligible live migrations; it bypasses target
|
|
100
|
+
selection and `--max-refactors`.
|
|
96
101
|
|
|
97
102
|
## What it does
|
|
98
103
|
|
|
99
|
-
- Resolves
|
|
104
|
+
- Resolves each source action from `--targets`, `--globs`, `--extensions`, or `--paths`, with optional natural-language scoping via `--scope-instruction`.
|
|
100
105
|
- Runs the agent with a refactoring prompt + your "taste" guidelines.
|
|
101
106
|
- Runs your validation command (default: `uv run pytest`).
|
|
102
107
|
- If green and there's a diff, it commits locally and leaves the branch for you to inspect.
|
|
103
|
-
- Repeats until it
|
|
108
|
+
- Repeats until it spends the action budget, exhausts a finite target file, hits the retry budget, or stacks too many failures.
|
|
104
109
|
|
|
105
110
|
## Requirements
|
|
106
111
|
|
|
@@ -143,10 +148,15 @@ continuous-refactoring run \
|
|
|
143
148
|
| `init` | Registers this directory as a project, creates a default `taste.md`, and can store `--live-migrations-dir` or `--in-repo-taste`. |
|
|
144
149
|
| `taste` | Prints the active taste file path. Add `--interview` to have an agent author it, `--refine` to iteratively improve an existing taste doc, `--upgrade` to refresh stale taste dimensions, `--global` for the shared file, and `--force` to let `--interview` overwrite custom content after writing a `.bak`. |
|
|
145
150
|
| `run-once` | Single pass on one resolved target. No retry. If there is a diff and validation passes, it commits locally and prints the diffstat. |
|
|
146
|
-
| `run` | The loop. Iterates
|
|
151
|
+
| `run` | The loop. Iterates refactor actions, retries on failure, and commits successful changes locally. Add `--focus-on-live-migrations` to bypass targeting and work only on eligible live migrations. |
|
|
147
152
|
| `upgrade` | Checks that the global config manifest is current, rewrites it idempotently, and warns if the global taste file is stale. |
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
153
|
+
| `migration list` | Lists visible migrations. Add `--status <status>` or `--awaiting-review` to filter. |
|
|
154
|
+
| `migration doctor <slug-or-path>` | Validates one visible migration's consistency. |
|
|
155
|
+
| `migration doctor --all` | Validates every visible migration plus internal transaction state. |
|
|
156
|
+
| `migration review <slug-or-path>` | Starts staged review for a migration awaiting human review. Requires `--with`, `--model`, and `--effort`. |
|
|
157
|
+
| `migration refine <slug-or-path>` | Records feedback for a planning or unexecuted ready migration and runs one staged planning revision. Requires `--message <text>` or `--file <path>`, plus `--with`, `--model`, and `--effort`; add `--show-agent-logs` to mirror the planning agent. |
|
|
158
|
+
|
|
159
|
+
Legacy `review list` and `review perform <migration>` remain compatibility aliases; prefer `migration list --awaiting-review` and `migration review`.
|
|
150
160
|
|
|
151
161
|
## Targeting / Useful flags
|
|
152
162
|
|
|
@@ -157,22 +167,46 @@ Target resolution is first-match-wins:
|
|
|
157
167
|
|
|
158
168
|
These flags are not mutually exclusive, but only the highest-priority populated source is used.
|
|
159
169
|
|
|
160
|
-
- `--targets path/to/targets.jsonl` — explicit list; one JSON object per line with `description`, `files`, optional `scoping`, `model-override`, `effort-override`. Effort overrides use `low`, `medium`, `high`, or `xhigh`.
|
|
161
|
-
- `--globs 'src/**/*.py:tests/**/*.py'` — colon-separated globs
|
|
162
|
-
- `--extensions .py,.ts` — shorthand that expands to `**/*.py`, `**/*.ts`; each matched file
|
|
163
|
-
- `--paths a.py:b.py` — literal paths, all treated as one target.
|
|
164
|
-
- `--scope-instruction "clean up the auth module"` — extra free-text scoping. If file
|
|
170
|
+
- `--targets path/to/targets.jsonl` — explicit finite list; one JSON object per line with `description`, `files`, optional `scoping`, `model-override`, `effort-override`. Effort overrides use `low`, `medium`, `high`, or `xhigh`. If `--max-refactors` is omitted, `run` processes the file once and stops.
|
|
171
|
+
- `--globs 'src/**/*.py:tests/**/*.py'` — colon-separated globs matched once against tracked files from `git ls-files`; each refactor action samples one matched file, so files can repeat.
|
|
172
|
+
- `--extensions .py,.ts` — shorthand that expands to `**/*.py`, `**/*.ts` against tracked files from `git ls-files`; each refactor action samples one matched file, so files can repeat.
|
|
173
|
+
- `--paths a.py:b.py` — literal user-provided paths, all treated as one grouped target; each refactor action reuses that group.
|
|
174
|
+
- `--scope-instruction "clean up the auth module"` — extra free-text scoping. If selected file patterns resolve nothing, this becomes the useful fallback context.
|
|
165
175
|
|
|
166
|
-
If
|
|
176
|
+
If `--globs` or `--extensions` match no tracked files and there is no
|
|
177
|
+
`--scope-instruction`, `run` completes successfully with zero refactor actions.
|
|
178
|
+
`--paths` is literal input and is not filtered through `git ls-files`.
|
|
179
|
+
|
|
180
|
+
If you provide none of `--targets`, `--globs`, `--extensions`, or `--paths`,
|
|
181
|
+
then `run` and `run-once` require `--scope-instruction`; the driver still
|
|
182
|
+
random-samples tracked files from `git ls-files` for each action and uses the
|
|
183
|
+
scope text as context for that target.
|
|
167
184
|
|
|
168
185
|
### Migrations & taste flags
|
|
169
186
|
|
|
170
187
|
- `init --live-migrations-dir PATH` — enables the larger-refactoring workflow for this project. The path is stored repo-relative in the project registry and created if missing.
|
|
171
188
|
- `init --in-repo-taste [PATH]` — stores this project's taste file in the repo and remembers the repo-relative path. Defaults to `.continuous-refactoring/taste.md`; re-run `init --in-repo-taste ...` to choose a different path.
|
|
189
|
+
- `migration list` — shows visible migrations; `--awaiting-review` narrows to human-review handoffs.
|
|
190
|
+
- `migration doctor <slug-or-path>` / `migration doctor --all` — read-only consistency checks. Doctor reports problems; it does not repair them.
|
|
191
|
+
- `migration review <slug-or-path> --with ... --model ... --effort ...` — resolves an `awaiting_human_review` migration through a staged workspace.
|
|
192
|
+
- `migration refine <slug-or-path> (--message <text>|--file <path>) --with ... --model ... --effort ... [--show-agent-logs]` — adds user feedback to a planning or unexecuted ready migration and resumes planning through the `revise` step when reopening ready work.
|
|
172
193
|
- `taste --refine` — opens a collaborative editing session for the taste file. The agent keeps refining until you tell it to write, then the session ends automatically after the settled write.
|
|
173
194
|
- `taste --upgrade` — re-interviews for taste dimensions added since your last version. No-op when already current; use `taste --refine` if you want to rework the doc anyway.
|
|
174
195
|
- `taste --force` — only applies to `--interview`; it allows a customized taste file to be overwritten after backing it up to `taste.md.bak`.
|
|
175
196
|
|
|
197
|
+
Canonical migration commands:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
continuous-refactoring migration list
|
|
201
|
+
continuous-refactoring migration list --status planning
|
|
202
|
+
continuous-refactoring migration list --awaiting-review
|
|
203
|
+
continuous-refactoring migration doctor <slug-or-path>
|
|
204
|
+
continuous-refactoring migration doctor --all
|
|
205
|
+
continuous-refactoring migration review <slug-or-path> --with codex --model gpt-5 --effort high
|
|
206
|
+
continuous-refactoring migration refine <slug-or-path> --message "split the risky phase" --with codex --model gpt-5 --effort high
|
|
207
|
+
continuous-refactoring migration refine <slug-or-path> --file feedback.md --with codex --model gpt-5 --effort high
|
|
208
|
+
```
|
|
209
|
+
|
|
176
210
|
### Shared `run` / `run-once` flags
|
|
177
211
|
|
|
178
212
|
- `--with`, `--model` — required agent backend/model selection.
|
|
@@ -187,10 +221,11 @@ If you provide none of `--targets`, `--globs`, `--extensions`, or `--paths`, the
|
|
|
187
221
|
|
|
188
222
|
### `run`-only flags
|
|
189
223
|
|
|
190
|
-
- `--max-attempts N` — per-
|
|
191
|
-
- `--max-refactors N` — cap the number of
|
|
192
|
-
- `--
|
|
193
|
-
- `--
|
|
224
|
+
- `--max-attempts N` — per-action retry budget. `1` = no retry, `0` = unlimited (which means permanently broken actions will never give up).
|
|
225
|
+
- `--max-refactors N` — cap the number of refactor actions per run. Required unless you use `--targets` or `--focus-on-live-migrations`.
|
|
226
|
+
- `--focus-on-live-migrations` — bypass target selection and `--max-refactors`; iterate eligible live migrations until they are done, deferred, blocked, or the failure budget trips.
|
|
227
|
+
- `--max-consecutive-failures N` — bail after N actions fail in a row. Default 3.
|
|
228
|
+
- `--sleep SECONDS` — pause between completed actions. Useful when you want a long batch without hammering the repo or your agent budget.
|
|
194
229
|
- `--commit-message-prefix TEXT` — subject prefix for successful refactor or migration-plan commits. Default `continuous refactor`.
|
|
195
230
|
|
|
196
231
|
## Safety behaviors
|
|
@@ -207,13 +242,27 @@ If you provide none of `--targets`, `--globs`, `--extensions`, or `--paths`, the
|
|
|
207
242
|
Each run writes to `$TMPDIR/continuous-refactoring/<run-id>/`:
|
|
208
243
|
|
|
209
244
|
- `summary.json` — rolling status, counts, per-attempt stats
|
|
210
|
-
- `events.jsonl` — structured event log
|
|
245
|
+
- `events.jsonl` — structured event log with call roles such as `classify`,
|
|
246
|
+
`planning.<step>`, `phase.ready-check`, `phase.execute`, and
|
|
247
|
+
`phase.validation`
|
|
211
248
|
- `run.log` — human-readable log
|
|
212
249
|
- `attempt-NNN/[retry-NN/]refactor/` — per-attempt agent + test stdout/stderr
|
|
250
|
+
- `baseline/initial/` — baseline validation stdout/stderr before work starts
|
|
251
|
+
- `classify/` — classifier agent stdout/stderr
|
|
252
|
+
- `scope-expansion/` — scope candidates, selection, and bypass reason
|
|
253
|
+
- `attempt-NNN/[retry-NN/]planning/<step>/` — planning agent stdout/stderr for
|
|
254
|
+
migration planning steps
|
|
255
|
+
- `phase-ready-check/` — phase precondition agent stdout/stderr
|
|
256
|
+
- `attempt-NNN/[retry-NN/]phase-execute/` — phase agent and validation logs
|
|
257
|
+
- `migration-probes/action-NNN/` — migration probe logs during normal `run`
|
|
258
|
+
actions, including planning, phase ready-checks, and phase execution
|
|
213
259
|
|
|
214
260
|
Mixed-effort runs are auditable: summaries and call events record the default effort, max allowed effort, requested effort, effective effort, source, and whether the request was capped.
|
|
215
261
|
|
|
216
|
-
The path prints at startup. Grep it when something goes sideways.
|
|
262
|
+
The path prints at startup. Grep it when something goes sideways. Failed
|
|
263
|
+
non-commit decisions also write durable XDG snapshots under the project failure
|
|
264
|
+
directory, usually
|
|
265
|
+
`~/.local/share/continuous-refactoring/projects/<uuid>/failures/`.
|
|
217
266
|
|
|
218
267
|
## Taste files
|
|
219
268
|
|
|
@@ -241,7 +290,7 @@ This tells the CLI where to store migration artifacts. The path is repo-relative
|
|
|
241
290
|
Each `run` / `run-once` tick now checks for eligible migration work before falling back to single-commit cleanups:
|
|
242
291
|
|
|
243
292
|
1. **Classify** — a classifier agent reads the target and decides: `cohesive-cleanup` (one-shot path) or `needs-plan` (migration path).
|
|
244
|
-
2. **Plan** — for `needs-plan` targets,
|
|
293
|
+
2. **Plan** — for `needs-plan` targets, each automation action runs exactly one planning step: approaches, pick-best, expand, review, optional revise/review-2, then final-review. Accepted steps update `.planning/state.json`, store stdout under `.planning/stages/`, and publish through a staged transaction. Failed current-step output stays in run artifacts and is not resume input.
|
|
245
294
|
3. **Execute** — each phase is a self-contained unit of work. The tick picks the oldest eligible migration, checks whether its current phase precondition is satisfied, and executes it on the current branch. Phase completion is judged against the phase file's `## Definition of Done`; commit message identifies the migration as `migration/<name>/<phase-file>.md`.
|
|
246
295
|
|
|
247
296
|
### Migration directory layout
|
|
@@ -250,14 +299,20 @@ Each `run` / `run-once` tick now checks for eligible migration work before falli
|
|
|
250
299
|
<live-migrations-dir>/
|
|
251
300
|
<migration-name>/
|
|
252
301
|
manifest.json # status, phases, wake-up schedule
|
|
302
|
+
.planning/
|
|
303
|
+
state.json # durable planning cursor and accepted step refs
|
|
304
|
+
stages/ # accepted planning stdout, suffixed on repeats
|
|
253
305
|
plan.md # the expanded plan
|
|
254
306
|
approaches/ # candidate approaches considered during planning
|
|
255
307
|
phase-1-<name>.md # per-phase specification
|
|
256
308
|
phase-2-<name>.md
|
|
257
309
|
...
|
|
310
|
+
__transactions__/ # internal staged publish state
|
|
258
311
|
__intentional_skips__/ # migrations rejected at final review
|
|
259
312
|
```
|
|
260
313
|
|
|
314
|
+
Do not edit `.planning/` or `__transactions__/` by hand. Use `migration doctor` when the shape looks wrong.
|
|
315
|
+
|
|
261
316
|
### Wake-up rules
|
|
262
317
|
|
|
263
318
|
Migrations don't run on every tick. The scheduler now separates **activity** from
|
|
@@ -291,7 +346,7 @@ Before executing a phase, a ready-check agent verifies that the current phase pr
|
|
|
291
346
|
|
|
292
347
|
- **ready: yes** — phase executes; on green tests, the phase is marked done, any prior deferral markers are cleared, and the migration advances immediately to the next phase.
|
|
293
348
|
- **ready: no** — manifest activity is bumped, a retry cooldown is started, and a future `wake_up_on` is recorded when needed; the tick moves on.
|
|
294
|
-
- **ready: unverifiable** — the migration is flagged `awaiting_human_review` and put on cooldown. Automated migration ticks skip flagged migrations until review clears the flag. Use `
|
|
349
|
+
- **ready: unverifiable** — the migration is flagged `awaiting_human_review` and put on cooldown. Automated migration ticks skip flagged migrations until review clears the flag. Use `migration list --awaiting-review` to find it and `migration review <slug-or-path> --with ... --model ... --effort ...` to resolve it interactively.
|
|
295
350
|
|
|
296
351
|
Human-facing migration references use the relative phase spec path, for example `phase-2-failure-report.md`. The manifest cursor stores the phase `name`, not a numeric index.
|
|
297
352
|
|
|
@@ -9,6 +9,8 @@ Small, test-gated cleanup commits by an AI coding agent.
|
|
|
9
9
|
|
|
10
10
|
Think of it as a supervised janitor loop: the agent proposes a cleanup, your tests decide if it stays.
|
|
11
11
|
|
|
12
|
+
Here's [an article](https://artisincode.com/essays/how-i-use-unspent-tokens/) I wrote about it.
|
|
13
|
+
|
|
12
14
|
## Install
|
|
13
15
|
|
|
14
16
|
Try it without installing:
|
|
@@ -69,15 +71,18 @@ continuous-refactoring run \
|
|
|
69
71
|
--max-attempts 2
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
That
|
|
74
|
+
That runs up to 10 refactor actions, then stops sooner if the finite target file
|
|
75
|
+
runs out or the loop starts failing. Use `run --focus-on-live-migrations` when
|
|
76
|
+
you want the loop to work only on eligible live migrations; it bypasses target
|
|
77
|
+
selection and `--max-refactors`.
|
|
73
78
|
|
|
74
79
|
## What it does
|
|
75
80
|
|
|
76
|
-
- Resolves
|
|
81
|
+
- Resolves each source action from `--targets`, `--globs`, `--extensions`, or `--paths`, with optional natural-language scoping via `--scope-instruction`.
|
|
77
82
|
- Runs the agent with a refactoring prompt + your "taste" guidelines.
|
|
78
83
|
- Runs your validation command (default: `uv run pytest`).
|
|
79
84
|
- If green and there's a diff, it commits locally and leaves the branch for you to inspect.
|
|
80
|
-
- Repeats until it
|
|
85
|
+
- Repeats until it spends the action budget, exhausts a finite target file, hits the retry budget, or stacks too many failures.
|
|
81
86
|
|
|
82
87
|
## Requirements
|
|
83
88
|
|
|
@@ -120,10 +125,15 @@ continuous-refactoring run \
|
|
|
120
125
|
| `init` | Registers this directory as a project, creates a default `taste.md`, and can store `--live-migrations-dir` or `--in-repo-taste`. |
|
|
121
126
|
| `taste` | Prints the active taste file path. Add `--interview` to have an agent author it, `--refine` to iteratively improve an existing taste doc, `--upgrade` to refresh stale taste dimensions, `--global` for the shared file, and `--force` to let `--interview` overwrite custom content after writing a `.bak`. |
|
|
122
127
|
| `run-once` | Single pass on one resolved target. No retry. If there is a diff and validation passes, it commits locally and prints the diffstat. |
|
|
123
|
-
| `run` | The loop. Iterates
|
|
128
|
+
| `run` | The loop. Iterates refactor actions, retries on failure, and commits successful changes locally. Add `--focus-on-live-migrations` to bypass targeting and work only on eligible live migrations. |
|
|
124
129
|
| `upgrade` | Checks that the global config manifest is current, rewrites it idempotently, and warns if the global taste file is stale. |
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
130
|
+
| `migration list` | Lists visible migrations. Add `--status <status>` or `--awaiting-review` to filter. |
|
|
131
|
+
| `migration doctor <slug-or-path>` | Validates one visible migration's consistency. |
|
|
132
|
+
| `migration doctor --all` | Validates every visible migration plus internal transaction state. |
|
|
133
|
+
| `migration review <slug-or-path>` | Starts staged review for a migration awaiting human review. Requires `--with`, `--model`, and `--effort`. |
|
|
134
|
+
| `migration refine <slug-or-path>` | Records feedback for a planning or unexecuted ready migration and runs one staged planning revision. Requires `--message <text>` or `--file <path>`, plus `--with`, `--model`, and `--effort`; add `--show-agent-logs` to mirror the planning agent. |
|
|
135
|
+
|
|
136
|
+
Legacy `review list` and `review perform <migration>` remain compatibility aliases; prefer `migration list --awaiting-review` and `migration review`.
|
|
127
137
|
|
|
128
138
|
## Targeting / Useful flags
|
|
129
139
|
|
|
@@ -134,22 +144,46 @@ Target resolution is first-match-wins:
|
|
|
134
144
|
|
|
135
145
|
These flags are not mutually exclusive, but only the highest-priority populated source is used.
|
|
136
146
|
|
|
137
|
-
- `--targets path/to/targets.jsonl` — explicit list; one JSON object per line with `description`, `files`, optional `scoping`, `model-override`, `effort-override`. Effort overrides use `low`, `medium`, `high`, or `xhigh`.
|
|
138
|
-
- `--globs 'src/**/*.py:tests/**/*.py'` — colon-separated globs
|
|
139
|
-
- `--extensions .py,.ts` — shorthand that expands to `**/*.py`, `**/*.ts`; each matched file
|
|
140
|
-
- `--paths a.py:b.py` — literal paths, all treated as one target.
|
|
141
|
-
- `--scope-instruction "clean up the auth module"` — extra free-text scoping. If file
|
|
147
|
+
- `--targets path/to/targets.jsonl` — explicit finite list; one JSON object per line with `description`, `files`, optional `scoping`, `model-override`, `effort-override`. Effort overrides use `low`, `medium`, `high`, or `xhigh`. If `--max-refactors` is omitted, `run` processes the file once and stops.
|
|
148
|
+
- `--globs 'src/**/*.py:tests/**/*.py'` — colon-separated globs matched once against tracked files from `git ls-files`; each refactor action samples one matched file, so files can repeat.
|
|
149
|
+
- `--extensions .py,.ts` — shorthand that expands to `**/*.py`, `**/*.ts` against tracked files from `git ls-files`; each refactor action samples one matched file, so files can repeat.
|
|
150
|
+
- `--paths a.py:b.py` — literal user-provided paths, all treated as one grouped target; each refactor action reuses that group.
|
|
151
|
+
- `--scope-instruction "clean up the auth module"` — extra free-text scoping. If selected file patterns resolve nothing, this becomes the useful fallback context.
|
|
142
152
|
|
|
143
|
-
If
|
|
153
|
+
If `--globs` or `--extensions` match no tracked files and there is no
|
|
154
|
+
`--scope-instruction`, `run` completes successfully with zero refactor actions.
|
|
155
|
+
`--paths` is literal input and is not filtered through `git ls-files`.
|
|
156
|
+
|
|
157
|
+
If you provide none of `--targets`, `--globs`, `--extensions`, or `--paths`,
|
|
158
|
+
then `run` and `run-once` require `--scope-instruction`; the driver still
|
|
159
|
+
random-samples tracked files from `git ls-files` for each action and uses the
|
|
160
|
+
scope text as context for that target.
|
|
144
161
|
|
|
145
162
|
### Migrations & taste flags
|
|
146
163
|
|
|
147
164
|
- `init --live-migrations-dir PATH` — enables the larger-refactoring workflow for this project. The path is stored repo-relative in the project registry and created if missing.
|
|
148
165
|
- `init --in-repo-taste [PATH]` — stores this project's taste file in the repo and remembers the repo-relative path. Defaults to `.continuous-refactoring/taste.md`; re-run `init --in-repo-taste ...` to choose a different path.
|
|
166
|
+
- `migration list` — shows visible migrations; `--awaiting-review` narrows to human-review handoffs.
|
|
167
|
+
- `migration doctor <slug-or-path>` / `migration doctor --all` — read-only consistency checks. Doctor reports problems; it does not repair them.
|
|
168
|
+
- `migration review <slug-or-path> --with ... --model ... --effort ...` — resolves an `awaiting_human_review` migration through a staged workspace.
|
|
169
|
+
- `migration refine <slug-or-path> (--message <text>|--file <path>) --with ... --model ... --effort ... [--show-agent-logs]` — adds user feedback to a planning or unexecuted ready migration and resumes planning through the `revise` step when reopening ready work.
|
|
149
170
|
- `taste --refine` — opens a collaborative editing session for the taste file. The agent keeps refining until you tell it to write, then the session ends automatically after the settled write.
|
|
150
171
|
- `taste --upgrade` — re-interviews for taste dimensions added since your last version. No-op when already current; use `taste --refine` if you want to rework the doc anyway.
|
|
151
172
|
- `taste --force` — only applies to `--interview`; it allows a customized taste file to be overwritten after backing it up to `taste.md.bak`.
|
|
152
173
|
|
|
174
|
+
Canonical migration commands:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
continuous-refactoring migration list
|
|
178
|
+
continuous-refactoring migration list --status planning
|
|
179
|
+
continuous-refactoring migration list --awaiting-review
|
|
180
|
+
continuous-refactoring migration doctor <slug-or-path>
|
|
181
|
+
continuous-refactoring migration doctor --all
|
|
182
|
+
continuous-refactoring migration review <slug-or-path> --with codex --model gpt-5 --effort high
|
|
183
|
+
continuous-refactoring migration refine <slug-or-path> --message "split the risky phase" --with codex --model gpt-5 --effort high
|
|
184
|
+
continuous-refactoring migration refine <slug-or-path> --file feedback.md --with codex --model gpt-5 --effort high
|
|
185
|
+
```
|
|
186
|
+
|
|
153
187
|
### Shared `run` / `run-once` flags
|
|
154
188
|
|
|
155
189
|
- `--with`, `--model` — required agent backend/model selection.
|
|
@@ -164,10 +198,11 @@ If you provide none of `--targets`, `--globs`, `--extensions`, or `--paths`, the
|
|
|
164
198
|
|
|
165
199
|
### `run`-only flags
|
|
166
200
|
|
|
167
|
-
- `--max-attempts N` — per-
|
|
168
|
-
- `--max-refactors N` — cap the number of
|
|
169
|
-
- `--
|
|
170
|
-
- `--
|
|
201
|
+
- `--max-attempts N` — per-action retry budget. `1` = no retry, `0` = unlimited (which means permanently broken actions will never give up).
|
|
202
|
+
- `--max-refactors N` — cap the number of refactor actions per run. Required unless you use `--targets` or `--focus-on-live-migrations`.
|
|
203
|
+
- `--focus-on-live-migrations` — bypass target selection and `--max-refactors`; iterate eligible live migrations until they are done, deferred, blocked, or the failure budget trips.
|
|
204
|
+
- `--max-consecutive-failures N` — bail after N actions fail in a row. Default 3.
|
|
205
|
+
- `--sleep SECONDS` — pause between completed actions. Useful when you want a long batch without hammering the repo or your agent budget.
|
|
171
206
|
- `--commit-message-prefix TEXT` — subject prefix for successful refactor or migration-plan commits. Default `continuous refactor`.
|
|
172
207
|
|
|
173
208
|
## Safety behaviors
|
|
@@ -184,13 +219,27 @@ If you provide none of `--targets`, `--globs`, `--extensions`, or `--paths`, the
|
|
|
184
219
|
Each run writes to `$TMPDIR/continuous-refactoring/<run-id>/`:
|
|
185
220
|
|
|
186
221
|
- `summary.json` — rolling status, counts, per-attempt stats
|
|
187
|
-
- `events.jsonl` — structured event log
|
|
222
|
+
- `events.jsonl` — structured event log with call roles such as `classify`,
|
|
223
|
+
`planning.<step>`, `phase.ready-check`, `phase.execute`, and
|
|
224
|
+
`phase.validation`
|
|
188
225
|
- `run.log` — human-readable log
|
|
189
226
|
- `attempt-NNN/[retry-NN/]refactor/` — per-attempt agent + test stdout/stderr
|
|
227
|
+
- `baseline/initial/` — baseline validation stdout/stderr before work starts
|
|
228
|
+
- `classify/` — classifier agent stdout/stderr
|
|
229
|
+
- `scope-expansion/` — scope candidates, selection, and bypass reason
|
|
230
|
+
- `attempt-NNN/[retry-NN/]planning/<step>/` — planning agent stdout/stderr for
|
|
231
|
+
migration planning steps
|
|
232
|
+
- `phase-ready-check/` — phase precondition agent stdout/stderr
|
|
233
|
+
- `attempt-NNN/[retry-NN/]phase-execute/` — phase agent and validation logs
|
|
234
|
+
- `migration-probes/action-NNN/` — migration probe logs during normal `run`
|
|
235
|
+
actions, including planning, phase ready-checks, and phase execution
|
|
190
236
|
|
|
191
237
|
Mixed-effort runs are auditable: summaries and call events record the default effort, max allowed effort, requested effort, effective effort, source, and whether the request was capped.
|
|
192
238
|
|
|
193
|
-
The path prints at startup. Grep it when something goes sideways.
|
|
239
|
+
The path prints at startup. Grep it when something goes sideways. Failed
|
|
240
|
+
non-commit decisions also write durable XDG snapshots under the project failure
|
|
241
|
+
directory, usually
|
|
242
|
+
`~/.local/share/continuous-refactoring/projects/<uuid>/failures/`.
|
|
194
243
|
|
|
195
244
|
## Taste files
|
|
196
245
|
|
|
@@ -218,7 +267,7 @@ This tells the CLI where to store migration artifacts. The path is repo-relative
|
|
|
218
267
|
Each `run` / `run-once` tick now checks for eligible migration work before falling back to single-commit cleanups:
|
|
219
268
|
|
|
220
269
|
1. **Classify** — a classifier agent reads the target and decides: `cohesive-cleanup` (one-shot path) or `needs-plan` (migration path).
|
|
221
|
-
2. **Plan** — for `needs-plan` targets,
|
|
270
|
+
2. **Plan** — for `needs-plan` targets, each automation action runs exactly one planning step: approaches, pick-best, expand, review, optional revise/review-2, then final-review. Accepted steps update `.planning/state.json`, store stdout under `.planning/stages/`, and publish through a staged transaction. Failed current-step output stays in run artifacts and is not resume input.
|
|
222
271
|
3. **Execute** — each phase is a self-contained unit of work. The tick picks the oldest eligible migration, checks whether its current phase precondition is satisfied, and executes it on the current branch. Phase completion is judged against the phase file's `## Definition of Done`; commit message identifies the migration as `migration/<name>/<phase-file>.md`.
|
|
223
272
|
|
|
224
273
|
### Migration directory layout
|
|
@@ -227,14 +276,20 @@ Each `run` / `run-once` tick now checks for eligible migration work before falli
|
|
|
227
276
|
<live-migrations-dir>/
|
|
228
277
|
<migration-name>/
|
|
229
278
|
manifest.json # status, phases, wake-up schedule
|
|
279
|
+
.planning/
|
|
280
|
+
state.json # durable planning cursor and accepted step refs
|
|
281
|
+
stages/ # accepted planning stdout, suffixed on repeats
|
|
230
282
|
plan.md # the expanded plan
|
|
231
283
|
approaches/ # candidate approaches considered during planning
|
|
232
284
|
phase-1-<name>.md # per-phase specification
|
|
233
285
|
phase-2-<name>.md
|
|
234
286
|
...
|
|
287
|
+
__transactions__/ # internal staged publish state
|
|
235
288
|
__intentional_skips__/ # migrations rejected at final review
|
|
236
289
|
```
|
|
237
290
|
|
|
291
|
+
Do not edit `.planning/` or `__transactions__/` by hand. Use `migration doctor` when the shape looks wrong.
|
|
292
|
+
|
|
238
293
|
### Wake-up rules
|
|
239
294
|
|
|
240
295
|
Migrations don't run on every tick. The scheduler now separates **activity** from
|
|
@@ -268,7 +323,7 @@ Before executing a phase, a ready-check agent verifies that the current phase pr
|
|
|
268
323
|
|
|
269
324
|
- **ready: yes** — phase executes; on green tests, the phase is marked done, any prior deferral markers are cleared, and the migration advances immediately to the next phase.
|
|
270
325
|
- **ready: no** — manifest activity is bumped, a retry cooldown is started, and a future `wake_up_on` is recorded when needed; the tick moves on.
|
|
271
|
-
- **ready: unverifiable** — the migration is flagged `awaiting_human_review` and put on cooldown. Automated migration ticks skip flagged migrations until review clears the flag. Use `
|
|
326
|
+
- **ready: unverifiable** — the migration is flagged `awaiting_human_review` and put on cooldown. Automated migration ticks skip flagged migrations until review clears the flag. Use `migration list --awaiting-review` to find it and `migration review <slug-or-path> --with ... --model ... --effort ...` to resolve it interactively.
|
|
272
327
|
|
|
273
328
|
Human-facing migration references use the relative phase spec path, for example `phase-2-failure-report.md`. The manifest cursor stores the phase `name`, not a numeric index.
|
|
274
329
|
|
{continuous_refactoring-0.2.0 → continuous_refactoring-0.3.0}/src/continuous_refactoring/cli.py
RENAMED
|
@@ -5,7 +5,9 @@ import shutil
|
|
|
5
5
|
import sys
|
|
6
6
|
import uuid
|
|
7
7
|
from collections.abc import Callable
|
|
8
|
+
from importlib.metadata import version as metadata_version
|
|
8
9
|
from pathlib import Path
|
|
10
|
+
from typing import Literal
|
|
9
11
|
|
|
10
12
|
__all__ = [
|
|
11
13
|
"build_parser",
|
|
@@ -28,8 +30,11 @@ from continuous_refactoring.loop import (
|
|
|
28
30
|
run_migrations_focused_loop,
|
|
29
31
|
run_once,
|
|
30
32
|
)
|
|
33
|
+
from continuous_refactoring.migration_cli import handle_migration
|
|
34
|
+
from continuous_refactoring.migrations import MIGRATION_STATUSES
|
|
31
35
|
from continuous_refactoring.review_cli import handle_review
|
|
32
36
|
|
|
37
|
+
_PACKAGE_DISTRIBUTION = "continuous-refactoring"
|
|
33
38
|
_TASTE_WARNING = "warning: taste out of date — run `continuous-refactoring taste --upgrade`"
|
|
34
39
|
_GLOBAL_TASTE_WARNING = (
|
|
35
40
|
"warning: global taste is out of date — "
|
|
@@ -37,6 +42,10 @@ _GLOBAL_TASTE_WARNING = (
|
|
|
37
42
|
)
|
|
38
43
|
|
|
39
44
|
|
|
45
|
+
def _version_banner() -> str:
|
|
46
|
+
return f"{_PACKAGE_DISTRIBUTION} {metadata_version(_PACKAGE_DISTRIBUTION)}"
|
|
47
|
+
|
|
48
|
+
|
|
40
49
|
def parse_max_attempts(value: str) -> int:
|
|
41
50
|
try:
|
|
42
51
|
attempts = int(value)
|
|
@@ -220,7 +229,7 @@ def _add_run_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
220
229
|
"--max-refactors",
|
|
221
230
|
type=int,
|
|
222
231
|
default=None,
|
|
223
|
-
help="
|
|
232
|
+
help="Refactor actions to run.",
|
|
224
233
|
)
|
|
225
234
|
run_parser.add_argument(
|
|
226
235
|
"--focus-on-live-migrations",
|
|
@@ -245,7 +254,7 @@ def _add_run_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
245
254
|
"--sleep",
|
|
246
255
|
type=parse_sleep_seconds,
|
|
247
256
|
default=0.0,
|
|
248
|
-
help="Seconds to sleep between completed
|
|
257
|
+
help="Seconds to sleep between completed actions.",
|
|
249
258
|
)
|
|
250
259
|
|
|
251
260
|
|
|
@@ -270,10 +279,95 @@ def _add_review_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
270
279
|
perform_parser.add_argument("--effort", required=True, help="Effort level.")
|
|
271
280
|
|
|
272
281
|
|
|
282
|
+
def _add_migration_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
283
|
+
migration_parser = subparsers.add_parser(
|
|
284
|
+
"migration",
|
|
285
|
+
help="Inspect live migrations.",
|
|
286
|
+
)
|
|
287
|
+
migration_parser.set_defaults(handler=handle_migration)
|
|
288
|
+
migration_sub = migration_parser.add_subparsers(dest="migration_command")
|
|
289
|
+
|
|
290
|
+
list_parser = migration_sub.add_parser(
|
|
291
|
+
"list",
|
|
292
|
+
help="List visible migrations.",
|
|
293
|
+
)
|
|
294
|
+
list_parser.add_argument(
|
|
295
|
+
"--status",
|
|
296
|
+
choices=MIGRATION_STATUSES,
|
|
297
|
+
default=None,
|
|
298
|
+
help="Only show migrations with this status.",
|
|
299
|
+
)
|
|
300
|
+
list_parser.add_argument(
|
|
301
|
+
"--awaiting-review",
|
|
302
|
+
action="store_true",
|
|
303
|
+
help="Only show migrations awaiting human review.",
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
doctor_parser = migration_sub.add_parser(
|
|
307
|
+
"doctor",
|
|
308
|
+
help="Validate migration consistency.",
|
|
309
|
+
)
|
|
310
|
+
doctor_parser.add_argument(
|
|
311
|
+
"target",
|
|
312
|
+
nargs="?",
|
|
313
|
+
help="Migration slug or contained path.",
|
|
314
|
+
)
|
|
315
|
+
doctor_parser.add_argument(
|
|
316
|
+
"--all",
|
|
317
|
+
action="store_true",
|
|
318
|
+
help="Validate every visible migration and transaction state.",
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
review_parser = migration_sub.add_parser(
|
|
322
|
+
"review",
|
|
323
|
+
help="Perform staged review on a flagged migration.",
|
|
324
|
+
)
|
|
325
|
+
review_parser.add_argument("target", help="Migration slug or contained path.")
|
|
326
|
+
review_parser.add_argument(
|
|
327
|
+
"--with", dest="agent", choices=("codex", "claude"), required=True,
|
|
328
|
+
help="Agent backend.",
|
|
329
|
+
)
|
|
330
|
+
review_parser.add_argument("--model", required=True, help="Model name.")
|
|
331
|
+
review_parser.add_argument(
|
|
332
|
+
"--effort", choices=EFFORT_TIERS, required=True, help="Effort level."
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
refine_parser = migration_sub.add_parser(
|
|
336
|
+
"refine",
|
|
337
|
+
help="Refine a planning migration with user feedback.",
|
|
338
|
+
)
|
|
339
|
+
refine_parser.add_argument("target", help="Migration slug or contained path.")
|
|
340
|
+
feedback_group = refine_parser.add_mutually_exclusive_group(required=True)
|
|
341
|
+
feedback_group.add_argument("--message", help="Refinement feedback text.")
|
|
342
|
+
feedback_group.add_argument(
|
|
343
|
+
"--file",
|
|
344
|
+
type=Path,
|
|
345
|
+
help="Path to a UTF-8 file containing refinement feedback.",
|
|
346
|
+
)
|
|
347
|
+
refine_parser.add_argument(
|
|
348
|
+
"--with", dest="agent", choices=("codex", "claude"), required=True,
|
|
349
|
+
help="Agent backend.",
|
|
350
|
+
)
|
|
351
|
+
refine_parser.add_argument("--model", required=True, help="Model name.")
|
|
352
|
+
refine_parser.add_argument(
|
|
353
|
+
"--effort", choices=EFFORT_TIERS, required=True, help="Effort level."
|
|
354
|
+
)
|
|
355
|
+
refine_parser.add_argument(
|
|
356
|
+
"--show-agent-logs",
|
|
357
|
+
action="store_true",
|
|
358
|
+
help="Mirror planning agent output to terminal.",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
273
362
|
def build_parser() -> argparse.ArgumentParser:
|
|
274
363
|
parser = argparse.ArgumentParser(
|
|
275
364
|
description="Continuous refactoring CLI for AI coding agents.",
|
|
276
365
|
)
|
|
366
|
+
parser.add_argument(
|
|
367
|
+
"--version",
|
|
368
|
+
action="version",
|
|
369
|
+
version=_version_banner(),
|
|
370
|
+
)
|
|
277
371
|
subparsers = parser.add_subparsers(dest="command")
|
|
278
372
|
|
|
279
373
|
_add_init_parser(subparsers)
|
|
@@ -285,6 +379,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
285
379
|
help="Verify and upgrade global configuration.",
|
|
286
380
|
)
|
|
287
381
|
upgrade_parser.set_defaults(handler=_handle_upgrade)
|
|
382
|
+
_add_migration_parser(subparsers)
|
|
288
383
|
_add_review_parser(subparsers)
|
|
289
384
|
|
|
290
385
|
return parser
|
|
@@ -312,36 +407,20 @@ def _handle_init(args: argparse.Namespace) -> None:
|
|
|
312
407
|
|
|
313
408
|
try:
|
|
314
409
|
if in_repo_taste_arg is not None:
|
|
315
|
-
repo_taste_resolved = (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
raise SystemExit(2)
|
|
322
|
-
if repo_taste_resolved.exists() and not repo_taste_resolved.is_file():
|
|
323
|
-
print(
|
|
324
|
-
f"Error: --in-repo-taste must point to a file: {in_repo_taste_arg}",
|
|
325
|
-
file=sys.stderr,
|
|
326
|
-
)
|
|
327
|
-
raise SystemExit(2)
|
|
328
|
-
repo_taste_relative = str(repo_taste_resolved.relative_to(path))
|
|
410
|
+
repo_taste_resolved, repo_taste_relative = _resolve_repo_relative_arg(
|
|
411
|
+
repo_root=path,
|
|
412
|
+
value=in_repo_taste_arg,
|
|
413
|
+
flag="--in-repo-taste",
|
|
414
|
+
expected_kind="file",
|
|
415
|
+
)
|
|
329
416
|
|
|
330
417
|
if live_dir_arg is not None:
|
|
331
|
-
resolved_live = (
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
raise SystemExit(2)
|
|
338
|
-
if resolved_live.exists() and not resolved_live.is_dir():
|
|
339
|
-
print(
|
|
340
|
-
f"Error: --live-migrations-dir must point to a directory: {live_dir_arg}",
|
|
341
|
-
file=sys.stderr,
|
|
342
|
-
)
|
|
343
|
-
raise SystemExit(2)
|
|
344
|
-
live_dir_relative = str(resolved_live.relative_to(path))
|
|
418
|
+
resolved_live, live_dir_relative = _resolve_repo_relative_arg(
|
|
419
|
+
repo_root=path,
|
|
420
|
+
value=live_dir_arg,
|
|
421
|
+
flag="--live-migrations-dir",
|
|
422
|
+
expected_kind="directory",
|
|
423
|
+
)
|
|
345
424
|
|
|
346
425
|
project = register_project(path)
|
|
347
426
|
if repo_taste_relative is not None:
|
|
@@ -380,6 +459,36 @@ def _handle_init(args: argparse.Namespace) -> None:
|
|
|
380
459
|
print(f"Live migrations dir: {resolved_live}")
|
|
381
460
|
|
|
382
461
|
|
|
462
|
+
def _resolve_repo_relative_arg(
|
|
463
|
+
*,
|
|
464
|
+
repo_root: Path,
|
|
465
|
+
value: Path,
|
|
466
|
+
flag: str,
|
|
467
|
+
expected_kind: Literal["file", "directory"],
|
|
468
|
+
) -> tuple[Path, str]:
|
|
469
|
+
resolved = (repo_root / value).resolve()
|
|
470
|
+
if not resolved.is_relative_to(repo_root):
|
|
471
|
+
print(
|
|
472
|
+
f"Error: {flag} must be inside the repo: {value}",
|
|
473
|
+
file=sys.stderr,
|
|
474
|
+
)
|
|
475
|
+
raise SystemExit(2)
|
|
476
|
+
if resolved.exists():
|
|
477
|
+
if expected_kind == "file" and not resolved.is_file():
|
|
478
|
+
print(
|
|
479
|
+
f"Error: {flag} must point to a file: {value}",
|
|
480
|
+
file=sys.stderr,
|
|
481
|
+
)
|
|
482
|
+
raise SystemExit(2)
|
|
483
|
+
if expected_kind == "directory" and not resolved.is_dir():
|
|
484
|
+
print(
|
|
485
|
+
f"Error: {flag} must point to a directory: {value}",
|
|
486
|
+
file=sys.stderr,
|
|
487
|
+
)
|
|
488
|
+
raise SystemExit(2)
|
|
489
|
+
return resolved, str(resolved.relative_to(repo_root))
|
|
490
|
+
|
|
491
|
+
|
|
383
492
|
def _configure_repo_taste(
|
|
384
493
|
*,
|
|
385
494
|
current: Path,
|