Planning-health check - .planning's core docs present, the STATE cursor, ROADMAP and REQUIREMENTS parseable and consistent. Not a traceability audit (that is /cad-audit)
Scanned 9/3/2026
Install to Claude Code
npx -y skills add crenshawdev/cadence --skill cad-health --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Cad Health?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/crenshawdev-cad-health)More formats (shields.io, HTML) on the badges page.
---
name: cad-health
description: "Planning-health check - .planning's core docs present, the STATE cursor, ROADMAP and REQUIREMENTS parseable and consistent. Not a traceability audit (that is /cad-audit)"
argument-hint: ""
allowed-tools:
- Read
- Bash
- Grep
- Glob
- AskUserQuestion
---
<objective>
A fast structural pulse on `.planning/` - can the spine even read its own state?
It checks presence, parseability, and internal consistency, nothing deeper. It
does NOT judge whether requirements were delivered (that is /cad-audit's job);
it judges whether the files are well-formed enough for the other skills to trust.
</objective>
<process>
Check, then report - do not fix without asking.
1. **Presence.** `.planning/` exists with PROJECT.md, REQUIREMENTS.md,
ROADMAP.md, STATE.md. A missing core doc is an issue (if the dir itself is
absent, point at /cad-new-project for a blank page and /cad-adopt for a repo
that already has code and history).
- The run record stays out of git. Run
`node "${CLAUDE_PLUGIN_ROOT}/cadence-core/bin/planning.mjs" trace ignore --root . --check`
and report an issue when `ignored` is false or `tracked` is true. Silent when
the record is ignored and untracked. A project scaffolded before that seam
existed has no line of its own; `--check` writes nothing and this step never
edits the user's `.gitignore`.
The two flags are separate facts and take DIFFERENT remedies, so name the one
that applies rather than one command for both: `ignored:false` is a missing
rule, fixed by the same command without `--check`; `tracked:true` means the
record is in the index ALREADY, where no ignore rule reaches it, and the fix
is `git rm --cached .planning/trace.jsonl`. Both can be true at once, and
then both steps are needed - adding the rule alone leaves a tracked file that
keeps getting committed.
- The capture queue, in two calls - a census of the file's sections, and a
verdict on the walked queue itself:
`node "${CLAUDE_PLUGIN_ROOT}/cadence-core/bin/planning.mjs" capture-sections`
`node "${CLAUDE_PLUGIN_ROOT}/cadence-core/bin/planning.mjs" capture-check`
From `capture-sections`, print one line per section whose `in_walk` is
false, naming its heading and its bullet count, then what the number MEANS
in one clause: those bullets are invisible to /cad-plan's recall. Silent
when `exists` is false or every section is in the walk. It is a NAMED NOTE,
not an issue, the way step 7's manifest clause is a distinct lower note:
`## Debt markers` is written wholesale by `debt-harvest` and is not a
queue, so calling every out-of-walk section an issue trains the user to
skim past exactly the line this is here to make readable. What is worth
their attention is a count that MOVED, which they can only see because the
steady-state number is printed too.
From `capture-check`, print three things, and these ARE issues.
`.planning/CAPTURE.md` holds the phase in flight and nothing else, so each
one says that stopped being true:
- `substantive` against `bound`, naming the crossing when `over_bound` is
true. A crossed bound means a filing path stopped filing - the queue is
carrying work that belongs on the tracker.
- every `annotations[]` entry with its section, line and text. An
annotation is an item adjudicated the WRONG WAY: an item is resolved by
REMOVAL, so re-verifying one in place made the bullet longer instead of
making it leave.
- `archive.heading` with `archive.bullets` when `archive.present` is true.
That heading has LEFT this file's contract - moving settled items to a
section of the same document changes nothing about the bytes.
Print both readings EVERY run, never filtered against a list of sections or
items you expect. That allowlist is precisely what would have hidden the
incident this check exists for - all five lost bullets sat under
`## Archive`, the section any allowlist would have named first.
2. **STATE cursor.** Exactly the 4-line schema (Phase / Status / Next / Updated -
references/conventions.md). `Status` is one of the lifecycle values
(`ready to plan | context gathered | planned | executed | phase complete |
paused`). `Phase: N of M` parses with N <= M (except in the closed-milestone
case rule 5 states). `Updated` is a date. Flag a 5th line, an unknown status,
or an unparseable phase.
3. **ROADMAP.** `## Phases` entries are `- [ ]` / `- [x]` **Phase N: Name**,
numbered 1..M with no gaps or dupes. An EMPTY `## Phases` is a legitimately
closed milestone, not a numbering gap - do not flag it.
4. **REQUIREMENTS.** The traceability table parses; every `Status` is `Pending`
or `Complete`; every `Phase` value names a phase that exists in ROADMAP.
5. **Consistency.** Cursor `M` == ROADMAP phase count; cursor `N` is within
range. When ROADMAP has zero phases the cursor reads `of 0`, and
`Phase: 1 of 0 (no active cycle)` is the expected closed-milestone shape -
both clauses pass, and a surviving `phases/<N>/` dir there means the prune
was interrupted (/cad-milestone finishes it).
`.planning/phases/<N>/` dirs correspond to real phases (a planned
phase with no dir yet is fine; a dir with no phase is an issue). The
directory grammar is stated in references/roadmap-phases.md: report every
`phase-dir-grammar` entry
`planning.mjs status` returns as an issue naming the entries it lists -
Cadence resolves no other spelling, so those directories are unsupported, and
renaming them is the user's call and never an auto-fix. Report a
`phase-dir-collision` entry too, but say the other thing: both names are
LEGAL and parse to one number, so no spelling is wrong - only ambiguous. A phase
marked `- [x]` in ROADMAP whose mapped REQUIREMENTS rows are not all
`Complete` (or a `Complete` requirement whose phase is still `- [ ]`) is a
status-drift issue - flag it. This is the cheap structural check that a
phase closed clean; whether the requirement was actually *delivered* is
/cad-audit's job, not this one.
6. **Inert config.** A setting that is ON and cannot take effect is worse than
one that is off: the user believes they have the behaviour and never sees it
missing. Check the known cases and report each as an issue naming the fix.
- `parallelization.enabled` true while
`node "${CLAUDE_PLUGIN_ROOT}/cadence-core/bin/worktree-base.mjs" resolve`
returns `parallelSafe: false` -> every /cad-execute run has been sequential
and always will be. Report the resolver's own `reason` and the fix it names
(`worktree.baseRef` set to `"head"`; /cad-config offers it).
- `parallelization.enabled` true with `use_worktrees` false -> parallel
dispatch without isolation is unsupported and falls back to sequential.
- `review.reviewers` naming a provider with no resolvable credential -> the
cross-model panel silently degrades to the in-process reviewer. Check
presence only, NEVER read or print a key's value.
- `parallelization` present but not an object (e.g. `"parallelization": true`)
-> the whole block is malformed, every key inside it reads as its schema
default, and nothing warned. Same for any config block the schema declares
as an object.
7. **Version drift.** The `PROJECT.md ### Active` milestone version must not be
one the project has ALREADY SHIPPED. Membership, not sort order: the issue is
an Active version that equals an existing release TAG (`git tag --list`).
Report it naming both numbers. No tags, or a version that parses as neither
semver, is clean: an unprovable comparison is not drift.
Tags are the publication evidence; a manifest in the checkout is NOT. The
manifest bumps during the close, before the merge and before the tag, so
between those points the manifest legitimately names the version still being
shipped. Reading it as proof would fire on every close in progress. When the
manifest equals the Active version and no tag does, report it as a
distinct, lower note - "the manifest already names the active milestone;
expected mid-close, stale otherwise" - never as drift.
Sort order refuses strictly more: an untagged maintenance milestone
like `v1.9.1` in a repo tagged `v1.9.0` and `v2.0.0` is a legitimate open
version the guard allows, and a health check calling it drift would push the
user to renumber or abandon a valid patch release.
Report: **healthy** with a one-line all-clear, or a short list of issues, each
with the file and what is wrong. For a trivial, unambiguous fix (cursor `of M`
count off, a stale `Updated`), offer to correct it via the ask-user seam - never
auto-edit. Anything structural (missing doc, phase-number gaps) is reported for
the user to resolve, possibly via /cad-phase.
</process>
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!