Run fallow's project-wide scans — fallow health, fallow dead-code, and fallow dupes — surface the top 10 issues, and fix them interactively, one at a time, asking the user before each fix.
Unlike /improve-diff (which runs the diff-scoped fallow audit and stops when the diff has no findings), this skill never uses the diff-based approach: it ranks issues across the entire codebase, inherited and new alike.
When to Use
- The user asks to "improve the codebase", "run a full audit", "find the worst hotspots", or wants a project-wide quality pass.
- The user invokes
/improve-codebase. - The user asks for a project-wide audit after
/improve-diffreportedNO_FINDINGS(the diff-scoped gate has nothing to work with on a clean tree).
Step 1 — Run the three project-wide scans
From the repository root. Do NOT use fallow audit (it is diff-scoped). Do NOT run bare fallow health with no section flags — the full pipeline can be slow or hang on large repos; select sections explicitly.
Run the three commands in parallel bash calls. Each writes JSON to .agents/tmp/ (files are large — hundreds of KB — so never cat them; summarize with python). Redirect stderr: npx prints install warnings there. Treat exit codes 0 and 1 as success (1 = findings found); exit 2 is a real error reported as a JSON envelope on stdout — report it and stop. Give each call a generous timeout (~240s).
Scan A — complexity (hotspots):
mkdir -p .agents/tmp && npx fallow health --complexity --format json > .agents/tmp/health-cx.json 2>/dev/null; echo "exit=$?"
python3 - <<'EOF'
import json
d=json.load(open(".agents/tmp/health-cx.json"))
f=d["findings"]
def istest(p): return "/__tests__/" in p or ".test." in p
src=[x for x in f if not istest(x["path"])]
def sevrank(s): return {"critical":0,"high":1,"moderate":2}.get(s,3)
def crap_only(x): return x.get("exceeded")=="crap" and x.get("coverage_tier")=="none"
src.sort(key=lambda x:(sevrank(x["severity"]), -x["cyclomatic"], -x["cognitive"]))
crit=sum(1 for x in src if x["severity"]=="critical")
high=sum(1 for x in src if x["severity"]=="high")
crapn=sum(1 for x in src if crap_only(x))
print("findings=%d src=%d critical=%d high=%d crap_artifacts=%d" % (len(f),len(src),crit,high,crapn))
for x in src[:25]:
print("%-8s cc=%3d cog=%3d %s:%s %s exceeded=%s" % (x["severity"],x["cyclomatic"],x["cognitive"],x["path"],x["line"],x["name"],x.get("exceeded")))
EOF
Key fields per finding: severity (critical/high/moderate), cyclomatic, cognitive, crap, exceeded (what it tripped: all, both, cyclomatic, cognitive, cyclomatic_crap, or crap), coverage_tier, path, line, name. large_functions (in the same JSON) holds unit-size findings — a useful secondary signal but not the primary ranking.
If you need to explain why a function scored high before refactoring, re-run with --complexity-breakdown to get the per-decision-point contributions[].
Scan B — dead code:
mkdir -p .agents/tmp && npx fallow dead-code --format json > .agents/tmp/dead-code.json 2>/dev/null; echo "exit=$?"
python3 - <<'EOF'
import json
d=json.load(open(".agents/tmp/dead-code.json"))
ue=d["unused_exports"]
byfile={}
for e in ue: byfile.setdefault(e["path"],[]).append(e)
clusters=sorted(byfile.items(), key=lambda kv:-len(kv[1]))
print("unused_exports=%d files_with=%d unused_files=%d" % (len(ue),len(byfile),len(d["unused_files"])))
print("-- clusters (5+ exports, largest first) --")
for p,es in clusters:
if len(es)<5: break
print("%3d %s" % (len(es), p))
print("-- remaining exports (top 15 files) --")
for p,es in clusters[:15]:
if len(es)>=5: continue
print("%3d %s %s" % (len(es), p, ",".join(x["export_name"] for x in es)[:100]))
print("-- unused deps --", d["unused_dependencies"], "dev:", d["unused_dev_dependencies"])
EOF
unused_exports entries carry path, export_name, line, is_type_only, is_re_export, and actions (often remove-export). unused_files may be empty if config excludes those paths; fallow health's vital_signs.dead_files count can still report dead files — cross-check if the counts disagree.
Scan C — duplication:
mkdir -p .agents/tmp && npx fallow dupes --format json > .agents/tmp/dupes.json 2>/dev/null; echo "exit=$?"
python3 - <<'EOF'
import json
d=json.load(open(".agents/tmp/dupes.json"))
cg=d["clone_groups"]
cg.sort(key=lambda g:-(g.get("line_count") or 0))
st=d.get("stats") or {}
print("clone_groups=%d stats=%s" % (len(cg), json.dumps(st)[:200]))
for g in cg[:12]:
inst=g.get("instances") or []
files=sorted(set(str(i.get("path") or i.get("file")) for i in inst))
print("lines=%3d n=%d %s" % (g.get("line_count",0), len(inst), " | ".join(files)[:140]))
EOF
Clone groups have instances (each with file, start_line, end_line), token_count, line_count, suggested_name, and actions.
Step 2 — Rank the top 10
No "introduced" attribution exists here — rank by real-world value across the whole codebase:
- Dead-code clusters first — files with 5+ unused exports, largest cluster first. Batch a whole file as one item.
- Critical complexity hotspots — non-test files,
severity: "critical", sorted bycyclomaticdescending (tie-break bycognitive). Prefer findings whoseexceededincludes cyclomatic/cognitive (all,both); findings that are purecrapwithcoverage_tier: "none"are test-coverage artifacts — see Notes. - High complexity hotspots — same ordering within
severity: "high". - Dead files — from
unused_files. - Duplication — largest clone groups by
line_count. - Unused dependencies — batch all as one item.
Exclude test files (*.test.ts(x), __tests__/) from the complexity ranking — refactoring them is low value. Deprioritize one-off scripts (e.g. scripts/, ai-data/scripts/) below app/CLI source. Cap at 10. For each item report: rank, file path, symbol/name, line, and the metric that made it rank (e.g. cc=31 cog=54, 24 unused exports, 230 dup lines). Keep each line concise.
Step 3 — Report and ask
Present the ranked top 10 as a numbered table/list. Then ask whether to fix issue #1 (use ask_user, recommended option first). Do not fix anything yet.
Step 4 — Interactive fix loop
For each issue the user agrees to fix:
-
Fix it. Prefer the auto-fixable action when available (e.g.
remove-export). For complexity hotspots, refactor by extracting named sub-functions/helpers — do not just suppress. For duplication, extract the shared fragment into one named helper (use the clone group'ssuggested_name). -
Re-run only the scan covering that issue (A, B, or C) and do a targeted check for the symbol:
npx fallow health --complexity --format json > .agents/tmp/health-cx.json 2>/dev/null; python3 - <<'EOF' import json d=json.load(open(".agents/tmp/health-cx.json")) name="SYMBOL_NAME" print("still flagged:", any(f["name"]==name for f in d["findings"])) print("critical total:", sum(1 for f in d["findings"] if f["severity"]=="critical")) EOFFor dead code: confirm the export name no longer appears in
unused_exports. For duplication: confirm the clone group's files no longer appear together.If the refactor introduced a new small helper that is now flagged (common with CRAP — no test coverage), either simplify it below the CRAP threshold or add a focused test for it; don't leave a new introduced finding behind. (If the diff gate matters for the change,
fallow audit --gate new-onlywill catch regressions on commit.) -
If the change was significant, run the
build-testskill to verify the pipeline. -
Report the result briefly, then ask about the next issue (issue #2, then #3, and so on) via
ask_user.
Stop when the user declines an issue and says to stop, or the list is exhausted. If the user declines one issue, move on to the next rather than stopping entirely, unless they say to stop.
Notes
- Do not suppress findings with
fallow-ignorecomments unless the user explicitly asks — prefer real fixes. - For unused exports, verify the symbol is genuinely unused before removing (check it is not part of an intentional public API / re-export). If it's used internally but not imported elsewhere, drop the
exportkeyword instead of deleting it. - Dead files: check for dynamic imports and side-effect-only entrypoints before deleting; fallow marks some
delete-fileactions as not auto-fixable for a reason. - A finding with
exceeded: "crap"(andcoverage_tier: "none") is a test-coverage artifact, not raw complexity: CRAP = CC² + CC with zero coverage. If the function is already below the cyclomatic/cognitive thresholds, the fix is to add a focused test (export the function if needed) rather than refactor further. For a pure helper, a small test file clears it.exceeded: "cyclomatic_crap"means both — tests help but won't clear the cyclomatic overage; refactor too. - Keep the loop interactive: one issue at a time, always ask before fixing.