A Claude Code plugin for structure-only codebase improvement, applying Kent Beck's Tidy First? discipline agentically: small named tidyings, separated from behavioral changes by commit and by PR, under a research-backed scope budget (≤200 LOC / ≤8 files target; ≤400 / ≤15 hard cap).
Six skills, one capability:
/code-tidying:dissolve-comments. Enforces self-describing, expressive code over a diff or target (a clean tree widens to the branch diff, then to the whole repository with confirmation, ranked by exposure and comment payload): deletes zero-information comments, dissolves code-expressible comments into names and structure via behavior-preserving refactoring (then deletes them), and keeps only terse, load-bearing comments code cannot express, held to a line budget. Deletions and function-local renames apply behind a token-level proof (change-shape.py, so they act on a repository with no test suite); additive refactors need a discovered test net; interface-creating ones are proposal-first.safemode restricts applied edits to removals;aggressivekeeps only exempt surfaces, paired records, and terse warnings of consequence, staging every other comment's narrative before deleting it;stripdeletes every comment but the exempt surfaces and rewrites no code.--notes <path>writes the staged block to an untracked file as well. No dial loosens a gate. Ships a comment census with a token estimate and a cross-language commented-out-code detector, and probes its reading layers (scc,pygments,tree-sitter,pwsh,ruff,ast-grep) at run time, naming what each absent one costs./code-tidying:audit-comment-residue. Read-only classifier for out-of-context comment residue (history narration, weak history cues, plan/session references, conversational antecedents, ticket/PR back-references); flags Tier 1/Tier 2 findings for author-applied deletion, edits nothing./code-tidying:tidy. Proactively hunts a rotated, glob-scoped lane of the codebase for safe structural improvements (Beck's 15 tidyings + a Fowler subset + prose tidyings), applies scope-budgeted edits, and ships one tight structure-only PR. Scope is a rotated lane or an ad hoc glob (tidy [dry-run] <glob>...).in-placeskips the branch and PR and leaves the edits staged;in-place=commitmakes one commit. Overflow is filed as deferred work items, never silently dropped./code-tidying:batch-simplify. Sweeps files through grouped, dependency-ordered simplification waves in one of three scope modes: a time window (48hdefault,7d, ...), the current branch, orrepo. Per-group verification and a fix-first deferral contract throughout: deferrals are resolved in the same run rather than filed as issues, and only items needing a human decision or a genuinely huge refactor survive to the report. The whole-repository mode is explicit-entry only, gates on a confirmed inventory, runs a mandatory per-group refutation verifier, and delivers one feature branch and one PR for the whole run, with per-group commits. Use it when you forgot to run/simplifyafter each task, or to sweep a repository that never had one.in-place(repo mode) runs on the current branch with no new branch or PR and leaves the edits staged, orin-place=commitmakes one commit./code-tidying:audit-dead-code, a read-only, whole-repo hunt for code nothing reaches any more, across four labeled lanes of deliberately unequal confidence (knip for TS/JS, vulture for Python, gopls for Go's unexported symbols, and a portable grep lane for shell and PowerShell symbols, JS/TS symbols outside apackage.jsonroot, plus unreferenced source files with a recognized extension). Every candidate is adjudicated against the dynamic-usage evidence static analyzers are blind to and lands asdead,uncertain, oralive. Reports in-session; writes nothing and deletes nothing./code-tidying:setup.checkinspects the tracked.claude/tidy-lanes/<lane>.mdproject lanes read-only (presence, required sections, leftover placeholders, tracked-not-ignored), validates the optional.claude/code-tidying/exclusion-overrides.md, and reports the storedhard_exclusionsposture;applyinterviews the repo and scaffolds those lane files from the bundled templates, sotidyresolves project-specific scope globs deterministically instead of falling back to the generic bundled lanes. Re-runnable to add or retune lanes.
Neither tidy nor batch-simplify is /simplify itself: /simplify refines the diff you just
wrote; batch-simplify catches up on a window of them; tidy hunts drift no
one has filed yet.
tidy operates on lanes. Bundled lanes cover surfaces that look the same in
most repos (shell-tooling, docs-prose); your project defines its own lanes
by dropping files into .claude/tidy-lanes/<lane>.md, which take
precedence over bundled lanes of the same name. Copy the closest scaffold from
the plugin's skills/tidy/templates/ (dependency-root, host-wiring, apps,
polyglot-services patterns) and fill in your scope globs, watch-for patterns,
exclusions, and verification commands. This surface does not resolve user-global
or *.local.* overlay layers. The bundled lane is the portable baseline, and
personal variation is limited to lane names the team does not track: an uncommitted
.claude/tidy-lanes/<lane>.md never added to the index (see setup and the
config-cascade contract).
- Structure-only, always. A "tidying" that breaks a test was secretly behavioral. It gets backed out, not shipped.
- Hard/soft exclusions gate every run: agent and CI configuration, hook
chains, and lint configs are not touched by default; unverifiable areas
(browser UI, auth flows, DB migrations) are deferred, not edited. Your
project's own
CLAUDE.md/ rules extend both lists. - The hard path list is overridable, through three channels that only ever
subtract from it: an
overrideargument ontidy,dissolve-comments, andbatch-simplify(this run), thehard_exclusionsoption below (this operator), and a tracked.claude/code-tidying/exclusion-overrides.mdof root-relative globs (this repository), resolved per path in that order. So/code-tidying:dissolve-comments override ruff.tomltriages a file the list would otherwise drop. Every lifted path is named in the run's report beside the channel that lifted it. What no channel lifts: the behavioral guards (structure-only is the skills' contract, not a path limit), the work-tracking exclusions, and the self-update lane's protection of this plugin's own contract surface. - Backlog throttle: ≥3 open
chore/tidy-*PRs stops the run instead of piling on (bundledopen-pr-count.sh, network access via your ownghauth). - No auto-merge. Tidy PRs are always merged by a human.
- Self-contained: taxonomy, scope-budget research, exclusion lists, lane
templates, and the throttle script all ship inside the plugin under
${CLAUDE_PLUGIN_ROOT}. The bundled scripts require Bash 4.3+ (they usemapfile, case-conversion expansions, and namerefs). On native Windows, install Git for Windows so they run under Git Bash; the scripts already handle CRLF and drive-letter paths. - Graceful degrade: if the
discoveryplugin is installed, explore/research phases use/discovery:explore+/discovery:research; ifwork-itemsis installed, deferrals file through/work-items:track add; ifcode-simplifier(or legacypr-review-toolkit) is installed, batch-simplify uses itscode-simplifieragent. Absent any of them, the skills fall back to inline exploration/research,gh issue create, and general-purpose agents. - Reads your conventions, assumes none: canonical build/test/lint commands, protected paths, and unverifiable areas come from your own project context.
/plugin marketplace add melodic-software/claude-code-plugins
/plugin install code-tidying@melodic-softwareFour userConfig options. Three tune dissolve-comments, and none of them
loosens a gate: they set what a run removes, never what it may apply without a
proof. The fourth is the personal-posture channel of the exclusion override
above, and loosening is its whole job:
| Option | Default | Effect |
|---|---|---|
hard_exclusions |
enforce |
enforce keeps every GLOBAL HARD path entry blocking; advisory reports each match and blocks nothing, so runs may reach lint config, agent config, CI workflows, and hook chains. Path entries only: the behavioral guards, the work-tracking entries, and the self-update protections hold at either value. |
comment_posture |
strict |
strict rewrites an over-budget kept comment terser and stages the removed narrative; balanced reports it instead; conservative applies class-A deletions only and proposes everything else; aggressive keeps only exempt surfaces, paired records, and terse warnings. The per-run tokens safe, strip, and aggressive beat this value, safe first. |
class_c_max_lines |
2 |
Line budget for a kept (class-C) comment before it is rewritten. |
apply_local_renames |
true |
Apply a function-local rename that change-shape.py certifies as RENAME-ONLY even with no test net; false proposes it. |
Everything else routes through
.claude/tidy-lanes/ lane files, the optional
.claude/code-tidying/exclusion-overrides.md, and your project's own CLAUDE.md /
.claude/rules (protected paths, verification commands). Run
/code-tidying:setup apply to interview your repo and scaffold those lane files
from the bundled templates (or check to inspect existing lanes, validate the
overrides file, and report the stored hard_exclusions posture read-only). It is
idempotent and safe to re-run to add or retune lanes.
Generated from this plugin's .claude-plugin/plugin.json. Every option Claude Code
will prompt for when the plugin is enabled, with the environment variable each hook
reads it from.
| Option | Type | Default | Environment variable | Description |
|---|---|---|---|---|
hard_exclusions |
string | "enforce" |
CLAUDE_PLUGIN_OPTION_HARD_EXCLUSIONS |
How tidy, dissolve-comments, and batch-simplify treat the GLOBAL HARD path list in skills/tidy/reference/exclusions.md. enforce (default): a path on that list is dropped before triage; advisory: the list is reported per path and never blocks, so a run may edit lint config, agent config, CI workflows, and hook chains. advisory is the standing form of the per-run override argument and is lifted for path entries only: the behavioral guards, the work-tracking entries, and the SELF-UPDATE EXTRA HARD list hold under every value. Any other value is read as enforce. |
comment_posture |
string | "strict" |
CLAUDE_PLUGIN_OPTION_COMMENT_POSTURE |
How dissolve-comments treats a kept comment. strict (default): every kept comment is held to class_c_max_lines and rewritten terser when over it, with the removed narrative staged for the commit message; balanced: the same triage, but an over-budget comment is reported instead of rewritten; conservative: class-A deletions only, every class-B item and class-C rewrite is proposed; aggressive: only exempt surfaces, paired records, and terse warnings of consequence survive, and every other comment is staged and deleted. The per-run tokens safe, strip, and aggressive beat this value, safe first. No posture loosens a gate: every applied deletion still carries the token proof and every tier-2 or tier-3 move still needs a test net. Any other value is read as strict. |
class_c_max_lines |
number min 1, max 40 |
2 |
CLAUDE_PLUGIN_OPTION_CLASS_C_MAX_LINES |
Lines a kept (class-C) comment may run before dissolve-comments rewrites it terser, staging any removed narrative for the commit message. A genuinely load-bearing multi-line contract may exceed it when the report says why. |
apply_local_renames |
boolean | true |
CLAUDE_PLUGIN_OPTION_APPLY_LOCAL_RENAMES |
When true (default), a function-local Rename Variable whose edit change-shape.py certifies as RENAME-ONLY is applied and reported with its identifier mapping even when no test net is discovered. When false, such renames are proposed. |
Three supported routes, in the order most people want them:
-
Interactively. Claude Code prompts for declared options when you enable the plugin. To change them later:
/plugin configure code-tidying@<marketplace>. -
Headless. Repeat
--configfor each option. Replace<marketplace>with the marketplace you installed this plugin from:claude plugin install code-tidying@<marketplace> -s <scope> --config hard_exclusions=<value>
The same command reconfigures a plugin that is already installed: it prints
already installedand still writes the value. The short-circuit message is about the install, not the config write. Do notclaude plugin uninstallto reconfigure: uninstalling drops this plugin's whole storedpluginConfigsentry, resetting every option in the table above to its default.-sdefaults touser, so pass the scopeclaude plugin listreports for this plugin. The verified-version record lives in the plugin-reconfiguration convention.The value is stored immediately; the session you are in does not change. Hooks are handed their
CLAUDE_PLUGIN_OPTION_*when the session starts, so start a fresh Claude Code session before expecting new behavior. A check run in the old session still reports the old value, and that is not a failed write. -
By hand, in settings. Add the value under
pluginConfigsin your user settings (~/.claude/settings.json):{ "pluginConfigs": { "code-tidying@<marketplace>": { "options": { "hard_exclusions": <value> } } } }Plugin option values are read from user,
--settings, and managed settings only, not from a project's.claude/settings.json. To vary behavior per repository, enable or disable the plugin in that project'senabledPluginsinstead of setting an option there.
Do not set the CLAUDE_PLUGIN_OPTION_* variables yourself. They are how Claude Code
hands a configured value to a hook process; the value comes from the routes above.
- User configuration: the
userConfigschema and theCLAUDE_PLUGIN_OPTION_<KEY>export - Plugin install options: the
--configflag's reference entry - Plugins and skills settings:
enabledPlugins,extraKnownMarketplaces,pluginConfigs - Settings files and who they affect: user vs project vs local precedence
- Manage installed plugins: enabling, disabling,
/plugin list
MIT (SPDX-License-Identifier: MIT).