# Batch Simplify

> Batch-run simplification across changed files, or across an entire repository, grouped by ecosystem and dependency order. Use when: 'batch simplify', 'simplify recent changes', 'forgot to run simplify', 'catch up on simplify', sweeping a named scope such as a branch, a whole repository, or one directory, or after a multi-session sprint. Accepts a time window (`24h`, `7d`), `branch` to diff the current branch vs the default branch, or `repo` for a confirmed whole-repository sweep; any scope narrows to one or more trailing paths; optional `docs` flag includes .md files for post-migration or post-refactor doc sweeps. Skip for single-file cleanup. Use /simplify instead.

- Skill: `melodic-software/batch-simplify` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add melodic-software/batch-simplify`
- Raw SKILL.md: https://api.skillmd.com/api/skills/melodic-software/batch-simplify/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: melodic-software (https://skillmd.com/u/melodic-software)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/melodic-software/batch-simplify

---


## Repository context. Gather first

Collect these with **individual** Bash calls, one command per call, never combined into a single
invocation:

- Current branch, `git branch --show-current`

Treat a failure (not a repository, git unavailable) as an unknown value and carry on. Keep these as
separate body Bash calls rather than pre-compute lines: the harness runs a skill's whole pre-compute
block as one shell invocation, and a worktree-isolated session refuses a compound command that
contains git. The dated record for that composition claim is the `source-control` plugin's
[worktree/reference/gather-block.md](https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/plugins/source-control/skills/worktree/reference/gather-block.md),
"The pre-compute block runs as one shell invocation".

## Variables

HARD path exclusions: `${user_config.hard_exclusions}` (unexpanded, empty, or any value outside
`enforce` and `advisory` means `enforce`).

## Purpose

Automate running simplification across changed code files, or every code file in the repository, grouped so each pass has tight focus and ecosystem-appropriate context. Replaces the manual process of remembering to run `/simplify` after each task.

## Emit checklist

For any batch run (Phases 1-8), copy `templates/checklist.md` into your project's working-notes location (or track the phases inline if it has none). Tick each phase as completed. Phase 6.5 SKIPPED when no deferred items surface; Phase 5 task tracking SKIPPED for single-group changes.

## Arguments

`$ARGUMENTS`, optional scope for the file scan: `[<scope>] [<path>...] [docs]`. The scope picks a *universe* (three modes below); a path narrows it to a *region*. They are independent, so any scope takes any path.

### Mode 1: Time window (default)

Supported formats: `24h`, `48h`, `72h`, `7d`, `2d`, `1w`. Default: `48h`.

**Normalize before use:** git's `--since` approxidate parser requires `24 hours`, not `24h`. Convert before passing to `git log`:

- `<N>h` → `<N> hours` (e.g., `48h` → `48 hours`)
- `<N>d` → `<N> days` (e.g., `7d` → `7 days`)
- `<N>w` → `<N> weeks` (e.g., `1w` → `1 weeks`)

### Mode 2: Branch diff

Trigger: the remaining argument, lowercased and whitespace-normalized, **equals** one of `branch`, `feature branch`, or `all commits`. Uses `git diff --name-only <default-branch>...HEAD` (three-dot. Diff from the merge base, so files changed only on the default branch since the branch point are NOT swept in) to find files this branch changed. Requires being on a non-default branch.

Match the whole argument, never a substring: an argument that merely *contains* "branch", a path, a filename, a future scope value, is not a branch-mode request, and routing it there silently sweeps the wrong file set.

**Detection heuristic** (after stripping the `docs` flag): read the **first** remaining token as the scope. Matches `^\d+[hdw]$` → time-window mode; equals a branch trigger phrase → branch mode; equals `repo` → repo mode. Empty argument → default `48h`. Every token after the scope is a path.

Decide the scope **before** testing any token as a path, never after. Stripping paths first lets a repository that happens to contain a directory named `repo` or `branch` swallow the scope keyword. `/code-tidying:batch-simplify repo` would resolve `repo` as a path, find no scope left, and silently run the 48-hour default narrowed to that directory instead of the whole-repository sweep that was asked for. If the first token is not a scope, the scope defaults to `48h` and *every* token is a path; a token that resolves to nothing is neither a scope nor a path, so it falls through to asking the user rather than guessing. To sweep a directory genuinely named `repo`, spell it `./repo`.

### Mode 3: Whole repository

Trigger: the remaining argument, lowercased and whitespace-normalized, **equals** `repo`. Sweeps every non-excluded file in the repository rather than a diff, including untracked files that are not ignored, so newly added work is swept too. Explicit entry only: it never auto-escalates from another mode, and it confirms the inventory with the user after Phase 4, once grouping and wave planning have produced the numbers that gate reports, and before any group is dispatched. A trailing path narrows the sweep (see **Narrowing to a path**) without changing any of the above, the confirmation gate still fires, on the narrowed inventory. Repo-scale machinery, grouping, waves, concurrency, resume, delivery, lives in [context/repo-mode.md](context/repo-mode.md), loaded only when this mode fires.

### Narrowing to a path

Any scope accepts one or more trailing paths: `48h plugins/knowledge`, `branch src/`, `repo plugins/code-tidying`. Narrowing is orthogonal to scope because the two answer different questions, the scope decides *which universe* (changed recently, changed on this branch, everything), the path decides *which region of it*. Binding paths to one mode would assert that only that universe may be narrowed, which nothing supports.

Apply the path as a native pathspec on that mode's own discovery command (`-- <path>`), never as a filter over the results: the pathspec is what makes the mode's own semantics hold over the narrowed set. That means merge-base for branch and `--since` for a window.

Resolve each path against the **invocation directory**, then express it **repo-root-relative** before handing it to a root-anchored command. Repo mode runs `git -C <repo-root>`, and `-C` changes directory before the pathspec is applied: a bare `code-tidying` typed from inside `plugins/` resolves locally but would be read at the root as a top-level `code-tidying`, sweeping the wrong set or nothing at all while reporting a clean run.

A token counts as a path only if it **resolves** to an existing file or directory. A token that resolves to nothing is neither a path nor a scope, so it falls through to the ask-the-user rule, which is what keeps a mistyped scope or path an explicit question instead of a silent sweep of nothing.

`repo <lane>` is deliberately **not** accepted; use a path. Rationale in [context/reference.md](context/reference.md) "Why narrowing is a path, not a lane".

### Flag: `docs`

Append `docs` to any mode to include `.md` files in the sweep. By default, `.md` files are excluded because they're prose, not code. The `docs` flag tells the simplifier to review documentation for consistency. Stale references, outdated library names, incorrect API examples, or references to renamed/removed code. Boundary with the sibling `/code-tidying:tidy`: batch-simplify owns factual staleness across the whole doc set in one pass; tidy's `docs-prose` lane owns incremental structural prose work under a scope budget.

**When to use `docs`:**

- After a library migration. Docs may reference old library names or patterns
- After a large refactoring. Docs may reference old file paths, class names, or API shapes
- After renaming or reorganizing modules. Docs may have stale cross-references

**Examples:** `/code-tidying:batch-simplify branch docs`, `/code-tidying:batch-simplify 72h docs`

**Detection:** split `$ARGUMENTS` into whitespace-separated tokens. If any token **equals** `docs` (case-insensitive), set the docs flag and drop that token; rejoin the rest as the remaining argument, which determines the mode.

Strip token-wise, never by substring: a substring strip mutates any argument that happens to contain those four letters, including a path such as `docs/`. Leaving a corrupted remainder for the mode parser to read.

### Flag: `override`

Append `override` to any mode to lift the **GLOBAL HARD path list** for this sweep, so the agent-and-enforcement-configuration class in Phase 2 becomes a reported class rather than an excluded one. Detected and stripped exactly like `docs`: token-wise equality, case-insensitive, dropped before the mode parser reads the remainder, with `./override` spelling the directory of that name.

It reaches path entries only. The append-only / historical-record protection in Phase 2 is a separate contract that no channel lifts (a bulk edit there rewrites history), and so are the behavioral guards, the work-tracking entries, and SELF-UPDATE EXTRA HARD. Every lifted path is named in the Phase 8 report with the channel that lifted it.

Two standing channels lift the same list without the flag: a root-relative glob in the repo's tracked `.claude/code-tidying/exclusion-overrides.md`, and the `hard_exclusions: advisory` userConfig posture. Precedence per path is argument, then repository file, then userConfig, then enforced. Full contract: the tidy skill's [exclusions reference](${CLAUDE_PLUGIN_ROOT}/skills/tidy/reference/exclusions.md) section 4.

## Workflow

### Phase 1: Discover changed code files

Both modes union committed history with uncommitted changes (staged + unstaged) so nothing is missed. When a path was given, append `-- <path>...` to every git command in this phase, including the working-tree `git diff`, so the narrowing holds over both halves of the union.

**Branch mode** (argument matches the branch/feature-branch/all-commits pattern):

```bash
# Committed on branch (vs merge base) + uncommitted working tree changes
{ git diff --diff-filter=ACDMR --name-only <default-branch>...HEAD; git diff --diff-filter=ACDMR --name-only HEAD; } | sort -u
```

Requires being on a non-default branch. If on the default branch, report the error and exit.

**Time-window mode** (default):

```bash
# Normalize shorthand: 48h → "48 hours", 7d → "7 days", 1w → "1 weeks"
# Committed in window + uncommitted working tree changes
{ git log --since="${NORMALIZED_TIME_WINDOW} ago" --diff-filter=ACDMR --name-only --pretty=format:""; git diff --diff-filter=ACDMR --name-only HEAD; } | sort -u | grep -v '^$'
```

**Repo mode**, the file universe is `git -C "$(git rev-parse --show-toplevel)" ls-files --cached --others --exclude-standard [-- <path>...]`, with any `<path>` converted to repo-root-relative first (the `-C` makes the pathspec root-anchored, so a path typed relative to a subdirectory would otherwise be read against the wrong base), anchored to the repo root so a run started in a subdirectory still sweeps the whole tree. Refuse to start if any file in that universe carries tracked modifications, naming them; scope that check to the swept universe and exclude the working-notes location from both the check and the sweep, a whole-tree refusal would block the checklist this skill writes as its own first step, and would block every resume.

### Phase 2: Filter to code files

Exclude non-code files. Keep only files that benefit from code simplification:

**Include** (code and code-like config):

- `.cs`, `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.py`, `.sh`, `.bash`, `.ps1`, `.psm1`, and any other source-code extensions the project uses
- Build-system config (`.csproj`, `.props`, `.targets`, `package.json`, `pyproject.toml`, and equivalents)
- Project config `.json` / `.jsonc` / `.toml` (e.g. `tsconfig.json`, `biome.json`, `.mcp.json`, not data files)
- Linter/format config (`.editorconfig`, `.gitattributes`, `.gitignore`, `.dockerignore`, `.shellcheckrc`)

**Exclude** (not useful for code simplification):

- `.md` files (documentation. Prose, not code). **Exception:** when the `docs` flag is set, include `.md` files that are NOT in the protected list below. The simplifier reviews docs for stale references, outdated library names, incorrect API examples, or references to renamed/removed code, not for prose quality
- `.lock` files (`uv.lock`, `package-lock.json`. Auto-generated)
- **Agent & enforcement configuration**. `.claude/hooks/**`, `.claude/settings*.json`, `.claude/agents/**`, `.mcp.json`, `.github/workflows/**`, git-hook manager config (`lefthook.yml`, `.husky/**`, `.pre-commit-config.yaml`): not handed to an autonomous simplifier by default (same safety model as this plugin's tidy skill). If they changed in the window, list them as read-only deferred items instead. This is the one class here that the HARD list gates, so it is the class the three override channels lift; a lifted path is swept like any other file and reported with its channel in Phase 8. Lint and format config is **not** in this class for this skill (it is in the Include list above, and always has been)
- Data files (fixtures, datasets, exported records. Anything that is content rather than logic)
- Skill/agent definition prose (`SKILL.md`, agent markdown), `README.md`, `CLAUDE.md`
- Generated or vendored code, and any directory the consuming repo documents as externally managed or sync-generated, a local edit there is silently overwritten on the next sync, so it is a read-only deferred class rather than a sweep target ([context/repo-mode.md](context/repo-mode.md))

**Append-only / historical-record protection** (applies even when the `docs` flag is set):

- **Filename patterns** (case-insensitive): `CHANGELOG.md`, `CHANGELOG.txt`, `HISTORY.md`, `RELEASES.md`, `NEWS.md`. ADR filenames matching `[0-9]{3,4}-*.md` under any `decisions/` or `adr/` directory (immutable post-acceptance by convention. Supersede, do not edit the body).
- **Body-text declaration**: any file whose first 20 lines contain a case-insensitive match for `append-only`, `append only`, `historical record`, `do not edit historical entries`, or `immutable`. The declaration is the file declaring its own policy. Honor it.
- **Why hard-excluded** (not a default-with-flag-override): a bulk find-replace on a changelog or accepted ADR rewrites history. Readers diffing the file later see what entries say NOW, not what they said when written. If a genuine update is needed, make a manual edit with explicit user authorization. Do not bulk-include via the simplifier.

### Phase 3: Verify existence

Check each file exists on disk. Files may have been deleted or renamed since the commit. Drop any that don't exist.

If no code files remain after filtering, report "No code files changed in {scope}" and exit (where scope is the time window or "branch vs default"). When the scope was a diff, name repo mode in that same report. *"nothing changed in {scope}; `/code-tidying:batch-simplify repo` sweeps the whole repository instead"*, and stop. Offering is not entering: run repo mode only if the user asks for it.

### Phase 4: Group files

Group files by project/ecosystem relatedness. Each group should contain files that share enough context for the simplifier to reason about them together.

**Grouping rules** and **dependency ordering** (root config → agent infra → scripts → shared libs → app code → cross-cutting tests → polyglot services): full priority lists in [context/reference.md](context/reference.md) "Grouping & dependency order (Phase 4)".

### Phase 5: Create tasks

Create one task per group using `TaskCreate`. Each task should include:

- Group number and name
- File list
- Ecosystem (for verification)
- Dependency notes

### Phase 6: Run simplification waves

**Before spawning any agents**, ground the run: if the `discovery` plugin is installed, invoke `/discovery:explore` via the Skill tool on the batch scope and `/discovery:research` via the Skill tool covering idioms relevant to the dominant ecosystems in the wave; otherwise read representative files per group and do a focused inline research pass on the ecosystems' current idioms.

Waves can run in parallel when groups touch non-overlapping files and ecosystems. Launch independent groups in a single message with multiple Agent tool calls; serialize only when groups have direct dependencies.

For each group:

1. **Mark the task in_progress** via `TaskUpdate`

2. **Spawn a simplifier agent** via the `Agent` tool. Use `subagent_type: "pr-review-toolkit:code-simplifier"` when that plugin is installed, else `subagent_type: "general-purpose"`. The prompt includes:
   - The complete list of files in the group (absolute paths)
   - The ecosystem and the consuming project's relevant convention files (its `CLAUDE.md` / `.claude/rules` paths), when they exist
   - Instructions to read each file and check for redundancy/inconsistency/dead code/simplification opportunities
   - Instructions to preserve every observable behavior: exit codes, output format, public API, CLI args
   - The ecosystem-specific verification commands to run after changes (see context/reference.md)
   - An escalation clause: *"If you discover mid-task that the requested change is wrong, conflicts with project conventions, or requires touching files outside your file list, STOP and report back instead of improvising."*
   - **Fix-first deferral contract (required):** *"Apply every simplification you identify. Deferral is the exception, and each deferral must name one of these grounds: (a) HUMAN-DECISION, the change turns on a judgment only a human can make (a behavior or public-API question, an ambiguous contract, product intent); (b) TOO-LARGE, a genuinely huge refactor whose scope would dwarf this sweep (a redesign spanning ecosystems, a breaking API migration); (c) CROSS-GROUP, the change requires editing files outside your file list (a later resolution wave in this same run will take it); (d) PROTECTED, the target is in a Phase 2 excluded class. 'Out of scope', 'would dilute the diff', or 'could be a follow-up' are NOT grounds; if you can do it safely and verify it, do it now. Record each deferral in a `## Deferred` section of your final report with this shape per item: `- <path>:<line or range> — <one-line description>. Ground: <HUMAN-DECISION|TOO-LARGE|CROSS-GROUP|PROTECTED>. Reason: <why that ground applies>. Scope: <trivial|small|medium|large>. Category: <refactor|dedup|modernize|perf|cleanup>.` Do not silently skip, if you noticed it, list it. 'Already idiomatic' or 'preserves documented contract' do NOT need to appear. Only candidates you considered actionable but set aside."*

3. **Collect deferred items**, when the agent returns, extract the `## Deferred` section verbatim into a running list keyed by group number. Do not lose or paraphrase these items.

4. **Report results**. Summarize what the agent changed (or didn't) for that group, plus a count of deferred items.

5. **Mark the task completed** via `TaskUpdate`

### Phase 6.5: Resolve deferred items in-run

After all groups complete, consolidate the deferred items collected in Phase 6 and RESOLVE them in this same run. The default is fix, not file: a filed issue is deferred work that piles into a backlog, so filing is reserved for items a fix genuinely cannot absorb. If you notice something and choose not to fix it now, say so and capture it rather than dropping it.

1. **Dedupe and group**. Multiple agents may flag the same cross-cutting concern. Merge into single items spanning all identified sites.

2. **Triage each item, fix-first.** Re-examine the recorded ground; agents defer conservatively, so many deferrals are fixable here where the whole run's context is visible:
   - **Fix-now** (the default). Everything else: CROSS-GROUP refactors (the wave boundary that forced the deferral is gone now), dedups, modernizations, mechanical large changes. If it can be done safely and verified, it belongs here regardless of size.
   - **Needs-human**. HUMAN-DECISION and PROTECTED items: a behavior or public-API question, product judgment, or an excluded class this skill must not edit.
   - **Too-large**. A genuinely huge refactor whose scope would dwarf the sweep itself. Be skeptical before granting this: "large" is not "too large"; the bar is work that would need its own planned effort, not just many edits.

3. **Run a resolution wave** for the Fix-now items: spawn agents with the same Phase 6 spawn contract (same verification, and in repo mode the same refutation verifier). The wave's edits land exactly like the primary wave's: uncommitted working-tree changes in the diff-scoped modes, commits on the run's single branch in repo mode. Give each agent the complete file set its concern spans, every consolidated site plus every file a CROSS-GROUP ground named, so the wave boundary that forced the deferral is actually gone and CROSS-GROUP cannot legitimately recur. If an agent still discovers a genuinely new file mid-task, fold it into that item's file list and re-dispatch the item once. One resolution wave plus that single re-dispatch, no further recursion: an item still deferred after it goes to the Phase 8 report carrying its recorded ground, whatever that ground is.

4. **Report the remainder, do not file it.** Needs-human items, Too-large items, and anything the resolution wave still could not finish go in the Phase 8 summary with their grounds and the agent's recorded rationale, so the user decides their fate. Do not file work items by default in any mode. Only when the user explicitly asks to file, invoke `/work-items:track add` via the Skill tool when that plugin is installed, else `gh issue create`, one item per concern (not per site), Conventional Commits-style titles (`refactor(<area>): <what>`), body carrying the recorded rationale, files/lines, and scope estimate.

### Phase 7: Final cross-ecosystem verification

After all groups complete, run final verification across all affected ecosystems using the consuming project's canonical build/test/lint commands (its `CLAUDE.md` usually names them; generic fallbacks per ecosystem in [context/reference.md](context/reference.md) "Ecosystem verification commands (Phase 7)").

In the diff-scoped modes, time window and branch, simplification is behavior-preserving and this objective cross-ecosystem pass is verification enough: a fresh-context verifier is the rule only where a verdict is subjective, not where the check is a mechanical pass/fail. That exemption is scoped to those modes and does not carry into repo mode, where no human reads the diff before it merges; there a per-group refutation verifier is mandatory ([context/repo-mode.md](context/repo-mode.md)). A change that passes only because it altered behavior is a regression this final verification exists to catch (Gotchas). Files with no mapped test suite are common in any repository: report them as unmapped rather than as passing. In repo mode they fall through to the per-group refutation verifier plus this end-of-run pass, which are then the only checks behind them.

Report the final verification results as a summary table.

### Phase 8: Summary report

Present a final report. Scope + files-scanned + a per-group results table (`# | Group | Files | Changes | Deferred | Verification`) + final cross-ecosystem verdict + resolved-in-run and remaining-deferrals sections. Full template in [context/reference.md](context/reference.md) "Summary report template (Phase 8)".

When any HARD path was lifted, add a `## Lifted HARD exclusions` section naming each path and the channel that lifted it (`override` flag, overrides file, or `hard_exclusions=advisory`). Omit the section when nothing was lifted; never print it empty.

If zero items were deferred across all groups, state explicitly: *"No items deferred. All identified simplifications were applied or determined to be no-ops."*

## Edge cases

- **No changes in time window**: report and exit cleanly
- **Single file changed**: still run the simplifier (skip grouping, just one group)
- **Agent reports no changes needed**: mark as "reviewed, no changes". Valid outcome. Still inspect the agent's output for a `## Deferred` section; zero applied changes does not mean zero deferrals
- **Agent reports deferrals**: non-optional path. Run Phase 6.5 and resolve them in-run; only items with a surviving ground (Needs-human, Too-large, or a deferral the resolution wave could not finish) reach the report. Never paraphrase or collapse deferred items without presenting them to the user
- **Verification fails after simplification**: report the failure prominently, the agent should have caught this, but the final verification is the safety net
- **Very large groups (>25 files)**: split into sub-groups by subdirectory to keep the simplifier focused
- **Files that span multiple ecosystems**: group by the primary ecosystem (e.g., a `.sh` hook that invokes `ruff` goes in the shell group, not Python)
- **A consumer hook blocks subagent edits**: some projects gate edits behind precondition hooks (e.g., explore/research-first gates). Comply. Satisfy the hook's precondition in the main session, then re-spawn the blocked groups. Do NOT bypass the hook via Bash workarounds and do NOT negotiate with it
- **Commits appear mid-run that this skill didn't make**: parallel sessions or auto-snapshot mechanisms may commit concurrently. `git log --oneline -5` reveals what was captured; treat those commits as authoritative for the changed state and proceed

