patchrelay 0.59.1 → 0.61.0
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/dist/build-info.json
CHANGED
|
@@ -336,6 +336,10 @@ async function evaluateGitHubIssueHealth(snapshot, config, runCommand, reviewQui
|
|
|
336
336
|
if (issue.delegatedToPatchRelay
|
|
337
337
|
&& gateCheckStatus === "failure"
|
|
338
338
|
&& issue.factoryState !== "repairing_ci"
|
|
339
|
+
// Plan §6.1 / §4.3: branch CI failures while In Deploy are
|
|
340
|
+
// metadata only — the lander's spec CI is the gate. Don't flag
|
|
341
|
+
// these as a missing-ci-repair condition.
|
|
342
|
+
&& issue.factoryState !== "awaiting_queue"
|
|
339
343
|
&& issue.activeRunId === undefined
|
|
340
344
|
&& ageMs >= RECONCILIATION_GRACE_MS) {
|
|
341
345
|
return {
|
package/dist/factory-state.js
CHANGED
|
@@ -58,9 +58,29 @@ const TRANSITION_RULES = [
|
|
|
58
58
|
{ event: "check_passed",
|
|
59
59
|
guard: (s) => s === "repairing_ci",
|
|
60
60
|
to: (_, ctx) => ctx.prReviewState === "approved" ? "awaiting_queue" : "pr_open" },
|
|
61
|
-
// CI failure when no run is active
|
|
61
|
+
// CI failure when no run is active. Plan §4.3: classification by
|
|
62
|
+
// failureSource determines whether we route to queue or branch repair.
|
|
63
|
+
// While In Deploy (`awaiting_queue`), branch CI failures are metadata
|
|
64
|
+
// only — the lander handles the spec; we don't initiate a ci_repair.
|
|
62
65
|
{ event: "check_failed",
|
|
63
|
-
guard: (s, ctx) => isOpen(s)
|
|
66
|
+
guard: (s, ctx) => isOpen(s)
|
|
67
|
+
&& ctx.activeRunId === undefined
|
|
68
|
+
&& ctx.failureSource === "queue_eviction",
|
|
69
|
+
to: "repairing_queue" },
|
|
70
|
+
{ event: "check_failed",
|
|
71
|
+
guard: (s, ctx) => isOpen(s)
|
|
72
|
+
&& ctx.activeRunId === undefined
|
|
73
|
+
&& ctx.failureSource === "branch_ci"
|
|
74
|
+
&& s !== "awaiting_queue",
|
|
75
|
+
to: "repairing_ci" },
|
|
76
|
+
// Backward-compat fallback for callers that haven't classified the
|
|
77
|
+
// failure (older code paths or test fixtures). Behaves like today's
|
|
78
|
+
// single rule but still skips while In Deploy.
|
|
79
|
+
{ event: "check_failed",
|
|
80
|
+
guard: (s, ctx) => isOpen(s)
|
|
81
|
+
&& ctx.activeRunId === undefined
|
|
82
|
+
&& ctx.failureSource === undefined
|
|
83
|
+
&& s !== "awaiting_queue",
|
|
64
84
|
to: "repairing_ci" },
|
|
65
85
|
// pr_synchronize: no rule → no transition (resets counters only)
|
|
66
86
|
// merge_group events: not used — merge queue is handled by external steward
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { resolveFactoryStateFromGitHub } from "./factory-state.js";
|
|
2
2
|
import { resolveMergeQueueProtocol, } from "./merge-queue-protocol.js";
|
|
3
|
-
import { isIssueTerminal } from "./pr-state.js";
|
|
4
3
|
const DEFAULT_GATE_CHECK_NAMES = ["verify", "tests"];
|
|
5
4
|
/**
|
|
6
5
|
* GitHub sends both check_run and check_suite completion events.
|
|
@@ -84,16 +83,16 @@ export function resolveGitHubFactoryStateForEvent(issue, event, project) {
|
|
|
84
83
|
&& (event.prState === "open" || event.prNumber !== undefined)
|
|
85
84
|
? "pr_open"
|
|
86
85
|
: issue.factoryState;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
86
|
+
// Classify check_failed events so the rule table can route them.
|
|
87
|
+
// The duplicate short-circuit that lived here before is gone — the
|
|
88
|
+
// table now handles queue_eviction via failureSource (plan §4.3).
|
|
89
|
+
const failureSource = event.triggerEvent === "check_failed"
|
|
90
|
+
? (isQueueEvictionFailure(issue, event, project) ? "queue_eviction" : "branch_ci")
|
|
91
|
+
: undefined;
|
|
94
92
|
const resolved = resolveFactoryStateFromGitHub(event.triggerEvent, effectiveCurrentState, {
|
|
95
93
|
prReviewState: issue.prReviewState,
|
|
96
94
|
activeRunId: issue.activeRunId,
|
|
95
|
+
failureSource,
|
|
97
96
|
});
|
|
98
97
|
if (resolved !== undefined) {
|
|
99
98
|
return resolved;
|
|
@@ -49,6 +49,22 @@ export async function maybeEnqueueGitHubReactiveRun(params) {
|
|
|
49
49
|
}
|
|
50
50
|
async function handleCheckFailedEvent(params) {
|
|
51
51
|
const { db, logger, feed, enqueueIssue, issue, event, project, failureContextResolver } = params;
|
|
52
|
+
// Plan §4.3: while In Deploy (`awaiting_queue`), branch CI is metadata
|
|
53
|
+
// only — the lander owns admission, and its spec CI on the integration
|
|
54
|
+
// tree is the gate. Queue eviction failures still flow through (they're
|
|
55
|
+
// how the lander signals a real integration regression).
|
|
56
|
+
if (issue.factoryState === "awaiting_queue" && !isQueueEvictionFailure(issue, event, project)) {
|
|
57
|
+
feed?.publish({
|
|
58
|
+
level: "info",
|
|
59
|
+
kind: "github",
|
|
60
|
+
issueKey: issue.issueKey,
|
|
61
|
+
projectId: issue.projectId,
|
|
62
|
+
stage: issue.factoryState,
|
|
63
|
+
status: "branch_ci_metadata_in_deploy",
|
|
64
|
+
summary: `Ignored ${event.checkName ?? "branch CI"} failure while In Deploy; lander owns admission`,
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
52
68
|
if (isQueueEvictionFailure(issue, event, project)) {
|
|
53
69
|
const queueRepairContext = buildQueueRepairContextFromEvent(event);
|
|
54
70
|
const failureContext = buildGitHubQueueFailureContext(event, project, queueRepairContext);
|
|
@@ -271,7 +271,7 @@ function buildStructuredReviewContext(context) {
|
|
|
271
271
|
lines.push("No inline review comments were captured for this review.");
|
|
272
272
|
return lines.join("\n");
|
|
273
273
|
}
|
|
274
|
-
lines.push(`Inline review comments captured: ${reviewComments.length}`, "Resolve each comment below or verify it is already fixed on the current head before you stop.", "
|
|
274
|
+
lines.push(`Inline review comments captured: ${reviewComments.length}`, "Resolve each comment below or verify it is already fixed on the current head before you stop.", "Complete the turn either by pushing a newer PR head with the fix, or — if your reviewer-pass produces only comments, test wording, or PR-body changes — by editing the PR body via `gh pr edit` instead of pushing. Do not push a commit that produces a patch-id-equivalent diff just to make the fix unmistakable.", "If you are blocked, deliberately escalate instead of pushing.", "");
|
|
275
275
|
for (const comment of reviewComments) {
|
|
276
276
|
const location = comment.path
|
|
277
277
|
? `${comment.path}${comment.line !== undefined ? `:${comment.line}` : ""}${comment.side ? ` (${comment.side})` : ""}`
|
|
@@ -293,12 +293,12 @@ function buildRequestedChangesContext(runType, context) {
|
|
|
293
293
|
const mode = resolveRequestedChangesMode(runType, context);
|
|
294
294
|
const lines = [];
|
|
295
295
|
if (mode === "branch_upkeep") {
|
|
296
|
-
lines.push("Branch upkeep is required on the existing PR branch.", "Goal: restore merge readiness on the current branch
|
|
296
|
+
lines.push("Branch upkeep is required on the existing PR branch.", "Goal: restore merge readiness on the current branch. Push a newer head only when the work actually changes the diff against the base; do not republish a patch-id-equivalent head.");
|
|
297
297
|
}
|
|
298
298
|
else {
|
|
299
299
|
const reviewer = typeof context?.reviewerName === "string" ? context.reviewerName : undefined;
|
|
300
300
|
const reviewBody = typeof context?.reviewBody === "string" ? context.reviewBody.trim() : "";
|
|
301
|
-
lines.push("Requested changes on the existing PR branch.", "Goal: restore review readiness
|
|
301
|
+
lines.push("Requested changes on the existing PR branch.", "Goal: restore review readiness on the current PR branch. Push a newer head only when the fix actually changes the diff; if the reviewer-pass produces only comments, test wording, or PR-body changes, edit the PR body via `gh pr edit` instead.", "Address the real concern behind the feedback and verify nearby invariants in the touched flow before you publish.", reviewer ? `Reviewer: ${reviewer}` : "", reviewBody ? `Review summary:\n${reviewBody}` : "");
|
|
302
302
|
appendStructuredReviewContext(lines, context);
|
|
303
303
|
}
|
|
304
304
|
return lines.join("\n").trim();
|