Conversation
…octor usage analysis, vocabulary activation Three-tier content governance to prevent and detect quality issues in AI-generated content: **Prevention (content_save):** - Detect duplicate dictionary values at write time and return advisory warnings - Cross-reference with vocabulary.json for canonical term consistency - Advisories are soft warnings — save succeeds, agent decides action **Validation (validator):** - Intra-dictionary duplicate value detection (warning severity) - Cross-dictionary duplicate value detection across models (notice severity) **Analysis (doctor --usage):** - Unused content keys: scan source files for unreferenced dictionary keys, collection IDs, document slugs - Duplicate values: detect different keys mapping to identical strings - Missing locale coverage: find keys present in default locale but absent in others **Vocabulary activation:** - Include vocabulary in describe_format, status, and describe (dictionary hint) - Cross-reference vocabulary terms in content_save advisories **Skills & rules:** - Add Content Governance section to essential rules - Add advisory review step and vocabulary guidance to content skill - Add dictionary deduplication rules to quality references Tests: 7 new tests covering advisory detection, validator duplicates, and integration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scaffolds the safety net for the upcoming MCP engine refactor: - Contentrain CLA (Apache ICLA-derived with relicense grant, so the monorepo retains flexibility to ship parts under AGPL or proprietary ee/ licenses in Studio's orbit) - cla-assistant-lite GitHub workflow — activates once the CLA_SIGNATURES_TOKEN repo secret is provisioned; until then PRs flow through without blocking - CONTRIBUTING.md documents the sign-off flow - Byte-identical conformance fixture suite under packages/mcp/tests/fixtures/conformance/ — locks the current write path output before refactor-induced refactoring starts. Each scenario ships setup/, scenario.json and expected/; the harness uses vi.setSystemTime + CONTENTRAIN_SOURCE=mcp-local for determinism - Three seed scenarios cover the collection, singleton and dictionary write paths; scenarios.json tracks the 15-fixture plan - .gitignore excludes /.internal symlink (private refactor docs live in studio/.internal/refactor/) Refs: .internal/refactor/01-mcp-engine-plan.md phase 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(phase-0): foundation — CLA + conformance harness
…ader
Adds the provider-agnostic plumbing the rest of the refactor will build on.
Pure additions; no existing source changed, no public surface touched.
- core/contracts/ defines the provider interfaces: RepoReader (read-only),
RepoWriter (applyPlan), RepoProvider (combined), plus the supporting types
FileChange, ProviderCapabilities, Branch, FileDiff, MergeResult, Commit,
CommitAuthor, ApplyPlanInput. LOCAL_CAPABILITIES is pre-set for the
LocalProvider slot we'll wire up in phase 3.
- core/serialization/ re-exports canonicalStringify, sortKeys,
parseMarkdownFrontmatter and serializeMarkdownFrontmatter from
@contentrain/types behind a single module, plus a typed parseCanonical
helper. This is the import surface Studio will consume post-refactor.
- providers/local/reader.ts ships LocalReader — a node:fs-backed RepoReader.
Plumbing only; nothing calls it yet. Phase 2 injects it into core ops.
- tests/serialization-parity.test.ts locks the byte-level serialization
contract with 20 fixtures (empty, unicode TR/CJK/emoji, nested, null and
undefined filtering, array order preservation, field-order override,
escape chars, number/boolean/mixed, object-map sort) plus 2 invariants
(trailing newline, 2-space indent).
Scope note: the original phase 1 plan also migrated core/*.ts reads onto
the RepoReader. During implementation it became clear phase 2 rewrites
those same functions anyway (plan/apply + FileChange[] return), so the
read migration was moved to phase 2 to avoid throwaway work. Plan doc
(.internal/refactor/01-mcp-engine-plan.md) updated.
Verification:
- pnpm vitest run conformance serialization-parity → 25/25 green
- oxlint + tsc --noEmit on all new files → clean
Refs: .internal/refactor/01-mcp-engine-plan.md phase 1,
.internal/refactor/03-conformance-contract.md §2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(mcp): phase 1 — contracts, serialization, LocalReader
First op on the plan/apply pattern. planContentSave reads existing state
through a RepoReader and returns a FileChange[] + typed result + advisories
— no disk writes inside the core function. The tool handler applies the
plan to the worktree via a thin shim and hands off to the existing
transaction flow (tx.commit still writes context.json through its current
write-through path — that stays in Faz 3 where LocalProvider takes over).
- core/ops/types.ts: OpPlan<T> envelope and ContentSaveEntryResult
- core/ops/paths.ts: content-root-relative content/meta/document path
helpers that mirror current resolveJsonFilePath/resolveMdFilePath logic
- core/ops/content-save.ts: planContentSave covers all four model kinds
(singleton/collection/dictionary/document), preserves collision checks,
dictionary-value advisories and vocabulary cross-ref advisories, and
uses in-memory coalescing so N entries → single content file + single
meta file per (path)
- core/ops/apply-to-worktree.ts: FileChange[] → worktree shim (writeFile /
rm); replaced by LocalProvider.applyPlan in Faz 3
- core/ops/index.ts: barrel export
- tools/content.ts content_save: invokes planContentSave before opening a
transaction, applies plan.changes inside tx.write, hands off the same
context payload to tx.commit. Error handling preserved. writeContent
import dropped from this handler (still available for delete path).
Verification:
- pnpm vitest run conformance serialization-parity → 25/25 green
- pnpm vitest run tools/content -t 'contentrain_content_save' → 10/10
green (singleton, collection new, collection update, document,
dictionary, unknown model, branch health, same-second concurrency,
advisory when duplicate value, no advisory when unique)
- oxlint + tsc --noEmit → clean
- Public MCP tool surface unchanged (same response JSON shape)
Refs: .internal/refactor/01-mcp-engine-plan.md phase 2,
.internal/refactor/00-principles.md §5.1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(mcp): phase 2.1 — plan/apply for content_save
Second op migrated to the plan/apply pattern. planContentDelete reads
existing state through a RepoReader, computes the file removals and
updates needed (entry removed from an object-map content file, meta
file updated or deleted when empty, etc.), and emits a FileChange[]
that the tool handler applies inside the existing tx.write callback.
- core/ops/content-delete.ts: planContentDelete covers all four kinds
- Collection: removes the entry ID from each locale's content +
meta files; deletes a meta file when the removal empties it; auto-
discovers locales when no locale argument is supplied
- Singleton: nulls the locale's content file + meta file (or the
whole meta dir contents for non-i18n)
- Dictionary: key-scoped removal rewrites the content file in place,
locale-scoped removal nulls content + meta
- Document: strategy-aware (file / suffix / directory / none),
plus meta removal under .contentrain/meta/{id}/{slug}/
- tools/content.ts content_delete: builds the plan before opening the
transaction, fails fast on validation errors (unknown keys for
dictionary, missing id/slug), applies plan.changes inside tx.write,
tx.commit unchanged. deleteContent import dropped.
- core/ops/index.ts: barrel exports planContentDelete and its types.
Verification:
- pnpm vitest run conformance serialization-parity → 25/25 green
- pnpm vitest run tools/content -t 'contentrain_content_delete' → 3/3
green (collection entry from object-map, collection metadata across
all locales, document slug directory)
- oxlint + tsc --noEmit → clean
- Public response JSON shape preserved (deleted, files_removed, git)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lete refactor(mcp): phase 2.2 — plan/apply for content_delete
Third and final op batch of phase 2. The public content and model tool
surface is now fully on plan/apply; legacy writeContent/writeModel/
deleteContent/deleteModel remain in core but are no longer imported by
the content or model tool handlers. Bulk, setup/init, and normalize
apply continue to use the legacy surface until their own phase lifts.
- core/ops/model-save.ts: planModelSave emits a single FileChange for
.contentrain/models/{id}.json with canonical field order preserved.
Detects create vs update by probing reader.readFile. Empty
content/meta dir scaffolding is dropped — git does not track empty
directories and write helpers mkdir -p on demand, so the legacy
ensureDir calls were operationally inert.
- core/ops/model-delete.ts: planModelDelete enumerates the model
definition file plus any content and meta files (up to two
directory levels deep — the schema never nests more than that).
Emits null FileChange per file. Returns the legacy three-entry
files_removed summary (models/{id}.json, content/{domain}/{id}/,
meta/{id}/) so the tool response shape is unchanged.
- tools/model.ts: model_save builds the plan before opening the
transaction; model_delete loads the model for existence + reference
checks, passes the loaded definition to planModelDelete, then
applies plan.changes inside tx.write. writeModel/deleteModel imports
dropped from this file.
- core/ops/index.ts: barrel exports extended.
Verification:
- pnpm vitest run conformance serialization-parity → 25/25 green
- pnpm vitest run tools/model → 8/8 green (create, update, field type
validation, relation validation, uninitialized guard, delete,
referenced-model block, nonexistent model)
- oxlint + tsc --noEmit → clean
- Public MCP tool response JSON shape preserved
Phase 2 wrap: content_save + content_delete + model_save + model_delete
are all plan/apply. Bulk tool, setup init/scaffold, and normalize apply
stay on the legacy write path and will migrate with phases 3, 4 and 6
respectively.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(mcp): phase 2.3 — plan/apply for model_save + model_delete (phase 2 wrap)
Introduces LocalProvider as the single entry point for local-filesystem content operations. Tool handlers no longer juggle createTransaction / tx.write / tx.commit / tx.complete / tx.cleanup by hand — they construct a LocalProvider, call planXxx against it (provider implements RepoReader), and drive the write through a single provider.applyPlan call. Provider owns worktree lifecycle and cleanup internally. Phase 3 stays behaviour-preserving: LocalProvider's applyPlan delegates to the existing createTransaction pipeline, so worktree setup, content branch guard, merge/update-ref, selective sync and error paths are bit- for-bit identical. Phase 6 will fold transaction.ts directly into providers/local/ and drop the wrap. - providers/local/types.ts LocalApplyPlanInput (branch + changes + message + optional context + optional workflowOverride), LocalApplyResult (extends Commit with workflowAction + selective-sync details + warning), LocalContextUpdate, LocalSelectiveSyncResult. - providers/local/provider.ts LocalProvider class. Implements RepoReader by delegating to LocalReader. applyPlan wraps createTransaction, emits a Commit-shaped result with author derived from env + defaults. Internal tx.cleanup in finally so tool handlers don't need to. - providers/local/index.ts Export LocalProvider and the new types alongside LocalReader. - tools/content.ts content_save, content_delete Swap LocalReader + createTransaction scaffolding for a single LocalProvider instance. Drop manual tx.cleanup calls. Response JSON shape preserved (git.action, git.commit, git.sync). - tools/model.ts model_save, model_delete Same migration pattern. model_save uses provider for both the plan read and the apply; model_delete passes the already-loaded model straight to planModelDelete. Verification: - pnpm vitest run conformance serialization-parity → 25/25 green - pnpm vitest run tools/content.test tools/model.test → 25/25 green (10 content_save + 2 advisory + 3 delete + 4 list + 8 model ~ 405s) - oxlint + tsc --noEmit → clean - Public MCP tool response JSON shape unchanged What's still on the legacy transaction.ts path (deferred): - bulk.ts (copy_locale / update_status / delete_entries) - setup.ts (contentrain_init / contentrain_scaffold) - normalize apply (core/apply-manager.ts) - workflow.ts (submit / merge — these call git directly, not via tx) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(mcp): phase 3 — LocalProvider wraps the transaction flow
Reorganises validation into @contentrain/mcp/core/validator and adds a
Studio-signature per-entry validator. validateProject behaviour is
preserved (workflow tests pass), Studio can now swap its content-
validation.ts import to this module with zero signature change.
Structure:
- core/validator/ replaces the flat core/validator.ts
- entry.ts validateContent(data, fields, modelId, locale,
entryId?, ctx?) — union of MCP's per-entry checks
(secret detection, schema validation via @contentrain/
types validateFieldValue, unique constraint) and
Studio's extras (email/url heuristics, polymorphic
relation structure, nested object + array-of-object
recursion).
- relation-integrity.ts checkRelationIntegrity — async relation
existence check with host-provided loadContent.
Warning-severity (matches Studio's policy; MCP's
legacy error severity belongs to validateProject's
project-wide orchestrator, not per-entry).
- schedule.ts validateScheduleFields — extracted from the legacy
validator; project.ts now imports it instead of
inlining.
- project.ts legacy validator.ts moved verbatim (only path + the
schedule import changed). validateProject's behaviour
is bit-for-bit identical.
- index.ts barrel exporting validateContent, validateProject,
checkRelationIntegrity, validateScheduleFields, and
the ValidationContext / ValidateOptions / ValidateResult
types.
- tools/workflow.ts + tools/content.ts
Import validateProject from core/validator/index.js instead of the
flat file.
- package.json
Build script rewires src/core/validator.ts → src/core/validator/index.ts.
The ./core/validator subpath export now points to dist/core/validator/
index.mjs so external consumers (Studio) get all of the above from a
single import.
Tests:
- tests/core/validator/entry.test.ts — 20 fixtures covering the union
of rules (valid entry, required field missing, type mismatch, secret
detection, unique constraint with/without exclusion, email and URL
heuristics, single + polymorphic relation structure, relations min/
max + item type, array of strings + integers, nested object, array-
of-object field path prefix, error context propagation).
- Existing tools/workflow.test.ts still passes (12/12 — validateProject
exercised end-to-end).
- Conformance + serialization-parity still at 25/25 (no byte drift).
What this does NOT do:
- Refactor validateProject internals to call validateContent. The
per-entry logic inside project.ts still mirrors the legacy validator;
unifying it would change observable behaviour (would start emitting
Studio-style email/url warnings during contentrain_validate) and is a
phase 4.2 task once we have the fixture coverage to catch regressions.
- Delete Studio's content-validation.ts. That handoff is documented in
.internal/refactor/02-studio-handoff.md phase S3.
Verification:
- pnpm vitest run conformance serialization-parity core/validator → 45/45
- pnpm vitest run tools/workflow → 12/12 (210s)
- oxlint + tsc --noEmit → clean
- Public MCP tool response JSON shape unchanged
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(mcp): phase 4 — unified validator module
…ules
Phase 4 promoted core/validator.ts to a directory (core/validator/
with index.ts + entry.ts + relation-integrity.ts + schedule.ts +
project.ts), which matches the published package surface — the
./core/validator export maps to dist/core/validator/index.{mjs,d.ts}.
The root tsconfig's path mapping still only pointed at the flat .ts
form, so downstream packages (CLI today, Studio later) could no longer
typecheck imports of @contentrain/mcp/core/validator during dev.
Accept both shapes:
\"@contentrain/mcp/core/*\": [
\"packages/mcp/src/core/*.ts\",
\"packages/mcp/src/core/*/index.ts\"
]
TypeScript picks the first resolvable match, so flat modules keep
working and directory-based ones (validator today, ops/contracts/
serialization tomorrow if we re-export them under core/*) resolve
via their index.ts. One-line unblock — no source changes, no test
changes; pnpm typecheck passes across all 6 packages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(tsconfig): resolve @contentrain/mcp/core/* to directory-based modules
…h ops)
First non-local RepoProvider. Implements the full RepoProvider interface
from phase 1 contracts on top of an Octokit-driven GitHub REST + Git
Data API client. No hard dependency — @octokit/rest is an optional peer
and the factory loads it via dynamic import.
Structure:
- providers/github/
- client.ts type-only alias for @octokit/rest Octokit
- types.ts RepoRef (owner/name/contentRoot) + GitHubAuth
(PAT today, App auth reserved for phase 5.2)
- paths.ts contentRoot-aware path resolver (forward-slash,
no leading slash — matches Git Data API conventions)
- capabilities.ts GITHUB_CAPABILITIES — no worktree, no source RW,
no AST; push + PR fallback + branch protection
detection ON. Normalize's capability-gate lands
in phase 6.
- reader.ts GitHubReader — readFile (base64 decode via
repos.getContent, falls back to git.getBlob for
files > 1 MB), listDirectory (empty on 404),
fileExists.
- apply-plan.ts applyPlanToGitHub — base SHA resolution
(existing branch HEAD or base branch), blob
creation per non-null FileChange, null-sha tree
entry for deletions, tree + commit + ref
create/update. One atomic Git Data commit.
- branch-ops.ts list / create / delete / diff / merge / isMerged
/ getDefaultBranch — paginated where it matters,
normalised return types.
- provider.ts GitHubProvider class wiring the pieces together
behind RepoProvider.
- factory.ts createGitHubClient + createGitHubProvider —
dynamic import + PAT auth path; App-auth path
throws pointing at phase 5.2.
- index.ts barrel export.
Tests (tests/providers/github/, 16 total):
- reader.test.ts (11) base64 decode, large-file blob fallback,
directory rejection, 404 → empty array,
contentRoot prefix, 500 propagation.
- apply-plan.test.ts (5) branch update vs. create, deletions (null-sha),
default branch fallback, contentRoot prefix.
Package surface:
- package.json adds @octokit/rest as optional peer dependency and dev
dependency; adds providers/github/index.ts to the tsdown build
entries; exposes ./providers/github as a subpath export (dist paths
follow the repo's existing .d.ts/.mjs shape).
- Root tsconfig path mapping accepts both flat and directory-based
providers/* modules so local typecheck + Studio consumption both
resolve.
What phase 5.1 does NOT ship (by design):
- HTTP transport — the Node-side server that fronts MCP tool calls
and hands them to GitHubProvider. Phase 5.2 adds it along with the
CLI --http flag.
- GitHub App installation auth. Phase 5.2, same reason.
- Tool handler wiring for remote providers. Tool handlers still use
LocalProvider exclusively; the HTTP transport will slot in a
provider resolver.
Verification:
- pnpm vitest run conformance serialization-parity core/validator
providers/github → 61/61 (no drift anywhere)
- pnpm build → dist/providers/github/ artefacts present
- pnpm typecheck → all 6 packages green
- oxlint on new code → 0 warnings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
refactor(mcp): phase 5.1 — GitHubProvider (reader + applyPlan + branch ops)
Ships an HTTP-mounted MCP endpoint alongside the stdio transport. The
same McpServer (every tool registered today) answers over both
transports — the only thing that changes is the wire. Phase 5.3 will
plug GitHubProvider into the routing so a single HTTP endpoint can
serve multiple repos; this PR keeps LocalProvider as the only backend
to avoid touching tool handlers before the parallel Studio handoff
lands.
MCP — packages/mcp:
- src/server/http/server.ts
startHttpMcpServer({ projectRoot, port, host?, authToken?, path? })
wraps @modelcontextprotocol/sdk's StreamableHTTPServerTransport in a
node:http listener. Stateful session IDs (randomUUID) because the
SDK's initialize flow relies on them. Optional Bearer-token guard
that short-circuits with 401 before the transport sees the request.
Mount path defaults to /mcp; anything else 404s. Handle exposes
server + mcp + url + close() for clean teardown.
- src/server/http/index.ts
barrel — exports startHttpMcpServer and the option/handle types.
- tests/server/http.test.ts (4 tests, ~3.7s)
real end-to-end: spins the server on an ephemeral port, drives it
with StreamableHTTPClientTransport from the SDK, asserts
contentrain_describe_format returns a spec over HTTP, auth guard
accepts matching Bearer + rejects missing, mount-path is enforced.
- package.json — adds ./server/http to exports and to the tsdown
build entry list.
CLI — packages/cli/src/commands/serve.ts:
- New --mcp-http flag (env: CONTENTRAIN_MCP_HTTP=true). When set, the
CLI starts the HTTP MCP server on the same --port/--host it already
accepts, with an optional --auth-token (env: CONTENTRAIN_AUTH_TOKEN)
Bearer guard. Mode precedence stays stdio > mcp-http > web-ui so
IDE setups keep working unchanged.
- SIGINT/SIGTERM handlers close the server cleanly before exit.
- Warn when binding 0.0.0.0 without an auth token.
Root:
- tsconfig.json adds @contentrain/mcp/server/http path mapping so the
CLI's static import resolves during typecheck.
Verification:
- pnpm vitest run conformance serialization-parity core/validator
providers/github server/http → 65/65 green
- pnpm build → dist/server/http/ artefacts emitted
- pnpm typecheck → all 6 packages clean
- oxlint on new code + the CLI change → 0 warnings
What 5.2 does NOT ship (staged for 5.3):
- GitHubProvider routing via HTTP — tool handlers still bind to
LocalProvider at construction time. Phase 5.3 adds a provider
resolver on the server side so an HTTP request can select
GitHubProvider for a given project.
- App-based GitHub auth (installation tokens). Pending phase 5.3 too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…port refactor(mcp,cli): phase 5.2 — HTTP transport for MCP
tsdown emits declaration files as .d.mts (ESM type declarations) but every subpath export in package.json claimed ./dist/…/*.d.ts. Local typecheck kept passing because the root tsconfig paths map to source .ts files, but external consumers installing @contentrain/mcp from npm would hit a types-resolution failure — TypeScript looks at the exports' "types" field first, the claimed .d.ts path does not exist on disk, and downstream modules become any. This bites Studio's planned phase S3 (drop content-validation.ts and content-paths.ts, import from @contentrain/mcp/core/validator + @contentrain/mcp/core/content-manager). Replacing the 19 .d.ts paths with .d.mts matches what tsdown actually emits so the package is consumable as shipped. No source changes, no behaviour changes, no test changes. Verification: - pnpm vitest run conformance serialization-parity core/validator providers/github server/http → 65/65 green - pnpm typecheck → all 6 packages clean - grep '\\.d\\.ts"' packages/mcp/package.json → 0 hits (was 19) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…utput fix(mcp): align subpath exports with tsdown's .d.mts output
… to validateContent
Completes the validator unification. validateProject's per-kind handlers
(collection, singleton, document) no longer carry inline secret /
field / unique / relation logic — they call validateContent for the
synchronous per-entry pass and checkRelationIntegrity for the async
relation-existence pass. validateDictionaryModel stays as-is (dictionaries
have no declared field schema). Project-wide orchestration (locale file
existence, canonical sort, entry parity, orphan meta/content, cross-
dictionary duplicate values) stays in project.ts — it is not per-entry
work.
This is the observable-behaviour change the Phase 4 commit deliberately
deferred: contentrain_validate now emits the Studio-side rules the unified
validateContent carries (email / URL heuristic warnings, polymorphic
relation structure errors, nested object + array-of-object recursion).
Every rule was already shipped in @contentrain/mcp/core/validator's
validateContent since Phase 4, so Studio's future handoff lands without
surprises.
Changes:
- core/validator/relation-integrity.ts
- Adds `severity` option (default 'warning' for Studio compat, MCP's
project validator passes 'error').
- Adds `resolveTarget` option — a richer target resolver that can
signal target-model absence ({ exists: false }) so callers emit a
"target model not found" error, matching the legacy checkRelation
behaviour MCP relies on.
- Backwards compatible: Studio's existing call sites pass only
loadContent and get warning-severity, no target-model lookup.
- core/validator/entry.ts
- Unique check now uses String(value) comparison (matches legacy
validator.ts uniqueness semantics — catches cross-type duplicates
like 42 vs '42' that strict === would miss).
- core/validator/project.ts
- New `buildProjectTargetResolver(projectRoot)` produces a ResolvedTarget
for the current fs layout: collections return their object-map
(empty {} when locale file missing so broken-ref detection still
fires), documents return a slug-exists marker map, singletons and
dictionaries return { content: null } so key enforcement is skipped.
- New `scanUndeclaredFieldsForSecrets` preserves the legacy behaviour
of flagging secrets in data fields that are not declared in
model.fields — validateContent only sees declared fields.
- validateCollectionModel / validateSingletonModel / validateDocumentModel
each:
1. run the undeclared-field secret scan,
2. call validateContent(data, fields, id, locale, entryId?, ctx?) and
merge errors,
3. call checkRelationIntegrity with severity:'error' + resolveTarget
and merge errors.
Document handler recomposes the validateContent call without the
synthetic `body` field (legacy skipped body there) and carries the
slug through the merged errors.
- Legacy inline checkRelation helper + inline secret/field/unique
loops removed.
- core/validator/index.ts
- Re-exports CheckRelationIntegrityOptions, LegacyLoadContent,
ResolvedTarget so external consumers (Studio) can type their
callbacks.
Tests:
- tests/tools/workflow.test.ts adds three validateProject-level cases
locking the newly-delegated behaviour:
* emits email heuristic warning (type:'email' field with invalid
string)
* emits url heuristic warning (type:'url' field with missing
protocol)
* flags nested object field errors ({ type:'object', fields }
missing required sub-field)
These lock the behaviour change in place — any future regression
in the delegation shows up here.
Verification:
- pnpm vitest run tools/workflow → 15/15 green (12 existing + 3 new,
~273s). The fixed broken-relation test continues to emit 'error'
severity because resolveTarget returns an empty-map content for
collection targets with missing locale files, so the checker treats
any ref as absent (legacy parity).
- pnpm vitest run conformance serialization-parity core/validator
providers/github server/http → 65/65 green.
- pnpm typecheck → all 6 packages clean.
- oxlint on new + modified code → 0 warnings.
Studio handoff note:
- .internal/refactor/02-studio-handoff.md phase S3 can now replace
Studio's server/utils/content-validation.ts with:
import { validateContent, checkRelationIntegrity, ValidationContext }
from '@contentrain/mcp/core/validator'
…and get the identical rule set (plus MCP's project-wide
orchestration if they later adopt validateProject).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…unify refactor(mcp): phase 4.2 — validateProject delegates per-entry checks to validateContent
…handlers
Completes the provider-abstraction plumbing. createServer now accepts a
provider option alongside projectRoot; tool register functions take the
provider as a first-class parameter; write-path handlers gate on
projectRoot presence and return a uniform capability error when local
disk access is unavailable. Read-only static tools (describe_format)
now work against any ToolProvider — the foundation for phase 5.4's
remote write path.
Changes:
Read helpers (core/)
- readConfig / readVocabulary (core/config.ts) and readModel / listModels
(core/model-manager.ts) add RepoReader-based overloads alongside the
legacy projectRoot signature. Backward-compatible: every existing
caller keeps working; GitHubProvider reads go through the same
helpers via reader-based overload.
Server plumbing (server.ts)
- ToolProvider type alias (RepoReader + capabilities) — narrower than
the full RepoProvider so LocalProvider (reader + applyPlan today) and
GitHubProvider (full provider) both satisfy it without requiring
LocalProvider to stub branch-ops yet.
- createServer({ provider, projectRoot? }) overload. String argument
continues to work via a backward-compat path that wraps projectRoot
in a LocalProvider. When only a provider is supplied and the provider
is a LocalProvider, projectRoot is derived from it; otherwise stays
undefined.
- Every registerXxxTools call receives (server, provider, projectRoot).
Tool handlers (tools/)
- guards.ts: new capabilityError helper emits a uniform JSON response
with capability_required, a hint, and isError:true so agents can
decide whether to retry against another transport.
- content, model, context, workflow, normalize, setup, bulk — signature
updated to accept ToolProvider + optional projectRoot. Each handler
adds a one-line gate at the top: if projectRoot is undefined, return
capabilityError. contentrain_describe_format is the exception — it
is static data and therefore works against any provider.
- Normalize surfaces the right capability key: contentrain_scan →
astScan, contentrain_apply → sourceRead (extract) or sourceWrite
(reuse). contentrain_bulk, contentrain_content_*, model_*, setup,
workflow → localWorktree.
HTTP transport (server/http/)
- startHttpMcpServerWith({ provider, projectRoot? }) — new variant that
accepts a pre-built provider. Internally shares the same
StreamableHTTPServerTransport wiring as the legacy
startHttpMcpServer({ projectRoot }) path. Node http server,
auth-token guard, mount-path enforcement identical to phase 5.2.
- Both entry points now go through a single startHttpMcpServerInternal
helper so the Bearer / mount / listen / close semantics stay in one
place.
Tests
- tests/server/http.test.ts adds two Phase 5.3 cases:
* describe_format over HTTP with a provider-only config (no
projectRoot) returns the format spec.
* contentrain_status over HTTP with a provider-only config returns
capability_required: localWorktree with isError:true.
- Existing 4 HTTP tests, 25 conformance / parity / validator / github
tests, and 40 tool tests (content 17, model 8, workflow 15) all stay
green — the stdio path is bit-for-bit unchanged.
Verification
- pnpm vitest run conformance serialization-parity core/validator
providers/github server/http → 65/65 (plus 2 new Phase 5.3 cases in
server/http = 6/6 there).
- pnpm vitest run tools/content.test tools/model.test tools/workflow.test
→ 40/40 (~610s).
- pnpm typecheck → all 6 packages clean.
- oxlint on mcp src/ → 0 warnings.
What phase 5.3 does NOT ship (staged for phase 5.4)
- Write path via GitHubProvider. LocalProvider.applyPlan still runs the
full worktree + workflow flow; GitHubProvider.applyPlan is available
but not yet routed from tool handlers. Phase 5.4 picks off
content_save / content_delete / model_save / model_delete and runs
them against a GitHubProvider once the workflow semantics are
adapted (PR fallback, no selective sync, base branch resolution).
- validateProject reader overload. Project-wide validation still reads
via projectRoot, so post-save validation runs only on LocalProvider.
A reader-backed validateProject lands alongside the write path in
phase 5.4.
Refs:
- .internal/refactor/01-mcp-engine-plan.md phase 5
- .internal/refactor/00-principles.md §5.3 capability system
- .internal/refactor/02-studio-handoff.md phase S6 (MCP Cloud endpoint)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bstraction refactor(mcp): phase 5.3 — thread RepoProvider through server + tool handlers
…tent + model CRUD
Opens the content/model CRUD tools to any RepoProvider, not just
LocalProvider. Each write handler now takes a dual path:
- LocalProvider instance → legacy flow (worktree + workflow + selective
sync + context write-through). Bit-for-bit identical to pre-5.4
behaviour; every stdio / HTTP+Local test keeps passing.
- Any other ToolProvider (GitHubProvider today, GitLab/Bitbucket later)
→ generic RepoWriter.applyPlan. Context.json is bundled as a
FileChange inside the plan; commit base defaults to the repo's
contentrain branch with fallback to config.repository.default_branch;
workflow action is always `pending-review` (no auto-merge for remote
provider — Studio/agent orchestrates the merge).
Changes:
- core/context.ts
buildContextChange(reader, operation, source?) — produces a FileChange
for .contentrain/context.json using a reader. Entries count is left
null for remote paths (no cheap directory walk via Git Data API yet);
the legacy writeContext continues to supply real counts for local
flows so the stdio conformance surface does not drift.
- providers/local/types.ts
LocalApplyPlanInput grows optional `author` and `base` fields — now a
superset of RepoWriter.ApplyPlanInput so LocalProvider cleanly
satisfies RepoWriter. author / base are accepted for interface parity
but still pass through to createTransaction which reads author from
the existing env vars.
- server.ts
ToolProvider widened to RepoReader & RepoWriter & { capabilities } so
both LocalProvider and GitHubProvider satisfy the type. Narrower than
the full RepoProvider (no branch-ops yet).
- tools/content.ts
content_save, content_delete: unified handler with the LocalProvider
vs remote split. projectRoot gate dropped; writes work against any
ToolProvider. Post-save validateProject runs only when projectRoot is
available (LocalProvider-backed servers). Branch-health gate and
checkReferences remain LocalProvider-only because they read directly
from the working tree.
- tools/model.ts
model_save, model_delete: same dual path. model_save's DX helpers
(import_snippet, example_file) are emitted only when projectRoot is
available — they are Local-specific UX and the Studio-backed agent
does not need them.
Tests (tests/server/http.test.ts):
- New end-to-end case: `commits content_save through a GitHubProvider-
like remote provider`. Mounts a mocked Octokit against a fresh
GitHubProvider, drives contentrain_content_save over HTTP, asserts
that the resulting commit
(a) lands on a cr/content/blog/... branch,
(b) reports action: 'pending-review' (no auto-merge),
(c) carries the expected tree payload — content + meta + context.json
blobs — with the base SHA resolved from the contentrain branch.
- Existing 6 HTTP tests still pass.
Verification:
- pnpm vitest run conformance serialization-parity core/validator
providers/github server/http → 67/67 green (6 Phase-0+1+2+3+4 tests,
16 github, 3 conformance, 20 validator, 22 parity, 7 http —
including the new E2E).
- pnpm vitest run tools/content.test tools/model.test → 25/25 green
(~434s, ~7 min). LocalProvider paths for content save / delete and
model save / delete all behave exactly as they did before phase 5.4.
- pnpm typecheck → all 6 packages clean.
- oxlint on packages/mcp/src + tests/server → 0 warnings.
Phase 5 scope check:
- 5.1 GitHubProvider (reader + applyPlan + branch ops) ✓
- 5.2 HTTP transport ✓
- 5.3 Provider threading through server + tool handlers ✓
- 5.4 Remote write path for content + model CRUD ✓ (this PR)
What phase 5.5 picks up next (intentionally not in this PR):
- bulk, workflow, setup, normalize tools — still projectRoot-only. Each
has local-disk-heavy logic (AST walks, git operations, dir scaffolding)
that needs a separate abstraction pass.
- validateProject reader overload. Post-save validation is skipped for
remote flows today; a reader-backed validateProject would fold the
post-write check into the remote path too.
- checkReferences reader overload — referenced_model pre-check for
model_delete on remote providers.
- Entry count via reader — populates stats.entries on the remote
context.json write-through.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the integration gap between the finished MCP refactor and the
consumers that want to embed it (Studio first, but the public guide
extends to any host). Also fixes two bugs surfaced while writing
the handoff.
### Studio handoff (internal)
`.internal/refactor/02-studio-handoff.md` was written at the start
of the refactor (Phase 2) and had drifted on every subsequent phase.
Updated to the finished-refactor state:
- Preconditions now list the actual shipped subpath exports
(`core/ops`, `core/overlay-reader`, `core/contracts`,
`providers/local`, etc.), not the Phase-2-era stubs.
- Contracts canonical source is `@contentrain/types`, not
`@contentrain/mcp/core/contracts` (which re-exports for back-compat).
- S2 (Content Engine Sökümü) code samples updated for today's APIs:
`buildContextChange(reader, operation)` with `OverlayReader`
wrapping, `provider.applyPlan({..., base: CONTENTRAIN_BRANCH})`
invariant, no `.reader()` accessor (didn't exist then, doesn't
now).
- New "Phase 10 tuzakları" section spelling out the three traps
Studio should avoid: fork from contentrain, OverlayReader before
context / validation, `isNotFoundError` is internal.
- S6 (MCP Cloud Endpoint) reframed around `startHttpMcpServerWith`
rather than a hand-rolled JSON-RPC layer — Studio can reuse the
MCP package's HTTP primitive and put Bearer / quota / metering in
a Nitro middleware in front of it.
- Removed the hypothetical "core branch-policy module" placeholder
(Phase 3.5 that never shipped separately — the helper logic lives
inline in `providers/local/migration.ts` now).
- Provider selection matrix clarifying that Studio never uses
LocalProvider, so normalize / scan / apply / init / scaffold /
submit / merge / validate --fix / bulk are always
`capability_required` on Studio's Cloud endpoint — not something
Studio has to filter client-side, the capability gate handles it.
### Public embedding guide (public docs)
New `docs/guides/embedding-mcp.md` — a consumer-agnostic guide that
sits under the existing Providers / HTTP Transport guides. Documents
four construction recipes (stdio+Local, HTTP+Local, HTTP+Remote,
programmatic no-transport), the three primitives every integrator
must understand (`CONTENTRAIN_BRANCH` as fork point, `OverlayReader`
for post-commit consistency, `capability_required` as a structured
error), auth model, and an extension point for custom providers.
Studio is called out as a reference integration alongside CI and
scripted automation. Sidebar + top nav updated.
### P2 — `ApplyPlanInput.base` contract vs implementation
The `@contentrain/types` docstring said "defaults to provider's
content-tracking branch", but both `GitHubProvider` and
`GitLabProvider` defaulted to the repository's default branch
(main / master / trunk). Tests locked in that wrong behaviour. MCP's
own tool-level write path (`commit-plan.ts`) bypassed the issue by
always passing `base: CONTENTRAIN_BRANCH` explicitly, but any
Studio-style direct `provider.applyPlan` call with `base` omitted
would silently fork from `main`.
Both implementations now default to `CONTENTRAIN_BRANCH`, matching
the documented contract and the local-transaction behaviour. Tests
rewritten to assert:
- `github.apply-plan.test.ts > defaults to the contentrain branch
when no base is provided` — `repos.get` is NOT called, `getRef`
resolves `heads/contentrain`.
- `gitlab.apply-plan.test.ts > defaults to the contentrain branch
when input.base is absent` — `Projects.show` is NOT called,
`startBranch` is `'contentrain'`.
Docstring tightened in `packages/types/src/provider.ts` to leave no
room for a split-brain reinterpretation.
`commit-plan.ts` can now omit `base` entirely (provider default is
correct), but keeps it explicit for readability.
### P2 — `contentrain_status` context field on remote
Remote `contentrain_status` returned `context: null` unconditionally,
even though remote writes commit `.contentrain/context.json` through
the overlay reader. Studio's surface reported "no last operation"
and "stats.entries: null" forever.
`readContext` gains a reader overload in `core/context.ts`.
`tools/context.ts` uses the reader form for remote sessions. The
HTTP E2E now seeds `.contentrain/context.json` and asserts the
`lastOperation` + `stats` surface from the remote provider.
### New public subpath exports
- `@contentrain/mcp/core/ops` — plan helpers + path helpers, exactly
what an embedder needs to compose custom write paths against any
`RepoProvider`.
- `@contentrain/mcp/core/overlay-reader` — the `OverlayReader`
primitive. Required by any integrator writing to a remote
provider so `buildContextChange` / `validateProject` see
post-commit state.
Both paths are referenced from the embedding guide and the Studio
handoff; without them, a from-scratch Studio-style integration would
require reaching into `dist/` which is explicitly blocked by
`package.json#exports`.
### Verification
- `pnpm -r typecheck` → 0 errors across 8 packages.
- `oxlint` → 0 warnings on 397 files.
- `pnpm --filter @contentrain/mcp build` → clean; dist contains
`core/ops/`, `core/overlay-reader.{d.mts,mjs}`, `core/contracts/`,
`providers/local/`.
- `vitest run tests/core tests/conformance tests/serialization-parity
tests/git tests/providers tests/server tests/util` →
440/440 green, 2 skipped.
- `vitest run tests/providers/github tests/providers/gitlab
tests/server/http.test.ts` → 60/60 green
(includes the rewritten base-default tests and the augmented
remote-context status E2E).
- `node -e import('@contentrain/mcp/core/ops')` + overlay-reader
+ contracts + providers/local → all four subpaths resolve.
### Changesets
`.changeset/mcp-phase-11-studio-handoff.md` — `@contentrain/mcp`
minor bump (new subpath exports, two P2 bug fixes). Stacks with the
existing Phase 10 changesets into a single minor release.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nment refactor(mcp,types): phase 10 — review alignment + P1/P2 fixes + docs parity
feat(mcp,docs): studio handoff + embedding guide + two P2 fixes
…y-default auth
Consolidates a four-agent review of `contentrain serve` and the
MCP helpers it consumes. Fixes drift, ships the UI affordances that
make the fixes visible, hardens auth, and eliminates ~300 lines of
duplicated merge/diff logic by delegating to MCP.
### MCP — public helpers + empty-repo init
- New `branchDiff(projectRoot, { branch, base? })` exported from
`@contentrain/mcp/git/branch-lifecycle`. Defaults `base` to
`CONTENTRAIN_BRANCH` — the singleton content-tracking branch every
feature branch forks from. Replaces the CLI's duplicated
`git.diff([${defaultBranch}...${branch}])` calls, which surfaced
unrelated historical content changes once contentrain advanced
past the repo's default branch.
- `tools/setup.ts` `contentrain_init` now handles greenfield
directories: seeds an `--allow-empty` initial commit when the repo
has zero commits so `ensureContentBranch` has a base ref to
anchor on. Previously the CLI `init` command created this commit
manually while the MCP tool skipped the step — the tool failed
on an empty directory the CLI handled fine.
- Tests: new `tests/git/branch-lifecycle.test.ts` (3 cases) +
new setup case (`initialises a greenfield directory with no .git
and no commits`).
### Serve server — correctness + new routes + auth
- Three duplicated merge-via-worktree implementations
(`/api/branches/approve`, `/api/normalize/approve`, CLI `diff`)
replaced with calls to MCP's `mergeBranch()` helper. Runs the
worktree transaction with selective sync + dirty-file protection.
Skipped files are cached and surfaced to the UI via a new
`sync:warning` WebSocket event and a new
`/api/branches/:name/sync-status` route. Merge conflicts now
broadcast `branch:merge-conflict` instead of silently succeeding.
- `/api/branches/diff` delegates to the new `branchDiff()` helper
with CONTENTRAIN_BRANCH as the default base.
- History filter tolerates BOTH legacy `Merge branch 'contentrain/'`
and current `Merge branch 'cr/'` commit patterns so post-Phase-7
history doesn't drop merges.
- `.catch(() => {})` error-swallowing at three sites removed.
Merge conflicts and cleanup failures no longer pretend to
succeed.
- Normalize plan approve broadcasts `branch:created` on the
returned `git.branch` metadata (parity with content save route).
- New `/api/capabilities` route — provider type, transport,
capability manifest, branch health, repo info in one call.
Dashboard consumes this to render the capability badge.
- New `/api/branches/:name/sync-status` — on-demand sync warning
fetch for the branch detail page; 1h TTL cache in memory.
- New WebSocket events: `branch:rejected`, `branch:merge-conflict`,
`sync:warning`.
- Zod input validation on every write route via new
`serve/schemas.ts`. Catches malformed bodies with structured 400
errors before they reach the MCP tool layer. Adds `zod` as a
direct CLI dependency.
- `serve` command — secure-by-default auth. Binding to a
non-localhost interface now HARD ERRORS when no `--authToken` is
set. No `--allow-unsafe` opt-out flag (OWASP Secure-by-Default;
matches Postgres, helm, kubectl port-forward conventions).
### Serve UI — level-ups that make the fixes visible
- `useWatch.ts` — WSEvent union widened for the new event types
(branch:rejected, branch:merge-conflict, sync:warning, connected).
- `project` store — `capabilities` state + `branchHealthAlarm`
computed + `fetchCapabilities()` action.
- `AppLayout` — global branch-health banner (warning / blocked
thresholds from MCP), sync-warning toasts with "View details"
action deep-linking to the branch detail page, merge-conflict
toasts with the failure message.
- `DashboardPage` — capability badge (provider type · transport)
next to the workflow + stack badges.
- `BranchDetailPage` — sync warnings panel listing every file the
selective sync skipped and why, rendered only when the last
mergeBranch() call produced warnings.
- `ValidatePage` — issues are now clickable when a `model` is
present; deep-links to the content list filtered to the issue's
`locale` / `id` / `slug`. Fallback to the model list for
aggregate issues (e.g. i18n parity).
### CLI — delegation to MCP helpers
- `commands/diff.ts` — both the per-branch summary line and the
merge confirmation call `branchDiff()` / `mergeBranch()` from
MCP. Surfaces `sync.skipped[]` warnings to the user with the
list of preserved files. Removes the duplicated
`contentrain` branch + worktree + update-ref + checkout dance
(~45 lines).
- `commands/doctor.ts` — branch health check delegates to MCP's
`checkBranchHealth()`. Previously filtered `contentrain/*`
directly after the Phase 7 naming migration to `cr/*`, so the
check silently reported "None" no matter how many branches had
accumulated. The delegation uses MCP's canonical thresholds
(warn at 50, block at 80).
- `commands/validate.ts` non-interactive path — captures
`tx.complete()` result and surfaces the branch name + workflow
action in review mode. Previously this metadata was dropped; the
user ran `contentrain validate --fix`, saw "fixed 3 issues", and
had no indication that the fix landed on a `cr/fix/validate/...`
branch that needed manual review.
### Verification
- `pnpm -r typecheck` → 0 errors across 8 packages.
- `oxlint` monorepo → 0 warnings across 399 files.
- `vue-tsc --noEmit` serve-ui → 0 errors.
- `pnpm --filter @contentrain/mcp build` +
`pnpm --filter contentrain build:cli-only` → clean.
- MCP fast suite → 443/443 green, 2 skipped.
### Tool surface
No changes. Same 16 MCP tools, same arg schemas, same JSON response
shapes. Stdio + LocalProvider flows behave identically to the
previous release.
### Changeset
`.changeset/phase-13-serve-correctness-levelup.md` — minor bump for
both `@contentrain/mcp` (new `branchDiff` export, empty-repo init
fix) and `contentrain` (new serve capabilities + secure auth + zod
dependency).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation The Phase 13 commit delegated CLI diff/merge paths to MCP helpers and hardened serve auth, but three CLI test files locked in the pre-refactor behavior and failed CI: - `tests/commands/diff.test.ts` — asserted diffs against `main` (default branch) and legacy `contentrain/review/hero` branch naming. Rewritten to mock `@contentrain/mcp/git/branch-lifecycle` branchDiff + `@contentrain/mcp/git/transaction` mergeBranch, assert the delegation, and use `cr/*` branch naming throughout. - `tests/commands/serve.test.ts` — mocked consola without `.error`, so the new "refuse to start on 0.0.0.0 without --authToken" path crashed at `consola.error(...)`. Mock now includes `error`; the existing `--host 0.0.0.0` test passes `authToken` to exercise the success path; a new test asserts the hard-error path. - `tests/integration/serve.integration.test.ts` — asserted the old merge-via-worktree dance (worktree add + update-ref + checkout). Rewritten to mock the MCP helpers instead, verify the delegation call shapes, and assert `sync:warning` + `branch:merge-conflict` WS broadcasts land on the fake client. CI target: `contentrain` package vitest run → 119/119 green (previously 111/117 with 6 failures; 8 new assertions added for the secure-auth + merge-conflict + sync-warning paths). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… polish
Closes the P2 "MCP entrypoint owns a private provider contract" finding
and folds four CLI gap-fillers into the same PR so the new shared
`openMcpSession` helper ships alongside the boundary work that makes
it safe to commit to.
### `@contentrain/types` — one shared MergeResult
- `MergeResult` gains an optional `sync?: SyncResult` field. Remote
providers (GitHub, GitLab) omit it; LocalProvider populates it so
selective-sync bookkeeping survives the trip through the shared
`RepoProvider.mergeBranch()` boundary.
### `@contentrain/mcp` — provider boundary
- `LocalProvider` now `implements RepoProvider` for real: the seven
branch-ops methods (`listBranches`, `createBranch`, `deleteBranch`,
`getBranchDiff`, `mergeBranch`, `isMerged`, `getDefaultBranch`)
wrap existing simple-git + transaction helpers through a new
`providers/local/branch-ops.ts` module that mirrors the shape of
`providers/github/branch-ops.ts`.
- `mergeBranch(branch, into)` asserts `into === CONTENTRAIN_BRANCH` —
the local flow merges feature branches into the content-tracking
branch and advances the base branch via `update-ref`, so arbitrary
merge targets would bypass that invariant.
- `server.ts`: the private `ToolProvider = RepoReader & RepoWriter &
{ capabilities }` alias collapses to `type ToolProvider =
RepoProvider`. Tool handlers now depend on the shared surface
directly; the alias stays re-exported so existing `ToolProvider`
imports do not migrate.
- `providers/local/types.ts`: `LocalSelectiveSyncResult` is removed
in favour of the shared `SyncResult`. `workflowOverride` types with
the shared `WorkflowMode` union instead of the duplicated literal.
Same swap in `git/transaction.ts` so the whole write path speaks
one union.
### `contentrain` — four new commands + shared MCP client
- `utils/mcp-client.ts` — shared `openMcpSession(projectRoot)` helper
built on `InMemoryTransport.createLinkedPair()`. Each command
opens a session, calls one tool, and closes in a `finally` block.
- `contentrain merge <branch>` — scriptable single-branch sibling to
the interactive `contentrain diff`. Same `mergeBranch()` helper, so
dirty-file protection + selective-sync warnings are identical.
`--yes` skips the confirmation prompt for CI/agents.
- `contentrain describe <model>` — wraps `contentrain_describe`.
Human-readable metadata + fields + stats + import snippet. Flags:
`--sample`, `--locale`, `--json`.
- `contentrain describe-format` — wraps `contentrain_describe_format`.
Quick format-spec primer for humans pairing with an agent.
- `contentrain scaffold --template <id>` — wraps
`contentrain_scaffold`. Interactive template picker when no flag
is passed; `--locales en,tr,de`, `--no-sample`, `--json`.
- `commands/status.ts`: branch-health thresholds (50/80) now come
from `checkBranchHealth()` instead of being duplicated inline. The
JSON output surfaces the full `branch_health` object so CI
consumers see the same warning/blocked state as text mode.
### Verification
- `pnpm -r typecheck` across types, mcp, cli → 0 errors.
- `oxlint` across mcp/cli/types src + tests → 0 warnings on 349 files.
- `@contentrain/types` vitest → 110/110.
- `contentrain` vitest → 130/130. Includes the 11 new command tests
(merge, describe, scaffold) and the updated status test against
the `checkBranchHealth()` mock.
- `@contentrain/mcp` fast suite (`tests/providers tests/git tests/core
tests/util tests/server tests/serialization-parity tests/conformance`)
→ **450 passed / 2 skipped / 30 files**. Includes the new
`tests/providers/local/branch-ops.test.ts` (7/7): contract shape,
prefix-filtered branch listing, create/delete round-trip, diff
status mapping (added/modified), post-merge `isMerged` flip,
`mergeBranch` target guard, and config-driven `getDefaultBranch`.
### Tool surface
No changes. Same 16 MCP tools, same arg schemas, same response shapes.
The boundary changes are purely internal — existing consumers
(Studio, CLI, IDE agents) observe identical behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…elup fix(serve,mcp,cli): serve correctness + MCP helper surface + secure-by-default auth
…ands Phase 14a: MCP boundary hardening + CLI command polish
…w routes, defensive Zod
Second pass on `contentrain serve` after Phase 13's auth + drift fixes.
Tight, surgical — additive routes and events, no behaviour regressions.
### File watcher coverage
- `.contentrain/meta/` now recognised: new `meta:changed` WS event
fires with `modelId`, optional `entryId`, `locale`. Covers both
per-model SEO layout (`meta/<model>/<locale>.json`) and per-entry
SEO layout (`meta/<model>/<entry>/<locale>.json`). Previously
editing a meta file left the Serve UI rendering stale metadata
until a full refresh.
- chokidar `'error'` handler was previously unhandled. Now broadcasts
`file-watch:error` with `message` + ISO `timestamp`. The UI can
render "watcher down, live updates paused" instead of silently
degrading (e.g. OS inotify limit on Linux).
### New HTTP routes
- `GET /api/describe-format` — thin wrapper around the
`contentrain_describe_format` MCP tool. The Serve UI's model
inspector can render the content-format spec as a reference panel.
- `GET /api/preview/merge?branch=cr/...` — side-effect-free preview
before approve. Returns `alreadyMerged`, `canFastForward`,
`conflicts` (best-effort via `git merge-tree`; `null` when unable
to run), `filesChanged`, `stat`. Complements the approve route,
which continues to surface runtime conflicts by throwing.
### Defensive Zod parity
- `/api/normalize/plan/reject` now parses an optional `{ reason? }`
body through a new `NormalizePlanRejectBodySchema`. Empty-body and
reason-only requests still work (backwards compatible); malformed
bodies return structured 400. Whole serve write surface now goes
through one `parseOrThrow()` gate.
### Explicitly out of scope
- `/api/doctor` — no `contentrain_doctor` MCP tool exists; only the
CLI's 540-line command. Rather than duplicate CLI logic into
serve, defer until doctor is extracted into a reusable MCP tool.
- `sdk:regenerated` WS event — `contentrain generate` runs outside
serve's process so the watcher can't observe it. Needs a different
hook; defer until the design is concrete.
### Verification
- `oxlint` cli src + tests → 0 warnings on 209 files.
- `contentrain` typecheck → 0 errors.
- `contentrain` vitest → **137/137** (was 130 on next-mcp). 7 new
tests cover `meta:changed` (with / without entryId),
`file-watch:error` payload, `/api/describe-format` tool call,
`/api/preview/merge` validation + happy FF path, plan/reject body
validation + backwards compat.
No MCP changes. No tool surface changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s-routes Phase 14b: serve backend — meta watcher, watcher errors, new routes, defensive Zod
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>
Phase 14c-e: doctor MCP tool + Serve UI integration + cross-cutting CLI flags
… + cr/* branches + parity tests
Closes the two P1 drift findings and installs a drift-detection
mechanism so they don't come back:
1. contentrain_merge missing from public catalog — @contentrain/rules
MCP_TOOLS listed 15 tools while @contentrain/mcp registers 17
(merge + doctor). Skills tool reference also jumped submit → bulk
with no merge section.
2. Legacy contentrain/{operation}/... branch namespace still taught
by essentials, review/normalize prompts, and multiple skills.
MCP's buildBranchName() returns cr/... (Phase 7). Agents following
shipped guidance would look for branches that don't exist.
### @contentrain/mcp
- Public export TOOL_NAMES in ./tools/annotations (frozen, derived
from TOOL_ANNOTATIONS). Single source of truth.
- New ./tools/annotations subpath export. Build script emits it.
### @contentrain/rules
- MCP_TOOLS extended to 17 (contentrain_merge + contentrain_doctor).
- essentials: doctor row added; feature-branch pattern → cr/...;
health-threshold language mentions cr/*.
- review-mode prompt: every legacy contentrain/<op>/... → cr/<op>/...
- normalize-mode prompt: branch pattern table rewritten.
- shared/workflow-rules: branch pattern spec rewritten.
- tests/mcp-parity.test.ts (new, 4 tests): MCP_TOOLS ↔ TOOL_NAMES
match, essentials cite every tool, buildBranchName emits cr/,
docs contain no legacy branch prefix.
- package.json: @contentrain/mcp workspace devDep for parity test.
### @contentrain/skills
- references/mcp-tools.md: new contentrain_merge (after submit) +
contentrain_doctor (new Doctor Tools subsection). Submit description
updated to cr/*.
- references/mcp-pipelines.md + workflow.md: branch spec + examples
rewritten to cr/*.
- contentrain-normalize SKILL + references (extraction, reuse):
4 stale contentrain/normalize/* → cr/normalize/*.
- contentrain-translate SKILL: translate branch pattern updated.
- tests/mcp-parity.test.ts (new, 2 tests): references/mcp-tools.md
has ### <tool> heading for every MCP tool; 7 skill docs contain no
legacy branch prefix.
- package.json: workspace devDep.
### Monorepo
- tsconfig.json paths: @contentrain/mcp/tools/* alias so TS + vitest
resolve the new subpath from source during dev.
### Verification
- oxlint rules + skills + mcp/tools → 0 warnings
- tsc --noEmit rules + skills + mcp → 0 errors
- @contentrain/rules vitest → 16/16 (was 12, +4 parity)
- @contentrain/skills vitest → 85/85 (was 83, +2 parity)
Tool surface: no behaviour changes. TOOL_NAMES export is additive;
everything else is documentation + tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every package README cross-checked against src/ exports, package.json exports map, and (for MCP) TOOL_ANNOTATIONS registry. Every claim in the rewritten READMEs is verified against current code. ### types - Provider contracts section (RepoProvider, RepoReader, RepoWriter, ProviderCapabilities, Commit, Branch, FileDiff, MergeResult with optional sync?: SyncResult, LOCAL_CAPABILITIES). - NormalizePlan* types, CONTENTRAIN_BRANCH, SECRET_PATTERNS, ModelSummary. - Browser-compatible validate/serialize surface for Studio integration. ### mcp - Tool count corrected to 17 (was 13/16 in different sections). contentrain_doctor row added to annotations table. - Subpath export list now lists every entry in package.json: /core/doctor, /core/contracts, /core/ops, /core/overlay-reader, /tools/annotations. - mergeBranch description uses cr/* branch naming. - Capability gates section mentions doctor alongside scan/apply. ### cli (contentrain) - Global --debug flag + CONTENTRAIN_DEBUG env var. - Flag matrix: --json on status/doctor/validate/generate/diff/describe/ scaffold; --watch on validate/generate; --demo and --mcpHttp / --authToken on serve. - setup, skills, merge, describe, describe-format, scaffold commands in command table. - Secure-by-default HTTP transport auth described. ### sdk (@contentrain/query) - contentrain generate (CLI) is now the recommended entry point; contentrain-query generate clarified as programmatic path. - Added programmatic generate() API snippet. ### rules - MCP_TOOLS length corrected to 17 (+merge +doctor). - New Parity section explaining tests/mcp-parity.test.ts. - shared/ directory catalog added (11 rule files, previously undocumented). - Context bridge section mentions the 4 stack templates. ### skills - Reference discovery pattern (references/*.md on-demand, tier table for progressive disclosure). - New Parity section mirroring rules package. - Public Exports includes discovery snippet. No code changes — READMEs only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every page under docs/ audited by 5 parallel Explore agents against
source (top-level, packages, reference, guides-infra, guides-content)
then applied with VitePress build verification.
### Tool-count corrections (16 → 17)
getting-started.md, concepts.md, packages/mcp.md, packages/cli.md,
guides/embedding-mcp.md, guides/http-transport.md, guides/providers.md,
guides/serve-ui.md — every "16 tools" updated to 17 (contentrain_merge
+ contentrain_doctor).
### Branch-naming corrections (Phase 7 migration)
concepts.md, guides/normalize.md — legacy contentrain/{operation}/...
branch prefixes rewritten to cr/*. MCP emits cr/; docs must not teach
the stale prefix.
### Major rewrites
- packages/mcp.md — full 17-tool table, contentrain_doctor in read
section, complete subpath-export list (adds /core/doctor,
/core/contracts, /core/ops, /core/overlay-reader, /tools/annotations).
- packages/cli.md — every command with real flags: --json on read
commands, --watch on validate+generate, global --debug /
CONTENTRAIN_DEBUG, new commands (merge, describe, describe-format,
scaffold, setup, skills). Serve section documents --demo, --mcpHttp,
secure-by-default bearer token requirement.
- packages/types.md — new Provider Contract Types section
(RepoProvider, RepoReader, RepoWriter, ProviderCapabilities,
FileChange, ApplyPlanInput, Commit, Branch, FileDiff, MergeResult
with sync?, SyncResult) + LOCAL_CAPABILITIES.
- packages/rules.md — MCP_TOOLS.length === 17 + explicit includes for
contentrain_merge and contentrain_doctor.
- reference/providers.md — complete capability matrix, MergeResult
shape with sync? for LocalProvider, supporting types, minimum-viable
custom-provider recipe.
- guides/serve-ui.md — new sections for every Phase 14b/c/d surface:
/doctor and /format UI pages, merge preview on BranchDetail,
meta:changed / file-watch:error / sync:warning /
branch:merge-conflict / branch:rejected WS events, new HTTP routes
(/api/doctor, /api/describe-format, /api/preview/merge,
/api/capabilities, /api/branches/:name/sync-status), secure-by-
default HTTP MCP auth.
### Minor
- packages/sdk.md — contentrain generate (CLI) is now the recommended
generation entry; @contentrain/query/generate API documented for
build-tool authors.
- demo.md — code snippet gets explicit `import { singleton } from
'#contentrain'`.
### Verified
- npx vitepress build → success in 5.33s, no broken links, no
rendering errors.
- Every claim cross-checked against current source code.
No code changes — docs only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…debase README.md: - Architecture diagram: MCP (16 tools) → MCP (17 tools) - Feature bullet: MCP engine 16 tools → 17 - Packages table: @contentrain/mcp row → 17 MCP tools CLAUDE.md: - Monorepo tree packages/mcp comment → 17 MCP tools - npm-packages table → 17 MCP tools - Obsolete "Octokit YOK in MCP" decision rewritten: @octokit/rest and @gitbeaker/rest are optional peer dependencies (Phase 5.1 + 8) AGENTS.md: - Essentials bullet 16 → 17 MCP tools - Packages table mcp row → 17 MCP tools RELEASING.md, CONTRIBUTING.md, CLA.md, CODE_OF_CONDUCT.md — no changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 pending changesets collectively produce this release, verified via pnpm release:status: @contentrain/mcp → 1.3.0 (minor) @contentrain/types → 0.5.0 (minor) contentrain → 0.5.0 (minor) @contentrain/rules → 0.4.0 (minor) @contentrain/skills → 0.4.0 (minor) @contentrain/query → 5.1.5 (patch) ### Studio handoff pre-flight — satisfied - @contentrain/types ≥ 0.5.0 ✓ (handoff pre-req was ≥ 0.4.2) - @contentrain/mcp ≥ 1.3.0 ✓ ### Pre-release verification - pnpm release:check → passed - pnpm release:status → 14 changesets, 5 minor + 1 patch - pnpm -r typecheck → 0 errors across 8 packages - pnpm lint → 0 warnings on 419 files - vitepress build → success ### Automated release flow (post-merge) 1. R1-R4 PRs merge into next-mcp 2. next-mcp merges into main 3. Changesets action opens a Version Packages PR 4. Merging that PR publishes to npm + per-package tags No manual pnpm release required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase R1: align rules/skills with MCP surface + cr/* branches + parity tests
Phase R2: align every package README with current public surface
Phase R3: align production docs/ site with current codebase
…ng-claude Phase R3b: align root README / CLAUDE / AGENTS with current codebase
Phase R4: release manifest + pre-flight verification
✅ Deploy Preview for contentrain-ai ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
The
next-mcpintegration branch is ready to merge intomain. After this merges, the Changesets action opens a "Version Packages" PR that publishes the releases below.Release manifest
@contentrain/mcp@contentrain/typescontentrain@contentrain/rules@contentrain/skills@contentrain/query14 changesets pending. Verified via
pnpm release:status+pnpm release:check.Studio handoff pre-flight — satisfied
@contentrain/types ≥ 0.5.0✓ (handoff pre-req was ≥ 0.4.2)@contentrain/mcp ≥ 1.3.0✓What ships
MCP engine refactor (Phase 0-10) — already in next-mcp
Provider-agnostic plan/apply engine.
RepoProvidercontract in@contentrain/types.LocalProvider+GitHubProvider+GitLabProvider, stdio + Streamable HTTP transports. Capability gates, branch migration tocr/*, validator unification, conformance harness.Phase 13 — serve correctness + secure-by-default auth
Drift fixes in
contentrain serve, sync-warning cache, capability badge, branch health banner, secure-by-default HTTP MCP auth (hard error on non-localhost bind without--authToken).Phase 14a — MCP boundary hardening + CLI commands
LocalProviderimplements fullRepoProvider.ToolProvider = RepoProvider.WorkflowMode/SyncResult/MergeResult.sync?consolidated in@contentrain/types. New CLI commands:merge,describe,describe-format,scaffold.Phase 14b — serve backend
/api/describe-format,/api/preview/mergeroutes.meta:changed+file-watch:errorWS events. Defensive Zod on/api/normalize/plan/reject. File-watcher error surfaced instead of silently swallowed.Phase 14c — doctor extraction
@contentrain/mcp/core/doctor+contentrain_doctorMCP tool +/api/doctorroute. CLIdoctor --jsonwith non-zero exit on failure.Phase 14d — Serve UI integration
/doctor+/formatpages. Merge preview panel on BranchDetail. Watcher-down banner. SEO metadata toast. Dictionary-first UI strings.Phase 14e — cross-cutting CLI flags
--jsonon diff/generate,--watchon validate, global--debug+CONTENTRAIN_DEBUGenv var.Phase R1 — rules/skills parity
Added
contentrain_merge+contentrain_doctorto publicMCP_TOOLScatalog (15 → 17). Migrated legacycontentrain/{op}/...branch prefixes tocr/*across essentials / prompts / skills. Cross-package parity tests lock the surfaces in lockstep with@contentrain/mcp.Phase R2 — package READMEs
All 6 package READMEs rewritten against actual
src/exports +package.jsonexports + MCP tool registry. 3 parallel audit agents.Phase R3 — production docs site
22 docs pages under
docs/audited by 5 parallel agents, then rewritten. VitePress build verified. Every claim cross-checked against source.Phase R3b — root guidance
README.md/CLAUDE.md/AGENTS.mdaligned with current tool count + optional-peer-deps decision.Phase R4 — release manifest
Release pre-flight verification + manifest.
Verification
pnpm release:check→ passedpnpm release:status→ 14 changesets, 5 minor + 1 patchpnpm -r typecheck→ 0 errors across 8 packagespnpm lint→ 0 warnings on 419 filesnpx vitepress build→ successPost-merge flow
mainreceives the merge@contentrain/mcp 1.3.0+@contentrain/types 0.5.0🤖 Generated with Claude Code