@ktpartners/dgs-platform 2.6.2
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/LICENSE +38 -0
- package/README.md +851 -0
- package/agents/dgs-codebase-cross-analyzer.md +183 -0
- package/agents/dgs-codebase-mapper.md +782 -0
- package/agents/dgs-codebase-synthesizer.md +156 -0
- package/agents/dgs-debugger.md +1256 -0
- package/agents/dgs-executor.md +550 -0
- package/agents/dgs-integration-checker.md +481 -0
- package/agents/dgs-nyquist-auditor.md +178 -0
- package/agents/dgs-phase-researcher.md +563 -0
- package/agents/dgs-phase-verifier.md +450 -0
- package/agents/dgs-plan-checker.md +708 -0
- package/agents/dgs-planner.md +1324 -0
- package/agents/dgs-project-researcher.md +631 -0
- package/agents/dgs-research-synthesizer.md +249 -0
- package/agents/dgs-roadmapper.md +652 -0
- package/agents/dgs-verifier.md +607 -0
- package/bin/install.js +2073 -0
- package/commands/dgs/add-doc.md +45 -0
- package/commands/dgs/add-idea.md +38 -0
- package/commands/dgs/add-phase.md +43 -0
- package/commands/dgs/add-repo.md +54 -0
- package/commands/dgs/add-tests.md +41 -0
- package/commands/dgs/add-todo.md +47 -0
- package/commands/dgs/approve-spec.md +38 -0
- package/commands/dgs/audit-milestone.md +36 -0
- package/commands/dgs/audit-phase.md +37 -0
- package/commands/dgs/cancel-job.md +23 -0
- package/commands/dgs/capture-principle.md +143 -0
- package/commands/dgs/check-todos.md +45 -0
- package/commands/dgs/cleanup.md +18 -0
- package/commands/dgs/complete-milestone.md +136 -0
- package/commands/dgs/complete-project.md +70 -0
- package/commands/dgs/consolidate-ideas.md +50 -0
- package/commands/dgs/create-milestone-job.md +37 -0
- package/commands/dgs/debug.md +164 -0
- package/commands/dgs/develop-idea.md +53 -0
- package/commands/dgs/discuss-idea.md +41 -0
- package/commands/dgs/discuss-phase.md +83 -0
- package/commands/dgs/execute-phase.md +41 -0
- package/commands/dgs/fast.md +38 -0
- package/commands/dgs/find-related-ideas.md +43 -0
- package/commands/dgs/health.md +28 -0
- package/commands/dgs/help.md +22 -0
- package/commands/dgs/import-spec.md +36 -0
- package/commands/dgs/init-product.md +28 -0
- package/commands/dgs/insert-phase.md +32 -0
- package/commands/dgs/join-discord.md +18 -0
- package/commands/dgs/list-docs.md +40 -0
- package/commands/dgs/list-ideas.md +42 -0
- package/commands/dgs/list-jobs.md +22 -0
- package/commands/dgs/list-phase-assumptions.md +46 -0
- package/commands/dgs/list-projects.md +57 -0
- package/commands/dgs/list-specs.md +40 -0
- package/commands/dgs/map-codebase.md +92 -0
- package/commands/dgs/new-milestone.md +44 -0
- package/commands/dgs/new-project.md +42 -0
- package/commands/dgs/node-repair.md +26 -0
- package/commands/dgs/overlap-check.md +20 -0
- package/commands/dgs/pause-work.md +38 -0
- package/commands/dgs/plan-milestone-gaps.md +34 -0
- package/commands/dgs/plan-phase.md +44 -0
- package/commands/dgs/progress.md +24 -0
- package/commands/dgs/quick.md +41 -0
- package/commands/dgs/reactivate-project.md +70 -0
- package/commands/dgs/reapply-patches.md +110 -0
- package/commands/dgs/refine-spec.md +38 -0
- package/commands/dgs/reject-idea.md +43 -0
- package/commands/dgs/remove-doc.md +44 -0
- package/commands/dgs/remove-phase.md +31 -0
- package/commands/dgs/remove-repo.md +69 -0
- package/commands/dgs/research-idea.md +43 -0
- package/commands/dgs/research-phase.md +189 -0
- package/commands/dgs/restore-idea.md +45 -0
- package/commands/dgs/resume-work.md +40 -0
- package/commands/dgs/rollback-job.md +24 -0
- package/commands/dgs/run-job.md +35 -0
- package/commands/dgs/search.md +40 -0
- package/commands/dgs/set-profile.md +34 -0
- package/commands/dgs/settings.md +38 -0
- package/commands/dgs/switch-project.md +58 -0
- package/commands/dgs/undo-consolidation.md +42 -0
- package/commands/dgs/update-idea.md +44 -0
- package/commands/dgs/update.md +37 -0
- package/commands/dgs/validate-phase.md +35 -0
- package/commands/dgs/verify-work.md +39 -0
- package/commands/dgs/write-spec.md +49 -0
- package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-01-SUMMARY.md +84 -0
- package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-02-SUMMARY.md +86 -0
- package/deliver-great-systems/.planning/phases/10-v1-to-v2-migration-flow/10-01-SUMMARY.md +85 -0
- package/deliver-great-systems/bin/dgs-tools.cjs +1444 -0
- package/deliver-great-systems/bin/lib/auto-test.cjs +1365 -0
- package/deliver-great-systems/bin/lib/commands.cjs +570 -0
- package/deliver-great-systems/bin/lib/config.cjs +417 -0
- package/deliver-great-systems/bin/lib/conflict-agent.cjs +1063 -0
- package/deliver-great-systems/bin/lib/conflict-agent.test.cjs +554 -0
- package/deliver-great-systems/bin/lib/context.cjs +929 -0
- package/deliver-great-systems/bin/lib/context.test.cjs +693 -0
- package/deliver-great-systems/bin/lib/core.cjs +744 -0
- package/deliver-great-systems/bin/lib/core.test.cjs +822 -0
- package/deliver-great-systems/bin/lib/docs.cjs +919 -0
- package/deliver-great-systems/bin/lib/docs.test.cjs +211 -0
- package/deliver-great-systems/bin/lib/execution.cjs +705 -0
- package/deliver-great-systems/bin/lib/execution.test.cjs +1472 -0
- package/deliver-great-systems/bin/lib/frontmatter.cjs +324 -0
- package/deliver-great-systems/bin/lib/ideas.cjs +1406 -0
- package/deliver-great-systems/bin/lib/ideas.test.cjs +1417 -0
- package/deliver-great-systems/bin/lib/identity.cjs +125 -0
- package/deliver-great-systems/bin/lib/init.cjs +1114 -0
- package/deliver-great-systems/bin/lib/init.test.cjs +1271 -0
- package/deliver-great-systems/bin/lib/jobs.cjs +2015 -0
- package/deliver-great-systems/bin/lib/jobs.test.cjs +2619 -0
- package/deliver-great-systems/bin/lib/merge-conflicts.cjs +654 -0
- package/deliver-great-systems/bin/lib/merge-conflicts.test.cjs +370 -0
- package/deliver-great-systems/bin/lib/migration.cjs +352 -0
- package/deliver-great-systems/bin/lib/migration.test.cjs +582 -0
- package/deliver-great-systems/bin/lib/milestone.cjs +243 -0
- package/deliver-great-systems/bin/lib/overlap.cjs +437 -0
- package/deliver-great-systems/bin/lib/overlap.test.cjs +747 -0
- package/deliver-great-systems/bin/lib/path-audit.test.cjs +384 -0
- package/deliver-great-systems/bin/lib/paths.cjs +144 -0
- package/deliver-great-systems/bin/lib/paths.test.cjs +486 -0
- package/deliver-great-systems/bin/lib/phase.cjs +910 -0
- package/deliver-great-systems/bin/lib/projects.cjs +691 -0
- package/deliver-great-systems/bin/lib/projects.test.cjs +871 -0
- package/deliver-great-systems/bin/lib/repos.cjs +1432 -0
- package/deliver-great-systems/bin/lib/repos.test.cjs +1882 -0
- package/deliver-great-systems/bin/lib/roadmap.cjs +305 -0
- package/deliver-great-systems/bin/lib/search.cjs +570 -0
- package/deliver-great-systems/bin/lib/specs.cjs +1303 -0
- package/deliver-great-systems/bin/lib/state.cjs +893 -0
- package/deliver-great-systems/bin/lib/template.cjs +228 -0
- package/deliver-great-systems/bin/lib/test-helpers.cjs +291 -0
- package/deliver-great-systems/bin/lib/verify.cjs +796 -0
- package/deliver-great-systems/references/checkpoints.md +776 -0
- package/deliver-great-systems/references/conflict-resolution.md +66 -0
- package/deliver-great-systems/references/context-tiers.md +166 -0
- package/deliver-great-systems/references/continuation-format.md +249 -0
- package/deliver-great-systems/references/decimal-phase-calculation.md +67 -0
- package/deliver-great-systems/references/git-integration.md +250 -0
- package/deliver-great-systems/references/git-planning-commit.md +40 -0
- package/deliver-great-systems/references/model-profile-resolution.md +36 -0
- package/deliver-great-systems/references/model-profiles.md +95 -0
- package/deliver-great-systems/references/phase-argument-parsing.md +61 -0
- package/deliver-great-systems/references/planning-config.md +224 -0
- package/deliver-great-systems/references/questioning.md +162 -0
- package/deliver-great-systems/references/spec-review-loop.md +177 -0
- package/deliver-great-systems/references/tdd.md +265 -0
- package/deliver-great-systems/references/ui-brand.md +160 -0
- package/deliver-great-systems/references/verification-patterns.md +612 -0
- package/deliver-great-systems/templates/DEBUG.md +166 -0
- package/deliver-great-systems/templates/UAT.md +251 -0
- package/deliver-great-systems/templates/VALIDATION.md +95 -0
- package/deliver-great-systems/templates/claude-md.md +74 -0
- package/deliver-great-systems/templates/codebase/architecture.md +257 -0
- package/deliver-great-systems/templates/codebase/concerns.md +312 -0
- package/deliver-great-systems/templates/codebase/conventions.md +309 -0
- package/deliver-great-systems/templates/codebase/integrations.md +282 -0
- package/deliver-great-systems/templates/codebase/stack.md +188 -0
- package/deliver-great-systems/templates/codebase/structure.md +287 -0
- package/deliver-great-systems/templates/codebase/testing.md +482 -0
- package/deliver-great-systems/templates/config.json +38 -0
- package/deliver-great-systems/templates/context.md +354 -0
- package/deliver-great-systems/templates/continue-here.md +80 -0
- package/deliver-great-systems/templates/debug-subagent-prompt.md +93 -0
- package/deliver-great-systems/templates/discovery.md +148 -0
- package/deliver-great-systems/templates/milestone-archive.md +125 -0
- package/deliver-great-systems/templates/milestone.md +117 -0
- package/deliver-great-systems/templates/phase-prompt.md +615 -0
- package/deliver-great-systems/templates/planner-subagent-prompt.md +119 -0
- package/deliver-great-systems/templates/project.md +186 -0
- package/deliver-great-systems/templates/requirements.md +233 -0
- package/deliver-great-systems/templates/research-project/ARCHITECTURE.md +206 -0
- package/deliver-great-systems/templates/research-project/FEATURES.md +149 -0
- package/deliver-great-systems/templates/research-project/PITFALLS.md +202 -0
- package/deliver-great-systems/templates/research-project/STACK.md +122 -0
- package/deliver-great-systems/templates/research-project/SUMMARY.md +172 -0
- package/deliver-great-systems/templates/research.md +554 -0
- package/deliver-great-systems/templates/retrospective.md +54 -0
- package/deliver-great-systems/templates/roadmap.md +204 -0
- package/deliver-great-systems/templates/state.md +178 -0
- package/deliver-great-systems/templates/summary-complex.md +59 -0
- package/deliver-great-systems/templates/summary-minimal.md +41 -0
- package/deliver-great-systems/templates/summary-standard.md +48 -0
- package/deliver-great-systems/templates/summary.md +253 -0
- package/deliver-great-systems/templates/user-setup.md +313 -0
- package/deliver-great-systems/templates/verification-report.md +324 -0
- package/deliver-great-systems/workflows/add-doc.md +151 -0
- package/deliver-great-systems/workflows/add-idea.md +96 -0
- package/deliver-great-systems/workflows/add-phase.md +120 -0
- package/deliver-great-systems/workflows/add-tests.md +359 -0
- package/deliver-great-systems/workflows/add-todo.md +162 -0
- package/deliver-great-systems/workflows/approve-spec.md +194 -0
- package/deliver-great-systems/workflows/audit-milestone.md +364 -0
- package/deliver-great-systems/workflows/audit-phase.md +462 -0
- package/deliver-great-systems/workflows/cancel-job.md +108 -0
- package/deliver-great-systems/workflows/check-todos.md +181 -0
- package/deliver-great-systems/workflows/cleanup.md +247 -0
- package/deliver-great-systems/workflows/codereview.md +526 -0
- package/deliver-great-systems/workflows/complete-milestone.md +1298 -0
- package/deliver-great-systems/workflows/consolidate-ideas.md +365 -0
- package/deliver-great-systems/workflows/create-milestone-job.md +177 -0
- package/deliver-great-systems/workflows/develop-idea.md +544 -0
- package/deliver-great-systems/workflows/diagnose-issues.md +231 -0
- package/deliver-great-systems/workflows/discovery-phase.md +301 -0
- package/deliver-great-systems/workflows/discuss-idea.md +263 -0
- package/deliver-great-systems/workflows/discuss-phase.md +733 -0
- package/deliver-great-systems/workflows/execute-phase.md +571 -0
- package/deliver-great-systems/workflows/execute-plan.md +592 -0
- package/deliver-great-systems/workflows/find-related-ideas.md +271 -0
- package/deliver-great-systems/workflows/health.md +173 -0
- package/deliver-great-systems/workflows/help.md +997 -0
- package/deliver-great-systems/workflows/import-spec.md +381 -0
- package/deliver-great-systems/workflows/init-product.md +767 -0
- package/deliver-great-systems/workflows/insert-phase.md +138 -0
- package/deliver-great-systems/workflows/list-docs.md +119 -0
- package/deliver-great-systems/workflows/list-ideas.md +154 -0
- package/deliver-great-systems/workflows/list-jobs.md +89 -0
- package/deliver-great-systems/workflows/list-phase-assumptions.md +192 -0
- package/deliver-great-systems/workflows/list-specs.md +101 -0
- package/deliver-great-systems/workflows/map-codebase.md +621 -0
- package/deliver-great-systems/workflows/new-milestone.md +591 -0
- package/deliver-great-systems/workflows/new-project.md +1113 -0
- package/deliver-great-systems/workflows/node-repair.md +94 -0
- package/deliver-great-systems/workflows/overlap-check.md +86 -0
- package/deliver-great-systems/workflows/pause-work.md +134 -0
- package/deliver-great-systems/workflows/plan-milestone-gaps.md +306 -0
- package/deliver-great-systems/workflows/plan-phase.md +698 -0
- package/deliver-great-systems/workflows/progress.md +386 -0
- package/deliver-great-systems/workflows/quick.md +845 -0
- package/deliver-great-systems/workflows/refine-spec.md +275 -0
- package/deliver-great-systems/workflows/reject-idea.md +109 -0
- package/deliver-great-systems/workflows/remove-doc.md +117 -0
- package/deliver-great-systems/workflows/remove-phase.md +163 -0
- package/deliver-great-systems/workflows/research-idea.md +325 -0
- package/deliver-great-systems/workflows/research-phase.md +81 -0
- package/deliver-great-systems/workflows/restore-idea.md +101 -0
- package/deliver-great-systems/workflows/resume-project.md +311 -0
- package/deliver-great-systems/workflows/rollback-job.md +130 -0
- package/deliver-great-systems/workflows/run-job.md +498 -0
- package/deliver-great-systems/workflows/search.md +130 -0
- package/deliver-great-systems/workflows/set-profile.md +83 -0
- package/deliver-great-systems/workflows/settings.md +470 -0
- package/deliver-great-systems/workflows/transition.md +563 -0
- package/deliver-great-systems/workflows/undo-consolidation.md +155 -0
- package/deliver-great-systems/workflows/update-idea.md +157 -0
- package/deliver-great-systems/workflows/update.md +242 -0
- package/deliver-great-systems/workflows/validate-phase.md +177 -0
- package/deliver-great-systems/workflows/verify-phase.md +253 -0
- package/deliver-great-systems/workflows/verify-work.md +671 -0
- package/deliver-great-systems/workflows/write-spec.md +450 -0
- package/hooks/dist/dgs-check-update.js +62 -0
- package/hooks/dist/dgs-context-monitor.js +141 -0
- package/hooks/dist/dgs-statusline.js +115 -0
- package/package.json +60 -0
- package/scripts/build-hooks.js +43 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge Conflicts — Conflict detection, context assembly, classification, and resolution recording
|
|
3
|
+
*
|
|
4
|
+
* Provides CLI commands for the merge-conflicts subcommand group:
|
|
5
|
+
* - detect: parse failed merge state, return conflicted files grouped by repo
|
|
6
|
+
* - context: assemble resolution context for a specific file
|
|
7
|
+
* - resolved: record resolution outcome to RESOLUTIONS.md
|
|
8
|
+
* - summary: aggregate all resolutions into a merge-level report
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { execGit, safeReadFile, loadConfig, output, error } = require('./core.cjs');
|
|
14
|
+
const { parseReposMd } = require('./repos.cjs');
|
|
15
|
+
const { extractFrontmatter } = require('./frontmatter.cjs');
|
|
16
|
+
|
|
17
|
+
// ─── Conflict Hunk Parsing ──────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse conflict hunks from file content containing merge conflict markers.
|
|
21
|
+
* Each hunk is bounded by <<<<<<< and >>>>>>>, with ======= separating ours/theirs.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} content - File content with conflict markers
|
|
24
|
+
* @returns {Array<{start_line: number, end_line: number, ours: string, theirs: string}>}
|
|
25
|
+
*/
|
|
26
|
+
function parseConflictHunks(content) {
|
|
27
|
+
const lines = content.split('\n');
|
|
28
|
+
const hunks = [];
|
|
29
|
+
let inConflict = false;
|
|
30
|
+
let inOurs = false;
|
|
31
|
+
let startLine = 0;
|
|
32
|
+
let oursLines = [];
|
|
33
|
+
let theirsLines = [];
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
const line = lines[i];
|
|
37
|
+
if (/^<{7}/.test(line)) {
|
|
38
|
+
inConflict = true;
|
|
39
|
+
inOurs = true;
|
|
40
|
+
startLine = i + 1; // 1-based
|
|
41
|
+
oursLines = [];
|
|
42
|
+
theirsLines = [];
|
|
43
|
+
} else if (inConflict && /^={7}/.test(line)) {
|
|
44
|
+
inOurs = false;
|
|
45
|
+
} else if (inConflict && /^>{7}/.test(line)) {
|
|
46
|
+
hunks.push({
|
|
47
|
+
start_line: startLine,
|
|
48
|
+
end_line: i + 1, // 1-based
|
|
49
|
+
ours: oursLines.join('\n'),
|
|
50
|
+
theirs: theirsLines.join('\n'),
|
|
51
|
+
});
|
|
52
|
+
inConflict = false;
|
|
53
|
+
inOurs = false;
|
|
54
|
+
} else if (inConflict) {
|
|
55
|
+
if (inOurs) {
|
|
56
|
+
oursLines.push(line);
|
|
57
|
+
} else {
|
|
58
|
+
theirsLines.push(line);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return hunks;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Classification Logic ───────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Structural pattern detection for code lines.
|
|
70
|
+
* Matches function declarations, class declarations, imports, exports,
|
|
71
|
+
* require statements, and scope-defining punctuation.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} line - Single line of code
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
function isStructuralLine(line) {
|
|
77
|
+
const trimmed = line.trim();
|
|
78
|
+
if (!trimmed) return false;
|
|
79
|
+
|
|
80
|
+
const structuralPatterns = [
|
|
81
|
+
/^\s*function\s+\w+/,
|
|
82
|
+
/^\s*class\s+\w+/,
|
|
83
|
+
/^\s*module\.exports/,
|
|
84
|
+
/^\s*require\s*\(/,
|
|
85
|
+
/^\s*import\s+/,
|
|
86
|
+
/^\s*export\s+/,
|
|
87
|
+
/^\s*const\s+\w+\s*=\s*require\s*\(/,
|
|
88
|
+
/^\s*[{}()[\]]\s*$/,
|
|
89
|
+
/^\s*[{}()[\]],?\s*$/,
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
return structuralPatterns.some(p => p.test(line));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if every non-empty line of the shorter side appears in the longer side.
|
|
97
|
+
*
|
|
98
|
+
* @param {string[]} shorter - Lines from shorter side
|
|
99
|
+
* @param {string[]} longer - Lines from longer side
|
|
100
|
+
* @returns {boolean}
|
|
101
|
+
*/
|
|
102
|
+
function isSupersetOf(shorter, longer) {
|
|
103
|
+
const longerSet = new Set(longer.map(l => l.trim()).filter(l => l));
|
|
104
|
+
return shorter.filter(l => l.trim()).every(l => longerSet.has(l.trim()));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Classify conflict hunks from a file's content.
|
|
109
|
+
*
|
|
110
|
+
* Pure function (no I/O) that takes a file's content (with conflict markers)
|
|
111
|
+
* and optional plan context (task descriptions that touched this file),
|
|
112
|
+
* and returns classified hunks.
|
|
113
|
+
*
|
|
114
|
+
* Classification types:
|
|
115
|
+
* - ADDITIVE: One side adds new code the other doesn't have
|
|
116
|
+
* - DELETION: One side removes lines the other keeps
|
|
117
|
+
* - DIVERGENT: Both sides modify the same lines differently
|
|
118
|
+
* - STRUCTURAL: Changes to function signatures, imports, exports, scope
|
|
119
|
+
*
|
|
120
|
+
* @param {string} fileContent - File content with conflict markers
|
|
121
|
+
* @param {Array<{task_name: string, action_excerpt: string}>|null} planContext - Optional plan context
|
|
122
|
+
* @returns {Array<{start_line: number, end_line: number, ours: string, theirs: string, type: string, confidence: string, ambiguous: boolean, reasoning: string}>}
|
|
123
|
+
*/
|
|
124
|
+
function classifyConflictHunks(fileContent, planContext) {
|
|
125
|
+
const hunks = parseConflictHunks(fileContent);
|
|
126
|
+
|
|
127
|
+
return hunks.map(hunk => {
|
|
128
|
+
const oursLines = hunk.ours.split('\n').filter(l => l.trim());
|
|
129
|
+
const theirsLines = hunk.theirs.split('\n').filter(l => l.trim());
|
|
130
|
+
const oursEmpty = oursLines.length === 0;
|
|
131
|
+
const theirsEmpty = theirsLines.length === 0;
|
|
132
|
+
|
|
133
|
+
let type = 'DIVERGENT';
|
|
134
|
+
let confidence = 'LOW';
|
|
135
|
+
let ambiguous = false;
|
|
136
|
+
let reasoning = '';
|
|
137
|
+
|
|
138
|
+
// Check STRUCTURAL first — both sides have structural patterns
|
|
139
|
+
const oursStructural = oursLines.filter(isStructuralLine);
|
|
140
|
+
const theirsStructural = theirsLines.filter(isStructuralLine);
|
|
141
|
+
|
|
142
|
+
if (oursStructural.length > 0 && theirsStructural.length > 0) {
|
|
143
|
+
type = 'STRUCTURAL';
|
|
144
|
+
confidence = 'HIGH';
|
|
145
|
+
reasoning = 'Both sides modify structural elements (functions, imports, exports, or scope)';
|
|
146
|
+
} else if (oursStructural.length > 0 || theirsStructural.length > 0) {
|
|
147
|
+
// Only one side has structural changes
|
|
148
|
+
const structSide = oursStructural.length > 0 ? 'ours' : 'theirs';
|
|
149
|
+
type = 'STRUCTURAL';
|
|
150
|
+
confidence = 'MEDIUM';
|
|
151
|
+
reasoning = `Structural patterns detected on ${structSide} side`;
|
|
152
|
+
}
|
|
153
|
+
// Check ADDITIVE
|
|
154
|
+
else if (oursEmpty || theirsEmpty) {
|
|
155
|
+
if (oursEmpty && !theirsEmpty) {
|
|
156
|
+
type = 'ADDITIVE';
|
|
157
|
+
confidence = 'HIGH';
|
|
158
|
+
reasoning = 'Ours side is empty — theirs adds new content';
|
|
159
|
+
} else if (theirsEmpty && !oursEmpty) {
|
|
160
|
+
// Could be DELETION (theirs removes content) or ADDITIVE (ours adds content)
|
|
161
|
+
// Without merge-base context, classify as DELETION
|
|
162
|
+
type = 'DELETION';
|
|
163
|
+
confidence = 'HIGH';
|
|
164
|
+
reasoning = 'Theirs side is empty — content was removed on theirs branch';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Check superset patterns
|
|
168
|
+
else if (isSupersetOf(oursLines, theirsLines)) {
|
|
169
|
+
type = 'ADDITIVE';
|
|
170
|
+
confidence = 'MEDIUM';
|
|
171
|
+
reasoning = 'Theirs is a superset of ours — theirs adds additional content';
|
|
172
|
+
} else if (isSupersetOf(theirsLines, oursLines)) {
|
|
173
|
+
type = 'ADDITIVE';
|
|
174
|
+
confidence = 'MEDIUM';
|
|
175
|
+
reasoning = 'Ours is a superset of theirs — ours adds additional content';
|
|
176
|
+
}
|
|
177
|
+
// Default: DIVERGENT
|
|
178
|
+
else {
|
|
179
|
+
type = 'DIVERGENT';
|
|
180
|
+
confidence = 'LOW';
|
|
181
|
+
reasoning = 'Both sides have content, neither is a superset of the other';
|
|
182
|
+
ambiguous = true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Enhance classification with plan context if available
|
|
186
|
+
if (planContext && Array.isArray(planContext) && planContext.length > 0) {
|
|
187
|
+
const contextText = planContext.map(c => (c.task_name || '') + ' ' + (c.action_excerpt || '')).join(' ').toLowerCase();
|
|
188
|
+
|
|
189
|
+
if (type === 'ADDITIVE' && (contextText.includes('add') || contextText.includes('new') || contextText.includes('create'))) {
|
|
190
|
+
confidence = 'HIGH';
|
|
191
|
+
reasoning += '. Plan context confirms additive intent';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (type === 'DIVERGENT' && contextText.length > 0) {
|
|
195
|
+
confidence = 'MEDIUM';
|
|
196
|
+
reasoning += '. Plan context available for both changes';
|
|
197
|
+
ambiguous = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
start_line: hunk.start_line,
|
|
203
|
+
end_line: hunk.end_line,
|
|
204
|
+
ours: hunk.ours,
|
|
205
|
+
theirs: hunk.theirs,
|
|
206
|
+
type,
|
|
207
|
+
confidence,
|
|
208
|
+
ambiguous,
|
|
209
|
+
reasoning,
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Conflict Detection ─────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Detect conflicted files after a failed merge.
|
|
218
|
+
* Parses git merge state and returns conflicted files grouped by repo
|
|
219
|
+
* with rich metadata.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} cwd - Working directory
|
|
222
|
+
* @param {string|null} phaseDir - Optional phase directory for context
|
|
223
|
+
* @param {boolean} raw - Raw output mode
|
|
224
|
+
*/
|
|
225
|
+
function cmdMergeConflictsDetect(cwd, phaseDir, raw) {
|
|
226
|
+
const timestamp = new Date().toISOString();
|
|
227
|
+
|
|
228
|
+
// Get conflicted files
|
|
229
|
+
const diffResult = execGit(cwd, ['diff', '--name-only', '--diff-filter=U']);
|
|
230
|
+
const conflictedFiles = diffResult.exitCode === 0 && diffResult.stdout
|
|
231
|
+
? diffResult.stdout.split('\n').filter(f => f.trim())
|
|
232
|
+
: [];
|
|
233
|
+
|
|
234
|
+
if (conflictedFiles.length === 0) {
|
|
235
|
+
output({
|
|
236
|
+
conflicted: false,
|
|
237
|
+
files: [],
|
|
238
|
+
repos: {},
|
|
239
|
+
metadata: {
|
|
240
|
+
timestamp,
|
|
241
|
+
branch: null,
|
|
242
|
+
merge_head: null,
|
|
243
|
+
head_sha: null,
|
|
244
|
+
merge_base: null,
|
|
245
|
+
command_version: '1.0',
|
|
246
|
+
},
|
|
247
|
+
}, raw);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Get metadata
|
|
252
|
+
const branchResult = execGit(cwd, ['branch', '--show-current']);
|
|
253
|
+
const branch = branchResult.exitCode === 0 ? branchResult.stdout : null;
|
|
254
|
+
|
|
255
|
+
const mergeHeadResult = execGit(cwd, ['rev-parse', 'MERGE_HEAD']);
|
|
256
|
+
const mergeHead = mergeHeadResult.exitCode === 0 ? mergeHeadResult.stdout : null;
|
|
257
|
+
|
|
258
|
+
const headResult = execGit(cwd, ['rev-parse', 'HEAD']);
|
|
259
|
+
const headSha = headResult.exitCode === 0 ? headResult.stdout : null;
|
|
260
|
+
|
|
261
|
+
let mergeBase = null;
|
|
262
|
+
if (mergeHead) {
|
|
263
|
+
const baseResult = execGit(cwd, ['merge-base', 'HEAD', 'MERGE_HEAD']);
|
|
264
|
+
mergeBase = baseResult.exitCode === 0 ? baseResult.stdout : null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Parse REPOS.md for grouping
|
|
268
|
+
const reposData = parseReposMd(cwd);
|
|
269
|
+
const repos = reposData ? reposData.repos : [];
|
|
270
|
+
|
|
271
|
+
// Group files by repo
|
|
272
|
+
const grouped = {};
|
|
273
|
+
|
|
274
|
+
for (const filePath of conflictedFiles) {
|
|
275
|
+
let repoName = '_product';
|
|
276
|
+
|
|
277
|
+
// Check which repo owns this file by prefix matching
|
|
278
|
+
for (const repo of repos) {
|
|
279
|
+
const repoPath = repo.path.replace(/^\.\//, '').replace(/^\.\.\//, '');
|
|
280
|
+
if (filePath.startsWith(repoPath + '/')) {
|
|
281
|
+
repoName = repo.name;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (!grouped[repoName]) {
|
|
287
|
+
grouped[repoName] = { files: [], count: 0 };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Count conflict markers in the file
|
|
291
|
+
let conflictMarkers = 0;
|
|
292
|
+
const fullPath = path.join(cwd, filePath);
|
|
293
|
+
const content = safeReadFile(fullPath);
|
|
294
|
+
if (content) {
|
|
295
|
+
const lines = content.split('\n');
|
|
296
|
+
conflictMarkers = lines.filter(l => /^<{7}/.test(l)).length;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Determine path relative to repo
|
|
300
|
+
let relPath = filePath;
|
|
301
|
+
if (repoName !== '_product') {
|
|
302
|
+
const repo = repos.find(r => r.name === repoName);
|
|
303
|
+
if (repo) {
|
|
304
|
+
const repoPath = repo.path.replace(/^\.\//, '').replace(/^\.\.\//, '');
|
|
305
|
+
relPath = filePath.slice(repoPath.length + 1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
grouped[repoName].files.push({
|
|
310
|
+
path: relPath,
|
|
311
|
+
full_path: filePath,
|
|
312
|
+
conflict_markers: conflictMarkers,
|
|
313
|
+
});
|
|
314
|
+
grouped[repoName].count++;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
output({
|
|
318
|
+
conflicted: true,
|
|
319
|
+
file_count: conflictedFiles.length,
|
|
320
|
+
repos: grouped,
|
|
321
|
+
metadata: {
|
|
322
|
+
timestamp,
|
|
323
|
+
branch,
|
|
324
|
+
merge_head: mergeHead,
|
|
325
|
+
head_sha: headSha,
|
|
326
|
+
merge_base: mergeBase,
|
|
327
|
+
command_version: '1.0',
|
|
328
|
+
},
|
|
329
|
+
}, raw);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ─── Context Assembly ───────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Assemble resolution context for a specific conflicted file.
|
|
336
|
+
* Gathers conflict hunks, plan task matching, SUMMARY context, and branch identification.
|
|
337
|
+
*
|
|
338
|
+
* @param {string} cwd - Working directory
|
|
339
|
+
* @param {string} filePath - Path to conflicted file
|
|
340
|
+
* @param {string|null} phaseDir - Phase directory for plan/summary context
|
|
341
|
+
* @param {boolean} raw - Raw output mode
|
|
342
|
+
*/
|
|
343
|
+
function cmdMergeConflictsContext(cwd, filePath, phaseDir, raw) {
|
|
344
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
|
|
345
|
+
|
|
346
|
+
// Validate file exists and has conflict markers
|
|
347
|
+
const fileContent = safeReadFile(fullPath);
|
|
348
|
+
if (!fileContent) {
|
|
349
|
+
error('File not found: ' + filePath);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (!/^<{7}/m.test(fileContent)) {
|
|
353
|
+
error('File has no conflict markers: ' + filePath);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Plan task matching
|
|
357
|
+
let planContext = [];
|
|
358
|
+
if (phaseDir) {
|
|
359
|
+
const absPhaseDir = path.isAbsolute(phaseDir) ? phaseDir : path.join(cwd, phaseDir);
|
|
360
|
+
try {
|
|
361
|
+
const phaseFiles = fs.readdirSync(absPhaseDir);
|
|
362
|
+
const planFiles = phaseFiles.filter(f => f.match(/\d+-PLAN\.md$/i) || f === 'PLAN.md');
|
|
363
|
+
|
|
364
|
+
for (const planFile of planFiles) {
|
|
365
|
+
const planContent = safeReadFile(path.join(absPhaseDir, planFile));
|
|
366
|
+
if (!planContent) continue;
|
|
367
|
+
|
|
368
|
+
const fm = extractFrontmatter(planContent);
|
|
369
|
+
const planNumber = fm.plan || planFile.replace(/-PLAN\.md$/i, '');
|
|
370
|
+
|
|
371
|
+
// Search for <files> tags in tasks and match against the conflicted file
|
|
372
|
+
const taskPattern = /<task\b[^>]*>([\s\S]*?)<\/task>/g;
|
|
373
|
+
let match;
|
|
374
|
+
while ((match = taskPattern.exec(planContent)) !== null) {
|
|
375
|
+
const taskBody = match[1];
|
|
376
|
+
const nameMatch = taskBody.match(/<name>([\s\S]*?)<\/name>/);
|
|
377
|
+
const filesMatch = taskBody.match(/<files>([\s\S]*?)<\/files>/);
|
|
378
|
+
const actionMatch = taskBody.match(/<action>([\s\S]*?)<\/action>/);
|
|
379
|
+
|
|
380
|
+
if (filesMatch) {
|
|
381
|
+
const filesList = filesMatch[1].split(/[\n,]/).map(f => f.trim()).filter(f => f);
|
|
382
|
+
const normalizedFilePath = filePath.replace(/^\.\//, '');
|
|
383
|
+
|
|
384
|
+
if (filesList.some(f => normalizedFilePath.includes(f.replace(/^\.\//, '')) || f.replace(/^\.\//, '').includes(normalizedFilePath))) {
|
|
385
|
+
const taskName = nameMatch ? nameMatch[1].trim() : 'unnamed';
|
|
386
|
+
const actionExcerpt = actionMatch ? actionMatch[1].trim().slice(0, 200) : '';
|
|
387
|
+
planContext.push({
|
|
388
|
+
plan: planNumber,
|
|
389
|
+
task_name: taskName,
|
|
390
|
+
action_excerpt: actionExcerpt,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
// Phase dir not readable — fall through to git fallback
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Fallback: git log commit matching
|
|
401
|
+
if (planContext.length === 0) {
|
|
402
|
+
const logResult = execGit(cwd, ['log', '--oneline', '--follow', '-10', '--', filePath]);
|
|
403
|
+
if (logResult.exitCode === 0 && logResult.stdout) {
|
|
404
|
+
const commits = logResult.stdout.split('\n').filter(l => l.trim());
|
|
405
|
+
for (const commit of commits) {
|
|
406
|
+
const planMatch = commit.match(/(?:feat|fix|test|refactor|chore)\((\d+-\d+)\)/);
|
|
407
|
+
if (planMatch) {
|
|
408
|
+
planContext.push({
|
|
409
|
+
plan: planMatch[1],
|
|
410
|
+
task_name: commit.replace(/^[a-f0-9]+\s+/, ''),
|
|
411
|
+
action_excerpt: '',
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Classify hunks
|
|
420
|
+
const hunks = classifyConflictHunks(fileContent, planContext.length > 0 ? planContext : null);
|
|
421
|
+
|
|
422
|
+
// SUMMARY.md context
|
|
423
|
+
let summaryContext = [];
|
|
424
|
+
if (phaseDir) {
|
|
425
|
+
const absPhaseDir = path.isAbsolute(phaseDir) ? phaseDir : path.join(cwd, phaseDir);
|
|
426
|
+
try {
|
|
427
|
+
const phaseFiles = fs.readdirSync(absPhaseDir);
|
|
428
|
+
const summaryFiles = phaseFiles.filter(f => f.match(/\d+-SUMMARY\.md$/i) || f === 'SUMMARY.md');
|
|
429
|
+
|
|
430
|
+
for (const summaryFile of summaryFiles) {
|
|
431
|
+
const summaryContent = safeReadFile(path.join(absPhaseDir, summaryFile));
|
|
432
|
+
if (!summaryContent) continue;
|
|
433
|
+
|
|
434
|
+
const fm = extractFrontmatter(summaryContent);
|
|
435
|
+
|
|
436
|
+
// Check if key-files mention this file
|
|
437
|
+
const keyFiles = fm['key-files'] || {};
|
|
438
|
+
const created = keyFiles.created || [];
|
|
439
|
+
const modified = keyFiles.modified || [];
|
|
440
|
+
const allKeyFiles = [...(Array.isArray(created) ? created : [created]), ...(Array.isArray(modified) ? modified : [modified])];
|
|
441
|
+
const normalizedFilePath = filePath.replace(/^\.\//, '');
|
|
442
|
+
|
|
443
|
+
if (allKeyFiles.some(f => f && (normalizedFilePath.includes(f.replace(/^\.\//, '')) || f.replace(/^\.\//, '').includes(normalizedFilePath)))) {
|
|
444
|
+
// Extract accomplishments section
|
|
445
|
+
const accomplishmentsMatch = summaryContent.match(/## Accomplishments\s*\n([\s\S]*?)(?=\n## |\n---)/);
|
|
446
|
+
const accomplishments = accomplishmentsMatch ? accomplishmentsMatch[1].trim() : '';
|
|
447
|
+
|
|
448
|
+
summaryContext.push({
|
|
449
|
+
plan: fm.plan || summaryFile.replace(/-SUMMARY\.md$/i, ''),
|
|
450
|
+
accomplishments,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
} catch {
|
|
455
|
+
// Phase dir not readable
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Branch identification
|
|
460
|
+
const branchResult = execGit(cwd, ['branch', '--show-current']);
|
|
461
|
+
const oursBranch = branchResult.exitCode === 0 ? branchResult.stdout : null;
|
|
462
|
+
|
|
463
|
+
let theirsBranch = null;
|
|
464
|
+
const mergeHeadResult = execGit(cwd, ['log', '-1', '--format=%D', 'MERGE_HEAD']);
|
|
465
|
+
if (mergeHeadResult.exitCode === 0 && mergeHeadResult.stdout) {
|
|
466
|
+
theirsBranch = mergeHeadResult.stdout;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Extract phase numbers from branch names
|
|
470
|
+
const phasePattern = /(?:dgs|gsd)\/.*phase-(\d+)/;
|
|
471
|
+
const oursPhase = oursBranch ? (oursBranch.match(phasePattern) || [])[1] || null : null;
|
|
472
|
+
const theirsPhase = theirsBranch ? (theirsBranch.match(phasePattern) || [])[1] || null : null;
|
|
473
|
+
|
|
474
|
+
// Merge base
|
|
475
|
+
let mergeBase = null;
|
|
476
|
+
const mergeHeadShaResult = execGit(cwd, ['rev-parse', 'MERGE_HEAD']);
|
|
477
|
+
if (mergeHeadShaResult.exitCode === 0) {
|
|
478
|
+
const baseResult = execGit(cwd, ['merge-base', 'HEAD', 'MERGE_HEAD']);
|
|
479
|
+
mergeBase = baseResult.exitCode === 0 ? baseResult.stdout : null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
output({
|
|
483
|
+
file: filePath,
|
|
484
|
+
hunks,
|
|
485
|
+
plan_context: planContext,
|
|
486
|
+
summary_context: summaryContext,
|
|
487
|
+
branches: {
|
|
488
|
+
ours: { name: oursBranch, phase: oursPhase },
|
|
489
|
+
theirs: { name: theirsBranch, phase: theirsPhase },
|
|
490
|
+
},
|
|
491
|
+
metadata: {
|
|
492
|
+
timestamp: new Date().toISOString(),
|
|
493
|
+
merge_base: mergeBase,
|
|
494
|
+
},
|
|
495
|
+
}, raw);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ─── Resolution Recording ───────────────────────────────────────────────────
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Record a resolution outcome to RESOLUTIONS.md in the phase directory.
|
|
502
|
+
*
|
|
503
|
+
* @param {string} cwd - Working directory
|
|
504
|
+
* @param {string} filePath - Path to the resolved file
|
|
505
|
+
* @param {string} phaseDir - Phase directory for RESOLUTIONS.md
|
|
506
|
+
* @param {string} resolution - JSON string with strategy, description, confidence
|
|
507
|
+
* @param {boolean} raw - Raw output mode
|
|
508
|
+
*/
|
|
509
|
+
function cmdMergeConflictsResolved(cwd, filePath, phaseDir, resolution, raw) {
|
|
510
|
+
if (!filePath) error('File path required');
|
|
511
|
+
if (!phaseDir) error('Phase directory required (--phase-dir)');
|
|
512
|
+
if (!resolution) error('Resolution JSON required (--resolution)');
|
|
513
|
+
|
|
514
|
+
// Parse resolution JSON
|
|
515
|
+
let res;
|
|
516
|
+
try {
|
|
517
|
+
res = JSON.parse(resolution);
|
|
518
|
+
} catch {
|
|
519
|
+
error('Invalid resolution JSON. Expected: {"strategy":"ours|theirs|manual|combined","description":"...","confidence":"HIGH|MEDIUM|LOW"}');
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const validStrategies = ['ours', 'theirs', 'manual', 'combined'];
|
|
523
|
+
if (!res.strategy || !validStrategies.includes(res.strategy)) {
|
|
524
|
+
error('Invalid strategy. Must be one of: ' + validStrategies.join(', '));
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const validConfidences = ['HIGH', 'MEDIUM', 'LOW'];
|
|
528
|
+
if (!res.confidence || !validConfidences.includes(res.confidence)) {
|
|
529
|
+
error('Invalid confidence. Must be one of: ' + validConfidences.join(', '));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Verify the file no longer has conflict markers
|
|
533
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
|
|
534
|
+
const fileContent = safeReadFile(fullPath);
|
|
535
|
+
if (fileContent && /^<{7}/m.test(fileContent)) {
|
|
536
|
+
error('File still has conflict markers. Resolve before recording.');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const timestamp = new Date().toISOString();
|
|
540
|
+
const absPhaseDir = path.isAbsolute(phaseDir) ? phaseDir : path.join(cwd, phaseDir);
|
|
541
|
+
const resolutionsPath = path.join(absPhaseDir, 'RESOLUTIONS.md');
|
|
542
|
+
|
|
543
|
+
// Create or append to RESOLUTIONS.md
|
|
544
|
+
let existing = safeReadFile(resolutionsPath);
|
|
545
|
+
if (!existing) {
|
|
546
|
+
// Extract phase name from directory
|
|
547
|
+
const phaseName = path.basename(absPhaseDir);
|
|
548
|
+
existing = `# Merge Conflict Resolutions\n\n**Phase:** ${phaseName}\n**Created:** ${timestamp}\n\n---\n\n`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Append resolution entry
|
|
552
|
+
const entry = [
|
|
553
|
+
`### ${filePath}\n`,
|
|
554
|
+
`- **Strategy:** ${res.strategy}`,
|
|
555
|
+
`- **Confidence:** ${res.confidence}`,
|
|
556
|
+
`- **Description:** ${res.description || 'No description provided'}`,
|
|
557
|
+
`- **Resolved:** ${timestamp}`,
|
|
558
|
+
'',
|
|
559
|
+
].join('\n');
|
|
560
|
+
|
|
561
|
+
const newContent = existing + entry + '\n';
|
|
562
|
+
fs.writeFileSync(resolutionsPath, newContent, 'utf-8');
|
|
563
|
+
|
|
564
|
+
output({
|
|
565
|
+
recorded: true,
|
|
566
|
+
file: filePath,
|
|
567
|
+
resolutions_path: resolutionsPath,
|
|
568
|
+
}, raw);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ─── Resolution Summary ─────────────────────────────────────────────────────
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Aggregate all per-file resolutions from RESOLUTIONS.md into a merge-level report.
|
|
575
|
+
*
|
|
576
|
+
* @param {string} cwd - Working directory
|
|
577
|
+
* @param {string} phaseDir - Phase directory containing RESOLUTIONS.md
|
|
578
|
+
* @param {boolean} raw - Raw output mode
|
|
579
|
+
*/
|
|
580
|
+
function cmdMergeConflictsSummary(cwd, phaseDir, raw) {
|
|
581
|
+
if (!phaseDir) error('Phase directory required (--phase-dir)');
|
|
582
|
+
|
|
583
|
+
const absPhaseDir = path.isAbsolute(phaseDir) ? phaseDir : path.join(cwd, phaseDir);
|
|
584
|
+
const resolutionsPath = path.join(absPhaseDir, 'RESOLUTIONS.md');
|
|
585
|
+
const content = safeReadFile(resolutionsPath);
|
|
586
|
+
|
|
587
|
+
if (!content) {
|
|
588
|
+
output({ found: false, resolutions: [], summary: null }, raw, '');
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Parse resolution entries — each is a ### <filepath> section with bullet fields
|
|
593
|
+
const entries = [];
|
|
594
|
+
const entryPattern = /### (.+)\n([\s\S]*?)(?=\n### |\n*$)/g;
|
|
595
|
+
let match;
|
|
596
|
+
|
|
597
|
+
while ((match = entryPattern.exec(content)) !== null) {
|
|
598
|
+
const file = match[1].trim();
|
|
599
|
+
const body = match[2];
|
|
600
|
+
|
|
601
|
+
const strategyMatch = body.match(/\*\*Strategy:\*\*\s*(\w+)/);
|
|
602
|
+
const confidenceMatch = body.match(/\*\*Confidence:\*\*\s*(\w+)/);
|
|
603
|
+
const descriptionMatch = body.match(/\*\*Description:\*\*\s*(.+)/);
|
|
604
|
+
const timestampMatch = body.match(/\*\*Resolved:\*\*\s*(.+)/);
|
|
605
|
+
|
|
606
|
+
entries.push({
|
|
607
|
+
file,
|
|
608
|
+
strategy: strategyMatch ? strategyMatch[1] : 'unknown',
|
|
609
|
+
confidence: confidenceMatch ? confidenceMatch[1] : 'unknown',
|
|
610
|
+
description: descriptionMatch ? descriptionMatch[1].trim() : '',
|
|
611
|
+
timestamp: timestampMatch ? timestampMatch[1].trim() : '',
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Aggregate
|
|
616
|
+
const byStrategy = { ours: 0, theirs: 0, manual: 0, combined: 0 };
|
|
617
|
+
const byConfidence = { HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
618
|
+
|
|
619
|
+
for (const entry of entries) {
|
|
620
|
+
if (byStrategy[entry.strategy] !== undefined) {
|
|
621
|
+
byStrategy[entry.strategy]++;
|
|
622
|
+
}
|
|
623
|
+
if (byConfidence[entry.confidence] !== undefined) {
|
|
624
|
+
byConfidence[entry.confidence]++;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
output({
|
|
629
|
+
found: true,
|
|
630
|
+
total_resolved: entries.length,
|
|
631
|
+
by_strategy: byStrategy,
|
|
632
|
+
by_confidence: byConfidence,
|
|
633
|
+
resolutions: entries.map(e => ({
|
|
634
|
+
file: e.file,
|
|
635
|
+
strategy: e.strategy,
|
|
636
|
+
confidence: e.confidence,
|
|
637
|
+
timestamp: e.timestamp,
|
|
638
|
+
})),
|
|
639
|
+
metadata: {
|
|
640
|
+
phase_dir: phaseDir,
|
|
641
|
+
timestamp: new Date().toISOString(),
|
|
642
|
+
},
|
|
643
|
+
}, raw);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
647
|
+
|
|
648
|
+
module.exports = {
|
|
649
|
+
cmdMergeConflictsDetect,
|
|
650
|
+
cmdMergeConflictsContext,
|
|
651
|
+
cmdMergeConflictsResolved,
|
|
652
|
+
cmdMergeConflictsSummary,
|
|
653
|
+
classifyConflictHunks,
|
|
654
|
+
};
|