@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.
Files changed (256) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +851 -0
  3. package/agents/dgs-codebase-cross-analyzer.md +183 -0
  4. package/agents/dgs-codebase-mapper.md +782 -0
  5. package/agents/dgs-codebase-synthesizer.md +156 -0
  6. package/agents/dgs-debugger.md +1256 -0
  7. package/agents/dgs-executor.md +550 -0
  8. package/agents/dgs-integration-checker.md +481 -0
  9. package/agents/dgs-nyquist-auditor.md +178 -0
  10. package/agents/dgs-phase-researcher.md +563 -0
  11. package/agents/dgs-phase-verifier.md +450 -0
  12. package/agents/dgs-plan-checker.md +708 -0
  13. package/agents/dgs-planner.md +1324 -0
  14. package/agents/dgs-project-researcher.md +631 -0
  15. package/agents/dgs-research-synthesizer.md +249 -0
  16. package/agents/dgs-roadmapper.md +652 -0
  17. package/agents/dgs-verifier.md +607 -0
  18. package/bin/install.js +2073 -0
  19. package/commands/dgs/add-doc.md +45 -0
  20. package/commands/dgs/add-idea.md +38 -0
  21. package/commands/dgs/add-phase.md +43 -0
  22. package/commands/dgs/add-repo.md +54 -0
  23. package/commands/dgs/add-tests.md +41 -0
  24. package/commands/dgs/add-todo.md +47 -0
  25. package/commands/dgs/approve-spec.md +38 -0
  26. package/commands/dgs/audit-milestone.md +36 -0
  27. package/commands/dgs/audit-phase.md +37 -0
  28. package/commands/dgs/cancel-job.md +23 -0
  29. package/commands/dgs/capture-principle.md +143 -0
  30. package/commands/dgs/check-todos.md +45 -0
  31. package/commands/dgs/cleanup.md +18 -0
  32. package/commands/dgs/complete-milestone.md +136 -0
  33. package/commands/dgs/complete-project.md +70 -0
  34. package/commands/dgs/consolidate-ideas.md +50 -0
  35. package/commands/dgs/create-milestone-job.md +37 -0
  36. package/commands/dgs/debug.md +164 -0
  37. package/commands/dgs/develop-idea.md +53 -0
  38. package/commands/dgs/discuss-idea.md +41 -0
  39. package/commands/dgs/discuss-phase.md +83 -0
  40. package/commands/dgs/execute-phase.md +41 -0
  41. package/commands/dgs/fast.md +38 -0
  42. package/commands/dgs/find-related-ideas.md +43 -0
  43. package/commands/dgs/health.md +28 -0
  44. package/commands/dgs/help.md +22 -0
  45. package/commands/dgs/import-spec.md +36 -0
  46. package/commands/dgs/init-product.md +28 -0
  47. package/commands/dgs/insert-phase.md +32 -0
  48. package/commands/dgs/join-discord.md +18 -0
  49. package/commands/dgs/list-docs.md +40 -0
  50. package/commands/dgs/list-ideas.md +42 -0
  51. package/commands/dgs/list-jobs.md +22 -0
  52. package/commands/dgs/list-phase-assumptions.md +46 -0
  53. package/commands/dgs/list-projects.md +57 -0
  54. package/commands/dgs/list-specs.md +40 -0
  55. package/commands/dgs/map-codebase.md +92 -0
  56. package/commands/dgs/new-milestone.md +44 -0
  57. package/commands/dgs/new-project.md +42 -0
  58. package/commands/dgs/node-repair.md +26 -0
  59. package/commands/dgs/overlap-check.md +20 -0
  60. package/commands/dgs/pause-work.md +38 -0
  61. package/commands/dgs/plan-milestone-gaps.md +34 -0
  62. package/commands/dgs/plan-phase.md +44 -0
  63. package/commands/dgs/progress.md +24 -0
  64. package/commands/dgs/quick.md +41 -0
  65. package/commands/dgs/reactivate-project.md +70 -0
  66. package/commands/dgs/reapply-patches.md +110 -0
  67. package/commands/dgs/refine-spec.md +38 -0
  68. package/commands/dgs/reject-idea.md +43 -0
  69. package/commands/dgs/remove-doc.md +44 -0
  70. package/commands/dgs/remove-phase.md +31 -0
  71. package/commands/dgs/remove-repo.md +69 -0
  72. package/commands/dgs/research-idea.md +43 -0
  73. package/commands/dgs/research-phase.md +189 -0
  74. package/commands/dgs/restore-idea.md +45 -0
  75. package/commands/dgs/resume-work.md +40 -0
  76. package/commands/dgs/rollback-job.md +24 -0
  77. package/commands/dgs/run-job.md +35 -0
  78. package/commands/dgs/search.md +40 -0
  79. package/commands/dgs/set-profile.md +34 -0
  80. package/commands/dgs/settings.md +38 -0
  81. package/commands/dgs/switch-project.md +58 -0
  82. package/commands/dgs/undo-consolidation.md +42 -0
  83. package/commands/dgs/update-idea.md +44 -0
  84. package/commands/dgs/update.md +37 -0
  85. package/commands/dgs/validate-phase.md +35 -0
  86. package/commands/dgs/verify-work.md +39 -0
  87. package/commands/dgs/write-spec.md +49 -0
  88. package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-01-SUMMARY.md +84 -0
  89. package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-02-SUMMARY.md +86 -0
  90. package/deliver-great-systems/.planning/phases/10-v1-to-v2-migration-flow/10-01-SUMMARY.md +85 -0
  91. package/deliver-great-systems/bin/dgs-tools.cjs +1444 -0
  92. package/deliver-great-systems/bin/lib/auto-test.cjs +1365 -0
  93. package/deliver-great-systems/bin/lib/commands.cjs +570 -0
  94. package/deliver-great-systems/bin/lib/config.cjs +417 -0
  95. package/deliver-great-systems/bin/lib/conflict-agent.cjs +1063 -0
  96. package/deliver-great-systems/bin/lib/conflict-agent.test.cjs +554 -0
  97. package/deliver-great-systems/bin/lib/context.cjs +929 -0
  98. package/deliver-great-systems/bin/lib/context.test.cjs +693 -0
  99. package/deliver-great-systems/bin/lib/core.cjs +744 -0
  100. package/deliver-great-systems/bin/lib/core.test.cjs +822 -0
  101. package/deliver-great-systems/bin/lib/docs.cjs +919 -0
  102. package/deliver-great-systems/bin/lib/docs.test.cjs +211 -0
  103. package/deliver-great-systems/bin/lib/execution.cjs +705 -0
  104. package/deliver-great-systems/bin/lib/execution.test.cjs +1472 -0
  105. package/deliver-great-systems/bin/lib/frontmatter.cjs +324 -0
  106. package/deliver-great-systems/bin/lib/ideas.cjs +1406 -0
  107. package/deliver-great-systems/bin/lib/ideas.test.cjs +1417 -0
  108. package/deliver-great-systems/bin/lib/identity.cjs +125 -0
  109. package/deliver-great-systems/bin/lib/init.cjs +1114 -0
  110. package/deliver-great-systems/bin/lib/init.test.cjs +1271 -0
  111. package/deliver-great-systems/bin/lib/jobs.cjs +2015 -0
  112. package/deliver-great-systems/bin/lib/jobs.test.cjs +2619 -0
  113. package/deliver-great-systems/bin/lib/merge-conflicts.cjs +654 -0
  114. package/deliver-great-systems/bin/lib/merge-conflicts.test.cjs +370 -0
  115. package/deliver-great-systems/bin/lib/migration.cjs +352 -0
  116. package/deliver-great-systems/bin/lib/migration.test.cjs +582 -0
  117. package/deliver-great-systems/bin/lib/milestone.cjs +243 -0
  118. package/deliver-great-systems/bin/lib/overlap.cjs +437 -0
  119. package/deliver-great-systems/bin/lib/overlap.test.cjs +747 -0
  120. package/deliver-great-systems/bin/lib/path-audit.test.cjs +384 -0
  121. package/deliver-great-systems/bin/lib/paths.cjs +144 -0
  122. package/deliver-great-systems/bin/lib/paths.test.cjs +486 -0
  123. package/deliver-great-systems/bin/lib/phase.cjs +910 -0
  124. package/deliver-great-systems/bin/lib/projects.cjs +691 -0
  125. package/deliver-great-systems/bin/lib/projects.test.cjs +871 -0
  126. package/deliver-great-systems/bin/lib/repos.cjs +1432 -0
  127. package/deliver-great-systems/bin/lib/repos.test.cjs +1882 -0
  128. package/deliver-great-systems/bin/lib/roadmap.cjs +305 -0
  129. package/deliver-great-systems/bin/lib/search.cjs +570 -0
  130. package/deliver-great-systems/bin/lib/specs.cjs +1303 -0
  131. package/deliver-great-systems/bin/lib/state.cjs +893 -0
  132. package/deliver-great-systems/bin/lib/template.cjs +228 -0
  133. package/deliver-great-systems/bin/lib/test-helpers.cjs +291 -0
  134. package/deliver-great-systems/bin/lib/verify.cjs +796 -0
  135. package/deliver-great-systems/references/checkpoints.md +776 -0
  136. package/deliver-great-systems/references/conflict-resolution.md +66 -0
  137. package/deliver-great-systems/references/context-tiers.md +166 -0
  138. package/deliver-great-systems/references/continuation-format.md +249 -0
  139. package/deliver-great-systems/references/decimal-phase-calculation.md +67 -0
  140. package/deliver-great-systems/references/git-integration.md +250 -0
  141. package/deliver-great-systems/references/git-planning-commit.md +40 -0
  142. package/deliver-great-systems/references/model-profile-resolution.md +36 -0
  143. package/deliver-great-systems/references/model-profiles.md +95 -0
  144. package/deliver-great-systems/references/phase-argument-parsing.md +61 -0
  145. package/deliver-great-systems/references/planning-config.md +224 -0
  146. package/deliver-great-systems/references/questioning.md +162 -0
  147. package/deliver-great-systems/references/spec-review-loop.md +177 -0
  148. package/deliver-great-systems/references/tdd.md +265 -0
  149. package/deliver-great-systems/references/ui-brand.md +160 -0
  150. package/deliver-great-systems/references/verification-patterns.md +612 -0
  151. package/deliver-great-systems/templates/DEBUG.md +166 -0
  152. package/deliver-great-systems/templates/UAT.md +251 -0
  153. package/deliver-great-systems/templates/VALIDATION.md +95 -0
  154. package/deliver-great-systems/templates/claude-md.md +74 -0
  155. package/deliver-great-systems/templates/codebase/architecture.md +257 -0
  156. package/deliver-great-systems/templates/codebase/concerns.md +312 -0
  157. package/deliver-great-systems/templates/codebase/conventions.md +309 -0
  158. package/deliver-great-systems/templates/codebase/integrations.md +282 -0
  159. package/deliver-great-systems/templates/codebase/stack.md +188 -0
  160. package/deliver-great-systems/templates/codebase/structure.md +287 -0
  161. package/deliver-great-systems/templates/codebase/testing.md +482 -0
  162. package/deliver-great-systems/templates/config.json +38 -0
  163. package/deliver-great-systems/templates/context.md +354 -0
  164. package/deliver-great-systems/templates/continue-here.md +80 -0
  165. package/deliver-great-systems/templates/debug-subagent-prompt.md +93 -0
  166. package/deliver-great-systems/templates/discovery.md +148 -0
  167. package/deliver-great-systems/templates/milestone-archive.md +125 -0
  168. package/deliver-great-systems/templates/milestone.md +117 -0
  169. package/deliver-great-systems/templates/phase-prompt.md +615 -0
  170. package/deliver-great-systems/templates/planner-subagent-prompt.md +119 -0
  171. package/deliver-great-systems/templates/project.md +186 -0
  172. package/deliver-great-systems/templates/requirements.md +233 -0
  173. package/deliver-great-systems/templates/research-project/ARCHITECTURE.md +206 -0
  174. package/deliver-great-systems/templates/research-project/FEATURES.md +149 -0
  175. package/deliver-great-systems/templates/research-project/PITFALLS.md +202 -0
  176. package/deliver-great-systems/templates/research-project/STACK.md +122 -0
  177. package/deliver-great-systems/templates/research-project/SUMMARY.md +172 -0
  178. package/deliver-great-systems/templates/research.md +554 -0
  179. package/deliver-great-systems/templates/retrospective.md +54 -0
  180. package/deliver-great-systems/templates/roadmap.md +204 -0
  181. package/deliver-great-systems/templates/state.md +178 -0
  182. package/deliver-great-systems/templates/summary-complex.md +59 -0
  183. package/deliver-great-systems/templates/summary-minimal.md +41 -0
  184. package/deliver-great-systems/templates/summary-standard.md +48 -0
  185. package/deliver-great-systems/templates/summary.md +253 -0
  186. package/deliver-great-systems/templates/user-setup.md +313 -0
  187. package/deliver-great-systems/templates/verification-report.md +324 -0
  188. package/deliver-great-systems/workflows/add-doc.md +151 -0
  189. package/deliver-great-systems/workflows/add-idea.md +96 -0
  190. package/deliver-great-systems/workflows/add-phase.md +120 -0
  191. package/deliver-great-systems/workflows/add-tests.md +359 -0
  192. package/deliver-great-systems/workflows/add-todo.md +162 -0
  193. package/deliver-great-systems/workflows/approve-spec.md +194 -0
  194. package/deliver-great-systems/workflows/audit-milestone.md +364 -0
  195. package/deliver-great-systems/workflows/audit-phase.md +462 -0
  196. package/deliver-great-systems/workflows/cancel-job.md +108 -0
  197. package/deliver-great-systems/workflows/check-todos.md +181 -0
  198. package/deliver-great-systems/workflows/cleanup.md +247 -0
  199. package/deliver-great-systems/workflows/codereview.md +526 -0
  200. package/deliver-great-systems/workflows/complete-milestone.md +1298 -0
  201. package/deliver-great-systems/workflows/consolidate-ideas.md +365 -0
  202. package/deliver-great-systems/workflows/create-milestone-job.md +177 -0
  203. package/deliver-great-systems/workflows/develop-idea.md +544 -0
  204. package/deliver-great-systems/workflows/diagnose-issues.md +231 -0
  205. package/deliver-great-systems/workflows/discovery-phase.md +301 -0
  206. package/deliver-great-systems/workflows/discuss-idea.md +263 -0
  207. package/deliver-great-systems/workflows/discuss-phase.md +733 -0
  208. package/deliver-great-systems/workflows/execute-phase.md +571 -0
  209. package/deliver-great-systems/workflows/execute-plan.md +592 -0
  210. package/deliver-great-systems/workflows/find-related-ideas.md +271 -0
  211. package/deliver-great-systems/workflows/health.md +173 -0
  212. package/deliver-great-systems/workflows/help.md +997 -0
  213. package/deliver-great-systems/workflows/import-spec.md +381 -0
  214. package/deliver-great-systems/workflows/init-product.md +767 -0
  215. package/deliver-great-systems/workflows/insert-phase.md +138 -0
  216. package/deliver-great-systems/workflows/list-docs.md +119 -0
  217. package/deliver-great-systems/workflows/list-ideas.md +154 -0
  218. package/deliver-great-systems/workflows/list-jobs.md +89 -0
  219. package/deliver-great-systems/workflows/list-phase-assumptions.md +192 -0
  220. package/deliver-great-systems/workflows/list-specs.md +101 -0
  221. package/deliver-great-systems/workflows/map-codebase.md +621 -0
  222. package/deliver-great-systems/workflows/new-milestone.md +591 -0
  223. package/deliver-great-systems/workflows/new-project.md +1113 -0
  224. package/deliver-great-systems/workflows/node-repair.md +94 -0
  225. package/deliver-great-systems/workflows/overlap-check.md +86 -0
  226. package/deliver-great-systems/workflows/pause-work.md +134 -0
  227. package/deliver-great-systems/workflows/plan-milestone-gaps.md +306 -0
  228. package/deliver-great-systems/workflows/plan-phase.md +698 -0
  229. package/deliver-great-systems/workflows/progress.md +386 -0
  230. package/deliver-great-systems/workflows/quick.md +845 -0
  231. package/deliver-great-systems/workflows/refine-spec.md +275 -0
  232. package/deliver-great-systems/workflows/reject-idea.md +109 -0
  233. package/deliver-great-systems/workflows/remove-doc.md +117 -0
  234. package/deliver-great-systems/workflows/remove-phase.md +163 -0
  235. package/deliver-great-systems/workflows/research-idea.md +325 -0
  236. package/deliver-great-systems/workflows/research-phase.md +81 -0
  237. package/deliver-great-systems/workflows/restore-idea.md +101 -0
  238. package/deliver-great-systems/workflows/resume-project.md +311 -0
  239. package/deliver-great-systems/workflows/rollback-job.md +130 -0
  240. package/deliver-great-systems/workflows/run-job.md +498 -0
  241. package/deliver-great-systems/workflows/search.md +130 -0
  242. package/deliver-great-systems/workflows/set-profile.md +83 -0
  243. package/deliver-great-systems/workflows/settings.md +470 -0
  244. package/deliver-great-systems/workflows/transition.md +563 -0
  245. package/deliver-great-systems/workflows/undo-consolidation.md +155 -0
  246. package/deliver-great-systems/workflows/update-idea.md +157 -0
  247. package/deliver-great-systems/workflows/update.md +242 -0
  248. package/deliver-great-systems/workflows/validate-phase.md +177 -0
  249. package/deliver-great-systems/workflows/verify-phase.md +253 -0
  250. package/deliver-great-systems/workflows/verify-work.md +671 -0
  251. package/deliver-great-systems/workflows/write-spec.md +450 -0
  252. package/hooks/dist/dgs-check-update.js +62 -0
  253. package/hooks/dist/dgs-context-monitor.js +141 -0
  254. package/hooks/dist/dgs-statusline.js +115 -0
  255. package/package.json +60 -0
  256. package/scripts/build-hooks.js +43 -0
@@ -0,0 +1,1063 @@
1
+ /**
2
+ * Conflict Agent -- Resolution engine, report generation, verification, semantic detection, and cascading context
3
+ *
4
+ * Provides the conflict resolution brain that takes a conflicted merge state,
5
+ * reads plan context for both sides, produces resolved file contents, and
6
+ * records the full audit trail. Uses Phase 38's detection and classification primitives.
7
+ *
8
+ * Exported functions:
9
+ * resolveConflicts Full resolution flow (alias for cmdConflictAgentRun)
10
+ * verifyResolution Post-resolution test/lint verification
11
+ * detectSemanticConflicts Flag same-domain changes in cleanly merged files
12
+ * selectResolutionStrategy Per-hunk strategy selection from classified hunks
13
+ * resolveFileContent Apply per-hunk strategies to produce resolved file
14
+ * buildResolutionReport Generate RESOLUTION-REPORT.md content
15
+ * createLearningContext Extract learnings from completed resolutions
16
+ * mergeLearningContexts Accumulate learnings across sequential phase merges
17
+ * cmdConflictAgentRun CLI handler: orchestrate full resolution
18
+ * cmdConflictAgentResolveFile CLI handler: resolve a single file
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const { execSync } = require('child_process');
24
+ const { execGit, safeReadFile, loadConfig, output, error } = require('./core.cjs');
25
+ const { classifyConflictHunks, cmdMergeConflictsDetect, cmdMergeConflictsContext } = require('./merge-conflicts.cjs');
26
+ const { extractFrontmatter } = require('./frontmatter.cjs');
27
+ const { parseReposMd } = require('./repos.cjs');
28
+
29
+ // ─── Section 1: Resolution Strategy Selection ────────────────────────────────
30
+
31
+ /**
32
+ * Select a resolution strategy for each classified hunk.
33
+ *
34
+ * Pure function taking classified hunks (from classifyConflictHunks),
35
+ * plan context for both sides, and optional accumulated learnings.
36
+ *
37
+ * @param {Array<{type: string, confidence: string, ours: string, theirs: string, start_line: number, end_line: number}>} classifiedHunks
38
+ * @param {Array<{task_name: string, action_excerpt: string}>|null} planContext
39
+ * @param {Object|null} learnings - Accumulated learnings from previous resolutions
40
+ * @returns {Array<{hunk_index: number, strategy: string, confidence: string, reasoning: string}>}
41
+ */
42
+ function selectResolutionStrategy(classifiedHunks, planContext, learnings) {
43
+ const contextText = planContext && Array.isArray(planContext)
44
+ ? planContext.map(c => (c.task_name || '') + ' ' + (c.action_excerpt || '')).join(' ').toLowerCase()
45
+ : '';
46
+
47
+ return classifiedHunks.map((hunk, index) => {
48
+ let strategy = 'escalate';
49
+ let confidence = hunk.confidence || 'LOW';
50
+ let reasoning = '';
51
+
52
+ switch (hunk.type) {
53
+ case 'ADDITIVE': {
54
+ // Take the side that adds content
55
+ const oursEmpty = !hunk.ours || hunk.ours.trim().length === 0;
56
+ const theirsEmpty = !hunk.theirs || hunk.theirs.trim().length === 0;
57
+
58
+ if (oursEmpty && !theirsEmpty) {
59
+ strategy = 'theirs';
60
+ confidence = 'HIGH';
61
+ reasoning = 'Additive hunk: theirs adds new content, ours is empty';
62
+ } else if (theirsEmpty && !oursEmpty) {
63
+ strategy = 'ours';
64
+ confidence = 'HIGH';
65
+ reasoning = 'Additive hunk: ours adds new content, theirs is empty';
66
+ } else {
67
+ // Both sides have content but one is a superset -- take the superset
68
+ const oursLines = hunk.ours.split('\n').filter(l => l.trim());
69
+ const theirsLines = hunk.theirs.split('\n').filter(l => l.trim());
70
+ if (theirsLines.length >= oursLines.length) {
71
+ strategy = 'theirs';
72
+ confidence = 'HIGH';
73
+ reasoning = 'Additive hunk: theirs is a superset of ours';
74
+ } else {
75
+ strategy = 'ours';
76
+ confidence = 'HIGH';
77
+ reasoning = 'Additive hunk: ours is a superset of theirs';
78
+ }
79
+ }
80
+ break;
81
+ }
82
+
83
+ case 'DELETION': {
84
+ // Take the side that keeps content unless plan context says deletion was intentional
85
+ const deletionIntentional = contextText.includes('remove') || contextText.includes('delete') ||
86
+ contextText.includes('deprecat') || contextText.includes('clean up') || contextText.includes('strip');
87
+
88
+ if (deletionIntentional) {
89
+ // Plan context suggests deletion was intentional
90
+ const oursEmpty = !hunk.ours || hunk.ours.trim().length === 0;
91
+ strategy = oursEmpty ? 'ours' : 'theirs';
92
+ confidence = 'MEDIUM';
93
+ reasoning = 'Deletion hunk: plan context suggests removal was intentional';
94
+ } else {
95
+ // Keep content (take the non-empty side)
96
+ const oursEmpty = !hunk.ours || hunk.ours.trim().length === 0;
97
+ strategy = oursEmpty ? 'theirs' : 'ours';
98
+ confidence = 'MEDIUM';
99
+ reasoning = 'Deletion hunk: keeping content (no plan context for intentional deletion)';
100
+ }
101
+ break;
102
+ }
103
+
104
+ case 'STRUCTURAL': {
105
+ // Attempt combined resolution if changes are to different structural elements
106
+ const oursLines = (hunk.ours || '').split('\n').filter(l => l.trim());
107
+ const theirsLines = (hunk.theirs || '').split('\n').filter(l => l.trim());
108
+
109
+ // Check if both sides add imports/requires (combinable)
110
+ const oursImports = oursLines.filter(l => /^\s*(const\s+\w+\s*=\s*require|import\s+|require\s*\()/.test(l));
111
+ const theirsImports = theirsLines.filter(l => /^\s*(const\s+\w+\s*=\s*require|import\s+|require\s*\()/.test(l));
112
+
113
+ if (oursImports.length > 0 && theirsImports.length > 0) {
114
+ // Both sides add imports -- can combine
115
+ strategy = 'combined';
116
+ confidence = 'HIGH';
117
+ reasoning = 'Structural hunk: both sides add imports/requires, combining and deduplicating';
118
+ } else if (oursImports.length > 0 || theirsImports.length > 0) {
119
+ // Only one side has imports
120
+ strategy = 'combined';
121
+ confidence = 'MEDIUM';
122
+ reasoning = 'Structural hunk: one side adds imports, combining with other changes';
123
+ } else {
124
+ // Same structural element modified differently
125
+ const oursSet = new Set(oursLines.map(l => l.trim()));
126
+ const theirsSet = new Set(theirsLines.map(l => l.trim()));
127
+ const overlap = oursLines.filter(l => theirsSet.has(l.trim()));
128
+
129
+ if (overlap.length === 0) {
130
+ // Different structural elements -- can combine
131
+ strategy = 'combined';
132
+ confidence = 'MEDIUM';
133
+ reasoning = 'Structural hunk: changes target different structural elements';
134
+ } else {
135
+ // Same element modified differently
136
+ strategy = 'escalate';
137
+ confidence = 'LOW';
138
+ reasoning = 'Structural hunk: same structural element modified differently, needs manual review';
139
+ }
140
+ }
141
+ break;
142
+ }
143
+
144
+ case 'DIVERGENT': {
145
+ // Use plan context to determine intent
146
+ if (contextText.length > 0) {
147
+ // Check if both plan tasks explain their changes clearly
148
+ const oursLines = (hunk.ours || '').split('\n').filter(l => l.trim());
149
+ const theirsLines = (hunk.theirs || '').split('\n').filter(l => l.trim());
150
+ const oursSet = new Set(oursLines.map(l => l.trim()));
151
+ const theirsSet = new Set(theirsLines.map(l => l.trim()));
152
+ const overlap = oursLines.filter(l => theirsSet.has(l.trim()));
153
+
154
+ if (overlap.length === 0) {
155
+ // No overlapping lines, changes don't conflict semantically
156
+ strategy = 'combined';
157
+ confidence = 'MEDIUM';
158
+ reasoning = 'Divergent hunk: plan context available, no overlapping lines, attempting combined resolution';
159
+ } else {
160
+ strategy = 'escalate';
161
+ confidence = 'LOW';
162
+ reasoning = 'Divergent hunk: overlapping lines with plan context, needs manual review';
163
+ }
164
+ } else {
165
+ strategy = 'escalate';
166
+ confidence = 'LOW';
167
+ reasoning = 'Divergent hunk: no plan context available, needs manual review';
168
+ }
169
+ break;
170
+ }
171
+
172
+ default:
173
+ strategy = 'escalate';
174
+ confidence = 'LOW';
175
+ reasoning = 'Unknown hunk type: ' + hunk.type;
176
+ }
177
+
178
+ // Boost confidence if accumulated learnings contain info about same domain/file
179
+ if (learnings && learnings.domain_changes) {
180
+ const domains = Object.keys(learnings.domain_changes);
181
+ if (domains.length > 0) {
182
+ const hunkText = ((hunk.ours || '') + ' ' + (hunk.theirs || '')).toLowerCase();
183
+ const domainMatch = domains.some(d => hunkText.includes(d.toLowerCase()));
184
+
185
+ if (domainMatch) {
186
+ if (confidence === 'LOW') {
187
+ confidence = 'MEDIUM';
188
+ reasoning += '. Confidence boosted by accumulated learnings from same domain';
189
+ } else if (confidence === 'MEDIUM') {
190
+ confidence = 'HIGH';
191
+ reasoning += '. Confidence boosted by accumulated learnings from same domain';
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ return { hunk_index: index, strategy, confidence, reasoning };
198
+ });
199
+ }
200
+
201
+ // ─── Section 2: Whole-File Resolution ────────────────────────────────────────
202
+
203
+ /**
204
+ * Resolve a file's content by applying per-hunk strategy decisions.
205
+ *
206
+ * @param {string} fileContent - File content with conflict markers
207
+ * @param {Array<{hunk_index: number, strategy: string}>} strategies - Per-hunk strategy decisions
208
+ * @param {string|null} mergeBaseContent - Optional file content from merge-base
209
+ * @returns {{resolved_content: string, has_unresolved: boolean, applied: number, escalated: number}}
210
+ */
211
+ function resolveFileContent(fileContent, strategies, mergeBaseContent) {
212
+ const lines = fileContent.split('\n');
213
+ const result = [];
214
+ let inConflict = false;
215
+ let inOurs = false;
216
+ let hunkIndex = 0;
217
+ let oursLines = [];
218
+ let theirsLines = [];
219
+ let applied = 0;
220
+ let escalated = 0;
221
+
222
+ for (let i = 0; i < lines.length; i++) {
223
+ const line = lines[i];
224
+
225
+ if (/^<{7}/.test(line)) {
226
+ inConflict = true;
227
+ inOurs = true;
228
+ oursLines = [];
229
+ theirsLines = [];
230
+ continue;
231
+ }
232
+
233
+ if (inConflict && /^={7}/.test(line)) {
234
+ inOurs = false;
235
+ continue;
236
+ }
237
+
238
+ if (inConflict && /^>{7}/.test(line)) {
239
+ // End of conflict hunk -- apply strategy
240
+ const strategyEntry = strategies.find(s => s.hunk_index === hunkIndex);
241
+ const strategy = strategyEntry ? strategyEntry.strategy : 'escalate';
242
+
243
+ switch (strategy) {
244
+ case 'ours':
245
+ result.push(...oursLines);
246
+ applied++;
247
+ break;
248
+
249
+ case 'theirs':
250
+ result.push(...theirsLines);
251
+ applied++;
252
+ break;
253
+
254
+ case 'combined': {
255
+ // For structural hunks with imports/requires, deduplicate and sort
256
+ const oursImportLines = oursLines.filter(l => /^\s*(const\s+\w+\s*=\s*require|import\s+|require\s*\()/.test(l));
257
+ const theirsImportLines = theirsLines.filter(l => /^\s*(const\s+\w+\s*=\s*require|import\s+|require\s*\()/.test(l));
258
+
259
+ if (oursImportLines.length > 0 && theirsImportLines.length > 0 &&
260
+ oursImportLines.length === oursLines.length && theirsImportLines.length === theirsLines.length) {
261
+ // All lines are imports -- deduplicate and sort alphabetically
262
+ const allImports = [...oursLines, ...theirsLines];
263
+ const unique = [...new Set(allImports.map(l => l.trim()))];
264
+ unique.sort((a, b) => a.localeCompare(b));
265
+ result.push(...unique);
266
+ } else {
267
+ // General combined: ours first, then theirs
268
+ result.push(...oursLines);
269
+ result.push(...theirsLines);
270
+ }
271
+ applied++;
272
+ break;
273
+ }
274
+
275
+ case 'escalate':
276
+ default:
277
+ // Leave conflict markers in place
278
+ result.push('<<<<<<< ours');
279
+ result.push(...oursLines);
280
+ result.push('=======');
281
+ result.push(...theirsLines);
282
+ result.push('>>>>>>> theirs');
283
+ escalated++;
284
+ break;
285
+ }
286
+
287
+ hunkIndex++;
288
+ inConflict = false;
289
+ inOurs = false;
290
+ continue;
291
+ }
292
+
293
+ if (inConflict) {
294
+ if (inOurs) {
295
+ oursLines.push(line);
296
+ } else {
297
+ theirsLines.push(line);
298
+ }
299
+ } else {
300
+ result.push(line);
301
+ }
302
+ }
303
+
304
+ const resolvedContent = result.join('\n');
305
+ return {
306
+ resolved_content: resolvedContent,
307
+ has_unresolved: escalated > 0,
308
+ applied,
309
+ escalated,
310
+ };
311
+ }
312
+
313
+ // ─── Section 3: Resolution Report ────────────────────────────────────────────
314
+
315
+ /**
316
+ * Build a RESOLUTION-REPORT.md content string from per-file resolution results.
317
+ *
318
+ * @param {Array<{file: string, hunks: Array, strategies: Array, result: Object}>} fileResults
319
+ * @param {{branches: {ours: string, theirs: string}, phase: string, timestamp: string, semantic_warnings: Array}} metadata
320
+ * @returns {string}
321
+ */
322
+ function buildResolutionReport(fileResults, metadata) {
323
+ const lines = [];
324
+
325
+ lines.push('# Conflict Resolution Report');
326
+ lines.push('');
327
+ lines.push('## Metadata');
328
+ lines.push('');
329
+ lines.push('| Field | Value |');
330
+ lines.push('| ----- | ----- |');
331
+ lines.push(`| Phase | ${metadata.phase || 'unknown'} |`);
332
+ lines.push(`| Ours Branch | ${metadata.branches?.ours || 'unknown'} |`);
333
+ lines.push(`| Theirs Branch | ${metadata.branches?.theirs || 'unknown'} |`);
334
+ lines.push(`| Timestamp | ${metadata.timestamp || new Date().toISOString()} |`);
335
+ lines.push(`| Total Files | ${fileResults.length} |`);
336
+
337
+ // Confidence breakdown
338
+ const confidenceCounts = { HIGH: 0, MEDIUM: 0, LOW: 0 };
339
+ const strategyCounts = { ours: 0, theirs: 0, combined: 0, escalate: 0 };
340
+
341
+ for (const fr of fileResults) {
342
+ for (const s of (fr.strategies || [])) {
343
+ if (confidenceCounts[s.confidence] !== undefined) confidenceCounts[s.confidence]++;
344
+ if (strategyCounts[s.strategy] !== undefined) strategyCounts[s.strategy]++;
345
+ }
346
+ }
347
+
348
+ lines.push(`| HIGH Confidence | ${confidenceCounts.HIGH} |`);
349
+ lines.push(`| MEDIUM Confidence | ${confidenceCounts.MEDIUM} |`);
350
+ lines.push(`| LOW Confidence | ${confidenceCounts.LOW} |`);
351
+ lines.push('');
352
+
353
+ // Resolved Conflicts section
354
+ lines.push('## Resolved Conflicts');
355
+ lines.push('');
356
+
357
+ for (const fr of fileResults) {
358
+ lines.push(`### ${fr.file}`);
359
+ lines.push('');
360
+
361
+ if (fr.strategies && fr.hunks) {
362
+ for (let i = 0; i < fr.strategies.length; i++) {
363
+ const s = fr.strategies[i];
364
+ const h = fr.hunks[i];
365
+
366
+ lines.push(`**Hunk ${i + 1}** (lines ${h ? h.start_line : '?'}-${h ? h.end_line : '?'})`);
367
+ lines.push('');
368
+ lines.push(`- Classification: ${h ? h.type : 'unknown'}`);
369
+ lines.push(`- Strategy: ${s.strategy}`);
370
+ lines.push(`- Confidence: ${s.confidence}`);
371
+ lines.push(`- Reasoning: ${s.reasoning}`);
372
+ lines.push('');
373
+
374
+ // Before/after snippets (truncated to 10 lines each side)
375
+ if (h) {
376
+ const oursSnippet = (h.ours || '').split('\n').slice(0, 10).join('\n');
377
+ const theirsSnippet = (h.theirs || '').split('\n').slice(0, 10).join('\n');
378
+
379
+ if (oursSnippet.trim()) {
380
+ lines.push('<details><summary>Ours side (truncated)</summary>');
381
+ lines.push('');
382
+ lines.push('```');
383
+ lines.push(oursSnippet);
384
+ lines.push('```');
385
+ lines.push('</details>');
386
+ lines.push('');
387
+ }
388
+
389
+ if (theirsSnippet.trim()) {
390
+ lines.push('<details><summary>Theirs side (truncated)</summary>');
391
+ lines.push('');
392
+ lines.push('```');
393
+ lines.push(theirsSnippet);
394
+ lines.push('```');
395
+ lines.push('</details>');
396
+ lines.push('');
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ if (fr.result) {
403
+ lines.push(`**Result:** ${fr.result.applied} applied, ${fr.result.escalated} escalated`);
404
+ lines.push('');
405
+ }
406
+ }
407
+
408
+ // Semantic Warnings section
409
+ lines.push('## Semantic Warnings');
410
+ lines.push('');
411
+
412
+ const warnings = metadata.semantic_warnings || [];
413
+ if (warnings.length === 0) {
414
+ lines.push('No semantic conflicts detected.');
415
+ } else {
416
+ for (const w of warnings) {
417
+ lines.push(`- **${w.file}** (${w.risk}): ${w.domain} -- ours: ${w.ours_changes}, theirs: ${w.theirs_changes}`);
418
+ }
419
+ }
420
+ lines.push('');
421
+
422
+ // Summary section
423
+ lines.push('## Summary');
424
+ lines.push('');
425
+ lines.push('| Strategy | Count |');
426
+ lines.push('| -------- | ----- |');
427
+ lines.push(`| ours | ${strategyCounts.ours} |`);
428
+ lines.push(`| theirs | ${strategyCounts.theirs} |`);
429
+ lines.push(`| combined | ${strategyCounts.combined} |`);
430
+ lines.push(`| escalate | ${strategyCounts.escalate} |`);
431
+ lines.push('');
432
+ lines.push('| Confidence | Count |');
433
+ lines.push('| ---------- | ----- |');
434
+ lines.push(`| HIGH | ${confidenceCounts.HIGH} |`);
435
+ lines.push(`| MEDIUM | ${confidenceCounts.MEDIUM} |`);
436
+ lines.push(`| LOW | ${confidenceCounts.LOW} |`);
437
+ lines.push('');
438
+
439
+ const allHigh = confidenceCounts.LOW === 0 && confidenceCounts.MEDIUM === 0;
440
+ const hasLow = confidenceCounts.LOW > 0;
441
+ const assessment = allHigh ? 'Clean merge -- all resolutions are HIGH confidence'
442
+ : hasLow ? 'Manual review recommended -- LOW confidence resolutions present'
443
+ : 'Mostly clean merge -- MEDIUM confidence resolutions may need review';
444
+
445
+ lines.push(`**Overall Assessment:** ${assessment}`);
446
+ lines.push('');
447
+
448
+ return lines.join('\n');
449
+ }
450
+
451
+ // ─── Section 4: Post-Resolution Verification ─────────────────────────────────
452
+
453
+ /**
454
+ * Run post-resolution verification (tests, linting, syntax check) on resolved files.
455
+ *
456
+ * @param {string} cwd - Working directory
457
+ * @param {string[]} resolvedFiles - List of resolved file paths
458
+ * @returns {{passed: boolean, test_output: string|null, lint_output: string|null, errors: string[]}}
459
+ */
460
+ function verifyResolution(cwd, resolvedFiles) {
461
+ const errors = [];
462
+ let testOutput = null;
463
+ let lintOutput = null;
464
+
465
+ // Check for package.json with test/lint scripts
466
+ const pkgPath = path.join(cwd, 'package.json');
467
+ const pkgContent = safeReadFile(pkgPath);
468
+ let pkg = null;
469
+
470
+ if (pkgContent) {
471
+ try {
472
+ pkg = JSON.parse(pkgContent);
473
+ } catch {
474
+ // Invalid package.json
475
+ }
476
+ }
477
+
478
+ const hasTestScript = pkg && pkg.scripts && pkg.scripts.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1';
479
+ const hasLintScript = pkg && pkg.scripts && (pkg.scripts.lint || pkg.scripts.eslint);
480
+
481
+ // Run tests if available
482
+ if (hasTestScript) {
483
+ try {
484
+ testOutput = execSync('npm test', {
485
+ cwd,
486
+ stdio: 'pipe',
487
+ encoding: 'utf-8',
488
+ timeout: 60000,
489
+ });
490
+ } catch (err) {
491
+ testOutput = (err.stdout || '') + '\n' + (err.stderr || '');
492
+ errors.push('Test suite failed: ' + (err.stderr || err.message || '').slice(0, 500));
493
+ }
494
+ }
495
+
496
+ // Run linter if available
497
+ if (hasLintScript) {
498
+ const lintCmd = pkg.scripts.lint ? 'npm run lint' : 'npm run eslint';
499
+ try {
500
+ lintOutput = execSync(lintCmd, {
501
+ cwd,
502
+ stdio: 'pipe',
503
+ encoding: 'utf-8',
504
+ timeout: 30000,
505
+ });
506
+ } catch (err) {
507
+ lintOutput = (err.stdout || '') + '\n' + (err.stderr || '');
508
+ errors.push('Linter failed: ' + (err.stderr || err.message || '').slice(0, 500));
509
+ }
510
+ }
511
+
512
+ // If no test/lint scripts, check syntax for JS/CJS/TS files
513
+ if (!hasTestScript && !hasLintScript) {
514
+ for (const filePath of resolvedFiles) {
515
+ const ext = path.extname(filePath).toLowerCase();
516
+ if (['.js', '.cjs', '.mjs', '.ts'].includes(ext)) {
517
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
518
+ try {
519
+ execSync('node --check ' + JSON.stringify(fullPath), {
520
+ cwd,
521
+ stdio: 'pipe',
522
+ encoding: 'utf-8',
523
+ timeout: 10000,
524
+ });
525
+ } catch (err) {
526
+ errors.push('Syntax error in ' + filePath + ': ' + (err.stderr || err.message || '').slice(0, 300));
527
+ }
528
+ }
529
+ }
530
+ }
531
+
532
+ return {
533
+ passed: errors.length === 0,
534
+ test_output: testOutput,
535
+ lint_output: lintOutput,
536
+ errors,
537
+ };
538
+ }
539
+
540
+ // ─── Section 5: Semantic Conflict Detection ──────────────────────────────────
541
+
542
+ /**
543
+ * Detect semantic conflicts in cleanly merged files.
544
+ *
545
+ * For files that merged cleanly (no conflict markers), checks if both branches
546
+ * modified the same file and if plan context shows they touched the same domain.
547
+ *
548
+ * @param {string} cwd - Working directory
549
+ * @param {string[]} cleanFiles - Files that merged cleanly
550
+ * @param {Array<{task_name: string, action_excerpt: string}>|null} planContext
551
+ * @returns {Array<{file: string, domain: string, ours_changes: string, theirs_changes: string, risk: string}>}
552
+ */
553
+ function detectSemanticConflicts(cwd, cleanFiles, planContext) {
554
+ const warnings = [];
555
+
556
+ for (const filePath of cleanFiles) {
557
+ // Check if BOTH branches modified this file
558
+ const oursLog = execGit(cwd, ['log', '--oneline', 'MERGE_HEAD..HEAD', '--', filePath]);
559
+ const theirsLog = execGit(cwd, ['log', '--oneline', 'HEAD..MERGE_HEAD', '--', filePath]);
560
+
561
+ const oursModified = oursLog.exitCode === 0 && oursLog.stdout && oursLog.stdout.trim().length > 0;
562
+ const theirsModified = theirsLog.exitCode === 0 && theirsLog.stdout && theirsLog.stdout.trim().length > 0;
563
+
564
+ if (!oursModified || !theirsModified) continue;
565
+
566
+ // Both branches modified this file -- check if same domain
567
+ const oursChanges = oursLog.stdout.split('\n').filter(l => l.trim()).map(l => l.trim()).join('; ');
568
+ const theirsChanges = theirsLog.stdout.split('\n').filter(l => l.trim()).map(l => l.trim()).join('; ');
569
+
570
+ // Extract domain from file path and plan context
571
+ const fileName = path.basename(filePath, path.extname(filePath));
572
+ let domain = fileName;
573
+
574
+ // Check plan context for domain overlap
575
+ let risk = 'info';
576
+ if (planContext && Array.isArray(planContext)) {
577
+ const contextText = planContext.map(c => (c.task_name || '') + ' ' + (c.action_excerpt || '')).join(' ').toLowerCase();
578
+
579
+ // Check if plan context mentions this file's domain
580
+ if (contextText.includes(fileName.toLowerCase())) {
581
+ // Both branches have plan context for this file's domain -- higher risk
582
+ risk = 'warning';
583
+ domain = fileName + ' (plan context overlap)';
584
+ }
585
+ }
586
+
587
+ // Check for same-function/class/exports modifications using diff
588
+ const oursDiff = execGit(cwd, ['diff', 'MERGE_HEAD...HEAD', '--', filePath]);
589
+ const theirsDiff = execGit(cwd, ['diff', 'HEAD...MERGE_HEAD', '--', filePath]);
590
+
591
+ if (oursDiff.exitCode === 0 && theirsDiff.exitCode === 0) {
592
+ const oursFunctions = extractModifiedFunctions(oursDiff.stdout);
593
+ const theirsFunctions = extractModifiedFunctions(theirsDiff.stdout);
594
+
595
+ const sharedFunctions = oursFunctions.filter(f => theirsFunctions.includes(f));
596
+ if (sharedFunctions.length > 0) {
597
+ risk = 'warning';
598
+ domain = sharedFunctions.join(', ');
599
+ }
600
+ }
601
+
602
+ warnings.push({
603
+ file: filePath,
604
+ domain,
605
+ ours_changes: oursChanges.slice(0, 200),
606
+ theirs_changes: theirsChanges.slice(0, 200),
607
+ risk,
608
+ });
609
+ }
610
+
611
+ return warnings;
612
+ }
613
+
614
+ /**
615
+ * Extract function/class names from a git diff output.
616
+ *
617
+ * @param {string} diffOutput - Output from git diff
618
+ * @returns {string[]}
619
+ */
620
+ function extractModifiedFunctions(diffOutput) {
621
+ const functions = [];
622
+ const lines = diffOutput.split('\n');
623
+
624
+ for (const line of lines) {
625
+ if (!line.startsWith('+') && !line.startsWith('-')) continue;
626
+ if (line.startsWith('+++') || line.startsWith('---')) continue;
627
+
628
+ const content = line.slice(1);
629
+ const funcMatch = content.match(/^\s*(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:function|async\s+function|\(|async\s*\())/);
630
+ if (funcMatch) {
631
+ functions.push(funcMatch[1] || funcMatch[2]);
632
+ }
633
+
634
+ const classMatch = content.match(/^\s*class\s+(\w+)/);
635
+ if (classMatch) {
636
+ functions.push(classMatch[1]);
637
+ }
638
+
639
+ const exportsMatch = content.match(/^\s*module\.exports\s*[.=]/);
640
+ if (exportsMatch) {
641
+ functions.push('module.exports');
642
+ }
643
+ }
644
+
645
+ return [...new Set(functions)];
646
+ }
647
+
648
+ // ─── Section 6: Cascading Context ────────────────────────────────────────────
649
+
650
+ /**
651
+ * Extract learnings from completed resolution results.
652
+ *
653
+ * @param {Array<{file: string, strategies: Array, result: Object}>} resolutionResults
654
+ * @returns {{files_resolved: string[], strategies_used: Array, domain_changes: Object, phase_merges_completed: number}}
655
+ */
656
+ function createLearningContext(resolutionResults) {
657
+ const filesResolved = [];
658
+ const strategiesUsed = [];
659
+ const domainChanges = {};
660
+
661
+ for (const res of resolutionResults) {
662
+ filesResolved.push(res.file);
663
+
664
+ if (res.strategies) {
665
+ for (const s of res.strategies) {
666
+ strategiesUsed.push({ file: res.file, strategy: s.strategy });
667
+ }
668
+ }
669
+
670
+ // Extract domain from file path
671
+ const domain = path.basename(res.file, path.extname(res.file));
672
+ domainChanges[domain] = `Resolved via ${(res.strategies || []).map(s => s.strategy).join(', ')}`;
673
+ }
674
+
675
+ return {
676
+ files_resolved: filesResolved,
677
+ strategies_used: strategiesUsed,
678
+ domain_changes: domainChanges,
679
+ phase_merges_completed: 1,
680
+ };
681
+ }
682
+
683
+ /**
684
+ * Accumulate learnings across sequential phase merges.
685
+ *
686
+ * @param {Object|null} existing - Existing learnings context
687
+ * @param {Object} newLearnings - New learnings to merge
688
+ * @returns {{files_resolved: string[], strategies_used: Array, domain_changes: Object, phase_merges_completed: number}}
689
+ */
690
+ function mergeLearningContexts(existing, newLearnings) {
691
+ if (!existing) return { ...newLearnings };
692
+
693
+ return {
694
+ files_resolved: [...(existing.files_resolved || []), ...(newLearnings.files_resolved || [])],
695
+ strategies_used: [...(existing.strategies_used || []), ...(newLearnings.strategies_used || [])],
696
+ domain_changes: { ...(existing.domain_changes || {}), ...(newLearnings.domain_changes || {}) },
697
+ phase_merges_completed: (existing.phase_merges_completed || 0) + (newLearnings.phase_merges_completed || 0),
698
+ };
699
+ }
700
+
701
+ // ─── Section 7: CLI Command Handlers ─────────────────────────────────────────
702
+
703
+ /**
704
+ * Orchestrate the full conflict resolution flow.
705
+ *
706
+ * 1. Detect conflicted files
707
+ * 2. For each file: get context, classify hunks, select strategy, resolve
708
+ * 3. Write resolved content to files
709
+ * 4. Run verification
710
+ * 5. If verification fails: roll back resolved files
711
+ * 6. If verification passes: record resolutions, build report
712
+ * 7. Output JSON summary
713
+ *
714
+ * @param {string} cwd - Working directory
715
+ * @param {string} phaseDir - Phase directory for context and report output
716
+ * @param {boolean} raw - Raw output mode
717
+ * @param {string|null} learningsFile - Path to accumulated learnings JSON file
718
+ */
719
+ function cmdConflictAgentRun(cwd, phaseDir, raw, learningsFile) {
720
+ if (!phaseDir) error('--phase-dir required for conflict-agent run');
721
+
722
+ const absPhaseDir = path.isAbsolute(phaseDir) ? phaseDir : path.join(cwd, phaseDir);
723
+
724
+ // Load accumulated learnings from prior phase merges
725
+ let learnings = null;
726
+ if (learningsFile) {
727
+ try {
728
+ const learningsContent = safeReadFile(learningsFile);
729
+ if (learningsContent) {
730
+ learnings = JSON.parse(learningsContent);
731
+ }
732
+ } catch {
733
+ // Learnings file not parseable -- proceed without
734
+ }
735
+ }
736
+ const timestamp = new Date().toISOString();
737
+
738
+ // Step 1: Detect conflicted files
739
+ const diffResult = execGit(cwd, ['diff', '--name-only', '--diff-filter=U']);
740
+ const conflictedFiles = diffResult.exitCode === 0 && diffResult.stdout
741
+ ? diffResult.stdout.split('\n').filter(f => f.trim())
742
+ : [];
743
+
744
+ if (conflictedFiles.length === 0) {
745
+ // No conflicts -- check for semantic conflicts in cleanly merged files
746
+ const mergeHeadResult = execGit(cwd, ['rev-parse', 'MERGE_HEAD']);
747
+ let semanticWarnings = [];
748
+
749
+ if (mergeHeadResult.exitCode === 0) {
750
+ // Get list of all files modified in the merge
751
+ const mergeFiles = execGit(cwd, ['diff', '--name-only', 'HEAD...MERGE_HEAD']);
752
+ if (mergeFiles.exitCode === 0 && mergeFiles.stdout) {
753
+ const cleanFiles = mergeFiles.stdout.split('\n').filter(f => f.trim());
754
+ semanticWarnings = detectSemanticConflicts(cwd, cleanFiles, null);
755
+ }
756
+ }
757
+
758
+ output({
759
+ resolved_count: 0,
760
+ escalated_count: 0,
761
+ verified: true,
762
+ semantic_warnings: semanticWarnings.length,
763
+ report_path: null,
764
+ }, raw);
765
+ return;
766
+ }
767
+
768
+ // Step 2: For each file, get context, classify, select strategy, resolve
769
+ const fileResults = [];
770
+ const resolvedFilePaths = [];
771
+ const originalContents = {};
772
+
773
+ for (const filePath of conflictedFiles) {
774
+ const fullPath = path.join(cwd, filePath);
775
+ const fileContent = safeReadFile(fullPath);
776
+ if (!fileContent) continue;
777
+
778
+ // Save original for rollback
779
+ originalContents[filePath] = fileContent;
780
+
781
+ // Assemble plan context from phase dir
782
+ let planContext = [];
783
+ try {
784
+ const phaseFiles = fs.readdirSync(absPhaseDir);
785
+ const planFiles = phaseFiles.filter(f => f.match(/\d+-PLAN\.md$/i) || f === 'PLAN.md');
786
+
787
+ for (const planFile of planFiles) {
788
+ const planContent = safeReadFile(path.join(absPhaseDir, planFile));
789
+ if (!planContent) continue;
790
+
791
+ const fm = extractFrontmatter(planContent);
792
+ const planNumber = fm.plan || planFile.replace(/-PLAN\.md$/i, '');
793
+
794
+ const taskPattern = /<task\b[^>]*>([\s\S]*?)<\/task>/g;
795
+ let match;
796
+ while ((match = taskPattern.exec(planContent)) !== null) {
797
+ const taskBody = match[1];
798
+ const nameMatch = taskBody.match(/<name>([\s\S]*?)<\/name>/);
799
+ const filesMatch = taskBody.match(/<files>([\s\S]*?)<\/files>/);
800
+ const actionMatch = taskBody.match(/<action>([\s\S]*?)<\/action>/);
801
+
802
+ if (filesMatch) {
803
+ const filesList = filesMatch[1].split(/[\n,]/).map(f => f.trim()).filter(f => f);
804
+ const normalizedFilePath = filePath.replace(/^\.\//, '');
805
+
806
+ if (filesList.some(f => normalizedFilePath.includes(f.replace(/^\.\//, '')) || f.replace(/^\.\//, '').includes(normalizedFilePath))) {
807
+ planContext.push({
808
+ plan: planNumber,
809
+ task_name: nameMatch ? nameMatch[1].trim() : 'unnamed',
810
+ action_excerpt: actionMatch ? actionMatch[1].trim().slice(0, 200) : '',
811
+ });
812
+ }
813
+ }
814
+ }
815
+ }
816
+ } catch {
817
+ // Phase dir not readable
818
+ }
819
+
820
+ // Classify hunks
821
+ const classifiedHunks = classifyConflictHunks(fileContent, planContext.length > 0 ? planContext : null);
822
+
823
+ // Select strategies
824
+ const strategies = selectResolutionStrategy(classifiedHunks, planContext, learnings);
825
+
826
+ // Resolve file content
827
+ const result = resolveFileContent(fileContent, strategies, null);
828
+
829
+ fileResults.push({
830
+ file: filePath,
831
+ hunks: classifiedHunks,
832
+ strategies,
833
+ result,
834
+ });
835
+
836
+ // Step 3: Write resolved content to file
837
+ if (!result.has_unresolved) {
838
+ fs.writeFileSync(fullPath, result.resolved_content, 'utf-8');
839
+ resolvedFilePaths.push(filePath);
840
+ }
841
+ }
842
+
843
+ // Step 4: Run verification
844
+ const verification = verifyResolution(cwd, resolvedFilePaths);
845
+
846
+ if (!verification.passed) {
847
+ // Step 5: Roll back resolved files
848
+ for (const filePath of resolvedFilePaths) {
849
+ const fullPath = path.join(cwd, filePath);
850
+ if (originalContents[filePath]) {
851
+ fs.writeFileSync(fullPath, originalContents[filePath], 'utf-8');
852
+ }
853
+ }
854
+
855
+ output({
856
+ resolved_count: 0,
857
+ escalated_count: fileResults.reduce((acc, fr) => acc + (fr.result?.escalated || 0), 0),
858
+ verified: false,
859
+ verification_errors: verification.errors,
860
+ semantic_warnings: 0,
861
+ report_path: null,
862
+ }, raw);
863
+ return;
864
+ }
865
+
866
+ // Step 6: Record resolutions and build report
867
+ // Record each resolution via merge-conflicts resolved (internal function call)
868
+ for (const fr of fileResults) {
869
+ if (fr.result && !fr.result.has_unresolved) {
870
+ const resolutionJson = JSON.stringify({
871
+ strategy: fr.strategies.length === 1 ? fr.strategies[0].strategy : 'combined',
872
+ description: fr.strategies.map(s => s.reasoning).join('; '),
873
+ confidence: fr.strategies.reduce((min, s) => {
874
+ const order = { HIGH: 3, MEDIUM: 2, LOW: 1 };
875
+ return (order[s.confidence] || 0) < (order[min] || 0) ? s.confidence : min;
876
+ }, 'HIGH'),
877
+ });
878
+
879
+ // Write resolution record
880
+ const resolutionsPath = path.join(absPhaseDir, 'RESOLUTIONS.md');
881
+ let existing = safeReadFile(resolutionsPath);
882
+ if (!existing) {
883
+ const phaseName = path.basename(absPhaseDir);
884
+ existing = `# Merge Conflict Resolutions\n\n**Phase:** ${phaseName}\n**Created:** ${timestamp}\n\n---\n\n`;
885
+ }
886
+
887
+ const res = JSON.parse(resolutionJson);
888
+ const entry = [
889
+ `### ${fr.file}\n`,
890
+ `- **Strategy:** ${res.strategy}`,
891
+ `- **Confidence:** ${res.confidence}`,
892
+ `- **Description:** ${res.description || 'No description provided'}`,
893
+ `- **Resolved:** ${timestamp}`,
894
+ '',
895
+ ].join('\n');
896
+
897
+ fs.writeFileSync(resolutionsPath, existing + entry + '\n', 'utf-8');
898
+ }
899
+ }
900
+
901
+ // Detect semantic conflicts in cleanly merged files
902
+ let semanticWarnings = [];
903
+ const mergeHeadResult = execGit(cwd, ['rev-parse', 'MERGE_HEAD']);
904
+ if (mergeHeadResult.exitCode === 0) {
905
+ const allDiff = execGit(cwd, ['diff', '--name-only', 'HEAD...MERGE_HEAD']);
906
+ if (allDiff.exitCode === 0 && allDiff.stdout) {
907
+ const allFiles = allDiff.stdout.split('\n').filter(f => f.trim());
908
+ const cleanFiles = allFiles.filter(f => !conflictedFiles.includes(f));
909
+ if (cleanFiles.length > 0) {
910
+ semanticWarnings = detectSemanticConflicts(cwd, cleanFiles, null);
911
+ }
912
+ }
913
+ }
914
+
915
+ // Get branch info for report
916
+ const branchResult = execGit(cwd, ['branch', '--show-current']);
917
+ const oursBranch = branchResult.exitCode === 0 ? branchResult.stdout : 'unknown';
918
+ let theirsBranch = 'unknown';
919
+ if (mergeHeadResult.exitCode === 0) {
920
+ const theirsRef = execGit(cwd, ['log', '-1', '--format=%D', 'MERGE_HEAD']);
921
+ if (theirsRef.exitCode === 0 && theirsRef.stdout) {
922
+ theirsBranch = theirsRef.stdout;
923
+ }
924
+ }
925
+
926
+ // Build report
927
+ const reportContent = buildResolutionReport(fileResults, {
928
+ branches: { ours: oursBranch, theirs: theirsBranch },
929
+ phase: path.basename(absPhaseDir),
930
+ timestamp,
931
+ semantic_warnings: semanticWarnings,
932
+ });
933
+
934
+ const reportPath = path.join(absPhaseDir, 'RESOLUTION-REPORT.md');
935
+ fs.writeFileSync(reportPath, reportContent, 'utf-8');
936
+
937
+ // Step 7: Output summary
938
+ const totalResolved = fileResults.reduce((acc, fr) => acc + (fr.result?.applied || 0), 0);
939
+ const totalEscalated = fileResults.reduce((acc, fr) => acc + (fr.result?.escalated || 0), 0);
940
+
941
+ // Extract learnings from resolution results for cascading context
942
+ const newLearnings = createLearningContext(fileResults);
943
+
944
+ output({
945
+ resolved_count: totalResolved,
946
+ escalated_count: totalEscalated,
947
+ verified: true,
948
+ semantic_warnings: semanticWarnings.length,
949
+ report_path: reportPath,
950
+ learnings: newLearnings,
951
+ }, raw);
952
+ }
953
+
954
+ /**
955
+ * Resolve a single file (used by escalation retry flow).
956
+ *
957
+ * @param {string} cwd - Working directory
958
+ * @param {string} filePath - Path to the conflicted file
959
+ * @param {string} phaseDir - Phase directory for context
960
+ * @param {boolean} raw - Raw output mode
961
+ * @param {string|null} hint - Optional user hint text for guiding resolution strategy
962
+ */
963
+ function cmdConflictAgentResolveFile(cwd, filePath, phaseDir, raw, hint) {
964
+ if (!filePath) error('File path required');
965
+ if (!phaseDir) error('--phase-dir required for resolve-file command');
966
+
967
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
968
+ const fileContent = safeReadFile(fullPath);
969
+
970
+ if (!fileContent) {
971
+ error('File not found: ' + filePath);
972
+ }
973
+
974
+ if (!/^<{7}/m.test(fileContent)) {
975
+ error('File has no conflict markers: ' + filePath);
976
+ }
977
+
978
+ const absPhaseDir = path.isAbsolute(phaseDir) ? phaseDir : path.join(cwd, phaseDir);
979
+
980
+ // Assemble plan context
981
+ let planContext = [];
982
+ try {
983
+ const phaseFiles = fs.readdirSync(absPhaseDir);
984
+ const planFiles = phaseFiles.filter(f => f.match(/\d+-PLAN\.md$/i) || f === 'PLAN.md');
985
+
986
+ for (const planFile of planFiles) {
987
+ const planContent = safeReadFile(path.join(absPhaseDir, planFile));
988
+ if (!planContent) continue;
989
+
990
+ const fm = extractFrontmatter(planContent);
991
+ const planNumber = fm.plan || planFile.replace(/-PLAN\.md$/i, '');
992
+
993
+ const taskPattern = /<task\b[^>]*>([\s\S]*?)<\/task>/g;
994
+ let match;
995
+ while ((match = taskPattern.exec(planContent)) !== null) {
996
+ const taskBody = match[1];
997
+ const nameMatch = taskBody.match(/<name>([\s\S]*?)<\/name>/);
998
+ const filesMatch = taskBody.match(/<files>([\s\S]*?)<\/files>/);
999
+ const actionMatch = taskBody.match(/<action>([\s\S]*?)<\/action>/);
1000
+
1001
+ if (filesMatch) {
1002
+ const filesList = filesMatch[1].split(/[\n,]/).map(f => f.trim()).filter(f => f);
1003
+ const normalizedFilePath = filePath.replace(/^\.\//, '');
1004
+
1005
+ if (filesList.some(f => normalizedFilePath.includes(f.replace(/^\.\//, '')) || f.replace(/^\.\//, '').includes(normalizedFilePath))) {
1006
+ planContext.push({
1007
+ plan: planNumber,
1008
+ task_name: nameMatch ? nameMatch[1].trim() : 'unnamed',
1009
+ action_excerpt: actionMatch ? actionMatch[1].trim().slice(0, 200) : '',
1010
+ });
1011
+ }
1012
+ }
1013
+ }
1014
+ }
1015
+ } catch {
1016
+ // Phase dir not readable
1017
+ }
1018
+
1019
+ // Classify and resolve
1020
+ const classifiedHunks = classifyConflictHunks(fileContent, planContext.length > 0 ? planContext : null);
1021
+
1022
+ // Augment plan context with user hint if provided
1023
+ let augmentedContext = planContext;
1024
+ if (hint && hint.trim()) {
1025
+ augmentedContext = [...planContext, {
1026
+ plan: 'user-hint',
1027
+ task_name: 'User escalation hint',
1028
+ action_excerpt: hint.trim(),
1029
+ }];
1030
+ }
1031
+ const strategies = selectResolutionStrategy(classifiedHunks, augmentedContext, null);
1032
+ const result = resolveFileContent(fileContent, strategies, null);
1033
+
1034
+ // Write resolved content
1035
+ if (!result.has_unresolved) {
1036
+ fs.writeFileSync(fullPath, result.resolved_content, 'utf-8');
1037
+ }
1038
+
1039
+ output({
1040
+ file: filePath,
1041
+ hint_applied: !!hint,
1042
+ hunks: classifiedHunks.length,
1043
+ applied: result.applied,
1044
+ escalated: result.escalated,
1045
+ has_unresolved: result.has_unresolved,
1046
+ strategies: strategies.map(s => ({ strategy: s.strategy, confidence: s.confidence })),
1047
+ }, raw);
1048
+ }
1049
+
1050
+ // ─── Exports ─────────────────────────────────────────────────────────────────
1051
+
1052
+ module.exports = {
1053
+ resolveConflicts: cmdConflictAgentRun,
1054
+ verifyResolution,
1055
+ detectSemanticConflicts,
1056
+ selectResolutionStrategy,
1057
+ resolveFileContent,
1058
+ buildResolutionReport,
1059
+ createLearningContext,
1060
+ mergeLearningContexts,
1061
+ cmdConflictAgentRun,
1062
+ cmdConflictAgentResolveFile,
1063
+ };