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.
- package/.aios-core/.session/current-session.json +14 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/registry/service-registry.json +6585 -6585
- package/.aios-core/data/entity-registry.yaml +208 -8
- package/.aios-core/data/registry-update-log.jsonl +165 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/validate-next-story.md +99 -2
- package/.aios-core/development/templates/service-template/README.md.hbs +158 -158
- package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
- package/.aios-core/development/templates/service-template/client.ts.hbs +403 -403
- package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -182
- package/.aios-core/development/templates/service-template/index.ts.hbs +120 -120
- package/.aios-core/development/templates/service-template/package.json.hbs +87 -87
- package/.aios-core/development/templates/service-template/types.ts.hbs +145 -145
- package/.aios-core/development/templates/squad-template/LICENSE +21 -21
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +335 -0
- package/.aios-core/docs/component-creation-guide.md +458 -0
- package/.aios-core/docs/session-update-pattern.md +307 -0
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +1963 -0
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +1190 -0
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +439 -0
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +5398 -0
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +523 -0
- package/.aios-core/docs/template-syntax.md +267 -0
- package/.aios-core/docs/troubleshooting-guide.md +625 -0
- package/.aios-core/infrastructure/templates/aios-sync.yaml.template +193 -193
- package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
- package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
- package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
- package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
- package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
- package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
- package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
- package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +501 -0
- package/.aios-core/install-manifest.yaml +101 -101
- package/.aios-core/local-config.yaml.template +70 -70
- package/.aios-core/manifests/agents.csv +29 -0
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/manifests/tasks.csv +198 -0
- package/.aios-core/manifests/workers.csv +204 -0
- package/.aios-core/monitor/hooks/lib/__init__.py +1 -1
- package/.aios-core/monitor/hooks/lib/enrich.py +58 -58
- package/.aios-core/monitor/hooks/lib/send_event.py +47 -47
- package/.aios-core/monitor/hooks/notification.py +29 -29
- package/.aios-core/monitor/hooks/post_tool_use.py +45 -45
- package/.aios-core/monitor/hooks/pre_compact.py +29 -29
- package/.aios-core/monitor/hooks/pre_tool_use.py +40 -40
- package/.aios-core/monitor/hooks/stop.py +29 -29
- package/.aios-core/monitor/hooks/subagent_stop.py +29 -29
- package/.aios-core/monitor/hooks/user_prompt_submit.py +38 -38
- package/.aios-core/product/templates/adr.hbs +125 -125
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/dbdr.hbs +241 -241
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/epic.hbs +212 -212
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/pmdr.hbs +186 -186
- package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
- package/.aios-core/product/templates/prd.hbs +201 -201
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/story.hbs +263 -263
- package/.aios-core/product/templates/task.hbs +170 -170
- package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
- package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
- package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
- package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
- package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
- package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
- package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
- package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
- package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
- package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
- package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
- package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
- package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
- package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
- package/.aios-core/product/templates/tmpl-view.sql +177 -177
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/.aios-core/scripts/pm.sh +0 -0
- package/.claude/hooks/enforce-architecture-first.py +196 -196
- package/.claude/hooks/mind-clone-governance.py +192 -192
- package/.claude/hooks/read-protection.py +151 -151
- package/.claude/hooks/slug-validation.py +176 -176
- package/.claude/hooks/sql-governance.py +182 -182
- package/.claude/hooks/write-path-validation.py +194 -194
- package/.claude/rules/agent-authority.md +105 -0
- package/.claude/rules/coderabbit-integration.md +93 -0
- package/.claude/rules/ids-principles.md +112 -0
- package/.claude/rules/story-lifecycle.md +139 -0
- package/.claude/rules/workflow-execution.md +150 -0
- package/LICENSE +48 -48
- package/bin/aios-minimal.js +0 -0
- package/bin/aios.js +0 -0
- package/package.json +1 -1
- package/packages/aios-install/bin/aios-install.js +0 -0
- package/packages/aios-install/bin/edmcp.js +0 -0
- package/packages/aios-pro-cli/bin/aios-pro.js +0 -0
- package/packages/installer/src/wizard/pro-setup.js +433 -49
- package/scripts/check-markdown-links.py +352 -352
- package/scripts/code-intel-health-check.js +343 -0
- package/scripts/dashboard-parallel-dev.sh +0 -0
- package/scripts/dashboard-parallel-phase3.sh +0 -0
- package/scripts/dashboard-parallel-phase4.sh +0 -0
- package/scripts/glue/README.md +355 -0
- package/scripts/glue/compose-agent-prompt.cjs +362 -0
- package/scripts/install-monitor-hooks.sh +0 -0
- 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;
|