@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.
- package/LICENSE +21 -0
- package/README.md +396 -0
- package/dist/ast/ast-types.d.ts +469 -0
- package/dist/ast/ast-types.d.ts.map +1 -0
- package/dist/ast/ast-types.js +11 -0
- package/dist/ast/ast-types.js.map +1 -0
- package/dist/ast/builder.js +158 -0
- package/dist/ast/builder.js.map +1 -0
- package/dist/ast/extract.js +748 -0
- package/dist/ast/extract.js.map +1 -0
- package/dist/ast/node-at-position.d.ts +147 -0
- package/dist/ast/node-at-position.d.ts.map +1 -0
- package/dist/ast/node-at-position.js +382 -0
- package/dist/ast/node-at-position.js.map +1 -0
- package/dist/ast/visitor.js +232 -0
- package/dist/ast/visitor.js.map +1 -0
- package/dist/checker/check.d.ts +53 -0
- package/dist/checker/check.d.ts.map +1 -0
- package/dist/checker/check.js +105 -0
- package/dist/checker/check.js.map +1 -0
- package/dist/checker/rules/actualize-missing-updated.js +34 -0
- package/dist/checker/rules/actualize-missing-updated.js.map +1 -0
- package/dist/checker/rules/actualize-unresolved-target.js +42 -0
- package/dist/checker/rules/actualize-unresolved-target.js.map +1 -0
- package/dist/checker/rules/alter-before-define.js +53 -0
- package/dist/checker/rules/alter-before-define.js.map +1 -0
- package/dist/checker/rules/alter-undefined-entity.js +32 -0
- package/dist/checker/rules/alter-undefined-entity.js.map +1 -0
- package/dist/checker/rules/create-requires-section.js +34 -0
- package/dist/checker/rules/create-requires-section.js.map +1 -0
- package/dist/checker/rules/define-entity-requires-section.js +31 -0
- package/dist/checker/rules/define-entity-requires-section.js.map +1 -0
- package/dist/checker/rules/duplicate-entity-definition.js +37 -0
- package/dist/checker/rules/duplicate-entity-definition.js.map +1 -0
- package/dist/checker/rules/duplicate-field-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-field-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-link-id.js +52 -0
- package/dist/checker/rules/duplicate-link-id.js.map +1 -0
- package/dist/checker/rules/duplicate-metadata-key.js +21 -0
- package/dist/checker/rules/duplicate-metadata-key.js.map +1 -0
- package/dist/checker/rules/duplicate-section-heading.js +41 -0
- package/dist/checker/rules/duplicate-section-heading.js.map +1 -0
- package/dist/checker/rules/duplicate-section-in-schema.js +38 -0
- package/dist/checker/rules/duplicate-section-in-schema.js.map +1 -0
- package/dist/checker/rules/duplicate-timestamp.js +104 -0
- package/dist/checker/rules/duplicate-timestamp.js.map +1 -0
- package/dist/checker/rules/empty-required-value.js +45 -0
- package/dist/checker/rules/empty-required-value.js.map +1 -0
- package/dist/checker/rules/empty-section.js +21 -0
- package/dist/checker/rules/empty-section.js.map +1 -0
- package/dist/checker/rules/invalid-date-range-value.js +56 -0
- package/dist/checker/rules/invalid-date-range-value.js.map +1 -0
- package/dist/checker/rules/invalid-default-value.js +86 -0
- package/dist/checker/rules/invalid-default-value.js.map +1 -0
- package/dist/checker/rules/invalid-field-type.js +45 -0
- package/dist/checker/rules/invalid-field-type.js.map +1 -0
- package/dist/checker/rules/missing-required-field.js +48 -0
- package/dist/checker/rules/missing-required-field.js.map +1 -0
- package/dist/checker/rules/missing-required-section.js +51 -0
- package/dist/checker/rules/missing-required-section.js.map +1 -0
- package/dist/checker/rules/missing-title.js +56 -0
- package/dist/checker/rules/missing-title.js.map +1 -0
- package/dist/checker/rules/remove-undefined-field.js +42 -0
- package/dist/checker/rules/remove-undefined-field.js.map +1 -0
- package/dist/checker/rules/remove-undefined-section.js +42 -0
- package/dist/checker/rules/remove-undefined-section.js.map +1 -0
- package/dist/checker/rules/rules.d.ts +71 -0
- package/dist/checker/rules/rules.d.ts.map +1 -0
- package/dist/checker/rules/rules.js +102 -0
- package/dist/checker/rules/rules.js.map +1 -0
- package/dist/checker/rules/synthesis-empty-query.js +35 -0
- package/dist/checker/rules/synthesis-empty-query.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-prompt.js +42 -0
- package/dist/checker/rules/synthesis-missing-prompt.js.map +1 -0
- package/dist/checker/rules/synthesis-missing-sources.js +32 -0
- package/dist/checker/rules/synthesis-missing-sources.js.map +1 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js +39 -0
- package/dist/checker/rules/synthesis-unknown-query-entity.js.map +1 -0
- package/dist/checker/rules/timestamp-out-of-order.js +55 -0
- package/dist/checker/rules/timestamp-out-of-order.js.map +1 -0
- package/dist/checker/rules/unknown-entity.js +32 -0
- package/dist/checker/rules/unknown-entity.js.map +1 -0
- package/dist/checker/rules/unknown-field.js +40 -0
- package/dist/checker/rules/unknown-field.js.map +1 -0
- package/dist/checker/rules/unknown-section.js +47 -0
- package/dist/checker/rules/unknown-section.js.map +1 -0
- package/dist/checker/rules/unresolved-link.js +34 -0
- package/dist/checker/rules/unresolved-link.js.map +1 -0
- package/dist/checker/rules/update-without-create.js +65 -0
- package/dist/checker/rules/update-without-create.js.map +1 -0
- package/dist/checker/visitor.d.ts +69 -0
- package/dist/checker/visitor.d.ts.map +1 -0
- package/dist/checker/visitor.js +67 -0
- package/dist/checker/visitor.js.map +1 -0
- package/dist/checker/workspace-index.d.ts +50 -0
- package/dist/checker/workspace-index.d.ts.map +1 -0
- package/dist/checker/workspace-index.js +108 -0
- package/dist/checker/workspace-index.js.map +1 -0
- package/dist/commands/actualize.d.ts +113 -0
- package/dist/commands/actualize.d.ts.map +1 -0
- package/dist/commands/actualize.js +111 -0
- package/dist/commands/actualize.js.map +1 -0
- package/dist/commands/check.d.ts +65 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +61 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/format.d.ts +90 -0
- package/dist/commands/format.d.ts.map +1 -0
- package/dist/commands/format.js +80 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/query.d.ts +152 -0
- package/dist/commands/query.d.ts.map +1 -0
- package/dist/commands/query.js +151 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/constants.d.ts +31 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +51 -0
- package/dist/constants.js.map +1 -0
- package/dist/files.d.ts +58 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +103 -0
- package/dist/files.js.map +1 -0
- package/dist/formatters.d.ts +39 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +200 -0
- package/dist/formatters.js.map +1 -0
- package/dist/fragment.d.ts +22 -0
- package/dist/fragment.d.ts.map +1 -0
- package/dist/git/git.js +240 -0
- package/dist/git/git.js.map +1 -0
- package/dist/merge/conflict-detector.d.ts +89 -0
- package/dist/merge/conflict-detector.d.ts.map +1 -0
- package/dist/merge/conflict-detector.js +352 -0
- package/dist/merge/conflict-detector.js.map +1 -0
- package/dist/merge/conflict-formatter.js +143 -0
- package/dist/merge/conflict-formatter.js.map +1 -0
- package/dist/merge/driver.d.ts +54 -0
- package/dist/merge/driver.d.ts.map +1 -0
- package/dist/merge/driver.js +112 -0
- package/dist/merge/driver.js.map +1 -0
- package/dist/merge/entry-matcher.d.ts +50 -0
- package/dist/merge/entry-matcher.d.ts.map +1 -0
- package/dist/merge/entry-matcher.js +141 -0
- package/dist/merge/entry-matcher.js.map +1 -0
- package/dist/merge/entry-merger.js +194 -0
- package/dist/merge/entry-merger.js.map +1 -0
- package/dist/merge/merge-result-builder.d.ts +62 -0
- package/dist/merge/merge-result-builder.d.ts.map +1 -0
- package/dist/merge/merge-result-builder.js +89 -0
- package/dist/merge/merge-result-builder.js.map +1 -0
- package/dist/mod.d.ts +31 -0
- package/dist/mod.js +23 -0
- package/dist/model/document.d.ts +134 -0
- package/dist/model/document.d.ts.map +1 -0
- package/dist/model/document.js +275 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/line-index.d.ts +85 -0
- package/dist/model/line-index.d.ts.map +1 -0
- package/dist/model/line-index.js +159 -0
- package/dist/model/line-index.js.map +1 -0
- package/dist/model/workspace.d.ts +296 -0
- package/dist/model/workspace.d.ts.map +1 -0
- package/dist/model/workspace.js +562 -0
- package/dist/model/workspace.js.map +1 -0
- package/dist/parser.js +27 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.native.d.ts +51 -0
- package/dist/parser.native.d.ts.map +1 -0
- package/dist/parser.native.js +62 -0
- package/dist/parser.native.js.map +1 -0
- package/dist/parser.shared.d.ts +99 -0
- package/dist/parser.shared.d.ts.map +1 -0
- package/dist/parser.shared.js +124 -0
- package/dist/parser.shared.js.map +1 -0
- package/dist/parser.web.d.ts +67 -0
- package/dist/parser.web.d.ts.map +1 -0
- package/dist/parser.web.js +49 -0
- package/dist/parser.web.js.map +1 -0
- package/dist/schema/registry.d.ts +108 -0
- package/dist/schema/registry.d.ts.map +1 -0
- package/dist/schema/registry.js +281 -0
- package/dist/schema/registry.js.map +1 -0
- package/dist/semantic/analyzer.d.ts +107 -0
- package/dist/semantic/analyzer.d.ts.map +1 -0
- package/dist/semantic/analyzer.js +261 -0
- package/dist/semantic/analyzer.js.map +1 -0
- package/dist/services/change-tracker/change-tracker.d.ts +111 -0
- package/dist/services/change-tracker/change-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/change-tracker.js +62 -0
- package/dist/services/change-tracker/change-tracker.js.map +1 -0
- package/dist/services/change-tracker/create-tracker.d.ts +42 -0
- package/dist/services/change-tracker/create-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/create-tracker.js +53 -0
- package/dist/services/change-tracker/create-tracker.js.map +1 -0
- package/dist/services/change-tracker/git-tracker.d.ts +59 -0
- package/dist/services/change-tracker/git-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/git-tracker.js +218 -0
- package/dist/services/change-tracker/git-tracker.js.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts +22 -0
- package/dist/services/change-tracker/timestamp-tracker.d.ts.map +1 -0
- package/dist/services/change-tracker/timestamp-tracker.js +74 -0
- package/dist/services/change-tracker/timestamp-tracker.js.map +1 -0
- package/dist/services/definition.d.ts +37 -0
- package/dist/services/definition.d.ts.map +1 -0
- package/dist/services/definition.js +43 -0
- package/dist/services/definition.js.map +1 -0
- package/dist/services/entity-navigation.d.ts +200 -0
- package/dist/services/entity-navigation.d.ts.map +1 -0
- package/dist/services/entity-navigation.js +211 -0
- package/dist/services/entity-navigation.js.map +1 -0
- package/dist/services/hover.d.ts +81 -0
- package/dist/services/hover.d.ts.map +1 -0
- package/dist/services/hover.js +669 -0
- package/dist/services/hover.js.map +1 -0
- package/dist/services/query.d.ts +116 -0
- package/dist/services/query.d.ts.map +1 -0
- package/dist/services/query.js +225 -0
- package/dist/services/query.js.map +1 -0
- package/dist/services/references.d.ts +52 -0
- package/dist/services/references.d.ts.map +1 -0
- package/dist/services/references.js +66 -0
- package/dist/services/references.js.map +1 -0
- package/dist/services/semantic-tokens.d.ts +54 -0
- package/dist/services/semantic-tokens.d.ts.map +1 -0
- package/dist/services/semantic-tokens.js +213 -0
- package/dist/services/semantic-tokens.js.map +1 -0
- package/dist/services/synthesis.d.ts +90 -0
- package/dist/services/synthesis.d.ts.map +1 -0
- package/dist/services/synthesis.js +113 -0
- package/dist/services/synthesis.js.map +1 -0
- package/dist/source-map.d.ts +42 -0
- package/dist/source-map.d.ts.map +1 -0
- package/dist/source-map.js +170 -0
- package/dist/source-map.js.map +1 -0
- package/package.json +128 -0
- package/tree-sitter-thalo.wasm +0 -0
- package/web-tree-sitter.wasm +0 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
//#region src/merge/conflict-detector.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detect conflicts in matched entries
|
|
4
|
+
*
|
|
5
|
+
* @param matches - Array of matched entry triplets
|
|
6
|
+
* @param options - Merge options (for custom rules)
|
|
7
|
+
* @returns Array of detected conflicts
|
|
8
|
+
*/
|
|
9
|
+
function detectConflicts(matches, options = {}) {
|
|
10
|
+
const conflicts = [];
|
|
11
|
+
const rules = [...DEFAULT_CONFLICT_RULES, ...options.conflictRules || []];
|
|
12
|
+
for (const match of matches) for (const rule of rules) {
|
|
13
|
+
const conflict = rule.detect(match);
|
|
14
|
+
if (conflict) {
|
|
15
|
+
conflicts.push(conflict);
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return conflicts;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Default conflict detection rules
|
|
23
|
+
*
|
|
24
|
+
* Rules are applied in order, stopping at first match
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_CONFLICT_RULES = [
|
|
27
|
+
{
|
|
28
|
+
name: "duplicate-link-id",
|
|
29
|
+
detect: (match) => {
|
|
30
|
+
if (!match.base && match.ours && match.theirs && match.identity.linkId) return {
|
|
31
|
+
type: "duplicate-link-id",
|
|
32
|
+
message: `Both sides created entry with link ID '^${match.identity.linkId}'`,
|
|
33
|
+
location: 0,
|
|
34
|
+
identity: match.identity,
|
|
35
|
+
ours: match.ours,
|
|
36
|
+
theirs: match.theirs,
|
|
37
|
+
context: { linkId: match.identity.linkId }
|
|
38
|
+
};
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "concurrent-metadata-update",
|
|
44
|
+
detect: (match) => {
|
|
45
|
+
if (!match.base || !match.ours || !match.theirs) return null;
|
|
46
|
+
if (match.ours.type !== "instance_entry" && match.ours.type !== "synthesis_entry" && match.ours.type !== "actualize_entry") return null;
|
|
47
|
+
if (match.theirs.type !== "instance_entry" && match.theirs.type !== "synthesis_entry" && match.theirs.type !== "actualize_entry") return null;
|
|
48
|
+
const baseMetadata = getMetadataMap(match.base);
|
|
49
|
+
const oursMetadata = getMetadataMap(match.ours);
|
|
50
|
+
const theirsMetadata = getMetadataMap(match.theirs);
|
|
51
|
+
const allKeys = new Set([
|
|
52
|
+
...baseMetadata.keys(),
|
|
53
|
+
...oursMetadata.keys(),
|
|
54
|
+
...theirsMetadata.keys()
|
|
55
|
+
]);
|
|
56
|
+
for (const key of allKeys) {
|
|
57
|
+
const baseValue = baseMetadata.get(key);
|
|
58
|
+
const oursValue = oursMetadata.get(key);
|
|
59
|
+
const theirsValue = theirsMetadata.get(key);
|
|
60
|
+
if (oursValue !== baseValue && theirsValue !== baseValue && oursValue !== theirsValue) return {
|
|
61
|
+
type: "concurrent-metadata-update",
|
|
62
|
+
message: `Both sides modified metadata key '${key}'`,
|
|
63
|
+
location: 0,
|
|
64
|
+
identity: match.identity,
|
|
65
|
+
base: match.base,
|
|
66
|
+
ours: match.ours,
|
|
67
|
+
theirs: match.theirs,
|
|
68
|
+
context: { metadataKey: key }
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "concurrent-content-edit",
|
|
76
|
+
detect: (match) => {
|
|
77
|
+
if (!match.base || !match.ours || !match.theirs) return null;
|
|
78
|
+
const baseContent = getEntryContent(match.base);
|
|
79
|
+
const oursContent = getEntryContent(match.ours);
|
|
80
|
+
const theirsContent = getEntryContent(match.theirs);
|
|
81
|
+
if (!baseContent) return null;
|
|
82
|
+
const oursChanged = !contentEquals(baseContent, oursContent);
|
|
83
|
+
const theirsChanged = !contentEquals(baseContent, theirsContent);
|
|
84
|
+
const different = !contentEquals(oursContent, theirsContent);
|
|
85
|
+
if (oursChanged && theirsChanged && different) return {
|
|
86
|
+
type: "concurrent-content-edit",
|
|
87
|
+
message: "Both sides modified content",
|
|
88
|
+
location: 0,
|
|
89
|
+
identity: match.identity,
|
|
90
|
+
base: match.base,
|
|
91
|
+
ours: match.ours,
|
|
92
|
+
theirs: match.theirs
|
|
93
|
+
};
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "incompatible-schema-change",
|
|
99
|
+
detect: (match) => {
|
|
100
|
+
if (match.ours?.type !== "schema_entry" || match.theirs?.type !== "schema_entry") return null;
|
|
101
|
+
if (!match.base) {
|
|
102
|
+
if (!schemaEntriesEqual(match.ours, match.theirs)) return {
|
|
103
|
+
type: "incompatible-schema-change",
|
|
104
|
+
message: "Both sides defined same entity with different schemas",
|
|
105
|
+
location: 0,
|
|
106
|
+
identity: match.identity,
|
|
107
|
+
ours: match.ours,
|
|
108
|
+
theirs: match.theirs
|
|
109
|
+
};
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (match.base.type !== "schema_entry") return null;
|
|
113
|
+
const oursFields = getSchemaFields(match.ours);
|
|
114
|
+
const theirsFields = getSchemaFields(match.theirs);
|
|
115
|
+
const baseFields = getSchemaFields(match.base);
|
|
116
|
+
for (const [fieldName, oursField] of oursFields) {
|
|
117
|
+
const baseField = baseFields.get(fieldName);
|
|
118
|
+
const theirsField = theirsFields.get(fieldName);
|
|
119
|
+
if (oursField !== baseField && theirsField !== baseField && oursField !== theirsField) return {
|
|
120
|
+
type: "incompatible-schema-change",
|
|
121
|
+
message: `Both sides modified schema field '${fieldName}' differently`,
|
|
122
|
+
location: 0,
|
|
123
|
+
identity: match.identity,
|
|
124
|
+
base: match.base,
|
|
125
|
+
ours: match.ours,
|
|
126
|
+
theirs: match.theirs,
|
|
127
|
+
context: { fieldName }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const oursSections = getSchemaSections(match.ours);
|
|
131
|
+
const theirsSections = getSchemaSections(match.theirs);
|
|
132
|
+
const baseSections = getSchemaSections(match.base);
|
|
133
|
+
const allSectionNames = new Set([
|
|
134
|
+
...baseSections.keys(),
|
|
135
|
+
...oursSections.keys(),
|
|
136
|
+
...theirsSections.keys()
|
|
137
|
+
]);
|
|
138
|
+
for (const sectionName of allSectionNames) {
|
|
139
|
+
const baseSection = baseSections.get(sectionName);
|
|
140
|
+
const oursSection = oursSections.get(sectionName);
|
|
141
|
+
const theirsSection = theirsSections.get(sectionName);
|
|
142
|
+
if (oursSection !== baseSection && theirsSection !== baseSection && oursSection !== theirsSection) return {
|
|
143
|
+
type: "incompatible-schema-change",
|
|
144
|
+
message: `Both sides modified section '${sectionName}' differently`,
|
|
145
|
+
location: 0,
|
|
146
|
+
identity: match.identity,
|
|
147
|
+
base: match.base,
|
|
148
|
+
ours: match.ours,
|
|
149
|
+
theirs: match.theirs,
|
|
150
|
+
context: { fieldName: sectionName }
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const oursRemovedFields = getRemovedFields(match.ours);
|
|
154
|
+
const theirsRemovedFields = getRemovedFields(match.theirs);
|
|
155
|
+
const baseRemovedFields = getRemovedFields(match.base);
|
|
156
|
+
const allRemovedFieldNames = new Set([
|
|
157
|
+
...baseRemovedFields.keys(),
|
|
158
|
+
...oursRemovedFields.keys(),
|
|
159
|
+
...theirsRemovedFields.keys()
|
|
160
|
+
]);
|
|
161
|
+
for (const fieldName of allRemovedFieldNames) {
|
|
162
|
+
const baseRemoved = baseRemovedFields.get(fieldName);
|
|
163
|
+
const oursRemoved = oursRemovedFields.get(fieldName);
|
|
164
|
+
const theirsRemoved = theirsRemovedFields.get(fieldName);
|
|
165
|
+
if (oursRemoved !== baseRemoved && theirsRemoved !== baseRemoved && oursRemoved !== theirsRemoved) return {
|
|
166
|
+
type: "incompatible-schema-change",
|
|
167
|
+
message: `Both sides modified field removal '${fieldName}' differently`,
|
|
168
|
+
location: 0,
|
|
169
|
+
identity: match.identity,
|
|
170
|
+
base: match.base,
|
|
171
|
+
ours: match.ours,
|
|
172
|
+
theirs: match.theirs,
|
|
173
|
+
context: { fieldName }
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const oursRemovedSections = getRemovedSections(match.ours);
|
|
177
|
+
const theirsRemovedSections = getRemovedSections(match.theirs);
|
|
178
|
+
const baseRemovedSections = getRemovedSections(match.base);
|
|
179
|
+
const allRemovedSectionNames = new Set([
|
|
180
|
+
...baseRemovedSections.keys(),
|
|
181
|
+
...oursRemovedSections.keys(),
|
|
182
|
+
...theirsRemovedSections.keys()
|
|
183
|
+
]);
|
|
184
|
+
for (const sectionName of allRemovedSectionNames) {
|
|
185
|
+
const baseRemoved = baseRemovedSections.get(sectionName);
|
|
186
|
+
const oursRemoved = oursRemovedSections.get(sectionName);
|
|
187
|
+
const theirsRemoved = theirsRemovedSections.get(sectionName);
|
|
188
|
+
if (oursRemoved !== baseRemoved && theirsRemoved !== baseRemoved && oursRemoved !== theirsRemoved) return {
|
|
189
|
+
type: "incompatible-schema-change",
|
|
190
|
+
message: `Both sides modified section removal '${sectionName}' differently`,
|
|
191
|
+
location: 0,
|
|
192
|
+
identity: match.identity,
|
|
193
|
+
base: match.base,
|
|
194
|
+
ours: match.ours,
|
|
195
|
+
theirs: match.theirs,
|
|
196
|
+
context: { fieldName: sectionName }
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "concurrent-title-change",
|
|
204
|
+
detect: (match) => {
|
|
205
|
+
if (!match.base || !match.ours || !match.theirs) return null;
|
|
206
|
+
const baseTitle = getEntryTitle(match.base);
|
|
207
|
+
const oursTitle = getEntryTitle(match.ours);
|
|
208
|
+
const theirsTitle = getEntryTitle(match.theirs);
|
|
209
|
+
if (baseTitle !== oursTitle && baseTitle !== theirsTitle && oursTitle !== theirsTitle) return {
|
|
210
|
+
type: "concurrent-title-change",
|
|
211
|
+
message: "Both sides changed entry title",
|
|
212
|
+
location: 0,
|
|
213
|
+
identity: match.identity,
|
|
214
|
+
base: match.base,
|
|
215
|
+
ours: match.ours,
|
|
216
|
+
theirs: match.theirs
|
|
217
|
+
};
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
];
|
|
222
|
+
/**
|
|
223
|
+
* Get metadata as a map from key to raw value
|
|
224
|
+
*/
|
|
225
|
+
function getMetadataMap(entry) {
|
|
226
|
+
const map = /* @__PURE__ */ new Map();
|
|
227
|
+
if (entry.type === "instance_entry" || entry.type === "synthesis_entry" || entry.type === "actualize_entry") for (const meta of entry.metadata) map.set(meta.key.value, meta.value.raw);
|
|
228
|
+
return map;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get content from an entry
|
|
232
|
+
*/
|
|
233
|
+
function getEntryContent(entry) {
|
|
234
|
+
if (entry.type === "instance_entry" || entry.type === "synthesis_entry") return entry.content;
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Check if two content nodes are equal
|
|
239
|
+
*/
|
|
240
|
+
function contentEquals(a, b) {
|
|
241
|
+
if (a === null && b === null) return true;
|
|
242
|
+
if (a === null || b === null) return false;
|
|
243
|
+
if (a.children.length !== b.children.length) return false;
|
|
244
|
+
for (let i = 0; i < a.children.length; i++) {
|
|
245
|
+
const childA = a.children[i];
|
|
246
|
+
const childB = b.children[i];
|
|
247
|
+
if (childA.type !== childB.type) return false;
|
|
248
|
+
if (childA.text !== childB.text) return false;
|
|
249
|
+
}
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Check if two schema entries are equal
|
|
254
|
+
*/
|
|
255
|
+
function schemaEntriesEqual(a, b) {
|
|
256
|
+
if (a.type !== "schema_entry" || b.type !== "schema_entry") return false;
|
|
257
|
+
const aFields = getSchemaFields(a);
|
|
258
|
+
const bFields = getSchemaFields(b);
|
|
259
|
+
if (aFields.size !== bFields.size) return false;
|
|
260
|
+
for (const [key, value] of aFields) if (bFields.get(key) !== value) return false;
|
|
261
|
+
const aSections = getSchemaSections(a);
|
|
262
|
+
const bSections = getSchemaSections(b);
|
|
263
|
+
if (aSections.size !== bSections.size) return false;
|
|
264
|
+
for (const [key, value] of aSections) if (bSections.get(key) !== value) return false;
|
|
265
|
+
const aRemovedFields = getRemovedFields(a);
|
|
266
|
+
const bRemovedFields = getRemovedFields(b);
|
|
267
|
+
if (aRemovedFields.size !== bRemovedFields.size) return false;
|
|
268
|
+
for (const [key, value] of aRemovedFields) if (bRemovedFields.get(key) !== value) return false;
|
|
269
|
+
const aRemovedSections = getRemovedSections(a);
|
|
270
|
+
const bRemovedSections = getRemovedSections(b);
|
|
271
|
+
if (aRemovedSections.size !== bRemovedSections.size) return false;
|
|
272
|
+
for (const [key, value] of aRemovedSections) if (bRemovedSections.get(key) !== value) return false;
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Get schema fields as a map from field name to serialized definition
|
|
277
|
+
*/
|
|
278
|
+
function getSchemaFields(entry) {
|
|
279
|
+
const map = /* @__PURE__ */ new Map();
|
|
280
|
+
if (entry.type !== "schema_entry") return map;
|
|
281
|
+
if (entry.metadataBlock) for (const field of entry.metadataBlock.fields) map.set(`field:${field.name.value}`, serializeFieldDef(field));
|
|
282
|
+
return map;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get schema sections as a map from section name to serialized definition
|
|
286
|
+
*/
|
|
287
|
+
function getSchemaSections(entry) {
|
|
288
|
+
const map = /* @__PURE__ */ new Map();
|
|
289
|
+
if (entry.type !== "schema_entry") return map;
|
|
290
|
+
if (entry.sectionsBlock) for (const section of entry.sectionsBlock.sections) map.set(section.name.value, JSON.stringify({
|
|
291
|
+
optional: section.optional,
|
|
292
|
+
description: section.description?.value ?? null
|
|
293
|
+
}));
|
|
294
|
+
return map;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get removed schema fields as a map
|
|
298
|
+
*/
|
|
299
|
+
function getRemovedFields(entry) {
|
|
300
|
+
const map = /* @__PURE__ */ new Map();
|
|
301
|
+
if (entry.type !== "schema_entry") return map;
|
|
302
|
+
if (entry.removeMetadataBlock) for (const removal of entry.removeMetadataBlock.fields) map.set(removal.name.value, removal.reason?.value ?? "");
|
|
303
|
+
return map;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get removed schema sections as a map
|
|
307
|
+
*/
|
|
308
|
+
function getRemovedSections(entry) {
|
|
309
|
+
const map = /* @__PURE__ */ new Map();
|
|
310
|
+
if (entry.type !== "schema_entry") return map;
|
|
311
|
+
if (entry.removeSectionsBlock) for (const removal of entry.removeSectionsBlock.sections) map.set(removal.name.value, removal.reason?.value ?? "");
|
|
312
|
+
return map;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Serialize a field definition to a string for comparison
|
|
316
|
+
*/
|
|
317
|
+
function serializeFieldDef(field) {
|
|
318
|
+
return JSON.stringify({
|
|
319
|
+
optional: field.optional,
|
|
320
|
+
type: serializeTypeExpr(field.typeExpr),
|
|
321
|
+
defaultValue: field.defaultValue ? field.defaultValue.raw : null,
|
|
322
|
+
description: field.description ? field.description.value : null
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Serialize a type expression to a string
|
|
327
|
+
*/
|
|
328
|
+
function serializeTypeExpr(typeExpr) {
|
|
329
|
+
switch (typeExpr.type) {
|
|
330
|
+
case "primitive_type": return typeExpr.name;
|
|
331
|
+
case "literal_type": return `"${typeExpr.value}"`;
|
|
332
|
+
case "array_type": return `${serializeTypeExpr(typeExpr.elementType)}[]`;
|
|
333
|
+
case "union_type": return typeExpr.members.map((m) => serializeTypeExpr(m)).join(" | ");
|
|
334
|
+
default: return "unknown";
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Get entry title
|
|
339
|
+
*/
|
|
340
|
+
function getEntryTitle(entry) {
|
|
341
|
+
switch (entry.type) {
|
|
342
|
+
case "instance_entry":
|
|
343
|
+
case "schema_entry":
|
|
344
|
+
case "synthesis_entry": return entry.header.title.value;
|
|
345
|
+
case "actualize_entry": return "";
|
|
346
|
+
default: return "";
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
//#endregion
|
|
351
|
+
export { detectConflicts };
|
|
352
|
+
//# sourceMappingURL=conflict-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflict-detector.js","names":["conflicts: MergeConflict[]","rules: ConflictRule[]","DEFAULT_CONFLICT_RULES: ConflictRule[]"],"sources":["../../src/merge/conflict-detector.ts"],"sourcesContent":["import type { Entry, Content, FieldDefinition } from \"../ast/ast-types.js\";\nimport type { EntryMatch, EntryIdentity } from \"./entry-matcher.js\";\nimport type { MergeOptions } from \"./driver.js\";\n\n/**\n * Types of conflicts that can be detected\n */\nexport type ConflictType =\n | \"duplicate-link-id\" // Both sides created entry with same ^link-id\n | \"concurrent-metadata-update\" // Both modified same metadata key\n | \"incompatible-schema-change\" // Conflicting schema modifications\n | \"concurrent-content-edit\" // Both modified content of same entry\n | \"concurrent-title-change\" // Both changed title of same entry\n | \"parse-error\" // Failed to parse one or more versions\n | \"merge-error\"; // Internal merge failure\n\n/**\n * Additional context for conflicts\n */\nexport interface ConflictContext {\n /**\n * The metadata key involved (for metadata conflicts)\n */\n metadataKey?: string;\n\n /**\n * The link ID involved (for link conflicts)\n */\n linkId?: string;\n\n /**\n * The entity name involved (for schema conflicts)\n */\n entityName?: string;\n\n /**\n * The field name involved (for schema field conflicts)\n */\n fieldName?: string;\n\n /**\n * Error message (for parse-error and merge-error conflicts)\n */\n errorMessage?: string;\n}\n\n/**\n * A conflict detected during merging\n */\nexport interface MergeConflict {\n /**\n * Type of conflict detected\n */\n type: ConflictType;\n\n /**\n * Human-readable description of the conflict\n */\n message: string;\n\n /**\n * Line number in the merged output where conflict appears\n * (Computed during result building)\n */\n location: number;\n\n /**\n * Identity of the conflicting entry (for unique keying)\n */\n identity: EntryIdentity;\n\n /**\n * The base entry (common ancestor), if applicable\n */\n base?: Entry;\n\n /**\n * The ours entry (local/current version)\n */\n ours?: Entry;\n\n /**\n * The theirs entry (incoming version)\n */\n theirs?: Entry;\n\n /**\n * Additional context about the conflict\n */\n context?: ConflictContext;\n}\n\n/**\n * A conflict detection rule\n */\nexport interface ConflictRule {\n /**\n * Rule identifier\n */\n name: string;\n\n /**\n * Check if this rule applies to a match\n * @returns MergeConflict if conflict detected, null otherwise\n */\n detect: (match: EntryMatch) => MergeConflict | null;\n}\n\n/**\n * Detect conflicts in matched entries\n *\n * @param matches - Array of matched entry triplets\n * @param options - Merge options (for custom rules)\n * @returns Array of detected conflicts\n */\nexport function detectConflicts(\n matches: EntryMatch[],\n options: MergeOptions = {},\n): MergeConflict[] {\n const conflicts: MergeConflict[] = [];\n\n const rules: ConflictRule[] = [...DEFAULT_CONFLICT_RULES, ...(options.conflictRules || [])];\n\n for (const match of matches) {\n for (const rule of rules) {\n const conflict = rule.detect(match);\n if (conflict) {\n conflicts.push(conflict);\n break;\n }\n }\n }\n\n return conflicts;\n}\n\n/**\n * Default conflict detection rules\n *\n * Rules are applied in order, stopping at first match\n */\nconst DEFAULT_CONFLICT_RULES: ConflictRule[] = [\n {\n name: \"duplicate-link-id\",\n detect: (match) => {\n if (!match.base && match.ours && match.theirs && match.identity.linkId) {\n return {\n type: \"duplicate-link-id\",\n message: `Both sides created entry with link ID '^${match.identity.linkId}'`,\n location: 0,\n identity: match.identity,\n ours: match.ours,\n theirs: match.theirs,\n context: { linkId: match.identity.linkId },\n };\n }\n return null;\n },\n },\n\n {\n name: \"concurrent-metadata-update\",\n detect: (match) => {\n if (!match.base || !match.ours || !match.theirs) {\n return null;\n }\n\n if (\n match.ours.type !== \"instance_entry\" &&\n match.ours.type !== \"synthesis_entry\" &&\n match.ours.type !== \"actualize_entry\"\n ) {\n return null;\n }\n if (\n match.theirs.type !== \"instance_entry\" &&\n match.theirs.type !== \"synthesis_entry\" &&\n match.theirs.type !== \"actualize_entry\"\n ) {\n return null;\n }\n\n const baseMetadata = getMetadataMap(match.base);\n const oursMetadata = getMetadataMap(match.ours);\n const theirsMetadata = getMetadataMap(match.theirs);\n\n // Iterate over union of all keys to catch delete-vs-edit conflicts\n const allKeys = new Set([\n ...baseMetadata.keys(),\n ...oursMetadata.keys(),\n ...theirsMetadata.keys(),\n ]);\n\n for (const key of allKeys) {\n const baseValue = baseMetadata.get(key);\n const oursValue = oursMetadata.get(key);\n const theirsValue = theirsMetadata.get(key);\n\n // Conflict if both sides changed from base differently\n if (oursValue !== baseValue && theirsValue !== baseValue && oursValue !== theirsValue) {\n return {\n type: \"concurrent-metadata-update\",\n message: `Both sides modified metadata key '${key}'`,\n location: 0,\n identity: match.identity,\n base: match.base,\n ours: match.ours,\n theirs: match.theirs,\n context: { metadataKey: key },\n };\n }\n }\n\n return null;\n },\n },\n\n {\n name: \"concurrent-content-edit\",\n detect: (match) => {\n if (!match.base || !match.ours || !match.theirs) {\n return null;\n }\n\n const baseContent = getEntryContent(match.base);\n const oursContent = getEntryContent(match.ours);\n const theirsContent = getEntryContent(match.theirs);\n\n if (!baseContent) {\n return null;\n }\n\n const oursChanged = !contentEquals(baseContent, oursContent);\n const theirsChanged = !contentEquals(baseContent, theirsContent);\n const different = !contentEquals(oursContent, theirsContent);\n\n if (oursChanged && theirsChanged && different) {\n return {\n type: \"concurrent-content-edit\",\n message: \"Both sides modified content\",\n location: 0,\n identity: match.identity,\n base: match.base,\n ours: match.ours,\n theirs: match.theirs,\n };\n }\n\n return null;\n },\n },\n\n {\n name: \"incompatible-schema-change\",\n detect: (match) => {\n if (match.ours?.type !== \"schema_entry\" || match.theirs?.type !== \"schema_entry\") {\n return null;\n }\n\n if (!match.base) {\n if (!schemaEntriesEqual(match.ours, match.theirs)) {\n return {\n type: \"incompatible-schema-change\",\n message: \"Both sides defined same entity with different schemas\",\n location: 0,\n identity: match.identity,\n ours: match.ours,\n theirs: match.theirs,\n };\n }\n return null;\n }\n\n if (match.base.type !== \"schema_entry\") {\n return null;\n }\n\n // Check metadata fields\n const oursFields = getSchemaFields(match.ours);\n const theirsFields = getSchemaFields(match.theirs);\n const baseFields = getSchemaFields(match.base);\n\n for (const [fieldName, oursField] of oursFields) {\n const baseField = baseFields.get(fieldName);\n const theirsField = theirsFields.get(fieldName);\n\n if (oursField !== baseField && theirsField !== baseField && oursField !== theirsField) {\n return {\n type: \"incompatible-schema-change\",\n message: `Both sides modified schema field '${fieldName}' differently`,\n location: 0,\n identity: match.identity,\n base: match.base,\n ours: match.ours,\n theirs: match.theirs,\n context: { fieldName },\n };\n }\n }\n\n // Check section definitions\n const oursSections = getSchemaSections(match.ours);\n const theirsSections = getSchemaSections(match.theirs);\n const baseSections = getSchemaSections(match.base);\n\n const allSectionNames = new Set([\n ...baseSections.keys(),\n ...oursSections.keys(),\n ...theirsSections.keys(),\n ]);\n\n for (const sectionName of allSectionNames) {\n const baseSection = baseSections.get(sectionName);\n const oursSection = oursSections.get(sectionName);\n const theirsSection = theirsSections.get(sectionName);\n\n if (\n oursSection !== baseSection &&\n theirsSection !== baseSection &&\n oursSection !== theirsSection\n ) {\n return {\n type: \"incompatible-schema-change\",\n message: `Both sides modified section '${sectionName}' differently`,\n location: 0,\n identity: match.identity,\n base: match.base,\n ours: match.ours,\n theirs: match.theirs,\n context: { fieldName: sectionName },\n };\n }\n }\n\n // Check removed fields\n const oursRemovedFields = getRemovedFields(match.ours);\n const theirsRemovedFields = getRemovedFields(match.theirs);\n const baseRemovedFields = getRemovedFields(match.base);\n\n const allRemovedFieldNames = new Set([\n ...baseRemovedFields.keys(),\n ...oursRemovedFields.keys(),\n ...theirsRemovedFields.keys(),\n ]);\n\n for (const fieldName of allRemovedFieldNames) {\n const baseRemoved = baseRemovedFields.get(fieldName);\n const oursRemoved = oursRemovedFields.get(fieldName);\n const theirsRemoved = theirsRemovedFields.get(fieldName);\n\n if (\n oursRemoved !== baseRemoved &&\n theirsRemoved !== baseRemoved &&\n oursRemoved !== theirsRemoved\n ) {\n return {\n type: \"incompatible-schema-change\",\n message: `Both sides modified field removal '${fieldName}' differently`,\n location: 0,\n identity: match.identity,\n base: match.base,\n ours: match.ours,\n theirs: match.theirs,\n context: { fieldName },\n };\n }\n }\n\n // Check removed sections\n const oursRemovedSections = getRemovedSections(match.ours);\n const theirsRemovedSections = getRemovedSections(match.theirs);\n const baseRemovedSections = getRemovedSections(match.base);\n\n const allRemovedSectionNames = new Set([\n ...baseRemovedSections.keys(),\n ...oursRemovedSections.keys(),\n ...theirsRemovedSections.keys(),\n ]);\n\n for (const sectionName of allRemovedSectionNames) {\n const baseRemoved = baseRemovedSections.get(sectionName);\n const oursRemoved = oursRemovedSections.get(sectionName);\n const theirsRemoved = theirsRemovedSections.get(sectionName);\n\n if (\n oursRemoved !== baseRemoved &&\n theirsRemoved !== baseRemoved &&\n oursRemoved !== theirsRemoved\n ) {\n return {\n type: \"incompatible-schema-change\",\n message: `Both sides modified section removal '${sectionName}' differently`,\n location: 0,\n identity: match.identity,\n base: match.base,\n ours: match.ours,\n theirs: match.theirs,\n context: { fieldName: sectionName },\n };\n }\n }\n\n return null;\n },\n },\n\n {\n name: \"concurrent-title-change\",\n detect: (match) => {\n if (!match.base || !match.ours || !match.theirs) {\n return null;\n }\n\n const baseTitle = getEntryTitle(match.base);\n const oursTitle = getEntryTitle(match.ours);\n const theirsTitle = getEntryTitle(match.theirs);\n\n if (baseTitle !== oursTitle && baseTitle !== theirsTitle && oursTitle !== theirsTitle) {\n return {\n type: \"concurrent-title-change\",\n message: \"Both sides changed entry title\",\n location: 0,\n identity: match.identity,\n base: match.base,\n ours: match.ours,\n theirs: match.theirs,\n };\n }\n\n return null;\n },\n },\n];\n\n/**\n * Get metadata as a map from key to raw value\n */\nfunction getMetadataMap(entry: Entry): Map<string, string> {\n const map = new Map<string, string>();\n\n if (\n entry.type === \"instance_entry\" ||\n entry.type === \"synthesis_entry\" ||\n entry.type === \"actualize_entry\"\n ) {\n for (const meta of entry.metadata) {\n map.set(meta.key.value, meta.value.raw);\n }\n }\n\n return map;\n}\n\n/**\n * Get content from an entry\n */\nfunction getEntryContent(entry: Entry): Content | null {\n if (entry.type === \"instance_entry\" || entry.type === \"synthesis_entry\") {\n return entry.content;\n }\n return null;\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 two schema entries are equal\n */\nfunction schemaEntriesEqual(a: Entry, b: Entry): boolean {\n if (a.type !== \"schema_entry\" || b.type !== \"schema_entry\") {\n return false;\n }\n\n // Check metadata fields\n const aFields = getSchemaFields(a);\n const bFields = getSchemaFields(b);\n\n if (aFields.size !== bFields.size) {\n return false;\n }\n\n for (const [key, value] of aFields) {\n if (bFields.get(key) !== value) {\n return false;\n }\n }\n\n // Check sections\n const aSections = getSchemaSections(a);\n const bSections = getSchemaSections(b);\n\n if (aSections.size !== bSections.size) {\n return false;\n }\n\n for (const [key, value] of aSections) {\n if (bSections.get(key) !== value) {\n return false;\n }\n }\n\n // Check removed fields\n const aRemovedFields = getRemovedFields(a);\n const bRemovedFields = getRemovedFields(b);\n\n if (aRemovedFields.size !== bRemovedFields.size) {\n return false;\n }\n\n for (const [key, value] of aRemovedFields) {\n if (bRemovedFields.get(key) !== value) {\n return false;\n }\n }\n\n // Check removed sections\n const aRemovedSections = getRemovedSections(a);\n const bRemovedSections = getRemovedSections(b);\n\n if (aRemovedSections.size !== bRemovedSections.size) {\n return false;\n }\n\n for (const [key, value] of aRemovedSections) {\n if (bRemovedSections.get(key) !== value) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Get schema fields as a map from field name to serialized definition\n */\nfunction getSchemaFields(entry: Entry): Map<string, string> {\n const map = new Map<string, string>();\n\n if (entry.type !== \"schema_entry\") {\n return map;\n }\n\n if (entry.metadataBlock) {\n for (const field of entry.metadataBlock.fields) {\n map.set(`field:${field.name.value}`, serializeFieldDef(field));\n }\n }\n\n return map;\n}\n\n/**\n * Get schema sections as a map from section name to serialized definition\n */\nfunction getSchemaSections(entry: Entry): Map<string, string> {\n const map = new Map<string, string>();\n\n if (entry.type !== \"schema_entry\") {\n return map;\n }\n\n if (entry.sectionsBlock) {\n for (const section of entry.sectionsBlock.sections) {\n map.set(\n section.name.value,\n JSON.stringify({\n optional: section.optional,\n description: section.description?.value ?? null,\n }),\n );\n }\n }\n\n return map;\n}\n\n/**\n * Get removed schema fields as a map\n */\nfunction getRemovedFields(entry: Entry): Map<string, string> {\n const map = new Map<string, string>();\n\n if (entry.type !== \"schema_entry\") {\n return map;\n }\n\n if (entry.removeMetadataBlock) {\n for (const removal of entry.removeMetadataBlock.fields) {\n map.set(removal.name.value, removal.reason?.value ?? \"\");\n }\n }\n\n return map;\n}\n\n/**\n * Get removed schema sections as a map\n */\nfunction getRemovedSections(entry: Entry): Map<string, string> {\n const map = new Map<string, string>();\n\n if (entry.type !== \"schema_entry\") {\n return map;\n }\n\n if (entry.removeSectionsBlock) {\n for (const removal of entry.removeSectionsBlock.sections) {\n map.set(removal.name.value, removal.reason?.value ?? \"\");\n }\n }\n\n return map;\n}\n\n/**\n * Serialize a field definition to a string for comparison\n */\nfunction serializeFieldDef(field: FieldDefinition): string {\n return JSON.stringify({\n optional: field.optional,\n type: serializeTypeExpr(field.typeExpr),\n defaultValue: field.defaultValue ? field.defaultValue.raw : null,\n description: field.description ? field.description.value : null,\n });\n}\n\n/**\n * Serialize a type expression to a string\n */\nfunction serializeTypeExpr(typeExpr: FieldDefinition[\"typeExpr\"]): string {\n switch (typeExpr.type) {\n case \"primitive_type\":\n return typeExpr.name;\n case \"literal_type\":\n return `\"${typeExpr.value}\"`;\n case \"array_type\":\n return `${serializeTypeExpr(typeExpr.elementType)}[]`;\n case \"union_type\":\n return typeExpr.members.map((m) => serializeTypeExpr(m)).join(\" | \");\n default:\n return \"unknown\";\n }\n}\n\n/**\n * Get entry title\n */\nfunction getEntryTitle(entry: Entry): string {\n switch (entry.type) {\n case \"instance_entry\":\n case \"schema_entry\":\n case \"synthesis_entry\":\n return entry.header.title.value;\n case \"actualize_entry\":\n return \"\";\n default:\n return \"\";\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
//#region src/merge/conflict-formatter.ts
|
|
2
|
+
/**
|
|
3
|
+
* Format a conflict with Git-style markers
|
|
4
|
+
*
|
|
5
|
+
* @param conflict - The conflict to format
|
|
6
|
+
* @param options - Merge options (for marker style)
|
|
7
|
+
* @returns Array of lines representing the conflict
|
|
8
|
+
*/
|
|
9
|
+
function formatConflict(conflict, options = {}) {
|
|
10
|
+
const lines = [];
|
|
11
|
+
const showBase = options.showBase || options.markerStyle === "diff3";
|
|
12
|
+
lines.push("<<<<<<< ours");
|
|
13
|
+
if (conflict.ours) lines.push(...formatEntry(conflict.ours));
|
|
14
|
+
if (showBase && conflict.base) {
|
|
15
|
+
lines.push("||||||| base");
|
|
16
|
+
lines.push(...formatEntry(conflict.base));
|
|
17
|
+
}
|
|
18
|
+
lines.push("=======");
|
|
19
|
+
if (conflict.theirs) lines.push(...formatEntry(conflict.theirs));
|
|
20
|
+
lines.push(">>>>>>> theirs");
|
|
21
|
+
return lines;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Format an entry back to source text
|
|
25
|
+
*
|
|
26
|
+
* Reconstructs source from AST nodes
|
|
27
|
+
*
|
|
28
|
+
* @param entry - Entry to format
|
|
29
|
+
* @returns Array of source lines
|
|
30
|
+
*/
|
|
31
|
+
function formatEntry(entry) {
|
|
32
|
+
const lines = [];
|
|
33
|
+
lines.push(formatHeader(entry));
|
|
34
|
+
if (entry.type === "instance_entry" || entry.type === "synthesis_entry" || entry.type === "actualize_entry") for (const meta of entry.metadata) lines.push(` ${meta.key.value}: ${meta.value.raw}`);
|
|
35
|
+
if (entry.type === "instance_entry" || entry.type === "synthesis_entry") {
|
|
36
|
+
if (entry.content) {
|
|
37
|
+
lines.push("");
|
|
38
|
+
for (const child of entry.content.children) lines.push(` ${child.text}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (entry.type === "schema_entry") {
|
|
42
|
+
if (entry.metadataBlock) {
|
|
43
|
+
lines.push(" # Metadata");
|
|
44
|
+
for (const field of entry.metadataBlock.fields) lines.push(` ${formatFieldDefinition(field)}`);
|
|
45
|
+
}
|
|
46
|
+
if (entry.sectionsBlock) {
|
|
47
|
+
lines.push(" # Sections");
|
|
48
|
+
for (const section of entry.sectionsBlock.sections) lines.push(` ${formatSectionDefinition(section)}`);
|
|
49
|
+
}
|
|
50
|
+
if (entry.removeMetadataBlock) {
|
|
51
|
+
lines.push(" # Remove Metadata");
|
|
52
|
+
for (const removal of entry.removeMetadataBlock.fields) lines.push(` ${removal.name.value}`);
|
|
53
|
+
}
|
|
54
|
+
if (entry.removeSectionsBlock) {
|
|
55
|
+
lines.push(" # Remove Sections");
|
|
56
|
+
for (const removal of entry.removeSectionsBlock.sections) lines.push(` ${removal.name.value}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Format entry header
|
|
63
|
+
*/
|
|
64
|
+
function formatHeader(entry) {
|
|
65
|
+
switch (entry.type) {
|
|
66
|
+
case "instance_entry": {
|
|
67
|
+
const h = entry.header;
|
|
68
|
+
const parts = [
|
|
69
|
+
h.timestamp.value,
|
|
70
|
+
h.directive,
|
|
71
|
+
h.entity,
|
|
72
|
+
`"${h.title.value}"`
|
|
73
|
+
];
|
|
74
|
+
if (h.link) parts.push(`^${h.link.id}`);
|
|
75
|
+
for (const tag of h.tags) parts.push(`#${tag.name}`);
|
|
76
|
+
return parts.join(" ");
|
|
77
|
+
}
|
|
78
|
+
case "schema_entry": {
|
|
79
|
+
const h = entry.header;
|
|
80
|
+
const parts = [
|
|
81
|
+
h.timestamp.value,
|
|
82
|
+
h.directive,
|
|
83
|
+
h.entityName.value,
|
|
84
|
+
`"${h.title.value}"`
|
|
85
|
+
];
|
|
86
|
+
if (h.link) parts.push(`^${h.link.id}`);
|
|
87
|
+
for (const tag of h.tags) parts.push(`#${tag.name}`);
|
|
88
|
+
return parts.join(" ");
|
|
89
|
+
}
|
|
90
|
+
case "synthesis_entry": {
|
|
91
|
+
const h = entry.header;
|
|
92
|
+
const parts = [
|
|
93
|
+
h.timestamp.value,
|
|
94
|
+
"define-synthesis",
|
|
95
|
+
`"${h.title.value}"`,
|
|
96
|
+
`^${h.linkId.id}`
|
|
97
|
+
];
|
|
98
|
+
for (const tag of h.tags) parts.push(`#${tag.name}`);
|
|
99
|
+
return parts.join(" ");
|
|
100
|
+
}
|
|
101
|
+
case "actualize_entry": {
|
|
102
|
+
const h = entry.header;
|
|
103
|
+
return `${h.timestamp.value} actualize-synthesis ^${h.target.id}`;
|
|
104
|
+
}
|
|
105
|
+
default: return "";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Format a field definition
|
|
110
|
+
*/
|
|
111
|
+
function formatFieldDefinition(field) {
|
|
112
|
+
let result = field.name.value;
|
|
113
|
+
if (field.optional) result += "?";
|
|
114
|
+
result += `: ${formatTypeExpr(field.typeExpr)}`;
|
|
115
|
+
if (field.defaultValue) result += ` = ${field.defaultValue.raw}`;
|
|
116
|
+
if (field.description) result += ` ; ${field.description.value}`;
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Format a section definition
|
|
121
|
+
*/
|
|
122
|
+
function formatSectionDefinition(section) {
|
|
123
|
+
let result = section.name.value;
|
|
124
|
+
if (section.optional) result += "?";
|
|
125
|
+
if (section.description) result += ` ; ${section.description.value}`;
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Format a type expression
|
|
130
|
+
*/
|
|
131
|
+
function formatTypeExpr(typeExpr) {
|
|
132
|
+
switch (typeExpr.type) {
|
|
133
|
+
case "primitive_type": return typeExpr.name ?? "unknown";
|
|
134
|
+
case "literal_type": return `"${typeExpr.value}"`;
|
|
135
|
+
case "array_type": return typeExpr.elementType ? `${formatTypeExpr(typeExpr.elementType)}[]` : "unknown[]";
|
|
136
|
+
case "union_type": return typeExpr.members ? typeExpr.members.map((m) => formatTypeExpr(m)).join(" | ") : "unknown";
|
|
137
|
+
default: return "unknown";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
//#endregion
|
|
142
|
+
export { formatConflict, formatEntry };
|
|
143
|
+
//# sourceMappingURL=conflict-formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflict-formatter.js","names":["lines: string[]"],"sources":["../../src/merge/conflict-formatter.ts"],"sourcesContent":["import type { Entry } from \"../ast/ast-types.js\";\nimport type { MergeConflict } from \"./conflict-detector.js\";\nimport type { MergeOptions } from \"./driver.js\";\n\n/**\n * Format a conflict with Git-style markers\n *\n * @param conflict - The conflict to format\n * @param options - Merge options (for marker style)\n * @returns Array of lines representing the conflict\n */\nexport function formatConflict(conflict: MergeConflict, options: MergeOptions = {}): string[] {\n const lines: string[] = [];\n const showBase = options.showBase || options.markerStyle === \"diff3\";\n\n lines.push(\"<<<<<<< ours\");\n if (conflict.ours) {\n lines.push(...formatEntry(conflict.ours));\n }\n\n if (showBase && conflict.base) {\n lines.push(\"||||||| base\");\n lines.push(...formatEntry(conflict.base));\n }\n\n lines.push(\"=======\");\n if (conflict.theirs) {\n lines.push(...formatEntry(conflict.theirs));\n }\n lines.push(\">>>>>>> theirs\");\n\n return lines;\n}\n\n/**\n * Format an entry back to source text\n *\n * Reconstructs source from AST nodes\n *\n * @param entry - Entry to format\n * @returns Array of source lines\n */\nexport function formatEntry(entry: Entry): string[] {\n const lines: string[] = [];\n\n lines.push(formatHeader(entry));\n\n if (\n entry.type === \"instance_entry\" ||\n entry.type === \"synthesis_entry\" ||\n entry.type === \"actualize_entry\"\n ) {\n for (const meta of entry.metadata) {\n lines.push(` ${meta.key.value}: ${meta.value.raw}`);\n }\n }\n\n if (entry.type === \"instance_entry\" || entry.type === \"synthesis_entry\") {\n if (entry.content) {\n lines.push(\"\");\n for (const child of entry.content.children) {\n lines.push(` ${child.text}`);\n }\n }\n }\n\n if (entry.type === \"schema_entry\") {\n if (entry.metadataBlock) {\n lines.push(\" # Metadata\");\n for (const field of entry.metadataBlock.fields) {\n lines.push(` ${formatFieldDefinition(field)}`);\n }\n }\n\n if (entry.sectionsBlock) {\n lines.push(\" # Sections\");\n for (const section of entry.sectionsBlock.sections) {\n lines.push(` ${formatSectionDefinition(section)}`);\n }\n }\n\n if (entry.removeMetadataBlock) {\n lines.push(\" # Remove Metadata\");\n for (const removal of entry.removeMetadataBlock.fields) {\n lines.push(` ${removal.name.value}`);\n }\n }\n\n if (entry.removeSectionsBlock) {\n lines.push(\" # Remove Sections\");\n for (const removal of entry.removeSectionsBlock.sections) {\n lines.push(` ${removal.name.value}`);\n }\n }\n }\n\n return lines;\n}\n\n/**\n * Format entry header\n */\nfunction formatHeader(entry: Entry): string {\n switch (entry.type) {\n case \"instance_entry\": {\n const h = entry.header;\n const parts = [h.timestamp.value, h.directive, h.entity, `\"${h.title.value}\"`];\n if (h.link) {\n parts.push(`^${h.link.id}`);\n }\n for (const tag of h.tags) {\n parts.push(`#${tag.name}`);\n }\n return parts.join(\" \");\n }\n\n case \"schema_entry\": {\n const h = entry.header;\n const parts = [h.timestamp.value, h.directive, h.entityName.value, `\"${h.title.value}\"`];\n if (h.link) {\n parts.push(`^${h.link.id}`);\n }\n for (const tag of h.tags) {\n parts.push(`#${tag.name}`);\n }\n return parts.join(\" \");\n }\n\n case \"synthesis_entry\": {\n const h = entry.header;\n const parts = [\n h.timestamp.value,\n \"define-synthesis\",\n `\"${h.title.value}\"`,\n `^${h.linkId.id}`,\n ];\n for (const tag of h.tags) {\n parts.push(`#${tag.name}`);\n }\n return parts.join(\" \");\n }\n\n case \"actualize_entry\": {\n const h = entry.header;\n return `${h.timestamp.value} actualize-synthesis ^${h.target.id}`;\n }\n\n default:\n return \"\";\n }\n}\n\n/**\n * Format a field definition\n */\nfunction formatFieldDefinition(field: {\n name: { value: string };\n optional: boolean;\n typeExpr: {\n type: string;\n name?: string;\n value?: string;\n elementType?: unknown;\n members?: unknown[];\n };\n defaultValue?: { raw: string } | null;\n description?: { value: string } | null;\n}): string {\n let result = field.name.value;\n if (field.optional) {\n result += \"?\";\n }\n result += `: ${formatTypeExpr(field.typeExpr)}`;\n if (field.defaultValue) {\n result += ` = ${field.defaultValue.raw}`;\n }\n if (field.description) {\n result += ` ; ${field.description.value}`;\n }\n return result;\n}\n\n/**\n * Format a section definition\n */\nfunction formatSectionDefinition(section: {\n name: { value: string };\n optional: boolean;\n description?: { value: string } | null;\n}): string {\n let result = section.name.value;\n if (section.optional) {\n result += \"?\";\n }\n if (section.description) {\n result += ` ; ${section.description.value}`;\n }\n return result;\n}\n\n/**\n * Format a type expression\n */\nfunction formatTypeExpr(typeExpr: {\n type: string;\n name?: string;\n value?: string;\n elementType?: unknown;\n members?: unknown[];\n}): string {\n switch (typeExpr.type) {\n case \"primitive_type\":\n return typeExpr.name ?? \"unknown\";\n\n case \"literal_type\":\n return `\"${typeExpr.value}\"`;\n\n case \"array_type\":\n return typeExpr.elementType\n ? `${formatTypeExpr(typeExpr.elementType as typeof typeExpr)}[]`\n : \"unknown[]\";\n\n case \"union_type\":\n return typeExpr.members\n ? (typeExpr.members as (typeof typeExpr)[]).map((m) => formatTypeExpr(m)).join(\" | \")\n : \"unknown\";\n\n default:\n return \"unknown\";\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ConflictRule } from "./conflict-detector.js";
|
|
2
|
+
import { MergeResult } from "./merge-result-builder.js";
|
|
3
|
+
|
|
4
|
+
//#region src/merge/driver.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for the merge driver
|
|
8
|
+
*/
|
|
9
|
+
interface MergeOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Conflict marker style
|
|
12
|
+
* - "git": Standard Git style (ours/theirs)
|
|
13
|
+
* - "diff3": Include base section
|
|
14
|
+
*/
|
|
15
|
+
markerStyle?: "git" | "diff3";
|
|
16
|
+
/**
|
|
17
|
+
* Whether to include base in markers (diff3 style)
|
|
18
|
+
* Deprecated: Use markerStyle: "diff3" instead
|
|
19
|
+
*/
|
|
20
|
+
showBase?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Custom conflict detection rules
|
|
23
|
+
* Applied after default rules
|
|
24
|
+
*/
|
|
25
|
+
conflictRules?: ConflictRule[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Perform three-way merge of thalo files
|
|
29
|
+
*
|
|
30
|
+
* This is the main entry point for the merge driver.
|
|
31
|
+
* It parses all three versions, matches entries, detects conflicts,
|
|
32
|
+
* and produces a merged result.
|
|
33
|
+
*
|
|
34
|
+
* @param base - Base version content (common ancestor)
|
|
35
|
+
* @param ours - Our version content (local changes)
|
|
36
|
+
* @param theirs - Their version content (incoming changes)
|
|
37
|
+
* @param options - Merge options
|
|
38
|
+
* @returns MergeResult with merged content and conflict information
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const base = '2026-01-01T00:00Z define-entity lore "Lore"';
|
|
43
|
+
* const ours = base + '\n2026-01-02T00:00Z create lore "My entry" ^entry1';
|
|
44
|
+
* const theirs = base + '\n2026-01-03T00:00Z create lore "Their entry" ^entry2';
|
|
45
|
+
*
|
|
46
|
+
* const result = mergeThaloFiles(base, ours, theirs);
|
|
47
|
+
* console.log(result.success); // true
|
|
48
|
+
* console.log(result.content); // Merged content with both entries
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
declare function mergeThaloFiles(base: string, ours: string, theirs: string, options?: MergeOptions): MergeResult;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { MergeOptions, mergeThaloFiles };
|
|
54
|
+
//# sourceMappingURL=driver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver.d.ts","names":[],"sources":["../../src/merge/driver.ts"],"sourcesContent":[],"mappings":";;;;;;;AAWA;AA6CgB,UA7CC,YAAA,CA6Cc;;;;;;;;;;;;;;;;kBA3Bb;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BF,eAAA,uDAIL,eACR"}
|