@contentrain/mcp 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/{apply-manager-BzpUJEbA.mjs → apply-manager-CMAoS_nu.mjs} +3 -3
- package/dist/{apply-manager-BzpUJEbA.mjs.map → apply-manager-CMAoS_nu.mjs.map} +1 -1
- package/dist/{branch-lifecycle-DB6sgeJi.mjs → branch-lifecycle-B7TgkxGr.mjs} +30 -11
- package/dist/branch-lifecycle-B7TgkxGr.mjs.map +1 -0
- package/dist/core/apply-manager.mjs +3 -3
- package/dist/git/branch-lifecycle.d.mts.map +1 -1
- package/dist/git/branch-lifecycle.mjs +1 -1
- package/dist/git/transaction.d.mts +8 -3
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +2 -2
- package/dist/index.mjs +4 -4
- package/dist/{server-DXLSsLSu.mjs → server-CDv6JNRi.mjs} +52 -37
- package/dist/server-CDv6JNRi.mjs.map +1 -0
- package/dist/server.mjs +4 -4
- package/dist/transaction-CncRbpI9.mjs +288 -0
- package/dist/transaction-CncRbpI9.mjs.map +1 -0
- package/package.json +2 -2
- package/dist/branch-lifecycle-DB6sgeJi.mjs.map +0 -1
- package/dist/server-DXLSsLSu.mjs.map +0 -1
- package/dist/transaction-BhSvWfSV.mjs +0 -128
- package/dist/transaction-BhSvWfSV.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-DXLSsLSu.mjs","names":["packageJson.version"],"sources":["../src/tools/context.ts","../src/tools/setup.ts","../src/tools/model.ts","../src/tools/content.ts","../src/tools/workflow.ts","../src/tools/normalize.ts","../src/tools/bulk.ts","../package.json","../src/server.ts"],"sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\nimport { readConfig, readVocabulary } from '../core/config.js'\nimport { readContext } from '../core/context.js'\nimport { countEntries, listModels, readModel } from '../core/model-manager.js'\nimport { resolveContentDir, resolveJsonFilePath, resolveLocaleStrategy } from '../core/content-manager.js'\nimport { detectStack } from '../util/detect.js'\nimport { join } from 'node:path'\nimport { contentrainDir, pathExists } from '../util/fs.js'\nimport { checkBranchHealth, cleanupMergedBranches } from '../git/branch-lifecycle.js'\n\nexport function registerContextTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_status ───\n server.tool(\n 'contentrain_status',\n 'Get full project status (read-only). Returns config, models, context. Do NOT manually edit .contentrain/ based on this output.',\n {},\n async () => {\n const crDir = contentrainDir(projectRoot)\n const initialized = await pathExists(join(crDir, 'config.json'))\n\n if (!initialized) {\n const detectedStack = await detectStack(projectRoot)\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n initialized: false,\n detected_stack: detectedStack,\n suggestion: 'Run contentrain_init to set up .contentrain/ structure',\n next_steps: ['Run contentrain_init'],\n }, null, 2),\n }],\n }\n }\n\n const config = await readConfig(projectRoot)\n const models = await listModels(projectRoot)\n const context = await readContext(projectRoot)\n const vocabulary = await readVocabulary(projectRoot)\n\n const errors: string[] = []\n if (!config) errors.push('.contentrain/config.json missing')\n\n const result: Record<string, unknown> = {\n initialized: true,\n config: config ? {\n stack: config.stack,\n workflow: config.workflow,\n locales: config.locales,\n domains: config.domains,\n repository: config.repository,\n } : null,\n models,\n context: context ? {\n lastOperation: context.lastOperation,\n stats: context.stats,\n } : null,\n vocabulary_size: vocabulary ? Object.keys(vocabulary.terms).length : 0,\n }\n\n // Branch lifecycle: lazy cleanup + health check (run BEFORE validation summary)\n const hasGitRepo = await pathExists(join(projectRoot, '.git'))\n if (hasGitRepo) {\n try {\n const cleanup = await cleanupMergedBranches(projectRoot)\n const health = await checkBranchHealth(projectRoot)\n result['branches'] = {\n total: health.total,\n merged: health.merged,\n unmerged: health.unmerged,\n cleaned_up: cleanup.deleted,\n }\n if (health.message) {\n result['branch_warning'] = health.message\n }\n if (health.blocked) {\n errors.push(health.message!)\n }\n } catch {\n // Branch health check is best-effort — don't fail status\n }\n }\n\n if (errors.length > 0) {\n result['validation'] = { errors: errors.length, warnings: 0, summary: errors }\n }\n\n const nextSteps: string[] = []\n if (models.length === 0) nextSteps.push('Create models with contentrain_model_save')\n if (errors.length > 0) nextSteps.push(`Fix ${errors.length} validation error(s)`)\n if (nextSteps.length === 0) nextSteps.push('Use contentrain_describe to inspect a model')\n result['next_steps'] = nextSteps\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n },\n )\n\n // ─── contentrain_describe ───\n server.tool(\n 'contentrain_describe',\n 'Get full schema of a single model (read-only). Do NOT manually create content files — use contentrain_content_save instead.',\n {\n model: z.string().describe('Model ID (e.g. \"blog-post\", \"hero\")'),\n include_sample: z.boolean().optional().default(false).describe('Include one sample entry'),\n locale: z.string().optional().describe('Locale for sample content (default: config default locale)'),\n },\n async ({ model: modelId, include_sample, locale }) => {\n const modelDef = await readModel(projectRoot, modelId)\n if (!modelDef) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${modelId}\" not found` }) }],\n isError: true,\n }\n }\n\n const config = await readConfig(projectRoot)\n const effectiveLocale = locale ?? config?.locales.default ?? 'en'\n const stats = await countEntries(projectRoot, modelDef)\n\n const result: Record<string, unknown> = {\n id: modelDef.id,\n name: modelDef.name,\n kind: modelDef.kind,\n domain: modelDef.domain,\n i18n: modelDef.i18n,\n description: modelDef.description,\n fields: modelDef.fields,\n stats: { total_entries: stats.total, locales: stats.locales },\n }\n\n if (include_sample) {\n const sample = await getSample(projectRoot, modelDef, effectiveLocale)\n if (sample) result['sample'] = sample\n }\n\n // Stack-aware import snippet\n const stack = config?.stack ?? 'other'\n result['import_snippet'] = generateImportSnippet(modelDef, stack, effectiveLocale)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n },\n )\n\n // ─── contentrain_describe_format ───\n server.tool(\n 'contentrain_describe_format',\n 'Describes the Contentrain content file format for any language/platform. Returns a comprehensive specification of the file structure, JSON formats, markdown conventions, meta files, and locale strategies.',\n {},\n async () => {\n const formatSpec = {\n overview: 'Contentrain stores content as plain JSON and Markdown files in a .contentrain/ directory at the project root. All files are committed to git.',\n directory_structure: {\n description: 'Standard layout of .contentrain/ directory',\n layout: {\n '.contentrain/config.json': 'Project configuration (stack, workflow, locales, domains)',\n '.contentrain/context.json': 'Last operation metadata (written by MCP after every write)',\n '.contentrain/vocabulary.json': 'Shared vocabulary/terms (optional)',\n '.contentrain/models/{model-id}.json': 'Model schema definitions',\n '.contentrain/content/{domain}/{model-id}/': 'Content files per model',\n '.contentrain/meta/{model-id}/': 'Metadata files per model (status, source, timestamps)',\n },\n },\n model_kinds: {\n collection: {\n description: 'Multiple entries stored as an object-map keyed by entry ID',\n storage_format: 'JSON object-map: { \"entry-id-1\": { ...fields }, \"entry-id-2\": { ...fields } }',\n note: 'Keys are sorted alphabetically for canonical output. Entry IDs are alphanumeric (1-40 chars, hyphens/underscores allowed).',\n example: '{ \"abc123\": { \"title\": \"Hello\", \"slug\": \"hello\" }, \"def456\": { \"title\": \"World\", \"slug\": \"world\" } }',\n },\n singleton: {\n description: 'Single entry (e.g. site settings, hero section)',\n storage_format: 'JSON object with field key-value pairs: { \"title\": \"My Site\", \"description\": \"...\" }',\n },\n document: {\n description: 'Markdown files with YAML-like frontmatter',\n storage_format: 'Markdown file: ---\\\\nslug: my-post\\\\ntitle: My Post\\\\n---\\\\n\\\\nMarkdown body content here.',\n frontmatter_rules: [\n 'Delimited by --- on its own line',\n 'Key-value pairs: key: value',\n 'Arrays: key:\\\\n - item1\\\\n - item2',\n 'Inline arrays: key: [item1, item2]',\n 'Values auto-parsed: true/false -> boolean, integers -> number, quoted -> string',\n '\"body\" key is reserved for the markdown content below the frontmatter',\n ],\n },\n dictionary: {\n description: 'Flat key-value string map (e.g. i18n translation files)',\n storage_format: 'JSON object: { \"greeting\": \"Hello\", \"farewell\": \"Goodbye\" }',\n note: 'All values must be strings.',\n },\n },\n meta_files: {\n description: 'Metadata is stored separately from content in .contentrain/meta/{model-id}/',\n collection_meta: 'Object-map: { \"entry-id\": { \"status\": \"draft\", \"source\": \"agent\", \"updated_by\": \"contentrain-mcp\" } }',\n singleton_meta: 'Single object: { \"status\": \"published\", \"source\": \"human\", \"updated_by\": \"user@example.com\" }',\n fields: {\n status: 'Content lifecycle status: \"draft\" | \"in_review\" | \"published\" | \"rejected\" | \"archived\"',\n source: 'Who created the entry: \"agent\" | \"human\" | \"import\"',\n updated_by: 'Author identifier string',\n approved_by: 'Optional: approver identifier',\n version: 'Optional: version string',\n publish_at: 'Optional: ISO 8601 date for scheduled publishing',\n expire_at: 'Optional: ISO 8601 date for scheduled expiry (must be after publish_at)',\n },\n },\n locale_strategies: {\n description: 'How localized content files are organized. Set per-model via locale_strategy field.',\n strategies: {\n file: {\n description: 'Default. Each locale is a separate file in the model directory.',\n json_pattern: '{content-dir}/{locale}.json (e.g. en.json, tr.json)',\n md_pattern: '{content-dir}/{slug}/{locale}.md',\n },\n suffix: {\n description: 'Locale appended to filename.',\n json_pattern: '{content-dir}/{model-id}.{locale}.json',\n md_pattern: '{content-dir}/{slug}.{locale}.md',\n },\n directory: {\n description: 'Locale as subdirectory.',\n json_pattern: '{content-dir}/{locale}/{model-id}.json',\n md_pattern: '{content-dir}/{locale}/{slug}.md',\n },\n none: {\n description: 'No locale in path (single-language or externally managed).',\n json_pattern: '{content-dir}/{model-id}.json',\n md_pattern: '{content-dir}/{slug}.md',\n },\n },\n },\n i18n_disabled: {\n description: 'When a model has i18n:false, locale is ignored in file paths.',\n json_pattern: '{content-dir}/data.json (always \"data.json\", no locale suffix)',\n md_pattern: '{content-dir}/{slug}.md (no locale in path)',\n },\n canonical_serialization: {\n description: 'All JSON files use deterministic serialization for clean git diffs.',\n rules: [\n 'Object keys sorted alphabetically',\n '2-space indentation',\n 'Trailing newline at end of file',\n 'No trailing commas',\n 'UTF-8 encoding',\n ],\n },\n content_path_override: {\n description: 'Models can specify a custom content_path to store content outside .contentrain/content/.',\n example: 'A model with content_path: \"content/blog\" stores files at {project-root}/content/blog/ instead of .contentrain/content/{domain}/{model-id}/',\n note: 'This is useful for frameworks that expect content in specific directories (e.g. Nuxt Content, Astro).',\n },\n reading_content: {\n steps: [\n '1. Read .contentrain/config.json to get locales and workflow settings',\n '2. Read .contentrain/models/{model-id}.json to get the schema and model kind',\n '3. Determine content directory: model.content_path or .contentrain/content/{domain}/{model-id}/',\n '4. Determine file path using locale_strategy and i18n flag',\n '5. Parse JSON (collection/singleton/dictionary) or Markdown frontmatter (document)',\n '6. Optionally read .contentrain/meta/{model-id}/{locale}.json for status/metadata',\n ],\n },\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(formatSpec, null, 2) }],\n }\n },\n )\n}\n\nasync function getSample(\n projectRoot: string,\n model: import('@contentrain/types').ModelDefinition,\n locale: string,\n): Promise<unknown> {\n const { readJson, readDir } = await import('../util/fs.js')\n const cDir = resolveContentDir(projectRoot, model)\n\n if (model.kind === 'collection') {\n const data = await readJson<Record<string, Record<string, unknown>>>(resolveJsonFilePath(cDir, model, locale))\n if (!data) return null\n const firstKey = Object.keys(data)[0]\n return firstKey ? { id: firstKey, ...data[firstKey] } : null\n }\n\n if (model.kind === 'singleton' || model.kind === 'dictionary') {\n return readJson(resolveJsonFilePath(cDir, model, locale))\n }\n\n if (model.kind === 'document') {\n const strategy = resolveLocaleStrategy(model)\n\n if (!model.i18n) {\n const entry = (await readDir(cDir)).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'file') {\n const slug = (await readDir(cDir))[0]\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'suffix') {\n const suffix = `.${locale}.md`\n const entry = (await readDir(cDir)).find(file => file.endsWith(suffix))\n const slug = entry?.slice(0, -suffix.length)\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'directory') {\n const entry = (await readDir(join(cDir, locale))).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n const entry = (await readDir(cDir)).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n return null\n}\n\nfunction buildContentPath(\n model: import('@contentrain/types').ModelDefinition,\n locale: string,\n): string {\n const strategy = model.locale_strategy ?? 'file'\n const baseDir = model.content_path ?? `.contentrain/content/${model.domain}/${model.id}`\n\n if (model.kind === 'document') {\n // When i18n is disabled, documents are flat {slug}.md\n if (!model.i18n) return `${baseDir}/{slug}.md`\n\n // Documents use directories — show the base path pattern\n switch (strategy) {\n case 'suffix': return `${baseDir}/{slug}.${locale}.md`\n case 'directory': return `${baseDir}/${locale}/{slug}.md`\n case 'none': return `${baseDir}/{slug}.md`\n case 'file':\n default: return `${baseDir}/{slug}/${locale}.md`\n }\n }\n\n // JSON-based models (collection, singleton, dictionary)\n // When i18n is disabled, always data.json\n if (!model.i18n) return `${baseDir}/data.json`\n\n switch (strategy) {\n case 'suffix': return `${baseDir}/${model.id}.${locale}.json`\n case 'directory': return `${baseDir}/${locale}/${model.id}.json`\n case 'none': return `${baseDir}/${model.id}.json`\n case 'file':\n default: return `${baseDir}/${locale}.json`\n }\n}\n\nfunction generateImportSnippet(\n model: import('@contentrain/types').ModelDefinition,\n stack: string,\n locale: string,\n): Record<string, string> {\n const path = buildContentPath(model, locale)\n\n const snippets: Record<string, string> = {\n generic: `import data from '${path}'`,\n }\n\n switch (stack) {\n case 'nuxt':\n snippets['nuxt'] = model.kind === 'document'\n ? `const { data } = await useAsyncData(() => queryContent('${model.domain}/${model.id}').locale('${locale}').find())`\n : `const { data } = await useFetch('/api/content/${model.id}?locale=${locale}')`\n break\n case 'next':\n snippets['next'] = `import data from '@/${path}'`\n break\n case 'astro':\n snippets['astro'] = `import data from '~/${path}'`\n break\n }\n\n return snippets\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainConfig, Vocabulary } from '@contentrain/types'\nimport { z } from 'zod'\nimport { join } from 'node:path'\nimport { readFile, writeFile, appendFile } from 'node:fs/promises'\nimport { simpleGit } from 'simple-git'\nimport { detectStack } from '../util/detect.js'\nimport { contentrainDir, ensureDir, pathExists, writeJson } from '../util/fs.js'\nimport { readConfig, readVocabulary } from '../core/config.js'\nimport { writeModel } from '../core/model-manager.js'\nimport { writeContent, type ContentEntry } from '../core/content-manager.js'\nimport { getTemplate, listTemplates } from '../templates/index.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\n\nexport function registerSetupTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_init ───\n server.tool(\n 'contentrain_init',\n 'Initialize .contentrain/ structure. Changes are auto-committed to git — do NOT manually create .contentrain/ files.',\n {\n stack: z.string().optional().describe('Framework stack (nuxt, next, astro, svelte, react-vite, other). Auto-detected if omitted.'),\n locales: z.array(z.string()).optional().describe('Supported locales. Default: [\"en\"]'),\n domains: z.array(z.string()).optional().describe('Content domains. Default: auto-suggested'),\n },\n async ({ stack, locales, domains }) => {\n const crDir = contentrainDir(projectRoot)\n\n // Already initialized?\n if (await pathExists(join(crDir, 'config.json'))) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'Already initialized',\n suggestion: 'Use contentrain_status to see current state',\n }) }],\n isError: true,\n }\n }\n\n // Detect stack\n const detectedStack = stack ?? await detectStack(projectRoot)\n const supportedLocales = locales ?? ['en']\n const suggestedDomains = domains ?? suggestDomains(projectRoot)\n\n // Ensure .git exists\n const hasGit = await pathExists(join(projectRoot, '.git'))\n if (!hasGit) {\n await simpleGit(projectRoot).init()\n }\n\n // Branch health gate\n const initHealth = await checkBranchHealth(projectRoot)\n if (initHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: initHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Create git transaction\n const branch = buildBranchName('new', 'init')\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n const wtCrDir = join(wt, '.contentrain')\n\n // Create directories with .gitkeep so git tracks them\n await Promise.all(['models', 'content', 'meta'].map(async (dir) => {\n await ensureDir(join(wtCrDir, dir))\n await writeFile(join(wtCrDir, dir, '.gitkeep'), '', 'utf-8')\n }))\n\n // Write config\n const config: ContentrainConfig = {\n version: 1,\n stack: detectedStack as ContentrainConfig['stack'],\n workflow: 'auto-merge',\n locales: {\n default: supportedLocales[0] ?? 'en',\n supported: supportedLocales,\n },\n domains: suggestedDomains,\n }\n await writeJson(join(wtCrDir, 'config.json'), config)\n\n // Write empty vocabulary\n const vocabulary: Vocabulary = { version: 1, terms: {} }\n await writeJson(join(wtCrDir, 'vocabulary.json'), vocabulary)\n\n // Write initial context\n await writeJson(join(wtCrDir, 'context.json'), {\n version: '1',\n lastOperation: {\n tool: 'contentrain_init',\n model: '',\n locale: supportedLocales[0] ?? 'en',\n timestamp: new Date().toISOString(),\n source: process.env['CONTENTRAIN_SOURCE'] === 'mcp-studio' ? 'mcp-studio' : 'mcp-local',\n },\n stats: { models: 0, entries: 0, locales: supportedLocales, lastSync: new Date().toISOString() },\n })\n\n // Update .gitignore\n await updateGitignore(wt)\n })\n\n await tx.commit('[contentrain] init: project setup')\n const gitResult = await tx.complete({\n tool: 'contentrain_init',\n model: '',\n locale: supportedLocales[0] ?? 'en',\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Project initialized and committed to git. Do NOT manually edit .contentrain/ files.',\n config_created: '.contentrain/config.json',\n detected_stack: detectedStack,\n detected_locales: supportedLocales,\n suggested_domains: suggestedDomains,\n files_created: [\n '.contentrain/config.json',\n '.contentrain/vocabulary.json',\n '.contentrain/context.json',\n ],\n directories_created: [\n '.contentrain/models/',\n '.contentrain/content/',\n '.contentrain/meta/',\n ],\n gitignore_updated: true,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n next_steps: [\n 'Create models with contentrain_model_save or contentrain_scaffold',\n 'Use contentrain_scan to find hardcoded strings',\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Init failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_scaffold ───\n server.tool(\n 'contentrain_scaffold',\n `Template-based project setup. Available templates: ${listTemplates().join(', ')}. Changes are auto-committed to git.`,\n {\n template: z.string().describe('Template ID: blog, landing, docs, ecommerce, saas, i18n, mobile'),\n locales: z.array(z.string()).optional().describe('Override locales'),\n with_sample_content: z.boolean().optional().default(true).describe('Include sample content (default: true)'),\n },\n async ({ template: templateId, locales, with_sample_content }) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'Project not initialized. Run contentrain_init first.',\n }) }],\n isError: true,\n }\n }\n\n const tmpl = getTemplate(templateId)\n if (!tmpl) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Unknown template: \"${templateId}\". Available: ${listTemplates().join(', ')}`,\n }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const scaffoldHealth = await checkBranchHealth(projectRoot)\n if (scaffoldHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: scaffoldHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const effectiveLocales = locales ?? config.locales.supported\n const defaultLocale = effectiveLocales[0] ?? 'en'\n\n const branch = buildBranchName('new', `scaffold-${templateId}`, defaultLocale)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n const modelsCreated: Array<{ id: string; kind: string; fields: number }> = []\n let contentCreated = 0\n let vocabAdded = 0\n\n await tx.write(async (wt) => {\n // Write models\n await Promise.all(tmpl.models.map(async (model) => {\n await writeModel(wt, model)\n modelsCreated.push({\n id: model.id,\n kind: model.kind,\n fields: model.fields ? Object.keys(model.fields).length : 0,\n })\n }))\n\n // Write sample content via content pipeline (handles content_path, locale_strategy, meta)\n if (with_sample_content && tmpl.sample_content) {\n contentCreated = await writeSampleContent(wt, tmpl, effectiveLocales, config)\n }\n\n // Merge vocabulary\n if (tmpl.vocabulary) {\n vocabAdded = await mergeVocabulary(wt, tmpl.vocabulary, effectiveLocales)\n }\n\n })\n\n await tx.commit(`[contentrain] scaffold: ${templateId} (${defaultLocale})`)\n const gitResult = await tx.complete({\n tool: 'contentrain_scaffold',\n model: tmpl.models.map(m => m.id).join(', '),\n locale: defaultLocale,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Scaffold applied and committed to git. Do NOT manually edit .contentrain/ files.',\n models_created: modelsCreated,\n content_created: contentCreated,\n vocabulary_terms_added: vocabAdded,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n context_updated: true,\n next_steps: [\n 'Customize models with contentrain_model_save',\n 'Add content with contentrain_content_save',\n 'Run contentrain_submit when ready',\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Scaffold failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n}\n\nfunction suggestDomains(_projectRoot: string): string[] {\n return ['marketing', 'blog', 'system']\n}\n\nasync function updateGitignore(projectRoot: string): Promise<void> {\n const gitignorePath = join(projectRoot, '.gitignore')\n const cacheEntry = '.contentrain/.cache/'\n\n if (await pathExists(gitignorePath)) {\n const content = await readFile(gitignorePath, 'utf-8')\n if (!content.includes(cacheEntry)) {\n await appendFile(gitignorePath, `\\n# Contentrain cache\\n${cacheEntry}\\n`)\n }\n } else {\n await writeFile(gitignorePath, `# Contentrain cache\\n${cacheEntry}\\n`, 'utf-8')\n }\n}\n\nasync function writeSampleContent(\n worktreePath: string,\n tmpl: import('@contentrain/types').ScaffoldTemplate,\n locales: string[],\n config: import('@contentrain/types').ContentrainConfig,\n): Promise<number> {\n if (!tmpl.sample_content) return 0\n\n let count = 0\n\n for (const model of tmpl.models) {\n const sampleData = tmpl.sample_content[model.id]\n if (!sampleData) continue\n\n for (const [localeOrKey, data] of Object.entries(sampleData)) {\n const entryMap = data as Record<string, Record<string, unknown>>\n let targetLocale: string\n if (localeOrKey === 'data') {\n targetLocale = locales[0] ?? 'en'\n } else if (locales.includes(localeOrKey)) {\n targetLocale = localeOrKey\n } else {\n continue\n }\n\n // Convert template payload to ContentEntry[] based on model kind\n let entries: ContentEntry[]\n\n if (model.kind === 'singleton' || model.kind === 'dictionary') {\n // Singleton/dictionary: the entire locale object IS the data for one entry\n entries = [{ locale: targetLocale, data: entryMap as Record<string, unknown> }]\n } else if (model.kind === 'document') {\n // Document: keys are slugs, values are frontmatter objects\n entries = Object.entries(entryMap).map(([slug, fields]) => ({\n slug,\n locale: targetLocale,\n data: fields,\n }))\n } else {\n // Collection: keys are entry IDs, values are field objects\n entries = Object.entries(entryMap).map(([id, fields]) => ({\n id,\n locale: targetLocale,\n data: fields,\n }))\n }\n\n await writeContent(worktreePath, model, entries, config)\n count += entries.length\n }\n }\n\n return count\n}\n\nasync function mergeVocabulary(\n projectRoot: string,\n newTerms: Record<string, Record<string, string>>,\n _locales: string[],\n): Promise<number> {\n const vocabPath = join(contentrainDir(projectRoot), 'vocabulary.json')\n const existing = await readVocabulary(projectRoot)\n const vocab: Vocabulary = existing ?? { version: 1, terms: {} }\n\n let added = 0\n for (const [key, translations] of Object.entries(newTerms)) {\n if (!(key in vocab.terms)) {\n added++\n }\n vocab.terms[key] = { ...vocab.terms[key], ...translations }\n }\n\n await writeJson(vocabPath, vocab)\n return added\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ModelDefinition } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\n\nimport { resolveContentDir, resolveJsonFilePath, resolveMdFilePath } from '../core/content-manager.js'\nimport { checkReferences, deleteModel, readModel, writeModel, validateModelDefinition, fieldDefZodSchema } from '../core/model-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\n\n// Shared field definition schema — single source of truth with normalize extract\nconst fieldDefSchema = fieldDefZodSchema\n\nexport function registerModelTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_model_save ───\n server.tool(\n 'contentrain_model_save',\n 'Create or update a model definition. Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n id: z.string().describe('Model ID (kebab-case, e.g. \"blog-post\")'),\n name: z.string().describe('Human-readable name'),\n kind: z.enum(['singleton', 'collection', 'document', 'dictionary']).describe('Model kind'),\n domain: z.string().describe('Content domain (e.g. \"blog\", \"marketing\", \"system\")'),\n i18n: z.boolean().describe('Whether this model supports localization'),\n description: z.string().optional().describe('Model description'),\n fields: fieldDefSchema.optional().describe('Field definitions (not needed for dictionary)'),\n content_path: z.string().optional().describe('Framework-relative path for content files (e.g. \"content/blog\", \"locales\"). When set, content is written here instead of .contentrain/content/'),\n locale_strategy: z.enum(['file', 'suffix', 'directory', 'none']).optional().describe('How locale is encoded in file names. Default: \"file\"'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n // Validate\n const errors = validateModel(input)\n if (errors.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Validation failed', details: errors }) }],\n isError: true,\n }\n }\n\n // Reject invalid locale_strategy + i18n combinations\n if (input.locale_strategy === 'none' && input.i18n !== false) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'locale_strategy \"none\" requires i18n:false. The \"none\" strategy stores a single file without locale encoding, which is incompatible with multi-locale content. Use \"file\", \"suffix\", or \"directory\" for i18n models.',\n }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const existing = await readModel(projectRoot, input.id)\n const action = existing ? 'updated' : 'created'\n\n const model: ModelDefinition = {\n id: input.id,\n name: input.name,\n kind: input.kind,\n domain: input.domain,\n i18n: input.i18n,\n description: input.description,\n fields: input.fields as ModelDefinition['fields'],\n content_path: input.content_path,\n locale_strategy: input.locale_strategy,\n }\n\n const branch = buildBranchName('model', input.id)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n await writeModel(wt, model)\n })\n\n await tx.commit(`[contentrain] ${action}: ${input.id}`)\n const gitResult = await tx.complete({ tool: 'contentrain_model_save', model: input.id })\n\n const defaultLocale = config.locales.default\n // Build accurate content path using path resolvers\n const contentDir = resolveContentDir(projectRoot, model)\n const contentPath = model.content_path ?? `.contentrain/content/${input.domain}/${input.id}`\n let exampleFilePath: string\n if (model.kind === 'document') {\n exampleFilePath = resolveMdFilePath(contentDir, model, defaultLocale, '{slug}')\n } else {\n exampleFilePath = resolveJsonFilePath(contentDir, model, defaultLocale)\n }\n // Make the path relative for display\n const displayPath = exampleFilePath.replace(projectRoot + '/', '').replace(projectRoot, '')\n const importSnippet: Record<string, string> = {\n generic: `import data from '${displayPath}'`,\n }\n if (config.stack === 'nuxt') {\n importSnippet['nuxt'] = model.kind === 'document'\n ? `const { data } = await useAsyncData(() => queryContent('${model.domain}/${model.id}').locale('${defaultLocale}').find())`\n : `const { data } = await useFetch('/api/content/${model.id}?locale=${defaultLocale}')`\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Model saved and committed to git. Do NOT manually edit .contentrain/ files.',\n action,\n model: input.id,\n validation: { valid: true, errors: [] },\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n context_updated: true,\n content_path: contentPath + '/',\n example_file: displayPath,\n import_snippet: importSnippet,\n next_steps: ['Add content with contentrain_content_save'],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Model save failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_model_delete ───\n server.tool(\n 'contentrain_model_delete',\n 'Delete a model and its content/meta. Changes are auto-committed to git — do NOT manually edit .contentrain/ files.',\n {\n model: z.string().describe('Model ID to delete'),\n confirm: z.literal(true).describe('Must be true to confirm deletion'),\n },\n async ({ model: modelId }) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n // Check model exists\n const existing = await readModel(projectRoot, modelId)\n if (!existing) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${modelId}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const deleteHealth = await checkBranchHealth(projectRoot)\n if (deleteHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: deleteHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Check references\n const refs = await checkReferences(projectRoot, modelId)\n if (refs.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n deleted: false,\n error: 'REFERENCED_MODEL',\n referenced_by: refs,\n next_steps: ['Remove relation fields from referencing models first'],\n }, null, 2) }],\n }\n }\n\n const branch = buildBranchName('model', modelId)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let filesRemoved: string[] = []\n\n await tx.write(async (wt) => {\n filesRemoved = await deleteModel(wt, modelId)\n })\n\n await tx.commit(`[contentrain] delete: ${modelId}`)\n const gitResult = await tx.complete({ tool: 'contentrain_model_delete', model: modelId })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Model deleted and committed to git. Do NOT manually edit .contentrain/ files.',\n deleted: true,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n files_removed: filesRemoved,\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Delete failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n}\n\n// validateModel is now validateModelDefinition from model-manager.ts (shared)\nconst validateModel = validateModelDefinition\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { readModel } from '../core/model-manager.js'\nimport { writeContent, deleteContent, listContent } from '../core/content-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { validateProject } from '../core/validator.js'\n\nexport function registerContentTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_content_save ───\n server.tool(\n 'contentrain_content_save',\n 'Save content entries. Entry format varies by model kind: DICTIONARY — provide \"locale\" and \"data\" (flat key-value, all string values); \"id\" and \"slug\" are ignored; data keys are the identities. COLLECTION — provide \"locale\" and \"data\"; \"id\" is optional (auto-generated if omitted); \"slug\" is ignored. DOCUMENT — provide \"slug\" (required), \"locale\", and \"data\"; use the \"body\" key inside data for markdown content. SINGLETON — provide only \"locale\" and \"data\". Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n model: z.string().describe('Model ID'),\n entries: z.array(z.object({\n id: z.string().optional().describe('Entry ID (collection only, auto-generated if omitted)'),\n slug: z.string().optional().describe('Slug (document only)'),\n locale: z.string().optional().describe('Locale code (defaults to config default)'),\n data: z.record(z.string(), z.unknown()).describe('Content data. For documents, include \"body\" key for markdown content.'),\n publish_at: z.string().optional().describe('ISO 8601 date for scheduled publishing (stored in meta)'),\n expire_at: z.string().optional().describe('ISO 8601 date for scheduled expiry (stored in meta, must be after publish_at)'),\n })).describe('Content entries to save'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Validate locales and scheduling fields before starting transaction\n for (const entry of input.entries) {\n if (entry.locale && !config.locales.supported.includes(entry.locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Locale \"${entry.locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n\n // Validate publish_at / expire_at\n if (entry.publish_at !== undefined) {\n const d = new Date(entry.publish_at)\n if (Number.isNaN(d.getTime())) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Invalid publish_at date: \"${entry.publish_at}\". Must be a valid ISO 8601 date string.`,\n }) }],\n isError: true,\n }\n }\n }\n if (entry.expire_at !== undefined) {\n const d = new Date(entry.expire_at)\n if (Number.isNaN(d.getTime())) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Invalid expire_at date: \"${entry.expire_at}\". Must be a valid ISO 8601 date string.`,\n }) }],\n isError: true,\n }\n }\n }\n if (entry.publish_at !== undefined && entry.expire_at !== undefined) {\n if (new Date(entry.expire_at) <= new Date(entry.publish_at)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `expire_at (\"${entry.expire_at}\") must be after publish_at (\"${entry.publish_at}\").`,\n }) }],\n isError: true,\n }\n }\n }\n\n // Merge scheduling fields into data so meta-manager picks them up\n if (entry.publish_at !== undefined) entry.data['publish_at'] = entry.publish_at\n if (entry.expire_at !== undefined) entry.data['expire_at'] = entry.expire_at\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('content', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let results: Awaited<ReturnType<typeof writeContent>>\n\n let entryIds: string[] = []\n\n await tx.write(async (wt) => {\n results = await writeContent(wt, model, input.entries, config)\n entryIds = results!.map(r => r.id ?? r.slug ?? r.locale).filter(Boolean) as string[]\n })\n\n await tx.commit(`[contentrain] content: ${input.model}`)\n const gitResult = await tx.complete({\n tool: 'contentrain_content_save',\n model: input.model,\n locale: input.entries[0]?.locale,\n entries: entryIds,\n })\n\n // Run real validation after save — don't fake it\n const validationResult = await validateProject(projectRoot, { model: input.model })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Content saved and committed to git. Do NOT manually edit .contentrain/ files.',\n results: results!,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n validation: {\n valid: validationResult.valid,\n errors: validationResult.issues.filter(i => i.severity === 'error').map(i => i.message),\n },\n context_updated: true,\n next_steps: [\n ...(!validationResult.valid ? ['WARNING: Content has validation errors — run contentrain_validate for details'] : []),\n ...(model.kind === 'collection'\n ? ['Use contentrain_content_list to verify', 'Add more entries or publish']\n : ['Use contentrain_content_list to verify']),\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Content save failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_content_delete ───\n server.tool(\n 'contentrain_content_delete',\n 'Delete content entries. For dictionaries, use \"keys\" to remove specific keys (omit to delete entire locale file). Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n model: z.string().describe('Model ID'),\n id: z.string().optional().describe('Entry ID (collection)'),\n slug: z.string().optional().describe('Slug (document)'),\n locale: z.string().optional().describe('Locale code'),\n keys: z.array(z.string()).optional().describe('Dictionary only: specific keys to remove. Omit to delete entire locale file.'),\n confirm: z.literal(true).describe('Must be true to confirm deletion'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const deleteHealth = await checkBranchHealth(projectRoot)\n if (deleteHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: deleteHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('content', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let removed: string[] = []\n\n await tx.write(async (wt) => {\n removed = await deleteContent(wt, model, {\n id: input.id,\n slug: input.slug,\n locale: input.locale,\n keys: input.keys,\n })\n })\n\n await tx.commit(`[contentrain] delete content: ${input.model}`)\n const gitResult = await tx.complete({\n tool: 'contentrain_content_delete',\n model: input.model,\n locale: input.locale,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Content deleted and committed to git. Do NOT manually edit .contentrain/ files.',\n deleted: true,\n files_removed: removed,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Delete failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_content_list ───\n server.tool(\n 'contentrain_content_list',\n 'List content entries (read-only). Returns data from .contentrain/ — do NOT manually create or modify content files.',\n {\n model: z.string().describe('Model ID'),\n locale: z.string().optional().describe('Locale code (defaults to config default)'),\n filter: z.record(z.string(), z.unknown()).optional().describe('Filter criteria (collection only)'),\n resolve: z.boolean().optional().describe('Resolve relation fields to actual data'),\n limit: z.number().optional().describe('Max entries to return'),\n offset: z.number().optional().describe('Skip N entries'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n try {\n const result = await listContent(projectRoot, model, {\n locale: input.locale,\n filter: input.filter as Record<string, unknown>,\n resolve: input.resolve,\n limit: input.limit,\n offset: input.offset,\n }, config)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `List failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\nimport { simpleGit } from 'simple-git'\nimport { validateProject } from '../core/validator.js'\nimport { readConfig } from '../core/config.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth, cleanupMergedBranches } from '../git/branch-lifecycle.js'\n\nexport function registerWorkflowTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_validate ───\n server.tool(\n 'contentrain_validate',\n 'Validate project content against model schemas. Detects required field violations, type mismatches, broken relations, secret leaks, i18n parity issues, and more. If fix:true, auto-fixes structural issues (canonical sort, orphan meta, missing locale files) — do NOT manually edit .contentrain/ files.',\n {\n model: z.string().optional().describe('Model ID to validate (omit for all models)'),\n fix: z.boolean().optional().describe('Auto-fix structural issues (canonical sort, orphan meta, missing locale files). Default: false'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n try {\n let result: Awaited<ReturnType<typeof validateProject>> | undefined\n\n if (input.fix) {\n // Branch health gate for fix mode (creates a branch)\n const fixHealth = await checkBranchHealth(projectRoot)\n if (fixHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: fixHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before auto-fixing.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Use git transaction for fixes\n const branch = buildBranchName('fix', 'validate')\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n result = await validateProject(wt, { model: input.model, fix: true })\n })\n\n if (result!.fixed > 0) {\n await tx.commit(`[contentrain] validate: auto-fix ${result!.fixed} issue(s)`)\n const gitResult = await tx.complete({\n tool: 'contentrain_validate',\n model: input.model ?? '*',\n })\n\n const nextSteps: string[] = []\n if (result!.summary.errors > 0) nextSteps.push('Fix remaining errors manually')\n if (result!.summary.warnings > 0) nextSteps.push('Review warnings')\n nextSteps.push('Run contentrain_validate again to verify')\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: `Validation complete. ${result!.fixed} issue(s) auto-fixed and committed to git. Do NOT manually edit .contentrain/ files.`,\n ...result!,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n context_updated: true,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } else {\n // Nothing to fix, cleanup the branch\n await tx.cleanup()\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n } finally {\n await tx.cleanup()\n }\n }\n\n // No fix or nothing was fixed — run read-only validation\n if (!result) {\n result = await validateProject(projectRoot, { model: input.model, fix: false })\n }\n\n const nextSteps: string[] = []\n if (result.summary.errors > 0) nextSteps.push('Fix errors in content using contentrain_content_save')\n if (result.summary.warnings > 0) nextSteps.push('Review warnings')\n if (result.valid) nextSteps.push('Run contentrain_submit to push changes')\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'validated',\n message: result.valid\n ? 'All validation checks passed.'\n : `Validation found ${result.summary.errors} error(s) and ${result.summary.warnings} warning(s).`,\n ...result,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Validation failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n\n // ─── contentrain_submit ───\n server.tool(\n 'contentrain_submit',\n 'Push contentrain/* branches to remote. MCP is push-only — PR creation is handled by the platform. Do NOT manually push or create PRs.',\n {\n branches: z.array(z.string()).optional().describe('Specific branch names to push (omit for all contentrain/* branches)'),\n message: z.string().optional().describe('Optional message for the push operation'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const git = simpleGit(projectRoot)\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n\n // Check remote exists\n let hasRemote = false\n try {\n const remotes = await git.getRemotes()\n hasRemote = remotes.some(r => r.name === remoteName)\n } catch {\n hasRemote = false\n }\n\n if (!hasRemote) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `No remote \"${remoteName}\" found. Configure a git remote first.`,\n next_steps: [`git remote add ${remoteName} <url>`],\n }) }],\n isError: true,\n }\n }\n\n try {\n // Determine the base branch for merge-status checks\n const baseBranch = config.repository?.default_branch\n ?? process.env['CONTENTRAIN_BRANCH']\n ?? ((await git.raw(['branch', '--show-current'])).trim() || 'main')\n\n // Get contentrain branches\n const branchSummary = await git.branchLocal()\n\n // Get branches NOT yet merged into base (only these need pushing)\n let unmergedRaw = ''\n try {\n unmergedRaw = await git.raw(['branch', '--no-merged', baseBranch])\n } catch {\n // If base branch doesn't exist yet, treat all as unmerged\n unmergedRaw = branchSummary.all.join('\\n')\n }\n const unmergedSet = new Set(\n unmergedRaw.split('\\n').map(b => b.replace(/^\\*?\\s+/, '').trim()).filter(Boolean),\n )\n\n let branchesToPush: string[]\n\n if (input.branches && input.branches.length > 0) {\n branchesToPush = input.branches.filter(b => branchSummary.all.includes(b))\n const missing = input.branches.filter(b => !branchSummary.all.includes(b))\n if (missing.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Branches not found: ${missing.join(', ')}`,\n }) }],\n isError: true,\n }\n }\n // Filter out already-merged branches from explicit list\n branchesToPush = branchesToPush.filter(b => unmergedSet.has(b))\n } else {\n branchesToPush = branchSummary.all.filter(b => b.startsWith('contentrain/') && unmergedSet.has(b))\n }\n\n if (branchesToPush.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'No unmerged contentrain/* branches found to push.',\n next_steps: ['Make changes first with contentrain_content_save or contentrain_model_save'],\n }) }],\n isError: true,\n }\n }\n\n // Push each branch\n const pushed: string[] = []\n const errors: Array<{ branch: string; error: string }> = []\n\n for (const branch of branchesToPush) {\n try {\n await git.push(remoteName, branch)\n pushed.push(branch)\n } catch (error) {\n errors.push({\n branch,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n // Lazy cleanup: delete merged branches after push\n const cleanup = await cleanupMergedBranches(projectRoot)\n\n const nextSteps: string[] = []\n if (pushed.length > 0) nextSteps.push('Create PRs on your git platform for review')\n if (errors.length > 0) nextSteps.push('Fix push errors and retry')\n if (cleanup.remaining >= 50) nextSteps.push(`Warning: ${cleanup.remaining} active contentrain branches. Consider reviewing old branches.`)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: `Pushed ${pushed.length} branch(es) to ${remoteName}.`,\n pushed,\n errors: errors.length > 0 ? errors : undefined,\n remote: remoteName,\n cleanup: cleanup.deleted > 0 ? { deleted: cleanup.deleted, remaining: cleanup.remaining } : undefined,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Submit failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { FieldDef } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { buildGraph } from '../core/graph-builder.js'\nimport { scanCandidates, scanSummary } from '../core/scanner.js'\nimport { applyExtract, applyReuse } from '../core/apply-manager.js'\nimport { fieldDefZodSchema } from '../core/model-manager.js'\n\nexport function registerNormalizeTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_scan ───\n server.tool(\n 'contentrain_scan',\n 'Scan project source code for content strings. Three modes: \"graph\" builds import/component graph for project intelligence, \"candidates\" extracts string literals with pre-filtering and pagination, \"summary\" provides quick overview stats. Read-only — no changes to disk or git. MCP finds strings deterministically; the agent decides what is content. Recommended workflow: start with \"summary\" or \"graph\" for orientation, then paginate through \"candidates\" to evaluate strings.',\n {\n mode: z.enum(['graph', 'candidates', 'summary']).optional().describe('Scan mode. Default: candidates'),\n paths: z.array(z.string()).optional().describe('Directories to scan (relative to project root). Default: auto-detect'),\n include: z.array(z.string()).optional().describe('File extensions to include. Default: .tsx, .jsx, .vue, .ts, .js, .mjs, .astro, .svelte'),\n exclude: z.array(z.string()).optional().describe('Additional directory names to exclude'),\n limit: z.number().optional().describe('Candidates mode: batch size. Default: 50'),\n offset: z.number().optional().describe('Candidates mode: pagination offset. Default: 0'),\n min_length: z.number().optional().describe('Candidates mode: minimum string length. Default: 2'),\n max_length: z.number().optional().describe('Candidates mode: maximum string length. Default: 500'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const mode = input.mode ?? 'candidates'\n\n try {\n switch (mode) {\n case 'graph': {\n const graph = await buildGraph(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'graph',\n ...graph,\n next_steps: [\n 'Use mode:candidates to scan specific files/directories for strings',\n 'Focus on pages and components with high string counts first',\n ],\n }, null, 2) }],\n }\n }\n\n case 'candidates': {\n const result = await scanCandidates(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n limit: input.limit,\n offset: input.offset,\n min_length: input.min_length,\n max_length: input.max_length,\n })\n\n const nextSteps: string[] = []\n if (result.stats.has_more) {\n nextSteps.push(`Use offset:${(input.offset ?? 0) + (input.limit ?? 50)} for next batch`)\n }\n nextSteps.push('Evaluate each candidate: is it user-facing content? Which domain/model?')\n if (result.duplicates.length > 0) {\n nextSteps.push('Consider deduplicating repeated strings into shared models')\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'candidates',\n ...result,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n }\n\n case 'summary': {\n const result = await scanSummary(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n min_length: input.min_length,\n max_length: input.max_length,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'summary',\n ...result,\n next_steps: [\n 'Use mode:graph for project structure analysis',\n 'Use mode:candidates to scan directories with most candidates',\n ],\n }, null, 2) }],\n }\n }\n\n default:\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown mode: ${mode}` }) }],\n isError: true,\n }\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Scan failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n\n // ─── contentrain_apply ───\n server.tool(\n 'contentrain_apply',\n 'Apply normalize operations. Two modes: \"extract\" writes agent-approved strings to Contentrain content files (source untouched), \"reuse\" patches source files with agent-provided replacement expressions. DRY RUN (default, dry_run:true): validates inputs, resolves conflicts, and returns a full preview — NO changes to disk or git. EXECUTE (dry_run:false): writes files to disk, commits to a branch, and requires branch health check to pass. Recommended workflow: always run dry_run first, review the preview, then call again with dry_run:false to execute. Normalize operations always use review workflow (never auto-merge).',\n {\n mode: z.enum(['extract', 'reuse']).describe('Apply mode: extract (content creation) or reuse (source patching)'),\n dry_run: z.boolean().optional().default(true).describe('Defaults to preview mode (dry_run:true). Set dry_run:false to execute after reviewing the preview.'),\n\n // Extract mode fields\n extractions: z.array(z.object({\n model: z.string().describe('Model ID (e.g. \"ui-texts\", \"hero-section\")'),\n kind: z.enum(['singleton', 'collection', 'dictionary', 'document']).describe('Model kind'),\n domain: z.string().describe('Content domain (e.g. \"marketing\", \"app\")'),\n i18n: z.boolean().optional().describe('Enable i18n. Default: true'),\n fields: fieldDefZodSchema.optional().describe('Field definitions — shared schema with model_save for full parity'),\n entries: z.array(z.object({\n locale: z.string().optional().describe('Locale. Default: project default'),\n slug: z.string().optional().describe('Document slug'),\n data: z.record(z.any()).describe('Content data'),\n source: z.object({\n file: z.string().describe('Source file path'),\n line: z.number().describe('Source line number'),\n value: z.string().describe('Original string value'),\n }).optional().describe('Source tracking for traceability'),\n sources: z.array(z.object({\n file: z.string().describe('Source file path'),\n line: z.number().describe('Source line number'),\n key: z.string().describe('Dictionary key this source maps to'),\n value: z.string().describe('Original string value'),\n })).optional().describe('Per-key source tracking for dictionary models'),\n })).describe('Content entries to create'),\n })).optional().describe('Extract mode: content extractions'),\n\n // Reuse mode fields\n scope: z.object({\n model: z.string().optional().describe('Target model ID'),\n domain: z.string().optional().describe('Target domain'),\n }).optional().describe('Reuse mode: scope (model or domain required)'),\n patches: z.array(z.object({\n file: z.string().describe('Relative file path to patch'),\n line: z.number().describe('Line number hint (±10 line search)'),\n old_value: z.string().describe('Original string to replace'),\n new_expression: z.string().describe('Replacement expression (agent-determined)'),\n import_statement: z.string().optional().describe('Import to add if needed'),\n })).optional().describe('Reuse mode: patches to apply (max 100)'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n try {\n switch (input.mode) {\n case 'extract': {\n if (!input.extractions || input.extractions.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Extract mode requires extractions array' }) }],\n isError: true,\n }\n }\n\n const result = await applyExtract(projectRoot, {\n extractions: input.extractions.map(e => ({\n ...e,\n fields: e.fields as Record<string, FieldDef> | undefined,\n })),\n dry_run: input.dry_run,\n })\n\n // Check for branch-blocked response\n if (result.error !== undefined) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'extract',\n ...result,\n }, null, 2) }],\n isError: true,\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'extract',\n ...result,\n }, null, 2) }],\n }\n }\n\n case 'reuse': {\n if (!input.scope) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Reuse mode requires scope (model or domain)' }) }],\n isError: true,\n }\n }\n if (!input.patches || input.patches.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Reuse mode requires patches array' }) }],\n isError: true,\n }\n }\n\n const result = await applyReuse(projectRoot, {\n scope: input.scope,\n patches: input.patches,\n dry_run: input.dry_run,\n })\n\n // Check for branch-blocked response\n if (result.error !== undefined) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'reuse',\n ...result,\n }, null, 2) }],\n isError: true,\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'reuse',\n ...result,\n }, null, 2) }],\n }\n }\n\n default:\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown mode: ${input.mode}` }) }],\n isError: true,\n }\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Apply failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { EntryMeta } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { readModel } from '../core/model-manager.js'\nimport { resolveContentDir, resolveJsonFilePath, deleteContent } from '../core/content-manager.js'\nimport { readMeta, writeMeta } from '../core/meta-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { readJson, writeJson } from '../util/fs.js'\n\nexport function registerBulkTools(server: McpServer, projectRoot: string): void {\n server.tool(\n 'contentrain_bulk',\n 'Batch operations on content entries. All operations are auto-committed to git.',\n {\n operation: z.enum(['copy_locale', 'update_status', 'delete_entries']),\n model: z.string().describe('Model ID'),\n source_locale: z.string().optional().describe('Source locale for copy_locale operation'),\n target_locale: z.string().optional().describe('Target locale for copy_locale operation'),\n entry_ids: z.array(z.string()).optional().describe('Entry IDs for update_status or delete_entries'),\n status: z.enum(['draft', 'in_review', 'published', 'rejected', 'archived']).optional().describe('New status for update_status'),\n confirm: z.boolean().optional().describe('Must be true for delete_entries'),\n },\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n switch (input.operation) {\n case 'copy_locale': {\n if (!input.source_locale || !input.target_locale) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'copy_locale requires source_locale and target_locale' }) }],\n isError: true,\n }\n }\n if (!config.locales.supported.includes(input.source_locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Source locale \"${input.source_locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n if (!config.locales.supported.includes(input.target_locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Target locale \"${input.target_locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n\n // Guard: copy_locale requires i18n model — non-i18n models use data.json for all locales\n if (!model.i18n) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Model \"${input.model}\" has i18n disabled. copy_locale only works with i18n-enabled models. Enable i18n first with contentrain_model_save.`,\n }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection' && model.kind !== 'singleton' && model.kind !== 'dictionary') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'copy_locale is only supported for collection, singleton, and dictionary models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model, input.target_locale)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let copiedCount = 0\n\n await tx.write(async (wt) => {\n const cDir = resolveContentDir(wt, model)\n const sourceFile = resolveJsonFilePath(cDir, model, input.source_locale!)\n const targetFile = resolveJsonFilePath(cDir, model, input.target_locale!)\n\n const sourceData = await readJson<Record<string, unknown>>(sourceFile)\n if (!sourceData) {\n throw new Error(`No content found for locale \"${input.source_locale}\" in model \"${input.model}\"`)\n }\n\n await writeJson(targetFile, sourceData)\n\n if (model.kind === 'collection') {\n const entries = sourceData as Record<string, Record<string, unknown>>\n const entryIds = Object.keys(entries)\n const metaOps = entryIds.map(entryId =>\n writeMeta(wt, model, { locale: input.target_locale!, entryId }, {\n status: 'draft',\n source: 'agent',\n updated_by: 'contentrain-mcp',\n }),\n )\n await Promise.all(metaOps)\n copiedCount = entryIds.length\n } else {\n await writeMeta(wt, model, { locale: input.target_locale! }, {\n status: 'draft',\n source: 'agent',\n updated_by: 'contentrain-mcp',\n })\n copiedCount = 1\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: copy ${input.source_locale} → ${input.target_locale} for ${input.model}`)\n const gitResult = await tx.complete({\n tool: 'contentrain_bulk',\n model: input.model,\n locale: input.target_locale!,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'copy_locale',\n message: `Copied ${copiedCount} entries from ${input.source_locale} to ${input.target_locale}.`,\n copied: copiedCount,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `copy_locale failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n case 'update_status': {\n if (!input.entry_ids || input.entry_ids.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status requires entry_ids' }) }],\n isError: true,\n }\n }\n if (!input.status) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status requires status' }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status with entry_ids is only supported for collection models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let updatedCount = 0\n const notFound: string[] = []\n\n await tx.write(async (wt) => {\n for (const locale of config.locales.supported) {\n const metaData = await readMeta(wt, model, { locale }) as Record<string, EntryMeta> | null\n\n const updateOps: Array<Promise<void>> = []\n for (const entryId of input.entry_ids!) {\n const existing = metaData?.[entryId]\n if (existing) {\n updateOps.push(writeMeta(wt, model, { locale, entryId }, {\n ...existing,\n status: input.status!,\n updated_by: 'contentrain-mcp',\n }))\n updatedCount++\n } else if (locale === config.locales.default) {\n notFound.push(entryId)\n }\n }\n await Promise.all(updateOps)\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: update status → ${input.status} for ${input.model}`)\n const gitResult = await tx.complete({\n tool: 'contentrain_bulk',\n model: input.model,\n entries: input.entry_ids!,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'update_status',\n message: `Updated ${updatedCount} meta entries to status \"${input.status}\".`,\n updated: updatedCount,\n not_found: notFound.length > 0 ? notFound : undefined,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `update_status failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n case 'delete_entries': {\n if (!input.entry_ids || input.entry_ids.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries requires entry_ids' }) }],\n isError: true,\n }\n }\n if (input.confirm !== true) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries requires confirm:true' }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries with entry_ids is only supported for collection models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n const allRemoved: string[] = []\n\n await tx.write(async (wt) => {\n for (const entryId of input.entry_ids!) {\n const removed = await deleteContent(wt, model, { id: entryId })\n allRemoved.push(...removed)\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: delete ${input.entry_ids.length} entries from ${input.model}`)\n const gitResult = await tx.complete({\n tool: 'contentrain_bulk',\n model: input.model,\n entries: input.entry_ids!,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'delete_entries',\n message: `Deleted ${input.entry_ids.length} entries.`,\n deleted: input.entry_ids.length,\n files_removed: allRemoved,\n git: { branch, action: gitResult.action, commit: gitResult.commit },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `delete_entries failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n default: {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown operation: ${input.operation}` }) }],\n isError: true,\n }\n }\n }\n },\n )\n}\n","","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { registerContextTools } from './tools/context.js'\nimport { registerSetupTools } from './tools/setup.js'\nimport { registerModelTools } from './tools/model.js'\nimport { registerContentTools } from './tools/content.js'\nimport { registerWorkflowTools } from './tools/workflow.js'\nimport { registerNormalizeTools } from './tools/normalize.js'\nimport { registerBulkTools } from './tools/bulk.js'\nimport packageJson from '../package.json' with { type: 'json' }\n\nexport function createServer(projectRoot: string): McpServer {\n const server = new McpServer({\n name: 'contentrain-mcp',\n version: packageJson.version,\n })\n\n registerContextTools(server, projectRoot)\n registerSetupTools(server, projectRoot)\n registerModelTools(server, projectRoot)\n registerContentTools(server, projectRoot)\n registerWorkflowTools(server, projectRoot)\n registerNormalizeTools(server, projectRoot)\n registerBulkTools(server, projectRoot)\n\n return server\n}\n"],"mappings":"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"}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { t as readConfig } from "./config-CPlhDwsv.mjs";
|
|
2
|
-
import { t as branchTimestamp } from "./id-BdD4HvMY.mjs";
|
|
3
|
-
import { n as writeContext } from "./context-CUFulSi2.mjs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
6
|
-
import { simpleGit } from "simple-git";
|
|
7
|
-
import { tmpdir } from "node:os";
|
|
8
|
-
//#region src/git/transaction.ts
|
|
9
|
-
async function createTransaction(projectRoot, branchName, options) {
|
|
10
|
-
const git = simpleGit(projectRoot);
|
|
11
|
-
const config = await readConfig(projectRoot);
|
|
12
|
-
const workflow = options?.workflowOverride ?? config?.workflow ?? "auto-merge";
|
|
13
|
-
const remoteName = process.env["CONTENTRAIN_REMOTE"] ?? "origin";
|
|
14
|
-
let baseBranch = process.env["CONTENTRAIN_BRANCH"] ?? config?.repository?.default_branch ?? "";
|
|
15
|
-
if (!baseBranch) try {
|
|
16
|
-
baseBranch = (await git.raw(["branch", "--show-current"])).trim();
|
|
17
|
-
} catch {}
|
|
18
|
-
if (!baseBranch) baseBranch = "main";
|
|
19
|
-
let hasRemote = false;
|
|
20
|
-
try {
|
|
21
|
-
hasRemote = (await git.getRemotes()).some((r) => r.name === remoteName);
|
|
22
|
-
} catch {
|
|
23
|
-
hasRemote = false;
|
|
24
|
-
}
|
|
25
|
-
if (hasRemote) try {
|
|
26
|
-
await git.fetch(remoteName, baseBranch);
|
|
27
|
-
} catch {}
|
|
28
|
-
const worktreePath = join(tmpdir(), `cr-${randomUUID()}`);
|
|
29
|
-
const branch = branchName;
|
|
30
|
-
const baseRef = hasRemote ? `${remoteName}/${baseBranch}` : baseBranch;
|
|
31
|
-
await git.raw([
|
|
32
|
-
"worktree",
|
|
33
|
-
"add",
|
|
34
|
-
worktreePath,
|
|
35
|
-
"-b",
|
|
36
|
-
branch,
|
|
37
|
-
baseRef
|
|
38
|
-
]);
|
|
39
|
-
const wtGit = simpleGit(worktreePath);
|
|
40
|
-
const authorName = process.env["CONTENTRAIN_AUTHOR_NAME"] ?? "Contentrain";
|
|
41
|
-
const authorEmail = process.env["CONTENTRAIN_AUTHOR_EMAIL"] ?? "mcp@contentrain.io";
|
|
42
|
-
await wtGit.addConfig("user.name", authorName);
|
|
43
|
-
await wtGit.addConfig("user.email", authorEmail);
|
|
44
|
-
let commitHash = "";
|
|
45
|
-
return {
|
|
46
|
-
worktree: worktreePath,
|
|
47
|
-
branch,
|
|
48
|
-
async write(callback) {
|
|
49
|
-
await callback(worktreePath);
|
|
50
|
-
},
|
|
51
|
-
async commit(message) {
|
|
52
|
-
await wtGit.add(".");
|
|
53
|
-
try {
|
|
54
|
-
await wtGit.raw([
|
|
55
|
-
"reset",
|
|
56
|
-
"HEAD",
|
|
57
|
-
"--",
|
|
58
|
-
".contentrain/context.json"
|
|
59
|
-
]);
|
|
60
|
-
} catch {}
|
|
61
|
-
commitHash = (await wtGit.commit(message, { "--allow-empty": null })).commit || "";
|
|
62
|
-
return commitHash;
|
|
63
|
-
},
|
|
64
|
-
async complete(contextUpdate) {
|
|
65
|
-
if (workflow === "review") {
|
|
66
|
-
if (hasRemote) await git.push(remoteName, branch);
|
|
67
|
-
return {
|
|
68
|
-
action: "pending-review",
|
|
69
|
-
commit: commitHash
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
if (hasRemote) try {
|
|
73
|
-
await git.fetch(remoteName, baseBranch);
|
|
74
|
-
} catch {}
|
|
75
|
-
const status = await git.status();
|
|
76
|
-
if (status.modified.length > 0 || status.not_added.length > 0 || status.created.length > 0) throw new Error("Working tree has uncommitted changes. Commit or stash them before completing the transaction.");
|
|
77
|
-
await git.checkout(baseBranch);
|
|
78
|
-
try {
|
|
79
|
-
await git.merge([branch, "--no-edit"]);
|
|
80
|
-
} catch {
|
|
81
|
-
try {
|
|
82
|
-
await git.merge(["--abort"]);
|
|
83
|
-
} catch {}
|
|
84
|
-
throw new Error(`Merge conflict when merging branch "${branch}" into "${baseBranch}". The branch still exists with your changes intact. Resolve the conflict manually, or delete the branch and retry.`);
|
|
85
|
-
}
|
|
86
|
-
if (hasRemote) try {
|
|
87
|
-
await git.push(remoteName, baseBranch);
|
|
88
|
-
} catch {}
|
|
89
|
-
if (contextUpdate) try {
|
|
90
|
-
await writeContext(projectRoot, contextUpdate);
|
|
91
|
-
await git.add(".contentrain/context.json");
|
|
92
|
-
await git.commit("[contentrain] update context", {
|
|
93
|
-
"--allow-empty": null,
|
|
94
|
-
"--author": `${authorName} <${authorEmail}>`
|
|
95
|
-
});
|
|
96
|
-
} catch {}
|
|
97
|
-
return {
|
|
98
|
-
action: "auto-merged",
|
|
99
|
-
commit: commitHash
|
|
100
|
-
};
|
|
101
|
-
},
|
|
102
|
-
async cleanup() {
|
|
103
|
-
try {
|
|
104
|
-
await git.raw([
|
|
105
|
-
"worktree",
|
|
106
|
-
"remove",
|
|
107
|
-
worktreePath,
|
|
108
|
-
"--force"
|
|
109
|
-
]);
|
|
110
|
-
} catch {}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function buildBranchName(scope, target, locale) {
|
|
115
|
-
const ts = branchTimestamp();
|
|
116
|
-
const parts = [
|
|
117
|
-
"contentrain",
|
|
118
|
-
scope,
|
|
119
|
-
target
|
|
120
|
-
];
|
|
121
|
-
if (locale) parts.push(locale);
|
|
122
|
-
parts.push(ts);
|
|
123
|
-
return parts.join("/");
|
|
124
|
-
}
|
|
125
|
-
//#endregion
|
|
126
|
-
export { createTransaction as n, buildBranchName as t };
|
|
127
|
-
|
|
128
|
-
//# sourceMappingURL=transaction-BhSvWfSV.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"transaction-BhSvWfSV.mjs","names":[],"sources":["../src/git/transaction.ts"],"sourcesContent":["import { simpleGit } from 'simple-git'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { randomUUID } from 'node:crypto'\nimport { readConfig } from '../core/config.js'\nimport { writeContext } from '../core/context.js'\nimport { branchTimestamp } from '../util/id.js'\n\nexport interface ContextUpdate {\n tool: string\n model: string\n locale?: string\n entries?: string[]\n}\n\nexport interface GitTransaction {\n worktree: string\n branch: string\n write(callback: (worktreePath: string) => Promise<void>): Promise<void>\n commit(message: string): Promise<string>\n complete(contextUpdate?: ContextUpdate): Promise<{ action: 'auto-merged' | 'pending-review'; commit: string }>\n cleanup(): Promise<void>\n}\n\nexport async function createTransaction(\n projectRoot: string,\n branchName: string,\n options?: { workflowOverride?: 'review' | 'auto-merge' },\n): Promise<GitTransaction> {\n const git = simpleGit(projectRoot)\n const config = await readConfig(projectRoot)\n const workflow = options?.workflowOverride ?? config?.workflow ?? 'auto-merge'\n\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n\n // Smart base branch detection: env → config → current branch → 'main'\n let baseBranch = process.env['CONTENTRAIN_BRANCH'] ?? config?.repository?.default_branch ?? ''\n if (!baseBranch) {\n try {\n baseBranch = (await git.raw(['branch', '--show-current'])).trim()\n } catch {\n // fallback below\n }\n }\n if (!baseBranch) {\n baseBranch = 'main'\n }\n\n // Detect remote\n let hasRemote = false\n try {\n const remotes = await git.getRemotes()\n hasRemote = remotes.some(r => r.name === remoteName)\n } catch {\n hasRemote = false\n }\n\n // Fetch latest if remote exists\n if (hasRemote) {\n try {\n await git.fetch(remoteName, baseBranch)\n } catch {\n // fetch may fail (no network), continue with local state\n }\n }\n\n const worktreePath = join(tmpdir(), `cr-${randomUUID()}`)\n const branch = branchName\n\n // Create worktree with new branch from base\n const baseRef = hasRemote ? `${remoteName}/${baseBranch}` : baseBranch\n await git.raw(['worktree', 'add', worktreePath, '-b', branch, baseRef])\n\n const wtGit = simpleGit(worktreePath)\n\n // Configure author\n const authorName = process.env['CONTENTRAIN_AUTHOR_NAME'] ?? 'Contentrain'\n const authorEmail = process.env['CONTENTRAIN_AUTHOR_EMAIL'] ?? 'mcp@contentrain.io'\n await wtGit.addConfig('user.name', authorName)\n await wtGit.addConfig('user.email', authorEmail)\n\n let commitHash = ''\n\n return {\n worktree: worktreePath,\n branch,\n\n async write(callback) {\n await callback(worktreePath)\n },\n\n async commit(message) {\n await wtGit.add('.')\n // Exclude context.json from branch commits — it contains timestamps\n // that cause merge conflicts on sequential operations. Context is\n // updated separately on the base branch after merge.\n try {\n await wtGit.raw(['reset', 'HEAD', '--', '.contentrain/context.json'])\n } catch {\n // context.json may not be staged — that's fine\n }\n const result = await wtGit.commit(message, { '--allow-empty': null })\n commitHash = result.commit || ''\n return commitHash\n },\n\n async complete(contextUpdate?: ContextUpdate) {\n if (workflow === 'review') {\n if (hasRemote) {\n await git.push(remoteName, branch)\n }\n return { action: 'pending-review', commit: commitHash }\n }\n\n // auto-merge: merge branch into base\n if (hasRemote) {\n try {\n await git.fetch(remoteName, baseBranch)\n } catch {\n // continue with local state\n }\n }\n\n // Ensure working tree is clean before checkout\n const status = await git.status()\n if (status.modified.length > 0 || status.not_added.length > 0 || status.created.length > 0) {\n throw new Error('Working tree has uncommitted changes. Commit or stash them before completing the transaction.')\n }\n\n await git.checkout(baseBranch)\n\n try {\n await git.merge([branch, '--no-edit'])\n } catch {\n // Merge conflict — abort and let the user resolve manually.\n try {\n await git.merge(['--abort'])\n } catch {\n // abort may fail if not in merge state\n }\n throw new Error(\n `Merge conflict when merging branch \"${branch}\" into \"${baseBranch}\". `\n + `The branch still exists with your changes intact. `\n + `Resolve the conflict manually, or delete the branch and retry.`,\n )\n }\n\n if (hasRemote) {\n try {\n await git.push(remoteName, baseBranch)\n } catch {\n // push may fail, branch is merged locally\n }\n }\n\n // Update context.json on base branch after successful merge\n if (contextUpdate) {\n try {\n await writeContext(projectRoot, contextUpdate)\n await git.add('.contentrain/context.json')\n await git.commit('[contentrain] update context', { '--allow-empty': null, '--author': `${authorName} <${authorEmail}>` })\n } catch {\n // Context update is best-effort — don't fail the transaction\n }\n }\n\n return { action: 'auto-merged', commit: commitHash }\n },\n\n async cleanup() {\n try {\n await git.raw(['worktree', 'remove', worktreePath, '--force'])\n } catch {\n // worktree may already be cleaned up\n }\n },\n }\n}\n\nexport function buildBranchName(scope: string, target: string, locale?: string): string {\n const ts = branchTimestamp()\n const parts = ['contentrain', scope, target]\n if (locale) parts.push(locale)\n parts.push(ts)\n return parts.join('/')\n}\n"],"mappings":"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"}
|