Kimo
Docs
v1.0
Changeloggetkimo.design
Docs
v1.0
File format spec
§21

Stored merge conflicts (feature merge/conflicts)

On this page4 sections
  1. 21.1 Root members
  2. 21.2 Conflict entries
  3. 21.3 Validation
  4. 21.4 Writing and resolving (normative)
Status
Stable (format version 1.0.0, frozen 2026-10). Backward compatible within 1.x (§16.4). Supersedes the draft v0.1, kept as a historical record; 0.1 files open through the 0.1 → 1.0 migration (§16.2).
Schema
https://getkimo.design/schema/kimo-1.0.json (JSON Schema draft 2020-12), generated into packages/core-schema/schema/kimo-1.0.schema.json. The 0.1 schema stays published at …/kimo-0.1.json (schema/kimo-0.1.schema.json, frozen).
Reference
@kimo/core-schema (packages/core-schema). Where this document and the implementation disagree, that is a bug in one of them; file an issue.
Plan of record
docs/teardown.md §10. Every place where this spec resolves an ambiguity in, or deviates from, that section is listed in §20.
License
MIT

kimo merge (the git merge driver, *.kimo merge=kimo, and the editor's merge of external changes) is semantic and three-way. It never drops a side silently: every disagreement it cannot settle becomes a stored conflict, and the file stays valid JSON that validates, renders and round-trips byte-stably. The editor loads it, lists the conflicts (board 18), applies sides live, and saving with open conflicts keeps them.

21.1 Root members

  • merge (struct MergeInfo, optional): base, ours, theirs — display labels (≤ 200 chars; git's %S/%X/%Y, e.g. "4e1a9c2", "main", "feat/pricing-v2") — and changes (integer ≥ 0: changes applied without conflict). Present only together with conflicts.
  • conflicts (record Id → Conflict, at least one entry): conflict ids use the node-id grammar. Serialized as a table (§15.1 rule 3) between merge and nodes: root order is …, assets, components, merge, conflicts, nodes.
  • Both require feature merge/conflicts. merge, conflicts and the feature are removed together when the merge is finished ("Mark resolved & save").
Keywords:
MUST
MUST NOT
SHOULD
MAY
set in small mono caps wherever they are normative

21.2 Conflict entries

Every entry has kind, applied and optional resolved, plus base / ours / theirs. A missing side means the value is absent on that side (property unset, node or variable deleted, themed entry missing): JSON has no undefined, so absence is encoded by omitting the key.

kindTarget fieldsSides holdBoard 18 row
propnode (Id), path (JSON Pointer inside the node, ≥ 1 token: /fill, /layout/gap, /args/label, /component/props/size)the property value"fill · both changed"
nodenode (Id), restored (optional record Id → Node)the whole node (Node schema)"node · deleted vs edited"
movenode (Id)Placement { parent, order, at? } — all three sides required"parent · moved twice"
variablevariable (local name), when (optional ThemeSelection)without when: the whole Variable; with when: the value of the themed entry whose when equals it exactly"$brand · light · both changed"
headerpath (JSON Pointer from the root into meta, themes, imports, assets, components or pages)the value at path—
prop
Target fields node (Id), path (JSON Pointer inside the node, ≥ 1 token: /fill, /layout/gap, /args/label, /component/props/size)
Sides hold the property value
Board 18 row "fill · both changed"
node
Target fields node (Id), restored (optional record Id → Node)
Sides hold the whole node (Node schema)
Board 18 row "node · deleted vs edited"
move
Target fields node (Id)
Sides hold Placement { parent, order, at? } — all three sides required
Board 18 row "parent · moved twice"
variable
Target fields variable (local name), when (optional ThemeSelection)
Sides hold without when: the whole Variable; with when: the value of the themed entry whose when equals it exactly
Board 18 row "$brand · light · both changed"
header
Target fields path (JSON Pointer from the root into meta, themes, imports, assets, components or pages)
Sides hold the value at path
Board 18 row —
  • applied (required): which side the document body currently holds — "ours", "theirs", "base", or "edited" when the user changed the target by hand after the merge.
  • resolved (boolean, default false): the user picked a side (or edited the target). Open conflicts are those not resolved.
  • A prop conflict MUST NOT address type (a type change is a node conflict) or parent/order/at (a move conflict).
  • node conflicts cover delete vs edit (one side absent) and type changes. restored holds snapshots of the other nodes that are in the body only because the target is kept — its descendants and any ancestors the deleting side removed — so that applying a side is reversible.
  • Entries are structs: kind is ranked (Appendix A); the other keys are not, so they follow by code unit and their subtrees are ordered purely by code unit (§15.2). This is deterministic and needs no change to Appendix A.

21.3 Validation

Structure: the Conflict union and MergeInfo are part of the JSON Schema ($defs/Conflict, $defs/MergeInfo, …). Semantic rules (whole-document runs only):

  • FEATURE_NOT_DECLARED when merge or conflicts is present without merge/conflicts;
  • MERGE_WITHOUT_CONFLICTS (error); CONFLICT_PATH_INVALID (error); CONFLICT_DUPLICATE (error: two entries on the same target, a node conflict and a prop/move conflict on the same node, a whole-variable and a themed-entry conflict on the same variable, or nested pointers);
  • whole-variable sides MUST be Variables (SCHEMA);
  • CONFLICT_OPEN (warning) for each unresolved entry — kimo validate in CI flags unfinished merges;
  • CONFLICT_STALE (warning) when the body no longer holds applied (including a prop/move target whose node is gone). This is deliberately a warning: a hand edit must never make the file open read-only.

21.4 Writing and resolving (normative)

  1. Body. The merged body holds ours for every conflict, except delete vs edit, where it holds the edited side (and restored); applied says so. The result MUST validate (no errors).
  2. Applying a side (applyConflictSide): prop sets or unsets the pointer (creating intermediate objects); node puts the side's node and every restored node back, or removes the node, its restored nodes and all their descendants; move sets parent/order/at; variable sets/deletes the variable, or sets/adds/removes the matching themed entry (never the last one); header sets/unsets the pointer. Then applied = the side and resolved = true. Applying another side afterwards restores that side exactly.
  3. Take all ours/theirs applies to unresolved entries only.
  4. Finishing (finishMerge) is refused while any entry is open; it removes merge, conflicts and the feature. The body already holds every choice.
  5. Saving with open conflicts writes them as they are. Editors keep autosave paused while any conflict is open; ⌘S writes.
  6. The three-way merge (contract ThreeWayMerge in @kimo/core-schema): inputs are the base, ours and theirs documents at the reader's format. Header members, variables (per themed entry when both sides kept the variable themed) and nodes (per property, by JSON Pointer into structs; records such as overrides, args, props, imports merge per key; arrays are atomic values) merge independently. A change on one side wins; equal changes on both sides are not conflicts; different changes become entries per 21.2. Placement changes (parent, order, at) merge as one unit (move). After merging, cycles are repaired per teardown §9.2 (the node whose move is later — ties: larger id — returns to its previous parent, or the root), and orphans whose parent was deleted on the other side become node conflicts on the parent with the orphans in restored. Asset manifests merge add-only.
  7. Git driver exit codes (MERGE_EXIT): 0 clean, 1 merged with stored conflicts (git marks the path conflicted), 2 not mergeable semantically (an input unparsable, invalid or newer): the driver leaves git's textual result, and the editor's failed-load banner explains the markers.

Fixtures: lint/CONFLICT_OPEN--board-18.kimo (board 18: 5 conflicts, 3 resolved), lint/CONFLICT_OPEN--every-kind.kimo, lint/CONFLICT_STALE--edited-by-hand.kimo, valid/merge-conflicts-resolved.kimo, and one invalid/ file per error code.