Skip to content
Development
Skill

/board-doctor

Use to validate docs/31-board.md before spawning any agent, and to verify a developer's "DONE" claim before moving a row to review. Triggers as step 0 of /app-build and /app-run, from /app-status, and any time the board looks inconsistent. Catches tickets the sprint loop cannot

From plugin
app-dev-team
432 skills30 agents27 commands2 hooks
Install
$ npx -y skills add vmobifystudio/app-dev-team --skill board-doctor --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/board-doctor

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use to validate docs/31-board.md before spawning any agent, and to verify a developer's "DONE" claim before moving a row to review. Triggers as step 0 of /app-build and /app-run, from /app-status, and any time the board looks inconsistent. Catches tickets the sprint loop cannot

SKILL.md

board-doctor.SKILL.md
name: board-doctor
description: Use to validate docs/31-board.md before spawning any agent, and to verify a developer's "DONE" claim before moving a row to review. Triggers as step 0 of /app-build and /app-run, from /app-status, and any time the board looks inconsistent. Catches tickets the sprint loop cannot see.

Board doctor

The board is the team's only memory across agent invocations, and every row is written by an LLM editing a Markdown table. That means the board can drift into states the sprint loop is structurally unable to notice.

The worst one, and the reason this skill exists:

> `/app-build` treats a ticket as ready when `Status = todo` **and** every `Depends on` ID is merged > (`qa` or `done`), and it exits the loop when there are no ready `todo` rows and nothing in > `review`/`qa`. A ticket whose dependency is `blocked` therefore satisfies neither condition — so > the loop **terminates and prints a successful sprint summary without ever mentioning it.**

Same for a row with a missing owner, an owner that isn't a real role, or a `Depends on` pointing at an ID that doesn't exist. The work is on the board, scheduled to nobody, reported as complete.

**Rule: nothing spawns while an anomaly is open.**

Two kinds of board, and what this skill is for on each

**Generated board** (`docs/31-board-events.jsonl` exists). `docs/31-board.md` is a rendering of the event log, and `scripts/board.mjs` refuses the illegal transition *before* it is written — a self-approval, a merge with no non-owner approval, a review requested on an unverified DONE, a claim on an unmerged dependency. Those states are unrepresentable, not merely detectable, so on this board the doctor is mostly a **drift detector**.

Mostly, and the exception matters. Anomalies on a generated board come in two kinds, and treating them alike sends you hunting something that never happened:

  • **Drift** — `malformed_row`, `status_invalid`, `duplicate_id`, `cycles_invalid`, `self_review`,

`done_without_review`. The CLI cannot produce these, so something wrote the Markdown directly or appended to the log by hand. Find what did it *before* you re-render over the evidence, because the next `board.mjs` call erases the edit and the trail with it.

  • **Emergent** — `stranded`, `dependency_cycle`, `cycle_cap_breached`, `owner_not_spawnable`.

These are properties of the ticket *graph*, not of the file. `board.mjs` produces them legally: blocking a ticket strands every `todo` that depends on it, and no single append was illegal. **There is no hand-edit to find.** Fix the graph — unblock the dependency, re-scope the ticket, or file it blocked in the first place (`board.mjs add <ID> --depends X --status blocked`), which is the honest shape when a bug is filed against a ticket that is itself stuck.

**Hand-written board** (no event log). Nothing changes: the doctor is the primary gate, exactly as described below, and every check still blocks. Do not refuse to run such a project — it is the normal state of anything planned before the log existed. `/app-plan` and `/app-build` offer it one migration (`board.mjs migrate`), and a board too old to parse legitimately stays on this path.

Either way, `docs/31-board.md` is what you read. It is deliberately still human-readable and diffable; that is what lets this check work without running the CLI at all.

Run it

node "${CLAUDE_PLUGIN_ROOT}/scripts/board-doctor.mjs" docs/31-board.md

If `CLAUDE_PLUGIN_ROOT` is not set in your environment, glob for `**/scripts/board-doctor.mjs` inside the plugin install and run it from there.

Exit codes: `0` coherent (warnings allowed) · `1` anomalies — **do not spawn** · `2` no board / no parseable table (run `/app-plan` first).

Add `--json` for machine-readable output, `--quiet` to print only when something is wrong.

If you cannot locate or run the script — Node missing, path unresolvable — **do not skip the check and do not silently proceed.** Work the **Manual fallback** below by hand and say in your output that you did, so the verdict is never mistaken for a machine-checked one.

What it checks

Precedence-ordered per row. The first structural problem suppresses the derived ones, because an invalid owner makes "who acts next" unanswerable.

| Code | Meaning | Fix | |---|---|---| | `malformed_row` | Cell count doesn't match the header | Repair the row | | `duplicate_id` | Ticket ID appears twice | Delete or renumber | | `status_invalid` | Status outside `todo/in_progress/review/qa/done/blocked` | Set a valid status | | `owner_missing` | No owner — can never become ready | Assign an owner | | `owner_invalid` | Owner isn't a known role | Reassign from the roster | | **`owner_not_spawnable`** | Owner is a real role, but `/app-build` never spawns it to work a ticket | **Reassign to a role the loop spawns** — otherwise the ticket is never picked up *and* never reported | | `dependency_self` | Ticket depends on itself | Remove the edge | | `dependency_missing` | Depends on an ID with no row | Restore or drop it | | `dependency_cycle` | A → B → A | Break the cycle | | **`stranded`** | `todo` behind a `blocked` dependency (transitively) | **Unblock, re-scope, or mark blocked so it is reported.** Emergent, not drift — no hand-edit to hunt. A ticket that *belongs* blocked should be created that way: `board.mjs add <ID> --depends X --status blocked` | | `reviewer_missing` | In `review` with no reviewer recorded | Record the reviewer | | `self_review` | Reviewer role == owner role, or the owner approved in the ledger | Assign a different reviewer; void the approval | | `done_without_review` | `qa`/`done` with no approval in the ledger | Move back to review, or append the missing line | | `cycles_invalid` | Cycles isn't an integer | Set an integer | | `cycle_cap_breached` | `Cycles >= 2` but status isn't `blocked` | Stop the ticket, set blocked, surface to the user | | `ledger_action_unknown` | A ledger row uses a word outs

Read more
Ships withapp-dev-team

Describe your app idea in one line. Get a shipped iOS & Android app. AI App Studio is a team of 30 AI specialists — a CEO, product manager, designers, iOS/Android engineers, a code reviewer, QA, and a release manager — that works like a real software studio.

Get the whole plugin

Other skills on app-dev-team.