ways
Concepts

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. --strict turns "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:

StatusMeaning
passverified present/valid
failrequired and missing/broken — comes with a copy-pasteable remediation
skippedoptional and absent — not an error
unknownpresent but unverifiable (e.g. a binding with no profile, an unrun check)
not-inspectableno 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.yaml is installed, its content digest still matches ways.lock, and its versionPin matches the resolved ref. Drift = fail with the exact re-acquire command.
  • Then per-way checks for every declared way. ways doctor incu/dev scopes 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:

  1. ways add shows the full plan — including every check command and the executable surface — and requires explicit approval.
  2. Approval records approvedDigest in ways.lock: trust is bound to the content digest, not the name.
  3. If the installed content changes in any way, the digest no longer matches: doctor drops back to presence-only checks (unknown), list flags 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), doctor reports that slot unknown with 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.

On this page