Tidy
SkillAI & modelsReview changed code for reuse, quality, and efficiency, then apply cleanup edits. Dispatches a fresh host-provided reviewer per iteration when available; the main thread applies mechanical edits and re-dispatches until no further edits remain. Non-interactive, no user prompts. Use after implementation as a code-cleanup pass complementary to correctness review.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Tidy skill
What this skill tells your AI
The instructions your AI receives, as published by hiroro-work/claude-plugins in skills/tidy/SKILL.md and read by ahel’s review.
The cleanup walk runs in a fresh host-provided reviewer per iteration when reviewer dispatch is available; Edit application stays in the main thread. The skill loops the dispatch + apply cycle until the reviewer returns no more mechanical_edits, max iterations is reached, or a safety rail stops the loop.
Scope: invocation targets are arbitrary source files (application code, config, SKILL.md, any text). The skill does not restrict the changed-file set to a fixed directory prefix — any path in the diff is reviewed unless caught by the exclusion list (Step 1 step 3).
Invocation contract
The caller passes these fields in natural language (the skill extracts them from the invocation text):
-
Base ref(optional, default<working-tree-vs-HEAD>) — git ref to diff against. When omitted, the skill looks at the working tree's uncommitted + staged + untracked changes (the default scope for a post-implementation cleanup pass). When specified (e.g.Base ref: main), the skill switches togit diff <Base ref>semantics — useful for callers that want to review a stack of already-committed changes between a base branch and HEAD.Untracked-file scope asymmetry: the default mode includes untracked new files (collected via
git status --porcelain=v1 --untracked-files=all -z), but the explicitBase refmode reads committed history only and excludes untracked files. A caller passingBase ref: HEAD~5expecting "everything since base" will silently miss new files added sinceHEAD~5that were never committed. If you need untracked files in scope, omitBase refand rely on the working-tree default. -
Max iterations(optional, default3) — upper bound on the refinement loop. -
Custom instructions(optional) — free-form text injected into the dispatch payload as additional constraints alongside the cleanup checklist. -
Model(optional) — model id applied as themodelparameter on the reviewerAgentdispatch in Step 3 (a). An independent optional field (not part of a fixed-arity mode gate); a caller such asdev-workflowpasses it to run the cleanup reviewer on a specific model. The same value is applied to every iteration's reviewer dispatch. Effective only on the Claude CodeAgent-dispatch path; on the reviewer-dispatch-unavailable inline fallback (Step 3 (a)) the executing agent's own model governs. Validity predicate: a value is valid only if it is one of the model ids the currentAgenttool'smodelparameter accepts — check the tool's live schema loaded in the current session; a fullclaude-*id (e.g.claude-sonnet-5) is outside that parameter's accepted aliases and is therefore invalid too. An absent field or an invalid value → no override; the reviewerAgentinherits the session model.
The caller must not stage changes while this skill is running. The skill reads the working tree; staged content would mix into the diff and corrupt the verdict. (The Base ref mode reads committed history vs the ref, so staging interference applies only to the default working-tree mode.)
Dispatch authorization
This skill's procedure dispatches subagents, so invoking the skill is the request to use that mechanism: an ambient instruction allowing subagent dispatch only when the user asked for it — a permission-shaped restriction — is already satisfied by this invocation. Do not ask the user to re-confirm the dispatch, and do not silently substitute inline execution for a dispatch this procedure specifies. Only two things justify that substitution: technical availability (the dispatch tool is not present and callable on the current tool surface), and an explicit contract term from the caller bounding this skill to its own thread. A permission-shaped restriction is neither.
Process
Step 1 — Detect changed files (main thread)
- Parse
Base ref,Max iterations,Custom instructions, and the optionalModelvalue from the invocation text per§ Invocation contract. HoldModelfor the Step 3 (a) reviewer dispatch; absent or invalid → no override (inherit). See§ Invocation contract'sModelfield for the validity predicate. - Compute the changed-file set based on
Base ref:- Default mode (no
Base refprovided):git diff --name-onlyfor unstaged tracked changesgit diff --name-only --cachedfor staged tracked changesgit status --porcelain=v1 --untracked-files=all -zand collect entries prefixed??for untracked files
- Explicit
Base refmode (e.g.Base ref: main):git diff --name-only <Base ref>only. Untracked files are out of scope (see§ Invocation contract).
- Default mode (no
- Apply the exclusion list — drop any path matching:
- Lockfiles / dependency manifests:
package-lock.json,pnpm-lock.yaml,yarn.lock,Gemfile.lock,Cargo.lock,poetry.lock,uv.lock,Pipfile.lock,go.sum, any*.lock - Generated / build artifacts: paths under
dist/,build/,node_modules/,target/,vendor/,.next/,.nuxt/ - Binary files (extension-based):
*.png,*.jpg,*.jpeg,*.gif,*.webp,*.ico,*.pdf,*.zip,*.tar,*.gz,*.so,*.dylib,*.exe,*.bin
- Lockfiles / dependency manifests:
- Hold the filtered set in main-thread context as
changed_files(the scope-check baseline for Step 3 (c)). Additionally retain the subset of paths that arrived as??-prefixed entries (untracked files) asuntracked_paths— Step 3 (c)'s frontmatter rail uses it for the HEAD-absent check. - If
changed_filesis empty, emit the verdict{"status": "no-actionable-findings", "iterations_used": 0, "applied_edits_count": 0, "notes_remaining_count": 0, "reverted_paths": [], "reason": null}per§ Return contractand stop.iterations_used: 0because the iteration loop never runs.
Step 2 — Gather review inputs (main thread)
For each entry in changed_files, in the main thread:
Readthe file's full current contents. Hold in main-thread context as the iter-1 snapshot.Readreferences/cleanup-checklist.md.- Pre-capture each changed file's
git diff:- Default mode: for staged-only changes use
git diff --cached -- <file>, for working-tree-only changes usegit diff -- <file>, for mixed staged + unstaged edits concatenate them (staged section first) into one unified diff payload. For untracked files there is no diff (the file did not exist at base) — present the full file contents as the "new file" hunk in the dispatch payload. - Explicit
Base refmode:git diff <Base ref> -- <file>(committed history vs the ref).
- Default mode: for staged-only changes use
Step 3 — Iteration loop (i = 1 .. Max iterations)
Pre-register iteration tasks — before entering the loop, TaskCreate one task per iteration with subject iteration 1, iteration 2, ..., iteration <Max iterations>. Mark in_progress (via TaskUpdate) before each dispatch, completed after parse + apply (a converged iteration marks completed immediately after parsing). On early convergence (no mechanical_edits returned) or safety-rail-triggered exit, mark remaining iteration tasks completed with the skip note recorded in the task's description field as — skipped: converged / — skipped: <reason>. Pre-registration is essential when the host supports it: without it, executor-driven loops tend to stop at the first iteration that looks acceptable.
Task tools unavailable fallback (e.g. the VSCode extension, a Codex host, or a nested subagent context where progress-tracking tools were not provided): skip the pre-registration step and hold iteration state (current i, cumulative applied_edits_count, notes_remaining_count, accumulated out_of_scope, reverted_paths) in main-thread context instead. Progress tracking is not correctness-critical — the loop semantics in (a)–(c) are unaffected.
(a) Dispatch reviewer
On i == 1, use the snapshot from Step 2. On i ≥ 2, only re-Read the subset of changed_files whose path appeared in a successfully-applied mechanical_edits entry during iter i - 1 (untouched files keep their iter-1 snapshot). For that same re-read subset, also re-run the per-file git diff so the diff payload reflects edits applied in prior iterations; files outside the subset keep their iter-1 diff.
Dispatch a fresh reviewer through the current host's reviewer-dispatch mechanism. In Claude Code, use the Agent tool when it is exposed and callable and no caller-imposed nesting bound applies (see the Reviewer-dispatch unavailable fallback paragraph below) — passing the parsed Model value (§ Invocation contract) as the Agent model parameter only when it resolved to a valid override, and omitting it otherwise (absent or invalid → inherit). Apply the same Model decision on every iteration's dispatch. In Codex, use the exposed subagent / delegation mechanism when available. Assemble the dispatch prompt from the five sections below, each framed with a clear --- LABEL --- fence:
--- CLEANUP CHECKLIST ---: the full content ofreferences/cleanup-checklist.md--- CHANGED FILES ---: each changed file's path, full content, and unified diff (one block per file, separated by### <path>sub-headings; for untracked files present "(new file — no prior contents)" before the full current contents)--- CUSTOM INSTRUCTIONS ---: the value ofCustom instructionsfrom the invocation, or the literal(none)placeholder when the field is empty--- REVIEWER PROMPT ---: the reviewer prompt below (verbatim)--- RESPONSE FORMAT ---: the response format and constraints below (verbatim)
Reviewer prompt (include verbatim in the dispatch):
You are a fresh reviewer of changed code. You have not seen prior conversation context — only the CLEANUP CHECKLIST, CHANGED FILES, and CUSTOM INSTRUCTIONS below. Walk the CLEANUP CHECKLIST against each CHANGED FILE.
Preserve functionality (hard constraint): never propose a change that alters observable behavior — features, outputs, error conditions, or side-effect ordering — regardless of which checklist item the finding matches. Read § Preserve functionality at the top of the CLEANUP CHECKLIST as the binding rule. If a candidate fix could plausibly change behavior, downgrade it to a
structural_note; when behavior preservation is clear from the diff and the fix matches § Behavior-preserving structural improvements, amechanical_editis correct.Only the changed lines and their immediately surrounding context are in-scope (lines added or modified, plus surrounding code where the change participates in a structural cleanup opportunity). Do not audit pre-existing untouched code in sibling regions. Project conventions under
.claude/rules/andCLAUDE.mdoverride the checklist where they conflict — defer to them for language-specific / framework-specific standards (import style, function-declaration form, return-type annotation conventions, error-handling patterns, component / module structure).Classify each finding:
- mechanical_edit: a fix that can be applied as a textual replacement — removing a redundant narration comment, deleting a dead branch, replacing a defensive guard on an already-safe path, collapsing a needless local helper, expanding a nested ternary into an
if/else, or a behavior-preserving structural improvement that meets the CLEANUP CHECKLIST's § Behavior-preserving structural improvements conditions. Return as a{file, old_string, new_string, rationale}Edit- structural_note: a fix that requires moving content between files, deleting sections, rewriting large portions, or carries any risk of behavior change. Return as a
{file, description}note. These will not be applied automatically — they are counted innotes_remaining_count
old_stringmust match exactly one location in the current file. Include 1–3 lines of surrounding context so the snippet is unique.If CUSTOM INSTRUCTIONS is non-empty, treat its text as additional constraints — apply alongside (not in place of) the checklist.
Balance rails (required): a candidate
mechanical_editthat would violate § Balance rails — anti-over-simplification at the bottom of the CLEANUP CHECKLIST is not actionable as a mechanical fix even when it matches one of items 1–10. Either downgrade tostructural_noteor skip the finding entirely. § Balance rails and § Preserve functionality are the negative gates (what must not be mechanical); § Behavior-preserving structural improvements is the positive gate a finding reaches only after passing them.Overlap resolution (required): when a finding could match more than one checklist item, apply the Overlap handling rules listed in the CLEANUP CHECKLIST and emit only the preferred classification (first-match-wins per finding — never split one finding across multiple items).
Gate reachability rule (required): when there are no actionable mechanical findings on this iteration, you must return
mechanical_edits: []. Do not emit speculative or "nice to have" edits —mechanical_edits == []is the convergence signal and must be empty when no apply work remains. Continuing to flag issues only asstructural_notesis fine; that is the documented exit path for non-mechanical findings.
Response format (include verbatim in the dispatch):
Write your reasoning and per-file findings in natural language, then end your response with a single fenced JSON block matching this schema:
```json { "mechanical_edits": [ {"file": "<path>", "old_string": "<unique 1-3 line snippet>", "new_string": "<replacement>", "rationale": "<short reason>"} ], "structural_notes": [ {"file": "<path>", "description": "<short description>"} ] } ```
Reviewer-dispatch unavailable fallback: detect availability by inspecting the current tool surface; do not attempt a speculative call just to probe availability. When no host-provided reviewer dispatch is available — the Agent tool is absent from the tool surface (e.g. this skill runs inside a nested subagent context where nested Agent is not surfaced), or the invoking request explicitly bounds this skill to its own thread (a caller-imposed nesting bound — see § Dispatch authorization, which excludes permission-shaped restrictions but not an explicit contract term from the caller) — walk the cleanup checklist over each changed file inline-sequentially in the main thread once per iteration. Being invoked as a sub-skill (e.g. via Skill() on the main thread) does not by itself trigger this path (see § Dispatch authorization): decide by whether Agent is exposed and callable and whether a caller-imposed nesting bound applies. Under the fallback, construct the same fenced JSON block defined above so step (b)'s parser handles both paths identically. Output scope: the per-iteration JSON produced under fallback is internal state for step (b)'s parser — hold it in main-thread context only; do not write it into the user-visible response. Only the terminal verdict JSON from § Return contract appears in the response (see the uniqueness clause there).
(b) Parse & apply — evaluate in this order, first match wins
Dispatch failures (reviewer-dispatch tool error / timeout / empty response) are handled in § Dispatch failure before this parse step runs and do not enter sub-cases 1–4 below.
- Verdict missing or malformed — no fenced JSON block found, or JSON parse fails → exit loop with terminal
{"status": "error", "iterations_used": <i>, "applied_edits_count": <cumulative>, "notes_remaining_count": 0, "reverted_paths": [], "reason": "verdict parse failure"}. Do not consume remaining iter slots. - Schema violation — required keys (
mechanical_edits,structural_notes) are missing, values are not arrays, or any entry fails its expected per-entry shape (mechanical_editsentries must have non-empty stringfile,old_string,new_string;structural_notesentries must have non-empty stringfile,description) → exit loop with terminal{"status": "error", "iterations_used": <i>, "applied_edits_count": <cumulative>, "notes_remaining_count": 0, "reverted_paths": [], "reason": "verdict schema violation"}. - No more apply work —
mechanical_edits == []→ exit loop. Determine the terminal status from cumulative state and the current iter'sstructural_notes:- cumulative
applied_edits_count == 0ANDstructural_notes == []→no-actionable-findings - cumulative
applied_edits_count > 0ANDstructural_notes == []→applied-edits(notes count = 0) structural_notes != [](regardless of cumulative count) → if cumulative > 0 thenapplied-edits(withnotes_remaining_count > 0), elsenotes-left
- cumulative
- Otherwise — apply
mechanical_editsin order:- For each entry, verify
file ∈ changed_files; if not, record the path in anout_of_scopelist and skip the entry without callingEdit. Theout_of_scopelist is later listed inreverted_pathsin the terminal verdict if non-empty. - For each in-scope entry, re-
Readthe target file (soold_stringmatches the current contents after any earlier edit was applied), then callEdit. - If an
old_stringis not found, skip that entry and continue with the next. This is expected when the reviewer emits multiple edits from a single snapshot and a later edit overlaps a region an earlier one already rewrote — the skip is a no-op fallback, not an error. - Increment
applied_edits_countonly for entries whoseEditcall succeeded — skipped entries do not count. - After the edits (applied or skipped), if at least one Edit succeeded, run the safety rails in (c), then continue to iteration
i + 1. If all entries skipped, also continue (the next iter will re-dispatch with the current file state).
- For each entry, verify
(c) Per-iteration safety rails — run only if at least one edit was applied
-
Frontmatter integrity — for each edited file that begins with a
----delimited YAML frontmatter block, re-Readand parse the frontmatter. If parsing fails:git checkout HEAD -- <file>Exit loop with terminal
{"status": "error", "iterations_used": <i>, "applied_edits_count": <surviving on disk>, "notes_remaining_count": 0, "reverted_paths": [<file>], "reason": "frontmatter broken"}.applied_edits_countreflects edits still on disk after the revert (the revert wipes this file's prior-iter contributions; cross-file edits to other paths in earlier iters survive). Files without a frontmatter block skip this rail.Untracked-path specialization: if the offending file is in
untracked_paths(i.e. HEAD-absent — the iteration just edited an untracked new file), skip thegit checkoutfor HEAD-absent paths; the file is left in its post-edit state,applied_edits_countretains all surviving on-disk edits (no revert occurred), andreverted_pathsstill carries the offending path as an informational entry so the caller sees what could not be reverted. -
Scope — the per-edit pre-check in (b) step 4 already skips out-of-scope writes, so no global
git diff --name-onlyrevert is needed. If theout_of_scopelist accumulated by (b) step 4 is non-empty for this iteration, exit loop with terminal{"status": "error", "iterations_used": <i>, "applied_edits_count": <cumulative>, "notes_remaining_count": 0, "reverted_paths": <out_of_scope>, "reason": "scope violation"}(no actualgit checkoutruns — the pre-check prevented the write).
Step 4 — Max iterations reached without convergence
If the loop runs all Max iterations without (b) sub-case 3 firing (i.e. the reviewer kept emitting mechanical_edits to the end, but apply progress stalled — typically all entries skipping because old_string collisions), determine the terminal status from cumulative state and the last iter's structural_notes:
- cumulative
applied_edits_count > 0→applied-edits(notes_remaining = last-iterstructural_notescount) - cumulative
applied_edits_count == 0AND last-iterstructural_notes == []→no-actionable-findings - cumulative
applied_edits_count == 0AND last-iterstructural_notes != []→notes-left
Step 5 — Emit verdict
End your response with a single fenced JSON block matching the schema in § Return contract. See § Sub-skill caller directive for the caller-side no-stall discipline that applies when this skill is invoked as a sub-skill.
Scope
- Only review files that have uncommitted changes (default mode) or that appear in the
Base ref-vs-HEAD diff — diff-scoped, not a full audit - Project conventions (
.claude/rules/,CLAUDE.md) override the checklist where they conflict - Don't chase perfection — fix real cleanup wins, note structural ones, move on
- Sub-skill scope note (caller-side): when this skill runs as a sub-skill, structural changes are counted in
notes_remaining_countrather than applied. The caller decides whether and how to act on them. See§ Sub-skill caller directivefor the no-stall discipline that applies on the sub-skill invocation path.
Return contract
The skill emits a single fenced JSON block at the very end of the invocation. Only one fenced JSON block must appear in the user-visible response — the verdict block. Intermediate structured outputs that the skill constructs internally for its own parser (e.g., the per-iteration reviewer JSON synthesized under the Agent-unavailable fallback path) are held in main-thread context and do not enter the response stream.
Emit a single fenced JSON block at the end of the response, matching the schema:
{
"status": "no-actionable-findings|applied-edits|notes-left|error",
"iterations_used": N,
"applied_edits_count": N,
"notes_remaining_count": N,
"reverted_paths": ["<path>", "..."],
"reason": "verdict parse failure|verdict schema violation|frontmatter broken|scope violation|dispatch error|null"
}
The |null token at the end of the reason enum means JSON null (not the string "null").
Field semantics:
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 47
- Forks
- 3
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
tidy-hiroro-work- Source
- github.com/hiroro-work/claude-plugins