@contentrain/mcp 3.9.33 → 3.10.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.
- package/README.md +18 -1
- package/dist/{apply-manager-BenQNb_b.mjs → apply-manager-B-O3eAbz.mjs} +16 -8
- package/dist/apply-manager-B-O3eAbz.mjs.map +1 -0
- package/dist/{branch-lifecycle-CirYfTXu.d.mts → branch-lifecycle-4iZax7a7.d.mts} +3 -3
- package/dist/{branch-lifecycle-CirYfTXu.d.mts.map → branch-lifecycle-4iZax7a7.d.mts.map} +1 -1
- package/dist/{branch-lifecycle-CcsSyfSw.mjs → branch-lifecycle-D4DxPgQg.mjs} +53 -5
- package/dist/branch-lifecycle-D4DxPgQg.mjs.map +1 -0
- package/dist/{config-BLwZtwlI.mjs → config-BnNYu128.mjs} +3 -2
- package/dist/config-BnNYu128.mjs.map +1 -0
- package/dist/content-manager-2g0pkLFh.d.mts.map +1 -1
- package/dist/{context-Cp8b8Nxm.mjs → context-D9HXnnGj.mjs} +5 -5
- package/dist/{context-Cp8b8Nxm.mjs.map → context-D9HXnnGj.mjs.map} +1 -1
- package/dist/core/apply-manager.d.mts.map +1 -1
- package/dist/core/apply-manager.mjs +6 -6
- package/dist/core/config.d.mts.map +1 -1
- package/dist/core/config.mjs +1 -1
- package/dist/core/content-manager.mjs +1 -1
- package/dist/core/context.mjs +3 -3
- package/dist/core/doctor.mjs +4 -4
- package/dist/core/model-manager.mjs +1 -1
- package/dist/core/ops/index.mjs +6 -6
- package/dist/core/validator/index.d.mts.map +1 -1
- package/dist/core/validator/index.mjs +3 -3
- package/dist/{doctor-Qabcb-Z1.mjs → doctor-Br1NqEl2.mjs} +4 -4
- package/dist/{doctor-Qabcb-Z1.mjs.map → doctor-Br1NqEl2.mjs.map} +1 -1
- package/dist/git/branch-lifecycle.d.mts +1 -1
- package/dist/git/branch-lifecycle.mjs +2 -2
- package/dist/git/reconcile.d.mts.map +1 -1
- package/dist/git/reconcile.mjs +8 -8
- package/dist/git/transaction.d.mts +7 -3
- package/dist/git/transaction.d.mts.map +1 -1
- package/dist/git/transaction.mjs +6 -6
- package/dist/index.mjs +13 -13
- package/dist/{local-BQVdC9MM.mjs → local-CBtRYFH4.mjs} +7 -7
- package/dist/{local-BQVdC9MM.mjs.map → local-CBtRYFH4.mjs.map} +1 -1
- package/dist/{model-manager-BYF2z7qC.mjs → model-manager-D0VztE8M.mjs} +8 -5
- package/dist/model-manager-D0VztE8M.mjs.map +1 -0
- package/dist/{ops-CQ_IIedh.mjs → ops-CVI308jA.mjs} +9 -4
- package/dist/ops-CVI308jA.mjs.map +1 -0
- package/dist/providers/local/index.mjs +9 -9
- package/dist/{reconcile-CRAukgae.mjs → reconcile-DSflMfhj.mjs} +8 -7
- package/dist/reconcile-DSflMfhj.mjs.map +1 -0
- package/dist/{reconcile-C7AaUfCl.mjs → reconcile-J-iorvSa.mjs} +5 -4
- package/dist/reconcile-J-iorvSa.mjs.map +1 -0
- package/dist/server/http/index.mjs +13 -13
- package/dist/{server-CumICnp3.mjs → server-DvWJelCh.mjs} +43 -14
- package/dist/server-DvWJelCh.mjs.map +1 -0
- package/dist/server.mjs +13 -13
- package/dist/{transaction-CcjGiVYg.mjs → transaction-DnrUATJF.mjs} +14 -10
- package/dist/transaction-DnrUATJF.mjs.map +1 -0
- package/dist/{validator-Bz3jnSnb.mjs → validator-S19GHR4V.mjs} +16 -5
- package/dist/{validator-Bz3jnSnb.mjs.map → validator-S19GHR4V.mjs.map} +1 -1
- package/package.json +2 -2
- package/dist/apply-manager-BenQNb_b.mjs.map +0 -1
- package/dist/branch-lifecycle-CcsSyfSw.mjs.map +0 -1
- package/dist/config-BLwZtwlI.mjs.map +0 -1
- package/dist/model-manager-BYF2z7qC.mjs.map +0 -1
- package/dist/ops-CQ_IIedh.mjs.map +0 -1
- package/dist/reconcile-C7AaUfCl.mjs.map +0 -1
- package/dist/reconcile-CRAukgae.mjs.map +0 -1
- package/dist/server-CumICnp3.mjs.map +0 -1
- package/dist/transaction-CcjGiVYg.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { M as documentFilePath, N as metaFilePath, j as contentFilePath } from "./model-manager-
|
|
1
|
+
import { a as preservedKeyConflicts$1, i as parseMarkdownFrontmatter$1, o as serializeMarkdownFrontmatter$1, s as canonicalStringify$1 } from "./context-D9HXnnGj.mjs";
|
|
2
|
+
import { M as documentFilePath, N as metaFilePath, j as contentFilePath } from "./model-manager-D0VztE8M.mjs";
|
|
3
3
|
import { r as mergeEntryMeta } from "./meta-manager-CSpKtjnP.mjs";
|
|
4
4
|
import { LOCALE_PATTERN, MODEL_FIELD_ORDER, SLUG_PATTERN, canonicalStringify, generateEntryId, rewriteEntryMedia, rewriteMarkdownMedia, validateEntryId, validateLocale, validateSlug } from "@contentrain/types";
|
|
5
5
|
//#region src/core/ops/content-delete.ts
|
|
@@ -344,7 +344,12 @@ async function planContentSave(reader, input) {
|
|
|
344
344
|
advisories.push(notice);
|
|
345
345
|
}
|
|
346
346
|
const normalizedBody = mediaBaseUrl ? rewriteMarkdownMedia(bodyContent, mediaBaseUrl) : bodyContent;
|
|
347
|
-
|
|
347
|
+
for (const key of preservedKeyConflicts$1(fmData, existing?.preserved)) {
|
|
348
|
+
const notice = `Document "${slug}" (${locale}): the saved "${key}" replaced a "${key}" block the file held in a shape Contentrain does not read.`;
|
|
349
|
+
entryAdvisories.push(notice);
|
|
350
|
+
advisories.push(notice);
|
|
351
|
+
}
|
|
352
|
+
markdownChanges.set(dPath, serializeMarkdownFrontmatter$1(normalizeMedia(fmData), normalizedBody, existing?.preserved));
|
|
348
353
|
metaByPath.set(mPath, mergeEntryMeta(await priorMeta(mPath), entry));
|
|
349
354
|
result.push({
|
|
350
355
|
action,
|
|
@@ -618,4 +623,4 @@ async function planVocabularyDelete(reader, input) {
|
|
|
618
623
|
//#endregion
|
|
619
624
|
export { planModelDelete as a, planModelSave as i, planVocabularySave as n, planContentSave as o, bindRef as r, planContentDelete as s, planVocabularyDelete as t };
|
|
620
625
|
|
|
621
|
-
//# sourceMappingURL=ops-
|
|
626
|
+
//# sourceMappingURL=ops-CVI308jA.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ops-CVI308jA.mjs","names":["canonicalStringify","parseMarkdownFrontmatter","preservedKeyConflicts","serializeMarkdownFrontmatter","canonicalStringify","canonicalStringify"],"sources":["../src/core/ops/content-delete.ts","../src/core/ops/content-save.ts","../src/core/ops/model-delete.ts","../src/core/ops/model-save.ts","../src/core/bind-ref.ts","../src/core/ops/vocabulary-save.ts"],"sourcesContent":["import type { EntryMeta, ModelDefinition } from '@contentrain/types'\nimport { validateSlug } from '@contentrain/types'\nimport type { FileChange, RepoReader } from '../contracts/index.js'\nimport { canonicalStringify } from '../serialization/index.js'\nimport { contentFilePath, documentFilePath, metaFilePath } from './paths.js'\nimport type { OpPlan } from './types.js'\n\nexport interface ContentDeleteInput {\n model: ModelDefinition\n id?: string\n slug?: string\n locale?: string\n keys?: string[]\n /**\n * `config.locales.default` — where a non-i18n model's single meta record\n * lives. Required to resolve meta paths; see `metaFilePath`.\n */\n defaultLocale: string\n}\n\nexport type ContentDeletePlan = OpPlan<string[]>\n\nfunction contentDir(model: Pick<ModelDefinition, 'domain' | 'id' | 'content_path'>): string {\n return model.content_path ?? `.contentrain/content/${model.domain}/${model.id}`\n}\n\nasync function tryReadJson<T>(reader: RepoReader, path: string): Promise<T | null> {\n try {\n return JSON.parse(await reader.readFile(path)) as T\n } catch {\n return null\n }\n}\n\nasync function discoverCollectionLocales(\n reader: RepoReader,\n model: ModelDefinition,\n): Promise<string[]> {\n const dir = contentDir(model)\n const files = await reader.listDirectory(dir)\n const strategy = model.locale_strategy ?? 'file'\n if (strategy === 'directory') {\n return files\n }\n return files\n .filter(f => f.endsWith('.json'))\n .map(f => f.replace('.json', '').replace(`${model.id}.`, ''))\n}\n\n/**\n * Build the FileChange[] required to delete content. Pure — touches no disk.\n *\n * Semantics preserved from legacy `deleteContent`:\n * - Collection: remove the entry ID from each locale's content file and its\n * meta file. Meta file is deleted outright if the removal empties it.\n * - Singleton: delete the locale's content + meta file (locale-scoped when\n * i18n, non-i18n removes whatever meta lives under `.contentrain/meta/{id}/`).\n * - Dictionary: two modes — key-scoped removal updates the file in place,\n * or locale-scoped removes the content + meta pair.\n * - Document: strategy-aware delete (file/suffix/directory/none), plus\n * meta removal under `.contentrain/meta/{id}/{slug}/`.\n */\nexport async function planContentDelete(\n reader: RepoReader,\n input: ContentDeleteInput,\n): Promise<ContentDeletePlan> {\n const { model } = input\n\n // A non-i18n model stores content locale-agnostically in data.json and keeps\n // exactly one meta record, pinned to the default locale. A locale-scoped\n // delete is therefore meaningless — and actively destructive: the locale\n // silently maps onto data.json and the default-locale meta, so passing a\n // non-default locale deletes the shared content and the wrong meta while the\n // named locale's stray meta is never touched. Reject it; stray per-locale\n // meta is cleaned up by `contentrain_validate fix:true`.\n if (!model.i18n && input.locale) {\n throw new Error(\n `Model \"${model.id}\" has i18n disabled — its content is locale-agnostic (stored in data.json), `\n + 'so a locale-scoped delete is invalid. Omit \"locale\" to delete the entry, '\n + 'or run contentrain_validate fix:true to remove stray per-locale meta files.',\n )\n }\n\n const changes: FileChange[] = []\n const removed: string[] = []\n\n switch (model.kind) {\n case 'collection': {\n if (!input.id) throw new Error('Collection delete requires an entry ID')\n\n const locales = input.locale\n ? [input.locale]\n : await discoverCollectionLocales(reader, model)\n\n for (const loc of locales) {\n const cPath = contentFilePath(model, loc)\n const data = await tryReadJson<Record<string, unknown>>(reader, cPath)\n if (!data || !(input.id in data)) continue\n delete data[input.id]\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(data).toSorted()) sorted[k] = data[k]\n changes.push({ path: cPath, content: canonicalStringify(sorted) })\n removed.push(`content/${model.domain}/${model.id}/${loc}.json#${input.id}`)\n }\n\n for (const loc of locales) {\n // For a non-i18n model `loc` is the pseudo-locale \"data\" (discovered from\n // data.json); metaFilePath maps it onto the default locale regardless.\n const mPath = metaFilePath(model, loc, input.defaultLocale)\n const meta = await tryReadJson<Record<string, EntryMeta>>(reader, mPath)\n if (!meta || !(input.id in meta)) continue\n delete meta[input.id]\n if (Object.keys(meta).length > 0) {\n changes.push({ path: mPath, content: canonicalStringify(meta) })\n } else {\n changes.push({ path: mPath, content: null })\n }\n }\n break\n }\n\n case 'singleton': {\n if (model.i18n && !input.locale) {\n throw new Error('Singleton delete requires a locale when i18n is enabled')\n }\n const locale = input.locale ?? 'data'\n const cPath = contentFilePath(model, locale)\n changes.push({ path: cPath, content: null })\n removed.push(model.i18n\n ? `content/${model.domain}/${model.id}/${locale}.json`\n : `content/${model.domain}/${model.id}/data.json`)\n\n if (model.i18n) {\n changes.push({ path: metaFilePath(model, locale, input.defaultLocale), content: null })\n } else {\n // Removing data.json leaves a non-i18n model with no content at all, so\n // every meta file under it goes — including strays left by older writes.\n await pushAllMetaForModel(reader, model.id, changes)\n }\n break\n }\n\n case 'dictionary': {\n if (model.i18n && !input.locale) {\n throw new Error('Dictionary delete requires a locale when i18n is enabled')\n }\n const locale = input.locale ?? 'data'\n const cPath = contentFilePath(model, locale)\n\n if (input.keys?.length) {\n const existing = (await tryReadJson<Record<string, string>>(reader, cPath)) ?? {}\n const notFound: string[] = []\n for (const key of input.keys) {\n if (key in existing) delete existing[key]\n else notFound.push(key)\n }\n if (notFound.length > 0) {\n throw new Error(`Dictionary \"${model.id}\" (${locale}): keys not found — [${notFound.join(', ')}]`)\n }\n changes.push({ path: cPath, content: canonicalStringify(existing) })\n removed.push(...input.keys.map(k => `${model.id}/${locale}:${k}`))\n } else {\n changes.push({ path: cPath, content: null })\n removed.push(model.i18n\n ? `content/${model.domain}/${model.id}/${locale}.json`\n : `content/${model.domain}/${model.id}/data.json`)\n if (model.i18n) {\n changes.push({ path: metaFilePath(model, locale, input.defaultLocale), content: null })\n } else {\n await pushAllMetaForModel(reader, model.id, changes)\n }\n }\n break\n }\n\n case 'document': {\n if (!input.slug) throw new Error('Document delete requires a slug')\n const slugErr = validateSlug(input.slug)\n if (slugErr) throw new Error(slugErr)\n\n const dir = contentDir(model)\n const strategy = model.locale_strategy ?? 'file'\n\n if (!model.i18n) {\n changes.push({ path: `${dir}/${input.slug}.md`, content: null })\n } else if (strategy === 'file') {\n const slugDir = `${dir}/${input.slug}`\n const files = await reader.listDirectory(slugDir)\n for (const f of files) changes.push({ path: `${slugDir}/${f}`, content: null })\n } else if (input.locale) {\n changes.push({ path: documentFilePath(model, input.locale, input.slug), content: null })\n } else if (strategy === 'suffix') {\n const files = await reader.listDirectory(dir)\n for (const f of files) {\n if (f.startsWith(`${input.slug}.`) && f.endsWith('.md')) {\n changes.push({ path: `${dir}/${f}`, content: null })\n }\n }\n } else if (strategy === 'directory') {\n const subdirs = await reader.listDirectory(dir)\n for (const d of subdirs) {\n const filePath = `${dir}/${d}/${input.slug}.md`\n if (await reader.fileExists(filePath)) {\n changes.push({ path: filePath, content: null })\n }\n }\n } else if (strategy === 'none') {\n const files = await reader.listDirectory(dir)\n if (files.includes(`${input.slug}.md`)) {\n changes.push({ path: `${dir}/${input.slug}.md`, content: null })\n }\n }\n\n removed.push(`${model.content_path ?? `content/${model.domain}/${model.id}`}/${input.slug}`)\n\n const metaSlugDir = `.contentrain/meta/${model.id}/${input.slug}`\n if (input.locale) {\n changes.push({ path: `${metaSlugDir}/${input.locale}.json`, content: null })\n } else {\n const metaFiles = await reader.listDirectory(metaSlugDir)\n for (const f of metaFiles) changes.push({ path: `${metaSlugDir}/${f}`, content: null })\n }\n break\n }\n }\n\n changes.sort((a, b) => a.path.localeCompare(b.path))\n return { changes, result: removed, advisories: [] }\n}\n\nasync function pushAllMetaForModel(reader: RepoReader, modelId: string, changes: FileChange[]): Promise<void> {\n const metaDir = `.contentrain/meta/${modelId}`\n const files = await reader.listDirectory(metaDir)\n for (const f of files) changes.push({ path: `${metaDir}/${f}`, content: null })\n}\n","import type { ContentrainConfig, EntryMeta, ModelDefinition, Vocabulary } from '@contentrain/types'\nimport { generateEntryId, rewriteEntryMedia, rewriteMarkdownMedia, validateEntryId, validateLocale, validateSlug } from '@contentrain/types'\nimport type { FileChange, RepoReader } from '../contracts/index.js'\nimport type { ContentEntry } from '../content-manager.js'\nimport { canonicalStringify, parseMarkdownFrontmatter, preservedKeyConflicts, serializeMarkdownFrontmatter } from '../serialization/index.js'\nimport type { ContentSaveEntryResult, ContentSavePlan } from './types.js'\nimport { contentFilePath, documentFilePath, metaFilePath } from './paths.js'\nimport { mergeEntryMeta } from '../meta-manager.js'\n\n/** Keys named individually in a replacement advisory before it collapses to a count. */\nconst MAX_REPORTED_KEYS = 5\n\ninterface PlanInput {\n model: ModelDefinition\n entries: ContentEntry[]\n config: ContentrainConfig\n vocabulary?: Vocabulary | null\n /**\n * Per-project public media delivery base (`RepoProvider.mediaBaseUrl`). When\n * set (hosted/cloud mode), relative `media/...` references in media fields\n * and markdown bodies are normalized to absolute delivery URLs before the\n * value is committed. Undefined in local mode — paths are kept verbatim.\n */\n mediaBaseUrl?: string\n}\n\n/**\n * Build the FileChange[] required to save a batch of content entries. The\n * plan is deterministic, pure, and does not touch disk — it reads existing\n * state through `reader` and returns changes for a single atomic commit.\n *\n * Grouping: multiple entries targeting the same (model, locale) share a\n * single content file and a single meta file. Reads are cached in-memory\n * so repeated reads of the same path during a plan are one IO.\n */\nexport async function planContentSave(reader: RepoReader, input: PlanInput): Promise<ContentSavePlan> {\n const { model, entries, config, vocabulary, mediaBaseUrl } = input\n const result: ContentSaveEntryResult[] = []\n const advisories: string[] = []\n\n // Hosted/cloud mode: normalize relative `media/...` references to absolute\n // delivery URLs so the committed value renders anywhere with no SDK. A no-op\n // in local mode (no base) or for field-less models (e.g. dictionaries).\n const normalizeMedia = (data: Record<string, unknown>): Record<string, unknown> =>\n mediaBaseUrl && model.fields ? rewriteEntryMedia(data, model.fields, mediaBaseUrl) : data\n\n // In-memory accumulators keyed by content-root-relative path.\n const contentByPath = new Map<string, unknown>()\n const metaByPath = new Map<string, unknown>()\n const markdownChanges = new Map<string, string>()\n\n async function readJsonOrEmpty<T>(path: string): Promise<T> {\n try {\n return JSON.parse(await reader.readFile(path)) as T\n } catch {\n return {} as T\n }\n }\n\n /** Absent file and absent entry both mean \"no prior meta\" — hence undefined, not `{}`. */\n async function readJsonOrUndefined<T>(path: string): Promise<T | undefined> {\n try {\n return JSON.parse(await reader.readFile(path)) as T\n } catch {\n return undefined\n }\n }\n\n /** Prior meta for a whole-file (non-collection) meta path, honouring the accumulator. */\n async function priorMeta(mPath: string): Promise<EntryMeta | undefined> {\n return (metaByPath.get(mPath) as EntryMeta | undefined)\n ?? await readJsonOrUndefined<EntryMeta>(mPath)\n }\n\n const defaultLocale = config.locales.default\n\n for (const entry of entries) {\n const locale = entry.locale ?? defaultLocale\n\n const localeErr = validateLocale(locale, config)\n if (localeErr) throw new Error(localeErr)\n if (entry.id) {\n const err = validateEntryId(entry.id)\n if (err) throw new Error(err)\n }\n if (entry.slug) {\n const err = validateSlug(entry.slug)\n if (err) throw new Error(err)\n }\n\n switch (model.kind) {\n case 'singleton': {\n const cPath = contentFilePath(model, locale)\n const mPath = metaFilePath(model, locale, defaultLocale)\n contentByPath.set(cPath, normalizeMedia(entry.data))\n metaByPath.set(mPath, mergeEntryMeta(await priorMeta(mPath), entry))\n result.push({ action: 'updated', locale })\n break\n }\n\n case 'collection': {\n const isNew = !entry.id\n const id = entry.id ?? generateEntryId()\n const cPath = contentFilePath(model, locale)\n const mPath = metaFilePath(model, locale, defaultLocale)\n\n const existing = (contentByPath.get(cPath) as Record<string, unknown> | undefined)\n ?? await readJsonOrEmpty<Record<string, unknown>>(cPath)\n\n const action: 'created' | 'updated' = isNew || !(id in existing) ? 'created' : 'updated'\n existing[id] = normalizeMedia(entry.data)\n\n const sorted: Record<string, unknown> = {}\n for (const key of Object.keys(existing).toSorted()) {\n sorted[key] = existing[key]\n }\n contentByPath.set(cPath, sorted)\n\n const existingMeta = (metaByPath.get(mPath) as Record<string, EntryMeta> | undefined)\n ?? await readJsonOrEmpty<Record<string, EntryMeta>>(mPath)\n existingMeta[id] = mergeEntryMeta(existingMeta[id], entry)\n metaByPath.set(mPath, existingMeta)\n\n result.push({ action, id, locale })\n break\n }\n\n case 'dictionary': {\n const cPath = contentFilePath(model, locale)\n const mPath = metaFilePath(model, locale, defaultLocale)\n\n const existing = (contentByPath.get(cPath) as Record<string, string> | undefined)\n ?? await readJsonOrEmpty<Record<string, string>>(cPath)\n\n const newData = entry.data as Record<string, string>\n const entryAdvisories: string[] = []\n\n // Overwriting a key used to be refused outright, which made the most\n // ordinary dictionary operation — correcting a translation — impossible:\n // the workaround was delete, merge, save, merge. Four operations and two\n // branches to fix one string. The refusal also advised \"include all keys\n // in a single save call\", which cannot work, because the check compares\n // values per key rather than counting them.\n //\n // Choosing a translation is a content decision, and MCP does not make\n // those. It reports them — the same way the duplicate-value case two\n // blocks below already does — and the branch diff shows the rest.\n const replaced = Object.keys(newData).filter(\n k => k in existing && existing[k] !== newData[k],\n )\n if (replaced.length > 0) {\n const shown = replaced.slice(0, MAX_REPORTED_KEYS)\n const detail = shown.map(k => `\"${k}\": \"${existing[k]}\" → \"${newData[k]}\"`).join(', ')\n const more = replaced.length > shown.length ? `, and ${replaced.length - shown.length} more` : ''\n entryAdvisories.push(\n `Dictionary \"${model.id}\" (${locale}): replaced ${replaced.length} existing value(s) — ${detail}${more}.`,\n )\n }\n\n const reverseMap = new Map<string, string>()\n for (const [k, v] of Object.entries(existing)) reverseMap.set(v, k)\n for (const [newKey, newValue] of Object.entries(newData)) {\n if (newKey in existing) continue\n const existingKey = reverseMap.get(newValue)\n if (existingKey && existingKey !== newKey) {\n entryAdvisories.push(\n `Value \"${newValue}\" already exists as key \"${existingKey}\". Consider reusing instead of creating \"${newKey}\".`,\n )\n }\n }\n\n if (vocabulary && Object.keys(vocabulary.terms).length > 0) {\n outer:\n for (const [newKey, newValue] of Object.entries(newData)) {\n if (newKey in existing) continue\n for (const translations of Object.values(vocabulary.terms)) {\n if (Object.values(translations).includes(newValue)) {\n entryAdvisories.push(\n `Value \"${newValue}\" matches a vocabulary term. Use the canonical form for consistency.`,\n )\n continue outer\n }\n }\n }\n }\n\n advisories.push(...entryAdvisories)\n\n contentByPath.set(cPath, { ...existing, ...newData })\n metaByPath.set(mPath, mergeEntryMeta(await priorMeta(mPath), entry))\n\n result.push({\n action: 'updated',\n locale,\n ...(entryAdvisories.length > 0 ? { advisories: entryAdvisories } : {}),\n })\n break\n }\n\n case 'document': {\n const entryAdvisories: string[] = []\n const slug = entry.slug ?? (entry.data['slug'] as string | undefined)\n if (!slug) throw new Error('Document entries require a slug')\n const slugErr = validateSlug(slug)\n if (slugErr) throw new Error(slugErr)\n\n const incomingFm = { ...entry.data }\n const bodySent = 'body' in incomingFm\n const incomingBody = (incomingFm['body'] as string | undefined) ?? ''\n delete incomingFm['body']\n if (!incomingFm['slug']) incomingFm['slug'] = slug\n\n const dPath = documentFilePath(model, locale, slug)\n const mPath = metaFilePath(model, locale, defaultLocale, slug)\n\n // Accumulator before disk, as the collection branch does: two entries\n // touching one document in a single call must compose, not race.\n let existingRaw: string | null = markdownChanges.get(dPath) ?? null\n if (existingRaw === null) {\n try { existingRaw = await reader.readFile(dPath) }\n catch { /* not yet */ }\n }\n // Same rule the collection branch uses: anything already present —\n // on disk or produced earlier in this plan — makes this an update.\n const action: 'created' | 'updated' = existingRaw ? 'updated' : 'created'\n\n // Merge with what is on disk, the way collections and singletons above\n // already do. Documents were the only kind that replaced instead of\n // merging, so a save that touched one frontmatter field wrote a file\n // containing only that field — and an entirely empty body. Nothing\n // reported it: the response said `valid: true`, because validation runs\n // over the plan's own output, which was internally consistent and\n // wrong. Under auto-merge that reaches the default branch directly.\n const existing = existingRaw ? parseMarkdownFrontmatter(existingRaw) : null\n const fmData = { ...existing?.frontmatter, ...incomingFm }\n\n // Absent `body` means \"I am not editing the body\" — keep what is there.\n // A `body` that is present, even empty, is an instruction, and is\n // honoured; clearing real content that way is announced rather than\n // silent, because it is indistinguishable from a templating mistake.\n const bodyContent = bodySent ? incomingBody : (existing?.body ?? '')\n if (bodySent && !incomingBody.trim() && existing?.body.trim()) {\n const notice = `Document \"${slug}\" (${locale}): body cleared — an explicit empty \"body\" replaced ${existing.body.length} characters. Omit \"body\" to leave it untouched.`\n entryAdvisories.push(notice)\n advisories.push(notice)\n }\n\n // Frontmatter media fields go through the schema-guided rewrite; the\n // markdown body has its `media/...` image/link targets rewritten too.\n const normalizedBody = mediaBaseUrl ? rewriteMarkdownMedia(bodyContent, mediaBaseUrl) : bodyContent\n // Lines the reader cannot turn into fields (a Turkish or spaced key, a nested map, a comment) are written\n // back as they were; a new value for one of those keys wins, and that is said out loud.\n for (const key of preservedKeyConflicts(fmData, existing?.preserved)) {\n const notice = `Document \"${slug}\" (${locale}): the saved \"${key}\" replaced a \"${key}\" block the file held in a shape Contentrain does not read.`\n entryAdvisories.push(notice)\n advisories.push(notice)\n }\n markdownChanges.set(dPath, serializeMarkdownFrontmatter(normalizeMedia(fmData), normalizedBody, existing?.preserved))\n metaByPath.set(mPath, mergeEntryMeta(await priorMeta(mPath), entry))\n\n result.push({\n action,\n slug,\n locale,\n ...(entryAdvisories.length > 0 ? { advisories: entryAdvisories } : {}),\n })\n break\n }\n }\n }\n\n const changes: FileChange[] = []\n for (const [path, data] of contentByPath) {\n changes.push({ path, content: canonicalStringify(data) })\n }\n for (const [path, meta] of metaByPath) {\n changes.push({ path, content: canonicalStringify(meta) })\n }\n for (const [path, content] of markdownChanges) {\n changes.push({ path, content })\n }\n changes.sort((a, b) => a.path.localeCompare(b.path))\n\n return { changes, result, advisories }\n}\n","import type { ModelDefinition } from '@contentrain/types'\nimport type { FileChange, RepoReader } from '../contracts/index.js'\nimport type { OpPlan } from './types.js'\n\nexport interface ModelDeleteInput {\n model: ModelDefinition\n}\n\nexport type ModelDeletePlan = OpPlan<string[]>\n\n/**\n * Build the FileChange[] required to delete a model and its associated\n * content/meta files.\n *\n * The schema is at most two directory levels deep (e.g. document meta at\n * `.contentrain/meta/{id}/{slug}/{locale}.json`). The helper below walks\n * that much, which is enough for every model kind + locale strategy and\n * keeps us free of a general-purpose recursive lister for now.\n *\n * `result` is the legacy three-entry summary:\n * - `models/{id}.json`\n * - `content/{domain}/{id}/` (only if any content files existed)\n * - `meta/{id}/` (only if any meta files existed)\n */\nexport async function planModelDelete(reader: RepoReader, input: ModelDeleteInput): Promise<ModelDeletePlan> {\n const { model } = input\n const changes: FileChange[] = []\n const removed: string[] = []\n\n changes.push({ path: `.contentrain/models/${model.id}.json`, content: null })\n removed.push(`models/${model.id}.json`)\n\n const contentDir = model.content_path ?? `.contentrain/content/${model.domain}/${model.id}`\n const contentFiles = await enumerateTwoLevels(reader, contentDir)\n for (const path of contentFiles) {\n changes.push({ path, content: null })\n }\n if (contentFiles.length > 0) {\n removed.push(model.content_path ?? `content/${model.domain}/${model.id}/`)\n }\n\n const metaDir = `.contentrain/meta/${model.id}`\n const metaFiles = await enumerateTwoLevels(reader, metaDir)\n for (const path of metaFiles) {\n changes.push({ path, content: null })\n }\n if (metaFiles.length > 0) {\n removed.push(`meta/${model.id}/`)\n }\n\n changes.sort((a, b) => a.path.localeCompare(b.path))\n return { changes, result: removed, advisories: [] }\n}\n\n/** List files up to two directory levels deep. Filters to `.json` and `.md`. */\nasync function enumerateTwoLevels(reader: RepoReader, dir: string): Promise<string[]> {\n const results: string[] = []\n const entries = await reader.listDirectory(dir)\n for (const entry of entries) {\n const path = `${dir}/${entry}`\n if (entry.endsWith('.json') || entry.endsWith('.md')) {\n results.push(path)\n continue\n }\n const subEntries = await reader.listDirectory(path)\n for (const sub of subEntries) {\n if (sub.endsWith('.json') || sub.endsWith('.md')) {\n results.push(`${path}/${sub}`)\n }\n }\n }\n return results\n}\n","import type { ModelDefinition } from '@contentrain/types'\nimport { MODEL_FIELD_ORDER } from '@contentrain/types'\nimport type { FileChange, RepoReader } from '../contracts/index.js'\nimport { canonicalStringify } from '../serialization/index.js'\nimport type { OpPlan } from './types.js'\n\nexport interface ModelSaveInput {\n model: ModelDefinition\n}\n\nexport interface ModelSaveResult {\n action: 'created' | 'updated'\n id: string\n}\n\nexport type ModelSavePlan = OpPlan<ModelSaveResult>\n\n/**\n * Build the FileChange[] required to create or update a model definition.\n *\n * The only disk change is the model JSON file itself. Content and meta\n * directories are created on-demand when their first write lands — git does\n * not track empty directories, and MCP's write helpers `mkdir -p` before\n * every file write, so there is no operational reason to emit an empty-dir\n * ensure step here.\n */\nexport async function planModelSave(reader: RepoReader, input: ModelSaveInput): Promise<ModelSavePlan> {\n const { model } = input\n const modelPath = `.contentrain/models/${model.id}.json`\n\n let action: 'created' | 'updated' = 'created'\n try {\n await reader.readFile(modelPath)\n action = 'updated'\n } catch { /* not yet exists */ }\n\n const changes: FileChange[] = [\n { path: modelPath, content: canonicalStringify(model, MODEL_FIELD_ORDER) },\n ]\n\n return {\n changes,\n result: { action, id: model.id },\n advisories: [],\n }\n}\n","import type { RepoReader } from './contracts/index.js'\n\n/**\n * Pin a `RepoReader` to one ref. The reconcile planner takes three of these\n * (merge-base / contentrain / base branch) and stays entirely ignorant of\n * refs — executors and tools decide what to bind, the planner just reads.\n *\n * For readers that honour the per-call `ref?` parameter (GitHub, GitLab)\n * this is the whole story; a reader that ignores it (LocalReader reads the\n * working tree) needs a ref-aware implementation instead — see\n * `git/ref-reader.ts`.\n */\nexport function bindRef(reader: RepoReader, ref: string): RepoReader {\n return {\n readFile: path => reader.readFile(path, ref),\n listDirectory: path => reader.listDirectory(path, ref),\n fileExists: path => reader.fileExists(path, ref),\n }\n}\n","import type { Vocabulary } from '@contentrain/types'\nimport { canonicalStringify, LOCALE_PATTERN, SLUG_PATTERN } from '@contentrain/types'\nimport type { FileChange, RepoReader } from '../contracts/index.js'\nimport type { OpPlan } from './types.js'\n\nconst VOCABULARY_PATH = '.contentrain/vocabulary.json'\n\n/** Terms named individually in an advisory before it collapses to a count. */\nconst MAX_REPORTED_TERMS = 5\n\nexport interface VocabularySaveInput {\n /**\n * Terms to add or update, keyed by term slug, each holding its translations\n * keyed by locale. Merged with what is already there — a term absent from\n * this map is untouched.\n */\n terms: Record<string, Record<string, string>>\n}\n\nexport interface VocabularyDeleteInput {\n /** Term slugs to remove. A slug that is not present is reported, not an error. */\n terms: string[]\n}\n\nexport interface VocabularySaveResult {\n added: string[]\n updated: string[]\n total: number\n}\n\nexport interface VocabularyDeleteResult {\n deleted: string[]\n missing: string[]\n total: number\n}\n\nexport type VocabularySavePlan = OpPlan<VocabularySaveResult>\nexport type VocabularyDeletePlan = OpPlan<VocabularyDeleteResult>\n\nasync function readVocabulary(reader: RepoReader): Promise<Vocabulary> {\n try {\n const parsed = JSON.parse(await reader.readFile(VOCABULARY_PATH)) as Partial<Vocabulary>\n return { version: parsed.version ?? 1, terms: parsed.terms ?? {} }\n } catch {\n return { version: 1, terms: {} }\n }\n}\n\n/**\n * Validate the shape callers get wrong.\n *\n * The nesting is `term -> locale -> translation`, and it reads identically the\n * other way round, so a caller that groups by locale at the top level produces\n * a structurally valid file that matches nothing. That is not a typo anyone\n * notices — the vocabulary simply stops having any effect. Checking that outer\n * keys are term slugs and inner keys are locale codes catches the inversion at\n * the point of writing.\n */\nfunction validateTerms(terms: Record<string, Record<string, string>>): string[] {\n const errors: string[] = []\n\n for (const [term, translations] of Object.entries(terms)) {\n if (!SLUG_PATTERN.test(term)) {\n errors.push(`Term \"${term}\": must be kebab-case. The outer key is the term, not a locale.`)\n }\n if (typeof translations !== 'object' || translations === null || Array.isArray(translations)) {\n errors.push(`Term \"${term}\": expected an object of locale → translation.`)\n continue\n }\n if (Object.keys(translations).length === 0) {\n errors.push(`Term \"${term}\": has no translations.`)\n }\n for (const [locale, value] of Object.entries(translations)) {\n if (!LOCALE_PATTERN.test(locale)) {\n errors.push(\n `Term \"${term}\": \"${locale}\" is not a locale code. Terms nest as { \"${term}\": { \"en\": \"…\" } } — one term, its translations inside.`,\n )\n }\n if (typeof value !== 'string') {\n errors.push(`Term \"${term}\" (${locale}): translation must be a string, got ${typeof value}.`)\n }\n }\n }\n\n return errors\n}\n\n/**\n * Build the FileChange[] to add or update vocabulary terms.\n *\n * Vocabulary had no write tool at all: the rules forbid editing `.contentrain/`\n * by hand, and there was no supported way to obey that. This closes it through\n * the same plan/apply path every other write uses, so it lands on a `cr/*`\n * branch and follows the project's workflow like anything else.\n */\nexport async function planVocabularySave(\n reader: RepoReader,\n input: VocabularySaveInput,\n): Promise<VocabularySavePlan> {\n const errors = validateTerms(input.terms)\n if (errors.length > 0) {\n throw new Error(`Vocabulary validation failed:\\n- ${errors.join('\\n- ')}`)\n }\n\n const existing = await readVocabulary(reader)\n const added: string[] = []\n const updated: string[] = []\n const advisories: string[] = []\n\n const merged: Record<string, Record<string, string>> = { ...existing.terms }\n for (const [term, translations] of Object.entries(input.terms)) {\n const prior = existing.terms[term]\n if (!prior) {\n added.push(term)\n merged[term] = { ...translations }\n continue\n }\n updated.push(term)\n // Merge per locale: adding a Turkish translation must not drop the English.\n merged[term] = { ...prior, ...translations }\n\n const changed = Object.entries(translations).filter(([l, v]) => l in prior && prior[l] !== v)\n if (changed.length > 0) {\n const shown = changed.slice(0, MAX_REPORTED_TERMS)\n const detail = shown.map(([l, v]) => `${l}: \"${prior[l]}\" → \"${v}\"`).join(', ')\n const more = changed.length > shown.length ? `, and ${changed.length - shown.length} more` : ''\n advisories.push(`Term \"${term}\": replaced ${changed.length} translation(s) — ${detail}${more}.`)\n }\n }\n\n // A canonical term whose value duplicates another term's is the whole thing\n // this file exists to prevent, so it is worth saying out loud.\n const byValue = new Map<string, string>()\n for (const [term, translations] of Object.entries(merged)) {\n for (const value of Object.values(translations)) {\n const owner = byValue.get(value)\n if (owner && owner !== term) {\n advisories.push(`Terms \"${owner}\" and \"${term}\" share the translation \"${value}\".`)\n } else {\n byValue.set(value, term)\n }\n }\n }\n\n const next: Vocabulary = { version: existing.version, terms: merged }\n const changes: FileChange[] = [\n { path: VOCABULARY_PATH, content: canonicalStringify(next) },\n ]\n\n return {\n changes,\n result: { added, updated, total: Object.keys(merged).length },\n advisories,\n }\n}\n\n/** Build the FileChange[] to remove vocabulary terms. */\nexport async function planVocabularyDelete(\n reader: RepoReader,\n input: VocabularyDeleteInput,\n): Promise<VocabularyDeletePlan> {\n const existing = await readVocabulary(reader)\n\n const deleted: string[] = []\n const missing: string[] = []\n const remaining = { ...existing.terms }\n\n for (const term of input.terms) {\n if (term in remaining) {\n delete remaining[term]\n deleted.push(term)\n } else {\n missing.push(term)\n }\n }\n\n const advisories = missing.length > 0\n ? [`Not in the vocabulary, so nothing to delete: ${missing.join(', ')}.`]\n : []\n\n // Nothing changed — emit no FileChange, so the caller can skip the commit\n // rather than create an empty branch.\n const changes: FileChange[] = deleted.length > 0\n ? [{ path: VOCABULARY_PATH, content: canonicalStringify({ version: existing.version, terms: remaining } satisfies Vocabulary) }]\n : []\n\n return {\n changes,\n result: { deleted, missing, total: Object.keys(remaining).length },\n advisories,\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import "../../contracts-DfL0BfrD.mjs";
|
|
2
|
-
import "../../reconcile-
|
|
3
|
-
import "../../context-
|
|
4
|
-
import { k as LocalReader } from "../../model-manager-
|
|
5
|
-
import "../../ops-
|
|
2
|
+
import "../../reconcile-J-iorvSa.mjs";
|
|
3
|
+
import "../../context-D9HXnnGj.mjs";
|
|
4
|
+
import { k as LocalReader } from "../../model-manager-D0VztE8M.mjs";
|
|
5
|
+
import "../../ops-CVI308jA.mjs";
|
|
6
6
|
import "../../fs-BFTuXbbw.mjs";
|
|
7
7
|
import "../../meta-manager-CSpKtjnP.mjs";
|
|
8
|
-
import "../../config-
|
|
9
|
-
import "../../validator-
|
|
8
|
+
import "../../config-BnNYu128.mjs";
|
|
9
|
+
import "../../validator-S19GHR4V.mjs";
|
|
10
10
|
import "../../identity-CxAB9Qb8.mjs";
|
|
11
11
|
import "../../base-branch-Cw9DWvCC.mjs";
|
|
12
|
-
import "../../branch-lifecycle-
|
|
12
|
+
import "../../branch-lifecycle-D4DxPgQg.mjs";
|
|
13
13
|
import "../../id-jrHWdtqn.mjs";
|
|
14
|
-
import "../../transaction-
|
|
15
|
-
import { t as LocalProvider } from "../../local-
|
|
14
|
+
import "../../transaction-DnrUATJF.mjs";
|
|
15
|
+
import { t as LocalProvider } from "../../local-CBtRYFH4.mjs";
|
|
16
16
|
export { LocalProvider, LocalReader };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { n as applyChangesToWorktree, t as planReconcile } from "./reconcile-
|
|
2
|
-
import { t as readConfig } from "./config-
|
|
1
|
+
import { n as applyChangesToWorktree, t as planReconcile } from "./reconcile-J-iorvSa.mjs";
|
|
2
|
+
import { t as readConfig } from "./config-BnNYu128.mjs";
|
|
3
3
|
import { n as authorConfig, r as createGit } from "./identity-CxAB9Qb8.mjs";
|
|
4
4
|
import { t as resolveBaseBranch } from "./base-branch-Cw9DWvCC.mjs";
|
|
5
|
-
import { l as networkGit } from "./branch-lifecycle-
|
|
6
|
-
import { a as isAncestor, c as selectiveSync, i as ensureContentBranch, s as pushContentBranches, t as NETWORK_TIMEOUT_MS } from "./transaction-
|
|
5
|
+
import { l as networkGit, p as pushPolicy } from "./branch-lifecycle-D4DxPgQg.mjs";
|
|
6
|
+
import { a as isAncestor, c as selectiveSync, i as ensureContentBranch, s as pushContentBranches, t as NETWORK_TIMEOUT_MS } from "./transaction-DnrUATJF.mjs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
@@ -225,8 +225,9 @@ async function reconcileBranches(projectRoot, options = {}) {
|
|
|
225
225
|
} catch {}
|
|
226
226
|
}
|
|
227
227
|
const sync = await selectiveSync(projectRoot, worktreePath, newTip, previousBaseRef, dirtyFilesBeforeUpdate);
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
const push = pushPolicy(config).push;
|
|
229
|
+
let remotePush = push ? "no-remote" : "disabled";
|
|
230
|
+
if (hasRemote && push) remotePush = await pushContentBranches(projectRoot, worktreePath, wtGit, remoteName, baseBranch, true);
|
|
230
231
|
return {
|
|
231
232
|
action: "reconciled",
|
|
232
233
|
plan,
|
|
@@ -251,4 +252,4 @@ async function reconcileBranches(projectRoot, options = {}) {
|
|
|
251
252
|
//#endregion
|
|
252
253
|
export { reconcileBranches as t };
|
|
253
254
|
|
|
254
|
-
//# sourceMappingURL=reconcile-
|
|
255
|
+
//# sourceMappingURL=reconcile-DSflMfhj.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile-DSflMfhj.mjs","names":[],"sources":["../src/git/ref-reader.ts","../src/git/reconcile.ts"],"sourcesContent":["import { type SimpleGit } from 'simple-git'\nimport { createGit } from './identity.js'\nimport type { RepoReader } from '../core/contracts/index.js'\n\n/**\n * GitRefReader — `RepoReader` over an arbitrary local git ref, without a\n * checkout. `git show <ref>:<path>` reads blobs, `ls-tree` lists a tree\n * level, `cat-file -e` answers existence. This is what lets a reconcile\n * planner read three trees (merge-base, contentrain, base branch) side by\n * side while the developer's working tree stays untouched.\n *\n * The ref is fixed at construction; the per-call `ref?` parameter of the\n * `RepoReader` contract is ignored — a bound reader answering for a\n * different ref than it was built for would be a lie. Bind another instance\n * instead.\n */\nexport class GitRefReader implements RepoReader {\n private readonly git: SimpleGit\n\n constructor(\n public readonly projectRoot: string,\n public readonly ref: string,\n ) {\n this.git = createGit(projectRoot)\n }\n\n async readFile(path: string, _ref?: string): Promise<string> {\n return this.git.show([`${this.ref}:${normalize(path)}`])\n }\n\n async listDirectory(path: string, _ref?: string): Promise<string[]> {\n try {\n // Trailing slash = \"list the tree at this path\", bare names via\n // --name-only; ls-tree is non-recursive by default, matching the\n // contract's \"does not recurse\".\n const dir = normalize(path)\n const args = dir && dir !== '.'\n ? ['ls-tree', '--name-only', this.ref, `${dir}/`]\n : ['ls-tree', '--name-only', this.ref]\n const raw = await this.git.raw(args)\n return raw\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean)\n .map(entry => entry.slice(entry.lastIndexOf('/') + 1))\n } catch {\n return []\n }\n }\n\n async fileExists(path: string, _ref?: string): Promise<boolean> {\n try {\n await this.git.raw(['cat-file', '-e', `${this.ref}:${normalize(path)}`])\n return true\n } catch {\n return false\n }\n }\n}\n\n/** Strip a trailing slash — `<ref>:dir/` and `<ref>:dir` name different things to git. */\nfunction normalize(path: string): string {\n return path.endsWith('/') ? path.slice(0, -1) : path\n}\n","import { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { randomUUID } from 'node:crypto'\nimport { CONTENTRAIN_BRANCH } from '@contentrain/types'\nimport type { BaseAdvance, ConflictResolution, ContextSource, RemotePush, SyncResult } from '@contentrain/types'\nimport { createGit, authorConfig } from './identity.js'\nimport { networkGit } from './branch-lifecycle.js'\nimport { GitRefReader } from './ref-reader.js'\nimport { resolveBaseBranch } from './base-branch.js'\nimport { pushPolicy } from './push-policy.js'\nimport {\n NETWORK_TIMEOUT_MS,\n ensureContentBranch,\n isAncestor,\n pushContentBranches,\n selectiveSync,\n} from './transaction.js'\nimport { readConfig } from '../core/config.js'\nimport { planReconcile } from '../core/ops/reconcile/index.js'\nimport type { ReconcilePlan } from '../core/ops/reconcile/index.js'\nimport { applyChangesToWorktree } from '../core/ops/apply-to-worktree.js'\n\nexport interface ReconcileBranchesOptions {\n /** Preview only — plan, never touch a ref. Default true. */\n dryRun?: boolean\n /** Decisions for previously reported conflicts, matched by id. */\n resolutions?: ConflictResolution[]\n /** Stamped into the regenerated context.json. */\n source?: ContextSource\n}\n\nexport interface ReconcileBranchesResult {\n /**\n * `in_sync` — nothing to reconcile (base already contained in contentrain\n * and no content differences; a base merely behind was fast-forwarded).\n * `preview` — dry-run: the plan says what WOULD happen.\n * `conflicts` — unresolved conflicts remain; nothing was written.\n * `reconciled` — the merge commit landed and the base branch advanced.\n */\n action: 'in_sync' | 'preview' | 'conflicts' | 'reconciled'\n plan: ReconcilePlan\n base: string\n merge_base: string | null\n commit?: string\n base_advance?: BaseAdvance\n sync?: SyncResult\n remote_push?: RemotePush\n}\n\n/**\n * Content-aware reconcile of a diverged `contentrain` ↔ base pair, local\n * executor. Plans with three `GitRefReader`s (no checkout, developer tree\n * untouched), then — when clean or fully resolved — performs a REAL\n * `git merge --no-commit` in a temp worktree and writes the ENTIRE plan\n * output over it before committing: for content-owned paths the planner is\n * authoritative, never git's textual auto-merge (its line-level result can\n * be non-canonical or wrong without ever surfacing as a conflict). Git\n * still resolves everything the planner does not own — source files, lock\n * files — exactly as a manual merge would. The commit joins both parents,\n * so afterwards the base branch is an ancestor again and the ordinary\n * fast-forward advance works.\n *\n * Reconcile never runs inside a write path: it is an explicit, separate\n * step (tool, CLI, or Studio background job).\n */\nexport async function reconcileBranches(\n projectRoot: string,\n options: ReconcileBranchesOptions = {},\n): Promise<ReconcileBranchesResult> {\n const dryRun = options.dryRun ?? true\n const git = createGit(projectRoot)\n const config = await readConfig(projectRoot)\n const remoteName = process.env['CONTENTRAIN_REMOTE'] ?? 'origin'\n\n const currentBranch = (await git.raw(['branch', '--show-current']).catch(() => '')).trim()\n const baseBranch = await resolveBaseBranch(git, config, { remoteName, currentBranch })\n if (currentBranch === CONTENTRAIN_BRANCH) {\n throw Object.assign(new Error(\n `The '${CONTENTRAIN_BRANCH}' branch is checked out in your working directory. `\n + `Switch to your working branch and retry.`,\n ), {\n code: 'CONTENT_BRANCH_CHECKED_OUT',\n agent_hint: 'Ask the developer to switch to their working branch, then retry.',\n developer_action: `git checkout ${baseBranch}`,\n })\n }\n\n await ensureContentBranch(projectRoot)\n\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 if (hasRemote) {\n const net = networkGit(projectRoot, NETWORK_TIMEOUT_MS)\n await Promise.all([\n net.fetch(remoteName, baseBranch).catch(() => {}),\n net.fetch(remoteName, CONTENTRAIN_BRANCH).catch(() => {}),\n ])\n }\n\n const [contentrainTip, baseTip] = await Promise.all([\n git.raw(['rev-parse', CONTENTRAIN_BRANCH]).then(s => s.trim()),\n git.raw(['rev-parse', baseBranch]).then(s => s.trim()),\n ])\n\n let mergeBase: string | null = null\n try {\n mergeBase = (await git.raw(['merge-base', CONTENTRAIN_BRANCH, baseBranch])).trim() || null\n } catch {\n mergeBase = null\n }\n if (!mergeBase) {\n throw Object.assign(new Error(\n `\"${CONTENTRAIN_BRANCH}\" and \"${baseBranch}\" share no history — reconcile needs a common ancestor.`,\n ), {\n code: 'RECONCILE_NO_MERGE_BASE',\n agent_hint: 'The branches are unrelated. This needs a human decision about which history is canonical.',\n })\n }\n\n const plan = await planReconcile({\n base: new GitRefReader(projectRoot, mergeBase),\n ours: new GitRefReader(projectRoot, CONTENTRAIN_BRANCH),\n theirs: new GitRefReader(projectRoot, baseBranch),\n resolutions: options.resolutions,\n source: options.source ?? 'mcp-local',\n })\n\n // Base already contained in contentrain: there is no divergence. A base\n // merely BEHIND is fast-forwarded on the spot (that is what the ordinary\n // write path would do next anyway).\n if (await isAncestor(git, baseTip, contentrainTip)) {\n if (!dryRun && baseTip !== contentrainTip) {\n await git.raw(['update-ref', `refs/heads/${baseBranch}`, contentrainTip])\n try {\n await git.raw(['read-tree', 'HEAD'])\n } catch {\n try { await git.raw(['reset', 'HEAD']) } catch { /* ignore */ }\n }\n return { action: 'reconciled', plan, base: baseBranch, merge_base: mergeBase, commit: contentrainTip, base_advance: 'advanced' }\n }\n return { action: 'in_sync', plan, base: baseBranch, merge_base: mergeBase }\n }\n\n if (dryRun) {\n return { action: 'preview', plan, base: baseBranch, merge_base: mergeBase }\n }\n if (plan.conflicts.length > 0) {\n return { action: 'conflicts', plan, base: baseBranch, merge_base: mergeBase }\n }\n\n // Execute: real merge in a temp worktree, planner output written on top.\n const worktreePath = join(tmpdir(), `cr-reconcile-${randomUUID()}`)\n await git.raw(['worktree', 'add', worktreePath, CONTENTRAIN_BRANCH])\n const wtGit = createGit(worktreePath, { config: authorConfig() })\n\n try {\n try {\n await wtGit.merge([baseBranch, '--no-commit', '--no-ff'])\n } catch {\n // Conflicts inside the worktree are expected — the planner's output\n // overwrites every content-owned path next. Anything left unmerged\n // after that is genuinely outside the planner's scope.\n }\n\n await applyChangesToWorktree(worktreePath, plan.changes)\n await wtGit.raw(['add', '-A'])\n\n const unmerged = (await wtGit.raw(['ls-files', '-u'])).trim()\n if (unmerged) {\n const paths = [...new Set(unmerged.split('\\n').map(l => l.split('\\t')[1]).filter(Boolean))]\n try { await wtGit.merge(['--abort']) } catch { /* not in merge state */ }\n throw Object.assign(new Error(\n `Reconcile stopped: ${paths.length} non-content file(s) conflict between \"${CONTENTRAIN_BRANCH}\" and \"${baseBranch}\": ${paths.join(', ')}. `\n + `These are outside the content planner's scope and need a manual merge.`,\n ), {\n code: 'RECONCILE_SOURCE_CONFLICT',\n agent_hint: 'Non-content files conflict. Ask the developer to merge the branches manually for those paths.',\n developer_action: `git checkout ${CONTENTRAIN_BRANCH} && git merge ${baseBranch}`,\n })\n }\n\n await wtGit.commit(\n `[contentrain] reconcile: merge ${baseBranch} into ${CONTENTRAIN_BRANCH}`,\n { '--no-verify': null },\n )\n\n const [newTip, previousBaseRef, statusBeforeUpdate] = await Promise.all([\n wtGit.raw(['rev-parse', 'HEAD']).then(s => s.trim()),\n git.raw(['rev-parse', baseBranch]).then(s => s.trim()),\n git.status(),\n ])\n const dirtyFilesBeforeUpdate = new Set(statusBeforeUpdate.files.map(f => f.path))\n\n // The merge commit has baseBranch as a parent, so this is a true\n // fast-forward again — the whole point of the exercise.\n await git.raw(['update-ref', `refs/heads/${baseBranch}`, newTip])\n try {\n await git.raw(['read-tree', 'HEAD'])\n } catch {\n try { await git.raw(['reset', 'HEAD']) } catch { /* ignore */ }\n }\n const sync = await selectiveSync(projectRoot, worktreePath, newTip, previousBaseRef, dirtyFilesBeforeUpdate)\n\n const push = pushPolicy(config).push\n let remotePush: RemotePush = push ? 'no-remote' : 'disabled'\n if (hasRemote && push) {\n remotePush = await pushContentBranches(projectRoot, worktreePath, wtGit, remoteName, baseBranch, true)\n }\n\n return {\n action: 'reconciled',\n plan,\n base: baseBranch,\n merge_base: mergeBase,\n commit: newTip,\n base_advance: 'advanced',\n sync,\n remote_push: remotePush,\n }\n } finally {\n try {\n await git.raw(['worktree', 'remove', worktreePath, '--force'])\n } catch {\n // worktree may already be cleaned up\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as buildContextChange } from "./context-
|
|
2
|
-
import { A as contentDirPath, M as documentFilePath, j as contentFilePath } from "./model-manager-
|
|
1
|
+
import { t as buildContextChange } from "./context-D9HXnnGj.mjs";
|
|
2
|
+
import { A as contentDirPath, M as documentFilePath, j as contentFilePath } from "./model-manager-D0VztE8M.mjs";
|
|
3
3
|
import { t as OverlayReader } from "./overlay-reader-Bx1YxGXq.mjs";
|
|
4
|
-
import { t as validateProject } from "./validator-
|
|
4
|
+
import { t as validateProject } from "./validator-S19GHR4V.mjs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { MODEL_FIELD_ORDER, canonicalStringify, conflictId, parseMarkdownFrontmatter, serializeMarkdownFrontmatter, sortKeys, stableHash } from "@contentrain/types";
|
|
@@ -565,6 +565,7 @@ function mergeDocumentFile(three, ctx) {
|
|
|
565
565
|
if (["ours", "theirs"].some((side) => {
|
|
566
566
|
const doc = parsed[side];
|
|
567
567
|
if (!doc) return false;
|
|
568
|
+
if (doc.preserved) return true;
|
|
568
569
|
const reserialized = parseMarkdownFrontmatter(serializeMarkdownFrontmatter(doc.frontmatter, doc.body));
|
|
569
570
|
return !eqCanonical(reserialized.frontmatter, doc.frontmatter) || reserialized.body !== doc.body;
|
|
570
571
|
})) {
|
|
@@ -1724,4 +1725,4 @@ function parseJson(raw) {
|
|
|
1724
1725
|
//#endregion
|
|
1725
1726
|
export { applyChangesToWorktree as n, planReconcile as t };
|
|
1726
1727
|
|
|
1727
|
-
//# sourceMappingURL=reconcile-
|
|
1728
|
+
//# sourceMappingURL=reconcile-J-iorvSa.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile-J-iorvSa.mjs","names":[],"sources":["../src/core/ops/apply-to-worktree.ts","../src/core/ops/reconcile/read.ts","../src/core/ops/reconcile/resolutions.ts","../src/core/ops/reconcile/types.ts","../src/core/ops/reconcile/three-way.ts","../src/core/ops/reconcile/merge-content.ts","../src/core/ops/reconcile/merge-document.ts","../src/core/ops/reconcile/merge-meta.ts","../src/core/ops/reconcile/merge-model.ts","../src/core/ops/reconcile/merge-vocabulary.ts","../src/core/ops/reconcile/walk.ts","../src/core/ops/reconcile/plan.ts"],"sourcesContent":["import { mkdir, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport type { FileChange } from '../contracts/index.js'\n\n/**\n * Phase 2 shim — applies a `FileChange[]` to a local worktree directory.\n *\n * Phase 3 replaces this with `LocalProvider.applyPlan` (same logic, owned by\n * the provider) while a `GitHubProvider.applyPlan` produces the equivalent\n * commit through the Git Data API. Until then, tool handlers call this shim\n * inside the existing `createTransaction` flow so Phase 2 gets plan/apply\n * semantics without rewriting the transaction machinery.\n *\n * - `content: string` → `writeFile` (creates parent dirs).\n * - `content: null` → `rm`; missing files are tolerated.\n * - Changes are applied in their given order; callers should sort for\n * determinism.\n */\nexport async function applyChangesToWorktree(worktree: string, changes: readonly FileChange[]): Promise<void> {\n for (const change of changes) {\n const absolute = join(worktree, change.path)\n if (change.content === null) {\n await rm(absolute, { force: true }).catch(() => { /* tolerate missing */ })\n continue\n }\n await mkdir(dirname(absolute), { recursive: true })\n await writeFile(absolute, change.content, 'utf-8')\n }\n}\n","import type { RepoReader } from '../../contracts/index.js'\n\n/**\n * Per-side promise-memo over a `RepoReader`: every `(path)` read and listing\n * happens at most once for the whole plan, and concurrent duplicate requests\n * coalesce onto the same promise instead of racing. On a GitHub-backed\n * reader this is the difference between one API call per file and one per\n * phase that touches it.\n *\n * The `RepoReader` contract is preserved (`readFile` throws on a missing\n * file) so a `CachedReader` can be handed to `OverlayReader`,\n * `validateProject` and `buildContextChange` unchanged; the planner's own\n * code uses the null-tolerant extras.\n */\nexport class CachedReader implements RepoReader {\n private readonly files = new Map<string, Promise<string | null>>()\n private readonly dirs = new Map<string, Promise<string[]>>()\n private readonly exists = new Map<string, Promise<boolean>>()\n\n constructor(private readonly inner: RepoReader) {}\n\n /** Contract-shaped read: throws when missing. */\n async readFile(path: string, _ref?: string): Promise<string> {\n const content = await this.readOrNull(path)\n if (content === null) throw new Error(`ENOENT: no such file, open '${path}'`)\n return content\n }\n\n /** Planner-shaped read: `null` when missing, cached either way. */\n readOrNull(path: string): Promise<string | null> {\n let cached = this.files.get(path)\n if (!cached) {\n cached = this.inner.readFile(path).then(content => content, () => null)\n this.files.set(path, cached)\n }\n return cached\n }\n\n listDirectory(path: string, _ref?: string): Promise<string[]> {\n let cached = this.dirs.get(path)\n if (!cached) {\n cached = this.inner.listDirectory(path).then(entries => entries, () => [])\n this.dirs.set(path, cached)\n }\n return cached\n }\n\n fileExists(path: string, _ref?: string): Promise<boolean> {\n let cached = this.exists.get(path)\n if (!cached) {\n cached = this.inner.fileExists(path).then(v => v, () => false)\n this.exists.set(path, cached)\n }\n return cached\n }\n}\n\n/** Parse a JSON file, `null` when missing or unparseable. */\nexport async function readJsonOrNull<T>(reader: CachedReader, path: string): Promise<T | null> {\n const raw = await reader.readOrNull(path)\n if (raw === null) return null\n try {\n return JSON.parse(raw) as T\n } catch {\n return null\n }\n}\n","import { conflictId } from '@contentrain/types'\nimport type { ConflictResolution } from '@contentrain/types'\n\n/**\n * Candidate identity at a conflict site — the same tuple `conflictId`\n * hashes, so a stored resolution matches exactly when position AND values\n * are unchanged since the dry-run that produced it (N1: compare-and-set).\n */\nexport interface ConflictCandidate {\n path: string\n key?: string\n field?: string\n locale?: string\n base?: unknown\n ours?: unknown\n theirs?: unknown\n}\n\n/**\n * The decisions carried into a second reconcile round, indexed by conflict\n * id. Merge functions call `consume` at the exact point they would create a\n * `ConflictItem`; a hit turns the conflict into a mechanical outcome, a\n * miss reports it. After planning, `unconsumed()` names the stale\n * resolutions — values changed between rounds, so the decision no longer\n * applies and the conflict reappears with a fresh id.\n */\nexport class ResolutionIndex {\n private readonly byId = new Map<string, ConflictResolution>()\n private readonly consumed = new Set<string>()\n\n constructor(resolutions: ConflictResolution[] = []) {\n for (const resolution of resolutions) this.byId.set(resolution.id, resolution)\n }\n\n consume(candidate: ConflictCandidate): ConflictResolution | null {\n const id = conflictId(candidate)\n const resolution = this.byId.get(id)\n if (!resolution) return null\n this.consumed.add(id)\n return resolution\n }\n\n unconsumed(): ConflictResolution[] {\n return [...this.byId.values()].filter(r => !this.consumed.has(r.id))\n }\n}\n\n/**\n * Apply a consumed resolution to the candidate's values. `choose` picks a\n * side (absence on that side means deletion → `undefined`); `value`\n * supplies a replacement outright.\n */\nexport function resolvedValue(\n resolution: ConflictResolution,\n candidate: ConflictCandidate,\n): unknown {\n if ('choose' in resolution) {\n return resolution.choose === 'ours' ? candidate.ours : candidate.theirs\n }\n return resolution.value\n}\n","import type { ConflictItem, ConflictResolution, ContextSource } from '@contentrain/types'\nimport type { RepoReader } from '../../contracts/index.js'\nimport type { OpPlan } from '../types.js'\nimport type { ResolutionIndex } from './resolutions.js'\n\n/**\n * Input to the content-aware three-way reconcile planner.\n *\n * All three readers are ref-bound (`bindRef` / `GitRefReader`): `base` reads\n * the merge-base tree, `ours` the content branch (contentrain), `theirs` the\n * base branch (main). The planner never sees a ref — executors decide what\n * to bind.\n */\nexport interface ReconcileInput {\n base: RepoReader\n ours: RepoReader\n theirs: RepoReader\n /** Second round: decisions for previously reported conflicts, matched by id. */\n resolutions?: ConflictResolution[]\n /** Stamped into the regenerated context.json. */\n source?: ContextSource\n}\n\nexport interface ReconcileResult {\n /** Content-layer files whose merged output differs from ours (context.json excluded). */\n files_merged: number\n /** Items resolved by taking the side that changed them. */\n entries_taken_ours: number\n entries_taken_theirs: number\n /** Items where both sides changed different fields and the union won. */\n entries_field_merged: number\n /** Regenerated derived files — `['.contentrain/context.json']` or empty on a no-op. */\n regenerated: string[]\n}\n\n/**\n * A reconcile plan: everything mechanical is already merged into `changes`\n * (applied on top of ours); `conflicts` are the items the policy table\n * cannot decide. A plan with conflicts is not applied — the caller collects\n * decisions and runs a second round with `resolutions`.\n */\nexport interface ReconcilePlan extends OpPlan<ReconcileResult> {\n conflicts: ConflictItem[]\n}\n\n// ─── Internal shapes (not exported from the barrel) ───\n\nexport type Side = 'ours' | 'theirs'\n\n/** One value as seen by the three trees; `undefined` = absent on that side. */\nexport interface ThreeWay<T> {\n base: T | undefined\n ours: T | undefined\n theirs: T | undefined\n}\n\nexport interface MergeStats {\n takenOurs: number\n takenTheirs: number\n fieldMerged: number\n}\n\nexport const EMPTY_STATS: MergeStats = { takenOurs: 0, takenTheirs: 0, fieldMerged: 0 }\n\nexport function addStats(a: MergeStats, b: MergeStats): MergeStats {\n return {\n takenOurs: a.takenOurs + b.takenOurs,\n takenTheirs: a.takenTheirs + b.takenTheirs,\n fieldMerged: a.fieldMerged + b.fieldMerged,\n }\n}\n\n/**\n * Context every per-kind merge function receives: where the merged file\n * lives, which model/locale it belongs to, and the resolution index that is\n * consulted at the exact point a conflict would otherwise be created.\n */\nexport interface FileCtx {\n outPath: string\n kind: ConflictItem['kind']\n model?: string\n locale?: string\n slug?: string\n resolutions: ResolutionIndex\n}\n","import { conflictId, sortKeys } from '@contentrain/types'\nimport type { ConflictItem } from '@contentrain/types'\nimport type { MergeStats, Side, ThreeWay } from './types.js'\nimport { EMPTY_STATS, addStats } from './types.js'\n\n/**\n * Canonical equality: both values normalized through `sortKeys` (which drops\n * `null`/`undefined` values and sorts keys recursively — the same normal\n * form `canonicalStringify` writes to disk), then compared as JSON. So\n * `{ a: null }` equals `{}`, key order never matters, and absent equals\n * null — exactly the distinctions the storage layer cannot represent.\n */\nexport function eqCanonical(a: unknown, b: unknown): boolean {\n const na = sortKeys(a)\n const nb = sortKeys(b)\n if (na === undefined || nb === undefined) return na === nb\n return JSON.stringify(na) === JSON.stringify(nb)\n}\n\n/** Outcome of a three-way merge of one leaf value. */\nexport type Leaf3 =\n | { ok: true, value: unknown, from: 'base' | 'both' | Side }\n | { ok: false, reason: 'both_changed' | 'delete_edit', deletedBy?: Side }\n\n/**\n * The three-way rule every policy row reduces to: a side that did not\n * change keeps no vote; identical changes converge; a lone change wins;\n * two different changes are a conflict — with delete-vs-edit split out\n * because it needs a different question (\"keep or drop?\" instead of\n * \"which value?\").\n */\nexport function mergeLeaf3(base: unknown, ours: unknown, theirs: unknown): Leaf3 {\n const oursSame = eqCanonical(ours, base)\n const theirsSame = eqCanonical(theirs, base)\n if (oursSame && theirsSame) return { ok: true, value: base, from: 'base' }\n if (eqCanonical(ours, theirs)) return { ok: true, value: ours, from: 'both' }\n if (oursSame) return { ok: true, value: theirs, from: 'theirs' }\n if (theirsSame) return { ok: true, value: ours, from: 'ours' }\n\n const baseAbsent = sortKeys(base) === undefined\n const oursAbsent = sortKeys(ours) === undefined\n const theirsAbsent = sortKeys(theirs) === undefined\n if (!baseAbsent && oursAbsent !== theirsAbsent) {\n return { ok: false, reason: 'delete_edit', deletedBy: oursAbsent ? 'ours' : 'theirs' }\n }\n return { ok: false, reason: 'both_changed' }\n}\n\n/** What a keyed-record item merge decided, plus its bookkeeping. */\nexport interface ItemOutcome<T> {\n /** Merged value; `undefined` = the item is absent from the output. */\n value: T | undefined\n conflicts: ConflictItem[]\n stats: MergeStats\n}\n\n/**\n * Three-way merge of a keyed record (entries by ID, dictionary by key,\n * terms by slug, schema by property name): iterate the sorted union of\n * keys, delegate each item to `mergeItem`, assemble the output map.\n */\nexport function mergeKeyedRecord3<T>(\n three: ThreeWay<Record<string, T>>,\n mergeItem: (key: string, item: ThreeWay<T>) => ItemOutcome<T>,\n): { merged: Record<string, T>, conflicts: ConflictItem[], stats: MergeStats } {\n const keys = [...new Set([\n ...Object.keys(three.base ?? {}),\n ...Object.keys(three.ours ?? {}),\n ...Object.keys(three.theirs ?? {}),\n ])].toSorted()\n\n const merged: Record<string, T> = {}\n const conflicts: ConflictItem[] = []\n let stats = EMPTY_STATS\n for (const key of keys) {\n const outcome = mergeItem(key, {\n base: three.base?.[key],\n ours: three.ours?.[key],\n theirs: three.theirs?.[key],\n })\n if (outcome.value !== undefined) merged[key] = outcome.value\n conflicts.push(...outcome.conflicts)\n stats = addStats(stats, outcome.stats)\n }\n return { merged, conflicts, stats }\n}\n\n/** Attach the value-derived id — the only way a ConflictItem is ever built. */\nexport function makeConflict(input: Omit<ConflictItem, 'id'>): ConflictItem {\n return { id: conflictId(input), ...input }\n}\n\n/** Stats bucket for a clean leaf outcome; `base`/`both` count nowhere. */\nexport function statsFor(from: 'base' | 'both' | Side): MergeStats {\n if (from === 'ours') return { takenOurs: 1, takenTheirs: 0, fieldMerged: 0 }\n if (from === 'theirs') return { takenOurs: 0, takenTheirs: 1, fieldMerged: 0 }\n return EMPTY_STATS\n}\n","import type { ConflictItem } from '@contentrain/types'\nimport type { FileCtx, MergeStats, ThreeWay } from './types.js'\nimport { EMPTY_STATS, addStats } from './types.js'\nimport { makeConflict, mergeKeyedRecord3, mergeLeaf3, statsFor } from './three-way.js'\nimport { resolvedValue } from './resolutions.js'\n\ntype Fields = Record<string, unknown>\n\n/**\n * Three-way merge of one entry's fields. Called when the entry as a whole\n * changed on BOTH sides: different fields both win (the union), the same\n * field with two values is a conflict, and a field deleted on one side but\n * edited on the other asks a different question and gets its own code.\n * An unresolved conflict keeps ours' value in the output — the plan is not\n * applied while conflicts remain, so nothing is ever silently overwritten.\n */\nexport function mergeEntryFields(\n three: ThreeWay<Fields>,\n ctx: FileCtx,\n entryKey?: string,\n): { merged: Fields, conflicts: ConflictItem[] } {\n const fields = [...new Set([\n ...Object.keys(three.base ?? {}),\n ...Object.keys(three.ours ?? {}),\n ...Object.keys(three.theirs ?? {}),\n ])].toSorted()\n\n const merged: Fields = {}\n const conflicts: ConflictItem[] = []\n for (const field of fields) {\n const candidate = {\n path: ctx.outPath,\n key: entryKey,\n field,\n locale: ctx.locale,\n base: three.base?.[field],\n ours: three.ours?.[field],\n theirs: three.theirs?.[field],\n }\n const leaf = mergeLeaf3(candidate.base, candidate.ours, candidate.theirs)\n if (leaf.ok) {\n if (leaf.value !== undefined) merged[field] = leaf.value\n continue\n }\n\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n const value = resolvedValue(resolution, candidate)\n if (value !== undefined) merged[field] = value\n continue\n }\n\n if (candidate.ours !== undefined) merged[field] = candidate.ours\n const label = entryKey ? `\"${entryKey}\" field \"${field}\"` : `field \"${field}\"`\n conflicts.push(makeConflict({\n ...candidate,\n kind: ctx.kind,\n model: ctx.model,\n code: leaf.reason === 'delete_edit' ? 'delete_edit_conflict' : 'field_value_conflict',\n message: leaf.reason === 'delete_edit'\n ? `${label} was deleted on one side and edited on the other in ${ctx.outPath}.`\n : `${label} was changed to different values on both sides in ${ctx.outPath}.`,\n }))\n }\n return { merged, conflicts }\n}\n\n/**\n * Collection file: entry-level three-way over the object-map, field-level\n * union when both sides touched the same entry.\n */\nexport function mergeCollectionFile(\n three: ThreeWay<Record<string, Fields>>,\n ctx: FileCtx,\n): { merged: Record<string, Fields> | undefined, conflicts: ConflictItem[], stats: MergeStats } {\n const fileLeaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (fileLeaf.ok) {\n return { merged: fileLeaf.value as Record<string, Fields> | undefined, conflicts: [], stats: statsFor(fileLeaf.from) }\n }\n\n const result = mergeKeyedRecord3(three, (entryId, entry) => {\n const leaf = mergeLeaf3(entry.base, entry.ours, entry.theirs)\n if (leaf.ok) {\n return { value: leaf.value as Fields | undefined, conflicts: [], stats: statsFor(leaf.from) }\n }\n if (leaf.reason === 'delete_edit') {\n const candidate = {\n path: ctx.outPath, key: entryId, locale: ctx.locale,\n base: entry.base, ours: entry.ours, theirs: entry.theirs,\n }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n const value = resolvedValue(resolution, candidate) as Fields | undefined\n return { value, conflicts: [], stats: EMPTY_STATS }\n }\n return {\n value: entry.ours,\n conflicts: [makeConflict({\n ...candidate,\n kind: ctx.kind,\n model: ctx.model,\n code: 'delete_edit_conflict',\n message: `Entry \"${entryId}\" was deleted on one side and edited on the other in ${ctx.outPath}.`,\n })],\n stats: EMPTY_STATS,\n }\n }\n // Both sides changed the entry: merge field-wise.\n const fieldMerge = mergeEntryFields(entry, ctx, entryId)\n return {\n value: fieldMerge.merged,\n conflicts: fieldMerge.conflicts,\n stats: fieldMerge.conflicts.length === 0 ? { takenOurs: 0, takenTheirs: 0, fieldMerged: 1 } : EMPTY_STATS,\n }\n })\n return result\n}\n\n/** Singleton file: one record — straight field-level three-way. */\nexport function mergeSingletonFile(\n three: ThreeWay<Fields>,\n ctx: FileCtx,\n): { merged: Fields | undefined, conflicts: ConflictItem[], stats: MergeStats } {\n const fileLeaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (fileLeaf.ok) {\n return { merged: fileLeaf.value as Fields | undefined, conflicts: [], stats: statsFor(fileLeaf.from) }\n }\n if (fileLeaf.reason === 'delete_edit') {\n const candidate = { path: ctx.outPath, locale: ctx.locale, base: three.base, ours: three.ours, theirs: three.theirs }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n return { merged: resolvedValue(resolution, candidate) as Fields | undefined, conflicts: [], stats: EMPTY_STATS }\n }\n return {\n merged: three.ours,\n conflicts: [makeConflict({\n ...candidate,\n kind: ctx.kind,\n model: ctx.model,\n code: 'delete_edit_conflict',\n message: `${ctx.outPath} was deleted on one side and edited on the other.`,\n })],\n stats: EMPTY_STATS,\n }\n }\n const fieldMerge = mergeEntryFields(three, ctx)\n return {\n merged: fieldMerge.merged,\n conflicts: fieldMerge.conflicts,\n stats: fieldMerge.conflicts.length === 0 ? { takenOurs: 0, takenTheirs: 0, fieldMerged: 1 } : EMPTY_STATS,\n }\n}\n\n/**\n * Dictionary file: keys are the identities, values are string leaves. The\n * three-way generalization of the inline upsert semantics: with an author\n * present (a save), the incoming value wins and is reported; with no\n * author (reconcile), two changed values are a question, not a pick.\n */\nexport function mergeDictionaryFile(\n three: ThreeWay<Record<string, string>>,\n ctx: FileCtx,\n): { merged: Record<string, string> | undefined, conflicts: ConflictItem[], stats: MergeStats } {\n const fileLeaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (fileLeaf.ok) {\n return { merged: fileLeaf.value as Record<string, string> | undefined, conflicts: [], stats: statsFor(fileLeaf.from) }\n }\n\n return mergeKeyedRecord3(three, (key, item) => {\n const leaf = mergeLeaf3(item.base, item.ours, item.theirs)\n if (leaf.ok) {\n return { value: leaf.value as string | undefined, conflicts: [], stats: statsFor(leaf.from) }\n }\n const candidate = {\n path: ctx.outPath, key, locale: ctx.locale,\n base: item.base, ours: item.ours, theirs: item.theirs,\n }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n return { value: resolvedValue(resolution, candidate) as string | undefined, conflicts: [], stats: EMPTY_STATS }\n }\n return {\n value: item.ours,\n conflicts: [makeConflict({\n ...candidate,\n kind: 'dictionary',\n model: ctx.model,\n code: leaf.reason === 'delete_edit' ? 'delete_edit_conflict' : 'dictionary_value_conflict',\n message: leaf.reason === 'delete_edit'\n ? `Dictionary key \"${key}\" was deleted on one side and edited on the other in ${ctx.outPath}.`\n : `Dictionary key \"${key}\" has two different values in ${ctx.outPath}.`,\n })],\n stats: EMPTY_STATS,\n }\n })\n}\n\n/**\n * `normalize-sources.json`: keyed tooling metadata — merged with the\n * dictionary rule, values compared canonically whatever their shape.\n */\nexport function mergeKeyedJson(\n three: ThreeWay<Record<string, unknown>>,\n ctx: FileCtx,\n): { merged: Record<string, unknown> | undefined, conflicts: ConflictItem[], stats: MergeStats } {\n const fileLeaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (fileLeaf.ok) {\n return { merged: fileLeaf.value as Record<string, unknown> | undefined, conflicts: [], stats: statsFor(fileLeaf.from) }\n }\n const result = mergeKeyedRecord3(three, (key, item) => {\n const leaf = mergeLeaf3(item.base, item.ours, item.theirs)\n if (leaf.ok) return { value: leaf.value, conflicts: [], stats: statsFor(leaf.from) }\n const candidate = { path: ctx.outPath, key, base: item.base, ours: item.ours, theirs: item.theirs }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) return { value: resolvedValue(resolution, candidate), conflicts: [], stats: EMPTY_STATS }\n return {\n value: item.ours,\n conflicts: [makeConflict({\n ...candidate,\n kind: 'file',\n code: 'file_conflict',\n message: `Key \"${key}\" of ${ctx.outPath} was changed differently on both sides.`,\n })],\n stats: EMPTY_STATS,\n }\n })\n return { merged: result.merged, conflicts: result.conflicts, stats: result.stats }\n}\n\n/** Sum helper used by the orchestrator when it folds per-file stats. */\nexport { addStats }\n","import { parseMarkdownFrontmatter, serializeMarkdownFrontmatter } from '@contentrain/types'\nimport type { ConflictItem } from '@contentrain/types'\nimport type { FileCtx, MergeStats, ThreeWay } from './types.js'\nimport { EMPTY_STATS } from './types.js'\nimport { eqCanonical, makeConflict, mergeLeaf3, statsFor } from './three-way.js'\nimport { resolvedValue } from './resolutions.js'\n\n/**\n * Document (.md) three-way merge: frontmatter key-by-key, body as one leaf.\n *\n * The body is deliberately never text-merged. Interleaving two prose edits\n * line-by-line produces syntactically plausible, editorially wrong hybrids —\n * choosing between two versions of prose is a content decision, and MCP\n * does not make those.\n *\n * The frontmatter parser round-trips only scalars and scalar arrays\n * (nested maps serialize but do not parse back). A side whose frontmatter\n * does not survive parse→serialize is not safe to rewrite, so the merge\n * falls back to whole-file resolution for that document instead of\n * silently destroying nested data.\n */\nexport function mergeDocumentFile(\n three: ThreeWay<string>,\n ctx: FileCtx & { slug: string },\n): { merged: string | undefined, conflicts: ConflictItem[], stats: MergeStats, advisories: string[] } {\n const fileLeaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (fileLeaf.ok) {\n return { merged: fileLeaf.value as string | undefined, conflicts: [], stats: statsFor(fileLeaf.from), advisories: [] }\n }\n\n if (fileLeaf.reason === 'delete_edit') {\n const candidate = {\n path: ctx.outPath, key: ctx.slug, locale: ctx.locale,\n base: three.base, ours: three.ours, theirs: three.theirs,\n }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n return { merged: resolvedValue(resolution, candidate) as string | undefined, conflicts: [], stats: EMPTY_STATS, advisories: [] }\n }\n return {\n merged: three.ours,\n conflicts: [makeConflict({\n ...candidate,\n kind: 'document',\n model: ctx.model,\n code: 'delete_edit_conflict',\n message: `Document \"${ctx.slug}\" was deleted on one side and edited on the other (${ctx.outPath}).`,\n })],\n stats: EMPTY_STATS,\n advisories: [],\n }\n }\n\n // Both sides changed. Structured merge is only safe when every present\n // side survives the parse→serialize round-trip.\n const parsed = {\n base: three.base === undefined ? undefined : parseMarkdownFrontmatter(three.base),\n ours: three.ours === undefined ? undefined : parseMarkdownFrontmatter(three.ours),\n theirs: three.theirs === undefined ? undefined : parseMarkdownFrontmatter(three.theirs),\n }\n const lossy = (['ours', 'theirs'] as const).some((side) => {\n const doc = parsed[side]\n if (!doc) return false\n // Lines the reader cannot parse are not part of the merge: only the conflict path keeps both sides whole.\n if (doc.preserved) return true\n const reserialized = parseMarkdownFrontmatter(serializeMarkdownFrontmatter(doc.frontmatter, doc.body))\n return !eqCanonical(reserialized.frontmatter, doc.frontmatter) || reserialized.body !== doc.body\n })\n if (lossy) {\n const candidate = {\n path: ctx.outPath, key: ctx.slug, locale: ctx.locale,\n base: three.base, ours: three.ours, theirs: three.theirs,\n }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n return { merged: resolvedValue(resolution, candidate) as string | undefined, conflicts: [], stats: EMPTY_STATS, advisories: [] }\n }\n return {\n merged: three.ours,\n conflicts: [makeConflict({\n ...candidate,\n kind: 'document',\n model: ctx.model,\n code: 'file_conflict',\n message: `Document \"${ctx.slug}\" (${ctx.outPath}) changed on both sides and carries frontmatter the structured merge cannot round-trip — choose a side.`,\n })],\n stats: EMPTY_STATS,\n advisories: [`Document \"${ctx.slug}\": fell back to whole-file resolution (nested frontmatter does not round-trip).`],\n }\n }\n\n // Frontmatter: key-level leaves. `body` is reserved and never appears in\n // frontmatter (the writer strips it), so no collision with the body leaf.\n const conflicts: ConflictItem[] = []\n const fmKeys = [...new Set([\n ...Object.keys(parsed.base?.frontmatter ?? {}),\n ...Object.keys(parsed.ours?.frontmatter ?? {}),\n ...Object.keys(parsed.theirs?.frontmatter ?? {}),\n ])].toSorted()\n const mergedFm: Record<string, unknown> = {}\n for (const key of fmKeys) {\n const candidate = {\n path: ctx.outPath, key: ctx.slug, field: key, locale: ctx.locale,\n base: parsed.base?.frontmatter[key],\n ours: parsed.ours?.frontmatter[key],\n theirs: parsed.theirs?.frontmatter[key],\n }\n const leaf = mergeLeaf3(candidate.base, candidate.ours, candidate.theirs)\n if (leaf.ok) {\n if (leaf.value !== undefined) mergedFm[key] = leaf.value\n continue\n }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n const value = resolvedValue(resolution, candidate)\n if (value !== undefined) mergedFm[key] = value\n continue\n }\n if (candidate.ours !== undefined) mergedFm[key] = candidate.ours\n conflicts.push(makeConflict({\n ...candidate,\n kind: 'document',\n model: ctx.model,\n code: leaf.reason === 'delete_edit' ? 'delete_edit_conflict' : 'frontmatter_value_conflict',\n message: `Frontmatter key \"${key}\" of document \"${ctx.slug}\" differs on both sides (${ctx.outPath}).`,\n }))\n }\n\n // Body: one leaf.\n let mergedBody: string\n const bodyCandidate = {\n path: ctx.outPath, key: ctx.slug, field: 'body', locale: ctx.locale,\n base: parsed.base?.body, ours: parsed.ours?.body, theirs: parsed.theirs?.body,\n }\n const bodyLeaf = mergeLeaf3(bodyCandidate.base, bodyCandidate.ours, bodyCandidate.theirs)\n if (bodyLeaf.ok) {\n mergedBody = (bodyLeaf.value as string | undefined) ?? ''\n } else {\n const resolution = ctx.resolutions.consume(bodyCandidate)\n if (resolution) {\n mergedBody = (resolvedValue(resolution, bodyCandidate) as string | undefined) ?? ''\n } else {\n mergedBody = bodyCandidate.ours ?? ''\n conflicts.push(makeConflict({\n ...bodyCandidate,\n kind: 'document',\n model: ctx.model,\n code: 'document_body_conflict',\n message: `Body of document \"${ctx.slug}\" was edited on both sides (${ctx.outPath}) — bodies are never text-merged.`,\n }))\n }\n }\n\n return {\n merged: serializeMarkdownFrontmatter(mergedFm, mergedBody),\n conflicts,\n stats: conflicts.length === 0 ? { takenOurs: 0, takenTheirs: 0, fieldMerged: 1 } : EMPTY_STATS,\n advisories: [],\n }\n}\n","import type { ConflictItem } from '@contentrain/types'\nimport type { FileCtx, MergeStats, ThreeWay } from './types.js'\nimport { EMPTY_STATS } from './types.js'\nimport { makeConflict, mergeKeyedRecord3, mergeLeaf3, statsFor } from './three-way.js'\nimport { resolvedValue } from './resolutions.js'\n\ntype MetaRecord = Record<string, unknown>\n\n/**\n * Three-way merge of one entry's meta. Special fields:\n *\n * - `updated_at`: the later timestamp wins (ISO strings compare lexically);\n * a present value beats an absent one.\n * - `updated_by`: follows the side that supplied the winning `updated_at`;\n * with no timestamps to arbitrate, ours is kept and an advisory says so.\n * - `status`: a publish decision belongs to whoever made it — one side's\n * change wins mechanically, but two different decisions are a question\n * for a human, never a pick.\n *\n * Entry-level delete-vs-edit resolves mechanically to the edited side with\n * an advisory: meta is bookkeeping that trails content, and the CONTENT\n * delete-vs-edit conflict (reported separately) is the real question. The\n * orphan-meta validator catches any residue once that resolves.\n */\nexport function mergeEntryMeta3(\n three: ThreeWay<MetaRecord>,\n ctx: FileCtx,\n entryKey?: string,\n): { merged: MetaRecord | undefined, conflicts: ConflictItem[], advisories: string[] } {\n const leaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (leaf.ok) return { merged: leaf.value as MetaRecord | undefined, conflicts: [], advisories: [] }\n if (leaf.reason === 'delete_edit') {\n const edited = leaf.deletedBy === 'ours' ? three.theirs : three.ours\n const label = entryKey ? `meta of \"${entryKey}\"` : `meta ${ctx.outPath}`\n return {\n merged: edited,\n conflicts: [],\n advisories: [`Kept the edited side of ${label} (deleted on the other) — bookkeeping follows the content decision.`],\n }\n }\n\n const conflicts: ConflictItem[] = []\n const advisories: string[] = []\n const merged: MetaRecord = {}\n\n // Arbitrate updated_at / updated_by together, before the generic fields.\n const oursAt = typeof three.ours?.['updated_at'] === 'string' ? three.ours['updated_at'] as string : undefined\n const theirsAt = typeof three.theirs?.['updated_at'] === 'string' ? three.theirs['updated_at'] as string : undefined\n let byWinner: 'ours' | 'theirs' | null = null\n if (oursAt !== undefined || theirsAt !== undefined) {\n if (oursAt === undefined) byWinner = 'theirs'\n else if (theirsAt === undefined) byWinner = 'ours'\n else byWinner = theirsAt > oursAt ? 'theirs' : 'ours'\n const winnerAt = byWinner === 'theirs' ? theirsAt : oursAt\n if (winnerAt !== undefined) merged['updated_at'] = winnerAt\n }\n\n const fields = [...new Set([\n ...Object.keys(three.base ?? {}),\n ...Object.keys(three.ours ?? {}),\n ...Object.keys(three.theirs ?? {}),\n ])].toSorted().filter(f => f !== 'updated_at')\n\n for (const field of fields) {\n const candidate = {\n path: ctx.outPath,\n key: entryKey,\n field,\n locale: ctx.locale,\n base: three.base?.[field],\n ours: three.ours?.[field],\n theirs: three.theirs?.[field],\n }\n\n if (field === 'updated_by' && byWinner !== null) {\n const value = byWinner === 'theirs' ? candidate.theirs ?? candidate.ours : candidate.ours ?? candidate.theirs\n if (value !== undefined) merged[field] = value\n continue\n }\n\n const fieldLeaf = mergeLeaf3(candidate.base, candidate.ours, candidate.theirs)\n if (fieldLeaf.ok) {\n if (fieldLeaf.value !== undefined) merged[field] = fieldLeaf.value\n continue\n }\n\n if (field === 'updated_by') {\n // No timestamps to arbitrate — keep ours, say so.\n if (candidate.ours !== undefined) merged[field] = candidate.ours\n advisories.push(`${entryKey ? `\"${entryKey}\": ` : ''}updated_by differed with no updated_at to arbitrate — kept ours.`)\n continue\n }\n\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n const value = resolvedValue(resolution, candidate)\n if (value !== undefined) merged[field] = value\n continue\n }\n\n if (candidate.ours !== undefined) merged[field] = candidate.ours\n conflicts.push(makeConflict({\n ...candidate,\n kind: 'meta',\n model: ctx.model,\n code: field === 'status' ? 'meta_status_conflict' : 'field_value_conflict',\n message: field === 'status'\n ? `Publish status of ${entryKey ? `entry \"${entryKey}\"` : ctx.outPath} was changed differently on both sides.`\n : `Meta field \"${field}\" of ${entryKey ? `entry \"${entryKey}\"` : ctx.outPath} differs on both sides.`,\n }))\n }\n\n return { merged, conflicts, advisories }\n}\n\n/**\n * A meta file: keyed by entry ID for collections, a single record\n * otherwise. Dispatch comes from the winning model definition — shapes are\n * never sniffed, because a dictionary meta record is indistinguishable\n * from a small collection map.\n */\nexport function mergeMetaFile(\n three: ThreeWay<MetaRecord>,\n ctx: FileCtx,\n perEntry: boolean,\n): { merged: MetaRecord | undefined, conflicts: ConflictItem[], stats: MergeStats, advisories: string[] } {\n const fileLeaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (fileLeaf.ok) {\n return { merged: fileLeaf.value as MetaRecord | undefined, conflicts: [], stats: statsFor(fileLeaf.from), advisories: [] }\n }\n\n if (!perEntry) {\n const single = mergeEntryMeta3(three, ctx)\n return { merged: single.merged, conflicts: single.conflicts, stats: EMPTY_STATS, advisories: single.advisories }\n }\n\n const advisories: string[] = []\n const result = mergeKeyedRecord3(\n three as ThreeWay<Record<string, MetaRecord>>,\n (entryId, entry) => {\n const entryLeaf = mergeLeaf3(entry.base, entry.ours, entry.theirs)\n if (entryLeaf.ok) {\n return { value: entryLeaf.value as MetaRecord | undefined, conflicts: [], stats: statsFor(entryLeaf.from) }\n }\n const entryMerge = mergeEntryMeta3(entry, ctx, entryId)\n advisories.push(...entryMerge.advisories)\n return { value: entryMerge.merged, conflicts: entryMerge.conflicts, stats: EMPTY_STATS }\n },\n )\n return { merged: result.merged, conflicts: result.conflicts, stats: result.stats, advisories }\n}\n","import type { ConflictItem } from '@contentrain/types'\nimport type { FileCtx, MergeStats, Side, ThreeWay } from './types.js'\nimport { EMPTY_STATS } from './types.js'\nimport { makeConflict, mergeLeaf3, statsFor } from './three-way.js'\nimport { resolvedValue } from './resolutions.js'\n\ntype RawModel = Record<string, unknown>\n\n/**\n * Schema keys that decide where a model's files live and how they are\n * keyed. When one of THESE is itself in conflict, the content of the model\n * cannot be located or merged safely — the orchestrator blocks the model's\n * content phase and surfaces only the model-level conflict.\n */\nexport const STRUCTURAL_MODEL_KEYS = ['kind', 'i18n', 'locale_strategy', 'content_path'] as const\n\nexport interface ModelMergeResult {\n /** Merged definition; `undefined` = the model is deleted in the output. */\n merged: RawModel | undefined\n conflicts: ConflictItem[]\n stats: MergeStats\n /**\n * Set when one side deleted the model file and the other edited it. No\n * conflict is emitted here — the orchestrator first checks whether the\n * surviving side also touched the model's CONTENT, and collapses\n * everything into one model-level question if so.\n */\n deleteEdit?: { deletedBy: Side }\n /** True when a structural key is in conflict — content merge must not run. */\n structurallyBlocked: boolean\n}\n\n/**\n * Model definition three-way: top-level keys are leaves, except `fields`,\n * which recurses one level so two sides adding different fields both win.\n * A key changed differently on both sides is a conflict with\n * `suggested: 'theirs'` — the policy table's \"the schema belongs to the\n * developer\" carried as a suggestion, never auto-applied.\n */\nexport function mergeModelFile(\n three: ThreeWay<RawModel>,\n ctx: FileCtx,\n): ModelMergeResult {\n const fileLeaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (fileLeaf.ok) {\n return {\n merged: fileLeaf.value as RawModel | undefined,\n conflicts: [],\n stats: statsFor(fileLeaf.from),\n structurallyBlocked: false,\n }\n }\n if (fileLeaf.reason === 'delete_edit') {\n return {\n merged: three.ours,\n conflicts: [],\n stats: EMPTY_STATS,\n deleteEdit: { deletedBy: fileLeaf.deletedBy! },\n structurallyBlocked: false,\n }\n }\n\n const conflicts: ConflictItem[] = []\n const merged: RawModel = {}\n\n const keys = [...new Set([\n ...Object.keys(three.base ?? {}),\n ...Object.keys(three.ours ?? {}),\n ...Object.keys(three.theirs ?? {}),\n ])].toSorted()\n\n for (const key of keys) {\n if (key === 'fields') continue\n const candidate = {\n path: ctx.outPath,\n key,\n base: three.base?.[key],\n ours: three.ours?.[key],\n theirs: three.theirs?.[key],\n }\n const leaf = mergeLeaf3(candidate.base, candidate.ours, candidate.theirs)\n if (leaf.ok) {\n if (leaf.value !== undefined) merged[key] = leaf.value\n continue\n }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n const value = resolvedValue(resolution, candidate)\n if (value !== undefined) merged[key] = value\n continue\n }\n if (candidate.ours !== undefined) merged[key] = candidate.ours\n conflicts.push(makeConflict({\n ...candidate,\n kind: 'model',\n model: ctx.model,\n code: 'model_key_conflict',\n message: `Model \"${ctx.model}\": schema key \"${key}\" was changed differently on both sides.`,\n suggested: 'theirs',\n }))\n }\n\n // `fields`: one level deeper — each field definition is a leaf.\n const fieldNames = [...new Set([\n ...Object.keys((three.base?.['fields'] as RawModel | undefined) ?? {}),\n ...Object.keys((three.ours?.['fields'] as RawModel | undefined) ?? {}),\n ...Object.keys((three.theirs?.['fields'] as RawModel | undefined) ?? {}),\n ])].toSorted()\n if (fieldNames.length > 0) {\n const mergedFields: RawModel = {}\n for (const name of fieldNames) {\n const candidate = {\n path: ctx.outPath,\n key: `fields.${name}`,\n base: (three.base?.['fields'] as RawModel | undefined)?.[name],\n ours: (three.ours?.['fields'] as RawModel | undefined)?.[name],\n theirs: (three.theirs?.['fields'] as RawModel | undefined)?.[name],\n }\n const leaf = mergeLeaf3(candidate.base, candidate.ours, candidate.theirs)\n if (leaf.ok) {\n if (leaf.value !== undefined) mergedFields[name] = leaf.value\n continue\n }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n const value = resolvedValue(resolution, candidate)\n if (value !== undefined) mergedFields[name] = value\n continue\n }\n if (candidate.ours !== undefined) mergedFields[name] = candidate.ours\n conflicts.push(makeConflict({\n ...candidate,\n kind: 'model',\n model: ctx.model,\n code: leaf.reason === 'delete_edit' ? 'delete_edit_conflict' : 'model_key_conflict',\n message: `Model \"${ctx.model}\": field \"${name}\" was changed differently on both sides.`,\n suggested: 'theirs',\n }))\n }\n if (Object.keys(mergedFields).length > 0) merged['fields'] = mergedFields\n }\n\n const structurallyBlocked = conflicts.some(c =>\n (STRUCTURAL_MODEL_KEYS as readonly string[]).includes(c.key ?? ''),\n )\n\n return {\n merged,\n conflicts,\n stats: conflicts.length === 0 ? { takenOurs: 0, takenTheirs: 0, fieldMerged: 1 } : EMPTY_STATS,\n structurallyBlocked,\n }\n}\n","import type { ConflictItem, Vocabulary } from '@contentrain/types'\nimport type { FileCtx, MergeStats, ThreeWay } from './types.js'\nimport { EMPTY_STATS, addStats } from './types.js'\nimport { makeConflict, mergeKeyedRecord3, mergeLeaf3, statsFor } from './three-way.js'\nimport { resolvedValue } from './resolutions.js'\n\ntype Terms = Record<string, Record<string, string>>\n\n/**\n * Vocabulary three-way at TERM + LOCALE granularity — finer than the brief\n * asked for, and the reason the collabers scenario resolves with zero\n * human input: one side adding a term's Turkish while the other fixes its\n * English are different leaves, both win. Only the same term in the same\n * locale with two different translations is a question.\n *\n * Matches the inline save semantics (`planVocabularySave` merges\n * per-locale) — one policy family, two doors.\n */\nexport function mergeVocabulary(\n three: ThreeWay<Vocabulary>,\n ctx: FileCtx,\n): { merged: Vocabulary | undefined, conflicts: ConflictItem[], stats: MergeStats } {\n const fileLeaf = mergeLeaf3(three.base, three.ours, three.theirs)\n if (fileLeaf.ok) {\n return { merged: fileLeaf.value as Vocabulary | undefined, conflicts: [], stats: statsFor(fileLeaf.from) }\n }\n\n const version = Math.max(three.base?.version ?? 1, three.ours?.version ?? 1, three.theirs?.version ?? 1)\n\n const termResult = mergeKeyedRecord3<Record<string, string>>(\n {\n base: three.base?.terms as Terms | undefined,\n ours: three.ours?.terms as Terms | undefined,\n theirs: three.theirs?.terms as Terms | undefined,\n },\n (term, item) => {\n const termLeaf = mergeLeaf3(item.base, item.ours, item.theirs)\n if (termLeaf.ok) {\n return { value: termLeaf.value as Record<string, string> | undefined, conflicts: [], stats: statsFor(termLeaf.from) }\n }\n if (termLeaf.reason === 'delete_edit') {\n const candidate = { path: ctx.outPath, key: term, base: item.base, ours: item.ours, theirs: item.theirs }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n return { value: resolvedValue(resolution, candidate) as Record<string, string> | undefined, conflicts: [], stats: EMPTY_STATS }\n }\n return {\n value: item.ours,\n conflicts: [makeConflict({\n ...candidate,\n kind: 'vocabulary',\n code: 'delete_edit_conflict',\n message: `Vocabulary term \"${term}\" was deleted on one side and edited on the other.`,\n })],\n stats: EMPTY_STATS,\n }\n }\n\n // Both sides touched the term: merge locale-by-locale.\n const inner = mergeKeyedRecord3<string>(item, (locale, translation) => {\n const leaf = mergeLeaf3(translation.base, translation.ours, translation.theirs)\n if (leaf.ok) {\n return { value: leaf.value as string | undefined, conflicts: [], stats: statsFor(leaf.from) }\n }\n const candidate = {\n path: ctx.outPath, key: term, locale,\n base: translation.base, ours: translation.ours, theirs: translation.theirs,\n }\n const resolution = ctx.resolutions.consume(candidate)\n if (resolution) {\n return { value: resolvedValue(resolution, candidate) as string | undefined, conflicts: [], stats: EMPTY_STATS }\n }\n return {\n value: translation.ours,\n conflicts: [makeConflict({\n ...candidate,\n kind: 'vocabulary',\n code: leaf.reason === 'delete_edit' ? 'delete_edit_conflict' : 'vocabulary_value_conflict',\n message: `Term \"${term}\" has two different \"${locale}\" translations.`,\n })],\n stats: EMPTY_STATS,\n }\n })\n return {\n value: Object.keys(inner.merged).length > 0 ? inner.merged : undefined,\n conflicts: inner.conflicts,\n stats: inner.conflicts.length === 0 ? addStats(inner.stats, { takenOurs: 0, takenTheirs: 0, fieldMerged: 1 }) : inner.stats,\n }\n },\n )\n\n return {\n merged: { version, terms: termResult.merged },\n conflicts: termResult.conflicts,\n stats: termResult.stats,\n }\n}\n","import type { ModelDefinition } from '@contentrain/types'\nimport { contentDirPath, contentFilePath, documentFilePath } from '../paths.js'\nimport type { CachedReader } from './read.js'\n\nexport const SIDES = ['base', 'ours', 'theirs'] as const\nexport type TreeSide = (typeof SIDES)[number]\n\nexport interface Readers {\n base: CachedReader\n ours: CachedReader\n theirs: CachedReader\n}\n\n/** Union of model ids across the three trees, sorted. */\nexport async function listModelIds(readers: Readers): Promise<string[]> {\n const listings = await Promise.all(SIDES.map(side => readers[side].listDirectory('.contentrain/models')))\n const ids = new Set<string>()\n for (const listing of listings) {\n for (const entry of listing) {\n if (entry.endsWith('.json')) ids.add(entry.slice(0, -'.json'.length))\n }\n }\n return [...ids].toSorted()\n}\n\n/** One physical content file of a model, as found on ONE side. */\nexport interface SideUnit {\n locale?: string\n slug?: string\n path: string\n}\n\n/**\n * Enumerate a model's content files on one side, using THAT side's model\n * definition — `content_path` and `locale_strategy` may differ between\n * sides, so each side's files are found where that side actually keeps\n * them. Filenames are mapped back to (locale, slug) by inverting the\n * `paths.ts` layout for the declared strategy; nothing is guessed from\n * config.\n */\nexport async function enumerateSideUnits(\n reader: CachedReader,\n def: ModelDefinition,\n defaultLocale: string,\n): Promise<SideUnit[]> {\n const dir = contentDirPath(def)\n const strategy = def.locale_strategy ?? 'file'\n const units: SideUnit[] = []\n\n if (def.kind === 'document') {\n if (!def.i18n || strategy === 'none') {\n const entries = await reader.listDirectory(dir)\n for (const entry of entries) {\n if (entry.endsWith('.md')) {\n units.push({ locale: defaultLocale, slug: entry.slice(0, -3), path: `${dir}/${entry}` })\n }\n }\n return units\n }\n if (strategy === 'suffix') {\n const entries = await reader.listDirectory(dir)\n for (const entry of entries) {\n const match = /^(.+)\\.([a-z]{2}(?:-[A-Za-z]{2})?)\\.md$/.exec(entry)\n if (match) units.push({ slug: match[1]!, locale: match[2]!, path: `${dir}/${entry}` })\n }\n return units\n }\n if (strategy === 'directory') {\n const locales = await reader.listDirectory(dir)\n const listings = await Promise.all(locales.map(async locale =>\n ({ locale, entries: locale.includes('.') ? [] : await reader.listDirectory(`${dir}/${locale}`) })))\n for (const { locale, entries } of listings) {\n for (const entry of entries) {\n if (entry.endsWith('.md')) units.push({ locale, slug: entry.slice(0, -3), path: `${dir}/${locale}/${entry}` })\n }\n }\n return units\n }\n // default 'file': {dir}/{slug}/{locale}.md\n const slugs = await reader.listDirectory(dir)\n const listings = await Promise.all(slugs.map(async slug =>\n ({ slug, entries: slug.includes('.') ? [] : await reader.listDirectory(`${dir}/${slug}`) })))\n for (const { slug, entries } of listings) {\n for (const entry of entries) {\n if (entry.endsWith('.md')) units.push({ slug, locale: entry.slice(0, -3), path: `${dir}/${slug}/${entry}` })\n }\n }\n return units\n }\n\n // JSON kinds: collection / singleton / dictionary.\n if (!def.i18n) {\n if (await reader.fileExists(`${dir}/data.json`)) {\n units.push({ locale: defaultLocale, path: `${dir}/data.json` })\n }\n return units\n }\n if (strategy === 'none') {\n if (await reader.fileExists(`${dir}/${def.id}.json`)) {\n units.push({ locale: defaultLocale, path: `${dir}/${def.id}.json` })\n }\n return units\n }\n if (strategy === 'suffix') {\n const entries = await reader.listDirectory(dir)\n const prefix = `${def.id}.`\n for (const entry of entries) {\n if (entry.startsWith(prefix) && entry.endsWith('.json')) {\n const locale = entry.slice(prefix.length, -'.json'.length)\n if (locale && !locale.includes('.')) units.push({ locale, path: `${dir}/${entry}` })\n }\n }\n return units\n }\n if (strategy === 'directory') {\n const locales = await reader.listDirectory(dir)\n const checks = await Promise.all(locales.map(async locale => ({\n locale,\n exists: locale.includes('.') ? false : await reader.fileExists(`${dir}/${locale}/${def.id}.json`),\n })))\n for (const { locale, exists } of checks) {\n if (exists) units.push({ locale, path: `${dir}/${locale}/${def.id}.json` })\n }\n return units\n }\n // default 'file': {dir}/{locale}.json\n const entries = await reader.listDirectory(dir)\n for (const entry of entries) {\n if (entry.endsWith('.json') && entry !== 'data.json') {\n units.push({ locale: entry.slice(0, -'.json'.length), path: `${dir}/${entry}` })\n }\n }\n return units\n}\n\n/** Resolve the output path of a logical unit under the WINNING definition. */\nexport function unitOutPath(winning: ModelDefinition, defaultLocale: string, locale?: string, slug?: string): string {\n if (winning.kind === 'document') {\n return documentFilePath(winning, locale ?? defaultLocale, slug ?? '')\n }\n return contentFilePath(winning, locale ?? defaultLocale)\n}\n\n/** One meta file of a model on one side. Meta layout never moves between sides. */\nexport interface MetaUnit {\n locale: string\n slug?: string\n path: string\n}\n\n/**\n * Enumerate a model's meta files on one side. The meta layout is fixed\n * (`.contentrain/meta/{id}/[{slug}/]{locale}.json`) regardless of\n * `content_path` or locale strategy, so this stays definition-light and\n * works even while the definitions disagree.\n */\nexport async function enumerateMetaUnits(reader: CachedReader, modelId: string): Promise<MetaUnit[]> {\n const dir = `.contentrain/meta/${modelId}`\n const entries = await reader.listDirectory(dir)\n const units: MetaUnit[] = []\n const slugDirs: string[] = []\n for (const entry of entries) {\n if (entry.endsWith('.json')) {\n units.push({ locale: entry.slice(0, -'.json'.length), path: `${dir}/${entry}` })\n } else if (!entry.includes('.')) {\n slugDirs.push(entry)\n }\n }\n const nested = await Promise.all(slugDirs.map(async slug =>\n ({ slug, entries: await reader.listDirectory(`${dir}/${slug}`) })))\n for (const { slug, entries: files } of nested) {\n for (const file of files) {\n if (file.endsWith('.json')) {\n units.push({ locale: file.slice(0, -'.json'.length), slug, path: `${dir}/${slug}/${file}` })\n }\n }\n }\n return units\n}\n\nconst SCAN_MAX_DEPTH = 6\nconst SCAN_SKIP = new Set(['.contentrain/client', '.contentrain/context.json'])\n\n/**\n * Recursively list candidate text files (`.json` / `.md`) under `root` on\n * one side. The generated client and context.json are never candidates;\n * depth is bounded as a runaway guard. Anything that is not a candidate\n * extension is treated as a possible directory — listing a file yields an\n * empty array on every reader, so the probe is safe.\n */\nexport async function listTextFiles(reader: CachedReader, root: string, depth = 0): Promise<string[]> {\n if (depth > SCAN_MAX_DEPTH) return []\n const entries = await reader.listDirectory(root)\n const results = await Promise.all(entries.map(async (entry) => {\n const path = root === '' ? entry : `${root}/${entry}`\n if (SCAN_SKIP.has(path)) return []\n if (entry.endsWith('.json') || entry.endsWith('.md')) return [path]\n return listTextFiles(reader, path, depth + 1)\n }))\n return results.flat()\n}\n","import {\n MODEL_FIELD_ORDER,\n canonicalStringify,\n conflictId,\n parseMarkdownFrontmatter,\n stableHash,\n} from '@contentrain/types'\nimport type { ConflictItem, ContentrainConfig, ModelDefinition, Vocabulary } from '@contentrain/types'\nimport type { FileChange } from '../../contracts/index.js'\nimport { OverlayReader } from '../../overlay-reader.js'\nimport { buildContextChange } from '../../context.js'\nimport { validateProject } from '../../validator/index.js'\nimport { CachedReader, readJsonOrNull } from './read.js'\nimport { ResolutionIndex } from './resolutions.js'\nimport { eqCanonical, makeConflict, mergeLeaf3 } from './three-way.js'\nimport { mergeCollectionFile, mergeDictionaryFile, mergeKeyedJson, mergeSingletonFile } from './merge-content.js'\nimport { mergeDocumentFile } from './merge-document.js'\nimport { mergeMetaFile } from './merge-meta.js'\nimport { mergeModelFile } from './merge-model.js'\nimport { mergeVocabulary } from './merge-vocabulary.js'\nimport {\n SIDES,\n enumerateMetaUnits,\n enumerateSideUnits,\n listModelIds,\n listTextFiles,\n unitOutPath,\n} from './walk.js'\nimport type { Readers, TreeSide } from './walk.js'\nimport type { ReconcileInput, ReconcilePlan, Side } from './types.js'\nimport { EMPTY_STATS, addStats } from './types.js'\nimport type { MergeStats } from './types.js'\n\nconst CONTEXT_PATH = '.contentrain/context.json'\nconst VOCABULARY_PATH = '.contentrain/vocabulary.json'\nconst SOURCES_PATH = '.contentrain/normalize-sources.json'\nconst CONFIG_PATH = '.contentrain/config.json'\nconst MAX_VALIDATION_ADVISORIES = 15\n\ntype RawModel = Record<string, unknown>\n\n/** A merged file awaiting assembly. `value: null` = delete the file. */\ninterface Output {\n path: string\n format: 'json' | 'model' | 'md' | 'raw'\n value: unknown\n}\n\n/**\n * Content-aware three-way merge of `.contentrain/**` (plus `content_path`\n * roots) between a merge-base, the content branch (ours) and the base\n * branch (theirs). Pure planning: reads through the three bound readers,\n * writes nothing, and returns `FileChange[]` to apply ON TOP OF OURS,\n * `ConflictItem[]` for whatever the policy table cannot decide, and a\n * regenerated context.json whenever the plan is not a no-op.\n *\n * Determinism: the same three trees with the same resolutions produce the\n * same changes and the same conflict ids — context.json's embedded\n * timestamp is the sole documented exception.\n */\nexport async function planReconcile(input: ReconcileInput): Promise<ReconcilePlan> {\n const readers: Readers = {\n base: new CachedReader(input.base),\n ours: new CachedReader(input.ours),\n theirs: new CachedReader(input.theirs),\n }\n const resolutions = new ResolutionIndex(input.resolutions ?? [])\n const conflicts: ConflictItem[] = []\n const advisories: string[] = []\n const outputs: Output[] = []\n const claimed = new Set<string>()\n let stats: MergeStats = EMPTY_STATS\n\n const config = await readJsonOrNull<ContentrainConfig>(readers.ours, '.contentrain/config.json')\n const defaultLocale = config?.locales?.default ?? 'en'\n\n // ─── Phase 1: models ───\n\n const modelIds = await listModelIds(readers)\n const models = await Promise.all(modelIds.map(async (id) => {\n const path = `.contentrain/models/${id}.json`\n const [base, ours, theirs] = await Promise.all(\n SIDES.map(side => readJsonOrNull<RawModel>(readers[side], path)),\n )\n return {\n id,\n path,\n three: { base: base ?? undefined, ours: ours ?? undefined, theirs: theirs ?? undefined },\n }\n }))\n\n const modelStates = models.map((m) => {\n claimed.add(m.path)\n const result = mergeModelFile(m.three, { outPath: m.path, kind: 'model', model: m.id, resolutions })\n return { id: m.id, path: m.path, three: m.three, result }\n })\n\n // ─── Phase 2: per-model content + meta ───\n\n await Promise.all(modelStates.map(async (state) => {\n const { id, path, three, result } = state\n\n // Deletion family: a clean deletion may still collapse into one\n // model-level question when the surviving side edited the content.\n const oursAbsent = three.ours === undefined\n const theirsAbsent = three.theirs === undefined\n const oneSideDeleted = three.base !== undefined && oursAbsent !== theirsAbsent\n if (result.deleteEdit || (result.merged === undefined && oneSideDeleted)) {\n const deletedBy: Side = result.deleteEdit?.deletedBy ?? (oursAbsent ? 'ours' : 'theirs')\n const survivor: Side = deletedBy === 'ours' ? 'theirs' : 'ours'\n await collapseModelDeletion(state.id, path, three, deletedBy, survivor, !!result.deleteEdit)\n return\n }\n\n conflicts.push(...result.conflicts)\n stats = addStats(stats, result.stats)\n\n if (result.merged === undefined) {\n // Deleted on both sides (or never present) — nothing left to place.\n await claimModelFiles(id, three)\n return\n }\n\n if (result.structurallyBlocked) {\n // The keys that locate this model's files are themselves in conflict.\n // Content cannot be merged safely; the model-level conflicts (already\n // pushed) are the only report. Claim the files so the unclaimed scan\n // does not double-report them.\n advisories.push(`Model \"${id}\": structural schema keys are in conflict — its content was left untouched until they are resolved.`)\n await claimModelFiles(id, three)\n return\n }\n\n const winning = result.merged as unknown as ModelDefinition\n if (typeof winning.kind !== 'string' || typeof winning.id !== 'string') {\n advisories.push(`Model \"${id}\": merged definition is structurally incomplete — content left untouched.`)\n await claimModelFiles(id, three)\n return\n }\n\n outputs.push({ path, format: 'model', value: result.merged })\n\n // Content units: each side enumerated with ITS OWN definition.\n const sideDefs: Partial<Record<TreeSide, ModelDefinition>> = {}\n for (const side of SIDES) {\n const def = three[side] as unknown as ModelDefinition | undefined\n if (def && typeof def.kind === 'string') sideDefs[side] = def\n }\n const perSide = await Promise.all(SIDES.map(async (side) => {\n const def = sideDefs[side]\n return { side, units: def ? await enumerateSideUnits(readers[side], def, defaultLocale) : [] }\n }))\n\n const unitMap = new Map<string, { locale?: string, slug?: string, paths: Partial<Record<TreeSide, string>> }>()\n for (const { side, units } of perSide) {\n for (const unit of units) {\n claimed.add(unit.path)\n const key = unitKey(unit)\n const entry = unitMap.get(key) ?? { locale: unit.locale, slug: unit.slug, paths: {} }\n entry.paths[side] = unit.path\n unitMap.set(key, entry)\n }\n }\n\n await Promise.all([...unitMap.values()].map(async (unit) => {\n const [baseRaw, oursRaw, theirsRaw] = await Promise.all(SIDES.map(side =>\n unit.paths[side] ? readers[side].readOrNull(unit.paths[side]!) : Promise.resolve(null)))\n const outPath = unitOutPath(winning, defaultLocale, unit.locale, unit.slug)\n claimed.add(outPath)\n const ctx = {\n outPath,\n kind: winning.kind as ConflictItem['kind'],\n model: id,\n locale: unit.locale,\n resolutions,\n }\n\n if (winning.kind === 'document') {\n const merge = mergeDocumentFile(\n { base: baseRaw ?? undefined, ours: oursRaw ?? undefined, theirs: theirsRaw ?? undefined },\n { ...ctx, slug: unit.slug ?? '' },\n )\n conflicts.push(...merge.conflicts)\n advisories.push(...merge.advisories)\n stats = addStats(stats, merge.stats)\n outputs.push({ path: outPath, format: 'md', value: merge.merged ?? null })\n } else {\n const parsed = {\n base: parseJson(baseRaw ?? null),\n ours: parseJson(oursRaw ?? null),\n theirs: parseJson(theirsRaw ?? null),\n }\n const merge = winning.kind === 'dictionary'\n ? mergeDictionaryFile(parsed as never, ctx)\n : winning.kind === 'singleton'\n ? mergeSingletonFile(parsed as never, ctx)\n : mergeCollectionFile(parsed as never, ctx)\n conflicts.push(...merge.conflicts)\n stats = addStats(stats, merge.stats)\n outputs.push({ path: outPath, format: 'json', value: merge.merged ?? null })\n }\n\n // The winning definition moved the file (content_path / strategy\n // change): the merged file lands at outPath and ours' old copy goes.\n if (unit.paths.ours && unit.paths.ours !== outPath) {\n outputs.push({ path: unit.paths.ours, format: 'raw', value: null })\n }\n }))\n\n // Meta: fixed layout, dispatch on the winning kind.\n const metaPerSide = await Promise.all(SIDES.map(side => enumerateMetaUnits(readers[side], id)))\n const metaMap = new Map<string, { locale: string, slug?: string, path: string }>()\n for (const units of metaPerSide) {\n for (const unit of units) {\n claimed.add(unit.path)\n metaMap.set(unit.path, unit)\n }\n }\n await Promise.all([...metaMap.values()].map(async (unit) => {\n const [base, ours, theirs] = await Promise.all(SIDES.map(side => readJsonOrNull<RawModel>(readers[side], unit.path)))\n const merge = mergeMetaFile(\n { base: base ?? undefined, ours: ours ?? undefined, theirs: theirs ?? undefined },\n { outPath: unit.path, kind: 'meta', model: id, locale: unit.locale, resolutions },\n winning.kind === 'collection',\n )\n conflicts.push(...merge.conflicts)\n advisories.push(...merge.advisories)\n stats = addStats(stats, merge.stats)\n outputs.push({ path: unit.path, format: 'json', value: merge.merged ?? null })\n }))\n }))\n\n // ─── Phase 3: fixed paths ───\n\n claimed.add(VOCABULARY_PATH)\n claimed.add(SOURCES_PATH)\n claimed.add(CONFIG_PATH)\n {\n const [base, ours, theirs] = await Promise.all(SIDES.map(side => readJsonOrNull<Vocabulary>(readers[side], VOCABULARY_PATH)))\n if (base !== null || ours !== null || theirs !== null) {\n const merge = mergeVocabulary(\n { base: base ?? undefined, ours: ours ?? undefined, theirs: theirs ?? undefined },\n { outPath: VOCABULARY_PATH, kind: 'vocabulary', resolutions },\n )\n conflicts.push(...merge.conflicts)\n stats = addStats(stats, merge.stats)\n outputs.push({ path: VOCABULARY_PATH, format: 'json', value: merge.merged ?? null })\n }\n }\n {\n const [base, ours, theirs] = await Promise.all(SIDES.map(side => readJsonOrNull<RawModel>(readers[side], SOURCES_PATH)))\n if (base !== null || ours !== null || theirs !== null) {\n const merge = mergeKeyedJson(\n { base: base ?? undefined, ours: ours ?? undefined, theirs: theirs ?? undefined },\n { outPath: SOURCES_PATH, kind: 'file', resolutions },\n )\n conflicts.push(...merge.conflicts)\n stats = addStats(stats, merge.stats)\n outputs.push({ path: SOURCES_PATH, format: 'json', value: merge.merged ?? null })\n }\n }\n {\n // Project settings are a keyed record, and the two sides change different\n // keys: a branch renames the content root while main flips the workflow.\n // Treating the whole file as opaque made every such pair a `file_conflict`\n // that stopped reconcile until someone picked a side — and picking a side\n // discards the other change. Same rule as normalize-sources: merge by\n // top-level key, and only a key BOTH sides moved differently is a question.\n const [base, ours, theirs] = await Promise.all(SIDES.map(side => readJsonOrNull<RawModel>(readers[side], CONFIG_PATH)))\n if (base !== null || ours !== null || theirs !== null) {\n const merge = mergeKeyedJson(\n { base: base ?? undefined, ours: ours ?? undefined, theirs: theirs ?? undefined },\n { outPath: CONFIG_PATH, kind: 'file', resolutions },\n )\n conflicts.push(...merge.conflicts)\n stats = addStats(stats, merge.stats)\n outputs.push({ path: CONFIG_PATH, format: 'json', value: merge.merged ?? null })\n }\n }\n\n // ─── Phase 4: unclaimed scan ───\n // Everything under the recognized roots that no phase claimed. A change\n // on one side is mechanical (`theirs`-only changes become plan output so\n // tree-building executors reproduce what git's merge machinery would);\n // a change on both sides is a whole-file question — the planner NEVER\n // invents content for a file it does not understand.\n\n const roots = new Set<string>(['.contentrain'])\n for (const state of modelStates) {\n for (const side of SIDES) {\n const contentPath = (state.three[side] as { content_path?: unknown } | undefined)?.content_path\n if (typeof contentPath === 'string' && contentPath.length > 0) roots.add(contentPath)\n }\n }\n const scanned = await Promise.all([...roots].map(async root =>\n Promise.all(SIDES.map(side => listTextFiles(readers[side], root)))))\n const candidates = new Set<string>()\n for (const perRoot of scanned) {\n for (const files of perRoot) {\n for (const file of files) {\n if (!claimed.has(file)) candidates.add(file)\n }\n }\n }\n await Promise.all([...candidates].toSorted().map(async (path) => {\n const [baseRaw, oursRaw, theirsRaw] = await Promise.all(SIDES.map(side => readers[side].readOrNull(path)))\n const leaf = mergeLeaf3(baseRaw ?? undefined, oursRaw ?? undefined, theirsRaw ?? undefined)\n if (leaf.ok) {\n if (leaf.from === 'theirs') outputs.push({ path, format: 'raw', value: (leaf.value as string | undefined) ?? null })\n return\n }\n // Identity via digests: content-sensitive (N1) without shipping whole\n // files in the conflict item.\n const digestCandidate = {\n path,\n base: baseRaw == null ? undefined : stableHash(baseRaw),\n ours: oursRaw == null ? undefined : stableHash(oursRaw),\n theirs: theirsRaw == null ? undefined : stableHash(theirsRaw),\n }\n const resolution = resolutions.consume(digestCandidate)\n if (resolution && 'choose' in resolution) {\n const chosen = resolution.choose === 'ours' ? oursRaw : theirsRaw\n outputs.push({ path, format: 'raw', value: chosen })\n return\n }\n if (resolution) {\n advisories.push(`Resolution for \"${path}\" supplied a value — file-level conflicts accept only { choose } and the resolution was ignored.`)\n }\n conflicts.push({\n id: conflictId(digestCandidate),\n path,\n kind: 'file',\n code: 'file_conflict',\n message: `${path} was changed on both sides and is not content this planner understands — choose a side.`,\n })\n }))\n\n // ─── Assembly ───\n\n const changes: FileChange[] = []\n await Promise.all(outputs.map(async (out) => {\n const oursRaw = await readers.ours.readOrNull(out.path)\n if (out.value === null) {\n if (oursRaw !== null) changes.push({ path: out.path, content: null })\n return\n }\n if (out.format === 'md' || out.format === 'raw') {\n const next = out.value as string\n if (oursRaw === null) {\n changes.push({ path: out.path, content: next })\n return\n }\n if (out.format === 'raw') {\n if (oursRaw !== next) changes.push({ path: out.path, content: next })\n return\n }\n const a = parseMarkdownFrontmatter(oursRaw)\n const b = parseMarkdownFrontmatter(next)\n if (!eqCanonical(a.frontmatter, b.frontmatter) || a.body !== b.body) {\n changes.push({ path: out.path, content: next })\n }\n return\n }\n // json / model: compare parsed values so a byte-level formatting\n // difference never becomes a change — reconcile is not a formatter.\n const oursParsed = parseJson(oursRaw)\n if (eqCanonical(out.value, oursParsed)) return\n const fieldOrder = out.format === 'model' ? MODEL_FIELD_ORDER : undefined\n changes.push({ path: out.path, content: canonicalStringify(out.value, fieldOrder) })\n }))\n\n const filesMerged = changes.length\n const regenerated: string[] = []\n if (changes.length > 0) {\n try {\n const contextChange = await buildContextChange(\n new OverlayReader(readers.ours, changes),\n { tool: 'contentrain_reconcile', model: '*' },\n input.source,\n )\n changes.push(contextChange)\n regenerated.push(CONTEXT_PATH)\n } catch {\n advisories.push('context.json could not be regenerated — run contentrain_validate after applying.')\n }\n }\n\n if (changes.length > 0) {\n try {\n const validation = await validateProject(new OverlayReader(readers.ours, changes), {})\n const issues = validation.issues.filter(i => i.severity !== 'notice')\n const shown = issues.slice(0, MAX_VALIDATION_ADVISORIES)\n for (const issue of shown) {\n advisories.push(`validation (${issue.severity}): ${issue.model ?? ''}${issue.field ? `.${issue.field}` : ''} — ${issue.message}`)\n }\n if (issues.length > shown.length) {\n advisories.push(`validation: ${issues.length - shown.length} more issue(s) — run contentrain_validate for the full report.`)\n }\n } catch {\n advisories.push('Post-merge validation could not run — validate after applying.')\n }\n }\n\n for (const stale of resolutions.unconsumed()) {\n advisories.push(`Resolution ${stale.id} no longer matches any conflict — values changed since the dry-run; the conflict is re-reported with a new id.`)\n }\n\n if (changes.length === 0 && conflicts.length === 0) {\n advisories.push('Trees are already reconciled — nothing to merge.')\n }\n\n changes.sort((a, b) => a.path.localeCompare(b.path, 'en'))\n conflicts.sort((a, b) =>\n a.path.localeCompare(b.path, 'en')\n || (a.key ?? '').localeCompare(b.key ?? '', 'en')\n || (a.field ?? '').localeCompare(b.field ?? '', 'en')\n || (a.locale ?? '').localeCompare(b.locale ?? '', 'en'))\n\n return {\n changes,\n conflicts,\n advisories,\n result: {\n files_merged: filesMerged,\n entries_taken_ours: stats.takenOurs,\n entries_taken_theirs: stats.takenTheirs,\n entries_field_merged: stats.fieldMerged,\n regenerated,\n },\n }\n\n // ─── Model deletion helpers (closures over readers/outputs/conflicts) ───\n\n /** Claim every side's files of a model so the scan never double-reports. */\n async function claimModelFiles(id: string, three: { base?: RawModel, ours?: RawModel, theirs?: RawModel }): Promise<void> {\n await Promise.all(SIDES.map(async (side) => {\n const def = three[side] as unknown as ModelDefinition | undefined\n if (def && typeof def.kind === 'string') {\n const units = await enumerateSideUnits(readers[side], def, defaultLocale)\n for (const unit of units) claimed.add(unit.path)\n }\n const meta = await enumerateMetaUnits(readers[side], id)\n for (const unit of meta) claimed.add(unit.path)\n }))\n }\n\n /**\n * One side deleted the model. If the surviving side left everything\n * untouched, the deletion cascades mechanically over content and meta.\n * The moment the survivor edited anything — the definition or one entry —\n * the whole model collapses into a SINGLE delete-vs-edit question\n * instead of one conflict per file.\n */\n async function collapseModelDeletion(\n id: string,\n modelPath: string,\n three: { base?: RawModel, ours?: RawModel, theirs?: RawModel },\n deletedBy: Side,\n survivor: Side,\n modelFileEdited: boolean,\n ): Promise<void> {\n await claimModelFiles(id, three)\n\n const survivorDef = three[survivor] as unknown as ModelDefinition | undefined\n const survivorFiles = await modelFileDigestList(survivor, id, survivorDef)\n const baseFiles = await modelFileDigestList('base', id, three.base as unknown as ModelDefinition | undefined)\n const contentEdited = JSON.stringify(survivorFiles) !== JSON.stringify(baseFiles)\n\n if (!modelFileEdited && !contentEdited) {\n // Mechanical cascade: deletion wins; drop everything ours still has.\n await deleteModelEverywhere(id, three)\n return\n }\n\n const digestFor = async (side: TreeSide): Promise<unknown> => {\n const def = three[side] as unknown as ModelDefinition | undefined\n if (!def && side !== 'base') return undefined\n const files = await modelFileDigestList(side, id, def)\n return { model: three[side] ? stableHash(JSON.stringify(three[side])) : null, files: stableHash(JSON.stringify(files)) }\n }\n const [baseDigest, oursDigest, theirsDigest] = await Promise.all([digestFor('base'), digestFor('ours'), digestFor('theirs')])\n const candidate = { path: modelPath, base: baseDigest, ours: oursDigest, theirs: theirsDigest }\n const resolution = resolutions.consume(candidate)\n if (resolution && 'choose' in resolution) {\n if (resolution.choose === deletedBy) {\n await deleteModelEverywhere(id, three)\n } else {\n await keepModelWholesale(id, modelPath, three, survivor)\n }\n return\n }\n if (resolution) {\n advisories.push(`Resolution for model \"${id}\" supplied a value — model delete-vs-edit accepts only { choose } and the resolution was ignored.`)\n }\n conflicts.push(makeConflict({\n ...candidate,\n kind: 'model',\n model: id,\n code: 'delete_edit_conflict',\n message: `Model \"${id}\" was deleted on one side (${deletedBy}) while the other side edited its ${modelFileEdited ? 'definition' : 'content'} — keep the model or drop it.`,\n }))\n }\n\n /** Digest list of one side's model-owned files (content + meta), sorted. */\n async function modelFileDigestList(side: TreeSide, id: string, def: ModelDefinition | undefined): Promise<Array<[string, string]>> {\n const units = def && typeof def.kind === 'string'\n ? await enumerateSideUnits(readers[side], def, defaultLocale)\n : []\n const meta = await enumerateMetaUnits(readers[side], id)\n const paths = [...units.map(u => u.path), ...meta.map(u => u.path)].toSorted()\n const raws = await Promise.all(paths.map(path => readers[side].readOrNull(path)))\n return paths.map((path, i) => [path, stableHash(raws[i] ?? '')] as [string, string])\n }\n\n /** Emit deletions for every model-owned file ours still carries. */\n async function deleteModelEverywhere(id: string, three: { base?: RawModel, ours?: RawModel, theirs?: RawModel }): Promise<void> {\n outputs.push({ path: `.contentrain/models/${id}.json`, format: 'raw', value: null })\n const oursDef = three.ours as unknown as ModelDefinition | undefined\n const units = oursDef && typeof oursDef.kind === 'string'\n ? await enumerateSideUnits(readers.ours, oursDef, defaultLocale)\n : []\n const meta = await enumerateMetaUnits(readers.ours, id)\n for (const unit of [...units.map(u => u.path), ...meta.map(u => u.path)]) {\n outputs.push({ path: unit, format: 'raw', value: null })\n }\n }\n\n /** Take the surviving side wholesale: definition, content, meta. */\n async function keepModelWholesale(\n id: string,\n modelPath: string,\n three: { base?: RawModel, ours?: RawModel, theirs?: RawModel },\n survivor: Side,\n ): Promise<void> {\n outputs.push({ path: modelPath, format: 'model', value: three[survivor] })\n const survivorDef = three[survivor] as unknown as ModelDefinition | undefined\n const units = survivorDef && typeof survivorDef.kind === 'string'\n ? await enumerateSideUnits(readers[survivor], survivorDef, defaultLocale)\n : []\n const meta = await enumerateMetaUnits(readers[survivor], id)\n const survivorPaths = [...units.map(u => u.path), ...meta.map(u => u.path)]\n const raws = await Promise.all(survivorPaths.map(path => readers[survivor].readOrNull(path)))\n survivorPaths.forEach((path, i) => {\n outputs.push({ path, format: 'raw', value: raws[i] })\n })\n // Ours-side files the survivor does not have go away.\n const oursDef = three.ours as unknown as ModelDefinition | undefined\n const oursUnits = oursDef && typeof oursDef.kind === 'string'\n ? await enumerateSideUnits(readers.ours, oursDef, defaultLocale)\n : []\n const oursMeta = await enumerateMetaUnits(readers.ours, id)\n const survivorSet = new Set(survivorPaths)\n for (const path of [...oursUnits.map(u => u.path), ...oursMeta.map(u => u.path)]) {\n if (!survivorSet.has(path)) outputs.push({ path, format: 'raw', value: null })\n }\n }\n}\n\nfunction unitKey(u: { locale?: string, slug?: string }): string {\n return `${u.locale ?? ''}::${u.slug ?? ''}`\n}\n\nfunction parseJson(raw: string | null): Record<string, unknown> | undefined {\n if (raw === null) return undefined\n try {\n return JSON.parse(raw) as Record<string, unknown>\n } catch {\n return undefined\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import "../../contracts-DfL0BfrD.mjs";
|
|
2
|
-
import "../../reconcile-
|
|
3
|
-
import "../../context-
|
|
4
|
-
import "../../model-manager-
|
|
5
|
-
import "../../ops-
|
|
2
|
+
import "../../reconcile-J-iorvSa.mjs";
|
|
3
|
+
import "../../context-D9HXnnGj.mjs";
|
|
4
|
+
import "../../model-manager-D0VztE8M.mjs";
|
|
5
|
+
import "../../ops-CVI308jA.mjs";
|
|
6
6
|
import "../../fs-BFTuXbbw.mjs";
|
|
7
7
|
import "../../meta-manager-CSpKtjnP.mjs";
|
|
8
|
-
import "../../config-
|
|
9
|
-
import "../../validator-
|
|
8
|
+
import "../../config-BnNYu128.mjs";
|
|
9
|
+
import "../../validator-S19GHR4V.mjs";
|
|
10
10
|
import "../../identity-CxAB9Qb8.mjs";
|
|
11
11
|
import "../../base-branch-Cw9DWvCC.mjs";
|
|
12
|
-
import "../../branch-lifecycle-
|
|
12
|
+
import "../../branch-lifecycle-D4DxPgQg.mjs";
|
|
13
13
|
import "../../id-jrHWdtqn.mjs";
|
|
14
|
-
import "../../transaction-
|
|
15
|
-
import "../../local-
|
|
14
|
+
import "../../transaction-DnrUATJF.mjs";
|
|
15
|
+
import "../../local-CBtRYFH4.mjs";
|
|
16
16
|
import "../../detect-B1QHYA0u.mjs";
|
|
17
17
|
import "../../annotations-BYkjoj2d.mjs";
|
|
18
|
-
import { n as createServer } from "../../server-
|
|
18
|
+
import { n as createServer } from "../../server-DvWJelCh.mjs";
|
|
19
19
|
import "../../errors-De8Noahk.mjs";
|
|
20
|
-
import "../../reconcile-
|
|
20
|
+
import "../../reconcile-DSflMfhj.mjs";
|
|
21
21
|
import "../../scan-config-S5fkzNhL.mjs";
|
|
22
22
|
import "../../graph-builder-BPhi-1KH.mjs";
|
|
23
23
|
import "../../scanner-0sc_pARB.mjs";
|
|
24
24
|
import "../../tsx-parser-DgFdpsea.mjs";
|
|
25
|
-
import "../../apply-manager-
|
|
26
|
-
import "../../doctor-
|
|
25
|
+
import "../../apply-manager-B-O3eAbz.mjs";
|
|
26
|
+
import "../../doctor-Br1NqEl2.mjs";
|
|
27
27
|
import { randomUUID } from "node:crypto";
|
|
28
28
|
import http from "node:http";
|
|
29
29
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|