@telora/daemon 0.23.14 → 0.23.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-info.json +5 -3
- package/dist/backends/agent-backend.d.ts +8 -1
- package/dist/backends/agent-backend.d.ts.map +1 -1
- package/dist/backends/claude/claude-backend.d.ts.map +1 -1
- package/dist/backends/claude/claude-backend.js +4 -0
- package/dist/backends/claude/claude-backend.js.map +1 -1
- package/dist/backends/claude/one-shot-model.d.ts +11 -0
- package/dist/backends/claude/one-shot-model.d.ts.map +1 -1
- package/dist/backends/claude/one-shot-model.js +7 -4
- package/dist/backends/claude/one-shot-model.js.map +1 -1
- package/dist/backends/claude/one-shot-session.d.ts +15 -0
- package/dist/backends/claude/one-shot-session.d.ts.map +1 -0
- package/dist/backends/claude/one-shot-session.js +120 -0
- package/dist/backends/claude/one-shot-session.js.map +1 -0
- package/dist/backends/codex/codex-backend.d.ts.map +1 -1
- package/dist/backends/codex/codex-backend.js +10 -0
- package/dist/backends/codex/codex-backend.js.map +1 -1
- package/dist/ci/candidate-ref.d.ts +28 -0
- package/dist/ci/candidate-ref.d.ts.map +1 -0
- package/dist/ci/candidate-ref.js +81 -0
- package/dist/ci/candidate-ref.js.map +1 -0
- package/dist/ci/ci-sensor-loop.d.ts.map +1 -1
- package/dist/ci/ci-sensor-loop.js +2 -1
- package/dist/ci/ci-sensor-loop.js.map +1 -1
- package/dist/ci/ci-status-git.d.ts +9 -0
- package/dist/ci/ci-status-git.d.ts.map +1 -1
- package/dist/ci/ci-status-git.js +16 -2
- package/dist/ci/ci-status-git.js.map +1 -1
- package/dist/ci/ci-status.d.ts.map +1 -1
- package/dist/ci/ci-status.js +5 -1
- package/dist/ci/ci-status.js.map +1 -1
- package/dist/completion/state-cascade/state-cascade.d.ts +14 -1
- package/dist/completion/state-cascade/state-cascade.d.ts.map +1 -1
- package/dist/completion/state-cascade/state-cascade.js +26 -1
- package/dist/completion/state-cascade/state-cascade.js.map +1 -1
- package/dist/completion/team-exit/merge-phase.d.ts +11 -10
- package/dist/completion/team-exit/merge-phase.d.ts.map +1 -1
- package/dist/completion/team-exit/merge-phase.js +10 -10
- package/dist/completion/team-exit/merge-phase.js.map +1 -1
- package/dist/completion/team-teardown/team-lifecycle.d.ts +4 -3
- package/dist/completion/team-teardown/team-lifecycle.d.ts.map +1 -1
- package/dist/completion/team-teardown/team-lifecycle.js +8 -8
- package/dist/completion/team-teardown/team-lifecycle.js.map +1 -1
- package/dist/composition/execution-git-context.d.ts.map +1 -1
- package/dist/composition/execution-git-context.js +2 -0
- package/dist/composition/execution-git-context.js.map +1 -1
- package/dist/composition/execution-merge-context.d.ts +11 -3
- package/dist/composition/execution-merge-context.d.ts.map +1 -1
- package/dist/composition/execution-merge-context.js +60 -12
- package/dist/composition/execution-merge-context.js.map +1 -1
- package/dist/composition/process-close-context.js +3 -3
- package/dist/composition/process-close-context.js.map +1 -1
- package/dist/composition/state-cascade-context.d.ts.map +1 -1
- package/dist/composition/state-cascade-context.js +10 -2
- package/dist/composition/state-cascade-context.js.map +1 -1
- package/dist/composition/team-lifecycle-context.js +3 -3
- package/dist/composition/team-lifecycle-context.js.map +1 -1
- package/dist/composition/verification-context.d.ts.map +1 -1
- package/dist/composition/verification-context.js +4 -23
- package/dist/composition/verification-context.js.map +1 -1
- package/dist/config/model-resolution.d.ts +25 -1
- package/dist/config/model-resolution.d.ts.map +1 -1
- package/dist/config/model-resolution.js +26 -1
- package/dist/config/model-resolution.js.map +1 -1
- package/dist/delivery/delivery-merge-ops.d.ts +2 -0
- package/dist/delivery/delivery-merge-ops.d.ts.map +1 -1
- package/dist/delivery/delivery-merge-ops.js +2 -2
- package/dist/delivery/delivery-merge-ops.js.map +1 -1
- package/dist/delivery/delivery-reconciler.d.ts +9 -0
- package/dist/delivery/delivery-reconciler.d.ts.map +1 -1
- package/dist/delivery/delivery-reconciler.js +42 -31
- package/dist/delivery/delivery-reconciler.js.map +1 -1
- package/dist/focus/focus-executor.d.ts.map +1 -1
- package/dist/focus/focus-executor.js +1 -0
- package/dist/focus/focus-executor.js.map +1 -1
- package/dist/focus/focus-poller-spawn.d.ts.map +1 -1
- package/dist/focus/focus-poller-spawn.js +3 -1
- package/dist/focus/focus-poller-spawn.js.map +1 -1
- package/dist/focus/focus-spawn-decision.d.ts.map +1 -1
- package/dist/focus/focus-spawn-decision.js +1 -0
- package/dist/focus/focus-spawn-decision.js.map +1 -1
- package/dist/git/index.d.ts.map +1 -1
- package/dist/git/index.js +2 -1
- package/dist/git/index.js.map +1 -1
- package/dist/git/merge.d.ts +12 -2
- package/dist/git/merge.d.ts.map +1 -1
- package/dist/git/merge.js +32 -33
- package/dist/git/merge.js.map +1 -1
- package/dist/merge/approval-gate-state.d.ts.map +1 -1
- package/dist/merge/approval-gate-state.js +8 -7
- package/dist/merge/approval-gate-state.js.map +1 -1
- package/dist/merge/candidate-rework.d.ts +44 -0
- package/dist/merge/candidate-rework.d.ts.map +1 -0
- package/dist/merge/candidate-rework.js +152 -0
- package/dist/merge/candidate-rework.js.map +1 -0
- package/dist/merge/completion-hold-record.d.ts +20 -0
- package/dist/merge/completion-hold-record.d.ts.map +1 -0
- package/dist/merge/completion-hold-record.js +130 -0
- package/dist/merge/completion-hold-record.js.map +1 -0
- package/dist/merge/completion-merge-driver.d.ts +96 -0
- package/dist/merge/completion-merge-driver.d.ts.map +1 -0
- package/dist/merge/completion-merge-driver.js +94 -0
- package/dist/merge/completion-merge-driver.js.map +1 -0
- package/dist/merge/completion-merge-gate.d.ts +71 -0
- package/dist/merge/completion-merge-gate.d.ts.map +1 -0
- package/dist/merge/completion-merge-gate.js +185 -0
- package/dist/merge/completion-merge-gate.js.map +1 -0
- package/dist/merge/completion-merge-pending.d.ts +30 -0
- package/dist/merge/completion-merge-pending.d.ts.map +1 -0
- package/dist/merge/completion-merge-pending.js +75 -0
- package/dist/merge/completion-merge-pending.js.map +1 -0
- package/dist/merge/focus-merge.d.ts +21 -6
- package/dist/merge/focus-merge.d.ts.map +1 -1
- package/dist/merge/focus-merge.js +65 -45
- package/dist/merge/focus-merge.js.map +1 -1
- package/dist/merge/focus-worktree-removal.d.ts +6 -6
- package/dist/merge/focus-worktree-removal.d.ts.map +1 -1
- package/dist/merge/focus-worktree-removal.js +10 -6
- package/dist/merge/focus-worktree-removal.js.map +1 -1
- package/dist/merge/merge-back-conflict.d.ts.map +1 -1
- package/dist/merge/merge-back-conflict.js +2 -0
- package/dist/merge/merge-back-conflict.js.map +1 -1
- package/dist/merge/merge-back-followup.d.ts.map +1 -1
- package/dist/merge/merge-back-followup.js +7 -0
- package/dist/merge/merge-back-followup.js.map +1 -1
- package/dist/merge/merge-back-loop.d.ts +36 -14
- package/dist/merge/merge-back-loop.d.ts.map +1 -1
- package/dist/merge/merge-back-loop.js +109 -66
- package/dist/merge/merge-back-loop.js.map +1 -1
- package/dist/merge/merge-gate-coverage.d.ts +9 -61
- package/dist/merge/merge-gate-coverage.d.ts.map +1 -1
- package/dist/merge/merge-gate-coverage.js +33 -167
- package/dist/merge/merge-gate-coverage.js.map +1 -1
- package/dist/nexus/escalation-bridge.d.ts.map +1 -1
- package/dist/nexus/escalation-bridge.js +2 -1
- package/dist/nexus/escalation-bridge.js.map +1 -1
- package/dist/nexus/file-escalation.d.ts.map +1 -1
- package/dist/nexus/file-escalation.js +5 -1
- package/dist/nexus/file-escalation.js.map +1 -1
- package/dist/queries/api-client/shared.js +1 -1
- package/dist/queries/deliveries/deliveries.d.ts.map +1 -1
- package/dist/queries/deliveries/deliveries.js +1 -0
- package/dist/queries/deliveries/deliveries.js.map +1 -1
- package/dist/queries/delivery-gate-state/delivery-gate-state.js +1 -1
- package/dist/queries/delivery-gate-state/verify-own-holds.d.ts +10 -3
- package/dist/queries/delivery-gate-state/verify-own-holds.d.ts.map +1 -1
- package/dist/queries/delivery-gate-state/verify-own-holds.js +2 -0
- package/dist/queries/delivery-gate-state/verify-own-holds.js.map +1 -1
- package/dist/queries/escalations/cause-key.d.ts.map +1 -1
- package/dist/queries/escalations/cause-key.js +1 -0
- package/dist/queries/escalations/cause-key.js.map +1 -1
- package/dist/queries/escalations/escalations.d.ts.map +1 -1
- package/dist/queries/escalations/escalations.js +2 -1
- package/dist/queries/escalations/escalations.js.map +1 -1
- package/dist/queries/focuses/focus-discovery.d.ts +14 -0
- package/dist/queries/focuses/focus-discovery.d.ts.map +1 -1
- package/dist/queries/focuses/focus-discovery.js +4 -0
- package/dist/queries/focuses/focus-discovery.js.map +1 -1
- package/dist/queries/focuses/focuses.d.ts +12 -2
- package/dist/queries/focuses/focuses.d.ts.map +1 -1
- package/dist/queries/focuses/focuses.js +24 -3
- package/dist/queries/focuses/focuses.js.map +1 -1
- package/dist/queries/org-config/local-models.d.ts.map +1 -1
- package/dist/queries/org-config/local-models.js +2 -1
- package/dist/queries/org-config/local-models.js.map +1 -1
- package/dist/queries/wire-schemas/schemas.d.ts +48 -0
- package/dist/queries/wire-schemas/schemas.d.ts.map +1 -1
- package/dist/queries/wire-schemas/schemas.js +15 -0
- package/dist/queries/wire-schemas/schemas.js.map +1 -1
- package/dist/spawn/model-routing.d.ts +13 -8
- package/dist/spawn/model-routing.d.ts.map +1 -1
- package/dist/spawn/model-routing.js +14 -8
- package/dist/spawn/model-routing.js.map +1 -1
- package/dist/spawn/review-hold-park.d.ts.map +1 -1
- package/dist/spawn/review-hold-park.js +1 -0
- package/dist/spawn/review-hold-park.js.map +1 -1
- package/dist/spawn/spawn-decision.d.ts +7 -0
- package/dist/spawn/spawn-decision.d.ts.map +1 -1
- package/dist/spawn/spawn-decision.js.map +1 -1
- package/dist/supervision/agent-resolvable-retirement.d.ts +1 -1
- package/dist/supervision/agent-resolvable-retirement.d.ts.map +1 -1
- package/dist/supervision/agent-resolvable-retirement.js +2 -1
- package/dist/supervision/agent-resolvable-retirement.js.map +1 -1
- package/dist/supervision/escalation-filing.js +1 -1
- package/dist/supervision/incident-correlation.d.ts +3 -0
- package/dist/supervision/incident-correlation.d.ts.map +1 -1
- package/dist/supervision/incident-correlation.js +4 -0
- package/dist/supervision/incident-correlation.js.map +1 -1
- package/dist/supervision/null-role-spawn.d.ts.map +1 -1
- package/dist/supervision/null-role-spawn.js +1 -0
- package/dist/supervision/null-role-spawn.js.map +1 -1
- package/dist/supervision/spawn-cooldown.js +1 -1
- package/dist/supervision/stop-rows-work-holds.d.ts +4 -4
- package/dist/supervision/stop-rows-work-holds.d.ts.map +1 -1
- package/dist/supervision/stop-rows-work-holds.js +17 -15
- package/dist/supervision/stop-rows-work-holds.js.map +1 -1
- package/dist/testing/fixtures/api-client.js +1 -1
- package/dist/testing/fixtures/index.d.ts.map +1 -1
- package/dist/testing/fixtures/index.js +3 -1
- package/dist/testing/fixtures/index.js.map +1 -1
- package/dist/testing/fixtures/judge-session.d.ts +14 -0
- package/dist/testing/fixtures/judge-session.d.ts.map +1 -0
- package/dist/testing/fixtures/judge-session.js +10 -0
- package/dist/testing/fixtures/judge-session.js.map +1 -0
- package/dist/types/focus.d.ts +9 -0
- package/dist/types/focus.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/verify/effect-reconciliation/judge-invoker.d.ts +14 -4
- package/dist/verify/effect-reconciliation/judge-invoker.d.ts.map +1 -1
- package/dist/verify/effect-reconciliation/judge-invoker.js +30 -12
- package/dist/verify/effect-reconciliation/judge-invoker.js.map +1 -1
- package/dist/verify/effect-reconciliation/verify-reconciliation.d.ts +18 -5
- package/dist/verify/effect-reconciliation/verify-reconciliation.d.ts.map +1 -1
- package/dist/verify/effect-reconciliation/verify-reconciliation.js +75 -18
- package/dist/verify/effect-reconciliation/verify-reconciliation.js.map +1 -1
- package/dist/verify/effect-reconciliation/verify-restatement.js +1 -1
- package/dist/verify/engine/verification-deps.js +1 -1
- package/dist/verify/engine/verification-engine.js +1 -1
- package/dist/verify/gate-config/verification-config.js +1 -1
- package/dist/verify/inspection-strategies/verification-strategies.js +1 -1
- package/dist/verify/inspection-strategies/verification-strategies.js.map +1 -1
- package/dist/verify/outcome-routing/incurable-model-hold.d.ts +9 -3
- package/dist/verify/outcome-routing/incurable-model-hold.d.ts.map +1 -1
- package/dist/verify/outcome-routing/incurable-model-hold.js +18 -5
- package/dist/verify/outcome-routing/incurable-model-hold.js.map +1 -1
- package/dist/verify/outcome-routing/repeating-model-hold.d.ts +30 -0
- package/dist/verify/outcome-routing/repeating-model-hold.d.ts.map +1 -0
- package/dist/verify/outcome-routing/repeating-model-hold.js +135 -0
- package/dist/verify/outcome-routing/repeating-model-hold.js.map +1 -0
- package/dist/verify/outcome-routing/verification-history-reset.d.ts.map +1 -1
- package/dist/verify/outcome-routing/verification-history-reset.js +1 -0
- package/dist/verify/outcome-routing/verification-history-reset.js.map +1 -1
- package/dist/verify/outcome-routing/verification-model-unavailable-hold.d.ts +10 -4
- package/dist/verify/outcome-routing/verification-model-unavailable-hold.d.ts.map +1 -1
- package/dist/verify/outcome-routing/verification-model-unavailable-hold.js +24 -6
- package/dist/verify/outcome-routing/verification-model-unavailable-hold.js.map +1 -1
- package/dist/verify/run-selection/default-on-guards.js +1 -1
- package/dist/verify/run-selection/pre-review-gate-dispatch.d.ts +4 -3
- package/dist/verify/run-selection/pre-review-gate-dispatch.d.ts.map +1 -1
- package/dist/verify/run-selection/pre-review-gate-dispatch.js +23 -7
- package/dist/verify/run-selection/pre-review-gate-dispatch.js.map +1 -1
- package/dist/verify/run-selection/verification-default-on.js +1 -1
- package/dist/verify/run-selection/verification-default-on.js.map +1 -1
- package/dist/worktree/provision.d.ts.map +1 -1
- package/dist/worktree/provision.js +1 -0
- package/dist/worktree/provision.js.map +1 -1
- package/dist/worktree/safety.d.ts.map +1 -1
- package/dist/worktree/safety.js +1 -0
- package/dist/worktree/safety.js.map +1 -1
- package/package.json +3 -3
|
@@ -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
|
|
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 (
|
|
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
|
-
*
|
|
172
|
-
*
|
|
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":"
|
|
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 {
|
|
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
|
-
//
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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)
|
|
446
|
-
* integration
|
|
447
|
-
*
|
|
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,
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
*
|
|
498
|
-
*
|
|
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,
|
|
569
|
+
await retryViaContainedFallbackResolution(config, branchName, representative, deps);
|
|
550
570
|
}
|
|
551
571
|
}
|
|
552
572
|
}
|