fix(extensions): surface type-extraction warnings in doctor JSON output (swamp-club#351)#1384
Conversation
…ut (swamp-club#351) Post-W7, models with non-literal type fields (e.g. `type: TYPE` where TYPE is a const) emit stderr warnings but don't appear in `swamp doctor extensions --json`. Runtime verification confirmed these extensions work correctly — the warning is about static catalog extraction, not a genuine failure. Add a `warnings[]` field to the doctor JSON report to surface these diagnostics without misleading users by treating working extensions as broken. - Add structured warning storage and getter to the extension load warning emitter alongside existing deduplication keys - Add `DoctorWarning` type and `warnings` field to `DoctorExtensionsReport` - Wire `getWarnings`/`resetWarnings` DI callbacks through `DoctorExtensionsDeps` (matches `getRecentTransitions` pattern) - Reset warning state at doctor invocation start to prevent stale bootstrap warnings from leaking into output - Render warnings in both JSON and log modes (advisory, not failures) - Exit code stays 0 for warnings-only Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
-
Log-mode: missing actionable hint after warnings — The orphan-files section (line 307–310) follows its warning header with a dim hint explaining what to do next (
swamp extension pull --force/swamp extension update). The new warnings section (lines 314–330) showssourcePathandmessagebut stops there. A short dim hint — e.g.Use a string literal for thetypefield (e.g.type: '@org/name') to enable static catalog extraction.— would give users a clear next action without having to look up the docs. -
Log-mode: slight wording drift — Orphan files says
(warnings, not failures); the new warnings section says(advisory, not failures). Both are clear, but aligning on one phrase would be cleaner. -
Stale comment — Line 387–388:
// All five keys are always present— there are now more than five keys. Code-quality only, no UX impact.
Verdict
PASS — warnings[] is correctly added to JSON output (always present, empty array on clean run), log-mode renders with consistent icon/color/indentation, exit code and overallStatus are unaffected by warnings. No blocking issues.
There was a problem hiding this comment.
Code Review
Clean, well-structured PR. The warning plumbing follows the established getRecentTransitions DI-callback pattern exactly, the domain layer stays free of infrastructure dependencies, and the test coverage is thorough (unit + integration + renderer fixtures).
Blocking Issues
None.
Suggestions
-
Dead variable in
doctor_test.ts:553—const warnings: DoctorWarning[] = []is declared, never populated, and silenced withvoid warnings. Looks like leftover scaffolding — can be removed. -
categorytyped asstring—DoctorWarning.categoryis a plainstring, but the only value produced today is"TypeExtractionFailed". A string literal union (e.g.,category: "TypeExtractionFailed") that can be extended later would give consumers autocomplete and exhaustive-switch support while keeping the future-proofing intent. -
Hardcoded category in
getWarningscallback (src/cli/commands/doctor_extensions.ts:280) — All events from the warning emitter get mapped tocategory: "TypeExtractionFailed", which is correct today since that's the only warning type. If a second warning type is added to the emitter, this mapping would silently assign the wrong category. Consider either attaching the category at emit time (in the infrastructure layer) or adding a brief comment noting the assumption.
All three are nice-to-haves — the PR is correct and ready to merge as-is.
There was a problem hiding this comment.
Adversarial Review
Critical / High
None found.
Medium
-
src/cli/commands/doctor_extensions.ts:280— Hardcodedcategory: "TypeExtractionFailed"for all warnings.
ThegetWarningscallback maps everyExtensionLoadWarningEventtocategory: "TypeExtractionFailed"regardless of what warning was actually emitted. Today this is correct —emitTypeExtractionFailureis the only production caller ofemitExtensionLoadWarning(confirmed viaextension_loader.ts:753). However, the PR description states thecategoryfield "future-proofs for additional warning types," yet the mapping doesn't source the category from the event — it hardcodes it. A future developer adding a new warning kind throughemitExtensionLoadWarningwould get silently miscategorized output.Breaking scenario: Someone adds
emitExtensionLoadWarning({ kind: "vault", file, error: "manifest checksum mismatch" })— the JSON output would label itTypeExtractionFailedinstead of a distinct category.Suggested fix: Either (a) add a
categoryfield toExtensionLoadWarningEventso the emitter site declares its own category, or (b) add a comment at the mapping site noting this assumption so a future developer knows to update it. Not blocking since the invariant holds today.
Low
-
src/libswamp/extensions/doctor_test.ts:553— Unusedwarningsvariable.const warnings: DoctorWarning[] = []is created, never populated, andvoid-ed at line 569. Looks like a leftover from an earlier approach. Harmless dead code but slightly confusing. -
src/infrastructure/logging/extension_load_warnings.ts:98-102—eventsarray grows without bound within a process. Each unique(kind, file, error)tuple appends toevents[]. In the doctor flow this is fine becauseresetExtensionLoadWarnings()is called at each invocation. In a hypothetical long-running process that never calls reset, the array grows monotonically. Non-issue for a CLI tool, but worth noting.
Verdict
PASS — Clean, well-structured change. The warning collection/reset lifecycle is correct: resetWarnings clears stale state at the top of doctorExtensions, loaders re-emit fresh warnings during ensureLoaded, and getWarnings reads them after all loaders complete. Deduplication, reset semantics, and double-invocation are all tested. The overallStatus is correctly unaffected by warning presence. JSON and log renderers both handle the new field. The one design concern (hardcoded category) is valid but not a current bug.
Summary
warnings[]field toswamp doctor extensions --jsonoutput to surface extension load warnings (e.g. non-literal type fields that prevent static catalog extraction)overallStatusand exit code are unaffectedgetWarnings/resetWarnings) onDoctorExtensionsDeps, matching thegetRecentTransitionspatternEach warning entry has the schema:
{ sourcePath, category, message }— thecategoryfield future-proofs for additional warning types.Key design decision
Triage verified that extensions with non-literal type fields work correctly at runtime (type search, method execution, and describe all succeed). The original issue framing ("model doesn't register") was incorrect — the failure is only in static regex-based catalog extraction. This means a new
TypeExtractionFailedRowState would have produced false alarms for working extensions. Thewarnings[]surface treats these as what they are: diagnostic findings about suboptimal-but-working patterns.Closes swamp-club#351.
Follow-up: swamp-club#353 (docs update for JSON schema).
Test Plan
doctor_test.tspin reset-at-invocation semantics (stale leak prevention, during-loader appearance, no double-counting)silent_load_failure_test.ts— assertswarnings[]in doctor JSON withTypeExtractionFailedcategorydoctor_extensions_failure_states_test.ts— warnings-only scenario:overallStatus=pass, exit 0,warnings[]populatedwarnings: []fieldswamp doctor extensions --jsonshows warning, exit code 0,overallStatus: "pass"🤖 Generated with Claude Code