patchrelay 0.50.5 → 0.50.6
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/issue-token.js +15 -4
- package/dist/cli/watch/state-visualization.js +23 -3
- package/dist/delegation-linked-pr.js +2 -2
- package/dist/linear-session-reporting.js +14 -0
- package/dist/linear-status-comment-sync.js +21 -1
- package/dist/pr-display-context.js +21 -0
- package/dist/prompting/patchrelay.js +33 -3
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isUndelegatedPausedIssue } from "../../paused-issue-state.js";
|
|
2
|
+
import { derivePrDisplayContext } from "../../pr-display-context.js";
|
|
2
3
|
import { hasFailedPrChecks, hasPendingPrChecks, isApprovedReviewState, isAwaitingReviewState, isChangesRequestedReviewState, prChecksFact, } from "./pr-status.js";
|
|
3
4
|
const GLYPH = {
|
|
4
5
|
running: "\u25cf",
|
|
@@ -89,9 +90,14 @@ export function prTokenFor(issue) {
|
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
92
|
function prKind(issue) {
|
|
92
|
-
|
|
93
|
+
const prContext = derivePrDisplayContext(issue);
|
|
94
|
+
if (prContext.kind === "merged_pr")
|
|
93
95
|
return "approved";
|
|
94
|
-
if (
|
|
96
|
+
if (prContext.kind === "closed_replacement_pending")
|
|
97
|
+
return "queued";
|
|
98
|
+
if (prContext.kind === "closed_pr_paused")
|
|
99
|
+
return "attention";
|
|
100
|
+
if (prContext.kind === "closed_historical_pr")
|
|
95
101
|
return "declined";
|
|
96
102
|
if (issue.prReviewState === "approved")
|
|
97
103
|
return "approved";
|
|
@@ -104,9 +110,14 @@ function prKind(issue) {
|
|
|
104
110
|
return "running";
|
|
105
111
|
}
|
|
106
112
|
function prPhraseFor(issue) {
|
|
107
|
-
|
|
113
|
+
const prContext = derivePrDisplayContext(issue);
|
|
114
|
+
if (prContext.kind === "merged_pr")
|
|
108
115
|
return "merged";
|
|
109
|
-
if (
|
|
116
|
+
if (prContext.kind === "closed_replacement_pending")
|
|
117
|
+
return "replace pr";
|
|
118
|
+
if (prContext.kind === "closed_pr_paused")
|
|
119
|
+
return "redelegate";
|
|
120
|
+
if (prContext.kind === "closed_historical_pr")
|
|
110
121
|
return "closed";
|
|
111
122
|
if (isChangesRequestedReviewState(issue.prReviewState))
|
|
112
123
|
return "changes req";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasOpenPr } from "../../pr-state.js";
|
|
2
|
+
import { derivePrDisplayContext } from "../../pr-display-context.js";
|
|
2
3
|
const STATE_LABELS = {
|
|
3
4
|
delegated: "delegated",
|
|
4
5
|
implementing: "implementing",
|
|
@@ -164,9 +165,28 @@ export function buildPatchRelayQueueObservations(issue, feedEvents) {
|
|
|
164
165
|
});
|
|
165
166
|
}
|
|
166
167
|
if (issue.prNumber !== undefined) {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
const prContext = derivePrDisplayContext(issue);
|
|
169
|
+
let prLabel;
|
|
170
|
+
switch (prContext.kind) {
|
|
171
|
+
case "active_pr":
|
|
172
|
+
case "merged_pr":
|
|
173
|
+
prLabel = hasOpenPr(issue.prNumber, issue.prState)
|
|
174
|
+
? `Tracked PR: #${issue.prNumber}`
|
|
175
|
+
: `Tracked PR: #${issue.prNumber}${issue.prState ? ` (${issue.prState})` : ""}`;
|
|
176
|
+
break;
|
|
177
|
+
case "closed_historical_pr":
|
|
178
|
+
prLabel = `Previous PR: #${prContext.prNumber} (closed)`;
|
|
179
|
+
break;
|
|
180
|
+
case "closed_replacement_pending":
|
|
181
|
+
prLabel = `Previous PR: #${prContext.prNumber} (closed; replacement pending)`;
|
|
182
|
+
break;
|
|
183
|
+
case "closed_pr_paused":
|
|
184
|
+
prLabel = `Previous PR: #${prContext.prNumber} (closed; redelegate to replace)`;
|
|
185
|
+
break;
|
|
186
|
+
case "no_pr":
|
|
187
|
+
prLabel = "PR context unavailable";
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
170
190
|
observations.push({
|
|
171
191
|
tone: "info",
|
|
172
192
|
text: `${prLabel}${issue.prReviewState ? ` (${issue.prReviewState})` : ""}`,
|
|
@@ -45,8 +45,8 @@ export function deriveLinkedPrAdoptionOutcome(project, prNumber, remote) {
|
|
|
45
45
|
}
|
|
46
46
|
if (prState === "closed") {
|
|
47
47
|
return {
|
|
48
|
-
factoryState: "
|
|
49
|
-
pendingRunType:
|
|
48
|
+
factoryState: "delegated",
|
|
49
|
+
pendingRunType: "implementation",
|
|
50
50
|
issueUpdates: {
|
|
51
51
|
...issueUpdates,
|
|
52
52
|
prIsDraft: false,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { formatRunTypeLabel } from "./agent-session-plan.js";
|
|
2
2
|
import { sanitizeOperatorFacingText } from "./presentation-text.js";
|
|
3
3
|
import { isClosedPrState } from "./pr-state.js";
|
|
4
|
+
import { derivePrDisplayContext } from "./pr-display-context.js";
|
|
4
5
|
function lowerRunTypeLabel(runType) {
|
|
5
6
|
return formatRunTypeLabel(runType).toLowerCase();
|
|
6
7
|
}
|
|
@@ -187,6 +188,7 @@ export function buildMergePrepEscalationActivity(attempts) {
|
|
|
187
188
|
};
|
|
188
189
|
}
|
|
189
190
|
export function summarizeIssueStateForLinear(issue) {
|
|
191
|
+
const prContext = derivePrDisplayContext(issue);
|
|
190
192
|
switch (issue.sessionState) {
|
|
191
193
|
case "waiting_input":
|
|
192
194
|
return issue.waitingReason ?? (issue.prNumber && !isClosedPrState(issue.prState) ? `PR #${issue.prNumber} is waiting for input.` : "Waiting for input.");
|
|
@@ -208,11 +210,23 @@ export function summarizeIssueStateForLinear(issue) {
|
|
|
208
210
|
}
|
|
209
211
|
switch (issue.factoryState) {
|
|
210
212
|
case "delegated":
|
|
213
|
+
if (prContext.kind === "closed_replacement_pending") {
|
|
214
|
+
return `Queued to replace closed PR #${prContext.prNumber}.`;
|
|
215
|
+
}
|
|
216
|
+
if (prContext.kind === "closed_pr_paused") {
|
|
217
|
+
return `Closed PR #${prContext.prNumber} needs redelegation before replacement.`;
|
|
218
|
+
}
|
|
211
219
|
if (!issue.delegatedToPatchRelay) {
|
|
212
220
|
return "PatchRelay is queued to start work, but automation is paused.";
|
|
213
221
|
}
|
|
214
222
|
return "Queued to start work.";
|
|
215
223
|
case "implementing":
|
|
224
|
+
if (prContext.kind === "closed_replacement_pending") {
|
|
225
|
+
return `Replacing closed PR #${prContext.prNumber} with a fresh PR.`;
|
|
226
|
+
}
|
|
227
|
+
if (prContext.kind === "closed_pr_paused") {
|
|
228
|
+
return `Closed PR #${prContext.prNumber} needs redelegation before replacement.`;
|
|
229
|
+
}
|
|
216
230
|
if (!issue.delegatedToPatchRelay) {
|
|
217
231
|
return "Implementation is paused because the issue is undelegated.";
|
|
218
232
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { extractCompletionCheck } from "./completion-check.js";
|
|
2
2
|
import { isClosedPrState } from "./pr-state.js";
|
|
3
|
+
import { derivePrDisplayContext } from "./pr-display-context.js";
|
|
3
4
|
import { deriveIssueStatusNote } from "./status-note.js";
|
|
4
5
|
import { derivePatchRelayWaitingReason } from "./waiting-reason.js";
|
|
5
6
|
export async function syncVisibleStatusComment(params) {
|
|
@@ -84,8 +85,17 @@ function renderStatusComment(db, issue, trackedIssue, options) {
|
|
|
84
85
|
lines.push("", `Reply in a Linear comment to continue, or run \`patchrelay issue prompt ${issueRef} "..."\`.`);
|
|
85
86
|
}
|
|
86
87
|
if (issue.prNumber !== undefined || issue.prUrl) {
|
|
88
|
+
const prContext = derivePrDisplayContext(issue);
|
|
87
89
|
const prLabel = issue.prNumber !== undefined ? `#${issue.prNumber}` : "open";
|
|
88
|
-
|
|
90
|
+
const linkedLabel = issue.prUrl ? `[${prLabel}](${issue.prUrl})` : prLabel;
|
|
91
|
+
const prLine = prContext.kind === "closed_historical_pr"
|
|
92
|
+
? `Previous PR: ${linkedLabel} (closed)`
|
|
93
|
+
: prContext.kind === "closed_replacement_pending"
|
|
94
|
+
? `Previous PR: ${linkedLabel} (closed; replacement PR needed)`
|
|
95
|
+
: prContext.kind === "closed_pr_paused"
|
|
96
|
+
? `Previous PR: ${linkedLabel} (closed; redelegate to replace it)`
|
|
97
|
+
: `PR: ${linkedLabel}`;
|
|
98
|
+
lines.push("", prLine);
|
|
89
99
|
}
|
|
90
100
|
if (latestRun) {
|
|
91
101
|
lines.push("", `Latest run: ${formatLatestRun(latestRun)}`);
|
|
@@ -103,6 +113,7 @@ function renderStatusComment(db, issue, trackedIssue, options) {
|
|
|
103
113
|
return lines.join("\n");
|
|
104
114
|
}
|
|
105
115
|
function statusHeadline(issue, activeRunType) {
|
|
116
|
+
const prContext = derivePrDisplayContext(issue);
|
|
106
117
|
if (activeRunType) {
|
|
107
118
|
return `Running ${humanize(activeRunType)}`;
|
|
108
119
|
}
|
|
@@ -123,6 +134,9 @@ function statusHeadline(issue, activeRunType) {
|
|
|
123
134
|
break;
|
|
124
135
|
}
|
|
125
136
|
if (!issue.delegatedToPatchRelay && issue.prNumber !== undefined) {
|
|
137
|
+
if (prContext.kind === "closed_pr_paused") {
|
|
138
|
+
return `Closed PR #${prContext.prNumber} is waiting for redelegation before replacement`;
|
|
139
|
+
}
|
|
126
140
|
if (issue.factoryState === "awaiting_queue" || issue.prReviewState === "approved") {
|
|
127
141
|
return `PR #${issue.prNumber} is awaiting downstream merge while PatchRelay is paused`;
|
|
128
142
|
}
|
|
@@ -144,8 +158,14 @@ function statusHeadline(issue, activeRunType) {
|
|
|
144
158
|
}
|
|
145
159
|
switch (issue.factoryState) {
|
|
146
160
|
case "delegated":
|
|
161
|
+
if (prContext.kind === "closed_replacement_pending") {
|
|
162
|
+
return `Queued to replace closed PR #${prContext.prNumber}`;
|
|
163
|
+
}
|
|
147
164
|
return "Queued to start work";
|
|
148
165
|
case "implementing":
|
|
166
|
+
if (prContext.kind === "closed_replacement_pending") {
|
|
167
|
+
return `Replacing closed PR #${prContext.prNumber} with a fresh PR`;
|
|
168
|
+
}
|
|
149
169
|
return "Implementing requested change";
|
|
150
170
|
case "pr_open":
|
|
151
171
|
return issue.prNumber !== undefined ? `PR #${issue.prNumber} opened` : "PR opened";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function isTerminalFactoryState(factoryState) {
|
|
2
|
+
return factoryState === "done" || factoryState === "failed" || factoryState === "escalated";
|
|
3
|
+
}
|
|
4
|
+
export function derivePrDisplayContext(issue) {
|
|
5
|
+
if (issue.prNumber === undefined) {
|
|
6
|
+
return { kind: "no_pr" };
|
|
7
|
+
}
|
|
8
|
+
if (issue.prState === "merged") {
|
|
9
|
+
return { kind: "merged_pr", prNumber: issue.prNumber };
|
|
10
|
+
}
|
|
11
|
+
if (issue.prState === "closed") {
|
|
12
|
+
if (isTerminalFactoryState(issue.factoryState)) {
|
|
13
|
+
return { kind: "closed_historical_pr", prNumber: issue.prNumber };
|
|
14
|
+
}
|
|
15
|
+
if (issue.delegatedToPatchRelay === false) {
|
|
16
|
+
return { kind: "closed_pr_paused", prNumber: issue.prNumber };
|
|
17
|
+
}
|
|
18
|
+
return { kind: "closed_replacement_pending", prNumber: issue.prNumber };
|
|
19
|
+
}
|
|
20
|
+
return { kind: "active_pr", prNumber: issue.prNumber };
|
|
21
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { derivePrDisplayContext } from "../pr-display-context.js";
|
|
3
4
|
const WORKFLOW_FILES = {
|
|
4
5
|
implementation: "IMPLEMENTATION_WORKFLOW.md",
|
|
5
6
|
review_fix: "REVIEW_WORKFLOW.md",
|
|
@@ -28,11 +29,23 @@ function hasWorkflowFile(repoPath, runType) {
|
|
|
28
29
|
return existsSync(filePath);
|
|
29
30
|
}
|
|
30
31
|
function buildPromptHeader(issue) {
|
|
32
|
+
const prContext = derivePrDisplayContext(issue);
|
|
33
|
+
const prLine = prContext.kind === "active_pr"
|
|
34
|
+
? `PR: #${prContext.prNumber}`
|
|
35
|
+
: prContext.kind === "merged_pr"
|
|
36
|
+
? `Merged PR: #${prContext.prNumber}`
|
|
37
|
+
: prContext.kind === "closed_historical_pr"
|
|
38
|
+
? `Previous PR: #${prContext.prNumber} (closed)`
|
|
39
|
+
: prContext.kind === "closed_replacement_pending"
|
|
40
|
+
? `Previous PR: #${prContext.prNumber} (closed; replacement PR needed)`
|
|
41
|
+
: prContext.kind === "closed_pr_paused"
|
|
42
|
+
? `Previous PR: #${prContext.prNumber} (closed; redelegate to replace it)`
|
|
43
|
+
: undefined;
|
|
31
44
|
return [
|
|
32
45
|
`Issue: ${issue.issueKey ?? issue.linearIssueId}`,
|
|
33
46
|
issue.title ? `Title: ${issue.title}` : undefined,
|
|
34
47
|
issue.branchName ? `Branch: ${issue.branchName}` : undefined,
|
|
35
|
-
|
|
48
|
+
prLine,
|
|
36
49
|
].filter(Boolean).join("\n");
|
|
37
50
|
}
|
|
38
51
|
function extractIssueSection(description, heading) {
|
|
@@ -354,6 +367,7 @@ function buildQueueRepairContext(context) {
|
|
|
354
367
|
return lines.filter(Boolean).join("\n");
|
|
355
368
|
}
|
|
356
369
|
function buildFollowUpContextLines(issue, runType, context) {
|
|
370
|
+
const prContext = derivePrDisplayContext(issue);
|
|
357
371
|
const wakeReason = typeof context?.wakeReason === "string" ? context.wakeReason : undefined;
|
|
358
372
|
const followUps = Array.isArray(context?.followUps) ? context.followUps : [];
|
|
359
373
|
const followUpLines = followUps
|
|
@@ -389,9 +403,25 @@ function buildFollowUpContextLines(issue, runType, context) {
|
|
|
389
403
|
followUpLines.forEach((line) => lines.push(`- ${line}`));
|
|
390
404
|
}
|
|
391
405
|
if (issue.prNumber || issue.prHeadSha || issue.prReviewState || context?.mergeStateStatus) {
|
|
392
|
-
|
|
406
|
+
const prHeading = prContext.kind === "closed_historical_pr"
|
|
407
|
+
|| prContext.kind === "closed_replacement_pending"
|
|
408
|
+
|| prContext.kind === "closed_pr_paused"
|
|
409
|
+
? "Previous PR facts:"
|
|
410
|
+
: "Current PR facts:";
|
|
411
|
+
const prLine = prContext.kind === "active_pr"
|
|
412
|
+
? `Current PR: #${prContext.prNumber}`
|
|
413
|
+
: prContext.kind === "merged_pr"
|
|
414
|
+
? `Merged PR: #${prContext.prNumber}`
|
|
415
|
+
: prContext.kind === "closed_historical_pr"
|
|
416
|
+
? `Previous PR: #${prContext.prNumber} (closed)`
|
|
417
|
+
: prContext.kind === "closed_replacement_pending"
|
|
418
|
+
? `Previous PR: #${prContext.prNumber} (closed; replacement PR needed)`
|
|
419
|
+
: prContext.kind === "closed_pr_paused"
|
|
420
|
+
? `Previous PR: #${prContext.prNumber} (closed; redelegate to replace it)`
|
|
421
|
+
: "";
|
|
422
|
+
lines.push("", prHeading, `Fact freshness: ${context?.githubFactsFresh === true
|
|
393
423
|
? "refreshed immediately before this turn was created."
|
|
394
|
-
: "may now be stale; refresh before making irreversible decisions."}`,
|
|
424
|
+
: "may now be stale; refresh before making irreversible decisions."}`, prLine, issue.prHeadSha ? `Current relevant head SHA: ${issue.prHeadSha}` : "", issue.prReviewState ? `Current review state: ${issue.prReviewState}` : "", typeof context?.mergeStateStatus === "string" ? `Merge state against ${String(context?.baseBranch ?? "main")}: ${String(context.mergeStateStatus)}` : "");
|
|
395
425
|
}
|
|
396
426
|
return lines.filter(Boolean);
|
|
397
427
|
}
|