/ccc-tuneup — Local Setup Optimizer
The companion to /ccc-doctor. Doctor diagnoses (read-only). Tune-up remediates — but safely: read-only scan first, backup before any edit, archive (never rm) for junk, and an explicit click-to-confirm before anything mutates.
CC Commander · Tune-Up · Docs
Arguments
--check— read-only audit + scorecard. NO prompt, NO mutation. (Default if the user seems unsure.)--fix— audit, then offer safe fixes via AskUserQuestion chips (destructive-leaning items OFF by default).--aggressive— same as--fixbut pre-selects session archival + superseded-agent retirement chips.- bare
/ccc-tuneup— runs the audit, shows the scorecard, then opens the chip picker.
Safety rules (NON-NEGOTIABLE)
- ❌ NEVER
rmortrashanything — junk moves to~/.claude/commander/tuneup-archive/<UTC-stamp>/. - ✅ Before editing
settings.jsonor anyCLAUDE.md:cp <file> <file>.backup-$(date +%Y%m%d-%H%M%S). - ❌ NEVER edit
~/.claude/CLAUDE.mdor any file under~/.claude/**— the user's global config may contain plugin-count strings that are NOT ours to rewrite. - ❌ NEVER edit a CLAUDE.md unless the
git rev-parse --show-toplevelof the target file resolves to a cc-commander checkout (verified by:basename $(git -C <dir> rev-parse --show-toplevel)= "cc-commander" ANDtest -f <root>/scripts/audit-counts.js). For end-user installs (no git root, or clone under~/.claude/plugins), the CLAUDE.md-count row is DETECT-ONLY — show the drift, do NOT offer to fix it. - ❌ NEVER run
git pull,openclaw/Desktop restarts, or marketplace re-adds — emit them as copy-paste terminal commands. - ❌ NEVER mutate the plugin clone's own source files unless running from a verified cc-commander git root.
- ✅ Every count is computed LIVE from the filesystem and manifests — NEVER hardcode "61 skills" / "23 events" / a version.
- ✅ Take ALL backups first; apply; on ANY failure restore ALL backups taken this run (ordered rollback).
- ✅ Re-read each file immediately before each Edit and stat-compare to scan; if changed since scan, skip the fix and report "file changed since scan — skipped".
- ✅ Keep only the last 3
*.backup-<ts>per file; offer to archive older ones into tuneup-archive.
Step 1 — Read-only audit (one Bash call)
1a. Resolve the clone root
The marketplace clone root is ~/.claude/plugins/marketplaces/commander-hub
(that dir has .git directly in it — the repo's OWN top-level layout then
nests the plugin source one level further at commander/cowork-plugin/; do
not add an extra /commander segment to the clone root itself, and do not
substitute installed_plugins.json's installPath here — that field points
into plugins/cache/<mp>/<plugin>/<version>/, a differently-shaped,
already-resolved copy with no repo wrapper).
CLONE="$HOME/.claude/plugins/marketplaces/commander-hub"
if [ ! -d "$CLONE" ]; then
# Authoritative fallback: known_marketplaces.json's installLocation.
CLONE=$(node -e "
try {
const d=JSON.parse(require('fs').readFileSync(process.env.HOME+'/.claude/plugins/known_marketplaces.json','utf8'));
const m=d['commander-hub'];
if(m&&m.installLocation) process.stdout.write(m.installLocation);
} catch(e) {}
" 2>/dev/null || echo "")
fi
[ -z "$CLONE" ] && CLONE="n/a"
[ "$CLONE" != "n/a" ] && [ ! -d "$CLONE" ] && CLONE="n/a"
echo "CLONE=$CLONE"
1b. Run the side-effect-free probe script
[ "$CLONE" != "n/a" ] && bash "$CLONE/commander/cowork-plugin/skills/ccc-tuneup/lib/tuneup-probes.sh" "$CLONE" || echo "PROBE=n/a"
The probe emits KEY=VALUE lines (all side-effect-free, every field 2>/dev/null || echo n/a):
| Key | What it measures |
|---|---|
JUNK_TEMP |
temp_* dirs in ~/.claude/plugins/marketplaces/ |
JUNK_BAK |
*.bak / *.old / *.backup-* files older than 14d (total count) |
ARCHIVE_DUPES |
hook files in both hooks/_archive/ AND hooks/ (exact name intersection) |
STALE_SESSIONS |
~/.claude/sessions/ files older than 30d (count, for --aggressive only) |
LOCAL_AGENT_OVERLAP |
~/.claude/agents/*.md basenames that exactly match a plugin agent |
SETTINGS_MISSING_KEYS |
settings.json keys absent from global ~/.claude/settings.json |
PLUGIN_ENABLED |
whether commander is in enabledPlugins (yes/no/missing) |
PLUGIN_DISABLED_INSTALLED |
commander installed-but-disabled flag |
MCP_CONNECT_STALE |
opt-in MCP server entries in settings.json whose configured path/command is missing |
COST_* |
per-key token-spend audit rows: current|suggested|verdict (verdict ∈ ok/missing/suboptimal) — see 1g |
COST_FLAGGED |
count of non-ok cost-settings keys (rollup) |
SKILL_COUNT |
live skill count from `ls skills/ |
AGENT_COUNT |
live agent count from `ls agents/*.md |
EVENT_COUNT |
live hook event count from `jq '.hooks |
HANDLER_COUNT |
live handler count from `jq '[.hooks[][].hooks // [] |
VENDOR_COUNT |
live vendor count from `ls vendor/ |
INSTALLED_VERSION |
version from installed_plugins.json or plugin.json |
CACHED_REMOTE_VERSION |
from ~/.claude/commander/update-cache.json if < 4h old |
1c. Consume ccc-doctor diagnostics (shared full-stack checks)
[ "$CLONE" != "n/a" ] && node -e "
const d=require('$CLONE/commander/cowork-plugin/skills/ccc-doctor/lib/diagnostics');
for(const r of d.runDiagnostics('$CLONE')){
console.log(r.status+'|'+r.category+'|'+r.message+'|'+(r.remediation||''));
}
" 2>/dev/null || echo "diagnostics n/a"
Use this output for read-only source-tree and settings diagnostics. Keep the probe output as the source of truth for tuneup-specific remediation planning, but surface every diagnostics.js row in the scorecard so /ccc-tuneup --check covers the same full stack as /ccc-doctor full.
1d. Semver freshness check (no python3, no require from update-check.js)
INSTALLED_V="$(node -e "
try{
const j=JSON.parse(require('fs').readFileSync(process.env.HOME+'/.claude/plugins/installed_plugins.json','utf8'));
const k=Object.keys(j.plugins||{}).find(k=>k.startsWith('commander'));
if(k&&j.plugins[k][0])process.stdout.write(j.plugins[k][0].version||'n/a');
else process.stdout.write('n/a');
}catch(e){process.stdout.write('n/a');}
" 2>/dev/null || echo n/a)"
CACHE="$HOME/.claude/commander/update-cache.json"
CACHED_REMOTE="$(node -e "
try{
const d=JSON.parse(require('fs').readFileSync('$CACHE','utf8'));
const age=Date.now()-d.timestamp;
if(age<14400000) process.stdout.write(d.remoteVersion||'stale');
else process.stdout.write('stale');
}catch(e){process.stdout.write('stale');}
" 2>/dev/null || echo stale)"
# If cache stale, try the update-check runner (self-fetches, silent on failure)
if [ "$CACHED_REMOTE" = "stale" ] && [ "$CLONE" != "n/a" ]; then
CACHED_REMOTE=$(node "$CLONE/commander/update-check.js" --remote-only 2>/dev/null || echo n/a)
fi
# Inline 3-field semver compare (no semverCompare import — update-check.js has no module.exports)
semver_gt() {
local a="$1" b="$2"
IFS='.' read -r a1 a2 a3 <<< "${a//[^0-9.]}"
IFS='.' read -r b1 b2 b3 <<< "${b//[^0-9.]}"
[ "${a1:-0}" -gt "${b1:-0}" ] && return 0
[ "${a1:-0}" -lt "${b1:-0}" ] && return 1
[ "${a2:-0}" -gt "${b2:-0}" ] && return 0
[ "${a2:-0}" -lt "${b2:-0}" ] && return 1
[ "${a3:-0}" -gt "${b3:-0}" ] && return 0
return 1
}
VERSION_STATUS="up-to-date"
if semver_gt "$CACHED_REMOTE" "$INSTALLED_V" 2>/dev/null; then
VERSION_STATUS="update available: $INSTALLED_V → $CACHED_REMOTE"
fi
echo "VERSION_STATUS=$VERSION_STATUS"
1e. CLAUDE.md drift check (maintainer-mode only)
Only run if the following are BOTH true:
git -C "$CLONE" rev-parse --show-toplevel 2>/dev/nullexits 0basename $(git -C "$CLONE" rev-parse --show-toplevel 2>/dev/null)="cc-commander"ANDtest -f "$(git -C "$CLONE" rev-parse --show-toplevel 2>/dev/null)/scripts/audit-counts.js"
If either condition fails: mark CLAUDE_MD_MODE=detect-only — show drift but DO NOT offer the fix edit.
GIT_ROOT=$(git -C "$CLONE" rev-parse --show-toplevel 2>/dev/null || echo "")
CLAUDE_MD_MODE="detect-only"
if [ -n "$GIT_ROOT" ] && [ "$(basename "$GIT_ROOT")" = "cc-commander" ] && [ -f "$GIT_ROOT/scripts/audit-counts.js" ]; then
CLAUDE_MD_MODE="maintainer"
fi
echo "CLAUDE_MD_MODE=$CLAUDE_MD_MODE"
Then read the SKILL_COUNT/AGENT_COUNT/EVENT_COUNT/HANDLER_COUNT from probe output and compare to what the local repo's CLAUDE.md claims using grep -oP '\d+(?= plugin skills)' etc. Report drift as CLAUDE_MD_DRIFT=<description> or CLAUDE_MD_DRIFT=none.
1f. Normalize model strings before comparison
When comparing agent model strings from frontmatter to plugin.json canonical values, strip any [1m] / [128k] / [...] context-suffix and surrounding whitespace before comparing:
normalize_model() { echo "$1" | sed 's/ *\[.*\]$//' | xargs; }
1g. Cost-settings audit (READ-ONLY — surface, suggest, NEVER auto-apply)
The SECTION:COST probe rows surface high-impact, often-undocumented settings.json keys that reduce token spend. This is detect-and-suggest only — unlike the ⚙️ Settings keys row (which promotes 3 structural keys on confirm), the cost row NEVER mutates settings.json. Each COST_<KEY> line is current|suggested|verdict:
| Key | Why it cuts spend | Suggested |
|---|---|---|
effortLevel |
Lower effort = fewer thinking tokens on routine turns; xhigh ≈ 2× spend |
high |
autoCompactEnabled |
Caps runaway context growth → fewer overflow re-reads | true |
env.MAX_THINKING_TOKENS |
Bounds per-turn reasoning budget (uncapped ≈ 32k/turn) | 10000 |
env.ENABLE_TOOL_SEARCH |
Defers MCP tool schemas — large saver with many connected servers | 1 |
showThinkingSummaries |
Summaries cost fewer output tokens than full thinking | true |
cleanupPeriodDays |
Bounds transcript retention → smaller resume/search surface | 30 |
model |
A premium pin (Opus/Fable) on the MAIN thread spends $$$ on routine turns | route routine work to Sonnet/Haiku subagents |
Verdict semantics: ok = satisfies intent (do not surface); missing = key unset; suboptimal = set to a higher-spend value. model is detect-only — surface the cost note when a premium pin exists but NEVER suggest overriding the user's deliberate model choice; just inform.
∅ in a current field means the key is unset. If COST_FLAGGED=n/a, the probe could not read settings — render the row ⚪ and move on (never crash).
Step 2 — Scorecard (one markdown table)
### 🔧 Tune-Up Report — <UTC datetime>
| Check | Status | Top finding | Action |
|-------|--------|-------------|--------|
| 📦 Version freshness | 🟢/🟡 | installed <X> vs latest <Y> | `git pull` |
| 🧹 Junk sweep | 🟠 | N temp_* + M stale .bak + K archive dupes | Archive |
| 📝 CLAUDE.md count drift | 🟠/⚪ | claims "38 handlers", actual 28 | Fix counts (maintainer) / Detect-only |
| 🤖 Local agent supersession | 🟡 | architect.md → plugin version supersedes | Retire local |
| 🪝 Hooks coverage | 🟢 | 23/23 events, all handlers resolve | — |
| 🔌 Plugin enabled | 🟢/🔴 | commander in enabledPlugins | Enable in Settings |
| 🌐 Opt-in MCP staleness | 🟡 | context7 path missing | Re-run /ccc-connect |
| ⚙️ Settings keys | 🟡 | showThinkingSummaries absent | Promote 3 keys |
| 💰 Cost settings | 🟡 | N spend-reducing keys missing/suboptimal (effortLevel=xhigh, …) | Suggest only (copy-paste) |
| 🗂️ Stale sessions (opt-in) | ⚪ | N files >30d | Archive (--aggressive only) |
**🎯 My call: fix <lowest-score row> first — <one-line rationale>.**
Score rubric: 🟢 90-100 · 🟡 70-89 · 🟠 50-69 · 🔴 0-49 · ⚪ n/a (probe failed or check skipped — never crash).
On --check: STOP HERE. Print the table and nothing else.
Cost-settings detail block (printed under the scorecard on EVERY mode)
If COST_FLAGGED > 0, print a short read-only detail block listing each non-ok COST_<KEY> as key: current → suggested (reason), followed by ONE copy-paste snippet the user can paste into ~/.claude/settings.json themselves. Example:
### 💰 Cost settings — suggestions (NOT applied)
- effortLevel: xhigh → high (xhigh burns ~2× on routine work)
- env.MAX_THINKING_TOKENS: unset → 10000 (caps per-turn reasoning spend)
- env.ENABLE_TOOL_SEARCH: unset → 1 (defer MCP tool schemas — saves heavily with many servers)
# To apply, paste into ~/.claude/settings.json (review first — these are YOUR cost trade-offs):
"effortLevel": "high",
"showThinkingSummaries": true,
"env": { "MAX_THINKING_TOKENS": "10000", "ENABLE_TOOL_SEARCH": "1" }
These are always suggest-only — the cost-settings audit NEVER appears as a mutating AskUserQuestion chip and /ccc-tuneup --fix/--aggressive never edits these keys. For model, only inform ("you've pinned on the main thread — routine turns are pricey; consider routing them to Sonnet/Haiku subagents") — never propose overwriting it.
Step 3 — Confirm fixes (AskUserQuestion)
Only list rows that are 🟡/🟠/🔴 AND safely fixable. Call AskUserQuestion:
question: "Which fixes should I apply?"
header: "Tune-Up"
multiSelect: true
options:
- label: "⭐ 🌐 All safe fixes"
description: "Junk sweep + settings keys (the two always-safe items). No CLAUDE.md edits."
preview: "Recommended starting point. Fully reversible."
- label: "🧹 Sweep junk → archive"
description: "Move temp_* dirs, stale *.bak/*.old, archive-hook dupes to a timestamped archive dir."
preview: "Reversible — nothing deleted. Restore from ~/.claude/commander/tuneup-archive/<stamp>/."
- label: "⚙️ Add settings keys"
description: "Promote showThinkingSummaries + agent-teams + tool-concurrency to the right layer."
preview: "Backs up settings.json first. Adds only missing keys, never overwrites yours."
- label: "📝 Fix CLAUDE.md counts (maintainer only)"
description: "Back up then correct stale count claims to match live filesystem. Only offered when running from verified cc-commander git root."
preview: "Backs up every CLAUDE.md first. One Edit per drifted claim. Verifies after each fix."
- label: "🤖 Retire superseded local agents"
description: "Archive ~/.claude/agents/*.md that the plugin persona strictly supersedes."
preview: "OFF by default — moved to archive, not deleted. Plugin version takes over."
- label: "🗂️ Archive stale sessions (>30d)"
description: "Move old ~/.claude/sessions/ files to tuneup-archive."
preview: "OFF by default. --aggressive pre-selects this. Frees clutter; transcripts preserved."
- label: "❌ Cancel"
description: "Make no changes."
preview: "Re-run anytime with /ccc-tuneup."
With --aggressive, also pre-select the two OFF-by-default items. The "Fix CLAUDE.md counts" option is only shown when CLAUDE_MD_MODE=maintainer AND drift was detected.
Step 4 — Apply (backup → archive → fix — TRANSACTIONAL)
Backup phase (ALL backups before ANY edit)
For EVERY file that will be mutated this run, take the backup first:
TS=$(date +%Y%m%d-%H%M%S)
cp ~/.claude/settings.json ~/.claude/settings.json.backup-$TS 2>/dev/null
# For each CLAUDE.md that will be edited (maintainer mode only):
cp "$GIT_ROOT/CLAUDE.md" "$GIT_ROOT/CLAUDE.md.backup-$TS" 2>/dev/null
Record every *.backup-<TS> path taken. On ANY failure mid-run: restore all backups in reverse order and abort.
Backup retention
After backups, check how many *.backup-* files exist per source file. If any has more than 3, offer to archive the oldest into ~/.claude/commander/tuneup-archive/old-backups-<TS>/. Never auto-archive without the user's confirmation (this is an AskUserQuestion chip).
Archive dir
ARCHIVE="$HOME/.claude/commander/tuneup-archive/$(date +%Y%m%dT%H%M%SZ)"
mkdir -p "$ARCHIVE"
Junk sweep (mv, never rm)
# temp_* dirs
for d in "$HOME/.claude/plugins/marketplaces/temp_"*; do
[ -d "$d" ] && mv "$d" "$ARCHIVE/" && echo "archived: $d"
done
# Stale .bak/.old files (>14d)
find "$HOME/.claude" -maxdepth 4 \( -name "*.bak" -o -name "*.old" \) -mtime +14 \
-not -path "$HOME/.claude/commander/tuneup-archive/*" \
-exec mv {} "$ARCHIVE/" \; 2>/dev/null
# Archive-hook dupes: names that exist in BOTH hooks/_archive/ AND hooks/
# (compute intersection at runtime — do NOT use a baked list)
if [ "$CLONE" != "n/a" ]; then
HOOKS_DIR="$CLONE/commander/cowork-plugin/hooks"
if [ -d "$HOOKS_DIR/_archive" ]; then
comm -12 \
<(ls "$HOOKS_DIR/_archive/" | sort) \
<(ls "$HOOKS_DIR/" | grep '\.js$' | sort) | \
while read f; do
mv "$HOOKS_DIR/_archive/$f" "$ARCHIVE/" 2>/dev/null && echo "archived dupe: $f"
done
fi
fi
Count fixes (maintainer mode only — only when CLAUDE_MD_MODE=maintainer)
For each drifted claim in $GIT_ROOT/CLAUDE.md:
- Re-read the file immediately before editing + compare mtime to scan-time mtime. If changed: skip and report "file changed since scan — skipped".
- Use
Editon the EXACT stale token (one Edit per drifted claim):- Replace the number only (e.g.,
61 plugin skills→62 plugin skills). - Verify after each fix: re-run the same live probe count and assert the edited line now matches it. If mismatch: restore the backup for that file and report failure.
- Replace the number only (e.g.,
- Never edit
~/.claude/**— only edit files under the verified cc-commander git root.
Settings keys
Edit ~/.claude/settings.json (backup already taken). Add ONLY keys that are missing — never overwrite existing values:
# Add showThinkingSummaries if absent
# Add env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS if absent globally
# Add toolConcurrency if absent
Use Edit with surgical key insertion. Verify the file is valid JSON after each edit.
Version refresh / marketplace re-add (EMIT ONLY — never run)
Marketplace-installed users have no working tree to git pull into — the
clone at ~/.claude/plugins/marketplaces/commander-hub is Claude Code's own
cache. The sanctioned sequence is the marketplace-update flow:
# To update the plugin — paste into terminal:
claude plugin marketplace update commander-hub
claude plugin update commander
# Then restart Claude Code / Cowork Desktop to apply
#
# To stop needing step 1 by hand: set "autoUpdate": true on the
# "commander-hub" entry in ~/.claude/plugins/known_marketplaces.json
# (or via the /plugin menu, if offered). /ccc-doctor offers to do this
# for you with a backup + confirmation.
Step 5 — Verify
After all fixes, re-run the probe for ONLY the rows that were changed. Assert each row now reads 🟢. If CLAUDE_MD_MODE=maintainer and count edits were made, also run:
node "$GIT_ROOT/scripts/audit-counts.js" --check 2>/dev/null || echo "audit-counts: failed"
If any fix introduced a regression: restore the matching *.backup-<TS> for that file and report it clearly.
Step 6 — Report
## Tune-Up complete
✅ Fixed: <list each fix applied>
🗂️ Archived (reversible): ~/.claude/commander/tuneup-archive/<stamp>/ (restore: mv <path> back)
💾 Backups: <each *.backup-stamp path>
⏭️ You must run by hand: git pull + Desktop restart (version), /ccc-connect re-run (MCP staleness)
For anything heavy/out-of-scope, offer ONE mcp__ccd_session__spawn_task chip (e.g. "deep-clean stale sessions older than 90d — review before deleting"). Never auto-spawn.
Anti-patterns — DO NOT
- ❌ Mutate anything on
--check. - ❌
rmortrash— archive only, always. - ❌ Edit
~/.claude/**— the global config is not ours to modify. - ❌ Edit
settings.jsonor anyCLAUDE.mdwithout a*.backup-<stamp>first. - ❌ Hardcode counts/versions — read live, diff against claims.
- ❌ Reimplement ccc-doctor's source-tree drift detection — consume
lib/diagnostics.jsfor the shared full-stack rows. - ❌ Use
python3in probes — purejq+bash+node -eonly. - ❌ Run
git pull/ restart Desktop yourself — emit commands. - ❌ Render rows for checks that passed as if they need fixing.
- ❌ Apply any fix without first verifying the result afterward.
- ❌ Offer CLAUDE.md count edits when running from an end-user install (detect-only).
- ❌ Compare model strings without normalizing away
[1m]/[128k]/[...]context suffixes first. - ❌ Auto-apply the 💰 cost-settings keys (effortLevel, MAX_THINKING_TOKENS, ENABLE_TOOL_SEARCH, model, …) — they are read-only suggestions; surface them, emit copy-paste, never edit settings.json for them.
- ❌ Suggest overriding the user's deliberate
modelpin — for cost-settingsmodelis inform-only. - ❌ Render
ok-verdict cost keys as if they need fixing — only surfacemissing/suboptimal.
Bottom line: scan read-only → scorecard → click to confirm → backup + archive + fix → verify → report. Safe by default, reversible by design, always current.
⚙️ Fable contract: plan before build · verifier ≠ worker · prove before alarm · loops need gates · leave durable state —
rules/fable-method.md