aios-core 4.1.0 → 4.2.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 (145) hide show
  1. package/.aios-core/.session/current-session.json +14 -0
  2. package/.aios-core/core/registry/registry-schema.json +166 -166
  3. package/.aios-core/core/registry/service-registry.json +6585 -6585
  4. package/.aios-core/data/entity-registry.yaml +208 -8
  5. package/.aios-core/data/registry-update-log.jsonl +165 -0
  6. package/.aios-core/development/scripts/approval-workflow.js +642 -642
  7. package/.aios-core/development/scripts/backup-manager.js +606 -606
  8. package/.aios-core/development/scripts/branch-manager.js +389 -389
  9. package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
  10. package/.aios-core/development/scripts/commit-message-generator.js +849 -849
  11. package/.aios-core/development/scripts/conflict-resolver.js +674 -674
  12. package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
  13. package/.aios-core/development/scripts/diff-generator.js +351 -351
  14. package/.aios-core/development/scripts/elicitation-engine.js +384 -384
  15. package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
  16. package/.aios-core/development/scripts/git-wrapper.js +461 -461
  17. package/.aios-core/development/scripts/manifest-preview.js +244 -244
  18. package/.aios-core/development/scripts/metrics-tracker.js +775 -775
  19. package/.aios-core/development/scripts/modification-validator.js +554 -554
  20. package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
  21. package/.aios-core/development/scripts/performance-analyzer.js +757 -757
  22. package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
  23. package/.aios-core/development/scripts/rollback-handler.js +530 -530
  24. package/.aios-core/development/scripts/security-checker.js +358 -358
  25. package/.aios-core/development/scripts/template-engine.js +239 -239
  26. package/.aios-core/development/scripts/template-validator.js +278 -278
  27. package/.aios-core/development/scripts/test-generator.js +843 -843
  28. package/.aios-core/development/scripts/transaction-manager.js +589 -589
  29. package/.aios-core/development/scripts/usage-tracker.js +673 -673
  30. package/.aios-core/development/scripts/validate-filenames.js +226 -226
  31. package/.aios-core/development/scripts/version-tracker.js +526 -526
  32. package/.aios-core/development/scripts/yaml-validator.js +396 -396
  33. package/.aios-core/development/tasks/validate-next-story.md +99 -2
  34. package/.aios-core/development/templates/service-template/README.md.hbs +158 -158
  35. package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
  36. package/.aios-core/development/templates/service-template/client.ts.hbs +403 -403
  37. package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -182
  38. package/.aios-core/development/templates/service-template/index.ts.hbs +120 -120
  39. package/.aios-core/development/templates/service-template/package.json.hbs +87 -87
  40. package/.aios-core/development/templates/service-template/types.ts.hbs +145 -145
  41. package/.aios-core/development/templates/squad-template/LICENSE +21 -21
  42. package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +335 -0
  43. package/.aios-core/docs/component-creation-guide.md +458 -0
  44. package/.aios-core/docs/session-update-pattern.md +307 -0
  45. package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +1963 -0
  46. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +1190 -0
  47. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +439 -0
  48. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +5398 -0
  49. package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +523 -0
  50. package/.aios-core/docs/template-syntax.md +267 -0
  51. package/.aios-core/docs/troubleshooting-guide.md +625 -0
  52. package/.aios-core/infrastructure/templates/aios-sync.yaml.template +193 -193
  53. package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
  54. package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
  55. package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
  56. package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
  57. package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
  58. package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
  59. package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
  60. package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
  61. package/.aios-core/infrastructure/tests/utilities-audit-results.json +501 -0
  62. package/.aios-core/install-manifest.yaml +101 -101
  63. package/.aios-core/local-config.yaml.template +70 -70
  64. package/.aios-core/manifests/agents.csv +29 -0
  65. package/.aios-core/manifests/schema/manifest-schema.json +190 -190
  66. package/.aios-core/manifests/tasks.csv +198 -0
  67. package/.aios-core/manifests/workers.csv +204 -0
  68. package/.aios-core/monitor/hooks/lib/__init__.py +1 -1
  69. package/.aios-core/monitor/hooks/lib/enrich.py +58 -58
  70. package/.aios-core/monitor/hooks/lib/send_event.py +47 -47
  71. package/.aios-core/monitor/hooks/notification.py +29 -29
  72. package/.aios-core/monitor/hooks/post_tool_use.py +45 -45
  73. package/.aios-core/monitor/hooks/pre_compact.py +29 -29
  74. package/.aios-core/monitor/hooks/pre_tool_use.py +40 -40
  75. package/.aios-core/monitor/hooks/stop.py +29 -29
  76. package/.aios-core/monitor/hooks/subagent_stop.py +29 -29
  77. package/.aios-core/monitor/hooks/user_prompt_submit.py +38 -38
  78. package/.aios-core/product/templates/adr.hbs +125 -125
  79. package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
  80. package/.aios-core/product/templates/dbdr.hbs +241 -241
  81. package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
  82. package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
  83. package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
  84. package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
  85. package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
  86. package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
  87. package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
  88. package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
  89. package/.aios-core/product/templates/epic.hbs +212 -212
  90. package/.aios-core/product/templates/eslintrc-security.json +32 -32
  91. package/.aios-core/product/templates/github-actions-cd.yml +212 -212
  92. package/.aios-core/product/templates/github-actions-ci.yml +172 -172
  93. package/.aios-core/product/templates/pmdr.hbs +186 -186
  94. package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
  95. package/.aios-core/product/templates/prd.hbs +201 -201
  96. package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
  97. package/.aios-core/product/templates/story.hbs +263 -263
  98. package/.aios-core/product/templates/task.hbs +170 -170
  99. package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
  100. package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
  101. package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
  102. package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
  103. package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
  104. package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
  105. package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
  106. package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
  107. package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
  108. package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
  109. package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
  110. package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
  111. package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
  112. package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
  113. package/.aios-core/product/templates/tmpl-view.sql +177 -177
  114. package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
  115. package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
  116. package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
  117. package/.aios-core/scripts/pm.sh +0 -0
  118. package/.claude/hooks/enforce-architecture-first.py +196 -196
  119. package/.claude/hooks/mind-clone-governance.py +192 -192
  120. package/.claude/hooks/read-protection.py +151 -151
  121. package/.claude/hooks/slug-validation.py +176 -176
  122. package/.claude/hooks/sql-governance.py +182 -182
  123. package/.claude/hooks/write-path-validation.py +194 -194
  124. package/.claude/rules/agent-authority.md +105 -0
  125. package/.claude/rules/coderabbit-integration.md +93 -0
  126. package/.claude/rules/ids-principles.md +112 -0
  127. package/.claude/rules/story-lifecycle.md +139 -0
  128. package/.claude/rules/workflow-execution.md +150 -0
  129. package/LICENSE +48 -48
  130. package/bin/aios-minimal.js +0 -0
  131. package/bin/aios.js +0 -0
  132. package/package.json +1 -1
  133. package/packages/aios-install/bin/aios-install.js +0 -0
  134. package/packages/aios-install/bin/edmcp.js +0 -0
  135. package/packages/aios-pro-cli/bin/aios-pro.js +0 -0
  136. package/packages/installer/src/wizard/pro-setup.js +433 -49
  137. package/scripts/check-markdown-links.py +352 -352
  138. package/scripts/code-intel-health-check.js +343 -0
  139. package/scripts/dashboard-parallel-dev.sh +0 -0
  140. package/scripts/dashboard-parallel-phase3.sh +0 -0
  141. package/scripts/dashboard-parallel-phase4.sh +0 -0
  142. package/scripts/glue/README.md +355 -0
  143. package/scripts/glue/compose-agent-prompt.cjs +362 -0
  144. package/scripts/install-monitor-hooks.sh +0 -0
  145. package/.aios-core/lib/build.json +0 -1
@@ -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;