Skip to content

feat(doctor): adapter-owned toolchain presentation via describeToolchain (#435, part 2) - #442

Merged
debugmcpdev merged 2 commits into
mainfrom
feat/435-describe-toolchain
Aug 23, 2026
Merged

feat(doctor): adapter-owned toolchain presentation via describeToolchain (#435, part 2)#442
debugmcpdev merged 2 commits into
mainfrom
feat/435-describe-toolchain

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Part 2 of #435 (part 1 landed in #437): doctor presentation moves out of the CLI and into the adapter packages.

What changed

New optional IAdapterFactory.describeToolchain(validation, options?) (@debugmcp/shared): each adapter renders its own doctor runtime/backend row from the validate() result it just produced. The stringly-typed details keys are now produced and consumed inside the same class in the same file, behind a package-local type alias — renaming a key is compiler-checked where it matters instead of silently blanking doctor output. The member is optional, so out-of-tree/older factories keep working (doctor shows empty cells), isAdapterFactory and all plain-object test fakes stay valid.

Shared rendering rules move to toolchainComponent() / normalizeToolchainDescription() in @debugmcp/shared: a cell renders only when something was actually detected (a bare label would make an absent toolchain read as present); (built-in) style labels stand alone; the CLI defensively normalizes whatever a plain-JS factory returns.

presenters.ts is deleted — the 19-key presentLanguage switch, collectDoctorExtras, and the literal-specifier dynamic-import switch (the esbuild bundling workaround) are all gone. Doctor reaches the presentation through probe.factory, which it already holds; no dynamic imports remain in the presentation path. The former extras probes now live in their own packages: dotnet runs getNetcoredbgVersion + getDotnetSdkVersion in parallel; cpp reuses the validate()-discovered compiler command for the banner probe. describeToolchain runs under the same remaining-validate-budget timeout the extras used, with probe.timedOut still feeding the handler's force-exit containment.

The "wire the doctor command" step is removed from the new-adapter checklist in CLAUDE.md — a new adapter's doctor row ships inside the adapter.

Deliberate output changes

  • Bug fix: the js-debug cell rendered js-debug (vendored) even when validate() errored "js-debug adapter not found" (the CLI hardcoded it). It now renders only when the vendored payload was actually found.
  • netcoredbgVersion / dotnetSdkVersion / compilerVersion move from the --json details bag into the typed runtime/backend cells (details is now the raw validate() output, verbatim). schemaVersion stays 1 — the row shapes are supersets of the old ones.
  • The javacPath extra is dropped entirely: it was collected on every doctor run but rendered nowhere (only visible by spelunking --json). Java's presentation is now spawn-free; findJavacExecutable remains exported.

Tests

  • Per-language cell expectations moved from the deleted presenters.test.ts into each adapter package's factory tests (new test files for rust and cpp, which had none); dotnet pins "no probe without a debugger path", cpp pins command-reuse and banner-vs-bare-command.
  • diagnose.test.ts: extras tests rewritten as describeToolchain equivalents (hang → probe.timedOut, slow-import budget exclusion, duration accounting, rows carried into runtime/backend, throw keeps the verdict) plus new cases: factory without the method → empty cells; malformed return → normalized; valid:false still described.
  • Shared rule tests in packages/shared/tests/unit/toolchain-description.test.ts.
  • e2e doctor smoke now pins mock's (built-in) cells — deterministic proof adapter-owned presentation survives the real built bundle.
  • Parity fence (server-doctor-parity.test.ts) unchanged apart from dropping the removed collectExtras injection.

Verified locally: full build, lint, 222-file/4017-test unit suite, doctor e2e smoke, and a real mcp-debugger doctor run rendering all nine adapter-owned rows (including the relocated dotnet/cpp probes).

Closes nothing on its own — #435 stays open for parts 3–4 (launch gate through the shared probe; typed registry surface), which follow in the next PR.

🤖 Generated with Claude Code

…ain (#435, part 2)

Each adapter factory now owns its doctor row through an optional
IAdapterFactory.describeToolchain(validation, options?) that receives the
just-computed validate() result, so the producer and consumer of every
details key live in the same class - key renames are compiler-checked
inside the package instead of silently blanking doctor output.

- shared: ToolchainComponent/ToolchainDescription/DescribeToolchainOptions
  types, optional describeToolchain on IAdapterFactory, and
  toolchainComponent()/normalizeToolchainDescription() helpers carrying the
  omit-undetected and "(built-in)" stand-alone rendering rules
- all nine adapter factories implement describeToolchain; dotnet/cpp absorb
  the former doctor-only extras probes (netcoredbg/SDK versions in
  parallel, compiler banner reusing the discovered command); the js-debug
  cell now renders only when the vendored payload was actually found
  (previously it showed "js-debug (vendored)" even when validate errored)
- doctor: diagnose.ts calls probe.factory.describeToolchain under the
  remaining validate budget and normalizes the result; presenters.ts (the
  19-key stringly-typed mapping + literal-specifier import switch) is
  deleted - no dynamic imports remain in the presentation path
- JSON: schemaVersion stays 1; netcoredbgVersion/dotnetSdkVersion/
  compilerVersion move from details into the typed runtime/backend cells;
  the never-rendered javacPath extra is dropped (java is now spawn-free)
- docs: "wire the doctor command" removed from the new-adapter checklist

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Review findings on the adapter-owned presentation path, fixed:

- diagnose computes the verdict from a pre-call snapshot and hands
  describeToolchain a defensive clone: a buggy or malicious plain-JS
  factory can no longer mutate the validation object into flipping
  broken->ok (and zeroing a gated run's exit code)
- withTimeout wraps in Promise.resolve: a sync non-thenable return from an
  out-of-tree factory is now normalized instead of being discarded while a
  leaked timer stalls the CLI
- new shared probeWithinBudget(): dotnet/cpp version probes race the
  advisory options.timeoutMs (with headroom) and skip spawning entirely on
  an exhausted budget, so a hung probe degrades to the detail-derived
  cells instead of the caller's hard timeout blanking the whole row
- a factory without describeToolchain (older adapter package) now gets a
  display-only version-skew warning instead of silently empty cells; the
  verdict still stands on validate() alone
- MockAdapterFactory.describeToolchain declares the validation parameter
  (strict-mode arity)
- '('-standalone label convention documented on ToolchainComponent.label
  and the describeToolchain JSDoc
- cpp/dotnet factory-test mocks spread importOriginal so unrelated exports
  stay real (missing-export trap)
- findJavacExecutable deleted (its only caller was the removed extras
  path; unreleased export) along with its tests
- js vendored-badge gate simplified to truthiness (the shared helper
  already enforces the non-empty-string rule)
- shared README documents the new doctor-presentation exports

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

2 participants