ultracode 5.3.0 → 5.5.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 (57) hide show
  1. package/dist/chunks/analysis-tool-handlers-H2RXLDPX.js +817 -0
  2. package/dist/chunks/analysis-tool-handlers-RJZAR6VT.js +817 -0
  3. package/dist/chunks/analysis-tool-handlers-Z2RF24T7.js +13 -0
  4. package/dist/chunks/autodoc-tool-handlers-CV5JEQUA.js +1112 -0
  5. package/dist/chunks/autodoc-tool-handlers-EHTNCH6I.js +1112 -0
  6. package/dist/chunks/autodoc-tool-handlers-MECXQJ2K.js +138 -0
  7. package/dist/chunks/chaos-CO7TOBOJ.js +18 -0
  8. package/dist/chunks/chaos-VM2PXERO.js +1573 -0
  9. package/dist/chunks/chaos-W3XRVJ7K.js +1564 -0
  10. package/dist/chunks/chunk-6K37BWK5.js +439 -0
  11. package/dist/chunks/chunk-EALTCYHZ.js +10 -0
  12. package/dist/chunks/chunk-FTBE7VMY.js +316 -0
  13. package/dist/chunks/chunk-KBW6LRQP.js +322 -0
  14. package/dist/chunks/chunk-NKUHX4CU.js +5 -0
  15. package/dist/chunks/chunk-NZFF4DQ4.js +3179 -0
  16. package/dist/chunks/chunk-RGP5UVQ7.js +3179 -0
  17. package/dist/chunks/chunk-RMZXFGQZ.js +322 -0
  18. package/dist/chunks/chunk-UG44F23Y.js +316 -0
  19. package/dist/chunks/chunk-V2SCB5H5.js +4403 -0
  20. package/dist/chunks/chunk-V6JAQNM3.js +1 -0
  21. package/dist/chunks/chunk-XFGXM4CR.js +4403 -0
  22. package/dist/chunks/dev-agent-JVIGBMHQ.js +1 -0
  23. package/dist/chunks/dev-agent-TRVP5U6N.js +1624 -0
  24. package/dist/chunks/dev-agent-Y5G5WKQ4.js +1624 -0
  25. package/dist/chunks/graph-storage-factory-AYZ57YSL.js +13 -0
  26. package/dist/chunks/graph-storage-factory-GTAIJEI5.js +1 -0
  27. package/dist/chunks/graph-storage-factory-T2WO5QVG.js +13 -0
  28. package/dist/chunks/incremental-updater-KDIQGAUU.js +14 -0
  29. package/dist/chunks/incremental-updater-OJRSTO3Q.js +1 -0
  30. package/dist/chunks/incremental-updater-SBEBH7KF.js +14 -0
  31. package/dist/chunks/indexer-agent-H3QIEL3Z.js +21 -0
  32. package/dist/chunks/indexer-agent-KHF5JMV7.js +21 -0
  33. package/dist/chunks/indexer-agent-SHJD6Z77.js +1 -0
  34. package/dist/chunks/indexing-pipeline-J6Z4BHKF.js +1 -0
  35. package/dist/chunks/indexing-pipeline-OY3337QN.js +249 -0
  36. package/dist/chunks/indexing-pipeline-WCXIDMAP.js +249 -0
  37. package/dist/chunks/merge-agent-LSUBDJB2.js +2481 -0
  38. package/dist/chunks/merge-agent-MJEW3HWU.js +2481 -0
  39. package/dist/chunks/merge-agent-O45OXF33.js +11 -0
  40. package/dist/chunks/merge-tool-handlers-BDSVNQVZ.js +277 -0
  41. package/dist/chunks/merge-tool-handlers-HP7DRBXJ.js +1 -0
  42. package/dist/chunks/merge-tool-handlers-RUJAKE3D.js +277 -0
  43. package/dist/chunks/pattern-tool-handlers-L62W3CXR.js +1549 -0
  44. package/dist/chunks/pattern-tool-handlers-SAHX2CVW.js +13 -0
  45. package/dist/chunks/query-agent-3TWDFIMT.js +191 -0
  46. package/dist/chunks/query-agent-HXQ3BMMF.js +191 -0
  47. package/dist/chunks/query-agent-USMC2GNG.js +1 -0
  48. package/dist/chunks/semantic-agent-MQCAWIAB.js +6381 -0
  49. package/dist/chunks/semantic-agent-NDGR3NAK.js +6381 -0
  50. package/dist/chunks/semantic-agent-S4ZL6GZC.js +137 -0
  51. package/dist/index.js +17 -17
  52. package/dist/roslyn-addon/.build-hash +1 -1
  53. package/dist/roslyn-addon/ILGPU.Algorithms.dll +0 -0
  54. package/dist/roslyn-addon/ILGPU.dll +0 -0
  55. package/dist/roslyn-addon/UltraCode.CSharp.deps.json +35 -0
  56. package/dist/roslyn-addon/UltraCode.CSharp.dll +0 -0
  57. package/package.json +1 -1
@@ -0,0 +1,2481 @@
1
+ import { getGlobalContainer, getOrCreateAgent } from './chunk-RMZXFGQZ.js';
2
+ import { BaseAgent } from './chunk-IGUCJL3R.js';
3
+ import { cosineSimilarity } from './chunk-QN237S7C.js';
4
+ import { init_fast_hash, hashText } from './chunk-HEMJHRHZ.js';
5
+ import './chunk-GMVGCSNU.js';
6
+ import { init_file_ops, readBytes } from './chunk-F7CKCMXI.js';
7
+ import './chunk-BMHPPH2B.js';
8
+ import { init_logging, log } from './chunk-VCCBEJQ5.js';
9
+ import { __export, __esm, __toCommonJS } from './chunk-NAQKA54E.js';
10
+ import { execSync } from 'child_process';
11
+ import { existsSync } from 'fs';
12
+ import { join } from 'path';
13
+
14
+ // src/merge/indexing/content-normalizer.ts
15
+ var content_normalizer_exports = {};
16
+ __export(content_normalizer_exports, {
17
+ ContentNormalizer: () => ContentNormalizer
18
+ });
19
+ var ContentNormalizer;
20
+ var init_content_normalizer = __esm({
21
+ "src/merge/indexing/content-normalizer.ts"() {
22
+ init_fast_hash();
23
+ init_file_ops();
24
+ ContentNormalizer = class {
25
+ /**
26
+ * Normalize a file.
27
+ *
28
+ * @param filePath - Absolute path to file
29
+ * @returns Normalized content with metadata
30
+ */
31
+ async normalize(filePath) {
32
+ const rawBytes = Buffer.from(await readBytes(filePath));
33
+ const { encoding, hasBom } = this.detectEncoding(rawBytes);
34
+ let content = rawBytes.toString(encoding);
35
+ if (hasBom && content.charCodeAt(0) === 65279) {
36
+ content = content.slice(1);
37
+ }
38
+ content = this.normalizeLineEndings(content);
39
+ content = this.trimTrailingWhitespace(content);
40
+ content = content.replace(/\n+$/, "\n");
41
+ return {
42
+ content,
43
+ originalEncoding: encoding,
44
+ hadBom: hasBom
45
+ };
46
+ }
47
+ /**
48
+ * Compute SHA256 hash of content.
49
+ *
50
+ * Used for Fast Path Level 1 matching (exact content match).
51
+ *
52
+ * @param content - Normalized content string
53
+ * @returns Hex-encoded SHA256 hash
54
+ */
55
+ computeContentHash(content) {
56
+ return hashText(content);
57
+ }
58
+ /**
59
+ * Detect encoding via BOM detection.
60
+ *
61
+ * @param bytes - Raw file bytes
62
+ * @returns Detected encoding and BOM presence
63
+ */
64
+ detectEncoding(bytes) {
65
+ if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) {
66
+ return { encoding: "utf8", hasBom: true };
67
+ }
68
+ if (bytes.length >= 2 && bytes[0] === 255 && bytes[1] === 254) {
69
+ return { encoding: "utf16le", hasBom: true };
70
+ }
71
+ if (bytes.length >= 2 && bytes[0] === 254 && bytes[1] === 255) {
72
+ return { encoding: "utf16le", hasBom: true };
73
+ }
74
+ return { encoding: "utf8", hasBom: false };
75
+ }
76
+ /**
77
+ * Normalize line endings (CRLF/CR → LF).
78
+ *
79
+ * @param content - Content with potentially mixed line endings
80
+ * @returns Content with Unix-style LF line endings
81
+ */
82
+ normalizeLineEndings(content) {
83
+ content = content.replace(/\r\n/g, "\n");
84
+ content = content.replace(/\r/g, "\n");
85
+ return content;
86
+ }
87
+ /**
88
+ * Remove trailing whitespace on each line.
89
+ *
90
+ * @param content - Content to trim
91
+ * @returns Content with trimmed lines
92
+ */
93
+ trimTrailingWhitespace(content) {
94
+ return content.split("\n").map((line) => line.replace(/[ \t]+$/, "")).join("\n");
95
+ }
96
+ };
97
+ }
98
+ });
99
+
100
+ // src/merge/models/code-unit.ts
101
+ var code_unit_exports = {};
102
+ __export(code_unit_exports, {
103
+ CodeUnitType: () => CodeUnitType
104
+ });
105
+ var CodeUnitType;
106
+ var init_code_unit = __esm({
107
+ "src/merge/models/code-unit.ts"() {
108
+ CodeUnitType = /* @__PURE__ */ ((CodeUnitType2) => {
109
+ CodeUnitType2["File"] = "file";
110
+ CodeUnitType2["Module"] = "module";
111
+ CodeUnitType2["Class"] = "class";
112
+ CodeUnitType2["Interface"] = "interface";
113
+ CodeUnitType2["Function"] = "function";
114
+ CodeUnitType2["Method"] = "method";
115
+ CodeUnitType2["Property"] = "property";
116
+ CodeUnitType2["Block"] = "block";
117
+ CodeUnitType2["Statement"] = "statement";
118
+ return CodeUnitType2;
119
+ })(CodeUnitType || {});
120
+ }
121
+ });
122
+
123
+ // src/agents/merge-agent.ts
124
+ init_logging();
125
+
126
+ // src/merge/engine/ai-conflict-resolver.ts
127
+ init_logging();
128
+
129
+ // src/merge/models/semantic-conflict.ts
130
+ function createOverlappingConflict(baseUnit, branchAUnit, branchBUnit) {
131
+ const id = `conflict-${branchAUnit.id}-${branchBUnit.id}-${Date.now()}`;
132
+ return {
133
+ id,
134
+ type: "OverlappingChanges" /* OverlappingChanges */,
135
+ severity: "Medium" /* Medium */,
136
+ baseUnit,
137
+ branchAUnit,
138
+ branchBUnit,
139
+ description: `Both branches modified ${branchAUnit.fullyQualifiedName}`,
140
+ conflictingRegions: [],
141
+ autoResolvable: false
142
+ };
143
+ }
144
+ function createIncompatibleIntentsConflict(baseUnit, branchAUnit, branchBUnit, branchAIntent, branchBIntent) {
145
+ const id = `conflict-${branchAUnit.id}-${branchBUnit.id}-${Date.now()}`;
146
+ return {
147
+ id,
148
+ type: "IncompatibleIntents" /* IncompatibleIntents */,
149
+ severity: "High" /* High */,
150
+ baseUnit,
151
+ branchAUnit,
152
+ branchBUnit,
153
+ branchAIntent,
154
+ branchBIntent,
155
+ description: `Incompatible intents: ${branchAIntent.type} vs ${branchBIntent.type}`,
156
+ conflictingRegions: [],
157
+ autoResolvable: false
158
+ };
159
+ }
160
+ function createAPIBreakingConflict(baseUnit, branchAUnit, branchBUnit) {
161
+ const id = `conflict-${branchAUnit.id}-${branchBUnit.id}-${Date.now()}`;
162
+ return {
163
+ id,
164
+ type: "APIBreakingChange" /* APIBreakingChange */,
165
+ severity: "Critical" /* Critical */,
166
+ baseUnit,
167
+ branchAUnit,
168
+ branchBUnit,
169
+ description: `API breaking change in ${branchAUnit.fullyQualifiedName}`,
170
+ conflictingRegions: [],
171
+ autoResolvable: false
172
+ };
173
+ }
174
+
175
+ // src/merge/engine/ai-conflict-resolver.ts
176
+ var AIConflictResolver = class {
177
+ config;
178
+ embeddingCache = /* @__PURE__ */ new Map();
179
+ constructor(config) {
180
+ this.config = config;
181
+ }
182
+ /**
183
+ * Analyze a conflict using AI
184
+ *
185
+ * @param conflict - Conflict to analyze
186
+ * @returns AI analysis result
187
+ */
188
+ async analyzeConflict(conflict) {
189
+ const { baseUnit, branchAUnit, branchBUnit } = conflict;
190
+ const [baseEmbedding, branchAEmbedding, branchBEmbedding] = await Promise.all([
191
+ this.getEmbedding(baseUnit),
192
+ this.getEmbedding(branchAUnit),
193
+ this.getEmbedding(branchBUnit)
194
+ ]);
195
+ const similarity = this.cosineSimilarity(branchAEmbedding, branchBEmbedding);
196
+ const branchADistance = baseEmbedding && branchAEmbedding ? this.cosineDistance(baseEmbedding, branchAEmbedding) : 0;
197
+ const branchBDistance = baseEmbedding && branchBEmbedding ? this.cosineDistance(baseEmbedding, branchBEmbedding) : 0;
198
+ const { strategy, confidence, explanation, mergedCode } = this.determineStrategy(
199
+ conflict,
200
+ similarity,
201
+ branchADistance,
202
+ branchBDistance
203
+ );
204
+ return {
205
+ similarity,
206
+ branchADistance,
207
+ branchBDistance,
208
+ suggestedStrategy: strategy,
209
+ confidence,
210
+ explanation,
211
+ mergedCode
212
+ };
213
+ }
214
+ /**
215
+ * Get or generate embedding for a code unit
216
+ */
217
+ async getEmbedding(unit) {
218
+ if (!unit) return null;
219
+ const cacheKey = this.getCacheKey(unit);
220
+ if (this.embeddingCache.has(cacheKey)) {
221
+ return this.embeddingCache.get(cacheKey);
222
+ }
223
+ if (unit.embedding) {
224
+ this.embeddingCache.set(cacheKey, unit.embedding);
225
+ return unit.embedding;
226
+ }
227
+ try {
228
+ const text = this.prepareTextForEmbedding(unit);
229
+ const embedding = await this.config.embeddingGenerator.generateEmbedding(text);
230
+ this.embeddingCache.set(cacheKey, embedding);
231
+ return embedding;
232
+ } catch (error) {
233
+ log.w("AICONFLICT", "embedding_fail", { id: unit.id, err: String(error) });
234
+ return null;
235
+ }
236
+ }
237
+ /**
238
+ * Prepare text for embedding
239
+ */
240
+ prepareTextForEmbedding(unit) {
241
+ const parts = [];
242
+ if (unit.fullyQualifiedName) {
243
+ parts.push(`Name: ${unit.fullyQualifiedName}`);
244
+ }
245
+ if (unit.signature) {
246
+ parts.push(`Signature: ${unit.signature}`);
247
+ }
248
+ if (unit.content) {
249
+ const normalized = unit.content.trim().replace(/\s+/g, " ");
250
+ parts.push(`Code: ${normalized}`);
251
+ }
252
+ return parts.join("\n");
253
+ }
254
+ /**
255
+ * Determine resolution strategy based on similarity analysis
256
+ */
257
+ determineStrategy(conflict, similarity, branchADistance, branchBDistance) {
258
+ const { branchAUnit, branchBUnit } = conflict;
259
+ if (similarity >= this.config.highSimilarityThreshold) {
260
+ const preferA = branchADistance <= branchBDistance;
261
+ return {
262
+ strategy: preferA ? "TakeBranchA" /* TakeBranchA */ : "TakeBranchB" /* TakeBranchB */,
263
+ confidence: 0.95,
264
+ explanation: `Both branches made semantically similar changes (similarity: ${similarity.toFixed(2)}). Choosing ${preferA ? "branchA" : "branchB"} as it's closer to base.`,
265
+ mergedCode: preferA ? branchAUnit.content : branchBUnit.content
266
+ };
267
+ }
268
+ if (similarity >= this.config.mediumSimilarityThreshold) {
269
+ const mergedCode = this.attemptIntelligentMerge(conflict, similarity);
270
+ if (mergedCode) {
271
+ return {
272
+ strategy: "MergeBoth" /* MergeBoth */,
273
+ confidence: 0.7 + similarity * 0.2,
274
+ // 0.7-0.9 range
275
+ explanation: `Changes are semantically compatible (similarity: ${similarity.toFixed(2)}). AI-generated merge proposed.`,
276
+ mergedCode
277
+ };
278
+ }
279
+ return {
280
+ strategy: "ManualReview" /* ManualReview */,
281
+ confidence: 0.6,
282
+ explanation: `Changes are moderately similar (${similarity.toFixed(2)}) but automatic merge is uncertain. Manual review recommended.`
283
+ };
284
+ }
285
+ if (similarity >= this.config.lowSimilarityThreshold) {
286
+ return {
287
+ strategy: "ManualReview" /* ManualReview */,
288
+ confidence: 0.4,
289
+ explanation: `Changes are semantically different (similarity: ${similarity.toFixed(2)}). Manual review required.`
290
+ };
291
+ }
292
+ return {
293
+ strategy: "ManualReview" /* ManualReview */,
294
+ confidence: 0.2,
295
+ explanation: `Changes are semantically divergent (similarity: ${similarity.toFixed(2)}). Careful manual review strongly recommended.`
296
+ };
297
+ }
298
+ /**
299
+ * Attempt intelligent merge based on semantic analysis
300
+ *
301
+ * Heuristic: if changes are semantically close, try to combine them
302
+ */
303
+ attemptIntelligentMerge(conflict, similarity) {
304
+ const { baseUnit, branchAUnit, branchBUnit } = conflict;
305
+ const baseLength = baseUnit?.content.length || 0;
306
+ const branchALength = branchAUnit.content.length;
307
+ const branchBLength = branchBUnit.content.length;
308
+ const branchAAdded = branchALength > baseLength;
309
+ const branchBAdded = branchBLength > baseLength;
310
+ if (branchAAdded && branchBAdded && similarity >= 0.7) {
311
+ return branchALength > branchBLength ? branchAUnit.content : branchBUnit.content;
312
+ }
313
+ const maxChange = Math.max(Math.abs(branchALength - baseLength), Math.abs(branchBLength - baseLength));
314
+ if (maxChange < 100 && similarity >= 0.8) {
315
+ return branchAUnit.content;
316
+ }
317
+ return null;
318
+ }
319
+ /**
320
+ * Cosine similarity between two vectors (0-1, where 1 = identical)
321
+ */
322
+ cosineSimilarity(a, b) {
323
+ if (!a || !b || a.length !== b.length) return 0;
324
+ let dotProduct = 0;
325
+ let normA = 0;
326
+ let normB = 0;
327
+ for (let i = 0; i < a.length; i++) {
328
+ const aVal = a[i] ?? 0;
329
+ const bVal = b[i] ?? 0;
330
+ dotProduct += aVal * bVal;
331
+ normA += aVal * aVal;
332
+ normB += bVal * bVal;
333
+ }
334
+ const denominator = Math.sqrt(normA) * Math.sqrt(normB);
335
+ if (denominator === 0) return 0;
336
+ return dotProduct / denominator;
337
+ }
338
+ /**
339
+ * Cosine distance (1 - similarity)
340
+ */
341
+ cosineDistance(a, b) {
342
+ const similarity = this.cosineSimilarity(a, b);
343
+ return 1 - similarity;
344
+ }
345
+ /**
346
+ * Cache key for embedding
347
+ */
348
+ getCacheKey(unit) {
349
+ return `${unit.id}-${unit.contentHash}`;
350
+ }
351
+ /**
352
+ * Create Resolution based on AI analysis
353
+ */
354
+ createResolution(aiAnalysis) {
355
+ return {
356
+ strategy: aiAnalysis.suggestedStrategy,
357
+ confidence: aiAnalysis.confidence,
358
+ mergedCode: aiAnalysis.mergedCode || "",
359
+ explanation: aiAnalysis.explanation
360
+ };
361
+ }
362
+ /**
363
+ * Clear cache
364
+ */
365
+ clearCache() {
366
+ this.embeddingCache.clear();
367
+ }
368
+ /**
369
+ * Get cache statistics
370
+ */
371
+ getCacheStats() {
372
+ return {
373
+ size: this.embeddingCache.size,
374
+ maxSize: 1e3
375
+ // Can be made configurable
376
+ };
377
+ }
378
+ };
379
+
380
+ // src/merge/engine/three-way-merger.ts
381
+ init_logging();
382
+
383
+ // src/merge/models/change-intent.ts
384
+ function createBugFixIntent(evidence) {
385
+ return {
386
+ type: "BugFix" /* BugFix */,
387
+ confidence: evidence.length > 0 ? Math.min(0.7 + evidence.length * 0.1, 1) : 0.5,
388
+ evidence,
389
+ description: "Code change appears to fix a bug"
390
+ };
391
+ }
392
+ function createRefactoringIntent(evidence) {
393
+ return {
394
+ type: "Refactoring" /* Refactoring */,
395
+ confidence: evidence.length > 0 ? Math.min(0.7 + evidence.length * 0.1, 1) : 0.5,
396
+ evidence,
397
+ description: "Code change is refactoring without logic changes"
398
+ };
399
+ }
400
+ function createFeatureAdditionIntent(evidence) {
401
+ return {
402
+ type: "FeatureAddition" /* FeatureAddition */,
403
+ confidence: evidence.length > 0 ? Math.min(0.7 + evidence.length * 0.1, 1) : 0.5,
404
+ evidence,
405
+ description: "Code change adds new functionality"
406
+ };
407
+ }
408
+ function createAPIChangeIntent(evidence) {
409
+ return {
410
+ type: "APIChange" /* APIChange */,
411
+ confidence: evidence.length > 0 ? Math.min(0.7 + evidence.length * 0.1, 1) : 0.5,
412
+ evidence,
413
+ description: "Code change modifies API signatures"
414
+ };
415
+ }
416
+ function createUnknownIntent() {
417
+ return {
418
+ type: "Unknown" /* Unknown */,
419
+ confidence: 0,
420
+ evidence: [],
421
+ description: "Unable to classify change intent"
422
+ };
423
+ }
424
+
425
+ // src/merge/analysis/conflict-detector.ts
426
+ var ConflictDetector = class {
427
+ /**
428
+ * Determine whether there is a conflict between two changes
429
+ *
430
+ * @param baseUnit - Unit in base (may be null)
431
+ * @param branchAUnit - Unit in branchA
432
+ * @param branchBUnit - Unit in branchB
433
+ * @param branchAIntent - Change intent in branchA (optional)
434
+ * @param branchBIntent - Change intent in branchB (optional)
435
+ * @returns SemanticConflict if there is a conflict, null if not
436
+ */
437
+ detectConflict(baseUnit, branchAUnit, branchBUnit, branchAIntent, branchBIntent) {
438
+ if (branchAUnit.contentHash === branchBUnit.contentHash) {
439
+ return null;
440
+ }
441
+ const apiConflict = this.detectAPIConflict(baseUnit, branchAUnit, branchBUnit);
442
+ if (apiConflict) {
443
+ return apiConflict;
444
+ }
445
+ if (branchAIntent && branchBIntent) {
446
+ const intentConflict = this.detectIntentConflict(
447
+ baseUnit,
448
+ branchAUnit,
449
+ branchBUnit,
450
+ branchAIntent,
451
+ branchBIntent
452
+ );
453
+ if (intentConflict) {
454
+ return intentConflict;
455
+ }
456
+ }
457
+ const conflict = createOverlappingConflict(baseUnit, branchAUnit, branchBUnit);
458
+ conflict.severity = this.classifySeverity(baseUnit, branchAUnit, branchBUnit);
459
+ conflict.autoResolvable = this.isAutoResolvable(conflict, branchAIntent, branchBIntent);
460
+ return conflict;
461
+ }
462
+ /**
463
+ * Detect API breaking changes
464
+ */
465
+ detectAPIConflict(baseUnit, branchAUnit, branchBUnit) {
466
+ if (!baseUnit) return null;
467
+ const branchASignatureChanged = baseUnit.signature !== branchAUnit.signature;
468
+ const branchBSignatureChanged = baseUnit.signature !== branchBUnit.signature;
469
+ if (branchASignatureChanged && branchBSignatureChanged) {
470
+ return createAPIBreakingConflict(baseUnit, branchAUnit, branchBUnit);
471
+ }
472
+ const branchAFQNChanged = baseUnit.fullyQualifiedName !== branchAUnit.fullyQualifiedName;
473
+ const branchBFQNChanged = baseUnit.fullyQualifiedName !== branchBUnit.fullyQualifiedName;
474
+ if (branchAFQNChanged && branchBFQNChanged) {
475
+ return createAPIBreakingConflict(baseUnit, branchAUnit, branchBUnit);
476
+ }
477
+ return null;
478
+ }
479
+ /**
480
+ * Detect intent conflict
481
+ */
482
+ detectIntentConflict(baseUnit, branchAUnit, branchBUnit, branchAIntent, branchBIntent) {
483
+ const compatible = this.areIntentsCompatible(branchAIntent.type, branchBIntent.type);
484
+ if (!compatible) {
485
+ return createIncompatibleIntentsConflict(baseUnit, branchAUnit, branchBUnit, branchAIntent, branchBIntent);
486
+ }
487
+ return null;
488
+ }
489
+ /**
490
+ * Check intent compatibility
491
+ */
492
+ areIntentsCompatible(intentA, intentB) {
493
+ const compatibilityMatrix = {
494
+ ["BugFix" /* BugFix */]: /* @__PURE__ */ new Set([
495
+ "BugFix" /* BugFix */,
496
+ // Two bug fixes are usually compatible
497
+ "Refactoring" /* Refactoring */
498
+ // BugFix + Refactoring = OK
499
+ ]),
500
+ ["Refactoring" /* Refactoring */]: /* @__PURE__ */ new Set(["BugFix" /* BugFix */, "Refactoring" /* Refactoring */]),
501
+ ["FeatureAddition" /* FeatureAddition */]: /* @__PURE__ */ new Set([
502
+ "FeatureAddition" /* FeatureAddition */
503
+ // Two feature additions can be compatible
504
+ ]),
505
+ ["APIChange" /* APIChange */]: /* @__PURE__ */ new Set([
506
+ // API changes are usually incompatible with other changes
507
+ ]),
508
+ ["Unknown" /* Unknown */]: /* @__PURE__ */ new Set(["Unknown" /* Unknown */])
509
+ };
510
+ const compatibleWith = compatibilityMatrix[intentA];
511
+ return compatibleWith ? compatibleWith.has(intentB) : false;
512
+ }
513
+ /**
514
+ * Classify conflict severity
515
+ */
516
+ classifySeverity(baseUnit, branchAUnit, branchBUnit) {
517
+ if (!baseUnit) {
518
+ return "Medium" /* Medium */;
519
+ }
520
+ const branchADiff = this.computeDifference(baseUnit.content, branchAUnit.content);
521
+ const branchBDiff = this.computeDifference(baseUnit.content, branchBUnit.content);
522
+ if (branchADiff > 0.5 && branchBDiff > 0.5) {
523
+ return "High" /* High */;
524
+ }
525
+ if (branchADiff > 0.3 || branchBDiff > 0.3) {
526
+ return "Medium" /* Medium */;
527
+ }
528
+ return "Low" /* Low */;
529
+ }
530
+ /**
531
+ * Compute difference between two code versions (0.0-1.0)
532
+ */
533
+ computeDifference(baseContent, changedContent) {
534
+ const maxLength = Math.max(baseContent.length, changedContent.length);
535
+ if (maxLength === 0) return 0;
536
+ const lengthDiff = Math.abs(baseContent.length - changedContent.length);
537
+ return Math.min(lengthDiff / maxLength, 1);
538
+ }
539
+ /**
540
+ * Check whether the conflict can be automatically resolved
541
+ */
542
+ isAutoResolvable(conflict, branchAIntent, branchBIntent) {
543
+ if (conflict.severity === "Critical" /* Critical */) {
544
+ return false;
545
+ }
546
+ if (conflict.type === "APIBreakingChange" /* APIBreakingChange */) {
547
+ return false;
548
+ }
549
+ if (branchAIntent && branchBIntent && this.areIntentsCompatible(branchAIntent.type, branchBIntent.type) && conflict.severity === "Low" /* Low */) {
550
+ return true;
551
+ }
552
+ return false;
553
+ }
554
+ /**
555
+ * Batch detection for multiple unit pairs
556
+ */
557
+ detectConflicts(matches) {
558
+ const conflicts = [];
559
+ for (const match of matches) {
560
+ const conflict = this.detectConflict(
561
+ match.baseUnit,
562
+ match.branchAUnit,
563
+ match.branchBUnit,
564
+ match.branchAIntent,
565
+ match.branchBIntent
566
+ );
567
+ if (conflict) {
568
+ conflicts.push(conflict);
569
+ }
570
+ }
571
+ return conflicts;
572
+ }
573
+ };
574
+
575
+ // src/merge/analysis/intent-classifier.ts
576
+ var IntentClassifier = class {
577
+ /**
578
+ * Classify the change intent
579
+ *
580
+ * @param baseUnit - Unit before the change (may be null for new units)
581
+ * @param changedUnit - Unit after the change
582
+ * @returns ChangeIntent with type and confidence
583
+ */
584
+ classifyIntent(baseUnit, changedUnit) {
585
+ if (!baseUnit) {
586
+ return this.classifyNewUnit(changedUnit);
587
+ }
588
+ const bugFixEvidence = this.collectBugFixEvidence(baseUnit, changedUnit);
589
+ const refactoringEvidence = this.collectRefactoringEvidence(baseUnit, changedUnit);
590
+ const featureEvidence = this.collectFeatureAdditionEvidence(baseUnit, changedUnit);
591
+ const apiChangeEvidence = this.collectAPIChangeEvidence(baseUnit, changedUnit);
592
+ const evidenceCounts = [
593
+ { type: "BugFix" /* BugFix */, evidence: bugFixEvidence, count: bugFixEvidence.length },
594
+ {
595
+ type: "Refactoring" /* Refactoring */,
596
+ evidence: refactoringEvidence,
597
+ count: refactoringEvidence.length
598
+ },
599
+ {
600
+ type: "FeatureAddition" /* FeatureAddition */,
601
+ evidence: featureEvidence,
602
+ count: featureEvidence.length
603
+ },
604
+ {
605
+ type: "APIChange" /* APIChange */,
606
+ evidence: apiChangeEvidence,
607
+ count: apiChangeEvidence.length
608
+ }
609
+ ];
610
+ evidenceCounts.sort((a, b) => b.count - a.count);
611
+ const winner = evidenceCounts[0];
612
+ if (!winner || winner.count === 0) {
613
+ return createUnknownIntent();
614
+ }
615
+ switch (winner.type) {
616
+ case "BugFix" /* BugFix */:
617
+ return createBugFixIntent(bugFixEvidence);
618
+ case "Refactoring" /* Refactoring */:
619
+ return createRefactoringIntent(refactoringEvidence);
620
+ case "FeatureAddition" /* FeatureAddition */:
621
+ return createFeatureAdditionIntent(featureEvidence);
622
+ case "APIChange" /* APIChange */:
623
+ return createAPIChangeIntent(apiChangeEvidence);
624
+ default:
625
+ return createUnknownIntent();
626
+ }
627
+ }
628
+ /**
629
+ * Classify a new unit (baseUnit === null)
630
+ */
631
+ classifyNewUnit(changedUnit) {
632
+ const evidence = [];
633
+ if (changedUnit.type === "class") {
634
+ evidence.push({
635
+ type: "NewClass" /* NewClass */,
636
+ description: `Added new class: ${changedUnit.name}`,
637
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
638
+ });
639
+ } else if (changedUnit.type === "function" || changedUnit.type === "method") {
640
+ evidence.push({
641
+ type: "NewMethod" /* NewMethod */,
642
+ description: `Added new method: ${changedUnit.name}`,
643
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
644
+ });
645
+ } else if (changedUnit.type === "property") {
646
+ evidence.push({
647
+ type: "NewProperty" /* NewProperty */,
648
+ description: `Added new property: ${changedUnit.name}`,
649
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
650
+ });
651
+ }
652
+ return createFeatureAdditionIntent(evidence);
653
+ }
654
+ /**
655
+ * Collect BugFix evidence
656
+ */
657
+ collectBugFixEvidence(baseUnit, changedUnit) {
658
+ const evidence = [];
659
+ const baseContent = baseUnit.content.toLowerCase();
660
+ const changedContent = changedUnit.content.toLowerCase();
661
+ if (!baseContent.includes("try") && changedContent.includes("try")) {
662
+ evidence.push({
663
+ type: "AddedTryCatch" /* AddedTryCatch */,
664
+ description: "Added try-catch block for error handling",
665
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
666
+ });
667
+ }
668
+ const baseIfCount = (baseContent.match(/\bif\s*\(/g) || []).length;
669
+ const changedIfCount = (changedContent.match(/\bif\s*\(/g) || []).length;
670
+ if (changedIfCount > baseIfCount) {
671
+ evidence.push({
672
+ type: "AddedValidation" /* AddedValidation */,
673
+ description: `Added validation checks (+${changedIfCount - baseIfCount} if statements)`,
674
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
675
+ });
676
+ }
677
+ const hasNullCheck = changedContent.includes("!= null") || changedContent.includes("!== null") || changedContent.includes("== null") || changedContent.includes("=== null");
678
+ if (hasNullCheck && !baseContent.includes("null")) {
679
+ evidence.push({
680
+ type: "AddedNullCheck" /* AddedNullCheck */,
681
+ description: "Added null/undefined check",
682
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
683
+ });
684
+ }
685
+ return evidence;
686
+ }
687
+ /**
688
+ * Collect Refactoring evidence
689
+ */
690
+ collectRefactoringEvidence(baseUnit, changedUnit) {
691
+ const evidence = [];
692
+ if (baseUnit.name !== changedUnit.name && baseUnit.structuralHash === changedUnit.structuralHash) {
693
+ evidence.push({
694
+ type: "RenamedVariable" /* RenamedVariable */,
695
+ description: `Renamed ${baseUnit.name} to ${changedUnit.name}`,
696
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
697
+ });
698
+ }
699
+ if (baseUnit.structuralHash === changedUnit.structuralHash && baseUnit.contentHash !== changedUnit.contentHash) {
700
+ evidence.push({
701
+ type: "CFGPreserved" /* CFGPreserved */,
702
+ description: "Code structure preserved (likely refactoring)",
703
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
704
+ });
705
+ }
706
+ return evidence;
707
+ }
708
+ /**
709
+ * Collect FeatureAddition evidence
710
+ */
711
+ collectFeatureAdditionEvidence(baseUnit, changedUnit) {
712
+ const evidence = [];
713
+ const newChildren = changedUnit.childIds.length - baseUnit.childIds.length;
714
+ if (newChildren > 0) {
715
+ evidence.push({
716
+ type: "NewMethod" /* NewMethod */,
717
+ description: `Added ${newChildren} new methods/properties`,
718
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
719
+ });
720
+ }
721
+ const sizeIncrease = (changedUnit.content.length - baseUnit.content.length) / baseUnit.content.length;
722
+ if (sizeIncrease > 0.3) {
723
+ evidence.push({
724
+ type: "NewMethod" /* NewMethod */,
725
+ description: `Code size increased by ${Math.round(sizeIncrease * 100)}%`,
726
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
727
+ });
728
+ }
729
+ return evidence;
730
+ }
731
+ /**
732
+ * Collect APIChange evidence
733
+ */
734
+ collectAPIChangeEvidence(baseUnit, changedUnit) {
735
+ const evidence = [];
736
+ if (baseUnit.signature && changedUnit.signature && baseUnit.signature !== changedUnit.signature) {
737
+ evidence.push({
738
+ type: "SignatureChanged" /* SignatureChanged */,
739
+ description: `Signature changed: ${baseUnit.signature} \u2192 ${changedUnit.signature}`,
740
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
741
+ });
742
+ }
743
+ if (baseUnit.fullyQualifiedName !== changedUnit.fullyQualifiedName) {
744
+ evidence.push({
745
+ type: "SignatureChanged" /* SignatureChanged */,
746
+ description: `Fully qualified name changed: ${baseUnit.fullyQualifiedName} \u2192 ${changedUnit.fullyQualifiedName}`,
747
+ location: `${changedUnit.filePath}:${changedUnit.startLine}`
748
+ });
749
+ }
750
+ return evidence;
751
+ }
752
+ };
753
+
754
+ // src/merge/indexing/lazy-embedding-cache.ts
755
+ var LazyEmbeddingCache = class {
756
+ embeddingGenerator;
757
+ cache = /* @__PURE__ */ new Map();
758
+ // In-memory cache
759
+ batchSize;
760
+ constructor(embeddingGenerator, options = {}) {
761
+ this.embeddingGenerator = embeddingGenerator;
762
+ this.batchSize = options.batchSize ?? 32;
763
+ }
764
+ /**
765
+ * Generate embeddings for units in the index.
766
+ *
767
+ * Only for units without embeddings (lazy generation).
768
+ *
769
+ * @param index - Versioned index
770
+ * @param unitsNeedingEmbeddings - Units that need embeddings
771
+ * @returns Promise that resolves when done
772
+ */
773
+ async generateEmbeddings(index, unitsNeedingEmbeddings) {
774
+ const unitsToProcess = unitsNeedingEmbeddings.filter((unit) => !unit.embedding);
775
+ if (unitsToProcess.length === 0) {
776
+ return;
777
+ }
778
+ for (let i = 0; i < unitsToProcess.length; i += this.batchSize) {
779
+ const batch = unitsToProcess.slice(i, i + this.batchSize);
780
+ await Promise.all(
781
+ batch.map(async (unit) => {
782
+ const embedding = await this.getOrGenerateEmbedding(unit);
783
+ unit.embedding = embedding;
784
+ index.units.set(unit.id, unit);
785
+ })
786
+ );
787
+ }
788
+ }
789
+ /**
790
+ * Get embedding from cache or generate a new one.
791
+ */
792
+ async getOrGenerateEmbedding(unit) {
793
+ const cacheKey = this.computeCacheKey(unit);
794
+ const cached = this.cache.get(cacheKey);
795
+ if (cached) {
796
+ return cached;
797
+ }
798
+ const embedding = await this.embeddingGenerator(unit.content);
799
+ this.cache.set(cacheKey, embedding);
800
+ return embedding;
801
+ }
802
+ /**
803
+ * Compute cache key for a unit.
804
+ *
805
+ * Based on contentHash (same content = same embedding).
806
+ */
807
+ computeCacheKey(unit) {
808
+ return unit.contentHash;
809
+ }
810
+ /**
811
+ * Clear in-memory cache.
812
+ */
813
+ clearCache() {
814
+ this.cache.clear();
815
+ }
816
+ /**
817
+ * Get cache statistics.
818
+ */
819
+ getCacheStats() {
820
+ return {
821
+ size: this.cache.size,
822
+ memoryUsage: this.estimateMemoryUsage()
823
+ };
824
+ }
825
+ /**
826
+ * Estimate cache memory usage (bytes).
827
+ */
828
+ estimateMemoryUsage() {
829
+ const bytesPerEmbedding = 384 * 4 + 100;
830
+ return this.cache.size * bytesPerEmbedding;
831
+ }
832
+ };
833
+
834
+ // src/merge/indexing/multi-version-indexer.ts
835
+ init_logging();
836
+ var MultiVersionIndexer = class {
837
+ branchManager;
838
+ gitIntegration;
839
+ conductor;
840
+ constructor(branchManager, gitIntegration, conductor) {
841
+ this.branchManager = branchManager;
842
+ this.gitIntegration = gitIntegration;
843
+ this.conductor = conductor;
844
+ }
845
+ /**
846
+ * Index 3 branches for merge: base, branchA, branchB
847
+ *
848
+ * This is the main entry point for Phase 4.
849
+ */
850
+ async indexThreeBranches(branchA, branchB, options = {}) {
851
+ const startTime = Date.now();
852
+ let cacheHits = 0;
853
+ log.i("MULTIVIDX", `[MultiVersionIndexer] Starting 3-way indexing: ${branchA} + ${branchB}`);
854
+ try {
855
+ const mergeBase = this.gitIntegration.getMergeBase(branchA, branchB);
856
+ if (!mergeBase) {
857
+ throw new Error(`No merge base found between ${branchA} and ${branchB}`);
858
+ }
859
+ log.i("MULTIVIDX", `[MultiVersionIndexer] Merge base: ${mergeBase.slice(0, 8)}`);
860
+ const baseIndex = await this.indexBranch(mergeBase, "base", options);
861
+ if (this.wasCacheHit(baseIndex)) cacheHits++;
862
+ const branchAIndex = await this.indexBranch(branchA, branchA, options);
863
+ if (this.wasCacheHit(branchAIndex)) cacheHits++;
864
+ const branchBIndex = await this.indexBranch(branchB, branchB, options);
865
+ if (this.wasCacheHit(branchBIndex)) cacheHits++;
866
+ await this.gitIntegration.restoreOriginalBranch();
867
+ const totalUnits = baseIndex.stats.totalUnits + branchAIndex.stats.totalUnits + branchBIndex.stats.totalUnits;
868
+ const totalFiles = (baseIndex.stats.byFile?.size || 0) + (branchAIndex.stats.byFile?.size || 0) + (branchBIndex.stats.byFile?.size || 0);
869
+ const indexingTimeMs = Date.now() - startTime;
870
+ log.i("MULTIVIDX", `[MultiVersionIndexer] Completed in ${indexingTimeMs}ms`);
871
+ log.i("MULTIVIDX", `[MultiVersionIndexer] Total units: ${totalUnits}, Cache hits: ${cacheHits}/3`);
872
+ return {
873
+ base: baseIndex,
874
+ branchA: branchAIndex,
875
+ branchB: branchBIndex,
876
+ mergeBase,
877
+ stats: {
878
+ totalUnits,
879
+ totalFiles,
880
+ indexingTimeMs,
881
+ cacheHits
882
+ }
883
+ };
884
+ } catch (error) {
885
+ await this.gitIntegration.cleanup();
886
+ throw error;
887
+ }
888
+ }
889
+ /**
890
+ * Index a single branch
891
+ *
892
+ * Strategy:
893
+ * 1. Check if branch DB exists and is up-to-date
894
+ * 2. If yes → load from cache
895
+ * 3. If no → checkout branch, run full index, save to cache
896
+ */
897
+ async indexBranch(branch, label, options) {
898
+ log.i("MULTIVIDX", `[MultiVersionIndexer] Indexing ${label}...`);
899
+ if (!options.fullScan && !options.reset) {
900
+ const cachedIndex = await this.loadCachedIndex(branch);
901
+ if (cachedIndex) {
902
+ log.i("MULTIVIDX", `[MultiVersionIndexer] Loaded ${label} from cache`);
903
+ return cachedIndex;
904
+ }
905
+ }
906
+ const startTime = Date.now();
907
+ await this.gitIntegration.checkoutBranch(branch);
908
+ if (!this.conductor) {
909
+ throw new Error("ConductorOrchestrator is required for fresh indexing");
910
+ }
911
+ const container = getGlobalContainer();
912
+ const devAgent = await getOrCreateAgent(container, this.conductor, "dev" /* DEV */);
913
+ const index = await this.runIndexing(devAgent, branch, options);
914
+ await this.saveCachedIndex(branch, index);
915
+ const elapsed = Date.now() - startTime;
916
+ log.i("MULTIVIDX", `[MultiVersionIndexer] Indexed ${label} in ${elapsed}ms (${index.stats.totalUnits} units)`);
917
+ return index;
918
+ }
919
+ /**
920
+ * Run actual indexing using DevAgent
921
+ */
922
+ async runIndexing(devAgent, branch, options) {
923
+ const repoPath = this.gitIntegration.repoPath;
924
+ const result = await devAgent.execute?.({
925
+ task: "index_codebase",
926
+ params: {
927
+ directory: repoPath,
928
+ incremental: options.incremental ?? true,
929
+ fullScan: options.fullScan ?? false,
930
+ excludePatterns: options.excludePatterns || [],
931
+ reset: options.reset ?? false
932
+ }
933
+ });
934
+ const index = {
935
+ branch,
936
+ indexedAt: /* @__PURE__ */ new Date(),
937
+ units: /* @__PURE__ */ new Map(),
938
+ contentHashIndex: /* @__PURE__ */ new Map(),
939
+ structuralHashIndex: /* @__PURE__ */ new Map(),
940
+ signatureIndex: /* @__PURE__ */ new Map(),
941
+ filePathIndex: /* @__PURE__ */ new Map(),
942
+ stats: {
943
+ totalUnits: 0,
944
+ byType: /* @__PURE__ */ new Map(),
945
+ byLanguage: /* @__PURE__ */ new Map(),
946
+ byFile: /* @__PURE__ */ new Map()
947
+ }
948
+ };
949
+ if (result?.entities) {
950
+ for (const entity of result.entities) {
951
+ const codeUnit = this.entityToCodeUnit(entity);
952
+ this.addUnitToIndex(index, codeUnit);
953
+ }
954
+ } else {
955
+ await this.populateIndexFromStorage(index);
956
+ }
957
+ return index;
958
+ }
959
+ /**
960
+ * Convert entity from DevAgent to CodeUnit
961
+ */
962
+ entityToCodeUnit(entity) {
963
+ const { ContentNormalizer: ContentNormalizer2 } = (init_content_normalizer(), __toCommonJS(content_normalizer_exports));
964
+ const normalizer = new ContentNormalizer2();
965
+ const content = entity.content || "";
966
+ const contentHash = normalizer.computeContentHash(content);
967
+ return {
968
+ id: entity.id,
969
+ type: this.mapEntityType(entity.type),
970
+ filePath: entity.filePath || "",
971
+ name: entity.name || "",
972
+ fullyQualifiedName: entity.fullyQualifiedName || entity.name || "",
973
+ startLine: entity.startLine || 1,
974
+ endLine: entity.endLine || 1,
975
+ content,
976
+ contentHash,
977
+ structuralHash: entity.structuralHash || contentHash,
978
+ signature: entity.signature,
979
+ language: entity.language || "unknown",
980
+ parentId: entity.parentId,
981
+ childIds: entity.childIds || [],
982
+ metadata: entity.metadata || {}
983
+ };
984
+ }
985
+ /**
986
+ * Map entity type string to CodeUnitType
987
+ */
988
+ mapEntityType(type) {
989
+ const { CodeUnitType: CodeUnitType2 } = (init_code_unit(), __toCommonJS(code_unit_exports));
990
+ const typeMap = {
991
+ file: CodeUnitType2.File,
992
+ module: CodeUnitType2.Module,
993
+ class: CodeUnitType2.Class,
994
+ interface: CodeUnitType2.Interface,
995
+ function: CodeUnitType2.Function,
996
+ method: CodeUnitType2.Method,
997
+ property: CodeUnitType2.Property
998
+ };
999
+ return typeMap[type?.toLowerCase()] || CodeUnitType2.Block;
1000
+ }
1001
+ /**
1002
+ * Add a CodeUnit to the index with all hash indexes
1003
+ */
1004
+ addUnitToIndex(index, unit) {
1005
+ index.units.set(unit.id, unit);
1006
+ if (!index.contentHashIndex.has(unit.contentHash)) {
1007
+ index.contentHashIndex.set(unit.contentHash, []);
1008
+ }
1009
+ index.contentHashIndex.get(unit.contentHash).push(unit.id);
1010
+ if (!index.structuralHashIndex.has(unit.structuralHash)) {
1011
+ index.structuralHashIndex.set(unit.structuralHash, []);
1012
+ }
1013
+ index.structuralHashIndex.get(unit.structuralHash).push(unit.id);
1014
+ if (unit.signature) {
1015
+ if (!index.signatureIndex.has(unit.signature)) {
1016
+ index.signatureIndex.set(unit.signature, []);
1017
+ }
1018
+ index.signatureIndex.get(unit.signature).push(unit.id);
1019
+ }
1020
+ if (!index.filePathIndex.has(unit.filePath)) {
1021
+ index.filePathIndex.set(unit.filePath, []);
1022
+ }
1023
+ index.filePathIndex.get(unit.filePath).push(unit.id);
1024
+ index.stats.totalUnits++;
1025
+ index.stats.byType.set(unit.type, (index.stats.byType.get(unit.type) || 0) + 1);
1026
+ index.stats.byLanguage.set(unit.language, (index.stats.byLanguage.get(unit.language) || 0) + 1);
1027
+ index.stats.byFile.set(unit.filePath, (index.stats.byFile.get(unit.filePath) || 0) + 1);
1028
+ }
1029
+ /**
1030
+ * Populate index from GraphStorage (fallback when DevAgent result is unavailable)
1031
+ */
1032
+ async populateIndexFromStorage(index) {
1033
+ try {
1034
+ const storage = this.conductor.getGraphStorage?.();
1035
+ if (!storage) {
1036
+ log.w("MULTIVIDX", "[MultiVersionIndexer] GraphStorage not available for fallback");
1037
+ return;
1038
+ }
1039
+ const entities = await storage.getAllEntities?.();
1040
+ if (!entities || entities.length === 0) {
1041
+ log.w("MULTIVIDX", "[MultiVersionIndexer] No entities found in GraphStorage");
1042
+ return;
1043
+ }
1044
+ for (const entity of entities) {
1045
+ const codeUnit = this.entityToCodeUnit(entity);
1046
+ this.addUnitToIndex(index, codeUnit);
1047
+ }
1048
+ log.i("MULTIVIDX", "storage_loaded", { units: index.stats.totalUnits });
1049
+ } catch (error) {
1050
+ log.e("MULTIVIDX", "storage_populate_fail", { err: String(error) });
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Load cached index for a branch
1055
+ */
1056
+ async loadCachedIndex(branch) {
1057
+ try {
1058
+ if (!this.branchManager.hasBranchDatabase(branch)) {
1059
+ return null;
1060
+ }
1061
+ const metadata = this.branchManager.getBranchMetadata(branch);
1062
+ if (!metadata) {
1063
+ return null;
1064
+ }
1065
+ const currentCommit = this.gitIntegration.getCommitHash(branch);
1066
+ if (metadata.lastCommitHash !== currentCommit) {
1067
+ log.i(
1068
+ "MULTIVIDX",
1069
+ `[MultiVersionIndexer] Cache outdated for ${branch} (${metadata.lastCommitHash?.slice(0, 8)} vs ${currentCommit.slice(0, 8)})`
1070
+ );
1071
+ return null;
1072
+ }
1073
+ const index = {
1074
+ branch,
1075
+ indexedAt: new Date(metadata.lastIndexedAt),
1076
+ units: /* @__PURE__ */ new Map(),
1077
+ contentHashIndex: /* @__PURE__ */ new Map(),
1078
+ structuralHashIndex: /* @__PURE__ */ new Map(),
1079
+ signatureIndex: /* @__PURE__ */ new Map(),
1080
+ filePathIndex: /* @__PURE__ */ new Map(),
1081
+ stats: {
1082
+ totalUnits: 0,
1083
+ byType: /* @__PURE__ */ new Map(),
1084
+ byLanguage: /* @__PURE__ */ new Map(),
1085
+ byFile: /* @__PURE__ */ new Map()
1086
+ }
1087
+ };
1088
+ index.stats.totalUnits = metadata.entityCount;
1089
+ index._fromCache = true;
1090
+ return index;
1091
+ } catch (error) {
1092
+ log.e("MULTIVIDX", "cache_load_fail", { branch, err: String(error) });
1093
+ return null;
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Save index to branch cache
1098
+ */
1099
+ async saveCachedIndex(branch, index) {
1100
+ try {
1101
+ const repoPath = this.gitIntegration.repoPath;
1102
+ const commitHash = this.gitIntegration.getCommitHash(branch);
1103
+ const repoHash = this.branchManager.getRepositoryHash(repoPath);
1104
+ this.branchManager.updateBranchMetadata({
1105
+ branch,
1106
+ repositoryPath: repoPath,
1107
+ repositoryHash: repoHash,
1108
+ lastCommitHash: commitHash,
1109
+ lastIndexedAt: Date.now(),
1110
+ fileCount: index.stats.byFile?.size || 0,
1111
+ entityCount: index.stats.totalUnits,
1112
+ relationshipCount: 0,
1113
+ // TODO: Track relationships
1114
+ indexVersion: "1.0.0",
1115
+ accessedAt: Date.now()
1116
+ });
1117
+ log.i("MULTIVIDX", "cache_saved", { branch, commit: commitHash.slice(0, 8) });
1118
+ } catch (error) {
1119
+ log.e("MULTIVIDX", "cache_save_fail", { branch, err: String(error) });
1120
+ }
1121
+ }
1122
+ /**
1123
+ * Check if index was loaded from cache
1124
+ */
1125
+ wasCacheHit(index) {
1126
+ return index._fromCache === true;
1127
+ }
1128
+ };
1129
+
1130
+ // src/merge/matching/fast-path-matcher.ts
1131
+ var FastPathMatcher = class {
1132
+ /**
1133
+ * Perform bulk matching between two indices.
1134
+ *
1135
+ * @param baseIndex - Base version index
1136
+ * @param targetIndex - Target version index (branchA or branchB)
1137
+ * @returns Map of targetUnitId → Match result
1138
+ */
1139
+ bulkMatch(baseIndex, targetIndex) {
1140
+ const results = /* @__PURE__ */ new Map();
1141
+ for (const [targetId, targetUnit] of targetIndex.units) {
1142
+ const match = this.findMatch(targetUnit, baseIndex);
1143
+ if (match) {
1144
+ results.set(targetId, match);
1145
+ }
1146
+ }
1147
+ return results;
1148
+ }
1149
+ /**
1150
+ * Find match for a single unit in the base index.
1151
+ *
1152
+ * Tries levels in order: Exact → Structural → Signature → ID
1153
+ */
1154
+ findMatch(targetUnit, baseIndex) {
1155
+ const exactMatch = this.tryExactMatch(targetUnit, baseIndex);
1156
+ if (exactMatch) {
1157
+ return {
1158
+ baseUnitId: exactMatch.id,
1159
+ baseUnit: exactMatch,
1160
+ level: "exact_content" /* ExactContent */,
1161
+ confidence: 1
1162
+ };
1163
+ }
1164
+ const structuralMatch = this.tryStructuralMatch(targetUnit, baseIndex);
1165
+ if (structuralMatch) {
1166
+ return {
1167
+ baseUnitId: structuralMatch.id,
1168
+ baseUnit: structuralMatch,
1169
+ level: "structural" /* Structural */,
1170
+ confidence: 0.95
1171
+ };
1172
+ }
1173
+ if (targetUnit.signature) {
1174
+ const signatureMatch = this.trySignatureMatch(targetUnit, baseIndex);
1175
+ if (signatureMatch) {
1176
+ return {
1177
+ baseUnitId: signatureMatch.id,
1178
+ baseUnit: signatureMatch,
1179
+ level: "signature" /* Signature */,
1180
+ confidence: 0.85
1181
+ };
1182
+ }
1183
+ }
1184
+ const idMatch = this.tryIdMatch(targetUnit, baseIndex);
1185
+ if (idMatch) {
1186
+ return {
1187
+ baseUnitId: idMatch.id,
1188
+ baseUnit: idMatch,
1189
+ level: "id" /* Id */,
1190
+ confidence: 0.7
1191
+ };
1192
+ }
1193
+ return void 0;
1194
+ }
1195
+ /**
1196
+ * Level 1: Exact Content Match.
1197
+ *
1198
+ * Matches if contentHash is identical (byte-for-byte same code).
1199
+ */
1200
+ tryExactMatch(targetUnit, baseIndex) {
1201
+ const candidates = baseIndex.contentHashIndex.get(targetUnit.contentHash);
1202
+ if (!candidates || candidates.length === 0) {
1203
+ return void 0;
1204
+ }
1205
+ if (candidates.length === 1) {
1206
+ const firstId2 = candidates[0];
1207
+ return firstId2 ? baseIndex.units.get(firstId2) : void 0;
1208
+ }
1209
+ const sameFileCandidate = candidates.find((id) => {
1210
+ const unit = baseIndex.units.get(id);
1211
+ return unit?.filePath === targetUnit.filePath;
1212
+ });
1213
+ if (sameFileCandidate) {
1214
+ return baseIndex.units.get(sameFileCandidate);
1215
+ }
1216
+ const firstId = candidates[0];
1217
+ return firstId ? baseIndex.units.get(firstId) : void 0;
1218
+ }
1219
+ /**
1220
+ * Level 2: Structural Match.
1221
+ *
1222
+ * Matches if structuralHash is identical (same AST structure).
1223
+ * Ignores whitespace, comments, formatting.
1224
+ */
1225
+ tryStructuralMatch(targetUnit, baseIndex) {
1226
+ const candidates = baseIndex.structuralHashIndex.get(targetUnit.structuralHash);
1227
+ if (!candidates || candidates.length === 0) {
1228
+ return void 0;
1229
+ }
1230
+ const typedCandidates = candidates.filter((id) => {
1231
+ const unit = baseIndex.units.get(id);
1232
+ return unit?.type === targetUnit.type;
1233
+ });
1234
+ if (typedCandidates.length === 0) {
1235
+ return void 0;
1236
+ }
1237
+ const bestCandidate = typedCandidates.find((id) => {
1238
+ const unit = baseIndex.units.get(id);
1239
+ return unit?.filePath === targetUnit.filePath && unit?.name === targetUnit.name;
1240
+ });
1241
+ if (bestCandidate) {
1242
+ return baseIndex.units.get(bestCandidate);
1243
+ }
1244
+ const firstId = typedCandidates[0];
1245
+ return firstId ? baseIndex.units.get(firstId) : void 0;
1246
+ }
1247
+ /**
1248
+ * Level 3: Signature Match.
1249
+ *
1250
+ * Matches if signature is identical (FQN + params for functions).
1251
+ * Useful for renamed/moved code with same signature.
1252
+ */
1253
+ trySignatureMatch(targetUnit, baseIndex) {
1254
+ if (!targetUnit.signature) {
1255
+ return void 0;
1256
+ }
1257
+ const candidates = baseIndex.signatureIndex.get(targetUnit.signature);
1258
+ if (!candidates || candidates.length === 0) {
1259
+ return void 0;
1260
+ }
1261
+ const typedCandidates = candidates.filter((id) => {
1262
+ const unit = baseIndex.units.get(id);
1263
+ return unit?.type === targetUnit.type;
1264
+ });
1265
+ if (typedCandidates.length === 0) {
1266
+ return void 0;
1267
+ }
1268
+ const firstCandidate = typedCandidates[0];
1269
+ if (!firstCandidate) {
1270
+ return void 0;
1271
+ }
1272
+ return baseIndex.units.get(firstCandidate);
1273
+ }
1274
+ /**
1275
+ * Level 4: ID Match.
1276
+ *
1277
+ * Matches by stable ID (based on FQN).
1278
+ * Only used as last resort - code may have changed significantly.
1279
+ */
1280
+ tryIdMatch(targetUnit, baseIndex) {
1281
+ const candidate = baseIndex.units.get(targetUnit.id);
1282
+ if (candidate && candidate.fullyQualifiedName === targetUnit.fullyQualifiedName) {
1283
+ return candidate;
1284
+ }
1285
+ return void 0;
1286
+ }
1287
+ /**
1288
+ * Compute Fast Path coverage statistics.
1289
+ */
1290
+ computeStatistics(matchResults, totalUnits) {
1291
+ const byLevel = /* @__PURE__ */ new Map();
1292
+ for (const result of matchResults.values()) {
1293
+ byLevel.set(result.level, (byLevel.get(result.level) || 0) + 1);
1294
+ }
1295
+ const totalMatched = matchResults.size;
1296
+ const coverage = totalUnits > 0 ? totalMatched / totalUnits : 0;
1297
+ return {
1298
+ totalUnits,
1299
+ totalMatched,
1300
+ coverage,
1301
+ exactContentMatches: byLevel.get("exact_content" /* ExactContent */) || 0,
1302
+ structuralMatches: byLevel.get("structural" /* Structural */) || 0,
1303
+ signatureMatches: byLevel.get("signature" /* Signature */) || 0,
1304
+ idMatches: byLevel.get("id" /* Id */) || 0
1305
+ };
1306
+ }
1307
+ };
1308
+
1309
+ // src/merge/matching/semantic-matcher.ts
1310
+ var SemanticMatcher = class {
1311
+ /**
1312
+ * Find semantic match for a unit in the base index.
1313
+ *
1314
+ * @param targetUnit - Target unit to match (must have embedding)
1315
+ * @param baseIndex - Base version index (units must have embeddings)
1316
+ * @param threshold - Minimum similarity threshold (0.0-1.0), default 0.7
1317
+ * @returns Match result or undefined if no match above threshold
1318
+ */
1319
+ findMatch(targetUnit, baseIndex, threshold = 0.7) {
1320
+ if (!targetUnit.embedding) {
1321
+ throw new Error(`Target unit ${targetUnit.id} missing embedding. Use LazyEmbeddingCache to generate.`);
1322
+ }
1323
+ const candidates = this.findCandidates(targetUnit, baseIndex);
1324
+ if (candidates.length === 0) {
1325
+ return void 0;
1326
+ }
1327
+ const similarities = candidates.map((candidate) => {
1328
+ const vectorSim = this.computeVectorSimilarity(targetUnit.embedding, candidate.embedding);
1329
+ const structuralSim = this.computeStructuralSimilarity(targetUnit, candidate);
1330
+ const combinedScore = vectorSim * 0.7 + structuralSim * 0.3;
1331
+ return {
1332
+ candidate,
1333
+ vectorSimilarity: vectorSim,
1334
+ structuralSimilarity: structuralSim,
1335
+ combinedScore
1336
+ };
1337
+ });
1338
+ similarities.sort((a, b) => b.combinedScore - a.combinedScore);
1339
+ const best = similarities[0];
1340
+ if (!best || best.combinedScore < threshold) {
1341
+ return void 0;
1342
+ }
1343
+ return {
1344
+ baseUnitId: best.candidate.id,
1345
+ baseUnit: best.candidate,
1346
+ vectorSimilarity: best.vectorSimilarity,
1347
+ structuralSimilarity: best.structuralSimilarity,
1348
+ combinedScore: best.combinedScore,
1349
+ confidence: this.scoreToConfidence(best.combinedScore)
1350
+ };
1351
+ }
1352
+ /**
1353
+ * Bulk semantic matching for multiple units.
1354
+ *
1355
+ * @param targetUnits - Units to match (with embeddings)
1356
+ * @param baseIndex - Base index (units with embeddings)
1357
+ * @param threshold - Minimum similarity threshold
1358
+ * @returns Map of targetUnitId → Match result
1359
+ */
1360
+ bulkMatch(targetUnits, baseIndex, threshold = 0.7) {
1361
+ const results = /* @__PURE__ */ new Map();
1362
+ for (const targetUnit of targetUnits) {
1363
+ if (!targetUnit.embedding) {
1364
+ continue;
1365
+ }
1366
+ const match = this.findMatch(targetUnit, baseIndex, threshold);
1367
+ if (match) {
1368
+ results.set(targetUnit.id, match);
1369
+ }
1370
+ }
1371
+ return results;
1372
+ }
1373
+ /**
1374
+ * Find candidates in the base index (units with embeddings of the same type).
1375
+ */
1376
+ findCandidates(targetUnit, baseIndex) {
1377
+ const candidates = [];
1378
+ for (const [, baseUnit] of baseIndex.units) {
1379
+ if (baseUnit.type === targetUnit.type && baseUnit.embedding) {
1380
+ candidates.push(baseUnit);
1381
+ }
1382
+ }
1383
+ return candidates;
1384
+ }
1385
+ /**
1386
+ * Compute vector similarity with GPU acceleration.
1387
+ *
1388
+ * Uses SIMD/WASM cosine similarity.
1389
+ */
1390
+ computeVectorSimilarity(embedding1, embedding2) {
1391
+ return cosineSimilarity(embedding1, embedding2);
1392
+ }
1393
+ /**
1394
+ * Compute structural similarity (based on hashes).
1395
+ *
1396
+ * Simple metric: matching structuralHash = 1.0, otherwise 0.0
1397
+ * (can be improved using edit distance)
1398
+ */
1399
+ computeStructuralSimilarity(unit1, unit2) {
1400
+ if (unit1.structuralHash === unit2.structuralHash) {
1401
+ return 1;
1402
+ }
1403
+ return 0;
1404
+ }
1405
+ /**
1406
+ * Convert combined score to confidence (0.0-1.0).
1407
+ *
1408
+ * Mapping:
1409
+ * - 0.9-1.0 → 0.9-1.0 (very high confidence)
1410
+ * - 0.8-0.9 → 0.75-0.9 (high confidence)
1411
+ * - 0.7-0.8 → 0.6-0.75 (medium confidence)
1412
+ */
1413
+ scoreToConfidence(score) {
1414
+ if (score >= 0.9) {
1415
+ return score;
1416
+ }
1417
+ if (score >= 0.8) {
1418
+ return 0.75 + (score - 0.8) * 1.5;
1419
+ }
1420
+ return 0.6 + (score - 0.7) * 1.5;
1421
+ }
1422
+ /**
1423
+ * Compute Semantic Path statistics.
1424
+ */
1425
+ computeStatistics(matchResults, totalUnits) {
1426
+ const totalMatched = matchResults.size;
1427
+ const coverage = totalUnits > 0 ? totalMatched / totalUnits : 0;
1428
+ let sumVector = 0;
1429
+ let sumStructural = 0;
1430
+ let sumCombined = 0;
1431
+ for (const result of matchResults.values()) {
1432
+ sumVector += result.vectorSimilarity;
1433
+ sumStructural += result.structuralSimilarity;
1434
+ sumCombined += result.combinedScore;
1435
+ }
1436
+ const count = totalMatched || 1;
1437
+ return {
1438
+ totalUnits,
1439
+ totalMatched,
1440
+ coverage,
1441
+ avgVectorSimilarity: sumVector / count,
1442
+ avgStructuralSimilarity: sumStructural / count,
1443
+ avgCombinedScore: sumCombined / count
1444
+ };
1445
+ }
1446
+ };
1447
+
1448
+ // src/merge/engine/three-way-merger.ts
1449
+ var ThreeWayMerger = class {
1450
+ branchManager;
1451
+ gitIntegration;
1452
+ conductor;
1453
+ config;
1454
+ // Components
1455
+ multiVersionIndexer;
1456
+ fastPathMatcher;
1457
+ semanticMatcher;
1458
+ intentClassifier;
1459
+ conflictDetector;
1460
+ constructor(_branchManager, _gitIntegration, _conductor, config = {}) {
1461
+ this.branchManager = _branchManager;
1462
+ this.gitIntegration = _gitIntegration;
1463
+ this.conductor = _conductor;
1464
+ this.config = {
1465
+ fastPathEnabled: true,
1466
+ semanticMatchingEnabled: true,
1467
+ semanticThreshold: 0.7,
1468
+ classifyIntents: true,
1469
+ detectConflicts: true,
1470
+ autoResolveConflicts: false,
1471
+ ...config
1472
+ };
1473
+ this.multiVersionIndexer = new MultiVersionIndexer(this.branchManager, this.gitIntegration, this.conductor);
1474
+ this.fastPathMatcher = new FastPathMatcher();
1475
+ this.semanticMatcher = new SemanticMatcher();
1476
+ this.intentClassifier = new IntentClassifier();
1477
+ this.conflictDetector = new ConflictDetector();
1478
+ }
1479
+ /**
1480
+ * Perform 3-way merge
1481
+ *
1482
+ * @param branchA - Name of the first branch to merge
1483
+ * @param branchB - Name of the second branch to merge
1484
+ * @returns MergeResult with matched units, conflicts, and merge actions
1485
+ */
1486
+ async performMerge(branchA, branchB) {
1487
+ log.i("3WAYMERGE", `[ThreeWayMerger] Starting 3-way merge: ${branchA} + ${branchB}`);
1488
+ const startTime = Date.now();
1489
+ log.i("3WAYMERGE", "[ThreeWayMerger] Phase 1: Indexing 3 branches...");
1490
+ const indexResult = await this.multiVersionIndexer.indexThreeBranches(branchA, branchB);
1491
+ const { base, branchA: indexA, branchB: indexB, mergeBase } = indexResult;
1492
+ log.i(
1493
+ "3WAYMERGE",
1494
+ `[ThreeWayMerger] Indexed: base=${base.stats.totalUnits}, A=${indexA.stats.totalUnits}, B=${indexB.stats.totalUnits}`
1495
+ );
1496
+ log.i("3WAYMERGE", "[ThreeWayMerger] Phase 1.5: Detecting file changes from git...");
1497
+ const changesA = await this.gitIntegration.getChangedFilesBetween(mergeBase, branchA);
1498
+ const changesB = await this.gitIntegration.getChangedFilesBetween(mergeBase, branchB);
1499
+ const renamedInA = changesA.filter((c) => c.status === "renamed");
1500
+ const renamedInB = changesB.filter((c) => c.status === "renamed");
1501
+ log.i(
1502
+ "3WAYMERGE",
1503
+ `[ThreeWayMerger] Git changes: A=${changesA.length} (${renamedInA.length} renamed), B=${changesB.length} (${renamedInB.length} renamed)`
1504
+ );
1505
+ log.i("3WAYMERGE", "[ThreeWayMerger] Phase 2: Fast Path matching...");
1506
+ const { matchedUnits, unmatchedA, unmatchedB } = await this.fastPathMatch(base, indexA, indexB);
1507
+ log.i(
1508
+ "3WAYMERGE",
1509
+ `[ThreeWayMerger] Fast Path: ${matchedUnits.length} matched, ${unmatchedA.length} unmapped in A, ${unmatchedB.length} unmapped in B`
1510
+ );
1511
+ log.i("3WAYMERGE", "[ThreeWayMerger] Phase 2.5: Detecting added/deleted units...");
1512
+ const { addedInA, addedInB, deletedUnits, renamedUnits } = this.detectAddedDeletedRenamed(
1513
+ base,
1514
+ indexA,
1515
+ indexB,
1516
+ unmatchedA,
1517
+ unmatchedB,
1518
+ renamedInA,
1519
+ renamedInB
1520
+ );
1521
+ log.i(
1522
+ "3WAYMERGE",
1523
+ `[ThreeWayMerger] Added: A=${addedInA.length}, B=${addedInB.length}, Deleted=${deletedUnits.length}, Renamed=${renamedUnits.length}`
1524
+ );
1525
+ const addedIds = /* @__PURE__ */ new Set([...addedInA.map((u) => u.id), ...addedInB.map((u) => u.id)]);
1526
+ const renamedIds = new Set(renamedUnits.map((r) => r.unit.id));
1527
+ const remainingUnmatchedA = unmatchedA.filter((u) => !addedIds.has(u.id) && !renamedIds.has(u.id));
1528
+ const remainingUnmatchedB = unmatchedB.filter((u) => !addedIds.has(u.id) && !renamedIds.has(u.id));
1529
+ let semanticMatches = [];
1530
+ if (this.config.semanticMatchingEnabled && (remainingUnmatchedA.length > 0 || remainingUnmatchedB.length > 0)) {
1531
+ log.i("3WAYMERGE", "[ThreeWayMerger] Phase 3: Semantic matching...");
1532
+ semanticMatches = await this.semanticMatch(base, remainingUnmatchedA, remainingUnmatchedB);
1533
+ log.i("3WAYMERGE", `[ThreeWayMerger] Semantic: ${semanticMatches.length} matched`);
1534
+ }
1535
+ const allMatches = [...matchedUnits, ...semanticMatches];
1536
+ let intents = /* @__PURE__ */ new Map();
1537
+ if (this.config.classifyIntents) {
1538
+ log.i("3WAYMERGE", "[ThreeWayMerger] Phase 4: Classifying intents...");
1539
+ intents = this.classifyIntents(allMatches);
1540
+ log.i("3WAYMERGE", `[ThreeWayMerger] Classified intents for ${intents.size} unit pairs`);
1541
+ }
1542
+ let conflicts = [];
1543
+ if (this.config.detectConflicts) {
1544
+ log.i("3WAYMERGE", "[ThreeWayMerger] Phase 5: Detecting conflicts...");
1545
+ conflicts = this.detectConflicts(allMatches, intents);
1546
+ const deleteModifyConflicts = this.detectDeleteModifyConflicts(deletedUnits);
1547
+ conflicts = [...conflicts, ...deleteModifyConflicts];
1548
+ log.i("3WAYMERGE", `[ThreeWayMerger] Detected ${conflicts.length} conflicts`);
1549
+ }
1550
+ const mergeActions = this.generateMergeActions(
1551
+ allMatches,
1552
+ conflicts,
1553
+ addedInA,
1554
+ addedInB,
1555
+ deletedUnits,
1556
+ renamedUnits
1557
+ );
1558
+ const stats = {
1559
+ totalUnitsInBase: base.stats.totalUnits,
1560
+ totalUnitsInA: indexA.stats.totalUnits,
1561
+ totalUnitsInB: indexB.stats.totalUnits,
1562
+ matchedCount: allMatches.length,
1563
+ conflictCount: conflicts.length,
1564
+ autoMergedCount: mergeActions.filter((a) => a.type === "auto-merge").length,
1565
+ manualReviewCount: mergeActions.filter((a) => a.type === "manual-review").length,
1566
+ addedFromACount: addedInA.length,
1567
+ addedFromBCount: addedInB.length,
1568
+ deletedCount: deletedUnits.length,
1569
+ renamedCount: renamedUnits.length,
1570
+ mergeTimeMs: Date.now() - startTime
1571
+ };
1572
+ const result = {
1573
+ branchA,
1574
+ branchB,
1575
+ mergeBase,
1576
+ baseIndex: base,
1577
+ branchAIndex: indexA,
1578
+ branchBIndex: indexB,
1579
+ matchedUnits: allMatches,
1580
+ addedInA,
1581
+ addedInB,
1582
+ deletedUnits,
1583
+ renamedUnits,
1584
+ conflicts,
1585
+ mergeActions,
1586
+ stats
1587
+ };
1588
+ log.i(
1589
+ "3WAYMERGE",
1590
+ `[ThreeWayMerger] Merge completed in ${stats.mergeTimeMs}ms: ${stats.autoMergedCount} auto-merged, ${stats.conflictCount} conflicts, ${stats.addedFromACount + stats.addedFromBCount} added, ${stats.deletedCount} deleted, ${stats.renamedCount} renamed`
1591
+ );
1592
+ return result;
1593
+ }
1594
+ /**
1595
+ * Phase 2: Fast Path Matching
1596
+ */
1597
+ async fastPathMatch(base, indexA, indexB) {
1598
+ const matchedUnits = [];
1599
+ const unmatchedA = [];
1600
+ const unmatchedB = [];
1601
+ const unitsA = Array.from(indexA.units.values());
1602
+ const unitsB = Array.from(indexB.units.values());
1603
+ for (const unitA of unitsA) {
1604
+ const matchResult = this.fastPathMatcher.findMatch(unitA, indexB);
1605
+ if (matchResult) {
1606
+ const baseUnit = base.units.get(unitA.id) || null;
1607
+ matchedUnits.push({
1608
+ baseUnit,
1609
+ branchAUnit: unitA,
1610
+ branchBUnit: matchResult.baseUnit
1611
+ });
1612
+ } else {
1613
+ unmatchedA.push(unitA);
1614
+ }
1615
+ }
1616
+ const matchedBIds = new Set(matchedUnits.map((m) => m.branchBUnit.id));
1617
+ for (const unitB of unitsB) {
1618
+ if (!matchedBIds.has(unitB.id)) {
1619
+ unmatchedB.push(unitB);
1620
+ }
1621
+ }
1622
+ return { matchedUnits, unmatchedA, unmatchedB };
1623
+ }
1624
+ /**
1625
+ * Phase 3: Semantic Matching
1626
+ */
1627
+ async semanticMatch(base, unmatchedA, unmatchedB) {
1628
+ if (this.config.embeddingGenerator) {
1629
+ const cache = new LazyEmbeddingCache(this.config.embeddingGenerator);
1630
+ await cache.generateEmbeddings(base, unmatchedA);
1631
+ await cache.generateEmbeddings(base, unmatchedB);
1632
+ }
1633
+ const matches = [];
1634
+ const tempIndexB = {
1635
+ ...base,
1636
+ units: new Map(unmatchedB.map((u) => [u.id, u]))
1637
+ };
1638
+ for (const unitA of unmatchedA) {
1639
+ const matchResult = this.semanticMatcher.findMatch(unitA, tempIndexB, this.config.semanticThreshold);
1640
+ if (matchResult) {
1641
+ const baseUnit = base.units.get(unitA.id) || null;
1642
+ matches.push({
1643
+ baseUnit,
1644
+ branchAUnit: unitA,
1645
+ branchBUnit: matchResult.baseUnit
1646
+ });
1647
+ }
1648
+ }
1649
+ return matches;
1650
+ }
1651
+ /**
1652
+ * Phase 4: Intent Classification
1653
+ */
1654
+ classifyIntents(matches) {
1655
+ const intents = /* @__PURE__ */ new Map();
1656
+ for (const match of matches) {
1657
+ const branchAIntent = this.intentClassifier.classifyIntent(match.baseUnit, match.branchAUnit);
1658
+ const branchBIntent = this.intentClassifier.classifyIntent(match.baseUnit, match.branchBUnit);
1659
+ intents.set(match.branchAUnit.id, { branchAIntent, branchBIntent });
1660
+ }
1661
+ return intents;
1662
+ }
1663
+ /**
1664
+ * Phase 5: Conflict Detection
1665
+ */
1666
+ detectConflicts(matches, intents) {
1667
+ const conflictsToDetect = matches.map((match) => {
1668
+ const intentPair = intents.get(match.branchAUnit.id);
1669
+ return {
1670
+ ...match,
1671
+ branchAIntent: intentPair?.branchAIntent,
1672
+ branchBIntent: intentPair?.branchBIntent
1673
+ };
1674
+ });
1675
+ return this.conflictDetector.detectConflicts(conflictsToDetect);
1676
+ }
1677
+ /**
1678
+ * Phase 2.5: Detect added, deleted, and renamed units
1679
+ */
1680
+ detectAddedDeletedRenamed(base, indexA, indexB, unmatchedA, unmatchedB, renamedInA, renamedInB) {
1681
+ const addedInA = [];
1682
+ const addedInB = [];
1683
+ const deletedUnits = [];
1684
+ const renamedUnits = [];
1685
+ const renameMapA = new Map(renamedInA.map((r) => [r.oldPath, r.path]));
1686
+ const renameMapB = new Map(renamedInB.map((r) => [r.oldPath, r.path]));
1687
+ const renameNewPathsA = new Set(renamedInA.map((r) => r.path));
1688
+ const renameNewPathsB = new Set(renamedInB.map((r) => r.path));
1689
+ for (const unit of unmatchedA) {
1690
+ const existsInBase = base.units.has(unit.id) || unit.filePath && this.findUnitByPath(base, unit.filePath);
1691
+ if (!existsInBase) {
1692
+ const isRenameDestination = unit.filePath && renameNewPathsA.has(unit.filePath);
1693
+ if (isRenameDestination) {
1694
+ for (const [oldPath, newPath] of renameMapA) {
1695
+ if (newPath === unit.filePath && oldPath) {
1696
+ renamedUnits.push({ oldPath, newPath, unit, branch: "branchA" });
1697
+ break;
1698
+ }
1699
+ }
1700
+ } else {
1701
+ addedInA.push(unit);
1702
+ }
1703
+ }
1704
+ }
1705
+ for (const unit of unmatchedB) {
1706
+ const existsInBase = base.units.has(unit.id) || unit.filePath && this.findUnitByPath(base, unit.filePath);
1707
+ if (!existsInBase) {
1708
+ const isRenameDestination = unit.filePath && renameNewPathsB.has(unit.filePath);
1709
+ if (isRenameDestination) {
1710
+ for (const [oldPath, newPath] of renameMapB) {
1711
+ if (newPath === unit.filePath && oldPath) {
1712
+ renamedUnits.push({ oldPath, newPath, unit, branch: "branchB" });
1713
+ break;
1714
+ }
1715
+ }
1716
+ } else {
1717
+ addedInB.push(unit);
1718
+ }
1719
+ }
1720
+ }
1721
+ for (const [unitId, baseUnit] of base.units) {
1722
+ const existsInA = indexA.units.has(unitId) || this.findUnitByPath(indexA, baseUnit.filePath);
1723
+ const existsInB = indexB.units.has(unitId) || this.findUnitByPath(indexB, baseUnit.filePath);
1724
+ const isRenamedInA = renameMapA.has(baseUnit.filePath);
1725
+ const isRenamedInB = renameMapB.has(baseUnit.filePath);
1726
+ if (!existsInA && !isRenamedInA && existsInB) {
1727
+ const unitInB = indexB.units.get(unitId) || this.findUnitByPath(indexB, baseUnit.filePath);
1728
+ const modifiedInB = unitInB && unitInB.contentHash !== baseUnit.contentHash;
1729
+ deletedUnits.push({
1730
+ baseUnit,
1731
+ deletedIn: "branchA",
1732
+ modifiedIn: modifiedInB ? "branchB" : void 0
1733
+ });
1734
+ } else if (!existsInB && !isRenamedInB && existsInA) {
1735
+ const unitInA = indexA.units.get(unitId) || this.findUnitByPath(indexA, baseUnit.filePath);
1736
+ const modifiedInA = unitInA && unitInA.contentHash !== baseUnit.contentHash;
1737
+ deletedUnits.push({
1738
+ baseUnit,
1739
+ deletedIn: "branchB",
1740
+ modifiedIn: modifiedInA ? "branchA" : void 0
1741
+ });
1742
+ }
1743
+ }
1744
+ return { addedInA, addedInB, deletedUnits, renamedUnits };
1745
+ }
1746
+ /**
1747
+ * Find unit by file path in an index
1748
+ */
1749
+ findUnitByPath(index, filePath) {
1750
+ if (!filePath) return void 0;
1751
+ const unitIds = index.filePathIndex.get(filePath);
1752
+ if (unitIds && unitIds.length > 0 && unitIds[0]) {
1753
+ return index.units.get(unitIds[0]);
1754
+ }
1755
+ return void 0;
1756
+ }
1757
+ /**
1758
+ * Detect delete-modify conflicts
1759
+ */
1760
+ detectDeleteModifyConflicts(deletedUnits) {
1761
+ const conflicts = [];
1762
+ for (const deleted of deletedUnits) {
1763
+ if (deleted.modifiedIn) {
1764
+ conflicts.push({
1765
+ id: `conflict-delete-modify-${deleted.baseUnit.id}`,
1766
+ type: "DeleteModify" /* DeleteModify */,
1767
+ severity: "High" /* High */,
1768
+ description: `File deleted in ${deleted.deletedIn} but modified in ${deleted.modifiedIn}`,
1769
+ baseUnit: deleted.baseUnit,
1770
+ branchAUnit: deleted.baseUnit,
1771
+ // Use base as placeholder
1772
+ branchBUnit: deleted.baseUnit,
1773
+ conflictingRegions: [],
1774
+ autoResolvable: false
1775
+ });
1776
+ }
1777
+ }
1778
+ return conflicts;
1779
+ }
1780
+ /**
1781
+ * Generate Merge Actions
1782
+ */
1783
+ generateMergeActions(matches, conflicts, addedInA, addedInB, deletedUnits, renamedUnits) {
1784
+ const actions = [];
1785
+ const conflictIds = new Set(conflicts.map((c) => c.branchAUnit.id));
1786
+ for (const match of matches) {
1787
+ if (conflictIds.has(match.branchAUnit.id)) {
1788
+ const conflict = conflicts.find((c) => c.branchAUnit.id === match.branchAUnit.id);
1789
+ actions.push({
1790
+ type: "manual-review",
1791
+ unitId: match.branchAUnit.id,
1792
+ description: `Conflict detected: ${conflict?.description}`,
1793
+ conflict
1794
+ });
1795
+ continue;
1796
+ }
1797
+ if (match.branchAUnit.contentHash === match.branchBUnit.contentHash) {
1798
+ actions.push({
1799
+ type: "auto-merge",
1800
+ unitId: match.branchAUnit.id,
1801
+ description: "Identical changes in both branches",
1802
+ mergedUnit: match.branchAUnit
1803
+ // Use either (they're identical)
1804
+ });
1805
+ continue;
1806
+ }
1807
+ actions.push({
1808
+ type: "manual-review",
1809
+ unitId: match.branchAUnit.id,
1810
+ description: "Different changes in both branches"
1811
+ });
1812
+ }
1813
+ for (const unit of addedInA) {
1814
+ actions.push({
1815
+ type: "add-from-branchA",
1816
+ unitId: unit.id,
1817
+ description: `New file added in branchA: ${unit.filePath}`,
1818
+ mergedUnit: unit,
1819
+ sourceBranch: "branchA"
1820
+ });
1821
+ }
1822
+ for (const unit of addedInB) {
1823
+ actions.push({
1824
+ type: "add-from-branchB",
1825
+ unitId: unit.id,
1826
+ description: `New file added in branchB: ${unit.filePath}`,
1827
+ mergedUnit: unit,
1828
+ sourceBranch: "branchB"
1829
+ });
1830
+ }
1831
+ for (const deleted of deletedUnits) {
1832
+ if (deleted.modifiedIn) {
1833
+ actions.push({
1834
+ type: "conflict-delete-modify",
1835
+ unitId: deleted.baseUnit.id,
1836
+ description: `Conflict: deleted in ${deleted.deletedIn}, modified in ${deleted.modifiedIn}`
1837
+ });
1838
+ } else {
1839
+ actions.push({
1840
+ type: deleted.deletedIn === "branchA" ? "delete-from-branchA" : "delete-from-branchB",
1841
+ unitId: deleted.baseUnit.id,
1842
+ description: `File deleted in ${deleted.deletedIn}: ${deleted.baseUnit.filePath}`
1843
+ });
1844
+ }
1845
+ }
1846
+ for (const renamed of renamedUnits) {
1847
+ actions.push({
1848
+ type: "rename",
1849
+ unitId: renamed.unit.id,
1850
+ description: `File renamed in ${renamed.branch}: ${renamed.oldPath} -> ${renamed.newPath}`,
1851
+ mergedUnit: renamed.unit,
1852
+ renameInfo: {
1853
+ oldPath: renamed.oldPath,
1854
+ newPath: renamed.newPath,
1855
+ sourceBranch: renamed.branch
1856
+ }
1857
+ });
1858
+ }
1859
+ return actions;
1860
+ }
1861
+ };
1862
+
1863
+ // src/merge/integration/git-integration.ts
1864
+ init_logging();
1865
+ var GitIntegration = class {
1866
+ config;
1867
+ originalBranch = null;
1868
+ constructor(config) {
1869
+ this.config = {
1870
+ allowDetachedHead: false,
1871
+ restoreOnError: true,
1872
+ ...config
1873
+ };
1874
+ if (!this.isGitRepository()) {
1875
+ throw new Error(`Not a git repository: ${this.config.repoPath}`);
1876
+ }
1877
+ }
1878
+ /**
1879
+ * Get repository path
1880
+ * Public getter to avoid intersection type issues with private config field
1881
+ */
1882
+ get repoPath() {
1883
+ return this.config.repoPath;
1884
+ }
1885
+ /**
1886
+ * Check if path is a git repository
1887
+ */
1888
+ isGitRepository() {
1889
+ const gitDir = join(this.config.repoPath, ".git");
1890
+ return existsSync(gitDir);
1891
+ }
1892
+ /**
1893
+ * Get current branch info
1894
+ */
1895
+ getCurrentBranch() {
1896
+ try {
1897
+ const branch = execSync("git symbolic-ref --short HEAD", {
1898
+ cwd: this.config.repoPath,
1899
+ encoding: "utf-8",
1900
+ stdio: ["pipe", "pipe", "ignore"],
1901
+ windowsHide: true
1902
+ }).trim();
1903
+ const commitHash = this.getCommitHash("HEAD");
1904
+ return {
1905
+ name: branch,
1906
+ commitHash,
1907
+ shortHash: commitHash.slice(0, 8),
1908
+ isDetached: false
1909
+ };
1910
+ } catch {
1911
+ const commitHash = this.getCommitHash("HEAD");
1912
+ return {
1913
+ name: `detached-${commitHash.slice(0, 8)}`,
1914
+ commitHash,
1915
+ shortHash: commitHash.slice(0, 8),
1916
+ isDetached: true
1917
+ };
1918
+ }
1919
+ }
1920
+ /**
1921
+ * Get commit hash for a reference (branch, tag, commit)
1922
+ */
1923
+ getCommitHash(ref) {
1924
+ try {
1925
+ const hash = execSync(`git rev-parse ${ref}`, {
1926
+ cwd: this.config.repoPath,
1927
+ encoding: "utf-8",
1928
+ stdio: ["pipe", "pipe", "ignore"],
1929
+ windowsHide: true
1930
+ }).trim();
1931
+ return hash;
1932
+ } catch (error) {
1933
+ throw new Error(`Failed to get commit hash for ${ref}`, { cause: error });
1934
+ }
1935
+ }
1936
+ /**
1937
+ * Check if a branch exists
1938
+ */
1939
+ branchExists(branch) {
1940
+ try {
1941
+ execSync(`git rev-parse --verify ${branch}`, {
1942
+ cwd: this.config.repoPath,
1943
+ stdio: ["pipe", "pipe", "ignore"],
1944
+ windowsHide: true
1945
+ });
1946
+ return true;
1947
+ } catch {
1948
+ return false;
1949
+ }
1950
+ }
1951
+ /**
1952
+ * Safely checkout a branch
1953
+ *
1954
+ * Saves current branch, checks out target, handles errors.
1955
+ */
1956
+ async checkoutBranch(branch) {
1957
+ if (!this.originalBranch) {
1958
+ this.originalBranch = this.getCurrentBranch().name;
1959
+ }
1960
+ if (!this.branchExists(branch)) {
1961
+ throw new Error(`Branch does not exist: ${branch}`);
1962
+ }
1963
+ if (this.hasUncommittedChanges()) {
1964
+ throw new Error("Cannot checkout branch: uncommitted changes detected. Please commit or stash changes first.");
1965
+ }
1966
+ try {
1967
+ execSync(`git checkout ${branch}`, {
1968
+ cwd: this.config.repoPath,
1969
+ stdio: ["pipe", "pipe", "pipe"],
1970
+ windowsHide: true
1971
+ });
1972
+ log.i("GITINTEGR", `[GitIntegration] Checked out branch: ${branch}`);
1973
+ } catch (error) {
1974
+ if (this.config.restoreOnError && this.originalBranch) {
1975
+ await this.restoreOriginalBranch();
1976
+ }
1977
+ throw new Error(`Failed to checkout branch ${branch}: ${error}`);
1978
+ }
1979
+ }
1980
+ /**
1981
+ * Restore original branch (before merge operations)
1982
+ */
1983
+ async restoreOriginalBranch() {
1984
+ if (!this.originalBranch) {
1985
+ log.w("GITINTEGR", "[GitIntegration] No original branch to restore");
1986
+ return;
1987
+ }
1988
+ try {
1989
+ execSync(`git checkout ${this.originalBranch}`, {
1990
+ cwd: this.config.repoPath,
1991
+ stdio: ["pipe", "pipe", "pipe"],
1992
+ windowsHide: true
1993
+ });
1994
+ log.i("GITINTEGR", `[GitIntegration] Restored original branch: ${this.originalBranch}`);
1995
+ this.originalBranch = null;
1996
+ } catch (error) {
1997
+ log.i("GITINTEGR", `[GitIntegration] Failed to restore branch: ${error}`);
1998
+ throw error;
1999
+ }
2000
+ }
2001
+ /**
2002
+ * Check if there are uncommitted changes
2003
+ */
2004
+ hasUncommittedChanges() {
2005
+ try {
2006
+ const output = execSync("git status --porcelain", {
2007
+ cwd: this.config.repoPath,
2008
+ encoding: "utf-8",
2009
+ stdio: ["pipe", "pipe", "ignore"],
2010
+ windowsHide: true
2011
+ });
2012
+ return output.trim().length > 0;
2013
+ } catch {
2014
+ return false;
2015
+ }
2016
+ }
2017
+ /**
2018
+ * Get changed files between two branches/commits
2019
+ */
2020
+ async getChangedFilesBetween(base, target) {
2021
+ try {
2022
+ const output = execSync(`git diff --name-status ${base}...${target}`, {
2023
+ cwd: this.config.repoPath,
2024
+ encoding: "utf-8",
2025
+ stdio: ["pipe", "pipe", "ignore"],
2026
+ windowsHide: true
2027
+ });
2028
+ const changes = [];
2029
+ const lines = output.trim().split("\n");
2030
+ for (const line of lines) {
2031
+ if (!line) continue;
2032
+ const parts = line.split(" ");
2033
+ const status = parts[0];
2034
+ if (!status) continue;
2035
+ let change;
2036
+ switch (status[0]) {
2037
+ case "A":
2038
+ change = { path: parts[1], status: "added" };
2039
+ break;
2040
+ case "M":
2041
+ change = { path: parts[1], status: "modified" };
2042
+ break;
2043
+ case "D":
2044
+ change = { path: parts[1], status: "deleted" };
2045
+ break;
2046
+ case "R":
2047
+ change = {
2048
+ path: parts[2],
2049
+ status: "renamed",
2050
+ oldPath: parts[1]
2051
+ };
2052
+ break;
2053
+ default:
2054
+ change = { path: parts[1], status: "modified" };
2055
+ }
2056
+ changes.push(change);
2057
+ }
2058
+ return changes;
2059
+ } catch (error) {
2060
+ log.i("GITINTEGR", `[GitIntegration] Failed to get changed files: ${error}`);
2061
+ return [];
2062
+ }
2063
+ }
2064
+ /**
2065
+ * Get detailed diff statistics between branches
2066
+ */
2067
+ async getDiffStats(base, target) {
2068
+ try {
2069
+ const files = await this.getChangedFilesBetween(base, target);
2070
+ const output = execSync(`git diff --shortstat ${base}...${target}`, {
2071
+ cwd: this.config.repoPath,
2072
+ encoding: "utf-8",
2073
+ stdio: ["pipe", "pipe", "ignore"],
2074
+ windowsHide: true
2075
+ });
2076
+ const match = output.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
2077
+ const filesChanged = match?.[1] ? Number.parseInt(match[1], 10) : 0;
2078
+ const insertions = match?.[2] ? Number.parseInt(match[2], 10) : 0;
2079
+ const deletions = match?.[3] ? Number.parseInt(match[3], 10) : 0;
2080
+ return {
2081
+ files,
2082
+ filesChanged,
2083
+ insertions,
2084
+ deletions
2085
+ };
2086
+ } catch (error) {
2087
+ log.i("GITINTEGR", `[GitIntegration] Failed to get diff stats: ${error}`);
2088
+ return {
2089
+ files: [],
2090
+ filesChanged: 0,
2091
+ insertions: 0,
2092
+ deletions: 0
2093
+ };
2094
+ }
2095
+ }
2096
+ /**
2097
+ * Get merge base between two branches
2098
+ *
2099
+ * The merge base is the common ancestor commit.
2100
+ */
2101
+ getMergeBase(branchA, branchB) {
2102
+ try {
2103
+ const base = execSync(`git merge-base ${branchA} ${branchB}`, {
2104
+ cwd: this.config.repoPath,
2105
+ encoding: "utf-8",
2106
+ stdio: ["pipe", "pipe", "ignore"],
2107
+ windowsHide: true
2108
+ }).trim();
2109
+ return base;
2110
+ } catch (error) {
2111
+ log.i("GITINTEGR", `[GitIntegration] Failed to get merge base: ${error}`);
2112
+ return null;
2113
+ }
2114
+ }
2115
+ /**
2116
+ * Get file content at specific revision
2117
+ *
2118
+ * @param path - File path relative to repository root
2119
+ * @param ref - Git reference (branch, tag, commit hash)
2120
+ * @returns File content as string, or null if file doesn't exist at ref
2121
+ */
2122
+ getFileContent(path, ref) {
2123
+ try {
2124
+ const content = execSync(`git show ${ref}:${path}`, {
2125
+ cwd: this.config.repoPath,
2126
+ encoding: "utf-8",
2127
+ stdio: ["pipe", "pipe", "ignore"],
2128
+ windowsHide: true
2129
+ });
2130
+ return content;
2131
+ } catch (_error) {
2132
+ return null;
2133
+ }
2134
+ }
2135
+ /**
2136
+ * Get list of changed file paths between two references
2137
+ *
2138
+ * Simplified version of getChangedFilesBetween that returns only paths.
2139
+ * Useful for quick file enumeration.
2140
+ *
2141
+ * @param fromRef - Starting reference (branch, tag, commit)
2142
+ * @param toRef - Target reference
2143
+ * @returns Array of file paths (without status information)
2144
+ */
2145
+ async getChangedFiles(fromRef, toRef) {
2146
+ try {
2147
+ const output = execSync(`git diff --name-only ${fromRef}...${toRef}`, {
2148
+ cwd: this.config.repoPath,
2149
+ encoding: "utf-8",
2150
+ stdio: ["pipe", "pipe", "ignore"],
2151
+ windowsHide: true
2152
+ });
2153
+ return output.trim().split("\n").filter((path) => path.length > 0);
2154
+ } catch (error) {
2155
+ log.i("GITINTEGR", `[GitIntegration] Failed to get changed files: ${error}`);
2156
+ return [];
2157
+ }
2158
+ }
2159
+ /**
2160
+ * Get list of all branches
2161
+ */
2162
+ getAllBranches() {
2163
+ try {
2164
+ const output = execSync("git branch --format='%(refname:short)'", {
2165
+ cwd: this.config.repoPath,
2166
+ encoding: "utf-8",
2167
+ stdio: ["pipe", "pipe", "ignore"],
2168
+ windowsHide: true
2169
+ });
2170
+ return output.trim().split("\n").map((b) => b.trim()).filter((b) => b.length > 0);
2171
+ } catch {
2172
+ return [];
2173
+ }
2174
+ }
2175
+ /**
2176
+ * Clean up - restore original branch if not already done
2177
+ */
2178
+ async cleanup() {
2179
+ if (this.originalBranch) {
2180
+ await this.restoreOriginalBranch();
2181
+ }
2182
+ }
2183
+ };
2184
+
2185
+ // src/agents/merge-agent.ts
2186
+ var MergeAgent = class extends BaseAgent {
2187
+ config;
2188
+ gitIntegration = null;
2189
+ threeWayMerger = null;
2190
+ aiResolver = null;
2191
+ branchManager = null;
2192
+ // Merge-specific metrics
2193
+ mergeMetrics = {
2194
+ mergesPerformed: 0,
2195
+ conflictsDetected: 0,
2196
+ conflictsAutoResolved: 0,
2197
+ totalMergeTimeMs: 0,
2198
+ lastMergeAt: 0
2199
+ };
2200
+ constructor(config) {
2201
+ super("merge" /* MERGE */, {
2202
+ maxConcurrency: config.maxConcurrency ?? 1,
2203
+ memoryLimit: 512,
2204
+ // MB
2205
+ priority: 5
2206
+ });
2207
+ this.config = {
2208
+ fastPathEnabled: true,
2209
+ semanticMatchingEnabled: true,
2210
+ semanticThreshold: 0.7,
2211
+ autoResolveConflicts: false,
2212
+ maxConcurrency: 1,
2213
+ ...config
2214
+ };
2215
+ }
2216
+ // =============================================================================
2217
+ // ABSTRACT METHOD IMPLEMENTATIONS
2218
+ // =============================================================================
2219
+ canProcessTask(task) {
2220
+ return task.type === "merge" || task.type === "merge:analyze" || task.type === "merge:perform" || task.type === "merge:suggestions";
2221
+ }
2222
+ async processTask(task) {
2223
+ log.d("MERGEAGENT", "proc_task", { id: task.id, type: task.type });
2224
+ const payload = task.payload;
2225
+ switch (task.type) {
2226
+ case "merge":
2227
+ case "merge:perform":
2228
+ return await this.performSemanticMerge({
2229
+ branchA: payload["branchA"],
2230
+ branchB: payload["branchB"],
2231
+ dryRun: payload["dryRun"],
2232
+ autoResolve: payload["autoResolve"],
2233
+ includeAISuggestions: payload["includeAISuggestions"]
2234
+ });
2235
+ case "merge:analyze":
2236
+ return await this.analyzeConflicts(payload["branchA"], payload["branchB"]);
2237
+ case "merge:suggestions":
2238
+ return await this.getSuggestions(payload["conflict"]);
2239
+ default:
2240
+ throw new Error(`Unknown task type: ${task.type}`);
2241
+ }
2242
+ }
2243
+ async handleMessage(message) {
2244
+ log.d("MERGEAGENT", "recv_msg", { from: message.from, type: message.type });
2245
+ }
2246
+ // =============================================================================
2247
+ // LIFECYCLE
2248
+ // =============================================================================
2249
+ async onInitialize() {
2250
+ log.i("MERGEAGENT", "init_start");
2251
+ this.gitIntegration = this.config.gitIntegration ?? new GitIntegration({
2252
+ repoPath: this.config.repoPath,
2253
+ allowDetachedHead: true,
2254
+ restoreOnError: true
2255
+ });
2256
+ this.branchManager = this.config.branchManager ?? null;
2257
+ const mergerConfig = {
2258
+ fastPathEnabled: this.config.fastPathEnabled,
2259
+ semanticMatchingEnabled: this.config.semanticMatchingEnabled,
2260
+ semanticThreshold: this.config.semanticThreshold,
2261
+ autoResolveConflicts: this.config.autoResolveConflicts
2262
+ };
2263
+ if (this.branchManager && this.gitIntegration) {
2264
+ this.threeWayMerger = new ThreeWayMerger(
2265
+ this.branchManager,
2266
+ this.gitIntegration,
2267
+ null,
2268
+ // conductor - not needed for basic merge operations
2269
+ mergerConfig
2270
+ );
2271
+ }
2272
+ this.aiResolver = null;
2273
+ log.i("MERGEAGENT", "init_done");
2274
+ }
2275
+ async onShutdown() {
2276
+ log.i("MERGEAGENT", "shutdown_start");
2277
+ if (this.gitIntegration) {
2278
+ await this.gitIntegration.cleanup();
2279
+ }
2280
+ this.threeWayMerger = null;
2281
+ this.gitIntegration = null;
2282
+ this.aiResolver = null;
2283
+ log.i("MERGEAGENT", "shutdown_done");
2284
+ }
2285
+ // =============================================================================
2286
+ // PUBLIC API
2287
+ // =============================================================================
2288
+ /**
2289
+ * Set dependencies after construction (for DI)
2290
+ */
2291
+ setDependencies(branchManager, gitIntegration) {
2292
+ this.branchManager = branchManager;
2293
+ if (gitIntegration) {
2294
+ this.gitIntegration = gitIntegration;
2295
+ }
2296
+ }
2297
+ /**
2298
+ * Set AI resolver with embedding generator
2299
+ */
2300
+ setAIResolver(config) {
2301
+ this.aiResolver = new AIConflictResolver(config);
2302
+ }
2303
+ /**
2304
+ * Perform semantic merge between two branches
2305
+ */
2306
+ async performSemanticMerge(options) {
2307
+ const startTime = Date.now();
2308
+ if (!this.threeWayMerger) {
2309
+ return {
2310
+ success: false,
2311
+ error: "MergeAgent not properly initialized - missing ThreeWayMerger",
2312
+ stats: this.createEmptyStats(0)
2313
+ };
2314
+ }
2315
+ try {
2316
+ log.i("MERGEAGENT", "merge_start", { from: options.branchA, to: options.branchB });
2317
+ const mergeResult = await this.threeWayMerger.performMerge(options.branchA, options.branchB);
2318
+ if (options.includeAISuggestions && mergeResult.conflicts.length > 0 && this.aiResolver) {
2319
+ await this.generateAISuggestions(mergeResult.conflicts);
2320
+ }
2321
+ let autoResolved = 0;
2322
+ if (options.autoResolve) {
2323
+ autoResolved = await this.autoResolveConflicts(mergeResult);
2324
+ }
2325
+ let appliedActions = [];
2326
+ if (!options.dryRun) {
2327
+ appliedActions = await this.applyMergeActions(mergeResult);
2328
+ }
2329
+ const mergeTimeMs = Date.now() - startTime;
2330
+ this.updateMergeMetrics(mergeResult, autoResolved, mergeTimeMs);
2331
+ return {
2332
+ success: true,
2333
+ mergeResult,
2334
+ appliedActions,
2335
+ stats: {
2336
+ totalUnitsAnalyzed: mergeResult.stats.totalUnitsInA + mergeResult.stats.totalUnitsInB,
2337
+ matchedUnits: mergeResult.stats.matchedCount,
2338
+ conflictsDetected: mergeResult.stats.conflictCount,
2339
+ autoResolved,
2340
+ manualReviewRequired: mergeResult.stats.manualReviewCount - autoResolved,
2341
+ mergeTimeMs
2342
+ }
2343
+ };
2344
+ } catch (error) {
2345
+ const errorMsg = error instanceof Error ? error.message : String(error);
2346
+ log.e("MERGEAGENT", "merge_fail", { err: errorMsg });
2347
+ return {
2348
+ success: false,
2349
+ error: errorMsg,
2350
+ stats: this.createEmptyStats(Date.now() - startTime)
2351
+ };
2352
+ }
2353
+ }
2354
+ /**
2355
+ * Analyze merge conflicts without performing merge
2356
+ */
2357
+ async analyzeConflicts(branchA, branchB) {
2358
+ const result = await this.performSemanticMerge({
2359
+ branchA,
2360
+ branchB,
2361
+ dryRun: true,
2362
+ autoResolve: false
2363
+ });
2364
+ if (!result.mergeResult) {
2365
+ return {
2366
+ conflicts: [],
2367
+ stats: { totalConflicts: 0, bySeverity: {}, byType: {} }
2368
+ };
2369
+ }
2370
+ const conflicts = result.mergeResult.conflicts;
2371
+ const bySeverity = {};
2372
+ const byType = {};
2373
+ for (const conflict of conflicts) {
2374
+ bySeverity[conflict.severity] = (bySeverity[conflict.severity] || 0) + 1;
2375
+ byType[conflict.type] = (byType[conflict.type] || 0) + 1;
2376
+ }
2377
+ return {
2378
+ conflicts,
2379
+ stats: {
2380
+ totalConflicts: conflicts.length,
2381
+ bySeverity,
2382
+ byType
2383
+ }
2384
+ };
2385
+ }
2386
+ /**
2387
+ * Get AI-generated suggestions for a specific conflict
2388
+ */
2389
+ async getSuggestions(conflict) {
2390
+ if (!this.aiResolver) {
2391
+ return ["AI resolver not available - configure embedding generator first"];
2392
+ }
2393
+ try {
2394
+ const aiAnalysis = await this.aiResolver.analyzeConflict(conflict);
2395
+ const resolution = this.aiResolver.createResolution(aiAnalysis);
2396
+ return [
2397
+ `Strategy: ${resolution.strategy}`,
2398
+ `Confidence: ${(resolution.confidence * 100).toFixed(1)}%`,
2399
+ `Explanation: ${resolution.explanation}`,
2400
+ ...resolution.mergedCode ? [`Suggested code:
2401
+ ${resolution.mergedCode}`] : []
2402
+ ];
2403
+ } catch (error) {
2404
+ log.e("MERGEAGENT", "ai_suggest_fail", { err: String(error) });
2405
+ return [];
2406
+ }
2407
+ }
2408
+ /**
2409
+ * Get merge-specific metrics
2410
+ */
2411
+ getMergeMetrics() {
2412
+ return { ...this.mergeMetrics };
2413
+ }
2414
+ // =============================================================================
2415
+ // PRIVATE METHODS
2416
+ // =============================================================================
2417
+ async generateAISuggestions(conflicts) {
2418
+ if (!this.aiResolver) return;
2419
+ log.d("MERGEAGENT", "gen_ai_suggest", { cnt: conflicts.length });
2420
+ for (const conflict of conflicts) {
2421
+ try {
2422
+ const aiAnalysis = await this.aiResolver.analyzeConflict(conflict);
2423
+ const resolution = this.aiResolver.createResolution(aiAnalysis);
2424
+ conflict.aiSuggestions = [resolution.explanation];
2425
+ conflict.aiConfidence = resolution.confidence;
2426
+ } catch (error) {
2427
+ log.w("MERGEAGENT", "ai_conflict_fail", { err: String(error) });
2428
+ }
2429
+ }
2430
+ }
2431
+ async autoResolveConflicts(mergeResult) {
2432
+ let resolved = 0;
2433
+ for (const action of mergeResult.mergeActions) {
2434
+ if (action.type === "manual-review" && action.conflict) {
2435
+ if (this.aiResolver) {
2436
+ try {
2437
+ const aiAnalysis = await this.aiResolver.analyzeConflict(action.conflict);
2438
+ if (aiAnalysis.confidence >= 0.9 && aiAnalysis.mergedCode) {
2439
+ action.type = "auto-merge";
2440
+ action.description = `Auto-resolved: ${aiAnalysis.explanation}`;
2441
+ resolved++;
2442
+ }
2443
+ } catch {
2444
+ }
2445
+ }
2446
+ }
2447
+ }
2448
+ return resolved;
2449
+ }
2450
+ async applyMergeActions(mergeResult) {
2451
+ const applied = [];
2452
+ for (const action of mergeResult.mergeActions) {
2453
+ if (action.type === "auto-merge" && action.mergedUnit) {
2454
+ applied.push(action);
2455
+ }
2456
+ }
2457
+ log.i("MERGEAGENT", "actions_applied", { cnt: applied.length });
2458
+ return applied;
2459
+ }
2460
+ updateMergeMetrics(mergeResult, autoResolved, mergeTimeMs) {
2461
+ this.mergeMetrics.mergesPerformed++;
2462
+ this.mergeMetrics.conflictsDetected += mergeResult.conflicts.length;
2463
+ this.mergeMetrics.conflictsAutoResolved += autoResolved;
2464
+ this.mergeMetrics.totalMergeTimeMs += mergeTimeMs;
2465
+ this.mergeMetrics.lastMergeAt = Date.now();
2466
+ }
2467
+ createEmptyStats(mergeTimeMs) {
2468
+ return {
2469
+ totalUnitsAnalyzed: 0,
2470
+ matchedUnits: 0,
2471
+ conflictsDetected: 0,
2472
+ autoResolved: 0,
2473
+ manualReviewRequired: 0,
2474
+ mergeTimeMs
2475
+ };
2476
+ }
2477
+ };
2478
+
2479
+ export { MergeAgent };
2480
+ //# sourceMappingURL=merge-agent-LSUBDJB2.js.map
2481
+ //# sourceMappingURL=merge-agent-LSUBDJB2.js.map