@telora/daemon 0.23.14 → 0.23.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/build-info.json +4 -3
  2. package/dist/backends/claude/one-shot-model.d.ts +11 -0
  3. package/dist/backends/claude/one-shot-model.d.ts.map +1 -1
  4. package/dist/backends/claude/one-shot-model.js +7 -4
  5. package/dist/backends/claude/one-shot-model.js.map +1 -1
  6. package/dist/backends/claude/one-shot-session.d.ts +15 -0
  7. package/dist/backends/claude/one-shot-session.d.ts.map +1 -0
  8. package/dist/backends/claude/one-shot-session.js +120 -0
  9. package/dist/backends/claude/one-shot-session.js.map +1 -0
  10. package/dist/ci/candidate-ref.d.ts +28 -0
  11. package/dist/ci/candidate-ref.d.ts.map +1 -0
  12. package/dist/ci/candidate-ref.js +81 -0
  13. package/dist/ci/candidate-ref.js.map +1 -0
  14. package/dist/ci/ci-sensor-loop.d.ts.map +1 -1
  15. package/dist/ci/ci-sensor-loop.js +2 -1
  16. package/dist/ci/ci-sensor-loop.js.map +1 -1
  17. package/dist/ci/ci-status-git.d.ts +9 -0
  18. package/dist/ci/ci-status-git.d.ts.map +1 -1
  19. package/dist/ci/ci-status-git.js +16 -2
  20. package/dist/ci/ci-status-git.js.map +1 -1
  21. package/dist/ci/ci-status.d.ts.map +1 -1
  22. package/dist/ci/ci-status.js +5 -1
  23. package/dist/ci/ci-status.js.map +1 -1
  24. package/dist/completion/state-cascade/state-cascade.d.ts +14 -1
  25. package/dist/completion/state-cascade/state-cascade.d.ts.map +1 -1
  26. package/dist/completion/state-cascade/state-cascade.js +26 -1
  27. package/dist/completion/state-cascade/state-cascade.js.map +1 -1
  28. package/dist/completion/team-exit/merge-phase.d.ts +11 -10
  29. package/dist/completion/team-exit/merge-phase.d.ts.map +1 -1
  30. package/dist/completion/team-exit/merge-phase.js +10 -10
  31. package/dist/completion/team-exit/merge-phase.js.map +1 -1
  32. package/dist/completion/team-teardown/team-lifecycle.d.ts +4 -3
  33. package/dist/completion/team-teardown/team-lifecycle.d.ts.map +1 -1
  34. package/dist/completion/team-teardown/team-lifecycle.js +8 -8
  35. package/dist/completion/team-teardown/team-lifecycle.js.map +1 -1
  36. package/dist/composition/execution-git-context.d.ts.map +1 -1
  37. package/dist/composition/execution-git-context.js +2 -0
  38. package/dist/composition/execution-git-context.js.map +1 -1
  39. package/dist/composition/execution-merge-context.d.ts +11 -3
  40. package/dist/composition/execution-merge-context.d.ts.map +1 -1
  41. package/dist/composition/execution-merge-context.js +60 -12
  42. package/dist/composition/execution-merge-context.js.map +1 -1
  43. package/dist/composition/process-close-context.js +3 -3
  44. package/dist/composition/process-close-context.js.map +1 -1
  45. package/dist/composition/state-cascade-context.d.ts.map +1 -1
  46. package/dist/composition/state-cascade-context.js +10 -2
  47. package/dist/composition/state-cascade-context.js.map +1 -1
  48. package/dist/composition/team-lifecycle-context.js +3 -3
  49. package/dist/composition/team-lifecycle-context.js.map +1 -1
  50. package/dist/composition/verification-context.d.ts.map +1 -1
  51. package/dist/composition/verification-context.js +4 -23
  52. package/dist/composition/verification-context.js.map +1 -1
  53. package/dist/delivery/delivery-merge-ops.d.ts +2 -0
  54. package/dist/delivery/delivery-merge-ops.d.ts.map +1 -1
  55. package/dist/delivery/delivery-merge-ops.js +2 -2
  56. package/dist/delivery/delivery-merge-ops.js.map +1 -1
  57. package/dist/delivery/delivery-reconciler.d.ts +9 -0
  58. package/dist/delivery/delivery-reconciler.d.ts.map +1 -1
  59. package/dist/delivery/delivery-reconciler.js +42 -31
  60. package/dist/delivery/delivery-reconciler.js.map +1 -1
  61. package/dist/focus/focus-spawn-decision.d.ts.map +1 -1
  62. package/dist/focus/focus-spawn-decision.js +1 -0
  63. package/dist/focus/focus-spawn-decision.js.map +1 -1
  64. package/dist/git/index.d.ts.map +1 -1
  65. package/dist/git/index.js +2 -1
  66. package/dist/git/index.js.map +1 -1
  67. package/dist/git/merge.d.ts +12 -2
  68. package/dist/git/merge.d.ts.map +1 -1
  69. package/dist/git/merge.js +32 -33
  70. package/dist/git/merge.js.map +1 -1
  71. package/dist/merge/approval-gate-state.d.ts.map +1 -1
  72. package/dist/merge/approval-gate-state.js +8 -7
  73. package/dist/merge/approval-gate-state.js.map +1 -1
  74. package/dist/merge/candidate-rework.d.ts +44 -0
  75. package/dist/merge/candidate-rework.d.ts.map +1 -0
  76. package/dist/merge/candidate-rework.js +152 -0
  77. package/dist/merge/candidate-rework.js.map +1 -0
  78. package/dist/merge/completion-hold-record.d.ts +20 -0
  79. package/dist/merge/completion-hold-record.d.ts.map +1 -0
  80. package/dist/merge/completion-hold-record.js +130 -0
  81. package/dist/merge/completion-hold-record.js.map +1 -0
  82. package/dist/merge/completion-merge-driver.d.ts +96 -0
  83. package/dist/merge/completion-merge-driver.d.ts.map +1 -0
  84. package/dist/merge/completion-merge-driver.js +94 -0
  85. package/dist/merge/completion-merge-driver.js.map +1 -0
  86. package/dist/merge/completion-merge-gate.d.ts +71 -0
  87. package/dist/merge/completion-merge-gate.d.ts.map +1 -0
  88. package/dist/merge/completion-merge-gate.js +185 -0
  89. package/dist/merge/completion-merge-gate.js.map +1 -0
  90. package/dist/merge/completion-merge-pending.d.ts +30 -0
  91. package/dist/merge/completion-merge-pending.d.ts.map +1 -0
  92. package/dist/merge/completion-merge-pending.js +75 -0
  93. package/dist/merge/completion-merge-pending.js.map +1 -0
  94. package/dist/merge/focus-merge.d.ts +21 -6
  95. package/dist/merge/focus-merge.d.ts.map +1 -1
  96. package/dist/merge/focus-merge.js +65 -45
  97. package/dist/merge/focus-merge.js.map +1 -1
  98. package/dist/merge/focus-worktree-removal.d.ts +6 -6
  99. package/dist/merge/focus-worktree-removal.d.ts.map +1 -1
  100. package/dist/merge/focus-worktree-removal.js +10 -6
  101. package/dist/merge/focus-worktree-removal.js.map +1 -1
  102. package/dist/merge/merge-back-conflict.d.ts.map +1 -1
  103. package/dist/merge/merge-back-conflict.js +2 -0
  104. package/dist/merge/merge-back-conflict.js.map +1 -1
  105. package/dist/merge/merge-back-followup.d.ts.map +1 -1
  106. package/dist/merge/merge-back-followup.js +7 -0
  107. package/dist/merge/merge-back-followup.js.map +1 -1
  108. package/dist/merge/merge-back-loop.d.ts +36 -14
  109. package/dist/merge/merge-back-loop.d.ts.map +1 -1
  110. package/dist/merge/merge-back-loop.js +109 -66
  111. package/dist/merge/merge-back-loop.js.map +1 -1
  112. package/dist/merge/merge-gate-coverage.d.ts +9 -61
  113. package/dist/merge/merge-gate-coverage.d.ts.map +1 -1
  114. package/dist/merge/merge-gate-coverage.js +33 -167
  115. package/dist/merge/merge-gate-coverage.js.map +1 -1
  116. package/dist/nexus/escalation-bridge.d.ts.map +1 -1
  117. package/dist/nexus/escalation-bridge.js +2 -1
  118. package/dist/nexus/escalation-bridge.js.map +1 -1
  119. package/dist/nexus/file-escalation.d.ts.map +1 -1
  120. package/dist/nexus/file-escalation.js +5 -1
  121. package/dist/nexus/file-escalation.js.map +1 -1
  122. package/dist/queries/api-client/shared.js +1 -1
  123. package/dist/queries/deliveries/deliveries.d.ts.map +1 -1
  124. package/dist/queries/deliveries/deliveries.js +1 -0
  125. package/dist/queries/deliveries/deliveries.js.map +1 -1
  126. package/dist/queries/delivery-gate-state/delivery-gate-state.js +1 -1
  127. package/dist/queries/delivery-gate-state/verify-own-holds.d.ts +10 -3
  128. package/dist/queries/delivery-gate-state/verify-own-holds.d.ts.map +1 -1
  129. package/dist/queries/delivery-gate-state/verify-own-holds.js +2 -0
  130. package/dist/queries/delivery-gate-state/verify-own-holds.js.map +1 -1
  131. package/dist/queries/escalations/cause-key.d.ts.map +1 -1
  132. package/dist/queries/escalations/cause-key.js +1 -0
  133. package/dist/queries/escalations/cause-key.js.map +1 -1
  134. package/dist/queries/escalations/escalations.d.ts.map +1 -1
  135. package/dist/queries/escalations/escalations.js +2 -1
  136. package/dist/queries/escalations/escalations.js.map +1 -1
  137. package/dist/queries/focuses/focuses.d.ts +12 -2
  138. package/dist/queries/focuses/focuses.d.ts.map +1 -1
  139. package/dist/queries/focuses/focuses.js +24 -3
  140. package/dist/queries/focuses/focuses.js.map +1 -1
  141. package/dist/spawn/review-hold-park.d.ts.map +1 -1
  142. package/dist/spawn/review-hold-park.js +1 -0
  143. package/dist/spawn/review-hold-park.js.map +1 -1
  144. package/dist/supervision/agent-resolvable-retirement.d.ts +1 -1
  145. package/dist/supervision/agent-resolvable-retirement.d.ts.map +1 -1
  146. package/dist/supervision/agent-resolvable-retirement.js +2 -1
  147. package/dist/supervision/agent-resolvable-retirement.js.map +1 -1
  148. package/dist/supervision/escalation-filing.js +1 -1
  149. package/dist/supervision/incident-correlation.d.ts +3 -0
  150. package/dist/supervision/incident-correlation.d.ts.map +1 -1
  151. package/dist/supervision/incident-correlation.js +4 -0
  152. package/dist/supervision/incident-correlation.js.map +1 -1
  153. package/dist/supervision/null-role-spawn.d.ts.map +1 -1
  154. package/dist/supervision/null-role-spawn.js +1 -0
  155. package/dist/supervision/null-role-spawn.js.map +1 -1
  156. package/dist/supervision/spawn-cooldown.js +1 -1
  157. package/dist/supervision/stop-rows-work-holds.d.ts +4 -4
  158. package/dist/supervision/stop-rows-work-holds.d.ts.map +1 -1
  159. package/dist/supervision/stop-rows-work-holds.js +17 -15
  160. package/dist/supervision/stop-rows-work-holds.js.map +1 -1
  161. package/dist/testing/fixtures/api-client.js +1 -1
  162. package/dist/testing/fixtures/index.js +1 -1
  163. package/dist/testing/fixtures/judge-session.d.ts +14 -0
  164. package/dist/testing/fixtures/judge-session.d.ts.map +1 -0
  165. package/dist/testing/fixtures/judge-session.js +10 -0
  166. package/dist/testing/fixtures/judge-session.js.map +1 -0
  167. package/dist/types/index.js +1 -1
  168. package/dist/verify/effect-reconciliation/judge-invoker.d.ts +14 -4
  169. package/dist/verify/effect-reconciliation/judge-invoker.d.ts.map +1 -1
  170. package/dist/verify/effect-reconciliation/judge-invoker.js +30 -12
  171. package/dist/verify/effect-reconciliation/judge-invoker.js.map +1 -1
  172. package/dist/verify/effect-reconciliation/verify-reconciliation.d.ts +18 -5
  173. package/dist/verify/effect-reconciliation/verify-reconciliation.d.ts.map +1 -1
  174. package/dist/verify/effect-reconciliation/verify-reconciliation.js +75 -18
  175. package/dist/verify/effect-reconciliation/verify-reconciliation.js.map +1 -1
  176. package/dist/verify/effect-reconciliation/verify-restatement.js +1 -1
  177. package/dist/verify/engine/verification-deps.js +1 -1
  178. package/dist/verify/engine/verification-engine.js +1 -1
  179. package/dist/verify/gate-config/verification-config.js +1 -1
  180. package/dist/verify/inspection-strategies/verification-strategies.js +1 -1
  181. package/dist/verify/inspection-strategies/verification-strategies.js.map +1 -1
  182. package/dist/verify/outcome-routing/incurable-model-hold.d.ts +9 -3
  183. package/dist/verify/outcome-routing/incurable-model-hold.d.ts.map +1 -1
  184. package/dist/verify/outcome-routing/incurable-model-hold.js +18 -5
  185. package/dist/verify/outcome-routing/incurable-model-hold.js.map +1 -1
  186. package/dist/verify/outcome-routing/repeating-model-hold.d.ts +30 -0
  187. package/dist/verify/outcome-routing/repeating-model-hold.d.ts.map +1 -0
  188. package/dist/verify/outcome-routing/repeating-model-hold.js +135 -0
  189. package/dist/verify/outcome-routing/repeating-model-hold.js.map +1 -0
  190. package/dist/verify/outcome-routing/verification-history-reset.d.ts.map +1 -1
  191. package/dist/verify/outcome-routing/verification-history-reset.js +1 -0
  192. package/dist/verify/outcome-routing/verification-history-reset.js.map +1 -1
  193. package/dist/verify/outcome-routing/verification-model-unavailable-hold.d.ts +10 -4
  194. package/dist/verify/outcome-routing/verification-model-unavailable-hold.d.ts.map +1 -1
  195. package/dist/verify/outcome-routing/verification-model-unavailable-hold.js +24 -6
  196. package/dist/verify/outcome-routing/verification-model-unavailable-hold.js.map +1 -1
  197. package/dist/verify/run-selection/default-on-guards.js +1 -1
  198. package/dist/verify/run-selection/pre-review-gate-dispatch.d.ts +4 -3
  199. package/dist/verify/run-selection/pre-review-gate-dispatch.d.ts.map +1 -1
  200. package/dist/verify/run-selection/pre-review-gate-dispatch.js +23 -7
  201. package/dist/verify/run-selection/pre-review-gate-dispatch.js.map +1 -1
  202. package/dist/verify/run-selection/verification-default-on.js +1 -1
  203. package/dist/verify/run-selection/verification-default-on.js.map +1 -1
  204. package/dist/worktree/provision.d.ts.map +1 -1
  205. package/dist/worktree/provision.js +1 -0
  206. package/dist/worktree/provision.js.map +1 -1
  207. package/dist/worktree/safety.d.ts.map +1 -1
  208. package/dist/worktree/safety.js +1 -0
  209. package/dist/worktree/safety.js.map +1 -1
  210. package/package.json +1 -1
@@ -0,0 +1,185 @@
1
+ // PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
2
+ // Pinned by: packages/daemon/src/completion/team-exit/teardown-merge-gate.test.ts,
3
+ // packages/daemon/src/completion/team-teardown/mid-flight-merge-gate.test.ts,
4
+ // packages/daemon/src/merge/attributed-capture-merge-gate.test.ts,
5
+ // packages/daemon/src/merge/completion-merge-gate.test.ts,
6
+ // packages/daemon/src/merge/merge-back-loop.test.ts, packages/daemon/src/merge/merge-gate-coverage.test.ts,
7
+ // packages/daemon/src/merge/resolved-conflict-merge-gate.test.ts,
8
+ // packages/daemon/src/merge/safe-remove-merge-gate.test.ts
9
+ // PINNED-BY-END
10
+ /**
11
+ * Whether a focus branch may reach integration: ONCE, when its focus is
12
+ * complete, by fast-forward to a candidate commit the full CI suite judged green.
13
+ *
14
+ * Every focus-to-integration entry point asks this gate -- the team-exit merge
15
+ * (completion/team-exit/merge-phase.ts attemptFocusMerge), the merge-back loop
16
+ * (merge/merge-back-loop.ts), the mid-flight merge
17
+ * (completion/team-teardown/team-lifecycle.ts), the merge-before-remove
18
+ * (merge/focus-worktree-removal.ts) and the resolved-conflict retry
19
+ * (merge/focus-merge.ts checkResolvedMergeConflicts) -- and every one holds a
20
+ * focus that is still mid-focus. Why: docs/DECISIONS.md, "A focus merges into
21
+ * integration once, at completion".
22
+ *
23
+ * The decision, in order (the first that fails holds):
24
+ * 1. COMPLETE: every focus delivery is done -- a cancelled one too holds, as
25
+ * the agreed criterion reads ("every delivery done"), and so does a focus
26
+ * with no delivery. Delivery facts, never product_focuses.status (humans
27
+ * own focus completion).
28
+ * 2. APPROVED HEAD: the focus's newest current approval
29
+ * (queries/focus-review/focus-review-approval.ts) is on the branch, the gate
30
+ * did not fail at it (merge/approval-gate-state.ts), and every commit after it
31
+ * is a merge git proves CLEAN (merge/merge-gate-coverage.ts isCleanMerge) --
32
+ * an integration sync that adds nothing over git's automatic merge, judged by
33
+ * step 4 rather than by review. A merge after it that DECIDED a conflict
34
+ * changed code no review saw: the next step is REVIEW_MERGE, the focus back
35
+ * to coding so its review judges that head. Commits under the approval obey
36
+ * the capture rule (a daemon WIP capture is never gated by itself;
37
+ * merge/merge-gate-coverage.ts judgeMergeGateCoverage).
38
+ * 3. CONTAINS INTEGRATION: the branch head contains the integration tip; else
39
+ * the next step is MERGE_OUT -- one automatic merge, kept only when clean; a
40
+ * conflict is aborted and handed to the team (merge/focus-merge.ts
41
+ * attemptCompletionMergeOut). No resolver decides a conflict on this path.
42
+ * 4. GREEN CANDIDATE: the head's refs/ci-status reads success (ci/candidate-ref.ts).
43
+ * Not pushed yet -> PUSH_CANDIDATE; pushed, no verdict -> WAIT_CI; red -> REWORK.
44
+ * A hold carries the step that would advance it (`next`) for the merge-back loop,
45
+ * which is the one driver of those steps; the other entry points just hold.
46
+ * An admission names the exact commit that passed (`candidateSha`), and
47
+ * integration advances only by fast-forward to that commit (git/merge.ts
48
+ * mergeToIntegration). Any unreadable input holds (fail closed).
49
+ *
50
+ * @module merge/completion-merge-gate
51
+ */
52
+ import { DELIVERY_STATUS } from '@telora/daemon-core';
53
+ import { stopFires } from '../supervision/stop-registry.js';
54
+ import { judgeMergeGateCoverage, } from './merge-gate-coverage.js';
55
+ /** Step 1: every delivery done (cancelled is not done), at least one delivery. Pure. */
56
+ export function focusCompletionHold(deliveries) {
57
+ const open = deliveries.filter((d) => d.executionStatus !== DELIVERY_STATUS.DONE);
58
+ if (open.length > 0) {
59
+ const names = open.slice(0, 3).map((d) => `"${d.name}" (${d.executionStatus ?? 'unknown'})`).join(', ');
60
+ return `the focus is not complete: ${open.length} delivery(ies) not done -- ${names}${open.length > 3 ? ', ...' : ''}`;
61
+ }
62
+ if (deliveries.length === 0)
63
+ return 'the focus is not complete: no delivery is done';
64
+ return null;
65
+ }
66
+ /** The merges after the approved commit that git cannot prove clean (they decided a conflict, or edited). Pure. */
67
+ export function unreviewedMergesAfter(commits, approval) {
68
+ return commits.filter((c) => !approval.contains.has(c.sha) && c.isMerge && c.cleanMerge !== true);
69
+ }
70
+ /**
71
+ * Step 2 over listed commits: everything after the approved commit must be a
72
+ * merge (an integration sync; one that is not clean is unreviewedMergesAfter's),
73
+ * and what the approval contains must be gated by it under the capture rule. Pure.
74
+ */
75
+ export function approvedHeadHold(commits, approval) {
76
+ const after = commits.filter((c) => !approval.contains.has(c.sha));
77
+ const authoredAfter = after.filter((c) => !c.isMerge);
78
+ if (authoredAfter.length > 0) {
79
+ return `${authoredAfter.length} commit(s) after the approved head ${approval.sha.slice(0, 9)} -- the newest review must approve HEAD`;
80
+ }
81
+ const under = commits.filter((c) => approval.contains.has(c.sha));
82
+ const coverage = judgeMergeGateCoverage(under, [approval]);
83
+ if (coverage.allGated)
84
+ return null;
85
+ const captures = coverage.ungatedWipCaptures.length === 0
86
+ ? ''
87
+ : `, including ${coverage.ungatedWipCaptures.length} daemon WIP capture(s) (${coverage.ungatedWipCaptures.map((c) => c.sha.slice(0, 9)).join(', ')})`;
88
+ return `${coverage.ungated.length} commit(s) not past the focus's review gate${captures}`;
89
+ }
90
+ async function readApproval(target, deps) {
91
+ const approved = await deps.readApprovedReviewSha(target);
92
+ if (approved === null)
93
+ return { hold: 'no current review approves the focus head' };
94
+ const resolved = deps.resolveAnchor(target.repoPath, target.integrationBranch, approved);
95
+ if (resolved === null)
96
+ return { hold: `the approved commit ${approved.slice(0, 9)} is not on ${target.branchName}` };
97
+ const state = await deps.judgeApprovalGate(target, resolved.sha);
98
+ if (state === 'failed')
99
+ return { hold: `the approving review at ${resolved.sha.slice(0, 9)} anchors nothing: the gate verdict there FAILED` };
100
+ if (state === 'pending') {
101
+ return { hold: `the approving review at ${resolved.sha.slice(0, 9)} anchors nothing: no gate verdict is recorded there yet and a gate is configured for the focus` };
102
+ }
103
+ return { ...resolved, source: state === 'auto_approval' ? 'auto_approval' : 'approved_review' };
104
+ }
105
+ /** Steps 3-4: the candidate. Returns the admission when the head is green. */
106
+ function candidateStage(target, deps) {
107
+ const head = deps.resolveBranchHead(target.repoPath, target.branchName);
108
+ if (head === null)
109
+ return { hold: `the head of ${target.branchName} could not be read` };
110
+ const tip = deps.resolveIntegrationTip(target);
111
+ if (tip === null)
112
+ return { hold: `the ${target.integrationBranch} tip could not be read` };
113
+ if (!deps.isAncestor(target.repoPath, tip, head)) {
114
+ return { hold: `${target.branchName} does not contain the ${target.integrationBranch} tip ${tip.slice(0, 9)} yet`, next: 'merge_out', headSha: head };
115
+ }
116
+ const status = deps.readCandidateStatus(target, head);
117
+ if (status === 'green')
118
+ return { candidateSha: head };
119
+ if (status === 'red')
120
+ return { hold: `CI is RED on the integration candidate ${head.slice(0, 9)}`, next: 'rework', headSha: head };
121
+ if (deps.readPushedCandidateSha(target) !== head) {
122
+ return { hold: `the integration candidate ${head.slice(0, 9)} has not been pushed for CI`, next: 'push_candidate', headSha: head };
123
+ }
124
+ return { hold: `CI has not judged the integration candidate ${head.slice(0, 9)} yet`, next: 'wait_ci', headSha: head };
125
+ }
126
+ async function decide(target, deps) {
127
+ const incomplete = focusCompletionHold(await deps.getFocusDeliveries(target.focusId));
128
+ if (incomplete !== null)
129
+ return { hold: incomplete, incomplete: true };
130
+ const approval = await readApproval(target, deps);
131
+ if ('hold' in approval)
132
+ return approval;
133
+ const commits = deps.listUnmergedCommits(target.repoPath, target.integrationBranch, target.branchName);
134
+ if (commits === null)
135
+ return { hold: `its gate coverage could not be read (the commits ${target.branchName} would add could not be listed)` };
136
+ const unapproved = approvedHeadHold(commits, approval);
137
+ if (unapproved !== null)
138
+ return { hold: unapproved };
139
+ const decided = unreviewedMergesAfter(commits, approval);
140
+ if (decided.length > 0 && commits[0]) {
141
+ return {
142
+ hold: `${decided.length} merge(s) after the approved head ${approval.sha.slice(0, 9)} decided a conflict (${decided.map((c) => c.sha.slice(0, 9)).join(', ')}) -- no review judged that code`,
143
+ next: 'review_merge',
144
+ headSha: commits[0].sha,
145
+ };
146
+ }
147
+ const candidate = candidateStage(target, deps);
148
+ if ('hold' in candidate)
149
+ return candidate;
150
+ return {
151
+ hold: null,
152
+ candidateSha: candidate.candidateSha,
153
+ admittedBy: `completion: ${approval.source} ${approval.sha.slice(0, 9)}, candidate ${candidate.candidateSha.slice(0, 9)} green`,
154
+ };
155
+ }
156
+ /**
157
+ * Whether a merge of `target` holds (why, and the step that advances it) or is
158
+ * admitted (at which commit). Never throws: a failed read holds. Whether it holds
159
+ * is the merge_gate_coverage stop-registry row's decision.
160
+ */
161
+ export async function readMergeGateDecision(target, deps) {
162
+ let decision;
163
+ let readable = true;
164
+ try {
165
+ decision = await decide(target, deps);
166
+ }
167
+ catch (err) {
168
+ readable = false;
169
+ decision = { hold: `its gate coverage could not be read (${err.message})` };
170
+ }
171
+ if (!stopFires('merge_gate_coverage', { gateReadable: readable, admitted: decision.hold === null }))
172
+ return decision;
173
+ return decision.hold === null ? { hold: 'held by the merge_gate_coverage stop row' } : decision;
174
+ }
175
+ /**
176
+ * The decision for a branch whose focus may be unknown. With no focus there is
177
+ * no completion to read, so the merge holds.
178
+ */
179
+ export function readMergeGateDecisionForBranch(target, deps) {
180
+ const { focusId, ...rest } = target;
181
+ if (focusId !== null)
182
+ return readMergeGateDecision({ ...rest, focusId }, deps);
183
+ return Promise.resolve({ hold: `no focus names ${target.branchName}, so it has no completion to merge on` });
184
+ }
185
+ //# sourceMappingURL=completion-merge-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-merge-gate.js","sourceRoot":"","sources":["../../src/merge/completion-merge-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /** What git says of the focus branch against integration. */
2
+ export type BranchAheadRead = 'ahead' | 'contained' | 'unknown';
3
+ /** Read whether `branchName` carries commits `integrationBranch` lacks; 'unknown' when git cannot tell. */
4
+ export declare function readBranchAhead(repoPath: string, branchName: string, integrationBranch: string): BranchAheadRead;
5
+ /** Whether a window opening now marks the merge pending: unless integration provably contains the branch. Pure. */
6
+ export declare function completionMergePendingFor(read: BranchAheadRead): boolean;
7
+ /** The writes the reconciliation makes; bound at the composition root (composition/state-cascade-context.ts). */
8
+ export interface CompletionMergePendingDeps {
9
+ /** Mark the focus's completion merge pending (sets completion_merge_pending_at). */
10
+ mark: (focusId: string) => Promise<void>;
11
+ /** The merge landed: clear completion_merge_pending_at. */
12
+ clear: (focusId: string) => Promise<void>;
13
+ }
14
+ /** One focus's facts this pass. */
15
+ export interface CompletionMergePendingFacts {
16
+ focusId: string;
17
+ reviewWindowOpen: boolean;
18
+ /** Every delivery done or cancelled (the arm clear's own terminality). */
19
+ deliveriesTerminal: boolean;
20
+ branch: BranchAheadRead;
21
+ }
22
+ /** Reset the reconciliation's memory -- tests start from a clean slate. */
23
+ export declare function __resetCompletionMergePendingForTests(): void;
24
+ /** The write this pass owes, if any. Pure. */
25
+ export declare function completionMergePendingAction(facts: CompletionMergePendingFacts): 'mark' | 'clear' | null;
26
+ /** Reconcile the mark for one focus. Never throws: a failed write is retried next pass. */
27
+ export declare function reconcileCompletionMergePending(facts: CompletionMergePendingFacts, deps: CompletionMergePendingDeps, warn: (message: string) => void): Promise<'marked' | 'cleared' | null>;
28
+ /** Note a write made outside the reconciliation (the window-open write marks; a landed merge clears). */
29
+ export declare function noteCompletionMergePendingWritten(focusId: string, written: 'marked' | 'cleared'): void;
30
+ //# sourceMappingURL=completion-merge-pending.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-merge-pending.d.ts","sourceRoot":"","sources":["../../src/merge/completion-merge-pending.ts"],"names":[],"mappings":"AA6BA,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,CAAC;AAEhE,2GAA2G;AAC3G,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,eAAe,CAKhH;AAED,mHAAmH;AACnH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAExE;AAED,iHAAiH;AACjH,MAAM,WAAW,0BAA0B;IACzC,oFAAoF;IACpF,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,2DAA2D;IAC3D,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,mCAAmC;AACnC,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,kBAAkB,EAAE,OAAO,CAAC;IAC5B,MAAM,EAAE,eAAe,CAAC;CACzB;AAKD,2EAA2E;AAC3E,wBAAgB,qCAAqC,IAAI,IAAI,CAE5D;AAED,8CAA8C;AAC9C,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,2BAA2B,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAIxG;AAED,2FAA2F;AAC3F,wBAAsB,+BAA+B,CACnD,KAAK,EAAE,2BAA2B,EAClC,IAAI,EAAE,0BAA0B,EAChC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC,CAatC;AAED,yGAAyG;AACzG,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,SAAS,GAAG,IAAI,CAEtG"}
@@ -0,0 +1,75 @@
1
+ // PINNED-BY-BEGIN (generated by gen:pinned-by -- do not hand-edit; checked by lint:pinned-by)
2
+ // Pinned by: packages/daemon/src/merge/completion-rework-armed.test.ts
3
+ // PINNED-BY-END
4
+ /**
5
+ * The focus's PENDING completion merge, kept on product_focuses.
6
+ * completion_merge_pending_at: while it is set the lifecycle arm clear defers
7
+ * (migration 20261004142135), so a red candidate, a missing verdict or a
8
+ * conflicting final merge re-opens its work on a focus that is still ARMED, and
9
+ * the team returns to it without a new human arm.
10
+ *
11
+ * Reconciled every state-cascade pass (completion/state-cascade/state-cascade.ts)
12
+ * from the focus row's OWN branch, repository and integration branch -- never an
13
+ * in-memory worktree map a restart can lose -- and FAIL CLOSED:
14
+ * - a window opens with the mark unless git PROVES integration contains the
15
+ * branch; an unreadable read marks it (holding an arm one cycle too long is
16
+ * recoverable; releasing one under pending re-work is not);
17
+ * - an already-open window is re-marked once per process (a window opened by
18
+ * an older daemon, or before a restart, carries the mark too);
19
+ * - the mark is CLEARED only when every delivery is terminal, no window is
20
+ * open, and git proves integration contains the branch -- the merge landed.
21
+ * A clear that fails is retried next pass, so a lost clear after a merge
22
+ * (mergeFocusBranch's own clear is best-effort) never holds the arm forever.
23
+ *
24
+ * @module merge/completion-merge-pending
25
+ */
26
+ import { runGitSync } from '../git/types.js';
27
+ /** Read whether `branchName` carries commits `integrationBranch` lacks; 'unknown' when git cannot tell. */
28
+ export function readBranchAhead(repoPath, branchName, integrationBranch) {
29
+ const result = runGitSync(['rev-list', '--count', `${integrationBranch}..${branchName}`, '--'], repoPath);
30
+ const count = Number.parseInt(result.output.trim(), 10);
31
+ if (!result.success || !Number.isFinite(count))
32
+ return 'unknown';
33
+ return count > 0 ? 'ahead' : 'contained';
34
+ }
35
+ /** Whether a window opening now marks the merge pending: unless integration provably contains the branch. Pure. */
36
+ export function completionMergePendingFor(read) {
37
+ return read !== 'contained';
38
+ }
39
+ /** What this process last wrote per focus, so a steady state writes nothing. */
40
+ const lastWritten = new Map();
41
+ /** Reset the reconciliation's memory -- tests start from a clean slate. */
42
+ export function __resetCompletionMergePendingForTests() {
43
+ lastWritten.clear();
44
+ }
45
+ /** The write this pass owes, if any. Pure. */
46
+ export function completionMergePendingAction(facts) {
47
+ if (facts.reviewWindowOpen)
48
+ return completionMergePendingFor(facts.branch) ? 'mark' : null;
49
+ if (facts.deliveriesTerminal && facts.branch === 'contained')
50
+ return 'clear';
51
+ return null;
52
+ }
53
+ /** Reconcile the mark for one focus. Never throws: a failed write is retried next pass. */
54
+ export async function reconcileCompletionMergePending(facts, deps, warn) {
55
+ const action = completionMergePendingAction(facts);
56
+ if (action === null)
57
+ return null;
58
+ const done = action === 'mark' ? 'marked' : 'cleared';
59
+ if (lastWritten.get(facts.focusId) === done)
60
+ return null;
61
+ try {
62
+ await (action === 'mark' ? deps.mark(facts.focusId) : deps.clear(facts.focusId));
63
+ lastWritten.set(facts.focusId, done);
64
+ return done;
65
+ }
66
+ catch (err) {
67
+ warn(`completion-merge pending ${action} failed for focus ${facts.focusId}: ${err.message}`);
68
+ return null;
69
+ }
70
+ }
71
+ /** Note a write made outside the reconciliation (the window-open write marks; a landed merge clears). */
72
+ export function noteCompletionMergePendingWritten(focusId, written) {
73
+ lastWritten.set(focusId, written);
74
+ }
75
+ //# sourceMappingURL=completion-merge-pending.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion-merge-pending.js","sourceRoot":"","sources":["../../src/merge/completion-merge-pending.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,uEAAuE;AACvE,gBAAgB;AAEhB;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAK7C,2GAA2G;AAC3G,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,UAAkB,EAAE,iBAAyB;IAC7F,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,iBAAiB,KAAK,UAAU,EAAE,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC1G,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACjE,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC;AAC3C,CAAC;AAED,mHAAmH;AACnH,MAAM,UAAU,yBAAyB,CAAC,IAAqB;IAC7D,OAAO,IAAI,KAAK,WAAW,CAAC;AAC9B,CAAC;AAmBD,gFAAgF;AAChF,MAAM,WAAW,GAAG,IAAI,GAAG,EAAgC,CAAC;AAE5D,2EAA2E;AAC3E,MAAM,UAAU,qCAAqC;IACnD,WAAW,CAAC,KAAK,EAAE,CAAC;AACtB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,4BAA4B,CAAC,KAAkC;IAC7E,IAAI,KAAK,CAAC,gBAAgB;QAAE,OAAO,yBAAyB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3F,IAAI,KAAK,CAAC,kBAAkB,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW;QAAE,OAAO,OAAO,CAAC;IAC7E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,+BAA+B,CACnD,KAAkC,EAClC,IAAgC,EAChC,IAA+B;IAE/B,MAAM,MAAM,GAAG,4BAA4B,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACjF,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,4BAA4B,MAAM,qBAAqB,KAAK,CAAC,OAAO,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACxG,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,iCAAiC,CAAC,OAAe,EAAE,OAA6B;IAC9F,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACpC,CAAC"}
@@ -11,7 +11,9 @@ import type { DaemonConfig, FocusTeamState, MergeConflictResolutionResult } from
11
11
  import type { CreateEscalationParams, CreateEscalationResult } from '../supervision/escalation-filing.js';
12
12
  import { getMergeConflictDeliveries } from '../queries/deliveries/deliveries.js';
13
13
  import type { AbandonedMergeRecovery } from './abandoned-merge-recovery.js';
14
- import type { BranchMergeGateTarget, MergeGateDecision } from './merge-gate-coverage.js';
14
+ import type { BranchMergeGateTarget } from './merge-gate-coverage.js';
15
+ import type { MergeGateDecision } from './completion-merge-gate.js';
16
+ import type { CompletionMergeOutOutcome } from './completion-merge-driver.js';
15
17
  import { type MergeBackConflictDeps } from './merge-back-conflict.js';
16
18
  import { type MergeBackAttemptOutcome, type MergeBackFollowUpTarget } from './merge-back-followup.js';
17
19
  /**
@@ -125,11 +127,21 @@ export declare function requestTeamLeadResolution(teamState: FocusTeamState, con
125
127
  * the gated merge paths.
126
128
  */
127
129
  export declare function attemptMergeBackCompletion(config: DaemonConfig, target: MergeBackFollowUpTarget, deps: MergeBackConflictDeps): Promise<MergeBackAttemptOutcome>;
130
+ /**
131
+ * The completion merge-out (merge/completion-merge-driver.ts): merge the
132
+ * integration tip into a COMPLETE focus's branch once, with git's automatic merge.
133
+ * A clean merge stays -- it is the next integration candidate, and the gate
134
+ * re-proves it clean (merge/merge-gate-coverage.ts isCleanMerge). A conflict is
135
+ * ABORTED, never resolved here: deciding it would change code after the review
136
+ * approved HEAD. The outcome names the conflicting paths and the integration
137
+ * commits the branch lacks, the team's work order.
138
+ */
139
+ export declare function attemptCompletionMergeOut(config: DaemonConfig, target: MergeBackFollowUpTarget, deps: Pick<MergeBackConflictDeps, 'readHead' | 'readFocusOwnDelta'>): Promise<CompletionMergeOutOutcome>;
128
140
  /**
129
141
  * {@link mergeFocusBranch} with its merge-back collaborators bound -- the shape
130
142
  * every merge path is handed (the composition root binds the collaborators).
131
143
  */
132
- export type MergeFocusBranch = (config: DaemonConfig, teamState: FocusTeamState, sessionId: string, branchName: string, workedDeliveryIds: string[], dirtyWorktreePolicy?: DirtyWorktreePolicy) => Promise<{
144
+ export type MergeFocusBranch = (config: DaemonConfig, teamState: FocusTeamState, sessionId: string, branchName: string, workedDeliveryIds: string[], admittedSha: string, dirtyWorktreePolicy?: DirtyWorktreePolicy) => Promise<{
133
145
  mergeSucceeded: boolean;
134
146
  exitReason: string;
135
147
  }>;
@@ -139,6 +151,9 @@ export type MergeFocusBranch = (config: DaemonConfig, teamState: FocusTeamState,
139
151
  * Uses the first delivery ID as the representative for merge operations
140
152
  * (all deliveries share the same branch in the focus model).
141
153
  *
154
+ * `admittedSha` is the candidate the completion merge gate admitted
155
+ * (merge/completion-merge-gate.ts): integration fast-forwards to exactly it.
156
+ *
142
157
  * `workedDeliveryIds` is the set of deliveries the team actually built in
143
158
  * this merge cycle. Git state and the merge commit SHA are recorded only
144
159
  * for these deliveries — never for queued/planning deliveries that happen
@@ -148,14 +163,14 @@ export type MergeFocusBranch = (config: DaemonConfig, teamState: FocusTeamState,
148
163
  * proceeds — the planning-phase / read-only safety nets live in
149
164
  * `shouldAttemptMerge`, not here.
150
165
  */
151
- export declare function mergeFocusBranch(deps: MergeFocusBranchDeps, config: DaemonConfig, teamState: FocusTeamState, sessionId: string, branchName: string, workedDeliveryIds: string[], dirtyWorktreePolicy?: DirtyWorktreePolicy): Promise<{
166
+ export declare function mergeFocusBranch(deps: MergeFocusBranchDeps, config: DaemonConfig, teamState: FocusTeamState, sessionId: string, branchName: string, workedDeliveryIds: string[], admittedSha: string, dirtyWorktreePolicy?: DirtyWorktreePolicy): Promise<{
152
167
  mergeSucceeded: boolean;
153
168
  exitReason: string;
154
169
  }>;
155
170
  /** Injected seams of the resolved-conflict retry; bound at the composition root (composition/execution-merge-context.ts). */
156
171
  export interface ResolvedConflictRetryDeps {
157
172
  getMergeConflictDeliveries: typeof getMergeConflictDeliveries;
158
- /** Hold the retry (an ungated commit or unreadable coverage, with why) or admit it (with its anchors) (merge/merge-gate-coverage.ts). */
173
+ /** Hold the retry (with why) or admit it at the green candidate (merge/completion-merge-gate.ts). */
159
174
  readMergeGate: (target: BranchMergeGateTarget) => Promise<MergeGateDecision>;
160
175
  /** Undo an abandoned merge in the focus worktree when nothing owns it (merge/abandoned-merge-recovery.ts). */
161
176
  recoverAbandonedMergeBack: (focusId: string) => Promise<AbandonedMergeRecovery>;
@@ -168,8 +183,8 @@ export interface ResolvedConflictRetryDeps {
168
183
  * Called once per poll cycle. Queries the API for deliveries with
169
184
  * merge_conflict git state, groups them by branch (to avoid duplicate
170
185
  * merges), and retries mergeToIntegration for each unique branch -- only when
171
- * every commit the merge would add has passed the focus's review or verify gate
172
- * (merge/merge-gate-coverage.ts). A held branch is not retried and gets no
186
+ * the completion merge gate admits the focus (merge/completion-merge-gate.ts).
187
+ * A held branch is not retried and gets no
173
188
  * resolution agent; its escalation stays open and the next poll asks again.
174
189
  *
175
190
  * On success: reports merged_to_integration for all deliveries on that
@@ -1 +1 @@
1
- {"version":3,"file":"focus-merge.d.ts","sourceRoot":"","sources":["../../src/merge/focus-merge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"focus-merge.d.ts","sourceRoot":"","sources":["../../src/merge/focus-merge.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,8 @@
2
2
  // Pinned by: packages/daemon/src/completion/team-teardown/session-hold.test.ts,
3
3
  // packages/daemon/src/integration/agent-owned-scoping-integration.test.ts,
4
4
  // packages/daemon/src/integration/merge-back-resolver-timeout-regression.test.ts,
5
+ // packages/daemon/src/merge/completion-conflict-rework.test.ts,
6
+ // packages/daemon/src/merge/completion-merge-out.test.ts,
5
7
  // packages/daemon/src/merge/dirty-worktree-merge-refusal.test.ts,
6
8
  // packages/daemon/src/merge/focus-merge-empty-deliveries.test.ts,
7
9
  // packages/daemon/src/merge/focus-merge-resolver-liveness.test.ts,
@@ -11,8 +13,9 @@
11
13
  // PINNED-BY-END
12
14
  import { runGitSync } from '../git/index.js';
13
15
  import { fetchEffectiveWorkflow } from '../queries/workflow-transitions/workflows.js';
16
+ import { clearCompletionMergePending } from '../queries/focuses/focuses.js';
14
17
  import { mergeBackFromIntegration } from '../git/index.js';
15
- import { mergeToIntegration } from '../git/merge.js';
18
+ import { hasOriginRemote, mergeToIntegration } from '../git/merge.js';
16
19
  import { mergeDeliveryBranch } from '../delivery/delivery-merge-ops.js';
17
20
  import { MERGE_CONFLICT_ESCALATION_KIND, MERGE_REFUSED_DIRTY_ESCALATION_KIND } from '../queries/escalations/escalations.js';
18
21
  import { resolveOpenWorktreeDirtyEscalations } from '../supervision/escalation-clearing.js';
@@ -25,7 +28,7 @@ import { noteMergeBackStarted } from './merge-back-activity.js';
25
28
  import { classifyMergeFailure } from './merge-failure-classify.js';
26
29
  import { NO_HUMAN_ACT } from '../queries/escalations/filing-observation.js';
27
30
  import { reportMergeConflictState } from './merge-conflict-escalation.js';
28
- import { handleMergeBackConflict, runContainedFallbackResolution, describeContainment, } from './merge-back-conflict.js';
31
+ import { runContainedFallbackResolution, describeContainment, } from './merge-back-conflict.js';
29
32
  import { recordResolverSuccess } from './merge-back-followup.js';
30
33
  import { createLogger } from '../log/daemon-log.js';
31
34
  import { focusPipelineModelForPass } from '../config/model-resolution.js';
@@ -272,12 +275,50 @@ export async function attemptMergeBackCompletion(config, target, deps) {
272
275
  }, deps);
273
276
  return result.resolved ? { kind: 'merged' } : { kind: 'failed', error: result.error, restore: result.restore };
274
277
  }
278
+ /**
279
+ * The completion merge-out (merge/completion-merge-driver.ts): merge the
280
+ * integration tip into a COMPLETE focus's branch once, with git's automatic merge.
281
+ * A clean merge stays -- it is the next integration candidate, and the gate
282
+ * re-proves it clean (merge/merge-gate-coverage.ts isCleanMerge). A conflict is
283
+ * ABORTED, never resolved here: deciding it would change code after the review
284
+ * approved HEAD. The outcome names the conflicting paths and the integration
285
+ * commits the branch lacks, the team's work order.
286
+ */
287
+ export async function attemptCompletionMergeOut(config, target, deps) {
288
+ const { worktreePath, branchName } = target;
289
+ const mb = await mergeBackWithRecoveryHead(worktreePath, config.integrationBranch, branchName, deps);
290
+ if (mb.preMergeHead === null)
291
+ return { kind: 'error', message: mb.refusal };
292
+ const { preMergeHead, mergeBack } = mb;
293
+ if (mergeBack.status === 'clean')
294
+ return { kind: 'merged' };
295
+ if (mergeBack.status === 'error')
296
+ return { kind: 'error', message: mergeBack.message };
297
+ const integrationRef = hasOriginRemote(worktreePath) ? `origin/${config.integrationBranch}` : config.integrationBranch;
298
+ const missing = runGitSync(['log', '--format=%h %s', '-n', '30', `${preMergeHead}..${integrationRef}`, '--'], worktreePath);
299
+ const abort = runGitSync(['merge', '--abort'], worktreePath);
300
+ if (!abort.success || deps.readHead(worktreePath) !== preMergeHead) {
301
+ return { kind: 'error', message: `the conflicting merge-out of ${branchName} could not be aborted: ${abort.error}` };
302
+ }
303
+ log.info(`[focus-merge] Completion merge-out of ${integrationRef} into ${branchName} conflicts; aborted -- the conflict is the team's work`);
304
+ return {
305
+ kind: 'conflict',
306
+ conflict: {
307
+ integrationBranch: config.integrationBranch,
308
+ conflictPaths: mergeBack.conflictFiles ?? [],
309
+ missingCommits: missing.success ? missing.output.split('\n').map((l) => l.trim()).filter(Boolean) : [],
310
+ },
311
+ };
312
+ }
275
313
  /**
276
314
  * Merge the focus branch to integration.
277
315
  *
278
316
  * Uses the first delivery ID as the representative for merge operations
279
317
  * (all deliveries share the same branch in the focus model).
280
318
  *
319
+ * `admittedSha` is the candidate the completion merge gate admitted
320
+ * (merge/completion-merge-gate.ts): integration fast-forwards to exactly it.
321
+ *
281
322
  * `workedDeliveryIds` is the set of deliveries the team actually built in
282
323
  * this merge cycle. Git state and the merge commit SHA are recorded only
283
324
  * for these deliveries — never for queued/planning deliveries that happen
@@ -287,7 +328,7 @@ export async function attemptMergeBackCompletion(config, target, deps) {
287
328
  * proceeds — the planning-phase / read-only safety nets live in
288
329
  * `shouldAttemptMerge`, not here.
289
330
  */
290
- export async function mergeFocusBranch(deps, config, teamState, sessionId, branchName, workedDeliveryIds, dirtyWorktreePolicy = 'escalate') {
331
+ export async function mergeFocusBranch(deps, config, teamState, sessionId, branchName, workedDeliveryIds, admittedSha, dirtyWorktreePolicy = 'escalate') {
291
332
  log.info(`Merging focus branch ${branchName} for "${teamState.focusName}"`, {
292
333
  focusId: teamState.focusId,
293
334
  sessionId,
@@ -337,31 +378,11 @@ export async function mergeFocusBranch(deps, config, teamState, sessionId, branc
337
378
  log.warn(`[focus-executor] Failed to fetch workflow for merge, using fallback:`, err.message);
338
379
  workflow = { id: 'fallback', stages: [] };
339
380
  }
340
- // ── Merge-back: sync integration into focus branch before merging out ──
341
- // Merge integration HEAD into the focus branch to surface conflicts early
342
- const worktreePath = teamState.worktreePath;
343
- if (worktreePath) {
344
- // A resolver that cannot finish is contained by restoring to the committed focus HEAD.
345
- const mb = await mergeBackWithRecoveryHead(worktreePath, config.integrationBranch, branchName, deps);
346
- if (mb.preMergeHead === null)
347
- return { mergeSucceeded: false, exitReason: mb.refusal };
348
- const { preMergeHead, mergeBack: mergeBackResult } = mb;
349
- if (mergeBackResult.status === 'conflict') {
350
- const conflictFiles = mergeBackResult.conflictFiles ?? [];
351
- log.info(`[focus-merge] Merge-back produced conflicts for "${teamState.focusName}": ` +
352
- `${conflictFiles.join(', ') || 'unknown files'}`);
353
- const conflict = await handleMergeBackConflict({ config, teamState, sessionId, branchName, representativeDeliveryId, worktreePath, preMergeHead, conflictFiles }, deps);
354
- if (!conflict.resolved) {
355
- return { mergeSucceeded: false, exitReason: conflict.exitReason };
356
- }
357
- }
358
- if (mergeBackResult.status === 'error') {
359
- log.error(`[focus-merge] Merge-back error for "${teamState.focusName}": ${mergeBackResult.message}`);
360
- return { mergeSucceeded: false, exitReason: mergeBackResult.message };
361
- }
362
- // status === 'clean' -- proceed to merge to integration
363
- log.info(`Merge-back clean for "${teamState.focusName}", proceeding to integration merge`);
364
- }
381
+ // No merge-back here: the completion gate admitted `admittedSha` only once it
382
+ // contained the integration tip, and integration fast-forwards to exactly it.
383
+ // If integration moved since, the fast-forward refuses and the next merge-back
384
+ // tick's merge-out (attemptCompletionMergeOut) brings the branch current --
385
+ // clean, or handed to the team. No resolver decides a conflict on this path.
365
386
  const mergeOutcome = await mergeDeliveryBranch({
366
387
  config,
367
388
  branchName,
@@ -371,11 +392,15 @@ export async function mergeFocusBranch(deps, config, teamState, sessionId, branc
371
392
  sessionId,
372
393
  workflow,
373
394
  focusId: teamState.focusId,
395
+ sourceSha: admittedSha,
374
396
  });
375
397
  // Report git state and merge commit SHA only for deliveries that were
376
398
  // actually worked on, never for queued/planning deliveries on the same branch.
377
399
  const deliveryIdsForGitState = new Set(workedDeliveryIds);
378
400
  if (mergeOutcome.mergeSucceeded) {
401
+ // The completion merge landed: end the pending mark that held the arm through it,
402
+ // which lets the deferred lifecycle clear release the arm (merge/completion-merge-pending.ts).
403
+ clearCompletionMergePending(teamState.focusId).catch((err) => log.warn(`[focus-merge] clearCompletionMergePending failed for focus ${teamState.focusId}: ${err.message}`));
379
404
  for (const deliveryId of deliveryIdsForGitState) {
380
405
  if (!teamState.mergedDeliveryIds.has(deliveryId)) {
381
406
  reportGitState(deliveryId, 'merged_to_integration').catch(err => log.warn(`[focus-executor] reportGitState merged_to_integration failed for ${deliveryId}:`, err.message));
@@ -442,11 +467,12 @@ function markConflictRetryMerged(deliveries) {
442
467
  * The resolved-conflict retry's fallback: merge integration back into the
443
468
  * focus worktree, run the fallback resolution agent with failure containment
444
469
  * (a resolver that cannot finish leaves the worktree restored to its committed
445
- * HEAD, partial work preserved -- merge/merge-back-conflict.ts), and retry the
446
- * integration merge once it resolves. The open merge_conflict escalation that
447
- * put these deliveries here stays the record on failure.
470
+ * HEAD, partial work preserved -- merge/merge-back-conflict.ts). A resolved
471
+ * head is a new integration candidate: integration does not move here, the next
472
+ * poll's completion gate drives it through CI. The open merge_conflict escalation
473
+ * that put these deliveries here stays the record on failure.
448
474
  */
449
- async function retryViaContainedFallbackResolution(config, branchName, representative, deliveries, deps) {
475
+ async function retryViaContainedFallbackResolution(config, branchName, representative, deps) {
450
476
  const worktreeInfo = representative.focus_id ? getFocusWorktree(representative.focus_id) : null;
451
477
  if (!worktreeInfo)
452
478
  return;
@@ -477,16 +503,9 @@ async function retryViaContainedFallbackResolution(config, branchName, represent
477
503
  log.warn(`[focus-merge] Fallback resolution failed for branch ${branchName}: ${fallback.error}. ${describeContainment(fallback.restore)}`);
478
504
  return;
479
505
  }
480
- const retryResult = await mergeToIntegration({
481
- config,
482
- branchName,
483
- deliveryName: representative.delivery_name,
484
- deliveryId: representative.delivery_id,
485
- });
486
- if (retryResult.success) {
487
- log.info(`Merge succeeded after fallback resolution for branch ${branchName}`);
488
- markConflictRetryMerged(deliveries);
489
- }
506
+ // The resolution is a new head no CI run has judged: integration does not move
507
+ // here. The next poll's gate drives it as a candidate (push, CI, fast-forward).
508
+ log.info(`[focus-merge] Fallback resolution finished the merge-back for ${branchName}; the completion merge gate judges the new head next.`);
490
509
  }
491
510
  /**
492
511
  * Check for deliveries stuck in merge_conflict state and retry the merge.
@@ -494,8 +513,8 @@ async function retryViaContainedFallbackResolution(config, branchName, represent
494
513
  * Called once per poll cycle. Queries the API for deliveries with
495
514
  * merge_conflict git state, groups them by branch (to avoid duplicate
496
515
  * merges), and retries mergeToIntegration for each unique branch -- only when
497
- * every commit the merge would add has passed the focus's review or verify gate
498
- * (merge/merge-gate-coverage.ts). A held branch is not retried and gets no
516
+ * the completion merge gate admits the focus (merge/completion-merge-gate.ts).
517
+ * A held branch is not retried and gets no
499
518
  * resolution agent; its escalation stays open and the next poll asks again.
500
519
  *
501
520
  * On success: reports merged_to_integration for all deliveries on that
@@ -537,6 +556,7 @@ export async function checkResolvedMergeConflicts(config, deps) {
537
556
  branchName,
538
557
  deliveryName: representative.delivery_name,
539
558
  deliveryId: representative.delivery_id,
559
+ sourceSha: gate.candidateSha,
540
560
  });
541
561
  if (mergeResult.success) {
542
562
  log.info(`[focus-merge] Merge retry succeeded for branch ${branchName} into ${integrationBranch} (${gate.admittedBy})`);
@@ -546,7 +566,7 @@ export async function checkResolvedMergeConflicts(config, deps) {
546
566
  log.info(`[focus-merge] Merge retry still failing for branch ${branchName}: ${mergeResult.error ?? 'unknown'}`);
547
567
  // Attempt fallback resolution agent if a worktree exists for this focus
548
568
  if (representative.focus_id) {
549
- await retryViaContainedFallbackResolution(config, branchName, representative, deliveries, deps);
569
+ await retryViaContainedFallbackResolution(config, branchName, representative, deps);
550
570
  }
551
571
  }
552
572
  }