Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLI
Assigneesstack72

#320 Implement W6: swamp doctor extensions aggregate-state rendering + repair surface (extension catalog rearchitecture)

Opened by stack72 · 5/11/2026· Shipped 5/11/2026

Problem

W1a (#1292), W1b (#1295), LockfileRepository prequel (#1298), W2 (#231 — closes swamp-club#201), W3 (#252 — closes the rebundle-loop bug class), W4 (#1XXX — closes swamp-club#214 and swamp-club#270), and W5 (#290 — closes the V8 module-graph aliasing class) collectively close all three structural concerns the audit identified. The loader-side rearchitecture is complete.

But the diagnostic surface for users is still primitive. `swamp doctor extensions` today reports basic invalidation-guard pass/fail. Users hitting weird states (stale rows, terminal-failure states, orphaned bundles) have no way to inspect the catalog without raw SQL queries. Several recent bugs (#200, #201, #310 territory) had users resorting to `rm -rf .swamp/_extension_catalog.db` as a recovery path because the existing diagnostic surface didn't show them what was wrong or how to repair it.

W6 closes this loop. It renders the post-rearchitecture aggregate state to users in a structured, actionable form, and provides a repair surface for catalog garbage collection (folding in the swamp-club#267 GC scope).

Full architectural context: `design/extension-rearchitecture.md` ("W6 — `swamp doctor extensions` aggregate-state rendering" section) — referenced from #211.

Scope

W6 has two phases. The carve question — whether W6 absorbs #267's extension-layer GC repair scope — is a pre-work decision (see below).

Phase 1 — Aggregate-state rendering (always in scope)

Extend `swamp doctor extensions` to render the catalog's aggregate state in user-friendly form:

  • Per-aggregate summary: name (e.g., `@local/[email protected]` or `@hivemq/[email protected]`), origin (local, source-mounted, pulled), source count, RowState distribution
  • Per-source detail (verbose mode): source path, RowState tag, fingerprint (truncated), bundle path
  • RowState legend: render all 7 tags (`Indexed | Bundled | ValidationFailed | BundleBuildFailed | EntryPointUnreadable | OrphanedBundleOnly | Tombstoned`) with brief descriptions
  • Orphan detection:
    • Catalog rows whose `source_path` doesn't exist on disk
    • Bundle files in `.swamp/-bundles/` not referenced by any catalog row
    • Aggregates in terminal states (Tombstoned, OrphanedBundleOnly) lingering after their lifecycle reasons
  • Output modes: `log` (human-readable, follows terminal-output skill conventions) and `json` (structured per the existing two-mode CLI convention)
  • Counts and rollups: "X total extensions across N kinds, M sources in healthy states, P orphan rows, Q orphan bundle files"

Phase 2 — Repair surface (folds in #267 if decided)

Add `swamp doctor extensions repair` (or `--apply` flag) for catalog and bundle file garbage collection:

  • `--dry-run` is the DEFAULT mode (mirrors `git clean -n` semantics). Lists what would be deleted with rationale per row.
  • `--apply` performs the cleanup operations.
  • Catalog row pruning: delete rows in `Tombstoned` state or terminal states where neither source nor bundle exists on disk.
  • Bundle file eviction: delete files in `.swamp/-bundles/` that aren't referenced by any `bundle_path` in the catalog.
  • Structured logging: every cleanup operation logged with what + why for scripting visibility.

The repair surface absorbs #267 entirely if Phase 2 is in scope. See pre-work decision #1.

Pre-work decisions to pin in the PR description

  1. Scope: render-only or render + repair? Recommend: render + repair (absorb #267 fully). Reasoning: `doctor extensions` is the natural home for the repair UX (per #267's own framing), and shipping rendering without repair leaves users with the same "see the problem but can't fix it" gap that exists today. The combined scope is workstream-shaped, not 2-day-shaped. If splitting is preferred, Phase 1 ships first; Phase 2 becomes the next workstream after W6 settles.

  2. Output format precedent: follow the terminal-output skill for log mode. JSON mode follows the existing CLI two-mode convention. Don't invent new patterns.

  3. Bundle file naming convention verification: per #267's pre-work, the eviction policy depends on whether bundle files are content-addressed (stale fingerprint versions accumulate as orphans) or overwrite-on-rebundle (only true orphans accumulate). Confirm in the audit phase before designing eviction rules.

  4. Repair safety model: dry-run as default, explicit `--apply` to act. Recommend: no interactive confirmation prompts for v1; the dry-run-by-default convention is the safety. Users who want belt-and-suspenders can pipe the dry-run output into `less` before applying. Mirror `git clean -n` / `-i` semantics if interactive mode is wanted later.

  5. Granularity of rendering: per-aggregate by default, per-source via `--verbose` or `--detail`. Avoid showing 1000-row tables by default; show summary + drill-down on demand.

Out of scope (deferred to other workstreams or never)

  • Workflow runner mid-step extension refresh — separate concern; W5 made it safe via per-fingerprint URLs but the workflow runner architecture doesn't currently re-import between steps. If wanted as a feature, file as its own tracker.
  • `allExtensionMethodsAttached` removal (swamp-club#318) — separate registration-path consolidation work; W6 doesn't touch this.
  • `sourceToRow` mtime user surface (swamp-club#271) — if W6's rendering wants to show mtime, that's fine; the underlying mtime threading is already done.
  • `getCatalogStore()` cleanup (the surviving `legacyStore`-shaped accessor pattern from W4 review) — could be tracked separately if cleanup is desired.
  • Per-fingerprint import URL changes — W5's territory.
  • Catalog schema changes — none needed; W6 reads existing state.
  • Reconcile semantic changes — W3's territory.

Success criteria

  • `swamp doctor extensions` renders aggregate state in both `log` and `json` modes covering all 7 RowState tags, with per-aggregate summary + verbose drill-down.
  • Orphan detection surfaces catalog-only rows, bundle-only files, and lingering Tombstoned aggregates.
  • `--dry-run` (default) lists every cleanup-eligible row + file with reason; `--apply` performs cleanup atomically; idempotent on re-run.
  • swamp-club#267 closes (absorbed into Phase 2 if Phase 2 is in scope).
  • Diagnostic shift: users hitting weird catalog states have `swamp doctor extensions` as the first-line repair path. The "delete `.swamp/_extension_catalog.db` and pray" anti-pattern from #200 stays closed.
  • All existing tests pass on Linux + macOS (Windows not a merge gate per W-series precedent).
  • Auto-ship-on-merge readiness verified via diversity-matrix soak.
  • Forward-only revert posture documented.

Suggested test additions

  • Rendering per RowState tag: parameterized test that seeds a catalog with one row per RowState, runs `swamp doctor extensions --json`, asserts each state is correctly classified and rendered.
  • Orphan detection: seed catalog rows whose source paths don't exist; seed bundle files not referenced by any catalog row; assert both classes surface in the rendering.
  • Output mode parity: `log` mode and `json` mode contain the same information (modulo formatting). Use the output-parity assertion pattern from W2's DuplicateTypeError test.
  • `--dry-run` safety: seed cleanup-eligible state, run `--dry-run`, assert no catalog or filesystem changes after the run.
  • `--apply` idempotence: run `--apply` twice; second run reports zero operations (state already clean).
  • Cross-platform paths: path rendering uses canonical forms; use `assertPathEquals` from `path_test_helpers.ts`.

Auto-ship-on-merge constraint

Standard gates plus W6-specific considerations:

  • CI green (all new + existing tests + type-check + lint + fmt)
  • Author smoke on real repo: run `doctor extensions` against actual installed extensions; verify output makes sense; run `--dry-run` then `--apply` on a repo with known cleanup-eligible state.
  • Reviewer smoke on different real repo: same checks.
  • Diversity-matrix soak (~12-24 hours since W6 is presentation-layer, narrower blast radius than W4):
    • Linux + macOS
    • Repos with varied state (clean, with Tombstoned rows, with terminal-failure states, with orphan bundles)
    • Verify output is readable and accurate across states
  • No user-visible regression for users who don't run `doctor extensions`
  • Repair operations verified to not affect repos that don't run `--apply`
  • Forward-only revert posture documented

Push-back encouraged

If the design doesn't fit the ground, surface before implementation. Specific watch list:

  • Repair scope larger than expected. If the bundle-file eviction logic discovers complexity (content-addressed naming, cross-platform path quirks), Phase 2 may warrant its own follow-up workstream rather than folding into W6. Apply the LockfileRepository-prequel threshold pattern: pre-commit to size thresholds before audit.
  • Rendering volume on large catalogs. A repo with 500+ rows might overwhelm log-mode output by default. The summary-by-default + drill-down-on-demand pattern should handle this, but verify against a real large-repo scenario.
  • Output format design decisions. Color usage, table formatting, tree rendering — design choices that should follow the terminal-output skill, not invent new conventions. If the skill doesn't cover something W6 needs, surface for a design conversation rather than picking ad-hoc.
  • Interaction with the existing `doctor extensions` invalidation-guard reporting. The existing command reports invalidation-guard pass/fail. W6 extends this with aggregate-state rendering. Confirm the two reports compose cleanly — invalidation-guard report stays at the top, aggregate state below it. Don't replace the existing output silently.

The two most expensive misses to watch for

  1. `--apply` deletes more than intended. A bug in the cleanup logic could delete rows that should have been preserved. Catch by: dry-run-by-default convention + comprehensive regression tests asserting that `Indexed` rows are NEVER touched, only terminal states are eligible for deletion.

  2. Phase 2 absorbing #267 silently grows scope past the W6 budget. Bundle file eviction has more edge cases than catalog row pruning (content-addressed naming, cross-platform paths, possible race with concurrent swamp processes). If audit reveals Phase 2 is ≥ ~600 LOC on its own, consider splitting into W6a (rendering) and W6b (repair).

References

  • Predecessors: #211 (W1 tracking), #223 (W1b), #231 (W2), #252 (W3), #269 (W4), #290 (W5)
  • Folded in: #267 (extension layer garbage collection — superseded by W6's Phase 2 if absorbed)
  • Possibly addressable: swamp-uat#200 (UAT scenario for bundle-cache self-recovery — could be a natural surface for verifying repair-after-corruption)
  • Design doc: `design/extension-rearchitecture.md`
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 3 MOREPR_MERGEDSHIPPED

Shipped

5/11/2026, 9:45:53 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack725/11/2026, 5:11:14 PM

Sign in to post a ripple.