Polish — Re-runnable Branch Quality Gate
Get a branch into clean, reviewable shape before it goes up for review — and run it again any time you've made a lot of changes and want to tidy up.
This skill is an orchestrator. It does not contain quality rules of its own; it composes two existing pieces over the current branch diff:
- The
pr-revieweragent — broad review via the branch's open PR (read-only; findings are surfaced for you to act on, applied viaimplement-suggestion). Requires an open PR —pr-reviewerhas no PR-less mode. If no PR is open, Pass A is skipped and you are told to open a draft PR via/create-prfirst. - The
code-qualityskill insimplifymode — applies Class M mechanical refactors one at a time behindSkill("confidence", "code") ≥ 90 %and a scoped fast-check, reverting any that fail. Class J (judgment) recipes stay as proposals.
/create-pr delegates its post-draft quality step to review-loop (the bounded convergence loop), which calls only polish simplify — never full polish. You can run polish standalone at any point for a pre-draft local check.
Modes
Parse the first token of $ARGUMENTS. Everything else is a flag.
| Mode | Trigger | What runs |
|---|---|---|
| full (default) | No mode token | review pass (pr-reviewer, read-only), then simplify pass. The "do the works" button. |
review |
First token review |
pr-reviewer pass only — read-only review surfaced as findings for you to act on. |
simplify |
First token simplify |
code-quality simplify pass only — apply Class M mechanical refactors. |
optimize |
First token optimize |
Optimality pass only — Skill("optimize-approach", "apply") over the branch diff (gated approach rewrite). |
quick |
First token quick |
Light mechanical pass only (comments, naming, dead code). No pr-reviewer pass, no structural refactors. |
In full and review modes the optimality lens runs inside the pr-reviewer pass (which runs optimize-approach in report mode at Step 2.4c). The standalone optimize mode above is for running only that lens without the rest of the review pass.
Flags (compose with any mode):
| Flag | Effect |
|---|---|
--no-commit |
Leave all changes in the working tree instead of committing each pass. Use mid-development to keep iterating. |
--critical |
Pass --critical through to the pr-reviewer agent (adversarial pre-mortem). Ignored by simplify / quick. |
--no-optimize |
Pass --no-optimize through to the pr-reviewer agent — skip the default-on optimality lens (Step 2.4c). Ignored by simplify / quick. |
Order is fixed in full mode: review first, then simplify. The review pass surfaces correctness and obvious cleanups; simplify then applies structural refactors to the already-cleaner code, so confidence gates evaluate the final shape.
Step 0: Resolve mode and preconditions
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "polish: not a git repo"; exit 1; }
BRANCH=$(git rev-parse --abbrev-ref HEAD)
git fetch origin main --quiet 2>/dev/null || git fetch origin --quiet 2>/dev/null
Refuse to run on the default branch — there is no branch diff to polish:
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
echo "polish: on $BRANCH — check out a feature branch first."
exit 1
fi
Establish the scope and whether there's anything to do:
git diff --name-only origin/main...HEAD # files changed on this branch
git diff --stat origin/main...HEAD
If the branch diff is empty, print polish: no changes vs origin/main — nothing to do. and exit.
If the branch diff is non-code only (docs, lockfiles, generated artefacts, asset binaries — decide from the file list, not the line count), print one line and exit: polish: branch diff is non-code only — skipping.
Step 1: Working-tree check
git status --porcelain
- Clean tree (empty output): ideal. Each pass's commit contains only that pass's changes.
- Dirty tree, committing (default): print a one-line warning —
polish: working tree is dirty; pass commits will include your uncommitted changes. Commit or stash first to keep them separate.— then continue. - Dirty tree,
--no-commit: no warning needed; nothing is committed.
Never stash, discard, or reset the user's uncommitted work to "clean up" the tree.
Step 2: Run the passes
Run only the passes the resolved mode selects (see the Modes table). Each pass below is self-contained.
Pass A — review (modes: full, review)
Invoke pr-reviewer against the branch's open PR.
pr-reviewer requires a PR — it always posts one visible COMMENT review and returns findings.
Polish does not apply those findings itself; surface them to the user.
# Resolve the current branch's PR — pr-reviewer requires one.
PR_URL=$(gh pr view --json url -q .url 2>/dev/null)
If no PR is open, skip Pass A and tell the user to open a draft PR first (via /create-pr), then re-run polish.
When a PR exists, dispatch the review. pr-reviewer is an agent — dispatch it via
the Task tool (subagent_type="pr-reviewer"), not Skill() (there is no
pr-reviewer skill; Skill("pr-reviewer") errors with Unknown skill):
Task(subagent_type="pr-reviewer", prompt="<PR-URL-or-number> [--critical if user passed it] [--no-optimize if user passed it]")
The pr-reviewer pass includes the optimality lens (Step 2.4c, report-only).
Pass --no-optimize through when the user set it on polish.
Capture from the agent's reply: the verdict and the findings list.
Surface all findings to the user — polish does not auto-apply them.
If you want findings applied, use review-loop (which calls implement-suggestion after each pr-reviewer pass) rather than polish review.
Pass B — simplify (modes: full, simplify)
Invoke the code-quality skill in simplify mode against the branch diff:
Skill("code-quality", "simplify")
This runs the code-quality review pass, then applies Class M (mechanical) refactors one at a time — each behind Skill("confidence", "code") ≥ 90 % and a scoped fast-check, reverting any that fail its check. Class J (judgment) recipes are returned as proposals only.
Capture from its output: which recipes were applied (by ID, e.g. R6, R12) and which were surfaced as judgment-required proposals.
Do not pass aggressive unless the user explicitly asked for it — the default (High-impact Class M only) is the safe pre-PR setting.
Pass D — optimize (mode: optimize only)
The standalone optimality pass. Invoke optimize-approach in apply mode against the branch diff:
Skill("optimize-approach", "apply")
This judges each approach unit against the four-axis rubric and applies at most one materially-better approach rewrite behind its own apply_safe + confidence(code) ≥ 90 % gate, with a scoped check and revert-on-failure. Anything not apply-safe or below the gate is surfaced as a proposal, not applied. Capture which rewrite was applied (axis + files) and which proposals were reported.
Do not run this pass in full mode — full's reviewer pass already covers optimality via Step 2.4c, and running it twice would double the work.
Pass C — quick (mode: quick only)
The light mechanical pass. Invoke code-quality in review mode against the branch diff, then auto-apply only the mechanical subset:
Skill("code-quality", "review")
Auto-apply a finding only when it meets all three:
- Footprint stays inside files already in the branch diff (no new files, no edits outside the diff).
- The fix is mechanical, not a judgment call: removing/rewriting a plain inline comment that explains WHAT or references the current task; renaming a local variable to a domain noun; dropping
elseafterreturn/throw; extracting a magic number to a named constant; deleting unreachable/dead code introduced on this branch; flipping a single guard clause to an early return. - The fix does not change behaviour observable from a test or a caller.
Docstring / JSDoc / TSDoc / Python-docstring blocks attached to a function, method, class, type, or exported constant are a special case. Never delete the block as noise removal — IDE hover, type strippers, and doc generators read it. Instead apply code-quality recipe R35 step 4: trim verbose prose to a one-sentence summary plus the structured tags (@param, @returns, @throws, @deprecated, @since, @example, @see, @internal, @experimental). Keep the summary line and every contract-bearing tag; drop only the restated-WHAT prose. If the block would be empty after trimming, surface it as a judgment-required finding instead of removing it. License / SPDX headers and linter pragmas (eslint-disable-next-line, @ts-expect-error, # noqa) are never removed.
Surface but do NOT auto-apply (out of scope for quick — that's what simplify is for): structural refactors, type-driven design changes, anything that expands blast radius into files outside the diff, anything where a sibling test would need updating.
Step 3: Commit each pass that changed files
Unless --no-commit was passed, commit after each pass that produced changes, as its own commit, so the diff stays traceable. Skip the commit if a pass made no edits.
# After Pass A (review):
# Note: pr-reviewer is read-only — no local changes to commit from Pass A.
# Commit only if polish itself made edits (e.g. from a quick pass or simplify).
git add -u && git commit -m "chore: review pass (findings from pr-reviewer)"
# After Pass B (simplify):
git add -u && git commit -m "chore: simplify pass (mechanical refactors)"
# After Pass C (quick):
git add -u && git commit -m "chore: code-quality pass (comments, naming, dead code)"
# After Pass D (optimize):
git add -u && git commit -m "chore: optimize pass (approach rewrite)"
In full mode this can produce up to two commits (review, then simplify). That is intended — each pass is independently revertible.
With --no-commit, stage nothing; leave every change in the working tree for the user to review and commit themselves.
Step 4: Report
Print a compact summary. Match the depth to what ran.
Polish (<mode>) on <branch>
Review pass (pr-reviewer):
Verdict: <PASS | FAIL | n/a (not run)>
Findings surfaced: <N findings; apply via review-loop, or "none">
Needs your judgment: <one line per blocking item, or "none">
Simplify pass:
Applied: <recipe IDs + one-line each, or "none">
Proposed (Class J, not applied): <one line each, or "none">
Quick pass: # only if mode == quick
Applied: <one line per mechanical fix, or "none">
Optimize pass: # only if mode == optimize
Applied: <axis + files of the applied approach rewrite, or "none">
Proposed (not applied): <one line each, or "none">
Commits: <SHA + message per pass, or "none (--no-commit)">
Surface the findings (review) and Class J proposals (simplify) prominently — these are the items the user still needs to decide on. Do not silently drop them.
Hard rules
- Never weaken the codebase to look clean. No deleting/skipping/weakening tests, no disabling lint rules or type checks, no
--no-verify. - Never change public API or exported types as a mechanical fix. That is always judgment-required — surface it.
- Never apply a Class J (judgment) refactor automatically. Only Class M, only behind the confidence gate. When unsure whether a fix is mechanical or judgment, treat it as judgment and surface it.
- Approach rewrites (optimize pass) apply only behind the confidence gate, scoped to the diff's files, with revert-on-failure. An approach rewrite touching a public API, a forbidden target, or files outside the diff is surfaced as a proposal, never auto-applied — that policy lives in the
optimize-approachskill and polish does not override it. - Never write to GitHub. Polish is local-only. PR creation and any GitHub-side review belong to
/create-pr. - Never stash, reset, or discard the user's uncommitted work.
- One pass each per invocation. Do not loop. If the branch still has issues after a polish run, that is a signal for the user to act on, not for the skill to grind.
Relationship to review-loop and /create-pr
review-loop calls only Skill("polish", "simplify") — never full polish or polish review.
This is the anti-circularity guarantee: review-loop drives pr-reviewer itself each iteration;
having polish review re-enter pr-reviewer would create a dispatch cycle.
/create-pr delegates its post-draft quality step to review-loop (the bounded convergence
loop), not directly to polish.
polish is still useful standalone as a pre-draft local check.
review-loop invocation |
Delegates to |
|---|---|
review-loop (each iteration step C) |
Skill("polish", "simplify") only |
/create-pr flag |
Post-draft quality path |
|---|---|
/create-pr (default) |
Skill("review-loop") (full convergence loop) |
/create-pr --no-review |
Skill("polish", "simplify") — simplify only, one pass |
/create-pr --no-simplify |
pr-reviewer agent (Task tool) one-shot only |
/create-pr --quick |
Skill("polish", "quick") on the draft diff |
/create-pr --no-quality |
(quality skipped) |