heal-cli 0.1.0

Hook-driven Evaluation & Autonomous Loop — code-health harness CLI for AI coding agents
Documentation
heal-cli-0.1.0 has been yanked.

HEAL

Hook-driven Evaluation & Autonomous Loop — a code-health harness that turns codebase decay signals into work for AI coding agents.

LLM coding agents are usually reactive: a human files a task before the agent moves. Codebases, meanwhile, decay continuously — complexity creeps, hotspots shift, duplicates accumulate. HEAL closes that gap by turning codebase state changes into agent triggers.

v0.1 is the observe half of the loop: collect metrics on every commit, expose them through heal status / heal check, and surface threshold breaches to Claude Code as a session-start nudge. Autonomous repair (heal run, PR generation) lands later.

⚠️ Status: v0.1. macOS / Linux only. API may shift before v0.2.

What it measures

Every metric is computed by a collector under src/observer/ and persisted on the post-commit hook to .heal/snapshots/YYYY-MM.jsonl.

Metric What it captures Languages
LOC Lines of code per language; primary-language detection (tokei) language-agnostic
CCN McCabe Cyclomatic Complexity per function (tree-sitter queries) TypeScript, Rust
Cognitive Sonar-style Cognitive Complexity (nesting + logical-chain switches) TypeScript, Rust
Churn Per-file commit frequency and added/deleted line totals over a since_days window language-agnostic (git)
Change Coupling Co-change pair counter (code-maat style); bulk-commit cap; sum-of-coupling per file language-agnostic (git)
Duplication Type-1 (exact) clones via tree-sitter leaf tokens + Rabin-Karp TypeScript, Rust
Hotspot commits × ccn_sum × weights composition over Churn + CCN TypeScript, Rust

Per-metric Cargo features (lang-ts, lang-rust) decide which language parsers compile in; the default build enables both.

Install

git clone https://github.com/kechol/heal
cd heal
cargo install --path crates/cli   # produces the `heal` binary

Toolchain: Rust 1.85+. The repo pins the exact version via misemise install from the project root if you use it, otherwise any rustup install ≥ 1.85 will work.

macOS and Linux only for v0.1. The hook scripts and path handling assume a POSIX-ish environment.

Quickstart

Inside any git repository:

heal init                # creates .heal/{config.toml,snapshots,logs,docs,reports}
                         # and installs .git/hooks/post-commit
heal skills install      # extracts the bundled Claude plugin into .claude/plugins/heal
heal status              # show metric summary and findings
heal logs                # stream the commit / edit / stop event timeline

After heal init, every git commit triggers the post-commit hook, which runs the observers and appends one MetricsSnapshot to .heal/snapshots/ plus a CommitInfo (sha, parent, author, subject, file/line counts) to .heal/logs/. Claude Code Edit/Stop hooks (installed by heal skills install) append to the same logs file.

heal status reads snapshots/; heal logs reads logs/. The two directories share a generic event-log format (append-only, month-rotated JSONL, transparent reads over .gz once compaction lands).

CLI

Command Purpose
heal init [--force] Create .heal/, write a default config.toml, install the post-commit hook, capture an initial snapshot.
heal status [--json] [--metric <name>] Metric summary and most recent finding. --metric scopes output to a single observer (loc / complexity / churn / change-coupling / duplication / hotspot).
heal logs [--since <RFC3339>] [--filter <event>] [--limit N] [--json] Stream .heal/logs/ events.
heal check [overview|hotspots|complexity|duplication|coupling] [-- <claude args>] Launch Claude Code (claude -p) with the matching read-only check-* skill. Anything after -- forwards verbatim to claude.
heal hook <commit|edit|stop|session-start> Hook entrypoint invoked by git or the Claude plugin. Not for direct use.
heal skills <install|update|status|uninstall> Manage the bundled Claude plugin under .claude/plugins/heal/.

Run heal --help or heal <subcommand> --help for full argument details.

Configuration

heal init writes .heal/config.toml. Every metric ships with sensible defaults; the config file is mostly there so you can override them. The schema is strict (deny_unknown_fields) so typos surface as errors rather than silently dropping settings.

Selected knobs:

[project]
# Free-form natural language passed to `heal check`. Use anything the
# model understands — "Japanese", "日本語", "ja", "français".
response_language = "Japanese"

[git]
since_days = 90              # Lookback window for churn / change coupling.
exclude_paths = ["dist/"]    # Substrings; matched against every observed path.

[metrics]
top_n = 5                    # Default size of every "worst-N" listing.

[metrics.loc]
inherit_git_excludes = true  # Combine git.exclude_paths with metrics.loc.exclude_paths.

[metrics.duplication]
min_tokens = 50              # Minimum window length for a duplicate block.

[metrics.hotspot]
weight_churn = 1.0
weight_complexity = 1.0
top_n = 8                    # Per-metric override of the global metrics.top_n.

[policy.high_complexity_new_function]
action = "report-only"
cooldown_hours = 24
threshold = { ccn = 15, delta_pct = 20 }

Policy entries drive per-rule cool-down for the SessionStart nudge today; the action ladder (report-onlynotifyproposeexecute) becomes meaningful when heal run lands.

Repository layout

heal/
├── crates/
│   └── cli/
│       ├── src/
│       │   ├── core/          # config, eventlog, snapshot, state, paths
│       │   ├── observer/      # LOC, complexity, churn, coupling, duplication, hotspot
│       │   ├── commands/      # CLI subcommand dispatch
│       │   └── …
│       ├── plugins/heal/      # Claude Code plugin (embedded via include_dir!)
│       └── queries/           # tree-sitter queries for CCN / Cognitive / functions
├── plugins → crates/cli/plugins   # convenience symlink for plugin authoring
├── LICENSE-MIT
├── LICENSE-APACHE
└── README.md

The crate-level crates/cli/plugins/heal/ directory is the source of truth for the Claude plugin tree. It sits inside heal-cli so the crates.io tarball ships it, and is materialised into .claude/plugins/heal/ by heal skills install. The top-level plugins/ symlink is a convenience for editors and shell tab-completion.

Claude plugin

heal skills install extracts:

  • Three hook scripts wired to heal hook <event>:
    • PostToolUse(Edit|Write|MultiEdit)heal hook edit (logs only)
    • Stopheal hook stop (logs only)
    • SessionStartheal hook session-start (cool-down-aware nudge)
  • Five read-only check-* skills (overview, hotspots, complexity, duplication, coupling) that pull from heal status --metric <x>.

Each installed asset's fingerprint is tracked in .heal-install.json, so heal skills update can refresh bundled assets without overwriting files the user has hand-edited (use --force to override).

Development

cargo build  --workspace
cargo test   --workspace
cargo fmt    --all
cargo clippy --workspace --all-targets -- -D warnings
cargo deny   check

CI runs all five on push / PR. clippy::pedantic = warn at the workspace level — new code is expected to pass clippy clean.

Module layout

The whole CLI lives in a single crate, heal-cli, organised internally as if the original three-layer split were still in place:

  • src/core/ — config, event log, snapshot, state, paths, error types. Pure data types and on-disk formats; no CLI or observer logic.
  • src/observer/ — metric collectors (LOC, complexity, churn, change coupling, duplication, hotspot). Stateless: reads the project tree and emits structured payloads.
  • src/commands/ + src/cli.rs + src/main.rs — argument parsing, command dispatch, hook entrypoints, plugin extraction.

Contributing

Issues and PRs welcome. The project is in early bootstrap; please open an issue before introducing a new crate, a new external dependency, or a schema change to .heal/.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.