patchrelay 0.12.6 → 0.12.7
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/db.js +10 -0
- package/dist/run-orchestrator.js +51 -0
- package/dist/service-runtime.js +5 -0
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
package/dist/db.js
CHANGED
|
@@ -217,6 +217,16 @@ export class PatchRelayDatabase {
|
|
|
217
217
|
linearIssueId: String(row.linear_issue_id),
|
|
218
218
|
}));
|
|
219
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Issues idle in pr_open with no active run — candidates for state
|
|
222
|
+
* advancement based on stored PR metadata (missed GitHub webhooks).
|
|
223
|
+
*/
|
|
224
|
+
listIdlePrOpenIssues() {
|
|
225
|
+
const rows = this.connection
|
|
226
|
+
.prepare("SELECT * FROM issues WHERE factory_state = 'pr_open' AND active_run_id IS NULL AND pending_run_type IS NULL AND pr_number IS NOT NULL")
|
|
227
|
+
.all();
|
|
228
|
+
return rows.map(mapIssueRow);
|
|
229
|
+
}
|
|
220
230
|
listIssuesByState(projectId, state) {
|
|
221
231
|
const rows = this.connection
|
|
222
232
|
.prepare("SELECT * FROM issues WHERE project_id = ? AND factory_state = ? ORDER BY pr_number ASC")
|
package/dist/run-orchestrator.js
CHANGED
|
@@ -349,6 +349,57 @@ export class RunOrchestrator {
|
|
|
349
349
|
for (const run of this.db.listRunningRuns()) {
|
|
350
350
|
await this.reconcileRun(run);
|
|
351
351
|
}
|
|
352
|
+
// Advance issues stuck in pr_open whose stored PR metadata already
|
|
353
|
+
// shows they should transition (e.g. approved PR, missed webhook).
|
|
354
|
+
await this.reconcileIdlePrOpenIssues();
|
|
355
|
+
}
|
|
356
|
+
async reconcileIdlePrOpenIssues() {
|
|
357
|
+
for (const issue of this.db.listIdlePrOpenIssues()) {
|
|
358
|
+
if (issue.prState === "merged") {
|
|
359
|
+
this.advanceIdleIssue(issue, "done");
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (issue.prReviewState === "approved") {
|
|
363
|
+
this.advanceIdleIssue(issue, "awaiting_queue");
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
// Stored metadata may be stale (missed webhooks during downtime).
|
|
367
|
+
// Query GitHub for the actual PR review state.
|
|
368
|
+
const project = this.config.projects.find((p) => p.id === issue.projectId);
|
|
369
|
+
if (!project?.github?.repoFullName || !issue.prNumber)
|
|
370
|
+
continue;
|
|
371
|
+
try {
|
|
372
|
+
const { stdout } = await execCommand("gh", [
|
|
373
|
+
"pr", "view", String(issue.prNumber),
|
|
374
|
+
"--repo", project.github.repoFullName,
|
|
375
|
+
"--json", "state,reviewDecision",
|
|
376
|
+
], { timeoutMs: 10_000 });
|
|
377
|
+
const pr = JSON.parse(stdout);
|
|
378
|
+
if (pr.state === "MERGED") {
|
|
379
|
+
this.db.upsertIssue({ projectId: issue.projectId, linearIssueId: issue.linearIssueId, prState: "merged" });
|
|
380
|
+
this.advanceIdleIssue(issue, "done");
|
|
381
|
+
}
|
|
382
|
+
else if (pr.reviewDecision === "APPROVED") {
|
|
383
|
+
this.db.upsertIssue({ projectId: issue.projectId, linearIssueId: issue.linearIssueId, prReviewState: "approved" });
|
|
384
|
+
this.advanceIdleIssue(issue, "awaiting_queue");
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
this.logger.debug({ issueKey: issue.issueKey, error: error instanceof Error ? error.message : String(error) }, "Failed to query GitHub PR state during reconciliation");
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
advanceIdleIssue(issue, newState) {
|
|
393
|
+
this.logger.info({ issueKey: issue.issueKey, from: issue.factoryState, to: newState }, "Reconciliation: advancing idle issue from stored PR metadata");
|
|
394
|
+
this.db.upsertIssue({
|
|
395
|
+
projectId: issue.projectId,
|
|
396
|
+
linearIssueId: issue.linearIssueId,
|
|
397
|
+
factoryState: newState,
|
|
398
|
+
...(newState === "awaiting_queue" ? { pendingMergePrep: true } : {}),
|
|
399
|
+
});
|
|
400
|
+
if (newState === "awaiting_queue") {
|
|
401
|
+
this.enqueueIssue(issue.projectId, issue.linearIssueId);
|
|
402
|
+
}
|
|
352
403
|
}
|
|
353
404
|
async reconcileRun(run) {
|
|
354
405
|
const issue = this.db.getIssue(run.projectId, run.linearIssueId);
|
package/dist/service-runtime.js
CHANGED
|
@@ -90,6 +90,11 @@ export class ServiceRuntime {
|
|
|
90
90
|
this.reconcileInProgress = true;
|
|
91
91
|
try {
|
|
92
92
|
await promiseWithTimeout(this.runReconciler.reconcileActiveRuns(), this.options.reconcileTimeoutMs ?? DEFAULT_RECONCILE_TIMEOUT_MS, "Background active-run reconciliation");
|
|
93
|
+
// Pick up issues that became ready outside the webhook path
|
|
94
|
+
// (e.g. CLI retry, manual DB edits) without requiring a restart.
|
|
95
|
+
for (const issue of this.readyIssueSource.listIssuesReadyForExecution()) {
|
|
96
|
+
this.enqueueIssue(issue.projectId, issue.linearIssueId);
|
|
97
|
+
}
|
|
93
98
|
}
|
|
94
99
|
catch (error) {
|
|
95
100
|
this.logger.warn({ error: error instanceof Error ? error.message : String(error) }, "Background active-run reconciliation failed");
|