@rejot-dev/thalo 0.0.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.
Files changed (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/dist/ast/ast-types.d.ts +469 -0
  4. package/dist/ast/ast-types.d.ts.map +1 -0
  5. package/dist/ast/ast-types.js +11 -0
  6. package/dist/ast/ast-types.js.map +1 -0
  7. package/dist/ast/builder.js +158 -0
  8. package/dist/ast/builder.js.map +1 -0
  9. package/dist/ast/extract.js +748 -0
  10. package/dist/ast/extract.js.map +1 -0
  11. package/dist/ast/node-at-position.d.ts +147 -0
  12. package/dist/ast/node-at-position.d.ts.map +1 -0
  13. package/dist/ast/node-at-position.js +382 -0
  14. package/dist/ast/node-at-position.js.map +1 -0
  15. package/dist/ast/visitor.js +232 -0
  16. package/dist/ast/visitor.js.map +1 -0
  17. package/dist/checker/check.d.ts +53 -0
  18. package/dist/checker/check.d.ts.map +1 -0
  19. package/dist/checker/check.js +105 -0
  20. package/dist/checker/check.js.map +1 -0
  21. package/dist/checker/rules/actualize-missing-updated.js +34 -0
  22. package/dist/checker/rules/actualize-missing-updated.js.map +1 -0
  23. package/dist/checker/rules/actualize-unresolved-target.js +42 -0
  24. package/dist/checker/rules/actualize-unresolved-target.js.map +1 -0
  25. package/dist/checker/rules/alter-before-define.js +53 -0
  26. package/dist/checker/rules/alter-before-define.js.map +1 -0
  27. package/dist/checker/rules/alter-undefined-entity.js +32 -0
  28. package/dist/checker/rules/alter-undefined-entity.js.map +1 -0
  29. package/dist/checker/rules/create-requires-section.js +34 -0
  30. package/dist/checker/rules/create-requires-section.js.map +1 -0
  31. package/dist/checker/rules/define-entity-requires-section.js +31 -0
  32. package/dist/checker/rules/define-entity-requires-section.js.map +1 -0
  33. package/dist/checker/rules/duplicate-entity-definition.js +37 -0
  34. package/dist/checker/rules/duplicate-entity-definition.js.map +1 -0
  35. package/dist/checker/rules/duplicate-field-in-schema.js +38 -0
  36. package/dist/checker/rules/duplicate-field-in-schema.js.map +1 -0
  37. package/dist/checker/rules/duplicate-link-id.js +52 -0
  38. package/dist/checker/rules/duplicate-link-id.js.map +1 -0
  39. package/dist/checker/rules/duplicate-metadata-key.js +21 -0
  40. package/dist/checker/rules/duplicate-metadata-key.js.map +1 -0
  41. package/dist/checker/rules/duplicate-section-heading.js +41 -0
  42. package/dist/checker/rules/duplicate-section-heading.js.map +1 -0
  43. package/dist/checker/rules/duplicate-section-in-schema.js +38 -0
  44. package/dist/checker/rules/duplicate-section-in-schema.js.map +1 -0
  45. package/dist/checker/rules/duplicate-timestamp.js +104 -0
  46. package/dist/checker/rules/duplicate-timestamp.js.map +1 -0
  47. package/dist/checker/rules/empty-required-value.js +45 -0
  48. package/dist/checker/rules/empty-required-value.js.map +1 -0
  49. package/dist/checker/rules/empty-section.js +21 -0
  50. package/dist/checker/rules/empty-section.js.map +1 -0
  51. package/dist/checker/rules/invalid-date-range-value.js +56 -0
  52. package/dist/checker/rules/invalid-date-range-value.js.map +1 -0
  53. package/dist/checker/rules/invalid-default-value.js +86 -0
  54. package/dist/checker/rules/invalid-default-value.js.map +1 -0
  55. package/dist/checker/rules/invalid-field-type.js +45 -0
  56. package/dist/checker/rules/invalid-field-type.js.map +1 -0
  57. package/dist/checker/rules/missing-required-field.js +48 -0
  58. package/dist/checker/rules/missing-required-field.js.map +1 -0
  59. package/dist/checker/rules/missing-required-section.js +51 -0
  60. package/dist/checker/rules/missing-required-section.js.map +1 -0
  61. package/dist/checker/rules/missing-title.js +56 -0
  62. package/dist/checker/rules/missing-title.js.map +1 -0
  63. package/dist/checker/rules/remove-undefined-field.js +42 -0
  64. package/dist/checker/rules/remove-undefined-field.js.map +1 -0
  65. package/dist/checker/rules/remove-undefined-section.js +42 -0
  66. package/dist/checker/rules/remove-undefined-section.js.map +1 -0
  67. package/dist/checker/rules/rules.d.ts +71 -0
  68. package/dist/checker/rules/rules.d.ts.map +1 -0
  69. package/dist/checker/rules/rules.js +102 -0
  70. package/dist/checker/rules/rules.js.map +1 -0
  71. package/dist/checker/rules/synthesis-empty-query.js +35 -0
  72. package/dist/checker/rules/synthesis-empty-query.js.map +1 -0
  73. package/dist/checker/rules/synthesis-missing-prompt.js +42 -0
  74. package/dist/checker/rules/synthesis-missing-prompt.js.map +1 -0
  75. package/dist/checker/rules/synthesis-missing-sources.js +32 -0
  76. package/dist/checker/rules/synthesis-missing-sources.js.map +1 -0
  77. package/dist/checker/rules/synthesis-unknown-query-entity.js +39 -0
  78. package/dist/checker/rules/synthesis-unknown-query-entity.js.map +1 -0
  79. package/dist/checker/rules/timestamp-out-of-order.js +55 -0
  80. package/dist/checker/rules/timestamp-out-of-order.js.map +1 -0
  81. package/dist/checker/rules/unknown-entity.js +32 -0
  82. package/dist/checker/rules/unknown-entity.js.map +1 -0
  83. package/dist/checker/rules/unknown-field.js +40 -0
  84. package/dist/checker/rules/unknown-field.js.map +1 -0
  85. package/dist/checker/rules/unknown-section.js +47 -0
  86. package/dist/checker/rules/unknown-section.js.map +1 -0
  87. package/dist/checker/rules/unresolved-link.js +34 -0
  88. package/dist/checker/rules/unresolved-link.js.map +1 -0
  89. package/dist/checker/rules/update-without-create.js +65 -0
  90. package/dist/checker/rules/update-without-create.js.map +1 -0
  91. package/dist/checker/visitor.d.ts +69 -0
  92. package/dist/checker/visitor.d.ts.map +1 -0
  93. package/dist/checker/visitor.js +67 -0
  94. package/dist/checker/visitor.js.map +1 -0
  95. package/dist/checker/workspace-index.d.ts +50 -0
  96. package/dist/checker/workspace-index.d.ts.map +1 -0
  97. package/dist/checker/workspace-index.js +108 -0
  98. package/dist/checker/workspace-index.js.map +1 -0
  99. package/dist/commands/actualize.d.ts +113 -0
  100. package/dist/commands/actualize.d.ts.map +1 -0
  101. package/dist/commands/actualize.js +111 -0
  102. package/dist/commands/actualize.js.map +1 -0
  103. package/dist/commands/check.d.ts +65 -0
  104. package/dist/commands/check.d.ts.map +1 -0
  105. package/dist/commands/check.js +61 -0
  106. package/dist/commands/check.js.map +1 -0
  107. package/dist/commands/format.d.ts +90 -0
  108. package/dist/commands/format.d.ts.map +1 -0
  109. package/dist/commands/format.js +80 -0
  110. package/dist/commands/format.js.map +1 -0
  111. package/dist/commands/query.d.ts +152 -0
  112. package/dist/commands/query.d.ts.map +1 -0
  113. package/dist/commands/query.js +151 -0
  114. package/dist/commands/query.js.map +1 -0
  115. package/dist/constants.d.ts +31 -0
  116. package/dist/constants.d.ts.map +1 -0
  117. package/dist/constants.js +51 -0
  118. package/dist/constants.js.map +1 -0
  119. package/dist/files.d.ts +58 -0
  120. package/dist/files.d.ts.map +1 -0
  121. package/dist/files.js +103 -0
  122. package/dist/files.js.map +1 -0
  123. package/dist/formatters.d.ts +39 -0
  124. package/dist/formatters.d.ts.map +1 -0
  125. package/dist/formatters.js +200 -0
  126. package/dist/formatters.js.map +1 -0
  127. package/dist/fragment.d.ts +22 -0
  128. package/dist/fragment.d.ts.map +1 -0
  129. package/dist/git/git.js +240 -0
  130. package/dist/git/git.js.map +1 -0
  131. package/dist/merge/conflict-detector.d.ts +89 -0
  132. package/dist/merge/conflict-detector.d.ts.map +1 -0
  133. package/dist/merge/conflict-detector.js +352 -0
  134. package/dist/merge/conflict-detector.js.map +1 -0
  135. package/dist/merge/conflict-formatter.js +143 -0
  136. package/dist/merge/conflict-formatter.js.map +1 -0
  137. package/dist/merge/driver.d.ts +54 -0
  138. package/dist/merge/driver.d.ts.map +1 -0
  139. package/dist/merge/driver.js +112 -0
  140. package/dist/merge/driver.js.map +1 -0
  141. package/dist/merge/entry-matcher.d.ts +50 -0
  142. package/dist/merge/entry-matcher.d.ts.map +1 -0
  143. package/dist/merge/entry-matcher.js +141 -0
  144. package/dist/merge/entry-matcher.js.map +1 -0
  145. package/dist/merge/entry-merger.js +194 -0
  146. package/dist/merge/entry-merger.js.map +1 -0
  147. package/dist/merge/merge-result-builder.d.ts +62 -0
  148. package/dist/merge/merge-result-builder.d.ts.map +1 -0
  149. package/dist/merge/merge-result-builder.js +89 -0
  150. package/dist/merge/merge-result-builder.js.map +1 -0
  151. package/dist/mod.d.ts +31 -0
  152. package/dist/mod.js +23 -0
  153. package/dist/model/document.d.ts +134 -0
  154. package/dist/model/document.d.ts.map +1 -0
  155. package/dist/model/document.js +275 -0
  156. package/dist/model/document.js.map +1 -0
  157. package/dist/model/line-index.d.ts +85 -0
  158. package/dist/model/line-index.d.ts.map +1 -0
  159. package/dist/model/line-index.js +159 -0
  160. package/dist/model/line-index.js.map +1 -0
  161. package/dist/model/workspace.d.ts +296 -0
  162. package/dist/model/workspace.d.ts.map +1 -0
  163. package/dist/model/workspace.js +562 -0
  164. package/dist/model/workspace.js.map +1 -0
  165. package/dist/parser.js +27 -0
  166. package/dist/parser.js.map +1 -0
  167. package/dist/parser.native.d.ts +51 -0
  168. package/dist/parser.native.d.ts.map +1 -0
  169. package/dist/parser.native.js +62 -0
  170. package/dist/parser.native.js.map +1 -0
  171. package/dist/parser.shared.d.ts +99 -0
  172. package/dist/parser.shared.d.ts.map +1 -0
  173. package/dist/parser.shared.js +124 -0
  174. package/dist/parser.shared.js.map +1 -0
  175. package/dist/parser.web.d.ts +67 -0
  176. package/dist/parser.web.d.ts.map +1 -0
  177. package/dist/parser.web.js +49 -0
  178. package/dist/parser.web.js.map +1 -0
  179. package/dist/schema/registry.d.ts +108 -0
  180. package/dist/schema/registry.d.ts.map +1 -0
  181. package/dist/schema/registry.js +281 -0
  182. package/dist/schema/registry.js.map +1 -0
  183. package/dist/semantic/analyzer.d.ts +107 -0
  184. package/dist/semantic/analyzer.d.ts.map +1 -0
  185. package/dist/semantic/analyzer.js +261 -0
  186. package/dist/semantic/analyzer.js.map +1 -0
  187. package/dist/services/change-tracker/change-tracker.d.ts +111 -0
  188. package/dist/services/change-tracker/change-tracker.d.ts.map +1 -0
  189. package/dist/services/change-tracker/change-tracker.js +62 -0
  190. package/dist/services/change-tracker/change-tracker.js.map +1 -0
  191. package/dist/services/change-tracker/create-tracker.d.ts +42 -0
  192. package/dist/services/change-tracker/create-tracker.d.ts.map +1 -0
  193. package/dist/services/change-tracker/create-tracker.js +53 -0
  194. package/dist/services/change-tracker/create-tracker.js.map +1 -0
  195. package/dist/services/change-tracker/git-tracker.d.ts +59 -0
  196. package/dist/services/change-tracker/git-tracker.d.ts.map +1 -0
  197. package/dist/services/change-tracker/git-tracker.js +218 -0
  198. package/dist/services/change-tracker/git-tracker.js.map +1 -0
  199. package/dist/services/change-tracker/timestamp-tracker.d.ts +22 -0
  200. package/dist/services/change-tracker/timestamp-tracker.d.ts.map +1 -0
  201. package/dist/services/change-tracker/timestamp-tracker.js +74 -0
  202. package/dist/services/change-tracker/timestamp-tracker.js.map +1 -0
  203. package/dist/services/definition.d.ts +37 -0
  204. package/dist/services/definition.d.ts.map +1 -0
  205. package/dist/services/definition.js +43 -0
  206. package/dist/services/definition.js.map +1 -0
  207. package/dist/services/entity-navigation.d.ts +200 -0
  208. package/dist/services/entity-navigation.d.ts.map +1 -0
  209. package/dist/services/entity-navigation.js +211 -0
  210. package/dist/services/entity-navigation.js.map +1 -0
  211. package/dist/services/hover.d.ts +81 -0
  212. package/dist/services/hover.d.ts.map +1 -0
  213. package/dist/services/hover.js +669 -0
  214. package/dist/services/hover.js.map +1 -0
  215. package/dist/services/query.d.ts +116 -0
  216. package/dist/services/query.d.ts.map +1 -0
  217. package/dist/services/query.js +225 -0
  218. package/dist/services/query.js.map +1 -0
  219. package/dist/services/references.d.ts +52 -0
  220. package/dist/services/references.d.ts.map +1 -0
  221. package/dist/services/references.js +66 -0
  222. package/dist/services/references.js.map +1 -0
  223. package/dist/services/semantic-tokens.d.ts +54 -0
  224. package/dist/services/semantic-tokens.d.ts.map +1 -0
  225. package/dist/services/semantic-tokens.js +213 -0
  226. package/dist/services/semantic-tokens.js.map +1 -0
  227. package/dist/services/synthesis.d.ts +90 -0
  228. package/dist/services/synthesis.d.ts.map +1 -0
  229. package/dist/services/synthesis.js +113 -0
  230. package/dist/services/synthesis.js.map +1 -0
  231. package/dist/source-map.d.ts +42 -0
  232. package/dist/source-map.d.ts.map +1 -0
  233. package/dist/source-map.js +170 -0
  234. package/dist/source-map.js.map +1 -0
  235. package/package.json +128 -0
  236. package/tree-sitter-thalo.wasm +0 -0
  237. package/web-tree-sitter.wasm +0 -0
@@ -0,0 +1,112 @@
1
+ import { extractSourceFile } from "../ast/extract.js";
2
+ import { parseDocument } from "../parser.js";
3
+ import { matchEntries } from "./entry-matcher.js";
4
+ import { detectConflicts } from "./conflict-detector.js";
5
+ import { buildMergedResult } from "./merge-result-builder.js";
6
+
7
+ //#region src/merge/driver.ts
8
+ /**
9
+ * Perform three-way merge of thalo files
10
+ *
11
+ * This is the main entry point for the merge driver.
12
+ * It parses all three versions, matches entries, detects conflicts,
13
+ * and produces a merged result.
14
+ *
15
+ * @param base - Base version content (common ancestor)
16
+ * @param ours - Our version content (local changes)
17
+ * @param theirs - Their version content (incoming changes)
18
+ * @param options - Merge options
19
+ * @returns MergeResult with merged content and conflict information
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const base = '2026-01-01T00:00Z define-entity lore "Lore"';
24
+ * const ours = base + '\n2026-01-02T00:00Z create lore "My entry" ^entry1';
25
+ * const theirs = base + '\n2026-01-03T00:00Z create lore "Their entry" ^entry2';
26
+ *
27
+ * const result = mergeThaloFiles(base, ours, theirs);
28
+ * console.log(result.success); // true
29
+ * console.log(result.content); // Merged content with both entries
30
+ * ```
31
+ */
32
+ function mergeThaloFiles(base, ours, theirs, options = {}) {
33
+ try {
34
+ const baseDoc = parseDocument(base, { fileType: "thalo" });
35
+ const oursDoc = parseDocument(ours, { fileType: "thalo" });
36
+ const theirsDoc = parseDocument(theirs, { fileType: "thalo" });
37
+ const baseAst = baseDoc.blocks.length > 0 ? extractSourceFile(baseDoc.blocks[0].tree.rootNode) : {
38
+ entries: [],
39
+ syntaxErrors: []
40
+ };
41
+ const oursAst = oursDoc.blocks.length > 0 ? extractSourceFile(oursDoc.blocks[0].tree.rootNode) : {
42
+ entries: [],
43
+ syntaxErrors: []
44
+ };
45
+ const theirsAst = theirsDoc.blocks.length > 0 ? extractSourceFile(theirsDoc.blocks[0].tree.rootNode) : {
46
+ entries: [],
47
+ syntaxErrors: []
48
+ };
49
+ const syntaxErrorConflicts = [];
50
+ if (baseAst.syntaxErrors.length > 0) syntaxErrorConflicts.push({
51
+ type: "parse-error",
52
+ message: `Parse error in base: ${baseAst.syntaxErrors.map((e) => e.message || "syntax error").join(", ")}`,
53
+ location: 0,
54
+ identity: { entryType: "parse-error" },
55
+ context: { errorMessage: "base version has syntax errors" }
56
+ });
57
+ if (oursAst.syntaxErrors.length > 0) syntaxErrorConflicts.push({
58
+ type: "parse-error",
59
+ message: `Parse error in ours: ${oursAst.syntaxErrors.map((e) => e.message || "syntax error").join(", ")}`,
60
+ location: 0,
61
+ identity: { entryType: "parse-error" },
62
+ context: { errorMessage: "ours version has syntax errors" }
63
+ });
64
+ if (theirsAst.syntaxErrors.length > 0) syntaxErrorConflicts.push({
65
+ type: "parse-error",
66
+ message: `Parse error in theirs: ${theirsAst.syntaxErrors.map((e) => e.message || "syntax error").join(", ")}`,
67
+ location: 0,
68
+ identity: { entryType: "parse-error" },
69
+ context: { errorMessage: "theirs version has syntax errors" }
70
+ });
71
+ if (syntaxErrorConflicts.length > 0) return {
72
+ success: false,
73
+ content: ours,
74
+ conflicts: syntaxErrorConflicts,
75
+ stats: {
76
+ totalEntries: 0,
77
+ oursOnly: 0,
78
+ theirsOnly: 0,
79
+ common: 0,
80
+ autoMerged: 0,
81
+ conflicts: syntaxErrorConflicts.length
82
+ }
83
+ };
84
+ const matches = matchEntries(baseAst.entries, oursAst.entries, theirsAst.entries);
85
+ return buildMergedResult(matches, detectConflicts(matches, options), options);
86
+ } catch (error) {
87
+ const errorMessage = error instanceof Error ? error.message : String(error);
88
+ return {
89
+ success: false,
90
+ content: ours,
91
+ conflicts: [{
92
+ type: "merge-error",
93
+ message: `Merge failed: ${errorMessage}`,
94
+ location: 0,
95
+ identity: { entryType: "error" },
96
+ context: { errorMessage }
97
+ }],
98
+ stats: {
99
+ totalEntries: 0,
100
+ oursOnly: 0,
101
+ theirsOnly: 0,
102
+ common: 0,
103
+ autoMerged: 0,
104
+ conflicts: 1
105
+ }
106
+ };
107
+ }
108
+ }
109
+
110
+ //#endregion
111
+ export { mergeThaloFiles };
112
+ //# sourceMappingURL=driver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driver.js","names":["syntaxErrorConflicts: MergeConflict[]"],"sources":["../../src/merge/driver.ts"],"sourcesContent":["import { parseDocument } from \"../parser.js\";\nimport { extractSourceFile } from \"../ast/extract.js\";\nimport type { MergeResult } from \"./merge-result-builder.js\";\nimport type { MergeConflict, ConflictRule } from \"./conflict-detector.js\";\nimport { matchEntries } from \"./entry-matcher.js\";\nimport { detectConflicts } from \"./conflict-detector.js\";\nimport { buildMergedResult } from \"./merge-result-builder.js\";\n\n/**\n * Options for the merge driver\n */\nexport interface MergeOptions {\n /**\n * Conflict marker style\n * - \"git\": Standard Git style (ours/theirs)\n * - \"diff3\": Include base section\n */\n markerStyle?: \"git\" | \"diff3\";\n\n /**\n * Whether to include base in markers (diff3 style)\n * Deprecated: Use markerStyle: \"diff3\" instead\n */\n showBase?: boolean;\n\n /**\n * Custom conflict detection rules\n * Applied after default rules\n */\n conflictRules?: ConflictRule[];\n}\n\n/**\n * Perform three-way merge of thalo files\n *\n * This is the main entry point for the merge driver.\n * It parses all three versions, matches entries, detects conflicts,\n * and produces a merged result.\n *\n * @param base - Base version content (common ancestor)\n * @param ours - Our version content (local changes)\n * @param theirs - Their version content (incoming changes)\n * @param options - Merge options\n * @returns MergeResult with merged content and conflict information\n *\n * @example\n * ```typescript\n * const base = '2026-01-01T00:00Z define-entity lore \"Lore\"';\n * const ours = base + '\\n2026-01-02T00:00Z create lore \"My entry\" ^entry1';\n * const theirs = base + '\\n2026-01-03T00:00Z create lore \"Their entry\" ^entry2';\n *\n * const result = mergeThaloFiles(base, ours, theirs);\n * console.log(result.success); // true\n * console.log(result.content); // Merged content with both entries\n * ```\n */\nexport function mergeThaloFiles(\n base: string,\n ours: string,\n theirs: string,\n options: MergeOptions = {},\n): MergeResult {\n try {\n const baseDoc = parseDocument(base, { fileType: \"thalo\" });\n const oursDoc = parseDocument(ours, { fileType: \"thalo\" });\n const theirsDoc = parseDocument(theirs, { fileType: \"thalo\" });\n\n const baseAst =\n baseDoc.blocks.length > 0\n ? extractSourceFile(baseDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n const oursAst =\n oursDoc.blocks.length > 0\n ? extractSourceFile(oursDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n const theirsAst =\n theirsDoc.blocks.length > 0\n ? extractSourceFile(theirsDoc.blocks[0].tree.rootNode)\n : { entries: [], syntaxErrors: [] };\n\n // Surface syntax errors as parse-error conflicts\n const syntaxErrorConflicts: MergeConflict[] = [];\n if (baseAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in base: ${baseAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"base version has syntax errors\" },\n });\n }\n if (oursAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in ours: ${oursAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"ours version has syntax errors\" },\n });\n }\n if (theirsAst.syntaxErrors.length > 0) {\n syntaxErrorConflicts.push({\n type: \"parse-error\",\n message: `Parse error in theirs: ${theirsAst.syntaxErrors.map((e) => e.message || \"syntax error\").join(\", \")}`,\n location: 0,\n identity: { entryType: \"parse-error\" },\n context: { errorMessage: \"theirs version has syntax errors\" },\n });\n }\n\n if (syntaxErrorConflicts.length > 0) {\n return {\n success: false,\n content: ours,\n conflicts: syntaxErrorConflicts,\n stats: {\n totalEntries: 0,\n oursOnly: 0,\n theirsOnly: 0,\n common: 0,\n autoMerged: 0,\n conflicts: syntaxErrorConflicts.length,\n },\n };\n }\n\n const matches = matchEntries(baseAst.entries, oursAst.entries, theirsAst.entries);\n\n const conflicts = detectConflicts(matches, options);\n\n const result = buildMergedResult(matches, conflicts, options);\n\n return result;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n success: false,\n content: ours,\n conflicts: [\n {\n type: \"merge-error\",\n message: `Merge failed: ${errorMessage}`,\n location: 0,\n identity: { entryType: \"error\" },\n context: { errorMessage },\n },\n ],\n stats: {\n totalEntries: 0,\n oursOnly: 0,\n theirsOnly: 0,\n common: 0,\n autoMerged: 0,\n conflicts: 1,\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAgB,gBACd,MACA,MACA,QACA,UAAwB,EAAE,EACb;AACb,KAAI;EACF,MAAM,UAAU,cAAc,MAAM,EAAE,UAAU,SAAS,CAAC;EAC1D,MAAM,UAAU,cAAc,MAAM,EAAE,UAAU,SAAS,CAAC;EAC1D,MAAM,YAAY,cAAc,QAAQ,EAAE,UAAU,SAAS,CAAC;EAE9D,MAAM,UACJ,QAAQ,OAAO,SAAS,IACpB,kBAAkB,QAAQ,OAAO,GAAG,KAAK,SAAS,GAClD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EACvC,MAAM,UACJ,QAAQ,OAAO,SAAS,IACpB,kBAAkB,QAAQ,OAAO,GAAG,KAAK,SAAS,GAClD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EACvC,MAAM,YACJ,UAAU,OAAO,SAAS,IACtB,kBAAkB,UAAU,OAAO,GAAG,KAAK,SAAS,GACpD;GAAE,SAAS,EAAE;GAAE,cAAc,EAAE;GAAE;EAGvC,MAAMA,uBAAwC,EAAE;AAChD,MAAI,QAAQ,aAAa,SAAS,EAChC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,wBAAwB,QAAQ,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GACxG,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,kCAAkC;GAC5D,CAAC;AAEJ,MAAI,QAAQ,aAAa,SAAS,EAChC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,wBAAwB,QAAQ,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GACxG,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,kCAAkC;GAC5D,CAAC;AAEJ,MAAI,UAAU,aAAa,SAAS,EAClC,sBAAqB,KAAK;GACxB,MAAM;GACN,SAAS,0BAA0B,UAAU,aAAa,KAAK,MAAM,EAAE,WAAW,eAAe,CAAC,KAAK,KAAK;GAC5G,UAAU;GACV,UAAU,EAAE,WAAW,eAAe;GACtC,SAAS,EAAE,cAAc,oCAAoC;GAC9D,CAAC;AAGJ,MAAI,qBAAqB,SAAS,EAChC,QAAO;GACL,SAAS;GACT,SAAS;GACT,WAAW;GACX,OAAO;IACL,cAAc;IACd,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,WAAW,qBAAqB;IACjC;GACF;EAGH,MAAM,UAAU,aAAa,QAAQ,SAAS,QAAQ,SAAS,UAAU,QAAQ;AAMjF,SAFe,kBAAkB,SAFf,gBAAgB,SAAS,QAAQ,EAEE,QAAQ;UAGtD,OAAO;EACd,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,SAAO;GACL,SAAS;GACT,SAAS;GACT,WAAW,CACT;IACE,MAAM;IACN,SAAS,iBAAiB;IAC1B,UAAU;IACV,UAAU,EAAE,WAAW,SAAS;IAChC,SAAS,EAAE,cAAc;IAC1B,CACF;GACD,OAAO;IACL,cAAc;IACd,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,WAAW;IACZ;GACF"}
@@ -0,0 +1,50 @@
1
+ import { Entry } from "../ast/ast-types.js";
2
+
3
+ //#region src/merge/entry-matcher.d.ts
4
+
5
+ /**
6
+ * Identity used to match entries across versions
7
+ */
8
+ interface EntryIdentity {
9
+ /**
10
+ * Primary identity: Explicit link ID (^link-id)
11
+ * Used when available for matching
12
+ */
13
+ linkId?: string;
14
+ /**
15
+ * Fallback identity: Timestamp for entries without explicit links
16
+ * Combined with entryType for uniqueness
17
+ */
18
+ timestamp?: string;
19
+ /**
20
+ * Entry type (for validation and fallback matching)
21
+ */
22
+ entryType: string;
23
+ }
24
+ /**
25
+ * A matched set of entries across three versions
26
+ */
27
+ interface EntryMatch {
28
+ /**
29
+ * Identity used to match this entry across versions
30
+ */
31
+ identity: EntryIdentity;
32
+ /**
33
+ * Entry from base version (common ancestor)
34
+ * null if entry was added in ours or theirs
35
+ */
36
+ base: Entry | null;
37
+ /**
38
+ * Entry from ours version (local/current)
39
+ * null if entry was deleted in ours
40
+ */
41
+ ours: Entry | null;
42
+ /**
43
+ * Entry from theirs version (incoming)
44
+ * null if entry was deleted in theirs
45
+ */
46
+ theirs: Entry | null;
47
+ }
48
+ //#endregion
49
+ export { EntryIdentity, EntryMatch };
50
+ //# sourceMappingURL=entry-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-matcher.d.ts","names":[],"sources":["../../src/merge/entry-matcher.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AAsBiB,UAtBA,aAAA,CAsBU;EAIf;;;;EAkBG,MAAA,CAAA,EAAA,MAAA;;;;;;;;;;;;;;UAtBE,UAAA;;;;YAIL;;;;;QAMJ;;;;;QAMA;;;;;UAME"}
@@ -0,0 +1,141 @@
1
+ //#region src/merge/entry-matcher.ts
2
+ /**
3
+ * Match entries across three versions based on identity
4
+ *
5
+ * Algorithm:
6
+ * 1. Build identity map for each version (link ID or timestamp)
7
+ * 2. Compute union of all identities
8
+ * 3. For each identity, find corresponding entry in each version
9
+ * 4. Return matched triplets
10
+ *
11
+ * @param base - Entries from base version (common ancestor)
12
+ * @param ours - Entries from ours version (local/current)
13
+ * @param theirs - Entries from theirs version (incoming)
14
+ * @returns Array of matched entry triplets
15
+ */
16
+ function matchEntries(base, ours, theirs) {
17
+ const baseMap = buildIdentityMap(base, "base");
18
+ const oursMap = buildIdentityMap(ours, "ours");
19
+ const theirsMap = buildIdentityMap(theirs, "theirs");
20
+ const allIdentities = /* @__PURE__ */ new Set();
21
+ for (const id of baseMap.keys()) allIdentities.add(id);
22
+ for (const id of oursMap.keys()) allIdentities.add(id);
23
+ for (const id of theirsMap.keys()) allIdentities.add(id);
24
+ const matches = [];
25
+ for (const idKey of allIdentities) {
26
+ const identity = parseIdentityKey(idKey);
27
+ matches.push({
28
+ identity,
29
+ base: baseMap.get(idKey) || null,
30
+ ours: oursMap.get(idKey) || null,
31
+ theirs: theirsMap.get(idKey) || null
32
+ });
33
+ }
34
+ return matches;
35
+ }
36
+ /**
37
+ * Build a map from identity key to entry
38
+ *
39
+ * Precondition: Each version must not contain duplicate identities.
40
+ * Callers should validate inputs via the checker module's duplicate-link-id
41
+ * and duplicate-timestamp rules before invoking the merge driver.
42
+ *
43
+ * @param entries - Array of entries to map
44
+ * @param versionName - Name of version for error messages (e.g., "base", "ours", "theirs")
45
+ * @returns Map from serialized identity to entry
46
+ * @throws Error if duplicate identity is found within the version
47
+ */
48
+ function buildIdentityMap(entries, versionName) {
49
+ const map = /* @__PURE__ */ new Map();
50
+ for (const entry of entries) {
51
+ const key = serializeIdentity(getEntryIdentity(entry));
52
+ if (map.has(key)) throw new Error(`Duplicate identity '${key}' found in ${versionName} version. Ensure inputs are validated via checker rules before merging.`);
53
+ map.set(key, entry);
54
+ }
55
+ return map;
56
+ }
57
+ /**
58
+ * Extract identity from an entry
59
+ *
60
+ * Priority:
61
+ * 1. Explicit link ID (^link-id) from header
62
+ * 2. Timestamp for entries without link IDs
63
+ *
64
+ * @param entry - Entry to extract identity from
65
+ * @returns Entry identity
66
+ */
67
+ function getEntryIdentity(entry) {
68
+ if (entry.type === "instance_entry" && entry.header.link) return {
69
+ linkId: entry.header.link.id,
70
+ entryType: entry.type
71
+ };
72
+ if (entry.type === "schema_entry" && entry.header.link) return {
73
+ linkId: entry.header.link.id,
74
+ entryType: entry.type
75
+ };
76
+ if (entry.type === "synthesis_entry") return {
77
+ linkId: entry.header.linkId.id,
78
+ entryType: entry.type
79
+ };
80
+ if (entry.type === "actualize_entry") return {
81
+ linkId: entry.header.target.id,
82
+ entryType: entry.type
83
+ };
84
+ return {
85
+ timestamp: getEntryTimestamp(entry) || void 0,
86
+ entryType: entry.type
87
+ };
88
+ }
89
+ /**
90
+ * Get timestamp string from an entry
91
+ *
92
+ * @param entry - Entry to extract timestamp from
93
+ * @returns Timestamp string or null
94
+ */
95
+ function getEntryTimestamp(entry) {
96
+ switch (entry.type) {
97
+ case "instance_entry":
98
+ case "schema_entry":
99
+ case "synthesis_entry":
100
+ case "actualize_entry": return entry.header.timestamp.value;
101
+ default: return null;
102
+ }
103
+ }
104
+ /**
105
+ * Serialize identity to a unique key string
106
+ *
107
+ * @param identity - Identity to serialize
108
+ * @returns Unique key string
109
+ */
110
+ function serializeIdentity(identity) {
111
+ if (identity.linkId) return `link:${identity.linkId}`;
112
+ if (identity.timestamp) return `ts:${identity.timestamp}:${identity.entryType}`;
113
+ return `type:${identity.entryType}`;
114
+ }
115
+ /**
116
+ * Parse identity key back to EntryIdentity
117
+ *
118
+ * @param key - Serialized identity key
119
+ * @returns Parsed identity
120
+ */
121
+ function parseIdentityKey(key) {
122
+ if (key.startsWith("link:")) return {
123
+ linkId: key.slice(5),
124
+ entryType: "unknown"
125
+ };
126
+ if (key.startsWith("ts:")) {
127
+ const withoutPrefix = key.slice(3);
128
+ const lastColonIndex = withoutPrefix.lastIndexOf(":");
129
+ if (lastColonIndex === -1) throw new Error(`Invalid timestamp identity key: ${key}`);
130
+ return {
131
+ timestamp: withoutPrefix.slice(0, lastColonIndex),
132
+ entryType: withoutPrefix.slice(lastColonIndex + 1)
133
+ };
134
+ }
135
+ if (key.startsWith("type:")) return { entryType: key.slice(5) };
136
+ throw new Error(`Invalid identity key: ${key}`);
137
+ }
138
+
139
+ //#endregion
140
+ export { getEntryIdentity, matchEntries, serializeIdentity };
141
+ //# sourceMappingURL=entry-matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-matcher.js","names":["matches: EntryMatch[]"],"sources":["../../src/merge/entry-matcher.ts"],"sourcesContent":["import type { Entry } from \"../ast/ast-types.js\";\n\n/**\n * Identity used to match entries across versions\n */\nexport interface EntryIdentity {\n /**\n * Primary identity: Explicit link ID (^link-id)\n * Used when available for matching\n */\n linkId?: string;\n\n /**\n * Fallback identity: Timestamp for entries without explicit links\n * Combined with entryType for uniqueness\n */\n timestamp?: string;\n\n /**\n * Entry type (for validation and fallback matching)\n */\n entryType: string;\n}\n\n/**\n * A matched set of entries across three versions\n */\nexport interface EntryMatch {\n /**\n * Identity used to match this entry across versions\n */\n identity: EntryIdentity;\n\n /**\n * Entry from base version (common ancestor)\n * null if entry was added in ours or theirs\n */\n base: Entry | null;\n\n /**\n * Entry from ours version (local/current)\n * null if entry was deleted in ours\n */\n ours: Entry | null;\n\n /**\n * Entry from theirs version (incoming)\n * null if entry was deleted in theirs\n */\n theirs: Entry | null;\n}\n\n/**\n * Match entries across three versions based on identity\n *\n * Algorithm:\n * 1. Build identity map for each version (link ID or timestamp)\n * 2. Compute union of all identities\n * 3. For each identity, find corresponding entry in each version\n * 4. Return matched triplets\n *\n * @param base - Entries from base version (common ancestor)\n * @param ours - Entries from ours version (local/current)\n * @param theirs - Entries from theirs version (incoming)\n * @returns Array of matched entry triplets\n */\nexport function matchEntries(base: Entry[], ours: Entry[], theirs: Entry[]): EntryMatch[] {\n const baseMap = buildIdentityMap(base, \"base\");\n const oursMap = buildIdentityMap(ours, \"ours\");\n const theirsMap = buildIdentityMap(theirs, \"theirs\");\n\n const allIdentities = new Set<string>();\n for (const id of baseMap.keys()) {\n allIdentities.add(id);\n }\n for (const id of oursMap.keys()) {\n allIdentities.add(id);\n }\n for (const id of theirsMap.keys()) {\n allIdentities.add(id);\n }\n\n const matches: EntryMatch[] = [];\n for (const idKey of allIdentities) {\n const identity = parseIdentityKey(idKey);\n matches.push({\n identity,\n base: baseMap.get(idKey) || null,\n ours: oursMap.get(idKey) || null,\n theirs: theirsMap.get(idKey) || null,\n });\n }\n\n return matches;\n}\n\n/**\n * Build a map from identity key to entry\n *\n * Precondition: Each version must not contain duplicate identities.\n * Callers should validate inputs via the checker module's duplicate-link-id\n * and duplicate-timestamp rules before invoking the merge driver.\n *\n * @param entries - Array of entries to map\n * @param versionName - Name of version for error messages (e.g., \"base\", \"ours\", \"theirs\")\n * @returns Map from serialized identity to entry\n * @throws Error if duplicate identity is found within the version\n */\nfunction buildIdentityMap(entries: Entry[], versionName: string): Map<string, Entry> {\n const map = new Map<string, Entry>();\n\n for (const entry of entries) {\n const identity = getEntryIdentity(entry);\n const key = serializeIdentity(identity);\n\n if (map.has(key)) {\n throw new Error(\n `Duplicate identity '${key}' found in ${versionName} version. ` +\n `Ensure inputs are validated via checker rules before merging.`,\n );\n }\n\n map.set(key, entry);\n }\n\n return map;\n}\n\n/**\n * Extract identity from an entry\n *\n * Priority:\n * 1. Explicit link ID (^link-id) from header\n * 2. Timestamp for entries without link IDs\n *\n * @param entry - Entry to extract identity from\n * @returns Entry identity\n */\nexport function getEntryIdentity(entry: Entry): EntryIdentity {\n if (entry.type === \"instance_entry\" && entry.header.link) {\n return {\n linkId: entry.header.link.id,\n entryType: entry.type,\n };\n }\n\n if (entry.type === \"schema_entry\" && entry.header.link) {\n return {\n linkId: entry.header.link.id,\n entryType: entry.type,\n };\n }\n\n if (entry.type === \"synthesis_entry\") {\n return {\n linkId: entry.header.linkId.id,\n entryType: entry.type,\n };\n }\n\n if (entry.type === \"actualize_entry\") {\n return {\n linkId: entry.header.target.id,\n entryType: entry.type,\n };\n }\n\n const timestamp = getEntryTimestamp(entry);\n return {\n timestamp: timestamp || undefined,\n entryType: entry.type,\n };\n}\n\n/**\n * Get timestamp string from an entry\n *\n * @param entry - Entry to extract timestamp from\n * @returns Timestamp string or null\n */\nfunction getEntryTimestamp(entry: Entry): string | null {\n switch (entry.type) {\n case \"instance_entry\":\n case \"schema_entry\":\n case \"synthesis_entry\":\n case \"actualize_entry\":\n return entry.header.timestamp.value;\n default:\n return null;\n }\n}\n\n/**\n * Serialize identity to a unique key string\n *\n * @param identity - Identity to serialize\n * @returns Unique key string\n */\nexport function serializeIdentity(identity: EntryIdentity): string {\n if (identity.linkId) {\n return `link:${identity.linkId}`;\n }\n\n if (identity.timestamp) {\n return `ts:${identity.timestamp}:${identity.entryType}`;\n }\n\n // Fallback for synthetic identities (e.g., error conflicts)\n return `type:${identity.entryType}`;\n}\n\n/**\n * Parse identity key back to EntryIdentity\n *\n * @param key - Serialized identity key\n * @returns Parsed identity\n */\nfunction parseIdentityKey(key: string): EntryIdentity {\n if (key.startsWith(\"link:\")) {\n const linkId = key.slice(5);\n return {\n linkId,\n entryType: \"unknown\",\n };\n }\n\n if (key.startsWith(\"ts:\")) {\n const withoutPrefix = key.slice(3);\n const lastColonIndex = withoutPrefix.lastIndexOf(\":\");\n if (lastColonIndex === -1) {\n throw new Error(`Invalid timestamp identity key: ${key}`);\n }\n const timestamp = withoutPrefix.slice(0, lastColonIndex);\n const entryType = withoutPrefix.slice(lastColonIndex + 1);\n return {\n timestamp,\n entryType,\n };\n }\n\n if (key.startsWith(\"type:\")) {\n const entryType = key.slice(5);\n return {\n entryType,\n };\n }\n\n throw new Error(`Invalid identity key: ${key}`);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkEA,SAAgB,aAAa,MAAe,MAAe,QAA+B;CACxF,MAAM,UAAU,iBAAiB,MAAM,OAAO;CAC9C,MAAM,UAAU,iBAAiB,MAAM,OAAO;CAC9C,MAAM,YAAY,iBAAiB,QAAQ,SAAS;CAEpD,MAAM,gCAAgB,IAAI,KAAa;AACvC,MAAK,MAAM,MAAM,QAAQ,MAAM,CAC7B,eAAc,IAAI,GAAG;AAEvB,MAAK,MAAM,MAAM,QAAQ,MAAM,CAC7B,eAAc,IAAI,GAAG;AAEvB,MAAK,MAAM,MAAM,UAAU,MAAM,CAC/B,eAAc,IAAI,GAAG;CAGvB,MAAMA,UAAwB,EAAE;AAChC,MAAK,MAAM,SAAS,eAAe;EACjC,MAAM,WAAW,iBAAiB,MAAM;AACxC,UAAQ,KAAK;GACX;GACA,MAAM,QAAQ,IAAI,MAAM,IAAI;GAC5B,MAAM,QAAQ,IAAI,MAAM,IAAI;GAC5B,QAAQ,UAAU,IAAI,MAAM,IAAI;GACjC,CAAC;;AAGJ,QAAO;;;;;;;;;;;;;;AAeT,SAAS,iBAAiB,SAAkB,aAAyC;CACnF,MAAM,sBAAM,IAAI,KAAoB;AAEpC,MAAK,MAAM,SAAS,SAAS;EAE3B,MAAM,MAAM,kBADK,iBAAiB,MAAM,CACD;AAEvC,MAAI,IAAI,IAAI,IAAI,CACd,OAAM,IAAI,MACR,uBAAuB,IAAI,aAAa,YAAY,yEAErD;AAGH,MAAI,IAAI,KAAK,MAAM;;AAGrB,QAAO;;;;;;;;;;;;AAaT,SAAgB,iBAAiB,OAA6B;AAC5D,KAAI,MAAM,SAAS,oBAAoB,MAAM,OAAO,KAClD,QAAO;EACL,QAAQ,MAAM,OAAO,KAAK;EAC1B,WAAW,MAAM;EAClB;AAGH,KAAI,MAAM,SAAS,kBAAkB,MAAM,OAAO,KAChD,QAAO;EACL,QAAQ,MAAM,OAAO,KAAK;EAC1B,WAAW,MAAM;EAClB;AAGH,KAAI,MAAM,SAAS,kBACjB,QAAO;EACL,QAAQ,MAAM,OAAO,OAAO;EAC5B,WAAW,MAAM;EAClB;AAGH,KAAI,MAAM,SAAS,kBACjB,QAAO;EACL,QAAQ,MAAM,OAAO,OAAO;EAC5B,WAAW,MAAM;EAClB;AAIH,QAAO;EACL,WAFgB,kBAAkB,MAAM,IAEhB;EACxB,WAAW,MAAM;EAClB;;;;;;;;AASH,SAAS,kBAAkB,OAA6B;AACtD,SAAQ,MAAM,MAAd;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,kBACH,QAAO,MAAM,OAAO,UAAU;EAChC,QACE,QAAO;;;;;;;;;AAUb,SAAgB,kBAAkB,UAAiC;AACjE,KAAI,SAAS,OACX,QAAO,QAAQ,SAAS;AAG1B,KAAI,SAAS,UACX,QAAO,MAAM,SAAS,UAAU,GAAG,SAAS;AAI9C,QAAO,QAAQ,SAAS;;;;;;;;AAS1B,SAAS,iBAAiB,KAA4B;AACpD,KAAI,IAAI,WAAW,QAAQ,CAEzB,QAAO;EACL,QAFa,IAAI,MAAM,EAAE;EAGzB,WAAW;EACZ;AAGH,KAAI,IAAI,WAAW,MAAM,EAAE;EACzB,MAAM,gBAAgB,IAAI,MAAM,EAAE;EAClC,MAAM,iBAAiB,cAAc,YAAY,IAAI;AACrD,MAAI,mBAAmB,GACrB,OAAM,IAAI,MAAM,mCAAmC,MAAM;AAI3D,SAAO;GACL,WAHgB,cAAc,MAAM,GAAG,eAAe;GAItD,WAHgB,cAAc,MAAM,iBAAiB,EAAE;GAIxD;;AAGH,KAAI,IAAI,WAAW,QAAQ,CAEzB,QAAO,EACL,WAFgB,IAAI,MAAM,EAAE,EAG7B;AAGH,OAAM,IAAI,MAAM,yBAAyB,MAAM"}
@@ -0,0 +1,194 @@
1
+ //#region src/merge/entry-merger.ts
2
+ /**
3
+ * Merge a single entry match (assumes no conflicts)
4
+ *
5
+ * Handles various merge scenarios:
6
+ * - Additions (one side only)
7
+ * - Deletions (missing on one side)
8
+ * - Single-side modifications
9
+ * - Both-side modifications with fine-grained merge
10
+ *
11
+ * @param match - The entry match to merge
12
+ * @returns Merged entry, or null if deleted
13
+ */
14
+ function mergeEntry(match) {
15
+ const { base, ours, theirs } = match;
16
+ if (!base && ours && !theirs) return ours;
17
+ if (!base && !ours && theirs) return theirs;
18
+ if (base && !ours && theirs) return null;
19
+ if (base && ours && !theirs) return null;
20
+ if (base && ours && theirs) {
21
+ const oursChanged = !entriesEqual(base, ours);
22
+ const theirsChanged = !entriesEqual(base, theirs);
23
+ if (oursChanged && !theirsChanged) return ours;
24
+ if (!oursChanged && theirsChanged) return theirs;
25
+ if (oursChanged && theirsChanged) return mergeEntryChanges(base, ours, theirs);
26
+ return base;
27
+ }
28
+ return ours || theirs || base;
29
+ }
30
+ /**
31
+ * Fine-grained merge of entry changes
32
+ *
33
+ * For instance/synthesis entries, merge metadata and content independently
34
+ */
35
+ function mergeEntryChanges(base, ours, theirs) {
36
+ if (ours.type === "instance_entry" && theirs.type === "instance_entry") {
37
+ const baseMetadata = base.type === "instance_entry" || base.type === "synthesis_entry" || base.type === "actualize_entry" ? base.metadata : [];
38
+ const baseContent = base.type === "instance_entry" || base.type === "synthesis_entry" ? base.content : null;
39
+ return {
40
+ ...ours,
41
+ metadata: mergeMetadata(baseMetadata, ours.metadata, theirs.metadata),
42
+ content: mergeContent(baseContent, ours.content, theirs.content)
43
+ };
44
+ }
45
+ if (ours.type === "synthesis_entry" && theirs.type === "synthesis_entry") {
46
+ const baseMetadata = base.type === "instance_entry" || base.type === "synthesis_entry" || base.type === "actualize_entry" ? base.metadata : [];
47
+ const baseContent = base.type === "instance_entry" || base.type === "synthesis_entry" ? base.content : null;
48
+ return {
49
+ ...ours,
50
+ metadata: mergeMetadata(baseMetadata, ours.metadata, theirs.metadata),
51
+ content: mergeContent(baseContent, ours.content, theirs.content)
52
+ };
53
+ }
54
+ if (ours.type === "actualize_entry" && theirs.type === "actualize_entry") {
55
+ const baseMetadata = base.type === "instance_entry" || base.type === "synthesis_entry" || base.type === "actualize_entry" ? base.metadata : [];
56
+ return {
57
+ ...ours,
58
+ metadata: mergeMetadata(baseMetadata, ours.metadata, theirs.metadata)
59
+ };
60
+ }
61
+ return ours;
62
+ }
63
+ /**
64
+ * Merge metadata lists (non-conflicting keys)
65
+ *
66
+ * Strategy: Take the changed version for each key
67
+ */
68
+ function mergeMetadata(base, ours, theirs) {
69
+ const baseMap = new Map(base.map((m) => [m.key.value, m]));
70
+ const oursMap = new Map(ours.map((m) => [m.key.value, m]));
71
+ const theirsMap = new Map(theirs.map((m) => [m.key.value, m]));
72
+ const allKeys = new Set([
73
+ ...baseMap.keys(),
74
+ ...oursMap.keys(),
75
+ ...theirsMap.keys()
76
+ ]);
77
+ const merged = [];
78
+ for (const key of allKeys) {
79
+ const baseVal = baseMap.get(key);
80
+ const oursVal = oursMap.get(key);
81
+ const theirsVal = theirsMap.get(key);
82
+ if (!oursVal && !theirsVal) continue;
83
+ if (oursVal && (!baseVal || !metadataEquals(baseVal, oursVal))) merged.push(oursVal);
84
+ else if (theirsVal && (!baseVal || !metadataEquals(baseVal, theirsVal))) merged.push(theirsVal);
85
+ else if (oursVal && theirsVal) merged.push(oursVal);
86
+ }
87
+ return merged;
88
+ }
89
+ /**
90
+ * Merge content (best effort)
91
+ *
92
+ * If both sides modified content, take ours for now
93
+ * More sophisticated merging could be added later
94
+ */
95
+ function mergeContent(base, ours, theirs) {
96
+ if (base === null && ours === null && theirs === null) return null;
97
+ if (ours === null && theirs === null) return null;
98
+ if (ours && theirs) {
99
+ const oursChanged = !contentEquals(base, ours);
100
+ const theirsChanged = !contentEquals(base, theirs);
101
+ if (oursChanged && !theirsChanged) return ours;
102
+ if (!oursChanged && theirsChanged) return theirs;
103
+ return ours;
104
+ }
105
+ return ours || theirs || base;
106
+ }
107
+ /**
108
+ * Check if two entries are deeply equal
109
+ */
110
+ function entriesEqual(a, b) {
111
+ if (a.type !== b.type) return false;
112
+ switch (a.type) {
113
+ case "instance_entry":
114
+ if (b.type !== "instance_entry") return false;
115
+ return headersEqual(a.header, b.header) && metadataListsEqual(a.metadata, b.metadata) && contentEquals(a.content, b.content);
116
+ case "schema_entry":
117
+ if (b.type !== "schema_entry") return false;
118
+ return headersEqual(a.header, b.header) && schemaBlocksEqual(a, b);
119
+ case "synthesis_entry":
120
+ if (b.type !== "synthesis_entry") return false;
121
+ return headersEqual(a.header, b.header) && metadataListsEqual(a.metadata, b.metadata) && contentEquals(a.content, b.content);
122
+ case "actualize_entry":
123
+ if (b.type !== "actualize_entry") return false;
124
+ return headersEqual(a.header, b.header) && metadataListsEqual(a.metadata, b.metadata);
125
+ default: return false;
126
+ }
127
+ }
128
+ /**
129
+ * Strip non-semantic fields (syntaxNode, location) for comparison.
130
+ * This avoids circular reference issues with tree-sitter nodes.
131
+ */
132
+ function stripForComparison(obj) {
133
+ if (obj === null || obj === void 0) return obj;
134
+ if (Array.isArray(obj)) return obj.map(stripForComparison);
135
+ if (typeof obj === "object") {
136
+ const result = {};
137
+ for (const [key, value] of Object.entries(obj)) {
138
+ if (key === "syntaxNode" || key === "location") continue;
139
+ result[key] = stripForComparison(value);
140
+ }
141
+ return result;
142
+ }
143
+ return obj;
144
+ }
145
+ /**
146
+ * Check if two headers are equal (comparing semantic fields only)
147
+ */
148
+ function headersEqual(a, b) {
149
+ return JSON.stringify(stripForComparison(a)) === JSON.stringify(stripForComparison(b));
150
+ }
151
+ /**
152
+ * Check if two metadata lists are equal
153
+ */
154
+ function metadataListsEqual(a, b) {
155
+ if (a.length !== b.length) return false;
156
+ const aMap = new Map(a.map((m) => [m.key.value, m.value.raw]));
157
+ const bMap = new Map(b.map((m) => [m.key.value, m.value.raw]));
158
+ if (aMap.size !== bMap.size) return false;
159
+ for (const [key, value] of aMap) if (bMap.get(key) !== value) return false;
160
+ return true;
161
+ }
162
+ /**
163
+ * Check if two content nodes are equal
164
+ */
165
+ function contentEquals(a, b) {
166
+ if (a === null && b === null) return true;
167
+ if (a === null || b === null) return false;
168
+ if (a.children.length !== b.children.length) return false;
169
+ for (let i = 0; i < a.children.length; i++) {
170
+ const childA = a.children[i];
171
+ const childB = b.children[i];
172
+ if (childA.type !== childB.type) return false;
173
+ if (childA.text !== childB.text) return false;
174
+ }
175
+ return true;
176
+ }
177
+ /**
178
+ * Check if schema blocks are equal (comparing semantic fields only)
179
+ */
180
+ function schemaBlocksEqual(a, b) {
181
+ const aObj = a;
182
+ const bObj = b;
183
+ return JSON.stringify(stripForComparison(aObj.metadataBlock)) === JSON.stringify(stripForComparison(bObj.metadataBlock)) && JSON.stringify(stripForComparison(aObj.sectionsBlock)) === JSON.stringify(stripForComparison(bObj.sectionsBlock)) && JSON.stringify(stripForComparison(aObj.removeMetadataBlock)) === JSON.stringify(stripForComparison(bObj.removeMetadataBlock)) && JSON.stringify(stripForComparison(aObj.removeSectionsBlock)) === JSON.stringify(stripForComparison(bObj.removeSectionsBlock));
184
+ }
185
+ /**
186
+ * Check if two metadata items are equal
187
+ */
188
+ function metadataEquals(a, b) {
189
+ return a.key.value === b.key.value && a.value.raw === b.value.raw;
190
+ }
191
+
192
+ //#endregion
193
+ export { entriesEqual, mergeEntry };
194
+ //# sourceMappingURL=entry-merger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-merger.js","names":["merged: Metadata[]","result: Record<string, unknown>"],"sources":["../../src/merge/entry-merger.ts"],"sourcesContent":["import type { Entry, Metadata, Content } from \"../ast/ast-types.js\";\nimport type { EntryMatch } from \"./entry-matcher.js\";\n\n/**\n * Merge a single entry match (assumes no conflicts)\n *\n * Handles various merge scenarios:\n * - Additions (one side only)\n * - Deletions (missing on one side)\n * - Single-side modifications\n * - Both-side modifications with fine-grained merge\n *\n * @param match - The entry match to merge\n * @returns Merged entry, or null if deleted\n */\nexport function mergeEntry(match: EntryMatch): Entry | null {\n const { base, ours, theirs } = match;\n\n if (!base && ours && !theirs) {\n return ours;\n }\n if (!base && !ours && theirs) {\n return theirs;\n }\n\n if (base && !ours && theirs) {\n return null;\n }\n if (base && ours && !theirs) {\n return null;\n }\n\n if (base && ours && theirs) {\n const oursChanged = !entriesEqual(base, ours);\n const theirsChanged = !entriesEqual(base, theirs);\n\n if (oursChanged && !theirsChanged) {\n return ours;\n }\n if (!oursChanged && theirsChanged) {\n return theirs;\n }\n\n if (oursChanged && theirsChanged) {\n return mergeEntryChanges(base, ours, theirs);\n }\n\n return base;\n }\n\n return ours || theirs || base;\n}\n\n/**\n * Fine-grained merge of entry changes\n *\n * For instance/synthesis entries, merge metadata and content independently\n */\nfunction mergeEntryChanges(base: Entry, ours: Entry, theirs: Entry): Entry {\n if (ours.type === \"instance_entry\" && theirs.type === \"instance_entry\") {\n const baseMetadata =\n base.type === \"instance_entry\" ||\n base.type === \"synthesis_entry\" ||\n base.type === \"actualize_entry\"\n ? base.metadata\n : [];\n const baseContent =\n base.type === \"instance_entry\" || base.type === \"synthesis_entry\" ? base.content : null;\n\n return {\n ...ours,\n metadata: mergeMetadata(baseMetadata, ours.metadata, theirs.metadata),\n content: mergeContent(baseContent, ours.content, theirs.content),\n };\n }\n\n if (ours.type === \"synthesis_entry\" && theirs.type === \"synthesis_entry\") {\n const baseMetadata =\n base.type === \"instance_entry\" ||\n base.type === \"synthesis_entry\" ||\n base.type === \"actualize_entry\"\n ? base.metadata\n : [];\n const baseContent =\n base.type === \"instance_entry\" || base.type === \"synthesis_entry\" ? base.content : null;\n\n return {\n ...ours,\n metadata: mergeMetadata(baseMetadata, ours.metadata, theirs.metadata),\n content: mergeContent(baseContent, ours.content, theirs.content),\n };\n }\n\n if (ours.type === \"actualize_entry\" && theirs.type === \"actualize_entry\") {\n const baseMetadata =\n base.type === \"instance_entry\" ||\n base.type === \"synthesis_entry\" ||\n base.type === \"actualize_entry\"\n ? base.metadata\n : [];\n\n return {\n ...ours,\n metadata: mergeMetadata(baseMetadata, ours.metadata, theirs.metadata),\n };\n }\n\n return ours;\n}\n\n/**\n * Merge metadata lists (non-conflicting keys)\n *\n * Strategy: Take the changed version for each key\n */\nfunction mergeMetadata(base: Metadata[], ours: Metadata[], theirs: Metadata[]): Metadata[] {\n const baseMap = new Map(base.map((m) => [m.key.value, m]));\n const oursMap = new Map(ours.map((m) => [m.key.value, m]));\n const theirsMap = new Map(theirs.map((m) => [m.key.value, m]));\n\n const allKeys = new Set([...baseMap.keys(), ...oursMap.keys(), ...theirsMap.keys()]);\n const merged: Metadata[] = [];\n\n for (const key of allKeys) {\n const baseVal = baseMap.get(key);\n const oursVal = oursMap.get(key);\n const theirsVal = theirsMap.get(key);\n\n // If key was deleted on both sides, don't resurrect it\n if (!oursVal && !theirsVal) {\n continue;\n }\n\n // If ours changed/added the key, use ours\n if (oursVal && (!baseVal || !metadataEquals(baseVal, oursVal))) {\n merged.push(oursVal);\n }\n // If theirs changed/added the key, and ours didn't change it, use theirs\n else if (theirsVal && (!baseVal || !metadataEquals(baseVal, theirsVal))) {\n merged.push(theirsVal);\n }\n // If both sides have the key and neither changed it, use either one\n else if (oursVal && theirsVal) {\n merged.push(oursVal);\n }\n }\n\n return merged;\n}\n\n/**\n * Merge content (best effort)\n *\n * If both sides modified content, take ours for now\n * More sophisticated merging could be added later\n */\nfunction mergeContent(\n base: Content | null,\n ours: Content | null,\n theirs: Content | null,\n): Content | null {\n if (base === null && ours === null && theirs === null) {\n return null;\n }\n\n // If both sides deleted content, don't resurrect base\n if (ours === null && theirs === null) {\n return null;\n }\n\n if (ours && theirs) {\n const oursChanged = !contentEquals(base, ours);\n const theirsChanged = !contentEquals(base, theirs);\n\n if (oursChanged && !theirsChanged) {\n return ours;\n }\n if (!oursChanged && theirsChanged) {\n return theirs;\n }\n\n return ours;\n }\n\n return ours || theirs || base;\n}\n\n/**\n * Check if two entries are deeply equal\n */\nexport function entriesEqual(a: Entry, b: Entry): boolean {\n if (a.type !== b.type) {\n return false;\n }\n\n switch (a.type) {\n case \"instance_entry\":\n if (b.type !== \"instance_entry\") {\n return false;\n }\n return (\n headersEqual(a.header, b.header) &&\n metadataListsEqual(a.metadata, b.metadata) &&\n contentEquals(a.content, b.content)\n );\n\n case \"schema_entry\":\n if (b.type !== \"schema_entry\") {\n return false;\n }\n return headersEqual(a.header, b.header) && schemaBlocksEqual(a, b);\n\n case \"synthesis_entry\":\n if (b.type !== \"synthesis_entry\") {\n return false;\n }\n return (\n headersEqual(a.header, b.header) &&\n metadataListsEqual(a.metadata, b.metadata) &&\n contentEquals(a.content, b.content)\n );\n\n case \"actualize_entry\":\n if (b.type !== \"actualize_entry\") {\n return false;\n }\n return headersEqual(a.header, b.header) && metadataListsEqual(a.metadata, b.metadata);\n\n default:\n return false;\n }\n}\n\n/**\n * Strip non-semantic fields (syntaxNode, location) for comparison.\n * This avoids circular reference issues with tree-sitter nodes.\n */\nfunction stripForComparison(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return obj;\n }\n if (Array.isArray(obj)) {\n return obj.map(stripForComparison);\n }\n if (typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n // Skip non-semantic fields that may have circular refs\n if (key === \"syntaxNode\" || key === \"location\") {\n continue;\n }\n result[key] = stripForComparison(value);\n }\n return result;\n }\n return obj;\n}\n\n/**\n * Check if two headers are equal (comparing semantic fields only)\n */\nfunction headersEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(stripForComparison(a)) === JSON.stringify(stripForComparison(b));\n}\n\n/**\n * Check if two metadata lists are equal\n */\nfunction metadataListsEqual(a: Metadata[], b: Metadata[]): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n const aMap = new Map(a.map((m) => [m.key.value, m.value.raw]));\n const bMap = new Map(b.map((m) => [m.key.value, m.value.raw]));\n\n if (aMap.size !== bMap.size) {\n return false;\n }\n\n for (const [key, value] of aMap) {\n if (bMap.get(key) !== value) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Check if two content nodes are equal\n */\nfunction contentEquals(a: Content | null, b: Content | null): boolean {\n if (a === null && b === null) {\n return true;\n }\n if (a === null || b === null) {\n return false;\n }\n\n if (a.children.length !== b.children.length) {\n return false;\n }\n\n for (let i = 0; i < a.children.length; i++) {\n const childA = a.children[i];\n const childB = b.children[i];\n\n if (childA.type !== childB.type) {\n return false;\n }\n if (childA.text !== childB.text) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Check if schema blocks are equal (comparing semantic fields only)\n */\nfunction schemaBlocksEqual(a: unknown, b: unknown): boolean {\n const aObj = a as {\n metadataBlock?: unknown;\n sectionsBlock?: unknown;\n removeMetadataBlock?: unknown;\n removeSectionsBlock?: unknown;\n };\n const bObj = b as {\n metadataBlock?: unknown;\n sectionsBlock?: unknown;\n removeMetadataBlock?: unknown;\n removeSectionsBlock?: unknown;\n };\n\n return (\n JSON.stringify(stripForComparison(aObj.metadataBlock)) ===\n JSON.stringify(stripForComparison(bObj.metadataBlock)) &&\n JSON.stringify(stripForComparison(aObj.sectionsBlock)) ===\n JSON.stringify(stripForComparison(bObj.sectionsBlock)) &&\n JSON.stringify(stripForComparison(aObj.removeMetadataBlock)) ===\n JSON.stringify(stripForComparison(bObj.removeMetadataBlock)) &&\n JSON.stringify(stripForComparison(aObj.removeSectionsBlock)) ===\n JSON.stringify(stripForComparison(bObj.removeSectionsBlock))\n );\n}\n\n/**\n * Check if two metadata items are equal\n */\nfunction metadataEquals(a: Metadata, b: Metadata): boolean {\n return a.key.value === b.key.value && a.value.raw === b.value.raw;\n}\n"],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { MergeConflict } from "./conflict-detector.js";
2
+
3
+ //#region src/merge/merge-result-builder.d.ts
4
+
5
+ /**
6
+ * Statistics about a merge operation
7
+ */
8
+ interface MergeStats {
9
+ /**
10
+ * Total entries in merged result
11
+ */
12
+ totalEntries: number;
13
+ /**
14
+ * Entries present only in ours (additions)
15
+ */
16
+ oursOnly: number;
17
+ /**
18
+ * Entries present only in theirs (additions)
19
+ */
20
+ theirsOnly: number;
21
+ /**
22
+ * Entries present in all three versions unchanged
23
+ */
24
+ common: number;
25
+ /**
26
+ * Entries successfully auto-merged
27
+ */
28
+ autoMerged: number;
29
+ /**
30
+ * Number of conflicts detected
31
+ */
32
+ conflicts: number;
33
+ }
34
+ /**
35
+ * Result of a three-way merge operation
36
+ */
37
+ interface MergeResult {
38
+ /**
39
+ * Whether the merge completed without conflicts
40
+ * - `true`: Clean merge, all changes reconciled
41
+ * - `false`: Conflicts detected, manual resolution required
42
+ */
43
+ success: boolean;
44
+ /**
45
+ * The merged content as a string
46
+ * - On success: Clean merged entries
47
+ * - On failure: Includes conflict markers
48
+ */
49
+ content: string;
50
+ /**
51
+ * List of conflicts detected during merge
52
+ * Empty array if success is true
53
+ */
54
+ conflicts: MergeConflict[];
55
+ /**
56
+ * Statistics about the merge operation
57
+ */
58
+ stats: MergeStats;
59
+ }
60
+ //#endregion
61
+ export { MergeResult, MergeStats };
62
+ //# sourceMappingURL=merge-result-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-result-builder.d.ts","names":[],"sources":["../../src/merge/merge-result-builder.ts"],"sourcesContent":[],"mappings":";;;;AAUA;AAmCA;;UAnCiB,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAmCA,WAAA;;;;;;;;;;;;;;;;;aAmBJ;;;;SAKJ"}