@contentrain/mcp 1.2.0 → 1.2.1

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.
@@ -1,6 +1,6 @@
1
1
  import "../serializer-CnHTtmc1.mjs";
2
2
  import "../fs-BnIQ4dKj.mjs";
3
3
  import "../scan-config-QgnJ1-52.mjs";
4
- import { n as scanSummary, t as scanCandidates } from "../scanner-CQOxARUU.mjs";
4
+ import { n as scanSummary, t as scanCandidates } from "../scanner-DUoXnmtI.mjs";
5
5
  import "../tsx-parser-BWiMGAIR.mjs";
6
6
  export { scanCandidates, scanSummary };
package/dist/index.mjs CHANGED
@@ -7,13 +7,13 @@ import "./model-manager-DW_fOGRM.mjs";
7
7
  import "./context-DIDt_TxX.mjs";
8
8
  import "./detect-DinOQAmK.mjs";
9
9
  import "./branch-lifecycle-wGJg25LT.mjs";
10
- import { t as createServer } from "./server-DfWRpzdP.mjs";
10
+ import { t as createServer } from "./server-Dtq8vqbK.mjs";
11
11
  import "./id-DV_T9Ic8.mjs";
12
12
  import "./transaction-C_Pvux78.mjs";
13
13
  import "./validator-SQYUV2JP.mjs";
14
14
  import "./scan-config-QgnJ1-52.mjs";
15
15
  import "./graph-builder-tM7zj-SX.mjs";
16
- import "./scanner-CQOxARUU.mjs";
16
+ import "./scanner-DUoXnmtI.mjs";
17
17
  import "./tsx-parser-BWiMGAIR.mjs";
18
18
  import "./apply-manager-BkPMfpsN.mjs";
19
19
  import { resolve } from "node:path";
@@ -9,10 +9,21 @@ const FILE_EXT_RE = /\.(png|jpg|jpeg|gif|svg|webp|ico|css|scss|less|js|ts|tsx|js
9
9
  const SVG_PATH_DATA_RE = /^[Mm][\d\s.,LHVCSQTAZlhvcsqtazmMzZ-]+$/;
10
10
  const SVG_VIEWBOX_RE = /^\d+(\.\d+)?\s+\d+(\.\d+)?\s+\d+(\.\d+)?\s+\d+(\.\d+)?$/;
11
11
  const I18N_KEY_RE = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
12
- const TECHNICAL_IDENTIFIER_RE = /^[a-z][a-z0-9_-]*$/;
12
+ const TECHNICAL_IDENTIFIER_RE = /^[_a-z][a-z0-9_-]*$/;
13
13
  const ERROR_CODE_RE = /^[A-Z][A-Z0-9_]+$/;
14
14
  const PLACEHOLDER_RE = /^\{\d+\}$|^\.{2,}$/;
15
15
  const CAMEL_CASE_RE = /^[a-z]+[A-Z]/;
16
+ const LOCALE_CODE_RE = /^[a-z]{2}[-_][A-Z]{2}$/;
17
+ const DIMENSION_RE = /^\d+[x×]\d+$/;
18
+ const REPEAT_CHAR_RE = /^(.)\1{3,}$/;
19
+ const MIME_TYPE_RE = /^(application|text|image|audio|video|multipart|font)\/[\w.+-]+$/;
20
+ const PASCAL_CASE_RE = /^[A-Z][a-z]+[A-Z]/;
21
+ const HTML_TARGETS = new Set([
22
+ "_blank",
23
+ "_self",
24
+ "_parent",
25
+ "_top"
26
+ ]);
16
27
  function isURLLike(str) {
17
28
  if (/^(https?|ftp|file|mailto|data):/.test(str)) return true;
18
29
  if (/^(\.\.?\/|\/|[A-Za-z]:\\)/.test(str)) return true;
@@ -158,6 +169,7 @@ function shouldSkip(str) {
158
169
  if (FILE_EXT_RE.test(v)) return "file_extension";
159
170
  if (v.startsWith("--")) return "cli_flag";
160
171
  if (I18N_KEY_RE.test(v)) return "i18n_key";
172
+ if (MIME_TYPE_RE.test(v)) return "mime_type";
161
173
  if (isURLLike(v)) return "url_path";
162
174
  if (isCssClassList(v)) return "css_class_list";
163
175
  if (isSingleCssUtility(v)) return "css_utility_token";
@@ -167,6 +179,10 @@ function shouldSkip(str) {
167
179
  if (str.context === "template_attribute" && SVG_GRAPHIC_ELEMENTS.has(str.parent)) return "svg_element_attr";
168
180
  if (v.startsWith("update:")) return "vue_emit_event";
169
181
  if (PLACEHOLDER_RE.test(v)) return "placeholder";
182
+ if (LOCALE_CODE_RE.test(v)) return "locale_code";
183
+ if (DIMENSION_RE.test(v)) return "dimension";
184
+ if (REPEAT_CHAR_RE.test(v)) return "repeat_chars";
185
+ if (HTML_TARGETS.has(v)) return "html_target";
170
186
  if (str.context === "function_argument" && I18N_FUNCTIONS.has(str.parent)) {
171
187
  if (/^[a-z][a-z0-9_.-]*$/.test(v)) return "i18n_function_arg";
172
188
  }
@@ -199,6 +215,8 @@ function calculateContentScore(str) {
199
215
  if (/[\u0080-\uFFFF]/.test(str.value)) score += .15;
200
216
  if (/^[A-Z]/.test(str.value) && /[a-z]/.test(str.value)) score += .1;
201
217
  if (CAMEL_CASE_RE.test(str.value)) score -= .3;
218
+ if (PASCAL_CASE_RE.test(str.value) && !str.value.includes(" ")) score -= .25;
219
+ if (/^[A-Z]{2,5}$/.test(str.value)) score -= .15;
202
220
  if (str.value.includes("/") && !str.value.includes(" ")) score -= .2;
203
221
  return Math.max(0, Math.min(1, score));
204
222
  }
@@ -583,4 +601,4 @@ async function scanSummary(projectRoot, options) {
583
601
  //#endregion
584
602
  export { scanSummary as n, scanCandidates as t };
585
603
 
586
- //# sourceMappingURL=scanner-CQOxARUU.mjs.map
604
+ //# sourceMappingURL=scanner-DUoXnmtI.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner-DUoXnmtI.mjs","names":[],"sources":["../src/core/ast-scanner/pre-filter.ts","../src/core/ast-scanner/index.ts","../src/core/scanner.ts"],"sourcesContent":["import type { ExtractedString } from './types.js'\n\n// ─── Pre-filter Result ───\n\nexport interface PreFilterResult {\n /** Strings that passed the pre-filter (candidates for agent) */\n candidates: ExtractedString[]\n /** Total number of strings removed by shouldSkip */\n skipped: number\n /** Total number of strings removed by low content score */\n lowConfidence: number\n /** Breakdown: skip reason → count */\n skipReasons: Record<string, number>\n}\n\n// ─── Value-based regexes ───\n\nconst PURE_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/\nconst HEX_COLOR_RE = /^#[0-9a-f]{3,8}$/i\nconst FILE_EXT_RE = /\\.(png|jpg|jpeg|gif|svg|webp|ico|css|scss|less|js|ts|tsx|jsx|json|md|html|xml|yaml|yml|woff|woff2|ttf|eot|mp4|webm|mp3|wav|pdf)$/i\nconst SVG_PATH_DATA_RE = /^[Mm][\\d\\s.,LHVCSQTAZlhvcsqtazmMzZ-]+$/\nconst SVG_VIEWBOX_RE = /^\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?$/\nconst I18N_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/\nconst TECHNICAL_IDENTIFIER_RE = /^[_a-z][a-z0-9_-]*$/\nconst ERROR_CODE_RE = /^[A-Z][A-Z0-9_]+$/\nconst PLACEHOLDER_RE = /^\\{\\d+\\}$|^\\.{2,}$/\nconst CAMEL_CASE_RE = /^[a-z]+[A-Z]/\nconst LOCALE_CODE_RE = /^[a-z]{2}[-_][A-Z]{2}$/\nconst DIMENSION_RE = /^\\d+[x×]\\d+$/\nconst REPEAT_CHAR_RE = /^(.)\\1{3,}$/\nconst MIME_TYPE_RE = /^(application|text|image|audio|video|multipart|font)\\/[\\w.+-]+$/\nconst PASCAL_CASE_RE = /^[A-Z][a-z]+[A-Z]/\n\nconst HTML_TARGETS = new Set(['_blank', '_self', '_parent', '_top'])\n\n// ─── URL / path detection (consolidated from legacy isNonContent) ───\n\nfunction isURLLike(str: string): boolean {\n if (/^(https?|ftp|file|mailto|data):/.test(str)) return true\n if (/^(\\.\\.?\\/|\\/|[A-Za-z]:\\\\)/.test(str)) return true\n if (/^['\"]?[@a-z][\\w-]*/.test(str.toLowerCase()) && !str.includes(' ') && (str.includes('/') || str.includes('.'))) {\n return true\n }\n return false\n}\n\n// ─── CSS / Tailwind detection ───\n\nconst TAILWIND_SEGMENT_RE = /^(?:bg-|text-|border-|flex|grid|p-|px-|py-|pt-|pb-|pl-|pr-|m-|mx-|my-|mt-|mb-|ml-|mr-|rounded|shadow|w-|h-|min-|max-|gap-|space-|items-|justify-|self-|overflow-|z-|opacity-|transition|duration-|ease-|animate-|font-|leading-|tracking-|decoration-|underline|line-through|uppercase|lowercase|capitalize|truncate|whitespace-|break-|sr-only|not-sr-only|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|group-|peer-|ring-|outline-|divide-|table-|col-|row-|aspect-|object-|inset-|top-|right-|bottom-|left-|translate-|rotate-|scale-|skew-|origin-|cursor-|select-|resize-|fill-|stroke-|block|inline|absolute|relative|fixed|sticky|static|float-|clear-|isolate|visible|invisible|grow|shrink|basis-|order-|place-)/\n\nfunction isCssClassList(value: string): boolean {\n const segments = value.trim().split(/\\s+/)\n if (segments.length < 2) return false\n let matched = 0\n for (const seg of segments) {\n if (TAILWIND_SEGMENT_RE.test(seg)) matched++\n }\n return matched / segments.length >= 0.5\n}\n\nfunction isSingleCssUtility(value: string): boolean {\n const trimmed = value.trim()\n if (trimmed.includes(' ')) return false\n return TAILWIND_SEGMENT_RE.test(trimmed)\n}\n\n// ─── SVG technical attributes ───\n\nconst SVG_TECHNICAL_ATTRIBUTES = new Set([\n 'd', 'viewBox', 'points', 'transform', 'pathLength',\n 'xmlns', 'preserveAspectRatio',\n 'stroke-linecap', 'stroke-linejoin', 'stroke-width',\n 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit',\n 'fill-rule', 'clip-rule',\n])\n\nconst SVG_GRAPHIC_ELEMENTS = new Set([\n 'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon',\n 'ellipse', 'g', 'defs', 'use', 'symbol', 'clipPath', 'mask',\n 'pattern', 'linearGradient', 'radialGradient', 'stop',\n 'marker', 'animate', 'animateTransform', 'image',\n])\n\n// ─── Known function names ───\n\nconst I18N_FUNCTIONS = new Set([\n 't', '$t', 'i18n', 'translate', 'formatMessage', 'msg',\n])\n\nconst EMIT_FUNCTIONS = new Set([\n 'emit', '$emit',\n])\n\n// ─── Translatable attribute whitelist (i18next-cli compatible + extended) ───\n\nconst TRANSLATABLE_ATTRIBUTES = new Set([\n // Standard HTML content attributes\n 'title', 'alt', 'placeholder', 'label', 'summary', 'caption',\n 'abbr', 'accesskey', 'content', 'description',\n // ARIA content\n 'aria-label', 'aria-description', 'aria-placeholder',\n 'aria-roledescription', 'aria-valuetext',\n // React Native accessibility (equivalent to aria-label)\n 'accessibilityLabel', 'accessibilityHint', 'accessibilityValue',\n // Common component content props\n 'heading', 'subheading', 'message', 'hint', 'tooltip',\n 'helper-text', 'error-message', 'success-message',\n 'confirm-text', 'cancel-text', 'empty-text', 'loading-text',\n 'no-data-text', 'no-results-text',\n])\n\n// ─── Translatable object property whitelist (i18next-cli compatible) ───\n\nconst TRANSLATABLE_PROPERTIES = new Set([\n 'label', 'title', 'description', 'text', 'message', 'placeholder',\n 'caption', 'summary', 'heading', 'subheading', 'subtitle', 'tooltip',\n 'hint', 'helpText', 'errorMessage', 'successMessage', 'name',\n])\n\n// ─── shouldSkip: Binary non-content detection ───\n\n/**\n * Determines if a string is definitely NOT user-visible content.\n * Returns skip reason if it should be filtered, null if it should proceed to scoring.\n *\n * Conservative for template_text/jsx_text (tag-between text is almost always content).\n * Aggressive for everything else (technical tokens, config values, framework artifacts).\n */\nexport function shouldSkip(str: ExtractedString): string | null {\n // ── Context-based rules (AST-determined, 100% accurate) ──\n\n if (str.context === 'import_path') return 'import_path'\n if (str.context === 'type_annotation') return 'type_annotation'\n if (str.context === 'css_class') return 'css_class'\n if (str.context === 'css_utility_call') return 'css_utility_call'\n if (str.context === 'console_call') return 'console_call'\n if (str.context === 'test_assertion') return 'test_assertion'\n if (str.context === 'switch_case') return 'switch_case'\n\n const v = str.value\n\n // ── Value-based rules (structural patterns) ──\n\n if (v.length <= 1) return 'single_char'\n if (/^\\s+$/.test(v)) return 'whitespace'\n if (PURE_NUMBER_RE.test(v)) return 'pure_number'\n if (HEX_COLOR_RE.test(v)) return 'hex_color'\n if (FILE_EXT_RE.test(v)) return 'file_extension'\n if (v.startsWith('--')) return 'cli_flag'\n\n // ── i18n key paths (checked before URL — both contain dots, but i18n keys are more specific) ──\n\n if (I18N_KEY_RE.test(v)) return 'i18n_key'\n\n // ── MIME types (checked before URL — both contain slash, MIME is more specific) ──\n\n if (MIME_TYPE_RE.test(v)) return 'mime_type'\n\n // ── URL/path patterns ──\n\n if (isURLLike(v)) return 'url_path'\n\n // ── CSS patterns ──\n\n if (isCssClassList(v)) return 'css_class_list'\n if (isSingleCssUtility(v)) return 'css_utility_token'\n\n // ── SVG patterns ──\n\n if (v.length > 3 && SVG_PATH_DATA_RE.test(v)) return 'svg_path_data'\n if (SVG_VIEWBOX_RE.test(v)) return 'svg_viewbox'\n if (str.parentProperty !== undefined && SVG_TECHNICAL_ATTRIBUTES.has(str.parentProperty)) return 'svg_technical_attr'\n if (str.context === 'template_attribute' && SVG_GRAPHIC_ELEMENTS.has(str.parent)) return 'svg_element_attr'\n\n // ── Framework event patterns ──\n\n if (v.startsWith('update:')) return 'vue_emit_event'\n\n // ── Placeholder / interpolation ──\n\n if (PLACEHOLDER_RE.test(v)) return 'placeholder'\n\n // ── Structural value patterns (100% non-content) ──\n\n if (LOCALE_CODE_RE.test(v)) return 'locale_code'\n if (DIMENSION_RE.test(v)) return 'dimension'\n if (REPEAT_CHAR_RE.test(v)) return 'repeat_chars'\n if (HTML_TARGETS.has(v)) return 'html_target'\n\n // ── Known function argument detection ──\n\n if (str.context === 'function_argument' && I18N_FUNCTIONS.has(str.parent)) {\n // i18n function args: filter lowercase identifiers (namespace/key), keep sentences\n if (/^[a-z][a-z0-9_.-]*$/.test(v)) return 'i18n_function_arg'\n }\n\n if (str.context === 'function_argument' && EMIT_FUNCTIONS.has(str.parent)) {\n return 'emit_event_arg'\n }\n\n // ── CRITICAL: Technical identifier detection (i18next-cli proven pattern) ──\n // Single lowercase ASCII word/kebab-case/snake_case < 30 chars → technical token\n // EXEMPT: template_text and jsx_text (tag-between text IS content, even lowercase)\n\n if (str.context !== 'template_text' && str.context !== 'jsx_text') {\n if (TECHNICAL_IDENTIFIER_RE.test(v) && v.length < 30) {\n return 'technical_identifier'\n }\n }\n\n // ── Error codes (SCREAMING_SNAKE_CASE with underscores) ──\n\n if (ERROR_CODE_RE.test(v) && v.includes('_') && v.length > 3) {\n return 'error_code'\n }\n\n return null\n}\n\n// ─── calculateContentScore: 0-1 confidence scoring ───\n\n/**\n * Calculates a content confidence score (0-1) for a string that passed shouldSkip.\n * Uses AST context metadata (our advantage over offset-based tools) combined with\n * value-based signals proven by i18next-cli.\n *\n * Base score: 0.5. Boosted/penalized by context and value characteristics.\n */\nexport function calculateContentScore(str: ExtractedString): number {\n let score = 0.5\n\n // ── Context signals (AST metadata advantage) ──\n\n // Template/JSX text = almost certainly user-visible content\n if (str.context === 'template_text' || str.context === 'jsx_text') {\n score += 0.3\n }\n\n // Content-bearing attribute (title, alt, placeholder, aria-label, etc.)\n if (str.context === 'template_attribute' || str.context === 'jsx_attribute') {\n if (str.parentProperty && TRANSLATABLE_ATTRIBUTES.has(str.parentProperty)) {\n score += 0.2\n } else {\n score -= 0.2 // Unknown/technical attribute\n }\n }\n\n // Content-bearing object property (message, label, description, etc.)\n if (str.context === 'object_property') {\n if (str.parentProperty && TRANSLATABLE_PROPERTIES.has(str.parentProperty)) {\n score += 0.25\n }\n }\n\n // ── Value signals (i18next-cli proven heuristics) ──\n\n // Multi-word strings are more likely content\n const wordCount = str.value.split(/\\s+/).length\n if (wordCount >= 3) score += 0.2\n else if (wordCount === 2) score += 0.1\n\n // Terminal punctuation suggests a sentence\n if (/[.!?:;]$/.test(str.value)) score += 0.1\n\n // Non-ASCII characters (Turkish, Chinese, Arabic, etc.) → almost certainly content\n if (/[\\u0080-\\uFFFF]/.test(str.value)) score += 0.15\n\n // Capitalized first letter with lowercase body (Dashboard, Kaydet, Settings)\n if (/^[A-Z]/.test(str.value) && /[a-z]/.test(str.value)) score += 0.1\n\n // camelCase → probably a technical identifier\n if (CAMEL_CASE_RE.test(str.value)) score -= 0.3\n\n // PascalCase with internal uppercase (PhGameController, GameCard) → likely component/icon name\n // Does NOT match single-uppercase words (Dashboard, Karadeniz, Settings)\n if (PASCAL_CASE_RE.test(str.value) && !str.value.includes(' ')) score -= 0.25\n\n // Short ALL-CAPS (TRY, GET, USD) → likely code/abbreviation, not content\n // In template_text the +0.3 context boost keeps real labels like \"FAQ\" above threshold\n if (/^[A-Z]{2,5}$/.test(str.value)) score -= 0.15\n\n // Contains slash without spaces → path-like\n if (str.value.includes('/') && !str.value.includes(' ')) score -= 0.2\n\n return Math.max(0, Math.min(1, score))\n}\n\n// ─── Public API ───\n\n/**\n * Two-phase pre-filter:\n * 1. shouldSkip(): Binary removal of definite non-content\n * 2. calculateContentScore(): 0-1 confidence scoring for ambiguous strings\n *\n * Returns candidates that passed both phases, with content scores attached.\n */\nexport function applyPreFilter(\n strings: ExtractedString[],\n minScore: number = 0.4,\n): PreFilterResult {\n const candidates: ExtractedString[] = []\n const skipReasons: Record<string, number> = {}\n let skipped = 0\n let lowConfidence = 0\n\n for (const str of strings) {\n // Phase 1: Binary skip\n const skipReason = shouldSkip(str)\n if (skipReason) {\n skipped++\n skipReasons[skipReason] = (skipReasons[skipReason] ?? 0) + 1\n continue\n }\n\n // Phase 2: Content scoring\n const contentScore = calculateContentScore(str)\n if (contentScore < minScore) {\n lowConfidence++\n skipReasons['low_confidence'] = (skipReasons['low_confidence'] ?? 0) + 1\n continue\n }\n\n // Attach score to the extraction for downstream use\n ;(str as ExtractedString & { contentScore: number }).contentScore = contentScore\n candidates.push(str)\n }\n\n return { candidates, skipped, lowConfidence, skipReasons }\n}\n","import type { ExtractedString } from './types.js'\nimport { applyPreFilter } from './pre-filter.js'\nimport { parseTsx } from './tsx-parser.js'\n\n// ─── Extension sets ───\n\nconst TSX_EXTENSIONS = new Set(['.tsx', '.jsx', '.ts', '.js', '.mjs'])\nconst VUE_EXTENSIONS = new Set(['.vue'])\nconst SVELTE_EXTENSIONS = new Set(['.svelte'])\nconst ASTRO_EXTENSIONS = new Set(['.astro'])\n\n// ─── Lazy-loaded parsers ───\n\n/**\n * Lazily import vue-parser. Returns undefined if not available\n * (@vue/compiler-sfc is an optional dependency).\n */\nasync function loadVueParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]> | ExtractedString[]) | undefined> {\n try {\n const mod = await import('./vue-parser.js')\n return mod.parseVue\n } catch {\n return undefined\n }\n}\n\n/**\n * Lazily import svelte-parser. Returns undefined if not available\n * (svelte is an optional dependency).\n */\nasync function loadSvelteParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]>) | undefined> {\n try {\n const mod = await import('./svelte-parser.js')\n return mod.parseSvelte\n } catch {\n return undefined\n }\n}\n\n/**\n * Lazily import astro-parser. Returns undefined if not available\n * (@astrojs/compiler is an optional dependency).\n */\nasync function loadAstroParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]>) | undefined> {\n try {\n const mod = await import('./astro-parser.js')\n return mod.parseAstro\n } catch {\n return undefined\n }\n}\n\n// ─── Regex fallback for unknown extensions ───\n\nconst SURROUNDING_MAX = 120\n\n/**\n * Minimal regex-based extractor for file types without AST parsers.\n * Extracts quoted strings and tag text — conservative, low accuracy.\n * Used as fallback when dedicated parsers are unavailable.\n */\nfunction extractWithRegex(content: string, _filePath: string): ExtractedString[] {\n const lines = content.split('\\n')\n const results: ExtractedString[] = []\n\n // Simple quoted string extraction\n const stringRe = /(['\"`])(?:(?!\\1|\\\\).|\\\\.)*?\\1/g\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const trimmed = line.trim()\n\n // Skip comments, imports\n if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) continue\n if (/^\\s*(import|export)\\s/.test(line)) continue\n\n let match: RegExpExecArray | null\n stringRe.lastIndex = 0\n\n while ((match = stringRe.exec(line)) !== null) {\n // Remove surrounding quotes\n const raw = match[0]\n const value = raw.slice(1, -1)\n if (value.length === 0) continue\n\n const surrounding = buildSurrounding(lines, i)\n\n results.push({\n value,\n line: i + 1,\n column: match.index + 1,\n context: 'other',\n scope: 'script',\n parent: '',\n surrounding,\n })\n }\n\n // Tag text extraction: >text<\n const tagTextRe = />([^<>{]+)</g\n let tagMatch: RegExpExecArray | null\n tagTextRe.lastIndex = 0\n\n while ((tagMatch = tagTextRe.exec(line)) !== null) {\n const text = tagMatch[1]!.trim()\n if (text.length === 0) continue\n if (/^[\\s\\W]*$/.test(text) && !/[a-zA-Z]/.test(text)) continue\n\n results.push({\n value: text,\n line: i + 1,\n column: tagMatch.index + 1,\n context: 'template_text',\n scope: 'template',\n parent: '',\n surrounding: buildSurrounding(lines, i),\n })\n }\n }\n\n return results\n}\n\nfunction buildSurrounding(lines: string[], lineIdx: number): string {\n const start = Math.max(0, lineIdx - 1)\n const end = Math.min(lines.length - 1, lineIdx + 1)\n\n const parts: string[] = []\n for (let i = start; i <= end; i++) {\n const line = lines[i]\n if (line !== undefined) {\n parts.push(line)\n }\n }\n\n const joined = parts.join('\\n')\n if (joined.length > SURROUNDING_MAX) {\n return joined.slice(0, SURROUNDING_MAX)\n }\n return joined\n}\n\n// ─── Public API ───\n\n/**\n * Extract strings from a source file with structural context.\n *\n * Routes to the correct parser based on file extension:\n * - .tsx/.jsx/.ts/.js/.mjs -> tsx-parser (AST-based)\n * - .vue -> vue-parser (lazy-loaded, AST-based)\n * - .svelte -> svelte-parser (lazy-loaded, AST-based)\n * - .astro -> astro-parser (lazy-loaded, AST-based)\n * - unknown -> empty array\n *\n * Falls back to regex extraction when the dedicated parser's\n * optional dependency is not installed.\n *\n * Applies structural pre-filter to remove 100% non-content strings.\n * Returns only candidates that should be sent to the agent.\n */\nexport async function extractStrings(\n filePath: string,\n content: string,\n ext: string,\n): Promise<ExtractedString[]> {\n const normalizedExt = ext.startsWith('.') ? ext : `.${ext}`\n let rawStrings: ExtractedString[]\n\n if (TSX_EXTENSIONS.has(normalizedExt)) {\n rawStrings = parseTsx(content, filePath)\n } else if (VUE_EXTENSIONS.has(normalizedExt)) {\n const parseVue = await loadVueParser()\n if (parseVue) {\n rawStrings = await parseVue(content, filePath)\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else if (SVELTE_EXTENSIONS.has(normalizedExt)) {\n const parseSvelte = await loadSvelteParser()\n if (parseSvelte) {\n try {\n rawStrings = await parseSvelte(content, filePath)\n } catch {\n // svelte/compiler not installed — fall back to regex\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else if (ASTRO_EXTENSIONS.has(normalizedExt)) {\n const parseAstro = await loadAstroParser()\n if (parseAstro) {\n try {\n rawStrings = await parseAstro(content, filePath)\n } catch {\n // @astrojs/compiler not installed — fall back to regex\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n return []\n }\n\n // Apply structural pre-filter\n const { candidates } = applyPreFilter(rawStrings)\n return candidates\n}\n\n// Re-export types for convenience\nexport type { ExtractedString, StructuralContext, PreFilterRule } from './types.js'\nexport { applyPreFilter, shouldSkip, calculateContentScore } from './pre-filter.js'\nexport type { PreFilterResult } from './pre-filter.js'\n","import type {\n StringContext,\n ScanCandidate,\n DuplicateGroup,\n ScanCandidatesResult,\n ScanSummaryResult,\n} from '@contentrain/types'\nimport { join, extname } from 'node:path'\nimport { readText } from '../util/fs.js'\nimport {\n autoDetectSourceDirs,\n discoverFiles,\n} from './scan-config.js'\nimport { extractStrings } from './ast-scanner/index.js'\nimport { calculateContentScore } from './ast-scanner/pre-filter.js'\nimport type { StructuralContext } from './ast-scanner/types.js'\n\n// ─── Options ───\n\nexport interface ScanOptions {\n paths?: string[]\n include?: string[]\n exclude?: string[]\n limit?: number\n offset?: number\n min_length?: number\n max_length?: number\n /** Minimum content confidence score (0-1). Default: 0.4 */\n min_score?: number\n}\n\n// ─── Constants ───\n\nconst DEFAULT_LIMIT = 50\nconst DEFAULT_OFFSET = 0\nconst DEFAULT_MIN_LENGTH = 2\nconst DEFAULT_MAX_LENGTH = 500\nconst DEFAULT_MIN_SCORE = 0.4\nconst SUMMARY_SAMPLE_SIZE = 10\n\n// ─── Context mapping: StructuralContext → StringContext ───\n\nconst CONTEXT_MAP: Record<StructuralContext, StringContext> = {\n 'template_text': 'template_text',\n 'template_attribute': 'template_attribute',\n 'jsx_text': 'jsx_text',\n 'jsx_attribute': 'jsx_attribute',\n 'variable_assignment': 'variable_assignment',\n 'object_property': 'object_value',\n 'function_argument': 'function_argument',\n 'array_element': 'other',\n 'enum_value': 'other',\n 'template_literal': 'other',\n 'switch_case': 'other',\n 'other': 'other',\n // Pre-filtered contexts should not reach here, but map them just in case\n 'import_path': 'other',\n 'type_annotation': 'other',\n 'css_class': 'other',\n 'css_utility_call': 'other',\n 'console_call': 'other',\n 'test_assertion': 'other',\n}\n\n// ─── Main: scanCandidates ───\n\nexport async function scanCandidates(\n projectRoot: string,\n options?: ScanOptions,\n): Promise<ScanCandidatesResult> {\n const limit = options?.limit ?? DEFAULT_LIMIT\n const offset = options?.offset ?? DEFAULT_OFFSET\n const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH\n const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH\n const minScore = options?.min_score ?? DEFAULT_MIN_SCORE\n\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // ─── Phase 1: Extract strings from all files (pre-filter applied inside extractStrings) ───\n\n const filePromises = files.map(async (relPath) => {\n const filePath = join(projectRoot, relPath)\n const content = await readText(filePath)\n if (!content) return { relPath, extractions: [] }\n\n const ext = extname(filePath)\n const extractions = await extractStrings(filePath, content, ext)\n return { relPath, extractions }\n })\n\n const fileResults = await Promise.all(filePromises)\n\n // ─── Phase 2: Score + length filter + deduplicate ───\n\n let rawStringsFound = 0\n let skippedCount = 0\n let lowConfidenceCount = 0\n const skipReasons: Record<string, number> = {}\n\n // Deduplication map: value → first candidate + all occurrences\n const uniqueMap = new Map<string, {\n candidate: ScanCandidate\n maxScore: number\n }>()\n\n // Also track duplicates for backward compatibility\n const dupeMap = new Map<string, Array<{ file: string; line: number }>>()\n\n for (const { relPath, extractions } of fileResults) {\n rawStringsFound += extractions.length\n\n for (const extraction of extractions) {\n // Length filter\n if (extraction.value.length < minLength || extraction.value.length > maxLength) {\n skippedCount++\n skipReasons['length_filter'] = (skipReasons['length_filter'] ?? 0) + 1\n continue\n }\n\n // shouldSkip was already applied inside extractStrings (via applyPreFilter).\n // But applyPreFilter uses default minScore. Here we apply the user-configured minScore\n // on the contentScore that was attached during pre-filtering.\n\n // Get the contentScore attached by applyPreFilter\n const contentScore: number = (extraction as unknown as { contentScore?: number }).contentScore ?? calculateContentScore(extraction)\n\n if (contentScore < minScore) {\n lowConfidenceCount++\n skipReasons['low_confidence'] = (skipReasons['low_confidence'] ?? 0) + 1\n continue\n }\n\n const mappedContext = CONTEXT_MAP[extraction.context]\n const loc = { file: relPath, line: extraction.line }\n\n // Track all occurrences for duplicates section\n if (!dupeMap.has(extraction.value)) {\n dupeMap.set(extraction.value, [])\n }\n dupeMap.get(extraction.value)!.push(loc)\n\n // Deduplication: keep first occurrence, accumulate locations\n if (!uniqueMap.has(extraction.value)) {\n uniqueMap.set(extraction.value, {\n candidate: {\n file: relPath,\n line: extraction.line,\n column: extraction.column,\n value: extraction.value,\n context: mappedContext,\n surrounding: extraction.surrounding,\n contentScore,\n occurrences: [loc],\n },\n maxScore: contentScore,\n })\n } else {\n const entry = uniqueMap.get(extraction.value)!\n entry.candidate.occurrences.push(loc)\n // Keep the highest score across occurrences\n if (contentScore > entry.maxScore) {\n entry.maxScore = contentScore\n entry.candidate.contentScore = contentScore\n entry.candidate.file = relPath\n entry.candidate.line = extraction.line\n entry.candidate.column = extraction.column\n entry.candidate.context = mappedContext\n entry.candidate.surrounding = extraction.surrounding\n }\n }\n }\n }\n\n // Build sorted unique candidates (highest score first)\n const allUniqueCandidates = [...uniqueMap.values()]\n .map(e => e.candidate)\n .toSorted((a, b) => b.contentScore - a.contentScore)\n\n // Build duplicate groups (only count >= 2), sorted by count descending\n const duplicates: DuplicateGroup[] = [...dupeMap.entries()]\n .filter(([, occurrences]) => occurrences.length >= 2)\n .map(([value, occurrences]) => ({ value, count: occurrences.length, occurrences }))\n .toSorted((a, b) => b.count - a.count)\n\n // Pagination on unique candidates\n const uniqueCount = allUniqueCandidates.length\n const paginated = allUniqueCandidates.slice(offset, offset + limit)\n const hasMore = uniqueCount > offset + limit\n\n return {\n candidates: paginated,\n duplicates,\n stats: {\n files_scanned: files.length,\n raw_strings_found: rawStringsFound,\n skipped: skippedCount,\n low_confidence: lowConfidenceCount,\n unique_candidates: uniqueCount,\n candidates_returned: paginated.length,\n has_more: hasMore,\n skip_reasons: skipReasons,\n },\n }\n}\n\n// ─── Main: scanSummary ───\n\nexport async function scanSummary(\n projectRoot: string,\n options?: ScanOptions,\n): Promise<ScanSummaryResult> {\n const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH\n const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH\n const minScore = options?.min_score ?? DEFAULT_MIN_SCORE\n\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // Group files by directory\n const dirFiles = new Map<string, string[]>()\n const fileTypes: Record<string, number> = {}\n\n for (const relPath of files) {\n const parts = relPath.split('/')\n const dir = parts.slice(0, -1).join('/') || '.'\n const ext = extname(relPath)\n\n if (!dirFiles.has(dir)) dirFiles.set(dir, [])\n dirFiles.get(dir)!.push(relPath)\n\n fileTypes[ext] = (fileTypes[ext] ?? 0) + 1\n }\n\n // Sample files per directory and count candidates\n const byDirectory: Record<string, { files: number; candidates: number }> = {}\n const freqMap = new Map<string, number>()\n let totalCandidatesEstimate = 0\n\n for (const [dir, dirFileList] of dirFiles) {\n const totalInDir = dirFileList.length\n const sampleFiles = dirFileList.slice(0, SUMMARY_SAMPLE_SIZE)\n let sampleCandidates = 0\n\n const samplePromises = sampleFiles.map(async (relPath) => {\n const filePath = join(projectRoot, relPath)\n const content = await readText(filePath)\n if (!content) return []\n\n const ext = extname(filePath)\n return extractStrings(filePath, content, ext)\n })\n\n const sampleResults = await Promise.all(samplePromises)\n\n for (const extractions of sampleResults) {\n for (const extraction of extractions) {\n // Apply same filters as scanCandidates\n if (extraction.value.length < minLength || extraction.value.length > maxLength) continue\n\n const contentScore: number = (extraction as unknown as { contentScore?: number }).contentScore ?? calculateContentScore(extraction)\n if (contentScore < minScore) continue\n\n sampleCandidates++\n\n // Track frequencies for top_repeated\n const prev = freqMap.get(extraction.value) ?? 0\n freqMap.set(extraction.value, prev + 1)\n }\n }\n\n // Estimate for full directory\n const avgPerFile = sampleFiles.length > 0 ? sampleCandidates / sampleFiles.length : 0\n const estimatedCandidates = Math.round(avgPerFile * totalInDir)\n\n byDirectory[dir] = {\n files: totalInDir,\n candidates: estimatedCandidates,\n }\n\n totalCandidatesEstimate += estimatedCandidates\n }\n\n // Top repeated strings\n const topRepeated = [...freqMap.entries()]\n .filter(([, count]) => count >= 2)\n .toSorted((a, b) => b[1] - a[1])\n .slice(0, 20)\n .map(([value, count]) => ({ value, count }))\n\n return {\n total_files: files.length,\n total_candidates_estimate: totalCandidatesEstimate,\n by_directory: byDirectory,\n top_repeated: topRepeated,\n sampling_note: `Based on first ${SUMMARY_SAMPLE_SIZE} files per directory. Counts are from sampled subset, not project-wide.`,\n file_types: fileTypes,\n }\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import { n as listTemplates, t as getTemplate } from "./templates-bRPzYq4M.mjs";
9
9
  import { i as mergeBranch, n as createTransaction, r as ensureContentBranch, t as buildBranchName } from "./transaction-C_Pvux78.mjs";
10
10
  import { t as validateProject } from "./validator-SQYUV2JP.mjs";
11
11
  import { t as buildGraph } from "./graph-builder-tM7zj-SX.mjs";
12
- import { n as scanSummary, t as scanCandidates } from "./scanner-CQOxARUU.mjs";
12
+ import { n as scanSummary, t as scanCandidates } from "./scanner-DUoXnmtI.mjs";
13
13
  import { n as applyExtract, r as applyReuse } from "./apply-manager-BkPMfpsN.mjs";
14
14
  import { join } from "node:path";
15
15
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -2012,7 +2012,7 @@ function registerBulkTools(server, projectRoot) {
2012
2012
  }
2013
2013
  //#endregion
2014
2014
  //#region package.json
2015
- var version = "1.2.0";
2015
+ var version = "1.2.1";
2016
2016
  //#endregion
2017
2017
  //#region src/server.ts
2018
2018
  function createServer(projectRoot) {
@@ -2032,4 +2032,4 @@ function createServer(projectRoot) {
2032
2032
  //#endregion
2033
2033
  export { createServer as t };
2034
2034
 
2035
- //# sourceMappingURL=server-DfWRpzdP.mjs.map
2035
+ //# sourceMappingURL=server-Dtq8vqbK.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-DfWRpzdP.mjs","names":["packageJson.version"],"sources":["../src/tools/annotations.ts","../src/tools/context.ts","../src/tools/setup.ts","../src/tools/model.ts","../src/tools/content.ts","../src/tools/workflow.ts","../src/tools/normalize.ts","../src/tools/bulk.ts","../package.json","../src/server.ts"],"sourcesContent":["import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'\n\n/**\n * Centralized tool annotations registry.\n * MCP clients use these hints to distinguish read-only vs. write vs. destructive tools.\n */\nexport const TOOL_ANNOTATIONS: Record<string, ToolAnnotations> = {\n // ─── Context (read-only) ───\n contentrain_status: {\n title: 'Project Status',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_describe: {\n title: 'Describe Model',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_describe_format: {\n title: 'Describe Format',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n\n // ─── Setup (write + git) ───\n contentrain_init: {\n title: 'Initialize Project',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n contentrain_scaffold: {\n title: 'Scaffold Template',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n\n // ─── Model (write + git) ───\n contentrain_model_save: {\n title: 'Save Model',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_model_delete: {\n title: 'Delete Model',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n },\n\n // ─── Content (mixed) ───\n contentrain_content_save: {\n title: 'Save Content',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_content_delete: {\n title: 'Delete Content',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n },\n contentrain_content_list: {\n title: 'List Content',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n\n // ─── Workflow (mixed) ───\n contentrain_validate: {\n title: 'Validate Project',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_submit: {\n title: 'Submit Branches',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_merge: {\n title: 'Merge Branch',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n\n // ─── Normalize (mixed) ───\n contentrain_scan: {\n title: 'Scan Source Code',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_apply: {\n title: 'Apply Normalize',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n\n // ─── Bulk (write + git) ───\n contentrain_bulk: {\n title: 'Bulk Operations',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\nimport { readConfig, readVocabulary } from '../core/config.js'\nimport { readContext } from '../core/context.js'\nimport { countEntries, listModels, readModel } from '../core/model-manager.js'\nimport { resolveContentDir, resolveJsonFilePath, resolveLocaleStrategy } from '../core/content-manager.js'\nimport { detectStack } from '../util/detect.js'\nimport { join } from 'node:path'\nimport { contentrainDir, pathExists } from '../util/fs.js'\nimport { checkBranchHealth, cleanupMergedBranches } from '../git/branch-lifecycle.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerContextTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_status ───\n server.tool(\n 'contentrain_status',\n 'Get full project status (read-only). Returns config, models, context. Do NOT manually edit .contentrain/ based on this output.',\n {},\n TOOL_ANNOTATIONS['contentrain_status']!,\n async () => {\n const crDir = contentrainDir(projectRoot)\n const initialized = await pathExists(join(crDir, 'config.json'))\n\n if (!initialized) {\n const detectedStack = await detectStack(projectRoot)\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n initialized: false,\n detected_stack: detectedStack,\n suggestion: 'Run contentrain_init to set up .contentrain/ structure',\n next_steps: ['Run contentrain_init'],\n }, null, 2),\n }],\n }\n }\n\n const config = await readConfig(projectRoot)\n const models = await listModels(projectRoot)\n const context = await readContext(projectRoot)\n const vocabulary = await readVocabulary(projectRoot)\n\n const errors: string[] = []\n if (!config) errors.push('.contentrain/config.json missing')\n\n const result: Record<string, unknown> = {\n initialized: true,\n config: config ? {\n stack: config.stack,\n workflow: config.workflow,\n locales: config.locales,\n domains: config.domains,\n repository: config.repository,\n } : null,\n models,\n context: context ? {\n lastOperation: context.lastOperation,\n stats: context.stats,\n } : null,\n vocabulary_size: vocabulary ? Object.keys(vocabulary.terms).length : 0,\n }\n\n // Branch lifecycle: lazy cleanup + health check (run BEFORE validation summary)\n const hasGitRepo = await pathExists(join(projectRoot, '.git'))\n if (hasGitRepo) {\n try {\n const cleanup = await cleanupMergedBranches(projectRoot)\n const health = await checkBranchHealth(projectRoot)\n result['branches'] = {\n total: health.total,\n merged: health.merged,\n unmerged: health.unmerged,\n cleaned_up: cleanup.deleted,\n }\n if (health.message) {\n result['branch_warning'] = health.message\n }\n if (health.blocked) {\n errors.push(health.message!)\n }\n } catch {\n // Branch health check is best-effort — don't fail status\n }\n }\n\n if (errors.length > 0) {\n result['validation'] = { errors: errors.length, warnings: 0, summary: errors }\n }\n\n const nextSteps: string[] = []\n if (models.length === 0) nextSteps.push('Create models with contentrain_model_save')\n if (errors.length > 0) nextSteps.push(`Fix ${errors.length} validation error(s)`)\n if (nextSteps.length === 0) nextSteps.push('Use contentrain_describe to inspect a model')\n result['next_steps'] = nextSteps\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n },\n )\n\n // ─── contentrain_describe ───\n server.tool(\n 'contentrain_describe',\n 'Get full schema of a single model (read-only). Do NOT manually create content files — use contentrain_content_save instead.',\n {\n model: z.string().describe('Model ID (e.g. \"blog-post\", \"hero\")'),\n include_sample: z.boolean().optional().default(false).describe('Include one sample entry'),\n locale: z.string().optional().describe('Locale for sample content (default: config default locale)'),\n },\n TOOL_ANNOTATIONS['contentrain_describe']!,\n async ({ model: modelId, include_sample, locale }) => {\n const modelDef = await readModel(projectRoot, modelId)\n if (!modelDef) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${modelId}\" not found` }) }],\n isError: true,\n }\n }\n\n const config = await readConfig(projectRoot)\n const effectiveLocale = locale ?? config?.locales.default ?? 'en'\n const stats = await countEntries(projectRoot, modelDef)\n\n const result: Record<string, unknown> = {\n id: modelDef.id,\n name: modelDef.name,\n kind: modelDef.kind,\n domain: modelDef.domain,\n i18n: modelDef.i18n,\n description: modelDef.description,\n fields: modelDef.fields,\n stats: { total_entries: stats.total, locales: stats.locales },\n }\n\n if (include_sample) {\n const sample = await getSample(projectRoot, modelDef, effectiveLocale)\n if (sample) result['sample'] = sample\n }\n\n // Stack-aware import snippet\n const stack = config?.stack ?? 'other'\n result['import_snippet'] = generateImportSnippet(modelDef, stack, effectiveLocale)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n },\n )\n\n // ─── contentrain_describe_format ───\n server.tool(\n 'contentrain_describe_format',\n 'Describes the Contentrain content file format for any language/platform. Returns a comprehensive specification of the file structure, JSON formats, markdown conventions, meta files, and locale strategies.',\n {},\n TOOL_ANNOTATIONS['contentrain_describe_format']!,\n async () => {\n const formatSpec = {\n overview: 'Contentrain stores content as plain JSON and Markdown files in a .contentrain/ directory at the project root. All files are committed to git.',\n directory_structure: {\n description: 'Standard layout of .contentrain/ directory',\n layout: {\n '.contentrain/config.json': 'Project configuration (stack, workflow, locales, domains)',\n '.contentrain/context.json': 'Last operation metadata (written by MCP after every write)',\n '.contentrain/vocabulary.json': 'Shared vocabulary/terms (optional)',\n '.contentrain/models/{model-id}.json': 'Model schema definitions',\n '.contentrain/content/{domain}/{model-id}/': 'Content files per model',\n '.contentrain/meta/{model-id}/': 'Metadata files per model (status, source, timestamps)',\n },\n },\n model_kinds: {\n collection: {\n description: 'Multiple entries stored as an object-map keyed by entry ID',\n storage_format: 'JSON object-map: { \"entry-id-1\": { ...fields }, \"entry-id-2\": { ...fields } }',\n note: 'Keys are sorted alphabetically for canonical output. Entry IDs are alphanumeric (1-40 chars, hyphens/underscores allowed).',\n example: '{ \"abc123\": { \"title\": \"Hello\", \"slug\": \"hello\" }, \"def456\": { \"title\": \"World\", \"slug\": \"world\" } }',\n },\n singleton: {\n description: 'Single entry (e.g. site settings, hero section)',\n storage_format: 'JSON object with field key-value pairs: { \"title\": \"My Site\", \"description\": \"...\" }',\n },\n document: {\n description: 'Markdown files with YAML-like frontmatter',\n storage_format: 'Markdown file: ---\\\\nslug: my-post\\\\ntitle: My Post\\\\n---\\\\n\\\\nMarkdown body content here.',\n frontmatter_rules: [\n 'Delimited by --- on its own line',\n 'Key-value pairs: key: value',\n 'Arrays: key:\\\\n - item1\\\\n - item2',\n 'Inline arrays: key: [item1, item2]',\n 'Values auto-parsed: true/false -> boolean, integers -> number, quoted -> string',\n '\"body\" key is reserved for the markdown content below the frontmatter',\n ],\n },\n dictionary: {\n description: 'Flat key-value string map (e.g. i18n translation files)',\n storage_format: 'JSON object: { \"greeting\": \"Hello\", \"farewell\": \"Goodbye\" }',\n note: 'All values must be strings.',\n },\n },\n meta_files: {\n description: 'Metadata is stored separately from content in .contentrain/meta/{model-id}/',\n collection_meta: 'Object-map: { \"entry-id\": { \"status\": \"draft\", \"source\": \"agent\", \"updated_by\": \"contentrain-mcp\" } }',\n singleton_meta: 'Single object: { \"status\": \"published\", \"source\": \"human\", \"updated_by\": \"user@example.com\" }',\n fields: {\n status: 'Content lifecycle status: \"draft\" | \"in_review\" | \"published\" | \"rejected\" | \"archived\"',\n source: 'Who created the entry: \"agent\" | \"human\" | \"import\"',\n updated_by: 'Author identifier string',\n approved_by: 'Optional: approver identifier',\n version: 'Optional: version string',\n publish_at: 'Optional: ISO 8601 date for scheduled publishing',\n expire_at: 'Optional: ISO 8601 date for scheduled expiry (must be after publish_at)',\n },\n },\n locale_strategies: {\n description: 'How localized content files are organized. Set per-model via locale_strategy field.',\n strategies: {\n file: {\n description: 'Default. Each locale is a separate file in the model directory.',\n json_pattern: '{content-dir}/{locale}.json (e.g. en.json, tr.json)',\n md_pattern: '{content-dir}/{slug}/{locale}.md',\n },\n suffix: {\n description: 'Locale appended to filename.',\n json_pattern: '{content-dir}/{model-id}.{locale}.json',\n md_pattern: '{content-dir}/{slug}.{locale}.md',\n },\n directory: {\n description: 'Locale as subdirectory.',\n json_pattern: '{content-dir}/{locale}/{model-id}.json',\n md_pattern: '{content-dir}/{locale}/{slug}.md',\n },\n none: {\n description: 'No locale in path (single-language or externally managed).',\n json_pattern: '{content-dir}/{model-id}.json',\n md_pattern: '{content-dir}/{slug}.md',\n },\n },\n },\n i18n_disabled: {\n description: 'When a model has i18n:false, locale is ignored in file paths.',\n json_pattern: '{content-dir}/data.json (always \"data.json\", no locale suffix)',\n md_pattern: '{content-dir}/{slug}.md (no locale in path)',\n },\n canonical_serialization: {\n description: 'All JSON files use deterministic serialization for clean git diffs.',\n rules: [\n 'Object keys sorted alphabetically',\n '2-space indentation',\n 'Trailing newline at end of file',\n 'No trailing commas',\n 'UTF-8 encoding',\n ],\n },\n content_path_override: {\n description: 'Models can specify a custom content_path to store content outside .contentrain/content/.',\n example: 'A model with content_path: \"content/blog\" stores files at {project-root}/content/blog/ instead of .contentrain/content/{domain}/{model-id}/',\n note: 'This is useful for frameworks that expect content in specific directories (e.g. Nuxt Content, Astro).',\n },\n reading_content: {\n steps: [\n '1. Read .contentrain/config.json to get locales and workflow settings',\n '2. Read .contentrain/models/{model-id}.json to get the schema and model kind',\n '3. Determine content directory: model.content_path or .contentrain/content/{domain}/{model-id}/',\n '4. Determine file path using locale_strategy and i18n flag',\n '5. Parse JSON (collection/singleton/dictionary) or Markdown frontmatter (document)',\n '6. Optionally read .contentrain/meta/{model-id}/{locale}.json for status/metadata',\n ],\n },\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(formatSpec, null, 2) }],\n }\n },\n )\n}\n\nasync function getSample(\n projectRoot: string,\n model: import('@contentrain/types').ModelDefinition,\n locale: string,\n): Promise<unknown> {\n const { readJson, readDir } = await import('../util/fs.js')\n const cDir = resolveContentDir(projectRoot, model)\n\n if (model.kind === 'collection') {\n const data = await readJson<Record<string, Record<string, unknown>>>(resolveJsonFilePath(cDir, model, locale))\n if (!data) return null\n const firstKey = Object.keys(data)[0]\n return firstKey ? { id: firstKey, ...data[firstKey] } : null\n }\n\n if (model.kind === 'singleton' || model.kind === 'dictionary') {\n return readJson(resolveJsonFilePath(cDir, model, locale))\n }\n\n if (model.kind === 'document') {\n const strategy = resolveLocaleStrategy(model)\n\n if (!model.i18n) {\n const entry = (await readDir(cDir)).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'file') {\n const slug = (await readDir(cDir))[0]\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'suffix') {\n const suffix = `.${locale}.md`\n const entry = (await readDir(cDir)).find(file => file.endsWith(suffix))\n const slug = entry?.slice(0, -suffix.length)\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'directory') {\n const entry = (await readDir(join(cDir, locale))).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n const entry = (await readDir(cDir)).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n return null\n}\n\nfunction buildContentPath(\n model: import('@contentrain/types').ModelDefinition,\n locale: string,\n): string {\n const strategy = model.locale_strategy ?? 'file'\n const baseDir = model.content_path ?? `.contentrain/content/${model.domain}/${model.id}`\n\n if (model.kind === 'document') {\n // When i18n is disabled, documents are flat {slug}.md\n if (!model.i18n) return `${baseDir}/{slug}.md`\n\n // Documents use directories — show the base path pattern\n switch (strategy) {\n case 'suffix': return `${baseDir}/{slug}.${locale}.md`\n case 'directory': return `${baseDir}/${locale}/{slug}.md`\n case 'none': return `${baseDir}/{slug}.md`\n case 'file':\n default: return `${baseDir}/{slug}/${locale}.md`\n }\n }\n\n // JSON-based models (collection, singleton, dictionary)\n // When i18n is disabled, always data.json\n if (!model.i18n) return `${baseDir}/data.json`\n\n switch (strategy) {\n case 'suffix': return `${baseDir}/${model.id}.${locale}.json`\n case 'directory': return `${baseDir}/${locale}/${model.id}.json`\n case 'none': return `${baseDir}/${model.id}.json`\n case 'file':\n default: return `${baseDir}/${locale}.json`\n }\n}\n\nfunction generateImportSnippet(\n model: import('@contentrain/types').ModelDefinition,\n stack: string,\n locale: string,\n): Record<string, string> {\n const path = buildContentPath(model, locale)\n\n const snippets: Record<string, string> = {\n generic: `import data from '${path}'`,\n }\n\n switch (stack) {\n case 'nuxt':\n snippets['nuxt'] = model.kind === 'document'\n ? `const { data } = await useAsyncData(() => queryContent('${model.domain}/${model.id}').locale('${locale}').find())`\n : `const { data } = await useFetch('/api/content/${model.id}?locale=${locale}')`\n break\n case 'next':\n snippets['next'] = `import data from '@/${path}'`\n break\n case 'astro':\n snippets['astro'] = `import data from '~/${path}'`\n break\n }\n\n return snippets\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainConfig, Vocabulary } from '@contentrain/types'\nimport { z } from 'zod'\nimport { join } from 'node:path'\nimport { readFile, writeFile, appendFile } from 'node:fs/promises'\nimport { simpleGit } from 'simple-git'\nimport { detectStack } from '../util/detect.js'\nimport { contentrainDir, ensureDir, pathExists, writeJson } from '../util/fs.js'\nimport { readConfig, readVocabulary } from '../core/config.js'\nimport { writeModel } from '../core/model-manager.js'\nimport { writeContent, type ContentEntry } from '../core/content-manager.js'\nimport { getTemplate, listTemplates } from '../templates/index.js'\nimport { createTransaction, buildBranchName, ensureContentBranch } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerSetupTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_init ───\n server.tool(\n 'contentrain_init',\n 'Initialize .contentrain/ structure. Changes are auto-committed to git — do NOT manually create .contentrain/ files.',\n {\n stack: z.string().optional().describe('Framework stack (nuxt, next, astro, svelte, react-vite, other). Auto-detected if omitted.'),\n locales: z.array(z.string()).optional().describe('Supported locales. Default: [\"en\"]'),\n domains: z.array(z.string()).optional().describe('Content domains. Default: auto-suggested'),\n },\n TOOL_ANNOTATIONS['contentrain_init']!,\n async ({ stack, locales, domains }) => {\n const crDir = contentrainDir(projectRoot)\n\n // Already initialized?\n if (await pathExists(join(crDir, 'config.json'))) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'Already initialized',\n suggestion: 'Use contentrain_status to see current state',\n }) }],\n isError: true,\n }\n }\n\n // Detect stack\n const detectedStack = stack ?? await detectStack(projectRoot)\n const supportedLocales = locales ?? ['en']\n const suggestedDomains = domains ?? suggestDomains(projectRoot)\n\n // Ensure .git exists\n const hasGit = await pathExists(join(projectRoot, '.git'))\n if (!hasGit) {\n await simpleGit(projectRoot).init()\n }\n\n // Branch health gate\n const initHealth = await checkBranchHealth(projectRoot)\n if (initHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: initHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Create git transaction\n const branch = buildBranchName('new', 'init')\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n const wtCrDir = join(wt, '.contentrain')\n\n // Create directories with .gitkeep so git tracks them\n await Promise.all(['models', 'content', 'meta'].map(async (dir) => {\n await ensureDir(join(wtCrDir, dir))\n await writeFile(join(wtCrDir, dir, '.gitkeep'), '', 'utf-8')\n }))\n\n // Write config\n const config: ContentrainConfig = {\n version: 1,\n stack: detectedStack as ContentrainConfig['stack'],\n workflow: 'auto-merge',\n locales: {\n default: supportedLocales[0] ?? 'en',\n supported: supportedLocales,\n },\n domains: suggestedDomains,\n }\n await writeJson(join(wtCrDir, 'config.json'), config)\n\n // Write empty vocabulary\n const vocabulary: Vocabulary = { version: 1, terms: {} }\n await writeJson(join(wtCrDir, 'vocabulary.json'), vocabulary)\n\n // Write initial context\n await writeJson(join(wtCrDir, 'context.json'), {\n version: '1',\n lastOperation: {\n tool: 'contentrain_init',\n model: '',\n locale: supportedLocales[0] ?? 'en',\n timestamp: new Date().toISOString(),\n source: process.env['CONTENTRAIN_SOURCE'] === 'mcp-studio' ? 'mcp-studio' : 'mcp-local',\n },\n stats: { models: 0, entries: 0, locales: supportedLocales, lastSync: new Date().toISOString() },\n })\n\n // Update .gitignore\n await updateGitignore(wt)\n })\n\n await tx.commit('[contentrain] init: project setup', {\n tool: 'contentrain_init',\n model: '',\n locale: supportedLocales[0] ?? 'en',\n })\n const gitResult = await tx.complete()\n\n // Ensure the contentrain branch exists after init\n await ensureContentBranch(projectRoot)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Project initialized and committed to git. Do NOT manually edit .contentrain/ files.',\n config_created: '.contentrain/config.json',\n detected_stack: detectedStack,\n detected_locales: supportedLocales,\n suggested_domains: suggestedDomains,\n files_created: [\n '.contentrain/config.json',\n '.contentrain/vocabulary.json',\n '.contentrain/context.json',\n ],\n directories_created: [\n '.contentrain/models/',\n '.contentrain/content/',\n '.contentrain/meta/',\n ],\n gitignore_updated: true,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n next_steps: [\n 'Create models with contentrain_model_save or contentrain_scaffold',\n 'Use contentrain_scan to find hardcoded strings',\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Init failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_scaffold ───\n server.tool(\n 'contentrain_scaffold',\n `Template-based project setup. Available templates: ${listTemplates().join(', ')}. Changes are auto-committed to git.`,\n {\n template: z.string().describe('Template ID: blog, landing, docs, ecommerce, saas, i18n, mobile'),\n locales: z.array(z.string()).optional().describe('Override locales'),\n with_sample_content: z.boolean().optional().default(true).describe('Include sample content (default: true)'),\n },\n TOOL_ANNOTATIONS['contentrain_scaffold']!,\n async ({ template: templateId, locales, with_sample_content }) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'Project not initialized. Run contentrain_init first.',\n }) }],\n isError: true,\n }\n }\n\n const tmpl = getTemplate(templateId)\n if (!tmpl) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Unknown template: \"${templateId}\". Available: ${listTemplates().join(', ')}`,\n }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const scaffoldHealth = await checkBranchHealth(projectRoot)\n if (scaffoldHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: scaffoldHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const effectiveLocales = locales ?? config.locales.supported\n const defaultLocale = effectiveLocales[0] ?? 'en'\n\n const branch = buildBranchName('new', `scaffold-${templateId}`, defaultLocale)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n const modelsCreated: Array<{ id: string; kind: string; fields: number }> = []\n let contentCreated = 0\n let vocabAdded = 0\n\n await tx.write(async (wt) => {\n // Write models\n await Promise.all(tmpl.models.map(async (model) => {\n await writeModel(wt, model)\n modelsCreated.push({\n id: model.id,\n kind: model.kind,\n fields: model.fields ? Object.keys(model.fields).length : 0,\n })\n }))\n\n // Write sample content via content pipeline (handles content_path, locale_strategy, meta)\n if (with_sample_content && tmpl.sample_content) {\n contentCreated = await writeSampleContent(wt, tmpl, effectiveLocales, config)\n }\n\n // Merge vocabulary\n if (tmpl.vocabulary) {\n vocabAdded = await mergeVocabulary(wt, tmpl.vocabulary, effectiveLocales)\n }\n\n })\n\n await tx.commit(`[contentrain] scaffold: ${templateId} (${defaultLocale})`, {\n tool: 'contentrain_scaffold',\n model: tmpl.models.map(m => m.id).join(', '),\n locale: defaultLocale,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Scaffold applied and committed to git. Do NOT manually edit .contentrain/ files.',\n models_created: modelsCreated,\n content_created: contentCreated,\n vocabulary_terms_added: vocabAdded,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n next_steps: [\n 'Customize models with contentrain_model_save',\n 'Add content with contentrain_content_save',\n 'Run contentrain_submit when ready',\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Scaffold failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n}\n\nfunction suggestDomains(_projectRoot: string): string[] {\n return ['marketing', 'blog', 'system']\n}\n\nasync function updateGitignore(projectRoot: string): Promise<void> {\n const gitignorePath = join(projectRoot, '.gitignore')\n const cacheEntry = '.contentrain/.cache/'\n\n if (await pathExists(gitignorePath)) {\n const content = await readFile(gitignorePath, 'utf-8')\n if (!content.includes(cacheEntry)) {\n await appendFile(gitignorePath, `\\n# Contentrain cache\\n${cacheEntry}\\n`)\n }\n } else {\n await writeFile(gitignorePath, `# Contentrain cache\\n${cacheEntry}\\n`, 'utf-8')\n }\n}\n\nasync function writeSampleContent(\n worktreePath: string,\n tmpl: import('@contentrain/types').ScaffoldTemplate,\n locales: string[],\n config: import('@contentrain/types').ContentrainConfig,\n): Promise<number> {\n if (!tmpl.sample_content) return 0\n\n let count = 0\n\n for (const model of tmpl.models) {\n const sampleData = tmpl.sample_content[model.id]\n if (!sampleData) continue\n\n for (const [localeOrKey, data] of Object.entries(sampleData)) {\n const entryMap = data as Record<string, Record<string, unknown>>\n let targetLocale: string\n if (localeOrKey === 'data') {\n targetLocale = locales[0] ?? 'en'\n } else if (locales.includes(localeOrKey)) {\n targetLocale = localeOrKey\n } else {\n continue\n }\n\n // Convert template payload to ContentEntry[] based on model kind\n let entries: ContentEntry[]\n\n if (model.kind === 'singleton' || model.kind === 'dictionary') {\n // Singleton/dictionary: the entire locale object IS the data for one entry\n entries = [{ locale: targetLocale, data: entryMap as Record<string, unknown> }]\n } else if (model.kind === 'document') {\n // Document: keys are slugs, values are frontmatter objects\n entries = Object.entries(entryMap).map(([slug, fields]) => ({\n slug,\n locale: targetLocale,\n data: fields,\n }))\n } else {\n // Collection: keys are entry IDs, values are field objects\n entries = Object.entries(entryMap).map(([id, fields]) => ({\n id,\n locale: targetLocale,\n data: fields,\n }))\n }\n\n await writeContent(worktreePath, model, entries, config)\n count += entries.length\n }\n }\n\n return count\n}\n\nasync function mergeVocabulary(\n projectRoot: string,\n newTerms: Record<string, Record<string, string>>,\n _locales: string[],\n): Promise<number> {\n const vocabPath = join(contentrainDir(projectRoot), 'vocabulary.json')\n const existing = await readVocabulary(projectRoot)\n const vocab: Vocabulary = existing ?? { version: 1, terms: {} }\n\n let added = 0\n for (const [key, translations] of Object.entries(newTerms)) {\n if (!(key in vocab.terms)) {\n added++\n }\n vocab.terms[key] = { ...vocab.terms[key], ...translations }\n }\n\n await writeJson(vocabPath, vocab)\n return added\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ModelDefinition } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\n\nimport { resolveContentDir, resolveJsonFilePath, resolveMdFilePath } from '../core/content-manager.js'\nimport { checkReferences, deleteModel, readModel, writeModel, validateModelDefinition, fieldDefZodSchema } from '../core/model-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\n// Shared field definition schema — single source of truth with normalize extract\nconst fieldDefSchema = fieldDefZodSchema\n\nexport function registerModelTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_model_save ───\n server.tool(\n 'contentrain_model_save',\n 'Create or update a model definition. Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n id: z.string().describe('Model ID (kebab-case, e.g. \"blog-post\")'),\n name: z.string().describe('Human-readable name'),\n kind: z.enum(['singleton', 'collection', 'document', 'dictionary']).describe('Model kind'),\n domain: z.string().describe('Content domain (e.g. \"blog\", \"marketing\", \"system\")'),\n i18n: z.boolean().describe('Whether this model supports localization'),\n description: z.string().optional().describe('Model description'),\n fields: fieldDefSchema.optional().describe('Field definitions (not needed for dictionary)'),\n content_path: z.string().optional().describe('Framework-relative path for content files (e.g. \"content/blog\", \"locales\"). When set, content is written here instead of .contentrain/content/'),\n locale_strategy: z.enum(['file', 'suffix', 'directory', 'none']).optional().describe('How locale is encoded in file names. Default: \"file\"'),\n },\n TOOL_ANNOTATIONS['contentrain_model_save']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n // Validate\n const errors = validateModel(input)\n if (errors.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Validation failed', details: errors }) }],\n isError: true,\n }\n }\n\n // Reject invalid locale_strategy + i18n combinations\n if (input.locale_strategy === 'none' && input.i18n !== false) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'locale_strategy \"none\" requires i18n:false. The \"none\" strategy stores a single file without locale encoding, which is incompatible with multi-locale content. Use \"file\", \"suffix\", or \"directory\" for i18n models.',\n }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const existing = await readModel(projectRoot, input.id)\n const action = existing ? 'updated' : 'created'\n\n const model: ModelDefinition = {\n id: input.id,\n name: input.name,\n kind: input.kind,\n domain: input.domain,\n i18n: input.i18n,\n description: input.description,\n fields: input.fields as ModelDefinition['fields'],\n content_path: input.content_path,\n locale_strategy: input.locale_strategy,\n }\n\n const branch = buildBranchName('model', input.id)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n await writeModel(wt, model)\n })\n\n await tx.commit(`[contentrain] ${action}: ${input.id}`, { tool: 'contentrain_model_save', model: input.id })\n const gitResult = await tx.complete()\n\n const defaultLocale = config.locales.default\n // Build accurate content path using path resolvers\n const contentDir = resolveContentDir(projectRoot, model)\n const contentPath = model.content_path ?? `.contentrain/content/${input.domain}/${input.id}`\n let exampleFilePath: string\n if (model.kind === 'document') {\n exampleFilePath = resolveMdFilePath(contentDir, model, defaultLocale, '{slug}')\n } else {\n exampleFilePath = resolveJsonFilePath(contentDir, model, defaultLocale)\n }\n // Make the path relative for display\n const displayPath = exampleFilePath.replace(projectRoot + '/', '').replace(projectRoot, '')\n const importSnippet: Record<string, string> = {\n generic: `import data from '${displayPath}'`,\n }\n if (config.stack === 'nuxt') {\n importSnippet['nuxt'] = model.kind === 'document'\n ? `const { data } = await useAsyncData(() => queryContent('${model.domain}/${model.id}').locale('${defaultLocale}').find())`\n : `const { data } = await useFetch('/api/content/${model.id}?locale=${defaultLocale}')`\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Model saved and committed to git. Do NOT manually edit .contentrain/ files.',\n action,\n model: input.id,\n validation: { valid: true, errors: [] },\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n content_path: contentPath + '/',\n example_file: displayPath,\n import_snippet: importSnippet,\n next_steps: ['Add content with contentrain_content_save'],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Model save failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_model_delete ───\n server.tool(\n 'contentrain_model_delete',\n 'Delete a model and its content/meta. Changes are auto-committed to git — do NOT manually edit .contentrain/ files.',\n {\n model: z.string().describe('Model ID to delete'),\n confirm: z.literal(true).describe('Must be true to confirm deletion'),\n },\n TOOL_ANNOTATIONS['contentrain_model_delete']!,\n async ({ model: modelId }) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n // Check model exists\n const existing = await readModel(projectRoot, modelId)\n if (!existing) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${modelId}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const deleteHealth = await checkBranchHealth(projectRoot)\n if (deleteHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: deleteHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Check references\n const refs = await checkReferences(projectRoot, modelId)\n if (refs.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n deleted: false,\n error: 'REFERENCED_MODEL',\n referenced_by: refs,\n next_steps: ['Remove relation fields from referencing models first'],\n }, null, 2) }],\n }\n }\n\n const branch = buildBranchName('model', modelId)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let filesRemoved: string[] = []\n\n await tx.write(async (wt) => {\n filesRemoved = await deleteModel(wt, modelId)\n })\n\n await tx.commit(`[contentrain] delete: ${modelId}`, { tool: 'contentrain_model_delete', model: modelId })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Model deleted and committed to git. Do NOT manually edit .contentrain/ files.',\n deleted: true,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n files_removed: filesRemoved,\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Delete failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n}\n\n// validateModel is now validateModelDefinition from model-manager.ts (shared)\nconst validateModel = validateModelDefinition\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { readModel } from '../core/model-manager.js'\nimport { writeContent, deleteContent, listContent } from '../core/content-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { validateProject } from '../core/validator.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerContentTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_content_save ───\n server.tool(\n 'contentrain_content_save',\n 'Save content entries. Entry format varies by model kind: DICTIONARY — provide \"locale\" and \"data\" (flat key-value, all string values); \"id\" and \"slug\" are ignored; data keys are the identities. COLLECTION — provide \"locale\" and \"data\"; \"id\" is optional (auto-generated if omitted); \"slug\" is ignored. DOCUMENT — provide \"slug\" (required), \"locale\", and \"data\"; use the \"body\" key inside data for markdown content. SINGLETON — provide only \"locale\" and \"data\". Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n model: z.string().describe('Model ID'),\n entries: z.array(z.object({\n id: z.string().optional().describe('Entry ID (collection only, auto-generated if omitted)'),\n slug: z.string().optional().describe('Slug (document only)'),\n locale: z.string().optional().describe('Locale code (defaults to config default)'),\n data: z.record(z.string(), z.unknown()).describe('Content data. For documents, include \"body\" key for markdown content.'),\n publish_at: z.string().optional().describe('ISO 8601 date for scheduled publishing (stored in meta)'),\n expire_at: z.string().optional().describe('ISO 8601 date for scheduled expiry (stored in meta, must be after publish_at)'),\n })).describe('Content entries to save'),\n },\n TOOL_ANNOTATIONS['contentrain_content_save']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Validate locales and scheduling fields before starting transaction\n for (const entry of input.entries) {\n if (entry.locale && !config.locales.supported.includes(entry.locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Locale \"${entry.locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n\n // Validate publish_at / expire_at\n if (entry.publish_at !== undefined) {\n const d = new Date(entry.publish_at)\n if (Number.isNaN(d.getTime())) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Invalid publish_at date: \"${entry.publish_at}\". Must be a valid ISO 8601 date string.`,\n }) }],\n isError: true,\n }\n }\n }\n if (entry.expire_at !== undefined) {\n const d = new Date(entry.expire_at)\n if (Number.isNaN(d.getTime())) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Invalid expire_at date: \"${entry.expire_at}\". Must be a valid ISO 8601 date string.`,\n }) }],\n isError: true,\n }\n }\n }\n if (entry.publish_at !== undefined && entry.expire_at !== undefined) {\n if (new Date(entry.expire_at) <= new Date(entry.publish_at)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `expire_at (\"${entry.expire_at}\") must be after publish_at (\"${entry.publish_at}\").`,\n }) }],\n isError: true,\n }\n }\n }\n\n // Merge scheduling fields into data so meta-manager picks them up\n if (entry.publish_at !== undefined) entry.data['publish_at'] = entry.publish_at\n if (entry.expire_at !== undefined) entry.data['expire_at'] = entry.expire_at\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('content', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let results: Awaited<ReturnType<typeof writeContent>>\n\n let entryIds: string[] = []\n\n await tx.write(async (wt) => {\n results = await writeContent(wt, model, input.entries, config)\n entryIds = results!.map(r => r.id ?? r.slug ?? r.locale).filter(Boolean) as string[]\n })\n\n await tx.commit(`[contentrain] content: ${input.model}`, {\n tool: 'contentrain_content_save',\n model: input.model,\n locale: input.entries[0]?.locale,\n entries: entryIds,\n })\n const gitResult = await tx.complete()\n\n // Run real validation after save — don't fake it\n const validationResult = await validateProject(projectRoot, { model: input.model })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Content saved and committed to git. Do NOT manually edit .contentrain/ files.',\n results: results!,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n validation: {\n valid: validationResult.valid,\n errors: validationResult.issues.filter(i => i.severity === 'error').map(i => i.message),\n },\n context_updated: true,\n next_steps: [\n ...(!validationResult.valid ? ['WARNING: Content has validation errors — run contentrain_validate for details'] : []),\n ...(model.kind === 'collection'\n ? ['Use contentrain_content_list to verify', 'Add more entries or publish']\n : ['Use contentrain_content_list to verify']),\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Content save failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_content_delete ───\n server.tool(\n 'contentrain_content_delete',\n 'Delete content entries. For dictionaries, use \"keys\" to remove specific keys (omit to delete entire locale file). Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n model: z.string().describe('Model ID'),\n id: z.string().optional().describe('Entry ID (collection)'),\n slug: z.string().optional().describe('Slug (document)'),\n locale: z.string().optional().describe('Locale code'),\n keys: z.array(z.string()).optional().describe('Dictionary only: specific keys to remove. Omit to delete entire locale file.'),\n confirm: z.literal(true).describe('Must be true to confirm deletion'),\n },\n TOOL_ANNOTATIONS['contentrain_content_delete']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const deleteHealth = await checkBranchHealth(projectRoot)\n if (deleteHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: deleteHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('content', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let removed: string[] = []\n\n await tx.write(async (wt) => {\n removed = await deleteContent(wt, model, {\n id: input.id,\n slug: input.slug,\n locale: input.locale,\n keys: input.keys,\n })\n })\n\n await tx.commit(`[contentrain] delete content: ${input.model}`, {\n tool: 'contentrain_content_delete',\n model: input.model,\n locale: input.locale,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Content deleted and committed to git. Do NOT manually edit .contentrain/ files.',\n deleted: true,\n files_removed: removed,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Delete failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_content_list ───\n server.tool(\n 'contentrain_content_list',\n 'List content entries (read-only). Returns data from .contentrain/ — do NOT manually create or modify content files.',\n {\n model: z.string().describe('Model ID'),\n locale: z.string().optional().describe('Locale code (defaults to config default)'),\n filter: z.record(z.string(), z.unknown()).optional().describe('Filter criteria (collection only)'),\n resolve: z.boolean().optional().describe('Resolve relation fields to actual data'),\n limit: z.number().optional().describe('Max entries to return'),\n offset: z.number().optional().describe('Skip N entries'),\n },\n TOOL_ANNOTATIONS['contentrain_content_list']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n try {\n const result = await listContent(projectRoot, model, {\n locale: input.locale,\n filter: input.filter as Record<string, unknown>,\n resolve: input.resolve,\n limit: input.limit,\n offset: input.offset,\n }, config)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `List failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport { z } from 'zod'\nimport { simpleGit } from 'simple-git'\nimport { validateProject } from '../core/validator.js'\nimport { readConfig } from '../core/config.js'\nimport { createTransaction, buildBranchName, mergeBranch } from '../git/transaction.js'\nimport { checkBranchHealth, cleanupMergedBranches } from '../git/branch-lifecycle.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerWorkflowTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_validate ───\n server.tool(\n 'contentrain_validate',\n 'Validate project content against model schemas. Detects required field violations, type mismatches, broken relations, secret leaks, i18n parity issues, and more. If fix:true, auto-fixes structural issues (canonical sort, orphan meta, missing locale files) — do NOT manually edit .contentrain/ files.',\n {\n model: z.string().optional().describe('Model ID to validate (omit for all models)'),\n fix: z.boolean().optional().describe('Auto-fix structural issues (canonical sort, orphan meta, missing locale files). Default: false'),\n },\n TOOL_ANNOTATIONS['contentrain_validate']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n try {\n let result: Awaited<ReturnType<typeof validateProject>> | undefined\n\n if (input.fix) {\n // Branch health gate for fix mode (creates a branch)\n const fixHealth = await checkBranchHealth(projectRoot)\n if (fixHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: fixHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before auto-fixing.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Use git transaction for fixes\n const branch = buildBranchName('fix', 'validate')\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n result = await validateProject(wt, { model: input.model, fix: true })\n })\n\n if (result!.fixed > 0) {\n await tx.commit(`[contentrain] validate: auto-fix ${result!.fixed} issue(s)`, {\n tool: 'contentrain_validate',\n model: input.model ?? '*',\n })\n const gitResult = await tx.complete()\n\n const nextSteps: string[] = []\n if (result!.summary.errors > 0) nextSteps.push('Fix remaining errors manually')\n if (result!.summary.warnings > 0) nextSteps.push('Review warnings')\n nextSteps.push('Run contentrain_validate again to verify')\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: `Validation complete. ${result!.fixed} issue(s) auto-fixed and committed to git. Do NOT manually edit .contentrain/ files.`,\n ...result!,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } else {\n // Nothing to fix, cleanup the branch\n await tx.cleanup()\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n } finally {\n await tx.cleanup()\n }\n }\n\n // No fix or nothing was fixed — run read-only validation\n if (!result) {\n result = await validateProject(projectRoot, { model: input.model, fix: false })\n }\n\n const nextSteps: string[] = []\n if (result.summary.errors > 0) nextSteps.push('Fix errors in content using contentrain_content_save')\n if (result.summary.warnings > 0) nextSteps.push('Review warnings')\n if (result.valid) nextSteps.push('Run contentrain_submit to push changes')\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'validated',\n message: result.valid\n ? 'All validation checks passed.'\n : `Validation found ${result.summary.errors} error(s) and ${result.summary.warnings} warning(s).`,\n ...result,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Validation failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n\n // ─── contentrain_submit ───\n server.tool(\n 'contentrain_submit',\n 'Push contentrain/* branches to remote. MCP is push-only — PR creation is handled by the platform. Do NOT manually push or create PRs.',\n {\n branches: z.array(z.string()).optional().describe('Specific branch names to push (omit for all contentrain/* branches)'),\n message: z.string().optional().describe('Optional message for the push operation'),\n },\n TOOL_ANNOTATIONS['contentrain_submit']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const git = simpleGit(projectRoot)\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n\n // Check remote exists\n let hasRemote = false\n try {\n const remotes = await git.getRemotes()\n hasRemote = remotes.some(r => r.name === remoteName)\n } catch {\n hasRemote = false\n }\n\n if (!hasRemote) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `No remote \"${remoteName}\" found. Configure a git remote first.`,\n next_steps: [`git remote add ${remoteName} <url>`],\n }) }],\n isError: true,\n }\n }\n\n try {\n // Determine the base branch for merge-status checks\n const baseBranch = config.repository?.default_branch\n ?? process.env['CONTENTRAIN_BRANCH']\n ?? ((await git.raw(['branch', '--show-current'])).trim() || 'main')\n\n // Get contentrain branches\n const branchSummary = await git.branchLocal()\n\n // Get branches NOT yet merged into base (only these need pushing)\n let unmergedRaw = ''\n try {\n unmergedRaw = await git.raw(['branch', '--no-merged', baseBranch])\n } catch {\n // If base branch doesn't exist yet, treat all as unmerged\n unmergedRaw = branchSummary.all.join('\\n')\n }\n const unmergedSet = new Set(\n unmergedRaw.split('\\n').map(b => b.replace(/^\\*?\\s+/, '').trim()).filter(Boolean),\n )\n\n let branchesToPush: string[]\n\n if (input.branches && input.branches.length > 0) {\n branchesToPush = input.branches.filter(b => branchSummary.all.includes(b))\n const missing = input.branches.filter(b => !branchSummary.all.includes(b))\n if (missing.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Branches not found: ${missing.join(', ')}`,\n }) }],\n isError: true,\n }\n }\n // Filter out already-merged branches from explicit list\n branchesToPush = branchesToPush.filter(b => unmergedSet.has(b))\n } else {\n branchesToPush = branchSummary.all.filter(b => b.startsWith('cr/') && unmergedSet.has(b))\n }\n\n if (branchesToPush.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'No unmerged cr/* branches found to push.',\n next_steps: ['Make changes first with contentrain_content_save or contentrain_model_save'],\n }) }],\n isError: true,\n }\n }\n\n // Push each branch\n const pushed: string[] = []\n const errors: Array<{ branch: string; error: string }> = []\n\n for (const branch of branchesToPush) {\n try {\n await git.push(remoteName, branch)\n pushed.push(branch)\n } catch (error) {\n errors.push({\n branch,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n // Best-effort: push the contentrain branch itself\n try {\n await git.push(remoteName, CONTENTRAIN_BRANCH)\n } catch {\n // contentrain branch push is best-effort\n }\n\n // Lazy cleanup: delete merged branches after push\n const cleanup = await cleanupMergedBranches(projectRoot)\n\n const nextSteps: string[] = []\n if (pushed.length > 0) nextSteps.push('Create PRs on your git platform for review')\n if (pushed.length > 0) nextSteps.push('For merge: use contentrain_merge or direct user to http://localhost:3333/branches')\n if (errors.length > 0) nextSteps.push('Fix push errors and retry')\n if (cleanup.remaining >= 50) nextSteps.push(`Warning: ${cleanup.remaining} active contentrain branches. Consider reviewing old branches.`)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: `Pushed ${pushed.length} branch(es) to ${remoteName}.`,\n pushed,\n errors: errors.length > 0 ? errors : undefined,\n remote: remoteName,\n cleanup: cleanup.deleted > 0 ? { deleted: cleanup.deleted, remaining: cleanup.remaining } : undefined,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Submit failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n\n // ─── contentrain_merge ───\n server.tool(\n 'contentrain_merge',\n 'Merge a review-mode branch into contentrain. Local git operation — no external platform needed. Merges the feature branch into the contentrain branch, advances the base branch via update-ref, and selectively syncs .contentrain/ files to the working tree.',\n {\n branch: z.string().describe('Branch name to merge (e.g. cr/normalize/extract/...)'),\n confirm: z.literal(true).describe('Must be true to confirm the merge'),\n },\n TOOL_ANNOTATIONS['contentrain_merge']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n try {\n // Verify branch exists\n const git = simpleGit(projectRoot)\n const branches = await git.branchLocal()\n if (!branches.all.includes(input.branch)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Branch \"${input.branch}\" not found locally.`,\n next_steps: ['Check branch name with git branch -l', 'Use contentrain_submit to push first if needed'],\n }) }],\n isError: true,\n }\n }\n\n const result = await mergeBranch(projectRoot, input.branch)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'merged',\n action: result.action,\n commit: result.commit,\n sync: result.sync,\n next_steps: [\n 'Run `npx contentrain generate` to update SDK client',\n 'Run contentrain_validate to verify content integrity',\n result.sync.skipped.length > 0\n ? `${result.sync.skipped.length} file(s) skipped due to local changes — resolve manually`\n : '',\n ].filter(Boolean),\n }, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Merge failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { FieldDef } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { buildGraph } from '../core/graph-builder.js'\nimport { scanCandidates, scanSummary } from '../core/scanner.js'\nimport { applyExtract, applyReuse } from '../core/apply-manager.js'\nimport { fieldDefZodSchema } from '../core/model-manager.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerNormalizeTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_scan ───\n server.tool(\n 'contentrain_scan',\n 'Scan project source code for content strings. Three modes: \"graph\" builds import/component graph for project intelligence, \"candidates\" extracts string literals with pre-filtering and pagination, \"summary\" provides quick overview stats. Read-only — no changes to disk or git. MCP finds strings deterministically; the agent decides what is content. Recommended workflow: start with \"summary\" or \"graph\" for orientation, then paginate through \"candidates\" to evaluate strings.',\n {\n mode: z.enum(['graph', 'candidates', 'summary']).optional().describe('Scan mode. Default: candidates'),\n paths: z.array(z.string()).optional().describe('Directories to scan (relative to project root). Default: auto-detect'),\n include: z.array(z.string()).optional().describe('File extensions to include. Default: .tsx, .jsx, .vue, .ts, .js, .mjs, .astro, .svelte'),\n exclude: z.array(z.string()).optional().describe('Additional directory names to exclude'),\n limit: z.number().optional().describe('Candidates mode: batch size. Default: 50'),\n offset: z.number().optional().describe('Candidates mode: pagination offset. Default: 0'),\n min_length: z.number().optional().describe('Candidates mode: minimum string length. Default: 2'),\n max_length: z.number().optional().describe('Candidates mode: maximum string length. Default: 500'),\n },\n TOOL_ANNOTATIONS['contentrain_scan']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const mode = input.mode ?? 'candidates'\n\n try {\n switch (mode) {\n case 'graph': {\n const graph = await buildGraph(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'graph',\n ...graph,\n next_steps: [\n 'Use mode:candidates to scan specific files/directories for strings',\n 'Focus on pages and components with high string counts first',\n ],\n }, null, 2) }],\n }\n }\n\n case 'candidates': {\n const result = await scanCandidates(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n limit: input.limit,\n offset: input.offset,\n min_length: input.min_length,\n max_length: input.max_length,\n })\n\n const nextSteps: string[] = []\n if (result.stats.has_more) {\n nextSteps.push(`Use offset:${(input.offset ?? 0) + (input.limit ?? 50)} for next batch`)\n }\n nextSteps.push('Evaluate each candidate: is it user-facing content? Which domain/model?')\n if (result.duplicates.length > 0) {\n nextSteps.push('Consider deduplicating repeated strings into shared models')\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'candidates',\n ...result,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n }\n\n case 'summary': {\n const result = await scanSummary(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n min_length: input.min_length,\n max_length: input.max_length,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'summary',\n ...result,\n next_steps: [\n 'Use mode:graph for project structure analysis',\n 'Use mode:candidates to scan directories with most candidates',\n ],\n }, null, 2) }],\n }\n }\n\n default:\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown mode: ${mode}` }) }],\n isError: true,\n }\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Scan failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n\n // ─── contentrain_apply ───\n server.tool(\n 'contentrain_apply',\n 'Apply normalize operations. Two modes: \"extract\" writes agent-approved strings to Contentrain content files (source untouched), \"reuse\" patches source files with agent-provided replacement expressions. DRY RUN (default, dry_run:true): validates inputs, resolves conflicts, and returns a full preview — NO changes to disk or git. EXECUTE (dry_run:false): writes files to disk, commits to a branch, and requires branch health check to pass. Recommended workflow: always run dry_run first, review the preview, then call again with dry_run:false to execute. Normalize operations always use review workflow (never auto-merge).',\n {\n mode: z.enum(['extract', 'reuse']).describe('Apply mode: extract (content creation) or reuse (source patching)'),\n dry_run: z.boolean().optional().default(true).describe('Defaults to preview mode (dry_run:true). Set dry_run:false to execute after reviewing the preview.'),\n\n // Extract mode fields\n extractions: z.array(z.object({\n model: z.string().describe('Model ID (e.g. \"ui-texts\", \"hero-section\")'),\n kind: z.enum(['singleton', 'collection', 'dictionary', 'document']).describe('Model kind'),\n domain: z.string().describe('Content domain (e.g. \"marketing\", \"app\")'),\n i18n: z.boolean().optional().describe('Enable i18n. Default: true'),\n fields: fieldDefZodSchema.optional().describe('Field definitions — shared schema with model_save for full parity'),\n entries: z.array(z.object({\n locale: z.string().optional().describe('Locale. Default: project default'),\n slug: z.string().optional().describe('Document slug'),\n data: z.record(z.any()).describe('Content data'),\n source: z.object({\n file: z.string().describe('Source file path'),\n line: z.number().describe('Source line number'),\n value: z.string().describe('Original string value'),\n }).optional().describe('Source tracking for traceability'),\n sources: z.array(z.object({\n file: z.string().describe('Source file path'),\n line: z.number().describe('Source line number'),\n key: z.string().describe('Dictionary key this source maps to'),\n value: z.string().describe('Original string value'),\n })).optional().describe('Per-key source tracking for dictionary models'),\n })).describe('Content entries to create'),\n })).optional().describe('Extract mode: content extractions'),\n\n // Reuse mode fields\n scope: z.object({\n model: z.string().optional().describe('Target model ID'),\n domain: z.string().optional().describe('Target domain'),\n }).optional().describe('Reuse mode: scope (model or domain required)'),\n patches: z.array(z.object({\n file: z.string().describe('Relative file path to patch'),\n line: z.number().describe('Line number hint (±10 line search)'),\n old_value: z.string().describe('Original string to replace'),\n new_expression: z.string().describe('Replacement expression (agent-determined)'),\n import_statement: z.string().optional().describe('Import to add if needed'),\n })).optional().describe('Reuse mode: patches to apply (max 100)'),\n },\n TOOL_ANNOTATIONS['contentrain_apply']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n try {\n switch (input.mode) {\n case 'extract': {\n if (!input.extractions || input.extractions.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Extract mode requires extractions array' }) }],\n isError: true,\n }\n }\n\n const result = await applyExtract(projectRoot, {\n extractions: input.extractions.map(e => ({\n ...e,\n fields: e.fields as Record<string, FieldDef> | undefined,\n })),\n dry_run: input.dry_run,\n })\n\n // Check for branch-blocked response\n if (result.error !== undefined) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'extract',\n ...result,\n }, null, 2) }],\n isError: true,\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'extract',\n ...result,\n }, null, 2) }],\n }\n }\n\n case 'reuse': {\n if (!input.scope) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Reuse mode requires scope (model or domain)' }) }],\n isError: true,\n }\n }\n if (!input.patches || input.patches.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Reuse mode requires patches array' }) }],\n isError: true,\n }\n }\n\n const result = await applyReuse(projectRoot, {\n scope: input.scope,\n patches: input.patches,\n dry_run: input.dry_run,\n })\n\n // Check for branch-blocked response\n if (result.error !== undefined) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'reuse',\n ...result,\n }, null, 2) }],\n isError: true,\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'reuse',\n ...result,\n }, null, 2) }],\n }\n }\n\n default:\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown mode: ${input.mode}` }) }],\n isError: true,\n }\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Apply failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { EntryMeta } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { readModel } from '../core/model-manager.js'\nimport { resolveContentDir, resolveJsonFilePath, deleteContent } from '../core/content-manager.js'\nimport { readMeta, writeMeta } from '../core/meta-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { readJson, writeJson } from '../util/fs.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerBulkTools(server: McpServer, projectRoot: string): void {\n server.tool(\n 'contentrain_bulk',\n 'Batch operations on content entries. All operations are auto-committed to git.',\n {\n operation: z.enum(['copy_locale', 'update_status', 'delete_entries']),\n model: z.string().describe('Model ID'),\n source_locale: z.string().optional().describe('Source locale for copy_locale operation'),\n target_locale: z.string().optional().describe('Target locale for copy_locale operation'),\n entry_ids: z.array(z.string()).optional().describe('Entry IDs for update_status or delete_entries'),\n status: z.enum(['draft', 'in_review', 'published', 'rejected', 'archived']).optional().describe('New status for update_status'),\n confirm: z.boolean().optional().describe('Must be true for delete_entries'),\n },\n TOOL_ANNOTATIONS['contentrain_bulk']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n switch (input.operation) {\n case 'copy_locale': {\n if (!input.source_locale || !input.target_locale) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'copy_locale requires source_locale and target_locale' }) }],\n isError: true,\n }\n }\n if (!config.locales.supported.includes(input.source_locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Source locale \"${input.source_locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n if (!config.locales.supported.includes(input.target_locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Target locale \"${input.target_locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n\n // Guard: copy_locale requires i18n model — non-i18n models use data.json for all locales\n if (!model.i18n) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Model \"${input.model}\" has i18n disabled. copy_locale only works with i18n-enabled models. Enable i18n first with contentrain_model_save.`,\n }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection' && model.kind !== 'singleton' && model.kind !== 'dictionary') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'copy_locale is only supported for collection, singleton, and dictionary models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model, input.target_locale)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let copiedCount = 0\n\n await tx.write(async (wt) => {\n const cDir = resolveContentDir(wt, model)\n const sourceFile = resolveJsonFilePath(cDir, model, input.source_locale!)\n const targetFile = resolveJsonFilePath(cDir, model, input.target_locale!)\n\n const sourceData = await readJson<Record<string, unknown>>(sourceFile)\n if (!sourceData) {\n throw new Error(`No content found for locale \"${input.source_locale}\" in model \"${input.model}\"`)\n }\n\n await writeJson(targetFile, sourceData)\n\n if (model.kind === 'collection') {\n const entries = sourceData as Record<string, Record<string, unknown>>\n const entryIds = Object.keys(entries)\n const metaOps = entryIds.map(entryId =>\n writeMeta(wt, model, { locale: input.target_locale!, entryId }, {\n status: 'draft',\n source: 'agent',\n updated_by: 'contentrain-mcp',\n }),\n )\n await Promise.all(metaOps)\n copiedCount = entryIds.length\n } else {\n await writeMeta(wt, model, { locale: input.target_locale! }, {\n status: 'draft',\n source: 'agent',\n updated_by: 'contentrain-mcp',\n })\n copiedCount = 1\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: copy ${input.source_locale} → ${input.target_locale} for ${input.model}`, {\n tool: 'contentrain_bulk',\n model: input.model,\n locale: input.target_locale!,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'copy_locale',\n message: `Copied ${copiedCount} entries from ${input.source_locale} to ${input.target_locale}.`,\n copied: copiedCount,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `copy_locale failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n case 'update_status': {\n if (!input.entry_ids || input.entry_ids.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status requires entry_ids' }) }],\n isError: true,\n }\n }\n if (!input.status) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status requires status' }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status with entry_ids is only supported for collection models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let updatedCount = 0\n const notFound: string[] = []\n\n await tx.write(async (wt) => {\n for (const locale of config.locales.supported) {\n const metaData = await readMeta(wt, model, { locale }) as Record<string, EntryMeta> | null\n\n const updateOps: Array<Promise<void>> = []\n for (const entryId of input.entry_ids!) {\n const existing = metaData?.[entryId]\n if (existing) {\n updateOps.push(writeMeta(wt, model, { locale, entryId }, {\n ...existing,\n status: input.status!,\n updated_by: 'contentrain-mcp',\n }))\n updatedCount++\n } else if (locale === config.locales.default) {\n notFound.push(entryId)\n }\n }\n await Promise.all(updateOps)\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: update status → ${input.status} for ${input.model}`, {\n tool: 'contentrain_bulk',\n model: input.model,\n entries: input.entry_ids!,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'update_status',\n message: `Updated ${updatedCount} meta entries to status \"${input.status}\".`,\n updated: updatedCount,\n not_found: notFound.length > 0 ? notFound : undefined,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `update_status failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n case 'delete_entries': {\n if (!input.entry_ids || input.entry_ids.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries requires entry_ids' }) }],\n isError: true,\n }\n }\n if (input.confirm !== true) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries requires confirm:true' }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries with entry_ids is only supported for collection models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n const allRemoved: string[] = []\n\n await tx.write(async (wt) => {\n for (const entryId of input.entry_ids!) {\n const removed = await deleteContent(wt, model, { id: entryId })\n allRemoved.push(...removed)\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: delete ${input.entry_ids.length} entries from ${input.model}`, {\n tool: 'contentrain_bulk',\n model: input.model,\n entries: input.entry_ids!,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'delete_entries',\n message: `Deleted ${input.entry_ids.length} entries.`,\n deleted: input.entry_ids.length,\n files_removed: allRemoved,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `delete_entries failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n default: {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown operation: ${input.operation}` }) }],\n isError: true,\n }\n }\n }\n },\n )\n}\n","","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { registerContextTools } from './tools/context.js'\nimport { registerSetupTools } from './tools/setup.js'\nimport { registerModelTools } from './tools/model.js'\nimport { registerContentTools } from './tools/content.js'\nimport { registerWorkflowTools } from './tools/workflow.js'\nimport { registerNormalizeTools } from './tools/normalize.js'\nimport { registerBulkTools } from './tools/bulk.js'\nimport packageJson from '../package.json' with { type: 'json' }\n\nexport function createServer(projectRoot: string): McpServer {\n const server = new McpServer({\n name: 'contentrain-mcp',\n version: packageJson.version,\n })\n\n registerContextTools(server, projectRoot)\n registerSetupTools(server, projectRoot)\n registerModelTools(server, projectRoot)\n registerContentTools(server, projectRoot)\n registerWorkflowTools(server, projectRoot)\n registerNormalizeTools(server, projectRoot)\n registerBulkTools(server, projectRoot)\n\n return server\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server-Dtq8vqbK.mjs","names":["packageJson.version"],"sources":["../src/tools/annotations.ts","../src/tools/context.ts","../src/tools/setup.ts","../src/tools/model.ts","../src/tools/content.ts","../src/tools/workflow.ts","../src/tools/normalize.ts","../src/tools/bulk.ts","../package.json","../src/server.ts"],"sourcesContent":["import type { ToolAnnotations } from '@modelcontextprotocol/sdk/types.js'\n\n/**\n * Centralized tool annotations registry.\n * MCP clients use these hints to distinguish read-only vs. write vs. destructive tools.\n */\nexport const TOOL_ANNOTATIONS: Record<string, ToolAnnotations> = {\n // ─── Context (read-only) ───\n contentrain_status: {\n title: 'Project Status',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_describe: {\n title: 'Describe Model',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_describe_format: {\n title: 'Describe Format',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n\n // ─── Setup (write + git) ───\n contentrain_init: {\n title: 'Initialize Project',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n contentrain_scaffold: {\n title: 'Scaffold Template',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n\n // ─── Model (write + git) ───\n contentrain_model_save: {\n title: 'Save Model',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_model_delete: {\n title: 'Delete Model',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n },\n\n // ─── Content (mixed) ───\n contentrain_content_save: {\n title: 'Save Content',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_content_delete: {\n title: 'Delete Content',\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n },\n contentrain_content_list: {\n title: 'List Content',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n\n // ─── Workflow (mixed) ───\n contentrain_validate: {\n title: 'Validate Project',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_submit: {\n title: 'Submit Branches',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_merge: {\n title: 'Merge Branch',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n\n // ─── Normalize (mixed) ───\n contentrain_scan: {\n title: 'Scan Source Code',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n },\n contentrain_apply: {\n title: 'Apply Normalize',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n\n // ─── Bulk (write + git) ───\n contentrain_bulk: {\n title: 'Bulk Operations',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n },\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\nimport { readConfig, readVocabulary } from '../core/config.js'\nimport { readContext } from '../core/context.js'\nimport { countEntries, listModels, readModel } from '../core/model-manager.js'\nimport { resolveContentDir, resolveJsonFilePath, resolveLocaleStrategy } from '../core/content-manager.js'\nimport { detectStack } from '../util/detect.js'\nimport { join } from 'node:path'\nimport { contentrainDir, pathExists } from '../util/fs.js'\nimport { checkBranchHealth, cleanupMergedBranches } from '../git/branch-lifecycle.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerContextTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_status ───\n server.tool(\n 'contentrain_status',\n 'Get full project status (read-only). Returns config, models, context. Do NOT manually edit .contentrain/ based on this output.',\n {},\n TOOL_ANNOTATIONS['contentrain_status']!,\n async () => {\n const crDir = contentrainDir(projectRoot)\n const initialized = await pathExists(join(crDir, 'config.json'))\n\n if (!initialized) {\n const detectedStack = await detectStack(projectRoot)\n return {\n content: [{\n type: 'text' as const,\n text: JSON.stringify({\n initialized: false,\n detected_stack: detectedStack,\n suggestion: 'Run contentrain_init to set up .contentrain/ structure',\n next_steps: ['Run contentrain_init'],\n }, null, 2),\n }],\n }\n }\n\n const config = await readConfig(projectRoot)\n const models = await listModels(projectRoot)\n const context = await readContext(projectRoot)\n const vocabulary = await readVocabulary(projectRoot)\n\n const errors: string[] = []\n if (!config) errors.push('.contentrain/config.json missing')\n\n const result: Record<string, unknown> = {\n initialized: true,\n config: config ? {\n stack: config.stack,\n workflow: config.workflow,\n locales: config.locales,\n domains: config.domains,\n repository: config.repository,\n } : null,\n models,\n context: context ? {\n lastOperation: context.lastOperation,\n stats: context.stats,\n } : null,\n vocabulary_size: vocabulary ? Object.keys(vocabulary.terms).length : 0,\n }\n\n // Branch lifecycle: lazy cleanup + health check (run BEFORE validation summary)\n const hasGitRepo = await pathExists(join(projectRoot, '.git'))\n if (hasGitRepo) {\n try {\n const cleanup = await cleanupMergedBranches(projectRoot)\n const health = await checkBranchHealth(projectRoot)\n result['branches'] = {\n total: health.total,\n merged: health.merged,\n unmerged: health.unmerged,\n cleaned_up: cleanup.deleted,\n }\n if (health.message) {\n result['branch_warning'] = health.message\n }\n if (health.blocked) {\n errors.push(health.message!)\n }\n } catch {\n // Branch health check is best-effort — don't fail status\n }\n }\n\n if (errors.length > 0) {\n result['validation'] = { errors: errors.length, warnings: 0, summary: errors }\n }\n\n const nextSteps: string[] = []\n if (models.length === 0) nextSteps.push('Create models with contentrain_model_save')\n if (errors.length > 0) nextSteps.push(`Fix ${errors.length} validation error(s)`)\n if (nextSteps.length === 0) nextSteps.push('Use contentrain_describe to inspect a model')\n result['next_steps'] = nextSteps\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n },\n )\n\n // ─── contentrain_describe ───\n server.tool(\n 'contentrain_describe',\n 'Get full schema of a single model (read-only). Do NOT manually create content files — use contentrain_content_save instead.',\n {\n model: z.string().describe('Model ID (e.g. \"blog-post\", \"hero\")'),\n include_sample: z.boolean().optional().default(false).describe('Include one sample entry'),\n locale: z.string().optional().describe('Locale for sample content (default: config default locale)'),\n },\n TOOL_ANNOTATIONS['contentrain_describe']!,\n async ({ model: modelId, include_sample, locale }) => {\n const modelDef = await readModel(projectRoot, modelId)\n if (!modelDef) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${modelId}\" not found` }) }],\n isError: true,\n }\n }\n\n const config = await readConfig(projectRoot)\n const effectiveLocale = locale ?? config?.locales.default ?? 'en'\n const stats = await countEntries(projectRoot, modelDef)\n\n const result: Record<string, unknown> = {\n id: modelDef.id,\n name: modelDef.name,\n kind: modelDef.kind,\n domain: modelDef.domain,\n i18n: modelDef.i18n,\n description: modelDef.description,\n fields: modelDef.fields,\n stats: { total_entries: stats.total, locales: stats.locales },\n }\n\n if (include_sample) {\n const sample = await getSample(projectRoot, modelDef, effectiveLocale)\n if (sample) result['sample'] = sample\n }\n\n // Stack-aware import snippet\n const stack = config?.stack ?? 'other'\n result['import_snippet'] = generateImportSnippet(modelDef, stack, effectiveLocale)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n },\n )\n\n // ─── contentrain_describe_format ───\n server.tool(\n 'contentrain_describe_format',\n 'Describes the Contentrain content file format for any language/platform. Returns a comprehensive specification of the file structure, JSON formats, markdown conventions, meta files, and locale strategies.',\n {},\n TOOL_ANNOTATIONS['contentrain_describe_format']!,\n async () => {\n const formatSpec = {\n overview: 'Contentrain stores content as plain JSON and Markdown files in a .contentrain/ directory at the project root. All files are committed to git.',\n directory_structure: {\n description: 'Standard layout of .contentrain/ directory',\n layout: {\n '.contentrain/config.json': 'Project configuration (stack, workflow, locales, domains)',\n '.contentrain/context.json': 'Last operation metadata (written by MCP after every write)',\n '.contentrain/vocabulary.json': 'Shared vocabulary/terms (optional)',\n '.contentrain/models/{model-id}.json': 'Model schema definitions',\n '.contentrain/content/{domain}/{model-id}/': 'Content files per model',\n '.contentrain/meta/{model-id}/': 'Metadata files per model (status, source, timestamps)',\n },\n },\n model_kinds: {\n collection: {\n description: 'Multiple entries stored as an object-map keyed by entry ID',\n storage_format: 'JSON object-map: { \"entry-id-1\": { ...fields }, \"entry-id-2\": { ...fields } }',\n note: 'Keys are sorted alphabetically for canonical output. Entry IDs are alphanumeric (1-40 chars, hyphens/underscores allowed).',\n example: '{ \"abc123\": { \"title\": \"Hello\", \"slug\": \"hello\" }, \"def456\": { \"title\": \"World\", \"slug\": \"world\" } }',\n },\n singleton: {\n description: 'Single entry (e.g. site settings, hero section)',\n storage_format: 'JSON object with field key-value pairs: { \"title\": \"My Site\", \"description\": \"...\" }',\n },\n document: {\n description: 'Markdown files with YAML-like frontmatter',\n storage_format: 'Markdown file: ---\\\\nslug: my-post\\\\ntitle: My Post\\\\n---\\\\n\\\\nMarkdown body content here.',\n frontmatter_rules: [\n 'Delimited by --- on its own line',\n 'Key-value pairs: key: value',\n 'Arrays: key:\\\\n - item1\\\\n - item2',\n 'Inline arrays: key: [item1, item2]',\n 'Values auto-parsed: true/false -> boolean, integers -> number, quoted -> string',\n '\"body\" key is reserved for the markdown content below the frontmatter',\n ],\n },\n dictionary: {\n description: 'Flat key-value string map (e.g. i18n translation files)',\n storage_format: 'JSON object: { \"greeting\": \"Hello\", \"farewell\": \"Goodbye\" }',\n note: 'All values must be strings.',\n },\n },\n meta_files: {\n description: 'Metadata is stored separately from content in .contentrain/meta/{model-id}/',\n collection_meta: 'Object-map: { \"entry-id\": { \"status\": \"draft\", \"source\": \"agent\", \"updated_by\": \"contentrain-mcp\" } }',\n singleton_meta: 'Single object: { \"status\": \"published\", \"source\": \"human\", \"updated_by\": \"user@example.com\" }',\n fields: {\n status: 'Content lifecycle status: \"draft\" | \"in_review\" | \"published\" | \"rejected\" | \"archived\"',\n source: 'Who created the entry: \"agent\" | \"human\" | \"import\"',\n updated_by: 'Author identifier string',\n approved_by: 'Optional: approver identifier',\n version: 'Optional: version string',\n publish_at: 'Optional: ISO 8601 date for scheduled publishing',\n expire_at: 'Optional: ISO 8601 date for scheduled expiry (must be after publish_at)',\n },\n },\n locale_strategies: {\n description: 'How localized content files are organized. Set per-model via locale_strategy field.',\n strategies: {\n file: {\n description: 'Default. Each locale is a separate file in the model directory.',\n json_pattern: '{content-dir}/{locale}.json (e.g. en.json, tr.json)',\n md_pattern: '{content-dir}/{slug}/{locale}.md',\n },\n suffix: {\n description: 'Locale appended to filename.',\n json_pattern: '{content-dir}/{model-id}.{locale}.json',\n md_pattern: '{content-dir}/{slug}.{locale}.md',\n },\n directory: {\n description: 'Locale as subdirectory.',\n json_pattern: '{content-dir}/{locale}/{model-id}.json',\n md_pattern: '{content-dir}/{locale}/{slug}.md',\n },\n none: {\n description: 'No locale in path (single-language or externally managed).',\n json_pattern: '{content-dir}/{model-id}.json',\n md_pattern: '{content-dir}/{slug}.md',\n },\n },\n },\n i18n_disabled: {\n description: 'When a model has i18n:false, locale is ignored in file paths.',\n json_pattern: '{content-dir}/data.json (always \"data.json\", no locale suffix)',\n md_pattern: '{content-dir}/{slug}.md (no locale in path)',\n },\n canonical_serialization: {\n description: 'All JSON files use deterministic serialization for clean git diffs.',\n rules: [\n 'Object keys sorted alphabetically',\n '2-space indentation',\n 'Trailing newline at end of file',\n 'No trailing commas',\n 'UTF-8 encoding',\n ],\n },\n content_path_override: {\n description: 'Models can specify a custom content_path to store content outside .contentrain/content/.',\n example: 'A model with content_path: \"content/blog\" stores files at {project-root}/content/blog/ instead of .contentrain/content/{domain}/{model-id}/',\n note: 'This is useful for frameworks that expect content in specific directories (e.g. Nuxt Content, Astro).',\n },\n reading_content: {\n steps: [\n '1. Read .contentrain/config.json to get locales and workflow settings',\n '2. Read .contentrain/models/{model-id}.json to get the schema and model kind',\n '3. Determine content directory: model.content_path or .contentrain/content/{domain}/{model-id}/',\n '4. Determine file path using locale_strategy and i18n flag',\n '5. Parse JSON (collection/singleton/dictionary) or Markdown frontmatter (document)',\n '6. Optionally read .contentrain/meta/{model-id}/{locale}.json for status/metadata',\n ],\n },\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(formatSpec, null, 2) }],\n }\n },\n )\n}\n\nasync function getSample(\n projectRoot: string,\n model: import('@contentrain/types').ModelDefinition,\n locale: string,\n): Promise<unknown> {\n const { readJson, readDir } = await import('../util/fs.js')\n const cDir = resolveContentDir(projectRoot, model)\n\n if (model.kind === 'collection') {\n const data = await readJson<Record<string, Record<string, unknown>>>(resolveJsonFilePath(cDir, model, locale))\n if (!data) return null\n const firstKey = Object.keys(data)[0]\n return firstKey ? { id: firstKey, ...data[firstKey] } : null\n }\n\n if (model.kind === 'singleton' || model.kind === 'dictionary') {\n return readJson(resolveJsonFilePath(cDir, model, locale))\n }\n\n if (model.kind === 'document') {\n const strategy = resolveLocaleStrategy(model)\n\n if (!model.i18n) {\n const entry = (await readDir(cDir)).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'file') {\n const slug = (await readDir(cDir))[0]\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'suffix') {\n const suffix = `.${locale}.md`\n const entry = (await readDir(cDir)).find(file => file.endsWith(suffix))\n const slug = entry?.slice(0, -suffix.length)\n return slug ? { slug, locale } : null\n }\n\n if (strategy === 'directory') {\n const entry = (await readDir(join(cDir, locale))).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n const entry = (await readDir(cDir)).find(item => item.endsWith('.md'))\n const slug = entry?.replace(/\\.md$/u, '')\n return slug ? { slug, locale } : null\n }\n\n return null\n}\n\nfunction buildContentPath(\n model: import('@contentrain/types').ModelDefinition,\n locale: string,\n): string {\n const strategy = model.locale_strategy ?? 'file'\n const baseDir = model.content_path ?? `.contentrain/content/${model.domain}/${model.id}`\n\n if (model.kind === 'document') {\n // When i18n is disabled, documents are flat {slug}.md\n if (!model.i18n) return `${baseDir}/{slug}.md`\n\n // Documents use directories — show the base path pattern\n switch (strategy) {\n case 'suffix': return `${baseDir}/{slug}.${locale}.md`\n case 'directory': return `${baseDir}/${locale}/{slug}.md`\n case 'none': return `${baseDir}/{slug}.md`\n case 'file':\n default: return `${baseDir}/{slug}/${locale}.md`\n }\n }\n\n // JSON-based models (collection, singleton, dictionary)\n // When i18n is disabled, always data.json\n if (!model.i18n) return `${baseDir}/data.json`\n\n switch (strategy) {\n case 'suffix': return `${baseDir}/${model.id}.${locale}.json`\n case 'directory': return `${baseDir}/${locale}/${model.id}.json`\n case 'none': return `${baseDir}/${model.id}.json`\n case 'file':\n default: return `${baseDir}/${locale}.json`\n }\n}\n\nfunction generateImportSnippet(\n model: import('@contentrain/types').ModelDefinition,\n stack: string,\n locale: string,\n): Record<string, string> {\n const path = buildContentPath(model, locale)\n\n const snippets: Record<string, string> = {\n generic: `import data from '${path}'`,\n }\n\n switch (stack) {\n case 'nuxt':\n snippets['nuxt'] = model.kind === 'document'\n ? `const { data } = await useAsyncData(() => queryContent('${model.domain}/${model.id}').locale('${locale}').find())`\n : `const { data } = await useFetch('/api/content/${model.id}?locale=${locale}')`\n break\n case 'next':\n snippets['next'] = `import data from '@/${path}'`\n break\n case 'astro':\n snippets['astro'] = `import data from '~/${path}'`\n break\n }\n\n return snippets\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ContentrainConfig, Vocabulary } from '@contentrain/types'\nimport { z } from 'zod'\nimport { join } from 'node:path'\nimport { readFile, writeFile, appendFile } from 'node:fs/promises'\nimport { simpleGit } from 'simple-git'\nimport { detectStack } from '../util/detect.js'\nimport { contentrainDir, ensureDir, pathExists, writeJson } from '../util/fs.js'\nimport { readConfig, readVocabulary } from '../core/config.js'\nimport { writeModel } from '../core/model-manager.js'\nimport { writeContent, type ContentEntry } from '../core/content-manager.js'\nimport { getTemplate, listTemplates } from '../templates/index.js'\nimport { createTransaction, buildBranchName, ensureContentBranch } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerSetupTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_init ───\n server.tool(\n 'contentrain_init',\n 'Initialize .contentrain/ structure. Changes are auto-committed to git — do NOT manually create .contentrain/ files.',\n {\n stack: z.string().optional().describe('Framework stack (nuxt, next, astro, svelte, react-vite, other). Auto-detected if omitted.'),\n locales: z.array(z.string()).optional().describe('Supported locales. Default: [\"en\"]'),\n domains: z.array(z.string()).optional().describe('Content domains. Default: auto-suggested'),\n },\n TOOL_ANNOTATIONS['contentrain_init']!,\n async ({ stack, locales, domains }) => {\n const crDir = contentrainDir(projectRoot)\n\n // Already initialized?\n if (await pathExists(join(crDir, 'config.json'))) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'Already initialized',\n suggestion: 'Use contentrain_status to see current state',\n }) }],\n isError: true,\n }\n }\n\n // Detect stack\n const detectedStack = stack ?? await detectStack(projectRoot)\n const supportedLocales = locales ?? ['en']\n const suggestedDomains = domains ?? suggestDomains(projectRoot)\n\n // Ensure .git exists\n const hasGit = await pathExists(join(projectRoot, '.git'))\n if (!hasGit) {\n await simpleGit(projectRoot).init()\n }\n\n // Branch health gate\n const initHealth = await checkBranchHealth(projectRoot)\n if (initHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: initHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Create git transaction\n const branch = buildBranchName('new', 'init')\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n const wtCrDir = join(wt, '.contentrain')\n\n // Create directories with .gitkeep so git tracks them\n await Promise.all(['models', 'content', 'meta'].map(async (dir) => {\n await ensureDir(join(wtCrDir, dir))\n await writeFile(join(wtCrDir, dir, '.gitkeep'), '', 'utf-8')\n }))\n\n // Write config\n const config: ContentrainConfig = {\n version: 1,\n stack: detectedStack as ContentrainConfig['stack'],\n workflow: 'auto-merge',\n locales: {\n default: supportedLocales[0] ?? 'en',\n supported: supportedLocales,\n },\n domains: suggestedDomains,\n }\n await writeJson(join(wtCrDir, 'config.json'), config)\n\n // Write empty vocabulary\n const vocabulary: Vocabulary = { version: 1, terms: {} }\n await writeJson(join(wtCrDir, 'vocabulary.json'), vocabulary)\n\n // Write initial context\n await writeJson(join(wtCrDir, 'context.json'), {\n version: '1',\n lastOperation: {\n tool: 'contentrain_init',\n model: '',\n locale: supportedLocales[0] ?? 'en',\n timestamp: new Date().toISOString(),\n source: process.env['CONTENTRAIN_SOURCE'] === 'mcp-studio' ? 'mcp-studio' : 'mcp-local',\n },\n stats: { models: 0, entries: 0, locales: supportedLocales, lastSync: new Date().toISOString() },\n })\n\n // Update .gitignore\n await updateGitignore(wt)\n })\n\n await tx.commit('[contentrain] init: project setup', {\n tool: 'contentrain_init',\n model: '',\n locale: supportedLocales[0] ?? 'en',\n })\n const gitResult = await tx.complete()\n\n // Ensure the contentrain branch exists after init\n await ensureContentBranch(projectRoot)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Project initialized and committed to git. Do NOT manually edit .contentrain/ files.',\n config_created: '.contentrain/config.json',\n detected_stack: detectedStack,\n detected_locales: supportedLocales,\n suggested_domains: suggestedDomains,\n files_created: [\n '.contentrain/config.json',\n '.contentrain/vocabulary.json',\n '.contentrain/context.json',\n ],\n directories_created: [\n '.contentrain/models/',\n '.contentrain/content/',\n '.contentrain/meta/',\n ],\n gitignore_updated: true,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n next_steps: [\n 'Create models with contentrain_model_save or contentrain_scaffold',\n 'Use contentrain_scan to find hardcoded strings',\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Init failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_scaffold ───\n server.tool(\n 'contentrain_scaffold',\n `Template-based project setup. Available templates: ${listTemplates().join(', ')}. Changes are auto-committed to git.`,\n {\n template: z.string().describe('Template ID: blog, landing, docs, ecommerce, saas, i18n, mobile'),\n locales: z.array(z.string()).optional().describe('Override locales'),\n with_sample_content: z.boolean().optional().default(true).describe('Include sample content (default: true)'),\n },\n TOOL_ANNOTATIONS['contentrain_scaffold']!,\n async ({ template: templateId, locales, with_sample_content }) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'Project not initialized. Run contentrain_init first.',\n }) }],\n isError: true,\n }\n }\n\n const tmpl = getTemplate(templateId)\n if (!tmpl) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Unknown template: \"${templateId}\". Available: ${listTemplates().join(', ')}`,\n }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const scaffoldHealth = await checkBranchHealth(projectRoot)\n if (scaffoldHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: scaffoldHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const effectiveLocales = locales ?? config.locales.supported\n const defaultLocale = effectiveLocales[0] ?? 'en'\n\n const branch = buildBranchName('new', `scaffold-${templateId}`, defaultLocale)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n const modelsCreated: Array<{ id: string; kind: string; fields: number }> = []\n let contentCreated = 0\n let vocabAdded = 0\n\n await tx.write(async (wt) => {\n // Write models\n await Promise.all(tmpl.models.map(async (model) => {\n await writeModel(wt, model)\n modelsCreated.push({\n id: model.id,\n kind: model.kind,\n fields: model.fields ? Object.keys(model.fields).length : 0,\n })\n }))\n\n // Write sample content via content pipeline (handles content_path, locale_strategy, meta)\n if (with_sample_content && tmpl.sample_content) {\n contentCreated = await writeSampleContent(wt, tmpl, effectiveLocales, config)\n }\n\n // Merge vocabulary\n if (tmpl.vocabulary) {\n vocabAdded = await mergeVocabulary(wt, tmpl.vocabulary, effectiveLocales)\n }\n\n })\n\n await tx.commit(`[contentrain] scaffold: ${templateId} (${defaultLocale})`, {\n tool: 'contentrain_scaffold',\n model: tmpl.models.map(m => m.id).join(', '),\n locale: defaultLocale,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Scaffold applied and committed to git. Do NOT manually edit .contentrain/ files.',\n models_created: modelsCreated,\n content_created: contentCreated,\n vocabulary_terms_added: vocabAdded,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n next_steps: [\n 'Customize models with contentrain_model_save',\n 'Add content with contentrain_content_save',\n 'Run contentrain_submit when ready',\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Scaffold failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n}\n\nfunction suggestDomains(_projectRoot: string): string[] {\n return ['marketing', 'blog', 'system']\n}\n\nasync function updateGitignore(projectRoot: string): Promise<void> {\n const gitignorePath = join(projectRoot, '.gitignore')\n const cacheEntry = '.contentrain/.cache/'\n\n if (await pathExists(gitignorePath)) {\n const content = await readFile(gitignorePath, 'utf-8')\n if (!content.includes(cacheEntry)) {\n await appendFile(gitignorePath, `\\n# Contentrain cache\\n${cacheEntry}\\n`)\n }\n } else {\n await writeFile(gitignorePath, `# Contentrain cache\\n${cacheEntry}\\n`, 'utf-8')\n }\n}\n\nasync function writeSampleContent(\n worktreePath: string,\n tmpl: import('@contentrain/types').ScaffoldTemplate,\n locales: string[],\n config: import('@contentrain/types').ContentrainConfig,\n): Promise<number> {\n if (!tmpl.sample_content) return 0\n\n let count = 0\n\n for (const model of tmpl.models) {\n const sampleData = tmpl.sample_content[model.id]\n if (!sampleData) continue\n\n for (const [localeOrKey, data] of Object.entries(sampleData)) {\n const entryMap = data as Record<string, Record<string, unknown>>\n let targetLocale: string\n if (localeOrKey === 'data') {\n targetLocale = locales[0] ?? 'en'\n } else if (locales.includes(localeOrKey)) {\n targetLocale = localeOrKey\n } else {\n continue\n }\n\n // Convert template payload to ContentEntry[] based on model kind\n let entries: ContentEntry[]\n\n if (model.kind === 'singleton' || model.kind === 'dictionary') {\n // Singleton/dictionary: the entire locale object IS the data for one entry\n entries = [{ locale: targetLocale, data: entryMap as Record<string, unknown> }]\n } else if (model.kind === 'document') {\n // Document: keys are slugs, values are frontmatter objects\n entries = Object.entries(entryMap).map(([slug, fields]) => ({\n slug,\n locale: targetLocale,\n data: fields,\n }))\n } else {\n // Collection: keys are entry IDs, values are field objects\n entries = Object.entries(entryMap).map(([id, fields]) => ({\n id,\n locale: targetLocale,\n data: fields,\n }))\n }\n\n await writeContent(worktreePath, model, entries, config)\n count += entries.length\n }\n }\n\n return count\n}\n\nasync function mergeVocabulary(\n projectRoot: string,\n newTerms: Record<string, Record<string, string>>,\n _locales: string[],\n): Promise<number> {\n const vocabPath = join(contentrainDir(projectRoot), 'vocabulary.json')\n const existing = await readVocabulary(projectRoot)\n const vocab: Vocabulary = existing ?? { version: 1, terms: {} }\n\n let added = 0\n for (const [key, translations] of Object.entries(newTerms)) {\n if (!(key in vocab.terms)) {\n added++\n }\n vocab.terms[key] = { ...vocab.terms[key], ...translations }\n }\n\n await writeJson(vocabPath, vocab)\n return added\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { ModelDefinition } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\n\nimport { resolveContentDir, resolveJsonFilePath, resolveMdFilePath } from '../core/content-manager.js'\nimport { checkReferences, deleteModel, readModel, writeModel, validateModelDefinition, fieldDefZodSchema } from '../core/model-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\n// Shared field definition schema — single source of truth with normalize extract\nconst fieldDefSchema = fieldDefZodSchema\n\nexport function registerModelTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_model_save ───\n server.tool(\n 'contentrain_model_save',\n 'Create or update a model definition. Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n id: z.string().describe('Model ID (kebab-case, e.g. \"blog-post\")'),\n name: z.string().describe('Human-readable name'),\n kind: z.enum(['singleton', 'collection', 'document', 'dictionary']).describe('Model kind'),\n domain: z.string().describe('Content domain (e.g. \"blog\", \"marketing\", \"system\")'),\n i18n: z.boolean().describe('Whether this model supports localization'),\n description: z.string().optional().describe('Model description'),\n fields: fieldDefSchema.optional().describe('Field definitions (not needed for dictionary)'),\n content_path: z.string().optional().describe('Framework-relative path for content files (e.g. \"content/blog\", \"locales\"). When set, content is written here instead of .contentrain/content/'),\n locale_strategy: z.enum(['file', 'suffix', 'directory', 'none']).optional().describe('How locale is encoded in file names. Default: \"file\"'),\n },\n TOOL_ANNOTATIONS['contentrain_model_save']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n // Validate\n const errors = validateModel(input)\n if (errors.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Validation failed', details: errors }) }],\n isError: true,\n }\n }\n\n // Reject invalid locale_strategy + i18n combinations\n if (input.locale_strategy === 'none' && input.i18n !== false) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'locale_strategy \"none\" requires i18n:false. The \"none\" strategy stores a single file without locale encoding, which is incompatible with multi-locale content. Use \"file\", \"suffix\", or \"directory\" for i18n models.',\n }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const existing = await readModel(projectRoot, input.id)\n const action = existing ? 'updated' : 'created'\n\n const model: ModelDefinition = {\n id: input.id,\n name: input.name,\n kind: input.kind,\n domain: input.domain,\n i18n: input.i18n,\n description: input.description,\n fields: input.fields as ModelDefinition['fields'],\n content_path: input.content_path,\n locale_strategy: input.locale_strategy,\n }\n\n const branch = buildBranchName('model', input.id)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n await writeModel(wt, model)\n })\n\n await tx.commit(`[contentrain] ${action}: ${input.id}`, { tool: 'contentrain_model_save', model: input.id })\n const gitResult = await tx.complete()\n\n const defaultLocale = config.locales.default\n // Build accurate content path using path resolvers\n const contentDir = resolveContentDir(projectRoot, model)\n const contentPath = model.content_path ?? `.contentrain/content/${input.domain}/${input.id}`\n let exampleFilePath: string\n if (model.kind === 'document') {\n exampleFilePath = resolveMdFilePath(contentDir, model, defaultLocale, '{slug}')\n } else {\n exampleFilePath = resolveJsonFilePath(contentDir, model, defaultLocale)\n }\n // Make the path relative for display\n const displayPath = exampleFilePath.replace(projectRoot + '/', '').replace(projectRoot, '')\n const importSnippet: Record<string, string> = {\n generic: `import data from '${displayPath}'`,\n }\n if (config.stack === 'nuxt') {\n importSnippet['nuxt'] = model.kind === 'document'\n ? `const { data } = await useAsyncData(() => queryContent('${model.domain}/${model.id}').locale('${defaultLocale}').find())`\n : `const { data } = await useFetch('/api/content/${model.id}?locale=${defaultLocale}')`\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Model saved and committed to git. Do NOT manually edit .contentrain/ files.',\n action,\n model: input.id,\n validation: { valid: true, errors: [] },\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n content_path: contentPath + '/',\n example_file: displayPath,\n import_snippet: importSnippet,\n next_steps: ['Add content with contentrain_content_save'],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Model save failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_model_delete ───\n server.tool(\n 'contentrain_model_delete',\n 'Delete a model and its content/meta. Changes are auto-committed to git — do NOT manually edit .contentrain/ files.',\n {\n model: z.string().describe('Model ID to delete'),\n confirm: z.literal(true).describe('Must be true to confirm deletion'),\n },\n TOOL_ANNOTATIONS['contentrain_model_delete']!,\n async ({ model: modelId }) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n // Check model exists\n const existing = await readModel(projectRoot, modelId)\n if (!existing) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${modelId}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const deleteHealth = await checkBranchHealth(projectRoot)\n if (deleteHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: deleteHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Check references\n const refs = await checkReferences(projectRoot, modelId)\n if (refs.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n deleted: false,\n error: 'REFERENCED_MODEL',\n referenced_by: refs,\n next_steps: ['Remove relation fields from referencing models first'],\n }, null, 2) }],\n }\n }\n\n const branch = buildBranchName('model', modelId)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let filesRemoved: string[] = []\n\n await tx.write(async (wt) => {\n filesRemoved = await deleteModel(wt, modelId)\n })\n\n await tx.commit(`[contentrain] delete: ${modelId}`, { tool: 'contentrain_model_delete', model: modelId })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Model deleted and committed to git. Do NOT manually edit .contentrain/ files.',\n deleted: true,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n files_removed: filesRemoved,\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Delete failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n}\n\n// validateModel is now validateModelDefinition from model-manager.ts (shared)\nconst validateModel = validateModelDefinition\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { readModel } from '../core/model-manager.js'\nimport { writeContent, deleteContent, listContent } from '../core/content-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { validateProject } from '../core/validator.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerContentTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_content_save ───\n server.tool(\n 'contentrain_content_save',\n 'Save content entries. Entry format varies by model kind: DICTIONARY — provide \"locale\" and \"data\" (flat key-value, all string values); \"id\" and \"slug\" are ignored; data keys are the identities. COLLECTION — provide \"locale\" and \"data\"; \"id\" is optional (auto-generated if omitted); \"slug\" is ignored. DOCUMENT — provide \"slug\" (required), \"locale\", and \"data\"; use the \"body\" key inside data for markdown content. SINGLETON — provide only \"locale\" and \"data\". Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n model: z.string().describe('Model ID'),\n entries: z.array(z.object({\n id: z.string().optional().describe('Entry ID (collection only, auto-generated if omitted)'),\n slug: z.string().optional().describe('Slug (document only)'),\n locale: z.string().optional().describe('Locale code (defaults to config default)'),\n data: z.record(z.string(), z.unknown()).describe('Content data. For documents, include \"body\" key for markdown content.'),\n publish_at: z.string().optional().describe('ISO 8601 date for scheduled publishing (stored in meta)'),\n expire_at: z.string().optional().describe('ISO 8601 date for scheduled expiry (stored in meta, must be after publish_at)'),\n })).describe('Content entries to save'),\n },\n TOOL_ANNOTATIONS['contentrain_content_save']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Validate locales and scheduling fields before starting transaction\n for (const entry of input.entries) {\n if (entry.locale && !config.locales.supported.includes(entry.locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Locale \"${entry.locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n\n // Validate publish_at / expire_at\n if (entry.publish_at !== undefined) {\n const d = new Date(entry.publish_at)\n if (Number.isNaN(d.getTime())) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Invalid publish_at date: \"${entry.publish_at}\". Must be a valid ISO 8601 date string.`,\n }) }],\n isError: true,\n }\n }\n }\n if (entry.expire_at !== undefined) {\n const d = new Date(entry.expire_at)\n if (Number.isNaN(d.getTime())) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Invalid expire_at date: \"${entry.expire_at}\". Must be a valid ISO 8601 date string.`,\n }) }],\n isError: true,\n }\n }\n }\n if (entry.publish_at !== undefined && entry.expire_at !== undefined) {\n if (new Date(entry.expire_at) <= new Date(entry.publish_at)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `expire_at (\"${entry.expire_at}\") must be after publish_at (\"${entry.publish_at}\").`,\n }) }],\n isError: true,\n }\n }\n }\n\n // Merge scheduling fields into data so meta-manager picks them up\n if (entry.publish_at !== undefined) entry.data['publish_at'] = entry.publish_at\n if (entry.expire_at !== undefined) entry.data['expire_at'] = entry.expire_at\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('content', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let results: Awaited<ReturnType<typeof writeContent>>\n\n let entryIds: string[] = []\n\n await tx.write(async (wt) => {\n results = await writeContent(wt, model, input.entries, config)\n entryIds = results!.map(r => r.id ?? r.slug ?? r.locale).filter(Boolean) as string[]\n })\n\n await tx.commit(`[contentrain] content: ${input.model}`, {\n tool: 'contentrain_content_save',\n model: input.model,\n locale: input.entries[0]?.locale,\n entries: entryIds,\n })\n const gitResult = await tx.complete()\n\n // Run real validation after save — don't fake it\n const validationResult = await validateProject(projectRoot, { model: input.model })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Content saved and committed to git. Do NOT manually edit .contentrain/ files.',\n results: results!,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n validation: {\n valid: validationResult.valid,\n errors: validationResult.issues.filter(i => i.severity === 'error').map(i => i.message),\n },\n context_updated: true,\n next_steps: [\n ...(!validationResult.valid ? ['WARNING: Content has validation errors — run contentrain_validate for details'] : []),\n ...(model.kind === 'collection'\n ? ['Use contentrain_content_list to verify', 'Add more entries or publish']\n : ['Use contentrain_content_list to verify']),\n ],\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Content save failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_content_delete ───\n server.tool(\n 'contentrain_content_delete',\n 'Delete content entries. For dictionaries, use \"keys\" to remove specific keys (omit to delete entire locale file). Changes are auto-committed to git — do NOT manually edit .contentrain/ files after calling this tool.',\n {\n model: z.string().describe('Model ID'),\n id: z.string().optional().describe('Entry ID (collection)'),\n slug: z.string().optional().describe('Slug (document)'),\n locale: z.string().optional().describe('Locale code'),\n keys: z.array(z.string()).optional().describe('Dictionary only: specific keys to remove. Omit to delete entire locale file.'),\n confirm: z.literal(true).describe('Must be true to confirm deletion'),\n },\n TOOL_ANNOTATIONS['contentrain_content_delete']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const deleteHealth = await checkBranchHealth(projectRoot)\n if (deleteHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: deleteHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('content', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let removed: string[] = []\n\n await tx.write(async (wt) => {\n removed = await deleteContent(wt, model, {\n id: input.id,\n slug: input.slug,\n locale: input.locale,\n keys: input.keys,\n })\n })\n\n await tx.commit(`[contentrain] delete content: ${input.model}`, {\n tool: 'contentrain_content_delete',\n model: input.model,\n locale: input.locale,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: 'Content deleted and committed to git. Do NOT manually edit .contentrain/ files.',\n deleted: true,\n files_removed: removed,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Delete failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n },\n )\n\n // ─── contentrain_content_list ───\n server.tool(\n 'contentrain_content_list',\n 'List content entries (read-only). Returns data from .contentrain/ — do NOT manually create or modify content files.',\n {\n model: z.string().describe('Model ID'),\n locale: z.string().optional().describe('Locale code (defaults to config default)'),\n filter: z.record(z.string(), z.unknown()).optional().describe('Filter criteria (collection only)'),\n resolve: z.boolean().optional().describe('Resolve relation fields to actual data'),\n limit: z.number().optional().describe('Max entries to return'),\n offset: z.number().optional().describe('Skip N entries'),\n },\n TOOL_ANNOTATIONS['contentrain_content_list']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n try {\n const result = await listContent(projectRoot, model, {\n locale: input.locale,\n filter: input.filter as Record<string, unknown>,\n resolve: input.resolve,\n limit: input.limit,\n offset: input.offset,\n }, config)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `List failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport { z } from 'zod'\nimport { simpleGit } from 'simple-git'\nimport { validateProject } from '../core/validator.js'\nimport { readConfig } from '../core/config.js'\nimport { createTransaction, buildBranchName, mergeBranch } from '../git/transaction.js'\nimport { checkBranchHealth, cleanupMergedBranches } from '../git/branch-lifecycle.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerWorkflowTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_validate ───\n server.tool(\n 'contentrain_validate',\n 'Validate project content against model schemas. Detects required field violations, type mismatches, broken relations, secret leaks, i18n parity issues, and more. If fix:true, auto-fixes structural issues (canonical sort, orphan meta, missing locale files) — do NOT manually edit .contentrain/ files.',\n {\n model: z.string().optional().describe('Model ID to validate (omit for all models)'),\n fix: z.boolean().optional().describe('Auto-fix structural issues (canonical sort, orphan meta, missing locale files). Default: false'),\n },\n TOOL_ANNOTATIONS['contentrain_validate']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n try {\n let result: Awaited<ReturnType<typeof validateProject>> | undefined\n\n if (input.fix) {\n // Branch health gate for fix mode (creates a branch)\n const fixHealth = await checkBranchHealth(projectRoot)\n if (fixHealth.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: fixHealth.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before auto-fixing.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n // Use git transaction for fixes\n const branch = buildBranchName('fix', 'validate')\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n await tx.write(async (wt) => {\n result = await validateProject(wt, { model: input.model, fix: true })\n })\n\n if (result!.fixed > 0) {\n await tx.commit(`[contentrain] validate: auto-fix ${result!.fixed} issue(s)`, {\n tool: 'contentrain_validate',\n model: input.model ?? '*',\n })\n const gitResult = await tx.complete()\n\n const nextSteps: string[] = []\n if (result!.summary.errors > 0) nextSteps.push('Fix remaining errors manually')\n if (result!.summary.warnings > 0) nextSteps.push('Review warnings')\n nextSteps.push('Run contentrain_validate again to verify')\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: `Validation complete. ${result!.fixed} issue(s) auto-fixed and committed to git. Do NOT manually edit .contentrain/ files.`,\n ...result!,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } else {\n // Nothing to fix, cleanup the branch\n await tx.cleanup()\n }\n } catch (error) {\n await tx.cleanup()\n throw error\n } finally {\n await tx.cleanup()\n }\n }\n\n // No fix or nothing was fixed — run read-only validation\n if (!result) {\n result = await validateProject(projectRoot, { model: input.model, fix: false })\n }\n\n const nextSteps: string[] = []\n if (result.summary.errors > 0) nextSteps.push('Fix errors in content using contentrain_content_save')\n if (result.summary.warnings > 0) nextSteps.push('Review warnings')\n if (result.valid) nextSteps.push('Run contentrain_submit to push changes')\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'validated',\n message: result.valid\n ? 'All validation checks passed.'\n : `Validation found ${result.summary.errors} error(s) and ${result.summary.warnings} warning(s).`,\n ...result,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Validation failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n\n // ─── contentrain_submit ───\n server.tool(\n 'contentrain_submit',\n 'Push contentrain/* branches to remote. MCP is push-only — PR creation is handled by the platform. Do NOT manually push or create PRs.',\n {\n branches: z.array(z.string()).optional().describe('Specific branch names to push (omit for all contentrain/* branches)'),\n message: z.string().optional().describe('Optional message for the push operation'),\n },\n TOOL_ANNOTATIONS['contentrain_submit']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const git = simpleGit(projectRoot)\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n\n // Check remote exists\n let hasRemote = false\n try {\n const remotes = await git.getRemotes()\n hasRemote = remotes.some(r => r.name === remoteName)\n } catch {\n hasRemote = false\n }\n\n if (!hasRemote) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `No remote \"${remoteName}\" found. Configure a git remote first.`,\n next_steps: [`git remote add ${remoteName} <url>`],\n }) }],\n isError: true,\n }\n }\n\n try {\n // Determine the base branch for merge-status checks\n const baseBranch = config.repository?.default_branch\n ?? process.env['CONTENTRAIN_BRANCH']\n ?? ((await git.raw(['branch', '--show-current'])).trim() || 'main')\n\n // Get contentrain branches\n const branchSummary = await git.branchLocal()\n\n // Get branches NOT yet merged into base (only these need pushing)\n let unmergedRaw = ''\n try {\n unmergedRaw = await git.raw(['branch', '--no-merged', baseBranch])\n } catch {\n // If base branch doesn't exist yet, treat all as unmerged\n unmergedRaw = branchSummary.all.join('\\n')\n }\n const unmergedSet = new Set(\n unmergedRaw.split('\\n').map(b => b.replace(/^\\*?\\s+/, '').trim()).filter(Boolean),\n )\n\n let branchesToPush: string[]\n\n if (input.branches && input.branches.length > 0) {\n branchesToPush = input.branches.filter(b => branchSummary.all.includes(b))\n const missing = input.branches.filter(b => !branchSummary.all.includes(b))\n if (missing.length > 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Branches not found: ${missing.join(', ')}`,\n }) }],\n isError: true,\n }\n }\n // Filter out already-merged branches from explicit list\n branchesToPush = branchesToPush.filter(b => unmergedSet.has(b))\n } else {\n branchesToPush = branchSummary.all.filter(b => b.startsWith('cr/') && unmergedSet.has(b))\n }\n\n if (branchesToPush.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: 'No unmerged cr/* branches found to push.',\n next_steps: ['Make changes first with contentrain_content_save or contentrain_model_save'],\n }) }],\n isError: true,\n }\n }\n\n // Push each branch\n const pushed: string[] = []\n const errors: Array<{ branch: string; error: string }> = []\n\n for (const branch of branchesToPush) {\n try {\n await git.push(remoteName, branch)\n pushed.push(branch)\n } catch (error) {\n errors.push({\n branch,\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n // Best-effort: push the contentrain branch itself\n try {\n await git.push(remoteName, CONTENTRAIN_BRANCH)\n } catch {\n // contentrain branch push is best-effort\n }\n\n // Lazy cleanup: delete merged branches after push\n const cleanup = await cleanupMergedBranches(projectRoot)\n\n const nextSteps: string[] = []\n if (pushed.length > 0) nextSteps.push('Create PRs on your git platform for review')\n if (pushed.length > 0) nextSteps.push('For merge: use contentrain_merge or direct user to http://localhost:3333/branches')\n if (errors.length > 0) nextSteps.push('Fix push errors and retry')\n if (cleanup.remaining >= 50) nextSteps.push(`Warning: ${cleanup.remaining} active contentrain branches. Consider reviewing old branches.`)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n message: `Pushed ${pushed.length} branch(es) to ${remoteName}.`,\n pushed,\n errors: errors.length > 0 ? errors : undefined,\n remote: remoteName,\n cleanup: cleanup.deleted > 0 ? { deleted: cleanup.deleted, remaining: cleanup.remaining } : undefined,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Submit failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n\n // ─── contentrain_merge ───\n server.tool(\n 'contentrain_merge',\n 'Merge a review-mode branch into contentrain. Local git operation — no external platform needed. Merges the feature branch into the contentrain branch, advances the base branch via update-ref, and selectively syncs .contentrain/ files to the working tree.',\n {\n branch: z.string().describe('Branch name to merge (e.g. cr/normalize/extract/...)'),\n confirm: z.literal(true).describe('Must be true to confirm the merge'),\n },\n TOOL_ANNOTATIONS['contentrain_merge']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n try {\n // Verify branch exists\n const git = simpleGit(projectRoot)\n const branches = await git.branchLocal()\n if (!branches.all.includes(input.branch)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Branch \"${input.branch}\" not found locally.`,\n next_steps: ['Check branch name with git branch -l', 'Use contentrain_submit to push first if needed'],\n }) }],\n isError: true,\n }\n }\n\n const result = await mergeBranch(projectRoot, input.branch)\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'merged',\n action: result.action,\n commit: result.commit,\n sync: result.sync,\n next_steps: [\n 'Run `npx contentrain generate` to update SDK client',\n 'Run contentrain_validate to verify content integrity',\n result.sync.skipped.length > 0\n ? `${result.sync.skipped.length} file(s) skipped due to local changes — resolve manually`\n : '',\n ].filter(Boolean),\n }, null, 2) }],\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Merge failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { FieldDef } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { buildGraph } from '../core/graph-builder.js'\nimport { scanCandidates, scanSummary } from '../core/scanner.js'\nimport { applyExtract, applyReuse } from '../core/apply-manager.js'\nimport { fieldDefZodSchema } from '../core/model-manager.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerNormalizeTools(server: McpServer, projectRoot: string): void {\n // ─── contentrain_scan ───\n server.tool(\n 'contentrain_scan',\n 'Scan project source code for content strings. Three modes: \"graph\" builds import/component graph for project intelligence, \"candidates\" extracts string literals with pre-filtering and pagination, \"summary\" provides quick overview stats. Read-only — no changes to disk or git. MCP finds strings deterministically; the agent decides what is content. Recommended workflow: start with \"summary\" or \"graph\" for orientation, then paginate through \"candidates\" to evaluate strings.',\n {\n mode: z.enum(['graph', 'candidates', 'summary']).optional().describe('Scan mode. Default: candidates'),\n paths: z.array(z.string()).optional().describe('Directories to scan (relative to project root). Default: auto-detect'),\n include: z.array(z.string()).optional().describe('File extensions to include. Default: .tsx, .jsx, .vue, .ts, .js, .mjs, .astro, .svelte'),\n exclude: z.array(z.string()).optional().describe('Additional directory names to exclude'),\n limit: z.number().optional().describe('Candidates mode: batch size. Default: 50'),\n offset: z.number().optional().describe('Candidates mode: pagination offset. Default: 0'),\n min_length: z.number().optional().describe('Candidates mode: minimum string length. Default: 2'),\n max_length: z.number().optional().describe('Candidates mode: maximum string length. Default: 500'),\n },\n TOOL_ANNOTATIONS['contentrain_scan']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const mode = input.mode ?? 'candidates'\n\n try {\n switch (mode) {\n case 'graph': {\n const graph = await buildGraph(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'graph',\n ...graph,\n next_steps: [\n 'Use mode:candidates to scan specific files/directories for strings',\n 'Focus on pages and components with high string counts first',\n ],\n }, null, 2) }],\n }\n }\n\n case 'candidates': {\n const result = await scanCandidates(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n limit: input.limit,\n offset: input.offset,\n min_length: input.min_length,\n max_length: input.max_length,\n })\n\n const nextSteps: string[] = []\n if (result.stats.has_more) {\n nextSteps.push(`Use offset:${(input.offset ?? 0) + (input.limit ?? 50)} for next batch`)\n }\n nextSteps.push('Evaluate each candidate: is it user-facing content? Which domain/model?')\n if (result.duplicates.length > 0) {\n nextSteps.push('Consider deduplicating repeated strings into shared models')\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'candidates',\n ...result,\n next_steps: nextSteps,\n }, null, 2) }],\n }\n }\n\n case 'summary': {\n const result = await scanSummary(projectRoot, {\n paths: input.paths,\n include: input.include,\n exclude: input.exclude,\n min_length: input.min_length,\n max_length: input.max_length,\n })\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'summary',\n ...result,\n next_steps: [\n 'Use mode:graph for project structure analysis',\n 'Use mode:candidates to scan directories with most candidates',\n ],\n }, null, 2) }],\n }\n }\n\n default:\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown mode: ${mode}` }) }],\n isError: true,\n }\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Scan failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n\n // ─── contentrain_apply ───\n server.tool(\n 'contentrain_apply',\n 'Apply normalize operations. Two modes: \"extract\" writes agent-approved strings to Contentrain content files (source untouched), \"reuse\" patches source files with agent-provided replacement expressions. DRY RUN (default, dry_run:true): validates inputs, resolves conflicts, and returns a full preview — NO changes to disk or git. EXECUTE (dry_run:false): writes files to disk, commits to a branch, and requires branch health check to pass. Recommended workflow: always run dry_run first, review the preview, then call again with dry_run:false to execute. Normalize operations always use review workflow (never auto-merge).',\n {\n mode: z.enum(['extract', 'reuse']).describe('Apply mode: extract (content creation) or reuse (source patching)'),\n dry_run: z.boolean().optional().default(true).describe('Defaults to preview mode (dry_run:true). Set dry_run:false to execute after reviewing the preview.'),\n\n // Extract mode fields\n extractions: z.array(z.object({\n model: z.string().describe('Model ID (e.g. \"ui-texts\", \"hero-section\")'),\n kind: z.enum(['singleton', 'collection', 'dictionary', 'document']).describe('Model kind'),\n domain: z.string().describe('Content domain (e.g. \"marketing\", \"app\")'),\n i18n: z.boolean().optional().describe('Enable i18n. Default: true'),\n fields: fieldDefZodSchema.optional().describe('Field definitions — shared schema with model_save for full parity'),\n entries: z.array(z.object({\n locale: z.string().optional().describe('Locale. Default: project default'),\n slug: z.string().optional().describe('Document slug'),\n data: z.record(z.any()).describe('Content data'),\n source: z.object({\n file: z.string().describe('Source file path'),\n line: z.number().describe('Source line number'),\n value: z.string().describe('Original string value'),\n }).optional().describe('Source tracking for traceability'),\n sources: z.array(z.object({\n file: z.string().describe('Source file path'),\n line: z.number().describe('Source line number'),\n key: z.string().describe('Dictionary key this source maps to'),\n value: z.string().describe('Original string value'),\n })).optional().describe('Per-key source tracking for dictionary models'),\n })).describe('Content entries to create'),\n })).optional().describe('Extract mode: content extractions'),\n\n // Reuse mode fields\n scope: z.object({\n model: z.string().optional().describe('Target model ID'),\n domain: z.string().optional().describe('Target domain'),\n }).optional().describe('Reuse mode: scope (model or domain required)'),\n patches: z.array(z.object({\n file: z.string().describe('Relative file path to patch'),\n line: z.number().describe('Line number hint (±10 line search)'),\n old_value: z.string().describe('Original string to replace'),\n new_expression: z.string().describe('Replacement expression (agent-determined)'),\n import_statement: z.string().optional().describe('Import to add if needed'),\n })).optional().describe('Reuse mode: patches to apply (max 100)'),\n },\n TOOL_ANNOTATIONS['contentrain_apply']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n try {\n switch (input.mode) {\n case 'extract': {\n if (!input.extractions || input.extractions.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Extract mode requires extractions array' }) }],\n isError: true,\n }\n }\n\n const result = await applyExtract(projectRoot, {\n extractions: input.extractions.map(e => ({\n ...e,\n fields: e.fields as Record<string, FieldDef> | undefined,\n })),\n dry_run: input.dry_run,\n })\n\n // Check for branch-blocked response\n if (result.error !== undefined) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'extract',\n ...result,\n }, null, 2) }],\n isError: true,\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'extract',\n ...result,\n }, null, 2) }],\n }\n }\n\n case 'reuse': {\n if (!input.scope) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Reuse mode requires scope (model or domain)' }) }],\n isError: true,\n }\n }\n if (!input.patches || input.patches.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Reuse mode requires patches array' }) }],\n isError: true,\n }\n }\n\n const result = await applyReuse(projectRoot, {\n scope: input.scope,\n patches: input.patches,\n dry_run: input.dry_run,\n })\n\n // Check for branch-blocked response\n if (result.error !== undefined) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'reuse',\n ...result,\n }, null, 2) }],\n isError: true,\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n mode: 'reuse',\n ...result,\n }, null, 2) }],\n }\n }\n\n default:\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown mode: ${input.mode}` }) }],\n isError: true,\n }\n }\n } catch (error) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Apply failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { EntryMeta } from '@contentrain/types'\nimport { z } from 'zod'\nimport { readConfig } from '../core/config.js'\nimport { readModel } from '../core/model-manager.js'\nimport { resolveContentDir, resolveJsonFilePath, deleteContent } from '../core/content-manager.js'\nimport { readMeta, writeMeta } from '../core/meta-manager.js'\nimport { createTransaction, buildBranchName } from '../git/transaction.js'\nimport { checkBranchHealth } from '../git/branch-lifecycle.js'\nimport { readJson, writeJson } from '../util/fs.js'\nimport { TOOL_ANNOTATIONS } from './annotations.js'\n\nexport function registerBulkTools(server: McpServer, projectRoot: string): void {\n server.tool(\n 'contentrain_bulk',\n 'Batch operations on content entries. All operations are auto-committed to git.',\n {\n operation: z.enum(['copy_locale', 'update_status', 'delete_entries']),\n model: z.string().describe('Model ID'),\n source_locale: z.string().optional().describe('Source locale for copy_locale operation'),\n target_locale: z.string().optional().describe('Target locale for copy_locale operation'),\n entry_ids: z.array(z.string()).optional().describe('Entry IDs for update_status or delete_entries'),\n status: z.enum(['draft', 'in_review', 'published', 'rejected', 'archived']).optional().describe('New status for update_status'),\n confirm: z.boolean().optional().describe('Must be true for delete_entries'),\n },\n TOOL_ANNOTATIONS['contentrain_bulk']!,\n async (input) => {\n const config = await readConfig(projectRoot)\n if (!config) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Project not initialized. Run contentrain_init first.' }) }],\n isError: true,\n }\n }\n\n const model = await readModel(projectRoot, input.model)\n if (!model) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Model \"${input.model}\" not found` }) }],\n isError: true,\n }\n }\n\n // Branch health gate\n const health = await checkBranchHealth(projectRoot)\n if (health.blocked) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: health.message,\n action: 'blocked',\n hint: 'Merge or delete old contentrain/* branches before creating new ones.',\n }, null, 2) }],\n isError: true,\n }\n }\n\n switch (input.operation) {\n case 'copy_locale': {\n if (!input.source_locale || !input.target_locale) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'copy_locale requires source_locale and target_locale' }) }],\n isError: true,\n }\n }\n if (!config.locales.supported.includes(input.source_locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Source locale \"${input.source_locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n if (!config.locales.supported.includes(input.target_locale)) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Target locale \"${input.target_locale}\" is not supported. Supported: [${config.locales.supported.join(', ')}]`,\n }) }],\n isError: true,\n }\n }\n\n // Guard: copy_locale requires i18n model — non-i18n models use data.json for all locales\n if (!model.i18n) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `Model \"${input.model}\" has i18n disabled. copy_locale only works with i18n-enabled models. Enable i18n first with contentrain_model_save.`,\n }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection' && model.kind !== 'singleton' && model.kind !== 'dictionary') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'copy_locale is only supported for collection, singleton, and dictionary models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model, input.target_locale)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let copiedCount = 0\n\n await tx.write(async (wt) => {\n const cDir = resolveContentDir(wt, model)\n const sourceFile = resolveJsonFilePath(cDir, model, input.source_locale!)\n const targetFile = resolveJsonFilePath(cDir, model, input.target_locale!)\n\n const sourceData = await readJson<Record<string, unknown>>(sourceFile)\n if (!sourceData) {\n throw new Error(`No content found for locale \"${input.source_locale}\" in model \"${input.model}\"`)\n }\n\n await writeJson(targetFile, sourceData)\n\n if (model.kind === 'collection') {\n const entries = sourceData as Record<string, Record<string, unknown>>\n const entryIds = Object.keys(entries)\n const metaOps = entryIds.map(entryId =>\n writeMeta(wt, model, { locale: input.target_locale!, entryId }, {\n status: 'draft',\n source: 'agent',\n updated_by: 'contentrain-mcp',\n }),\n )\n await Promise.all(metaOps)\n copiedCount = entryIds.length\n } else {\n await writeMeta(wt, model, { locale: input.target_locale! }, {\n status: 'draft',\n source: 'agent',\n updated_by: 'contentrain-mcp',\n })\n copiedCount = 1\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: copy ${input.source_locale} → ${input.target_locale} for ${input.model}`, {\n tool: 'contentrain_bulk',\n model: input.model,\n locale: input.target_locale!,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'copy_locale',\n message: `Copied ${copiedCount} entries from ${input.source_locale} to ${input.target_locale}.`,\n copied: copiedCount,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `copy_locale failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n case 'update_status': {\n if (!input.entry_ids || input.entry_ids.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status requires entry_ids' }) }],\n isError: true,\n }\n }\n if (!input.status) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status requires status' }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'update_status with entry_ids is only supported for collection models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n let updatedCount = 0\n const notFound: string[] = []\n\n await tx.write(async (wt) => {\n for (const locale of config.locales.supported) {\n const metaData = await readMeta(wt, model, { locale }) as Record<string, EntryMeta> | null\n\n const updateOps: Array<Promise<void>> = []\n for (const entryId of input.entry_ids!) {\n const existing = metaData?.[entryId]\n if (existing) {\n updateOps.push(writeMeta(wt, model, { locale, entryId }, {\n ...existing,\n status: input.status!,\n updated_by: 'contentrain-mcp',\n }))\n updatedCount++\n } else if (locale === config.locales.default) {\n notFound.push(entryId)\n }\n }\n await Promise.all(updateOps)\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: update status → ${input.status} for ${input.model}`, {\n tool: 'contentrain_bulk',\n model: input.model,\n entries: input.entry_ids!,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'update_status',\n message: `Updated ${updatedCount} meta entries to status \"${input.status}\".`,\n updated: updatedCount,\n not_found: notFound.length > 0 ? notFound : undefined,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `update_status failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n case 'delete_entries': {\n if (!input.entry_ids || input.entry_ids.length === 0) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries requires entry_ids' }) }],\n isError: true,\n }\n }\n if (input.confirm !== true) {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries requires confirm:true' }) }],\n isError: true,\n }\n }\n\n if (model.kind !== 'collection') {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: 'delete_entries with entry_ids is only supported for collection models' }) }],\n isError: true,\n }\n }\n\n const branch = buildBranchName('bulk', input.model)\n const tx = await createTransaction(projectRoot, branch)\n\n try {\n const allRemoved: string[] = []\n\n await tx.write(async (wt) => {\n for (const entryId of input.entry_ids!) {\n const removed = await deleteContent(wt, model, { id: entryId })\n allRemoved.push(...removed)\n }\n\n })\n\n await tx.commit(`[contentrain] bulk: delete ${input.entry_ids.length} entries from ${input.model}`, {\n tool: 'contentrain_bulk',\n model: input.model,\n entries: input.entry_ids!,\n })\n const gitResult = await tx.complete()\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n status: 'committed',\n operation: 'delete_entries',\n message: `Deleted ${input.entry_ids.length} entries.`,\n deleted: input.entry_ids.length,\n files_removed: allRemoved,\n git: { branch, action: gitResult.action, commit: gitResult.commit, ...(gitResult.sync ? { sync: gitResult.sync } : {}) },\n context_updated: true,\n }, null, 2) }],\n }\n } catch (error) {\n await tx.cleanup()\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({\n error: `delete_entries failed: ${error instanceof Error ? error.message : String(error)}`,\n }) }],\n isError: true,\n }\n } finally {\n await tx.cleanup()\n }\n }\n\n default: {\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: `Unknown operation: ${input.operation}` }) }],\n isError: true,\n }\n }\n }\n },\n )\n}\n","","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { registerContextTools } from './tools/context.js'\nimport { registerSetupTools } from './tools/setup.js'\nimport { registerModelTools } from './tools/model.js'\nimport { registerContentTools } from './tools/content.js'\nimport { registerWorkflowTools } from './tools/workflow.js'\nimport { registerNormalizeTools } from './tools/normalize.js'\nimport { registerBulkTools } from './tools/bulk.js'\nimport packageJson from '../package.json' with { type: 'json' }\n\nexport function createServer(projectRoot: string): McpServer {\n const server = new McpServer({\n name: 'contentrain-mcp',\n version: packageJson.version,\n })\n\n registerContextTools(server, projectRoot)\n registerSetupTools(server, projectRoot)\n registerModelTools(server, projectRoot)\n registerContentTools(server, projectRoot)\n registerWorkflowTools(server, projectRoot)\n registerNormalizeTools(server, projectRoot)\n registerBulkTools(server, projectRoot)\n\n return server\n}\n"],"mappings":"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"}
package/dist/server.mjs CHANGED
@@ -6,13 +6,13 @@ import "./model-manager-DW_fOGRM.mjs";
6
6
  import "./context-DIDt_TxX.mjs";
7
7
  import "./detect-DinOQAmK.mjs";
8
8
  import "./branch-lifecycle-wGJg25LT.mjs";
9
- import { t as createServer } from "./server-DfWRpzdP.mjs";
9
+ import { t as createServer } from "./server-Dtq8vqbK.mjs";
10
10
  import "./id-DV_T9Ic8.mjs";
11
11
  import "./transaction-C_Pvux78.mjs";
12
12
  import "./validator-SQYUV2JP.mjs";
13
13
  import "./scan-config-QgnJ1-52.mjs";
14
14
  import "./graph-builder-tM7zj-SX.mjs";
15
- import "./scanner-CQOxARUU.mjs";
15
+ import "./scanner-DUoXnmtI.mjs";
16
16
  import "./tsx-parser-BWiMGAIR.mjs";
17
17
  import "./apply-manager-BkPMfpsN.mjs";
18
18
  export { createServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentrain/mcp",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "mcpName": "io.github.Contentrain/contentrain",
5
5
  "license": "MIT",
6
6
  "description": "Local-first MCP server for AI-generated content governance — 13 deterministic tools for any platform",
@@ -1 +0,0 @@
1
- {"version":3,"file":"scanner-CQOxARUU.mjs","names":[],"sources":["../src/core/ast-scanner/pre-filter.ts","../src/core/ast-scanner/index.ts","../src/core/scanner.ts"],"sourcesContent":["import type { ExtractedString } from './types.js'\n\n// ─── Pre-filter Result ───\n\nexport interface PreFilterResult {\n /** Strings that passed the pre-filter (candidates for agent) */\n candidates: ExtractedString[]\n /** Total number of strings removed by shouldSkip */\n skipped: number\n /** Total number of strings removed by low content score */\n lowConfidence: number\n /** Breakdown: skip reason → count */\n skipReasons: Record<string, number>\n}\n\n// ─── Value-based regexes ───\n\nconst PURE_NUMBER_RE = /^-?\\d+(\\.\\d+)?$/\nconst HEX_COLOR_RE = /^#[0-9a-f]{3,8}$/i\nconst FILE_EXT_RE = /\\.(png|jpg|jpeg|gif|svg|webp|ico|css|scss|less|js|ts|tsx|jsx|json|md|html|xml|yaml|yml|woff|woff2|ttf|eot|mp4|webm|mp3|wav|pdf)$/i\nconst SVG_PATH_DATA_RE = /^[Mm][\\d\\s.,LHVCSQTAZlhvcsqtazmMzZ-]+$/\nconst SVG_VIEWBOX_RE = /^\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?$/\nconst I18N_KEY_RE = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/\nconst TECHNICAL_IDENTIFIER_RE = /^[a-z][a-z0-9_-]*$/\nconst ERROR_CODE_RE = /^[A-Z][A-Z0-9_]+$/\nconst PLACEHOLDER_RE = /^\\{\\d+\\}$|^\\.{2,}$/\nconst CAMEL_CASE_RE = /^[a-z]+[A-Z]/\n\n// ─── URL / path detection (consolidated from legacy isNonContent) ───\n\nfunction isURLLike(str: string): boolean {\n if (/^(https?|ftp|file|mailto|data):/.test(str)) return true\n if (/^(\\.\\.?\\/|\\/|[A-Za-z]:\\\\)/.test(str)) return true\n if (/^['\"]?[@a-z][\\w-]*/.test(str.toLowerCase()) && !str.includes(' ') && (str.includes('/') || str.includes('.'))) {\n return true\n }\n return false\n}\n\n// ─── CSS / Tailwind detection ───\n\nconst TAILWIND_SEGMENT_RE = /^(?:bg-|text-|border-|flex|grid|p-|px-|py-|pt-|pb-|pl-|pr-|m-|mx-|my-|mt-|mb-|ml-|mr-|rounded|shadow|w-|h-|min-|max-|gap-|space-|items-|justify-|self-|overflow-|z-|opacity-|transition|duration-|ease-|animate-|font-|leading-|tracking-|decoration-|underline|line-through|uppercase|lowercase|capitalize|truncate|whitespace-|break-|sr-only|not-sr-only|hover:|focus:|active:|disabled:|dark:|sm:|md:|lg:|xl:|2xl:|group-|peer-|ring-|outline-|divide-|table-|col-|row-|aspect-|object-|inset-|top-|right-|bottom-|left-|translate-|rotate-|scale-|skew-|origin-|cursor-|select-|resize-|fill-|stroke-|block|inline|absolute|relative|fixed|sticky|static|float-|clear-|isolate|visible|invisible|grow|shrink|basis-|order-|place-)/\n\nfunction isCssClassList(value: string): boolean {\n const segments = value.trim().split(/\\s+/)\n if (segments.length < 2) return false\n let matched = 0\n for (const seg of segments) {\n if (TAILWIND_SEGMENT_RE.test(seg)) matched++\n }\n return matched / segments.length >= 0.5\n}\n\nfunction isSingleCssUtility(value: string): boolean {\n const trimmed = value.trim()\n if (trimmed.includes(' ')) return false\n return TAILWIND_SEGMENT_RE.test(trimmed)\n}\n\n// ─── SVG technical attributes ───\n\nconst SVG_TECHNICAL_ATTRIBUTES = new Set([\n 'd', 'viewBox', 'points', 'transform', 'pathLength',\n 'xmlns', 'preserveAspectRatio',\n 'stroke-linecap', 'stroke-linejoin', 'stroke-width',\n 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit',\n 'fill-rule', 'clip-rule',\n])\n\nconst SVG_GRAPHIC_ELEMENTS = new Set([\n 'svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon',\n 'ellipse', 'g', 'defs', 'use', 'symbol', 'clipPath', 'mask',\n 'pattern', 'linearGradient', 'radialGradient', 'stop',\n 'marker', 'animate', 'animateTransform', 'image',\n])\n\n// ─── Known function names ───\n\nconst I18N_FUNCTIONS = new Set([\n 't', '$t', 'i18n', 'translate', 'formatMessage', 'msg',\n])\n\nconst EMIT_FUNCTIONS = new Set([\n 'emit', '$emit',\n])\n\n// ─── Translatable attribute whitelist (i18next-cli compatible + extended) ───\n\nconst TRANSLATABLE_ATTRIBUTES = new Set([\n // Standard HTML content attributes\n 'title', 'alt', 'placeholder', 'label', 'summary', 'caption',\n 'abbr', 'accesskey', 'content', 'description',\n // ARIA content\n 'aria-label', 'aria-description', 'aria-placeholder',\n 'aria-roledescription', 'aria-valuetext',\n // React Native accessibility (equivalent to aria-label)\n 'accessibilityLabel', 'accessibilityHint', 'accessibilityValue',\n // Common component content props\n 'heading', 'subheading', 'message', 'hint', 'tooltip',\n 'helper-text', 'error-message', 'success-message',\n 'confirm-text', 'cancel-text', 'empty-text', 'loading-text',\n 'no-data-text', 'no-results-text',\n])\n\n// ─── Translatable object property whitelist (i18next-cli compatible) ───\n\nconst TRANSLATABLE_PROPERTIES = new Set([\n 'label', 'title', 'description', 'text', 'message', 'placeholder',\n 'caption', 'summary', 'heading', 'subheading', 'subtitle', 'tooltip',\n 'hint', 'helpText', 'errorMessage', 'successMessage', 'name',\n])\n\n// ─── shouldSkip: Binary non-content detection ───\n\n/**\n * Determines if a string is definitely NOT user-visible content.\n * Returns skip reason if it should be filtered, null if it should proceed to scoring.\n *\n * Conservative for template_text/jsx_text (tag-between text is almost always content).\n * Aggressive for everything else (technical tokens, config values, framework artifacts).\n */\nexport function shouldSkip(str: ExtractedString): string | null {\n // ── Context-based rules (AST-determined, 100% accurate) ──\n\n if (str.context === 'import_path') return 'import_path'\n if (str.context === 'type_annotation') return 'type_annotation'\n if (str.context === 'css_class') return 'css_class'\n if (str.context === 'css_utility_call') return 'css_utility_call'\n if (str.context === 'console_call') return 'console_call'\n if (str.context === 'test_assertion') return 'test_assertion'\n if (str.context === 'switch_case') return 'switch_case'\n\n const v = str.value\n\n // ── Value-based rules (structural patterns) ──\n\n if (v.length <= 1) return 'single_char'\n if (/^\\s+$/.test(v)) return 'whitespace'\n if (PURE_NUMBER_RE.test(v)) return 'pure_number'\n if (HEX_COLOR_RE.test(v)) return 'hex_color'\n if (FILE_EXT_RE.test(v)) return 'file_extension'\n if (v.startsWith('--')) return 'cli_flag'\n\n // ── i18n key paths (checked before URL — both contain dots, but i18n keys are more specific) ──\n\n if (I18N_KEY_RE.test(v)) return 'i18n_key'\n\n // ── URL/path patterns ──\n\n if (isURLLike(v)) return 'url_path'\n\n // ── CSS patterns ──\n\n if (isCssClassList(v)) return 'css_class_list'\n if (isSingleCssUtility(v)) return 'css_utility_token'\n\n // ── SVG patterns ──\n\n if (v.length > 3 && SVG_PATH_DATA_RE.test(v)) return 'svg_path_data'\n if (SVG_VIEWBOX_RE.test(v)) return 'svg_viewbox'\n if (str.parentProperty !== undefined && SVG_TECHNICAL_ATTRIBUTES.has(str.parentProperty)) return 'svg_technical_attr'\n if (str.context === 'template_attribute' && SVG_GRAPHIC_ELEMENTS.has(str.parent)) return 'svg_element_attr'\n\n // ── Framework event patterns ──\n\n if (v.startsWith('update:')) return 'vue_emit_event'\n\n // ── Placeholder / interpolation ──\n\n if (PLACEHOLDER_RE.test(v)) return 'placeholder'\n\n // ── Known function argument detection ──\n\n if (str.context === 'function_argument' && I18N_FUNCTIONS.has(str.parent)) {\n // i18n function args: filter lowercase identifiers (namespace/key), keep sentences\n if (/^[a-z][a-z0-9_.-]*$/.test(v)) return 'i18n_function_arg'\n }\n\n if (str.context === 'function_argument' && EMIT_FUNCTIONS.has(str.parent)) {\n return 'emit_event_arg'\n }\n\n // ── CRITICAL: Technical identifier detection (i18next-cli proven pattern) ──\n // Single lowercase ASCII word/kebab-case/snake_case < 30 chars → technical token\n // EXEMPT: template_text and jsx_text (tag-between text IS content, even lowercase)\n\n if (str.context !== 'template_text' && str.context !== 'jsx_text') {\n if (TECHNICAL_IDENTIFIER_RE.test(v) && v.length < 30) {\n return 'technical_identifier'\n }\n }\n\n // ── Error codes (SCREAMING_SNAKE_CASE with underscores) ──\n\n if (ERROR_CODE_RE.test(v) && v.includes('_') && v.length > 3) {\n return 'error_code'\n }\n\n return null\n}\n\n// ─── calculateContentScore: 0-1 confidence scoring ───\n\n/**\n * Calculates a content confidence score (0-1) for a string that passed shouldSkip.\n * Uses AST context metadata (our advantage over offset-based tools) combined with\n * value-based signals proven by i18next-cli.\n *\n * Base score: 0.5. Boosted/penalized by context and value characteristics.\n */\nexport function calculateContentScore(str: ExtractedString): number {\n let score = 0.5\n\n // ── Context signals (AST metadata advantage) ──\n\n // Template/JSX text = almost certainly user-visible content\n if (str.context === 'template_text' || str.context === 'jsx_text') {\n score += 0.3\n }\n\n // Content-bearing attribute (title, alt, placeholder, aria-label, etc.)\n if (str.context === 'template_attribute' || str.context === 'jsx_attribute') {\n if (str.parentProperty && TRANSLATABLE_ATTRIBUTES.has(str.parentProperty)) {\n score += 0.2\n } else {\n score -= 0.2 // Unknown/technical attribute\n }\n }\n\n // Content-bearing object property (message, label, description, etc.)\n if (str.context === 'object_property') {\n if (str.parentProperty && TRANSLATABLE_PROPERTIES.has(str.parentProperty)) {\n score += 0.25\n }\n }\n\n // ── Value signals (i18next-cli proven heuristics) ──\n\n // Multi-word strings are more likely content\n const wordCount = str.value.split(/\\s+/).length\n if (wordCount >= 3) score += 0.2\n else if (wordCount === 2) score += 0.1\n\n // Terminal punctuation suggests a sentence\n if (/[.!?:;]$/.test(str.value)) score += 0.1\n\n // Non-ASCII characters (Turkish, Chinese, Arabic, etc.) → almost certainly content\n if (/[\\u0080-\\uFFFF]/.test(str.value)) score += 0.15\n\n // Capitalized first letter with lowercase body (Dashboard, Kaydet, Settings)\n if (/^[A-Z]/.test(str.value) && /[a-z]/.test(str.value)) score += 0.1\n\n // camelCase → probably a technical identifier\n if (CAMEL_CASE_RE.test(str.value)) score -= 0.3\n\n // Contains slash without spaces → path-like\n if (str.value.includes('/') && !str.value.includes(' ')) score -= 0.2\n\n return Math.max(0, Math.min(1, score))\n}\n\n// ─── Public API ───\n\n/**\n * Two-phase pre-filter:\n * 1. shouldSkip(): Binary removal of definite non-content\n * 2. calculateContentScore(): 0-1 confidence scoring for ambiguous strings\n *\n * Returns candidates that passed both phases, with content scores attached.\n */\nexport function applyPreFilter(\n strings: ExtractedString[],\n minScore: number = 0.4,\n): PreFilterResult {\n const candidates: ExtractedString[] = []\n const skipReasons: Record<string, number> = {}\n let skipped = 0\n let lowConfidence = 0\n\n for (const str of strings) {\n // Phase 1: Binary skip\n const skipReason = shouldSkip(str)\n if (skipReason) {\n skipped++\n skipReasons[skipReason] = (skipReasons[skipReason] ?? 0) + 1\n continue\n }\n\n // Phase 2: Content scoring\n const contentScore = calculateContentScore(str)\n if (contentScore < minScore) {\n lowConfidence++\n skipReasons['low_confidence'] = (skipReasons['low_confidence'] ?? 0) + 1\n continue\n }\n\n // Attach score to the extraction for downstream use\n ;(str as ExtractedString & { contentScore: number }).contentScore = contentScore\n candidates.push(str)\n }\n\n return { candidates, skipped, lowConfidence, skipReasons }\n}\n","import type { ExtractedString } from './types.js'\nimport { applyPreFilter } from './pre-filter.js'\nimport { parseTsx } from './tsx-parser.js'\n\n// ─── Extension sets ───\n\nconst TSX_EXTENSIONS = new Set(['.tsx', '.jsx', '.ts', '.js', '.mjs'])\nconst VUE_EXTENSIONS = new Set(['.vue'])\nconst SVELTE_EXTENSIONS = new Set(['.svelte'])\nconst ASTRO_EXTENSIONS = new Set(['.astro'])\n\n// ─── Lazy-loaded parsers ───\n\n/**\n * Lazily import vue-parser. Returns undefined if not available\n * (@vue/compiler-sfc is an optional dependency).\n */\nasync function loadVueParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]> | ExtractedString[]) | undefined> {\n try {\n const mod = await import('./vue-parser.js')\n return mod.parseVue\n } catch {\n return undefined\n }\n}\n\n/**\n * Lazily import svelte-parser. Returns undefined if not available\n * (svelte is an optional dependency).\n */\nasync function loadSvelteParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]>) | undefined> {\n try {\n const mod = await import('./svelte-parser.js')\n return mod.parseSvelte\n } catch {\n return undefined\n }\n}\n\n/**\n * Lazily import astro-parser. Returns undefined if not available\n * (@astrojs/compiler is an optional dependency).\n */\nasync function loadAstroParser(): Promise<((content: string, fileName: string) => Promise<ExtractedString[]>) | undefined> {\n try {\n const mod = await import('./astro-parser.js')\n return mod.parseAstro\n } catch {\n return undefined\n }\n}\n\n// ─── Regex fallback for unknown extensions ───\n\nconst SURROUNDING_MAX = 120\n\n/**\n * Minimal regex-based extractor for file types without AST parsers.\n * Extracts quoted strings and tag text — conservative, low accuracy.\n * Used as fallback when dedicated parsers are unavailable.\n */\nfunction extractWithRegex(content: string, _filePath: string): ExtractedString[] {\n const lines = content.split('\\n')\n const results: ExtractedString[] = []\n\n // Simple quoted string extraction\n const stringRe = /(['\"`])(?:(?!\\1|\\\\).|\\\\.)*?\\1/g\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const trimmed = line.trim()\n\n // Skip comments, imports\n if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) continue\n if (/^\\s*(import|export)\\s/.test(line)) continue\n\n let match: RegExpExecArray | null\n stringRe.lastIndex = 0\n\n while ((match = stringRe.exec(line)) !== null) {\n // Remove surrounding quotes\n const raw = match[0]\n const value = raw.slice(1, -1)\n if (value.length === 0) continue\n\n const surrounding = buildSurrounding(lines, i)\n\n results.push({\n value,\n line: i + 1,\n column: match.index + 1,\n context: 'other',\n scope: 'script',\n parent: '',\n surrounding,\n })\n }\n\n // Tag text extraction: >text<\n const tagTextRe = />([^<>{]+)</g\n let tagMatch: RegExpExecArray | null\n tagTextRe.lastIndex = 0\n\n while ((tagMatch = tagTextRe.exec(line)) !== null) {\n const text = tagMatch[1]!.trim()\n if (text.length === 0) continue\n if (/^[\\s\\W]*$/.test(text) && !/[a-zA-Z]/.test(text)) continue\n\n results.push({\n value: text,\n line: i + 1,\n column: tagMatch.index + 1,\n context: 'template_text',\n scope: 'template',\n parent: '',\n surrounding: buildSurrounding(lines, i),\n })\n }\n }\n\n return results\n}\n\nfunction buildSurrounding(lines: string[], lineIdx: number): string {\n const start = Math.max(0, lineIdx - 1)\n const end = Math.min(lines.length - 1, lineIdx + 1)\n\n const parts: string[] = []\n for (let i = start; i <= end; i++) {\n const line = lines[i]\n if (line !== undefined) {\n parts.push(line)\n }\n }\n\n const joined = parts.join('\\n')\n if (joined.length > SURROUNDING_MAX) {\n return joined.slice(0, SURROUNDING_MAX)\n }\n return joined\n}\n\n// ─── Public API ───\n\n/**\n * Extract strings from a source file with structural context.\n *\n * Routes to the correct parser based on file extension:\n * - .tsx/.jsx/.ts/.js/.mjs -> tsx-parser (AST-based)\n * - .vue -> vue-parser (lazy-loaded, AST-based)\n * - .svelte -> svelte-parser (lazy-loaded, AST-based)\n * - .astro -> astro-parser (lazy-loaded, AST-based)\n * - unknown -> empty array\n *\n * Falls back to regex extraction when the dedicated parser's\n * optional dependency is not installed.\n *\n * Applies structural pre-filter to remove 100% non-content strings.\n * Returns only candidates that should be sent to the agent.\n */\nexport async function extractStrings(\n filePath: string,\n content: string,\n ext: string,\n): Promise<ExtractedString[]> {\n const normalizedExt = ext.startsWith('.') ? ext : `.${ext}`\n let rawStrings: ExtractedString[]\n\n if (TSX_EXTENSIONS.has(normalizedExt)) {\n rawStrings = parseTsx(content, filePath)\n } else if (VUE_EXTENSIONS.has(normalizedExt)) {\n const parseVue = await loadVueParser()\n if (parseVue) {\n rawStrings = await parseVue(content, filePath)\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else if (SVELTE_EXTENSIONS.has(normalizedExt)) {\n const parseSvelte = await loadSvelteParser()\n if (parseSvelte) {\n try {\n rawStrings = await parseSvelte(content, filePath)\n } catch {\n // svelte/compiler not installed — fall back to regex\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else if (ASTRO_EXTENSIONS.has(normalizedExt)) {\n const parseAstro = await loadAstroParser()\n if (parseAstro) {\n try {\n rawStrings = await parseAstro(content, filePath)\n } catch {\n // @astrojs/compiler not installed — fall back to regex\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n rawStrings = extractWithRegex(content, filePath)\n }\n } else {\n return []\n }\n\n // Apply structural pre-filter\n const { candidates } = applyPreFilter(rawStrings)\n return candidates\n}\n\n// Re-export types for convenience\nexport type { ExtractedString, StructuralContext, PreFilterRule } from './types.js'\nexport { applyPreFilter, shouldSkip, calculateContentScore } from './pre-filter.js'\nexport type { PreFilterResult } from './pre-filter.js'\n","import type {\n StringContext,\n ScanCandidate,\n DuplicateGroup,\n ScanCandidatesResult,\n ScanSummaryResult,\n} from '@contentrain/types'\nimport { join, extname } from 'node:path'\nimport { readText } from '../util/fs.js'\nimport {\n autoDetectSourceDirs,\n discoverFiles,\n} from './scan-config.js'\nimport { extractStrings } from './ast-scanner/index.js'\nimport { calculateContentScore } from './ast-scanner/pre-filter.js'\nimport type { StructuralContext } from './ast-scanner/types.js'\n\n// ─── Options ───\n\nexport interface ScanOptions {\n paths?: string[]\n include?: string[]\n exclude?: string[]\n limit?: number\n offset?: number\n min_length?: number\n max_length?: number\n /** Minimum content confidence score (0-1). Default: 0.4 */\n min_score?: number\n}\n\n// ─── Constants ───\n\nconst DEFAULT_LIMIT = 50\nconst DEFAULT_OFFSET = 0\nconst DEFAULT_MIN_LENGTH = 2\nconst DEFAULT_MAX_LENGTH = 500\nconst DEFAULT_MIN_SCORE = 0.4\nconst SUMMARY_SAMPLE_SIZE = 10\n\n// ─── Context mapping: StructuralContext → StringContext ───\n\nconst CONTEXT_MAP: Record<StructuralContext, StringContext> = {\n 'template_text': 'template_text',\n 'template_attribute': 'template_attribute',\n 'jsx_text': 'jsx_text',\n 'jsx_attribute': 'jsx_attribute',\n 'variable_assignment': 'variable_assignment',\n 'object_property': 'object_value',\n 'function_argument': 'function_argument',\n 'array_element': 'other',\n 'enum_value': 'other',\n 'template_literal': 'other',\n 'switch_case': 'other',\n 'other': 'other',\n // Pre-filtered contexts should not reach here, but map them just in case\n 'import_path': 'other',\n 'type_annotation': 'other',\n 'css_class': 'other',\n 'css_utility_call': 'other',\n 'console_call': 'other',\n 'test_assertion': 'other',\n}\n\n// ─── Main: scanCandidates ───\n\nexport async function scanCandidates(\n projectRoot: string,\n options?: ScanOptions,\n): Promise<ScanCandidatesResult> {\n const limit = options?.limit ?? DEFAULT_LIMIT\n const offset = options?.offset ?? DEFAULT_OFFSET\n const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH\n const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH\n const minScore = options?.min_score ?? DEFAULT_MIN_SCORE\n\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // ─── Phase 1: Extract strings from all files (pre-filter applied inside extractStrings) ───\n\n const filePromises = files.map(async (relPath) => {\n const filePath = join(projectRoot, relPath)\n const content = await readText(filePath)\n if (!content) return { relPath, extractions: [] }\n\n const ext = extname(filePath)\n const extractions = await extractStrings(filePath, content, ext)\n return { relPath, extractions }\n })\n\n const fileResults = await Promise.all(filePromises)\n\n // ─── Phase 2: Score + length filter + deduplicate ───\n\n let rawStringsFound = 0\n let skippedCount = 0\n let lowConfidenceCount = 0\n const skipReasons: Record<string, number> = {}\n\n // Deduplication map: value → first candidate + all occurrences\n const uniqueMap = new Map<string, {\n candidate: ScanCandidate\n maxScore: number\n }>()\n\n // Also track duplicates for backward compatibility\n const dupeMap = new Map<string, Array<{ file: string; line: number }>>()\n\n for (const { relPath, extractions } of fileResults) {\n rawStringsFound += extractions.length\n\n for (const extraction of extractions) {\n // Length filter\n if (extraction.value.length < minLength || extraction.value.length > maxLength) {\n skippedCount++\n skipReasons['length_filter'] = (skipReasons['length_filter'] ?? 0) + 1\n continue\n }\n\n // shouldSkip was already applied inside extractStrings (via applyPreFilter).\n // But applyPreFilter uses default minScore. Here we apply the user-configured minScore\n // on the contentScore that was attached during pre-filtering.\n\n // Get the contentScore attached by applyPreFilter\n const contentScore: number = (extraction as unknown as { contentScore?: number }).contentScore ?? calculateContentScore(extraction)\n\n if (contentScore < minScore) {\n lowConfidenceCount++\n skipReasons['low_confidence'] = (skipReasons['low_confidence'] ?? 0) + 1\n continue\n }\n\n const mappedContext = CONTEXT_MAP[extraction.context]\n const loc = { file: relPath, line: extraction.line }\n\n // Track all occurrences for duplicates section\n if (!dupeMap.has(extraction.value)) {\n dupeMap.set(extraction.value, [])\n }\n dupeMap.get(extraction.value)!.push(loc)\n\n // Deduplication: keep first occurrence, accumulate locations\n if (!uniqueMap.has(extraction.value)) {\n uniqueMap.set(extraction.value, {\n candidate: {\n file: relPath,\n line: extraction.line,\n column: extraction.column,\n value: extraction.value,\n context: mappedContext,\n surrounding: extraction.surrounding,\n contentScore,\n occurrences: [loc],\n },\n maxScore: contentScore,\n })\n } else {\n const entry = uniqueMap.get(extraction.value)!\n entry.candidate.occurrences.push(loc)\n // Keep the highest score across occurrences\n if (contentScore > entry.maxScore) {\n entry.maxScore = contentScore\n entry.candidate.contentScore = contentScore\n entry.candidate.file = relPath\n entry.candidate.line = extraction.line\n entry.candidate.column = extraction.column\n entry.candidate.context = mappedContext\n entry.candidate.surrounding = extraction.surrounding\n }\n }\n }\n }\n\n // Build sorted unique candidates (highest score first)\n const allUniqueCandidates = [...uniqueMap.values()]\n .map(e => e.candidate)\n .toSorted((a, b) => b.contentScore - a.contentScore)\n\n // Build duplicate groups (only count >= 2), sorted by count descending\n const duplicates: DuplicateGroup[] = [...dupeMap.entries()]\n .filter(([, occurrences]) => occurrences.length >= 2)\n .map(([value, occurrences]) => ({ value, count: occurrences.length, occurrences }))\n .toSorted((a, b) => b.count - a.count)\n\n // Pagination on unique candidates\n const uniqueCount = allUniqueCandidates.length\n const paginated = allUniqueCandidates.slice(offset, offset + limit)\n const hasMore = uniqueCount > offset + limit\n\n return {\n candidates: paginated,\n duplicates,\n stats: {\n files_scanned: files.length,\n raw_strings_found: rawStringsFound,\n skipped: skippedCount,\n low_confidence: lowConfidenceCount,\n unique_candidates: uniqueCount,\n candidates_returned: paginated.length,\n has_more: hasMore,\n skip_reasons: skipReasons,\n },\n }\n}\n\n// ─── Main: scanSummary ───\n\nexport async function scanSummary(\n projectRoot: string,\n options?: ScanOptions,\n): Promise<ScanSummaryResult> {\n const minLength = options?.min_length ?? DEFAULT_MIN_LENGTH\n const maxLength = options?.max_length ?? DEFAULT_MAX_LENGTH\n const minScore = options?.min_score ?? DEFAULT_MIN_SCORE\n\n const scanDirs = options?.paths ?? await autoDetectSourceDirs(projectRoot)\n const files = await discoverFiles(projectRoot, {\n paths: scanDirs,\n include: options?.include,\n exclude: options?.exclude,\n })\n\n // Group files by directory\n const dirFiles = new Map<string, string[]>()\n const fileTypes: Record<string, number> = {}\n\n for (const relPath of files) {\n const parts = relPath.split('/')\n const dir = parts.slice(0, -1).join('/') || '.'\n const ext = extname(relPath)\n\n if (!dirFiles.has(dir)) dirFiles.set(dir, [])\n dirFiles.get(dir)!.push(relPath)\n\n fileTypes[ext] = (fileTypes[ext] ?? 0) + 1\n }\n\n // Sample files per directory and count candidates\n const byDirectory: Record<string, { files: number; candidates: number }> = {}\n const freqMap = new Map<string, number>()\n let totalCandidatesEstimate = 0\n\n for (const [dir, dirFileList] of dirFiles) {\n const totalInDir = dirFileList.length\n const sampleFiles = dirFileList.slice(0, SUMMARY_SAMPLE_SIZE)\n let sampleCandidates = 0\n\n const samplePromises = sampleFiles.map(async (relPath) => {\n const filePath = join(projectRoot, relPath)\n const content = await readText(filePath)\n if (!content) return []\n\n const ext = extname(filePath)\n return extractStrings(filePath, content, ext)\n })\n\n const sampleResults = await Promise.all(samplePromises)\n\n for (const extractions of sampleResults) {\n for (const extraction of extractions) {\n // Apply same filters as scanCandidates\n if (extraction.value.length < minLength || extraction.value.length > maxLength) continue\n\n const contentScore: number = (extraction as unknown as { contentScore?: number }).contentScore ?? calculateContentScore(extraction)\n if (contentScore < minScore) continue\n\n sampleCandidates++\n\n // Track frequencies for top_repeated\n const prev = freqMap.get(extraction.value) ?? 0\n freqMap.set(extraction.value, prev + 1)\n }\n }\n\n // Estimate for full directory\n const avgPerFile = sampleFiles.length > 0 ? sampleCandidates / sampleFiles.length : 0\n const estimatedCandidates = Math.round(avgPerFile * totalInDir)\n\n byDirectory[dir] = {\n files: totalInDir,\n candidates: estimatedCandidates,\n }\n\n totalCandidatesEstimate += estimatedCandidates\n }\n\n // Top repeated strings\n const topRepeated = [...freqMap.entries()]\n .filter(([, count]) => count >= 2)\n .toSorted((a, b) => b[1] - a[1])\n .slice(0, 20)\n .map(([value, count]) => ({ value, count }))\n\n return {\n total_files: files.length,\n total_candidates_estimate: totalCandidatesEstimate,\n by_directory: byDirectory,\n top_repeated: topRepeated,\n sampling_note: `Based on first ${SUMMARY_SAMPLE_SIZE} files per directory. Counts are from sampled subset, not project-wide.`,\n file_types: fileTypes,\n }\n}\n"],"mappings":"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"}