Skip to content
Back to skills

Health

ASecurity

Holistic project audit across endpoints, versions, docs, security, quality, CI, and library dimensions. Use when checking overall project health or running a periodic cross-cutting audit. Trigger on "project health", "audit the project", "health check", "is this project in good shape".

  • 25 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 6, 2026
documentationpythonrustgoshellbashnextjsnodeexpressfastapidocker

Works with

  • claude code
  • vscode
  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned June 6, 2026

npx -y skills add matteocervelli/llms --skill health --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Health?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Health
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/matteocervelli-health/badge)](https://www.skillsdirectory.com/skills/matteocervelli-health)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: health
description: Holistic project audit across endpoints, versions, docs, security, quality, CI, and library dimensions. Use when checking overall project health or running a periodic cross-cutting audit. Trigger on "project health", "audit the project", "health check", "is this project in good shape".
allowed-tools: Read, Bash, Grep, Glob
---

# /health — Project Health Audit

Orchestrates a cross-cutting audit across all project dimensions. Designed to run periodically via
the native `/loop` bundled skill.

**Architecture — hybrid orchestrator:**

- **Native checks** (no dedicated sub-skill exists for these): `endpoints`, `versions`, `docs`
- **Pure delegation** (sub-skill owns the logic): `security` → `/security-verify scan`, `quality` → `/quality-check`, `library` → `/library`, `releases` → `/release verify tags`, `ci` → `/ci-setup audit`, `website` → `/website-health`

## Quick Start

```
/health                    # auto-detect project, show summary
/health full               # comprehensive audit across all dimensions
/health endpoints          # check routes vs docs
/health versions           # version string consistency
/health docs               # CLAUDE.md + README staleness + license check
/health roadmap            # bidirectional sync: ROADMAP.md ↔ GitHub Issues ↔ CHANGELOG
/health license            # check LICENSE file; advisor mode if missing
/health security           # delegate to /security-verify scan
/health quality            # delegate to /quality-check + tests + coverage
/health techdebt           # delegate to /techdebt (duplicates, dead code, TODOs, oversized functions)
/health deps               # delegate to /deps scan (outdated deps, CVEs, decision coverage)
/health hygiene            # filesystem lint: gitignore gaps, orphans, structure drift, root clutter
/health library            # delegate to /library scan + sync + drift + new
/health releases           # delegate to /release verify tags
/health ci                 # delegate to /ci-setup audit (Forgejo workflow drift, conditional)
/health agents             # review unreviewed agent-created issues (ai-suggested label)
/health agents triage      # interactive triage: dismiss, promote, or skip each signal
/health docker             # delegate to /docker-audit (if Dockerfile* or compose.yaml detected)
/health website            # delegate to /website-health (if website project detected)
/health branches           # stale branch audit — local, GitHub, Forgejo
/health issues             # open GitHub BACKLOG issues for findings
```

## Loop Integration

```
/loop 1h /health full      # hourly comprehensive audit
/loop 1d /health security  # daily security check
/loop 1d /health library   # daily library drift check
```

Uses the native Claude Code `/loop` bundled skill (CronCreate under the hood).
Tasks are session-scoped and auto-expire after 3 days.

---

## Subcommand: `/health` (summary)

Auto-detect project type and print a one-line status per dimension.

**Steps:**

1. Detect project type: check for `pyproject.toml` (Python), `package.json` (Node), `Cargo.toml` (Rust), MCP server markers (`server.py`, `mcp.json`)
2. Run each dimension sequentially (each sub-check runs to completion; delegated checks like security/library may take 30-300s)
3. Print consolidated summary with overall gate

**Output:**

```
## Health Summary — <project-name> — <date>

Endpoints  [PASS]  14 routes, all documented
Versions   [WARN]  pyproject.toml (0.3.1) ≠ README badge (0.3.0)
Docs       [PASS]  CLAUDE.md current, README current
Security   [PASS]  No critical/high findings
Quality    [PASS]  0 lint errors, 45/45 tests, 87% coverage
Tech Debt  [PASS]  0 critical findings
Hygiene    [WARN]  .coverage not in .gitignore; 2 unexpected top-level dirs
Releases   [PASS]  3 tags, all match CHANGELOG + GH/FJ releases + artifacts (pypi: 3/3)
Agents     [WARN]  12 unreviewed signals across 3 repos — run /health agents triage
Branches   [WARN]  3 local merged, 1 remote stale on Forgejo (if git repo)
Docker     [WARN]  Non-root USER missing in Dockerfile (if Docker files detected)
Website    [PASS]  7/7 dimensions pass (if website project detected)

Overall: WARN — 3 FIX-NOW (apply immediately) · 2 TRACK (open issues)
```

---

## Subcommand: `/health endpoints`

Compare implemented routes vs what is documented in README or API docs.

**Detection patterns:**

| Stack           | Pattern                                                            |
| --------------- | ------------------------------------------------------------------ |
| FastAPI         | `@router.(get\|post\|put\|delete\|patch)`, `@app.(get\|post\|...)` |
| Click CLI       | `@click.command`, `@cli.command`, `@<group>.command`               |
| MCP             | `@mcp.tool`, `@server.tool`, `@server.list_tools`                  |
| Express/Next.js | `app.(get\|post\|...)`, `router.(get\|...)`, `pages/api/**` glob   |

**Steps:**

1. Grep codebase for route/command decorators → build implemented set
2. Parse README "API Reference", "Commands", or "Endpoints" section → build documented set
3. Diff: implemented ∩ documented = OK; implemented \ documented = undocumented; documented \ implemented = dead docs

**Gate:**

- `PASS`: all implemented routes documented
- `WARN`: undocumented routes exist (non-security-critical)
- `FAIL`: undocumented route with auth/data-mutation pattern (POST/PUT/DELETE without docs)

---

## Subcommand: `/health versions`

Cross-check all version strings across manifest files and README.

**Version file detection** (reuses `/release changelog` Step 6 patterns):

| File          | Path                                                              |
| ------------- | ----------------------------------------------------------------- |
| Python        | `pyproject.toml` → `[project] version` or `[tool.poetry] version` |
| Node          | `package.json` → `.version`                                       |
| Rust          | `Cargo.toml` → `[package] version`                                |
| Legacy Python | `setup.cfg` → `version =`                                         |
| README badge  | `shields.io/badge/version-X.Y.Z` or similar                       |

**Steps:**

1. Find all version files via glob
2. Extract version string from each
3. Compare — flag any that differ
4. Output version matrix table

**Gate:**

- `PASS`: all versions match
- `WARN`: README badge differs (cosmetic)
- `FAIL`: any two production manifests disagree (pyproject.toml, package.json, Cargo.toml, setup.cfg — README badge excluded from FAIL since it's cosmetic)
  Note: normalize ranges (`^1.2` → `1.2.x`) before comparing; VCS/path deps are flagged as WARN with a note to pin

---

## Subcommand: `/health docs`

Check required documentation exists at the root, is in the right location, and is not stale.
Also audits user-facing docs via `/docs audit` (dual mode: Starlight for apps, markdown for libs/services).

**Delegates to:**

- `/docs audit` — user-facing docs (dual mode auto-detected from repo):
  - **App mode** (`app-*`): `site/` present? Starlight pages stale? links rotti? language parity IT↔EN?
- `/documentation-updater` — checklist completeness (only when all required files exist)
- `/registry stale 30` — stale `act`-status skills (>30 days); `plan`/`do`/`check` with null dates are excluded (not yet in validation phase)

### Step 0 — Existence check

Required root-level docs and their init path when missing:

| File            | Severity if missing | Init suggestion                               |
| --------------- | ------------------- | --------------------------------------------- |
| `CLAUDE.md`     | `FAIL`              | `/claude-md-management:claude-md-improver`    |
| `README.md`     | `FAIL`              | `/documentation-updater`                      |
| `LICENSE`       | `WARN`              | `/health license` (advisor mode)              |
| `ROADMAP.md`    | `WARN`              | `/documentation-updater` (architecture docs)  |
| `TECH-STACK.md` | `WARN`              | `/documentation-updater` (configuration docs) |

For each missing file: report the finding, state the severity, and offer the init command.

**LICENSE note:** If `LICENSE` is missing, note it as WARN and print: "Run `/health license` to get an interactive advisor that will recommend the right license based on your project type."

### Step 1 — Location check

Search for required docs in non-root locations and flag misplaced copies:

```bash
for f in CLAUDE.md README.md ROADMAP.md TECH-STACK.md; do
  # Check root
  root_present=$([ -f "$f" ] && echo "yes" || echo "no")
  # Find any copies elsewhere (exclude .git, node_modules, .venv)
  elsewhere=$(find . -name "$f" \
    -not -path "./.git/*" \
    -not -path "./node_modules/*" \
    -not -path "./.venv/*" \
    -not -path "./.archive/*" \
    -not -maxdepth 1 2>/dev/null)
  # If found in subdir but not at root → WARN: propose move
  # If found in subdir AND at root → INFO: note the duplicate
done
```

Report as `WARN` with the path and suggest: "Move to project root and update any links."

### Step 2 — Staleness check

Only runs for files confirmed present at root. Uses Unix timestamps to avoid sign-flip bug when file is absent.

```bash
# Last CLAUDE.md/README change vs last code change — use Unix timestamps for arithmetic
DOC_TS=$(git log -1 --format="%ct" -- CLAUDE.md README.md ROADMAP.md TECH-STACK.md 2>/dev/null || echo 0)
CODE_TS=$(git log -1 --format="%ct" -- src/ app/ lib/ 2>/dev/null || echo 0)
DIFF_DAYS=$(( (CODE_TS - DOC_TS) / 86400 ))
# Positive DIFF_DAYS = code newer than docs (stale)
# Only evaluate if DOC_TS > 0 (files exist in git history)
```

**Gate:**

- `PASS`: all required files present at root AND `DIFF_DAYS` < 7
- `WARN`: `ROADMAP.md` or `TECH-STACK.md` missing OR any misplaced doc OR `DIFF_DAYS` ≥ 14
- `FAIL`: `CLAUDE.md` or `README.md` missing OR `DIFF_DAYS` ≥ 28 OR registry has >5 stale `act`-status skills (exclude `plan`/`do`/`check` with null `last_manual_test` — expected pre-validation)

### Step 2.5 — Instruction file structure check

Runs after staleness check. Checks CLAUDE.md, AGENTS.md, or both (whichever are present).

**a) Line count**

```bash
for f in CLAUDE.md AGENTS.md; do
  [ -f "$f" ] && wc -l < "$f" || echo 0
done
```

- `PASS`: ≤150 lines each
- `WARN`: 151–250 lines → "Consider extracting coding patterns to `.claude/rules/`"
- `FAIL`: >250 lines → "Instruction file bloated — split into root + rules or per-package files"

Line count FAIL escalates the overall Docs gate to FAIL.

**b) Monorepo without per-package instruction files**

```bash
# Detect monorepo
is_monorepo=$([ -f pnpm-workspace.yaml ] || [ -f turbo.json ] || [ -f lerna.json ] && echo yes || echo no)
# Count packages with their own CLAUDE.md
pkg_count=$(find apps packages libs -maxdepth 2 -name "CLAUDE.md" -o -name "AGENTS.md" 2>/dev/null | wc -l)
# Count total packages
total_pkgs=$(find apps packages libs -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
```

- `WARN` if monorepo detected AND `pkg_count < total_pkgs / 2`
- Message: "Monorepo detected — consider per-package CLAUDE.md for packages missing one"

Monorepo check is WARN-only (advisory).

**c) Dual-file alignment**

If both `CLAUDE.md` and `AGENTS.md` exist:

```bash
# Extract ## section headers from both files
claude_headers=$(grep "^## " CLAUDE.md 2>/dev/null | sort)
agents_headers=$(grep "^## " AGENTS.md 2>/dev/null | sort)
overlap=$(comm -12 <(echo "$claude_headers") <(echo "$agents_headers") | wc -l)
total=$(echo "$claude_headers" "$agents_headers" | sort -u | wc -l)
```

- `WARN` if overlap/total > 0.30 (>30% section headers shared)
- `INFO` always: "Both CLAUDE.md and AGENTS.md present — CLAUDE.md = architecture/context, AGENTS.md = build/style"

### Step 3 — User-facing docs audit (dual mode)

Delegates to `/docs audit` which auto-detects mode from repo:

**App mode** (`app-*` repos):

- Checks `site/` exists with `astro.config.mjs`
- Reads `site/docs-registry.yaml` → counts pages by status
- Verifies staleness: code newer than docs by >30 days?
- Checks language parity: IT vs EN page count
- Checks for broken internal links


- Checks `docs/user-guide/` exists
- Reads `docs/docs-registry.yaml` if present
- Verifies staleness: code changes vs `docs/user-guide/` last update
- Reports missing essential docs (getting-started, api-reference for services)

**Gate (combined with Steps 0–2.5):**

- `PASS`: Steps 0–2.5 pass AND `/docs audit` returns PASS
- `WARN`: Steps 0–2.5 pass but `/docs audit` returns WARN (stale pages, missing EN, etc.)
- `FAIL`: Steps 0–2.5 fail OR `/docs audit` returns FAIL (required pages missing, site/ absent for app)


---

## Subcommand: `/health security`

Delegates to `/security-verify scan` (fast SAST) and `/security-verify audit` (adversarial logic review).

```
Step 1: Invoke /security-verify scan    → pattern-based SAST (seconds)
Step 2: Invoke /security-verify audit   → adversarial logic review (minutes, spawns subagent)
```

Output of each step is shown. Gate:

- `PASS`: scan clean AND audit finds no HIGH/CRITICAL
- `WARN`: scan clean but audit finds MEDIUM findings
- `FAIL`: scan finds critical/high OR audit finds HIGH/CRITICAL

When invoked from `/health full`, both steps run. When invoked from `/loop 1d /health security`, both steps run. `scan` alone is the pre-commit gate (not this subcommand).

---

## Subcommand: `/health library`

Delegates entirely to `/library`. Runs four sub-checks in sequence:

```
Invoke: /library sync   → version matrix (current project vs local source)
Invoke: /library drift  → duplication findings
Invoke: /library scan   → extraction candidates
Invoke: /library new    → repeated pattern proposals (≥3x)
```

Summarizes findings into single `Library [PASS|WARN|FAIL]` line for the health report.


---

## Subcommand: `/health releases`

Verify release integrity — tag↔changelog↔GitHub-release consistency.

Always derive owner and repo from the Forgejo push remote:

```bash
FJ_OWNER=$(echo "$FORGEJO_REMOTE" | sed 's|.*:\(.*\)/.*\.git|\1|')
FJ_REPO=$(basename "$FORGEJO_REMOTE" .git)
```


**Delegates to:** `/release verify tags`

```
Invoke: /release verify tags
```

Output is passed through verbatim. Gate inherits from `/release verify`:

- `PASS`: all tags ↔ sections match, GH/FJ releases present, all expected artifacts published
- `WARN`: missing GH or Forgejo release for a tag, OR expected artifact MISSING (publish workflow exists but package absent)
- `FAIL`: tag without section OR section without tag

---

## Subcommand: `/health ci`



**Delegates to:** `/ci-setup audit`

```
Invoke: /ci-setup audit
```

Detection check:

```bash
```

Output is collapsed into a single gate line for the consolidated report:

```
CI  [PASS]  4 workflows match template · secrets: REGISTRY_TOKEN ✓ PYPI_TOKEN ✓
CI  [WARN]  missing release.yml · REGISTRY_TOKEN absent
CI  [FAIL]  legacy unified pattern detected · REGISTRY_TOKEN missing
```

Gate thresholds:

- `PASS`: all expected workflows present, all match templates, REGISTRY_TOKEN provisioned
- `WARN`: minor drift (e.g., missing `release.yml` but quality workflows present) OR REGISTRY_TOKEN missing
- `FAIL`: legacy patterns (`old-unified-pattern`, `legacy-setup-deps`), missing security workflow, OR REGISTRY_TOKEN missing

Run `/ci-setup fix` to apply any fixes surfaced by this check.

---

## Subcommand: `/health quality`

Periodic code quality snapshot — lint, tests, coverage.

**Delegates to:**

- `/quality-check` — linting, formatting, type checking
- Test runner (auto-detected: `pytest` / `npm test` / `cargo test`)
- Coverage tool (auto-detected: `pytest --cov` / `npm test --coverage`)

**Steps:**

1. Detect project type (Python/Node/Rust) from manifest files
2. Run lint + format check (`ruff` / `eslint` / `clippy`)
3. Run fast test suite (`-m "not integration and not e2e"`) — full suite is CI's domain
4. Measure coverage (informational only locally; threshold enforced on CI)

**Gate:**

- `PASS`: 0 lint errors, all fast tests pass
- `WARN`: 1-5 lint errors OR fast tests partially failing
- `FAIL`: fast tests failing OR > 5 lint errors

**Note:** This is a periodic health snapshot. For commit-adjacent validation, use `/pre-commit` which also includes security scanning and changelog updates. Coverage >= 80% is a CI gate, not a local health gate.

---

## Subcommand: `/health techdebt`

Delegates entirely to `/techdebt`. Scans for accumulated technical debt across the codebase.

```
Invoke: /techdebt
```

**Checks delegated:**

- Duplicated code blocks (≥10 identical lines across files)
- Dead code (unused functions, unreachable branches, commented-out blocks)
- TODOs / FIXMEs / HACKs left in code
- Oversized functions (>50 lines) and files (>500 lines per code-quality.md)

Output is passed through verbatim. Gate derived from finding counts:

- `PASS`: 0 critical findings (no duplicates >20 lines, no dead code, ≤3 TODOs)
- `WARN`: 1-5 duplicate blocks OR 1-10 TODOs OR 1-3 oversized functions
- `FAIL`: >5 duplicate blocks OR >10 TODOs OR >3 oversized functions OR dead code in critical paths
- `SKIP`: No source files detected (config-only or docs-only repo)

**Note:** Tech debt TRACK findings open with `--milestone TECH-DEBT` (not BACKLOG) via `/health issues`.

---

## Subcommand: `/health deps`

Delegates entirely to `/deps scan`. Audits dependency freshness, security posture, and
decision coverage — how many outdated deps have a recorded update/defer/skip rationale.

```
Invoke: /deps scan
```

**What it checks:**

- Outdated packages (Python, Node.js, Rust, Go) — classified by semver bump severity
- CVEs and EOL packages via `pip audit` / `npm audit` / `cargo audit`
- Active decisions in Atrium — marks assessed deps, flags overdue deferred decisions

Output gate derived from `/deps scan` results:

- `PASS`: 0 critical (CVE/EOL) · 0 unassessed HIGH · all deferred decisions within review date
- `WARN`: 0 critical · unassessed HIGH/MEDIUM exist · OR ≥1 decision due within 7 days
- `FAIL`: Any CRITICAL (CVE/EOL) · OR any overdue deferred decision
- `SKIP`: No manifest files found (config-only or docs-only repo)

**Atrium offline:** Gate still works (local scan only). Decision coverage shows as `n/a`.

**Note:** When FAIL on a TRACK finding (CVE, overdue decision), open issue with `--milestone TECH-DEBT`. Patch/minor updates are FIX-NOW — bump and test inline.
Run `/deps decide` after the audit to record decisions for unassessed findings.

---

## Subcommand: `/health hygiene`

Filesystem lint — catches structural rot, gitignore gaps, orphaned artifacts, and directory drift.
This is a **native check** (no delegation — uses Glob, Grep, Bash directly).

### Check A — Gitignore hygiene

**A1 — Untracked files matching known noise patterns per detected stack:**

```bash
git ls-files --others --exclude-standard
```

Cross-reference against known noise patterns:

| Stack       | Patterns that should be in .gitignore                                            |
| ----------- | -------------------------------------------------------------------------------- |
| All         | `.DS_Store`, `Thumbs.db`, `*.swp`, `*~`, `.env`, `.env.local`, `.env.*.local`    |
| Python      | `__pycache__/`, `*.pyc`, `.venv/`, `*.egg-info/`, `dist/`, `build/`, `.coverage` |
| Node        | `node_modules/`, `.next/`, `.nuxt/`, `.output/`, `dist/`                         |
| Rust        | `target/`                                                                        |
| IDE         | `.idea/`, `.vscode/` (except shared settings), `*.code-workspace`                |
| Testing     | `.pytest_cache/`, `.mypy_cache/`, `.ruff_cache/`, `coverage/`, `htmlcov/`        |
| Claude Code | `projects/`, `plans/`, `tasks/`, `debug/`, `history.jsonl`, `memory-index.db*`   |

Report each match with the suggested `.gitignore` line to add.

**A2 — Tracked files that match .gitignore patterns (`git rm --cached` candidates):**

```bash
git ls-files -i --exclude-standard 2>/dev/null
```

Each result is a FAIL finding with the fix command: `git rm --cached <file>`.

**A3 — Missing gitignore entries for detected stack:**

Detect stack from manifest files (`pyproject.toml` → Python, `package.json` → Node, `Cargo.toml` → Rust).
Compare required patterns (table above) against actual `.gitignore` content. Report missing as WARN.

### Check B — Orphaned and stale files

**B1 — Backup/temp files tracked:**

```bash
git ls-files | grep -E '\.(bak|tmp|orig|swp|swo)$|~$'
```

**B2 — Zero-byte tracked files** (exclude `.gitkeep`, `__init__.py`, `py.typed`):

```bash
git ls-files | while read f; do [ -f "$f" ] && [ ! -s "$f" ] && echo "$f"; done
```

**B3 — OS artifacts in tracked files:**

```bash
git ls-files | grep -E '\.DS_Store$|Thumbs\.db$|desktop\.ini$'
```

### Check C — Directory structure audit

**C1 — Parse expected structure from README tree block:**

```bash
grep -n "^├──\|^└──\|^│" README.md 2>/dev/null
```

If a tree block exists: extract directory names → build expected set.
If no tree block: skip C2 (no reference to compare against).

**C2 — Compare actual vs expected top-level dirs:**

```bash
ls -d */ 2>/dev/null | sed 's|/$||'
```

- `EXPECTED + PRESENT` → OK
- `EXPECTED + MISSING` → WARN
- `PRESENT + NOT EXPECTED` → INFO (undocumented, not necessarily wrong)

**C3 — Misplaced files:**

| Signal                                  | Check                            |
| --------------------------------------- | -------------------------------- |
| `*.py` at root when `src/` exists       | Python file may belong in `src/` |
| `*.test.*` at root when `tests/` exists | Test file may belong in `tests/` |

Root-level config files (`setup.py`, `conftest.py`, `Makefile`, `pyproject.toml`) are excluded.

### Check D — Large and binary files

**D1 — Large tracked files:**

```bash
git ls-files | while read f; do
  [ -f "$f" ] && size=$(stat -f%z "$f" 2>/dev/null || stat -c%s "$f" 2>/dev/null)
  [ "$size" -gt 1048576 ] && echo "$f ($((size / 1048576))MB)"
done
```

- > 1MB: INFO | >10MB: WARN | >50MB: FAIL

**D2 — Binary files without LFS:**

Check tracked binaries (MIME type `application/`, `image/`, `audio/`, `video/`) against `.gitattributes`.
If `.gitattributes` has no LFS rule for the file type: WARN (small binaries in `docs/` <100KB excluded).

### Check E — Root-level clutter

**E1 — Root file count:**

```bash
git ls-files --full-name | grep -cv '/'
```

- ≤15: PASS | 16-25: INFO | >25: WARN

**E2 — Nested .git directories** (accidental submodules):

```bash
find . -name ".git" -type d -not -path "./.git" 2>/dev/null
```

If found: WARN — "nested .git detected; verify with `git submodule status`."

**E3 — Orphaned lock files** (lock without corresponding manifest):

```bash
[ -f "package-lock.json" ] && [ ! -f "package.json" ] && echo "package-lock.json"
[ -f "poetry.lock" ] && [ ! -f "pyproject.toml" ] && echo "poetry.lock"
[ -f "uv.lock" ] && [ ! -f "pyproject.toml" ] && echo "uv.lock"
[ -f "Cargo.lock" ] && [ ! -f "Cargo.toml" ] && echo "Cargo.lock"
```

### Check F — Stash audit

```bash
git stash list
```

If the stash is empty: PASS (no output).

For each stash entry, capture:

- **Index** (`stash@{N}`)
- **Branch** it was stashed from (from the `WIP on <branch>` part)
- **Message** (commit description)
- **Age** — compute from reflog: `git log -g --format="%ci" stash@{N} | head -1`

**Age classification:**

| Age       | Severity |
| --------- | -------- |
| <7 days   | INFO     |
| 7–30 days | WARN     |
| >30 days  | WARN     |

**Output (when stash non-empty):**

```
**Stash:**
- ⚠ stash@{0}: "WIP on main: fix auth" — 12 days old
- ⚠ stash@{1}: "WIP on feature/x: draft" — 45 days old
```

Any stash entry triggers at minimum WARN — a non-empty stash means forgotten work. List the `git stash show stash@{N}` command next to each entry so the user can see what's in it.

### Gate

- `PASS`: 0 WARN/FAIL findings
- `WARN`: tracked OS artifacts, missing gitignore for stack, >25 root files, >10MB file, binary without LFS, OR any stash entry
- `FAIL`: tracked file matching .gitignore, >50MB file, orphaned lock file, nested .git (non-submodule)
- `SKIP`: Not a git repository

### Output format

```
### Hygiene [PASS|WARN|FAIL]

**Gitignore:**
- ✓ .gitignore covers detected stack (Python)
- ⚠ Missing entry: `.coverage` (Python testing artifact)
- ✗ Tracked file matches .gitignore: `.DS_Store` → run: git rm --cached .DS_Store

**Orphaned files:**
- ✓ No backup/temp files tracked
- ✓ No OS artifacts tracked

**Structure:**
- ✓ 6/6 expected directories present (per README tree)
- ℹ Unexpected top-level: `commands/`, `shared/` (not in README tree — may be intentional)

**Large files:**
- ✓ No files >1MB tracked

**Root cleanliness:**
- ✓ 12 root-level files (clean)
- ✓ No nested .git directories
- ✓ No orphaned lock files

**Stash:**
- ✓ Stash is empty
```

**Note:** Hygiene TRACK findings (orphaned lock files, nested .git) open with `--milestone TECH-DEBT`. FIX-NOW hygiene items (.gitignore gaps, tracked artifacts) are fixed inline — no issue needed.

---

## Subcommand: `/health issues`

Open GitHub issues **only for TRACK findings** — findings that cannot be resolved immediately
(structural work, investigation required, multi-session effort, or delegated sub-skills).

**Never open issues for FIX-NOW findings.** Those must be fixed in-place first.

**For each TRACK finding:**

1. Format as `[BACKLOG] [P2] #NEW <finding summary>` per github-workflow.md convention
   (permanent milestones like BACKLOG, TECH-DEBT, BUG have no order suffix;
   sprint/version milestones use `|ORDER`, e.g. `[SPRINT-1|03]`)
2. Propose `gh issue create` command with `--milestone BACKLOG`, `--label enhancement`
3. Ask user to confirm **all at once** — one Y/n for the full batch, not per-finding

**Shell safety — titles with metacharacters:** Issue titles may contain `|` (from
`[MILESTONE|ORDER]` convention on sprint/version milestones). Always use **single quotes**
for `--title` values to prevent shell interpretation of `|`, `$`, backticks, and other
metacharacters. If the title itself contains a single quote, escape it as `'\''`.

**Example:**

```bash
gh issue create \
  --title '[BACKLOG] [P2] Undocumented POST /upload route' \
  --body "Found by /health endpoints on 2026-03-28. Needs API docs update." \
  --label "enhancement" \
  --milestone "BACKLOG"
```

---

## Subcommand: `/health agents`

Native check — no delegation. Scans all repos with GitHub remotes for open issues labeled `ai-suggested` (auto-created by the session analyzer nightly job).

**Steps:**

1. Discover repos: for each directory in `~/dev/*/` plus `~/.claude`, run:

   ```bash
   git -C <dir> remote get-url origin 2>/dev/null
   ```

   Extract `owner/repo` slugs via regex. Deduplicate.

2. For each repo with a GitHub remote, run:

   ```bash
   gh issue list -R owner/repo --label ai-suggested --state open \
     --json number,title,labels,createdAt \
     --jq '.[] | {number, title, createdAt, kind: ([.labels[].name] | map(select(. == "bug" or . == "tech-debt" or . == "enhancement")) | first)}'
   ```

3. Group by repo, then by kind (`bug`, `tech-debt`, `enhancement`).

4. Compute age of oldest issue per repo.

5. Print summary table:

   ```
   ### Agents [PASS|WARN|FAIL]

   | Repo                   | Bugs | Tech-Debt | Enhancements | Total | Oldest |
   |------------------------|------|-----------|--------------|-------|--------|
   | claude-dotfiles        | 8    | 2         | 1            | 11    | 5d ago |

   Total: 15 unreviewed signals across 2 repos — run `/health agents triage` to review
   ```

6. If WARN/FAIL: suggest `/health agents triage`.

**Gate:**

- `PASS`: 0 open `ai-suggested` issues
- `WARN`: 1–15 open issues across all repos
- `FAIL`: >15 open issues OR any issue older than 14 days
- `SKIP`: No GitHub remote in current repo (still runs globally from project scan)

---

## Subcommand: `/health agents triage`

Interactive triage mode. Works across all repos, not just the current one.

**Steps:**

1. Run the same repo discovery and issue query as `/health agents`.
2. For each repo with open issues, print the issue list grouped by kind.
3. For each issue (or allow batch selection), present 3 actions:
   - **Dismiss** — close as not actionable:
     ```bash
     gh issue close N -R owner/repo --comment "Dismissed during /health agents triage — not actionable or already fixed."
     ```
   - **Promote** — make it a human-owned issue (ask for target milestone: BACKLOG, BUG, SPRINT-N):
     ```bash
     gh issue edit N -R owner/repo \
       --remove-label ai-suggested \
       --milestone <chosen-milestone>
     ```
   - **Skip** — leave as-is for next triage session.

4. After all repos are processed, print updated counts.

**Key invariant:** promoting removes the `ai-suggested` label → the issue becomes human-owned and exits the agents check. Dismissing closes it. Both actions reduce the count.

---

## Subcommand: `/health branches`

Stale branch audit across local, GitHub, and Forgejo remotes. Native check — no delegation.

**Steps:**

### Step 1 — Fetch and collect branch data

```bash
# Refresh remote refs (prune deleted remote-tracking branches)
git fetch --prune origin

# Local branches merged into main
git branch --merged main | grep -v '^\*\s*main$'

# Local branches NOT merged, sorted by last commit date
git for-each-ref --sort=committerdate refs/heads/ \
  --format='%(committerdate:iso8601) %(refname:short) %(objectname:short)'

# Remote tracking branches (all remotes)
git for-each-ref --sort=committerdate refs/remotes/ \
  --format='%(committerdate:iso8601) %(refname:short)'
```

### Step 2 — Forgejo remote branches via API


```bash
fj branch list "$OWNER/$REPO"
# Raw output if needed: fj --json branch list "$OWNER/$REPO" | python3 -c "
import json, sys, datetime
now = datetime.datetime.now(datetime.timezone.utc)
for b in json.load(sys.stdin):
    name = b['name']
    if name in ('main', 'master'): continue
    ts = b['commit']['timestamp']
    dt = datetime.datetime.fromisoformat(ts.replace('Z','+00:00'))
    age_days = (now - dt).days
    print(f'{age_days:4d}d  {name}')
"
```

### Step 3 — GitHub remote branches via gh CLI

If `gh auth status` succeeds AND repo has a GitHub remote:

```bash
gh api repos/{owner}/{repo}/branches --paginate \
  --jq '.[] | select(.name | test("^(main|master)$") | not) |
        [.name, .commit.commit.committer.date] | @tsv'
```

Correlate with open PRs: branches with an open PR are **not stale** regardless of age.

```bash
gh pr list --state open --json headRefName --jq '.[].headRefName'
```

### Step 4 — Classify findings

| Category                      | Condition                                   | Severity                 |
| ----------------------------- | ------------------------------------------- | ------------------------ |
| Local merged                  | Branch already merged into main             | FIX-NOW (safe to delete) |
| Local stale                   | Unmerged, no commit in >30 days, no open PR | WARN                     |
| Local stale                   | Unmerged, no commit in >60 days, no open PR | FAIL                     |
| Remote stale (GitHub/Forgejo) | No commit in >30 days, no open PR           | WARN                     |
| Remote stale (GitHub/Forgejo) | No commit in >60 days, no open PR           | FAIL                     |

Branches with an associated open PR on either forge are **excluded** from stale classification.
Branch `main` and `master` are always excluded.

### Step 5 — Suggest cleanup commands

For each FIX-NOW finding, print the exact delete command (do NOT run it — user confirms):

```bash
# Local merged branch
git branch -d <branch-name>

# Remote branch on GitHub
git push origin --delete <branch-name>

# Remote branch on Forgejo (via API — no direct git push since Forgejo is push-only remote)
fj branch delete {owner}/{repo} {branch}
```

**Gate:**

- `PASS`: No stale branches anywhere
- `WARN`: ≥1 local merged branch OR ≥1 remote stale >30 days
- `FAIL`: ≥1 branch stale >60 days on any remote
- `SKIP`: Not a git repo, or bare repo with no branches

---

## Subcommand: `/health website`

Conditional dimension — only runs if the project is a website. Also user-invokable directly: `/health website`.

**Detection** (check in order, use first match):

| Stack           | Marker                                                                           | baseURL extraction                          |
| --------------- | -------------------------------------------------------------------------------- | ------------------------------------------- |
| Astro Starlight | `astro.config.mjs` or `astro.config.ts` + `@astrojs/starlight` in `package.json` | `site:` field in astro config or `SITE` env |
| Astro           | `astro.config.mjs` or `astro.config.ts`                                          | `site:` field in astro config or `SITE` env |
| Hugo            | `hugo.toml` or `config.toml` with `baseURL`                                      | `baseURL` value                             |
| Netlify         | `netlify.toml`                                                                   | from underlying framework config            |
| Next.js         | `next.config.js` or `next.config.ts`                                             | `NEXT_PUBLIC_SITE_URL` env or localhost     |
| Gatsby          | `gatsby-config.js` with `siteMetadata.siteUrl`                                   | `siteUrl` value                             |
| Generic         | `package.json` with `homepage`                                                   | `homepage` value                            |

**Astro Starlight note:** When detected, pass `--stack astro-starlight` to `/website-health` so it can apply docs-site specific checks (sidebar nav, search index, versioned docs, translation completeness).

If no marker found: skip dimension (report `[SKIP] No website project detected`).

**Delegates to:** `/website-health <baseURL>` — invokes the quick summary mode (all 7 dimensions, L1 depth).

**Output normalization:** Extract the overall gate (PASS/WARN/FAIL) and one-line status from `/website-health` output. Collapse into the standard health report format:

```
Website  [PASS|WARN|FAIL]  <one-line summary from /website-health>
```

**Gate:**

- Inherits from `/website-health` overall gate (PASS/WARN/FAIL)

---

## Subcommand: `/health docker`

Conditional check — **delegates to `/docker-audit`**. Skipped if no Docker files are detected.

**Detection:**

```bash
# Check for any Docker-related files
find . -maxdepth 3 \
  \( -name "Dockerfile" -o -name "Dockerfile.*" -o \
     -name "compose.yaml" -o -name "compose.yml" -o \
     -name "docker-compose.yml" -o -name "docker-compose.yaml" \) \
  -not -path "./.git/*" \
  -not -path "./node_modules/*" \
  | head -10
```

If no files found: report `[SKIP] No Docker files detected` — stop here.

**Delegates to:** `/docker-audit [path]` — runs the full 10-category audit plus Compose checklist.

**Output normalization:** Extract the overall score and CRITICAL/HIGH/MEDIUM/LOW counts from `/docker-audit` output. Collapse into standard health format:

```
Docker  [PASS|WARN|FAIL]  <N> files · <score>/10 categories · <k> critical, <n> high findings
```

**Gate:**

- `PASS`: 0 critical, 0 high findings
- `WARN`: any HIGH finding (non-root user, no HEALTHCHECK, `:latest` tag, no `.dockerignore`)
- `FAIL`: any CRITICAL finding (secrets in Dockerfile ENV/ARG/COPY, root user with privileged port exposure)
- `SKIP`: no Docker files found

---

## Subcommand: `/health roadmap`

Bidirectional sync between `ROADMAP.md`, GitHub Issues, and `CHANGELOG.md`.
Ensures roadmap items have corresponding issues and issues are reflected in the roadmap.

**Prerequisites:** GitHub CLI authenticated (`gh auth status`). Roadmap check is skipped (SKIP) if no GitHub remote is detected.

### Step 1 — Parse ROADMAP.md

If `ROADMAP.md` is absent: report `[SKIP]` with note "run /documentation-updater to create ROADMAP.md first."

Parse sections looking for:

- Version headings: `## v1.2.0`, `## Unreleased`, `## Planned`, `## In Progress`, `## Released`
- Feature lines: any `- `, `* ` bullet OR item with `#N` issue reference
- Extract: (item text, version/section, issue ref if present)

```bash
# Extract headings and items from ROADMAP.md
grep -n "^#\|^- \|^* " ROADMAP.md
```

### Step 2 — Fetch GitHub Issues and Milestones

```bash
REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null)

# Existing milestones (open + closed)
gh api "repos/$REPO/milestones?state=all&per_page=100" \
  --jq '.[] | {number: .number, title: .title, state: .state, open_issues: .open_issues, closed_issues: .closed_issues}'

# Open issues with milestones
gh api "repos/$REPO/issues?state=open&per_page=100" \
  --jq '.[] | select(.pull_request == null) | {number: .number, title: .title, milestone: .milestone.title, labels: [.labels[].name]}'

# Recently closed issues (last 90 days — may already be shipped)
gh api "repos/$REPO/issues?state=closed&per_page=50" \
  --jq '.[] | select(.pull_request == null) | {number: .number, title: .title, closed_at: .closed_at}'
```

Build a **milestone map**: `{ roadmap_section_title → github_milestone_number_or_null }` by matching ROADMAP.md section headings (`## v1.2.0`, `## Q2 2026`, etc.) against existing GitHub milestone titles (case-insensitive, strip `##` prefix and trim).

For each section heading in ROADMAP.md that is NOT in BACKLOG/BUG/TECH-DEBT/Unreleased/Released **and** matches a versioning/sprint pattern (`vX.Y.Z`, `SPRINT-N`, `QN YYYY`, or `SPRINT-N-feature`): note whether the milestone exists in GitHub or is `MISSING`.

Skip sections that do not match these patterns (e.g., `## Ideas for v2`, `## Parking Lot`, `## In Progress`) — these are informational headings, not actionable milestones.

### Step 3 — Map roadmap items → issues

For each roadmap item:

1. Check if it contains an explicit `#N` reference → verify that issue exists
2. If no explicit ref: fuzzy-match title against open issues (≥60% word overlap)
3. Classify as:
   - `LINKED` — has verified issue reference
   - `FUZZY_MATCH` — probable match found (show candidate)
   - `ORPHANED` — no issue found

### Step 4 — Find orphaned roadmap items → create milestones + issues + write back

#### Step 4a — Create missing milestones

For each ROADMAP.md section that has `MISSING` milestone in GitHub (from Step 2 milestone map), print the proposed creation:

```
⚠ ROADMAP section has no GitHub Milestone:
  Section: ## v1.3.0
  Action:  gh api repos/$REPO/milestones --method POST -f title="v1.3.0"
```

After listing all missing milestones, ask **once**: "Create all N milestones on GitHub? [Y/n]"

**If confirmed:** run each `gh api` call and update the milestone map with the newly created milestone numbers. Report:

```
✓ Created milestone "v1.3.0" (#5 on GitHub)
```

**Priority inference for issues:** determine milestone from the roadmap section the item belongs to:

- Section `## v1.X.Y` or `## SPRINT-N` → use that milestone title
- Section `## Planned` or `## In Progress` (no version) → use `BACKLOG`
- Section `## Unreleased` → use `BACKLOG`

#### Step 4b — Create issues for orphaned items

For each `ORPHANED` item in "Planned", "In Progress", or versioned sections, print the proposed command using the **correct milestone** (not hardcoded BACKLOG):

```
⚠ ROADMAP item has no GitHub Issue:
  Section: ## v1.3.0  →  milestone: v1.3.0
  Item:    "Add multi-tenant support"
  Action:  gh issue create \
             --title '[v1.3.0|05] [P2] Add multi-tenant support' \
             --body "From ROADMAP.md ## v1.3.0. No existing issue found." \
             --label "enhancement" \
             --milestone "v1.3.0"
```

Title prefix follows `[MILESTONE|ORDER]` convention from `github-workflow.md`.
**Shell safety:** Always use **single quotes** for `--title` values — titles with `|` (e.g.
`[v1.3.0|05]`) will break the shell if double-quoted or unquoted.

After listing all orphaned items, ask **once**: "Create all N issues and update ROADMAP.md? [Y/n]"

**If confirmed (Y or Enter):**

1. For each orphaned item, run the `gh issue create` command and capture the returned issue URL/number:

   ```bash
   ISSUE_URL=$(gh issue create \
     --title '[v1.3.0|05] [P2] Add multi-tenant support' \
     --body "From ROADMAP.md ## v1.3.0. No existing issue found." \
     --label "enhancement" --milestone "v1.3.0")
   ISSUE_NUM=$(echo "$ISSUE_URL" | grep -o '[0-9]*$')
   ```

2. Immediately update `ROADMAP.md` — append `#N` to the matching item line:

   ```
   Before: - Add multi-tenant support
   After:  - Add multi-tenant support #42
   ```

   Use direct file edit, matching the exact item text.

3. Report each update:
   ```
   ✓ Created #42 "Add multi-tenant support" (milestone: v1.3.0) → ROADMAP.md updated
   ```

**If skipped (n):** list the commands for manual execution and note that ROADMAP.md was not updated.

### Step 5 — Find GitHub Issues not in roadmap → write back

For each open issue with a milestone (excluding BACKLOG/BUG/TECH-DEBT):

1. Check if it appears in ROADMAP.md (by `#N` ref or fuzzy title match)
2. If not present: flag as `MISSING_FROM_ROADMAP`

```
⚠ Issue not reflected in ROADMAP.md:
  #42 "OAuth2 integration" (milestone: v1.2.0)
  Suggest adding to ROADMAP.md under ## v1.2.0
```

List all missing items with proposed markdown snippet for ROADMAP.md.

After listing, ask **once**: "Add all N missing issues to ROADMAP.md? [Y/n]"

**If confirmed (Y or Enter):**

For each `MISSING_FROM_ROADMAP` issue, insert a new bullet under the matching version/milestone section in ROADMAP.md:

```
- <issue title> #N
```

If the section doesn't exist yet, create it (e.g., `## v1.2.0`) immediately before the first lower version or at the end of the planned area.

Report:

```
✓ Added #42 "OAuth2 integration" under ## v1.2.0 in ROADMAP.md
```

**If skipped (n):** print the snippets for manual insertion.

**After all write-backs (Steps 4 and 5):** if ROADMAP.md was modified, note that the file should be committed — suggest `/ship` or a quick `git add ROADMAP.md`.

### Step 6 — CHANGELOG reconciliation

Cross-check `CHANGELOG.md [Unreleased]` against ROADMAP.md "Planned" items:

1. Items that appear in CHANGELOG [Unreleased] AND in ROADMAP Planned → flag as `SHIPPED_BUT_PLANNED`
   - These should move from "Planned" to "Released" (or be removed from Planned)
2. Items in CHANGELOG released sections (tagged versions) that still appear in ROADMAP Planned → same flag

```
⚠ SHIPPED items still listed as Planned in ROADMAP.md:
  "Add structured logging" — in CHANGELOG v1.1.0 but still in ROADMAP ## Planned
  Suggest: move to ## Released or remove from Planned.
```

### Step 7 — Next version proposal

Based on milestone completion state:

```bash
# Get milestone progress
gh api "repos/$REPO/milestones" \
  --jq '.[] | select(.state=="open") | "\(.title): \(.closed_issues)/\(.open_issues + .closed_issues) closed (\((.closed_issues * 100 / (.open_issues + .closed_issues + 0.001)) | floor)%)"'
```

For any milestone ≥80% closed: propose adding/updating a version section in ROADMAP.md with the remaining open issues.
For milestones ≥95% closed: suggest bumping to "In Progress" or flagging for imminent release via `/release plan`.

**Gate:**

- `PASS`: all roadmap items linked to issues AND no CHANGELOG reconciliation issues
- `WARN`: ≥1 orphaned roadmap items OR ≥1 missing-from-roadmap issues OR ≥1 SHIPPED_BUT_PLANNED items
- `FAIL`: ROADMAP.md present but >50% of Planned items are orphaned (roadmap is effectively disconnected from issues)
- `SKIP`: ROADMAP.md absent OR no GitHub remote

---

## Subcommand: `/health license`

Audit the `LICENSE` file at project root. If absent, run interactive advisor to recommend the right license.

### Step 1 — Detection

```bash
# Check for common license file names (case-insensitive)
ls LICENSE LICENSE.md LICENSE.txt COPYING COPYING.md 2>/dev/null | head -1
```

If found: parse and identify the license type (MIT, Apache-2.0, GPL-3.0, etc.) and report `[PASS]`.

**Parse logic:**

- Check first line or SPDX header for identifier: `SPDX-License-Identifier: MIT`
- Fallback: keyword match on first 5 lines ("MIT License", "Apache License", "GNU GENERAL PUBLIC LICENSE", etc.)
- If unrecognizable: report `[WARN] License file present but type unknown — verify SPDX identifier`

**Apache 2.0 — NOTICE file check:**

Apache 2.0 does not embed the copyright holder inside `LICENSE` (it's a generic text). The owner is declared in a separate `NOTICE` file. When Apache 2.0 is detected:

```bash
ls NOTICE 2>/dev/null || echo "missing"
```

- `NOTICE` present with copyright line → `PASS`
- `NOTICE` absent → `WARN: Apache-2.0 detected but no NOTICE file found — add NOTICE with "Copyright <year> <holder>"`

Suggest creating: `echo "Copyright $(date +%Y) $(git config user.name)" > NOTICE`

### Step 2 — Advisor mode (LICENSE absent)

Ask 5 questions to recommend the best license. Present as a numbered menu each time.

**Q1 — Project type:**

```
1. Open source library (others will import/depend on it)
2. Open source application (end users run it)
3. Internal / proprietary (not publicly distributed)
4. Commercial SaaS (hosted service, not distributed)
```

**Q2 — Copyleft preference:**

```
1. Permissive — anyone can use, including in proprietary products (MIT, Apache, BSD)
2. Weak copyleft — modifications must stay open, but linking is fine (LGPL, MPL)
3. Strong copyleft — derivative works must also be open source (GPL, AGPL)
4. Network copyleft — copyleft applies even to SaaS use (AGPL)
```

**Q3 — Patent clause:**

```
1. Yes — include explicit patent grant (Apache-2.0, GPL-3.0)
2. No — no explicit patents (MIT, BSD)
3. Don't know — recommend based on other answers
```

**Q4 — Attribution requirement:**

```
1. Must preserve copyright notice and attribution (all major licenses do this)
2. Cannot use my name/brand in derived products (BSD 3-Clause, Apache)
3. Minimal — just keep the license text (MIT)
```

**Q5 — Compatibility requirements:**

```
1. Must be compatible with GPL (MIT, Apache-2.0, LGPL)
2. Must be compatible with Apache-2.0 (MIT, BSD, Apache)
3. No specific compatibility requirement
```

### Step 3 — License recommendation matrix

Based on answers, output one of:

| Scenario                          | Recommended                        | Why                                                             |
| --------------------------------- | ---------------------------------- | --------------------------------------------------------------- |
| Library, permissive, patent grant | **Apache-2.0**                     | Permissive + patent protection, industry standard for libraries |
| Library, permissive, minimal      | **MIT**                            | Simplest, most permissive, widely understood                    |
| Library, weak copyleft            | **MPL-2.0**                        | File-level copyleft, allows proprietary linking                 |
| App, strong copyleft              | **GPL-3.0**                        | Forces derivative works open, patent grant                      |
| App/SaaS, network copyleft        | **AGPL-3.0**                       | Closes SaaS loophole, requires source even for hosted use       |
| Internal/proprietary              | **All Rights Reserved**            | No distribution rights, explicit copyright notice only          |
| Commercial SaaS w/ open core      | **Apache-2.0 + commercial addons** | Open core model                                                 |
| BSD variant needed                | **BSD-3-Clause**                   | MIT-like + no-endorsement clause                                |

After recommendation, offer to:

1. Fetch the full license text and write `LICENSE` file (with current year + copyright holder)
2. Show the SPDX identifier to add to `pyproject.toml` / `package.json`

```bash
# Detect copyright holder from git config
git config user.name
git config user.email
```

**Gate:**

- `PASS`: LICENSE file present and type identified
- `WARN`: LICENSE file present but type unrecognizable
- `FAIL`: No LICENSE file AND this is a public repository (`gh repo view --json isPrivate --jq '.isPrivate'` = false)
- `INFO` (not gated): No LICENSE file on a private repo (common for proprietary/internal work) — still suggest running advisor

---

## Fix-vs-Issue Decision Matrix

Apply this matrix during `/health full` step 16 triage. Every WARN/FAIL finding is either
`FIX-NOW` (apply immediately in this session) or `TRACK` (open a GitHub issue for future work).

**FIX-NOW criteria:** single-file change, no design decisions required, reversible, takes < 5 min.
**TRACK criteria:** investigation needed, multi-file structural work, external dependencies, or
requires a design decision before acting.

| Dimension | Finding                                                 | Class   | How to fix                                    |
| --------- | ------------------------------------------------------- | ------- | --------------------------------------------- |
| Versions  | README badge mismatch                                   | FIX-NOW | Update badge string in README                 |
| Versions  | Cross-manifest mismatch (single bump)                   | FIX-NOW | Bump the lagging file                         |
| Versions  | Cross-manifest mismatch (breaking change)               | TRACK   | Requires version strategy                     |
| Hygiene   | Artifact not in .gitignore (.coverage, .DS_Store, etc.) | FIX-NOW | Append to .gitignore                          |
| Hygiene   | Tracked file that matches .gitignore                    | FIX-NOW | `git rm --cached <file>`                      |
| Hygiene   | Root clutter (>25 files, unexpected dirs)               | FIX-NOW | Move or delete offenders                      |
| Hygiene   | Orphaned lock file                                      | TRACK   | Needs investigation                           |
| Hygiene   | Non-empty stash (<7 days)                               | FIX-NOW | Review with `git stash show` and pop or drop  |
| Hygiene   | Non-empty stash (≥7 days old)                           | TRACK   | Old stash likely forgotten — review and clear |
| Docs      | ROADMAP.md missing                                      | FIX-NOW | Create skeleton file                          |
| Docs      | TECH-STACK.md missing                                   | FIX-NOW | Create skeleton file                          |
| Docs      | LICENSE missing (private repo)                          | FIX-NOW | Add standard LICENSE                          |
| Docs      | Stale docs (code updated, docs not)                     | TRACK   | Requires actual writing                       |
| Docs      | CLAUDE.md / README missing                              | TRACK   | Requires content decisions                    |
| Endpoints | Undocumented non-mutating route                         | TRACK   | Add docs                                      |
| Endpoints | Undocumented POST/PUT/DELETE                            | TRACK   | High priority — add docs                      |
| Security  | Any CRITICAL/HIGH finding                               | TRACK   | Investigate before touching                   |
| Security  | LOW/INFO false positive (e.g., B101 in tests)           | FIX-NOW | Add `# nosec` annotation                      |
| Quality   | Lint errors (auto-fixable, e.g. `ruff --fix`)           | FIX-NOW | Run the linter's auto-fix                     |
| Quality   | Tests failing                                           | TRACK   | Needs root cause investigation                |
| Quality   | Coverage below threshold                                | TRACK   | Needs test writing                            |
| Tech Debt | TODOs / FIXMEs                                          | TRACK   | Can't resolve in health run                   |
| Tech Debt | Oversized functions                                     | TRACK   | Refactor work                                 |
| Deps      | Patch/minor update available                            | FIX-NOW | Bump version, run tests                       |
| Deps      | CRITICAL/HIGH CVE                                       | TRACK   | Needs investigation                           |
| Library   | Extraction candidate                                    | TRACK   | Design decision required                      |
| Roadmap   | Released section not updated (version missing)          | FIX-NOW | Add entry to `## Released` section in ROADMAP |
| Releases  | Missing GH release for a tag                            | FIX-NOW | `gh release create <tag>`                     |
| Releases  | Missing Forgejo release for a tag                       | FIX-NOW | Create via Forgejo API                        |
| Releases  | Artifact MISSING (publish workflow exists, no package)  | FIX-NOW | Re-run publish workflow or push manually      |
| Releases  | Artifact SKIP(pre-registry) (tag predates first pub)    | TRACK   | Decide whether to backfill or document gap    |
| Releases  | Tag↔changelog mismatch                                  | TRACK   | Requires changelog work                       |
| CI        | Missing security/release workflow                       | FIX-NOW | `/ci-setup fix` copies from template          |
| CI        | Minor template drift (whitespace, comments)             | FIX-NOW | `/ci-setup fix` re-copies file                |
| CI        | Legacy unified pattern (quality+tests in one file)      | TRACK   | May have intentional customizations           |
| CI        | GH_PAT missing                                          | TRACK   | Manual web UI action — WAF blocks API         |
| Branches  | Local merged branch                                     | FIX-NOW | `git branch -d <name>`                        |
| Branches  | Remote stale >30 days, no open PR                       | FIX-NOW | Delete via `git push --delete` or Forgejo API |
| Branches  | Remote stale >60 days, no open PR                       | TRACK   | Confirm intent before deleting                |

**When in doubt:** if you can write the fix in this response without asking a question, it's FIX-NOW.
If you'd need to ask "what should this say?" or "is this intentional?", it's TRACK.

---

## Subcommand: `/health full`

Runs all checks in sequence, then triages findings via the Fix-vs-Issue Decision Matrix.

**Execution order:**

1. `/health endpoints`
2. `/health versions`
3. `/health docs` (includes LICENSE existence check — WARN if absent)
4. `/health license` (advisor mode — only if LICENSE absent from Step 3; otherwise PASS)
5. `/health roadmap` (bidirectional sync — SKIP if no GitHub remote or ROADMAP.md absent)
6. `/health security`
7. `/health quality`
8. `/health techdebt` (SKIP on config-only or docs-only repos)
9. `/health deps` (SKIP if no manifest files found)
10. `/health hygiene` (filesystem lint — SKIP if not a git repo)
11. `/health library`
12. `/health releases`
14. `/health docker` (conditional — only if `Dockerfile*`, `compose.yaml`, or `docker-compose*.yml` found)
15. `/health website` (conditional — only if website project detected)
16. `/health agents` (cross-repo scan for unreviewed `ai-suggested` issues)
17. `/health branches` (stale branch audit — local, GitHub, Forgejo — SKIP if not a git repo)
18. Print consolidated report with Overall gate
19. Triage all WARN/FAIL findings using the **Fix-vs-Issue Decision Matrix** below:
    - Collect all FIX-NOW findings → print list → ask once: "Apply all N fixes now? [Y/n]"
    - If yes: apply fixes on a `fix/health-audit-<date>` branch (see post-audit commit protocol)
    - Collect remaining TRACK findings → ask once: "Open GitHub issues for these M findings? [Y/n]"
    - Do NOT default to issue creation for FIX-NOW findings. Fix them.

**Post-audit commit protocol:**

Any fixes applied during or after the audit (doc creation, file moves, version bumps, etc.) MUST be committed via a branch, not directly to main:

```bash
git checkout -b fix/health-audit-<date>   # e.g. fix/health-audit-2026-03-14
# apply fixes...
/pre-commit    # full validation pipeline
/ship          # commit + push + PR creation
```

Always suggest this branch+PR flow before touching any files. Never commit health fixes directly to main.
Exception: if already on a feature/fix branch, continue on that branch.

---

## Output Format

All subcommands use this uniform structure:

```
## Health Report — <project> — <date>

### <Dimension> [PASS|WARN|FAIL]
- ✓ <passing finding>
- ⚠ <warning finding>
- ✗ <failing finding>
```

**Overall gate:** FAIL if any dimension is FAIL. WARN if any is WARN. PASS only if all pass.

---

## Thresholds

| Dimension | WARN threshold                                                                                                     | FAIL threshold                                                                                                          |
| --------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| Endpoints | any undocumented non-mutating                                                                                      | any undocumented POST/PUT/DELETE                                                                                        |
| Versions  | README badge differs                                                                                               | cross-manifest mismatch                                                                                                 |
| Docs      | ROADMAP/TECH-STACK/LICENSE missing OR misplaced doc OR >2 weeks stale                                              | CLAUDE.md/README missing OR >4 weeks stale OR >5 stale `act`-status registry skills (null `plan`/`do`/`check` excluded) |
| License   | LICENSE present but type unrecognizable                                                                            | LICENSE absent on a public repo                                                                                         |
| Roadmap   | ≥1 orphaned roadmap item OR ≥1 issue missing from roadmap OR ≥1 SHIPPED_BUT_PLANNED item                           | >50% of Planned items are orphaned (roadmap disconnected from issues)                                                   |
| Security  | (inherits from /security-verify)                                                                                   | (inherits from /security-verify)                                                                                        |
| Quality   | 1-5 lint errors OR coverage 60-79%                                                                                 | tests failing OR coverage < 60% OR > 5 lint errors                                                                      |
| Tech Debt | 1-5 duplicate blocks OR 1-10 TODOs OR 1-3 oversized functions                                                      | >5 duplicates OR >10 TODOs OR dead code in critical paths                                                               |
| Deps      | unassessed HIGH/MEDIUM deps exist OR ≥1 deferred decision due within 7 days                                        | any CRITICAL dep (CVE/EOL) OR any overdue deferred decision                                                             |
| Hygiene   | tracked OS artifacts, missing gitignore for stack, >25 root files, >10MB file, binary without LFS, any stash entry | tracked file matching .gitignore, >50MB file, orphaned lock file, nested .git (non-submodule)                           |
| Releases  | missing GH release for a tag                                                                                       | tag↔changelog section mismatch                                                                                          |
| CI        | minor drift (missing `release.yml`) OR `REGISTRY_TOKEN` missing                                                    | legacy patterns (`old-unified-pattern`, `legacy-setup-deps`) OR missing security workflow OR `REGISTRY_TOKEN` missing   |
| Docker    | any HIGH finding (non-root USER, no HEALTHCHECK, no .dockerignore, `:latest` tag)                                  | any CRITICAL finding (secrets in Dockerfile/ARG/ENV, root user with privileged ports)                                   |
| Website   | (inherits from /website-health)                                                                                    | (inherits from /website-health)                                                                                         |
| Agents    | 1–15 open `ai-suggested` issues across all repos                                                                   | >15 open issues OR any `ai-suggested` issue older than 14 days                                                          |
| Branches  | ≥1 local merged branch OR ≥1 remote stale >30 days (no open PR)                                                    | ≥1 branch stale >60 days on any remote (local or GitHub or Forgejo)                                                     |

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.