@dev-loops/core 1.0.4-pre.0 → 1.0.4-pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/analysis/change-classifier.mjs +16 -30
- package/src/analysis/diff-analyzer.mjs +38 -15
- package/src/claude/asset-generation.mjs +36 -1
- package/src/config/config.mjs +158 -81
- package/src/config/extension-defaults.yaml +18 -6
- package/src/github/copilot-helpers.mjs +22 -8
- package/src/github/gh.mjs +14 -1
- package/src/github/review-threads.mjs +6 -0
- package/src/loop/copilot-loop-state.mjs +44 -4
- package/src/loop/finding-cluster.mjs +9 -9
- package/src/loop/gate-fanin.mjs +21 -0
- package/src/loop/main-checkout-ff.mjs +169 -17
- package/src/loop/merge-approval.mjs +122 -26
- package/src/loop/pr-gate-coordination.mjs +283 -62
- package/src/loop/refinement-grill-state.mjs +120 -8
- package/src/loop/review-operation.mjs +161 -0
- package/src/loop/spec-authority.mjs +21 -0
- package/src/projects/move-queue-item.mjs +5 -79
- package/src/projects/projects-access.mjs +124 -0
|
@@ -100,7 +100,19 @@ function normalizeGateComment(summary = null) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
// `unresolvedGateThreadCount` (ADR 0088): the gate-authored review-thread
|
|
104
|
+
// invariant close-gate-findings.mjs/ready-for-review.mjs already enforce
|
|
105
|
+
// (GATE-EXEC-FINDING-THREADS) — a clean marker verdict alone is not
|
|
106
|
+
// sufficient while a gate-authored thread (e.g. an unanswered or
|
|
107
|
+
// not-yet-judge-rejected `question`) still dangles. Folded ONLY into
|
|
108
|
+
// `currentHeadClean` here, never `cleanEvidenceExists`: `undefined`/`null`
|
|
109
|
+
// (the caller did not supply the signal — preApprovalGate's own
|
|
110
|
+
// toGateStatus call never does) preserves the marker-only definition
|
|
111
|
+
// exactly, so only draftGate's `currentHeadClean` — the one field that
|
|
112
|
+
// gates MARK_READY_FOR_REVIEW below — gains this guard. -1 (fail-closed
|
|
113
|
+
// "could not read thread state") counts as NOT clean, same as any positive
|
|
114
|
+
// count.
|
|
115
|
+
function toGateStatus(comment, marker, currentHeadSha, unresolvedGateThreadCount) {
|
|
104
116
|
const normalizedComment = normalizeGateComment(comment);
|
|
105
117
|
const normalizedMarker = normalizeGateComment(marker);
|
|
106
118
|
const markerHeadMatches = normalizedMarker.headSha !== null
|
|
@@ -109,6 +121,26 @@ function toGateStatus(comment, marker, currentHeadSha) {
|
|
|
109
121
|
const anyVisible = normalizedComment.visible || normalizedMarker.visible;
|
|
110
122
|
|
|
111
123
|
const cleanEvidenceExists = normalizedComment.visible && normalizedComment.verdict === "clean" && normalizedComment.headSha !== null;
|
|
124
|
+
// Only null/undefined means "the caller supplied no signal" (preApprovalGate's
|
|
125
|
+
// own toGateStatus call never passes this argument). Anything else that is
|
|
126
|
+
// not a non-negative integer — NaN, a float, a string, a negative number
|
|
127
|
+
// other than -1 — is a malformed signal from a future caller, treated the
|
|
128
|
+
// same as -1 (unreadable) rather than "no signal": this is a public core
|
|
129
|
+
// export, and failing open on a malformed count would let a broken caller
|
|
130
|
+
// report currentHeadClean=true with a dangling thread.
|
|
131
|
+
const hasThreadSignal = unresolvedGateThreadCount !== null && unresolvedGateThreadCount !== undefined;
|
|
132
|
+
const safeThreadCount = hasThreadSignal
|
|
133
|
+
? (Number.isInteger(unresolvedGateThreadCount) && unresolvedGateThreadCount >= 0 ? unresolvedGateThreadCount : -1)
|
|
134
|
+
: null;
|
|
135
|
+
const gateThreadsClean = !hasThreadSignal || safeThreadCount === 0;
|
|
136
|
+
// -1 is a distinct signal from a real positive count: it means the caller
|
|
137
|
+
// could not even READ the thread state (API failure) or supplied a
|
|
138
|
+
// malformed value, not that a thread is known to dangle. Both still block
|
|
139
|
+
// MARK_READY_FOR_REVIEW, but the two get different reasons below (a real
|
|
140
|
+
// thread names its own remedy; an unreadable count names retrying the read
|
|
141
|
+
// instead).
|
|
142
|
+
const gateThreadCountUnreadable = safeThreadCount === -1;
|
|
143
|
+
const markerVerdictClean = normalizedMarker.visible && markerHeadMatches && normalizedMarker.verdict === "clean" && normalizedMarker.contractComplete;
|
|
112
144
|
|
|
113
145
|
return {
|
|
114
146
|
visible: normalizedComment.visible,
|
|
@@ -120,7 +152,18 @@ function toGateStatus(comment, marker, currentHeadSha) {
|
|
|
120
152
|
findingsSummary: normalizedComment.findingsSummary ?? normalizedMarker.findingsSummary,
|
|
121
153
|
nextAction: normalizedComment.nextAction ?? normalizedMarker.nextAction,
|
|
122
154
|
contractComplete: normalizedMarker.visible && markerHeadMatches && normalizedMarker.contractComplete,
|
|
123
|
-
currentHeadClean:
|
|
155
|
+
currentHeadClean: markerVerdictClean && gateThreadsClean,
|
|
156
|
+
// ADR 0088: the marker verdict is clean on its own terms, but a
|
|
157
|
+
// gate-authored thread still dangles — distinct from "not clean at all"
|
|
158
|
+
// (draftGate below uses this to name the thread blocker directly rather
|
|
159
|
+
// than re-running the draft gate or blocking on CI, neither of which can
|
|
160
|
+
// ever clear a dangling thread). Excludes the -1 (unreadable) case below,
|
|
161
|
+
// which gets its own distinct reason.
|
|
162
|
+
markerCleanThreadsUnresolved: markerVerdictClean && !gateThreadsClean && !gateThreadCountUnreadable,
|
|
163
|
+
// The thread-state READ itself failed (-1) rather than reporting a real
|
|
164
|
+
// dangling thread — MARK_READY_FOR_REVIEW stays forbidden either way, but
|
|
165
|
+
// this names "retry the read" instead of "resolve a thread".
|
|
166
|
+
markerCleanThreadStateUnreadable: markerVerdictClean && gateThreadCountUnreadable,
|
|
124
167
|
cleanEvidenceExists,
|
|
125
168
|
};
|
|
126
169
|
}
|
|
@@ -467,6 +510,9 @@ function buildResult({
|
|
|
467
510
|
* @param {boolean} [params.postConvergenceSignificantChange=false] - significant
|
|
468
511
|
* post-convergence changes on a newer head start a new review cycle and must
|
|
469
512
|
* not be treated as round-cap clean-fallback suppression.
|
|
513
|
+
* @param {boolean} [params.postConvergenceReviewSuppressed=false] - the caller
|
|
514
|
+
* verified a carried convergence for this head, so no further Copilot request
|
|
515
|
+
* is due (mirrors applyUnsettledCopilotReviewEntryGuard).
|
|
470
516
|
* @param {string} params.gateBoundary - current gate boundary
|
|
471
517
|
* @returns {boolean}
|
|
472
518
|
*/
|
|
@@ -478,6 +524,7 @@ export function shouldGuardCopilotReviewRequest({
|
|
|
478
524
|
sameHeadCleanConverged = false,
|
|
479
525
|
roundCapCleanFallback = false,
|
|
480
526
|
postConvergenceSignificantChange = false,
|
|
527
|
+
postConvergenceReviewSuppressed = false,
|
|
481
528
|
gateBoundary,
|
|
482
529
|
}) {
|
|
483
530
|
const gateBoundariesRequiringCopilotFormalRequest = new Set([
|
|
@@ -497,6 +544,11 @@ export function shouldGuardCopilotReviewRequest({
|
|
|
497
544
|
if (copilotReviewRequestStatus !== "none") {
|
|
498
545
|
return false;
|
|
499
546
|
}
|
|
547
|
+
// A carried convergence settles this head without a new Copilot round; the
|
|
548
|
+
// request tool would return a suppressed status, so a forced request livelocks.
|
|
549
|
+
if (postConvergenceReviewSuppressed === true) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
500
552
|
// Durable signal: if Copilot was ever formally requested as a reviewer,
|
|
501
553
|
// the current "none" status is from a fulfilled request (normal cycle),
|
|
502
554
|
// not from a missing request. Do not guard the happy path.
|
|
@@ -624,18 +676,21 @@ function applyUnsettledCopilotReviewEntryGuard(input, result) {
|
|
|
624
676
|
// grant boundary here after gating CI/threads, and a significant
|
|
625
677
|
// post-convergence change routes to a rerequest (a new cycle) before this
|
|
626
678
|
// guard runs, so this exemption cannot mask a genuinely-unreviewed change;
|
|
627
|
-
// - postConvergenceReviewSuppressed:
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
// the
|
|
679
|
+
// - postConvergenceReviewSuppressed: the caller verified a carried
|
|
680
|
+
// convergence (or an operator suppression marker) for this head: no
|
|
681
|
+
// outstanding request, zero unresolved threads, and a converged latest
|
|
682
|
+
// Copilot review on an earlier head. By default that review carries
|
|
683
|
+
// whatever the delta (converged-once); under the strict setting only a
|
|
684
|
+
// provably docs-only or integrate-only delta carries. The prior converged
|
|
685
|
+
// review then stands for this head (never derived here from other
|
|
686
|
+
// snapshot facts).
|
|
632
687
|
// Fail closed on ANY non-outstanding status, not only the literal "none":
|
|
633
688
|
// this is the independent gate-ENTRY re-check, so it must not trust the
|
|
634
689
|
// caller's status string. A non-canonical/unknown value ("", "unavailable",
|
|
635
690
|
// "failed", a typo) with a grant-y lifecycleState and no current-head review
|
|
636
691
|
// fails closed rather than slipping through the "none"-only default. Only a
|
|
637
|
-
// driven current-head review (or the impossible-further-round cap state, or
|
|
638
|
-
//
|
|
692
|
+
// driven current-head review (or the impossible-further-round cap state, or a
|
|
693
|
+
// caller-verified carried convergence) exempts, so an agent that skips
|
|
639
694
|
// the explicit Copilot round cannot reach a clean pre_approval verdict via any
|
|
640
695
|
// grant-y lifecycleState (e.g. a stale/racy low_signal_converged label, or one
|
|
641
696
|
// carried by prior-head rounds).
|
|
@@ -723,6 +778,67 @@ function applyUnsettledCopilotReviewEntryGuard(input, result) {
|
|
|
723
778
|
});
|
|
724
779
|
}
|
|
725
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Boundaries at which a PR must carry clean draft_gate evidence; without it
|
|
783
|
+
* the only legal next action is reconcile_draft_gate
|
|
784
|
+
* (skills/docs/pr-lifecycle-contract.md, LIFECYCLE-FAIL-CLOSED).
|
|
785
|
+
*/
|
|
786
|
+
const DRAFT_GATE_EVIDENCE_GUARDED_BOUNDARIES = Object.freeze([
|
|
787
|
+
PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
788
|
+
PR_CHECKPOINT.FEEDBACK_RESOLUTION,
|
|
789
|
+
PR_CHECKPOINT.PRE_APPROVAL_GATE_NEEDED,
|
|
790
|
+
PR_CHECKPOINT.PRE_APPROVAL_GATE_WINDOW,
|
|
791
|
+
PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
792
|
+
]);
|
|
793
|
+
|
|
794
|
+
function applyDraftGateEvidenceGuard(result, { prDraft = false } = {}) {
|
|
795
|
+
if (!result || typeof result !== "object") {
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
798
|
+
// The PR is still draft: absent/not-yet-clean draft_gate evidence is the
|
|
799
|
+
// EXPECTED state while draft_gate remediation is in progress (e.g. a fixer
|
|
800
|
+
// mid-way through COMPLETE_FIXER_DISPOSITION for threads it tackled from an
|
|
801
|
+
// earlier draft_gate round) — the PR_DRAFT branch above already owns this
|
|
802
|
+
// state (RUN_DRAFT_GATE / MARK_READY_FOR_REVIEW). This guard exists for the
|
|
803
|
+
// POST-draft merge-path boundaries only (mirrors the wrapper's own
|
|
804
|
+
// prDraft skip for the title-marker guard, below); rewriting an in-progress
|
|
805
|
+
// draft-side result to reconcile_draft_gate would misdirect the fixer.
|
|
806
|
+
if (prDraft) {
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
if (result.draftGate?.cleanEvidenceExists === true) {
|
|
810
|
+
return result;
|
|
811
|
+
}
|
|
812
|
+
if (!DRAFT_GATE_EVIDENCE_GUARDED_BOUNDARIES.includes(result.gateBoundary)) {
|
|
813
|
+
return result;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const allowedNextActions = [];
|
|
817
|
+
const forbiddenActions = [];
|
|
818
|
+
pushUnique(allowedNextActions, [PR_CHECKPOINT_ACTION.RECONCILE_DRAFT_GATE]);
|
|
819
|
+
pushUnique(forbiddenActions, [
|
|
820
|
+
PR_CHECKPOINT_ACTION.RUN_DRAFT_GATE,
|
|
821
|
+
PR_CHECKPOINT_ACTION.MARK_READY_FOR_REVIEW,
|
|
822
|
+
PR_CHECKPOINT_ACTION.REQUEST_COPILOT_REVIEW,
|
|
823
|
+
PR_CHECKPOINT_ACTION.WAIT_FOR_COPILOT_REVIEW,
|
|
824
|
+
PR_CHECKPOINT_ACTION.RUN_PRE_APPROVAL_GATE,
|
|
825
|
+
PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
826
|
+
PR_CHECKPOINT_ACTION.DECLARE_MERGE_READY,
|
|
827
|
+
]);
|
|
828
|
+
|
|
829
|
+
return {
|
|
830
|
+
...result,
|
|
831
|
+
gateBoundary: PR_CHECKPOINT.DRAFT_GATE_NEEDED,
|
|
832
|
+
nextAction: PR_CHECKPOINT_ACTION.RECONCILE_DRAFT_GATE,
|
|
833
|
+
allowedNextActions,
|
|
834
|
+
forbiddenActions,
|
|
835
|
+
reason: result.draftGate?.anyVisible
|
|
836
|
+
? "Clean draft_gate evidence is required before merge (no gate exemptions, #579). A draft_gate comment exists but is not clean; convert the PR back to draft before re-running draft_gate, or clear the existing evidence before running reconcile_draft_gate."
|
|
837
|
+
: "Clean draft_gate evidence is required before merge (no gate exemptions, #579). No visible clean draft_gate comment exists for this PR; run reconcile_draft_gate before proceeding.",
|
|
838
|
+
gateEvidenceNote: null,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
726
842
|
/**
|
|
727
843
|
* Evaluates PR gate coordination, then re-asserts the merge-blocking title guard
|
|
728
844
|
* at the pre-approval / final-approval boundary for non-draft PRs.
|
|
@@ -731,31 +847,38 @@ function applyUnsettledCopilotReviewEntryGuard(input, result) {
|
|
|
731
847
|
* sites (defense in depth); this wrapper additionally covers the pre-approval
|
|
732
848
|
* gate boundary, which is reached before any pre-approval evidence exists and so
|
|
733
849
|
* is not protected by the inline checks.
|
|
850
|
+
*
|
|
851
|
+
* Invariant: every return path passes through `applyDraftGateEvidenceGuard`,
|
|
852
|
+
* which rewrites the five DRAFT_GATE_EVIDENCE_GUARDED_BOUNDARIES boundaries to
|
|
853
|
+
* DRAFT_GATE_NEEDED / reconcile_draft_gate whenever clean draft_gate evidence
|
|
854
|
+
* is absent — a still-draft PR is exempted (ADR 0088): absent/not-yet-clean
|
|
855
|
+
* draft_gate evidence is the normal state while draft_gate remediation is in
|
|
856
|
+
* progress, and the PR_DRAFT branch already owns that state.
|
|
734
857
|
*/
|
|
735
858
|
export function evaluatePrGateCoordination(input = {}) {
|
|
736
859
|
const result = evaluatePrGateCoordinationCore(input);
|
|
860
|
+
const prDraft = input.prDraft === true;
|
|
737
861
|
|
|
738
862
|
const unsettledReviewResult = applyUnsettledCopilotReviewEntryGuard(input, result);
|
|
739
863
|
if (unsettledReviewResult) {
|
|
740
|
-
return unsettledReviewResult;
|
|
864
|
+
return applyDraftGateEvidenceGuard(unsettledReviewResult, { prDraft });
|
|
741
865
|
}
|
|
742
866
|
|
|
743
|
-
const prDraft = input.prDraft === true;
|
|
744
867
|
const prTitle = typeof input.prTitle === "string" ? input.prTitle : "";
|
|
745
868
|
// Draft PRs may legitimately carry a WIP title; the marker only blocks once
|
|
746
869
|
// the PR has left draft and is at a pre-approval/final-approval boundary.
|
|
747
870
|
if (prDraft || !result || typeof result !== "object") {
|
|
748
|
-
return result;
|
|
871
|
+
return applyDraftGateEvidenceGuard(result, { prDraft });
|
|
749
872
|
}
|
|
750
873
|
if (!TITLE_MARKER_GUARDED_BOUNDARIES.includes(result.gateBoundary)) {
|
|
751
|
-
return result;
|
|
874
|
+
return applyDraftGateEvidenceGuard(result, { prDraft });
|
|
752
875
|
}
|
|
753
876
|
const markers = findBlockingTitleMarkers(prTitle);
|
|
754
877
|
if (markers.length === 0) {
|
|
755
|
-
return result;
|
|
878
|
+
return applyDraftGateEvidenceGuard(result, { prDraft });
|
|
756
879
|
}
|
|
757
880
|
|
|
758
|
-
return buildTitleMarkerBlockedResult({
|
|
881
|
+
return applyDraftGateEvidenceGuard(buildTitleMarkerBlockedResult({
|
|
759
882
|
input,
|
|
760
883
|
currentHeadSha: result.currentHeadSha ?? null,
|
|
761
884
|
draftGateAlreadySatisfied: result.draftGateAlreadySatisfied === true,
|
|
@@ -765,7 +888,7 @@ export function evaluatePrGateCoordination(input = {}) {
|
|
|
765
888
|
conflictFiles: result.conflictFiles ?? [],
|
|
766
889
|
markers,
|
|
767
890
|
refinementArtifact: result.refinementArtifact ?? null,
|
|
768
|
-
});
|
|
891
|
+
}), { prDraft });
|
|
769
892
|
}
|
|
770
893
|
|
|
771
894
|
function evaluatePrGateCoordinationCore(input = {}) {
|
|
@@ -775,14 +898,15 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
775
898
|
const prDraft = input.prDraft === true;
|
|
776
899
|
const prClosed = input.prClosed === true;
|
|
777
900
|
const prMerged = input.prMerged === true;
|
|
778
|
-
const
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
//
|
|
901
|
+
const copilotConvergenceOk = input.copilotConvergenceOk === true;
|
|
902
|
+
// Carried convergence: set only when the caller verified, through the
|
|
903
|
+
// shared carried-convergence predicate the Copilot request tool also uses,
|
|
904
|
+
// that the prior converged Copilot review still stands for this head (no
|
|
905
|
+
// outstanding request, zero unresolved threads, and in strict mode a
|
|
906
|
+
// provably docs-only or integrate-only delta), or that an operator suppression marker re-verifies
|
|
907
|
+
// on the same terms. Never derived here from other snapshot facts: this
|
|
908
|
+
// evaluator trusts the caller's verification, so the request tool and the
|
|
909
|
+
// gate cannot disagree about the same head.
|
|
786
910
|
const postConvergenceReviewSuppressed = input.postConvergenceReviewSuppressed === true;
|
|
787
911
|
// maxCopilotRounds: 0 disables the external Copilot review gate entirely
|
|
788
912
|
// (for repos without Copilot / local-harness-only review). It reuses the
|
|
@@ -833,9 +957,20 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
833
957
|
const refinementArtifactStatus = normalizeRefinementArtifactStatus(refinementArtifact?.status);
|
|
834
958
|
const refinementLinkedIssue = Number.isInteger(refinementArtifact?.linkedIssue) ? refinementArtifact.linkedIssue : null;
|
|
835
959
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
960
|
+
// Keep the shared loop's body-feedback signal intact. Gate entry alone may
|
|
961
|
+
// reinterpret a thread-clean, current-head review that the shared convergence
|
|
962
|
+
// evaluator accepts (notably a conductor-overridable blue review).
|
|
963
|
+
const gateEntryConverged = copilotConvergenceOk
|
|
964
|
+
&& input.copilotReviewOnCurrentHead === true
|
|
965
|
+
&& unresolvedThreadCount === 0;
|
|
966
|
+
const effectiveLifecycleState = gateEntryConverged && lifecycleState === STATE.UNRESOLVED_FEEDBACK_PRESENT
|
|
967
|
+
? STATE.READY_TO_REREQUEST_REVIEW
|
|
968
|
+
: (gateEntryConverged && lifecycleState === STATE.ROUND_CAP_REACHED
|
|
969
|
+
? STATE.ROUND_CAP_CLEAN_FALLBACK
|
|
970
|
+
: lifecycleState);
|
|
971
|
+
const sameHeadCleanConverged = input.sameHeadCleanConverged === true || gateEntryConverged;
|
|
972
|
+
|
|
973
|
+
const draftGate = toGateStatus(input.draftGate, input.draftGateMarker, currentHeadSha, input.unresolvedGateThreadCount);
|
|
839
974
|
const preApprovalGate = toGateStatus(input.preApprovalGate, input.preApprovalGateMarker, currentHeadSha);
|
|
840
975
|
const draftGateAlreadySatisfied = !prDraft && (draftGate?.cleanEvidenceExists ?? false);
|
|
841
976
|
|
|
@@ -1118,7 +1253,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1118
1253
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1119
1254
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1120
1255
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1121
|
-
draftGateAlreadySatisfied
|
|
1256
|
+
draftGateAlreadySatisfied,
|
|
1122
1257
|
draftGate,
|
|
1123
1258
|
preApprovalGate,
|
|
1124
1259
|
allowedNextActions,
|
|
@@ -1138,6 +1273,83 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1138
1273
|
PR_CHECKPOINT_ACTION.DECLARE_MERGE_READY,
|
|
1139
1274
|
];
|
|
1140
1275
|
|
|
1276
|
+
// ADR 0088: the round's draft_gate marker verdict is ALREADY clean — CI
|
|
1277
|
+
// pending/failing or re-running draft_gate can never clear a dangling
|
|
1278
|
+
// gate-authored thread (e.g. an answered, judge-rejected `question`
|
|
1279
|
+
// close-gate-findings has not yet reject-closed), so this must run BEFORE
|
|
1280
|
+
// the CI-status checks below: neither a CI-blocked nor a run_draft_gate
|
|
1281
|
+
// result would name the real blocker, and a loop that followed
|
|
1282
|
+
// run_draft_gate would re-run a gate round that already passed.
|
|
1283
|
+
// reply_resolve_review_threads (FEEDBACK_RESOLUTION) is reused as-is —
|
|
1284
|
+
// resolving the thread (reject-close or fixer triage) is exactly what
|
|
1285
|
+
// this action already means on the post-draft path.
|
|
1286
|
+
if (draftGate.markerCleanThreadsUnresolved) {
|
|
1287
|
+
pushUnique(allowedNextActions, [
|
|
1288
|
+
PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS,
|
|
1289
|
+
// The reason text names "stop for the operator" as the remedy for an
|
|
1290
|
+
// unanswered question that cannot be answered — that escalation must
|
|
1291
|
+
// be a legal next action, not just prose an allowedNextActions-honoring
|
|
1292
|
+
// conductor has no sanctioned way to take.
|
|
1293
|
+
PR_CHECKPOINT_ACTION.REPORT_BLOCKED,
|
|
1294
|
+
]);
|
|
1295
|
+
pushUnique(forbiddenActions, [
|
|
1296
|
+
PR_CHECKPOINT_ACTION.RUN_DRAFT_GATE,
|
|
1297
|
+
...draftReviewForbidden,
|
|
1298
|
+
]);
|
|
1299
|
+
return buildResult({
|
|
1300
|
+
repo: input.repo ?? null,
|
|
1301
|
+
pr: Number.isInteger(input.pr) ? input.pr : null,
|
|
1302
|
+
currentHeadSha,
|
|
1303
|
+
lifecycleState: lifecycleState || STATE.PR_DRAFT,
|
|
1304
|
+
loopDisposition: DISPOSITION.UNRESOLVED_FEEDBACK,
|
|
1305
|
+
gateBoundary: PR_CHECKPOINT.FEEDBACK_RESOLUTION,
|
|
1306
|
+
draftGateAlreadySatisfied,
|
|
1307
|
+
draftGate,
|
|
1308
|
+
preApprovalGate,
|
|
1309
|
+
allowedNextActions,
|
|
1310
|
+
forbiddenActions,
|
|
1311
|
+
nextAction: PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS,
|
|
1312
|
+
reason: "The PR is still draft and this round's draft_gate verdict is clean, but a gate-authored review thread is still unresolved (ADR 0088) — reply/resolve it rather than re-running the draft gate. An open defect thread is resolved via fixer fix-close, or the disposition pass's defer-close (close-gate-findings) once eligible. A question thread's remedy depends on the judge's disposition: unanswered — post an answer reply, then rerun close-gate-findings, which reject-closes it if the judge disposed it reject; answered and judge-disposed reject — rerun close-gate-findings to reject-close it; answered and judge-disposed act — resolved by the fixer's own answer-and-resolve path; judge-deferred, or ambiguous/unrecorded disposition — needs an operator decision, no automated path.",
|
|
1313
|
+
mergeStateStatus,
|
|
1314
|
+
conflictFiles,
|
|
1315
|
+
refinementArtifact,
|
|
1316
|
+
copilotReviewRoundCount,
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Distinct from markerCleanThreadsUnresolved above: the thread-state READ
|
|
1321
|
+
// itself failed (unresolvedGateThreadCount === -1), not a real dangling
|
|
1322
|
+
// thread — naming "resolve the thread" would be misleading when there is
|
|
1323
|
+
// no known thread to resolve. MARK_READY_FOR_REVIEW stays forbidden
|
|
1324
|
+
// either way (draftReviewForbidden); reply_resolve_review_threads is
|
|
1325
|
+
// reused as-is rather than adding a new action for "retry the read".
|
|
1326
|
+
if (draftGate.markerCleanThreadStateUnreadable) {
|
|
1327
|
+
pushUnique(allowedNextActions, [PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS]);
|
|
1328
|
+
pushUnique(forbiddenActions, [
|
|
1329
|
+
PR_CHECKPOINT_ACTION.RUN_DRAFT_GATE,
|
|
1330
|
+
...draftReviewForbidden,
|
|
1331
|
+
]);
|
|
1332
|
+
return buildResult({
|
|
1333
|
+
repo: input.repo ?? null,
|
|
1334
|
+
pr: Number.isInteger(input.pr) ? input.pr : null,
|
|
1335
|
+
currentHeadSha,
|
|
1336
|
+
lifecycleState: lifecycleState || STATE.PR_DRAFT,
|
|
1337
|
+
loopDisposition: DISPOSITION.UNRESOLVED_FEEDBACK,
|
|
1338
|
+
gateBoundary: PR_CHECKPOINT.FEEDBACK_RESOLUTION,
|
|
1339
|
+
draftGateAlreadySatisfied,
|
|
1340
|
+
draftGate,
|
|
1341
|
+
preApprovalGate,
|
|
1342
|
+
allowedNextActions,
|
|
1343
|
+
forbiddenActions,
|
|
1344
|
+
nextAction: PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS,
|
|
1345
|
+
reason: "The PR is still draft and this round's draft_gate verdict is clean, but this round's gate-authored review-thread count could not be read (API failure) — could not read review-thread state; re-run when API connectivity is restored, rather than treating an unreadable count as either clean or a real dangling thread.",
|
|
1346
|
+
mergeStateStatus,
|
|
1347
|
+
conflictFiles,
|
|
1348
|
+
refinementArtifact,
|
|
1349
|
+
copilotReviewRoundCount,
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1141
1353
|
if (!draftGate.currentHeadClean && draftGateRequireCi) {
|
|
1142
1354
|
if (ciStatus === "failure") {
|
|
1143
1355
|
pushUnique(allowedNextActions, [PR_CHECKPOINT_ACTION.REPORT_BLOCKED]);
|
|
@@ -1152,7 +1364,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1152
1364
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1153
1365
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1154
1366
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1155
|
-
draftGateAlreadySatisfied
|
|
1367
|
+
draftGateAlreadySatisfied,
|
|
1156
1368
|
draftGate,
|
|
1157
1369
|
preApprovalGate,
|
|
1158
1370
|
allowedNextActions,
|
|
@@ -1178,7 +1390,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1178
1390
|
lifecycleState: STATE.WAITING_FOR_CI,
|
|
1179
1391
|
loopDisposition: DISPOSITION.PENDING,
|
|
1180
1392
|
gateBoundary: PR_CHECKPOINT.DRAFT_REVIEW,
|
|
1181
|
-
draftGateAlreadySatisfied
|
|
1393
|
+
draftGateAlreadySatisfied,
|
|
1182
1394
|
draftGate,
|
|
1183
1395
|
preApprovalGate,
|
|
1184
1396
|
allowedNextActions,
|
|
@@ -1250,7 +1462,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1250
1462
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1251
1463
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1252
1464
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1253
|
-
draftGateAlreadySatisfied
|
|
1465
|
+
draftGateAlreadySatisfied,
|
|
1254
1466
|
draftGate,
|
|
1255
1467
|
preApprovalGate,
|
|
1256
1468
|
allowedNextActions,
|
|
@@ -1270,7 +1482,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1270
1482
|
return buildTitleMarkerBlockedResult({
|
|
1271
1483
|
input,
|
|
1272
1484
|
currentHeadSha,
|
|
1273
|
-
draftGateAlreadySatisfied
|
|
1485
|
+
draftGateAlreadySatisfied,
|
|
1274
1486
|
draftGate,
|
|
1275
1487
|
preApprovalGate,
|
|
1276
1488
|
mergeStateStatus,
|
|
@@ -1302,7 +1514,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1302
1514
|
lifecycleState: effectiveLifecycleState,
|
|
1303
1515
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1304
1516
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1305
|
-
draftGateAlreadySatisfied
|
|
1517
|
+
draftGateAlreadySatisfied,
|
|
1306
1518
|
draftGate,
|
|
1307
1519
|
preApprovalGate,
|
|
1308
1520
|
allowedNextActions,
|
|
@@ -1326,7 +1538,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1326
1538
|
lifecycleState: effectiveLifecycleState,
|
|
1327
1539
|
loopDisposition: loopDisposition ?? DISPOSITION.ACTION_REQUIRED,
|
|
1328
1540
|
gateBoundary: PR_CHECKPOINT.PRE_APPROVAL_GATE_WINDOW,
|
|
1329
|
-
draftGateAlreadySatisfied
|
|
1541
|
+
draftGateAlreadySatisfied,
|
|
1330
1542
|
draftGate,
|
|
1331
1543
|
preApprovalGate,
|
|
1332
1544
|
allowedNextActions,
|
|
@@ -1453,7 +1665,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1453
1665
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1454
1666
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1455
1667
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1456
|
-
draftGateAlreadySatisfied
|
|
1668
|
+
draftGateAlreadySatisfied,
|
|
1457
1669
|
draftGate,
|
|
1458
1670
|
preApprovalGate,
|
|
1459
1671
|
allowedNextActions,
|
|
@@ -1478,7 +1690,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1478
1690
|
lifecycleState: STATE.WAITING_FOR_CI,
|
|
1479
1691
|
loopDisposition: DISPOSITION.PENDING,
|
|
1480
1692
|
gateBoundary: PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
1481
|
-
draftGateAlreadySatisfied
|
|
1693
|
+
draftGateAlreadySatisfied,
|
|
1482
1694
|
draftGate,
|
|
1483
1695
|
preApprovalGate,
|
|
1484
1696
|
allowedNextActions,
|
|
@@ -1512,7 +1724,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1512
1724
|
lifecycleState: effectiveLifecycleState,
|
|
1513
1725
|
loopDisposition: loopDisposition ?? DISPOSITION.ACTION_REQUIRED,
|
|
1514
1726
|
gateBoundary: PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
1515
|
-
draftGateAlreadySatisfied
|
|
1727
|
+
draftGateAlreadySatisfied,
|
|
1516
1728
|
draftGate,
|
|
1517
1729
|
preApprovalGate,
|
|
1518
1730
|
allowedNextActions,
|
|
@@ -1533,7 +1745,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1533
1745
|
return buildTitleMarkerBlockedResult({
|
|
1534
1746
|
input,
|
|
1535
1747
|
currentHeadSha,
|
|
1536
|
-
draftGateAlreadySatisfied
|
|
1748
|
+
draftGateAlreadySatisfied,
|
|
1537
1749
|
draftGate,
|
|
1538
1750
|
preApprovalGate,
|
|
1539
1751
|
mergeStateStatus,
|
|
@@ -1543,7 +1755,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1543
1755
|
});
|
|
1544
1756
|
}
|
|
1545
1757
|
|
|
1546
|
-
if (!draftGate.cleanEvidenceExists
|
|
1758
|
+
if (!draftGate.cleanEvidenceExists) {
|
|
1547
1759
|
return buildDraftGateNeededForMergeResult({
|
|
1548
1760
|
input,
|
|
1549
1761
|
currentHeadSha,
|
|
@@ -1571,14 +1783,14 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1571
1783
|
lifecycleState: effectiveLifecycleState,
|
|
1572
1784
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1573
1785
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1574
|
-
draftGateAlreadySatisfied
|
|
1786
|
+
draftGateAlreadySatisfied,
|
|
1575
1787
|
draftGate,
|
|
1576
1788
|
preApprovalGate,
|
|
1577
1789
|
allowedNextActions,
|
|
1578
1790
|
forbiddenActions,
|
|
1579
1791
|
nextAction: PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
1580
1792
|
reason: roundCapReached
|
|
1581
|
-
? `Round-cap clean fallback
|
|
1793
|
+
? `Round-cap clean fallback (${copilotReviewRoundCount}/${maxCopilotRounds} rounds, zero unresolved threads, ${describeAcceptedCiState(ciStatus, preApprovalRequireCi)}) with clean draft_gate evidence; the current head has clean \`pre_approval_gate\` evidence, so the PR is at the final approval boundary.`
|
|
1582
1794
|
: (ciStatus === "crediblyGreen"
|
|
1583
1795
|
? "The current head has both a clean settled review cycle and clean `pre_approval_gate` evidence, and its zero-suite CI state is accepted as credibly green, so the PR is at the final approval boundary."
|
|
1584
1796
|
: "The current head has both a clean settled review cycle and clean `pre_approval_gate` evidence, so the PR is at the final approval boundary."),
|
|
@@ -1611,7 +1823,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1611
1823
|
reason: roundCapReached
|
|
1612
1824
|
? `The Copilot round limit is exhausted (${copilotReviewRoundCount}/${maxCopilotRounds}), and the current head has zero unresolved threads with ${describeAcceptedCiState(ciStatus, preApprovalRequireCi)}, so \`pre_approval_gate\` fallback is now the next legal boundary.`
|
|
1613
1825
|
: (postConvergenceReviewSuppressed && !sameHeadCleanConverged
|
|
1614
|
-
? "
|
|
1826
|
+
? "The current head carries the prior converged Copilot review (carriedConvergence names the source review): no request is outstanding, no review thread is unresolved, and the carry rule holds (converged-once by default; a provably docs-only or integrate-only delta under refinement.requireCopilotConvergenceAtLatestHead), so `pre_approval_gate` is now the next legal boundary."
|
|
1615
1827
|
: (ciStatus === "crediblyGreen"
|
|
1616
1828
|
? "The current head has a clean settled post-draft review cycle, and its zero-suite CI state is accepted as credibly green, so `pre_approval_gate` is now the next legal boundary."
|
|
1617
1829
|
: "The current head has a clean settled post-draft review cycle, so `pre_approval_gate` is now the next legal boundary.")),
|
|
@@ -1647,7 +1859,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1647
1859
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1648
1860
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1649
1861
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1650
|
-
draftGateAlreadySatisfied
|
|
1862
|
+
draftGateAlreadySatisfied,
|
|
1651
1863
|
draftGate,
|
|
1652
1864
|
preApprovalGate,
|
|
1653
1865
|
allowedNextActions,
|
|
@@ -1671,7 +1883,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1671
1883
|
lifecycleState: STATE.WAITING_FOR_CI,
|
|
1672
1884
|
loopDisposition: DISPOSITION.PENDING,
|
|
1673
1885
|
gateBoundary: PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
1674
|
-
draftGateAlreadySatisfied
|
|
1886
|
+
draftGateAlreadySatisfied,
|
|
1675
1887
|
draftGate,
|
|
1676
1888
|
preApprovalGate,
|
|
1677
1889
|
allowedNextActions,
|
|
@@ -1689,7 +1901,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1689
1901
|
return buildTitleMarkerBlockedResult({
|
|
1690
1902
|
input,
|
|
1691
1903
|
currentHeadSha,
|
|
1692
|
-
draftGateAlreadySatisfied
|
|
1904
|
+
draftGateAlreadySatisfied,
|
|
1693
1905
|
draftGate,
|
|
1694
1906
|
preApprovalGate,
|
|
1695
1907
|
mergeStateStatus,
|
|
@@ -1700,11 +1912,12 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1700
1912
|
}
|
|
1701
1913
|
// Mirror LOW_SIGNAL_CONVERGED: a clean current head with no clean
|
|
1702
1914
|
// draft_gate evidence must reconcile the draft gate rather than jump to
|
|
1703
|
-
// final approval. This
|
|
1704
|
-
//
|
|
1705
|
-
//
|
|
1706
|
-
//
|
|
1707
|
-
//
|
|
1915
|
+
// final approval. This is the SAME rule applyDraftGateEvidenceGuard
|
|
1916
|
+
// enforces generically for every DRAFT_GATE_EVIDENCE_GUARDED_BOUNDARIES
|
|
1917
|
+
// boundary on a non-draft PR (draftGate.cleanEvidenceExists false →
|
|
1918
|
+
// reconcile_draft_gate); this inline check covers the same outcome for
|
|
1919
|
+
// FINAL_APPROVAL_READY specifically, before that wrapper-level guard
|
|
1920
|
+
// runs. Without this guard the final-approval-without-draft-gate
|
|
1708
1921
|
// branch is dead through the real script and asserts behavior it never
|
|
1709
1922
|
// produces.
|
|
1710
1923
|
if (!draftGate.cleanEvidenceExists) {
|
|
@@ -1737,13 +1950,13 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1737
1950
|
lifecycleState: effectiveLifecycleState,
|
|
1738
1951
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1739
1952
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1740
|
-
draftGateAlreadySatisfied
|
|
1953
|
+
draftGateAlreadySatisfied,
|
|
1741
1954
|
draftGate,
|
|
1742
1955
|
preApprovalGate,
|
|
1743
1956
|
allowedNextActions,
|
|
1744
1957
|
forbiddenActions,
|
|
1745
1958
|
nextAction: PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
1746
|
-
reason: `Round-cap clean fallback
|
|
1959
|
+
reason: `Round-cap clean fallback (${copilotReviewRoundCount}/${maxCopilotRounds} rounds, zero unresolved threads, ${describeAcceptedCiState(ciStatus, preApprovalRequireCi)}) with clean draft_gate evidence; the current head has clean \`pre_approval_gate\` evidence, so the PR is at the final approval boundary.`,
|
|
1747
1960
|
mergeStateStatus,
|
|
1748
1961
|
conflictFiles,
|
|
1749
1962
|
refinementArtifact,
|
|
@@ -1764,7 +1977,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1764
1977
|
lifecycleState: effectiveLifecycleState,
|
|
1765
1978
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1766
1979
|
gateBoundary: PR_CHECKPOINT.PRE_APPROVAL_GATE_WINDOW,
|
|
1767
|
-
draftGateAlreadySatisfied
|
|
1980
|
+
draftGateAlreadySatisfied,
|
|
1768
1981
|
draftGate,
|
|
1769
1982
|
preApprovalGate,
|
|
1770
1983
|
allowedNextActions,
|
|
@@ -1807,7 +2020,15 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1807
2020
|
// human-read reason/evidence text for the latter case would be a false CI
|
|
1808
2021
|
// claim, so describe the actual grant basis instead.
|
|
1809
2022
|
const ciClause = ciStatus === "success" ? "green CI" : "CI not required by config";
|
|
1810
|
-
|
|
2023
|
+
// No current-head Copilot review at the cap IS the round-cap clean
|
|
2024
|
+
// fallback, so the convergence evaluator's absent-review refusal must not
|
|
2025
|
+
// block this grant; a current-head review with findings still does. An
|
|
2026
|
+
// unknown head never opens the absent-review branch. Nor does an
|
|
2027
|
+
// earlier-head body-only finding without a disposition record: the caller
|
|
2028
|
+
// must state it absent (fail closed when the fact is missing).
|
|
2029
|
+
const absentReviewFallback = currentHeadSha !== null && input.copilotReviewOnCurrentHead === false
|
|
2030
|
+
&& input.copilotPriorHeadBodyFeedbackUnresolved === false;
|
|
2031
|
+
if (unresolvedThreadCount === 0 && ciConfirmedGreen && (copilotConvergenceOk || absentReviewFallback)) {
|
|
1811
2032
|
if (preApprovalGate.currentHeadClean) {
|
|
1812
2033
|
// Inline title-marker check, mirroring ROUND_CAP_CLEAN_FALLBACK: the
|
|
1813
2034
|
// outer post-pass guards FINAL_APPROVAL_READY and
|
|
@@ -1819,7 +2040,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1819
2040
|
return buildTitleMarkerBlockedResult({
|
|
1820
2041
|
input,
|
|
1821
2042
|
currentHeadSha,
|
|
1822
|
-
draftGateAlreadySatisfied
|
|
2043
|
+
draftGateAlreadySatisfied,
|
|
1823
2044
|
draftGate,
|
|
1824
2045
|
preApprovalGate,
|
|
1825
2046
|
mergeStateStatus,
|
|
@@ -1859,13 +2080,13 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1859
2080
|
lifecycleState: effectiveLifecycleState,
|
|
1860
2081
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1861
2082
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1862
|
-
draftGateAlreadySatisfied
|
|
2083
|
+
draftGateAlreadySatisfied,
|
|
1863
2084
|
draftGate,
|
|
1864
2085
|
preApprovalGate,
|
|
1865
2086
|
allowedNextActions,
|
|
1866
2087
|
forbiddenActions,
|
|
1867
2088
|
nextAction: PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
1868
|
-
reason: `Round-cap exhaustion fallback
|
|
2089
|
+
reason: `Round-cap exhaustion fallback (${copilotReviewRoundCount}/${maxCopilotRounds} rounds, zero unresolved threads, ${ciClause}) with clean draft_gate evidence; the current head has clean \`pre_approval_gate\` evidence, so the PR is at the final approval boundary.`,
|
|
1869
2090
|
mergeStateStatus,
|
|
1870
2091
|
conflictFiles,
|
|
1871
2092
|
refinementArtifact,
|
|
@@ -1887,7 +2108,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1887
2108
|
lifecycleState: effectiveLifecycleState,
|
|
1888
2109
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1889
2110
|
gateBoundary: PR_CHECKPOINT.PRE_APPROVAL_GATE_WINDOW,
|
|
1890
|
-
draftGateAlreadySatisfied
|
|
2111
|
+
draftGateAlreadySatisfied,
|
|
1891
2112
|
draftGate,
|
|
1892
2113
|
preApprovalGate,
|
|
1893
2114
|
allowedNextActions,
|
|
@@ -1914,7 +2135,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1914
2135
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1915
2136
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1916
2137
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1917
|
-
draftGateAlreadySatisfied
|
|
2138
|
+
draftGateAlreadySatisfied,
|
|
1918
2139
|
draftGate,
|
|
1919
2140
|
preApprovalGate,
|
|
1920
2141
|
allowedNextActions,
|
|
@@ -1938,7 +2159,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1938
2159
|
lifecycleState: STATE.WAITING_FOR_CI,
|
|
1939
2160
|
loopDisposition: DISPOSITION.PENDING,
|
|
1940
2161
|
gateBoundary: PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
1941
|
-
draftGateAlreadySatisfied
|
|
2162
|
+
draftGateAlreadySatisfied,
|
|
1942
2163
|
draftGate,
|
|
1943
2164
|
preApprovalGate,
|
|
1944
2165
|
allowedNextActions,
|
|
@@ -1956,7 +2177,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1956
2177
|
return buildTitleMarkerBlockedResult({
|
|
1957
2178
|
input,
|
|
1958
2179
|
currentHeadSha,
|
|
1959
|
-
draftGateAlreadySatisfied
|
|
2180
|
+
draftGateAlreadySatisfied,
|
|
1960
2181
|
draftGate,
|
|
1961
2182
|
preApprovalGate,
|
|
1962
2183
|
mergeStateStatus,
|
|
@@ -1994,7 +2215,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1994
2215
|
lifecycleState: effectiveLifecycleState,
|
|
1995
2216
|
loopDisposition: DISPOSITION.DONE,
|
|
1996
2217
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1997
|
-
draftGateAlreadySatisfied
|
|
2218
|
+
draftGateAlreadySatisfied,
|
|
1998
2219
|
draftGate,
|
|
1999
2220
|
preApprovalGate,
|
|
2000
2221
|
allowedNextActions,
|