---
name: doctor
description: "Diagnose and optionally repair MindrianOS install: legacy install leftovers, sentinel gaps, active-room guard, surface-verification, ROOM.md drift, UI compliance, statusline visibility, Brain smoke, and a paste-ready command-registration bug report for Anthropic"
help_jtbd: "Diagnose and optionally repair an off-feeling install."
argument-hint: "[--fix] [--all] [--cascade-rooms] [--graph-derive-health] [--heal-room] [--verify-surface] [--room-md] [--ui-compliance] [--statusline-visibility] [--card-fire-health] [--install-state] [--stale-first-touch] [--deprecated-usage] [--brain-smoke] [--eureka-smoke] [--drift] [--report-registration-bug] [--reset-install-id] [--acceptance] [--pre-tag] [--pre-flight] [--session-start] [--only=<id,id>] [--dogfood-acceptance] [--claims] [--check-rs-engine] [--bind-check] [--icm-walk] [--clean-doubled-rooms=<digest>] [--room <dir>] [--simulate-write] [--scan-commands] [--scan-scripts] [--light-npx] [--dry-run] [--json]"
body_shape: E (Action Report)
layer: "harness"
layer_why: "Diagnoses install-state and deployment-surface drift, ROOM.md gaps, statusline visibility and UI compliance; the canonical doctor-organ measurement and repair tool the rubric's own step 3 signal names by name."
hitl_shape: "F.0"
hitl_why: "It surfaces one diagnosed repair for a single approve-or-defer decision."
# Phase 267.3-04, ruled in 267.3-CLASSIFICATION.md (Row 7): first delivery at commands/doctor.md:42, one status row per check plus a tallied summary, which the rule doc names outright as not a reward.
interactive_first_reward: "--none (diagnostic surface)"
body_shape_detail: per-class status rows with [before → after] pattern, summary totals, F.1 Next Move selector when drift detected without --fix
serves_jtbd: ["audit-room"]
teaching: "When something feels off with the install, /mos:doctor diagnoses install-state drift, ROOM.md gaps, statusline visibility, and UI compliance. Optionally repairs with --fix."
allowed-tools:
  - Bash
  - Read
  - AskUserQuestion
# --- Phase 172-06 CIRS R1 exclude (Canon Part 11) ---
connector:
  excluded: true
  reason: "Utility command. A diagnostics / health-check surface the navigator or release pipeline runs deliberately; it inspects the install, never reacts to a navigator problem-state."
---

<!-- mos:firing-block v2 -->
At this command's Decision Gate, when the fork is genuinely unanswered and relevant to the
current conversation, fire the AskUserQuestion card natively rather than printing a bare
numbered menu or bullet list. Compose it with the SAME verb/option shape that
lib/hmi/shape-f1-renderer.cjs (renderShapeF1) produces and that lib/hmi/selector-dispatcher.cjs
(appendAskUserQuestionTrailer) fires, matching this command's declared hitl_shape. Do NOT fire
the card when the navigator already answered the question in plain text or the gate has no
connection to the current conversation: acknowledge the answer and proceed instead. Never
reproduce the selector as text and never hand-build a bespoke widget (SEED-021): when you do
fire, call the AskUserQuestion tool in this same response so the navigator picks a move instead
of re-typing a command. Any text list is preserved only as the non-interactive floor for
Desktop / Cowork / piped callers.
<!-- /mos:firing-block -->

# /mos:doctor

Self-service diagnostic for MindrianOS installs. It started as a drift detector for the legacy install directory (a real failure mode that happened twice -- see `docs/autopsies/2026-04-13-wrong-workspace-incident.md` and `docs/autopsies/2026-04-28-install-cache-drift-incident.md`) and grew into a family of health checks. As of Phase 217 every check except two carve-outs is a data-driven module: one row in `data/doctor-modules.json` plus one runner file under `lib/core/doctor/`.

## How it works

`scripts/doctor.cjs` keeps only the orchestration: it parses the flags, walks the module registry (`data/doctor-modules.json`), calls each runner's `check(ctx)` (and its `fix(ctx)` under `--fix`), then renders one status row per result and tallies the summary. The per-check logic lives in the runner files, not in the script. Add a new check and the script does not change.

Each registry row carries a `cadence`:

- `cadence: always` -- a per-invocation diagnostic. Its `check()` runs on EVERY `doctor` call and is watermark-immune, so a migrated check never goes silent after the first run. This is the exact silent-diagnostic failure mode Phase 217 killed.
- `cadence: once` -- a watermark-gated heal (the umbilical module). It runs only while its `introduced_version` sits in the `(applied_through, running]` window and never re-runs once the watermark at `~/.mindrian/doctor-applied.json` advances past it.

Both cadences are also gated by `introduced_version <= running`, so an install is never faulted for an organ it predates, and a future-version module is deferred until the user upgrades.

`--fix` support is declared per module by the boolean `fix_supported` in `data/doctor-modules.json` (never omitted -- silence is not a legal no-fix declaration). Under `--fix`, a fix-supported module whose `check()` surfaced a `warn`/`error` is passed to `fix()`, then `check()` re-runs so the reported row reflects the post-fix state. The completeness of every module's declaration is enforced as a HARD-BLOCKING test by `tests/test-doctor-module-contract-parity.cjs` (the D-03 gate). The doc-vs-code parity of this file is guarded by `tests/test-doctor-doc-parity.cjs`.

## The check inventory

Every check has a stable class letter (or a registry id). The class flag that activates it is in parentheses; check-only means no `--fix` remediation.

- **class B -- cascade-rooms sentinel** (`--cascade-rooms`; `--fix` supported) -- every registered room carries its `.room-root` sentinel. `--fix` CREATES missing sentinels for rooms whose dir exists (dir-missing rooms are suggested, never auto-created). This fix was NEWLY wired in Phase 217 Plan 04 -- before that the class-B `--fix` claim was a false doc promise.
- **class C -- active-room guard silence** (`--cascade-rooms`; check-only) -- detects when a write to a non-active room would be silenced before the cascade side-channel runs. Shares the `--cascade-rooms` flag with class B.
- **class D -- verify-surface** (`--verify-surface`; check-only) -- live cascade end-to-end. Spawns the child runner `tests/test-cascade-surface-e2e.cjs` (30s timeout) and asserts its 8-key shape. Self-skips honestly when `bash` or the harness is absent (never faults an install for a missing dev harness).
- **class E -- room-md** (`--room-md`; `--fix` supported) -- ROOM.md + MINTO.md presence under the active room's `.room-root`. `--fix` invokes `generate-section-intelligence.cjs --recursive`, then re-checks.
- **class F -- ui-compliance** (`--ui-compliance`; check-only) -- UI Ruling System scan across `commands/*.md` and `scripts/*.cjs` (frontmatter body_shape, forbidden box chars / glyphs, renderer Zone 1 + Zone 4 patterns).
- **class G -- statusline-visibility** (`--statusline-visibility`; `--fix` supported) -- stale user-settings path / broken plugin install / statusline-mos isolated execution / disableAllHooks. `--fix` removes the stale user-settings statusLine override so the plugin-level config takes effect.
- **class I -- install-state** (`--install-state`; `--fix` supported) -- install-state record + topology classification + 6-way version-of-record consistency. `--fix` returns MULTIPLE recovery records (session-start record write, LV rewrite, legacy-config reconcile, legacy-clone backup-verify-remove with the dev-clone safety belt). Runs BEFORE class J so J reads its result via `ctx.checks`.
- **class J -- deployment-surfaces** (shares `--install-state`; `--fix` supported) -- reconciles every owned surface in `data/deployment-surfaces.json` against disk. Reads topology / active-root / active-version from class I's same-invocation result (self-derives via `shared.cjs` when absent). `--fix` re-stamps `ok:false` session-start-owned surfaces and prunes the marketplace cache.
- **class K -- stale-first-touch-copy** (`--stale-first-touch`; check-only) -- greeting surfaces declared by `data/first-touch-surfaces.json` (banner, splash, onboard, sessionstart, operator-update, larry-extended) scanned for stale version literals and U+2014 em-dash violations. SEED-007 absorption. Also activated by `--all`.
- **class L -- deprecated-usage** (`--deprecated-usage`; check-only) -- scans the last 7 days of `~/.claude/projects/.../*.jsonl` session transcripts for `/mos:<deprecated>` patterns and surfaces a per-command "use `/mos:<new>` instead" hint. Pure LOCAL scan; zero network, zero Brain. Also activated by `--all`.
- **class M -- brain-smoke** (`--brain-smoke`; check-only) -- 7-layer Brain end-to-end probe: layer 0 origin and shadow connector (quick task 260911-axz -- resolved Brain origin, Theo status, `MINDRIAN_BRAIN_URL` override, Theo's live mode/build sha, and any Claude Code `~/.claude.json` entry shadowing the plugin's own `mindrian-brain` shim), then plugin root resolver, key resolver, HTTPS schema, MCP stdio handshake, e2e brain_schema via the bundled shim, and store identity (stale-replica detection). Diagnostic-only; reports the exact failing layer. This is an async runner and stays special-cased (carve-out, below). Activated by `--all`.
- **class N -- plugin-enabled-state** (no flag; bare run + `--all`; check-only) -- the silent-disable watchdog (DRIFT-12). Reads `~/.claude/settings.json` enabledPlugins + installed_plugins.json; installed && enabled===false is CRITICAL with a re-enable hint. It runs on a bare run and under `--all` precisely because a DISABLED plugin cannot fire its own SessionStart hooks to report itself. LOCAL read only; never writes settings.json.
- **class P / Q / R -- drift** (`--drift`; opt-in, NOT in `--all`) -- class P is prose-vs-code drift (skill-vs-code + first-touch, report-only even under `--fix`), class Q is gsd-record drift (shells out to `gsd-tools validate health`, parses W007 ROADMAP gaps + I001 missing SUMMARYs; `--fix` writes the DRIFT.md baseline and stubs missing SUMMARYs), class R is runtime-reachability drift (FAILS NON-ZERO when a capability is WIRED in the connector registry but UNREACHABLE by `decide()` at runtime). LOCAL-only, zero network.
- **class S -- eureka-smoke** (`--eureka-smoke`) -- 4-layer Eureka local-embedding-stack probe (deps present, vec backend, model cache, graceful degrade). Non-cascading; never downloads a model unless `MINDRIAN_EUREKA_SMOKE_ALLOW_DOWNLOAD=1`; rolls into `--acceptance`. Another async carve-out (below). `--fix eureka` installs the local embedding stack into `~/.mindrian/eureka-deps/` (one-time, about 380 MB), the same action as `/mos:eureka enable`; `--fix eureka --dry-run` reports the projected install without spawning the installer.
- **card-fire-health** (`--card-fire-health`; check-only; D-05) -- health of the `check-card-fire.cjs` self-diagnostic instrument: intercept log exists / valid JSONL / fresh (`~/.mindrian/card-fire-intercepts.log`), classifier and counter library seams intact, render-coverage registry parses, session store readable. `cadence: always`, LOCAL-only. Activated by `--all`.
- **graph-derive-health** (`--graph-derive-health`; `--fix` supported, sugar `--heal-room`) -- did this room's SEMANTIC edge layer ever land? A room.db with `BELONGS_TO` edges is a filing cabinet: it knows which artifact sits in which section. A room.db with cascade edges (INFORMS / CONTRADICTS / CONVERGES / INVALIDATES / ENABLES / REFINES / ROOT_CAUSES) additionally knows how the ideas relate. Before Phase 224-02 a failed derivation silently cleared its own retry signal, so a room could end up with the first and never the second. FAIL is exactly that shape (BELONGS_TO present, cascade count zero); WARN is a derive queue entry stuck past 3 days or a recorded `graph-derive-failures.json`. Scopes to the active room by default; add `--cascade-rooms` to sweep every registered room. `--fix` (or `--heal-room`) re-enqueues each affected room through the existing dedup-by-roomDir derive sweep, so re-running never duplicates an entry. LOCAL only: read-only `room.db` reads through the navigation door plus a local JSON queue write, zero network. Closes RCA item 4d. Activated by `--all`.
- **graph-derive-heal-retrofit** (no flag; `cadence: once`; check-only) -- the one-time automatic repair for rooms damaged BEFORE the Phase 224-02 fix shipped. It fires by itself the first time doctor runs after an update, walks every registered room, reuses the same detection signal as graph-derive-health, and re-enqueues each zero-cascade-edge room. Watermark-gated, so it runs once per version window and never repeats. No flag and no `--fix` needed: a user should not have to know their graph was damaged in order to get it repaired. Closes RCA item 4c.
- **room-graph-density** (no flag; bare run + `--all`; check-only) -- per-registered-room census of `room.db` `nodes` and `edges` row counts, plus a system-wide total. Reads every room through the read-only navigation door added in Phase 232.1, so the sweep can never migrate or otherwise mutate a room it only measures. A room with no `room.db` reports 0 / 0 and is not an error. `cadence: always`, LOCAL-only. Reports raw counts ONLY: it makes no density, health, or risk claim in either direction (SEED-074's hard guard on downstream claims).
- **room-graph-integrity** (no flag; bare run + `--all`; check-only) -- four measurable defect statements against the bound room's `room.db`, plus two informational counts: edge rows whose source or target id has no `nodes` row, claim nodes carrying no `SOURCED_FROM` or `DERIVED_FROM` out-edge, proposed nodes older than the review window, and edge rows whose type falls outside the `writeEdge` allowlist (with the offending type names, capped and shape-filtered), alongside unresolved `CONTRADICTS` edges and self-referencing edges. Two candidate defect statements (a stub-or-placeholder node, a memory-event provenance edge) are reported as not measurable, with the reason for each, in the organ's own output rather than shipped as statements. A column a legacy three-column room cannot answer reports null, never zero. The output also names the writer behind the unanchored-claim count and the writers that bypass the `writeEdge` chokepoint, and states that changing either is Phase 273 territory. Each room's payload also carries a `claim_counter_metric` object (claims filed, claims filed past the citation lag, claims carrying a `CONTRADICTS` edge, claims with no incoming edge past the lag, and a tri-state `divergence` boolean, rolled up into `totals.claim_counter_metric`); a column the schema cannot answer reports null, never zero, same as the rest of this organ, and interpretation lives in `docs/COUNTER-METRIC-DOCTRINE.md`, not here. Scopes to the active room by default; add `--cascade-rooms` to sweep every registered room plus fleet totals. `cadence: always`, LOCAL-only, read-only navigation door. Reports raw counts ONLY: it makes no claim in either direction (SEED-074's hard guard on downstream claims).
- **room-runtime** (no flag; bare run + `--all`; check-only) -- names the bash and the Python that room creation would use, their versions, every candidate that was tried and why it was rejected (the WSL launcher and the Microsoft Store placeholder never count), and on Windows the shell that the hooks would pick. A warning here is the early signal that a room cannot be created on this machine. `cadence: always`, LOCAL-only, no `--fix`.
- **doubled-room-trees** (no flag; bare run + `--all`; check-only in the engine) -- lists the folders that older room creation wrote by mistake inside the rooms home: a copy of a room's own path (for example `home/<you>/MindrianRooms/<room>`, or a `C:` folder on Windows) that holds only the unchanged starter files `ROOM.md`, `STATE.md`, `USER.md` and an empty `.mindrian` folder, and whose copied path is a real room. It prints the count, the top folders and a digest of the exact list. It deletes nothing; the repair is the F.0 gated mode below. `cadence: always`, LOCAL-only, no `--fix`.

### Retired classes

- **class A** (the drift check between the legacy install directory and the marketplace cache, with a rename-and-copy recovery) was retired in Phase 341. The plugin loader now owns the swap, there is one install location, and nothing in the plugin creates the legacy directory. If you still have an old `~/.claude/plugins/mindrian-os` directory, class I reports it and `--install-state --fix` backs it up, verifies the backup and removes it. The old report keys (`install`, `cache`, `drift`, `classARecovered`) and the old bare-run exit codes 2, 3 and 4 are gone.
- **class H** (the shell-installer completeness check) was retired in Phase 341. Its only input was a receipt file written by the shell installer, and that installer no longer exists, so nothing can write the receipt. If an old receipt file is still on your disk it is inert and safe to ignore. A leftover old install directory is reported by class I, and `--install-state --fix` backs it up and removes it. The `--statusline-visibility` flag is unchanged and now runs class G alone.

## Step 1: Parse the user's intent

Look at the user's invocation:

- `/mos:doctor` (no flag) -> class N plugin-enabled-state + the accumulative engine (the `cadence: once` heals like umbilical plus any `flag: null` always-modules). Fast; this is the bare-run surface.
- `/mos:doctor --all` -> activates B, C, D, E, F, G, I, J, K, L, M, and card-fire-health. `--drift` (P/Q/R) and `--eureka-smoke` (S) stay OPT-IN by design and are NOT part of `--all`.
- `/mos:doctor --cascade-rooms` -> class B (.room-root sentinel) + class C (active-room guard silence)
- `/mos:doctor --graph-derive-health` -> graph-derive-health (semantic-edge derivation health for the active room; add `--cascade-rooms` to sweep every room)
- `/mos:doctor --heal-room` -> the same class with `--fix` on: re-enqueues every WARN/FAIL room's derive so the next in-session derive rebuilds its semantic layer
- `/mos:doctor --verify-surface` -> class D live cascade end-to-end via `tests/test-cascade-surface-e2e.cjs`
- `/mos:doctor --room-md` -> class E (ROOM.md/MINTO.md presence under `.room-root` subtrees)
- `/mos:doctor --ui-compliance` -> class F (UI Ruling System scan)
- `/mos:doctor --statusline-visibility` -> class G (user-settings drift, plugin install integrity, statusline-mos isolated execution)
- `/mos:doctor --card-fire-health` -> card-fire-health module (D-05 instrument-health)
- `/mos:doctor --install-state` -> class I (install-state + topology + 6-way version-of-record) + class J (deployment-surface reconciliation)
- `/mos:doctor --stale-first-touch` -> class K
- `/mos:doctor --deprecated-usage` -> class L
- `/mos:doctor --brain-smoke` -> class M (7-layer Brain probe)
- `/mos:doctor --eureka-smoke` -> class S (4-layer Eureka probe; opt-in)
- `/mos:doctor --drift` -> class P + class Q + class R (opt-in; NOT in `--all`)
- `/mos:doctor --clean-doubled-rooms=<digest>` -> the F.0 gated delete of the doubled-path folders (below). NOT a class flag, NOT part of `--all`, NEVER reached by `--fix`.
- `/mos:doctor --report-registration-bug` -> READ-ONLY escalation reporter (below). NOT a class flag, NOT part of `--all`, NOT a `--fix`.
- `/mos:doctor --fix` -> diagnostic + auto-recovery for every class that declares `fix_supported: true`: class B, E, G, I, J. Fix support is declared per module in `data/doctor-modules.json` and enforced by `tests/test-doctor-module-contract-parity.cjs`.
- `/mos:doctor --json` -> machine-readable output (for hooks / regression tests)
- `/mos:doctor --verbose` (or `-v`) -> extra per-check detail in the human render.
- `/mos:doctor --dry-run` -> `--fix` runners simulate their remediation without mutating disk.

Combine flags freely: `/mos:doctor --all --json --fix`.

### Release-gate and lifecycle flags (separate from the class roster)

These siblings own their own exit contracts and are not part of the class-flag drift roster:

- `--acceptance` -> the release-gate checklist (install-state + deployment-surfaces + version-of-record + verify-release + doctor-all + published + npx-roundtrip). Hard abort on any sub-check failure.
- `--pre-tag` -> with `--acceptance`, filter to the pre-tag-applicable points (implies `--acceptance`).
- `--pre-flight` -> the strict-subset tier `release.sh` uses before it mutates the tree (implies `--acceptance`).
- `--session-start` -> with `--acceptance`, filter to the bounded OFFLINE tier the SessionStart hook runs once per install or update: four points (install-state, session-start-active-version, harness-policies, eureka-optin-state). The network wire point stays off this tier. Implies `--acceptance`. Precedence: `--pre-tag`, then `--pre-flight`, then `--session-start`.
- `--only=<id,id,...>` -> with `--acceptance`, narrow the selected tier to the named point ids (an unknown id selects nothing). The detached session probe uses it.
- `--dogfood-acceptance` -> the Canon Part 6 dog-fooding acceptance pass.
- `--claims` -> the claims-verification leg.
- `--check-rs-engine` -> the reason-select engine probe (NOT part of `--all`).
- `--reset-install-id` -> rotates the opaque per-install bucket key the plugin sends to Theo, prints `install id rotated`, never prints the value, and always exits 0. Full contract: `docs/THEO-INSTALL-ID.md`.
- `--bind-check <roomDir>` -> a lightweight LOCAL room-health check run at BIND-TIME (never per-turn, never a Brain call). NEVER-BLOCK: an unhealthy room degrades to an advisory and STILL exits 0.
- `--icm-walk [--room <dir>] [--json]` -> the ICM walk (below): a measurement-only report of the room root and every nest against the ten ICM invariants and the walk test. NOT part of `--all`; `--room` defaults to the registry's active room; writes nothing, fixes nothing, exits 0 after printing.
- `--light-npx` -> the lighter npx-roundtrip variant for `--acceptance`.
- `--simulate-write=<path>` -> class C test seam: simulate a write to a specific room path.
- `--scan-commands=<dir>` / `--scan-scripts=<dir>` -> class F test seams: point the UI scan at scratch directories.
- `--help` (or `-h`) -> print the usage text and exit 0.

## The `--icm-walk` mode (the ICM walk, measurement only)

`/mos:doctor --icm-walk` walks the room root and every nest (a folder holding a `ROOM.md` or a `CONTEXT.md`) and prints one block of measured rows per nest, then a room summary. It is the audit in `FEYNMINTO-ICM-AUDIT.md` turned into a tool, so a change to a nest's faces can be measured before and after. The walk reads each nest's FeyMinto faces, `MINTO.md`, `FEYNMAN.md` and `BRAIN.md`, and its `BRIEF.md`, and reads the room identity in `room.db` through the read-only door.
- The rows are the ten ICM invariants and the walk test: the job stated inside (I1), the entry file's lines, bytes and wikilinks (I2), the `CONTEXT.md` contract parts (I4), generated against authored files (I5), the edit-surface marker on each face (I6), loaded bytes and approximate tokens (I7), the one-home checks for sources, room identity and the Theo face (I8a, I8b, I8c), state by scanning (I9), scaffold markers (I10) and the reads needed to find your way (W).
- Every value is a count, a byte or approximate-token figure (bytes divided by four) or a presence flag. A file that is not there reads `missing` and is never scored; the only words beyond the numbers are the measured flags (over 60 lines, over 8000 tokens, over budget, duplicate, stale, no Room node).
- The summary names the three duplications when they exist in that room (MINTO sources relisting `ROOM.md` links, room identity by slug with no Room node or identity row in `room.db`, the Theo face restating the `CONTEXT.md` sequence) and how many face files carry no edit-surface marker.
- The room block also carries I0: how many of the eleven core sections (`CORE_SECTIONS` in `lib/core/section-registry.cjs`) exist as directories, the missing names, and the top-level nests found outside the canon.
- `room.db` is read through the navigation read-only door on a throwaway copy, so a room with no `room.db` still walks its files and says `room.db: missing`.
- It writes nothing, fixes nothing and sends nothing (Canon Part 8: local reads only); `--json` carries the same values.

## The doubled-path folder repair (F.0 gated)

The `doubled-room-trees` row lists folders that older room creation wrote by mistake inside the rooms home. Nothing is deleted by a plain `/mos:doctor` run, by `--all` or by `--fix`: the delete is the separate flag `--clean-doubled-rooms=<digest>`, and it runs only after the navigator approves.
1. Run `/mos:doctor` and read the `doubled-room-trees` row: the folder count, the top folders and the digest.
2. Fire the F.0 card with the AskUserQuestion tool: Approve, Reject or Defer. The card shows the top folders, the folder count and the digest, and says that only unchanged starter files are removed. Do not run the delete before the answer.
3. Only on Approve run `node "${CLAUDE_PLUGIN_ROOT}/scripts/doctor.cjs" --clean-doubled-rooms=<digest>`, with the digest from that same listing. On Reject or Defer, change nothing.
4. A changed listing changes the digest, so an old approval deletes nothing: the command prints that nothing was deleted and exits 1. Run `/mos:doctor` again and ask again.
5. A folder with a file you changed, an extra file, or no matching real room is never deleted and is named in the output. The command never follows a link and never deletes a folder that is not empty after its own starter files are gone.

## The `--report-registration-bug` mode (escalation reporter)

When a user reports that `/mos:*` commands do not register despite a valid install, run `/mos:doctor --report-registration-bug`. It is a read-only sibling mode (not a class flag, not part of `--all`, and NOT a `--fix`). It reuses the doctor's existing evidence collectors to prove every locally-checkable cause is CLEAN, then prints a paste-ready bug report for Anthropic. The root cause is a confirmed host-side Claude Code core bug (see `.planning/debug/every-mos-command-unknown.md`), so there is nothing to repair locally; the report is the deliverable the navigator pastes upstream. If a local cause is NOT clean (for example install-cache drift), that line is FLAGGED "fix locally first" and the mode still exits 0. Never a status claim; the report intentionally avoids declaring the install well or repaired.

## Step 2: Execute

Run via Bash:

```bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/doctor.cjs" $ARGUMENTS
```

If `CLAUDE_PLUGIN_ROOT` is unset (older Claude Code versions), fall back to:

```bash
node ~/.claude/plugins/mindrian-os/scripts/doctor.cjs $ARGUMENTS
```

## Step 3: Render the output

The script outputs a 4-zone Shape E (Action Report) per skills/ui-system/SKILL.md. The renderer is structural: it prints one row per non-skip `report.checks[<id>]` entry, so a new module gets a row for free. Display the script's stdout directly. Do not re-format. Do not strip ANSI color codes.

## Exit codes

| Code | Meaning |
|---|---|
| 0 | Healthy, no drift |
| 1 | Drift detected (read-only mode, or `--fix` did not run) |

Two invariants layer on top of the table:

- **Class-flag runs force exit 0.** When any class flag is active (`classFlagsActive`), the run honors the graceful-degradation invariant (Canon Part 8) and exits 0 unless an explicit `--fix` attempt failed. A detected drift is still visible in the rows; it does not abort a scoped scan. Hermetic scratch-registry test runs rely on this.
- **Two narrow non-zero escalations.** A `--drift` run whose class-R runtime-reachability check returns `warn` exits 1 (a WIRED-but-UNREACHABLE capability is a hard CIRS R9 fail, not a warning). And on a bare / non-class-flag run, the class-N plugin-disabled state (`warn`) exits 1, because that is the one failure mode no SessionStart hook can ever self-report.

## The two Phase 217 carve-outs

Every other check is a registry module; these two are deliberately special-cased in `main()`, each opened with a written `Phase 217 carve-out:` justification comment (loud and auditable, never a silent omission):

1. **class M brain-smoke** -- an `async` runner; the engine loop is synchronous, so it stays in its own `await` block.
2. **class S eureka-smoke** -- the same async-vs-sync-engine reason as brain-smoke.

## Extension architecture

A new check is two files and no script edit:

1. One entry in `data/doctor-modules.json`: `{ id, introduced_version, cadence ("always" | "once"), flag (a parseArgs flags key or null), fix_supported (explicit boolean), runner, description }`.
2. One runner at `lib/core/doctor/<id>-module.cjs` exporting `check(ctx)` -> `{ status: 'ok'|'warn'|'error'|'skip', detail, ...payload }`, and `fix(ctx)` when `fix_supported: true`. Shared helpers and constants live in `lib/core/doctor/shared.cjs`; a runner never back-requires `scripts/doctor.cjs` (that would be a circular require).

The D-03 contract test fails the suite if a module omits `fix_supported`, returns a status outside the vocabulary, forgets its `detail` string, or declares `fix_supported: true` without exporting a `fix` function.

## When to suggest /mos:doctor

Surface this command proactively when:

- A user reports unexpected behavior that might be version-related ("my new feature isn't showing up")
- After a marketplace update (`/plugin marketplace update` followed by reports of broken commands)
- During session start if the workspace guard hook reports drift
- As a follow-up after `claude plugin update` to confirm the update actually landed

## Voice rules (if invoked through Larry)

- No "I" sentences
- Lead with the script's output, then a one-line interpretation
- If drift detected without `--fix`: suggest `/mos:doctor --fix` as the next step
- If a `--fix` recovered something: name what changed from the `fix` rows, and recommend `/clear` and a fresh session when plugin files were touched

## Example output (healthy)

```
-- MindrianOS -- doctor -- no-drift --

  ■ dev-source                 ✓ consistent (1.12.0)

  Summary: 1 healthy / 0 drift / 0 warnings

  ▶ /mos:status                  # room state overview
  ▷ /mos:doctor --all          # re-run all classes
  ▷ /mos:doctor --json         # machine-readable output
```

## Example output (drift detected, no --fix)

```
-- MindrianOS -- doctor -- drift-detected --

  ■ room-md                    ⚠ 3 dir(s) missing ROOM.md or MINTO.md

  Summary: 0 healthy / 1 drift / 0 warnings

  [F.1 Next Move]
   ▶ Run /mos:doctor --fix
   ▷ Defer
   ▷ Free-Text

  ▶ /mos:doctor --fix --all     # auto-recover all drift classes
  ▷ /mos:rooms                 # inspect known rooms
  ▷ /mos:doctor --json         # machine-readable output
```

Note: per D-19, the renderer above is structural. Larry handles narrative interpretation of any drift finding when surfacing conversationally (e.g., "what does this mean?"). See ${CLAUDE_PLUGIN_ROOT}/references/personality/voice-dna.md for voice patterns.

## Zone 4 (Action Footer)

After presenting results, suggest next actions:

> Want to repair what we found? -> /mos:doctor --fix
> Want the full picture? -> /mos:status
