patchrelay 0.35.15 → 0.35.17
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
package/dist/cli/watch/App.js
CHANGED
|
@@ -186,10 +186,10 @@ export function App({ baseUrl, bearerToken, initialIssueKey }) {
|
|
|
186
186
|
else if (input === "k" || key.upArrow) {
|
|
187
187
|
dispatch({ type: "detail-scroll", delta: -1 });
|
|
188
188
|
}
|
|
189
|
-
else if (key.pageDown || (key.ctrl && input === "d")) {
|
|
189
|
+
else if (key.pageDown || (key.ctrl && input === "d") || (key.ctrl && key.downArrow)) {
|
|
190
190
|
dispatch({ type: "detail-page", direction: "down" });
|
|
191
191
|
}
|
|
192
|
-
else if (key.pageUp || (key.ctrl && input === "u")) {
|
|
192
|
+
else if (key.pageUp || (key.ctrl && input === "u") || (key.ctrl && key.upArrow)) {
|
|
193
193
|
dispatch({ type: "detail-page", direction: "up" });
|
|
194
194
|
}
|
|
195
195
|
else if (key.home) {
|
|
@@ -4,7 +4,7 @@ export function HelpBar({ view, follow, detailTab }) {
|
|
|
4
4
|
let text;
|
|
5
5
|
if (view === "detail") {
|
|
6
6
|
const tabHint = detailTab === "history" ? "t: timeline" : "h: history";
|
|
7
|
-
text = [tabHint, "j/k: scroll", "
|
|
7
|
+
text = [tabHint, "j/k: scroll", "Ctrl-U/Ctrl-D: page", "[ ]: issue", "Home/End: jump", `f: live ${follow ? "on" : "off"}`, "p: prompt", "s: stop", "r: retry"]
|
|
8
8
|
.filter(Boolean)
|
|
9
9
|
.join(" ");
|
|
10
10
|
}
|
|
@@ -70,7 +70,7 @@ function buildHeaderLines(input, width) {
|
|
|
70
70
|
const issue = input.issue;
|
|
71
71
|
const session = sessionDisplay(issue);
|
|
72
72
|
const stage = stageDisplay(issue);
|
|
73
|
-
const facts =
|
|
73
|
+
const facts = buildFactSegments(issue, input.issueContext);
|
|
74
74
|
const meta = buildMeta(input.tokenUsage, input.diffSummary, input.issueContext);
|
|
75
75
|
const headerSegments = [
|
|
76
76
|
{ text: issue.issueKey ?? issue.projectId, bold: true },
|
|
@@ -81,7 +81,7 @@ function buildHeaderLines(input, width) {
|
|
|
81
81
|
];
|
|
82
82
|
if (facts.length > 0) {
|
|
83
83
|
headerSegments.push({ text: " ", dimColor: true });
|
|
84
|
-
headerSegments.push(
|
|
84
|
+
headerSegments.push(...joinFactSegments(facts));
|
|
85
85
|
}
|
|
86
86
|
if (input.activeRunStartedAt) {
|
|
87
87
|
headerSegments.push({ text: " ", dimColor: true });
|
|
@@ -394,28 +394,38 @@ function renderSideTripLines(trip, runOffset, width) {
|
|
|
394
394
|
}));
|
|
395
395
|
return lines;
|
|
396
396
|
}
|
|
397
|
-
function
|
|
397
|
+
function buildFactSegments(issue, issueContext) {
|
|
398
398
|
const facts = [];
|
|
399
399
|
const rereviewNeeded = isRereviewNeeded(issue);
|
|
400
400
|
if (issue.prNumber !== undefined)
|
|
401
|
-
facts.push(`PR #${issue.prNumber}
|
|
401
|
+
facts.push([{ text: `PR #${issue.prNumber}`, color: "cyan" }]);
|
|
402
402
|
if (issue.prReviewState === "approved")
|
|
403
|
-
facts.push("approved");
|
|
403
|
+
facts.push([{ text: "approved", color: "green" }]);
|
|
404
404
|
else if (rereviewNeeded)
|
|
405
|
-
facts.push("re-review needed");
|
|
405
|
+
facts.push([{ text: "re-review needed", color: "yellow" }]);
|
|
406
406
|
else if (issue.prReviewState === "changes_requested")
|
|
407
|
-
facts.push("changes requested");
|
|
407
|
+
facts.push([{ text: "changes requested", color: "yellow" }]);
|
|
408
408
|
if (issue.waitingReason && issue.sessionState === "waiting_input")
|
|
409
|
-
facts.push(issue.waitingReason);
|
|
409
|
+
facts.push([{ text: issue.waitingReason, color: "yellow" }]);
|
|
410
410
|
const checks = prChecksFact({
|
|
411
411
|
...issue,
|
|
412
412
|
latestFailureCheckName: issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName,
|
|
413
413
|
});
|
|
414
414
|
if (checks) {
|
|
415
|
-
facts.push(checks.text);
|
|
415
|
+
facts.push([{ text: checks.text, color: checks.color }]);
|
|
416
416
|
}
|
|
417
417
|
return facts;
|
|
418
418
|
}
|
|
419
|
+
function joinFactSegments(facts) {
|
|
420
|
+
const segments = [];
|
|
421
|
+
for (const [index, fact] of facts.entries()) {
|
|
422
|
+
if (index > 0) {
|
|
423
|
+
segments.push({ text: " · ", dimColor: true });
|
|
424
|
+
}
|
|
425
|
+
segments.push(...fact);
|
|
426
|
+
}
|
|
427
|
+
return segments;
|
|
428
|
+
}
|
|
419
429
|
function buildMeta(tokenUsage, diffSummary, issueContext) {
|
|
420
430
|
const meta = [];
|
|
421
431
|
if (tokenUsage)
|
|
@@ -127,7 +127,7 @@ function detailStateAfterLayout(state, viewportRows, contentRows) {
|
|
|
127
127
|
detailContentRows: Math.max(0, contentRows),
|
|
128
128
|
};
|
|
129
129
|
const maxOffset = maxDetailScrollOffset(nextState.detailContentRows, nextState.detailViewportRows);
|
|
130
|
-
const shouldFollow = state.follow
|
|
130
|
+
const shouldFollow = state.follow;
|
|
131
131
|
const nextOffset = shouldFollow ? maxOffset : Math.min(state.detailScrollOffset, maxOffset);
|
|
132
132
|
return {
|
|
133
133
|
detailViewportRows: nextState.detailViewportRows,
|
|
@@ -7,6 +7,20 @@ import { execCommand } from "./utils.js";
|
|
|
7
7
|
function isFailingCheckStatus(status) {
|
|
8
8
|
return status === "failed" || status === "failure";
|
|
9
9
|
}
|
|
10
|
+
function isReviewDecisionApproved(value) {
|
|
11
|
+
return value?.trim().toUpperCase() === "APPROVED";
|
|
12
|
+
}
|
|
13
|
+
function isReviewDecisionChangesRequested(value) {
|
|
14
|
+
return value?.trim().toUpperCase() === "CHANGES_REQUESTED";
|
|
15
|
+
}
|
|
16
|
+
function isReviewDecisionReviewRequired(value) {
|
|
17
|
+
return value?.trim().toUpperCase() === "REVIEW_REQUIRED";
|
|
18
|
+
}
|
|
19
|
+
function hasCompletedReviewQuillVerdict(entries) {
|
|
20
|
+
return (entries ?? []).some((entry) => entry.__typename === "CheckRun"
|
|
21
|
+
&& entry.name === "review-quill/verdict"
|
|
22
|
+
&& entry.status === "COMPLETED");
|
|
23
|
+
}
|
|
10
24
|
function getGateCheckNames(project) {
|
|
11
25
|
const configured = project?.gateChecks?.map((entry) => entry.trim()).filter(Boolean) ?? [];
|
|
12
26
|
return configured.length > 0 ? configured : ["verify"];
|
|
@@ -370,11 +384,13 @@ export class IdleIssueReconciler {
|
|
|
370
384
|
linearIssueId: issue.linearIssueId,
|
|
371
385
|
...(pr.headRefOid ? { prHeadSha: pr.headRefOid } : {}),
|
|
372
386
|
...(pr.state === "OPEN" ? { prState: "open" } : {}),
|
|
373
|
-
...(pr.reviewDecision
|
|
387
|
+
...(isReviewDecisionApproved(pr.reviewDecision)
|
|
374
388
|
? { prReviewState: "approved" }
|
|
375
|
-
: pr.reviewDecision
|
|
389
|
+
: isReviewDecisionChangesRequested(pr.reviewDecision)
|
|
376
390
|
? { prReviewState: "changes_requested" }
|
|
377
|
-
:
|
|
391
|
+
: isReviewDecisionReviewRequired(pr.reviewDecision)
|
|
392
|
+
? { prReviewState: "commented" }
|
|
393
|
+
: {}),
|
|
378
394
|
...(gateCheckStatus ? { prCheckStatus: gateCheckStatus } : {}),
|
|
379
395
|
...(pr.headRefOid && gateCheckStatus
|
|
380
396
|
? {
|
|
@@ -399,6 +415,22 @@ export class IdleIssueReconciler {
|
|
|
399
415
|
});
|
|
400
416
|
return;
|
|
401
417
|
}
|
|
418
|
+
if (isReviewDecisionReviewRequired(pr.reviewDecision)
|
|
419
|
+
&& gateCheckStatus === "success"
|
|
420
|
+
&& hasCompletedReviewQuillVerdict(pr.statusCheckRollup)) {
|
|
421
|
+
this.logger.warn({ issueKey: issue.issueKey, prNumber: issue.prNumber, reviewDecision: pr.reviewDecision }, "Reconciliation: review-quill completed without a decisive GitHub review; escalating for operator input");
|
|
422
|
+
this.advanceIdleIssue(issue, "awaiting_input");
|
|
423
|
+
this.feed?.publish({
|
|
424
|
+
level: "warn",
|
|
425
|
+
kind: "github",
|
|
426
|
+
issueKey: issue.issueKey,
|
|
427
|
+
projectId: issue.projectId,
|
|
428
|
+
stage: "awaiting_input",
|
|
429
|
+
status: "non_decisive_review",
|
|
430
|
+
summary: `PR #${issue.prNumber} needs operator input: review-quill finished but GitHub still requires review`,
|
|
431
|
+
});
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
402
434
|
const downstreamOwned = issue.factoryState === "awaiting_queue" || issue.prReviewState === "approved" || pr.reviewDecision === "APPROVED";
|
|
403
435
|
const mergeConflictDetected = pr.mergeable === "CONFLICTING" || pr.mergeStateStatus === "DIRTY";
|
|
404
436
|
const refreshedIssue = this.db.getIssue(issue.projectId, issue.linearIssueId) ?? issue;
|
|
@@ -432,7 +464,7 @@ export class IdleIssueReconciler {
|
|
|
432
464
|
});
|
|
433
465
|
return;
|
|
434
466
|
}
|
|
435
|
-
if (pr.reviewDecision
|
|
467
|
+
if (isReviewDecisionApproved(pr.reviewDecision)) {
|
|
436
468
|
this.db.upsertIssue({
|
|
437
469
|
projectId: issue.projectId,
|
|
438
470
|
linearIssueId: issue.linearIssueId,
|