patchrelay 0.70.0 → 0.71.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/dist/build-info.json
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { resolveMergeQueueProtocol } from "./merge-queue-protocol.js";
|
|
2
1
|
import { resolvePreferredCompletedLinearState } from "./linear-workflow.js";
|
|
3
|
-
import { buildMainRepairBranchName,
|
|
2
|
+
import { buildMainRepairBranchName, isMainRepairIssue, } from "./main-repair.js";
|
|
4
3
|
import { execCommand } from "./utils.js";
|
|
5
4
|
const MAIN_BRANCH_HEALTH_GRACE_MS = 120_000;
|
|
6
5
|
function isUnhealthyMainConclusion(conclusion) {
|
|
@@ -14,14 +13,14 @@ export class MainBranchHealthMonitor {
|
|
|
14
13
|
db;
|
|
15
14
|
config;
|
|
16
15
|
linearProvider;
|
|
17
|
-
wakeDispatcher;
|
|
18
16
|
logger;
|
|
19
17
|
feed;
|
|
20
|
-
|
|
18
|
+
/** Per-project throttle for the information-only "main is red" log. */
|
|
19
|
+
lastUnhealthyReportAt = new Map();
|
|
20
|
+
constructor(db, config, linearProvider, logger, feed) {
|
|
21
21
|
this.db = db;
|
|
22
22
|
this.config = config;
|
|
23
23
|
this.linearProvider = linearProvider;
|
|
24
|
-
this.wakeDispatcher = wakeDispatcher;
|
|
25
24
|
this.logger = logger;
|
|
26
25
|
this.feed = feed;
|
|
27
26
|
}
|
|
@@ -46,64 +45,27 @@ export class MainBranchHealthMonitor {
|
|
|
46
45
|
}
|
|
47
46
|
return;
|
|
48
47
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const client = await this.linearProvider.forProject(projectId);
|
|
61
|
-
if (!client?.createIssue) {
|
|
62
|
-
this.logger.warn({ projectId, repoFullName: project.github.repoFullName }, "Cannot create main repair issue because Linear issue creation is unavailable");
|
|
48
|
+
// main CI is red. The merge queue (merge-steward) gates only on its own
|
|
49
|
+
// speculative-SHA checks and ignores main entirely, so a red main no longer
|
|
50
|
+
// warrants an automated repair job — main CI is information-only. Report it
|
|
51
|
+
// (throttled) and post nothing. Any pre-existing repair issue is left to close
|
|
52
|
+
// via resolveRecoveredMainRepair once main recovers.
|
|
53
|
+
this.reportUnhealthyMain(projectId, project.github.repoFullName, baseBranch, summary);
|
|
54
|
+
}
|
|
55
|
+
reportUnhealthyMain(projectId, repoFullName, baseBranch, summary) {
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const lastReportedAt = this.lastUnhealthyReportAt.get(projectId);
|
|
58
|
+
if (lastReportedAt !== undefined && now - lastReportedAt < MAIN_BRANCH_HEALTH_GRACE_MS) {
|
|
63
59
|
return;
|
|
64
60
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
title: buildMainRepairTitle(project),
|
|
68
|
-
description: buildMainRepairDescription(project, summary, protocol.priorityLabel),
|
|
69
|
-
});
|
|
70
|
-
const issue = this.db.upsertIssue({
|
|
61
|
+
this.lastUnhealthyReportAt.set(projectId, now);
|
|
62
|
+
this.logger.warn({
|
|
71
63
|
projectId,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
...(created.url ? { url: created.url } : {}),
|
|
78
|
-
...(created.priority != null ? { priority: created.priority } : {}),
|
|
79
|
-
...(created.estimate != null ? { estimate: created.estimate } : {}),
|
|
80
|
-
...(created.stateName ? { currentLinearState: created.stateName } : {}),
|
|
81
|
-
...(created.stateType ? { currentLinearStateType: created.stateType } : {}),
|
|
82
|
-
branchName,
|
|
83
|
-
factoryState: "delegated",
|
|
84
|
-
});
|
|
85
|
-
this.wakeDispatcher.recordEventAndDispatch(projectId, issue.linearIssueId, {
|
|
86
|
-
eventType: "delegated",
|
|
87
|
-
eventJson: JSON.stringify({
|
|
88
|
-
runType: "main_repair",
|
|
89
|
-
baseSha: summary.baseSha,
|
|
90
|
-
failingChecks: summary.failingChecks,
|
|
91
|
-
pendingChecks: summary.pendingChecks,
|
|
92
|
-
priorityLabel: protocol.priorityLabel,
|
|
93
|
-
promptContext: buildMainRepairPromptContext(project, summary, protocol.priorityLabel),
|
|
94
|
-
}),
|
|
95
|
-
dedupeKey: `main_repair:${projectId}:${summary.baseSha}:${summary.failingChecks.map((check) => check.name).join("|")}`,
|
|
96
|
-
});
|
|
97
|
-
this.feed?.publish({
|
|
98
|
-
level: "warn",
|
|
99
|
-
kind: "github",
|
|
100
|
-
issueKey: issue.issueKey,
|
|
101
|
-
projectId,
|
|
102
|
-
stage: "delegated",
|
|
103
|
-
status: "main_repair_queued",
|
|
104
|
-
summary: `Queued main_repair for ${project.github.repoFullName}@${baseBranch}`,
|
|
105
|
-
detail: summary.failingChecks.map((check) => check.name).join(", "),
|
|
106
|
-
});
|
|
64
|
+
repoFullName,
|
|
65
|
+
baseBranch,
|
|
66
|
+
baseSha: summary.baseSha,
|
|
67
|
+
failingChecks: summary.failingChecks.map((check) => check.name),
|
|
68
|
+
}, "main branch CI is red — information only; no repair job posted (merge queue gates on its own spec CI)");
|
|
107
69
|
}
|
|
108
70
|
findExistingMainRepair(projectId, branchName) {
|
|
109
71
|
const candidates = this.db.listIssues()
|
|
@@ -132,35 +94,6 @@ export class MainBranchHealthMonitor {
|
|
|
132
94
|
return 3;
|
|
133
95
|
return 4;
|
|
134
96
|
}
|
|
135
|
-
queueExistingMainRepair(issue, summary, priorityLabel) {
|
|
136
|
-
if (issue.activeRunId !== undefined)
|
|
137
|
-
return;
|
|
138
|
-
if (this.db.issueSessions.hasPendingIssueSessionEvents(issue.projectId, issue.linearIssueId))
|
|
139
|
-
return;
|
|
140
|
-
if (issue.prState === "open" || issue.factoryState === "awaiting_queue" || issue.factoryState === "pr_open")
|
|
141
|
-
return;
|
|
142
|
-
this.db.upsertIssue({
|
|
143
|
-
projectId: issue.projectId,
|
|
144
|
-
linearIssueId: issue.linearIssueId,
|
|
145
|
-
delegatedToPatchRelay: true,
|
|
146
|
-
factoryState: "delegated",
|
|
147
|
-
pendingRunType: null,
|
|
148
|
-
pendingRunContextJson: null,
|
|
149
|
-
activeRunId: null,
|
|
150
|
-
});
|
|
151
|
-
this.wakeDispatcher.recordEventAndDispatch(issue.projectId, issue.linearIssueId, {
|
|
152
|
-
eventType: "delegated",
|
|
153
|
-
eventJson: JSON.stringify({
|
|
154
|
-
runType: "main_repair",
|
|
155
|
-
baseSha: summary.baseSha,
|
|
156
|
-
failingChecks: summary.failingChecks,
|
|
157
|
-
pendingChecks: summary.pendingChecks,
|
|
158
|
-
priorityLabel,
|
|
159
|
-
promptContext: buildMainRepairPromptContext(this.config.projects.find((project) => project.id === issue.projectId) ?? { id: issue.projectId }, summary, priorityLabel),
|
|
160
|
-
}),
|
|
161
|
-
dedupeKey: `main_repair:${issue.projectId}:${summary.baseSha}:${summary.failingChecks.map((check) => check.name).join("|")}`,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
97
|
async resolveRecoveredMainRepair(issue) {
|
|
165
98
|
if (issue.activeRunId !== undefined)
|
|
166
99
|
return;
|
package/dist/run-orchestrator.js
CHANGED
|
@@ -137,7 +137,7 @@ export class RunOrchestrator {
|
|
|
137
137
|
this.runReconciler = new RunReconciler(db, logger, linearProvider, this.linearSync, this.interruptedRunRecovery, this.runFinalizer, this.leasePorts.withHeldLease, this.leasePorts.releaseLease, this.threadPorts.readThreadWithRetry, this.recoveryPorts.recoverOrEscalate, (projectId) => this.config.projects.find((project) => project.id === projectId)?.github?.repoFullName, feed);
|
|
138
138
|
this.runWakePlanner = new RunWakePlanner(db);
|
|
139
139
|
this.idleReconciler = new IdleIssueReconciler(db, config, this.wakeDispatcher, logger, feed);
|
|
140
|
-
this.mainBranchHealthMonitor = new MainBranchHealthMonitor(db, config, linearProvider,
|
|
140
|
+
this.mainBranchHealthMonitor = new MainBranchHealthMonitor(db, config, linearProvider, logger, feed);
|
|
141
141
|
this.mergedLinearCompletionReconciler = new MergedLinearCompletionReconciler(db, linearProvider, logger);
|
|
142
142
|
this.queueHealthMonitor = new QueueHealthMonitor(db, config, {
|
|
143
143
|
advanceIdleIssue: (issue, newState, options) => this.idleReconciler.advanceIdleIssue(issue, newState, options),
|
package/package.json
CHANGED
package/service.env.example
CHANGED
|
@@ -12,8 +12,11 @@ LINEAR_OAUTH_CLIENT_ID=replace-with-linear-oauth-client-id
|
|
|
12
12
|
LINEAR_OAUTH_CLIENT_SECRET=replace-with-linear-oauth-client-secret
|
|
13
13
|
|
|
14
14
|
# Optional: GitHub App for bot identity.
|
|
15
|
-
# When configured, PatchRelay
|
|
16
|
-
#
|
|
15
|
+
# When configured, PatchRelay mints short-lived installation tokens and keeps a
|
|
16
|
+
# private gh config dir fresh (GH_CONFIG_DIR), so both gh and git authenticate as
|
|
17
|
+
# app-name[bot] with an always-fresh token. git reads credentials from gh via the
|
|
18
|
+
# credential helper; nothing is written into repo or global git config, so these
|
|
19
|
+
# credentials never leak into interactive shell sessions.
|
|
17
20
|
# Create a GitHub App at Settings > Developer settings > GitHub Apps.
|
|
18
21
|
#
|
|
19
22
|
# The private key resolves through the provider-agnostic fallback:
|