@gitwand/core 2.2.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/dist/__tests__/corpus.d.ts.map +1 -1
  2. package/dist/__tests__/corpus.js +115 -0
  3. package/dist/__tests__/corpus.js.map +1 -1
  4. package/dist/__tests__/patterns/complex.test.d.ts +9 -0
  5. package/dist/__tests__/patterns/complex.test.d.ts.map +1 -0
  6. package/dist/__tests__/patterns/complex.test.js +198 -0
  7. package/dist/__tests__/patterns/complex.test.js.map +1 -0
  8. package/dist/__tests__/patterns/delete-no-change.test.d.ts +11 -0
  9. package/dist/__tests__/patterns/delete-no-change.test.d.ts.map +1 -0
  10. package/dist/__tests__/patterns/delete-no-change.test.js +178 -0
  11. package/dist/__tests__/patterns/delete-no-change.test.js.map +1 -0
  12. package/dist/__tests__/patterns/non-overlapping.test.d.ts +11 -0
  13. package/dist/__tests__/patterns/non-overlapping.test.d.ts.map +1 -0
  14. package/dist/__tests__/patterns/non-overlapping.test.js +240 -0
  15. package/dist/__tests__/patterns/non-overlapping.test.js.map +1 -0
  16. package/dist/__tests__/patterns/one-side-change.test.d.ts +10 -0
  17. package/dist/__tests__/patterns/one-side-change.test.d.ts.map +1 -0
  18. package/dist/__tests__/patterns/one-side-change.test.js +191 -0
  19. package/dist/__tests__/patterns/one-side-change.test.js.map +1 -0
  20. package/dist/__tests__/patterns/same-change.test.d.ts +9 -0
  21. package/dist/__tests__/patterns/same-change.test.d.ts.map +1 -0
  22. package/dist/__tests__/patterns/same-change.test.js +173 -0
  23. package/dist/__tests__/patterns/same-change.test.js.map +1 -0
  24. package/dist/__tests__/patterns/value-only-change.test.d.ts +11 -0
  25. package/dist/__tests__/patterns/value-only-change.test.d.ts.map +1 -0
  26. package/dist/__tests__/patterns/value-only-change.test.js +159 -0
  27. package/dist/__tests__/patterns/value-only-change.test.js.map +1 -0
  28. package/dist/__tests__/patterns/whitespace-only.test.d.ts +10 -0
  29. package/dist/__tests__/patterns/whitespace-only.test.d.ts.map +1 -0
  30. package/dist/__tests__/patterns/whitespace-only.test.js +177 -0
  31. package/dist/__tests__/patterns/whitespace-only.test.js.map +1 -0
  32. package/dist/__tests__/resolvers/css.test.d.ts +12 -0
  33. package/dist/__tests__/resolvers/css.test.d.ts.map +1 -0
  34. package/dist/__tests__/resolvers/css.test.js +171 -0
  35. package/dist/__tests__/resolvers/css.test.js.map +1 -0
  36. package/dist/__tests__/resolvers/imports.test.d.ts +12 -0
  37. package/dist/__tests__/resolvers/imports.test.d.ts.map +1 -0
  38. package/dist/__tests__/resolvers/imports.test.js +135 -0
  39. package/dist/__tests__/resolvers/imports.test.js.map +1 -0
  40. package/dist/__tests__/resolvers/json.test.d.ts +12 -0
  41. package/dist/__tests__/resolvers/json.test.d.ts.map +1 -0
  42. package/dist/__tests__/resolvers/json.test.js +184 -0
  43. package/dist/__tests__/resolvers/json.test.js.map +1 -0
  44. package/dist/__tests__/resolvers/lockfile-npm.test.d.ts +12 -0
  45. package/dist/__tests__/resolvers/lockfile-npm.test.d.ts.map +1 -0
  46. package/dist/__tests__/resolvers/lockfile-npm.test.js +187 -0
  47. package/dist/__tests__/resolvers/lockfile-npm.test.js.map +1 -0
  48. package/dist/__tests__/resolvers/lockfile-pnpm.test.d.ts +12 -0
  49. package/dist/__tests__/resolvers/lockfile-pnpm.test.d.ts.map +1 -0
  50. package/dist/__tests__/resolvers/lockfile-pnpm.test.js +175 -0
  51. package/dist/__tests__/resolvers/lockfile-pnpm.test.js.map +1 -0
  52. package/dist/__tests__/resolvers/lockfile-yarn.test.d.ts +12 -0
  53. package/dist/__tests__/resolvers/lockfile-yarn.test.d.ts.map +1 -0
  54. package/dist/__tests__/resolvers/lockfile-yarn.test.js +165 -0
  55. package/dist/__tests__/resolvers/lockfile-yarn.test.js.map +1 -0
  56. package/dist/__tests__/resolvers/markdown.test.d.ts +12 -0
  57. package/dist/__tests__/resolvers/markdown.test.d.ts.map +1 -0
  58. package/dist/__tests__/resolvers/markdown.test.js +188 -0
  59. package/dist/__tests__/resolvers/markdown.test.js.map +1 -0
  60. package/dist/__tests__/resolvers/vue.test.d.ts +12 -0
  61. package/dist/__tests__/resolvers/vue.test.d.ts.map +1 -0
  62. package/dist/__tests__/resolvers/vue.test.js +225 -0
  63. package/dist/__tests__/resolvers/vue.test.js.map +1 -0
  64. package/dist/__tests__/resolvers/yaml.test.d.ts +12 -0
  65. package/dist/__tests__/resolvers/yaml.test.d.ts.map +1 -0
  66. package/dist/__tests__/resolvers/yaml.test.js +203 -0
  67. package/dist/__tests__/resolvers/yaml.test.js.map +1 -0
  68. package/dist/__tests__/structural/grandeur-nature.test.d.ts +31 -0
  69. package/dist/__tests__/structural/grandeur-nature.test.d.ts.map +1 -0
  70. package/dist/__tests__/structural/grandeur-nature.test.js +264 -0
  71. package/dist/__tests__/structural/grandeur-nature.test.js.map +1 -0
  72. package/dist/__tests__/structural/languages.test.d.ts +5 -0
  73. package/dist/__tests__/structural/languages.test.d.ts.map +1 -0
  74. package/dist/__tests__/structural/languages.test.js +74 -0
  75. package/dist/__tests__/structural/languages.test.js.map +1 -0
  76. package/dist/__tests__/structural/matching.test.d.ts +6 -0
  77. package/dist/__tests__/structural/matching.test.d.ts.map +1 -0
  78. package/dist/__tests__/structural/matching.test.js +113 -0
  79. package/dist/__tests__/structural/matching.test.js.map +1 -0
  80. package/dist/__tests__/structural/merge.test.d.ts +6 -0
  81. package/dist/__tests__/structural/merge.test.d.ts.map +1 -0
  82. package/dist/__tests__/structural/merge.test.js +117 -0
  83. package/dist/__tests__/structural/merge.test.js.map +1 -0
  84. package/dist/__tests__/structural/reconstruct.test.d.ts +6 -0
  85. package/dist/__tests__/structural/reconstruct.test.d.ts.map +1 -0
  86. package/dist/__tests__/structural/reconstruct.test.js +104 -0
  87. package/dist/__tests__/structural/reconstruct.test.js.map +1 -0
  88. package/dist/__tests__/structural/structural-index.test.d.ts +14 -0
  89. package/dist/__tests__/structural/structural-index.test.d.ts.map +1 -0
  90. package/dist/__tests__/structural/structural-index.test.js +108 -0
  91. package/dist/__tests__/structural/structural-index.test.js.map +1 -0
  92. package/dist/__tests__/v2-core-scenarios.test.d.ts +35 -0
  93. package/dist/__tests__/v2-core-scenarios.test.d.ts.map +1 -0
  94. package/dist/__tests__/v2-core-scenarios.test.js +692 -0
  95. package/dist/__tests__/v2-core-scenarios.test.js.map +1 -0
  96. package/dist/__tests__/validation-parse-tree.test.d.ts +15 -0
  97. package/dist/__tests__/validation-parse-tree.test.d.ts.map +1 -0
  98. package/dist/__tests__/validation-parse-tree.test.js +243 -0
  99. package/dist/__tests__/validation-parse-tree.test.js.map +1 -0
  100. package/dist/config.d.ts +25 -0
  101. package/dist/config.d.ts.map +1 -1
  102. package/dist/config.js +17 -0
  103. package/dist/config.js.map +1 -1
  104. package/dist/diff/index.d.ts.map +1 -1
  105. package/dist/diff/index.js +1 -3
  106. package/dist/diff/index.js.map +1 -1
  107. package/dist/index.d.ts +5 -1
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +5 -1
  110. package/dist/index.js.map +1 -1
  111. package/dist/patterns/utils.d.ts +8 -7
  112. package/dist/patterns/utils.d.ts.map +1 -1
  113. package/dist/patterns/utils.js +13 -12
  114. package/dist/patterns/utils.js.map +1 -1
  115. package/dist/resolver/adapters/strict-node.d.ts +32 -0
  116. package/dist/resolver/adapters/strict-node.d.ts.map +1 -0
  117. package/dist/resolver/adapters/strict-node.js +117 -0
  118. package/dist/resolver/adapters/strict-node.js.map +1 -0
  119. package/dist/resolver/index.d.ts +33 -0
  120. package/dist/resolver/index.d.ts.map +1 -1
  121. package/dist/resolver/index.js +113 -0
  122. package/dist/resolver/index.js.map +1 -1
  123. package/dist/resolver/policy.d.ts.map +1 -1
  124. package/dist/resolver/policy.js +3 -0
  125. package/dist/resolver/policy.js.map +1 -1
  126. package/dist/resolver/validate-parse-tree.d.ts +52 -0
  127. package/dist/resolver/validate-parse-tree.d.ts.map +1 -0
  128. package/dist/resolver/validate-parse-tree.js +87 -0
  129. package/dist/resolver/validate-parse-tree.js.map +1 -0
  130. package/dist/resolver/validate-strict.d.ts +27 -0
  131. package/dist/resolver/validate-strict.d.ts.map +1 -0
  132. package/dist/resolver/validate-strict.js +41 -0
  133. package/dist/resolver/validate-strict.js.map +1 -0
  134. package/dist/resolver/validation.d.ts.map +1 -1
  135. package/dist/resolver/validation.js +15 -1
  136. package/dist/resolver/validation.js.map +1 -1
  137. package/dist/resolvers/dispatcher.d.ts +17 -1
  138. package/dist/resolvers/dispatcher.d.ts.map +1 -1
  139. package/dist/resolvers/dispatcher.js.map +1 -1
  140. package/dist/structural/entities.d.ts +44 -0
  141. package/dist/structural/entities.d.ts.map +1 -0
  142. package/dist/structural/entities.js +315 -0
  143. package/dist/structural/entities.js.map +1 -0
  144. package/dist/structural/index.d.ts +48 -0
  145. package/dist/structural/index.d.ts.map +1 -0
  146. package/dist/structural/index.js +177 -0
  147. package/dist/structural/index.js.map +1 -0
  148. package/dist/structural/matching.d.ts +46 -0
  149. package/dist/structural/matching.d.ts.map +1 -0
  150. package/dist/structural/matching.js +83 -0
  151. package/dist/structural/matching.js.map +1 -0
  152. package/dist/structural/merge.d.ts +45 -0
  153. package/dist/structural/merge.d.ts.map +1 -0
  154. package/dist/structural/merge.js +127 -0
  155. package/dist/structural/merge.js.map +1 -0
  156. package/dist/structural/parsers/adapters/browser.d.ts +22 -0
  157. package/dist/structural/parsers/adapters/browser.d.ts.map +1 -0
  158. package/dist/structural/parsers/adapters/browser.js +27 -0
  159. package/dist/structural/parsers/adapters/browser.js.map +1 -0
  160. package/dist/structural/parsers/adapters/node.d.ts +18 -0
  161. package/dist/structural/parsers/adapters/node.d.ts.map +1 -0
  162. package/dist/structural/parsers/adapters/node.js +42 -0
  163. package/dist/structural/parsers/adapters/node.js.map +1 -0
  164. package/dist/structural/parsers/adapters/tauri.d.ts +26 -0
  165. package/dist/structural/parsers/adapters/tauri.d.ts.map +1 -0
  166. package/dist/structural/parsers/adapters/tauri.js +34 -0
  167. package/dist/structural/parsers/adapters/tauri.js.map +1 -0
  168. package/dist/structural/parsers/grammars/languages.d.ts +32 -0
  169. package/dist/structural/parsers/grammars/languages.d.ts.map +1 -0
  170. package/dist/structural/parsers/grammars/languages.js +73 -0
  171. package/dist/structural/parsers/grammars/languages.js.map +1 -0
  172. package/dist/structural/parsers/grammars/ts.d.ts +26 -0
  173. package/dist/structural/parsers/grammars/ts.d.ts.map +1 -0
  174. package/dist/structural/parsers/grammars/ts.js +46 -0
  175. package/dist/structural/parsers/grammars/ts.js.map +1 -0
  176. package/dist/structural/parsers/loader.d.ts +74 -0
  177. package/dist/structural/parsers/loader.d.ts.map +1 -0
  178. package/dist/structural/parsers/loader.js +181 -0
  179. package/dist/structural/parsers/loader.js.map +1 -0
  180. package/dist/structural/reconstruct.d.ts +28 -0
  181. package/dist/structural/reconstruct.d.ts.map +1 -0
  182. package/dist/structural/reconstruct.js +63 -0
  183. package/dist/structural/reconstruct.js.map +1 -0
  184. package/dist/types.d.ts +60 -3
  185. package/dist/types.d.ts.map +1 -1
  186. package/package.json +16 -2
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @gitwand/core v2.3 — Structural (AST-based) merge dispatcher
3
+ *
4
+ * Provides `tryStructuralMergeResolve()`, an async entry point that:
5
+ *
6
+ * 1. Reconstructs the three file versions (base/ours/theirs) from the
7
+ * Git conflict markers in the conflicted content.
8
+ * 2. Parses each version with tree-sitter (via `web-tree-sitter`, optional peer).
9
+ * 3. Extracts top-level entities and matches them across the three versions.
10
+ * 4. Merges entities individually — only auto-resolvable conflicts are handled.
11
+ * 5. Reconstructs the merged file following theirs entity order.
12
+ *
13
+ * Returns `null` (never throws) when:
14
+ * - `web-tree-sitter` is not installed (graceful degradation)
15
+ * - The grammar WASM file is unavailable
16
+ * - Any version has parse errors
17
+ * - Any entity has a real (both-changed-diff) conflict
18
+ *
19
+ * In all these cases the caller falls back to the standard hunk-based resolver.
20
+ */
21
+ import { parseConflictMarkers } from "../parser.js";
22
+ import { validateMergedContent } from "../resolver/validation.js";
23
+ import { loadGrammarForFile } from "./parsers/grammars/ts.js";
24
+ import { createParser } from "./parsers/loader.js";
25
+ import { extractEntities, hasParseErrors } from "./entities.js";
26
+ import { matchEntities } from "./matching.js";
27
+ import { mergeEntity, hasEntityConflict } from "./merge.js";
28
+ import { reconstructFile } from "./reconstruct.js";
29
+ import { isStructuralLanguage } from "./parsers/grammars/languages.js";
30
+ export { isStructuralLanguage };
31
+ // ─── Backward-compat alias ────────────────────────────────────────────────────
32
+ /**
33
+ * Returns `true` for `.ts` and `.tsx` files.
34
+ * @deprecated Use `isStructuralLanguage` — now covers JS/JSX/Python/Go/Rust too.
35
+ */
36
+ export function isTypeScriptFile(filePath) {
37
+ return /\.(ts|tsx)$/i.test(filePath) && !/\.d\.ts$/i.test(filePath);
38
+ }
39
+ /**
40
+ * Reconstruct the three clean file versions from a conflicted file.
41
+ *
42
+ * - `ours` = text segments + ours lines from each conflict hunk
43
+ * - `theirs` = text segments + theirs lines from each conflict hunk
44
+ * - `base` = text segments + base lines (diff3) or ours lines (diff2 fallback)
45
+ */
46
+ function reconstructVersions(conflictedContent) {
47
+ const { segments } = parseConflictMarkers(conflictedContent);
48
+ const oursLines = [];
49
+ const theirsLines = [];
50
+ const baseLines = [];
51
+ for (const seg of segments) {
52
+ if (seg.type === "text") {
53
+ oursLines.push(...seg.lines);
54
+ theirsLines.push(...seg.lines);
55
+ baseLines.push(...seg.lines);
56
+ }
57
+ else {
58
+ const { conflict } = seg;
59
+ oursLines.push(...conflict.oursLines);
60
+ theirsLines.push(...conflict.theirsLines);
61
+ // Use diff3 base if available; fall back to ours (conservative)
62
+ baseLines.push(...(conflict.baseLines.length > 0 ? conflict.baseLines : conflict.oursLines));
63
+ }
64
+ }
65
+ return {
66
+ base: baseLines.join("\n"),
67
+ ours: oursLines.join("\n"),
68
+ theirs: theirsLines.join("\n"),
69
+ };
70
+ }
71
+ // ─── Main entry point ─────────────────────────────────────────────────────────
72
+ /**
73
+ * Attempt structural (AST-based) merge resolution for a TypeScript/TSX file.
74
+ *
75
+ * @param conflictedContent - File content with Git conflict markers
76
+ * @param filePath - File path (grammar selection + type detection)
77
+ * @param opts - Optional loader overrides (WASM paths, custom loaders)
78
+ * @returns Merged content string on success, `null` on any failure
79
+ */
80
+ export async function tryStructuralMergeResolve(conflictedContent, filePath, opts = {}) {
81
+ // Only handle supported languages
82
+ if (!isStructuralLanguage(filePath))
83
+ return null;
84
+ // Load tree-sitter grammar (returns null if optional peer not installed)
85
+ const language = await loadGrammarForFile(filePath, opts);
86
+ if (!language)
87
+ return null;
88
+ // Create parser
89
+ const parser = await createParser(language, opts);
90
+ if (!parser)
91
+ return null;
92
+ // Reconstruct the three file versions
93
+ const { base, ours, theirs } = reconstructVersions(conflictedContent);
94
+ // Parse all three versions
95
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
+ const p = parser;
97
+ const [baseTree, oursTree, theirsTree] = [base, ours, theirs].map((src) => p.parse(src));
98
+ // Abort on parse errors in any version
99
+ if (hasParseErrors(baseTree) || hasParseErrors(oursTree) || hasParseErrors(theirsTree)) {
100
+ return null;
101
+ }
102
+ // Extract top-level entities
103
+ const baseEntities = extractEntities(baseTree, base);
104
+ const oursEntities = extractEntities(oursTree, ours);
105
+ const theirsEntities = extractEntities(theirsTree, theirs);
106
+ // 3-way entity matching
107
+ const matches = matchEntities(baseEntities, oursEntities, theirsEntities);
108
+ // Entity-level merge decisions
109
+ const merges = matches.map(mergeEntity);
110
+ // Abort if any entity has a real conflict
111
+ if (hasEntityConflict(merges))
112
+ return null;
113
+ // Reconstruct the merged file
114
+ const mergedContent = reconstructFile(merges, theirsEntities, oursEntities, theirs);
115
+ return mergedContent;
116
+ }
117
+ // ─── MergeResult wrapper ──────────────────────────────────────────────────────
118
+ /**
119
+ * Wrap a structurally merged content string in a `MergeResult` object.
120
+ *
121
+ * All hunks are marked as auto-resolved via the "structural-merge" resolver.
122
+ * This keeps the return type compatible with the synchronous `resolve()`.
123
+ */
124
+ export function wrapStructuralResult(conflictedContent, mergedContent, filePath) {
125
+ const { segments } = parseConflictMarkers(conflictedContent);
126
+ const hunks = segments
127
+ .filter((s) => s.type === "conflict")
128
+ .map((s) => ({
129
+ baseLines: s.conflict.baseLines,
130
+ oursLines: s.conflict.oursLines,
131
+ theirsLines: s.conflict.theirsLines,
132
+ startLine: s.conflict.startLine,
133
+ type: "complex",
134
+ confidence: {
135
+ score: 100,
136
+ label: "certain",
137
+ dimensions: {
138
+ typeClassification: 100,
139
+ dataRisk: 0,
140
+ scopeImpact: 0,
141
+ fileFrequency: 0,
142
+ baseAvailability: s.conflict.baseLines.length > 0 ? 100 : 0,
143
+ },
144
+ boosters: ["structural-merge"],
145
+ penalties: [],
146
+ },
147
+ explanation: "Résolu via merge structurel (tree-sitter AST)",
148
+ trace: {
149
+ steps: [],
150
+ selected: "complex",
151
+ summary: "Résolu via analyse structurelle AST — merge par entités TypeScript",
152
+ hasBase: s.conflict.baseLines.length > 0,
153
+ },
154
+ }));
155
+ const resolutions = hunks.map((hunk) => ({
156
+ hunk,
157
+ resolvedLines: null,
158
+ autoResolved: true,
159
+ resolutionReason: "structural-merge: résolu via analyse AST tree-sitter",
160
+ }));
161
+ const byType = hunks.length > 0 ? { complex: hunks.length } : {};
162
+ const validation = validateMergedContent(mergedContent, filePath);
163
+ return {
164
+ filePath,
165
+ mergedContent,
166
+ hunks,
167
+ resolutions,
168
+ stats: {
169
+ totalConflicts: hunks.length,
170
+ autoResolved: hunks.length,
171
+ remaining: 0,
172
+ byType,
173
+ },
174
+ validation,
175
+ };
176
+ }
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/structural/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 3-way entity matching for structural merge.
3
+ *
4
+ * Matches top-level entities across the three file versions (base / ours /
5
+ * theirs) using their `signature` as a stable key. Each entity triple is
6
+ * assigned a status that describes what happened to it during the merge.
7
+ */
8
+ import type { TopLevelEntity } from "./entities.js";
9
+ export type EntityMatchStatus =
10
+ /** Text identical in all three versions */
11
+ "unchanged"
12
+ /** Changed in ours, unchanged in theirs */
13
+ | "ours-only-change"
14
+ /** Changed in theirs, unchanged in ours */
15
+ | "theirs-only-change"
16
+ /** Both sides made the same change (or both deleted) */
17
+ | "both-changed-same"
18
+ /** Both sides changed differently — unresolvable at entity level */
19
+ | "both-changed-diff"
20
+ /** Added only by ours (not in base, not in theirs) */
21
+ | "ours-added"
22
+ /** Added only by theirs (not in base, not in ours) */
23
+ | "theirs-added"
24
+ /** Deleted by ours (was in base and theirs) */
25
+ | "ours-deleted"
26
+ /** Deleted by theirs (was in base and ours) */
27
+ | "theirs-deleted";
28
+ export interface EntityMatch {
29
+ signature: string;
30
+ status: EntityMatchStatus;
31
+ base: TopLevelEntity | null;
32
+ ours: TopLevelEntity | null;
33
+ theirs: TopLevelEntity | null;
34
+ }
35
+ /**
36
+ * Compute 3-way entity matches between base, ours, and theirs.
37
+ *
38
+ * The returned array covers every unique signature found across all three
39
+ * versions. Order follows: base order → ours-only → theirs-only sigs.
40
+ *
41
+ * @param base - Entities extracted from the base (ancestor) version
42
+ * @param ours - Entities extracted from our version (current branch)
43
+ * @param theirs - Entities extracted from their version (incoming branch)
44
+ */
45
+ export declare function matchEntities(base: TopLevelEntity[], ours: TopLevelEntity[], theirs: TopLevelEntity[]): EntityMatch[];
46
+ //# sourceMappingURL=matching.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matching.d.ts","sourceRoot":"","sources":["../../src/structural/matching.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAIpD,MAAM,MAAM,iBAAiB;AAC3B,2CAA2C;AACzC,WAAW;AACb,2CAA2C;GACzC,kBAAkB;AACpB,2CAA2C;GACzC,oBAAoB;AACtB,wDAAwD;GACtD,mBAAmB;AACrB,oEAAoE;GAClE,mBAAmB;AACrB,sDAAsD;GACpD,YAAY;AACd,sDAAsD;GACpD,cAAc;AAChB,+CAA+C;GAC7C,cAAc;AAChB,+CAA+C;GAC7C,gBAAgB,CAAC;AAErB,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;CAC/B;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,cAAc,EAAE,EACtB,IAAI,EAAE,cAAc,EAAE,EACtB,MAAM,EAAE,cAAc,EAAE,GACvB,WAAW,EAAE,CA8Bf"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * 3-way entity matching for structural merge.
3
+ *
4
+ * Matches top-level entities across the three file versions (base / ours /
5
+ * theirs) using their `signature` as a stable key. Each entity triple is
6
+ * assigned a status that describes what happened to it during the merge.
7
+ */
8
+ // ─── Internal ─────────────────────────────────────────────────────────────────
9
+ function classifyMatch(base, ours, theirs) {
10
+ const inBase = base !== null;
11
+ const inOurs = ours !== null;
12
+ const inTheirs = theirs !== null;
13
+ // Not in base → newly added in this merge
14
+ if (!inBase) {
15
+ if (inOurs && inTheirs) {
16
+ return ours.text === theirs.text ? "both-changed-same" : "both-changed-diff";
17
+ }
18
+ if (inOurs)
19
+ return "ours-added";
20
+ if (inTheirs)
21
+ return "theirs-added";
22
+ // Unreachable: signature would not be in allSigs without being in at least one version
23
+ return "unchanged";
24
+ }
25
+ // Entity present in base — did ours/theirs change or delete it?
26
+ const inBothMissing = !inOurs && !inTheirs;
27
+ if (inBothMissing)
28
+ return "both-changed-same"; // both deleted = same outcome
29
+ if (!inOurs)
30
+ return "ours-deleted";
31
+ if (!inTheirs)
32
+ return "theirs-deleted";
33
+ const oursChanged = ours.text !== base.text;
34
+ const theirsChanged = theirs.text !== base.text;
35
+ if (!oursChanged && !theirsChanged)
36
+ return "unchanged";
37
+ if (oursChanged && !theirsChanged)
38
+ return "ours-only-change";
39
+ if (!oursChanged && theirsChanged)
40
+ return "theirs-only-change";
41
+ // Both changed
42
+ return ours.text === theirs.text ? "both-changed-same" : "both-changed-diff";
43
+ }
44
+ // ─── Public API ───────────────────────────────────────────────────────────────
45
+ /**
46
+ * Compute 3-way entity matches between base, ours, and theirs.
47
+ *
48
+ * The returned array covers every unique signature found across all three
49
+ * versions. Order follows: base order → ours-only → theirs-only sigs.
50
+ *
51
+ * @param base - Entities extracted from the base (ancestor) version
52
+ * @param ours - Entities extracted from our version (current branch)
53
+ * @param theirs - Entities extracted from their version (incoming branch)
54
+ */
55
+ export function matchEntities(base, ours, theirs) {
56
+ const baseMap = new Map(base.map((e) => [e.signature, e]));
57
+ const oursMap = new Map(ours.map((e) => [e.signature, e]));
58
+ const theirsMap = new Map(theirs.map((e) => [e.signature, e]));
59
+ // Collect all signatures, preserving a sensible order (base → ours-only → theirs-only)
60
+ const seen = new Set();
61
+ const orderedSigs = [];
62
+ for (const map of [baseMap, oursMap, theirsMap]) {
63
+ for (const sig of map.keys()) {
64
+ if (!seen.has(sig)) {
65
+ seen.add(sig);
66
+ orderedSigs.push(sig);
67
+ }
68
+ }
69
+ }
70
+ return orderedSigs.map((sig) => {
71
+ const b = baseMap.get(sig) ?? null;
72
+ const o = oursMap.get(sig) ?? null;
73
+ const t = theirsMap.get(sig) ?? null;
74
+ return {
75
+ signature: sig,
76
+ status: classifyMatch(b, o, t),
77
+ base: b,
78
+ ours: o,
79
+ theirs: t,
80
+ };
81
+ });
82
+ }
83
+ //# sourceMappingURL=matching.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matching.js","sourceRoot":"","sources":["../../src/structural/matching.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Entity-level merge decisions for structural merge.
3
+ *
4
+ * Given a 3-way entity match, decides which text version to use in the
5
+ * merged output. Conservative by design: if both sides changed an entity
6
+ * differently, we return a "conflict" result and abort the structural merge
7
+ * for the whole file (falling back to the hunk-based resolver).
8
+ */
9
+ import type { EntityMatch } from "./matching.js";
10
+ export interface EntityMergeResult {
11
+ signature: string;
12
+ /**
13
+ * Whether this entity should appear in the merged output.
14
+ * `false` means the entity was deleted by ours, theirs, or both.
15
+ */
16
+ include: boolean;
17
+ /**
18
+ * The text to emit for this entity in the merged file.
19
+ * `null` signals an unresolvable conflict (structural merge aborted).
20
+ */
21
+ mergedText: string | null;
22
+ /** Human-readable explanation of the merge decision */
23
+ reason: string;
24
+ }
25
+ /**
26
+ * Decide how to merge a single entity based on its 3-way match status.
27
+ *
28
+ * Rules (conservative, correct-by-default):
29
+ * - unchanged → emit as-is
30
+ * - both-changed-same → emit the common changed version
31
+ * - ours-only-change → emit ours
32
+ * - theirs-only-change → emit theirs
33
+ * - ours-added → emit ours (append after theirs structure)
34
+ * - theirs-added → emit theirs
35
+ * - ours-deleted → omit (unless theirs also modified it → conflict)
36
+ * - theirs-deleted → omit (unless ours also modified it → conflict)
37
+ * - both-changed-diff → conflict (return mergedText: null → abort)
38
+ */
39
+ export declare function mergeEntity(match: EntityMatch): EntityMergeResult;
40
+ /**
41
+ * Returns `true` if any entity merge has an unresolvable conflict
42
+ * (i.e. the structural merge cannot produce a clean output).
43
+ */
44
+ export declare function hasEntityConflict(merges: EntityMergeResult[]): boolean;
45
+ //# sourceMappingURL=merge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/structural/merge.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAIjD,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,iBAAiB,CAwGjE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAEtE"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Entity-level merge decisions for structural merge.
3
+ *
4
+ * Given a 3-way entity match, decides which text version to use in the
5
+ * merged output. Conservative by design: if both sides changed an entity
6
+ * differently, we return a "conflict" result and abort the structural merge
7
+ * for the whole file (falling back to the hunk-based resolver).
8
+ */
9
+ // ─── Public API ───────────────────────────────────────────────────────────────
10
+ /**
11
+ * Decide how to merge a single entity based on its 3-way match status.
12
+ *
13
+ * Rules (conservative, correct-by-default):
14
+ * - unchanged → emit as-is
15
+ * - both-changed-same → emit the common changed version
16
+ * - ours-only-change → emit ours
17
+ * - theirs-only-change → emit theirs
18
+ * - ours-added → emit ours (append after theirs structure)
19
+ * - theirs-added → emit theirs
20
+ * - ours-deleted → omit (unless theirs also modified it → conflict)
21
+ * - theirs-deleted → omit (unless ours also modified it → conflict)
22
+ * - both-changed-diff → conflict (return mergedText: null → abort)
23
+ */
24
+ export function mergeEntity(match) {
25
+ const { signature, status, base, ours, theirs } = match;
26
+ switch (status) {
27
+ case "unchanged":
28
+ return {
29
+ signature,
30
+ include: true,
31
+ mergedText: (ours ?? theirs ?? base).text,
32
+ reason: "unchanged",
33
+ };
34
+ case "both-changed-same":
35
+ if (ours && theirs) {
36
+ return {
37
+ signature,
38
+ include: true,
39
+ mergedText: theirs.text,
40
+ reason: "both changed identically — taking theirs",
41
+ };
42
+ }
43
+ // Both deleted
44
+ return {
45
+ signature,
46
+ include: false,
47
+ mergedText: null,
48
+ reason: "both deleted",
49
+ };
50
+ case "ours-only-change":
51
+ return {
52
+ signature,
53
+ include: ours !== null,
54
+ mergedText: ours?.text ?? null,
55
+ reason: "ours-only change",
56
+ };
57
+ case "theirs-only-change":
58
+ return {
59
+ signature,
60
+ include: theirs !== null,
61
+ mergedText: theirs?.text ?? null,
62
+ reason: "theirs-only change",
63
+ };
64
+ case "ours-added":
65
+ return {
66
+ signature,
67
+ include: true,
68
+ mergedText: ours.text,
69
+ reason: "ours added",
70
+ };
71
+ case "theirs-added":
72
+ return {
73
+ signature,
74
+ include: true,
75
+ mergedText: theirs.text,
76
+ reason: "theirs added",
77
+ };
78
+ case "ours-deleted":
79
+ // If theirs also modified this entity, it's a conflict (we'd silently
80
+ // drop a change by theirs). Treat as unresolvable.
81
+ if (theirs && base && theirs.text !== base.text) {
82
+ return {
83
+ signature,
84
+ include: false,
85
+ mergedText: null,
86
+ reason: "conflict: ours deleted, theirs modified",
87
+ };
88
+ }
89
+ return {
90
+ signature,
91
+ include: false,
92
+ mergedText: null,
93
+ reason: "ours deleted",
94
+ };
95
+ case "theirs-deleted":
96
+ // Symmetric: if ours modified the entity, conflict.
97
+ if (ours && base && ours.text !== base.text) {
98
+ return {
99
+ signature,
100
+ include: false,
101
+ mergedText: null,
102
+ reason: "conflict: theirs deleted, ours modified",
103
+ };
104
+ }
105
+ return {
106
+ signature,
107
+ include: false,
108
+ mergedText: null,
109
+ reason: "theirs deleted",
110
+ };
111
+ case "both-changed-diff":
112
+ return {
113
+ signature,
114
+ include: false,
115
+ mergedText: null,
116
+ reason: "conflict: both changed differently",
117
+ };
118
+ }
119
+ }
120
+ /**
121
+ * Returns `true` if any entity merge has an unresolvable conflict
122
+ * (i.e. the structural merge cannot produce a clean output).
123
+ */
124
+ export function hasEntityConflict(merges) {
125
+ return merges.some((m) => m.include === false && m.reason.startsWith("conflict:"));
126
+ }
127
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/structural/merge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Browser adapter for loading tree-sitter grammar WASM files.
3
+ *
4
+ * Fetches the WASM file from a URL. The bundler / dev server must make the
5
+ * grammar WASM files available at a known public URL.
6
+ *
7
+ * Configure via `LoaderOptions.grammarBaseUrl` (default: "/assets/grammars").
8
+ */
9
+ export interface BrowserLoaderOptions {
10
+ /** Base URL where grammar WASM files are served (no trailing slash). */
11
+ grammarBaseUrl?: string;
12
+ }
13
+ /**
14
+ * Load grammar WASM bytes via fetch.
15
+ *
16
+ * Fetches `{grammarBaseUrl}/{grammarName}.wasm`.
17
+ *
18
+ * @param grammarName - e.g. "tree-sitter-typescript"
19
+ * @param opts - Browser-specific options
20
+ */
21
+ export declare function loadGrammarBytes(grammarName: string, opts?: BrowserLoaderOptions): Promise<Uint8Array>;
22
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../../src/structural/parsers/adapters/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,oBAAoB;IACnC,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,UAAU,CAAC,CAarB"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Browser adapter for loading tree-sitter grammar WASM files.
3
+ *
4
+ * Fetches the WASM file from a URL. The bundler / dev server must make the
5
+ * grammar WASM files available at a known public URL.
6
+ *
7
+ * Configure via `LoaderOptions.grammarBaseUrl` (default: "/assets/grammars").
8
+ */
9
+ /**
10
+ * Load grammar WASM bytes via fetch.
11
+ *
12
+ * Fetches `{grammarBaseUrl}/{grammarName}.wasm`.
13
+ *
14
+ * @param grammarName - e.g. "tree-sitter-typescript"
15
+ * @param opts - Browser-specific options
16
+ */
17
+ export async function loadGrammarBytes(grammarName, opts = {}) {
18
+ const base = opts.grammarBaseUrl ?? "/assets/grammars";
19
+ const url = `${base}/${grammarName}.wasm`;
20
+ const response = await fetch(url);
21
+ if (!response.ok) {
22
+ throw new Error(`[structural] Failed to fetch grammar WASM: ${url} (HTTP ${response.status})`);
23
+ }
24
+ const buffer = await response.arrayBuffer();
25
+ return new Uint8Array(buffer);
26
+ }
27
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../../../../src/structural/parsers/adapters/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,WAAmB,EACnB,OAA6B,EAAE;IAE/B,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,IAAI,kBAAkB,CAAC;IACvD,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,WAAW,OAAO,CAAC;IAE1C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,8CAA8C,GAAG,UAAU,QAAQ,CAAC,MAAM,GAAG,CAC9E,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC5C,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Node.js adapter for loading tree-sitter grammar WASM files.
3
+ *
4
+ * Reads WASM bytes from the filesystem. Grammar resolution order:
5
+ * 1. `tree-sitter-wasms` package (if installed as optional peer)
6
+ * 2. `@gitwand/core` local assets directory (packages/core/assets/grammars/)
7
+ */
8
+ /**
9
+ * Resolve the filesystem path to a grammar WASM file.
10
+ *
11
+ * @param grammarName - e.g. "tree-sitter-typescript"
12
+ */
13
+ export declare function resolveGrammarPath(grammarName: string): string;
14
+ /**
15
+ * Load grammar WASM bytes from the Node.js filesystem.
16
+ */
17
+ export declare function loadGrammarBytes(grammarName: string): Promise<Uint8Array>;
18
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../../src/structural/parsers/adapters/node.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAe9D;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAI/E"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Node.js adapter for loading tree-sitter grammar WASM files.
3
+ *
4
+ * Reads WASM bytes from the filesystem. Grammar resolution order:
5
+ * 1. `tree-sitter-wasms` package (if installed as optional peer)
6
+ * 2. `@gitwand/core` local assets directory (packages/core/assets/grammars/)
7
+ */
8
+ import { readFile } from "node:fs/promises";
9
+ import { createRequire } from "node:module";
10
+ import { dirname, resolve } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ const _require = createRequire(import.meta.url);
13
+ /**
14
+ * Resolve the filesystem path to a grammar WASM file.
15
+ *
16
+ * @param grammarName - e.g. "tree-sitter-typescript"
17
+ */
18
+ export function resolveGrammarPath(grammarName) {
19
+ // 1. Try tree-sitter-wasms (optional peer) — publishes to out/ dir
20
+ try {
21
+ const pkgJson = _require.resolve("tree-sitter-wasms/package.json");
22
+ const pkgDir = dirname(pkgJson);
23
+ return resolve(pkgDir, "out", `${grammarName}.wasm`);
24
+ }
25
+ catch {
26
+ // tree-sitter-wasms not installed
27
+ }
28
+ // 2. Fallback: local assets directory (four levels up from this file)
29
+ // __file__ = packages/core/src/structural/parsers/adapters/node.ts
30
+ // target = packages/core/assets/grammars/
31
+ const thisDir = dirname(fileURLToPath(import.meta.url));
32
+ return resolve(thisDir, "../../../../../assets/grammars", `${grammarName}.wasm`);
33
+ }
34
+ /**
35
+ * Load grammar WASM bytes from the Node.js filesystem.
36
+ */
37
+ export async function loadGrammarBytes(grammarName) {
38
+ const grammarPath = resolveGrammarPath(grammarName);
39
+ const buf = await readFile(grammarPath);
40
+ return new Uint8Array(buf);
41
+ }
42
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../../../src/structural/parsers/adapters/node.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEhD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IACpD,mEAAmE;IACnE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW,OAAO,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,kCAAkC;IACpC,CAAC;IAED,sEAAsE;IACtE,sEAAsE;IACtE,+CAA+C;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACxD,OAAO,OAAO,CAAC,OAAO,EAAE,gCAAgC,EAAE,GAAG,WAAW,OAAO,CAAC,CAAC;AACnF,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,WAAmB;IACxD,MAAM,WAAW,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;AAC7B,CAAC"}