Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three stacked phases in one PR, each as its own commit so review can proceed phase-by-phase.
Commits
071c46f— Phase 14c: Extractdoctorinto a reusable MCP surface84af43c— Phase 14d: Serve UI consumes the 14b + 14c backend capabilitiese234e0e— Phase 14e: Cross-cutting CLI flags (--json,--watch,--debug)Content-save commits (
154470f,c59ab6c) are the auto-mergedserve-ui-textsdictionary updates the 14d UI code references — eating our own dog food viacontentrain_content_save.Phase 14c — Doctor extraction
@contentrain/mcp/core/doctor—runDoctor(projectRoot, {usage?})returns a structuredDoctorReportwithchecks[](each carryingseverity: 'error' | 'warning' | 'info'),summary, optionalusageblock.contentrain_doctorMCP tool — read-only, gated behindlocalWorktree. Advertised in tools list.contentrain doctor— thin pretty-printer overrunDoctor(). Byte-identical interactive output. New--jsonflag. Non-zero exit on failure.GET /api/doctor?usage=...serve route wrapping the MCP tool.Phase 14d — Serve UI integration
/doctorpage — stat cards + per-check rows + 3 collapsibles for usage (unused keys / duplicate values / missing locales)./formatpage — renders/api/describe-formatas collapsible sections./api/preview/merge, renders one of {already-merged, FF clean, requires-3way, conflicts} above the sync-warning panel.file-watch:errorpersistent banner (Dismiss button) +meta:changedlight toast.doctor,formatReference,fileWatchErrorstate + matching fetchers.useWatch—WSEventunion extended withmeta:changed+file-watch:error+ newentryId,timestampfields.serve-ui-texts(no hardcoded copy). New keys added viacontentrain_content_saveand auto-merged — visible as154470f+c59ab6cin the commit log.Phase 14e — CLI flags
--jsonondiffandgenerate(skips interactive modes, emits structured JSON to stdout).--watchonvalidate— 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).--debugflag +CONTENTRAIN_DEBUG=1env. Newutils/debug.tswithdebug(),debugJson(),debugTimer(). All output → stderr (stdout stays clean for--json).Test plan
oxlintacross mcp + cli src + tests → 0 warnings on 350+ files@contentrain/mcptypecheck → 0 errorscontentraintypecheck → 0 errorsvue-tsc --noEmiton serve-ui → 0 errorsScope notes
contentrain_doctorislocalWorktree-gated — throws a structured capability error on remote providers (GitHub/GitLab), matchingcontentrain_setup/contentrain_scaffold. Doctor is inherently filesystem work (Node version, git binary, mtime comparisons).validate --watchdeliberately disables--fix/--interactive. Any future "auto-fix in watch mode" would need a design decision on branch cadence.contentrain_doctortool. All other phases are internal wiring / UI / flags.🤖 Generated with Claude Code