Doctor & trust
Honest environment validation, and why nothing runs before you approve it.
ways doctor answers one question before anyone runs a flow: is this environment actually
ready? It resolves everything the way (and its bound slots) requires and checks it: CLIs, env
vars, MCP servers, slot bindings, companion ways, pins.
When this matters
Worth being straight about it. If you're a small team where everyone already has the tooling
installed and configured, doctor will pass on the first run and you'll rarely think about it
again. That's the expected outcome for you.
It earns its keep when the environment can't be assumed:
- Onboarding. A developer joins and their machine has none of it. Instead of a wiki page that went stale, they get a list of exactly what's missing and the command to fix each item.
- Locked-down or regulated environments. Where you can't install whatever you want and the approved toolchain differs per client or per business unit.
- Client projects. You bring your way of working to someone else's stack and need to know up front which of your assumptions don't hold there.
- CI.
--strictturns "probably fine" into a build failure, so drift is caught by the pipeline rather than by whoever runs the flow next.
The failure mode it prevents is discovering a missing CLI or MCP server halfway through a gated flow, when you've already spent an hour of work and have to unwind.
Honest statuses, always
Every check reports exactly one of five statuses — and the CLI never pretends to know more than it does:
| Status | Meaning |
|---|---|
pass | verified present/valid |
fail | required and missing/broken — comes with a copy-pasteable remediation |
skipped | optional and absent — not an error |
unknown | present but unverifiable (e.g. a binding with no profile, an unrun check) |
not-inspectable | no inspector can see it (e.g. MCP config of an unsupported agent) |
Exit code 0 when nothing failed; --strict (CI) also fails on unknown. MCP configuration is
read through a pluggable agent inspector — the claude inspector ships first; other agents
honestly report not-inspectable rather than guessing.
Project mode
With no arguments, doctor validates the whole repo:
- Conflicts across installed ways (contract rule #14): the same slot id declared with incompatible contracts, or two ways colliding on a skill name.
- Pins: every way declared in
ways.yamlis installed, its content digest still matchesways.lock, and itsversionPinmatches the resolved ref. Drift =failwith the exact re-acquire command. - Then per-way checks for every declared way.
ways doctor incu/devscopes to one way — by installed name, no path needed.
Trust: approval is bound to content
The way's declared check commands (gh auth status, git --version) are executable content —
so doctor only runs them for trusted ways:
ways addshows the full plan — including everycheckcommand and the executable surface — and requires explicit approval.- Approval records
approvedDigestinways.lock: trust is bound to the content digest, not the name. - If the installed content changes in any way, the digest no longer matches:
doctordrops back to presence-only checks (unknown),listflags the drift, and re-approval is required.
No shell is ever used to execute checks (argv-spawn only), nothing runs before approval, and secrets are never echoed in any check output.
Provenance is only half of it
Everything above answers where the content came from — this repo, this commit, this digest, and you
approved it. It says nothing about whether that content is safe to place: a bundled SKILL.md
can carry a live API key, and a bundled MCP server can launch whatever @latest resolves to, at a
commit whose provenance is impeccable.
That's the other half, and it's checked automatically. Ways Shield scans
the acquired, pinned content — leaked credentials, risky or unpinned MCP servers, chained hook
commands — right before the approval gate runs, on both add and install. A leaked credential
refuses the install outright; the rest is disclosed in the plan and refuses under --strict.
Contracts a bound impl must satisfy
Once a slot is bound, doctor resolves the impl's Binding profile through the
contract catalogs and checks it
for real:
- Coverage. A binding that doesn't cover all of its
SlotContract's operations (coversOperations) is reported: advisory ⚠ by default, a failure under--strict. - Unresolvable contract →
unknown. If a slot's contract can't be resolved (no catalog provides it),doctorreports that slotunknownwith the real reason, and every other check still runs — one gap never masks the rest.
A catalog is executable surface, so it's trusted exactly like a way: its Bindings contribute
check commands, ways add discloses each catalog in the approval plan (attributed to catalog +
impl), a changed catalog digest forces re-approval, and read-only commands (doctor, bind,
ways contracts) are cache-only — they never fetch. WAYS_NO_CATALOGS=1 turns resolution off.