Skip to content

feat: behold doctor — the read-only first-touch diagnosis (#236) - #244

Merged
lex00 merged 1 commit into
mainfrom
feat/doctor-236
Aug 10, 2026
Merged

feat: behold doctor — the read-only first-touch diagnosis (#236)#244
lex00 merged 1 commit into
mainfrom
feat/doctor-236

Conversation

@lex00

@lex00 lex00 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #236.

behold doctor [dir] [--json] — a read-only console report of everything behold needs to serve a project well. Seven lines, each pass/warn/fail with a one-line fix, exit 0 iff nothing failed.

behold doctor 0.7.0 — /path/to/example-k8s

  pass  project     chant project (chant.config.ts)
  pass  chant       chant 0.44.2 (behold's floor 0.38.0)
  pass  lexicons    2 installed: k8s 0.44.2, temporal 0.44.2
  pass  envs        local — `behold serve example-k8s --env local` for the live overlay
  warn  kube        context not in the kubeconfig — local: k3d-behold-k3d-demo
        fix         Add the context to your kubeconfig, or point k8s.profiles at one you have (…, +10 more).
  warn  substrates  Docker up, k3d down, Temporal blocked
        fix         Bring up: k3d (bash scripts/local/local-up.sh), Temporal (no temporal.profiles in chant.config.ts — chant run will refuse)
  pass  ops         1 committed: k3d-apply (apply); chant MCP available (the project's own chant)

  5 pass, 2 warn, 0 fail

The checks

line source fail when
project detectProjectShape (project.ts) neither a chant project nor an estate root
chant resolveChant (chant.ts) the project has no chant of its own; warns below behold's floor
lexicons resolveLexicons (chant.ts) a declared lexicon isn't installed
envs detectProject never — no envs warns, and says "no live overlay" plainly
kube loadKubeconfig + resolveK8sTarget (k8s-target.ts) never — no kubeconfig, an unresolvable context, or an ambient/declared mismatch (chant#1100 refuses the live read) all warn
substrates detectSubstrates (substrates.ts) never — a down substrate costs the live half, not the graph
ops discoverEstateOps (ops.ts) never — no committed Ops warns; MCP availability rides along

A fail is something that stops behold serving the project. A warn costs the live half — which is why a k8s project with no kube context warns rather than fails, per the acceptance list.

Reuse, not new probing

Every fact comes from the module the server already reads it from — including k8s-target.ts exactly as-is (no chant-k8s-client; #231 stays its own lane). Where a check needed detection the server didn't have, the logic went into the shared module:

  • project.tsdetectProjectShape (chant.config.* / .behold.json members / npm workspaces) and chantConfigPath. cli.ts's warnIfNotChantProject now shares it, so behold serve on an estate root points at the members instead of reporting a missing chant.config.ts.
  • chant.tsresolveChant / resolveLexicons / chantFloor / meetsFloor, with chantBin re-expressed through resolveChant and chantBinFrom kept as a wrapper over the new chantPackageFrom.

resolveChant compares package roots, not requires, to tell the project's own chant from behold's fallback. Node resolution walks up parent directories, so every bundled example resolves behold's chant without having one of its own — reported as "the project's", the check would have missed the exact missing install it exists to catch.

Estates are diagnosed across their members: per-member chant and lexicon lines, unioned envs and lexicons, discoverEstateOps for the Ops, and the serve hint names the members rather than the unservable root.

Tests

src/doctor.test.ts — healthy project (all pass), no-project dir (single fail + fix), missing install (fail naming npm install), k8s with no context (warn), bound context resolved, ambient/declared mismatch, down substrate, no envs, no Ops, estate via .behold.json and via workspaces. Plus a --json shape test (key order, one check per name, fix present iff not pass) and an inline snapshot of the console output. Fixtures are built in the OS tmpdir and cleaned up — example-writes' node_modules is absent in a fresh checkout and present after just example-install, so a test keyed on either state would pass half the time. The two live probes (kubeconfig, substrates) are injected, so nothing depends on the host's Docker or kubeconfig.

Docs: README quickstart, AGENTS.md discovery step, and the "Open your own project" docs page.

just check green.

🤖 Generated with Claude Code

Seven lines, each pass/warn/fail with a one-line fix: the project's kind
(chant project, estate root, neither), the chant install behold will
actually shell and its version against behold's declared floor, declared
lexicons against what's installed, the envs the picker will infer, the
kube context chant binds versus the ambient one, substrate readiness
without starting a server, and committed Ops plus whether chant's MCP is
reachable at all. Exit 0 iff nothing failed; `--json` for machines.

A reuse surface, not new probing: every fact comes from the module the
server already reads it from. Three pieces of detection that were missing
went into the shared modules rather than into doctor —
`detectProjectShape` (project.ts: chant.config.* / `.behold.json`
members / npm workspaces), which `cli.ts`'s startup warning now shares so
an estate root gets the member list instead of "no chant.config.ts"; and
`resolveChant`/`resolveLexicons`/`chantFloor`/`meetsFloor` (chant.ts),
with `chantBin` re-expressed through the first of them.

`resolveChant` compares package roots, not requires, to tell the
project's own chant from behold's fallback: Node resolution walks up
parent directories, so every bundled example resolves behold's chant
without having one, and the check that exists to catch a missing install
would have missed it.

The severity split is the point. A fail stops behold serving the project
(no chant in the project's node_modules is the common first-touch one).
A warn costs the live half, not the graph — no envs, no cluster binding,
a down emulator — so a k8s project with no kube context warns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lex00
lex00 merged commit a2b4f5b into main Aug 10, 2026
3 checks passed
lex00 added a commit that referenced this pull request Aug 10, 2026
 #249) (#251)

Six dependabot alerts (five hono, one @hono/node-server) all had compatible
fixes — hono ^4.6.0 → ^4.12.34 stays within its major, @hono/node-server
^1.13.0 → ^2.0.5 needed a major bump (no fix landed on 1.x) but its only two
breaking changes don't touch us: Node ≥20 (CI already pins 24) and the
removed Vercel adapter (never imported). Reasoning and the alert table are
recorded as a comment on #248.

Docs freshness: example-argo-estate/README.md's logical-lens bullet still
described the control-plane-alone view as the headline from before behold#224
taught the composed estate to draw the projection — reworded to lead with the
composed picture. example-flux-estate/README.md and AGENTS.md were checked
against current src/server.ts behavior and are already accurate (fixed by
#241 and #244 respectively, no change needed). Root README's painter section
gained three paragraphs — lexicon-native icons (#227), hand layout (#228),
the identity pass (#229) — the only features among the four named in #249
with zero prior README coverage; doctor (#236) already has one.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@lex00
lex00 deleted the feat/doctor-236 branch August 30, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

behold doctor — the first-touch diagnosis: why won't this project serve well

1 participant