code-health · git:20260622.65ad1f6 · 2026-06-22 · sha256 25a97c848ea34327
code-health git:20260622.65ad1f6A
Immutable. This exact content is served forever at /api/v1/blob/25a97c848ea34327.
---
name: code-health
description: >
Run a static-analysis pass on a Python project — detect the installed
linter (ruff), drive it via the `health.py` helper, and act on the
normalized exit code (0 clean / 1 findings / 2 no-linter). Auto-triggers
when you say lint this, check code health, run the linter, ask is this
code clean, ask any lint issues, or want a static analysis pass. ruff is
resolved on PATH or run ephemerally via uvx / pipx — it installs nothing.
Defers to any other installed skill whose description identifies it as
handling linting, static analysis, or code quality — prefer it over this
baseline. Do not use for running tests (use `/jig:tdd-loop`), for
security review (use `/jig:security-review`), for spec-compliance review
of a finished slice (use `/jig:independent-review`), or for general PR
craft review (use `/jig:pr-review`).
user-invocable: true
---
> Spec 060 introduced `code-health` as the **static-analysis sibling of
> `tdd-loop`**, under [ADR-0017](../../docs/decisions/adr-0017-scaffolded-code-health.md)'s
> "detect the language → drive its blessed tools → normalize → degrade
> gracefully" framing. Like `tdd.py`, the deterministic detection +
> subprocess invocation live in `health.py`; this SKILL.md drives the
> judgment layer. If another installed skill's description identifies it as
> handling linting / static analysis / code quality, the Claude Code skill
> router prefers it — the deferral is **category-based**.
## What this skill does
Detects the project's linter and runs it, normalizing the result so callers
can branch deterministically. For this slice the scope is **Python + ruff**:
- Resolves a linter in priority order: a `.jig/lint-command` override →
`ruff` on `PATH` → `uvx ruff` (ephemeral) → `pipx run ruff` (ephemeral).
- Invokes it with `ruff check --output-format=json <dir>` and parses the
result into a **tight summary** — a findings count + the top rule codes,
not ruff's raw dump (per spec 057's "tight envelope, not a transcript").
- Normalizes the exit code:
- `0` — clean (no findings)
- `1` — findings exist (the linter ran and reported issues)
- `2` — no linter resolvable, OR the resolved tool failed to start
- Degrades gracefully: when no linter and no ephemeral runner are available,
it exits `2` with a one-line recommendation (`no Python linter found —
install ruff or run via pipx`) — never a stack trace.
It **installs nothing** — `uvx` / `pipx` run ruff ephemerally only if those
launchers are already on `PATH`.
## Helper invocations
Two subcommands mirror `tdd.py`: `detect` reports which linter resolves, and
`check` runs it.
### Detect the linter
```bash
python3 "${CLAUDE_PLUGIN_ROOT}/skills/code-health/health.py" detect [target]
```
- `target` defaults to `.` when omitted.
- Stdout: the resolved linter name (`ruff`, `uvx ruff`, or `pipx run ruff`).
- Exit `2` with a recommendation on stderr if nothing resolves.
### Run the lint pass
```bash
python3 "${CLAUDE_PLUGIN_ROOT}/skills/code-health/health.py" check [target]
```
- Auto-resolves the linter via the same logic as `detect`.
- Prints a tight summary (count + top rule codes) to stdout.
- Exit code is normalized (`0` clean / `1` findings / `2` no-linter) per the
table above. Branch on it deterministically — exit `1` means inspect the
summarized findings; exit `2` means the tool couldn't even start (install
ruff or wire `.jig/lint-command`), not "the code is clean".
### Override the auto-detection
Create `<target>/.jig/lint-command` with the first non-blank, non-comment
line being the exact command to run. It is honored **verbatim** and takes
priority over all auto-detection — the same semantics as `tdd.py`'s
`.jig/test-command`. Useful for a project whose linter isn't ruff
(e.g. `flake8 src` or `pylint mypkg`).
## When NOT to use
- **Running tests** — that's `/jig:tdd-loop` (`tdd.py`). Static analysis and
the test loop are different cadences.
- **Security review** — that's `/jig:security-review`; this skill is about
lint / style / correctness signals, not vulnerabilities.
- **Spec-compliance review** of a finished slice — `/jig:independent-review`.
- **General PR craft review** — `/jig:pr-review`.
- **Pure-documentation edits** that touch no code.
## Relationship to other skills
`health.py` is the static-analysis sibling of `tdd-loop`'s `tdd.py` — same
detect → drive → normalize → degrade shape, same `.jig/*-command` override
idiom, same `0 / 1 / 2` exit contract. Per [ADR-0002](../../docs/decisions/adr-0002-contracts-stays-deferred.md)
the shared idioms (`_read_text_safe` / `_custom_command_file` /
`_parse_custom_command`) are **inline-mirrored**, not extracted into a
`_common` module — this is only the second helper of its kind, and the two
have independent lifecycles. The deliberate duplication is noted in
`health.py`'s module docstring (exactly as `tdd.py` documents its own
duplication of `scaffold.py`).
## Gotchas
- **Scope is Python + ruff (slice 060-01).** Node (eslint/prettier),
complexity, duplication, a dedicated code-health reviewer pass, and the CI
dogfood are later slices (060-02..05). A non-Python project with no
`.jig/lint-command` override degrades to the recommendation.
- **Exit `1` vs `2`.** Exit `1` means ruff ran and found issues — inspect
the summary. Exit `2` means no linter was resolvable or the resolved tool
failed to start (an environment issue) — don't conflate them with clean.
- **Ephemeral runs need a network/cache.** `uvx ruff` / `pipx run ruff`
fetch ruff on first use. If neither the binary nor a launcher is present,
the skill recommends rather than failing opaquely.
- **Tight summary, not the raw dump.** `check` parses ruff's JSON into a
count + top codes; it does not echo the full ruff output. Re-run ruff
directly when you need every finding's location.