heal-cli 0.1.0

Hook-driven Evaluation & Autonomous Loop — code-health harness CLI for AI coding agents
Documentation
# HEAL

> **H**ook-driven **E**valuation & **A**utonomous **L**oop — 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

```sh
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
[mise](https://mise.jdx.dev) — `mise 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:

```sh
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:

```toml
[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-only` → `notify` → `propose` → `execute`)
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)
  - `Stop``heal hook stop` (logs only)
  - `SessionStart``heal 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

```sh
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

- Apache License, Version 2.0 ([LICENSE-APACHE]./LICENSE-APACHE or
  <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT]./LICENSE-MIT or
  <http://opensource.org/licenses/MIT>)

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.