Repo healthcheck — git hygiene, test status, and deep freshness verification of status/directive docs. Gracefully degrades when project-specific conventions (git-erg tickets, STATE.md, etc.) are absent.
Installs into .claude/skills of the current project.
Are you the author of Yawn?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/minhhaduong-yawn)
---
name: yawn
description: "Repo healthcheck — git hygiene, test status, and deep freshness verification of status/directive docs. Gracefully degrades when project-specific conventions (git-erg tickets, STATE.md, etc.) are absent."
disable-model-invocation: false
user-invocable: true
argument-hint:
---
Bounded, mostly mechanical audit work; choose the worker per the `route` skill (`skills/route/SKILL.md`).
# Yawn — repository healthcheck
For helper commands, set `IDH_ROOT="$(cd -P "$(dirname "<loaded-SKILL.md>")/../.." && pwd -P)"` in the same shell call. Replace `<loaded-SKILL.md>` with the absolute path the runtime supplied for this skill. This follows a projected skill symlink to the canonical checkout; do not derive the helper root from the project cwd.
Run a healthcheck on the current repository. Report results concisely — one line per check, flag anything abnormal.
This skill is user-level and must **gracefully degrade**: each check runs only if its prerequisites are present. Missing prerequisites yield a `skip` status with a one-line reason, never a fail.
## Data collection
First, run the mechanical probe to collect structured state:
```bash
python3 "$IDH_ROOT/scripts/project-state.py" "$(git rev-parse --show-toplevel)" --tests
```
Parse the JSON output. Use its fields to populate checks 1–12 below without re-running git commands — the probe covers everything through check 10 and checks 12–13; only check 11 requires additional file reads. If the script is missing or fails, fall back to ad-hoc commands per check.
## Checks
1. **Recent activity** — from `git.recent_commits`. List count and key themes.
2. **Open PRs** — from `prs.open` / `prs.items`. Skip if `prs.error`.
3. **Origin sync** — from `git.ahead` / `git.behind`. Skip if no remote. Also explicitly check local `main` vs `origin/main`:
```bash
git rev-list --left-right --count main...origin/main
```
Flag `warn` if local main is behind origin/main (needs pull) or ahead (unpushed commits on main).
4. **Branch hygiene** — from `branches.local` / `branches.remote` / `branches.details`. For each local branch:
- **Non-ticket branches** with `commits_beyond_default > 0`: flag as cleanup candidate.
- **Closed-ticket branches** (name matches `t<NNNN>-*` and ticket NNNN has a `Closed:` line) where the merge probe succeeds: classify as **fix-now** — safe to delete. Run this probe per branch:
```bash
bash "$IDH_ROOT/skills/molt/ticket-sweep.sh" merged-probe <branch>
```
Exit 0 means the branch landed on main: ancestry (`git merge-base --is-ancestor`) or, as an equal second signal, the merge request number from the ticket's `Closed:` header appearing as `(#N)` in a subject on main. Emit one fix-now bullet per matching branch: `Delete local branch \`<branch>\` (closed ticket <NNNN>, merged into main)`.
Note: the merge-request-subject signal stays because ancestry misses squash merges made before 2026-05-25, which left no ancestry; the probe tries both.
- **Closed-ticket branches** where the merge probe exits 1 (not merged) or 2 (`could-not-determine`: the branch maps to no ticket or the ticket records no merge request number, or `tickets/` or erg is missing): flag as cleanup candidate for manual review, not fix-now. Exit 2 is never a deletion.
5. **Worktrees** — from `worktrees`. Flag entries with `locked: true` whose lock pid is no longer running. For each worktree, also run:
```bash
git log <worktree-head>..<main-head> --oneline | wc -l
```
If the count is > 0, flag as `warn`: "worktree `<name>` is N commits behind main — filesystem state (tickets/, docs/) may be stale relative to main".
6. **Working tree** — from `git.clean` / `git.dirty_files`. List uncommitted files if dirty.
7. **Tests green** — from `tests.status` / `tests.detail`. Skip if `tests.runner == "none"`.
8. **Housekeeping** — from `housekeeping.state` / `housekeeping.age_hours` / `housekeeping.branch`. Report `ok` if `state == "clean"` (age ≤ 12 h), `warn` if `state == "needed"` (overdue) or `state == "tidying"` (a `claude/housekeeping-*` branch is in flight). Skip if probe missing.
9. **Ticket archival** — from `tickets.closed_unarchived` (list of ticket IDs sitting in `tickets/*.erg` with a `Closed:` header, never moved to `tickets/closed/`). `ok` if empty; `warn` listing the IDs otherwise. This is the close-without-archive escape: a PR merged outside `erg-pr-merge` skips the `erg archive` step. Classify the fix as **fix-now** — `bash "$IDH_ROOT/skills/molt/ticket-sweep.sh" archive` (moves all closed tickets to `tickets/closed/` and stages the move; commit it). Skip if `tickets.error` is set or no `tickets/` directory.
10. **Hook freshness** — from `hooks`. Skip (silent) if `hooks.hooks_path` is null or `hooks.in_worktree` is false — no working-tree `core.hooksPath`, so the stale-hook-in-worktree trap does not exist. `skip` with the reason if `hooks.error` is set (no remote, detached HEAD). `warn` if `hooks.stale_files` is non-empty, naming the files: "working-tree hooks differ from origin/<default> — worktrees will run the main checkout's stale hooks until it is updated". `ok` if in-worktree hooks match the default branch. Advisory only, never a hard gate.
11. **Docs freshness (deep verification)** — cross-check status/directive docs (`STATE.md`, `README.md`, etc.):
- **Staleness** — flag docs whose content predates recent repo activity
- **Ticket cross-check** — references to tickets whose status contradicts
the doc (todo but closed, done but open, broken ref). Skip if no `.erg` tickets.
(Use `tickets.ready_ids` for the initial ticket list; only read specific tickets
whose status contradicts a doc reference.)
- **PR cross-check** — PRs described as pending but already merged/closed.
Use `prs.items` from the probe. Skip if `prs.error`.
- **Count consistency** — "N open tickets" claims vs `tickets.open` from probe
- **Stale forward promises** — resident skill and rule bodies are directive
docs too. Grep `skills/*/SKILL.md` and `rules/` for `until the`, `not yet
drafted`, `when converted`, `for now`, then confront each promise with the
decision behind it: a study voided, a ticket closed wontfix, work never
scheduled. Such text reads as pending work and agents act on it — a flag
from an A/B cancelled on 2026-07-14 was still being recommended as a live
cost lever two months later (PR #856). Judgment call, deliberately
unguarded: report the stale ones, never auto-edit.
12. **Session scratch (host-level)** — from `session_scratch`. The one check
that is not about this repo: every session gets a scratch directory under
the user's temp root, one per distinct cwd it visits, and nothing but the
`SessionEnd` hook removes one. `skip` (silent) if `session_scratch.exists`
is false. `warn` when `session_scratch.status` is `warn`, quoting
`session_scratch.reasons`:
- **Usage** — `root_bytes` against `cap_bytes`. Read `cap_source` before
quoting the percentage: where the temp root is a quota-enabled tmpfs the
cap is *inferred* from the init system's default per-user share, because
the quota tools cannot read a tmpfs quota. Say "inferred cap", never
"quota reads". A fill here is not cosmetic. It kills the Bash tool with
EDQUOT in every session of that user at once (2026-09-06, three sessions
and a reboot; ticket 0854).
- **Truncation** — read `session_scratch.truncated` before quoting any byte
figure. When it is true the directory walk stopped at its entry ceiling,
so `root_bytes` and `orphan_bytes` are a floor rather than a total; the
probe says so in `reasons` and its status is `warn`, never `ok`.
- **Orphans** — `orphan_count` / `orphan_bytes`, session directories no live
process owns (no open descriptor inside them, no process cwd'd there, and
the id named by nothing running). Classify as **fix-now**: `Sweep N orphan
session scratch directories (X reclaimable)`. /molt applies it.
- In the warning, name the relocation knob but do not set it: moving the
temp root is an operator decision, since the root must also stay short —
sandbox socket paths are built under it. The runtime knob is
<!-- harness-extension-point -->
`CLAUDE_CODE_TMPDIR`, which takes precedence over `TMPDIR`.
## Output format
```
## Yawn — {date}
| Check | Status | Detail |
|------------------|--------|------------------------------|
| Recent activity | ... | N commits (last 12h) |
| Open PRs | ... | N open |
| Origin sync | ... | synced / ahead N / ... |
| Branch hygiene | ... | N local, N remote |
| Worktrees | ... | N active |
| Working tree | ... | clean / N changes |
| Tests green | ... | N passed / K failed |
| Housekeeping | ... | clean / needed / tidying |
| Ticket archival | ... | N closed-but-unarchived |
| Hook freshness | ... | in sync / N stale / skip |
| Docs freshness | ... | N docs scanned, K stale refs |
| Session scratch | ... | N orphan dirs, X of cap |
```
Use `ok` for normal status, `warn` for attention-needed, `fail` for problems, `skip` for gracefully-degraded checks (detail column explains why).
If docs freshness is warn/fail, list each stale finding under the table as one bullet per finding, with the doc, line reference, and the fix (e.g., `STATE.md:39 — ticket 0095 listed as TODO but Status: closed (PR #259)`). This detail is the point of the deep check — do not compress it into a single line.
After the table (and any stale-findings list), add a one-line summary verdict.
## Action plan
After the verdict, if any findings are warn or fail, emit an **Action plan** section.
Classify every finding into exactly one of three categories:
- `fix-now` — trivial, no branch needed, reversible, no design decision: do it in the
current session immediately after the user says "do it"
- `open-ticket` — multi-file, needs a branch, requires a design decision, or worth
tracking across sessions: create a ticket. Apply the severity floor (`rules/workflow.md` § Autonomous action), in every repo — findings that don't block a merge, corrupt state, or bite the science are reported in the run summary, not ticketed.
- `skip` — cosmetic, already tracked elsewhere, or not worth acting on now: note why
Format:
```
## Action plan
**fix-now**
- {one-line description of fix}
**open-ticket**
- {title} — {one-line reason it needs a ticket}
**skip**
- {finding} — {reason}
```
Omit a heading if it has no entries. If all checks are `ok`, omit the Action plan entirely.
> **Contract** — `/molt` parses this section programmatically. The bold headings `**fix-now**`, `**open-ticket**`, `**skip**` are a stable interface; do not rename or reformat them.
Once the Action plan is fully effected, propose: "Surface remaining nits?"
13. **Disk headroom at session start** — from `disk_space`. Warn if either
`/tmp` or `/home` has less than 10% free, naming the mount and reported
percentage. A missing/unreadable mount is unknown, not healthy. This is an
advisory warning; it never blocks startup.