Skip to content

Phase 14c-e: doctor MCP tool + Serve UI integration + cross-cutting CLI flags#41

Merged
ABB65 merged 8 commits intonext-mcpfrom
feat/phase-14cde-doctor-ui-flags
Apr 17, 2026
Merged

Phase 14c-e: doctor MCP tool + Serve UI integration + cross-cutting CLI flags#41
ABB65 merged 8 commits intonext-mcpfrom
feat/phase-14cde-doctor-ui-flags

Conversation

@ABB65
Copy link
Copy Markdown
Member

@ABB65 ABB65 commented Apr 17, 2026

Three stacked phases in one PR, each as its own commit so review can proceed phase-by-phase.

Commits

  1. 071c46f — Phase 14c: Extract doctor into a reusable MCP surface
  2. 84af43c — Phase 14d: Serve UI consumes the 14b + 14c backend capabilities
  3. e234e0e — Phase 14e: Cross-cutting CLI flags (--json, --watch, --debug)

Content-save commits (154470f, c59ab6c) are the auto-merged serve-ui-texts dictionary updates the 14d UI code references — eating our own dog food via contentrain_content_save.


Phase 14c — Doctor extraction

  • @contentrain/mcp/core/doctorrunDoctor(projectRoot, {usage?}) returns a structured DoctorReport with checks[] (each carrying severity: 'error' | 'warning' | 'info'), summary, optional usage block.
  • contentrain_doctor MCP tool — read-only, gated behind localWorktree. Advertised in tools list.
  • CLI contentrain doctor — thin pretty-printer over runDoctor(). Byte-identical interactive output. New --json flag. Non-zero exit on failure.
  • GET /api/doctor?usage=... serve route wrapping the MCP tool.
  • 21 new tests (6 core + 4 tool + 7 CLI + 4 serve) all pass.

Phase 14d — Serve UI integration

  • New /doctor page — stat cards + per-check rows + 3 collapsibles for usage (unused keys / duplicate values / missing locales).
  • New /format page — renders /api/describe-format as collapsible sections.
  • BranchDetailPage merge preview — fetches /api/preview/merge, renders one of {already-merged, FF clean, requires-3way, conflicts} above the sync-warning panel.
  • Global shellfile-watch:error persistent banner (Dismiss button) + meta:changed light toast.
  • Storedoctor, formatReference, fileWatchError state + matching fetchers.
  • useWatchWSEvent union extended with meta:changed + file-watch:error + new entryId, timestamp fields.
  • Dictionary-first: all new UI strings pulled from serve-ui-texts (no hardcoded copy). New keys added via contentrain_content_save and auto-merged — visible as 154470f + c59ab6c in the commit log.

Phase 14e — CLI flags

  • --json on diff and generate (skips interactive modes, emits structured JSON to stdout).
  • --watch on validate — chokidar watcher, 300ms debounce, graceful SIGINT. Read-only by design (fix mode disabled — would spawn a cr/fix/* branch per keystroke). Composes with --json (one JSON line per run).
  • Global --debug flag + CONTENTRAIN_DEBUG=1 env. New utils/debug.ts with debug(), debugJson(), debugTimer(). All output → stderr (stdout stays clean for --json).
  • 13 new unit tests, all pass.

Test plan

  • oxlint across mcp + cli src + tests → 0 warnings on 350+ files
  • @contentrain/mcp typecheck → 0 errors
  • contentrain typecheck → 0 errors
  • vue-tsc --noEmit on serve-ui → 0 errors
  • New 14c MCP tests: 10/10 (doctor core + tool)
  • New 14c CLI tests: 11/11 (doctor CLI + serve route)
  • New 14e CLI tests: 13/13 (debug helper + --json on diff/generate + --watch flag)
  • Full CLI command unit suite: 38/38 pass

Scope notes

  • contentrain_doctor is localWorktree-gated — throws a structured capability error on remote providers (GitHub/GitLab), matching contentrain_setup / contentrain_scaffold. Doctor is inherently filesystem work (Node version, git binary, mtime comparisons).
  • validate --watch deliberately disables --fix / --interactive. Any future "auto-fix in watch mode" would need a design decision on branch cadence.
  • No MCP tool arg-schema changes beyond the new contentrain_doctor tool. All other phases are internal wiring / UI / flags.

🤖 Generated with Claude Code

Contentrain and others added 8 commits March 29, 2026 20:42
Pulls the 540-line `contentrain doctor` CLI command apart so the same
health report drives three consumers: the CLI, a new
`contentrain_doctor` MCP tool, and the Serve UI's `/api/doctor` route.

### `@contentrain/mcp`

- `@contentrain/mcp/core/doctor` — `runDoctor(projectRoot, { usage? })`
  returns a structured `DoctorReport`:
    { checks: Array<{ name, pass, detail, severity? }>,
      summary: { total, passed, failed, warnings },
      usage?: { unusedKeys, duplicateValues, missingLocaleKeys } }
  Every check now carries an explicit severity (error / warning /
  info) so consumers render independently instead of inferring from
  text. Orphan content + stale SDK client drop to `warning`; missing
  git / config / structure stay at `error`.
- `contentrain_doctor` MCP tool — read-only, gated behind
  `localWorktree`. Arg: `{ usage?: boolean }`. Returns the
  `DoctorReport` verbatim. Advertised alongside describe-format.

### `contentrain`

- CLI `contentrain doctor` collapses to a thin pretty-printer over
  `runDoctor()`. Default interactive output is byte-identical —
  same labels, same icons, same grouped usage blocks. New:
    --json — silent, emits raw report; exits non-zero on failures.
    Interactive mode also now exits non-zero on failure (was always 0).
- `GET /api/doctor?usage=true` wraps the MCP tool for the Serve UI.

### Scope notes

- Doctor is inherently local-filesystem work (Node version, git
  binary, mtime comparisons, orphan walk, source scan), so the MCP
  tool is capability-gated behind `localWorktree` and throws a
  structured capability error over remote providers — matches the
  `contentrain_setup` / `contentrain_scaffold` pattern.
- No behaviour change for existing CLI users beyond the additive
  --json flag + exit-code hardening.

### Verification

- oxlint across mcp+cli src+tests → 0 warnings on 350 files.
- @contentrain/mcp typecheck → 0 errors.
- contentrain typecheck → 0 errors.
- Unit tests (21 new, all pass):
  - tests/core/doctor.test.ts 6/6 — uninitialised, minimal, orphan
    warning, default-omits-usage, usage-adds-3-checks, stale-SDK.
  - tests/tools/doctor.test.ts 4/4 — structured report, usage opt-in,
    capability error over remote provider, tools-list advert.
  - tests/commands/doctor.test.ts (CLI) 7/7 — rewritten to mock
    runDoctor directly. Covers --json, exit codes, usage detail
    rendering, flag forwarding.
  - tests/integration/serve.integration.test.ts 24/24 — new
    /api/doctor cases: default, ?usage=true, ?usage=1.

Tool surface: +1 tool (contentrain_doctor). Everything else
unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the Serve UI to the routes and events added in 14b + 14c so the
new backend capabilities become visible to the user.

### New pages

- /doctor — structured health report from /api/doctor. Four stat
  cards (passed / errors / warnings / summary) mirror ValidatePage.
  Per-check rows with severity icon + badge. Optional usage mode
  expands into three collapsibles (unused keys, duplicate values,
  missing locale keys).
- /format — content-format spec from /api/describe-format, grouped
  by top-level section, each a collapsible Card.

### Extended pages

- BranchDetailPage — new "Merge preview" panel fetched on mount
  from /api/preview/merge. Four render states: already-merged
  (info), fast-forward clean (success), requires three-way
  (warning), conflicts (error + lists conflicting paths). Sits
  above the sync-warning panel so reviewers see the upcoming
  merge before the previous merge's outcome.

### Global shell (AppLayout)

- File-watcher error banner — when chokidar emits error the backend
  broadcasts `file-watch:error`; the layout renders a persistent
  destructive banner with message + Dismiss button.
- `meta:changed` toast — light informational toast for SEO metadata
  edits (no CTA).

### Store + composable

- stores/project.ts: doctor, formatReference, fileWatchError state.
  fetchDoctor, fetchFormatReference, fetchMergePreview actions.
  setFileWatchError / dismissFileWatchError. Types DoctorReport,
  DoctorCheck, DoctorUsage, MergePreview, FileWatchError.
- composables/useWatch.ts: WSEvent union extended with meta:changed
  and file-watch:error. New optional fields entryId, timestamp.

### Dictionary-first (eating our own dog food)

Every new user-facing string is pulled from
dictionary('serve-ui-texts').locale('en').get() — no hardcoded
copy. New keys added via contentrain_content_save (auto-merged,
landed as two content ops in the branch history). Reused existing
keys where applicable: dashboard.run, trust-badge.warnings,
validate.all-checks-passed, validate.errors, dashboard.total,
common.on/off.

### Verification

- vue-tsc --noEmit → 0 errors
- oxlint cli src → 0 warnings on 185 files

No backend changes. Pure UI wiring on top of 14b + 14c.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the CLI ergonomics gap identified in 14b/14c audits. Three
additive flags that make the CLI usable in CI, dev loops, and when
something goes wrong internally.

### --json on diff and generate

- contentrain diff --json — structured pending-branches summary,
  skips the interactive review loop:
    { branches: [{ name, base, filesChanged, insertions, deletions,
                   stat }] }
  Agents and CI can inspect cr/* branches without a TTY.
- contentrain generate --json — emits SDK-generate result
  (generatedFiles, typesCount, dataModulesCount, packageJsonUpdated)
  so pipelines can wire generation into automated refresh flows.
- doctor --json already shipped in 14c; this completes the set for
  the most CI-relevant read commands.

### --watch on validate

- validate --watch: chokidar watcher on .contentrain/content,
  .contentrain/models, config.json. Re-runs validation on change
  with 300ms debounce. Graceful SIGINT teardown.
- Read-only by design — force-disables --fix / --interactive
  because those would spawn a fresh cr/fix/* branch per keystroke.
- --json composes: each run prints one JSON line so
  `validate --watch --json | jq` works.

### --debug + CONTENTRAIN_DEBUG

- Global --debug flag, stripped at the root before citty parses
  subcommands so every command's debug() / debugTimer() calls
  see it. Same effect from CONTENTRAIN_DEBUG=1.
- utils/debug.ts: debug(ctx, msg), debugJson(ctx, label, value),
  debugTimer(ctx, label) → end() that no-ops when off. All output
  → stderr so --json stdout payloads stay clean.
- validate --watch is first consumer; future commands can sprinkle
  where user-facing output isn't enough to diagnose.

### Verification

- oxlint cli src+tests → 0 warnings on 213 files
- contentrain typecheck → 0 errors
- 13 new unit tests pass:
  - tests/utils/debug.test.ts (5): default silent, enableDebug()
    turns on, CONTENTRAIN_DEBUG=1 env var, timer no-op, timer ms.
  - diff.test.ts (+1): --json emits branches + no select().
  - generate.test.ts (+1): --json emits result, suppresses pretty.
  - validate.test.ts (+1): --watch advertised.
- Full command unit suite 38/38.

No backend or tool-surface changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ABB65 ABB65 merged commit cd44934 into next-mcp Apr 17, 2026
1 check passed
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.

1 participant