@gitwand/core 2.0.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +58 -0
  2. package/dist/__tests__/bench.bench.js +39 -0
  3. package/dist/__tests__/bench.bench.js.map +1 -1
  4. package/dist/__tests__/corpus.d.ts.map +1 -1
  5. package/dist/__tests__/corpus.js +363 -0
  6. package/dist/__tests__/corpus.js.map +1 -1
  7. package/dist/__tests__/diff/block-move.test.d.ts +5 -0
  8. package/dist/__tests__/diff/block-move.test.d.ts.map +1 -0
  9. package/dist/__tests__/diff/block-move.test.js +132 -0
  10. package/dist/__tests__/diff/block-move.test.js.map +1 -0
  11. package/dist/__tests__/diff/histogram.test.d.ts +8 -0
  12. package/dist/__tests__/diff/histogram.test.d.ts.map +1 -0
  13. package/dist/__tests__/diff/histogram.test.js +150 -0
  14. package/dist/__tests__/diff/histogram.test.js.map +1 -0
  15. package/dist/__tests__/diff/parity.test.d.ts +17 -0
  16. package/dist/__tests__/diff/parity.test.d.ts.map +1 -0
  17. package/dist/__tests__/diff/parity.test.js +149 -0
  18. package/dist/__tests__/diff/parity.test.js.map +1 -0
  19. package/dist/__tests__/diff.test.js +6 -2
  20. package/dist/__tests__/diff.test.js.map +1 -1
  21. package/dist/__tests__/format-profiles/integration.test.d.ts +7 -0
  22. package/dist/__tests__/format-profiles/integration.test.d.ts.map +1 -0
  23. package/dist/__tests__/format-profiles/integration.test.js +193 -0
  24. package/dist/__tests__/format-profiles/integration.test.js.map +1 -0
  25. package/dist/__tests__/format-profiles/json-patch.test.d.ts +12 -0
  26. package/dist/__tests__/format-profiles/json-patch.test.d.ts.map +1 -0
  27. package/dist/__tests__/format-profiles/json-patch.test.js +222 -0
  28. package/dist/__tests__/format-profiles/json-patch.test.js.map +1 -0
  29. package/dist/__tests__/format-profiles/registry.test.d.ts +5 -0
  30. package/dist/__tests__/format-profiles/registry.test.d.ts.map +1 -0
  31. package/dist/__tests__/format-profiles/registry.test.js +124 -0
  32. package/dist/__tests__/format-profiles/registry.test.js.map +1 -0
  33. package/dist/__tests__/patterns/make-score.test.d.ts +9 -0
  34. package/dist/__tests__/patterns/make-score.test.d.ts.map +1 -0
  35. package/dist/__tests__/patterns/make-score.test.js +49 -0
  36. package/dist/__tests__/patterns/make-score.test.js.map +1 -0
  37. package/dist/__tests__/structural/grandeur-nature.test.d.ts +31 -0
  38. package/dist/__tests__/structural/grandeur-nature.test.d.ts.map +1 -0
  39. package/dist/__tests__/structural/grandeur-nature.test.js +264 -0
  40. package/dist/__tests__/structural/grandeur-nature.test.js.map +1 -0
  41. package/dist/__tests__/structural/languages.test.d.ts +5 -0
  42. package/dist/__tests__/structural/languages.test.d.ts.map +1 -0
  43. package/dist/__tests__/structural/languages.test.js +74 -0
  44. package/dist/__tests__/structural/languages.test.js.map +1 -0
  45. package/dist/__tests__/structural/matching.test.d.ts +6 -0
  46. package/dist/__tests__/structural/matching.test.d.ts.map +1 -0
  47. package/dist/__tests__/structural/matching.test.js +113 -0
  48. package/dist/__tests__/structural/matching.test.js.map +1 -0
  49. package/dist/__tests__/structural/merge.test.d.ts +6 -0
  50. package/dist/__tests__/structural/merge.test.d.ts.map +1 -0
  51. package/dist/__tests__/structural/merge.test.js +117 -0
  52. package/dist/__tests__/structural/merge.test.js.map +1 -0
  53. package/dist/__tests__/structural/reconstruct.test.d.ts +6 -0
  54. package/dist/__tests__/structural/reconstruct.test.d.ts.map +1 -0
  55. package/dist/__tests__/structural/reconstruct.test.js +104 -0
  56. package/dist/__tests__/structural/reconstruct.test.js.map +1 -0
  57. package/dist/__tests__/structural/structural-index.test.d.ts +14 -0
  58. package/dist/__tests__/structural/structural-index.test.d.ts.map +1 -0
  59. package/dist/__tests__/structural/structural-index.test.js +108 -0
  60. package/dist/__tests__/structural/structural-index.test.js.map +1 -0
  61. package/dist/diff/block-move.d.ts +53 -0
  62. package/dist/diff/block-move.d.ts.map +1 -0
  63. package/dist/diff/block-move.js +192 -0
  64. package/dist/diff/block-move.js.map +1 -0
  65. package/dist/diff/histogram.d.ts +45 -0
  66. package/dist/diff/histogram.d.ts.map +1 -0
  67. package/dist/diff/histogram.js +172 -0
  68. package/dist/diff/histogram.js.map +1 -0
  69. package/dist/diff/index.d.ts +30 -0
  70. package/dist/diff/index.d.ts.map +1 -0
  71. package/dist/diff/index.js +47 -0
  72. package/dist/diff/index.js.map +1 -0
  73. package/dist/diff/lcs.d.ts +34 -0
  74. package/dist/diff/lcs.d.ts.map +1 -0
  75. package/dist/diff/lcs.js +184 -0
  76. package/dist/diff/lcs.js.map +1 -0
  77. package/dist/diff/shared.d.ts +54 -0
  78. package/dist/diff/shared.d.ts.map +1 -0
  79. package/dist/diff/shared.js +164 -0
  80. package/dist/diff/shared.js.map +1 -0
  81. package/dist/diff.d.ts +6 -65
  82. package/dist/diff.d.ts.map +1 -1
  83. package/dist/diff.js +6 -324
  84. package/dist/diff.js.map +1 -1
  85. package/dist/format-profiles/index.d.ts +34 -0
  86. package/dist/format-profiles/index.d.ts.map +1 -0
  87. package/dist/format-profiles/index.js +86 -0
  88. package/dist/format-profiles/index.js.map +1 -0
  89. package/dist/format-profiles/json-patch.d.ts +61 -0
  90. package/dist/format-profiles/json-patch.d.ts.map +1 -0
  91. package/dist/format-profiles/json-patch.js +269 -0
  92. package/dist/format-profiles/json-patch.js.map +1 -0
  93. package/dist/format-profiles/merge-strategies.d.ts +54 -0
  94. package/dist/format-profiles/merge-strategies.d.ts.map +1 -0
  95. package/dist/format-profiles/merge-strategies.js +156 -0
  96. package/dist/format-profiles/merge-strategies.js.map +1 -0
  97. package/dist/format-profiles/profiles/composer.d.ts +18 -0
  98. package/dist/format-profiles/profiles/composer.d.ts.map +1 -0
  99. package/dist/format-profiles/profiles/composer.js +45 -0
  100. package/dist/format-profiles/profiles/composer.js.map +1 -0
  101. package/dist/format-profiles/profiles/helm-values.d.ts +21 -0
  102. package/dist/format-profiles/profiles/helm-values.d.ts.map +1 -0
  103. package/dist/format-profiles/profiles/helm-values.js +40 -0
  104. package/dist/format-profiles/profiles/helm-values.js.map +1 -0
  105. package/dist/format-profiles/profiles/kubernetes.d.ts +22 -0
  106. package/dist/format-profiles/profiles/kubernetes.d.ts.map +1 -0
  107. package/dist/format-profiles/profiles/kubernetes.js +60 -0
  108. package/dist/format-profiles/profiles/kubernetes.js.map +1 -0
  109. package/dist/format-profiles/profiles/package-json.d.ts +18 -0
  110. package/dist/format-profiles/profiles/package-json.d.ts.map +1 -0
  111. package/dist/format-profiles/profiles/package-json.js +36 -0
  112. package/dist/format-profiles/profiles/package-json.js.map +1 -0
  113. package/dist/format-profiles/profiles/tsconfig.d.ts +21 -0
  114. package/dist/format-profiles/profiles/tsconfig.d.ts.map +1 -0
  115. package/dist/format-profiles/profiles/tsconfig.js +47 -0
  116. package/dist/format-profiles/profiles/tsconfig.js.map +1 -0
  117. package/dist/format-profiles/types.d.ts +67 -0
  118. package/dist/format-profiles/types.d.ts.map +1 -0
  119. package/dist/format-profiles/types.js +9 -0
  120. package/dist/format-profiles/types.js.map +1 -0
  121. package/dist/index.d.ts +9 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/index.js +10 -1
  124. package/dist/index.js.map +1 -1
  125. package/dist/patterns/insertion-at-boundary.d.ts.map +1 -1
  126. package/dist/patterns/insertion-at-boundary.js +15 -33
  127. package/dist/patterns/insertion-at-boundary.js.map +1 -1
  128. package/dist/patterns/utils.d.ts +11 -8
  129. package/dist/patterns/utils.d.ts.map +1 -1
  130. package/dist/patterns/utils.js +28 -10
  131. package/dist/patterns/utils.js.map +1 -1
  132. package/dist/resolver/format-dispatch.d.ts.map +1 -1
  133. package/dist/resolver/format-dispatch.js +3 -1
  134. package/dist/resolver/format-dispatch.js.map +1 -1
  135. package/dist/resolver/index.d.ts +14 -0
  136. package/dist/resolver/index.d.ts.map +1 -1
  137. package/dist/resolver/index.js +29 -0
  138. package/dist/resolver/index.js.map +1 -1
  139. package/dist/resolver/policy.d.ts.map +1 -1
  140. package/dist/resolver/policy.js +2 -0
  141. package/dist/resolver/policy.js.map +1 -1
  142. package/dist/resolvers/dispatcher.d.ts +22 -2
  143. package/dist/resolvers/dispatcher.d.ts.map +1 -1
  144. package/dist/resolvers/dispatcher.js +8 -3
  145. package/dist/resolvers/dispatcher.js.map +1 -1
  146. package/dist/resolvers/json.d.ts +11 -2
  147. package/dist/resolvers/json.d.ts.map +1 -1
  148. package/dist/resolvers/json.js +55 -7
  149. package/dist/resolvers/json.js.map +1 -1
  150. package/dist/resolvers/yaml.d.ts +8 -2
  151. package/dist/resolvers/yaml.d.ts.map +1 -1
  152. package/dist/resolvers/yaml.js +156 -2
  153. package/dist/resolvers/yaml.js.map +1 -1
  154. package/dist/structural/entities.d.ts +44 -0
  155. package/dist/structural/entities.d.ts.map +1 -0
  156. package/dist/structural/entities.js +315 -0
  157. package/dist/structural/entities.js.map +1 -0
  158. package/dist/structural/index.d.ts +48 -0
  159. package/dist/structural/index.d.ts.map +1 -0
  160. package/dist/structural/index.js +177 -0
  161. package/dist/structural/index.js.map +1 -0
  162. package/dist/structural/matching.d.ts +46 -0
  163. package/dist/structural/matching.d.ts.map +1 -0
  164. package/dist/structural/matching.js +83 -0
  165. package/dist/structural/matching.js.map +1 -0
  166. package/dist/structural/merge.d.ts +45 -0
  167. package/dist/structural/merge.d.ts.map +1 -0
  168. package/dist/structural/merge.js +127 -0
  169. package/dist/structural/merge.js.map +1 -0
  170. package/dist/structural/parsers/adapters/browser.d.ts +22 -0
  171. package/dist/structural/parsers/adapters/browser.d.ts.map +1 -0
  172. package/dist/structural/parsers/adapters/browser.js +27 -0
  173. package/dist/structural/parsers/adapters/browser.js.map +1 -0
  174. package/dist/structural/parsers/adapters/node.d.ts +18 -0
  175. package/dist/structural/parsers/adapters/node.d.ts.map +1 -0
  176. package/dist/structural/parsers/adapters/node.js +42 -0
  177. package/dist/structural/parsers/adapters/node.js.map +1 -0
  178. package/dist/structural/parsers/adapters/tauri.d.ts +26 -0
  179. package/dist/structural/parsers/adapters/tauri.d.ts.map +1 -0
  180. package/dist/structural/parsers/adapters/tauri.js +34 -0
  181. package/dist/structural/parsers/adapters/tauri.js.map +1 -0
  182. package/dist/structural/parsers/grammars/languages.d.ts +32 -0
  183. package/dist/structural/parsers/grammars/languages.d.ts.map +1 -0
  184. package/dist/structural/parsers/grammars/languages.js +73 -0
  185. package/dist/structural/parsers/grammars/languages.js.map +1 -0
  186. package/dist/structural/parsers/grammars/ts.d.ts +26 -0
  187. package/dist/structural/parsers/grammars/ts.d.ts.map +1 -0
  188. package/dist/structural/parsers/grammars/ts.js +46 -0
  189. package/dist/structural/parsers/grammars/ts.js.map +1 -0
  190. package/dist/structural/parsers/loader.d.ts +74 -0
  191. package/dist/structural/parsers/loader.d.ts.map +1 -0
  192. package/dist/structural/parsers/loader.js +181 -0
  193. package/dist/structural/parsers/loader.js.map +1 -0
  194. package/dist/structural/reconstruct.d.ts +28 -0
  195. package/dist/structural/reconstruct.d.ts.map +1 -0
  196. package/dist/structural/reconstruct.js +63 -0
  197. package/dist/structural/reconstruct.js.map +1 -0
  198. package/dist/types.d.ts +25 -0
  199. package/dist/types.d.ts.map +1 -1
  200. package/package.json +16 -2
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Tests for structural/merge.ts — entity-level merge decisions.
3
+ * Pure logic, no tree-sitter / WASM dependency.
4
+ */
5
+ import { describe, it, expect } from "vitest";
6
+ import { mergeEntity, hasEntityConflict } from "../../structural/merge.js";
7
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
8
+ function entity(text) {
9
+ return {
10
+ signature: "function:foo",
11
+ kind: "function",
12
+ text,
13
+ startByte: 0,
14
+ endByte: text.length,
15
+ startLine: 0,
16
+ };
17
+ }
18
+ function match(status, base, ours, theirs) {
19
+ return {
20
+ signature: "function:foo",
21
+ status,
22
+ base: base !== undefined ? entity(base) : null,
23
+ ours: ours !== undefined ? entity(ours) : null,
24
+ theirs: theirs !== undefined ? entity(theirs) : null,
25
+ };
26
+ }
27
+ // ─── Tests ────────────────────────────────────────────────────────────────────
28
+ describe("mergeEntity", () => {
29
+ it("unchanged → emit ours text (same as theirs)", () => {
30
+ const result = mergeEntity(match("unchanged", "fn()", "fn()", "fn()"));
31
+ expect(result.include).toBe(true);
32
+ expect(result.mergedText).toBe("fn()");
33
+ expect(result.reason).toBe("unchanged");
34
+ });
35
+ it("both-changed-same with text → emit theirs", () => {
36
+ const result = mergeEntity(match("both-changed-same", "fn()", "fn2()", "fn2()"));
37
+ expect(result.include).toBe(true);
38
+ expect(result.mergedText).toBe("fn2()");
39
+ });
40
+ it("both-changed-same with both deleted → exclude", () => {
41
+ const result = mergeEntity(match("both-changed-same", "fn()", undefined, undefined));
42
+ expect(result.include).toBe(false);
43
+ expect(result.mergedText).toBeNull();
44
+ expect(result.reason).toBe("both deleted");
45
+ });
46
+ it("ours-only-change → emit ours", () => {
47
+ const result = mergeEntity(match("ours-only-change", "fn()", "fn2()", "fn()"));
48
+ expect(result.include).toBe(true);
49
+ expect(result.mergedText).toBe("fn2()");
50
+ });
51
+ it("theirs-only-change → emit theirs", () => {
52
+ const result = mergeEntity(match("theirs-only-change", "fn()", "fn()", "fn3()"));
53
+ expect(result.include).toBe(true);
54
+ expect(result.mergedText).toBe("fn3()");
55
+ });
56
+ it("ours-added → include ours text", () => {
57
+ const result = mergeEntity(match("ours-added", undefined, "fn_new()", undefined));
58
+ expect(result.include).toBe(true);
59
+ expect(result.mergedText).toBe("fn_new()");
60
+ });
61
+ it("theirs-added → include theirs text", () => {
62
+ const result = mergeEntity(match("theirs-added", undefined, undefined, "fn_new()"));
63
+ expect(result.include).toBe(true);
64
+ expect(result.mergedText).toBe("fn_new()");
65
+ });
66
+ it("ours-deleted (theirs unchanged) → exclude", () => {
67
+ const result = mergeEntity(match("ours-deleted", "fn()", undefined, "fn()"));
68
+ expect(result.include).toBe(false);
69
+ expect(result.reason).toBe("ours deleted");
70
+ });
71
+ it("ours-deleted (theirs modified) → conflict", () => {
72
+ const result = mergeEntity(match("ours-deleted", "fn()", undefined, "fn3()"));
73
+ expect(result.include).toBe(false);
74
+ expect(result.reason).toContain("conflict:");
75
+ });
76
+ it("theirs-deleted (ours unchanged) → exclude", () => {
77
+ const result = mergeEntity(match("theirs-deleted", "fn()", "fn()", undefined));
78
+ expect(result.include).toBe(false);
79
+ expect(result.reason).toBe("theirs deleted");
80
+ });
81
+ it("theirs-deleted (ours modified) → conflict", () => {
82
+ const result = mergeEntity(match("theirs-deleted", "fn()", "fn2()", undefined));
83
+ expect(result.include).toBe(false);
84
+ expect(result.reason).toContain("conflict:");
85
+ });
86
+ it("both-changed-diff → conflict with null mergedText", () => {
87
+ const result = mergeEntity(match("both-changed-diff", "fn()", "fn1()", "fn2()"));
88
+ expect(result.include).toBe(false);
89
+ expect(result.mergedText).toBeNull();
90
+ expect(result.reason).toContain("conflict:");
91
+ });
92
+ });
93
+ describe("hasEntityConflict", () => {
94
+ it("returns false when all merges are clean", () => {
95
+ const merges = [
96
+ mergeEntity(match("unchanged", "a", "a", "a")),
97
+ mergeEntity(match("ours-added", undefined, "b", undefined)),
98
+ ];
99
+ expect(hasEntityConflict(merges)).toBe(false);
100
+ });
101
+ it("returns true when any merge has a conflict", () => {
102
+ const merges = [
103
+ mergeEntity(match("unchanged", "a", "a", "a")),
104
+ mergeEntity(match("both-changed-diff", "a", "b", "c")),
105
+ ];
106
+ expect(hasEntityConflict(merges)).toBe(true);
107
+ });
108
+ it("ours-deleted (unchanged theirs) is NOT a conflict", () => {
109
+ const merges = [mergeEntity(match("ours-deleted", "fn()", undefined, "fn()"))];
110
+ expect(hasEntityConflict(merges)).toBe(false);
111
+ });
112
+ it("ours-deleted (modified theirs) IS a conflict", () => {
113
+ const merges = [mergeEntity(match("ours-deleted", "fn()", undefined, "fn_modified()"))];
114
+ expect(hasEntityConflict(merges)).toBe(true);
115
+ });
116
+ });
117
+ //# sourceMappingURL=merge.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge.test.js","sourceRoot":"","sources":["../../../src/__tests__/structural/merge.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests for structural/reconstruct.ts — file reconstruction from entity merges.
3
+ * Pure logic, no tree-sitter / WASM dependency.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=reconstruct.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconstruct.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/structural/reconstruct.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Tests for structural/reconstruct.ts — file reconstruction from entity merges.
3
+ * Pure logic, no tree-sitter / WASM dependency.
4
+ */
5
+ import { describe, it, expect } from "vitest";
6
+ import { reconstructFile } from "../../structural/reconstruct.js";
7
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
8
+ function makeEntity(sig, text, start) {
9
+ return {
10
+ signature: sig,
11
+ kind: "function",
12
+ text,
13
+ startByte: start,
14
+ endByte: start + text.length,
15
+ startLine: 0,
16
+ };
17
+ }
18
+ function included(sig, mergedText) {
19
+ return { signature: sig, include: true, mergedText, reason: "test" };
20
+ }
21
+ function excluded(sig) {
22
+ return { signature: sig, include: false, mergedText: null, reason: "deleted" };
23
+ }
24
+ // ─── Tests ────────────────────────────────────────────────────────────────────
25
+ describe("reconstructFile", () => {
26
+ it("emits a single entity verbatim", () => {
27
+ const source = "function foo() {}";
28
+ const e = makeEntity("function:foo", "function foo() {}", 0);
29
+ const result = reconstructFile([included("function:foo", "function foo() {}")], [e], [], source);
30
+ expect(result).toBe("function foo() {}");
31
+ });
32
+ it("preserves gap between two entities", () => {
33
+ const source = "function foo() {}\n\nfunction bar() {}";
34
+ const foo = makeEntity("function:foo", "function foo() {}", 0);
35
+ const bar = makeEntity("function:bar", "function bar() {}", 19);
36
+ const result = reconstructFile([
37
+ included("function:foo", "function foo() {}"),
38
+ included("function:bar", "function bar() {}"),
39
+ ], [foo, bar], [], source);
40
+ expect(result).toBe(source);
41
+ });
42
+ it("uses merged text instead of theirs text when changed", () => {
43
+ const source = "function foo() {}";
44
+ const e = makeEntity("function:foo", "function foo() {}", 0);
45
+ const result = reconstructFile([included("function:foo", "function foo() { return 42; }")], [e], [], source);
46
+ expect(result).toBe("function foo() { return 42; }");
47
+ });
48
+ it("omits deleted entities while preserving surrounding gaps", () => {
49
+ // theirs: foo\n\nbar — bar is deleted
50
+ const source = "function foo() {}\n\nfunction bar() {}";
51
+ const foo = makeEntity("function:foo", "function foo() {}", 0);
52
+ const bar = makeEntity("function:bar", "function bar() {}", 19);
53
+ const result = reconstructFile([included("function:foo", "function foo() {}"), excluded("function:bar")], [foo, bar], [], source);
54
+ // The gap "\n\n" before bar is emitted, but bar's text is omitted
55
+ expect(result).toBe("function foo() {}\n\n");
56
+ });
57
+ it("appends ours-added entities after theirs content", () => {
58
+ const theirsSource = "function foo() {}";
59
+ const theirsFoo = makeEntity("function:foo", "function foo() {}", 0);
60
+ const oursBar = makeEntity("function:bar", "function bar() {}", 99);
61
+ const result = reconstructFile([
62
+ included("function:foo", "function foo() {}"),
63
+ included("function:bar", "function bar() {}"),
64
+ ], [theirsFoo], [oursBar], theirsSource);
65
+ expect(result).toContain("function foo() {}");
66
+ expect(result).toContain("function bar() {}");
67
+ });
68
+ it("preserves trailing content from theirs source", () => {
69
+ const source = "function foo() {}\n// EOF\n";
70
+ const e = makeEntity("function:foo", "function foo() {}", 0);
71
+ const result = reconstructFile([included("function:foo", "function foo() {}")], [e], [], source);
72
+ expect(result).toBe("function foo() {}\n// EOF\n");
73
+ });
74
+ it("handles empty entity list", () => {
75
+ const result = reconstructFile([], [], [], "");
76
+ expect(result).toBe("");
77
+ });
78
+ it("does not double-emit entities that appear in both theirs and ours", () => {
79
+ const source = "function foo() {}";
80
+ const e = makeEntity("function:foo", "function foo() {}", 0);
81
+ const result = reconstructFile([included("function:foo", "function foo() {}")], [e], [e], // same entity in ours
82
+ source);
83
+ // Should appear exactly once
84
+ const count = (result.match(/function foo/g) ?? []).length;
85
+ expect(count).toBe(1);
86
+ });
87
+ it("handles reordered entities by following theirs order", () => {
88
+ // theirs order: bar then foo
89
+ const source = "function bar() {}\nfunction foo() {}";
90
+ const bar = makeEntity("function:bar", "function bar() {}", 0);
91
+ const foo = makeEntity("function:foo", "function foo() {}", 18);
92
+ const result = reconstructFile([
93
+ included("function:bar", "function bar() {}"),
94
+ included("function:foo", "function foo() {}"),
95
+ ], [bar, foo], // theirs order
96
+ [foo, bar], // ours order (reversed)
97
+ source);
98
+ // Result should follow theirs order (bar before foo)
99
+ const barIdx = result.indexOf("function bar");
100
+ const fooIdx = result.indexOf("function foo");
101
+ expect(barIdx).toBeLessThan(fooIdx);
102
+ });
103
+ });
104
+ //# sourceMappingURL=reconstruct.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconstruct.test.js","sourceRoot":"","sources":["../../../src/__tests__/structural/reconstruct.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Integration tests for structural/index.ts
3
+ *
4
+ * `tryStructuralMergeResolve` requires web-tree-sitter (optional peer),
5
+ * which is not installed in the test environment.
6
+ *
7
+ * These tests verify:
8
+ * - Graceful degradation when web-tree-sitter is absent (returns null).
9
+ * - The `isTypeScriptFile` / `isStructuralLanguage` guards.
10
+ * - `wrapStructuralResult` produces a valid MergeResult.
11
+ * - `reconstructVersions` (via the public API) extracts ours/theirs correctly.
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=structural-index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural-index.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/structural/structural-index.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Integration tests for structural/index.ts
3
+ *
4
+ * `tryStructuralMergeResolve` requires web-tree-sitter (optional peer),
5
+ * which is not installed in the test environment.
6
+ *
7
+ * These tests verify:
8
+ * - Graceful degradation when web-tree-sitter is absent (returns null).
9
+ * - The `isTypeScriptFile` / `isStructuralLanguage` guards.
10
+ * - `wrapStructuralResult` produces a valid MergeResult.
11
+ * - `reconstructVersions` (via the public API) extracts ours/theirs correctly.
12
+ */
13
+ import { describe, it, expect } from "vitest";
14
+ import { tryStructuralMergeResolve, wrapStructuralResult, isTypeScriptFile, isStructuralLanguage, } from "../../structural/index.js";
15
+ // ─── isTypeScriptFile ─────────────────────────────────────────────────────────
16
+ describe("isTypeScriptFile", () => {
17
+ it("accepts .ts", () => expect(isTypeScriptFile("src/app.ts")).toBe(true));
18
+ it("accepts .tsx", () => expect(isTypeScriptFile("src/comp.tsx")).toBe(true));
19
+ it("rejects .d.ts", () => expect(isTypeScriptFile("types.d.ts")).toBe(false));
20
+ it("rejects .js", () => expect(isTypeScriptFile("src/app.js")).toBe(false));
21
+ });
22
+ // ─── isStructuralLanguage ─────────────────────────────────────────────────────
23
+ describe("isStructuralLanguage", () => {
24
+ it("accepts .ts, .tsx, .js, .jsx, .py, .go, .rs", () => {
25
+ for (const ext of [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs"]) {
26
+ expect(isStructuralLanguage(`src/file${ext}`)).toBe(true);
27
+ }
28
+ });
29
+ it("rejects .d.ts", () => expect(isStructuralLanguage("types.d.ts")).toBe(false));
30
+ it("rejects .css", () => expect(isStructuralLanguage("style.css")).toBe(false));
31
+ });
32
+ // ─── tryStructuralMergeResolve — graceful degradation ────────────────────────
33
+ const SIMPLE_CONFLICT = `\
34
+ function foo() {
35
+ <<<<<<< ours
36
+ return 1;
37
+ ||||||| base
38
+ return 0;
39
+ =======
40
+ return 2;
41
+ >>>>>>> theirs
42
+ }
43
+ `;
44
+ describe("tryStructuralMergeResolve", () => {
45
+ it("returns null for unsupported file types", async () => {
46
+ const result = await tryStructuralMergeResolve(SIMPLE_CONFLICT, "styles.css");
47
+ expect(result).toBeNull();
48
+ });
49
+ it("returns null for .d.ts files", async () => {
50
+ const result = await tryStructuralMergeResolve(SIMPLE_CONFLICT, "types.d.ts");
51
+ expect(result).toBeNull();
52
+ });
53
+ it("returns null gracefully when web-tree-sitter is not installed", async () => {
54
+ // web-tree-sitter is an optional peer dep — not installed in test env.
55
+ // The loader returns null, and tryStructuralMergeResolve returns null.
56
+ const result = await tryStructuralMergeResolve(SIMPLE_CONFLICT, "src/app.ts");
57
+ expect(result).toBeNull();
58
+ });
59
+ it("returns null gracefully for Python files too", async () => {
60
+ const result = await tryStructuralMergeResolve(SIMPLE_CONFLICT, "src/app.py");
61
+ expect(result).toBeNull();
62
+ });
63
+ it("never throws — always returns string | null", async () => {
64
+ await expect(tryStructuralMergeResolve(SIMPLE_CONFLICT, "src/app.ts")).resolves.not.toThrow();
65
+ });
66
+ });
67
+ // ─── wrapStructuralResult ─────────────────────────────────────────────────────
68
+ describe("wrapStructuralResult", () => {
69
+ const conflicted = `// header\n<<<<<<< ours\nreturn 1;\n||||||| base\nreturn 0;\n=======\nreturn 2;\n>>>>>>> theirs\n// footer\n`;
70
+ const merged = "// header\nreturn 1;\n// footer\n";
71
+ it("returns filePath correctly", () => {
72
+ const result = wrapStructuralResult(conflicted, merged, "src/app.ts");
73
+ expect(result.filePath).toBe("src/app.ts");
74
+ });
75
+ it("sets mergedContent to the provided merged string", () => {
76
+ const result = wrapStructuralResult(conflicted, merged, "src/app.ts");
77
+ expect(result.mergedContent).toBe(merged);
78
+ });
79
+ it("marks all resolutions as autoResolved", () => {
80
+ const result = wrapStructuralResult(conflicted, merged, "src/app.ts");
81
+ expect(result.stats.totalConflicts).toBe(1);
82
+ expect(result.stats.autoResolved).toBe(1);
83
+ expect(result.stats.remaining).toBe(0);
84
+ for (const r of result.resolutions) {
85
+ expect(r.autoResolved).toBe(true);
86
+ }
87
+ });
88
+ it("extracts the correct number of hunks", () => {
89
+ const twoConflicts = [
90
+ "<<<<<<< ours\na\n=======\nb\n>>>>>>> theirs",
91
+ "<<<<<<< ours\nc\n=======\nd\n>>>>>>> theirs",
92
+ ].join("\n");
93
+ const result = wrapStructuralResult(twoConflicts, "merged", "src/app.ts");
94
+ expect(result.hunks).toHaveLength(2);
95
+ expect(result.stats.totalConflicts).toBe(2);
96
+ });
97
+ it("validation result is present and valid for a clean merged content", () => {
98
+ const result = wrapStructuralResult(conflicted, merged, "src/app.ts");
99
+ expect(result.validation.isValid).toBe(true);
100
+ expect(result.validation.hasResidualMarkers).toBe(false);
101
+ });
102
+ it("validation detects residual conflict markers if merged content has them", () => {
103
+ const result = wrapStructuralResult(conflicted, "<<<<<<< ours\nreturn 1;\n", "src/app.ts");
104
+ expect(result.validation.hasResidualMarkers).toBe(true);
105
+ expect(result.validation.isValid).toBe(false);
106
+ });
107
+ });
108
+ //# sourceMappingURL=structural-index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural-index.test.js","sourceRoot":"","sources":["../../../src/__tests__/structural/structural-index.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * GitWand — Détection de blocs déplacés (block-move)
3
+ *
4
+ * Approche Rabin-Karp : on hash chaque fenêtre de N lignes consécutives dans
5
+ * `base`, `ours` et `theirs`, on cherche les fenêtres présentes dans **ours et
6
+ * theirs mais pas dans base** (ou à des positions différentes), et on les
7
+ * confirme littéralement (anti-collision).
8
+ *
9
+ * Usage v2.1 : primitive seule, exposée via `src/index.ts`. Les patterns
10
+ * peuvent l'utiliser pour pénaliser le score de confiance de `complex` quand
11
+ * un refactor massif est détecté côté ours et côté theirs (signal « pas
12
+ * d'auto-résolution »). Cette dimension `algorithmStability` est wired dans
13
+ * `ConfidenceScore` au commit suivant — mais aucun pattern ne la consomme
14
+ * encore (la primitive sert pour la v2.6 refactoring-aware merge).
15
+ */
16
+ /** Un bloc déplacé entre `ours` et `theirs`, absent de `base` à cette position. */
17
+ export interface MovedBlock {
18
+ /** Les lignes brutes du bloc (depuis `ours`, non normalisées). */
19
+ block: string[];
20
+ /** Position de début dans `ours` (inclus). */
21
+ oursPos: number;
22
+ /** Position de début dans `theirs` (inclus). */
23
+ theirsPos: number;
24
+ /** Position dans `base` si la fenêtre y existe, sinon `null`. */
25
+ basePos: number | null;
26
+ }
27
+ /** Options pour `detectBlockMove`. */
28
+ export interface BlockMoveOptions {
29
+ /** Taille de la fenêtre de hash. Défaut 5 — équilibre robustesse vs sensibilité. */
30
+ windowSize?: number;
31
+ /**
32
+ * Diversité minimale de tokens uniques (split sur whitespace + ponctuation)
33
+ * dans la fenêtre normalisée — sous ce seuil, on skip pour éviter les faux
34
+ * positifs sur les boucles `for (let i = 0; ...)` répétées. Défaut 4.
35
+ */
36
+ minTokenDiversity?: number;
37
+ }
38
+ /**
39
+ * Détecte les blocs de N lignes consécutives présents à la fois dans `ours` et
40
+ * `theirs` mais absents (ou à une autre position) dans `base`.
41
+ *
42
+ * Heuristique anti-faux-positif :
43
+ * 1. Whitespace trim sur chaque ligne pour le hash (le contenu du bloc reste
44
+ * original dans la sortie).
45
+ * 2. Filtre `minTokenDiversity` : la fenêtre doit contenir suffisamment de
46
+ * tokens distincts pour ne pas matcher du code triviale répété.
47
+ * 3. Confirmation littérale : après match de hash, comparaison ligne-à-ligne.
48
+ * 4. Compaction : blocs adjacents fusionnés en un seul `MovedBlock` plus grand.
49
+ *
50
+ * @returns tableau de blocs déplacés détectés (vide si rien)
51
+ */
52
+ export declare function detectBlockMove(base: string[], ours: string[], theirs: string[], opts?: BlockMoveOptions): MovedBlock[];
53
+ //# sourceMappingURL=block-move.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-move.d.ts","sourceRoot":"","sources":["../../src/diff/block-move.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,mFAAmF;AACnF,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,sCAAsC;AACtC,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,CAAC,EAAE,gBAAgB,GACtB,UAAU,EAAE,CA4Dd"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * GitWand — Détection de blocs déplacés (block-move)
3
+ *
4
+ * Approche Rabin-Karp : on hash chaque fenêtre de N lignes consécutives dans
5
+ * `base`, `ours` et `theirs`, on cherche les fenêtres présentes dans **ours et
6
+ * theirs mais pas dans base** (ou à des positions différentes), et on les
7
+ * confirme littéralement (anti-collision).
8
+ *
9
+ * Usage v2.1 : primitive seule, exposée via `src/index.ts`. Les patterns
10
+ * peuvent l'utiliser pour pénaliser le score de confiance de `complex` quand
11
+ * un refactor massif est détecté côté ours et côté theirs (signal « pas
12
+ * d'auto-résolution »). Cette dimension `algorithmStability` est wired dans
13
+ * `ConfidenceScore` au commit suivant — mais aucun pattern ne la consomme
14
+ * encore (la primitive sert pour la v2.6 refactoring-aware merge).
15
+ */
16
+ const DEFAULT_WINDOW_SIZE = 5;
17
+ const DEFAULT_MIN_TOKEN_DIVERSITY = 4;
18
+ /**
19
+ * Détecte les blocs de N lignes consécutives présents à la fois dans `ours` et
20
+ * `theirs` mais absents (ou à une autre position) dans `base`.
21
+ *
22
+ * Heuristique anti-faux-positif :
23
+ * 1. Whitespace trim sur chaque ligne pour le hash (le contenu du bloc reste
24
+ * original dans la sortie).
25
+ * 2. Filtre `minTokenDiversity` : la fenêtre doit contenir suffisamment de
26
+ * tokens distincts pour ne pas matcher du code triviale répété.
27
+ * 3. Confirmation littérale : après match de hash, comparaison ligne-à-ligne.
28
+ * 4. Compaction : blocs adjacents fusionnés en un seul `MovedBlock` plus grand.
29
+ *
30
+ * @returns tableau de blocs déplacés détectés (vide si rien)
31
+ */
32
+ export function detectBlockMove(base, ours, theirs, opts) {
33
+ const W = opts?.windowSize ?? DEFAULT_WINDOW_SIZE;
34
+ const minDiv = opts?.minTokenDiversity ?? DEFAULT_MIN_TOKEN_DIVERSITY;
35
+ if (ours.length < W || theirs.length < W)
36
+ return [];
37
+ // ─── 1. Hash de chaque fenêtre dans les trois fichiers ─────
38
+ const hashOurs = hashWindows(ours, W);
39
+ const hashTheirs = hashWindows(theirs, W);
40
+ const hashBase = hashWindows(base, W);
41
+ // Index : pour chaque hash, la première position dans chaque fichier.
42
+ const oursIndex = buildPositionIndex(hashOurs);
43
+ const theirsIndex = buildPositionIndex(hashTheirs);
44
+ const baseIndex = buildPositionIndex(hashBase);
45
+ // ─── 2. Pour chaque hash présent dans ours ET theirs, vérifier ─
46
+ const candidates = [];
47
+ for (const [hash, oursPositions] of oursIndex) {
48
+ const theirsPositions = theirsIndex.get(hash);
49
+ if (!theirsPositions)
50
+ continue;
51
+ // Considérer la première occurrence de chaque côté (les suivantes sont
52
+ // gérées par compaction au step 3 si elles sont adjacentes).
53
+ const oursPos = oursPositions[0];
54
+ const theirsPos = theirsPositions[0];
55
+ // Confirmation littérale (anti-collision Rabin-Karp).
56
+ if (!windowsMatch(ours, oursPos, theirs, theirsPos, W))
57
+ continue;
58
+ // Filtre de diversité de tokens.
59
+ const block = ours.slice(oursPos, oursPos + W);
60
+ if (countDistinctTokens(block) < minDiv)
61
+ continue;
62
+ // Position dans base : si la fenêtre y existe avec exactement le même
63
+ // contenu, on note la position (utile au consommateur pour qualifier le
64
+ // déplacement). Sinon `null` — la fenêtre est purement nouvelle des deux
65
+ // côtés.
66
+ const basePositions = baseIndex.get(hash);
67
+ let basePos = null;
68
+ if (basePositions) {
69
+ for (const bp of basePositions) {
70
+ if (windowsMatch(ours, oursPos, base, bp, W)) {
71
+ basePos = bp;
72
+ break;
73
+ }
74
+ }
75
+ // Si la fenêtre est dans base à la même position relative dans les trois
76
+ // fichiers (delta de position négligeable), ce n'est pas un block-move.
77
+ if (basePos !== null && Math.abs(oursPos - basePos) < W && Math.abs(theirsPos - basePos) < W) {
78
+ continue;
79
+ }
80
+ }
81
+ candidates.push({ block, oursPos, theirsPos, basePos });
82
+ }
83
+ // ─── 3. Compaction : fusionner les blocs adjacents avec le même delta ─
84
+ return compactAdjacent(candidates, W);
85
+ }
86
+ // ─── Utils internes ───────────────────────────────────────────
87
+ /** Polynomial rolling hash — base 257, modulo 2^32 (overflow naturel JS bit-ops). */
88
+ function hashWindows(lines, W) {
89
+ const n = lines.length;
90
+ if (n < W)
91
+ return [];
92
+ const hashes = new Array(n - W + 1);
93
+ // Pré-calcule un hash sur la ligne normalisée (trim) pour stabilité whitespace.
94
+ const lineHashes = lines.map((l) => stringHash(l.trim()));
95
+ // Fenêtre : combine W line-hashes via une formule polynomiale.
96
+ for (let i = 0; i + W <= n; i++) {
97
+ let h = 0;
98
+ for (let k = 0; k < W; k++) {
99
+ h = (Math.imul(h, 257) + lineHashes[i + k]) | 0;
100
+ }
101
+ hashes[i] = h;
102
+ }
103
+ return hashes;
104
+ }
105
+ /** Hash 32-bit d'une chaîne (variante djb2). */
106
+ function stringHash(s) {
107
+ let h = 5381;
108
+ for (let i = 0; i < s.length; i++) {
109
+ h = (Math.imul(h, 33) ^ s.charCodeAt(i)) | 0;
110
+ }
111
+ return h;
112
+ }
113
+ /** Index hash → liste des positions où il apparaît. */
114
+ function buildPositionIndex(hashes) {
115
+ const index = new Map();
116
+ for (let i = 0; i < hashes.length; i++) {
117
+ const h = hashes[i];
118
+ let arr = index.get(h);
119
+ if (!arr) {
120
+ arr = [];
121
+ index.set(h, arr);
122
+ }
123
+ arr.push(i);
124
+ }
125
+ return index;
126
+ }
127
+ /** Compare littéralement deux fenêtres (ligne par ligne, contenu brut). */
128
+ function windowsMatch(a, aPos, b, bPos, W) {
129
+ for (let k = 0; k < W; k++) {
130
+ if (a[aPos + k] !== b[bPos + k])
131
+ return false;
132
+ }
133
+ return true;
134
+ }
135
+ /** Nombre de tokens distincts dans un bloc (split simple sur whitespace + ponctuation). */
136
+ function countDistinctTokens(block) {
137
+ const tokens = new Set();
138
+ for (const line of block) {
139
+ for (const t of line.split(/[\s{}[\](),:;"'`=<>+\-*/!?]+/)) {
140
+ if (t.length > 0)
141
+ tokens.add(t);
142
+ }
143
+ }
144
+ return tokens.size;
145
+ }
146
+ /**
147
+ * Fusionne les blocs adjacents avec la même paire (deltaOurs, deltaTheirs).
148
+ * Deux candidats sont adjacents si la fenêtre suivante commence à exactement
149
+ * `oursPos + 1` (et idem en theirs) — typique d'un grand bloc de longueur
150
+ * L > W découpé en (L - W + 1) fenêtres qui se chevauchent.
151
+ *
152
+ * On track séparément la position de la *dernière* fenêtre fusionnée (`lastOursPos`,
153
+ * `lastTheirsPos`) parce que `current.oursPos` reste figé sur le début du bloc
154
+ * compacté. Un `basePos` non-null d'un candidat plus tardif gagne sur un null
155
+ * antérieur (la moved-block info est précieuse).
156
+ */
157
+ function compactAdjacent(candidates, W) {
158
+ if (candidates.length === 0)
159
+ return [];
160
+ const sorted = [...candidates].sort((a, b) => a.oursPos - b.oursPos);
161
+ const result = [];
162
+ let current = sorted[0];
163
+ let lastOursPos = current.oursPos;
164
+ let lastTheirsPos = current.theirsPos;
165
+ for (let i = 1; i < sorted.length; i++) {
166
+ const next = sorted[i];
167
+ const isAdjacent = next.oursPos === lastOursPos + 1 &&
168
+ next.theirsPos === lastTheirsPos + 1;
169
+ if (isAdjacent) {
170
+ current = {
171
+ ...current,
172
+ block: [...current.block, next.block[W - 1]],
173
+ // basePos : si le candidat suivant en a un et nous pas (ou vice versa),
174
+ // on garde celui qui est non-null. Si les deux en ont un avec un delta
175
+ // constant vs oursPos, on garde le premier (sémantique : le bloc commence
176
+ // à la position d'origine du déplacement).
177
+ basePos: current.basePos ?? next.basePos,
178
+ };
179
+ lastOursPos = next.oursPos;
180
+ lastTheirsPos = next.theirsPos;
181
+ }
182
+ else {
183
+ result.push(current);
184
+ current = next;
185
+ lastOursPos = next.oursPos;
186
+ lastTheirsPos = next.theirsPos;
187
+ }
188
+ }
189
+ result.push(current);
190
+ return result;
191
+ }
192
+ //# sourceMappingURL=block-move.js.map