dw-doctor — read-only environment diagnostic
Confirm the machine actually has what this repo's hooks and skills assume, and that the wiring
resolves — before a missing tool silently degrades things. The sharpest case: every
.claude/hooks/*.sh that parses a payload opens with command -v jq >/dev/null || exit 0 —
bash-guard.sh, the dispatcher every Bash guard hangs off, among them — so without jq the
dangerous-command block, .env protection, pnpm enforcement, and lint-on-edit/typecheck-on-commit hooks
all quietly no-op and nobody notices. Same failure class for a missing pnpm, a
settings.json pointing at a hook that isn't executable, or a typecheck hook with no tsc to
call.
Read-only, and it takes no arguments: it probes (command -v, --version) and reads files,
then reports. It never installs a tool, never edits a file, never runs the fixes it suggests —
applying them is your call.
What it reads
It diagnoses the current git repo (resolved via git rev-parse --show-toplevel), not the
skill's own location. Checks are conditional on what the repo declares, so nothing about a stack is
assumed:
package.json — the Node pin (devEngines.runtime first, then engines.node), the pnpm pin
(devEngines.packageManager first, then the older packageManager), declared deps, and
scripts.typecheck (drives the JS/TS checks).
tsconfig.json, .nvmrc — presence informs the tsc check and whether the node fix hint names
.nvmrc at all; a repo pinning through devEngines.runtime has no such file.
.claude/settings.json — parsed for every wired hook command; each referenced *.sh is checked
for existence, the executable bit, and drift against this plugin's templates/hooks/, since a
stale vendored copy passes the first two while missing what the template learned since. WARN,
never FAIL — a patched hook is a legitimate choice; a hook with no template is the repo's own.
.ai/work/ — the scaffold this lane runs on; its absence points at dw-init, and a .ai/runs/
directory is flagged as the other lane's repo rather than half-checked.
docs/decisions/ and CONTEXT.md — the promotion targets dw-land writes into. Presence only:
the record contract is prose dw-land reads while writing one, not something this skill parses.
AGENTS.md — the one always-loaded file, and the block worth the most here. Present? Does it
declare a Budget: line, is that line parseable, and is the file inside it? Is there a
## Task Router with rows, and does every docs/agents/*.md have one? Did a {{PLACEHOLDER}}
survive the render? Is CLAUDE.md the symlink to it rather than a second copy that will
diverge? And is scripts/check-agents-docs.mjs there to enforce any of it.
- The checker's opt-in size gates — the corpus ratchet, the record ceiling, the topic and term
budgets. Each says nothing when switched off, which is right for a gate and blinding for a
diagnostic, so each is reported here with the line or command that turns it on.
info, not warn:
declining one is a choice, and only the layers a repo actually keeps are mentioned at all.
- The
- **Lint command**: / - **Typecheck command**: bullets, resolved in the hooks' own
order — AGENTS.md, then a legacy CLAUDE.local.md — and extracted the way the hooks extract
them, first backticked span else the rest of the line. Reporting a command the hook would not
actually run is the one failure mode a diagnostic cannot afford. A value of none reports OK: it
is what tells the hook to skip.
CLAUDE.local.md — informational only. Nothing writes it any more; where one exists it is a
legacy fallback, not a gap.
.claude-plugin/marketplace.json — only if present (a marketplace repo); a light
plugin/version-sync glance.
- Tool presence on
PATH via command -v: git, jq, gh, codex, node, pnpm, and the
project-local agnix / prettier / tsc binaries. codex is WARN-tier and never FAIL — the
loop works without it, only dw-check's outside reviewer and dw-land's review offer on the PR degrade —
and the check stops at "installed": probing auth would mean a network call from a read-only
diagnostic.
- The
dw-solo-extras plugin, found by its cache directory rather than on PATH. It ships
dw-decisions, which dw-land, dw-grill and dw-shape each route to "where installed" without
being able to look — so the answer belongs here. WARN-tier like codex, and stopping at
"installed": whether it is enabled lives in the user's global settings, which a repo diagnostic
does not read.
Workflow
1. Run the bundled script
From anywhere inside the target repo, run the script shipped with this skill:
bash "<this-skill-dir>/scripts/doctor.sh"
<this-skill-dir> is the directory holding this SKILL.md (e.g. skills/dw-doctor in source, or
the installed plugin's skills/dw-doctor). The script resolves the repo itself, so the working
directory only needs to be somewhere inside the repo you want diagnosed.
2. Relay the report
The script prints grouped OK / WARN / FAIL lines with a one-line fix on each non-OK.
Summarize it for the user and lead with any FAIL — especially jq or git, since those gate
everything else. Surface the install commands it prints (e.g. brew install jq,
corepack enable, pnpm install) verbatim so they can copy-paste, but do not run them yourself.
3. Stop
Report and hand off. Fixing the environment is the user's action; dw-doctor only diagnoses. If
the report shows .ai/runs/, the repo runs the team lane from the other marketplace — say so
plainly instead of pointing anywhere in this one.
Guardrails
- Stack-adaptive. JS/TS checks run only when
package.json exists; tsc only when the repo asks
for typechecking. The marketplace check fires only when marketplace.json is present.
- Never guesses. It reports observed state and the consequence of each gap; it doesn't infer
intent or "fix" anything for you.
Next: dw-init if the scaffold is incomplete, else dw-shape for a new change or dw-next to
pick the active one back up.
1---2name: dw-doctor3description: Read-only environment diagnostic for a solo-lane repo: whether the tools the hooks and skills assume are installed, and whether the guardrails and the pre-commit really fire here or are silently skipping, each gap reported with a copy-paste fix.4---56# dw-doctor — read-only environment diagnostic78Confirm the machine actually has what this repo's hooks and skills assume, and that the wiring9resolves — before a missing tool silently degrades things. The sharpest case: every10`.claude/hooks/*.sh` that parses a payload opens with `command -v jq >/dev/null || exit 0` —11`bash-guard.sh`, the dispatcher every Bash guard hangs off, among them — so without `jq` the12dangerous-command block, `.env` protection, pnpm enforcement, and lint-on-edit/typecheck-on-commit hooks13**all quietly no-op** and nobody notices. Same failure class for a missing `pnpm`, a14`settings.json` pointing at a hook that isn't executable, or a typecheck hook with no `tsc` to15call.1617**Read-only, and it takes no arguments:** it probes (`command -v`, `--version`) and reads files,18then reports. It never installs a tool, never edits a file, never runs the fixes it suggests —19applying them is your call.2021## What it reads2223It diagnoses the **current git repo** (resolved via `git rev-parse --show-toplevel`), not the24skill's own location. Checks are conditional on what the repo declares, so nothing about a stack is25assumed:2627- `package.json` — the Node pin (`devEngines.runtime` first, then `engines.node`), the pnpm pin28 (`devEngines.packageManager` first, then the older `packageManager`), declared deps, and29 `scripts.typecheck` (drives the JS/TS checks).30- `tsconfig.json`, `.nvmrc` — presence informs the `tsc` check and whether the node fix hint names31 `.nvmrc` at all; a repo pinning through `devEngines.runtime` has no such file.32- `.claude/settings.json` — parsed for every wired hook command; each referenced `*.sh` is checked33 for existence, the executable bit, and **drift** against this plugin's `templates/hooks/`, since a34 stale vendored copy passes the first two while missing what the template learned since. `WARN`,35 never `FAIL` — a patched hook is a legitimate choice; a hook with no template is the repo's own.36- `.ai/work/` — the scaffold this lane runs on; its absence points at `dw-init`, and a `.ai/runs/`37 directory is flagged as the other lane's repo rather than half-checked.38- `docs/decisions/` and `CONTEXT.md` — the promotion targets `dw-land` writes into. Presence only:39 the record contract is prose `dw-land` reads while writing one, not something this skill parses.40- **`AGENTS.md` — the one always-loaded file, and the block worth the most here.** Present? Does it41 declare a `Budget:` line, is that line parseable, and is the file inside it? Is there a42 `## Task Router` with rows, and does every `docs/agents/*.md` have one? Did a `{{PLACEHOLDER}}`43 survive the render? Is `CLAUDE.md` the **symlink** to it rather than a second copy that will44 diverge? And is `scripts/check-agents-docs.mjs` there to enforce any of it.45- **The checker's opt-in size gates — the corpus ratchet, the record ceiling, the topic and term46 budgets.** Each says nothing when switched off, which is right for a gate and blinding for a47 diagnostic, so each is reported here with the line or command that turns it on. `info`, not `warn`:48 declining one is a choice, and only the layers a repo actually keeps are mentioned at all.49- **The `- **Lint command**:` / `- **Typecheck command**:` bullets**, resolved in the hooks' own50 order — `AGENTS.md`, then a legacy `CLAUDE.local.md` — and **extracted the way the hooks extract51 them**, first backticked span else the rest of the line. Reporting a command the hook would not52 actually run is the one failure mode a diagnostic cannot afford. A value of `none` reports OK: it53 is what tells the hook to skip.54- `CLAUDE.local.md` — informational only. Nothing writes it any more; where one exists it is a55 legacy fallback, not a gap.56- `.claude-plugin/marketplace.json` — only if present (a marketplace repo); a light57 plugin/version-sync glance.58- Tool presence on `PATH` via `command -v`: `git`, `jq`, `gh`, `codex`, `node`, `pnpm`, and the59 project-local `agnix` / `prettier` / `tsc` binaries. `codex` is WARN-tier and **never** FAIL — the60 loop works without it, only `dw-check`'s outside reviewer and `dw-land`'s review offer on the PR degrade —61 and the check stops at "installed": probing auth would mean a network call from a read-only62 diagnostic.63- **The `dw-solo-extras` plugin**, found by its cache directory rather than on `PATH`. It ships64 `dw-decisions`, which `dw-land`, `dw-grill` and `dw-shape` each route to "where installed" without65 being able to look — so the answer belongs here. WARN-tier like `codex`, and stopping at66 "installed": whether it is _enabled_ lives in the user's global settings, which a repo diagnostic67 does not read.6869## Workflow7071### 1. Run the bundled script7273From anywhere inside the target repo, run the script shipped with this skill:7475```76bash "<this-skill-dir>/scripts/doctor.sh"77```7879`<this-skill-dir>` is the directory holding this `SKILL.md` (e.g. `skills/dw-doctor` in source, or80the installed plugin's `skills/dw-doctor`). The script resolves the repo itself, so the working81directory only needs to be somewhere inside the repo you want diagnosed.8283### 2. Relay the report8485The script prints grouped `OK` / `WARN` / `FAIL` lines with a one-line fix on each non-OK.86Summarize it for the user and **lead with any `FAIL`** — especially `jq` or `git`, since those gate87everything else. Surface the install commands it prints (e.g. `brew install jq`,88`corepack enable`, `pnpm install`) verbatim so they can copy-paste, but do not run them yourself.8990### 3. Stop9192Report and hand off. Fixing the environment is the user's action; `dw-doctor` only diagnoses. If93the report shows `.ai/runs/`, the repo runs the team lane from the other marketplace — say so94plainly instead of pointing anywhere in this one.9596## Guardrails9798- **Stack-adaptive.** JS/TS checks run only when `package.json` exists; `tsc` only when the repo asks99 for typechecking. The marketplace check fires only when `marketplace.json` is present.100- **Never guesses.** It reports observed state and the consequence of each gap; it doesn't infer101 intent or "fix" anything for you.102103**Next:** `dw-init` if the scaffold is incomplete, else `dw-shape` for a new change or `dw-next` to104pick the active one back up.