@contentrain/mcp 3.4.3 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/dist/core/ops/index.mjs +2 -2
- package/dist/core/validator/index.d.mts.map +1 -1
- package/dist/core/validator/index.mjs +1 -1
- package/dist/git/reconcile.mjs +3 -3
- package/dist/index.mjs +5 -5
- package/dist/{local-DbPlR0Br.mjs → local-DgjDad1x.mjs} +2 -2
- package/dist/{local-DbPlR0Br.mjs.map → local-DgjDad1x.mjs.map} +1 -1
- package/dist/providers/github/index.mjs +43 -12
- package/dist/providers/github/index.mjs.map +1 -1
- package/dist/providers/local/index.mjs +3 -3
- package/dist/{reconcile-TyPQKjme.mjs → reconcile-B55dZqhg.mjs} +2 -2
- package/dist/{reconcile-TyPQKjme.mjs.map → reconcile-B55dZqhg.mjs.map} +1 -1
- package/dist/{reconcile-EANDIp_T.mjs → reconcile-CwS4LUbo.mjs} +2 -2
- package/dist/{reconcile-EANDIp_T.mjs.map → reconcile-CwS4LUbo.mjs.map} +1 -1
- package/dist/server/http/index.mjs +5 -5
- package/dist/{server-BNPwmVFh.mjs → server-CCn_fR9q.mjs} +10 -6
- package/dist/server-CCn_fR9q.mjs.map +1 -0
- package/dist/server.mjs +5 -5
- package/dist/{validator-CLugFhHw.mjs → validator-BD0Z0O0_.mjs} +39 -17
- package/dist/validator-BD0Z0O0_.mjs.map +1 -0
- package/package.json +2 -2
- package/dist/server-BNPwmVFh.mjs.map +0 -1
- package/dist/validator-CLugFhHw.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-TyPQKjme.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 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":";;;;;;;;;;;;;;;;;;;;;;AAkBA,eAAsB,uBAAuB,UAAkB,SAA+C;AAC5G,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,WAAW,KAAK,UAAU,OAAO,KAAK;AAC5C,MAAI,OAAO,YAAY,MAAM;AAC3B,SAAM,GAAG,UAAU,EAAE,OAAO,MAAM,CAAC,CAAC,YAAY,GAA2B;AAC3E;;AAEF,QAAM,MAAM,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,QAAM,UAAU,UAAU,OAAO,SAAS,QAAQ;;;;;;;;;;;;;;;;;ACZtD,IAAa,eAAb,MAAgD;CAC9C,wBAAyB,IAAI,KAAqC;CAClE,uBAAwB,IAAI,KAAgC;CAC5D,yBAA0B,IAAI,KAA+B;CAE7D,YAAY,OAAoC;AAAnB,OAAA,QAAA;;;CAG7B,MAAM,SAAS,MAAc,MAAgC;EAC3D,MAAM,UAAU,MAAM,KAAK,WAAW,KAAK;AAC3C,MAAI,YAAY,KAAM,OAAM,IAAI,MAAM,+BAA+B,KAAK,GAAG;AAC7E,SAAO;;;CAIT,WAAW,MAAsC;EAC/C,IAAI,SAAS,KAAK,MAAM,IAAI,KAAK;AACjC,MAAI,CAAC,QAAQ;AACX,YAAS,KAAK,MAAM,SAAS,KAAK,CAAC,MAAK,YAAW,eAAe,KAAK;AACvE,QAAK,MAAM,IAAI,MAAM,OAAO;;AAE9B,SAAO;;CAGT,cAAc,MAAc,MAAkC;EAC5D,IAAI,SAAS,KAAK,KAAK,IAAI,KAAK;AAChC,MAAI,CAAC,QAAQ;AACX,YAAS,KAAK,MAAM,cAAc,KAAK,CAAC,MAAK,YAAW,eAAe,EAAE,CAAC;AAC1E,QAAK,KAAK,IAAI,MAAM,OAAO;;AAE7B,SAAO;;CAGT,WAAW,MAAc,MAAiC;EACxD,IAAI,SAAS,KAAK,OAAO,IAAI,KAAK;AAClC,MAAI,CAAC,QAAQ;AACX,YAAS,KAAK,MAAM,WAAW,KAAK,CAAC,MAAK,MAAK,SAAS,MAAM;AAC9D,QAAK,OAAO,IAAI,MAAM,OAAO;;AAE/B,SAAO;;;;AAKX,eAAsB,eAAkB,QAAsB,MAAiC;CAC7F,MAAM,MAAM,MAAM,OAAO,WAAW,KAAK;AACzC,KAAI,QAAQ,KAAM,QAAO;AACzB,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;SAChB;AACN,SAAO;;;;;;;;;;;;;ACtCX,IAAa,kBAAb,MAA6B;CAC3B,uBAAwB,IAAI,KAAiC;CAC7D,2BAA4B,IAAI,KAAa;CAE7C,YAAY,cAAoC,EAAE,EAAE;AAClD,OAAK,MAAM,cAAc,YAAa,MAAK,KAAK,IAAI,WAAW,IAAI,WAAW;;CAGhF,QAAQ,WAAyD;EAC/D,MAAM,KAAK,WAAW,UAAU;EAChC,MAAM,aAAa,KAAK,KAAK,IAAI,GAAG;AACpC,MAAI,CAAC,WAAY,QAAO;AACxB,OAAK,SAAS,IAAI,GAAG;AACrB,SAAO;;CAGT,aAAmC;AACjC,SAAO,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,QAAO,MAAK,CAAC,KAAK,SAAS,IAAI,EAAE,GAAG,CAAC;;;;;;;;AASxE,SAAgB,cACd,YACA,WACS;AACT,KAAI,YAAY,WACd,QAAO,WAAW,WAAW,SAAS,UAAU,OAAO,UAAU;AAEnE,QAAO,WAAW;;;;ACGpB,MAAa,cAA0B;CAAE,WAAW;CAAG,aAAa;CAAG,aAAa;CAAG;AAEvF,SAAgB,SAAS,GAAe,GAA2B;AACjE,QAAO;EACL,WAAW,EAAE,YAAY,EAAE;EAC3B,aAAa,EAAE,cAAc,EAAE;EAC/B,aAAa,EAAE,cAAc,EAAE;EAChC;;;;;;;;;;;ACzDH,SAAgB,YAAY,GAAY,GAAqB;CAC3D,MAAM,KAAK,SAAS,EAAE;CACtB,MAAM,KAAK,SAAS,EAAE;AACtB,KAAI,OAAO,KAAA,KAAa,OAAO,KAAA,EAAW,QAAO,OAAO;AACxD,QAAO,KAAK,UAAU,GAAG,KAAK,KAAK,UAAU,GAAG;;;;;;;;;AAelD,SAAgB,WAAW,MAAe,MAAe,QAAwB;CAC/E,MAAM,WAAW,YAAY,MAAM,KAAK;CACxC,MAAM,aAAa,YAAY,QAAQ,KAAK;AAC5C,KAAI,YAAY,WAAY,QAAO;EAAE,IAAI;EAAM,OAAO;EAAM,MAAM;EAAQ;AAC1E,KAAI,YAAY,MAAM,OAAO,CAAE,QAAO;EAAE,IAAI;EAAM,OAAO;EAAM,MAAM;EAAQ;AAC7E,KAAI,SAAU,QAAO;EAAE,IAAI;EAAM,OAAO;EAAQ,MAAM;EAAU;AAChE,KAAI,WAAY,QAAO;EAAE,IAAI;EAAM,OAAO;EAAM,MAAM;EAAQ;CAE9D,MAAM,aAAa,SAAS,KAAK,KAAK,KAAA;CACtC,MAAM,aAAa,SAAS,KAAK,KAAK,KAAA;CACtC,MAAM,eAAe,SAAS,OAAO,KAAK,KAAA;AAC1C,KAAI,CAAC,cAAc,eAAe,aAChC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAe,WAAW,aAAa,SAAS;EAAU;AAExF,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAgB;;;;;;;AAgB9C,SAAgB,kBACd,OACA,WAC6E;CAC7E,MAAM,OAAO,CAAC,GAAG,IAAI,IAAI;EACvB,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,CAAC;EAChC,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,CAAC;EAChC,GAAG,OAAO,KAAK,MAAM,UAAU,EAAE,CAAC;EACnC,CAAC,CAAC,CAAC,UAAU;CAEd,MAAM,SAA4B,EAAE;CACpC,MAAM,YAA4B,EAAE;CACpC,IAAI,QAAQ;AACZ,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,UAAU,UAAU,KAAK;GAC7B,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO;GACnB,QAAQ,MAAM,SAAS;GACxB,CAAC;AACF,MAAI,QAAQ,UAAU,KAAA,EAAW,QAAO,OAAO,QAAQ;AACvD,YAAU,KAAK,GAAG,QAAQ,UAAU;AACpC,UAAQ,SAAS,OAAO,QAAQ,MAAM;;AAExC,QAAO;EAAE;EAAQ;EAAW;EAAO;;;AAIrC,SAAgB,aAAa,OAA+C;AAC1E,QAAO;EAAE,IAAI,WAAW,MAAM;EAAE,GAAG;EAAO;;;AAI5C,SAAgB,SAAS,MAA0C;AACjE,KAAI,SAAS,OAAQ,QAAO;EAAE,WAAW;EAAG,aAAa;EAAG,aAAa;EAAG;AAC5E,KAAI,SAAS,SAAU,QAAO;EAAE,WAAW;EAAG,aAAa;EAAG,aAAa;EAAG;AAC9E,QAAO;;;;;;;;;;;;AChFT,SAAgB,iBACd,OACA,KACA,UAC+C;CAC/C,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI;EACzB,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,CAAC;EAChC,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,CAAC;EAChC,GAAG,OAAO,KAAK,MAAM,UAAU,EAAE,CAAC;EACnC,CAAC,CAAC,CAAC,UAAU;CAEd,MAAM,SAAiB,EAAE;CACzB,MAAM,YAA4B,EAAE;AACpC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,YAAY;GAChB,MAAM,IAAI;GACV,KAAK;GACL;GACA,QAAQ,IAAI;GACZ,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO;GACnB,QAAQ,MAAM,SAAS;GACxB;EACD,MAAM,OAAO,WAAW,UAAU,MAAM,UAAU,MAAM,UAAU,OAAO;AACzE,MAAI,KAAK,IAAI;AACX,OAAI,KAAK,UAAU,KAAA,EAAW,QAAO,SAAS,KAAK;AACnD;;EAGF,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,YAAY;GACd,MAAM,QAAQ,cAAc,YAAY,UAAU;AAClD,OAAI,UAAU,KAAA,EAAW,QAAO,SAAS;AACzC;;AAGF,MAAI,UAAU,SAAS,KAAA,EAAW,QAAO,SAAS,UAAU;EAC5D,MAAM,QAAQ,WAAW,IAAI,SAAS,WAAW,MAAM,KAAK,UAAU,MAAM;AAC5E,YAAU,KAAK,aAAa;GAC1B,GAAG;GACH,MAAM,IAAI;GACV,OAAO,IAAI;GACX,MAAM,KAAK,WAAW,gBAAgB,yBAAyB;GAC/D,SAAS,KAAK,WAAW,gBACrB,GAAG,MAAM,sDAAsD,IAAI,QAAQ,KAC3E,GAAG,MAAM,oDAAoD,IAAI,QAAQ;GAC9E,CAAC,CAAC;;AAEL,QAAO;EAAE;EAAQ;EAAW;;;;;;AAO9B,SAAgB,oBACd,OACA,KAC8F;CAC9F,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AACjE,KAAI,SAAS,GACX,QAAO;EAAE,QAAQ,SAAS;EAA6C,WAAW,EAAE;EAAE,OAAO,SAAS,SAAS,KAAK;EAAE;AAsCxH,QAnCe,kBAAkB,QAAQ,SAAS,UAAU;EAC1D,MAAM,OAAO,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AAC7D,MAAI,KAAK,GACP,QAAO;GAAE,OAAO,KAAK;GAA6B,WAAW,EAAE;GAAE,OAAO,SAAS,KAAK,KAAK;GAAE;AAE/F,MAAI,KAAK,WAAW,eAAe;GACjC,MAAM,YAAY;IAChB,MAAM,IAAI;IAAS,KAAK;IAAS,QAAQ,IAAI;IAC7C,MAAM,MAAM;IAAM,MAAM,MAAM;IAAM,QAAQ,MAAM;IACnD;GACD,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,OAAI,WAEF,QAAO;IAAE,OADK,cAAc,YAAY,UAAU;IAClC,WAAW,EAAE;IAAE,OAAO;IAAa;AAErD,UAAO;IACL,OAAO,MAAM;IACb,WAAW,CAAC,aAAa;KACvB,GAAG;KACH,MAAM,IAAI;KACV,OAAO,IAAI;KACX,MAAM;KACN,SAAS,UAAU,QAAQ,uDAAuD,IAAI,QAAQ;KAC/F,CAAC,CAAC;IACH,OAAO;IACR;;EAGH,MAAM,aAAa,iBAAiB,OAAO,KAAK,QAAQ;AACxD,SAAO;GACL,OAAO,WAAW;GAClB,WAAW,WAAW;GACtB,OAAO,WAAW,UAAU,WAAW,IAAI;IAAE,WAAW;IAAG,aAAa;IAAG,aAAa;IAAG,GAAG;GAC/F;GACD;;;AAKJ,SAAgB,mBACd,OACA,KAC8E;CAC9E,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AACjE,KAAI,SAAS,GACX,QAAO;EAAE,QAAQ,SAAS;EAA6B,WAAW,EAAE;EAAE,OAAO,SAAS,SAAS,KAAK;EAAE;AAExG,KAAI,SAAS,WAAW,eAAe;EACrC,MAAM,YAAY;GAAE,MAAM,IAAI;GAAS,QAAQ,IAAI;GAAQ,MAAM,MAAM;GAAM,MAAM,MAAM;GAAM,QAAQ,MAAM;GAAQ;EACrH,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,WACF,QAAO;GAAE,QAAQ,cAAc,YAAY,UAAU;GAAwB,WAAW,EAAE;GAAE,OAAO;GAAa;AAElH,SAAO;GACL,QAAQ,MAAM;GACd,WAAW,CAAC,aAAa;IACvB,GAAG;IACH,MAAM,IAAI;IACV,OAAO,IAAI;IACX,MAAM;IACN,SAAS,GAAG,IAAI,QAAQ;IACzB,CAAC,CAAC;GACH,OAAO;GACR;;CAEH,MAAM,aAAa,iBAAiB,OAAO,IAAI;AAC/C,QAAO;EACL,QAAQ,WAAW;EACnB,WAAW,WAAW;EACtB,OAAO,WAAW,UAAU,WAAW,IAAI;GAAE,WAAW;GAAG,aAAa;GAAG,aAAa;GAAG,GAAG;EAC/F;;;;;;;;AASH,SAAgB,oBACd,OACA,KAC8F;CAC9F,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AACjE,KAAI,SAAS,GACX,QAAO;EAAE,QAAQ,SAAS;EAA6C,WAAW,EAAE;EAAE,OAAO,SAAS,SAAS,KAAK;EAAE;AAGxH,QAAO,kBAAkB,QAAQ,KAAK,SAAS;EAC7C,MAAM,OAAO,WAAW,KAAK,MAAM,KAAK,MAAM,KAAK,OAAO;AAC1D,MAAI,KAAK,GACP,QAAO;GAAE,OAAO,KAAK;GAA6B,WAAW,EAAE;GAAE,OAAO,SAAS,KAAK,KAAK;GAAE;EAE/F,MAAM,YAAY;GAChB,MAAM,IAAI;GAAS;GAAK,QAAQ,IAAI;GACpC,MAAM,KAAK;GAAM,MAAM,KAAK;GAAM,QAAQ,KAAK;GAChD;EACD,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,WACF,QAAO;GAAE,OAAO,cAAc,YAAY,UAAU;GAAwB,WAAW,EAAE;GAAE,OAAO;GAAa;AAEjH,SAAO;GACL,OAAO,KAAK;GACZ,WAAW,CAAC,aAAa;IACvB,GAAG;IACH,MAAM;IACN,OAAO,IAAI;IACX,MAAM,KAAK,WAAW,gBAAgB,yBAAyB;IAC/D,SAAS,KAAK,WAAW,gBACrB,mBAAmB,IAAI,uDAAuD,IAAI,QAAQ,KAC1F,mBAAmB,IAAI,gCAAgC,IAAI,QAAQ;IACxE,CAAC,CAAC;GACH,OAAO;GACR;GACD;;;;;;AAOJ,SAAgB,eACd,OACA,KAC+F;CAC/F,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AACjE,KAAI,SAAS,GACX,QAAO;EAAE,QAAQ,SAAS;EAA8C,WAAW,EAAE;EAAE,OAAO,SAAS,SAAS,KAAK;EAAE;CAEzH,MAAM,SAAS,kBAAkB,QAAQ,KAAK,SAAS;EACrD,MAAM,OAAO,WAAW,KAAK,MAAM,KAAK,MAAM,KAAK,OAAO;AAC1D,MAAI,KAAK,GAAI,QAAO;GAAE,OAAO,KAAK;GAAO,WAAW,EAAE;GAAE,OAAO,SAAS,KAAK,KAAK;GAAE;EACpF,MAAM,YAAY;GAAE,MAAM,IAAI;GAAS;GAAK,MAAM,KAAK;GAAM,MAAM,KAAK;GAAM,QAAQ,KAAK;GAAQ;EACnG,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,WAAY,QAAO;GAAE,OAAO,cAAc,YAAY,UAAU;GAAE,WAAW,EAAE;GAAE,OAAO;GAAa;AACzG,SAAO;GACL,OAAO,KAAK;GACZ,WAAW,CAAC,aAAa;IACvB,GAAG;IACH,MAAM;IACN,MAAM;IACN,SAAS,QAAQ,IAAI,OAAO,IAAI,QAAQ;IACzC,CAAC,CAAC;GACH,OAAO;GACR;GACD;AACF,QAAO;EAAE,QAAQ,OAAO;EAAQ,WAAW,OAAO;EAAW,OAAO,OAAO;EAAO;;;;;;;;;;;;;;;;;;AC7MpF,SAAgB,kBACd,OACA,KACoG;CACpG,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AACjE,KAAI,SAAS,GACX,QAAO;EAAE,QAAQ,SAAS;EAA6B,WAAW,EAAE;EAAE,OAAO,SAAS,SAAS,KAAK;EAAE,YAAY,EAAE;EAAE;AAGxH,KAAI,SAAS,WAAW,eAAe;EACrC,MAAM,YAAY;GAChB,MAAM,IAAI;GAAS,KAAK,IAAI;GAAM,QAAQ,IAAI;GAC9C,MAAM,MAAM;GAAM,MAAM,MAAM;GAAM,QAAQ,MAAM;GACnD;EACD,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,WACF,QAAO;GAAE,QAAQ,cAAc,YAAY,UAAU;GAAwB,WAAW,EAAE;GAAE,OAAO;GAAa,YAAY,EAAE;GAAE;AAElI,SAAO;GACL,QAAQ,MAAM;GACd,WAAW,CAAC,aAAa;IACvB,GAAG;IACH,MAAM;IACN,OAAO,IAAI;IACX,MAAM;IACN,SAAS,aAAa,IAAI,KAAK,qDAAqD,IAAI,QAAQ;IACjG,CAAC,CAAC;GACH,OAAO;GACP,YAAY,EAAE;GACf;;CAKH,MAAM,SAAS;EACb,MAAM,MAAM,SAAS,KAAA,IAAY,KAAA,IAAY,yBAAyB,MAAM,KAAK;EACjF,MAAM,MAAM,SAAS,KAAA,IAAY,KAAA,IAAY,yBAAyB,MAAM,KAAK;EACjF,QAAQ,MAAM,WAAW,KAAA,IAAY,KAAA,IAAY,yBAAyB,MAAM,OAAO;EACxF;AAOD,KANe,CAAC,QAAQ,SAAS,CAAW,MAAM,SAAS;EACzD,MAAM,MAAM,OAAO;AACnB,MAAI,CAAC,IAAK,QAAO;EACjB,MAAM,eAAe,yBAAyB,6BAA6B,IAAI,aAAa,IAAI,KAAK,CAAC;AACtG,SAAO,CAAC,YAAY,aAAa,aAAa,IAAI,YAAY,IAAI,aAAa,SAAS,IAAI;GAC5F,EACS;EACT,MAAM,YAAY;GAChB,MAAM,IAAI;GAAS,KAAK,IAAI;GAAM,QAAQ,IAAI;GAC9C,MAAM,MAAM;GAAM,MAAM,MAAM;GAAM,QAAQ,MAAM;GACnD;EACD,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,WACF,QAAO;GAAE,QAAQ,cAAc,YAAY,UAAU;GAAwB,WAAW,EAAE;GAAE,OAAO;GAAa,YAAY,EAAE;GAAE;AAElI,SAAO;GACL,QAAQ,MAAM;GACd,WAAW,CAAC,aAAa;IACvB,GAAG;IACH,MAAM;IACN,OAAO,IAAI;IACX,MAAM;IACN,SAAS,aAAa,IAAI,KAAK,KAAK,IAAI,QAAQ;IACjD,CAAC,CAAC;GACH,OAAO;GACP,YAAY,CAAC,aAAa,IAAI,KAAK,iFAAiF;GACrH;;CAKH,MAAM,YAA4B,EAAE;CACpC,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI;EACzB,GAAG,OAAO,KAAK,OAAO,MAAM,eAAe,EAAE,CAAC;EAC9C,GAAG,OAAO,KAAK,OAAO,MAAM,eAAe,EAAE,CAAC;EAC9C,GAAG,OAAO,KAAK,OAAO,QAAQ,eAAe,EAAE,CAAC;EACjD,CAAC,CAAC,CAAC,UAAU;CACd,MAAM,WAAoC,EAAE;AAC5C,MAAK,MAAM,OAAO,QAAQ;EACxB,MAAM,YAAY;GAChB,MAAM,IAAI;GAAS,KAAK,IAAI;GAAM,OAAO;GAAK,QAAQ,IAAI;GAC1D,MAAM,OAAO,MAAM,YAAY;GAC/B,MAAM,OAAO,MAAM,YAAY;GAC/B,QAAQ,OAAO,QAAQ,YAAY;GACpC;EACD,MAAM,OAAO,WAAW,UAAU,MAAM,UAAU,MAAM,UAAU,OAAO;AACzE,MAAI,KAAK,IAAI;AACX,OAAI,KAAK,UAAU,KAAA,EAAW,UAAS,OAAO,KAAK;AACnD;;EAEF,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,YAAY;GACd,MAAM,QAAQ,cAAc,YAAY,UAAU;AAClD,OAAI,UAAU,KAAA,EAAW,UAAS,OAAO;AACzC;;AAEF,MAAI,UAAU,SAAS,KAAA,EAAW,UAAS,OAAO,UAAU;AAC5D,YAAU,KAAK,aAAa;GAC1B,GAAG;GACH,MAAM;GACN,OAAO,IAAI;GACX,MAAM,KAAK,WAAW,gBAAgB,yBAAyB;GAC/D,SAAS,oBAAoB,IAAI,iBAAiB,IAAI,KAAK,2BAA2B,IAAI,QAAQ;GACnG,CAAC,CAAC;;CAIL,IAAI;CACJ,MAAM,gBAAgB;EACpB,MAAM,IAAI;EAAS,KAAK,IAAI;EAAM,OAAO;EAAQ,QAAQ,IAAI;EAC7D,MAAM,OAAO,MAAM;EAAM,MAAM,OAAO,MAAM;EAAM,QAAQ,OAAO,QAAQ;EAC1E;CACD,MAAM,WAAW,WAAW,cAAc,MAAM,cAAc,MAAM,cAAc,OAAO;AACzF,KAAI,SAAS,GACX,cAAc,SAAS,SAAgC;MAClD;EACL,MAAM,aAAa,IAAI,YAAY,QAAQ,cAAc;AACzD,MAAI,WACF,cAAc,cAAc,YAAY,cAAc,IAA2B;OAC5E;AACL,gBAAa,cAAc,QAAQ;AACnC,aAAU,KAAK,aAAa;IAC1B,GAAG;IACH,MAAM;IACN,OAAO,IAAI;IACX,MAAM;IACN,SAAS,qBAAqB,IAAI,KAAK,8BAA8B,IAAI,QAAQ;IAClF,CAAC,CAAC;;;AAIP,QAAO;EACL,QAAQ,6BAA6B,UAAU,WAAW;EAC1D;EACA,OAAO,UAAU,WAAW,IAAI;GAAE,WAAW;GAAG,aAAa;GAAG,aAAa;GAAG,GAAG;EACnF,YAAY,EAAE;EACf;;;;;;;;;;;;;;;;;;;;ACpIH,SAAgB,gBACd,OACA,KACA,UACqF;CACrF,MAAM,OAAO,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AAC7D,KAAI,KAAK,GAAI,QAAO;EAAE,QAAQ,KAAK;EAAiC,WAAW,EAAE;EAAE,YAAY,EAAE;EAAE;AACnG,KAAI,KAAK,WAAW,cAGlB,QAAO;EACL,QAHa,KAAK,cAAc,SAAS,MAAM,SAAS,MAAM;EAI9D,WAAW,EAAE;EACb,YAAY,CAAC,2BAJD,WAAW,YAAY,SAAS,KAAK,QAAQ,IAAI,UAIf,qEAAqE;EACpH;CAGH,MAAM,YAA4B,EAAE;CACpC,MAAM,aAAuB,EAAE;CAC/B,MAAM,SAAqB,EAAE;CAG7B,MAAM,SAAS,OAAO,MAAM,OAAO,kBAAkB,WAAW,MAAM,KAAK,gBAA0B,KAAA;CACrG,MAAM,WAAW,OAAO,MAAM,SAAS,kBAAkB,WAAW,MAAM,OAAO,gBAA0B,KAAA;CAC3G,IAAI,WAAqC;AACzC,KAAI,WAAW,KAAA,KAAa,aAAa,KAAA,GAAW;AAClD,MAAI,WAAW,KAAA,EAAW,YAAW;WAC5B,aAAa,KAAA,EAAW,YAAW;MACvC,YAAW,WAAW,SAAS,WAAW;EAC/C,MAAM,WAAW,aAAa,WAAW,WAAW;AACpD,MAAI,aAAa,KAAA,EAAW,QAAO,gBAAgB;;CAGrD,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI;EACzB,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,CAAC;EAChC,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,CAAC;EAChC,GAAG,OAAO,KAAK,MAAM,UAAU,EAAE,CAAC;EACnC,CAAC,CAAC,CAAC,UAAU,CAAC,QAAO,MAAK,MAAM,aAAa;AAE9C,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,YAAY;GAChB,MAAM,IAAI;GACV,KAAK;GACL;GACA,QAAQ,IAAI;GACZ,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO;GACnB,QAAQ,MAAM,SAAS;GACxB;AAED,MAAI,UAAU,gBAAgB,aAAa,MAAM;GAC/C,MAAM,QAAQ,aAAa,WAAW,UAAU,UAAU,UAAU,OAAO,UAAU,QAAQ,UAAU;AACvG,OAAI,UAAU,KAAA,EAAW,QAAO,SAAS;AACzC;;EAGF,MAAM,YAAY,WAAW,UAAU,MAAM,UAAU,MAAM,UAAU,OAAO;AAC9E,MAAI,UAAU,IAAI;AAChB,OAAI,UAAU,UAAU,KAAA,EAAW,QAAO,SAAS,UAAU;AAC7D;;AAGF,MAAI,UAAU,cAAc;AAE1B,OAAI,UAAU,SAAS,KAAA,EAAW,QAAO,SAAS,UAAU;AAC5D,cAAW,KAAK,GAAG,WAAW,IAAI,SAAS,OAAO,GAAG,kEAAkE;AACvH;;EAGF,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,YAAY;GACd,MAAM,QAAQ,cAAc,YAAY,UAAU;AAClD,OAAI,UAAU,KAAA,EAAW,QAAO,SAAS;AACzC;;AAGF,MAAI,UAAU,SAAS,KAAA,EAAW,QAAO,SAAS,UAAU;AAC5D,YAAU,KAAK,aAAa;GAC1B,GAAG;GACH,MAAM;GACN,OAAO,IAAI;GACX,MAAM,UAAU,WAAW,yBAAyB;GACpD,SAAS,UAAU,WACf,qBAAqB,WAAW,UAAU,SAAS,KAAK,IAAI,QAAQ,2CACpE,eAAe,MAAM,OAAO,WAAW,UAAU,SAAS,KAAK,IAAI,QAAQ;GAChF,CAAC,CAAC;;AAGL,QAAO;EAAE;EAAQ;EAAW;EAAY;;;;;;;;AAS1C,SAAgB,cACd,OACA,KACA,UACwG;CACxG,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AACjE,KAAI,SAAS,GACX,QAAO;EAAE,QAAQ,SAAS;EAAiC,WAAW,EAAE;EAAE,OAAO,SAAS,SAAS,KAAK;EAAE,YAAY,EAAE;EAAE;AAG5H,KAAI,CAAC,UAAU;EACb,MAAM,SAAS,gBAAgB,OAAO,IAAI;AAC1C,SAAO;GAAE,QAAQ,OAAO;GAAQ,WAAW,OAAO;GAAW,OAAO;GAAa,YAAY,OAAO;GAAY;;CAGlH,MAAM,aAAuB,EAAE;CAC/B,MAAM,SAAS,kBACb,QACC,SAAS,UAAU;EAClB,MAAM,YAAY,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AAClE,MAAI,UAAU,GACZ,QAAO;GAAE,OAAO,UAAU;GAAiC,WAAW,EAAE;GAAE,OAAO,SAAS,UAAU,KAAK;GAAE;EAE7G,MAAM,aAAa,gBAAgB,OAAO,KAAK,QAAQ;AACvD,aAAW,KAAK,GAAG,WAAW,WAAW;AACzC,SAAO;GAAE,OAAO,WAAW;GAAQ,WAAW,WAAW;GAAW,OAAO;GAAa;GAE3F;AACD,QAAO;EAAE,QAAQ,OAAO;EAAQ,WAAW,OAAO;EAAW,OAAO,OAAO;EAAO;EAAY;;;;;;;;;;ACvIhG,MAAa,wBAAwB;CAAC;CAAQ;CAAQ;CAAmB;CAAe;;;;;;;;AAyBxF,SAAgB,eACd,OACA,KACkB;CAClB,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AACjE,KAAI,SAAS,GACX,QAAO;EACL,QAAQ,SAAS;EACjB,WAAW,EAAE;EACb,OAAO,SAAS,SAAS,KAAK;EAC9B,qBAAqB;EACtB;AAEH,KAAI,SAAS,WAAW,cACtB,QAAO;EACL,QAAQ,MAAM;EACd,WAAW,EAAE;EACb,OAAO;EACP,YAAY,EAAE,WAAW,SAAS,WAAY;EAC9C,qBAAqB;EACtB;CAGH,MAAM,YAA4B,EAAE;CACpC,MAAM,SAAmB,EAAE;CAE3B,MAAM,OAAO,CAAC,GAAG,IAAI,IAAI;EACvB,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,CAAC;EAChC,GAAG,OAAO,KAAK,MAAM,QAAQ,EAAE,CAAC;EAChC,GAAG,OAAO,KAAK,MAAM,UAAU,EAAE,CAAC;EACnC,CAAC,CAAC,CAAC,UAAU;AAEd,MAAK,MAAM,OAAO,MAAM;AACtB,MAAI,QAAQ,SAAU;EACtB,MAAM,YAAY;GAChB,MAAM,IAAI;GACV;GACA,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO;GACnB,QAAQ,MAAM,SAAS;GACxB;EACD,MAAM,OAAO,WAAW,UAAU,MAAM,UAAU,MAAM,UAAU,OAAO;AACzE,MAAI,KAAK,IAAI;AACX,OAAI,KAAK,UAAU,KAAA,EAAW,QAAO,OAAO,KAAK;AACjD;;EAEF,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,MAAI,YAAY;GACd,MAAM,QAAQ,cAAc,YAAY,UAAU;AAClD,OAAI,UAAU,KAAA,EAAW,QAAO,OAAO;AACvC;;AAEF,MAAI,UAAU,SAAS,KAAA,EAAW,QAAO,OAAO,UAAU;AAC1D,YAAU,KAAK,aAAa;GAC1B,GAAG;GACH,MAAM;GACN,OAAO,IAAI;GACX,MAAM;GACN,SAAS,UAAU,IAAI,MAAM,iBAAiB,IAAI;GAClD,WAAW;GACZ,CAAC,CAAC;;CAIL,MAAM,aAAa,CAAC,GAAG,IAAI,IAAI;EAC7B,GAAG,OAAO,KAAM,MAAM,OAAO,aAAsC,EAAE,CAAC;EACtE,GAAG,OAAO,KAAM,MAAM,OAAO,aAAsC,EAAE,CAAC;EACtE,GAAG,OAAO,KAAM,MAAM,SAAS,aAAsC,EAAE,CAAC;EACzE,CAAC,CAAC,CAAC,UAAU;AACd,KAAI,WAAW,SAAS,GAAG;EACzB,MAAM,eAAyB,EAAE;AACjC,OAAK,MAAM,QAAQ,YAAY;GAC7B,MAAM,YAAY;IAChB,MAAM,IAAI;IACV,KAAK,UAAU;IACf,OAAO,MAAM,OAAO,aAAqC;IACzD,OAAO,MAAM,OAAO,aAAqC;IACzD,SAAS,MAAM,SAAS,aAAqC;IAC9D;GACD,MAAM,OAAO,WAAW,UAAU,MAAM,UAAU,MAAM,UAAU,OAAO;AACzE,OAAI,KAAK,IAAI;AACX,QAAI,KAAK,UAAU,KAAA,EAAW,cAAa,QAAQ,KAAK;AACxD;;GAEF,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,OAAI,YAAY;IACd,MAAM,QAAQ,cAAc,YAAY,UAAU;AAClD,QAAI,UAAU,KAAA,EAAW,cAAa,QAAQ;AAC9C;;AAEF,OAAI,UAAU,SAAS,KAAA,EAAW,cAAa,QAAQ,UAAU;AACjE,aAAU,KAAK,aAAa;IAC1B,GAAG;IACH,MAAM;IACN,OAAO,IAAI;IACX,MAAM,KAAK,WAAW,gBAAgB,yBAAyB;IAC/D,SAAS,UAAU,IAAI,MAAM,YAAY,KAAK;IAC9C,WAAW;IACZ,CAAC,CAAC;;AAEL,MAAI,OAAO,KAAK,aAAa,CAAC,SAAS,EAAG,QAAO,YAAY;;CAG/D,MAAM,sBAAsB,UAAU,MAAK,MACxC,sBAA4C,SAAS,EAAE,OAAO,GAAG,CACnE;AAED,QAAO;EACL;EACA;EACA,OAAO,UAAU,WAAW,IAAI;GAAE,WAAW;GAAG,aAAa;GAAG,aAAa;GAAG,GAAG;EACnF;EACD;;;;;;;;;;;;;;ACrIH,SAAgB,gBACd,OACA,KACkF;CAClF,MAAM,WAAW,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO;AACjE,KAAI,SAAS,GACX,QAAO;EAAE,QAAQ,SAAS;EAAiC,WAAW,EAAE;EAAE,OAAO,SAAS,SAAS,KAAK;EAAE;CAG5G,MAAM,UAAU,KAAK,IAAI,MAAM,MAAM,WAAW,GAAG,MAAM,MAAM,WAAW,GAAG,MAAM,QAAQ,WAAW,EAAE;CAExG,MAAM,aAAa,kBACjB;EACE,MAAM,MAAM,MAAM;EAClB,MAAM,MAAM,MAAM;EAClB,QAAQ,MAAM,QAAQ;EACvB,GACA,MAAM,SAAS;EACd,MAAM,WAAW,WAAW,KAAK,MAAM,KAAK,MAAM,KAAK,OAAO;AAC9D,MAAI,SAAS,GACX,QAAO;GAAE,OAAO,SAAS;GAA6C,WAAW,EAAE;GAAE,OAAO,SAAS,SAAS,KAAK;GAAE;AAEvH,MAAI,SAAS,WAAW,eAAe;GACrC,MAAM,YAAY;IAAE,MAAM,IAAI;IAAS,KAAK;IAAM,MAAM,KAAK;IAAM,MAAM,KAAK;IAAM,QAAQ,KAAK;IAAQ;GACzG,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,OAAI,WACF,QAAO;IAAE,OAAO,cAAc,YAAY,UAAU;IAAwC,WAAW,EAAE;IAAE,OAAO;IAAa;AAEjI,UAAO;IACL,OAAO,KAAK;IACZ,WAAW,CAAC,aAAa;KACvB,GAAG;KACH,MAAM;KACN,MAAM;KACN,SAAS,oBAAoB,KAAK;KACnC,CAAC,CAAC;IACH,OAAO;IACR;;EAIH,MAAM,QAAQ,kBAA0B,OAAO,QAAQ,gBAAgB;GACrE,MAAM,OAAO,WAAW,YAAY,MAAM,YAAY,MAAM,YAAY,OAAO;AAC/E,OAAI,KAAK,GACP,QAAO;IAAE,OAAO,KAAK;IAA6B,WAAW,EAAE;IAAE,OAAO,SAAS,KAAK,KAAK;IAAE;GAE/F,MAAM,YAAY;IAChB,MAAM,IAAI;IAAS,KAAK;IAAM;IAC9B,MAAM,YAAY;IAAM,MAAM,YAAY;IAAM,QAAQ,YAAY;IACrE;GACD,MAAM,aAAa,IAAI,YAAY,QAAQ,UAAU;AACrD,OAAI,WACF,QAAO;IAAE,OAAO,cAAc,YAAY,UAAU;IAAwB,WAAW,EAAE;IAAE,OAAO;IAAa;AAEjH,UAAO;IACL,OAAO,YAAY;IACnB,WAAW,CAAC,aAAa;KACvB,GAAG;KACH,MAAM;KACN,MAAM,KAAK,WAAW,gBAAgB,yBAAyB;KAC/D,SAAS,SAAS,KAAK,uBAAuB,OAAO;KACtD,CAAC,CAAC;IACH,OAAO;IACR;IACD;AACF,SAAO;GACL,OAAO,OAAO,KAAK,MAAM,OAAO,CAAC,SAAS,IAAI,MAAM,SAAS,KAAA;GAC7D,WAAW,MAAM;GACjB,OAAO,MAAM,UAAU,WAAW,IAAI,SAAS,MAAM,OAAO;IAAE,WAAW;IAAG,aAAa;IAAG,aAAa;IAAG,CAAC,GAAG,MAAM;GACvH;GAEJ;AAED,QAAO;EACL,QAAQ;GAAE;GAAS,OAAO,WAAW;GAAQ;EAC7C,WAAW,WAAW;EACtB,OAAO,WAAW;EACnB;;;;AC3FH,MAAa,QAAQ;CAAC;CAAQ;CAAQ;CAAS;;AAU/C,eAAsB,aAAa,SAAqC;CACtE,MAAM,WAAW,MAAM,QAAQ,IAAI,MAAM,KAAI,SAAQ,QAAQ,MAAM,cAAc,sBAAsB,CAAC,CAAC;CACzG,MAAM,sBAAM,IAAI,KAAa;AAC7B,MAAK,MAAM,WAAW,SACpB,MAAK,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,QAAQ,CAAE,KAAI,IAAI,MAAM,MAAM,GAAG,GAAgB,CAAC;AAGzE,QAAO,CAAC,GAAG,IAAI,CAAC,UAAU;;;;;;;;;;AAkB5B,eAAsB,mBACpB,QACA,KACA,eACqB;CACrB,MAAM,MAAM,eAAe,IAAI;CAC/B,MAAM,WAAW,IAAI,mBAAmB;CACxC,MAAM,QAAoB,EAAE;AAE5B,KAAI,IAAI,SAAS,YAAY;AAC3B,MAAI,CAAC,IAAI,QAAQ,aAAa,QAAQ;GACpC,MAAM,UAAU,MAAM,OAAO,cAAc,IAAI;AAC/C,QAAK,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,MAAM,CACvB,OAAM,KAAK;IAAE,QAAQ;IAAe,MAAM,MAAM,MAAM,GAAG,GAAG;IAAE,MAAM,GAAG,IAAI,GAAG;IAAS,CAAC;AAG5F,UAAO;;AAET,MAAI,aAAa,UAAU;GACzB,MAAM,UAAU,MAAM,OAAO,cAAc,IAAI;AAC/C,QAAK,MAAM,SAAS,SAAS;IAC3B,MAAM,QAAQ,0CAA0C,KAAK,MAAM;AACnE,QAAI,MAAO,OAAM,KAAK;KAAE,MAAM,MAAM;KAAK,QAAQ,MAAM;KAAK,MAAM,GAAG,IAAI,GAAG;KAAS,CAAC;;AAExF,UAAO;;AAET,MAAI,aAAa,aAAa;GAC5B,MAAM,UAAU,MAAM,OAAO,cAAc,IAAI;GAC/C,MAAM,WAAW,MAAM,QAAQ,IAAI,QAAQ,IAAI,OAAM,YAClD;IAAE;IAAQ,SAAS,OAAO,SAAS,IAAI,GAAG,EAAE,GAAG,MAAM,OAAO,cAAc,GAAG,IAAI,GAAG,SAAS;IAAE,EAAE,CAAC;AACrG,QAAK,MAAM,EAAE,QAAQ,aAAa,SAChC,MAAK,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,MAAM,CAAE,OAAM,KAAK;IAAE;IAAQ,MAAM,MAAM,MAAM,GAAG,GAAG;IAAE,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG;IAAS,CAAC;AAGlH,UAAO;;EAGT,MAAM,QAAQ,MAAM,OAAO,cAAc,IAAI;EAC7C,MAAM,WAAW,MAAM,QAAQ,IAAI,MAAM,IAAI,OAAM,UAChD;GAAE;GAAM,SAAS,KAAK,SAAS,IAAI,GAAG,EAAE,GAAG,MAAM,OAAO,cAAc,GAAG,IAAI,GAAG,OAAO;GAAE,EAAE,CAAC;AAC/F,OAAK,MAAM,EAAE,MAAM,aAAa,SAC9B,MAAK,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,MAAM,CAAE,OAAM,KAAK;GAAE;GAAM,QAAQ,MAAM,MAAM,GAAG,GAAG;GAAE,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG;GAAS,CAAC;AAGhH,SAAO;;AAIT,KAAI,CAAC,IAAI,MAAM;AACb,MAAI,MAAM,OAAO,WAAW,GAAG,IAAI,YAAY,CAC7C,OAAM,KAAK;GAAE,QAAQ;GAAe,MAAM,GAAG,IAAI;GAAa,CAAC;AAEjE,SAAO;;AAET,KAAI,aAAa,QAAQ;AACvB,MAAI,MAAM,OAAO,WAAW,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,CAClD,OAAM,KAAK;GAAE,QAAQ;GAAe,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG;GAAQ,CAAC;AAEtE,SAAO;;AAET,KAAI,aAAa,UAAU;EACzB,MAAM,UAAU,MAAM,OAAO,cAAc,IAAI;EAC/C,MAAM,SAAS,GAAG,IAAI,GAAG;AACzB,OAAK,MAAM,SAAS,QAClB,KAAI,MAAM,WAAW,OAAO,IAAI,MAAM,SAAS,QAAQ,EAAE;GACvD,MAAM,SAAS,MAAM,MAAM,OAAO,QAAQ,GAAgB;AAC1D,OAAI,UAAU,CAAC,OAAO,SAAS,IAAI,CAAE,OAAM,KAAK;IAAE;IAAQ,MAAM,GAAG,IAAI,GAAG;IAAS,CAAC;;AAGxF,SAAO;;AAET,KAAI,aAAa,aAAa;EAC5B,MAAM,UAAU,MAAM,OAAO,cAAc,IAAI;EAC/C,MAAM,SAAS,MAAM,QAAQ,IAAI,QAAQ,IAAI,OAAM,YAAW;GAC5D;GACA,QAAQ,OAAO,SAAS,IAAI,GAAG,QAAQ,MAAM,OAAO,WAAW,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO;GAClG,EAAE,CAAC;AACJ,OAAK,MAAM,EAAE,QAAQ,YAAY,OAC/B,KAAI,OAAQ,OAAM,KAAK;GAAE;GAAQ,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,GAAG;GAAQ,CAAC;AAE7E,SAAO;;CAGT,MAAM,UAAU,MAAM,OAAO,cAAc,IAAI;AAC/C,MAAK,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,QAAQ,IAAI,UAAU,YACvC,OAAM,KAAK;EAAE,QAAQ,MAAM,MAAM,GAAG,GAAgB;EAAE,MAAM,GAAG,IAAI,GAAG;EAAS,CAAC;AAGpF,QAAO;;;AAIT,SAAgB,YAAY,SAA0B,eAAuB,QAAiB,MAAuB;AACnH,KAAI,QAAQ,SAAS,WACnB,QAAO,iBAAiB,SAAS,UAAU,eAAe,QAAQ,GAAG;AAEvE,QAAO,gBAAgB,SAAS,UAAU,cAAc;;;;;;;;AAgB1D,eAAsB,mBAAmB,QAAsB,SAAsC;CACnG,MAAM,MAAM,qBAAqB;CACjC,MAAM,UAAU,MAAM,OAAO,cAAc,IAAI;CAC/C,MAAM,QAAoB,EAAE;CAC5B,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,QAAQ,CACzB,OAAM,KAAK;EAAE,QAAQ,MAAM,MAAM,GAAG,GAAgB;EAAE,MAAM,GAAG,IAAI,GAAG;EAAS,CAAC;UACvE,CAAC,MAAM,SAAS,IAAI,CAC7B,UAAS,KAAK,MAAM;CAGxB,MAAM,SAAS,MAAM,QAAQ,IAAI,SAAS,IAAI,OAAM,UACjD;EAAE;EAAM,SAAS,MAAM,OAAO,cAAc,GAAG,IAAI,GAAG,OAAO;EAAE,EAAE,CAAC;AACrE,MAAK,MAAM,EAAE,MAAM,SAAS,WAAW,OACrC,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,QAAQ,CACxB,OAAM,KAAK;EAAE,QAAQ,KAAK,MAAM,GAAG,GAAgB;EAAE;EAAM,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG;EAAQ,CAAC;AAIlG,QAAO;;AAGT,MAAM,iBAAiB;AACvB,MAAM,YAAY,IAAI,IAAI,CAAC,uBAAuB,4BAA4B,CAAC;;;;;;;;AAS/E,eAAsB,cAAc,QAAsB,MAAc,QAAQ,GAAsB;AACpG,KAAI,QAAQ,eAAgB,QAAO,EAAE;CACrC,MAAM,UAAU,MAAM,OAAO,cAAc,KAAK;AAOhD,SANgB,MAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO,UAAU;EAC7D,MAAM,OAAO,SAAS,KAAK,QAAQ,GAAG,KAAK,GAAG;AAC9C,MAAI,UAAU,IAAI,KAAK,CAAE,QAAO,EAAE;AAClC,MAAI,MAAM,SAAS,QAAQ,IAAI,MAAM,SAAS,MAAM,CAAE,QAAO,CAAC,KAAK;AACnE,SAAO,cAAc,QAAQ,MAAM,QAAQ,EAAE;GAC7C,CAAC,EACY,MAAM;;;;ACtKvB,MAAM,eAAe;AACrB,MAAM,kBAAkB;AACxB,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,4BAA4B;;;;;;;;;;;;;AAuBlC,eAAsB,cAAc,OAA+C;CACjF,MAAM,UAAmB;EACvB,MAAM,IAAI,aAAa,MAAM,KAAK;EAClC,MAAM,IAAI,aAAa,MAAM,KAAK;EAClC,QAAQ,IAAI,aAAa,MAAM,OAAO;EACvC;CACD,MAAM,cAAc,IAAI,gBAAgB,MAAM,eAAe,EAAE,CAAC;CAChE,MAAM,YAA4B,EAAE;CACpC,MAAM,aAAuB,EAAE;CAC/B,MAAM,UAAoB,EAAE;CAC5B,MAAM,0BAAU,IAAI,KAAa;CACjC,IAAI,QAAoB;CAGxB,MAAM,iBADS,MAAM,eAAkC,QAAQ,MAAM,2BAA2B,GAClE,SAAS,WAAW;CAIlD,MAAM,WAAW,MAAM,aAAa,QAAQ;CAa5C,MAAM,eAZS,MAAM,QAAQ,IAAI,SAAS,IAAI,OAAO,OAAO;EAC1D,MAAM,OAAO,uBAAuB,GAAG;EACvC,MAAM,CAAC,MAAM,MAAM,UAAU,MAAM,QAAQ,IACzC,MAAM,KAAI,SAAQ,eAAyB,QAAQ,OAAO,KAAK,CAAC,CACjE;AACD,SAAO;GACL;GACA;GACA,OAAO;IAAE,MAAM,QAAQ,KAAA;IAAW,MAAM,QAAQ,KAAA;IAAW,QAAQ,UAAU,KAAA;IAAW;GACzF;GACD,CAAC,EAEwB,KAAK,MAAM;AACpC,UAAQ,IAAI,EAAE,KAAK;EACnB,MAAM,SAAS,eAAe,EAAE,OAAO;GAAE,SAAS,EAAE;GAAM,MAAM;GAAS,OAAO,EAAE;GAAI;GAAa,CAAC;AACpG,SAAO;GAAE,IAAI,EAAE;GAAI,MAAM,EAAE;GAAM,OAAO,EAAE;GAAO;GAAQ;GACzD;AAIF,OAAM,QAAQ,IAAI,YAAY,IAAI,OAAO,UAAU;EACjD,MAAM,EAAE,IAAI,MAAM,OAAO,WAAW;EAIpC,MAAM,aAAa,MAAM,SAAS,KAAA;EAClC,MAAM,eAAe,MAAM,WAAW,KAAA;EACtC,MAAM,iBAAiB,MAAM,SAAS,KAAA,KAAa,eAAe;AAClE,MAAI,OAAO,cAAe,OAAO,WAAW,KAAA,KAAa,gBAAiB;GACxE,MAAM,YAAkB,OAAO,YAAY,cAAc,aAAa,SAAS;GAC/E,MAAM,WAAiB,cAAc,SAAS,WAAW;AACzD,SAAM,sBAAsB,MAAM,IAAI,MAAM,OAAO,WAAW,UAAU,CAAC,CAAC,OAAO,WAAW;AAC5F;;AAGF,YAAU,KAAK,GAAG,OAAO,UAAU;AACnC,UAAQ,SAAS,OAAO,OAAO,MAAM;AAErC,MAAI,OAAO,WAAW,KAAA,GAAW;AAE/B,SAAM,gBAAgB,IAAI,MAAM;AAChC;;AAGF,MAAI,OAAO,qBAAqB;AAK9B,cAAW,KAAK,UAAU,GAAG,qGAAqG;AAClI,SAAM,gBAAgB,IAAI,MAAM;AAChC;;EAGF,MAAM,UAAU,OAAO;AACvB,MAAI,OAAO,QAAQ,SAAS,YAAY,OAAO,QAAQ,OAAO,UAAU;AACtE,cAAW,KAAK,UAAU,GAAG,2EAA2E;AACxG,SAAM,gBAAgB,IAAI,MAAM;AAChC;;AAGF,UAAQ,KAAK;GAAE;GAAM,QAAQ;GAAS,OAAO,OAAO;GAAQ,CAAC;EAG7D,MAAM,WAAuD,EAAE;AAC/D,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,MAAM,MAAM;AAClB,OAAI,OAAO,OAAO,IAAI,SAAS,SAAU,UAAS,QAAQ;;EAE5D,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,IAAI,OAAO,SAAS;GAC1D,MAAM,MAAM,SAAS;AACrB,UAAO;IAAE;IAAM,OAAO,MAAM,MAAM,mBAAmB,QAAQ,OAAO,KAAK,cAAc,GAAG,EAAE;IAAE;IAC9F,CAAC;EAEH,MAAM,0BAAU,IAAI,KAA2F;AAC/G,OAAK,MAAM,EAAE,MAAM,WAAW,QAC5B,MAAK,MAAM,QAAQ,OAAO;AACxB,WAAQ,IAAI,KAAK,KAAK;GACtB,MAAM,MAAM,QAAQ,KAAK;GACzB,MAAM,QAAQ,QAAQ,IAAI,IAAI,IAAI;IAAE,QAAQ,KAAK;IAAQ,MAAM,KAAK;IAAM,OAAO,EAAE;IAAE;AACrF,SAAM,MAAM,QAAQ,KAAK;AACzB,WAAQ,IAAI,KAAK,MAAM;;AAI3B,QAAM,QAAQ,IAAI,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC,IAAI,OAAO,SAAS;GAC1D,MAAM,CAAC,SAAS,SAAS,aAAa,MAAM,QAAQ,IAAI,MAAM,KAAI,SAChE,KAAK,MAAM,QAAQ,QAAQ,MAAM,WAAW,KAAK,MAAM,MAAO,GAAG,QAAQ,QAAQ,KAAK,CAAC,CAAC;GAC1F,MAAM,UAAU,YAAY,SAAS,eAAe,KAAK,QAAQ,KAAK,KAAK;AAC3E,WAAQ,IAAI,QAAQ;GACpB,MAAM,MAAM;IACV;IACA,MAAM,QAAQ;IACd,OAAO;IACP,QAAQ,KAAK;IACb;IACD;AAED,OAAI,QAAQ,SAAS,YAAY;IAC/B,MAAM,QAAQ,kBACZ;KAAE,MAAM,WAAW,KAAA;KAAW,MAAM,WAAW,KAAA;KAAW,QAAQ,aAAa,KAAA;KAAW,EAC1F;KAAE,GAAG;KAAK,MAAM,KAAK,QAAQ;KAAI,CAClC;AACD,cAAU,KAAK,GAAG,MAAM,UAAU;AAClC,eAAW,KAAK,GAAG,MAAM,WAAW;AACpC,YAAQ,SAAS,OAAO,MAAM,MAAM;AACpC,YAAQ,KAAK;KAAE,MAAM;KAAS,QAAQ;KAAM,OAAO,MAAM,UAAU;KAAM,CAAC;UACrE;IACL,MAAM,SAAS;KACb,MAAM,UAAU,WAAW,KAAK;KAChC,MAAM,UAAU,WAAW,KAAK;KAChC,QAAQ,UAAU,aAAa,KAAK;KACrC;IACD,MAAM,QAAQ,QAAQ,SAAS,eAC3B,oBAAoB,QAAiB,IAAI,GACzC,QAAQ,SAAS,cACf,mBAAmB,QAAiB,IAAI,GACxC,oBAAoB,QAAiB,IAAI;AAC/C,cAAU,KAAK,GAAG,MAAM,UAAU;AAClC,YAAQ,SAAS,OAAO,MAAM,MAAM;AACpC,YAAQ,KAAK;KAAE,MAAM;KAAS,QAAQ;KAAQ,OAAO,MAAM,UAAU;KAAM,CAAC;;AAK9E,OAAI,KAAK,MAAM,QAAQ,KAAK,MAAM,SAAS,QACzC,SAAQ,KAAK;IAAE,MAAM,KAAK,MAAM;IAAM,QAAQ;IAAO,OAAO;IAAM,CAAC;IAErE,CAAC;EAGH,MAAM,cAAc,MAAM,QAAQ,IAAI,MAAM,KAAI,SAAQ,mBAAmB,QAAQ,OAAO,GAAG,CAAC,CAAC;EAC/F,MAAM,0BAAU,IAAI,KAA8D;AAClF,OAAK,MAAM,SAAS,YAClB,MAAK,MAAM,QAAQ,OAAO;AACxB,WAAQ,IAAI,KAAK,KAAK;AACtB,WAAQ,IAAI,KAAK,MAAM,KAAK;;AAGhC,QAAM,QAAQ,IAAI,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC,IAAI,OAAO,SAAS;GAC1D,MAAM,CAAC,MAAM,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAAI,SAAQ,eAAyB,QAAQ,OAAO,KAAK,KAAK,CAAC,CAAC;GACrH,MAAM,QAAQ,cACZ;IAAE,MAAM,QAAQ,KAAA;IAAW,MAAM,QAAQ,KAAA;IAAW,QAAQ,UAAU,KAAA;IAAW,EACjF;IAAE,SAAS,KAAK;IAAM,MAAM;IAAQ,OAAO;IAAI,QAAQ,KAAK;IAAQ;IAAa,EACjF,QAAQ,SAAS,aAClB;AACD,aAAU,KAAK,GAAG,MAAM,UAAU;AAClC,cAAW,KAAK,GAAG,MAAM,WAAW;AACpC,WAAQ,SAAS,OAAO,MAAM,MAAM;AACpC,WAAQ,KAAK;IAAE,MAAM,KAAK;IAAM,QAAQ;IAAQ,OAAO,MAAM,UAAU;IAAM,CAAC;IAC9E,CAAC;GACH,CAAC;AAIH,SAAQ,IAAI,gBAAgB;AAC5B,SAAQ,IAAI,aAAa;AACzB,SAAQ,IAAI,YAAY;CACxB;EACE,MAAM,CAAC,MAAM,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAAI,SAAQ,eAA2B,QAAQ,OAAO,gBAAgB,CAAC,CAAC;AAC7H,MAAI,SAAS,QAAQ,SAAS,QAAQ,WAAW,MAAM;GACrD,MAAM,QAAQ,gBACZ;IAAE,MAAM,QAAQ,KAAA;IAAW,MAAM,QAAQ,KAAA;IAAW,QAAQ,UAAU,KAAA;IAAW,EACjF;IAAE,SAAS;IAAiB,MAAM;IAAc;IAAa,CAC9D;AACD,aAAU,KAAK,GAAG,MAAM,UAAU;AAClC,WAAQ,SAAS,OAAO,MAAM,MAAM;AACpC,WAAQ,KAAK;IAAE,MAAM;IAAiB,QAAQ;IAAQ,OAAO,MAAM,UAAU;IAAM,CAAC;;;CAGxF;EACE,MAAM,CAAC,MAAM,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAAI,SAAQ,eAAyB,QAAQ,OAAO,aAAa,CAAC,CAAC;AACxH,MAAI,SAAS,QAAQ,SAAS,QAAQ,WAAW,MAAM;GACrD,MAAM,QAAQ,eACZ;IAAE,MAAM,QAAQ,KAAA;IAAW,MAAM,QAAQ,KAAA;IAAW,QAAQ,UAAU,KAAA;IAAW,EACjF;IAAE,SAAS;IAAc,MAAM;IAAQ;IAAa,CACrD;AACD,aAAU,KAAK,GAAG,MAAM,UAAU;AAClC,WAAQ,SAAS,OAAO,MAAM,MAAM;AACpC,WAAQ,KAAK;IAAE,MAAM;IAAc,QAAQ;IAAQ,OAAO,MAAM,UAAU;IAAM,CAAC;;;CAGrF;EAOE,MAAM,CAAC,MAAM,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,KAAI,SAAQ,eAAyB,QAAQ,OAAO,YAAY,CAAC,CAAC;AACvH,MAAI,SAAS,QAAQ,SAAS,QAAQ,WAAW,MAAM;GACrD,MAAM,QAAQ,eACZ;IAAE,MAAM,QAAQ,KAAA;IAAW,MAAM,QAAQ,KAAA;IAAW,QAAQ,UAAU,KAAA;IAAW,EACjF;IAAE,SAAS;IAAa,MAAM;IAAQ;IAAa,CACpD;AACD,aAAU,KAAK,GAAG,MAAM,UAAU;AAClC,WAAQ,SAAS,OAAO,MAAM,MAAM;AACpC,WAAQ,KAAK;IAAE,MAAM;IAAa,QAAQ;IAAQ,OAAO,MAAM,UAAU;IAAM,CAAC;;;CAWpF,MAAM,QAAQ,IAAI,IAAY,CAAC,eAAe,CAAC;AAC/C,MAAK,MAAM,SAAS,YAClB,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,cAAe,MAAM,MAAM,OAAkD;AACnF,MAAI,OAAO,gBAAgB,YAAY,YAAY,SAAS,EAAG,OAAM,IAAI,YAAY;;CAGzF,MAAM,UAAU,MAAM,QAAQ,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,OAAM,SACrD,QAAQ,IAAI,MAAM,KAAI,SAAQ,cAAc,QAAQ,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;CACtE,MAAM,6BAAa,IAAI,KAAa;AACpC,MAAK,MAAM,WAAW,QACpB,MAAK,MAAM,SAAS,QAClB,MAAK,MAAM,QAAQ,MACjB,KAAI,CAAC,QAAQ,IAAI,KAAK,CAAE,YAAW,IAAI,KAAK;AAIlD,OAAM,QAAQ,IAAI,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,IAAI,OAAO,SAAS;EAC/D,MAAM,CAAC,SAAS,SAAS,aAAa,MAAM,QAAQ,IAAI,MAAM,KAAI,SAAQ,QAAQ,MAAM,WAAW,KAAK,CAAC,CAAC;EAC1G,MAAM,OAAO,WAAW,WAAW,KAAA,GAAW,WAAW,KAAA,GAAW,aAAa,KAAA,EAAU;AAC3F,MAAI,KAAK,IAAI;AACX,OAAI,KAAK,SAAS,SAAU,SAAQ,KAAK;IAAE;IAAM,QAAQ;IAAO,OAAQ,KAAK,SAAgC;IAAM,CAAC;AACpH;;EAIF,MAAM,kBAAkB;GACtB;GACA,MAAM,WAAW,OAAO,KAAA,IAAY,WAAW,QAAQ;GACvD,MAAM,WAAW,OAAO,KAAA,IAAY,WAAW,QAAQ;GACvD,QAAQ,aAAa,OAAO,KAAA,IAAY,WAAW,UAAU;GAC9D;EACD,MAAM,aAAa,YAAY,QAAQ,gBAAgB;AACvD,MAAI,cAAc,YAAY,YAAY;GACxC,MAAM,SAAS,WAAW,WAAW,SAAS,UAAU;AACxD,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAO,OAAO;IAAQ,CAAC;AACpD;;AAEF,MAAI,WACF,YAAW,KAAK,mBAAmB,KAAK,kGAAkG;AAE5I,YAAU,KAAK;GACb,IAAI,WAAW,gBAAgB;GAC/B;GACA,MAAM;GACN,MAAM;GACN,SAAS,GAAG,KAAK;GAClB,CAAC;GACF,CAAC;CAIH,MAAM,UAAwB,EAAE;AAChC,OAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO,QAAQ;EAC3C,MAAM,UAAU,MAAM,QAAQ,KAAK,WAAW,IAAI,KAAK;AACvD,MAAI,IAAI,UAAU,MAAM;AACtB,OAAI,YAAY,KAAM,SAAQ,KAAK;IAAE,MAAM,IAAI;IAAM,SAAS;IAAM,CAAC;AACrE;;AAEF,MAAI,IAAI,WAAW,QAAQ,IAAI,WAAW,OAAO;GAC/C,MAAM,OAAO,IAAI;AACjB,OAAI,YAAY,MAAM;AACpB,YAAQ,KAAK;KAAE,MAAM,IAAI;KAAM,SAAS;KAAM,CAAC;AAC/C;;AAEF,OAAI,IAAI,WAAW,OAAO;AACxB,QAAI,YAAY,KAAM,SAAQ,KAAK;KAAE,MAAM,IAAI;KAAM,SAAS;KAAM,CAAC;AACrE;;GAEF,MAAM,IAAI,yBAAyB,QAAQ;GAC3C,MAAM,IAAI,yBAAyB,KAAK;AACxC,OAAI,CAAC,YAAY,EAAE,aAAa,EAAE,YAAY,IAAI,EAAE,SAAS,EAAE,KAC7D,SAAQ,KAAK;IAAE,MAAM,IAAI;IAAM,SAAS;IAAM,CAAC;AAEjD;;EAIF,MAAM,aAAa,UAAU,QAAQ;AACrC,MAAI,YAAY,IAAI,OAAO,WAAW,CAAE;EACxC,MAAM,aAAa,IAAI,WAAW,UAAU,oBAAoB,KAAA;AAChE,UAAQ,KAAK;GAAE,MAAM,IAAI;GAAM,SAAS,mBAAmB,IAAI,OAAO,WAAW;GAAE,CAAC;GACpF,CAAC;CAEH,MAAM,cAAc,QAAQ;CAC5B,MAAM,cAAwB,EAAE;AAChC,KAAI,QAAQ,SAAS,EACnB,KAAI;EACF,MAAM,gBAAgB,MAAM,mBAC1B,IAAI,cAAc,QAAQ,MAAM,QAAQ,EACxC;GAAE,MAAM;GAAyB,OAAO;GAAK,EAC7C,MAAM,OACP;AACD,UAAQ,KAAK,cAAc;AAC3B,cAAY,KAAK,aAAa;SACxB;AACN,aAAW,KAAK,mFAAmF;;AAIvG,KAAI,QAAQ,SAAS,EACnB,KAAI;EAEF,MAAM,UADa,MAAM,gBAAgB,IAAI,cAAc,QAAQ,MAAM,QAAQ,EAAE,EAAE,CAAC,EAC5D,OAAO,QAAO,MAAK,EAAE,aAAa,SAAS;EACrE,MAAM,QAAQ,OAAO,MAAM,GAAG,0BAA0B;AACxD,OAAK,MAAM,SAAS,MAClB,YAAW,KAAK,eAAe,MAAM,SAAS,KAAK,MAAM,SAAS,KAAK,MAAM,QAAQ,IAAI,MAAM,UAAU,GAAG,KAAK,MAAM,UAAU;AAEnI,MAAI,OAAO,SAAS,MAAM,OACxB,YAAW,KAAK,eAAe,OAAO,SAAS,MAAM,OAAO,gEAAgE;SAExH;AACN,aAAW,KAAK,iEAAiE;;AAIrF,MAAK,MAAM,SAAS,YAAY,YAAY,CAC1C,YAAW,KAAK,cAAc,MAAM,GAAG,gHAAgH;AAGzJ,KAAI,QAAQ,WAAW,KAAK,UAAU,WAAW,EAC/C,YAAW,KAAK,mDAAmD;AAGrE,SAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAC1D,WAAU,MAAM,GAAG,MACjB,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,KAC9B,EAAE,OAAO,IAAI,cAAc,EAAE,OAAO,IAAI,KAAK,KAC7C,EAAE,SAAS,IAAI,cAAc,EAAE,SAAS,IAAI,KAAK,KACjD,EAAE,UAAU,IAAI,cAAc,EAAE,UAAU,IAAI,KAAK,CAAC;AAE1D,QAAO;EACL;EACA;EACA;EACA,QAAQ;GACN,cAAc;GACd,oBAAoB,MAAM;GAC1B,sBAAsB,MAAM;GAC5B,sBAAsB,MAAM;GAC5B;GACD;EACF;;CAKD,eAAe,gBAAgB,IAAY,OAA+E;AACxH,QAAM,QAAQ,IAAI,MAAM,IAAI,OAAO,SAAS;GAC1C,MAAM,MAAM,MAAM;AAClB,OAAI,OAAO,OAAO,IAAI,SAAS,UAAU;IACvC,MAAM,QAAQ,MAAM,mBAAmB,QAAQ,OAAO,KAAK,cAAc;AACzE,SAAK,MAAM,QAAQ,MAAO,SAAQ,IAAI,KAAK,KAAK;;GAElD,MAAM,OAAO,MAAM,mBAAmB,QAAQ,OAAO,GAAG;AACxD,QAAK,MAAM,QAAQ,KAAM,SAAQ,IAAI,KAAK,KAAK;IAC/C,CAAC;;;;;;;;;CAUL,eAAe,sBACb,IACA,WACA,OACA,WACA,UACA,iBACe;AACf,QAAM,gBAAgB,IAAI,MAAM;EAEhC,MAAM,cAAc,MAAM;EAC1B,MAAM,gBAAgB,MAAM,oBAAoB,UAAU,IAAI,YAAY;EAC1E,MAAM,YAAY,MAAM,oBAAoB,QAAQ,IAAI,MAAM,KAA+C;EAC7G,MAAM,gBAAgB,KAAK,UAAU,cAAc,KAAK,KAAK,UAAU,UAAU;AAEjF,MAAI,CAAC,mBAAmB,CAAC,eAAe;AAEtC,SAAM,sBAAsB,IAAI,MAAM;AACtC;;EAGF,MAAM,YAAY,OAAO,SAAqC;GAC5D,MAAM,MAAM,MAAM;AAClB,OAAI,CAAC,OAAO,SAAS,OAAQ,QAAO,KAAA;GACpC,MAAM,QAAQ,MAAM,oBAAoB,MAAM,IAAI,IAAI;AACtD,UAAO;IAAE,OAAO,MAAM,QAAQ,WAAW,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG;IAAM,OAAO,WAAW,KAAK,UAAU,MAAM,CAAC;IAAE;;EAE1H,MAAM,CAAC,YAAY,YAAY,gBAAgB,MAAM,QAAQ,IAAI;GAAC,UAAU,OAAO;GAAE,UAAU,OAAO;GAAE,UAAU,SAAS;GAAC,CAAC;EAC7H,MAAM,YAAY;GAAE,MAAM;GAAW,MAAM;GAAY,MAAM;GAAY,QAAQ;GAAc;EAC/F,MAAM,aAAa,YAAY,QAAQ,UAAU;AACjD,MAAI,cAAc,YAAY,YAAY;AACxC,OAAI,WAAW,WAAW,UACxB,OAAM,sBAAsB,IAAI,MAAM;OAEtC,OAAM,mBAAmB,IAAI,WAAW,OAAO,SAAS;AAE1D;;AAEF,MAAI,WACF,YAAW,KAAK,yBAAyB,GAAG,mGAAmG;AAEjJ,YAAU,KAAK,aAAa;GAC1B,GAAG;GACH,MAAM;GACN,OAAO;GACP,MAAM;GACN,SAAS,UAAU,GAAG,6BAA6B,UAAU,oCAAoC,kBAAkB,eAAe,UAAU;GAC7I,CAAC,CAAC;;;CAIL,eAAe,oBAAoB,MAAgB,IAAY,KAAoE;EACjI,MAAM,QAAQ,OAAO,OAAO,IAAI,SAAS,WACrC,MAAM,mBAAmB,QAAQ,OAAO,KAAK,cAAc,GAC3D,EAAE;EACN,MAAM,OAAO,MAAM,mBAAmB,QAAQ,OAAO,GAAG;EACxD,MAAM,QAAQ,CAAC,GAAG,MAAM,KAAI,MAAK,EAAE,KAAK,EAAE,GAAG,KAAK,KAAI,MAAK,EAAE,KAAK,CAAC,CAAC,UAAU;EAC9E,MAAM,OAAO,MAAM,QAAQ,IAAI,MAAM,KAAI,SAAQ,QAAQ,MAAM,WAAW,KAAK,CAAC,CAAC;AACjF,SAAO,MAAM,KAAK,MAAM,MAAM,CAAC,MAAM,WAAW,KAAK,MAAM,GAAG,CAAC,CAAqB;;;CAItF,eAAe,sBAAsB,IAAY,OAA+E;AAC9H,UAAQ,KAAK;GAAE,MAAM,uBAAuB,GAAG;GAAQ,QAAQ;GAAO,OAAO;GAAM,CAAC;EACpF,MAAM,UAAU,MAAM;EACtB,MAAM,QAAQ,WAAW,OAAO,QAAQ,SAAS,WAC7C,MAAM,mBAAmB,QAAQ,MAAM,SAAS,cAAc,GAC9D,EAAE;EACN,MAAM,OAAO,MAAM,mBAAmB,QAAQ,MAAM,GAAG;AACvD,OAAK,MAAM,QAAQ,CAAC,GAAG,MAAM,KAAI,MAAK,EAAE,KAAK,EAAE,GAAG,KAAK,KAAI,MAAK,EAAE,KAAK,CAAC,CACtE,SAAQ,KAAK;GAAE,MAAM;GAAM,QAAQ;GAAO,OAAO;GAAM,CAAC;;;CAK5D,eAAe,mBACb,IACA,WACA,OACA,UACe;AACf,UAAQ,KAAK;GAAE,MAAM;GAAW,QAAQ;GAAS,OAAO,MAAM;GAAW,CAAC;EAC1E,MAAM,cAAc,MAAM;EAC1B,MAAM,QAAQ,eAAe,OAAO,YAAY,SAAS,WACrD,MAAM,mBAAmB,QAAQ,WAAW,aAAa,cAAc,GACvE,EAAE;EACN,MAAM,OAAO,MAAM,mBAAmB,QAAQ,WAAW,GAAG;EAC5D,MAAM,gBAAgB,CAAC,GAAG,MAAM,KAAI,MAAK,EAAE,KAAK,EAAE,GAAG,KAAK,KAAI,MAAK,EAAE,KAAK,CAAC;EAC3E,MAAM,OAAO,MAAM,QAAQ,IAAI,cAAc,KAAI,SAAQ,QAAQ,UAAU,WAAW,KAAK,CAAC,CAAC;AAC7F,gBAAc,SAAS,MAAM,MAAM;AACjC,WAAQ,KAAK;IAAE;IAAM,QAAQ;IAAO,OAAO,KAAK;IAAI,CAAC;IACrD;EAEF,MAAM,UAAU,MAAM;EACtB,MAAM,YAAY,WAAW,OAAO,QAAQ,SAAS,WACjD,MAAM,mBAAmB,QAAQ,MAAM,SAAS,cAAc,GAC9D,EAAE;EACN,MAAM,WAAW,MAAM,mBAAmB,QAAQ,MAAM,GAAG;EAC3D,MAAM,cAAc,IAAI,IAAI,cAAc;AAC1C,OAAK,MAAM,QAAQ,CAAC,GAAG,UAAU,KAAI,MAAK,EAAE,KAAK,EAAE,GAAG,SAAS,KAAI,MAAK,EAAE,KAAK,CAAC,CAC9E,KAAI,CAAC,YAAY,IAAI,KAAK,CAAE,SAAQ,KAAK;GAAE;GAAM,QAAQ;GAAO,OAAO;GAAM,CAAC;;;AAKpF,SAAS,QAAQ,GAA+C;AAC9D,QAAO,GAAG,EAAE,UAAU,GAAG,IAAI,EAAE,QAAQ;;AAGzC,SAAS,UAAU,KAAyD;AAC1E,KAAI,QAAQ,KAAM,QAAO,KAAA;AACzB,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;SAChB;AACN"}
|
|
1
|
+
{"version":3,"file":"reconcile-B55dZqhg.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 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,4 +1,4 @@
|
|
|
1
|
-
import { n as applyChangesToWorktree, t as planReconcile } from "./reconcile-
|
|
1
|
+
import { n as applyChangesToWorktree, t as planReconcile } from "./reconcile-B55dZqhg.mjs";
|
|
2
2
|
import { t as readConfig } from "./config-B8a4JBT1.mjs";
|
|
3
3
|
import { n as authorConfig, r as createGit } from "./identity-CxAB9Qb8.mjs";
|
|
4
4
|
import { l as networkGit } from "./branch-lifecycle-DUOoyNWn.mjs";
|
|
@@ -246,4 +246,4 @@ async function reconcileBranches(projectRoot, options = {}) {
|
|
|
246
246
|
//#endregion
|
|
247
247
|
export { reconcileBranches as t };
|
|
248
248
|
|
|
249
|
-
//# sourceMappingURL=reconcile-
|
|
249
|
+
//# sourceMappingURL=reconcile-CwS4LUbo.mjs.map
|