codeburn optimize --apply: execute config-class fixes with undo
Part of the acting-layer epic. Depends on the action journal issue; do not start before it lands.
What to build
codeburn optimize --apply turns the config-class findings that optimize already prints into applied, journaled, undoable changes. Behavioral findings (session openers, prompts) are explicitly not appliable here; they belong to the guard issue.
Step 1: stable finding ids
WasteFinding (src/optimize.ts) has no id today. Add id: FindingId to the type and to every detector, and include it in the JSON report shape (OptimizeJsonReport.findings[]). Ids are kebab-case and stable across runs:
read-edit-ratio, mcp-low-coverage, mcp-project-scope, low-worth-sessions,
context-heavy-sessions, cost-outliers, redundant-rereads, unused-skills,
unused-agents, unused-commands, build-folder-reads, bash-output-cap,
claude-md-too-long, warmup-heavy, retry-heavy-capabilities
(Match the actual detector list in the file; the names above cover the ones visible in current output, extend if the code has more.)
Step 2: appliable plans
Add src/act/plans.ts with planFor(finding): ActionPlan | null. A plan describes concrete file mutations for the journal framework (see src/act/types.ts). Appliable finding ids and their exact semantics:
mcp-low-coverage and mcp-project-scope (kind: mcp-remove / mcp-project-scope)
- Locate each server by reading, in order: project
.mcp.json (mcpServers key), project .claude/settings.json and .claude/settings.local.json (mcpServers), user ~/.claude.json (top-level mcpServers and per-project projects[<path>].mcpServers). Handle only servers found in these files; if a server comes from somewhere else (plugin, managed config), mark it not-appliable with a note in the plan preview.
- Remove: delete the server entry from the file(s) it appears in.
- Project-scope: remove the entry from the global location and add it verbatim to
<project>/.mcp.json for each project the finding lists as a keeper (create the file with { "mcpServers": {} } scaffold if missing).
- JSON edits must preserve the rest of the document; parse, mutate,
JSON.stringify(obj, null, 2) plus trailing newline. If a target file fails to parse, skip that server and surface the parse error in the summary rather than aborting the whole apply.
unused-skills, unused-agents, unused-commands (kind: archive-*)
- Move each directory or file to a sibling
.archived/ directory (~/.claude/skills/.archived/<name> etc., matching the paths the detector reports). Create .archived/ on demand. Name collision in the archive: suffix -2, -3.
claude-md-rule destinations (kind: claude-md-rule)
- For findings whose fix is
{ type: 'paste', destination: 'claude-md' }: append to the project CLAUDE.md (or ~/.claude/CLAUDE.md when the finding is global) wrapped in markers:
<!-- codeburn:begin <finding-id> -->
<rule text>
<!-- codeburn:end <finding-id> -->
- Idempotent: if a block with the same finding-id already exists, replace its contents instead of appending a duplicate. Undo removes the whole block (the journal backup already guarantees byte-exact restore; the marker discipline is for humans and idempotency).
bash-output-cap (kind: shell-config)
- Append the suggested export to the user's shell rc (detect from
$SHELL: .zshrc or .bashrc) inside the same marker scheme using # comments.
Everything else returns null (not appliable) and is shown greyed out with "manual" in the picker.
Step 3: UX
codeburn optimize --apply # plan + interactive confirm
codeburn optimize --apply --yes # apply all appliable without prompting
codeburn optimize --apply --dry-run # print the plan and exit 0, change nothing
codeburn optimize --apply --only mcp-low-coverage,unused-skills
Flow: run the normal optimize scan, list appliable findings numbered with estimated savings and the exact files each plan touches, prompt Apply all / pick numbers / quit (plain readline; do not pull in a new TUI dependency for this). After applying, print a summary with each action's journal id and the line Undo anytime: codeburn act undo <id>.
--apply combined with --json is an error (exit 2) to keep the JSON contract stable.
Tests (tests/optimize-apply.test.ts)
Fixture home dir with crafted ~/.claude.json, project .mcp.json, skills tree, CLAUDE.md:
- mcp remove deletes exactly the named server from the right file, other keys untouched, undo restores byte-identical
- project-scope moves the entry global -> project
.mcp.json, creates the file when missing
- server defined in an unparseable file is reported and skipped, remaining plans still apply
- archive moves and undo round-trip, collision gets
-2 suffix
- CLAUDE.md marker block: fresh append, second apply replaces in place (no duplicate), undo removes the block
--dry-run leaves the fixture tree byte-identical (hash the tree before/after)
- every finding in a full optimize run has a stable non-empty id (regression guard for step 1)
Acceptance
- On a machine with real findings,
codeburn optimize --apply --dry-run prints a correct plan and --apply + codeburn act undo --last round-trips cleanly.
optimize --json now carries finding ids; no other JSON shape changes.
- Zero new dependencies, all existing tests green.
Part of epic #602.
codeburn optimize --apply: execute config-class fixes with undoPart of the acting-layer epic. Depends on the action journal issue; do not start before it lands.
What to build
codeburn optimize --applyturns the config-class findings that optimize already prints into applied, journaled, undoable changes. Behavioral findings (session openers, prompts) are explicitly not appliable here; they belong to the guard issue.Step 1: stable finding ids
WasteFinding(src/optimize.ts) has no id today. Addid: FindingIdto the type and to every detector, and include it in the JSON report shape (OptimizeJsonReport.findings[]). Ids are kebab-case and stable across runs:(Match the actual detector list in the file; the names above cover the ones visible in current output, extend if the code has more.)
Step 2: appliable plans
Add
src/act/plans.tswithplanFor(finding): ActionPlan | null. A plan describes concrete file mutations for the journal framework (seesrc/act/types.ts). Appliable finding ids and their exact semantics:mcp-low-coverageandmcp-project-scope(kind: mcp-remove / mcp-project-scope).mcp.json(mcpServerskey), project.claude/settings.jsonand.claude/settings.local.json(mcpServers), user~/.claude.json(top-levelmcpServersand per-projectprojects[<path>].mcpServers). Handle only servers found in these files; if a server comes from somewhere else (plugin, managed config), mark it not-appliable with a note in the plan preview.<project>/.mcp.jsonfor each project the finding lists as a keeper (create the file with{ "mcpServers": {} }scaffold if missing).JSON.stringify(obj, null, 2)plus trailing newline. If a target file fails to parse, skip that server and surface the parse error in the summary rather than aborting the whole apply.unused-skills,unused-agents,unused-commands(kind: archive-*).archived/directory (~/.claude/skills/.archived/<name>etc., matching the paths the detector reports). Create.archived/on demand. Name collision in the archive: suffix-2,-3.claude-md-ruledestinations (kind: claude-md-rule){ type: 'paste', destination: 'claude-md' }: append to the project CLAUDE.md (or~/.claude/CLAUDE.mdwhen the finding is global) wrapped in markers:bash-output-cap(kind: shell-config)$SHELL:.zshrcor.bashrc) inside the same marker scheme using#comments.Everything else returns null (not appliable) and is shown greyed out with "manual" in the picker.
Step 3: UX
Flow: run the normal optimize scan, list appliable findings numbered with estimated savings and the exact files each plan touches, prompt
Apply all / pick numbers / quit(plain readline; do not pull in a new TUI dependency for this). After applying, print a summary with each action's journal id and the lineUndo anytime: codeburn act undo <id>.--applycombined with--jsonis an error (exit 2) to keep the JSON contract stable.Tests (
tests/optimize-apply.test.ts)Fixture home dir with crafted
~/.claude.json, project.mcp.json, skills tree, CLAUDE.md:.mcp.json, creates the file when missing-2suffix--dry-runleaves the fixture tree byte-identical (hash the tree before/after)Acceptance
codeburn optimize --apply --dry-runprints a correct plan and--apply+codeburn act undo --lastround-trips cleanly.optimize --jsonnow carries finding ids; no other JSON shape changes.Part of epic #602.