| name | refactor |
| description | Post-PR refactor pass. Sizes the diff against the remote default branch, fans out refactor-simple (and refactor-deep on large changes) as fresh subagents that run blind to each other, merges their plans once with max-severity rules, shows the merged report, and hands it to refactor-apply on the user's word. Use after a PR is open, or whenever the user asks to refactor or clean up the branch. |
| argument-hint | [--size=small|large] [--plan-only] |
Refactor
One command for the post-PR quality pass. It sizes the change, runs the right
analyses independently, merges once, stops for the user, then applies. It
runs after review and before fresh-eyes on the PR body and before QA: it
changes the head, and QA evidence must be current-head evidence.
Why the analyses run blind
Two analyses that see each other's findings converge into one opinion. Run
independently, refactor-simple and refactor-deep overlap on about half
their findings, the other half is complementary, and the severe correctness
findings tend to come from one of them alone. So each analysis runs in a
fresh subagent with no access to the other's output, and the merge happens
exactly once, here, after both are done. Repeated runs on the same diff
converge on the same findings, and averaging them has demoted real Criticals;
one run per analysis is the rule.
Process
1. Size the change
BASE=$(git symbolic-ref -q refs/remotes/origin/HEAD | sed 's|refs/remotes/||')
[ -n "$BASE" ] || BASE=origin/$(git remote show origin | sed -n 's/.*HEAD branch: //p')
git fetch origin "${BASE#origin/}"
git diff "$(git merge-base "$BASE" HEAD)" --numstat
Merge-base to working tree, so uncommitted work counts; the remote's real
default branch, not an assumed main.
Exclude lockfiles, generated files, and vendored directories from the count.
Under ~10 hand-written files and ~500 lines is small; above is large.
--size overrides. State the size and the file count before fanning out.
2. Fan out, independently
Each analysis runs as a separate fresh-context subagent (the Agent tool, one
per analysis, launched in the same message so they run concurrently). Each
subagent invokes its skill and returns the absolute path of the plan it wrote.
The subagent prompt names the skill and the worktree, and nothing else.
- small:
refactor-simple only.
- large:
refactor-simple and refactor-deep, concurrently.
Each writes its own file under ./tmp/, and reads only its own.
3. Merge once
Read every plan and produce one merged report at
./tmp/refactor-merged-[timestamp].md. Rules, in priority order:
- Cluster findings that point at the same file/area and the same
underlying issue, even if worded differently.
- Keep the maximum severity. A finding that is Critical in one plan and
Warning in another is Critical. A reproduced defect keeps its reproduction.
- Sole-source findings are kept. Corroboration is not required; the
independent runs are expected to disagree, and the disagreement is signal.
- Tag every item with its source,
[S], [D], or [S+D], so the
reader can see who found what.
- Carry each plan's quality score as reported, plus the merged
Critical/Warning/Info counts. The merged report has no combined score.
- Pre-existing-not-against-this-PR items stay in Info, unchanged.
Keep every item's file:line, fix, and auto-fixable flag from its source
plan. The merged report is the same shape as the individual plans, so
refactor-apply reads it unchanged.
4. Stop for the user
Run fresh-eyes on the merged report first: a person reads it to decide what
to change in their code. Fresh-eyes may reorder, retitle, and clarify;
severities, findings, and file:line stay as written. Then show it, Criticals
in full, Warnings and Info summarised, with the auto-fixable and manual
counts, and stop. This is the gate: nothing
is applied until the user says so. --plan-only ends here.
Auto-fixable items are the safe class; still show them. Manual items always
wait for the user's judgment on each.
5. Apply, then prove it
On the user's go, invoke refactor-apply on the merged report, auto-fixable
first, then manual with the user. refactor-apply leaves its edits
uncommitted; this step commits them as one scoped commit named for the plan
and the round, so the adversary has an exact diff. Every repair is committed
the same way before its review.
Then run the adversarial loop as one continuous chain, and report when it
ends. Three review passes is the cap.
- Pass 1 reviews the apply commit. A fresh subagent reads that commit's
diff alone (
git diff <sha>~1..<sha>), briefed to prove it broke
something: every claim in the apply report is a claim to falsify; a
behaviour-preserving change must have preserved behaviour; a consolidation
must keep every edge case a caller relied on. A test that claims to fix a
defect runs on the parent (must fail) and the head (must pass);
characterization tests for behaviour-preserving changes may pass on both.
It returns CLEAN, or REGRESSION with file:line and a repro.
- On REGRESSION, the applier repairs, then two reviewers look. The
repro goes back to the apply session, which knows the code; the repair is
committed with the repro as a test proven failing on its parent. Then, in
the same message: the reviewer who found the finding gets the repair as a
follow-up in its own session, to re-run its repro and say whether that
finding is closed; and a fresh subagent reads the repair commit's diff
alone, briefed to break the repair itself, since a repair that changed
code can break an adjacent case the first reviewer was primed to look
past. The follow-up settles the finding; the fresh pass is the numbered
pass and counts toward the cap.
- Pass 3 is the last. Reaching it means each repair fixed the reported
inputs and broke the next; the repair before pass 3 is spec-driven: the
applier writes the input class as a test table, or replaces the mechanism
with a pure derivation, and stops rather than commits when the correct fix
needs scope beyond the files at hand. Whatever pass 3 finds goes to the
user with the plan.
A CLEAN pass advances. At the cap, advance anyway: the survivors are reported
as open Criticals beside the delta, and the user decides. Then re-run the
analyses (step 2, fresh subagents) once and show the delta: what closed,
what remains, anything new.
On the first real run, two apply commits passed every check and their own new
tests, and the adversary found a reproduced regression in each; the repairs
then broke adjacent cases twice, with tests that passed trivially. The
adversary told to falsify caught all of it.
Rules
- Steps 1-4 modify no tracked files. Only step 5 edits code, and only after
the user's explicit go.
- Analyses run in fresh subagents, blind to each other; a plan stays out of
every other analysis.
- Merge means cluster and keep the maximum severity.
- If a subagent fails or returns no plan, say so and merge what exists.
- The adversarial loop runs in one chain. Waiting on a human between rounds
turns ten minutes of agent work into hours; report at the end.
- The reviewer who found a finding verifies its repair, in its own session;
a fresh reviewer hunts what the repair introduced. Two questions, two
readers.