patchrelay 0.30.1 → 0.32.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 +3 -3
- package/dist/cli/watch/IssueDetailView.js +1 -1
- package/dist/cli/watch/use-detail-stream.js +5 -0
- package/dist/cli/watch/watch-state.js +14 -0
- package/dist/db/migrations.js +10 -0
- package/dist/db.js +106 -2
- package/dist/github-failure-context.js +298 -0
- package/dist/github-webhook-handler.js +318 -38
- package/dist/issue-query-service.js +6 -0
- package/dist/run-orchestrator.js +162 -10
- package/dist/service.js +21 -2
- package/package.json +1 -1
package/dist/run-orchestrator.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ACTIVE_RUN_STATES, TERMINAL_STATES } from "./factory-state.js";
|
|
4
|
+
import { parseGitHubFailureContext } from "./github-failure-context.js";
|
|
4
5
|
import { buildHookEnv, runProjectHook } from "./hook-runner.js";
|
|
5
6
|
import { buildAgentSessionPlanForIssue, } from "./agent-session-plan.js";
|
|
6
7
|
import { buildStageReport, countEventMethods, extractTurnId, resolveRunCompletionStatus, summarizeCurrentThread, } from "./run-reporting.js";
|
|
@@ -56,9 +57,17 @@ function buildRunPrompt(issue, runType, repoPath, context) {
|
|
|
56
57
|
}
|
|
57
58
|
// Add run-type-specific context for reactive runs
|
|
58
59
|
switch (runType) {
|
|
59
|
-
case "ci_repair":
|
|
60
|
-
|
|
60
|
+
case "ci_repair": {
|
|
61
|
+
const snapshot = context?.ciSnapshot && typeof context.ciSnapshot === "object"
|
|
62
|
+
? context.ciSnapshot
|
|
63
|
+
: undefined;
|
|
64
|
+
lines.push("## CI Repair", "", "A full CI iteration has settled failed on your PR. Diagnose the whole snapshot, fix the root cause and directly related fallout, then push to the same PR branch.", snapshot?.gateCheckName ? `Gate check: ${String(snapshot.gateCheckName)}` : "", snapshot?.gateCheckStatus ? `Gate status: ${String(snapshot.gateCheckStatus)}` : "", snapshot?.settledAt ? `Settled at: ${String(snapshot.settledAt)}` : "", context?.failureHeadSha ? `Failing head SHA: ${String(context.failureHeadSha)}` : "", context?.checkName ? `Failed check: ${String(context.checkName)}` : "", context?.jobName && context?.jobName !== context?.checkName ? `Failed job: ${String(context.jobName)}` : "", context?.stepName ? `Failed step: ${String(context.stepName)}` : "", context?.summary ? `Failure summary: ${String(context.summary)}` : "", Array.isArray(snapshot?.failedChecks) && snapshot.failedChecks.length > 0
|
|
65
|
+
? `All failed checks in settled snapshot:\n${snapshot.failedChecks.map((entry) => `- ${String(entry.name ?? "unknown")}${entry.summary ? `: ${String(entry.summary)}` : ""}`).join("\n")}`
|
|
66
|
+
: "", context?.checkUrl ? `Check URL: ${String(context.checkUrl)}` : "", Array.isArray(context?.annotations) && context.annotations.length > 0
|
|
67
|
+
? `Annotations:\n${context.annotations.map((entry) => `- ${String(entry)}`).join("\n")}`
|
|
68
|
+
: "", "", "Read the latest CI logs, consider the broader PR context, fix the likely root cause and any directly related fallout in one pass, run verification, commit and push.", "Do not open a new PR. Keep working on the existing branch until CI goes green or the situation is clearly stuck.", "Do not change test expectations unless the test is genuinely wrong.", "");
|
|
61
69
|
break;
|
|
70
|
+
}
|
|
62
71
|
case "review_fix":
|
|
63
72
|
lines.push("## Review Changes Requested", "", "A reviewer has requested changes on your PR. Address the feedback and push.", context?.reviewerName ? `Reviewer: ${String(context.reviewerName)}` : "", context?.reviewBody ? `\n## Review comment\n\n${String(context.reviewBody)}` : "", "", "Steps:", "1. Read the review feedback and PR comments (`gh pr view --comments`).", "2. Check the current diff (`git diff origin/main`) — a prior rebase may have already resolved some concerns (e.g., scope-bundling from stale commits).", "3. For each review point: if already resolved, note why. If not, fix it.", "4. Run verification, commit and push.", "5. If you believe all concerns are resolved, request a re-review: `gh pr edit <PR#> --add-reviewer <reviewer>`.", " Do NOT just post a comment saying \"resolved\" — the reviewer must re-review to dismiss the CHANGES_REQUESTED state.", "");
|
|
64
73
|
break;
|
|
@@ -155,6 +164,10 @@ export class RunOrchestrator {
|
|
|
155
164
|
runType,
|
|
156
165
|
promptText: prompt,
|
|
157
166
|
});
|
|
167
|
+
const failureHeadSha = typeof context?.failureHeadSha === "string"
|
|
168
|
+
? context.failureHeadSha
|
|
169
|
+
: typeof context?.headSha === "string" ? context.headSha : undefined;
|
|
170
|
+
const failureSignature = typeof context?.failureSignature === "string" ? context.failureSignature : undefined;
|
|
158
171
|
this.db.upsertIssue({
|
|
159
172
|
projectId: item.projectId,
|
|
160
173
|
linearIssueId: item.issueId,
|
|
@@ -168,6 +181,12 @@ export class RunOrchestrator {
|
|
|
168
181
|
: runType === "review_fix" ? "changes_requested"
|
|
169
182
|
: runType === "queue_repair" ? "repairing_queue"
|
|
170
183
|
: "implementing",
|
|
184
|
+
...((runType === "ci_repair" || runType === "queue_repair") && failureSignature
|
|
185
|
+
? {
|
|
186
|
+
lastAttemptedFailureSignature: failureSignature,
|
|
187
|
+
lastAttemptedFailureHeadSha: failureHeadSha ?? null,
|
|
188
|
+
}
|
|
189
|
+
: {}),
|
|
171
190
|
});
|
|
172
191
|
return created;
|
|
173
192
|
});
|
|
@@ -402,6 +421,26 @@ export class RunOrchestrator {
|
|
|
402
421
|
const report = buildStageReport(run, trackedIssue, thread, countEventMethods(this.db.listThreadEvents(run.id)));
|
|
403
422
|
// Determine post-run state based on current PR metadata.
|
|
404
423
|
const freshIssue = this.db.getIssue(run.projectId, run.linearIssueId) ?? issue;
|
|
424
|
+
const verifiedRepairError = await this.verifyReactiveRunAdvancedBranch(run, freshIssue);
|
|
425
|
+
if (verifiedRepairError) {
|
|
426
|
+
const holdState = resolveRecoverablePostRunState(freshIssue) ?? "failed";
|
|
427
|
+
this.failRunAndClear(run, verifiedRepairError, holdState);
|
|
428
|
+
const heldIssue = this.db.getIssue(run.projectId, run.linearIssueId) ?? freshIssue;
|
|
429
|
+
this.feed?.publish({
|
|
430
|
+
level: "warn",
|
|
431
|
+
kind: "turn",
|
|
432
|
+
issueKey: freshIssue.issueKey,
|
|
433
|
+
projectId: run.projectId,
|
|
434
|
+
stage: run.runType,
|
|
435
|
+
status: "branch_not_advanced",
|
|
436
|
+
summary: verifiedRepairError,
|
|
437
|
+
});
|
|
438
|
+
void this.emitLinearActivity(heldIssue, buildRunFailureActivity(run.runType, verifiedRepairError));
|
|
439
|
+
void this.syncLinearSession(heldIssue, { activeRunType: run.runType });
|
|
440
|
+
this.progressThrottle.delete(run.id);
|
|
441
|
+
this.activeThreadId = undefined;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
405
444
|
const postRunState = resolvePostRunState(freshIssue);
|
|
406
445
|
this.db.transaction(() => {
|
|
407
446
|
this.db.finishRun(run.id, {
|
|
@@ -419,10 +458,15 @@ export class RunOrchestrator {
|
|
|
419
458
|
...(postRunState === "awaiting_queue" || postRunState === "done"
|
|
420
459
|
? {
|
|
421
460
|
lastGitHubFailureSource: null,
|
|
461
|
+
lastGitHubFailureHeadSha: null,
|
|
462
|
+
lastGitHubFailureSignature: null,
|
|
422
463
|
lastGitHubFailureCheckName: null,
|
|
423
464
|
lastGitHubFailureCheckUrl: null,
|
|
465
|
+
lastGitHubFailureContextJson: null,
|
|
424
466
|
lastGitHubFailureAt: null,
|
|
425
467
|
lastQueueIncidentJson: null,
|
|
468
|
+
lastAttemptedFailureHeadSha: null,
|
|
469
|
+
lastAttemptedFailureSignature: null,
|
|
426
470
|
}
|
|
427
471
|
: {}),
|
|
428
472
|
});
|
|
@@ -534,8 +578,11 @@ export class RunOrchestrator {
|
|
|
534
578
|
// Checks failed + idle — route based on durable GitHub failure provenance.
|
|
535
579
|
if (issue.prCheckStatus === "failed") {
|
|
536
580
|
if (issue.lastGitHubFailureSource === "queue_eviction") {
|
|
537
|
-
|
|
538
|
-
|
|
581
|
+
const pendingRunContext = buildFailureContext(issue);
|
|
582
|
+
if (isDuplicateRepairAttempt(issue, pendingRunContext)) {
|
|
583
|
+
this.advanceIdleIssue(issue, "repairing_queue");
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
539
586
|
this.advanceIdleIssue(issue, "repairing_queue", {
|
|
540
587
|
pendingRunType: "queue_repair",
|
|
541
588
|
...(pendingRunContext ? { pendingRunContext } : {}),
|
|
@@ -544,8 +591,11 @@ export class RunOrchestrator {
|
|
|
544
591
|
continue;
|
|
545
592
|
}
|
|
546
593
|
if (issue.lastGitHubFailureSource === "branch_ci") {
|
|
547
|
-
|
|
548
|
-
|
|
594
|
+
const pendingRunContext = buildFailureContext(issue);
|
|
595
|
+
if (isDuplicateRepairAttempt(issue, pendingRunContext)) {
|
|
596
|
+
this.advanceIdleIssue(issue, "repairing_ci");
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
549
599
|
this.advanceIdleIssue(issue, "repairing_ci", {
|
|
550
600
|
pendingRunType: "ci_repair",
|
|
551
601
|
...(pendingRunContext ? { pendingRunContext } : {}),
|
|
@@ -566,8 +616,11 @@ export class RunOrchestrator {
|
|
|
566
616
|
});
|
|
567
617
|
continue;
|
|
568
618
|
}
|
|
569
|
-
|
|
570
|
-
|
|
619
|
+
const pendingRunContext = buildFailureContext(issue);
|
|
620
|
+
if (isDuplicateRepairAttempt(issue, pendingRunContext)) {
|
|
621
|
+
this.advanceIdleIssue(issue, "repairing_ci");
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
571
624
|
this.advanceIdleIssue(issue, "repairing_ci", {
|
|
572
625
|
pendingRunType: "ci_repair",
|
|
573
626
|
...(pendingRunContext ? { pendingRunContext } : {}),
|
|
@@ -606,6 +659,9 @@ export class RunOrchestrator {
|
|
|
606
659
|
}
|
|
607
660
|
}
|
|
608
661
|
advanceIdleIssue(issue, newState, options) {
|
|
662
|
+
if (issue.factoryState === newState && !options?.pendingRunType && !options?.clearFailureProvenance) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
609
665
|
this.logger.info({ issueKey: issue.issueKey, from: issue.factoryState, to: newState, pendingRunType: options?.pendingRunType }, "Reconciliation: advancing idle issue");
|
|
610
666
|
this.db.upsertIssue({
|
|
611
667
|
projectId: issue.projectId,
|
|
@@ -620,10 +676,15 @@ export class RunOrchestrator {
|
|
|
620
676
|
...(options?.clearFailureProvenance
|
|
621
677
|
? {
|
|
622
678
|
lastGitHubFailureSource: null,
|
|
679
|
+
lastGitHubFailureHeadSha: null,
|
|
680
|
+
lastGitHubFailureSignature: null,
|
|
623
681
|
lastGitHubFailureCheckName: null,
|
|
624
682
|
lastGitHubFailureCheckUrl: null,
|
|
683
|
+
lastGitHubFailureContextJson: null,
|
|
625
684
|
lastGitHubFailureAt: null,
|
|
626
685
|
lastQueueIncidentJson: null,
|
|
686
|
+
lastAttemptedFailureHeadSha: null,
|
|
687
|
+
lastAttemptedFailureSignature: null,
|
|
627
688
|
}
|
|
628
689
|
: {}),
|
|
629
690
|
});
|
|
@@ -790,7 +851,7 @@ export class RunOrchestrator {
|
|
|
790
851
|
else if (run.runType === "review_fix" && issue.reviewFixAttempts > 0) {
|
|
791
852
|
this.db.upsertIssue({ projectId: issue.projectId, linearIssueId: issue.linearIssueId, reviewFixAttempts: issue.reviewFixAttempts - 1 });
|
|
792
853
|
}
|
|
793
|
-
const recoveredState =
|
|
854
|
+
const recoveredState = resolveRecoverablePostRunState(this.db.getIssue(run.projectId, run.linearIssueId) ?? issue);
|
|
794
855
|
this.failRunAndClear(run, "Codex turn was interrupted", recoveredState);
|
|
795
856
|
const failedIssue = this.db.getIssue(run.projectId, run.linearIssueId) ?? issue;
|
|
796
857
|
if (recoveredState) {
|
|
@@ -815,6 +876,21 @@ export class RunOrchestrator {
|
|
|
815
876
|
const trackedIssue = this.db.issueToTrackedIssue(issue);
|
|
816
877
|
const report = buildStageReport(run, trackedIssue, thread, countEventMethods(this.db.listThreadEvents(run.id)));
|
|
817
878
|
const freshIssue = this.db.getIssue(run.projectId, run.linearIssueId) ?? issue;
|
|
879
|
+
const verifiedRepairError = await this.verifyReactiveRunAdvancedBranch(run, freshIssue);
|
|
880
|
+
if (verifiedRepairError) {
|
|
881
|
+
const holdState = resolveRecoverablePostRunState(freshIssue) ?? "failed";
|
|
882
|
+
this.failRunAndClear(run, verifiedRepairError, holdState);
|
|
883
|
+
this.feed?.publish({
|
|
884
|
+
level: "warn",
|
|
885
|
+
kind: "turn",
|
|
886
|
+
issueKey: issue.issueKey,
|
|
887
|
+
projectId: run.projectId,
|
|
888
|
+
stage: run.runType,
|
|
889
|
+
status: "branch_not_advanced",
|
|
890
|
+
summary: verifiedRepairError,
|
|
891
|
+
});
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
818
894
|
const postRunState = resolvePostRunState(freshIssue);
|
|
819
895
|
this.db.transaction(() => {
|
|
820
896
|
this.db.finishRun(run.id, {
|
|
@@ -832,10 +908,15 @@ export class RunOrchestrator {
|
|
|
832
908
|
...(postRunState === "awaiting_queue" || postRunState === "done"
|
|
833
909
|
? {
|
|
834
910
|
lastGitHubFailureSource: null,
|
|
911
|
+
lastGitHubFailureHeadSha: null,
|
|
912
|
+
lastGitHubFailureSignature: null,
|
|
835
913
|
lastGitHubFailureCheckName: null,
|
|
836
914
|
lastGitHubFailureCheckUrl: null,
|
|
915
|
+
lastGitHubFailureContextJson: null,
|
|
837
916
|
lastGitHubFailureAt: null,
|
|
838
917
|
lastQueueIncidentJson: null,
|
|
918
|
+
lastAttemptedFailureHeadSha: null,
|
|
919
|
+
lastAttemptedFailureSignature: null,
|
|
839
920
|
}
|
|
840
921
|
: {}),
|
|
841
922
|
});
|
|
@@ -908,6 +989,41 @@ export class RunOrchestrator {
|
|
|
908
989
|
});
|
|
909
990
|
});
|
|
910
991
|
}
|
|
992
|
+
async verifyReactiveRunAdvancedBranch(run, issue) {
|
|
993
|
+
if (run.runType !== "ci_repair" && run.runType !== "queue_repair") {
|
|
994
|
+
return undefined;
|
|
995
|
+
}
|
|
996
|
+
if (!issue.prNumber || issue.prState !== "open" || !issue.lastGitHubFailureHeadSha) {
|
|
997
|
+
return undefined;
|
|
998
|
+
}
|
|
999
|
+
const project = this.config.projects.find((entry) => entry.id === run.projectId);
|
|
1000
|
+
if (!project?.github?.repoFullName) {
|
|
1001
|
+
return undefined;
|
|
1002
|
+
}
|
|
1003
|
+
try {
|
|
1004
|
+
const { stdout, exitCode } = await execCommand("gh", [
|
|
1005
|
+
"pr", "view", String(issue.prNumber),
|
|
1006
|
+
"--repo", project.github.repoFullName,
|
|
1007
|
+
"--json", "headRefOid,state",
|
|
1008
|
+
], { timeoutMs: 10_000 });
|
|
1009
|
+
if (exitCode !== 0)
|
|
1010
|
+
return undefined;
|
|
1011
|
+
const pr = JSON.parse(stdout);
|
|
1012
|
+
if (pr.state?.toUpperCase() !== "OPEN")
|
|
1013
|
+
return undefined;
|
|
1014
|
+
if (!pr.headRefOid || pr.headRefOid !== issue.lastGitHubFailureHeadSha)
|
|
1015
|
+
return undefined;
|
|
1016
|
+
return `Repair finished but PR #${issue.prNumber} is still on failing head ${issue.lastGitHubFailureHeadSha.slice(0, 8)}`;
|
|
1017
|
+
}
|
|
1018
|
+
catch (error) {
|
|
1019
|
+
this.logger.debug({
|
|
1020
|
+
issueKey: issue.issueKey,
|
|
1021
|
+
prNumber: issue.prNumber,
|
|
1022
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1023
|
+
}, "Failed to verify PR head advancement after repair");
|
|
1024
|
+
return undefined;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
911
1027
|
async emitLinearActivity(issue, content, options) {
|
|
912
1028
|
if (!issue.agentSessionId)
|
|
913
1029
|
return;
|
|
@@ -1025,23 +1141,59 @@ function resolvePostRunState(issue) {
|
|
|
1025
1141
|
}
|
|
1026
1142
|
return undefined;
|
|
1027
1143
|
}
|
|
1144
|
+
function resolveRecoverablePostRunState(issue) {
|
|
1145
|
+
if (!issue.prNumber) {
|
|
1146
|
+
return resolvePostRunState(issue);
|
|
1147
|
+
}
|
|
1148
|
+
if (issue.prState === "merged")
|
|
1149
|
+
return "done";
|
|
1150
|
+
if (issue.prState === "open") {
|
|
1151
|
+
if (issue.lastGitHubFailureSource === "queue_eviction")
|
|
1152
|
+
return "repairing_queue";
|
|
1153
|
+
if (issue.prCheckStatus === "failed" || issue.lastGitHubFailureSource === "branch_ci")
|
|
1154
|
+
return "repairing_ci";
|
|
1155
|
+
if (issue.prReviewState === "changes_requested")
|
|
1156
|
+
return "changes_requested";
|
|
1157
|
+
if (issue.prReviewState === "approved")
|
|
1158
|
+
return "awaiting_queue";
|
|
1159
|
+
return "pr_open";
|
|
1160
|
+
}
|
|
1161
|
+
return resolvePostRunState(issue);
|
|
1162
|
+
}
|
|
1028
1163
|
function buildFailureContext(issue) {
|
|
1164
|
+
const storedFailureContext = parseGitHubFailureContext(issue.lastGitHubFailureContextJson);
|
|
1029
1165
|
const queueRepairContext = issue.lastQueueIncidentJson
|
|
1030
1166
|
? parseStoredQueueRepairContext(issue.lastQueueIncidentJson)
|
|
1031
1167
|
: undefined;
|
|
1032
1168
|
if (!queueRepairContext
|
|
1033
1169
|
&& !issue.lastGitHubFailureSource
|
|
1170
|
+
&& !issue.lastGitHubFailureHeadSha
|
|
1171
|
+
&& !issue.lastGitHubFailureSignature
|
|
1034
1172
|
&& !issue.lastGitHubFailureCheckName
|
|
1035
|
-
&& !issue.lastGitHubFailureCheckUrl
|
|
1173
|
+
&& !issue.lastGitHubFailureCheckUrl
|
|
1174
|
+
&& !storedFailureContext) {
|
|
1036
1175
|
return undefined;
|
|
1037
1176
|
}
|
|
1038
1177
|
return {
|
|
1039
1178
|
...(issue.lastGitHubFailureSource ? { failureReason: issue.lastGitHubFailureSource } : {}),
|
|
1179
|
+
...(issue.lastGitHubFailureHeadSha ? { failureHeadSha: issue.lastGitHubFailureHeadSha } : {}),
|
|
1180
|
+
...(issue.lastGitHubFailureSignature ? { failureSignature: issue.lastGitHubFailureSignature } : {}),
|
|
1040
1181
|
...(issue.lastGitHubFailureCheckName ? { checkName: issue.lastGitHubFailureCheckName } : {}),
|
|
1041
1182
|
...(issue.lastGitHubFailureCheckUrl ? { checkUrl: issue.lastGitHubFailureCheckUrl } : {}),
|
|
1183
|
+
...(storedFailureContext ? storedFailureContext : {}),
|
|
1042
1184
|
...(queueRepairContext ? queueRepairContext : {}),
|
|
1043
1185
|
};
|
|
1044
1186
|
}
|
|
1187
|
+
function isDuplicateRepairAttempt(issue, context) {
|
|
1188
|
+
const signature = typeof context?.failureSignature === "string" ? context.failureSignature : undefined;
|
|
1189
|
+
const headSha = typeof context?.failureHeadSha === "string"
|
|
1190
|
+
? context.failureHeadSha
|
|
1191
|
+
: typeof context?.headSha === "string" ? context.headSha : undefined;
|
|
1192
|
+
if (!signature)
|
|
1193
|
+
return false;
|
|
1194
|
+
return issue.lastAttemptedFailureSignature === signature
|
|
1195
|
+
&& (headSha === undefined || issue.lastAttemptedFailureHeadSha === headSha);
|
|
1196
|
+
}
|
|
1045
1197
|
function appendQueueRepairContext(lines, context) {
|
|
1046
1198
|
const incidentTitle = typeof context?.incidentTitle === "string" ? context.incidentTitle.trim() : "";
|
|
1047
1199
|
const incidentSummary = typeof context?.incidentSummary === "string" ? context.incidentSummary.trim() : "";
|
package/dist/service.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveGitHubAppCredentials, createGitHubAppTokenManager, ensureGhWrapper, } from "./github-app-token.js";
|
|
2
|
+
import { parseGitHubFailureContext, summarizeGitHubFailureContext } from "./github-failure-context.js";
|
|
2
3
|
import { GitHubWebhookHandler } from "./github-webhook-handler.js";
|
|
3
4
|
import { IssueQueryService } from "./issue-query-service.js";
|
|
4
5
|
import { DatabaseBackedLinearClientProvider } from "./linear-client.js";
|
|
@@ -216,6 +217,10 @@ export class PatchRelayService {
|
|
|
216
217
|
i.current_linear_state, i.factory_state, i.updated_at,
|
|
217
218
|
i.pending_run_type,
|
|
218
219
|
i.pr_number, i.pr_review_state, i.pr_check_status,
|
|
220
|
+
i.last_github_failure_source,
|
|
221
|
+
i.last_github_failure_head_sha,
|
|
222
|
+
i.last_github_failure_check_name,
|
|
223
|
+
i.last_github_failure_context_json,
|
|
219
224
|
active_run.run_type AS active_run_type,
|
|
220
225
|
latest_run.run_type AS latest_run_type,
|
|
221
226
|
latest_run.status AS latest_run_status,
|
|
@@ -257,13 +262,22 @@ export class PatchRelayService {
|
|
|
257
262
|
ORDER BY i.updated_at DESC, i.issue_key ASC`)
|
|
258
263
|
.all();
|
|
259
264
|
return rows.map((row) => {
|
|
265
|
+
const failureContext = parseGitHubFailureContext(typeof row.last_github_failure_context_json === "string" ? row.last_github_failure_context_json : undefined);
|
|
260
266
|
const statusNote = extractStatusNote(typeof row.latest_run_summary_json === "string" ? row.latest_run_summary_json : undefined, typeof row.latest_run_report_json === "string" ? row.latest_run_report_json : undefined);
|
|
261
267
|
const blockedByKeys = parseStringArray(typeof row.blocked_by_keys_json === "string" ? row.blocked_by_keys_json : undefined);
|
|
262
268
|
const blockedByCount = Number(row.blocked_by_count ?? 0);
|
|
263
269
|
const readyForExecution = row.pending_run_type !== null && row.pending_run_type !== undefined && row.active_run_type === null && blockedByCount === 0;
|
|
270
|
+
const failureSummary = summarizeGitHubFailureContext(failureContext);
|
|
271
|
+
const derivedStatusNote = blockedByCount > 0
|
|
272
|
+
? `Blocked by ${blockedByKeys.join(", ")}`
|
|
273
|
+
: failureSummary && (row.factory_state === "repairing_ci"
|
|
274
|
+
|| row.factory_state === "repairing_queue"
|
|
275
|
+
|| row.factory_state === "failed")
|
|
276
|
+
? failureSummary
|
|
277
|
+
: statusNote;
|
|
264
278
|
const statusNoteWithBlockers = blockedByCount > 0
|
|
265
279
|
? `Blocked by ${blockedByKeys.join(", ")}`
|
|
266
|
-
:
|
|
280
|
+
: derivedStatusNote;
|
|
267
281
|
return {
|
|
268
282
|
...(row.issue_key !== null ? { issueKey: String(row.issue_key) } : {}),
|
|
269
283
|
...(row.title !== null ? { title: String(row.title) } : {}),
|
|
@@ -281,6 +295,11 @@ export class PatchRelayService {
|
|
|
281
295
|
...(row.pr_number !== null ? { prNumber: Number(row.pr_number) } : {}),
|
|
282
296
|
...(row.pr_review_state !== null ? { prReviewState: String(row.pr_review_state) } : {}),
|
|
283
297
|
...(row.pr_check_status !== null ? { prCheckStatus: String(row.pr_check_status) } : {}),
|
|
298
|
+
...(row.last_github_failure_source !== null ? { latestFailureSource: String(row.last_github_failure_source) } : {}),
|
|
299
|
+
...(row.last_github_failure_head_sha !== null ? { latestFailureHeadSha: String(row.last_github_failure_head_sha) } : {}),
|
|
300
|
+
...(row.last_github_failure_check_name !== null ? { latestFailureCheckName: String(row.last_github_failure_check_name) } : {}),
|
|
301
|
+
...(failureContext?.stepName ? { latestFailureStepName: failureContext.stepName } : {}),
|
|
302
|
+
...(failureContext?.summary ? { latestFailureSummary: failureContext.summary } : {}),
|
|
284
303
|
updatedAt: String(row.updated_at),
|
|
285
304
|
};
|
|
286
305
|
});
|
|
@@ -417,7 +436,7 @@ export class PatchRelayService {
|
|
|
417
436
|
// Infer run type from current state instead of always resetting to implementation
|
|
418
437
|
let runType = "implementation";
|
|
419
438
|
let factoryState = "delegated";
|
|
420
|
-
if (issue.prNumber && issue.prCheckStatus === "failed") {
|
|
439
|
+
if (issue.prNumber && (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure" || issue.lastGitHubFailureSource === "branch_ci")) {
|
|
421
440
|
runType = "ci_repair";
|
|
422
441
|
factoryState = "repairing_ci";
|
|
423
442
|
}
|