aios-core 4.2.13 → 4.2.15

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 (95) hide show
  1. package/.aios-core/core/code-intel/helpers/dev-helper.js +206 -0
  2. package/.aios-core/core/registry/registry-schema.json +166 -166
  3. package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
  4. package/.aios-core/data/entity-registry.yaml +27 -0
  5. package/.aios-core/development/scripts/approval-workflow.js +642 -642
  6. package/.aios-core/development/scripts/backup-manager.js +606 -606
  7. package/.aios-core/development/scripts/branch-manager.js +389 -389
  8. package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
  9. package/.aios-core/development/scripts/commit-message-generator.js +849 -849
  10. package/.aios-core/development/scripts/conflict-resolver.js +674 -674
  11. package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
  12. package/.aios-core/development/scripts/diff-generator.js +351 -351
  13. package/.aios-core/development/scripts/elicitation-engine.js +384 -384
  14. package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
  15. package/.aios-core/development/scripts/git-wrapper.js +461 -461
  16. package/.aios-core/development/scripts/manifest-preview.js +244 -244
  17. package/.aios-core/development/scripts/metrics-tracker.js +775 -775
  18. package/.aios-core/development/scripts/modification-validator.js +554 -554
  19. package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
  20. package/.aios-core/development/scripts/performance-analyzer.js +757 -757
  21. package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
  22. package/.aios-core/development/scripts/rollback-handler.js +530 -530
  23. package/.aios-core/development/scripts/security-checker.js +358 -358
  24. package/.aios-core/development/scripts/template-engine.js +239 -239
  25. package/.aios-core/development/scripts/template-validator.js +278 -278
  26. package/.aios-core/development/scripts/test-generator.js +843 -843
  27. package/.aios-core/development/scripts/transaction-manager.js +589 -589
  28. package/.aios-core/development/scripts/usage-tracker.js +673 -673
  29. package/.aios-core/development/scripts/validate-filenames.js +226 -226
  30. package/.aios-core/development/scripts/version-tracker.js +526 -526
  31. package/.aios-core/development/scripts/yaml-validator.js +396 -396
  32. package/.aios-core/development/tasks/build-autonomous.md +10 -4
  33. package/.aios-core/development/tasks/create-service.md +23 -0
  34. package/.aios-core/development/tasks/dev-develop-story.md +12 -6
  35. package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
  36. package/.aios-core/development/tasks/publish-npm.md +3 -3
  37. package/.aios-core/hooks/unified/README.md +1 -1
  38. package/.aios-core/install-manifest.yaml +65 -61
  39. package/.aios-core/manifests/schema/manifest-schema.json +190 -190
  40. package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
  41. package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
  42. package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
  43. package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
  44. package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
  45. package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
  46. package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
  47. package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
  48. package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
  49. package/.aios-core/product/templates/eslintrc-security.json +32 -32
  50. package/.aios-core/product/templates/github-actions-cd.yml +212 -212
  51. package/.aios-core/product/templates/github-actions-ci.yml +172 -172
  52. package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
  53. package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
  54. package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
  55. package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
  56. package/README.en.md +747 -0
  57. package/README.md +4 -2
  58. package/bin/aios.js +7 -4
  59. package/package.json +1 -1
  60. package/packages/aios-pro-cli/src/recover.js +1 -1
  61. package/packages/installer/src/wizard/ide-config-generator.js +6 -6
  62. package/packages/installer/src/wizard/pro-setup.js +3 -3
  63. package/pro/license/degradation.js +220 -220
  64. package/pro/license/errors.js +450 -450
  65. package/pro/license/feature-gate.js +354 -354
  66. package/pro/license/index.js +181 -181
  67. package/pro/license/license-cache.js +523 -523
  68. package/pro/license/license-crypto.js +303 -303
  69. package/scripts/package-synapse.js +5 -5
  70. package/scripts/validate-package-completeness.js +3 -3
  71. package/.aios-core/.session/current-session.json +0 -14
  72. package/.aios-core/data/registry-update-log.jsonl +0 -191
  73. package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
  74. package/.aios-core/docs/component-creation-guide.md +0 -458
  75. package/.aios-core/docs/session-update-pattern.md +0 -307
  76. package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
  77. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
  78. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
  79. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
  80. package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
  81. package/.aios-core/docs/template-syntax.md +0 -267
  82. package/.aios-core/docs/troubleshooting-guide.md +0 -625
  83. package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
  84. package/.aios-core/manifests/agents.csv +0 -29
  85. package/.aios-core/manifests/tasks.csv +0 -198
  86. package/.aios-core/manifests/workers.csv +0 -204
  87. package/.claude/rules/agent-authority.md +0 -105
  88. package/.claude/rules/coderabbit-integration.md +0 -93
  89. package/.claude/rules/ids-principles.md +0 -112
  90. package/.claude/rules/story-lifecycle.md +0 -139
  91. package/.claude/rules/workflow-execution.md +0 -150
  92. package/scripts/glue/README.md +0 -355
  93. package/scripts/glue/compose-agent-prompt.cjs +0 -362
  94. /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
  95. /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
@@ -1,675 +1,675 @@
1
- const fs = require('fs').promises;
2
- const path = require('path');
3
- const chalk = require('chalk');
4
- const _diffLib = require('diff');
5
- const inquirer = require('inquirer');
6
- const GitWrapper = require('./git-wrapper');
7
-
8
- /**
9
- * Handles conflict detection and resolution for meta-agent modifications
10
- */
11
- class ConflictResolver {
12
- constructor(options = {}) {
13
- this.git = new GitWrapper(options);
14
- this.rootPath = options.rootPath || process.cwd();
15
- this.strategies = {
16
- 'ours': this.resolveOurs.bind(this),
17
- 'theirs': this.resolveTheirs.bind(this),
18
- 'manual': this.resolveManual.bind(this),
19
- 'auto': this.resolveAuto.bind(this),
20
- 'interactive': this.resolveInteractive.bind(this)
21
- };
22
- }
23
-
24
- /**
25
- * Detect conflicts in the repository
26
- * @returns {Promise<Object>} Conflict information
27
- */
28
- async detectConflicts() {
29
- try {
30
- const conflicts = await this.git.getConflicts();
31
-
32
- if (conflicts.length === 0) {
33
- return {
34
- hasConflicts: false,
35
- files: []
36
- };
37
- }
38
-
39
- const conflictDetails = [];
40
- for (const file of conflicts) {
41
- const content = await fs.readFile(
42
- path.join(this.rootPath, file),
43
- 'utf-8'
44
- );
45
-
46
- const conflictInfo = this.parseConflictMarkers(_content);
47
- conflictDetails.push({
48
- file,
49
- conflicts: conflictInfo.conflicts,
50
- conflictCount: conflictInfo.conflicts.length,
51
- type: this.detectConflictType(file, conflictInfo)
52
- });
53
- }
54
-
55
- return {
56
- hasConflicts: true,
57
- files: conflictDetails,
58
- totalConflicts: conflictDetails.reduce((sum, f) => sum + f.conflictCount, 0)
59
- };
60
- } catch (error) {
61
- console.error(chalk.red(`Error detecting conflicts: ${error.message}`));
62
- return {
63
- hasConflicts: false,
64
- error: error.message
65
- };
66
- }
67
- }
68
-
69
- /**
70
- * Parse conflict markers in file content
71
- * @private
72
- */
73
- parseConflictMarkers(_content) {
74
- const conflicts = [];
75
- const lines = content.split('\n');
76
- let inConflict = false;
77
- let currentConflict = null;
78
- let lineNumber = 0;
79
-
80
- for (const line of lines) {
81
- lineNumber++;
82
-
83
- if (line.startsWith('<<<<<<<')) {
84
- inConflict = true;
85
- currentConflict = {
86
- startLine: lineNumber,
87
- ours: [],
88
- theirs: [],
89
- separator: null,
90
- endLine: null,
91
- branch: line.substring(8).trim()
92
- };
93
- } else if (inConflict && line.startsWith('=======')) {
94
- currentConflict.separator = lineNumber;
95
- } else if (inConflict && line.startsWith('>>>>>>>')) {
96
- currentConflict.endLine = lineNumber;
97
- currentConflict.theirBranch = line.substring(8).trim();
98
- conflicts.push(currentConflict);
99
- inConflict = false;
100
- currentConflict = null;
101
- } else if (inConflict && currentConflict) {
102
- if (currentConflict.separator === null) {
103
- currentConflict.ours.push(line);
104
- } else {
105
- currentConflict.theirs.push(line);
106
- }
107
- }
108
- }
109
-
110
- return { conflicts, totalLines: lineNumber };
111
- }
112
-
113
- /**
114
- * Detect the type of conflict
115
- * @private
116
- */
117
- detectConflictType(file, conflictInfo) {
118
- const ext = path.extname(file);
119
- const conflicts = conflictInfo.conflicts;
120
-
121
- // Check for specific conflict patterns
122
- for (const _conflict of conflicts) {
123
- const oursContent = conflict.ours.join('\n');
124
- const theirsContent = conflict.theirs.join('\n');
125
-
126
- // Whitespace only conflict
127
- if (oursContent.trim() === theirsContent.trim()) {
128
- return 'whitespace';
129
- }
130
-
131
- // Import/require conflict
132
- if ((oursContent.includes('import') || oursContent.includes('require')) &&
133
- (theirsContent.includes('import') || theirsContent.includes('require'))) {
134
- return 'imports';
135
- }
136
-
137
- // Version number conflict
138
- if (oursContent.match(/\d+\.\d+\.\d+/) && theirsContent.match(/\d+\.\d+\.\d+/)) {
139
- return 'version';
140
- }
141
- }
142
-
143
- // File type specific
144
- if (ext === '.json') return 'json';
145
- if (ext === '.yaml' || ext === '.yml') return 'yaml';
146
- if (['.js', '.ts', '.jsx', '.tsx'].includes(ext)) return 'code';
147
- if (ext === '.md') return 'markdown';
148
-
149
- return 'general';
150
- }
151
-
152
- /**
153
- * Resolve conflicts using a specific strategy
154
- * @param {string} strategy - Resolution strategy
155
- * @param {Object} options - Resolution options
156
- * @returns {Promise<Object>} Resolution result
157
- */
158
- async resolveConflicts(strategy = 'interactive', options = {}) {
159
- const conflictInfo = await this.detectConflicts();
160
-
161
- if (!conflictInfo.hasConflicts) {
162
- console.log(chalk.green('✅ No conflicts detected'));
163
- return { success: true, resolved: 0 };
164
- }
165
-
166
- console.log(chalk.yellow(
167
- `Found ${conflictInfo.totalConflicts} conflicts in ${conflictInfo.files.length} files`
168
- ));
169
-
170
- const resolver = this.strategies[strategy];
171
- if (!resolver) {
172
- throw new Error(`Unknown resolution strategy: ${strategy}`);
173
- }
174
-
175
- const results = {
176
- resolved: 0,
177
- failed: 0,
178
- files: []
179
- };
180
-
181
- for (const fileInfo of conflictInfo.files) {
182
- try {
183
- console.log(chalk.blue(`\nResolving conflicts in: ${fileInfo.file}`));
184
- const resolved = await resolver(_fileInfo, options);
185
-
186
- if (resolved.success) {
187
- results.resolved += resolved.conflictsResolved;
188
- results.files.push({
189
- file: fileInfo.file,
190
- status: 'resolved',
191
- method: resolved.method
192
- });
193
- } else {
194
- results.failed++;
195
- results.files.push({
196
- file: fileInfo.file,
197
- status: 'failed',
198
- error: resolved.error
199
- });
200
- }
201
- } catch (error) {
202
- results.failed++;
203
- results.files.push({
204
- file: fileInfo.file,
205
- status: 'error',
206
- error: error.message
207
- });
208
- }
209
- }
210
-
211
- return results;
212
- }
213
-
214
- /**
215
- * Resolve using 'ours' strategy (keep current branch changes)
216
- * @private
217
- */
218
- async resolveOurs(_fileInfo) {
219
- try {
220
- await this.git.execGit(`checkout --ours "${fileInfo.file}"`);
221
- await this.git.execGit(`add "${fileInfo.file}"`);
222
-
223
- return {
224
- success: true,
225
- conflictsResolved: fileInfo.conflictCount,
226
- method: 'ours'
227
- };
228
- } catch (error) {
229
- return {
230
- success: false,
231
- error: error.message
232
- };
233
- }
234
- }
235
-
236
- /**
237
- * Resolve using 'theirs' strategy (keep incoming branch changes)
238
- * @private
239
- */
240
- async resolveTheirs(_fileInfo) {
241
- try {
242
- await this.git.execGit(`checkout --theirs "${fileInfo.file}"`);
243
- await this.git.execGit(`add "${fileInfo.file}"`);
244
-
245
- return {
246
- success: true,
247
- conflictsResolved: fileInfo.conflictCount,
248
- method: 'theirs'
249
- };
250
- } catch (error) {
251
- return {
252
- success: false,
253
- error: error.message
254
- };
255
- }
256
- }
257
-
258
- /**
259
- * Resolve conflicts manually by editing the file
260
- * @private
261
- */
262
- async resolveManual(_fileInfo) {
263
- const filePath = path.join(this.rootPath, fileInfo.file);
264
- const content = await fs.readFile(filePath, 'utf-8');
265
-
266
- console.log(chalk.yellow(
267
- `Manual resolution required for ${fileInfo.file}`
268
- ));
269
- console.log(chalk.gray(
270
- 'Edit the file to resolve conflicts, then mark as resolved'
271
- ));
272
-
273
- // In a real implementation, this would open an editor
274
- // For now, we'll return a message
275
- return {
276
- success: false,
277
- error: 'Manual resolution required',
278
- instruction: `Edit ${filePath} and run: git add "${fileInfo.file}"`
279
- };
280
- }
281
-
282
- /**
283
- * Automatically resolve conflicts based on type
284
- * @private
285
- */
286
- async resolveAuto(_fileInfo) {
287
- const filePath = path.join(this.rootPath, fileInfo.file);
288
- const content = await fs.readFile(filePath, 'utf-8');
289
-
290
- let resolved = content;
291
- let resolvedCount = 0;
292
-
293
- switch (fileInfo.type) {
294
- case 'whitespace':
295
- // For whitespace conflicts, keep theirs
296
- resolved = await this.autoResolveWhitespace(_content, fileInfo);
297
- resolvedCount = fileInfo.conflictCount;
298
- break;
299
-
300
- case 'imports':
301
- // For import conflicts, merge both
302
- resolved = await this.autoResolveImports(_content, fileInfo);
303
- resolvedCount = fileInfo.conflictCount;
304
- break;
305
-
306
- case 'version':
307
- // For version conflicts, keep higher version
308
- resolved = await this.autoResolveVersion(_content, fileInfo);
309
- resolvedCount = fileInfo.conflictCount;
310
- break;
311
-
312
- case 'json':
313
- // For JSON conflicts, attempt to merge
314
- resolved = await this.autoResolveJSON(_content, fileInfo);
315
- resolvedCount = fileInfo.conflictCount;
316
- break;
317
-
318
- default:
319
- // Can't auto-resolve
320
- return {
321
- success: false,
322
- error: `Cannot auto-resolve ${fileInfo.type} conflicts`
323
- };
324
- }
325
-
326
- // Write resolved content
327
- await fs.writeFile(filePath, resolved);
328
- await this.git.execGit(`add "${fileInfo.file}"`);
329
-
330
- return {
331
- success: true,
332
- conflictsResolved: resolvedCount,
333
- method: `auto-${fileInfo.type}`
334
- };
335
- }
336
-
337
- /**
338
- * Interactive conflict resolution
339
- * @private
340
- */
341
- async resolveInteractive(_fileInfo) {
342
- const filePath = path.join(this.rootPath, fileInfo.file);
343
- const content = await fs.readFile(filePath, 'utf-8');
344
- const conflicts = this.parseConflictMarkers(_content).conflicts;
345
-
346
- let resolvedContent = content;
347
- let resolvedCount = 0;
348
-
349
- console.log(chalk.blue(`\nResolving ${fileInfo.file} (${conflicts.length} conflicts)`));
350
-
351
- for (let i = 0; i < conflicts.length; i++) {
352
- const _conflict = conflicts[i];
353
- console.log(chalk.yellow(`\nConflict ${i + 1}/${conflicts.length}:`));
354
-
355
- // Show conflict preview
356
- console.log(chalk.red('<<<< OURS:'));
357
- console.log(conflict.ours.slice(0, 5).join('\n'));
358
- if (conflict.ours.length > 5) console.log(chalk.gray('...'));
359
-
360
- console.log(chalk.green('\n>>>> THEIRS:'));
361
- console.log(conflict.theirs.slice(0, 5).join('\n'));
362
- if (conflict.theirs.length > 5) console.log(chalk.gray('...'));
363
-
364
- const { resolution } = await inquirer.prompt([{
365
- type: 'list',
366
- name: 'resolution',
367
- message: 'How to resolve this conflict?',
368
- choices: [
369
- { name: 'Keep ours (current branch)', value: 'ours' },
370
- { name: 'Keep theirs (incoming)', value: 'theirs' },
371
- { name: 'Keep both (ours first)', value: 'both-ours' },
372
- { name: 'Keep both (theirs first)', value: 'both-theirs' },
373
- { name: 'Custom merge', value: 'custom' },
374
- { name: 'Skip this conflict', value: 'skip' }
375
- ]
376
- }]);
377
-
378
- if (resolution !== 'skip') {
379
- resolvedContent = await this.applyResolution(
380
- resolvedContent,
381
- _conflict,
382
- resolution
383
- );
384
- resolvedCount++;
385
- }
386
- }
387
-
388
- if (resolvedCount > 0) {
389
- await fs.writeFile(filePath, resolvedContent);
390
- await this.git.execGit(`add "${fileInfo.file}"`);
391
- }
392
-
393
- return {
394
- success: true,
395
- conflictsResolved: resolvedCount,
396
- method: 'interactive'
397
- };
398
- }
399
-
400
- /**
401
- * Apply a specific resolution to content
402
- * @private
403
- */
404
- async applyResolution(_content, _conflict, resolution) {
405
- const lines = content.split('\n');
406
- let newLines = [];
407
- let skipUntil = null;
408
-
409
- for (let i = 0; i < lines.length; i++) {
410
- if (skipUntil && i < skipUntil) continue;
411
-
412
- if (i === conflict.startLine - 1) {
413
- switch (resolution) {
414
- case 'ours':
415
- newLines.push(...conflict.ours);
416
- break;
417
- case 'theirs':
418
- newLines.push(...conflict.theirs);
419
- break;
420
- case 'both-ours':
421
- newLines.push(...conflict.ours);
422
- newLines.push(...conflict.theirs);
423
- break;
424
- case 'both-theirs':
425
- newLines.push(...conflict.theirs);
426
- newLines.push(...conflict.ours);
427
- break;
428
- case 'custom':
429
- const { custom } = await inquirer.prompt([{
430
- type: 'editor',
431
- name: 'custom',
432
- message: 'Enter custom resolution:',
433
- default: conflict.ours.join('\n')
434
- }]);
435
- newLines.push(...custom.split('\n'));
436
- break;
437
- }
438
- skipUntil = conflict.endLine;
439
- } else {
440
- newLines.push(lines[i]);
441
- }
442
- }
443
-
444
- return newLines.join('\n');
445
- }
446
-
447
- /**
448
- * Auto-resolve whitespace conflicts
449
- * @private
450
- */
451
- async autoResolveWhitespace(_content, fileInfo) {
452
- // Remove conflict markers and keep theirs (usually has correct formatting)
453
- let resolved = content;
454
-
455
- for (const _conflict of fileInfo.conflicts) {
456
- const pattern = new RegExp(
457
- `<<<<<<<[^\\n]*\\n[\\s\\S]*?=======\\n([\\s\\S]*?)>>>>>>>[^\\n]*\\n`,
458
- 'g'
459
- );
460
- resolved = resolved.replace(pattern, '$1');
461
- }
462
-
463
- return resolved;
464
- }
465
-
466
- /**
467
- * Auto-resolve import conflicts
468
- * @private
469
- */
470
- async autoResolveImports(_content, fileInfo) {
471
- // Merge imports from both sides, removing duplicates
472
- const imports = new Set();
473
-
474
- for (const _conflict of fileInfo.conflicts) {
475
- // Extract imports from both sides
476
- const oursImports = conflict.ours
477
- .filter(line => line.includes('import') || line.includes('require'))
478
- .map(line => line.trim());
479
-
480
- const theirsImports = conflict.theirs
481
- .filter(line => line.includes('import') || line.includes('require'))
482
- .map(line => line.trim());
483
-
484
- // Add all unique imports
485
- [...oursImports, ...theirsImports].forEach(imp => imports.add(imp));
486
- }
487
-
488
- // Replace conflicts with merged imports
489
- let resolved = content;
490
- for (const _conflict of fileInfo.conflicts) {
491
- const pattern = new RegExp(
492
- `<<<<<<<[^\\n]*\\n[\\s\\S]*?>>>>>>>[^\\n]*\\n`,
493
- 'g'
494
- );
495
- resolved = resolved.replace(pattern, Array.from(imports).join('\n') + '\n');
496
- }
497
-
498
- return resolved;
499
- }
500
-
501
- /**
502
- * Auto-resolve version conflicts
503
- * @private
504
- */
505
- async autoResolveVersion(_content, fileInfo) {
506
- let resolved = content;
507
-
508
- for (const _conflict of fileInfo.conflicts) {
509
- const oursVersion = conflict.ours.join('').match(/(\d+)\.(\d+)\.(\d+)/);
510
- const theirsVersion = conflict.theirs.join('').match(/(\d+)\.(\d+)\.(\d+)/);
511
-
512
- if (oursVersion && theirsVersion) {
513
- // Compare versions and keep higher
514
- const ours = oursVersion.slice(1, 4).map(Number);
515
- const theirs = theirsVersion.slice(1, 4).map(Number);
516
-
517
- let useTheirs = false;
518
- for (let i = 0; i < 3; i++) {
519
- if (theirs[i] > ours[i]) {
520
- useTheirs = true;
521
- break;
522
- } else if (ours[i] > theirs[i]) {
523
- break;
524
- }
525
- }
526
-
527
- const pattern = new RegExp(
528
- `<<<<<<<[^\\n]*\\n[\\s\\S]*?=======\\n([\\s\\S]*?)>>>>>>>[^\\n]*\\n`
529
- );
530
-
531
- if (useTheirs) {
532
- resolved = resolved.replace(pattern, '$1');
533
- } else {
534
- resolved = resolved.replace(pattern, conflict.ours.join('\n') + '\n');
535
- }
536
- }
537
- }
538
-
539
- return resolved;
540
- }
541
-
542
- /**
543
- * Auto-resolve JSON conflicts
544
- * @private
545
- */
546
- async autoResolveJSON(_content, fileInfo) {
547
- try {
548
- // Try to parse and merge JSON objects
549
- const oursMatch = content.match(/<<<<<<<[^{]*({[\s\S]*?})[\s\S]*?=======/);
550
- const theirsMatch = content.match(/=======[\s\S]*?({[\s\S]*?})[\s\S]*?>>>>>>>/);
551
-
552
- if (oursMatch && theirsMatch) {
553
- const oursObj = JSON.parse(oursMatch[1]);
554
- const theirsObj = JSON.parse(theirsMatch[1]);
555
-
556
- // Deep merge objects
557
- const merged = this.deepMerge(oursObj, theirsObj);
558
-
559
- // Replace entire file with merged JSON
560
- return JSON.stringify(merged, null, 2);
561
- }
562
- } catch (error) {
563
- console.error(chalk.red('Failed to auto-resolve JSON:', error.message));
564
- }
565
-
566
- // Fallback to manual resolution
567
- return content;
568
- }
569
-
570
- /**
571
- * Deep merge two objects
572
- * @private
573
- */
574
- deepMerge(obj1, obj2) {
575
- const result = { ...obj1 };
576
-
577
- for (const key in obj2) {
578
- if (obj2.hasOwnProperty(key)) {
579
- if (typeof obj2[key] === 'object' && !Array.isArray(obj2[key]) &&
580
- obj1[key] && typeof obj1[key] === 'object') {
581
- result[key] = this.deepMerge(obj1[key], obj2[key]);
582
- } else {
583
- result[key] = obj2[key];
584
- }
585
- }
586
- }
587
-
588
- return result;
589
- }
590
-
591
- /**
592
- * Generate conflict report
593
- * @returns {Promise<Object>} Conflict report
594
- */
595
- async generateConflictReport() {
596
- const conflictInfo = await this.detectConflicts();
597
-
598
- if (!conflictInfo.hasConflicts) {
599
- return {
600
- summary: 'No conflicts detected',
601
- details: []
602
- };
603
- }
604
-
605
- const report = {
606
- summary: `${conflictInfo.totalConflicts} conflicts in ${conflictInfo.files.length} files`,
607
- timestamp: new Date().toISOString(),
608
- details: conflictInfo.files.map(file => ({
609
- file: file.file,
610
- type: file.type,
611
- conflicts: file.conflictCount,
612
- preview: file.conflicts.map(c => ({
613
- lines: `${c.startLine}-${c.endLine}`,
614
- oursPreview: c.ours.slice(0, 2).join('\n'),
615
- theirsPreview: c.theirs.slice(0, 2).join('\n')
616
- }))
617
- })),
618
- recommendations: this.generateRecommendations(conflictInfo)
619
- };
620
-
621
- return report;
622
- }
623
-
624
- /**
625
- * Generate resolution recommendations
626
- * @private
627
- */
628
- generateRecommendations(conflictInfo) {
629
- const recommendations = [];
630
- const types = {};
631
-
632
- // Count conflict types
633
- conflictInfo.files.forEach(file => {
634
- types[file.type] = (types[file.type] || 0) + file.conflictCount;
635
- });
636
-
637
- // Generate recommendations based on types
638
- if (types.whitespace > 0) {
639
- recommendations.push({
640
- type: 'whitespace',
641
- suggestion: 'Use auto-resolution for whitespace conflicts',
642
- command: "resolver.resolveConflicts('auto', { type: 'whitespace' })"
643
- });
644
- }
645
-
646
- if (types.imports > 0) {
647
- recommendations.push({
648
- type: 'imports',
649
- suggestion: 'Merge import statements from both branches',
650
- command: "resolver.resolveConflicts('auto', { type: 'imports' })"
651
- });
652
- }
653
-
654
- if (types.version > 0) {
655
- recommendations.push({
656
- type: 'version',
657
- suggestion: 'Keep the higher version number',
658
- command: "resolver.resolveConflicts('auto', { type: 'version' })"
659
- });
660
- }
661
-
662
- // General recommendation
663
- if (conflictInfo.totalConflicts > 10) {
664
- recommendations.push({
665
- type: 'general',
666
- suggestion: 'Consider reviewing branch merge strategy',
667
- command: 'Use smaller, more focused branches'
668
- });
669
- }
670
-
671
- return recommendations;
672
- }
673
- }
674
-
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const _diffLib = require('diff');
5
+ const inquirer = require('inquirer');
6
+ const GitWrapper = require('./git-wrapper');
7
+
8
+ /**
9
+ * Handles conflict detection and resolution for meta-agent modifications
10
+ */
11
+ class ConflictResolver {
12
+ constructor(options = {}) {
13
+ this.git = new GitWrapper(options);
14
+ this.rootPath = options.rootPath || process.cwd();
15
+ this.strategies = {
16
+ 'ours': this.resolveOurs.bind(this),
17
+ 'theirs': this.resolveTheirs.bind(this),
18
+ 'manual': this.resolveManual.bind(this),
19
+ 'auto': this.resolveAuto.bind(this),
20
+ 'interactive': this.resolveInteractive.bind(this)
21
+ };
22
+ }
23
+
24
+ /**
25
+ * Detect conflicts in the repository
26
+ * @returns {Promise<Object>} Conflict information
27
+ */
28
+ async detectConflicts() {
29
+ try {
30
+ const conflicts = await this.git.getConflicts();
31
+
32
+ if (conflicts.length === 0) {
33
+ return {
34
+ hasConflicts: false,
35
+ files: []
36
+ };
37
+ }
38
+
39
+ const conflictDetails = [];
40
+ for (const file of conflicts) {
41
+ const content = await fs.readFile(
42
+ path.join(this.rootPath, file),
43
+ 'utf-8'
44
+ );
45
+
46
+ const conflictInfo = this.parseConflictMarkers(_content);
47
+ conflictDetails.push({
48
+ file,
49
+ conflicts: conflictInfo.conflicts,
50
+ conflictCount: conflictInfo.conflicts.length,
51
+ type: this.detectConflictType(file, conflictInfo)
52
+ });
53
+ }
54
+
55
+ return {
56
+ hasConflicts: true,
57
+ files: conflictDetails,
58
+ totalConflicts: conflictDetails.reduce((sum, f) => sum + f.conflictCount, 0)
59
+ };
60
+ } catch (error) {
61
+ console.error(chalk.red(`Error detecting conflicts: ${error.message}`));
62
+ return {
63
+ hasConflicts: false,
64
+ error: error.message
65
+ };
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Parse conflict markers in file content
71
+ * @private
72
+ */
73
+ parseConflictMarkers(_content) {
74
+ const conflicts = [];
75
+ const lines = content.split('\n');
76
+ let inConflict = false;
77
+ let currentConflict = null;
78
+ let lineNumber = 0;
79
+
80
+ for (const line of lines) {
81
+ lineNumber++;
82
+
83
+ if (line.startsWith('<<<<<<<')) {
84
+ inConflict = true;
85
+ currentConflict = {
86
+ startLine: lineNumber,
87
+ ours: [],
88
+ theirs: [],
89
+ separator: null,
90
+ endLine: null,
91
+ branch: line.substring(8).trim()
92
+ };
93
+ } else if (inConflict && line.startsWith('=======')) {
94
+ currentConflict.separator = lineNumber;
95
+ } else if (inConflict && line.startsWith('>>>>>>>')) {
96
+ currentConflict.endLine = lineNumber;
97
+ currentConflict.theirBranch = line.substring(8).trim();
98
+ conflicts.push(currentConflict);
99
+ inConflict = false;
100
+ currentConflict = null;
101
+ } else if (inConflict && currentConflict) {
102
+ if (currentConflict.separator === null) {
103
+ currentConflict.ours.push(line);
104
+ } else {
105
+ currentConflict.theirs.push(line);
106
+ }
107
+ }
108
+ }
109
+
110
+ return { conflicts, totalLines: lineNumber };
111
+ }
112
+
113
+ /**
114
+ * Detect the type of conflict
115
+ * @private
116
+ */
117
+ detectConflictType(file, conflictInfo) {
118
+ const ext = path.extname(file);
119
+ const conflicts = conflictInfo.conflicts;
120
+
121
+ // Check for specific conflict patterns
122
+ for (const _conflict of conflicts) {
123
+ const oursContent = conflict.ours.join('\n');
124
+ const theirsContent = conflict.theirs.join('\n');
125
+
126
+ // Whitespace only conflict
127
+ if (oursContent.trim() === theirsContent.trim()) {
128
+ return 'whitespace';
129
+ }
130
+
131
+ // Import/require conflict
132
+ if ((oursContent.includes('import') || oursContent.includes('require')) &&
133
+ (theirsContent.includes('import') || theirsContent.includes('require'))) {
134
+ return 'imports';
135
+ }
136
+
137
+ // Version number conflict
138
+ if (oursContent.match(/\d+\.\d+\.\d+/) && theirsContent.match(/\d+\.\d+\.\d+/)) {
139
+ return 'version';
140
+ }
141
+ }
142
+
143
+ // File type specific
144
+ if (ext === '.json') return 'json';
145
+ if (ext === '.yaml' || ext === '.yml') return 'yaml';
146
+ if (['.js', '.ts', '.jsx', '.tsx'].includes(ext)) return 'code';
147
+ if (ext === '.md') return 'markdown';
148
+
149
+ return 'general';
150
+ }
151
+
152
+ /**
153
+ * Resolve conflicts using a specific strategy
154
+ * @param {string} strategy - Resolution strategy
155
+ * @param {Object} options - Resolution options
156
+ * @returns {Promise<Object>} Resolution result
157
+ */
158
+ async resolveConflicts(strategy = 'interactive', options = {}) {
159
+ const conflictInfo = await this.detectConflicts();
160
+
161
+ if (!conflictInfo.hasConflicts) {
162
+ console.log(chalk.green('✅ No conflicts detected'));
163
+ return { success: true, resolved: 0 };
164
+ }
165
+
166
+ console.log(chalk.yellow(
167
+ `Found ${conflictInfo.totalConflicts} conflicts in ${conflictInfo.files.length} files`
168
+ ));
169
+
170
+ const resolver = this.strategies[strategy];
171
+ if (!resolver) {
172
+ throw new Error(`Unknown resolution strategy: ${strategy}`);
173
+ }
174
+
175
+ const results = {
176
+ resolved: 0,
177
+ failed: 0,
178
+ files: []
179
+ };
180
+
181
+ for (const fileInfo of conflictInfo.files) {
182
+ try {
183
+ console.log(chalk.blue(`\nResolving conflicts in: ${fileInfo.file}`));
184
+ const resolved = await resolver(_fileInfo, options);
185
+
186
+ if (resolved.success) {
187
+ results.resolved += resolved.conflictsResolved;
188
+ results.files.push({
189
+ file: fileInfo.file,
190
+ status: 'resolved',
191
+ method: resolved.method
192
+ });
193
+ } else {
194
+ results.failed++;
195
+ results.files.push({
196
+ file: fileInfo.file,
197
+ status: 'failed',
198
+ error: resolved.error
199
+ });
200
+ }
201
+ } catch (error) {
202
+ results.failed++;
203
+ results.files.push({
204
+ file: fileInfo.file,
205
+ status: 'error',
206
+ error: error.message
207
+ });
208
+ }
209
+ }
210
+
211
+ return results;
212
+ }
213
+
214
+ /**
215
+ * Resolve using 'ours' strategy (keep current branch changes)
216
+ * @private
217
+ */
218
+ async resolveOurs(_fileInfo) {
219
+ try {
220
+ await this.git.execGit(`checkout --ours "${fileInfo.file}"`);
221
+ await this.git.execGit(`add "${fileInfo.file}"`);
222
+
223
+ return {
224
+ success: true,
225
+ conflictsResolved: fileInfo.conflictCount,
226
+ method: 'ours'
227
+ };
228
+ } catch (error) {
229
+ return {
230
+ success: false,
231
+ error: error.message
232
+ };
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Resolve using 'theirs' strategy (keep incoming branch changes)
238
+ * @private
239
+ */
240
+ async resolveTheirs(_fileInfo) {
241
+ try {
242
+ await this.git.execGit(`checkout --theirs "${fileInfo.file}"`);
243
+ await this.git.execGit(`add "${fileInfo.file}"`);
244
+
245
+ return {
246
+ success: true,
247
+ conflictsResolved: fileInfo.conflictCount,
248
+ method: 'theirs'
249
+ };
250
+ } catch (error) {
251
+ return {
252
+ success: false,
253
+ error: error.message
254
+ };
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Resolve conflicts manually by editing the file
260
+ * @private
261
+ */
262
+ async resolveManual(_fileInfo) {
263
+ const filePath = path.join(this.rootPath, fileInfo.file);
264
+ const content = await fs.readFile(filePath, 'utf-8');
265
+
266
+ console.log(chalk.yellow(
267
+ `Manual resolution required for ${fileInfo.file}`
268
+ ));
269
+ console.log(chalk.gray(
270
+ 'Edit the file to resolve conflicts, then mark as resolved'
271
+ ));
272
+
273
+ // In a real implementation, this would open an editor
274
+ // For now, we'll return a message
275
+ return {
276
+ success: false,
277
+ error: 'Manual resolution required',
278
+ instruction: `Edit ${filePath} and run: git add "${fileInfo.file}"`
279
+ };
280
+ }
281
+
282
+ /**
283
+ * Automatically resolve conflicts based on type
284
+ * @private
285
+ */
286
+ async resolveAuto(_fileInfo) {
287
+ const filePath = path.join(this.rootPath, fileInfo.file);
288
+ const content = await fs.readFile(filePath, 'utf-8');
289
+
290
+ let resolved = content;
291
+ let resolvedCount = 0;
292
+
293
+ switch (fileInfo.type) {
294
+ case 'whitespace':
295
+ // For whitespace conflicts, keep theirs
296
+ resolved = await this.autoResolveWhitespace(_content, fileInfo);
297
+ resolvedCount = fileInfo.conflictCount;
298
+ break;
299
+
300
+ case 'imports':
301
+ // For import conflicts, merge both
302
+ resolved = await this.autoResolveImports(_content, fileInfo);
303
+ resolvedCount = fileInfo.conflictCount;
304
+ break;
305
+
306
+ case 'version':
307
+ // For version conflicts, keep higher version
308
+ resolved = await this.autoResolveVersion(_content, fileInfo);
309
+ resolvedCount = fileInfo.conflictCount;
310
+ break;
311
+
312
+ case 'json':
313
+ // For JSON conflicts, attempt to merge
314
+ resolved = await this.autoResolveJSON(_content, fileInfo);
315
+ resolvedCount = fileInfo.conflictCount;
316
+ break;
317
+
318
+ default:
319
+ // Can't auto-resolve
320
+ return {
321
+ success: false,
322
+ error: `Cannot auto-resolve ${fileInfo.type} conflicts`
323
+ };
324
+ }
325
+
326
+ // Write resolved content
327
+ await fs.writeFile(filePath, resolved);
328
+ await this.git.execGit(`add "${fileInfo.file}"`);
329
+
330
+ return {
331
+ success: true,
332
+ conflictsResolved: resolvedCount,
333
+ method: `auto-${fileInfo.type}`
334
+ };
335
+ }
336
+
337
+ /**
338
+ * Interactive conflict resolution
339
+ * @private
340
+ */
341
+ async resolveInteractive(_fileInfo) {
342
+ const filePath = path.join(this.rootPath, fileInfo.file);
343
+ const content = await fs.readFile(filePath, 'utf-8');
344
+ const conflicts = this.parseConflictMarkers(_content).conflicts;
345
+
346
+ let resolvedContent = content;
347
+ let resolvedCount = 0;
348
+
349
+ console.log(chalk.blue(`\nResolving ${fileInfo.file} (${conflicts.length} conflicts)`));
350
+
351
+ for (let i = 0; i < conflicts.length; i++) {
352
+ const _conflict = conflicts[i];
353
+ console.log(chalk.yellow(`\nConflict ${i + 1}/${conflicts.length}:`));
354
+
355
+ // Show conflict preview
356
+ console.log(chalk.red('<<<< OURS:'));
357
+ console.log(conflict.ours.slice(0, 5).join('\n'));
358
+ if (conflict.ours.length > 5) console.log(chalk.gray('...'));
359
+
360
+ console.log(chalk.green('\n>>>> THEIRS:'));
361
+ console.log(conflict.theirs.slice(0, 5).join('\n'));
362
+ if (conflict.theirs.length > 5) console.log(chalk.gray('...'));
363
+
364
+ const { resolution } = await inquirer.prompt([{
365
+ type: 'list',
366
+ name: 'resolution',
367
+ message: 'How to resolve this conflict?',
368
+ choices: [
369
+ { name: 'Keep ours (current branch)', value: 'ours' },
370
+ { name: 'Keep theirs (incoming)', value: 'theirs' },
371
+ { name: 'Keep both (ours first)', value: 'both-ours' },
372
+ { name: 'Keep both (theirs first)', value: 'both-theirs' },
373
+ { name: 'Custom merge', value: 'custom' },
374
+ { name: 'Skip this conflict', value: 'skip' }
375
+ ]
376
+ }]);
377
+
378
+ if (resolution !== 'skip') {
379
+ resolvedContent = await this.applyResolution(
380
+ resolvedContent,
381
+ _conflict,
382
+ resolution
383
+ );
384
+ resolvedCount++;
385
+ }
386
+ }
387
+
388
+ if (resolvedCount > 0) {
389
+ await fs.writeFile(filePath, resolvedContent);
390
+ await this.git.execGit(`add "${fileInfo.file}"`);
391
+ }
392
+
393
+ return {
394
+ success: true,
395
+ conflictsResolved: resolvedCount,
396
+ method: 'interactive'
397
+ };
398
+ }
399
+
400
+ /**
401
+ * Apply a specific resolution to content
402
+ * @private
403
+ */
404
+ async applyResolution(_content, _conflict, resolution) {
405
+ const lines = content.split('\n');
406
+ let newLines = [];
407
+ let skipUntil = null;
408
+
409
+ for (let i = 0; i < lines.length; i++) {
410
+ if (skipUntil && i < skipUntil) continue;
411
+
412
+ if (i === conflict.startLine - 1) {
413
+ switch (resolution) {
414
+ case 'ours':
415
+ newLines.push(...conflict.ours);
416
+ break;
417
+ case 'theirs':
418
+ newLines.push(...conflict.theirs);
419
+ break;
420
+ case 'both-ours':
421
+ newLines.push(...conflict.ours);
422
+ newLines.push(...conflict.theirs);
423
+ break;
424
+ case 'both-theirs':
425
+ newLines.push(...conflict.theirs);
426
+ newLines.push(...conflict.ours);
427
+ break;
428
+ case 'custom':
429
+ const { custom } = await inquirer.prompt([{
430
+ type: 'editor',
431
+ name: 'custom',
432
+ message: 'Enter custom resolution:',
433
+ default: conflict.ours.join('\n')
434
+ }]);
435
+ newLines.push(...custom.split('\n'));
436
+ break;
437
+ }
438
+ skipUntil = conflict.endLine;
439
+ } else {
440
+ newLines.push(lines[i]);
441
+ }
442
+ }
443
+
444
+ return newLines.join('\n');
445
+ }
446
+
447
+ /**
448
+ * Auto-resolve whitespace conflicts
449
+ * @private
450
+ */
451
+ async autoResolveWhitespace(_content, fileInfo) {
452
+ // Remove conflict markers and keep theirs (usually has correct formatting)
453
+ let resolved = content;
454
+
455
+ for (const _conflict of fileInfo.conflicts) {
456
+ const pattern = new RegExp(
457
+ `<<<<<<<[^\\n]*\\n[\\s\\S]*?=======\\n([\\s\\S]*?)>>>>>>>[^\\n]*\\n`,
458
+ 'g'
459
+ );
460
+ resolved = resolved.replace(pattern, '$1');
461
+ }
462
+
463
+ return resolved;
464
+ }
465
+
466
+ /**
467
+ * Auto-resolve import conflicts
468
+ * @private
469
+ */
470
+ async autoResolveImports(_content, fileInfo) {
471
+ // Merge imports from both sides, removing duplicates
472
+ const imports = new Set();
473
+
474
+ for (const _conflict of fileInfo.conflicts) {
475
+ // Extract imports from both sides
476
+ const oursImports = conflict.ours
477
+ .filter(line => line.includes('import') || line.includes('require'))
478
+ .map(line => line.trim());
479
+
480
+ const theirsImports = conflict.theirs
481
+ .filter(line => line.includes('import') || line.includes('require'))
482
+ .map(line => line.trim());
483
+
484
+ // Add all unique imports
485
+ [...oursImports, ...theirsImports].forEach(imp => imports.add(imp));
486
+ }
487
+
488
+ // Replace conflicts with merged imports
489
+ let resolved = content;
490
+ for (const _conflict of fileInfo.conflicts) {
491
+ const pattern = new RegExp(
492
+ `<<<<<<<[^\\n]*\\n[\\s\\S]*?>>>>>>>[^\\n]*\\n`,
493
+ 'g'
494
+ );
495
+ resolved = resolved.replace(pattern, Array.from(imports).join('\n') + '\n');
496
+ }
497
+
498
+ return resolved;
499
+ }
500
+
501
+ /**
502
+ * Auto-resolve version conflicts
503
+ * @private
504
+ */
505
+ async autoResolveVersion(_content, fileInfo) {
506
+ let resolved = content;
507
+
508
+ for (const _conflict of fileInfo.conflicts) {
509
+ const oursVersion = conflict.ours.join('').match(/(\d+)\.(\d+)\.(\d+)/);
510
+ const theirsVersion = conflict.theirs.join('').match(/(\d+)\.(\d+)\.(\d+)/);
511
+
512
+ if (oursVersion && theirsVersion) {
513
+ // Compare versions and keep higher
514
+ const ours = oursVersion.slice(1, 4).map(Number);
515
+ const theirs = theirsVersion.slice(1, 4).map(Number);
516
+
517
+ let useTheirs = false;
518
+ for (let i = 0; i < 3; i++) {
519
+ if (theirs[i] > ours[i]) {
520
+ useTheirs = true;
521
+ break;
522
+ } else if (ours[i] > theirs[i]) {
523
+ break;
524
+ }
525
+ }
526
+
527
+ const pattern = new RegExp(
528
+ `<<<<<<<[^\\n]*\\n[\\s\\S]*?=======\\n([\\s\\S]*?)>>>>>>>[^\\n]*\\n`
529
+ );
530
+
531
+ if (useTheirs) {
532
+ resolved = resolved.replace(pattern, '$1');
533
+ } else {
534
+ resolved = resolved.replace(pattern, conflict.ours.join('\n') + '\n');
535
+ }
536
+ }
537
+ }
538
+
539
+ return resolved;
540
+ }
541
+
542
+ /**
543
+ * Auto-resolve JSON conflicts
544
+ * @private
545
+ */
546
+ async autoResolveJSON(_content, fileInfo) {
547
+ try {
548
+ // Try to parse and merge JSON objects
549
+ const oursMatch = content.match(/<<<<<<<[^{]*({[\s\S]*?})[\s\S]*?=======/);
550
+ const theirsMatch = content.match(/=======[\s\S]*?({[\s\S]*?})[\s\S]*?>>>>>>>/);
551
+
552
+ if (oursMatch && theirsMatch) {
553
+ const oursObj = JSON.parse(oursMatch[1]);
554
+ const theirsObj = JSON.parse(theirsMatch[1]);
555
+
556
+ // Deep merge objects
557
+ const merged = this.deepMerge(oursObj, theirsObj);
558
+
559
+ // Replace entire file with merged JSON
560
+ return JSON.stringify(merged, null, 2);
561
+ }
562
+ } catch (error) {
563
+ console.error(chalk.red('Failed to auto-resolve JSON:', error.message));
564
+ }
565
+
566
+ // Fallback to manual resolution
567
+ return content;
568
+ }
569
+
570
+ /**
571
+ * Deep merge two objects
572
+ * @private
573
+ */
574
+ deepMerge(obj1, obj2) {
575
+ const result = { ...obj1 };
576
+
577
+ for (const key in obj2) {
578
+ if (obj2.hasOwnProperty(key)) {
579
+ if (typeof obj2[key] === 'object' && !Array.isArray(obj2[key]) &&
580
+ obj1[key] && typeof obj1[key] === 'object') {
581
+ result[key] = this.deepMerge(obj1[key], obj2[key]);
582
+ } else {
583
+ result[key] = obj2[key];
584
+ }
585
+ }
586
+ }
587
+
588
+ return result;
589
+ }
590
+
591
+ /**
592
+ * Generate conflict report
593
+ * @returns {Promise<Object>} Conflict report
594
+ */
595
+ async generateConflictReport() {
596
+ const conflictInfo = await this.detectConflicts();
597
+
598
+ if (!conflictInfo.hasConflicts) {
599
+ return {
600
+ summary: 'No conflicts detected',
601
+ details: []
602
+ };
603
+ }
604
+
605
+ const report = {
606
+ summary: `${conflictInfo.totalConflicts} conflicts in ${conflictInfo.files.length} files`,
607
+ timestamp: new Date().toISOString(),
608
+ details: conflictInfo.files.map(file => ({
609
+ file: file.file,
610
+ type: file.type,
611
+ conflicts: file.conflictCount,
612
+ preview: file.conflicts.map(c => ({
613
+ lines: `${c.startLine}-${c.endLine}`,
614
+ oursPreview: c.ours.slice(0, 2).join('\n'),
615
+ theirsPreview: c.theirs.slice(0, 2).join('\n')
616
+ }))
617
+ })),
618
+ recommendations: this.generateRecommendations(conflictInfo)
619
+ };
620
+
621
+ return report;
622
+ }
623
+
624
+ /**
625
+ * Generate resolution recommendations
626
+ * @private
627
+ */
628
+ generateRecommendations(conflictInfo) {
629
+ const recommendations = [];
630
+ const types = {};
631
+
632
+ // Count conflict types
633
+ conflictInfo.files.forEach(file => {
634
+ types[file.type] = (types[file.type] || 0) + file.conflictCount;
635
+ });
636
+
637
+ // Generate recommendations based on types
638
+ if (types.whitespace > 0) {
639
+ recommendations.push({
640
+ type: 'whitespace',
641
+ suggestion: 'Use auto-resolution for whitespace conflicts',
642
+ command: "resolver.resolveConflicts('auto', { type: 'whitespace' })"
643
+ });
644
+ }
645
+
646
+ if (types.imports > 0) {
647
+ recommendations.push({
648
+ type: 'imports',
649
+ suggestion: 'Merge import statements from both branches',
650
+ command: "resolver.resolveConflicts('auto', { type: 'imports' })"
651
+ });
652
+ }
653
+
654
+ if (types.version > 0) {
655
+ recommendations.push({
656
+ type: 'version',
657
+ suggestion: 'Keep the higher version number',
658
+ command: "resolver.resolveConflicts('auto', { type: 'version' })"
659
+ });
660
+ }
661
+
662
+ // General recommendation
663
+ if (conflictInfo.totalConflicts > 10) {
664
+ recommendations.push({
665
+ type: 'general',
666
+ suggestion: 'Consider reviewing branch merge strategy',
667
+ command: 'Use smaller, more focused branches'
668
+ });
669
+ }
670
+
671
+ return recommendations;
672
+ }
673
+ }
674
+
675
675
  module.exports = ConflictResolver;