@contentrain/mcp 1.0.0 → 1.0.3

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 CHANGED
@@ -1,5 +1,8 @@
1
1
  # `@contentrain/mcp`
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/%40contentrain%2Fmcp?label=%40contentrain%2Fmcp)](https://www.npmjs.com/package/@contentrain/mcp)
4
+ [![GitHub source](https://img.shields.io/badge/source-Contentrain%2Fai-181717?logo=github)](https://github.com/Contentrain/ai/tree/main/packages/mcp)
5
+
3
6
  Local-first MCP server and core primitives for Contentrain.
4
7
 
5
8
  Contentrain is AI-generated content governance infrastructure:
@@ -186,6 +189,10 @@ pnpm exec oxlint packages/mcp/src packages/mcp/tests
186
189
  - `@contentrain/rules` — IDE/agent rules and prompts
187
190
  - `@contentrain/types` — shared schema and model types
188
191
 
192
+ ## 📚 Documentation
193
+
194
+ Full documentation at **[ai.contentrain.io/packages/mcp](https://ai.contentrain.io/packages/mcp)**.
195
+
189
196
  ## 📄 License
190
197
 
191
198
  MIT
@@ -1,9 +1,9 @@
1
1
  import { c as writeText, o as readText, r as pathExists } from "./fs-BbuGnVcG.mjs";
2
2
  import { t as readConfig } from "./config-CPlhDwsv.mjs";
3
- import { S as writeContent, _ as resolveMdFilePath, c as validateModelDefinition, h as resolveJsonFilePath, l as writeModel, m as resolveContentDir, o as listModels, s as readModel } from "./model-manager-BcWWu5l2.mjs";
4
- import { n as writeContext } from "./context-yh3cuw5N.mjs";
3
+ import { S as writeContent, _ as resolveMdFilePath, c as validateModelDefinition, h as resolveJsonFilePath, l as writeModel, m as resolveContentDir, o as listModels, s as readModel } from "./model-manager-sfPdrVIJ.mjs";
4
+ import { n as writeContext } from "./context-CRBCTYPB.mjs";
5
5
  import { t as checkBranchHealth } from "./branch-lifecycle-DB6sgeJi.mjs";
6
- import { n as createTransaction, t as buildBranchName } from "./transaction-BsECnmL5.mjs";
6
+ import { n as createTransaction, t as buildBranchName } from "./transaction-DXkMulzr.mjs";
7
7
  import { extname, join } from "node:path";
8
8
  //#region src/core/apply-manager.ts
9
9
  const MAX_PATCHES = 100;
@@ -748,4 +748,4 @@ function addImportsToLines(lines, imports) {
748
748
  //#endregion
749
749
  export { detectFileFramework as a, validatePatchPath as c, checkSyntax as i, applyExtract as n, replaceInLine as o, applyReuse as r, validateFrameworkExpression as s, PATCHABLE_EXTENSIONS as t };
750
750
 
751
- //# sourceMappingURL=apply-manager-CaTuu7Lj.mjs.map
751
+ //# sourceMappingURL=apply-manager-BAeqrAHX.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"apply-manager-CaTuu7Lj.mjs","names":[],"sources":["../src/core/apply-manager.ts"],"sourcesContent":["import type { ModelDefinition, FieldDef } from '@contentrain/types'\nimport { join, extname } from 'node:path'\nimport { readText, writeText, pathExists } from '../util/fs.js'\nimport { readModel, writeModel, listModels, validateModelDefinition } from './model-manager.js'\nimport { writeContent, resolveContentDir, resolveJsonFilePath, resolveMdFilePath, type ContentEntry } from './content-manager.js'\nimport { readConfig } from './config.js'\nimport { writeContext } from './context.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\n\n// ─── Types ───\n\nexport interface ExtractionEntry {\n model: string\n kind: 'singleton' | 'collection' | 'dictionary' | 'document'\n domain: string\n i18n?: boolean\n fields?: Record<string, FieldDef>\n entries: Array<{\n locale?: string\n slug?: string\n data: Record<string, unknown>\n source?: { file: string; line: number; value: string }\n sources?: Array<{ file: string; line: number; key: string; value: string }>\n }>\n}\n\nexport interface ExtractionInput {\n extractions: ExtractionEntry[]\n dry_run?: boolean\n}\n\nexport interface ExtractionPreview {\n models_to_create: string[]\n models_to_update: string[]\n total_entries: number\n content_files: string[]\n}\n\nexport interface ExtractionResult {\n dry_run: boolean\n preview?: ExtractionPreview\n error?: string\n validation_errors?: string[]\n results?: {\n models_created: string[]\n models_updated: string[]\n entries_written: number\n source_map: Array<{ model: string; locale: string; value: string; file: string; line: number }>\n }\n git?: { branch: string; action: string; commit: string }\n context_updated?: boolean\n next_steps: string[]\n}\n\nexport interface PatchEntry {\n file: string\n line: number\n old_value: string\n new_expression: string\n import_statement?: string\n}\n\nexport interface ReuseInput {\n scope: { model?: string; domain?: string }\n patches: PatchEntry[]\n dry_run?: boolean\n}\n\nexport interface SyntaxError {\n file: string\n error: string\n}\n\nexport interface ReuseResult {\n dry_run: boolean\n error?: string\n scope_warnings?: string[]\n preview?: {\n files_to_modify: string[]\n patches_count: number\n imports_to_add: number\n }\n results?: {\n files_modified: string[]\n patches_applied: number\n patches_skipped: Array<{ file: string; line: number; reason: string }>\n imports_added: number\n framework_warnings?: Array<{ file: string; warning: string }>\n syntax_errors?: SyntaxError[]\n }\n git?: { branch: string; action: string; commit: string }\n next_steps: string[]\n}\n\n// ─── Constants ───\n\nconst MAX_PATCHES = 100\n\n/** File extensions allowed for patching — scannable source files only */\nexport const PATCHABLE_EXTENSIONS = new Set([\n '.vue', '.tsx', '.jsx', '.ts', '.js', '.mjs', '.astro', '.svelte',\n])\n\n/** Directories that must never be patched */\nconst FORBIDDEN_PATH_SEGMENTS = new Set([\n '.contentrain', 'node_modules', '.git', 'dist', 'build', '.next', '.nuxt',\n])\n\n// ─── Framework Detection (Guardrail #2) ───\n\nexport type FileFramework = 'vue' | 'svelte' | 'jsx' | 'astro' | 'script'\n\nexport function detectFileFramework(filePath: string): FileFramework {\n const ext = extname(filePath).toLowerCase()\n switch (ext) {\n case '.vue': return 'vue'\n case '.svelte': return 'svelte'\n case '.tsx':\n case '.jsx': return 'jsx'\n case '.astro': return 'astro'\n case '.ts':\n case '.js':\n case '.mjs': return 'script'\n default: return 'script'\n }\n}\n\n/**\n * Validate that a replacement expression uses the correct template syntax\n * for the target file's framework. Returns a warning string or null.\n */\nexport function validateFrameworkExpression(\n filePath: string,\n newExpression: string,\n context: 'tag_text' | 'other',\n): string | null {\n if (context !== 'tag_text') return null\n\n const framework = detectFileFramework(filePath)\n\n switch (framework) {\n case 'vue':\n if (!newExpression.includes('{{')) {\n return `Vue file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{{\" — expected Vue template syntax like {{ $t('key') }}`\n }\n break\n case 'jsx':\n if (!newExpression.includes('{')) {\n return `JSX file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected JSX syntax like {t('key')}`\n }\n break\n case 'svelte':\n if (!newExpression.includes('{')) {\n return `Svelte file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected Svelte syntax like {$t('key')}`\n }\n break\n case 'astro':\n if (!newExpression.includes('{')) {\n return `Astro file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected Astro syntax like {t('key')}`\n }\n break\n case 'script':\n // Script files don't have template interpolation — warn if attempting tag text replacement\n return `Script file \"${filePath}\": tag text replacement not applicable for .ts/.js files`\n }\n\n return null\n}\n\n// ─── Scope Validation (Guardrail #1) ───\n\n/**\n * Validate that a patch file path is safe and within allowed scope.\n * Returns an error message or null if valid.\n */\nexport function validatePatchPath(filePath: string): string | null {\n const normalizedPath = filePath.replace(/\\\\/g, '/')\n\n // Reject path traversal\n if (normalizedPath.includes('..')) {\n return `Path traversal detected: \"${filePath}\"`\n }\n\n // Reject absolute paths\n if (normalizedPath.startsWith('/')) {\n return `Absolute path not allowed: \"${filePath}\"`\n }\n\n // Reject forbidden directories\n const segments = normalizedPath.split('/')\n for (const seg of segments) {\n if (FORBIDDEN_PATH_SEGMENTS.has(seg)) {\n return `Patching files inside \"${seg}/\" is not allowed: \"${filePath}\"`\n }\n }\n\n // Reject non-scannable extensions\n const ext = extname(filePath).toLowerCase()\n if (!PATCHABLE_EXTENSIONS.has(ext)) {\n return `File extension \"${ext}\" is not patchable. Allowed: ${[...PATCHABLE_EXTENSIONS].join(', ')}. Path: \"${filePath}\"`\n }\n\n return null\n}\n\n// ─── Syntax Check (Guardrail #5) ───\n\n/**\n * Perform a basic syntax check on a patched file.\n * Returns an error message or null if syntax appears valid.\n */\nexport function checkSyntax(filePath: string, content: string): string | null {\n const ext = extname(filePath).toLowerCase()\n\n switch (ext) {\n case '.ts':\n case '.tsx':\n case '.js':\n case '.jsx':\n case '.mjs':\n return checkJsSyntax(content)\n case '.vue':\n return checkVueSyntax(content)\n case '.svelte':\n case '.astro':\n return checkTagBalance(content)\n default:\n return null\n }\n}\n\n/**\n * Basic JS/TS syntax check: bracket/paren/brace balance + string literal closure.\n * This is intentionally conservative — it catches obvious breakage without\n * requiring a full parser.\n */\nfunction checkJsSyntax(content: string): string | null {\n const stack: string[] = []\n const pairs: Record<string, string> = { ')': '(', ']': '[', '}': '{' }\n let inString: string | null = null\n let escaped = false\n let inLineComment = false\n let inBlockComment = false\n\n for (let i = 0; i < content.length; i++) {\n const ch = content[i]!\n const next = content[i + 1]\n\n // Handle escape sequences inside strings\n if (escaped) {\n escaped = false\n continue\n }\n\n if (ch === '\\\\' && inString !== null) {\n escaped = true\n continue\n }\n\n // Line comment\n if (!inString && !inBlockComment && ch === '/' && next === '/') {\n inLineComment = true\n continue\n }\n if (inLineComment) {\n if (ch === '\\n') inLineComment = false\n continue\n }\n\n // Block comment\n if (!inString && !inBlockComment && ch === '/' && next === '*') {\n inBlockComment = true\n i++ // skip *\n continue\n }\n if (inBlockComment) {\n if (ch === '*' && next === '/') {\n inBlockComment = false\n i++ // skip /\n }\n continue\n }\n\n // String handling\n if (inString !== null) {\n if (ch === inString) {\n // Template literal allows multi-line, others don't\n inString = null\n } else if (inString !== '`' && ch === '\\n') {\n return `Unterminated string literal near offset ${i}`\n }\n continue\n }\n\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inString = ch\n continue\n }\n\n // Bracket matching\n if (ch === '(' || ch === '[' || ch === '{') {\n stack.push(ch)\n } else if (ch === ')' || ch === ']' || ch === '}') {\n const expected = pairs[ch]!\n if (stack.length === 0) {\n return `Unmatched closing \"${ch}\" near offset ${i}`\n }\n const top = stack.pop()!\n if (top !== expected) {\n return `Mismatched bracket: expected closing for \"${top}\" but found \"${ch}\" near offset ${i}`\n }\n }\n }\n\n if (inString !== null) {\n return `Unterminated string literal (opened with ${inString})`\n }\n\n if (stack.length > 0) {\n return `Unclosed bracket \"${stack[stack.length - 1]}\" — ${stack.length} unclosed bracket(s)`\n }\n\n return null\n}\n\n/**\n * Vue SFC syntax check: ensure <template>, <script>, <style> tags are balanced.\n */\nfunction checkVueSyntax(content: string): string | null {\n const tagBalance = checkTagBalance(content)\n if (tagBalance) return tagBalance\n\n // Vue-specific: check that SFC root tags are present and balanced\n for (const tag of ['template', 'script']) {\n const openRe = new RegExp(`<${tag}[\\\\s>]`, 'g')\n const closeRe = new RegExp(`</${tag}>`, 'g')\n const opens = content.match(openRe)?.length ?? 0\n const closes = content.match(closeRe)?.length ?? 0\n if (opens !== closes) {\n return `Unbalanced <${tag}> tag: ${opens} opening vs ${closes} closing`\n }\n }\n\n return null\n}\n\n/**\n * Basic tag balance check for HTML-like files.\n * Checks that self-closing tags are handled and major structural tags are balanced.\n */\nfunction checkTagBalance(content: string): string | null {\n // Check for common structural tags balance\n const structuralTags = ['div', 'section', 'main', 'header', 'footer', 'nav', 'article', 'aside', 'ul', 'ol', 'table']\n\n for (const tag of structuralTags) {\n const openRe = new RegExp(`<${tag}[\\\\s>]`, 'g')\n const closeRe = new RegExp(`</${tag}>`, 'g')\n const opens = content.match(openRe)?.length ?? 0\n const closes = content.match(closeRe)?.length ?? 0\n if (opens !== closes) {\n return `Unbalanced <${tag}> tag: ${opens} opening vs ${closes} closing`\n }\n }\n\n return null\n}\n\n// ─── Extract Mode ───\n\nexport async function applyExtract(\n projectRoot: string,\n input: ExtractionInput,\n): Promise<ExtractionResult> {\n const config = await readConfig(projectRoot)\n if (!config) throw new Error('Project not initialized. Run contentrain_init first.')\n\n const { extractions, dry_run } = input\n\n // Analyze what will happen\n const existingModels = await listModels(projectRoot)\n const existingIds = new Set(existingModels.map(m => m.id))\n\n const modelsToCreate: string[] = []\n const modelsToUpdate: string[] = []\n const contentFiles: string[] = []\n let totalEntries = 0\n\n const validationErrors: string[] = []\n\n for (const ext of extractions) {\n // Validate model definition with same rules as model_save\n const modelErrors = validateModelDefinition({\n id: ext.model,\n kind: ext.kind,\n fields: ext.fields as Record<string, unknown> | undefined,\n })\n if (modelErrors.length > 0) {\n validationErrors.push(...modelErrors.map(e => `[${ext.model}] ${e}`))\n }\n\n for (const entry of ext.entries) {\n if (ext.kind === 'dictionary') {\n if (entry.data['id'] !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Dictionary entries should not have id or slug`)\n }\n for (const [key, val] of Object.entries(entry.data)) {\n if (typeof val !== 'string') {\n validationErrors.push(`[${ext.model}] Dictionary entry value for key \"${key}\" must be a string, got ${typeof val}`)\n }\n }\n } else if (ext.kind === 'document') {\n if (!entry.slug && !entry.data['slug']) {\n validationErrors.push(`[${ext.model}] Document entries must have a slug`)\n }\n } else if (ext.kind === 'collection') {\n if (entry.slug !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Collection entries should not have slug`)\n }\n } else if (ext.kind === 'singleton') {\n if (entry.data['id'] !== undefined || entry.slug !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Singleton entries should not have id or slug`)\n }\n }\n }\n\n if (existingIds.has(ext.model)) {\n modelsToUpdate.push(ext.model)\n } else {\n modelsToCreate.push(ext.model)\n }\n totalEntries += ext.entries.length\n\n // Guardrail #3: Preview-Execute Parity — use real model metadata if it exists\n let previewModel: ModelDefinition\n if (existingIds.has(ext.model)) {\n const real = await readModel(projectRoot, ext.model)\n if (real) {\n previewModel = real\n } else {\n previewModel = { id: ext.model, kind: ext.kind, domain: ext.domain, i18n: ext.i18n ?? true } as ModelDefinition\n }\n } else {\n previewModel = { id: ext.model, kind: ext.kind, domain: ext.domain, i18n: ext.i18n ?? true } as ModelDefinition\n }\n\n const cDir = resolveContentDir(projectRoot, previewModel)\n for (const entry of ext.entries) {\n const locale = entry.locale ?? config.locales.default\n if (ext.kind === 'document' && entry.slug) {\n contentFiles.push(resolveMdFilePath(cDir, previewModel, locale, entry.slug))\n } else {\n contentFiles.push(resolveJsonFilePath(cDir, previewModel, locale))\n }\n }\n }\n\n const preview: ExtractionPreview = {\n models_to_create: modelsToCreate,\n models_to_update: modelsToUpdate,\n total_entries: totalEntries,\n content_files: [...new Set(contentFiles)],\n }\n\n // Dry run — return preview only (include validation errors if any)\n if (dry_run !== false) {\n return {\n dry_run: true,\n preview,\n ...(validationErrors.length > 0 ? { validation_errors: validationErrors } : {}),\n next_steps: [\n ...(validationErrors.length > 0\n ? [`WARNING: ${validationErrors.length} validation error(s) found — fix before executing`]\n : []),\n 'Review the preview above',\n 'Call contentrain_apply with mode:extract and dry_run:false to execute',\n ],\n }\n }\n\n // Block execute if validation errors exist\n if (validationErrors.length > 0) {\n return {\n dry_run: false,\n error: 'Model validation failed — cannot execute extract with invalid model definitions',\n validation_errors: validationErrors,\n next_steps: ['Fix the validation errors and retry'],\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n error: health.message,\n action: 'blocked' as const,\n hint: 'Merge or delete old contentrain/* branches before executing normalize.',\n } as unknown as ExtractionResult\n }\n\n // Execute — git transaction (always review mode for normalize)\n const branchName = buildBranchName('normalize', 'extract')\n const tx = await createTransaction(projectRoot, branchName, { workflowOverride: 'review' })\n const sourceMap: Array<{ model: string; locale: string; value: string; file: string; line: number }> = []\n const modelsCreated: string[] = []\n const modelsUpdated: string[] = []\n let entriesWritten = 0\n\n try {\n await tx.write(async (wt) => {\n for (const ext of extractions) {\n // Create or merge model\n const existing = await readModel(wt, ext.model)\n if (existing) {\n // Merge fields: add new, keep existing\n if (ext.fields) {\n const merged = { ...existing.fields, ...ext.fields }\n // Only overwrite if new fields were actually added\n const newFieldNames = Object.keys(ext.fields).filter(k => !(k in (existing.fields ?? {})))\n if (newFieldNames.length > 0) {\n existing.fields = merged\n await writeModel(wt, existing)\n modelsUpdated.push(ext.model)\n }\n }\n } else {\n // Create new model\n const newModel: ModelDefinition = {\n id: ext.model,\n name: ext.model.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),\n kind: ext.kind,\n domain: ext.domain,\n i18n: ext.i18n ?? true,\n fields: ext.fields,\n }\n await writeModel(wt, newModel)\n modelsCreated.push(ext.model)\n }\n\n // Write content entries\n const model = (await readModel(wt, ext.model))!\n const entries: ContentEntry[] = ext.entries.map(e => ({\n locale: e.locale,\n slug: e.slug,\n data: e.data,\n }))\n\n const wtConfig = await readConfig(wt) ?? config\n await writeContent(wt, model, entries, wtConfig)\n entriesWritten += entries.length\n\n // Track source map\n for (const entry of ext.entries) {\n // For dictionary entries with per-key source tracking\n if (ext.kind === 'dictionary' && entry.sources) {\n for (const s of entry.sources) {\n sourceMap.push({\n model: ext.model,\n locale: entry.locale ?? config.locales.default,\n value: s.value,\n file: s.file,\n line: s.line,\n })\n }\n } else if (entry.source) {\n sourceMap.push({\n model: ext.model,\n locale: entry.locale ?? config.locales.default,\n value: entry.source.value,\n file: entry.source.file,\n line: entry.source.line,\n })\n }\n }\n }\n\n // Write source map for reuse scope enforcement\n if (sourceMap.length > 0) {\n const sourcesByModel: Record<string, { source_files: string[]; entry_count: number }> = {}\n for (const s of sourceMap) {\n if (!sourcesByModel[s.model]) {\n sourcesByModel[s.model] = { source_files: [], entry_count: 0 }\n }\n const modelEntry = sourcesByModel[s.model]!\n if (!modelEntry.source_files.includes(s.file)) {\n modelEntry.source_files.push(s.file)\n }\n modelEntry.entry_count++\n }\n const sourcesJson = JSON.stringify({\n version: 1,\n created_at: new Date().toISOString(),\n models: sourcesByModel,\n }, null, 2) + '\\n'\n // Write to worktree only — merge brings it to main\n // Phase 2 (reuse) must wait for extract branch to be merged first\n await writeText(join(wt, '.contentrain', 'normalize-sources.json'), sourcesJson)\n }\n\n // Update context\n await writeContext(wt, {\n tool: 'contentrain_apply',\n model: extractions.map(e => e.model).join(','),\n locale: config.locales.default,\n entries: extractions.flatMap(e => e.entries.map(en => en.slug ?? 'entry')),\n })\n })\n\n const commitMsg = `[contentrain] normalize: extract ${entriesWritten} entries to ${extractions.length} models`\n await tx.commit(commitMsg)\n\n const gitResult = { branch: branchName, action: 'pending-review', commit: '' }\n try {\n const completed = await tx.complete()\n gitResult.action = completed.action\n gitResult.commit = completed.commit\n } catch {\n gitResult.action = 'pending-review'\n } finally {\n await tx.cleanup()\n }\n\n return {\n dry_run: false,\n results: {\n models_created: modelsCreated,\n models_updated: modelsUpdated,\n entries_written: entriesWritten,\n source_map: sourceMap,\n },\n git: gitResult,\n context_updated: true,\n next_steps: [\n 'Run contentrain_validate to check the extracted content',\n 'Run contentrain_submit to push the branch for review',\n 'After review, proceed with mode:reuse to patch source files',\n ],\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n }\n}\n\n// ─── Reuse Mode ───\n\nexport async function applyReuse(\n projectRoot: string,\n input: ReuseInput,\n): Promise<ReuseResult> {\n const config = await readConfig(projectRoot)\n if (!config) throw new Error('Project not initialized. Run contentrain_init first.')\n\n const { scope, patches, dry_run } = input\n\n // Validate scope\n if (!scope.model && !scope.domain) {\n throw new Error('Scope required: provide model or domain. Whole-project patching is not allowed.')\n }\n\n // Validate patch count\n if (patches.length > MAX_PATCHES) {\n throw new Error(`Too many patches (${patches.length}). Maximum ${MAX_PATCHES} per operation. Split into multiple calls.`)\n }\n\n // Check content exists for scope (soft warning)\n if (scope.model) {\n const model = await readModel(projectRoot, scope.model)\n if (!model) {\n throw new Error(`Model \"${scope.model}\" not found. Run extract phase first.`)\n }\n }\n\n const scopeWarnings: string[] = []\n\n // Guardrail #1: Scope Real Enforcement\n // Step 1: Path safety — every patch must target a valid, patchable source file\n for (const patch of patches) {\n const pathError = validatePatchPath(patch.file)\n if (pathError) {\n throw new Error(`Invalid patch path: ${pathError}`)\n }\n }\n\n // Step 2: Semantic scope — verify scope model/domain exists and cross-check patch files\n if (scope.model || scope.domain) {\n const models = await listModels(projectRoot)\n const scopeModels = scope.model\n ? models.filter(m => m.id === scope.model)\n : scope.domain\n ? models.filter(m => m.domain === scope.domain)\n : models\n\n if (scopeModels.length === 0) {\n throw new Error(`No models found for scope ${scope.model ? `model=\"${scope.model}\"` : `domain=\"${scope.domain}\"`}`)\n }\n\n // Step 3: Verify patch files are source files (not content/config/meta files)\n // and belong to detectable source directories (not random locations)\n const { autoDetectSourceDirs } = await import('./scan-config.js')\n const sourceDirs = await autoDetectSourceDirs(projectRoot)\n\n // Build allowed file prefixes from source dirs\n const allowedPrefixes = sourceDirs.map(d => d === '.' ? '' : d + '/')\n\n for (const patch of patches) {\n const normalizedPath = patch.file.replace(/\\\\/g, '/')\n\n // Reject patches targeting .contentrain/ directory (content files should never be patched by reuse)\n if (normalizedPath.startsWith('.contentrain/') || normalizedPath.includes('/.contentrain/')) {\n throw new Error(`Cannot patch content/config files directly: \"${patch.file}\". Reuse patches source files only.`)\n }\n\n // If source dirs were detected (not just \".\"), verify patch files are within them\n if (sourceDirs.length > 0 && sourceDirs[0] !== '.') {\n const inSourceDir = allowedPrefixes.some(prefix =>\n prefix === '' || normalizedPath.startsWith(prefix),\n )\n if (!inSourceDir) {\n throw new Error(\n `Patch file \"${patch.file}\" is outside detected source directories (${sourceDirs.join(', ')}). ` +\n `Reuse patches must target source files within the project's source tree.`,\n )\n }\n }\n }\n\n // Step 4: Semantic source→model cross-check via normalize-sources.json\n // Source map is written by extract into the review branch worktree.\n // It only exists on base after extract branch is merged.\n // If missing: dry_run proceeds with warning, execute is blocked.\n if (scope.model || scope.domain) {\n const sourcesPath = join(projectRoot, '.contentrain', 'normalize-sources.json')\n const sourcesRaw = await readText(sourcesPath)\n\n if (!sourcesRaw) {\n // Source map not found — check if scoped model is a dictionary\n // Dictionaries store all keys in one entry, so per-file source tracking\n // is not available. Allow reuse with a warning instead of blocking.\n const scopedModel = scopeModels.find(m => m.id === scope.model)\n const isDictionary = scopedModel?.kind === 'dictionary'\n\n if (isDictionary) {\n scopeWarnings.push(\n 'normalize-sources.json not found. Dictionary models do not generate per-file source maps. ' +\n 'Scope enforcement is based on source-tree locality only.',\n )\n } else if (dry_run !== false) {\n scopeWarnings.push(\n 'normalize-sources.json not found. Semantic scope enforcement is unavailable. ' +\n 'Merge the extract branch first, then reuse will have full scope protection.',\n )\n } else {\n throw new Error(\n 'Cannot execute reuse: normalize-sources.json not found on base branch. ' +\n 'The extract branch must be merged before reuse can execute. ' +\n 'This ensures semantic scope enforcement protects against out-of-scope patching.',\n )\n }\n } else {\n const sourcesData = JSON.parse(sourcesRaw) as {\n models?: Record<string, { source_files?: string[] }>\n }\n\n // Collect all allowed source files for the scope\n let allowedSourceFiles: string[] = []\n let scopeLabel = ''\n\n if (scope.model) {\n const modelSources = sourcesData.models?.[scope.model]?.source_files\n if (modelSources) allowedSourceFiles = modelSources\n scopeLabel = `model \"${scope.model}\"`\n } else if (scope.domain) {\n const domainModelIds = scopeModels.map(m => m.id)\n for (const modelId of domainModelIds) {\n const modelSources = sourcesData.models?.[modelId]?.source_files\n if (modelSources) allowedSourceFiles.push(...modelSources)\n }\n allowedSourceFiles = [...new Set(allowedSourceFiles)]\n scopeLabel = `domain \"${scope.domain}\"`\n }\n\n if (allowedSourceFiles.length > 0) {\n const outOfScopePatches: string[] = []\n for (const patch of patches) {\n const normalizedPath = patch.file.replace(/\\\\/g, '/')\n if (!allowedSourceFiles.includes(normalizedPath)) {\n outOfScopePatches.push(patch.file)\n }\n }\n if (outOfScopePatches.length > 0) {\n throw new Error(\n `Scope enforcement: ${outOfScopePatches.length} patch file(s) are not associated with ${scopeLabel}. ` +\n `Out-of-scope files: ${outOfScopePatches.join(', ')}. ` +\n `Known source files: ${allowedSourceFiles.join(', ')}.`,\n )\n }\n }\n }\n }\n }\n\n // Group patches by file\n const patchesByFile = new Map<string, PatchEntry[]>()\n for (const patch of patches) {\n if (!patchesByFile.has(patch.file)) {\n patchesByFile.set(patch.file, [])\n }\n patchesByFile.get(patch.file)!.push(patch)\n }\n\n const filesToModify = [...patchesByFile.keys()]\n const importsToAdd = patches.filter(p => p.import_statement).length\n\n // Dry run — return preview only\n if (dry_run !== false) {\n return {\n dry_run: true,\n preview: {\n files_to_modify: filesToModify,\n patches_count: patches.length,\n imports_to_add: importsToAdd,\n },\n ...(scopeWarnings.length > 0 ? { scope_warnings: scopeWarnings } : {}),\n next_steps: [\n ...(scopeWarnings.length > 0 ? [`WARNING: ${scopeWarnings.length} patch file(s) not in extract source map — verify intent`] : []),\n 'Review the files and patches above',\n 'Call contentrain_apply with mode:reuse and dry_run:false to execute',\n ],\n }\n }\n\n // Branch health gate\n const reuseHealth = await checkBranchHealth(projectRoot)\n if (reuseHealth.blocked) {\n return {\n dry_run: false,\n error: `Branch blocked: ${reuseHealth.message}`,\n next_steps: ['Merge or delete old contentrain/* branches before executing reuse.'],\n }\n }\n\n // Execute — git transaction\n const scopeTarget = scope.model ?? scope.domain!\n const branchName = buildBranchName('normalize/reuse', scopeTarget)\n const tx = await createTransaction(projectRoot, branchName, { workflowOverride: 'review' })\n\n const filesModified: string[] = []\n let patchesApplied = 0\n let importsAdded = 0\n const patchesSkipped: Array<{ file: string; line: number; reason: string }> = []\n const frameworkWarnings: Array<{ file: string; warning: string }> = []\n const syntaxErrors: SyntaxError[] = []\n\n try {\n await tx.write(async (wt) => {\n for (const [relFile, filePatches] of patchesByFile) {\n const absPath = join(wt, relFile)\n\n if (!(await pathExists(absPath))) {\n for (const p of filePatches) {\n patchesSkipped.push({ file: relFile, line: p.line, reason: 'file not found' })\n }\n continue\n }\n\n const content = await readText(absPath)\n if (content === null) {\n for (const p of filePatches) {\n patchesSkipped.push({ file: relFile, line: p.line, reason: 'file unreadable' })\n }\n continue\n }\n\n // Sort patches by line DESC (bottom-up to avoid line shifts)\n const sorted = [...filePatches].toSorted((a, b) => b.line - a.line)\n\n const lines = content.split('\\n')\n let fileModified = false\n\n for (const patch of sorted) {\n // Determine replacement context before applying\n const targetLine = lines[patch.line - 1] ?? ''\n const isTagTextContext = targetLine.includes(`>${patch.old_value}<`)\n const replacementContext = isTagTextContext ? 'tag_text' as const : 'other' as const\n\n // Guardrail #2: Framework-aware validation — only warn for tag text replacements\n if (isTagTextContext) {\n const fwWarning = validateFrameworkExpression(relFile, patch.new_expression, replacementContext)\n if (fwWarning) {\n frameworkWarnings.push({ file: relFile, warning: fwWarning })\n }\n }\n\n const applied = applyPatchToLines(lines, patch)\n if (applied) {\n patchesApplied++\n fileModified = true\n } else {\n patchesSkipped.push({ file: relFile, line: patch.line, reason: 'old_value not found at or near specified line' })\n }\n }\n\n // Add imports (deduplicate)\n const importStatements = new Set(\n filePatches\n .filter(p => p.import_statement)\n .map(p => p.import_statement!),\n )\n\n if (importStatements.size > 0) {\n const added = addImportsToLines(lines, importStatements)\n importsAdded += added\n if (added > 0) fileModified = true\n }\n\n if (fileModified) {\n const newContent = lines.join('\\n')\n await writeText(absPath, newContent)\n filesModified.push(relFile)\n\n // Guardrail #5: Syntax check after patching\n const syntaxError = checkSyntax(relFile, newContent)\n if (syntaxError) {\n syntaxErrors.push({ file: relFile, error: syntaxError })\n }\n }\n }\n\n // Update context\n await writeContext(wt, {\n tool: 'contentrain_apply',\n model: scopeTarget,\n locale: config.locales.default,\n })\n })\n\n if (filesModified.length === 0) {\n await tx.cleanup()\n return {\n dry_run: false,\n results: {\n files_modified: [],\n patches_applied: 0,\n patches_skipped: patchesSkipped,\n imports_added: 0,\n framework_warnings: frameworkWarnings.length > 0 ? frameworkWarnings : undefined,\n },\n next_steps: ['No files were modified. Check patch definitions and try again.'],\n }\n }\n\n const commitMsg = `[contentrain] normalize: reuse ${scopeTarget} — patch ${filesModified.length} files (${patchesApplied} replacements)`\n await tx.commit(commitMsg)\n\n const gitResult = { branch: branchName, action: 'pending-review', commit: '' }\n try {\n const completed = await tx.complete()\n gitResult.action = completed.action\n gitResult.commit = completed.commit\n } catch {\n gitResult.action = 'pending-review'\n } finally {\n await tx.cleanup()\n }\n\n return {\n dry_run: false,\n results: {\n files_modified: filesModified,\n patches_applied: patchesApplied,\n patches_skipped: patchesSkipped,\n imports_added: importsAdded,\n framework_warnings: frameworkWarnings.length > 0 ? frameworkWarnings : undefined,\n syntax_errors: syntaxErrors.length > 0 ? syntaxErrors : undefined,\n },\n ...(scopeWarnings.length > 0 ? { scope_warnings: scopeWarnings } : {}),\n git: gitResult,\n next_steps: [\n 'Run contentrain_validate to verify the patched files',\n patchesSkipped.length > 0 ? `${patchesSkipped.length} patches were skipped — review and retry if needed` : '',\n syntaxErrors.length > 0 ? `WARNING: ${syntaxErrors.length} file(s) may have syntax errors after patching — review manually` : '',\n scopeWarnings.length > 0 ? `NOTE: ${scopeWarnings.length} patch file(s) not in extract source map` : '',\n 'Run contentrain_submit to push the branch for review',\n ].filter(Boolean),\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n }\n}\n\n// ─── Patch Helpers ───\n\n/**\n * Apply a single patch to a lines array. Mutates lines in place.\n * Uses line hint for proximity matching — searches ±10 lines from hint.\n */\nfunction applyPatchToLines(lines: string[], patch: PatchEntry): boolean {\n const { line, old_value, new_expression } = patch\n const lineIdx = line - 1 // 0-based\n\n // Search range: ±10 lines from hint\n const searchStart = Math.max(0, lineIdx - 10)\n const searchEnd = Math.min(lines.length, lineIdx + 11)\n\n // First pass: exact line match\n if (lineIdx >= 0 && lineIdx < lines.length) {\n const replaced = replaceInLine(lines[lineIdx]!, old_value, new_expression)\n if (replaced !== null) {\n lines[lineIdx] = replaced\n return true\n }\n }\n\n // Second pass: proximity search\n for (let i = searchStart; i < searchEnd; i++) {\n if (i === lineIdx) continue // already tried\n const replaced = replaceInLine(lines[i]!, old_value, new_expression)\n if (replaced !== null) {\n lines[i] = replaced\n return true\n }\n }\n\n return false\n}\n\n/**\n * Replace old_value with new_expression in a single line.\n * Matches the string literal (quoted or unquoted tag text).\n * Returns the modified line, or null if not found.\n *\n * Guardrail #4: Safer patch matching — word boundary awareness and\n * ambiguity rejection for plain text fallback.\n */\nexport function replaceInLine(line: string, oldValue: string, newExpression: string): string | null {\n // Try exact match of the value in quoted strings\n // Match: \"old_value\", 'old_value', `old_value`\n for (const quote of ['\"', \"'\", '`']) {\n const quoted = `${quote}${oldValue}${quote}`\n if (line.includes(quoted)) {\n return line.replace(quoted, newExpression)\n }\n }\n\n // Try unquoted tag text match: >old_value<\n // The agent provides the complete new_expression with correct framework syntax:\n // JSX: {t('key')}\n // Vue: {{ $t('key') }}\n // Svelte: {$t('key')}\n // So we insert the expression as-is between > and <, without wrapping in braces.\n if (line.includes(`>${oldValue}<`)) {\n return line.replace(`>${oldValue}<`, `>${newExpression}<`)\n }\n\n // Guardrail #4: Safer plain text fallback\n // Only match if old_value appears at a word boundary and is unambiguous\n if (line.includes(oldValue)) {\n // Count occurrences — if multiple, it's ambiguous\n const occurrences = countOccurrences(line, oldValue)\n if (occurrences > 1) {\n return null // ambiguous — let proximity search handle it\n }\n\n // Word boundary check: don't replace \"Submit\" inside \"SubmitButton\"\n // Reject if the old_value is a substring of a larger word — check if\n // adjacent characters are word characters that extend the token.\n const idx = line.indexOf(oldValue)\n const charBefore = idx > 0 ? line[idx - 1]! : ''\n const charAfter = idx + oldValue.length < line.length ? line[idx + oldValue.length]! : ''\n\n const oldStartsWithWord = oldValue.length > 0 && isWordChar(oldValue[0]!)\n const oldEndsWithWord = oldValue.length > 0 && isWordChar(oldValue[oldValue.length - 1]!)\n\n // If old_value starts with a word char and char before is also word char, it's a partial match\n if (oldStartsWithWord && isWordChar(charBefore)) {\n return null\n }\n // If old_value ends with a word char and char after is also word char, it's a partial match\n if (oldEndsWithWord && isWordChar(charAfter)) {\n return null\n }\n\n return line.replace(oldValue, newExpression)\n }\n\n return null\n}\n\n/** Count non-overlapping occurrences of a substring */\nfunction countOccurrences(str: string, sub: string): number {\n let count = 0\n let pos = 0\n while (pos <= str.length - sub.length) {\n const idx = str.indexOf(sub, pos)\n if (idx === -1) break\n count++\n pos = idx + sub.length\n }\n return count\n}\n\n/** Check if a character is a word character (letter, digit, underscore) */\nfunction isWordChar(ch: string): boolean {\n return /\\w/.test(ch)\n}\n\n/**\n * Add import statements to the top of a file (after existing imports).\n * Deduplicates — won't add if the import already exists.\n * Returns number of imports actually added.\n */\nfunction addImportsToLines(lines: string[], imports: Set<string>): number {\n let added = 0\n const existingContent = lines.join('\\n')\n\n // Find the last import line position (handles multi-line imports)\n let lastImportIdx = -1\n let inMultiLineImport = false\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim()\n if (inMultiLineImport) {\n lastImportIdx = i\n if (trimmed.includes('}')) inMultiLineImport = false\n continue\n }\n if (trimmed.startsWith('import ') || trimmed.startsWith('import{')) {\n lastImportIdx = i\n // Check if this is a multi-line import (has { but no closing })\n if (trimmed.includes('{') && !trimmed.includes('}')) {\n inMultiLineImport = true\n }\n continue\n }\n // Stop searching after a non-import, non-empty, non-comment line following imports\n if (lastImportIdx >= 0 && trimmed.length > 0 && !trimmed.startsWith('//') && !trimmed.startsWith('/*') && !trimmed.startsWith('*')) {\n break\n }\n }\n\n let insertAt: number\n if (lastImportIdx >= 0) {\n insertAt = lastImportIdx + 1\n } else {\n // No existing imports found — insert after shebang and directive lines\n insertAt = 0\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim()\n if (i === 0 && trimmed.startsWith('#!')) {\n insertAt = i + 1\n continue\n }\n if (trimmed === \"'use client'\" || trimmed === '\"use client\"'\n || trimmed === \"'use server'\" || trimmed === '\"use server\"'\n || trimmed === \"'use client';\" || trimmed === '\"use client\";'\n || trimmed === \"'use server';\" || trimmed === '\"use server\";') {\n insertAt = i + 1\n continue\n }\n if (insertAt > 0 && trimmed.length > 0) break\n if (insertAt === 0 && trimmed.length > 0) break\n }\n }\n const toInsert: string[] = []\n\n for (const imp of imports) {\n // Check if this import already exists (by checking the from clause)\n if (!existingContent.includes(imp)) {\n toInsert.push(imp)\n added++\n }\n }\n\n if (toInsert.length > 0) {\n lines.splice(insertAt, 0, ...toInsert)\n }\n\n return added\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"apply-manager-BAeqrAHX.mjs","names":[],"sources":["../src/core/apply-manager.ts"],"sourcesContent":["import type { ModelDefinition, FieldDef } from '@contentrain/types'\nimport { join, extname } from 'node:path'\nimport { readText, writeText, pathExists } from '../util/fs.js'\nimport { readModel, writeModel, listModels, validateModelDefinition } from './model-manager.js'\nimport { writeContent, resolveContentDir, resolveJsonFilePath, resolveMdFilePath, type ContentEntry } from './content-manager.js'\nimport { readConfig } from './config.js'\nimport { writeContext } from './context.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\n\n// ─── Types ───\n\nexport interface ExtractionEntry {\n model: string\n kind: 'singleton' | 'collection' | 'dictionary' | 'document'\n domain: string\n i18n?: boolean\n fields?: Record<string, FieldDef>\n entries: Array<{\n locale?: string\n slug?: string\n data: Record<string, unknown>\n source?: { file: string; line: number; value: string }\n sources?: Array<{ file: string; line: number; key: string; value: string }>\n }>\n}\n\nexport interface ExtractionInput {\n extractions: ExtractionEntry[]\n dry_run?: boolean\n}\n\nexport interface ExtractionPreview {\n models_to_create: string[]\n models_to_update: string[]\n total_entries: number\n content_files: string[]\n}\n\nexport interface ExtractionResult {\n dry_run: boolean\n preview?: ExtractionPreview\n error?: string\n validation_errors?: string[]\n results?: {\n models_created: string[]\n models_updated: string[]\n entries_written: number\n source_map: Array<{ model: string; locale: string; value: string; file: string; line: number }>\n }\n git?: { branch: string; action: string; commit: string }\n context_updated?: boolean\n next_steps: string[]\n}\n\nexport interface PatchEntry {\n file: string\n line: number\n old_value: string\n new_expression: string\n import_statement?: string\n}\n\nexport interface ReuseInput {\n scope: { model?: string; domain?: string }\n patches: PatchEntry[]\n dry_run?: boolean\n}\n\nexport interface SyntaxError {\n file: string\n error: string\n}\n\nexport interface ReuseResult {\n dry_run: boolean\n error?: string\n scope_warnings?: string[]\n preview?: {\n files_to_modify: string[]\n patches_count: number\n imports_to_add: number\n }\n results?: {\n files_modified: string[]\n patches_applied: number\n patches_skipped: Array<{ file: string; line: number; reason: string }>\n imports_added: number\n framework_warnings?: Array<{ file: string; warning: string }>\n syntax_errors?: SyntaxError[]\n }\n git?: { branch: string; action: string; commit: string }\n next_steps: string[]\n}\n\n// ─── Constants ───\n\nconst MAX_PATCHES = 100\n\n/** File extensions allowed for patching — scannable source files only */\nexport const PATCHABLE_EXTENSIONS = new Set([\n '.vue', '.tsx', '.jsx', '.ts', '.js', '.mjs', '.astro', '.svelte',\n])\n\n/** Directories that must never be patched */\nconst FORBIDDEN_PATH_SEGMENTS = new Set([\n '.contentrain', 'node_modules', '.git', 'dist', 'build', '.next', '.nuxt',\n])\n\n// ─── Framework Detection (Guardrail #2) ───\n\nexport type FileFramework = 'vue' | 'svelte' | 'jsx' | 'astro' | 'script'\n\nexport function detectFileFramework(filePath: string): FileFramework {\n const ext = extname(filePath).toLowerCase()\n switch (ext) {\n case '.vue': return 'vue'\n case '.svelte': return 'svelte'\n case '.tsx':\n case '.jsx': return 'jsx'\n case '.astro': return 'astro'\n case '.ts':\n case '.js':\n case '.mjs': return 'script'\n default: return 'script'\n }\n}\n\n/**\n * Validate that a replacement expression uses the correct template syntax\n * for the target file's framework. Returns a warning string or null.\n */\nexport function validateFrameworkExpression(\n filePath: string,\n newExpression: string,\n context: 'tag_text' | 'other',\n): string | null {\n if (context !== 'tag_text') return null\n\n const framework = detectFileFramework(filePath)\n\n switch (framework) {\n case 'vue':\n if (!newExpression.includes('{{')) {\n return `Vue file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{{\" — expected Vue template syntax like {{ $t('key') }}`\n }\n break\n case 'jsx':\n if (!newExpression.includes('{')) {\n return `JSX file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected JSX syntax like {t('key')}`\n }\n break\n case 'svelte':\n if (!newExpression.includes('{')) {\n return `Svelte file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected Svelte syntax like {$t('key')}`\n }\n break\n case 'astro':\n if (!newExpression.includes('{')) {\n return `Astro file \"${filePath}\": tag text expression \"${newExpression}\" does not contain \"{\" — expected Astro syntax like {t('key')}`\n }\n break\n case 'script':\n // Script files don't have template interpolation — warn if attempting tag text replacement\n return `Script file \"${filePath}\": tag text replacement not applicable for .ts/.js files`\n }\n\n return null\n}\n\n// ─── Scope Validation (Guardrail #1) ───\n\n/**\n * Validate that a patch file path is safe and within allowed scope.\n * Returns an error message or null if valid.\n */\nexport function validatePatchPath(filePath: string): string | null {\n const normalizedPath = filePath.replace(/\\\\/g, '/')\n\n // Reject path traversal\n if (normalizedPath.includes('..')) {\n return `Path traversal detected: \"${filePath}\"`\n }\n\n // Reject absolute paths\n if (normalizedPath.startsWith('/')) {\n return `Absolute path not allowed: \"${filePath}\"`\n }\n\n // Reject forbidden directories\n const segments = normalizedPath.split('/')\n for (const seg of segments) {\n if (FORBIDDEN_PATH_SEGMENTS.has(seg)) {\n return `Patching files inside \"${seg}/\" is not allowed: \"${filePath}\"`\n }\n }\n\n // Reject non-scannable extensions\n const ext = extname(filePath).toLowerCase()\n if (!PATCHABLE_EXTENSIONS.has(ext)) {\n return `File extension \"${ext}\" is not patchable. Allowed: ${[...PATCHABLE_EXTENSIONS].join(', ')}. Path: \"${filePath}\"`\n }\n\n return null\n}\n\n// ─── Syntax Check (Guardrail #5) ───\n\n/**\n * Perform a basic syntax check on a patched file.\n * Returns an error message or null if syntax appears valid.\n */\nexport function checkSyntax(filePath: string, content: string): string | null {\n const ext = extname(filePath).toLowerCase()\n\n switch (ext) {\n case '.ts':\n case '.tsx':\n case '.js':\n case '.jsx':\n case '.mjs':\n return checkJsSyntax(content)\n case '.vue':\n return checkVueSyntax(content)\n case '.svelte':\n case '.astro':\n return checkTagBalance(content)\n default:\n return null\n }\n}\n\n/**\n * Basic JS/TS syntax check: bracket/paren/brace balance + string literal closure.\n * This is intentionally conservative — it catches obvious breakage without\n * requiring a full parser.\n */\nfunction checkJsSyntax(content: string): string | null {\n const stack: string[] = []\n const pairs: Record<string, string> = { ')': '(', ']': '[', '}': '{' }\n let inString: string | null = null\n let escaped = false\n let inLineComment = false\n let inBlockComment = false\n\n for (let i = 0; i < content.length; i++) {\n const ch = content[i]!\n const next = content[i + 1]\n\n // Handle escape sequences inside strings\n if (escaped) {\n escaped = false\n continue\n }\n\n if (ch === '\\\\' && inString !== null) {\n escaped = true\n continue\n }\n\n // Line comment\n if (!inString && !inBlockComment && ch === '/' && next === '/') {\n inLineComment = true\n continue\n }\n if (inLineComment) {\n if (ch === '\\n') inLineComment = false\n continue\n }\n\n // Block comment\n if (!inString && !inBlockComment && ch === '/' && next === '*') {\n inBlockComment = true\n i++ // skip *\n continue\n }\n if (inBlockComment) {\n if (ch === '*' && next === '/') {\n inBlockComment = false\n i++ // skip /\n }\n continue\n }\n\n // String handling\n if (inString !== null) {\n if (ch === inString) {\n // Template literal allows multi-line, others don't\n inString = null\n } else if (inString !== '`' && ch === '\\n') {\n return `Unterminated string literal near offset ${i}`\n }\n continue\n }\n\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n inString = ch\n continue\n }\n\n // Bracket matching\n if (ch === '(' || ch === '[' || ch === '{') {\n stack.push(ch)\n } else if (ch === ')' || ch === ']' || ch === '}') {\n const expected = pairs[ch]!\n if (stack.length === 0) {\n return `Unmatched closing \"${ch}\" near offset ${i}`\n }\n const top = stack.pop()!\n if (top !== expected) {\n return `Mismatched bracket: expected closing for \"${top}\" but found \"${ch}\" near offset ${i}`\n }\n }\n }\n\n if (inString !== null) {\n return `Unterminated string literal (opened with ${inString})`\n }\n\n if (stack.length > 0) {\n return `Unclosed bracket \"${stack[stack.length - 1]}\" — ${stack.length} unclosed bracket(s)`\n }\n\n return null\n}\n\n/**\n * Vue SFC syntax check: ensure <template>, <script>, <style> tags are balanced.\n */\nfunction checkVueSyntax(content: string): string | null {\n const tagBalance = checkTagBalance(content)\n if (tagBalance) return tagBalance\n\n // Vue-specific: check that SFC root tags are present and balanced\n for (const tag of ['template', 'script']) {\n const openRe = new RegExp(`<${tag}[\\\\s>]`, 'g')\n const closeRe = new RegExp(`</${tag}>`, 'g')\n const opens = content.match(openRe)?.length ?? 0\n const closes = content.match(closeRe)?.length ?? 0\n if (opens !== closes) {\n return `Unbalanced <${tag}> tag: ${opens} opening vs ${closes} closing`\n }\n }\n\n return null\n}\n\n/**\n * Basic tag balance check for HTML-like files.\n * Checks that self-closing tags are handled and major structural tags are balanced.\n */\nfunction checkTagBalance(content: string): string | null {\n // Check for common structural tags balance\n const structuralTags = ['div', 'section', 'main', 'header', 'footer', 'nav', 'article', 'aside', 'ul', 'ol', 'table']\n\n for (const tag of structuralTags) {\n const openRe = new RegExp(`<${tag}[\\\\s>]`, 'g')\n const closeRe = new RegExp(`</${tag}>`, 'g')\n const opens = content.match(openRe)?.length ?? 0\n const closes = content.match(closeRe)?.length ?? 0\n if (opens !== closes) {\n return `Unbalanced <${tag}> tag: ${opens} opening vs ${closes} closing`\n }\n }\n\n return null\n}\n\n// ─── Extract Mode ───\n\nexport async function applyExtract(\n projectRoot: string,\n input: ExtractionInput,\n): Promise<ExtractionResult> {\n const config = await readConfig(projectRoot)\n if (!config) throw new Error('Project not initialized. Run contentrain_init first.')\n\n const { extractions, dry_run } = input\n\n // Analyze what will happen\n const existingModels = await listModels(projectRoot)\n const existingIds = new Set(existingModels.map(m => m.id))\n\n const modelsToCreate: string[] = []\n const modelsToUpdate: string[] = []\n const contentFiles: string[] = []\n let totalEntries = 0\n\n const validationErrors: string[] = []\n\n for (const ext of extractions) {\n // Validate model definition with same rules as model_save\n const modelErrors = validateModelDefinition({\n id: ext.model,\n kind: ext.kind,\n fields: ext.fields as Record<string, unknown> | undefined,\n })\n if (modelErrors.length > 0) {\n validationErrors.push(...modelErrors.map(e => `[${ext.model}] ${e}`))\n }\n\n for (const entry of ext.entries) {\n if (ext.kind === 'dictionary') {\n if (entry.data['id'] !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Dictionary entries should not have id or slug`)\n }\n for (const [key, val] of Object.entries(entry.data)) {\n if (typeof val !== 'string') {\n validationErrors.push(`[${ext.model}] Dictionary entry value for key \"${key}\" must be a string, got ${typeof val}`)\n }\n }\n } else if (ext.kind === 'document') {\n if (!entry.slug && !entry.data['slug']) {\n validationErrors.push(`[${ext.model}] Document entries must have a slug`)\n }\n } else if (ext.kind === 'collection') {\n if (entry.slug !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Collection entries should not have slug`)\n }\n } else if (ext.kind === 'singleton') {\n if (entry.data['id'] !== undefined || entry.slug !== undefined || entry.data['slug'] !== undefined) {\n validationErrors.push(`[${ext.model}] Singleton entries should not have id or slug`)\n }\n }\n }\n\n if (existingIds.has(ext.model)) {\n modelsToUpdate.push(ext.model)\n } else {\n modelsToCreate.push(ext.model)\n }\n totalEntries += ext.entries.length\n\n // Guardrail #3: Preview-Execute Parity — use real model metadata if it exists\n let previewModel: ModelDefinition\n if (existingIds.has(ext.model)) {\n const real = await readModel(projectRoot, ext.model)\n if (real) {\n previewModel = real\n } else {\n previewModel = { id: ext.model, kind: ext.kind, domain: ext.domain, i18n: ext.i18n ?? true } as ModelDefinition\n }\n } else {\n previewModel = { id: ext.model, kind: ext.kind, domain: ext.domain, i18n: ext.i18n ?? true } as ModelDefinition\n }\n\n const cDir = resolveContentDir(projectRoot, previewModel)\n for (const entry of ext.entries) {\n const locale = entry.locale ?? config.locales.default\n if (ext.kind === 'document' && entry.slug) {\n contentFiles.push(resolveMdFilePath(cDir, previewModel, locale, entry.slug))\n } else {\n contentFiles.push(resolveJsonFilePath(cDir, previewModel, locale))\n }\n }\n }\n\n const preview: ExtractionPreview = {\n models_to_create: modelsToCreate,\n models_to_update: modelsToUpdate,\n total_entries: totalEntries,\n content_files: [...new Set(contentFiles)],\n }\n\n // Dry run — return preview only (include validation errors if any)\n if (dry_run !== false) {\n return {\n dry_run: true,\n preview,\n ...(validationErrors.length > 0 ? { validation_errors: validationErrors } : {}),\n next_steps: [\n ...(validationErrors.length > 0\n ? [`WARNING: ${validationErrors.length} validation error(s) found — fix before executing`]\n : []),\n 'Review the preview above',\n 'Call contentrain_apply with mode:extract and dry_run:false to execute',\n ],\n }\n }\n\n // Block execute if validation errors exist\n if (validationErrors.length > 0) {\n return {\n dry_run: false,\n error: 'Model validation failed — cannot execute extract with invalid model definitions',\n validation_errors: validationErrors,\n next_steps: ['Fix the validation errors and retry'],\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n error: health.message,\n action: 'blocked' as const,\n hint: 'Merge or delete old contentrain/* branches before executing normalize.',\n } as unknown as ExtractionResult\n }\n\n // Execute — git transaction (always review mode for normalize)\n const branchName = buildBranchName('normalize', 'extract')\n const tx = await createTransaction(projectRoot, branchName, { workflowOverride: 'review' })\n const sourceMap: Array<{ model: string; locale: string; value: string; file: string; line: number }> = []\n const modelsCreated: string[] = []\n const modelsUpdated: string[] = []\n let entriesWritten = 0\n\n try {\n await tx.write(async (wt) => {\n for (const ext of extractions) {\n // Create or merge model\n const existing = await readModel(wt, ext.model)\n if (existing) {\n // Merge fields: add new, keep existing\n if (ext.fields) {\n const merged = { ...existing.fields, ...ext.fields }\n // Only overwrite if new fields were actually added\n const newFieldNames = Object.keys(ext.fields).filter(k => !(k in (existing.fields ?? {})))\n if (newFieldNames.length > 0) {\n existing.fields = merged\n await writeModel(wt, existing)\n modelsUpdated.push(ext.model)\n }\n }\n } else {\n // Create new model\n const newModel: ModelDefinition = {\n id: ext.model,\n name: ext.model.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),\n kind: ext.kind,\n domain: ext.domain,\n i18n: ext.i18n ?? true,\n fields: ext.fields,\n }\n await writeModel(wt, newModel)\n modelsCreated.push(ext.model)\n }\n\n // Write content entries\n const model = (await readModel(wt, ext.model))!\n const entries: ContentEntry[] = ext.entries.map(e => ({\n locale: e.locale,\n slug: e.slug,\n data: e.data,\n }))\n\n const wtConfig = await readConfig(wt) ?? config\n await writeContent(wt, model, entries, wtConfig)\n entriesWritten += entries.length\n\n // Track source map\n for (const entry of ext.entries) {\n // For dictionary entries with per-key source tracking\n if (ext.kind === 'dictionary' && entry.sources) {\n for (const s of entry.sources) {\n sourceMap.push({\n model: ext.model,\n locale: entry.locale ?? config.locales.default,\n value: s.value,\n file: s.file,\n line: s.line,\n })\n }\n } else if (entry.source) {\n sourceMap.push({\n model: ext.model,\n locale: entry.locale ?? config.locales.default,\n value: entry.source.value,\n file: entry.source.file,\n line: entry.source.line,\n })\n }\n }\n }\n\n // Write source map for reuse scope enforcement\n if (sourceMap.length > 0) {\n const sourcesByModel: Record<string, { source_files: string[]; entry_count: number }> = {}\n for (const s of sourceMap) {\n if (!sourcesByModel[s.model]) {\n sourcesByModel[s.model] = { source_files: [], entry_count: 0 }\n }\n const modelEntry = sourcesByModel[s.model]!\n if (!modelEntry.source_files.includes(s.file)) {\n modelEntry.source_files.push(s.file)\n }\n modelEntry.entry_count++\n }\n const sourcesJson = JSON.stringify({\n version: 1,\n created_at: new Date().toISOString(),\n models: sourcesByModel,\n }, null, 2) + '\\n'\n // Write to worktree only — merge brings it to main\n // Phase 2 (reuse) must wait for extract branch to be merged first\n await writeText(join(wt, '.contentrain', 'normalize-sources.json'), sourcesJson)\n }\n\n // Update context\n await writeContext(wt, {\n tool: 'contentrain_apply',\n model: extractions.map(e => e.model).join(','),\n locale: config.locales.default,\n entries: extractions.flatMap(e => e.entries.map(en => en.slug ?? 'entry')),\n })\n })\n\n const commitMsg = `[contentrain] normalize: extract ${entriesWritten} entries to ${extractions.length} models`\n await tx.commit(commitMsg)\n\n const gitResult = { branch: branchName, action: 'pending-review', commit: '' }\n try {\n const completed = await tx.complete()\n gitResult.action = completed.action\n gitResult.commit = completed.commit\n } catch {\n gitResult.action = 'pending-review'\n } finally {\n await tx.cleanup()\n }\n\n return {\n dry_run: false,\n results: {\n models_created: modelsCreated,\n models_updated: modelsUpdated,\n entries_written: entriesWritten,\n source_map: sourceMap,\n },\n git: gitResult,\n context_updated: true,\n next_steps: [\n 'Run contentrain_validate to check the extracted content',\n 'Run contentrain_submit to push the branch for review',\n 'After review, proceed with mode:reuse to patch source files',\n ],\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n }\n}\n\n// ─── Reuse Mode ───\n\nexport async function applyReuse(\n projectRoot: string,\n input: ReuseInput,\n): Promise<ReuseResult> {\n const config = await readConfig(projectRoot)\n if (!config) throw new Error('Project not initialized. Run contentrain_init first.')\n\n const { scope, patches, dry_run } = input\n\n // Validate scope\n if (!scope.model && !scope.domain) {\n throw new Error('Scope required: provide model or domain. Whole-project patching is not allowed.')\n }\n\n // Validate patch count\n if (patches.length > MAX_PATCHES) {\n throw new Error(`Too many patches (${patches.length}). Maximum ${MAX_PATCHES} per operation. Split into multiple calls.`)\n }\n\n // Check content exists for scope (soft warning)\n if (scope.model) {\n const model = await readModel(projectRoot, scope.model)\n if (!model) {\n throw new Error(`Model \"${scope.model}\" not found. Run extract phase first.`)\n }\n }\n\n const scopeWarnings: string[] = []\n\n // Guardrail #1: Scope Real Enforcement\n // Step 1: Path safety — every patch must target a valid, patchable source file\n for (const patch of patches) {\n const pathError = validatePatchPath(patch.file)\n if (pathError) {\n throw new Error(`Invalid patch path: ${pathError}`)\n }\n }\n\n // Step 2: Semantic scope — verify scope model/domain exists and cross-check patch files\n if (scope.model || scope.domain) {\n const models = await listModels(projectRoot)\n const scopeModels = scope.model\n ? models.filter(m => m.id === scope.model)\n : scope.domain\n ? models.filter(m => m.domain === scope.domain)\n : models\n\n if (scopeModels.length === 0) {\n throw new Error(`No models found for scope ${scope.model ? `model=\"${scope.model}\"` : `domain=\"${scope.domain}\"`}`)\n }\n\n // Step 3: Verify patch files are source files (not content/config/meta files)\n // and belong to detectable source directories (not random locations)\n const { autoDetectSourceDirs } = await import('./scan-config.js')\n const sourceDirs = await autoDetectSourceDirs(projectRoot)\n\n // Build allowed file prefixes from source dirs\n const allowedPrefixes = sourceDirs.map(d => d === '.' ? '' : d + '/')\n\n for (const patch of patches) {\n const normalizedPath = patch.file.replace(/\\\\/g, '/')\n\n // Reject patches targeting .contentrain/ directory (content files should never be patched by reuse)\n if (normalizedPath.startsWith('.contentrain/') || normalizedPath.includes('/.contentrain/')) {\n throw new Error(`Cannot patch content/config files directly: \"${patch.file}\". Reuse patches source files only.`)\n }\n\n // If source dirs were detected (not just \".\"), verify patch files are within them\n if (sourceDirs.length > 0 && sourceDirs[0] !== '.') {\n const inSourceDir = allowedPrefixes.some(prefix =>\n prefix === '' || normalizedPath.startsWith(prefix),\n )\n if (!inSourceDir) {\n throw new Error(\n `Patch file \"${patch.file}\" is outside detected source directories (${sourceDirs.join(', ')}). ` +\n `Reuse patches must target source files within the project's source tree.`,\n )\n }\n }\n }\n\n // Step 4: Semantic source→model cross-check via normalize-sources.json\n // Source map is written by extract into the review branch worktree.\n // It only exists on base after extract branch is merged.\n // If missing: dry_run proceeds with warning, execute is blocked.\n if (scope.model || scope.domain) {\n const sourcesPath = join(projectRoot, '.contentrain', 'normalize-sources.json')\n const sourcesRaw = await readText(sourcesPath)\n\n if (!sourcesRaw) {\n // Source map not found — check if scoped model is a dictionary\n // Dictionaries store all keys in one entry, so per-file source tracking\n // is not available. Allow reuse with a warning instead of blocking.\n const scopedModel = scopeModels.find(m => m.id === scope.model)\n const isDictionary = scopedModel?.kind === 'dictionary'\n\n if (isDictionary) {\n scopeWarnings.push(\n 'normalize-sources.json not found. Dictionary models do not generate per-file source maps. ' +\n 'Scope enforcement is based on source-tree locality only.',\n )\n } else if (dry_run !== false) {\n scopeWarnings.push(\n 'normalize-sources.json not found. Semantic scope enforcement is unavailable. ' +\n 'Merge the extract branch first, then reuse will have full scope protection.',\n )\n } else {\n throw new Error(\n 'Cannot execute reuse: normalize-sources.json not found on base branch. ' +\n 'The extract branch must be merged before reuse can execute. ' +\n 'This ensures semantic scope enforcement protects against out-of-scope patching.',\n )\n }\n } else {\n const sourcesData = JSON.parse(sourcesRaw) as {\n models?: Record<string, { source_files?: string[] }>\n }\n\n // Collect all allowed source files for the scope\n let allowedSourceFiles: string[] = []\n let scopeLabel = ''\n\n if (scope.model) {\n const modelSources = sourcesData.models?.[scope.model]?.source_files\n if (modelSources) allowedSourceFiles = modelSources\n scopeLabel = `model \"${scope.model}\"`\n } else if (scope.domain) {\n const domainModelIds = scopeModels.map(m => m.id)\n for (const modelId of domainModelIds) {\n const modelSources = sourcesData.models?.[modelId]?.source_files\n if (modelSources) allowedSourceFiles.push(...modelSources)\n }\n allowedSourceFiles = [...new Set(allowedSourceFiles)]\n scopeLabel = `domain \"${scope.domain}\"`\n }\n\n if (allowedSourceFiles.length > 0) {\n const outOfScopePatches: string[] = []\n for (const patch of patches) {\n const normalizedPath = patch.file.replace(/\\\\/g, '/')\n if (!allowedSourceFiles.includes(normalizedPath)) {\n outOfScopePatches.push(patch.file)\n }\n }\n if (outOfScopePatches.length > 0) {\n throw new Error(\n `Scope enforcement: ${outOfScopePatches.length} patch file(s) are not associated with ${scopeLabel}. ` +\n `Out-of-scope files: ${outOfScopePatches.join(', ')}. ` +\n `Known source files: ${allowedSourceFiles.join(', ')}.`,\n )\n }\n }\n }\n }\n }\n\n // Group patches by file\n const patchesByFile = new Map<string, PatchEntry[]>()\n for (const patch of patches) {\n if (!patchesByFile.has(patch.file)) {\n patchesByFile.set(patch.file, [])\n }\n patchesByFile.get(patch.file)!.push(patch)\n }\n\n const filesToModify = [...patchesByFile.keys()]\n const importsToAdd = patches.filter(p => p.import_statement).length\n\n // Dry run — return preview only\n if (dry_run !== false) {\n return {\n dry_run: true,\n preview: {\n files_to_modify: filesToModify,\n patches_count: patches.length,\n imports_to_add: importsToAdd,\n },\n ...(scopeWarnings.length > 0 ? { scope_warnings: scopeWarnings } : {}),\n next_steps: [\n ...(scopeWarnings.length > 0 ? [`WARNING: ${scopeWarnings.length} patch file(s) not in extract source map — verify intent`] : []),\n 'Review the files and patches above',\n 'Call contentrain_apply with mode:reuse and dry_run:false to execute',\n ],\n }\n }\n\n // Branch health gate\n const reuseHealth = await checkBranchHealth(projectRoot)\n if (reuseHealth.blocked) {\n return {\n dry_run: false,\n error: `Branch blocked: ${reuseHealth.message}`,\n next_steps: ['Merge or delete old contentrain/* branches before executing reuse.'],\n }\n }\n\n // Execute — git transaction\n const scopeTarget = scope.model ?? scope.domain!\n const branchName = buildBranchName('normalize/reuse', scopeTarget)\n const tx = await createTransaction(projectRoot, branchName, { workflowOverride: 'review' })\n\n const filesModified: string[] = []\n let patchesApplied = 0\n let importsAdded = 0\n const patchesSkipped: Array<{ file: string; line: number; reason: string }> = []\n const frameworkWarnings: Array<{ file: string; warning: string }> = []\n const syntaxErrors: SyntaxError[] = []\n\n try {\n await tx.write(async (wt) => {\n for (const [relFile, filePatches] of patchesByFile) {\n const absPath = join(wt, relFile)\n\n if (!(await pathExists(absPath))) {\n for (const p of filePatches) {\n patchesSkipped.push({ file: relFile, line: p.line, reason: 'file not found' })\n }\n continue\n }\n\n const content = await readText(absPath)\n if (content === null) {\n for (const p of filePatches) {\n patchesSkipped.push({ file: relFile, line: p.line, reason: 'file unreadable' })\n }\n continue\n }\n\n // Sort patches by line DESC (bottom-up to avoid line shifts)\n const sorted = [...filePatches].toSorted((a, b) => b.line - a.line)\n\n const lines = content.split('\\n')\n let fileModified = false\n\n for (const patch of sorted) {\n // Determine replacement context before applying\n const targetLine = lines[patch.line - 1] ?? ''\n const isTagTextContext = targetLine.includes(`>${patch.old_value}<`)\n const replacementContext = isTagTextContext ? 'tag_text' as const : 'other' as const\n\n // Guardrail #2: Framework-aware validation — only warn for tag text replacements\n if (isTagTextContext) {\n const fwWarning = validateFrameworkExpression(relFile, patch.new_expression, replacementContext)\n if (fwWarning) {\n frameworkWarnings.push({ file: relFile, warning: fwWarning })\n }\n }\n\n const applied = applyPatchToLines(lines, patch)\n if (applied) {\n patchesApplied++\n fileModified = true\n } else {\n patchesSkipped.push({ file: relFile, line: patch.line, reason: 'old_value not found at or near specified line' })\n }\n }\n\n // Add imports (deduplicate)\n const importStatements = new Set(\n filePatches\n .filter(p => p.import_statement)\n .map(p => p.import_statement!),\n )\n\n if (importStatements.size > 0) {\n const added = addImportsToLines(lines, importStatements)\n importsAdded += added\n if (added > 0) fileModified = true\n }\n\n if (fileModified) {\n const newContent = lines.join('\\n')\n await writeText(absPath, newContent)\n filesModified.push(relFile)\n\n // Guardrail #5: Syntax check after patching\n const syntaxError = checkSyntax(relFile, newContent)\n if (syntaxError) {\n syntaxErrors.push({ file: relFile, error: syntaxError })\n }\n }\n }\n\n // Update context\n await writeContext(wt, {\n tool: 'contentrain_apply',\n model: scopeTarget,\n locale: config.locales.default,\n })\n })\n\n if (filesModified.length === 0) {\n await tx.cleanup()\n return {\n dry_run: false,\n results: {\n files_modified: [],\n patches_applied: 0,\n patches_skipped: patchesSkipped,\n imports_added: 0,\n framework_warnings: frameworkWarnings.length > 0 ? frameworkWarnings : undefined,\n },\n next_steps: ['No files were modified. Check patch definitions and try again.'],\n }\n }\n\n const commitMsg = `[contentrain] normalize: reuse ${scopeTarget} — patch ${filesModified.length} files (${patchesApplied} replacements)`\n await tx.commit(commitMsg)\n\n const gitResult = { branch: branchName, action: 'pending-review', commit: '' }\n try {\n const completed = await tx.complete()\n gitResult.action = completed.action\n gitResult.commit = completed.commit\n } catch {\n gitResult.action = 'pending-review'\n } finally {\n await tx.cleanup()\n }\n\n return {\n dry_run: false,\n results: {\n files_modified: filesModified,\n patches_applied: patchesApplied,\n patches_skipped: patchesSkipped,\n imports_added: importsAdded,\n framework_warnings: frameworkWarnings.length > 0 ? frameworkWarnings : undefined,\n syntax_errors: syntaxErrors.length > 0 ? syntaxErrors : undefined,\n },\n ...(scopeWarnings.length > 0 ? { scope_warnings: scopeWarnings } : {}),\n git: gitResult,\n next_steps: [\n 'Run contentrain_validate to verify the patched files',\n patchesSkipped.length > 0 ? `${patchesSkipped.length} patches were skipped — review and retry if needed` : '',\n syntaxErrors.length > 0 ? `WARNING: ${syntaxErrors.length} file(s) may have syntax errors after patching — review manually` : '',\n scopeWarnings.length > 0 ? `NOTE: ${scopeWarnings.length} patch file(s) not in extract source map` : '',\n 'Run contentrain_submit to push the branch for review',\n ].filter(Boolean),\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n }\n}\n\n// ─── Patch Helpers ───\n\n/**\n * Apply a single patch to a lines array. Mutates lines in place.\n * Uses line hint for proximity matching — searches ±10 lines from hint.\n */\nfunction applyPatchToLines(lines: string[], patch: PatchEntry): boolean {\n const { line, old_value, new_expression } = patch\n const lineIdx = line - 1 // 0-based\n\n // Search range: ±10 lines from hint\n const searchStart = Math.max(0, lineIdx - 10)\n const searchEnd = Math.min(lines.length, lineIdx + 11)\n\n // First pass: exact line match\n if (lineIdx >= 0 && lineIdx < lines.length) {\n const replaced = replaceInLine(lines[lineIdx]!, old_value, new_expression)\n if (replaced !== null) {\n lines[lineIdx] = replaced\n return true\n }\n }\n\n // Second pass: proximity search\n for (let i = searchStart; i < searchEnd; i++) {\n if (i === lineIdx) continue // already tried\n const replaced = replaceInLine(lines[i]!, old_value, new_expression)\n if (replaced !== null) {\n lines[i] = replaced\n return true\n }\n }\n\n return false\n}\n\n/**\n * Replace old_value with new_expression in a single line.\n * Matches the string literal (quoted or unquoted tag text).\n * Returns the modified line, or null if not found.\n *\n * Guardrail #4: Safer patch matching — word boundary awareness and\n * ambiguity rejection for plain text fallback.\n */\nexport function replaceInLine(line: string, oldValue: string, newExpression: string): string | null {\n // Try exact match of the value in quoted strings\n // Match: \"old_value\", 'old_value', `old_value`\n for (const quote of ['\"', \"'\", '`']) {\n const quoted = `${quote}${oldValue}${quote}`\n if (line.includes(quoted)) {\n return line.replace(quoted, newExpression)\n }\n }\n\n // Try unquoted tag text match: >old_value<\n // The agent provides the complete new_expression with correct framework syntax:\n // JSX: {t('key')}\n // Vue: {{ $t('key') }}\n // Svelte: {$t('key')}\n // So we insert the expression as-is between > and <, without wrapping in braces.\n if (line.includes(`>${oldValue}<`)) {\n return line.replace(`>${oldValue}<`, `>${newExpression}<`)\n }\n\n // Guardrail #4: Safer plain text fallback\n // Only match if old_value appears at a word boundary and is unambiguous\n if (line.includes(oldValue)) {\n // Count occurrences — if multiple, it's ambiguous\n const occurrences = countOccurrences(line, oldValue)\n if (occurrences > 1) {\n return null // ambiguous — let proximity search handle it\n }\n\n // Word boundary check: don't replace \"Submit\" inside \"SubmitButton\"\n // Reject if the old_value is a substring of a larger word — check if\n // adjacent characters are word characters that extend the token.\n const idx = line.indexOf(oldValue)\n const charBefore = idx > 0 ? line[idx - 1]! : ''\n const charAfter = idx + oldValue.length < line.length ? line[idx + oldValue.length]! : ''\n\n const oldStartsWithWord = oldValue.length > 0 && isWordChar(oldValue[0]!)\n const oldEndsWithWord = oldValue.length > 0 && isWordChar(oldValue[oldValue.length - 1]!)\n\n // If old_value starts with a word char and char before is also word char, it's a partial match\n if (oldStartsWithWord && isWordChar(charBefore)) {\n return null\n }\n // If old_value ends with a word char and char after is also word char, it's a partial match\n if (oldEndsWithWord && isWordChar(charAfter)) {\n return null\n }\n\n return line.replace(oldValue, newExpression)\n }\n\n return null\n}\n\n/** Count non-overlapping occurrences of a substring */\nfunction countOccurrences(str: string, sub: string): number {\n let count = 0\n let pos = 0\n while (pos <= str.length - sub.length) {\n const idx = str.indexOf(sub, pos)\n if (idx === -1) break\n count++\n pos = idx + sub.length\n }\n return count\n}\n\n/** Check if a character is a word character (letter, digit, underscore) */\nfunction isWordChar(ch: string): boolean {\n return /\\w/.test(ch)\n}\n\n/**\n * Add import statements to the top of a file (after existing imports).\n * Deduplicates — won't add if the import already exists.\n * Returns number of imports actually added.\n */\nfunction addImportsToLines(lines: string[], imports: Set<string>): number {\n let added = 0\n const existingContent = lines.join('\\n')\n\n // Find the last import line position (handles multi-line imports)\n let lastImportIdx = -1\n let inMultiLineImport = false\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim()\n if (inMultiLineImport) {\n lastImportIdx = i\n if (trimmed.includes('}')) inMultiLineImport = false\n continue\n }\n if (trimmed.startsWith('import ') || trimmed.startsWith('import{')) {\n lastImportIdx = i\n // Check if this is a multi-line import (has { but no closing })\n if (trimmed.includes('{') && !trimmed.includes('}')) {\n inMultiLineImport = true\n }\n continue\n }\n // Stop searching after a non-import, non-empty, non-comment line following imports\n if (lastImportIdx >= 0 && trimmed.length > 0 && !trimmed.startsWith('//') && !trimmed.startsWith('/*') && !trimmed.startsWith('*')) {\n break\n }\n }\n\n let insertAt: number\n if (lastImportIdx >= 0) {\n insertAt = lastImportIdx + 1\n } else {\n // No existing imports found — insert after shebang and directive lines\n insertAt = 0\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i]!.trim()\n if (i === 0 && trimmed.startsWith('#!')) {\n insertAt = i + 1\n continue\n }\n if (trimmed === \"'use client'\" || trimmed === '\"use client\"'\n || trimmed === \"'use server'\" || trimmed === '\"use server\"'\n || trimmed === \"'use client';\" || trimmed === '\"use client\";'\n || trimmed === \"'use server';\" || trimmed === '\"use server\";') {\n insertAt = i + 1\n continue\n }\n if (insertAt > 0 && trimmed.length > 0) break\n if (insertAt === 0 && trimmed.length > 0) break\n }\n }\n const toInsert: string[] = []\n\n for (const imp of imports) {\n // Check if this import already exists (by checking the from clause)\n if (!existingContent.includes(imp)) {\n toInsert.push(imp)\n added++\n }\n }\n\n if (toInsert.length > 0) {\n lines.splice(insertAt, 0, ...toInsert)\n }\n\n return added\n}\n"],"mappings":";;;;;;;;AAiGA,MAAM,cAAc;;AAGpB,MAAa,uBAAuB,IAAI,IAAI;CAC1C;CAAQ;CAAQ;CAAQ;CAAO;CAAO;CAAQ;CAAU;CACzD,CAAC;;AAGF,MAAM,0BAA0B,IAAI,IAAI;CACtC;CAAgB;CAAgB;CAAQ;CAAQ;CAAS;CAAS;CACnE,CAAC;AAMF,SAAgB,oBAAoB,UAAiC;AAEnE,SADY,QAAQ,SAAS,CAAC,aAAa,EAC3C;EACE,KAAK,OAAQ,QAAO;EACpB,KAAK,UAAW,QAAO;EACvB,KAAK;EACL,KAAK,OAAQ,QAAO;EACpB,KAAK,SAAU,QAAO;EACtB,KAAK;EACL,KAAK;EACL,KAAK,OAAQ,QAAO;EACpB,QAAS,QAAO;;;;;;;AAQpB,SAAgB,4BACd,UACA,eACA,SACe;AACf,KAAI,YAAY,WAAY,QAAO;AAInC,SAFkB,oBAAoB,SAAS,EAE/C;EACE,KAAK;AACH,OAAI,CAAC,cAAc,SAAS,KAAK,CAC/B,QAAO,aAAa,SAAS,0BAA0B,cAAc;AAEvE;EACF,KAAK;AACH,OAAI,CAAC,cAAc,SAAS,IAAI,CAC9B,QAAO,aAAa,SAAS,0BAA0B,cAAc;AAEvE;EACF,KAAK;AACH,OAAI,CAAC,cAAc,SAAS,IAAI,CAC9B,QAAO,gBAAgB,SAAS,0BAA0B,cAAc;AAE1E;EACF,KAAK;AACH,OAAI,CAAC,cAAc,SAAS,IAAI,CAC9B,QAAO,eAAe,SAAS,0BAA0B,cAAc;AAEzE;EACF,KAAK,SAEH,QAAO,gBAAgB,SAAS;;AAGpC,QAAO;;;;;;AAST,SAAgB,kBAAkB,UAAiC;CACjE,MAAM,iBAAiB,SAAS,QAAQ,OAAO,IAAI;AAGnD,KAAI,eAAe,SAAS,KAAK,CAC/B,QAAO,6BAA6B,SAAS;AAI/C,KAAI,eAAe,WAAW,IAAI,CAChC,QAAO,+BAA+B,SAAS;CAIjD,MAAM,WAAW,eAAe,MAAM,IAAI;AAC1C,MAAK,MAAM,OAAO,SAChB,KAAI,wBAAwB,IAAI,IAAI,CAClC,QAAO,0BAA0B,IAAI,sBAAsB,SAAS;CAKxE,MAAM,MAAM,QAAQ,SAAS,CAAC,aAAa;AAC3C,KAAI,CAAC,qBAAqB,IAAI,IAAI,CAChC,QAAO,mBAAmB,IAAI,+BAA+B,CAAC,GAAG,qBAAqB,CAAC,KAAK,KAAK,CAAC,WAAW,SAAS;AAGxH,QAAO;;;;;;AAST,SAAgB,YAAY,UAAkB,SAAgC;AAG5E,SAFY,QAAQ,SAAS,CAAC,aAAa,EAE3C;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,OACH,QAAO,cAAc,QAAQ;EAC/B,KAAK,OACH,QAAO,eAAe,QAAQ;EAChC,KAAK;EACL,KAAK,SACH,QAAO,gBAAgB,QAAQ;EACjC,QACE,QAAO;;;;;;;;AASb,SAAS,cAAc,SAAgC;CACrD,MAAM,QAAkB,EAAE;CAC1B,MAAM,QAAgC;EAAE,KAAK;EAAK,KAAK;EAAK,KAAK;EAAK;CACtE,IAAI,WAA0B;CAC9B,IAAI,UAAU;CACd,IAAI,gBAAgB;CACpB,IAAI,iBAAiB;AAErB,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,KAAK,QAAQ;EACnB,MAAM,OAAO,QAAQ,IAAI;AAGzB,MAAI,SAAS;AACX,aAAU;AACV;;AAGF,MAAI,OAAO,QAAQ,aAAa,MAAM;AACpC,aAAU;AACV;;AAIF,MAAI,CAAC,YAAY,CAAC,kBAAkB,OAAO,OAAO,SAAS,KAAK;AAC9D,mBAAgB;AAChB;;AAEF,MAAI,eAAe;AACjB,OAAI,OAAO,KAAM,iBAAgB;AACjC;;AAIF,MAAI,CAAC,YAAY,CAAC,kBAAkB,OAAO,OAAO,SAAS,KAAK;AAC9D,oBAAiB;AACjB;AACA;;AAEF,MAAI,gBAAgB;AAClB,OAAI,OAAO,OAAO,SAAS,KAAK;AAC9B,qBAAiB;AACjB;;AAEF;;AAIF,MAAI,aAAa,MAAM;AACrB,OAAI,OAAO,SAET,YAAW;YACF,aAAa,OAAO,OAAO,KACpC,QAAO,2CAA2C;AAEpD;;AAGF,MAAI,OAAO,QAAO,OAAO,OAAO,OAAO,KAAK;AAC1C,cAAW;AACX;;AAIF,MAAI,OAAO,OAAO,OAAO,OAAO,OAAO,IACrC,OAAM,KAAK,GAAG;WACL,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;GACjD,MAAM,WAAW,MAAM;AACvB,OAAI,MAAM,WAAW,EACnB,QAAO,sBAAsB,GAAG,gBAAgB;GAElD,MAAM,MAAM,MAAM,KAAK;AACvB,OAAI,QAAQ,SACV,QAAO,6CAA6C,IAAI,eAAe,GAAG,gBAAgB;;;AAKhG,KAAI,aAAa,KACf,QAAO,4CAA4C,SAAS;AAG9D,KAAI,MAAM,SAAS,EACjB,QAAO,qBAAqB,MAAM,MAAM,SAAS,GAAG,MAAM,MAAM,OAAO;AAGzE,QAAO;;;;;AAMT,SAAS,eAAe,SAAgC;CACtD,MAAM,aAAa,gBAAgB,QAAQ;AAC3C,KAAI,WAAY,QAAO;AAGvB,MAAK,MAAM,OAAO,CAAC,YAAY,SAAS,EAAE;EACxC,MAAM,SAAS,IAAI,OAAO,IAAI,IAAI,SAAS,IAAI;EAC/C,MAAM,UAAU,IAAI,OAAO,KAAK,IAAI,IAAI,IAAI;EAC5C,MAAM,QAAQ,QAAQ,MAAM,OAAO,EAAE,UAAU;EAC/C,MAAM,SAAS,QAAQ,MAAM,QAAQ,EAAE,UAAU;AACjD,MAAI,UAAU,OACZ,QAAO,eAAe,IAAI,SAAS,MAAM,cAAc,OAAO;;AAIlE,QAAO;;;;;;AAOT,SAAS,gBAAgB,SAAgC;AAIvD,MAAK,MAAM,OAFY;EAAC;EAAO;EAAW;EAAQ;EAAU;EAAU;EAAO;EAAW;EAAS;EAAM;EAAM;EAAQ,EAEnF;EAChC,MAAM,SAAS,IAAI,OAAO,IAAI,IAAI,SAAS,IAAI;EAC/C,MAAM,UAAU,IAAI,OAAO,KAAK,IAAI,IAAI,IAAI;EAC5C,MAAM,QAAQ,QAAQ,MAAM,OAAO,EAAE,UAAU;EAC/C,MAAM,SAAS,QAAQ,MAAM,QAAQ,EAAE,UAAU;AACjD,MAAI,UAAU,OACZ,QAAO,eAAe,IAAI,SAAS,MAAM,cAAc,OAAO;;AAIlE,QAAO;;AAKT,eAAsB,aACpB,aACA,OAC2B;CAC3B,MAAM,SAAS,MAAM,WAAW,YAAY;AAC5C,KAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,uDAAuD;CAEpF,MAAM,EAAE,aAAa,YAAY;CAGjC,MAAM,iBAAiB,MAAM,WAAW,YAAY;CACpD,MAAM,cAAc,IAAI,IAAI,eAAe,KAAI,MAAK,EAAE,GAAG,CAAC;CAE1D,MAAM,iBAA2B,EAAE;CACnC,MAAM,iBAA2B,EAAE;CACnC,MAAM,eAAyB,EAAE;CACjC,IAAI,eAAe;CAEnB,MAAM,mBAA6B,EAAE;AAErC,MAAK,MAAM,OAAO,aAAa;EAE7B,MAAM,cAAc,wBAAwB;GAC1C,IAAI,IAAI;GACR,MAAM,IAAI;GACV,QAAQ,IAAI;GACb,CAAC;AACF,MAAI,YAAY,SAAS,EACvB,kBAAiB,KAAK,GAAG,YAAY,KAAI,MAAK,IAAI,IAAI,MAAM,IAAI,IAAI,CAAC;AAGvE,OAAK,MAAM,SAAS,IAAI,QACtB,KAAI,IAAI,SAAS,cAAc;AAC7B,OAAI,MAAM,KAAK,UAAU,KAAA,KAAa,MAAM,KAAK,YAAY,KAAA,EAC3D,kBAAiB,KAAK,IAAI,IAAI,MAAM,iDAAiD;AAEvF,QAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,MAAM,KAAK,CACjD,KAAI,OAAO,QAAQ,SACjB,kBAAiB,KAAK,IAAI,IAAI,MAAM,oCAAoC,IAAI,0BAA0B,OAAO,MAAM;aAG9G,IAAI,SAAS;OAClB,CAAC,MAAM,QAAQ,CAAC,MAAM,KAAK,QAC7B,kBAAiB,KAAK,IAAI,IAAI,MAAM,qCAAqC;aAElE,IAAI,SAAS;OAClB,MAAM,SAAS,KAAA,KAAa,MAAM,KAAK,YAAY,KAAA,EACrD,kBAAiB,KAAK,IAAI,IAAI,MAAM,2CAA2C;aAExE,IAAI,SAAS;OAClB,MAAM,KAAK,UAAU,KAAA,KAAa,MAAM,SAAS,KAAA,KAAa,MAAM,KAAK,YAAY,KAAA,EACvF,kBAAiB,KAAK,IAAI,IAAI,MAAM,gDAAgD;;AAK1F,MAAI,YAAY,IAAI,IAAI,MAAM,CAC5B,gBAAe,KAAK,IAAI,MAAM;MAE9B,gBAAe,KAAK,IAAI,MAAM;AAEhC,kBAAgB,IAAI,QAAQ;EAG5B,IAAI;AACJ,MAAI,YAAY,IAAI,IAAI,MAAM,EAAE;GAC9B,MAAM,OAAO,MAAM,UAAU,aAAa,IAAI,MAAM;AACpD,OAAI,KACF,gBAAe;OAEf,gBAAe;IAAE,IAAI,IAAI;IAAO,MAAM,IAAI;IAAM,QAAQ,IAAI;IAAQ,MAAM,IAAI,QAAQ;IAAM;QAG9F,gBAAe;GAAE,IAAI,IAAI;GAAO,MAAM,IAAI;GAAM,QAAQ,IAAI;GAAQ,MAAM,IAAI,QAAQ;GAAM;EAG9F,MAAM,OAAO,kBAAkB,aAAa,aAAa;AACzD,OAAK,MAAM,SAAS,IAAI,SAAS;GAC/B,MAAM,SAAS,MAAM,UAAU,OAAO,QAAQ;AAC9C,OAAI,IAAI,SAAS,cAAc,MAAM,KACnC,cAAa,KAAK,kBAAkB,MAAM,cAAc,QAAQ,MAAM,KAAK,CAAC;OAE5E,cAAa,KAAK,oBAAoB,MAAM,cAAc,OAAO,CAAC;;;CAKxE,MAAM,UAA6B;EACjC,kBAAkB;EAClB,kBAAkB;EAClB,eAAe;EACf,eAAe,CAAC,GAAG,IAAI,IAAI,aAAa,CAAC;EAC1C;AAGD,KAAI,YAAY,MACd,QAAO;EACL,SAAS;EACT;EACA,GAAI,iBAAiB,SAAS,IAAI,EAAE,mBAAmB,kBAAkB,GAAG,EAAE;EAC9E,YAAY;GACV,GAAI,iBAAiB,SAAS,IAC1B,CAAC,YAAY,iBAAiB,OAAO,mDAAmD,GACxF,EAAE;GACN;GACA;GACD;EACF;AAIH,KAAI,iBAAiB,SAAS,EAC5B,QAAO;EACL,SAAS;EACT,OAAO;EACP,mBAAmB;EACnB,YAAY,CAAC,sCAAsC;EACpD;CAIH,MAAM,SAAS,MAAM,kBAAkB,YAAY;AACnD,KAAI,OAAO,QACT,QAAO;EACL,OAAO,OAAO;EACd,QAAQ;EACR,MAAM;EACP;CAIH,MAAM,aAAa,gBAAgB,aAAa,UAAU;CAC1D,MAAM,KAAK,MAAM,kBAAkB,aAAa,YAAY,EAAE,kBAAkB,UAAU,CAAC;CAC3F,MAAM,YAAiG,EAAE;CACzG,MAAM,gBAA0B,EAAE;CAClC,MAAM,gBAA0B,EAAE;CAClC,IAAI,iBAAiB;AAErB,KAAI;AACF,QAAM,GAAG,MAAM,OAAO,OAAO;AAC3B,QAAK,MAAM,OAAO,aAAa;IAE7B,MAAM,WAAW,MAAM,UAAU,IAAI,IAAI,MAAM;AAC/C,QAAI;SAEE,IAAI,QAAQ;MACd,MAAM,SAAS;OAAE,GAAG,SAAS;OAAQ,GAAG,IAAI;OAAQ;AAGpD,UADsB,OAAO,KAAK,IAAI,OAAO,CAAC,QAAO,MAAK,EAAE,MAAM,SAAS,UAAU,EAAE,GAAG,CACxE,SAAS,GAAG;AAC5B,gBAAS,SAAS;AAClB,aAAM,WAAW,IAAI,SAAS;AAC9B,qBAAc,KAAK,IAAI,MAAM;;;WAG5B;AAUL,WAAM,WAAW,IARiB;MAChC,IAAI,IAAI;MACR,MAAM,IAAI,MAAM,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,OAAO,EAAE,CAAC,aAAa,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,IAAI;MACrF,MAAM,IAAI;MACV,QAAQ,IAAI;MACZ,MAAM,IAAI,QAAQ;MAClB,QAAQ,IAAI;MACb,CAC6B;AAC9B,mBAAc,KAAK,IAAI,MAAM;;IAI/B,MAAM,QAAS,MAAM,UAAU,IAAI,IAAI,MAAM;IAC7C,MAAM,UAA0B,IAAI,QAAQ,KAAI,OAAM;KACpD,QAAQ,EAAE;KACV,MAAM,EAAE;KACR,MAAM,EAAE;KACT,EAAE;AAGH,UAAM,aAAa,IAAI,OAAO,SADb,MAAM,WAAW,GAAG,IAAI,OACO;AAChD,sBAAkB,QAAQ;AAG1B,SAAK,MAAM,SAAS,IAAI,QAEtB,KAAI,IAAI,SAAS,gBAAgB,MAAM,QACrC,MAAK,MAAM,KAAK,MAAM,QACpB,WAAU,KAAK;KACb,OAAO,IAAI;KACX,QAAQ,MAAM,UAAU,OAAO,QAAQ;KACvC,OAAO,EAAE;KACT,MAAM,EAAE;KACR,MAAM,EAAE;KACT,CAAC;aAEK,MAAM,OACf,WAAU,KAAK;KACb,OAAO,IAAI;KACX,QAAQ,MAAM,UAAU,OAAO,QAAQ;KACvC,OAAO,MAAM,OAAO;KACpB,MAAM,MAAM,OAAO;KACnB,MAAM,MAAM,OAAO;KACpB,CAAC;;AAMR,OAAI,UAAU,SAAS,GAAG;IACxB,MAAM,iBAAkF,EAAE;AAC1F,SAAK,MAAM,KAAK,WAAW;AACzB,SAAI,CAAC,eAAe,EAAE,OACpB,gBAAe,EAAE,SAAS;MAAE,cAAc,EAAE;MAAE,aAAa;MAAG;KAEhE,MAAM,aAAa,eAAe,EAAE;AACpC,SAAI,CAAC,WAAW,aAAa,SAAS,EAAE,KAAK,CAC3C,YAAW,aAAa,KAAK,EAAE,KAAK;AAEtC,gBAAW;;IAEb,MAAM,cAAc,KAAK,UAAU;KACjC,SAAS;KACT,6BAAY,IAAI,MAAM,EAAC,aAAa;KACpC,QAAQ;KACT,EAAE,MAAM,EAAE,GAAG;AAGd,UAAM,UAAU,KAAK,IAAI,gBAAgB,yBAAyB,EAAE,YAAY;;AAIlF,SAAM,aAAa,IAAI;IACrB,MAAM;IACN,OAAO,YAAY,KAAI,MAAK,EAAE,MAAM,CAAC,KAAK,IAAI;IAC9C,QAAQ,OAAO,QAAQ;IACvB,SAAS,YAAY,SAAQ,MAAK,EAAE,QAAQ,KAAI,OAAM,GAAG,QAAQ,QAAQ,CAAC;IAC3E,CAAC;IACF;EAEF,MAAM,YAAY,oCAAoC,eAAe,cAAc,YAAY,OAAO;AACtG,QAAM,GAAG,OAAO,UAAU;EAE1B,MAAM,YAAY;GAAE,QAAQ;GAAY,QAAQ;GAAkB,QAAQ;GAAI;AAC9E,MAAI;GACF,MAAM,YAAY,MAAM,GAAG,UAAU;AACrC,aAAU,SAAS,UAAU;AAC7B,aAAU,SAAS,UAAU;UACvB;AACN,aAAU,SAAS;YACX;AACR,SAAM,GAAG,SAAS;;AAGpB,SAAO;GACL,SAAS;GACT,SAAS;IACP,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,YAAY;IACb;GACD,KAAK;GACL,iBAAiB;GACjB,YAAY;IACV;IACA;IACA;IACD;GACF;UACM,OAAO;AACd,QAAM,GAAG,SAAS;AAClB,QAAM;;;AAMV,eAAsB,WACpB,aACA,OACsB;CACtB,MAAM,SAAS,MAAM,WAAW,YAAY;AAC5C,KAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,uDAAuD;CAEpF,MAAM,EAAE,OAAO,SAAS,YAAY;AAGpC,KAAI,CAAC,MAAM,SAAS,CAAC,MAAM,OACzB,OAAM,IAAI,MAAM,kFAAkF;AAIpG,KAAI,QAAQ,SAAS,YACnB,OAAM,IAAI,MAAM,qBAAqB,QAAQ,OAAO,aAAa,YAAY,4CAA4C;AAI3H,KAAI,MAAM;MAEJ,CADU,MAAM,UAAU,aAAa,MAAM,MAAM,CAErD,OAAM,IAAI,MAAM,UAAU,MAAM,MAAM,uCAAuC;;CAIjF,MAAM,gBAA0B,EAAE;AAIlC,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,YAAY,kBAAkB,MAAM,KAAK;AAC/C,MAAI,UACF,OAAM,IAAI,MAAM,uBAAuB,YAAY;;AAKvD,KAAI,MAAM,SAAS,MAAM,QAAQ;EAC/B,MAAM,SAAS,MAAM,WAAW,YAAY;EAC5C,MAAM,cAAc,MAAM,QACtB,OAAO,QAAO,MAAK,EAAE,OAAO,MAAM,MAAM,GACxC,MAAM,SACJ,OAAO,QAAO,MAAK,EAAE,WAAW,MAAM,OAAO,GAC7C;AAEN,MAAI,YAAY,WAAW,EACzB,OAAM,IAAI,MAAM,6BAA6B,MAAM,QAAQ,UAAU,MAAM,MAAM,KAAK,WAAW,MAAM,OAAO,KAAK;EAKrH,MAAM,EAAE,yBAAyB,MAAM,OAAO;EAC9C,MAAM,aAAa,MAAM,qBAAqB,YAAY;EAG1D,MAAM,kBAAkB,WAAW,KAAI,MAAK,MAAM,MAAM,KAAK,IAAI,IAAI;AAErE,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,iBAAiB,MAAM,KAAK,QAAQ,OAAO,IAAI;AAGrD,OAAI,eAAe,WAAW,gBAAgB,IAAI,eAAe,SAAS,iBAAiB,CACzF,OAAM,IAAI,MAAM,gDAAgD,MAAM,KAAK,qCAAqC;AAIlH,OAAI,WAAW,SAAS,KAAK,WAAW,OAAO;QAIzC,CAHgB,gBAAgB,MAAK,WACvC,WAAW,MAAM,eAAe,WAAW,OAAO,CACnD,CAEC,OAAM,IAAI,MACR,eAAe,MAAM,KAAK,4CAA4C,WAAW,KAAK,KAAK,CAAC,6EAE7F;;;AASP,MAAI,MAAM,SAAS,MAAM,QAAQ;GAE/B,MAAM,aAAa,MAAM,SADL,KAAK,aAAa,gBAAgB,yBAAyB,CACjC;AAE9C,OAAI,CAAC,WAOH,KAHoB,YAAY,MAAK,MAAK,EAAE,OAAO,MAAM,MAAM,EAC7B,SAAS,aAGzC,eAAc,KACZ,qJAED;YACQ,YAAY,MACrB,eAAc,KACZ,2JAED;OAED,OAAM,IAAI,MACR,qNAGD;QAEE;IACL,MAAM,cAAc,KAAK,MAAM,WAAW;IAK1C,IAAI,qBAA+B,EAAE;IACrC,IAAI,aAAa;AAEjB,QAAI,MAAM,OAAO;KACf,MAAM,eAAe,YAAY,SAAS,MAAM,QAAQ;AACxD,SAAI,aAAc,sBAAqB;AACvC,kBAAa,UAAU,MAAM,MAAM;eAC1B,MAAM,QAAQ;KACvB,MAAM,iBAAiB,YAAY,KAAI,MAAK,EAAE,GAAG;AACjD,UAAK,MAAM,WAAW,gBAAgB;MACpC,MAAM,eAAe,YAAY,SAAS,UAAU;AACpD,UAAI,aAAc,oBAAmB,KAAK,GAAG,aAAa;;AAE5D,0BAAqB,CAAC,GAAG,IAAI,IAAI,mBAAmB,CAAC;AACrD,kBAAa,WAAW,MAAM,OAAO;;AAGvC,QAAI,mBAAmB,SAAS,GAAG;KACjC,MAAM,oBAA8B,EAAE;AACtC,UAAK,MAAM,SAAS,SAAS;MAC3B,MAAM,iBAAiB,MAAM,KAAK,QAAQ,OAAO,IAAI;AACrD,UAAI,CAAC,mBAAmB,SAAS,eAAe,CAC9C,mBAAkB,KAAK,MAAM,KAAK;;AAGtC,SAAI,kBAAkB,SAAS,EAC7B,OAAM,IAAI,MACR,sBAAsB,kBAAkB,OAAO,yCAAyC,WAAW,wBAC5E,kBAAkB,KAAK,KAAK,CAAC,wBAC7B,mBAAmB,KAAK,KAAK,CAAC,GACtD;;;;;CAQX,MAAM,gCAAgB,IAAI,KAA2B;AACrD,MAAK,MAAM,SAAS,SAAS;AAC3B,MAAI,CAAC,cAAc,IAAI,MAAM,KAAK,CAChC,eAAc,IAAI,MAAM,MAAM,EAAE,CAAC;AAEnC,gBAAc,IAAI,MAAM,KAAK,CAAE,KAAK,MAAM;;CAG5C,MAAM,gBAAgB,CAAC,GAAG,cAAc,MAAM,CAAC;CAC/C,MAAM,eAAe,QAAQ,QAAO,MAAK,EAAE,iBAAiB,CAAC;AAG7D,KAAI,YAAY,MACd,QAAO;EACL,SAAS;EACT,SAAS;GACP,iBAAiB;GACjB,eAAe,QAAQ;GACvB,gBAAgB;GACjB;EACD,GAAI,cAAc,SAAS,IAAI,EAAE,gBAAgB,eAAe,GAAG,EAAE;EACrE,YAAY;GACV,GAAI,cAAc,SAAS,IAAI,CAAC,YAAY,cAAc,OAAO,0DAA0D,GAAG,EAAE;GAChI;GACA;GACD;EACF;CAIH,MAAM,cAAc,MAAM,kBAAkB,YAAY;AACxD,KAAI,YAAY,QACd,QAAO;EACL,SAAS;EACT,OAAO,mBAAmB,YAAY;EACtC,YAAY,CAAC,qEAAqE;EACnF;CAIH,MAAM,cAAc,MAAM,SAAS,MAAM;CACzC,MAAM,aAAa,gBAAgB,mBAAmB,YAAY;CAClE,MAAM,KAAK,MAAM,kBAAkB,aAAa,YAAY,EAAE,kBAAkB,UAAU,CAAC;CAE3F,MAAM,gBAA0B,EAAE;CAClC,IAAI,iBAAiB;CACrB,IAAI,eAAe;CACnB,MAAM,iBAAwE,EAAE;CAChF,MAAM,oBAA8D,EAAE;CACtE,MAAM,eAA8B,EAAE;AAEtC,KAAI;AACF,QAAM,GAAG,MAAM,OAAO,OAAO;AAC3B,QAAK,MAAM,CAAC,SAAS,gBAAgB,eAAe;IAClD,MAAM,UAAU,KAAK,IAAI,QAAQ;AAEjC,QAAI,CAAE,MAAM,WAAW,QAAQ,EAAG;AAChC,UAAK,MAAM,KAAK,YACd,gBAAe,KAAK;MAAE,MAAM;MAAS,MAAM,EAAE;MAAM,QAAQ;MAAkB,CAAC;AAEhF;;IAGF,MAAM,UAAU,MAAM,SAAS,QAAQ;AACvC,QAAI,YAAY,MAAM;AACpB,UAAK,MAAM,KAAK,YACd,gBAAe,KAAK;MAAE,MAAM;MAAS,MAAM,EAAE;MAAM,QAAQ;MAAmB,CAAC;AAEjF;;IAIF,MAAM,SAAS,CAAC,GAAG,YAAY,CAAC,UAAU,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK;IAEnE,MAAM,QAAQ,QAAQ,MAAM,KAAK;IACjC,IAAI,eAAe;AAEnB,SAAK,MAAM,SAAS,QAAQ;KAG1B,MAAM,oBADa,MAAM,MAAM,OAAO,MAAM,IACR,SAAS,IAAI,MAAM,UAAU,GAAG;KACpE,MAAM,qBAAqB,mBAAmB,aAAsB;AAGpE,SAAI,kBAAkB;MACpB,MAAM,YAAY,4BAA4B,SAAS,MAAM,gBAAgB,mBAAmB;AAChG,UAAI,UACF,mBAAkB,KAAK;OAAE,MAAM;OAAS,SAAS;OAAW,CAAC;;AAKjE,SADgB,kBAAkB,OAAO,MAAM,EAClC;AACX;AACA,qBAAe;WAEf,gBAAe,KAAK;MAAE,MAAM;MAAS,MAAM,MAAM;MAAM,QAAQ;MAAiD,CAAC;;IAKrH,MAAM,mBAAmB,IAAI,IAC3B,YACG,QAAO,MAAK,EAAE,iBAAiB,CAC/B,KAAI,MAAK,EAAE,iBAAkB,CACjC;AAED,QAAI,iBAAiB,OAAO,GAAG;KAC7B,MAAM,QAAQ,kBAAkB,OAAO,iBAAiB;AACxD,qBAAgB;AAChB,SAAI,QAAQ,EAAG,gBAAe;;AAGhC,QAAI,cAAc;KAChB,MAAM,aAAa,MAAM,KAAK,KAAK;AACnC,WAAM,UAAU,SAAS,WAAW;AACpC,mBAAc,KAAK,QAAQ;KAG3B,MAAM,cAAc,YAAY,SAAS,WAAW;AACpD,SAAI,YACF,cAAa,KAAK;MAAE,MAAM;MAAS,OAAO;MAAa,CAAC;;;AAM9D,SAAM,aAAa,IAAI;IACrB,MAAM;IACN,OAAO;IACP,QAAQ,OAAO,QAAQ;IACxB,CAAC;IACF;AAEF,MAAI,cAAc,WAAW,GAAG;AAC9B,SAAM,GAAG,SAAS;AAClB,UAAO;IACL,SAAS;IACT,SAAS;KACP,gBAAgB,EAAE;KAClB,iBAAiB;KACjB,iBAAiB;KACjB,eAAe;KACf,oBAAoB,kBAAkB,SAAS,IAAI,oBAAoB,KAAA;KACxE;IACD,YAAY,CAAC,iEAAiE;IAC/E;;EAGH,MAAM,YAAY,kCAAkC,YAAY,WAAW,cAAc,OAAO,UAAU,eAAe;AACzH,QAAM,GAAG,OAAO,UAAU;EAE1B,MAAM,YAAY;GAAE,QAAQ;GAAY,QAAQ;GAAkB,QAAQ;GAAI;AAC9E,MAAI;GACF,MAAM,YAAY,MAAM,GAAG,UAAU;AACrC,aAAU,SAAS,UAAU;AAC7B,aAAU,SAAS,UAAU;UACvB;AACN,aAAU,SAAS;YACX;AACR,SAAM,GAAG,SAAS;;AAGpB,SAAO;GACL,SAAS;GACT,SAAS;IACP,gBAAgB;IAChB,iBAAiB;IACjB,iBAAiB;IACjB,eAAe;IACf,oBAAoB,kBAAkB,SAAS,IAAI,oBAAoB,KAAA;IACvE,eAAe,aAAa,SAAS,IAAI,eAAe,KAAA;IACzD;GACD,GAAI,cAAc,SAAS,IAAI,EAAE,gBAAgB,eAAe,GAAG,EAAE;GACrE,KAAK;GACL,YAAY;IACV;IACA,eAAe,SAAS,IAAI,GAAG,eAAe,OAAO,sDAAsD;IAC3G,aAAa,SAAS,IAAI,YAAY,aAAa,OAAO,oEAAoE;IAC9H,cAAc,SAAS,IAAI,SAAS,cAAc,OAAO,4CAA4C;IACrG;IACD,CAAC,OAAO,QAAQ;GAClB;UACM,OAAO;AACd,QAAM,GAAG,SAAS;AAClB,QAAM;;;;;;;AAUV,SAAS,kBAAkB,OAAiB,OAA4B;CACtE,MAAM,EAAE,MAAM,WAAW,mBAAmB;CAC5C,MAAM,UAAU,OAAO;CAGvB,MAAM,cAAc,KAAK,IAAI,GAAG,UAAU,GAAG;CAC7C,MAAM,YAAY,KAAK,IAAI,MAAM,QAAQ,UAAU,GAAG;AAGtD,KAAI,WAAW,KAAK,UAAU,MAAM,QAAQ;EAC1C,MAAM,WAAW,cAAc,MAAM,UAAW,WAAW,eAAe;AAC1E,MAAI,aAAa,MAAM;AACrB,SAAM,WAAW;AACjB,UAAO;;;AAKX,MAAK,IAAI,IAAI,aAAa,IAAI,WAAW,KAAK;AAC5C,MAAI,MAAM,QAAS;EACnB,MAAM,WAAW,cAAc,MAAM,IAAK,WAAW,eAAe;AACpE,MAAI,aAAa,MAAM;AACrB,SAAM,KAAK;AACX,UAAO;;;AAIX,QAAO;;;;;;;;;;AAWT,SAAgB,cAAc,MAAc,UAAkB,eAAsC;AAGlG,MAAK,MAAM,SAAS;EAAC;EAAK;EAAK;EAAI,EAAE;EACnC,MAAM,SAAS,GAAG,QAAQ,WAAW;AACrC,MAAI,KAAK,SAAS,OAAO,CACvB,QAAO,KAAK,QAAQ,QAAQ,cAAc;;AAU9C,KAAI,KAAK,SAAS,IAAI,SAAS,GAAG,CAChC,QAAO,KAAK,QAAQ,IAAI,SAAS,IAAI,IAAI,cAAc,GAAG;AAK5D,KAAI,KAAK,SAAS,SAAS,EAAE;AAG3B,MADoB,iBAAiB,MAAM,SAAS,GAClC,EAChB,QAAO;EAMT,MAAM,MAAM,KAAK,QAAQ,SAAS;EAClC,MAAM,aAAa,MAAM,IAAI,KAAK,MAAM,KAAM;EAC9C,MAAM,YAAY,MAAM,SAAS,SAAS,KAAK,SAAS,KAAK,MAAM,SAAS,UAAW;EAEvF,MAAM,oBAAoB,SAAS,SAAS,KAAK,WAAW,SAAS,GAAI;EACzE,MAAM,kBAAkB,SAAS,SAAS,KAAK,WAAW,SAAS,SAAS,SAAS,GAAI;AAGzF,MAAI,qBAAqB,WAAW,WAAW,CAC7C,QAAO;AAGT,MAAI,mBAAmB,WAAW,UAAU,CAC1C,QAAO;AAGT,SAAO,KAAK,QAAQ,UAAU,cAAc;;AAG9C,QAAO;;;AAIT,SAAS,iBAAiB,KAAa,KAAqB;CAC1D,IAAI,QAAQ;CACZ,IAAI,MAAM;AACV,QAAO,OAAO,IAAI,SAAS,IAAI,QAAQ;EACrC,MAAM,MAAM,IAAI,QAAQ,KAAK,IAAI;AACjC,MAAI,QAAQ,GAAI;AAChB;AACA,QAAM,MAAM,IAAI;;AAElB,QAAO;;;AAIT,SAAS,WAAW,IAAqB;AACvC,QAAO,KAAK,KAAK,GAAG;;;;;;;AAQtB,SAAS,kBAAkB,OAAiB,SAA8B;CACxE,IAAI,QAAQ;CACZ,MAAM,kBAAkB,MAAM,KAAK,KAAK;CAGxC,IAAI,gBAAgB;CACpB,IAAI,oBAAoB;AACxB,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,UAAU,MAAM,GAAI,MAAM;AAChC,MAAI,mBAAmB;AACrB,mBAAgB;AAChB,OAAI,QAAQ,SAAS,IAAI,CAAE,qBAAoB;AAC/C;;AAEF,MAAI,QAAQ,WAAW,UAAU,IAAI,QAAQ,WAAW,UAAU,EAAE;AAClE,mBAAgB;AAEhB,OAAI,QAAQ,SAAS,IAAI,IAAI,CAAC,QAAQ,SAAS,IAAI,CACjD,qBAAoB;AAEtB;;AAGF,MAAI,iBAAiB,KAAK,QAAQ,SAAS,KAAK,CAAC,QAAQ,WAAW,KAAK,IAAI,CAAC,QAAQ,WAAW,KAAK,IAAI,CAAC,QAAQ,WAAW,IAAI,CAChI;;CAIJ,IAAI;AACJ,KAAI,iBAAiB,EACnB,YAAW,gBAAgB;MACtB;AAEL,aAAW;AACX,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,UAAU,MAAM,GAAI,MAAM;AAChC,OAAI,MAAM,KAAK,QAAQ,WAAW,KAAK,EAAE;AACvC,eAAW,IAAI;AACf;;AAEF,OAAI,YAAY,kBAAkB,YAAY,oBACzC,YAAY,kBAAkB,YAAY,oBAC1C,YAAY,mBAAmB,YAAY,qBAC3C,YAAY,mBAAmB,YAAY,mBAAiB;AAC/D,eAAW,IAAI;AACf;;AAEF,OAAI,WAAW,KAAK,QAAQ,SAAS,EAAG;AACxC,OAAI,aAAa,KAAK,QAAQ,SAAS,EAAG;;;CAG9C,MAAM,WAAqB,EAAE;AAE7B,MAAK,MAAM,OAAO,QAEhB,KAAI,CAAC,gBAAgB,SAAS,IAAI,EAAE;AAClC,WAAS,KAAK,IAAI;AAClB;;AAIJ,KAAI,SAAS,SAAS,EACpB,OAAM,OAAO,UAAU,GAAG,GAAG,SAAS;AAGxC,QAAO"}
@@ -1,6 +1,6 @@
1
1
  import { a as readJson, s as writeJson, t as contentrainDir } from "./fs-BbuGnVcG.mjs";
2
2
  import { t as readConfig } from "./config-CPlhDwsv.mjs";
3
- import { o as listModels, r as countEntries, s as readModel } from "./model-manager-BcWWu5l2.mjs";
3
+ import { o as listModels, r as countEntries, s as readModel } from "./model-manager-sfPdrVIJ.mjs";
4
4
  import { join } from "node:path";
5
5
  //#region src/core/context.ts
6
6
  async function readContext(projectRoot) {
@@ -43,4 +43,4 @@ async function writeContext(projectRoot, operation) {
43
43
  //#endregion
44
44
  export { writeContext as n, readContext as t };
45
45
 
46
- //# sourceMappingURL=context-yh3cuw5N.mjs.map
46
+ //# sourceMappingURL=context-CRBCTYPB.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"context-yh3cuw5N.mjs","names":[],"sources":["../src/core/context.ts"],"sourcesContent":["import type { ContextJson, ContextSource } from '@contentrain/types'\nimport { join } from 'node:path'\nimport { contentrainDir, readJson, writeJson } from '../util/fs.js'\nimport { listModels, readModel, countEntries } from './model-manager.js'\nimport { readConfig } from './config.js'\n\nexport async function readContext(projectRoot: string): Promise<ContextJson | null> {\n return readJson<ContextJson>(join(contentrainDir(projectRoot), 'context.json'))\n}\n\nexport async function writeContext(\n projectRoot: string,\n operation: { tool: string; model: string; locale?: string; entries?: string[] },\n): Promise<void> {\n const models = await listModels(projectRoot)\n const config = await readConfig(projectRoot)\n const locales = config?.locales.supported ?? ['en']\n\n // Calculate real entry count across all models\n let totalEntries: number | null = null\n try {\n let count = 0\n const fullModels = await Promise.all(\n models.map(m => readModel(projectRoot, m.id)),\n )\n const counts = await Promise.all(\n fullModels\n .filter((m): m is NonNullable<typeof m> => m !== null)\n .map(m => countEntries(projectRoot, m)),\n )\n for (const c of counts) {\n count += c.total\n }\n totalEntries = count\n } catch {\n // If counting fails, signal unknown rather than zero\n totalEntries = null\n }\n\n const source: ContextSource = process.env['CONTENTRAIN_SOURCE'] === 'mcp-studio'\n ? 'mcp-studio'\n : 'mcp-local'\n\n const context: ContextJson = {\n version: '1',\n lastOperation: {\n tool: operation.tool,\n model: operation.model,\n locale: operation.locale ?? config?.locales.default ?? 'en',\n entries: operation.entries,\n timestamp: new Date().toISOString(),\n source,\n },\n stats: {\n models: models.length,\n entries: totalEntries as number,\n locales,\n lastSync: new Date().toISOString(),\n },\n }\n\n await writeJson(join(contentrainDir(projectRoot), 'context.json'), context)\n}\n"],"mappings":";;;;;AAMA,eAAsB,YAAY,aAAkD;AAClF,QAAO,SAAsB,KAAK,eAAe,YAAY,EAAE,eAAe,CAAC;;AAGjF,eAAsB,aACpB,aACA,WACe;CACf,MAAM,SAAS,MAAM,WAAW,YAAY;CAC5C,MAAM,SAAS,MAAM,WAAW,YAAY;CAC5C,MAAM,UAAU,QAAQ,QAAQ,aAAa,CAAC,KAAK;CAGnD,IAAI,eAA8B;AAClC,KAAI;EACF,IAAI,QAAQ;EACZ,MAAM,aAAa,MAAM,QAAQ,IAC/B,OAAO,KAAI,MAAK,UAAU,aAAa,EAAE,GAAG,CAAC,CAC9C;EACD,MAAM,SAAS,MAAM,QAAQ,IAC3B,WACG,QAAQ,MAAkC,MAAM,KAAK,CACrD,KAAI,MAAK,aAAa,aAAa,EAAE,CAAC,CAC1C;AACD,OAAK,MAAM,KAAK,OACd,UAAS,EAAE;AAEb,iBAAe;SACT;AAEN,iBAAe;;CAGjB,MAAM,SAAwB,QAAQ,IAAI,0BAA0B,eAChE,eACA;CAEJ,MAAM,UAAuB;EAC3B,SAAS;EACT,eAAe;GACb,MAAM,UAAU;GAChB,OAAO,UAAU;GACjB,QAAQ,UAAU,UAAU,QAAQ,QAAQ,WAAW;GACvD,SAAS,UAAU;GACnB,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC;GACD;EACD,OAAO;GACL,QAAQ,OAAO;GACf,SAAS;GACT;GACA,2BAAU,IAAI,MAAM,EAAC,aAAa;GACnC;EACF;AAED,OAAM,UAAU,KAAK,eAAe,YAAY,EAAE,eAAe,EAAE,QAAQ"}
1
+ {"version":3,"file":"context-CRBCTYPB.mjs","names":[],"sources":["../src/core/context.ts"],"sourcesContent":["import type { ContextJson, ContextSource } from '@contentrain/types'\nimport { join } from 'node:path'\nimport { contentrainDir, readJson, writeJson } from '../util/fs.js'\nimport { listModels, readModel, countEntries } from './model-manager.js'\nimport { readConfig } from './config.js'\n\nexport async function readContext(projectRoot: string): Promise<ContextJson | null> {\n return readJson<ContextJson>(join(contentrainDir(projectRoot), 'context.json'))\n}\n\nexport async function writeContext(\n projectRoot: string,\n operation: { tool: string; model: string; locale?: string; entries?: string[] },\n): Promise<void> {\n const models = await listModels(projectRoot)\n const config = await readConfig(projectRoot)\n const locales = config?.locales.supported ?? ['en']\n\n // Calculate real entry count across all models\n let totalEntries: number | null = null\n try {\n let count = 0\n const fullModels = await Promise.all(\n models.map(m => readModel(projectRoot, m.id)),\n )\n const counts = await Promise.all(\n fullModels\n .filter((m): m is NonNullable<typeof m> => m !== null)\n .map(m => countEntries(projectRoot, m)),\n )\n for (const c of counts) {\n count += c.total\n }\n totalEntries = count\n } catch {\n // If counting fails, signal unknown rather than zero\n totalEntries = null\n }\n\n const source: ContextSource = process.env['CONTENTRAIN_SOURCE'] === 'mcp-studio'\n ? 'mcp-studio'\n : 'mcp-local'\n\n const context: ContextJson = {\n version: '1',\n lastOperation: {\n tool: operation.tool,\n model: operation.model,\n locale: operation.locale ?? config?.locales.default ?? 'en',\n entries: operation.entries,\n timestamp: new Date().toISOString(),\n source,\n },\n stats: {\n models: models.length,\n entries: totalEntries as number,\n locales,\n lastSync: new Date().toISOString(),\n },\n }\n\n await writeJson(join(contentrainDir(projectRoot), 'context.json'), context)\n}\n"],"mappings":";;;;;AAMA,eAAsB,YAAY,aAAkD;AAClF,QAAO,SAAsB,KAAK,eAAe,YAAY,EAAE,eAAe,CAAC;;AAGjF,eAAsB,aACpB,aACA,WACe;CACf,MAAM,SAAS,MAAM,WAAW,YAAY;CAC5C,MAAM,SAAS,MAAM,WAAW,YAAY;CAC5C,MAAM,UAAU,QAAQ,QAAQ,aAAa,CAAC,KAAK;CAGnD,IAAI,eAA8B;AAClC,KAAI;EACF,IAAI,QAAQ;EACZ,MAAM,aAAa,MAAM,QAAQ,IAC/B,OAAO,KAAI,MAAK,UAAU,aAAa,EAAE,GAAG,CAAC,CAC9C;EACD,MAAM,SAAS,MAAM,QAAQ,IAC3B,WACG,QAAQ,MAAkC,MAAM,KAAK,CACrD,KAAI,MAAK,aAAa,aAAa,EAAE,CAAC,CAC1C;AACD,OAAK,MAAM,KAAK,OACd,UAAS,EAAE;AAEb,iBAAe;SACT;AAEN,iBAAe;;CAGjB,MAAM,SAAwB,QAAQ,IAAI,0BAA0B,eAChE,eACA;CAEJ,MAAM,UAAuB;EAC3B,SAAS;EACT,eAAe;GACb,MAAM,UAAU;GAChB,OAAO,UAAU;GACjB,QAAQ,UAAU,UAAU,QAAQ,QAAQ,WAAW;GACvD,SAAS,UAAU;GACnB,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC;GACD;EACD,OAAO;GACL,QAAQ,OAAO;GACf,SAAS;GACT;GACA,2BAAU,IAAI,MAAM,EAAC,aAAa;GACnC;EACF;AAED,OAAM,UAAU,KAAK,eAAe,YAAY,EAAE,eAAe,EAAE,QAAQ"}
@@ -1,6 +1,6 @@
1
- import "../model-manager-BcWWu5l2.mjs";
2
- import "../context-yh3cuw5N.mjs";
1
+ import "../model-manager-sfPdrVIJ.mjs";
2
+ import "../context-CRBCTYPB.mjs";
3
3
  import "../branch-lifecycle-DB6sgeJi.mjs";
4
- import "../transaction-BsECnmL5.mjs";
5
- import { a as detectFileFramework, c as validatePatchPath, i as checkSyntax, n as applyExtract, o as replaceInLine, r as applyReuse, s as validateFrameworkExpression, t as PATCHABLE_EXTENSIONS } from "../apply-manager-CaTuu7Lj.mjs";
4
+ import "../transaction-DXkMulzr.mjs";
5
+ import { a as detectFileFramework, c as validatePatchPath, i as checkSyntax, n as applyExtract, o as replaceInLine, r as applyReuse, s as validateFrameworkExpression, t as PATCHABLE_EXTENSIONS } from "../apply-manager-BAeqrAHX.mjs";
6
6
  export { PATCHABLE_EXTENSIONS, applyExtract, applyReuse, checkSyntax, detectFileFramework, replaceInLine, validateFrameworkExpression, validatePatchPath };
@@ -1 +1 @@
1
- {"version":3,"file":"content-manager.d.mts","names":[],"sources":["../../src/core/content-manager.ts"],"mappings":";;;iBAcgB,YAAA,CAAa,IAAA;AAAA,iBAOb,eAAA,CAAgB,EAAA;AAAA,iBAKhB,cAAA,CAAe,MAAA,UAAgB,MAAA,EAAQ,iBAAA;AAAA,iBAUvC,gBAAA,CAAiB,OAAA;EAAoB,WAAA,EAAa,MAAA;EAAyB,IAAA;AAAA;AAAA,iBAuE3E,oBAAA,CAAqB,IAAA,EAAM,MAAA,mBAAyB,IAAA;AAAA,iBAsEpD,iBAAA,CAAkB,WAAA,UAAqB,KAAA,EAAO,eAAA;AAAA,iBAO9C,qBAAA,CAAsB,KAAA,EAAO,eAAA,GAAkB,cAAA;;iBAK/C,mBAAA,CAAoB,GAAA,UAAa,KAAA,EAAO,eAAA,EAAiB,MAAA;AAnKzE;AAAA,iBAiLgB,iBAAA,CAAkB,GAAA,UAAa,KAAA,EAAO,eAAA,EAAiB,MAAA,UAAgB,IAAA;AAAA,UAetE,YAAA;EACf,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA,EAAM,MAAA;AAAA;AAAA,UAGS,WAAA;EACf,MAAA;EACA,EAAA;EACA,IAAA;EACA,MAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,UAGe,QAAA;EACf,MAAA;EACA,MAAA,GAAS,MAAA;EACT,OAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,iBAsBoB,YAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,OAAA,EAAS,YAAA,IACT,MAAA,EAAQ,iBAAA,GACP,OAAA,CAAQ,WAAA;AAAA,iBAyGW,aAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,UAAA,GACL,OAAA;AAAA,iBAiHmB,WAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,MAAA,EAAQ,iBAAA,GACP,OAAA;AAAA,iBAyHmB,WAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA;EAAQ,MAAA;EAAgB,OAAA;EAAkB,IAAA;AAAA,IACzC,OAAA"}
1
+ {"version":3,"file":"content-manager.d.mts","names":[],"sources":["../../src/core/content-manager.ts"],"mappings":";;;iBAcgB,YAAA,CAAa,IAAA;AAAA,iBAOb,eAAA,CAAgB,EAAA;AAAA,iBAKhB,cAAA,CAAe,MAAA,UAAgB,MAAA,EAAQ,iBAAA;AAAA,iBAUvC,gBAAA,CAAiB,OAAA;EAAoB,WAAA,EAAa,MAAA;EAAyB,IAAA;AAAA;AAAA,iBAkK3E,oBAAA,CAAqB,IAAA,EAAM,MAAA,mBAAyB,IAAA;AAAA,iBA+CpD,iBAAA,CAAkB,WAAA,UAAqB,KAAA,EAAO,eAAA;AAAA,iBAO9C,qBAAA,CAAsB,KAAA,EAAO,eAAA,GAAkB,cAAA;;iBAK/C,mBAAA,CAAoB,GAAA,UAAa,KAAA,EAAO,eAAA,EAAiB,MAAA;AAvOzE;AAAA,iBAqPgB,iBAAA,CAAkB,GAAA,UAAa,KAAA,EAAO,eAAA,EAAiB,MAAA,UAAgB,IAAA;AAAA,UAetE,YAAA;EACf,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA,EAAM,MAAA;AAAA;AAAA,UAGS,WAAA;EACf,MAAA;EACA,EAAA;EACA,IAAA;EACA,MAAA;AAAA;AAAA,UAGe,UAAA;EACf,EAAA;EACA,IAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,UAGe,QAAA;EACf,MAAA;EACA,MAAA,GAAS,MAAA;EACT,OAAA;EACA,KAAA;EACA,MAAA;AAAA;AAAA,iBAsBoB,YAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,OAAA,EAAS,YAAA,IACT,MAAA,EAAQ,iBAAA,GACP,OAAA,CAAQ,WAAA;AAAA,iBAyGW,aAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,UAAA,GACL,OAAA;AAAA,iBAiHmB,WAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA,EAAM,QAAA,EACN,MAAA,EAAQ,iBAAA,GACP,OAAA;AAAA,iBAyHmB,WAAA,CACpB,WAAA,UACA,KAAA,EAAO,eAAA,EACP,IAAA;EAAQ,MAAA;EAAgB,OAAA;EAAkB,IAAA;AAAA,IACzC,OAAA"}
@@ -1,2 +1,2 @@
1
- import { S as writeContent, _ as resolveMdFilePath, b as validateLocale, d as listContent, f as parseFrontmatter, g as resolveLocaleStrategy, h as resolveJsonFilePath, m as resolveContentDir, p as readContent, u as deleteContent, v as serializeFrontmatter, x as validateSlug, y as validateEntryId } from "../model-manager-BcWWu5l2.mjs";
1
+ import { S as writeContent, _ as resolveMdFilePath, b as validateLocale, d as listContent, f as parseFrontmatter, g as resolveLocaleStrategy, h as resolveJsonFilePath, m as resolveContentDir, p as readContent, u as deleteContent, v as serializeFrontmatter, x as validateSlug, y as validateEntryId } from "../model-manager-sfPdrVIJ.mjs";
2
2
  export { deleteContent, listContent, parseFrontmatter, readContent, resolveContentDir, resolveJsonFilePath, resolveLocaleStrategy, resolveMdFilePath, serializeFrontmatter, validateEntryId, validateLocale, validateSlug, writeContent };
@@ -1,3 +1,3 @@
1
- import "../model-manager-BcWWu5l2.mjs";
2
- import { n as writeContext, t as readContext } from "../context-yh3cuw5N.mjs";
1
+ import "../model-manager-sfPdrVIJ.mjs";
2
+ import { n as writeContext, t as readContext } from "../context-CRBCTYPB.mjs";
3
3
  export { readContext, writeContext };
@@ -1,2 +1,2 @@
1
- import { a as fieldDefZodSchema, c as validateModelDefinition, i as deleteModel, l as writeModel, n as checkReferences, o as listModels, r as countEntries, s as readModel, t as FIELD_TYPE_ENUM } from "../model-manager-BcWWu5l2.mjs";
1
+ import { a as fieldDefZodSchema, c as validateModelDefinition, i as deleteModel, l as writeModel, n as checkReferences, o as listModels, r as countEntries, s as readModel, t as FIELD_TYPE_ENUM } from "../model-manager-sfPdrVIJ.mjs";
2
2
  export { FIELD_TYPE_ENUM, checkReferences, countEntries, deleteModel, fieldDefZodSchema, listModels, readModel, validateModelDefinition, writeModel };
@@ -1,3 +1,3 @@
1
- import "../model-manager-BcWWu5l2.mjs";
2
- import { t as validateProject } from "../validator-ku84AyFr.mjs";
1
+ import "../model-manager-sfPdrVIJ.mjs";
2
+ import { t as validateProject } from "../validator-C3lZqO3U.mjs";
3
3
  export { validateProject };
@@ -1,4 +1,4 @@
1
- import "../model-manager-BcWWu5l2.mjs";
2
- import "../context-yh3cuw5N.mjs";
3
- import { n as createTransaction, t as buildBranchName } from "../transaction-BsECnmL5.mjs";
1
+ import "../model-manager-sfPdrVIJ.mjs";
2
+ import "../context-CRBCTYPB.mjs";
3
+ import { n as createTransaction, t as buildBranchName } from "../transaction-DXkMulzr.mjs";
4
4
  export { buildBranchName, createTransaction };
package/dist/index.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import "./model-manager-BcWWu5l2.mjs";
3
- import "./context-yh3cuw5N.mjs";
2
+ import "./model-manager-sfPdrVIJ.mjs";
3
+ import "./context-CRBCTYPB.mjs";
4
4
  import "./branch-lifecycle-DB6sgeJi.mjs";
5
- import { t as createServer } from "./server-CTcDpxwW.mjs";
6
- import "./transaction-BsECnmL5.mjs";
7
- import "./validator-ku84AyFr.mjs";
5
+ import { t as createServer } from "./server-CC12z_xq.mjs";
6
+ import "./transaction-DXkMulzr.mjs";
7
+ import "./validator-C3lZqO3U.mjs";
8
8
  import "./graph-builder-fPrDDwnu.mjs";
9
9
  import "./scanner-BdTiv8sw.mjs";
10
10
  import "./tsx-parser-CYVyBWjn.mjs";
11
- import "./apply-manager-CaTuu7Lj.mjs";
11
+ import "./apply-manager-BAeqrAHX.mjs";
12
12
  import { resolve } from "node:path";
13
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
14
  //#region src/index.ts
@@ -77,6 +77,68 @@ function parseYamlValue(raw) {
77
77
  if (raw.startsWith("\"") && raw.endsWith("\"") || raw.startsWith("'") && raw.endsWith("'")) return raw.slice(1, -1);
78
78
  return raw;
79
79
  }
80
+ function yamlScalar(value) {
81
+ const str = String(value);
82
+ if (str.includes(":") || str.includes("#") || str.includes("{") || str.includes("}") || str.includes("[") || str.includes("]") || str.includes("*") || str.includes("&") || str.includes("!") || str.includes("|") || str.includes(">") || str.includes("'") || str.includes("\"") || str.includes("%") || str.includes("@") || str.includes("`") || str.startsWith("-") || str.startsWith("?") || str === "true" || str === "false" || str === "null" || str === "yes" || str === "no" || str === "") return `"${str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
83
+ return str;
84
+ }
85
+ function serializeYamlValue(value, indent) {
86
+ const pad = " ".repeat(indent);
87
+ if (value === null || value === void 0) return [];
88
+ if (typeof value === "object" && !Array.isArray(value)) {
89
+ const lines = [];
90
+ for (const [k, v] of Object.entries(value)) {
91
+ if (v === null || v === void 0) continue;
92
+ if (typeof v === "object" && !Array.isArray(v)) {
93
+ lines.push(`${pad}${k}:`);
94
+ lines.push(...serializeYamlValue(v, indent + 1));
95
+ } else if (Array.isArray(v)) {
96
+ lines.push(`${pad}${k}:`);
97
+ for (const item of v) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
98
+ const nested = serializeYamlValue(item, indent + 2);
99
+ if (nested.length > 0) {
100
+ lines.push(`${pad} - ${nested[0].trimStart()}`);
101
+ lines.push(...nested.slice(1));
102
+ }
103
+ } else lines.push(`${pad} - ${yamlScalar(item)}`);
104
+ } else lines.push(`${pad}${k}: ${yamlScalar(v)}`);
105
+ }
106
+ return lines;
107
+ }
108
+ if (Array.isArray(value)) {
109
+ const lines = [];
110
+ for (const item of value) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
111
+ const nested = serializeYamlValue(item, indent + 1);
112
+ if (nested.length > 0) {
113
+ lines.push(`${pad}- ${nested[0].trimStart()}`);
114
+ lines.push(...nested.slice(1));
115
+ }
116
+ } else lines.push(`${pad}- ${yamlScalar(item)}`);
117
+ return lines;
118
+ }
119
+ return [`${pad}${yamlScalar(value)}`];
120
+ }
121
+ function serializeYamlFields(data) {
122
+ const lines = [];
123
+ for (const [key, value] of Object.entries(data)) {
124
+ if (key === "body") continue;
125
+ if (value === null || value === void 0) continue;
126
+ if (typeof value === "object" && !Array.isArray(value)) {
127
+ lines.push(`${key}:`);
128
+ lines.push(...serializeYamlValue(value, 1));
129
+ } else if (Array.isArray(value)) {
130
+ lines.push(`${key}:`);
131
+ for (const item of value) if (typeof item === "object" && item !== null && !Array.isArray(item)) {
132
+ const nested = serializeYamlValue(item, 2);
133
+ if (nested.length > 0) {
134
+ lines.push(` - ${nested[0].trimStart()}`);
135
+ lines.push(...nested.slice(1));
136
+ }
137
+ } else lines.push(` - ${yamlScalar(item)}`);
138
+ } else lines.push(`${key}: ${yamlScalar(value)}`);
139
+ }
140
+ return lines;
141
+ }
80
142
  function serializeFrontmatter(data, body) {
81
143
  const trimmedBody = body.trimStart();
82
144
  if (trimmedBody.startsWith("---")) {
@@ -85,14 +147,7 @@ function serializeFrontmatter(data, body) {
85
147
  const bodyFmContent = trimmedBody.slice(3, endIdx).trim();
86
148
  const afterFm = trimmedBody.slice(endIdx + 3);
87
149
  const lines = ["---"];
88
- for (const [key, value] of Object.entries(data)) {
89
- if (key === "body") continue;
90
- if (value === null || value === void 0) continue;
91
- if (Array.isArray(value)) {
92
- lines.push(`${key}:`);
93
- for (const item of value) lines.push(` - ${String(item)}`);
94
- } else lines.push(`${key}: ${String(value)}`);
95
- }
150
+ lines.push(...serializeYamlFields(data));
96
151
  if (bodyFmContent) {
97
152
  const modelKeys = new Set(Object.keys(data));
98
153
  for (const line of bodyFmContent.split("\n")) {
@@ -111,14 +166,7 @@ function serializeFrontmatter(data, body) {
111
166
  }
112
167
  }
113
168
  const lines = ["---"];
114
- for (const [key, value] of Object.entries(data)) {
115
- if (key === "body") continue;
116
- if (value === null || value === void 0) continue;
117
- if (Array.isArray(value)) {
118
- lines.push(`${key}:`);
119
- for (const item of value) lines.push(` - ${String(item)}`);
120
- } else lines.push(`${key}: ${String(value)}`);
121
- }
169
+ lines.push(...serializeYamlFields(data));
122
170
  lines.push("---");
123
171
  lines.push("");
124
172
  if (body) {
@@ -375,10 +423,11 @@ async function listContent(projectRoot, model, opts, config) {
375
423
  const slug = f.replace(".md", "");
376
424
  const raw = await readText(join(cDir, f));
377
425
  if (!raw) continue;
378
- const { frontmatter } = parseFrontmatter(raw);
426
+ const { frontmatter, body } = parseFrontmatter(raw);
379
427
  entries.push({
380
428
  slug,
381
- frontmatter
429
+ frontmatter,
430
+ body
382
431
  });
383
432
  }
384
433
  } else if (strategy === "file") {
@@ -386,10 +435,11 @@ async function listContent(projectRoot, model, opts, config) {
386
435
  for (const slug of slugDirs) {
387
436
  const raw = await readText(join(cDir, slug, `${locale}.md`));
388
437
  if (!raw) continue;
389
- const { frontmatter } = parseFrontmatter(raw);
438
+ const { frontmatter, body } = parseFrontmatter(raw);
390
439
  entries.push({
391
440
  slug,
392
- frontmatter
441
+ frontmatter,
442
+ body
393
443
  });
394
444
  }
395
445
  } else if (strategy === "suffix") {
@@ -400,10 +450,11 @@ async function listContent(projectRoot, model, opts, config) {
400
450
  const slug = f.slice(0, -suffix.length);
401
451
  const raw = await readText(join(cDir, f));
402
452
  if (!raw) continue;
403
- const { frontmatter } = parseFrontmatter(raw);
453
+ const { frontmatter, body } = parseFrontmatter(raw);
404
454
  entries.push({
405
455
  slug,
406
- frontmatter
456
+ frontmatter,
457
+ body
407
458
  });
408
459
  }
409
460
  } else if (strategy === "directory") {
@@ -414,10 +465,11 @@ async function listContent(projectRoot, model, opts, config) {
414
465
  const slug = f.replace(".md", "");
415
466
  const raw = await readText(join(localeDir, f));
416
467
  if (!raw) continue;
417
- const { frontmatter } = parseFrontmatter(raw);
468
+ const { frontmatter, body } = parseFrontmatter(raw);
418
469
  entries.push({
419
470
  slug,
420
- frontmatter
471
+ frontmatter,
472
+ body
421
473
  });
422
474
  }
423
475
  } else {
@@ -427,10 +479,11 @@ async function listContent(projectRoot, model, opts, config) {
427
479
  const slug = f.replace(".md", "");
428
480
  const raw = await readText(join(cDir, f));
429
481
  if (!raw) continue;
430
- const { frontmatter } = parseFrontmatter(raw);
482
+ const { frontmatter, body } = parseFrontmatter(raw);
431
483
  entries.push({
432
484
  slug,
433
- frontmatter
485
+ frontmatter,
486
+ body
434
487
  });
435
488
  }
436
489
  }
@@ -960,4 +1013,4 @@ function validateModelDefinition(input) {
960
1013
  //#endregion
961
1014
  export { writeContent as S, resolveMdFilePath as _, fieldDefZodSchema as a, validateLocale as b, validateModelDefinition as c, listContent as d, parseFrontmatter as f, resolveLocaleStrategy as g, resolveJsonFilePath as h, deleteModel as i, writeModel as l, resolveContentDir as m, checkReferences as n, listModels as o, readContent as p, countEntries as r, readModel as s, FIELD_TYPE_ENUM as t, deleteContent as u, serializeFrontmatter as v, validateSlug as x, validateEntryId as y };
962
1015
 
963
- //# sourceMappingURL=model-manager-BcWWu5l2.mjs.map
1016
+ //# sourceMappingURL=model-manager-sfPdrVIJ.mjs.map