Follow-ups from the #423 doctor command review — two structural improvements deferred from the initial implementation:
1. One shared per-entry availability probe
diagnoseLanguage (src/cli/commands/doctor/diagnose.ts) hand-mirrors the server's handleListSupportedLanguages per-entry loop (src/server.ts: disabled gate → installed gate → getFactory().catch → metadata-over-entry attach preference), with only computeModeAvailability actually shared. Doctor's "can never disagree with the server" guarantee currently rests on manual mirroring: the next change to the server's loop semantics lands in server.ts only and doctor silently diverges — the exact class of drift doctor exists to expose.
Extract one shared per-entry probe (natural home: src/utils/language-availability.ts) consumed by both handleListSupportedLanguages and diagnose, and add a parity test that runs both paths against the same fake registry and asserts identical modes.
2. Adapter-owned doctor presentation
presentLanguage and collectDoctorExtras (src/cli/commands/doctor/presenters.ts) hardcode ~20 stringly-typed validate().details keys plus language→package→export-name mappings in the CLI layer, duck-typed through Partial casts with silent fallbacks. Renaming a details key or an extras export in an adapter package compiles clean and passes all tests while doctor's output silently blanks.
Deeper fix: adapter-owned presentation — typed runtime/backend fields on AdapterMetadata, or an optional IAdapterFactory.describeToolchain() — so an adapter package owns its own doctor row and the CLI stops restating adapter internals. This also removes the "wire the doctor command" step from the new-adapter checklist in CLAUDE.md.
Follow-ups from the #423 doctor command review — two structural improvements deferred from the initial implementation:
1. One shared per-entry availability probe
diagnoseLanguage(src/cli/commands/doctor/diagnose.ts) hand-mirrors the server'shandleListSupportedLanguagesper-entry loop (src/server.ts: disabled gate → installed gate →getFactory().catch→ metadata-over-entry attach preference), with onlycomputeModeAvailabilityactually shared. Doctor's "can never disagree with the server" guarantee currently rests on manual mirroring: the next change to the server's loop semantics lands in server.ts only and doctor silently diverges — the exact class of drift doctor exists to expose.Extract one shared per-entry probe (natural home:
src/utils/language-availability.ts) consumed by bothhandleListSupportedLanguagesanddiagnose, and add a parity test that runs both paths against the same fake registry and asserts identical modes.2. Adapter-owned doctor presentation
presentLanguageandcollectDoctorExtras(src/cli/commands/doctor/presenters.ts) hardcode ~20 stringly-typedvalidate().detailskeys plus language→package→export-name mappings in the CLI layer, duck-typed throughPartialcasts with silent fallbacks. Renaming a details key or an extras export in an adapter package compiles clean and passes all tests while doctor's output silently blanks.Deeper fix: adapter-owned presentation — typed runtime/backend fields on
AdapterMetadata, or an optionalIAdapterFactory.describeToolchain()— so an adapter package owns its own doctor row and the CLI stops restating adapter internals. This also removes the "wire the doctor command" step from the new-adapter checklist in CLAUDE.md.