CLI
heal is a single binary. Every interaction goes through one of the
subcommands below. Run heal --help or heal <subcommand> --help
for the full argument list.
User commands
Section titled “User commands”The day-to-day surface — these are the four you’ll actually type.
| Command | Purpose |
|---|---|
heal init | Set up .heal/, calibrate, and install the post-commit hook in the current repository. |
heal skills | Install / update / inspect / remove the bundled Claude skill set. |
heal status | Render the current TODO list (or refresh it). Reads .heal/findings/. |
heal diff | Compare the live worktree against an earlier commit (default: the calibration baseline). Like git diff for findings. |
Automation commands
Section titled “Automation commands”These run on your behalf — from the git post-commit hook, from a
bundled Claude skill, or only when your codebase has shifted enough
to warrant attention. Hidden from --help.
| Command | Driven by | Purpose |
|---|---|---|
heal hook | git post-commit | Run observers and emit the Severity nudge after each commit. |
heal mark fix | /heal-code-patch skill | Record that a commit fixed a finding so the next heal status reconciles it. |
heal mark accept | /heal-code-review skill | Record an intrinsic finding the team has decided not to refactor. |
heal metrics | /heal-code-review skill | Per-metric summary recomputed on every invocation. |
heal calibrate | /heal-setup skill | Reset Severity thresholds to today’s codebase distribution. |
heal metrics and heal calibrate are listed here because the
bundled skills decide when to run them — /heal-code-review reads
the per-metric summary while orchestrating an audit, and
/heal-setup watches for calibration drift and recommends a
recalibration when the codebase has moved enough. Run them by hand
only when you need the raw output without going through Claude.
heal init
Section titled “heal init”Bootstraps heal inside a git repository:
heal init # interactive — one Y/N prompt per detected agentheal init --yes # extract skills for every detected agent (no prompt)heal init --no-skills # skip every agent (CI / no AI agent installed)heal init --force # overwrite an existing config.toml / hook; refresh skillsheal init --explicit # write every default to config.toml (long form)By default, heal init writes config.toml in minimal form —
only fields the user has actually customized appear on disk. A
fresh project is essentially an empty file. --explicit writes the
full default tree so the file doubles as a discoverable reference
of every available knob.
heal init does:
-
Create
.heal/withconfig.toml,calibration.toml, andfindings/.config.toml,calibration.toml, and the cache underfindings/are all tracked in git, so teammates on the same commit see the same Severity ladder and drain queue. -
Run every observer once and compute the codebase’s percentile distribution per metric — that becomes
calibration.toml. -
Install
.git/hooks/post-commit(idempotent — re-installation never duplicates the line). -
For each AI agent on
PATH, extract the bundled skill set into that agent’s project-scope discovery path:claude→.claude/skills/codex→.agents/skills/
In a TTY, you get one Y/N prompt per detected agent (default
Y).--yesaccepts every prompt;--no-skillsskips every prompt. Agents whose CLI is not onPATHare silently skipped — their skills would have nowhere to be invoked from.
When done, heal init prints an “Installed:” summary listing every
file it wrote — config, calibration, post-commit hook, and one line
per agent target (or the reason it was skipped).
Re-running is safe: config.toml is left in place unless --force
is passed; the post-commit hook is replaced only when it carries
the heal marker. If a non-heal post-commit hook already exists,
heal init leaves it alone — pass --force to overwrite.
heal skills
Section titled “heal skills”Manages the bundled skill set across every agent target. Each
subcommand takes --target <detected|claude|codex|all> (default
detected, mirroring heal init):
heal skills install # extract for every CLI on PATHheal skills install --target codex # only `.agents/skills/`heal skills install --target all # every known target regardless of detectionheal skills update # refresh after a heal binary upgradeheal skills status # per-target installed version + driftheal skills uninstall --target all # remove from every treeThe skill set is embedded in the heal binary at compile time, so
each subcommand always operates on the version matching the binary
in use. update is drift-aware: files that have been hand-edited
are left in place per target (use --force to overwrite anyway).
The Claude target’s install / update also sweep legacy
heal hook edit / heal hook stop entries from
.claude/settings.json; Codex has no sibling settings file.
The bundled set ships eleven skills, grouped by feature family:
Code (always on):
/heal-code-review(read-only) ingestsheal status --all --json, deep-reads the flagged code, and produces an architectural reading plus a prioritized refactor TODO list./heal-code-patch(write) drains the TODO list one finding per commit (Severity order;Critical 🔥first)./heal-cliis a concise reference for thehealCLI surface./heal-setupis the setup wizard. It calibrates the project, asks for a strictness level, writesconfig.toml, then asks whether to enable the optional[features.docs]and[features.test]families — chaining to/heal-doc-pair-setupand/heal-test-reporter-setupwhen you opt in. Also detects calibration drift and recommendsheal calibrate --forcewhen warranted.
[features.docs] (opt-in):
/heal-doc-pair-setup(write.heal/doc_pairs.json) detects doc ⇔ src pairings./heal-doc-scaffold(write under[features.docs] scaffold_root) stands up a fresh documentation tree from codebase signals alone./heal-doc-review(read-only) audits the docs slice through a Diátaxis lens./heal-doc-patch(write) drains broken internal links, dangling identifiers, orphan pages, and resolvable TODOs.
[features.test] (opt-in):
/heal-test-reporter-setup(read-only) proposes lcov reporter + CI configuration./heal-test-review(read-only) audits the test slice through a test-pyramid lens./heal-test-patch(write) drains uncovered hot paths, drifting tests, and skipped tests whose reason no longer holds.
See Code › Skills, Test › Skills, and Docs › Skills for the full contracts.
heal status
Section titled “heal status”Runs every observer, classifies each finding by Severity, and writes
the TODO list /heal-code-review audits and /heal-code-patch
drains:
heal status # render the cached TODO (default)heal status --refresh # re-scan and overwrite the cacheheal status --metric lcom # only LCOM findingsheal status --metric coverage-pct # only coverage findings ([features.test])heal status --metric doc-drift # only doc-drift findings ([features.docs])heal status --severity critical # only Critical (and above with --all)heal status --feature code # only the code family (drop test / docs)heal status --feature test # only the test family ([features.test])heal status --feature docs # only the docs family ([features.docs])heal status --path src/payments # restrict to one path prefix (was --feature pre-v0.4)heal status --all # show Medium / Ok plus the low-Severity hotspot sectionheal status --top 5 # cap each Severity bucket at 5 rowsheal status --no-pager # write straight to stdout (skip the pager)heal status --json # machine-readable shape on stdoutWhen stdout is a terminal, heal status pipes through $PAGER (or
less) — the same convention as git diff / git log. Pass
--no-pager to write straight to stdout, or pipe the output anywhere
(redirect, | cat, CI logs) and the pager is skipped automatically.
--json always writes raw to stdout.
By default heal status is a read-only render of the cached TODO:
runs are free once the cache is warm. Pass --refresh to invalidate
and re-run every observer; this is the only path that writes the
cache. A missing cache (e.g. immediately after heal init) also
triggers a scan, so the first invocation in a project still works
without flags.
Output groups findings under 🔴 Critical 🔥 / 🔴 Critical / 🟠 High 🔥 / 🟠 High / 🟡 Medium / ✅ Ok (last two require --all), aggregates
one row per file, and ends with Goal: 0 Critical, 0 High plus a
“next steps” line pointing at claude /heal-code-patch. With
--all, an extra “Ok / Medium 🔥 (low Severity, top-10% hotspot)”
section surfaces files that aren’t classified as a problem yet but
get touched often enough to be worth a look.
heal diff
Section titled “heal diff”Compare the live worktree against the findings at an earlier commit.
Default ref: the calibration baseline SHA (recorded by heal init /
heal calibrate --force), falling back to HEAD when no baseline is
recorded — so “Progress: N% complete” reads naturally as “drained
since calibration”:
heal diff # live vs the calibration baselineheal diff HEAD # live vs the last commitheal diff main # live vs mainheal diff v0.2.1 # live vs the v0.2.1 tagheal diff HEAD~5 # live vs 5 commits backheal diff --all # also surface Improved + Unchanged + below-High entriesheal diff --no-pager # write straight to stdout (skip the pager)heal diff --json # machine-readable shape<git-ref> accepts anything git rev-parse understands. heal
re-evaluates the requested ref under the current config.toml and
calibration.toml so the comparison is apples-to-apples — you’re
seeing how today’s rules judge then-and-now, not the historical
ratings.
When stdout is a terminal, heal diff pipes through $PAGER (or
less) — same convention as heal status. Pass --no-pager to write
straight to stdout; --json always writes raw to stdout.
Output buckets: Resolved / Regressed / Improved / New / Unchanged, plus a progress percentage. The right-hand side is always a fresh in-memory scan of the current worktree — never persisted.
By default the human renderer hides entries whose from and to
Severity both sit below High (a noisy baseline drowns the actionable
rows otherwise) and prints a [N entries below High hidden — pass --all] footer. --all bypasses the filter alongside surfacing the
Improved / Unchanged buckets. The --json payload is unfiltered
either way — skills and CI keep seeing every row.
For very large repos the comparison can be expensive; [diff] in
config.toml exposes a LOC ceiling that switches to a manual
two-branch recipe above the threshold. See
Code › Configuration.
heal metrics
Section titled “heal metrics”heal metricsheal metrics --jsonheal metrics --metric complexityheal metrics --metric lcomheal metrics --metric coverage-pctheal metrics --metric doc-freshnessheal metrics --no-pagerPrints a summary of every enabled metric — primary language, worst-N
complex functions, top hotspots, most-split classes. --metric <name> scopes output to one observer; valid names:
- Code (always available):
loc,complexity,churn,change-coupling,duplication,hotspot,lcom. [features.docs](when enabled):doc-freshness,doc-drift,doc-coverage,doc-link-health,orphan-pages,todo-density,doc-hotspot.[features.test](when enabled):coverage-pct,skip-ratio,test-hotspot.
--json produces the same data as machine-readable JSON, suitable
for piping into jq.
When stdout is a terminal, heal metrics pipes through $PAGER (or
less) — same convention as heal status / heal diff. Pass
--no-pager to write straight to stdout.
Recomputed from scratch on every invocation — there is no historical record to delta against.
heal calibrate
Section titled “heal calibrate”heal calibrate # create calibration.toml if missing; otherwise no-opheal calibrate --force # always rescan and overwrite calibration.tomlheal never recalibrates automatically — a refactor that genuinely
improves the codebase shouldn’t silently move the goalposts. Run
--force when:
- A large structural change has shifted the distribution (the
/heal-setupskill watches for this and recommends). - You’ve changed
floor_critical/floor_okoverrides inconfig.tomland want the percentile ladder rebuilt against them.
The generated calibration.toml carries a comment header noting its
provenance, so anyone opening the file can find their way back to
this command. Put floor_critical / floor_ok overrides in
config.toml, not calibration.toml — that way heal calibrate --force doesn’t clobber them.
Inspecting the cache
Section titled “Inspecting the cache”heal status --json is the contract for scripts. If you want to peek
at the on-disk state directly, three flat files live under
.heal/findings/:
| File | Purpose |
|---|---|
.heal/findings/latest.json | The current TODO list — refreshed by heal status --refresh. |
.heal/findings/fixed.json | Bounded record of fixes claimed by /heal-code-patch. |
.heal/findings/regressed.jsonl | Audit trail for fixes that were re-detected. |
These are plain files, readable with jq:
jq '.severity_counts' .heal/findings/latest.jsonjq 'keys | length' .heal/findings/fixed.json # number of recorded fixestail .heal/findings/regressed.jsonlheal hook commit
Section titled “heal hook commit”Invoked automatically by the git post-commit hook installed by
heal init. Runs every observer and prints a one-line Severity nudge
— every Critical and High finding to stdout, with Hotspot-flagged
entries first. There is no cool-down: the same problem reappears
every commit until it’s fixed — that’s the point. Nothing is written
to disk; the nudge is the only output.
When [features.test.coverage] is enabled and any High / Critical
coverage_pct finding sits on a hotspot file, the nudge gains a
second indented line counting “uncovered hotspot” findings — the
shortest possible “the next test should land here” reminder.
Manual invocation is occasionally useful for debugging:
heal hook commitheal statusis the canonical workflow. After a meaningful commit, run it to refresh the cache and see what’s still on the TODO list.heal diff(no args) shows progress against the calibration baseline — the ”% complete” number reads as “drained since calibration”. PassHEADfor “since the last commit”, or any othergit rev-parse-compatible ref.- Preserve the post-commit hook. Removing it stops the Severity
nudge from running after each commit, but
heal statusstill works on demand.