@dev-loops/core 1.0.4-pre.0 → 1.0.4-pre.1
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 +1 -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 +143 -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 +38 -2
- 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 +87 -22
- package/src/loop/pr-gate-coordination.mjs +270 -60
- 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
|
}
|
|
@@ -624,11 +667,12 @@ function applyUnsettledCopilotReviewEntryGuard(input, result) {
|
|
|
624
667
|
// grant boundary here after gating CI/threads, and a significant
|
|
625
668
|
// post-convergence change routes to a rerequest (a new cycle) before this
|
|
626
669
|
// guard runs, so this exemption cannot mask a genuinely-unreviewed change;
|
|
627
|
-
// - postConvergenceReviewSuppressed:
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
//
|
|
670
|
+
// - postConvergenceReviewSuppressed: the caller verified a carried
|
|
671
|
+
// convergence (or an operator suppression marker) for this head: no
|
|
672
|
+
// outstanding request, zero unresolved threads, and a provably docs-only
|
|
673
|
+
// or integrate-only delta since Copilot's last submitted review, so the
|
|
674
|
+
// prior converged review stands for this head (never derived here from
|
|
675
|
+
// other snapshot facts).
|
|
632
676
|
// Fail closed on ANY non-outstanding status, not only the literal "none":
|
|
633
677
|
// this is the independent gate-ENTRY re-check, so it must not trust the
|
|
634
678
|
// caller's status string. A non-canonical/unknown value ("", "unavailable",
|
|
@@ -723,6 +767,67 @@ function applyUnsettledCopilotReviewEntryGuard(input, result) {
|
|
|
723
767
|
});
|
|
724
768
|
}
|
|
725
769
|
|
|
770
|
+
/**
|
|
771
|
+
* Boundaries at which a PR must carry clean draft_gate evidence; without it
|
|
772
|
+
* the only legal next action is reconcile_draft_gate
|
|
773
|
+
* (skills/docs/pr-lifecycle-contract.md, LIFECYCLE-FAIL-CLOSED).
|
|
774
|
+
*/
|
|
775
|
+
const DRAFT_GATE_EVIDENCE_GUARDED_BOUNDARIES = Object.freeze([
|
|
776
|
+
PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
777
|
+
PR_CHECKPOINT.FEEDBACK_RESOLUTION,
|
|
778
|
+
PR_CHECKPOINT.PRE_APPROVAL_GATE_NEEDED,
|
|
779
|
+
PR_CHECKPOINT.PRE_APPROVAL_GATE_WINDOW,
|
|
780
|
+
PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
781
|
+
]);
|
|
782
|
+
|
|
783
|
+
function applyDraftGateEvidenceGuard(result, { prDraft = false } = {}) {
|
|
784
|
+
if (!result || typeof result !== "object") {
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
// The PR is still draft: absent/not-yet-clean draft_gate evidence is the
|
|
788
|
+
// EXPECTED state while draft_gate remediation is in progress (e.g. a fixer
|
|
789
|
+
// mid-way through COMPLETE_FIXER_DISPOSITION for threads it tackled from an
|
|
790
|
+
// earlier draft_gate round) — the PR_DRAFT branch above already owns this
|
|
791
|
+
// state (RUN_DRAFT_GATE / MARK_READY_FOR_REVIEW). This guard exists for the
|
|
792
|
+
// POST-draft merge-path boundaries only (mirrors the wrapper's own
|
|
793
|
+
// prDraft skip for the title-marker guard, below); rewriting an in-progress
|
|
794
|
+
// draft-side result to reconcile_draft_gate would misdirect the fixer.
|
|
795
|
+
if (prDraft) {
|
|
796
|
+
return result;
|
|
797
|
+
}
|
|
798
|
+
if (result.draftGate?.cleanEvidenceExists === true) {
|
|
799
|
+
return result;
|
|
800
|
+
}
|
|
801
|
+
if (!DRAFT_GATE_EVIDENCE_GUARDED_BOUNDARIES.includes(result.gateBoundary)) {
|
|
802
|
+
return result;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const allowedNextActions = [];
|
|
806
|
+
const forbiddenActions = [];
|
|
807
|
+
pushUnique(allowedNextActions, [PR_CHECKPOINT_ACTION.RECONCILE_DRAFT_GATE]);
|
|
808
|
+
pushUnique(forbiddenActions, [
|
|
809
|
+
PR_CHECKPOINT_ACTION.RUN_DRAFT_GATE,
|
|
810
|
+
PR_CHECKPOINT_ACTION.MARK_READY_FOR_REVIEW,
|
|
811
|
+
PR_CHECKPOINT_ACTION.REQUEST_COPILOT_REVIEW,
|
|
812
|
+
PR_CHECKPOINT_ACTION.WAIT_FOR_COPILOT_REVIEW,
|
|
813
|
+
PR_CHECKPOINT_ACTION.RUN_PRE_APPROVAL_GATE,
|
|
814
|
+
PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
815
|
+
PR_CHECKPOINT_ACTION.DECLARE_MERGE_READY,
|
|
816
|
+
]);
|
|
817
|
+
|
|
818
|
+
return {
|
|
819
|
+
...result,
|
|
820
|
+
gateBoundary: PR_CHECKPOINT.DRAFT_GATE_NEEDED,
|
|
821
|
+
nextAction: PR_CHECKPOINT_ACTION.RECONCILE_DRAFT_GATE,
|
|
822
|
+
allowedNextActions,
|
|
823
|
+
forbiddenActions,
|
|
824
|
+
reason: result.draftGate?.anyVisible
|
|
825
|
+
? "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."
|
|
826
|
+
: "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.",
|
|
827
|
+
gateEvidenceNote: null,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
726
831
|
/**
|
|
727
832
|
* Evaluates PR gate coordination, then re-asserts the merge-blocking title guard
|
|
728
833
|
* at the pre-approval / final-approval boundary for non-draft PRs.
|
|
@@ -731,31 +836,38 @@ function applyUnsettledCopilotReviewEntryGuard(input, result) {
|
|
|
731
836
|
* sites (defense in depth); this wrapper additionally covers the pre-approval
|
|
732
837
|
* gate boundary, which is reached before any pre-approval evidence exists and so
|
|
733
838
|
* is not protected by the inline checks.
|
|
839
|
+
*
|
|
840
|
+
* Invariant: every return path passes through `applyDraftGateEvidenceGuard`,
|
|
841
|
+
* which rewrites the five DRAFT_GATE_EVIDENCE_GUARDED_BOUNDARIES boundaries to
|
|
842
|
+
* DRAFT_GATE_NEEDED / reconcile_draft_gate whenever clean draft_gate evidence
|
|
843
|
+
* is absent — a still-draft PR is exempted (ADR 0088): absent/not-yet-clean
|
|
844
|
+
* draft_gate evidence is the normal state while draft_gate remediation is in
|
|
845
|
+
* progress, and the PR_DRAFT branch already owns that state.
|
|
734
846
|
*/
|
|
735
847
|
export function evaluatePrGateCoordination(input = {}) {
|
|
736
848
|
const result = evaluatePrGateCoordinationCore(input);
|
|
849
|
+
const prDraft = input.prDraft === true;
|
|
737
850
|
|
|
738
851
|
const unsettledReviewResult = applyUnsettledCopilotReviewEntryGuard(input, result);
|
|
739
852
|
if (unsettledReviewResult) {
|
|
740
|
-
return unsettledReviewResult;
|
|
853
|
+
return applyDraftGateEvidenceGuard(unsettledReviewResult, { prDraft });
|
|
741
854
|
}
|
|
742
855
|
|
|
743
|
-
const prDraft = input.prDraft === true;
|
|
744
856
|
const prTitle = typeof input.prTitle === "string" ? input.prTitle : "";
|
|
745
857
|
// Draft PRs may legitimately carry a WIP title; the marker only blocks once
|
|
746
858
|
// the PR has left draft and is at a pre-approval/final-approval boundary.
|
|
747
859
|
if (prDraft || !result || typeof result !== "object") {
|
|
748
|
-
return result;
|
|
860
|
+
return applyDraftGateEvidenceGuard(result, { prDraft });
|
|
749
861
|
}
|
|
750
862
|
if (!TITLE_MARKER_GUARDED_BOUNDARIES.includes(result.gateBoundary)) {
|
|
751
|
-
return result;
|
|
863
|
+
return applyDraftGateEvidenceGuard(result, { prDraft });
|
|
752
864
|
}
|
|
753
865
|
const markers = findBlockingTitleMarkers(prTitle);
|
|
754
866
|
if (markers.length === 0) {
|
|
755
|
-
return result;
|
|
867
|
+
return applyDraftGateEvidenceGuard(result, { prDraft });
|
|
756
868
|
}
|
|
757
869
|
|
|
758
|
-
return buildTitleMarkerBlockedResult({
|
|
870
|
+
return applyDraftGateEvidenceGuard(buildTitleMarkerBlockedResult({
|
|
759
871
|
input,
|
|
760
872
|
currentHeadSha: result.currentHeadSha ?? null,
|
|
761
873
|
draftGateAlreadySatisfied: result.draftGateAlreadySatisfied === true,
|
|
@@ -765,7 +877,7 @@ export function evaluatePrGateCoordination(input = {}) {
|
|
|
765
877
|
conflictFiles: result.conflictFiles ?? [],
|
|
766
878
|
markers,
|
|
767
879
|
refinementArtifact: result.refinementArtifact ?? null,
|
|
768
|
-
});
|
|
880
|
+
}), { prDraft });
|
|
769
881
|
}
|
|
770
882
|
|
|
771
883
|
function evaluatePrGateCoordinationCore(input = {}) {
|
|
@@ -775,14 +887,15 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
775
887
|
const prDraft = input.prDraft === true;
|
|
776
888
|
const prClosed = input.prClosed === true;
|
|
777
889
|
const prMerged = input.prMerged === true;
|
|
778
|
-
const
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
//
|
|
785
|
-
//
|
|
890
|
+
const copilotConvergenceOk = input.copilotConvergenceOk === true;
|
|
891
|
+
// Carried convergence: set only when the caller verified, through the
|
|
892
|
+
// shared carried-convergence predicate the Copilot request tool also uses,
|
|
893
|
+
// that the prior converged Copilot review still stands for this head (no
|
|
894
|
+
// outstanding request, zero unresolved threads, a provably docs-only or
|
|
895
|
+
// integrate-only delta), or that an operator suppression marker re-verifies
|
|
896
|
+
// on the same terms. Never derived here from other snapshot facts: this
|
|
897
|
+
// evaluator trusts the caller's verification, so the request tool and the
|
|
898
|
+
// gate cannot disagree about the same head.
|
|
786
899
|
const postConvergenceReviewSuppressed = input.postConvergenceReviewSuppressed === true;
|
|
787
900
|
// maxCopilotRounds: 0 disables the external Copilot review gate entirely
|
|
788
901
|
// (for repos without Copilot / local-harness-only review). It reuses the
|
|
@@ -833,9 +946,20 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
833
946
|
const refinementArtifactStatus = normalizeRefinementArtifactStatus(refinementArtifact?.status);
|
|
834
947
|
const refinementLinkedIssue = Number.isInteger(refinementArtifact?.linkedIssue) ? refinementArtifact.linkedIssue : null;
|
|
835
948
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
949
|
+
// Keep the shared loop's body-feedback signal intact. Gate entry alone may
|
|
950
|
+
// reinterpret a thread-clean, current-head review that the shared convergence
|
|
951
|
+
// evaluator accepts (notably a conductor-overridable blue review).
|
|
952
|
+
const gateEntryConverged = copilotConvergenceOk
|
|
953
|
+
&& input.copilotReviewOnCurrentHead === true
|
|
954
|
+
&& unresolvedThreadCount === 0;
|
|
955
|
+
const effectiveLifecycleState = gateEntryConverged && lifecycleState === STATE.UNRESOLVED_FEEDBACK_PRESENT
|
|
956
|
+
? STATE.READY_TO_REREQUEST_REVIEW
|
|
957
|
+
: (gateEntryConverged && lifecycleState === STATE.ROUND_CAP_REACHED
|
|
958
|
+
? STATE.ROUND_CAP_CLEAN_FALLBACK
|
|
959
|
+
: lifecycleState);
|
|
960
|
+
const sameHeadCleanConverged = input.sameHeadCleanConverged === true || gateEntryConverged;
|
|
961
|
+
|
|
962
|
+
const draftGate = toGateStatus(input.draftGate, input.draftGateMarker, currentHeadSha, input.unresolvedGateThreadCount);
|
|
839
963
|
const preApprovalGate = toGateStatus(input.preApprovalGate, input.preApprovalGateMarker, currentHeadSha);
|
|
840
964
|
const draftGateAlreadySatisfied = !prDraft && (draftGate?.cleanEvidenceExists ?? false);
|
|
841
965
|
|
|
@@ -1118,7 +1242,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1118
1242
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1119
1243
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1120
1244
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1121
|
-
draftGateAlreadySatisfied
|
|
1245
|
+
draftGateAlreadySatisfied,
|
|
1122
1246
|
draftGate,
|
|
1123
1247
|
preApprovalGate,
|
|
1124
1248
|
allowedNextActions,
|
|
@@ -1138,6 +1262,83 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1138
1262
|
PR_CHECKPOINT_ACTION.DECLARE_MERGE_READY,
|
|
1139
1263
|
];
|
|
1140
1264
|
|
|
1265
|
+
// ADR 0088: the round's draft_gate marker verdict is ALREADY clean — CI
|
|
1266
|
+
// pending/failing or re-running draft_gate can never clear a dangling
|
|
1267
|
+
// gate-authored thread (e.g. an answered, judge-rejected `question`
|
|
1268
|
+
// close-gate-findings has not yet reject-closed), so this must run BEFORE
|
|
1269
|
+
// the CI-status checks below: neither a CI-blocked nor a run_draft_gate
|
|
1270
|
+
// result would name the real blocker, and a loop that followed
|
|
1271
|
+
// run_draft_gate would re-run a gate round that already passed.
|
|
1272
|
+
// reply_resolve_review_threads (FEEDBACK_RESOLUTION) is reused as-is —
|
|
1273
|
+
// resolving the thread (reject-close or fixer triage) is exactly what
|
|
1274
|
+
// this action already means on the post-draft path.
|
|
1275
|
+
if (draftGate.markerCleanThreadsUnresolved) {
|
|
1276
|
+
pushUnique(allowedNextActions, [
|
|
1277
|
+
PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS,
|
|
1278
|
+
// The reason text names "stop for the operator" as the remedy for an
|
|
1279
|
+
// unanswered question that cannot be answered — that escalation must
|
|
1280
|
+
// be a legal next action, not just prose an allowedNextActions-honoring
|
|
1281
|
+
// conductor has no sanctioned way to take.
|
|
1282
|
+
PR_CHECKPOINT_ACTION.REPORT_BLOCKED,
|
|
1283
|
+
]);
|
|
1284
|
+
pushUnique(forbiddenActions, [
|
|
1285
|
+
PR_CHECKPOINT_ACTION.RUN_DRAFT_GATE,
|
|
1286
|
+
...draftReviewForbidden,
|
|
1287
|
+
]);
|
|
1288
|
+
return buildResult({
|
|
1289
|
+
repo: input.repo ?? null,
|
|
1290
|
+
pr: Number.isInteger(input.pr) ? input.pr : null,
|
|
1291
|
+
currentHeadSha,
|
|
1292
|
+
lifecycleState: lifecycleState || STATE.PR_DRAFT,
|
|
1293
|
+
loopDisposition: DISPOSITION.UNRESOLVED_FEEDBACK,
|
|
1294
|
+
gateBoundary: PR_CHECKPOINT.FEEDBACK_RESOLUTION,
|
|
1295
|
+
draftGateAlreadySatisfied,
|
|
1296
|
+
draftGate,
|
|
1297
|
+
preApprovalGate,
|
|
1298
|
+
allowedNextActions,
|
|
1299
|
+
forbiddenActions,
|
|
1300
|
+
nextAction: PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS,
|
|
1301
|
+
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.",
|
|
1302
|
+
mergeStateStatus,
|
|
1303
|
+
conflictFiles,
|
|
1304
|
+
refinementArtifact,
|
|
1305
|
+
copilotReviewRoundCount,
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// Distinct from markerCleanThreadsUnresolved above: the thread-state READ
|
|
1310
|
+
// itself failed (unresolvedGateThreadCount === -1), not a real dangling
|
|
1311
|
+
// thread — naming "resolve the thread" would be misleading when there is
|
|
1312
|
+
// no known thread to resolve. MARK_READY_FOR_REVIEW stays forbidden
|
|
1313
|
+
// either way (draftReviewForbidden); reply_resolve_review_threads is
|
|
1314
|
+
// reused as-is rather than adding a new action for "retry the read".
|
|
1315
|
+
if (draftGate.markerCleanThreadStateUnreadable) {
|
|
1316
|
+
pushUnique(allowedNextActions, [PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS]);
|
|
1317
|
+
pushUnique(forbiddenActions, [
|
|
1318
|
+
PR_CHECKPOINT_ACTION.RUN_DRAFT_GATE,
|
|
1319
|
+
...draftReviewForbidden,
|
|
1320
|
+
]);
|
|
1321
|
+
return buildResult({
|
|
1322
|
+
repo: input.repo ?? null,
|
|
1323
|
+
pr: Number.isInteger(input.pr) ? input.pr : null,
|
|
1324
|
+
currentHeadSha,
|
|
1325
|
+
lifecycleState: lifecycleState || STATE.PR_DRAFT,
|
|
1326
|
+
loopDisposition: DISPOSITION.UNRESOLVED_FEEDBACK,
|
|
1327
|
+
gateBoundary: PR_CHECKPOINT.FEEDBACK_RESOLUTION,
|
|
1328
|
+
draftGateAlreadySatisfied,
|
|
1329
|
+
draftGate,
|
|
1330
|
+
preApprovalGate,
|
|
1331
|
+
allowedNextActions,
|
|
1332
|
+
forbiddenActions,
|
|
1333
|
+
nextAction: PR_CHECKPOINT_ACTION.REPLY_RESOLVE_REVIEW_THREADS,
|
|
1334
|
+
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.",
|
|
1335
|
+
mergeStateStatus,
|
|
1336
|
+
conflictFiles,
|
|
1337
|
+
refinementArtifact,
|
|
1338
|
+
copilotReviewRoundCount,
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1141
1342
|
if (!draftGate.currentHeadClean && draftGateRequireCi) {
|
|
1142
1343
|
if (ciStatus === "failure") {
|
|
1143
1344
|
pushUnique(allowedNextActions, [PR_CHECKPOINT_ACTION.REPORT_BLOCKED]);
|
|
@@ -1152,7 +1353,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1152
1353
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1153
1354
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1154
1355
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1155
|
-
draftGateAlreadySatisfied
|
|
1356
|
+
draftGateAlreadySatisfied,
|
|
1156
1357
|
draftGate,
|
|
1157
1358
|
preApprovalGate,
|
|
1158
1359
|
allowedNextActions,
|
|
@@ -1178,7 +1379,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1178
1379
|
lifecycleState: STATE.WAITING_FOR_CI,
|
|
1179
1380
|
loopDisposition: DISPOSITION.PENDING,
|
|
1180
1381
|
gateBoundary: PR_CHECKPOINT.DRAFT_REVIEW,
|
|
1181
|
-
draftGateAlreadySatisfied
|
|
1382
|
+
draftGateAlreadySatisfied,
|
|
1182
1383
|
draftGate,
|
|
1183
1384
|
preApprovalGate,
|
|
1184
1385
|
allowedNextActions,
|
|
@@ -1250,7 +1451,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1250
1451
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1251
1452
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1252
1453
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1253
|
-
draftGateAlreadySatisfied
|
|
1454
|
+
draftGateAlreadySatisfied,
|
|
1254
1455
|
draftGate,
|
|
1255
1456
|
preApprovalGate,
|
|
1256
1457
|
allowedNextActions,
|
|
@@ -1270,7 +1471,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1270
1471
|
return buildTitleMarkerBlockedResult({
|
|
1271
1472
|
input,
|
|
1272
1473
|
currentHeadSha,
|
|
1273
|
-
draftGateAlreadySatisfied
|
|
1474
|
+
draftGateAlreadySatisfied,
|
|
1274
1475
|
draftGate,
|
|
1275
1476
|
preApprovalGate,
|
|
1276
1477
|
mergeStateStatus,
|
|
@@ -1302,7 +1503,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1302
1503
|
lifecycleState: effectiveLifecycleState,
|
|
1303
1504
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1304
1505
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1305
|
-
draftGateAlreadySatisfied
|
|
1506
|
+
draftGateAlreadySatisfied,
|
|
1306
1507
|
draftGate,
|
|
1307
1508
|
preApprovalGate,
|
|
1308
1509
|
allowedNextActions,
|
|
@@ -1326,7 +1527,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1326
1527
|
lifecycleState: effectiveLifecycleState,
|
|
1327
1528
|
loopDisposition: loopDisposition ?? DISPOSITION.ACTION_REQUIRED,
|
|
1328
1529
|
gateBoundary: PR_CHECKPOINT.PRE_APPROVAL_GATE_WINDOW,
|
|
1329
|
-
draftGateAlreadySatisfied
|
|
1530
|
+
draftGateAlreadySatisfied,
|
|
1330
1531
|
draftGate,
|
|
1331
1532
|
preApprovalGate,
|
|
1332
1533
|
allowedNextActions,
|
|
@@ -1453,7 +1654,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1453
1654
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1454
1655
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1455
1656
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1456
|
-
draftGateAlreadySatisfied
|
|
1657
|
+
draftGateAlreadySatisfied,
|
|
1457
1658
|
draftGate,
|
|
1458
1659
|
preApprovalGate,
|
|
1459
1660
|
allowedNextActions,
|
|
@@ -1478,7 +1679,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1478
1679
|
lifecycleState: STATE.WAITING_FOR_CI,
|
|
1479
1680
|
loopDisposition: DISPOSITION.PENDING,
|
|
1480
1681
|
gateBoundary: PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
1481
|
-
draftGateAlreadySatisfied
|
|
1682
|
+
draftGateAlreadySatisfied,
|
|
1482
1683
|
draftGate,
|
|
1483
1684
|
preApprovalGate,
|
|
1484
1685
|
allowedNextActions,
|
|
@@ -1512,7 +1713,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1512
1713
|
lifecycleState: effectiveLifecycleState,
|
|
1513
1714
|
loopDisposition: loopDisposition ?? DISPOSITION.ACTION_REQUIRED,
|
|
1514
1715
|
gateBoundary: PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
1515
|
-
draftGateAlreadySatisfied
|
|
1716
|
+
draftGateAlreadySatisfied,
|
|
1516
1717
|
draftGate,
|
|
1517
1718
|
preApprovalGate,
|
|
1518
1719
|
allowedNextActions,
|
|
@@ -1533,7 +1734,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1533
1734
|
return buildTitleMarkerBlockedResult({
|
|
1534
1735
|
input,
|
|
1535
1736
|
currentHeadSha,
|
|
1536
|
-
draftGateAlreadySatisfied
|
|
1737
|
+
draftGateAlreadySatisfied,
|
|
1537
1738
|
draftGate,
|
|
1538
1739
|
preApprovalGate,
|
|
1539
1740
|
mergeStateStatus,
|
|
@@ -1543,7 +1744,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1543
1744
|
});
|
|
1544
1745
|
}
|
|
1545
1746
|
|
|
1546
|
-
if (!draftGate.cleanEvidenceExists
|
|
1747
|
+
if (!draftGate.cleanEvidenceExists) {
|
|
1547
1748
|
return buildDraftGateNeededForMergeResult({
|
|
1548
1749
|
input,
|
|
1549
1750
|
currentHeadSha,
|
|
@@ -1571,14 +1772,14 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1571
1772
|
lifecycleState: effectiveLifecycleState,
|
|
1572
1773
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1573
1774
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1574
|
-
draftGateAlreadySatisfied
|
|
1775
|
+
draftGateAlreadySatisfied,
|
|
1575
1776
|
draftGate,
|
|
1576
1777
|
preApprovalGate,
|
|
1577
1778
|
allowedNextActions,
|
|
1578
1779
|
forbiddenActions,
|
|
1579
1780
|
nextAction: PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
1580
1781
|
reason: roundCapReached
|
|
1581
|
-
? `Round-cap clean fallback
|
|
1782
|
+
? `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
1783
|
: (ciStatus === "crediblyGreen"
|
|
1583
1784
|
? "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
1785
|
: "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 +1812,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1611
1812
|
reason: roundCapReached
|
|
1612
1813
|
? `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
1814
|
: (postConvergenceReviewSuppressed && !sameHeadCleanConverged
|
|
1614
|
-
? "
|
|
1815
|
+
? "The current head carries the prior converged Copilot review: no request is outstanding, no review thread is unresolved, and the delta since Copilot's last submitted review is provably docs-only or integrate-only, so `pre_approval_gate` is now the next legal boundary."
|
|
1615
1816
|
: (ciStatus === "crediblyGreen"
|
|
1616
1817
|
? "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
1818
|
: "The current head has a clean settled post-draft review cycle, so `pre_approval_gate` is now the next legal boundary.")),
|
|
@@ -1647,7 +1848,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1647
1848
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1648
1849
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1649
1850
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1650
|
-
draftGateAlreadySatisfied
|
|
1851
|
+
draftGateAlreadySatisfied,
|
|
1651
1852
|
draftGate,
|
|
1652
1853
|
preApprovalGate,
|
|
1653
1854
|
allowedNextActions,
|
|
@@ -1671,7 +1872,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1671
1872
|
lifecycleState: STATE.WAITING_FOR_CI,
|
|
1672
1873
|
loopDisposition: DISPOSITION.PENDING,
|
|
1673
1874
|
gateBoundary: PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
1674
|
-
draftGateAlreadySatisfied
|
|
1875
|
+
draftGateAlreadySatisfied,
|
|
1675
1876
|
draftGate,
|
|
1676
1877
|
preApprovalGate,
|
|
1677
1878
|
allowedNextActions,
|
|
@@ -1689,7 +1890,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1689
1890
|
return buildTitleMarkerBlockedResult({
|
|
1690
1891
|
input,
|
|
1691
1892
|
currentHeadSha,
|
|
1692
|
-
draftGateAlreadySatisfied
|
|
1893
|
+
draftGateAlreadySatisfied,
|
|
1693
1894
|
draftGate,
|
|
1694
1895
|
preApprovalGate,
|
|
1695
1896
|
mergeStateStatus,
|
|
@@ -1700,11 +1901,12 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1700
1901
|
}
|
|
1701
1902
|
// Mirror LOW_SIGNAL_CONVERGED: a clean current head with no clean
|
|
1702
1903
|
// draft_gate evidence must reconcile the draft gate rather than jump to
|
|
1703
|
-
// final approval. This
|
|
1704
|
-
//
|
|
1705
|
-
//
|
|
1706
|
-
//
|
|
1707
|
-
//
|
|
1904
|
+
// final approval. This is the SAME rule applyDraftGateEvidenceGuard
|
|
1905
|
+
// enforces generically for every DRAFT_GATE_EVIDENCE_GUARDED_BOUNDARIES
|
|
1906
|
+
// boundary on a non-draft PR (draftGate.cleanEvidenceExists false →
|
|
1907
|
+
// reconcile_draft_gate); this inline check covers the same outcome for
|
|
1908
|
+
// FINAL_APPROVAL_READY specifically, before that wrapper-level guard
|
|
1909
|
+
// runs. Without this guard the final-approval-without-draft-gate
|
|
1708
1910
|
// branch is dead through the real script and asserts behavior it never
|
|
1709
1911
|
// produces.
|
|
1710
1912
|
if (!draftGate.cleanEvidenceExists) {
|
|
@@ -1737,13 +1939,13 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1737
1939
|
lifecycleState: effectiveLifecycleState,
|
|
1738
1940
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1739
1941
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1740
|
-
draftGateAlreadySatisfied
|
|
1942
|
+
draftGateAlreadySatisfied,
|
|
1741
1943
|
draftGate,
|
|
1742
1944
|
preApprovalGate,
|
|
1743
1945
|
allowedNextActions,
|
|
1744
1946
|
forbiddenActions,
|
|
1745
1947
|
nextAction: PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
1746
|
-
reason: `Round-cap clean fallback
|
|
1948
|
+
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
1949
|
mergeStateStatus,
|
|
1748
1950
|
conflictFiles,
|
|
1749
1951
|
refinementArtifact,
|
|
@@ -1764,7 +1966,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1764
1966
|
lifecycleState: effectiveLifecycleState,
|
|
1765
1967
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1766
1968
|
gateBoundary: PR_CHECKPOINT.PRE_APPROVAL_GATE_WINDOW,
|
|
1767
|
-
draftGateAlreadySatisfied
|
|
1969
|
+
draftGateAlreadySatisfied,
|
|
1768
1970
|
draftGate,
|
|
1769
1971
|
preApprovalGate,
|
|
1770
1972
|
allowedNextActions,
|
|
@@ -1807,7 +2009,15 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1807
2009
|
// human-read reason/evidence text for the latter case would be a false CI
|
|
1808
2010
|
// claim, so describe the actual grant basis instead.
|
|
1809
2011
|
const ciClause = ciStatus === "success" ? "green CI" : "CI not required by config";
|
|
1810
|
-
|
|
2012
|
+
// No current-head Copilot review at the cap IS the round-cap clean
|
|
2013
|
+
// fallback, so the convergence evaluator's absent-review refusal must not
|
|
2014
|
+
// block this grant; a current-head review with findings still does. An
|
|
2015
|
+
// unknown head never opens the absent-review branch. Nor does an
|
|
2016
|
+
// earlier-head body-only finding without a disposition record: the caller
|
|
2017
|
+
// must state it absent (fail closed when the fact is missing).
|
|
2018
|
+
const absentReviewFallback = currentHeadSha !== null && input.copilotReviewOnCurrentHead === false
|
|
2019
|
+
&& input.copilotPriorHeadBodyFeedbackUnresolved === false;
|
|
2020
|
+
if (unresolvedThreadCount === 0 && ciConfirmedGreen && (copilotConvergenceOk || absentReviewFallback)) {
|
|
1811
2021
|
if (preApprovalGate.currentHeadClean) {
|
|
1812
2022
|
// Inline title-marker check, mirroring ROUND_CAP_CLEAN_FALLBACK: the
|
|
1813
2023
|
// outer post-pass guards FINAL_APPROVAL_READY and
|
|
@@ -1819,7 +2029,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1819
2029
|
return buildTitleMarkerBlockedResult({
|
|
1820
2030
|
input,
|
|
1821
2031
|
currentHeadSha,
|
|
1822
|
-
draftGateAlreadySatisfied
|
|
2032
|
+
draftGateAlreadySatisfied,
|
|
1823
2033
|
draftGate,
|
|
1824
2034
|
preApprovalGate,
|
|
1825
2035
|
mergeStateStatus,
|
|
@@ -1859,13 +2069,13 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1859
2069
|
lifecycleState: effectiveLifecycleState,
|
|
1860
2070
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1861
2071
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1862
|
-
draftGateAlreadySatisfied
|
|
2072
|
+
draftGateAlreadySatisfied,
|
|
1863
2073
|
draftGate,
|
|
1864
2074
|
preApprovalGate,
|
|
1865
2075
|
allowedNextActions,
|
|
1866
2076
|
forbiddenActions,
|
|
1867
2077
|
nextAction: PR_CHECKPOINT_ACTION.AWAIT_FINAL_HUMAN_APPROVAL,
|
|
1868
|
-
reason: `Round-cap exhaustion fallback
|
|
2078
|
+
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
2079
|
mergeStateStatus,
|
|
1870
2080
|
conflictFiles,
|
|
1871
2081
|
refinementArtifact,
|
|
@@ -1887,7 +2097,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1887
2097
|
lifecycleState: effectiveLifecycleState,
|
|
1888
2098
|
loopDisposition: loopDisposition ?? DISPOSITION.CLEAN_CONVERGED,
|
|
1889
2099
|
gateBoundary: PR_CHECKPOINT.PRE_APPROVAL_GATE_WINDOW,
|
|
1890
|
-
draftGateAlreadySatisfied
|
|
2100
|
+
draftGateAlreadySatisfied,
|
|
1891
2101
|
draftGate,
|
|
1892
2102
|
preApprovalGate,
|
|
1893
2103
|
allowedNextActions,
|
|
@@ -1914,7 +2124,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1914
2124
|
lifecycleState: STATE.BLOCKED_NEEDS_USER_DECISION,
|
|
1915
2125
|
loopDisposition: DISPOSITION.BLOCKED,
|
|
1916
2126
|
gateBoundary: PR_CHECKPOINT.BLOCKED,
|
|
1917
|
-
draftGateAlreadySatisfied
|
|
2127
|
+
draftGateAlreadySatisfied,
|
|
1918
2128
|
draftGate,
|
|
1919
2129
|
preApprovalGate,
|
|
1920
2130
|
allowedNextActions,
|
|
@@ -1938,7 +2148,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1938
2148
|
lifecycleState: STATE.WAITING_FOR_CI,
|
|
1939
2149
|
loopDisposition: DISPOSITION.PENDING,
|
|
1940
2150
|
gateBoundary: PR_CHECKPOINT.POST_DRAFT_EXTERNAL_REVIEW,
|
|
1941
|
-
draftGateAlreadySatisfied
|
|
2151
|
+
draftGateAlreadySatisfied,
|
|
1942
2152
|
draftGate,
|
|
1943
2153
|
preApprovalGate,
|
|
1944
2154
|
allowedNextActions,
|
|
@@ -1956,7 +2166,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1956
2166
|
return buildTitleMarkerBlockedResult({
|
|
1957
2167
|
input,
|
|
1958
2168
|
currentHeadSha,
|
|
1959
|
-
draftGateAlreadySatisfied
|
|
2169
|
+
draftGateAlreadySatisfied,
|
|
1960
2170
|
draftGate,
|
|
1961
2171
|
preApprovalGate,
|
|
1962
2172
|
mergeStateStatus,
|
|
@@ -1994,7 +2204,7 @@ function evaluatePrGateCoordinationCore(input = {}) {
|
|
|
1994
2204
|
lifecycleState: effectiveLifecycleState,
|
|
1995
2205
|
loopDisposition: DISPOSITION.DONE,
|
|
1996
2206
|
gateBoundary: PR_CHECKPOINT.FINAL_APPROVAL_READY,
|
|
1997
|
-
draftGateAlreadySatisfied
|
|
2207
|
+
draftGateAlreadySatisfied,
|
|
1998
2208
|
draftGate,
|
|
1999
2209
|
preApprovalGate,
|
|
2000
2210
|
allowedNextActions,
|
|
@@ -498,6 +498,27 @@ export function validateSpecAuthorityVerdict(verdict, { findingsCount, criterion
|
|
|
498
498
|
};
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
+
/**
|
|
502
|
+
* Dispose every spec-authority `finding_conflicts` finding `reject`, whatever
|
|
503
|
+
* its relevance disposition. Mutates the judge-enriched findings in place; the
|
|
504
|
+
* judge pass and the durable findings-log writer share it so the ledger's
|
|
505
|
+
* `judgeDisposition` matches what the judge pass enforces.
|
|
506
|
+
*
|
|
507
|
+
* @param {object[]} findings — judge-enriched findings, indexed as the verdict
|
|
508
|
+
* @param {Iterable<number>} conflictIndices — the `finding_conflicts` indexes
|
|
509
|
+
* @returns {object[]} the same `findings` array
|
|
510
|
+
*/
|
|
511
|
+
export function rejectFindingConflicts(findings, conflictIndices) {
|
|
512
|
+
const rejected = new Set(conflictIndices);
|
|
513
|
+
for (const [i, f] of findings.entries()) {
|
|
514
|
+
if (rejected.has(i) && f.judgeDisposition !== "reject") {
|
|
515
|
+
f.judgeRationale = `spec-authority finding_conflicts: rejected against the spec (was relevance-${f.judgeDisposition}) — ${f.judgeRationale ?? ""}`.trim();
|
|
516
|
+
f.judgeDisposition = "reject";
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return findings;
|
|
520
|
+
}
|
|
521
|
+
|
|
501
522
|
/**
|
|
502
523
|
* Resolve which prior criterion approvals survive a revision change. This is the
|
|
503
524
|
* one authority for both invalidation rules:
|