# Doctor

> Run doctor for schema, config, and proxy truth-drift findings; apply auto fixes; route mention and route severities; opt out of boot staleness checks.

- Repository: pbakaus/impeccable
- GitHub: https://github.com/pbakaus/impeccable
- Human docs: https://grok-wiki.com/public/docs/pbakaus-impeccable-adadc04d8de4
- Complete Markdown: https://grok-wiki.com/public/docs/pbakaus-impeccable-adadc04d8de4/llms-full.txt

## Source Files

- `skill/reference/doctor.md`
- `skill/scripts/doctor.mjs`
- `skill/scripts/lib/staleness.mjs`
- `skill/scripts/lib/staleness-deep.mjs`
- `skill/scripts/lib/staleness-notice.mjs`
- `tests/doctor.test.mjs`
- `tests/staleness.test.mjs`

---

---
title: Doctor
description: Run doctor for schema, config, and proxy truth-drift findings; apply auto fixes; route mention and route severities; opt out of boot staleness checks.
---

`doctor` is a **maintenance utility**, not one of the 23 design sub-commands. It reports (and optionally repairs) drift between project Impeccable artifacts and what the installed skill version reads: `PRODUCT.md`, `DESIGN.md` and `.impeccable/design.json`, `.impeccable/config.json` / `config.local.json`, surface briefs, the design hook, and monorepo workspace context.

It does **not** redesign UI, open unrelated files, or run other commands as side effects. Findings are not errors: exit code is `0` unless the process itself fails.

## What doctor owns vs what it does not

Three kinds of “out of date” are kept separate:

| Kind | Meaning | Owner |
| --- | --- | --- |
| Tool version | Installed skill older than published | Boot `UPDATE_AVAILABLE` from `context.mjs`; fix with package update (`npx impeccable update`) |
| Schema drift | Older fields, missing stamps, retired paths, unknown config keys | **Doctor** (mechanical; many are fixable) |
| Truth drift | Docs no longer describe the code | Proxy signals only; **`init`** owns `PRODUCT.md`, **`document`** owns `DESIGN.md` |

Do not treat a large commit-count proxy as proof that `DESIGN.md` is wrong.

## Two tiers: boot vs on-demand

```mermaid
flowchart LR
  boot[Session boot via context.mjs] --> tier1[Tier 1 collectBootFindings]
  tier1 --> throttle[Weekly throttle for mention/route]
  throttle --> directive[CONTEXT_STALE directive]
  doctor[impeccable doctor / doctor.mjs] --> tier2[Tier 2 deep checks]
  tier2 --> report[Full findings + workspaces]
  report --> fixopt["--fix applies severity auto only"]
```

| Tier | When | Cost budget | Examples |
| --- | --- | --- | --- |
| **Tier 1** | Every session boot that runs `context.mjs` | Parses markdown already in memory, bounded `stat`s, small JSON already read. No git, no full directory walks for deep work | Deprecated `## Register`, product schema stamp, sidecar path/schema/mtime, unknown config keys, orphaned surface briefs, empty `projectRoots`, root-level native-vs-web mismatch |
| **Tier 2** | `/impeccable doctor` or `node …/doctor.mjs` | Git log, per-workspace sweep, detector rule registry, hook script resolution | `design-md-drift`, `design-md-coverage`, ignore list validation, hook install, legacy live paths, workspace inheritance / native evidence |

Boot emits **one** `CONTEXT_STALE` directive for the whole set. Severity `auto` findings are never throttled and are not meant for the user; `mention` and `route` surface at most **once per week per project** (cache under `~/.impeccable/staleness-check.json`, overridable with `IMPECCABLE_STALENESS_CACHE`).

Skill routing: load doctor when the user invokes it or asks what is stale; act on a boot `CONTEXT_STALE` per that directive, not by launching doctor unasked. Never repair drift as a side quest of a design task, except `auto` migrations the next write would perform anyway.

## Run doctor

### From the skill

Invoke `/impeccable doctor` (provider command prefix may be `/` or `$`). The agent should run the script with JSON output, then act by severity.

### From the script

Path is provider-aware (`{{scripts_path}}` in skill text). From a checkout:

```bash
node skill/scripts/doctor.mjs
node skill/scripts/doctor.mjs --json
node skill/scripts/doctor.mjs --fix
node skill/scripts/doctor.mjs --target apps/mobile
node skill/scripts/doctor.mjs --help
```

<ParamField body="--json" type="boolean">
Emit a machine-readable report (preferred for agents).
</ParamField>

<ParamField body="--fix" type="boolean">
Apply **only** severity `auto` migrations. No judgment calls (platform, truth drift, init/document).
</ParamField>

<ParamField body="--target" type="string">
Select a monorepo workspace, file, or route path. Without it, the report is for the resolved project root (often wrong in monorepos). Also accepts `-t` / `--target=`.
</ParamField>

<ParamField body="--help" type="boolean">
Print usage and exit without running checks.
</ParamField>

**Exit codes:** `0` on success (including when findings exist); non-zero if argument parsing fails or the run throws. Findings are never treated as failures.

### Expected clean outcome

Human text:

```text
Impeccable doctor: .
No drift found. Every artifact matches what this version reads.
```

JSON: `"findings": []`.

## Severity model

Severity is **what should happen**, not how bad it is.

| Severity | Agent / user action |
| --- | --- |
| `auto` | No decision. Run `doctor.mjs --fix` once, report what moved in one line. Do not ask first or after. |
| `mention` | State each finding in a sentence with its offered fix. No blocking decision required now. |
| `route` | Name the owning command and the gap. Run `init` / `document` only if the user asks this turn. |

Report all groups in one pass. Text mode groups as *needs a command* → *worth saying* → *automatic*.

### Deprecated fields are binding

Findings for deprecated PRODUCT sections (today: `## Register`) are not style notes. Treat that field as **absent** for every decision this session, whatever value it holds, and offer deletion. Preserving “just in case” keeps a retired axis steering output.

### Truth-drift restraint

- `design-md-drift` counts commits to visual source dirs (`src`, `app`, `pages`, `components`, `site`, `styles`, `public`) since `DESIGN.md` was last committed. Default threshold: **25** commits. It is a **proxy**: report the number and measurement, do not assert the document is wrong.
- `workspace-context-inherited` is designed inheritance, not a defect. Whether one product record describes several apps is a user judgment.

## Finding shape

Every finding is data shared by boot, doctor text, and JSON:

<ResponseField name="id" type="string">
Stable identifier (e.g. `product-schema-legacy`, `design-sidecar-legacy-path`).
</ResponseField>

<ResponseField name="artifact" type="string">
Logical artifact: `PRODUCT.md`, `DESIGN.md`, `design.json`, `config.json`, `surface brief`, `hook manifest`, `live state`, etc.
</ResponseField>

<ResponseField name="path" type="string | null">
Project-relative path when known.
</ResponseField>

<ResponseField name="severity" type="string">
`auto` | `mention` | `route`.
</ResponseField>

<ResponseField name="summary" type="string">
What was detected.
</ResponseField>

<ResponseField name="fix" type="string">
What to do next.
</ResponseField>

### JSON report envelope

When `--json` is set, stdout is an object with:

| Field | Meaning |
| --- | --- |
| `projectRoot`, `repoRoot`, `isMonorepo` | Resolution context |
| `productPath`, `designPath`, `platform` | Active product/design/platform |
| `ruleRegistryAvailable` | `false` if detector rule ids could not be validated; say so, do not imply ignore lists are clean |
| `findings` | Array of findings |
| `workspaces` | Per-app product/design/platform status in monorepos |
| `fixes` | Present only with `--fix`: `{ applied: string[], skipped: { id, reason }[] }` |

## Checks catalog

### PRODUCT.md (Tier 1 + doctor)

| `id` | Severity | Trigger | Typical fix |
| --- | --- | --- | --- |
| `product-deprecated-<section>` | `mention` | Deprecated section present (e.g. `Register`) | Treat as absent; offer delete |
| `product-schema-legacy` | `route` | No stamp and none of the v4 sections (`Positioning`, `Operating Context`, `Evidence on Hand`, `Product Principles`) | Offer `init` (preserves confirmed answers) |
| `product-schema-outdated` | `route` | Stamp older than current product schema (`1`) | Offer `init` |
| `platform-native-evidence` | `mention` | Resolves to `web` but native evidence exists (Flutter/RN/Expo, `ios/Podfile`, Android gradle, etc.) | Confirm `## Platform` (`ios` / `android` / `adaptive`) |

Schema stamps look like `<!-- impeccable:product-schema 1 -->`. Stamps are **schema** versions, not skill release versions.

### DESIGN.md and sidecar

| `id` | Tier | Severity | Trigger | Typical fix |
| --- | --- | --- | --- | --- |
| `design-sidecar-legacy-path` | 1 | `auto` | Sidecar at `DESIGN.json` (or legacy context path) instead of `.impeccable/design.json` | `--fix` moves when canonical missing |
| `design-sidecar-schema-outdated` | 1 | `route` | Sidecar schema behind current (`2`) | Offer `document` |
| `design-sidecar-stale` | 1 | `mention` | `DESIGN.md` mtime newer than sidecar | Offer `document` |
| `design-md-drift` | 2 | `route` | ≥25 commits to visual dirs since DESIGN last edited | Re-read tokens/components; `document` if truly drifted |
| `design-md-coverage` | 2 | `mention` | Missing required sections (seed: colors/typography; full: + components) | Ask if N/A; else `document` |

### Config and detector ignores

Recognized **top-level** config keys: `hook`, `detector`, `updateCheck`, `stalenessCheck`, `projectRoots`, `$schema`, `version`.

Recognized **`detector`** keys: `ignoreRules`, `ignoreFiles`, `ignoreValues`, `designSystem`, `extensions`.

| `id` | Severity | Trigger |
| --- | --- | --- |
| `config-unknown-keys` | `mention` | Top-level keys nothing reads (typos never applied) |
| `config-unknown-detector-keys` | `mention` | e.g. singular `ignoreRule` instead of `ignoreRules` |
| `config-project-roots-match-nothing` | `mention` | Every positive `projectRoots` glob misses → repo root silently active |
| `detector-ignore-rules-unknown` | `mention` | Ignore rule ids not in live `ANTIPATTERNS` (skipped if registry unavailable) |
| `detector-ignore-files-missing` | `mention` | Non-glob ignore file paths that no longer exist |

### Surface briefs, hook, live

| `id` | Severity | Trigger | Notes |
| --- | --- | --- | --- |
| `surface-brief-orphaned` | `mention` | Primary target path missing (skips `http(s):` and `route:`) | Repoint or delete brief |
| `hook-script-missing` | `mention` | Provider hook manifest points at non-existent script | Reinstall hooks; unresolved placeholders (`${CLAUDE_PLUGIN_ROOT}`, `$(…)`) are **not** reported missing |
| `hook-enabled-conflict` | `mention` | Manifest installed but `hook.enabled: false` | Enable or uninstall manifest |
| `legacy-live-state` | `auto` | `.impeccable-live.json` / `.impeccable-live` present | Current live uses `.impeccable/live/`; **`--fix` does not delete** (avoid killing a running session) |

### Monorepo (doctor)

| `id` | Severity | Trigger | Fix guidance |
| --- | --- | --- | --- |
| `workspace-platform-native-evidence` | `mention` | Workspace has native evidence while product resolves to web (especially when **inheriting** root PRODUCT) | Child `PRODUCT.md` with correct `## Platform` |
| `workspace-context-inherited` | `mention` | One or more workspaces inherit root PRODUCT | Informational; `init` in workspace if inheritance is wrong |

Use the `workspaces` table (`path`, `productStatus`, `designStatus`, `platform`) before proposing structure changes.

## What `--fix` actually does

| Case | Behavior |
| --- | --- |
| `design-sidecar-legacy-path` | `rename` to `.impeccable/design.json` if canonical does not already exist; never overwrite |
| Product stamp | Stamps current schema when PRODUCT has content that is **not** `product-schema-legacy` (legacy needs `init`, not a silent stamp) |
| `legacy-live-state` | Skipped: “delete by hand once no live session is running” |
| All other severities | Skipped: “needs a decision from the user” |

## Opt out of boot staleness only

Doctor still runs fully when boot checks are off. Prefer this when you want reports **only on demand**.

| Mechanism | Scope |
| --- | --- |
| `"stalenessCheck": false` in `.impeccable/config.json` | Project default |
| Same key in `config.local.json` | Overrides shared (last boolean wins across roots scanned) |
| `IMPECCABLE_NO_STALENESS_CHECK=1` | One session / process |
| `IMPECCABLE_STALENESS_CACHE` | Override path for the throttle cache file |

```json
{
  "stalenessCheck": false
}
```

Tests that assert on other boot directives should disable staleness so `CONTEXT_STALE` noise does not flake assertions.

## Workflow

<Steps>
  <Step title="Run the pass">
    Prefer `node <scripts_path>/doctor.mjs --json`. Add `--target <path>` in monorepos. Empty `findings` → say clean and stop.
  </Step>
  <Step title="Apply automatic migrations">
    If any finding has `severity: "auto"`, run once with `--fix`. Report applied lines only.
  </Step>
  <Step title="Surface mention findings">
    One sentence each with the offered fix. Do not expand scope into redesign.
  </Step>
  <Step title="Route command-owned gaps">
    Name `init` or `document` (or hooks reinstall) and the gap. Run only if the user asks this turn.
  </Step>
  <Step title="Honor deprecated fields">
    For deprecated PRODUCT sections, treat values as absent for the rest of the session.
  </Step>
</Steps>

## Troubleshooting

| Symptom | Likely cause | Action |
| --- | --- | --- |
| Boot never mentions drift | Disabled via config/env, or still inside weekly throttle | Run doctor explicitly; check `stalenessCheck` / `IMPECCABLE_NO_STALENESS_CHECK` |
| `ruleRegistryAvailable: false` | Detector engine not resolvable from skill install layout | Do not claim ignore rule ids are valid; reinstall skill or run from full checkout |
| Monorepo always “root” | Empty `projectRoots` matches | Fix globs; use `--target` |
| Native app gets web guidance | Missing/wrong `## Platform` or inherited web PRODUCT | Child PRODUCT + platform value |
| `--fix` did not stamp PRODUCT | `product-schema-legacy` needs interview | Offer `init` |
| Hook “installed” but silent | Broken script path or `hook.enabled: false` | Doctor `hook-*` findings; reinstall or align config |
| Live legacy paths remain after `--fix` | Intentional | Delete `.impeccable-live*` only when no live session runs |

## Implementation map

| Piece | Role |
| --- | --- |
| `skill/reference/doctor.md` | Agent procedure for the utility command |
| `skill/scripts/doctor.mjs` | CLI: collect, render, `--fix` |
| `skill/scripts/lib/staleness.mjs` | Tier 1 checks + `collectBootFindings` |
| `skill/scripts/lib/staleness-deep.mjs` | Tier 2 checks + rule registry load |
| `skill/scripts/lib/staleness-notice.mjs` | Opt-out, throttle, `CONTEXT_STALE` text |
| `skill/scripts/lib/artifact-schema.mjs` | Product/sidecar schema versions and stamps |
| `skill/scripts/context.mjs` | `appendStalenessDirective` on boot |
| `tests/doctor.test.mjs`, `tests/staleness.test.mjs` | CLI and pure-check coverage |

## Related pages

<CardGroup cols={2}>
  <Card title="Project artifacts" href="/project-artifacts">
    PRODUCT.md, DESIGN.md, sidecar, schema stamps, and `.impeccable/` layout.
  </Card>
  <Card title="Initialize product context" href="/init-and-document">
    `init` and `document` own truth rewrites doctor only routes.
  </Card>
  <Card title="Configuration reference" href="/configuration-reference">
    `stalenessCheck`, `projectRoots`, detector, and hook keys.
  </Card>
  <Card title="Environment variables" href="/environment-variables">
    `IMPECCABLE_NO_STALENESS_CHECK` and related controls.
  </Card>
  <Card title="Modes and platform" href="/modes-and-platform">
    Platform values and why native evidence mismatches matter.
  </Card>
  <Card title="Design hook" href="/design-hook">
    Hook install paths doctor validates.
  </Card>
</CardGroup>
