refactor(mcp): phase 5.5 — remote reads complete (validateProject + countEntries + checkReferences)#32
Merged
Conversation
…rloads Enriches the remote write path that landed in phase 5.4 so GitHubProvider- backed tool calls match LocalProvider in two visible ways: 1. Remote context.json now carries real stats.entries. Previously buildContextChange emitted entries: null because no reader-based countEntries existed; remote writes therefore diverged from local writes in a small but meaningful field that Studio surfaces to users. countEntries now has the same dual overload as readConfig / readModel / listModels — projectRoot string OR RepoReader. Helper functions (countDocumentFileStrategy, countDocumentSuffixStrategy, countDocumentDirectoryStrategy, countDocumentNoneStrategy, countCollectionEntries) were refactored to take a reader and content-root-relative paths; the string-path variant wraps a LocalReader so stdio behaviour is bit-for-bit unchanged. A new `contentDirPath(model)` helper produces relative paths symmetrical with `resolveContentDir(projectRoot, model)` but free of the projectRoot prefix. 2. Remote model_delete now runs the REFERENCED_MODEL pre-check too. checkReferences gets the same dual overload; the handler dropped its `provider instanceof LocalProvider` guard around the reference check and now runs it unconditionally. Local deletes continue to branch- health gate; remote deletes skip that (no local worktree to inspect) but still honour referential integrity. Changes: core/model-manager.ts - `contentDirPath(model)` — content-root-relative helper. - countDocumentFileStrategy / countDocumentSuffixStrategy / countDocumentDirectoryStrategy / countDocumentNoneStrategy / countCollectionEntries — helpers now take (reader, contentDir, entries) and walk via reader.listDirectory + tryReadJsonViaReader. - countEntries — dual overload (projectRoot | reader). The string path wraps LocalReader; the body uses reader exclusively. Every locale- strategy branch (file, suffix, directory, none × collection / document / singleton / dictionary) now goes through the reader. - checkReferences — dual overload. Reuses the already-overloaded listModels and readModel, so the body is unchanged beyond the signature fan-out. core/context.ts - buildContextChange now walks listModels + readModel + countEntries through the reader to populate stats.entries. Falls back to null when any walk fails, matching the behaviour of the legacy writeContext. tools/model.ts - model_delete's reference check moved outside the LocalProvider-only branch; it now runs on any provider via checkReferences(provider, modelId). Local deletes still do the branch-health gate first. Verification: - pnpm vitest run conformance serialization-parity core/validator providers/github server/http → 68/68 green. - pnpm vitest run tools/model.test → 8/8 green (~100s). REFERENCED_MODEL guard fires exactly as before. - pnpm typecheck → all 6 packages clean. - oxlint on packages/mcp/src → 0 warnings. What phase 5.5b picks up next (intentionally staged): - validateProject reader overload. This is a 795-line file that needs a careful (rather than mechanical) refactor because fix mode is inherently projectRoot-bound and the per-kind helpers all reach into the filesystem. The split lets 5.5a ship with tight review scope; 5.5b lands alongside workflow_validate's remote path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends Phase 5.5a: the read surface (context.json, countEntries,
checkReferences) now runs over any RepoReader. This PR makes
validateProject itself reader-backed so post-save validation and the
standalone contentrain_validate tool work against remote providers —
not just LocalProvider — closing the last gap in the remote read path.
validateProject now has two signatures:
- `validateProject(projectRoot, options?)` — legacy disk-backed flow.
`options.fix: true` still applies structural repairs (canonical sort,
orphan meta, missing locale files) directly on disk.
- `validateProject(reader, options?)` — reader-backed flow. `fix` is
ignored because remote providers cannot write to the local
filesystem; fix mode is a local-only capability.
Read-side behaviour is identical across both signatures, so Studio
(HTTP + GitHubProvider) and the local CLI see the same issue set for
the same content state.
Changes:
- core/ops/paths.ts
`contentDirPath(model)` now exported — content-root-relative content
directory for a model. Single source of truth for reader-backed
callers and plan-API consumers.
- core/model-manager.ts
Drops its private `contentDirPath`; delegates to `ops/paths.ts` so
the two helpers cannot drift.
- core/validator/project.ts
Every per-kind validator (collection, singleton, dictionary,
document) now takes `(reader, projectRoot | undefined, ...)`. All
reads go through the `RepoReader` surface (readFile / listDirectory
/ fileExists); fix-mode writes gate on `projectRoot !== undefined`
and still call `writeJson` / `writeText` / `writeMeta` directly.
`buildProjectTargetResolver`, `discoverDocumentSlugs`, and
`checkOrphanContent` are reader-based too, so cross-dictionary and
relation-integrity checks work against remote providers.
- tools/content.ts
Post-save validation drops the local-only guard — remote flows now
drive `validateProject(provider, ...)` instead of synthesising an
empty result. Agents orchestrating a GitHubProvider-backed server
get the same validation envelope as stdio callers.
- tools/workflow.ts
`contentrain_validate` splits on `input.fix`: fix mode still
capability-gates on `localWorktree` (uses git transaction +
worktree), read-only mode runs over any provider. `config` lookup
moved to the shared `provider` reader so stdio / HTTP both use the
same path.
Verification:
- pnpm --filter @contentrain/mcp typecheck → 0 errors.
- oxlint packages/mcp/src + tests/ → 0 warnings.
- vitest run tests/core tests/conformance.test.ts
tests/serialization-parity.test.ts tests/git tests/providers
tests/server tests/util → 377/377 green (2 skipped).
- vitest run tests/tools/content.test.ts tests/tools/workflow.test.ts
tests/tools/model.test.ts → 40/40 green (~800s).
Phase 5 scope after this PR:
- 5.1 GitHubProvider ✓
- 5.2 HTTP transport ✓
- 5.3 Provider threading ✓
- 5.4 Remote write path for content + model CRUD ✓
- 5.5a countEntries + checkReferences reader overloads,
context.json entries stats for remote ✓
- 5.5b validateProject reader overload ✓ (this PR)
What Phase 5.5 intentionally does not cover (handled in later phases):
- bulk / normalize / setup / submit / merge tools — still
projectRoot-only. AST walks, git operations, and dir scaffolding
need separate abstraction passes before they can run against
a remote provider.
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.
Summary
Phase 5.5 closes the last gap in the remote read path. Every tool reachable over HTTP + a remote provider (GitHubProvider today, GitLab/Bitbucket later) now gets the same read-side behaviour as stdio + LocalProvider callers — counts, reference checks, and validation all run over the shared
RepoReadersurface.Two commits, ship together:
7abcf90) —countEntries+checkReferencesreader overloads;buildContextChangepopulates realstats.entriesvia the reader;contentrain_model_deleteruns theREFERENCED_MODELpre-check against any provider.8684f2f) —validateProjectreader overload. Every per-kind validator (collection/singleton/dictionary/document) reads viaRepoReader; fix-mode writes stay gated onprojectRoot. Post-save validation incontentrain_content_saveand read-onlycontentrain_validatenow run against remote providers too; fix mode still capability-gates onlocalWorktree.Net effect: Studio (HTTP + GitHubProvider) now gets the same validation + reference-integrity + entry-count envelope that stdio callers have had since Phase 0, without any duplicated code paths.
Test plan
pnpm --filter @contentrain/mcp typecheck→ 0 errorsoxlint packages/mcp/src packages/mcp/tests→ 0 warningstests/core tests/conformance tests/serialization-parity tests/git tests/providers tests/server tests/util) → 377/377 green, 2 skippedtests/tools/content.test.ts tests/tools/model.test.ts tests/tools/workflow.test.ts) → 40/40 green (~800s)tests/server/http.test.ts→ 7/7 green (includes the remote content_save E2E seeded in 5.4)Scope notes
Phase 5.5 completes the remote read path. Intentionally not in this PR (handled later):
bulk,normalize,setup,submit,merge— still local-only. Each has AST walks, git operations, or dir scaffolding that need their own abstraction pass before they can run over a remote provider.🤖 Generated with Claude Code