patchrelay 0.35.13 → 0.35.14

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "service": "patchrelay",
3
- "version": "0.35.13",
4
- "commit": "f82366476725",
5
- "builtAt": "2026-04-07T23:06:53.397Z"
3
+ "version": "0.35.14",
4
+ "commit": "a8a92461c05e",
5
+ "builtAt": "2026-04-08T08:42:31.210Z"
6
6
  }
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from "ink";
3
3
  import { summarizeIssueStatusNote } from "./issue-status-note.js";
4
4
  import { relativeTime, truncate } from "./format-utils.js";
5
+ import { hasDisplayPrBlocker, isRereviewNeeded, prChecksFact } from "./pr-status.js";
5
6
  // ─── State display ──────────────────────────────────────────────
6
7
  const TERMINAL_STATES = new Set(["done", "failed", "escalated"]);
7
8
  function needsOperatorIntervention(issue) {
@@ -14,10 +15,10 @@ function effectiveState(issue) {
14
15
  return "failed";
15
16
  if (issue.blockedByCount > 0 && !issue.activeRunType)
16
17
  return "blocked";
17
- if (issue.readyForExecution && !issue.activeRunType)
18
- return "ready";
19
18
  if (issue.sessionState === "waiting_input")
20
19
  return "awaiting_input";
20
+ if (issue.readyForExecution && !issue.activeRunType && !hasDisplayPrBlocker(issue))
21
+ return "ready";
21
22
  return issue.factoryState;
22
23
  }
23
24
  function sessionDisplay(issue) {
@@ -61,9 +62,7 @@ function stageLabel(issue) {
61
62
  // ─── Context facts (what matters right now) ─────────────────────
62
63
  function buildFacts(issue, selected) {
63
64
  const facts = [];
64
- const rereviewNeeded = issue.prReviewState === "changes_requested"
65
- && (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
66
- && !issue.activeRunType;
65
+ const rereviewNeeded = isRereviewNeeded(issue);
67
66
  // PR number
68
67
  if (issue.prNumber !== undefined) {
69
68
  facts.push({ text: `PR #${issue.prNumber}` });
@@ -94,23 +93,9 @@ function buildFacts(issue, selected) {
94
93
  facts.push({ text: "awaiting review", color: "yellow" });
95
94
  }
96
95
  // Check status — compact
97
- if (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success") {
98
- facts.push({ text: "checks passed", color: "green" });
99
- }
100
- else if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
101
- const failedNames = issue.prChecksSummary?.failedNames ?? [];
102
- const checkInfo = issue.latestFailureCheckName
103
- ?? (failedNames.length > 0 ? failedNames.slice(0, 2).join(", ") : "checks");
104
- facts.push({ text: `${checkInfo} failed`, color: "red" });
105
- }
106
- else if (issue.prCheckStatus === "pending" || issue.prCheckStatus === "in_progress") {
107
- const summary = issue.prChecksSummary;
108
- if (summary && summary.total > 0) {
109
- facts.push({ text: `checks ${summary.completed}/${summary.total}`, color: "yellow" });
110
- }
111
- else {
112
- facts.push({ text: "checks running", color: "yellow" });
113
- }
96
+ const checksFact = prChecksFact(issue);
97
+ if (checksFact) {
98
+ facts.push(checksFact);
114
99
  }
115
100
  // Blocker
116
101
  if (issue.blockedByCount > 0) {
@@ -120,9 +105,7 @@ function buildFacts(issue, selected) {
120
105
  }
121
106
  // ─── What's blocking progress ───────────────────────────────────
122
107
  function blockerText(issue) {
123
- const rereviewNeeded = issue.prReviewState === "changes_requested"
124
- && (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
125
- && !issue.activeRunType;
108
+ const rereviewNeeded = isRereviewNeeded(issue);
126
109
  if (issue.sessionState === "waiting_input")
127
110
  return issue.waitingReason ?? "Waiting for input";
128
111
  if (needsOperatorIntervention(issue))
@@ -137,9 +120,12 @@ function blockerText(issue) {
137
120
  const check = issue.latestFailureCheckName ?? "CI";
138
121
  return `Repairing ${check}`;
139
122
  }
140
- if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
141
- const check = issue.latestFailureCheckName ?? "checks";
142
- return `${check} failed`;
123
+ const checksFact = prChecksFact(issue);
124
+ if (checksFact?.color === "red") {
125
+ return checksFact.text;
126
+ }
127
+ if (checksFact?.color === "yellow" && checksFact.text.startsWith("checks ")) {
128
+ return `${checksFact.text} still running`;
143
129
  }
144
130
  if (rereviewNeeded)
145
131
  return "Awaiting re-review after requested changes";
@@ -3,6 +3,7 @@ import { buildTimelineRows } from "./timeline-presentation.js";
3
3
  import { planStepColor, planStepSymbol } from "./plan-helpers.js";
4
4
  import { progressBar } from "./format-utils.js";
5
5
  import { describePatchRelayFreshness } from "./freshness.js";
6
+ import { hasDisplayPrBlocker, isRereviewNeeded, prChecksFact } from "./pr-status.js";
6
7
  import { renderRichTextLines, renderTextLines } from "./render-rich-text.js";
7
8
  const SESSION_DISPLAY = {
8
9
  idle: { label: "idle", color: "blueBright" },
@@ -117,7 +118,7 @@ function buildHeaderLines(input, width) {
117
118
  }));
118
119
  }
119
120
  if (issue.statusNote && issue.statusNote !== blocker) {
120
- lines.push(...renderTextLines(issue.statusNote, {
121
+ lines.push(...renderRichTextLines(issue.statusNote, {
121
122
  key: "detail-note",
122
123
  width,
123
124
  style: { dimColor: true },
@@ -396,9 +397,7 @@ function renderSideTripLines(trip, runOffset, width) {
396
397
  }
397
398
  function buildFacts(issue, issueContext) {
398
399
  const facts = [];
399
- const rereviewNeeded = issue.prReviewState === "changes_requested"
400
- && (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
401
- && !issue.activeRunType;
400
+ const rereviewNeeded = isRereviewNeeded(issue);
402
401
  if (issue.prNumber !== undefined)
403
402
  facts.push(`PR #${issue.prNumber}`);
404
403
  if (issue.prReviewState === "approved")
@@ -409,14 +408,12 @@ function buildFacts(issue, issueContext) {
409
408
  facts.push("changes requested");
410
409
  if (issue.waitingReason && issue.sessionState === "waiting_input")
411
410
  facts.push(issue.waitingReason);
412
- if (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
413
- facts.push("checks passed");
414
- else if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
415
- const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "checks";
416
- facts.push(`${check} failed`);
417
- }
418
- else if (issue.prChecksSummary?.total) {
419
- facts.push(`checks ${issue.prChecksSummary.completed}/${issue.prChecksSummary.total}`);
411
+ const checks = prChecksFact({
412
+ ...issue,
413
+ latestFailureCheckName: issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName,
414
+ });
415
+ if (checks) {
416
+ facts.push(checks.text);
420
417
  }
421
418
  return facts;
422
419
  }
@@ -503,16 +500,14 @@ function effectiveState(issue) {
503
500
  return "failed";
504
501
  if (issue.blockedByCount > 0 && !issue.activeRunType)
505
502
  return "blocked";
506
- if (issue.readyForExecution && !issue.activeRunType)
507
- return "ready";
508
503
  if (issue.sessionState === "waiting_input")
509
504
  return "awaiting_input";
505
+ if (issue.readyForExecution && !issue.activeRunType && !hasDisplayPrBlocker(issue))
506
+ return "ready";
510
507
  return issue.factoryState;
511
508
  }
512
509
  function blockerText(issue, issueContext) {
513
- const rereviewNeeded = issue.prReviewState === "changes_requested"
514
- && (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
515
- && !issue.activeRunType;
510
+ const rereviewNeeded = isRereviewNeeded(issue);
516
511
  if (issue.sessionState === "waiting_input")
517
512
  return issue.waitingReason ?? "Waiting for input";
518
513
  if (issue.sessionState === "failed" || issue.factoryState === "failed" || issue.factoryState === "escalated") {
@@ -528,9 +523,15 @@ function blockerText(issue, issueContext) {
528
523
  const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "CI";
529
524
  return `Repairing ${check}`;
530
525
  }
531
- if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
532
- const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "checks";
533
- return `${check} failed`;
526
+ const checks = prChecksFact({
527
+ ...issue,
528
+ latestFailureCheckName: issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName,
529
+ });
530
+ if (checks?.color === "red") {
531
+ return checks.text;
532
+ }
533
+ if (checks?.color === "yellow" && checks.text.startsWith("checks ")) {
534
+ return `${checks.text} still running`;
534
535
  }
535
536
  if (rereviewNeeded)
536
537
  return "Awaiting re-review after requested changes";
@@ -0,0 +1,74 @@
1
+ function isPassingCheckStatus(status) {
2
+ return status === "passed" || status === "success";
3
+ }
4
+ function isFailingCheckStatus(status) {
5
+ return status === "failed" || status === "failure";
6
+ }
7
+ function isPendingCheckStatus(status) {
8
+ return status === "pending" || status === "in_progress";
9
+ }
10
+ export function hasPendingPrChecks(issue) {
11
+ const summary = issue.prChecksSummary;
12
+ if (summary?.total) {
13
+ return summary.pending > 0 || summary.completed < summary.total;
14
+ }
15
+ return isPendingCheckStatus(issue.prCheckStatus);
16
+ }
17
+ export function hasFailedPrChecks(issue) {
18
+ const summary = issue.prChecksSummary;
19
+ if (summary?.total) {
20
+ return summary.failed > 0 || summary.overall === "failure";
21
+ }
22
+ return isFailingCheckStatus(issue.prCheckStatus);
23
+ }
24
+ export function arePrChecksCompleteAndGreen(issue) {
25
+ const summary = issue.prChecksSummary;
26
+ if (summary?.total) {
27
+ return summary.pending === 0 && summary.failed === 0;
28
+ }
29
+ return isPassingCheckStatus(issue.prCheckStatus);
30
+ }
31
+ export function isRereviewNeeded(issue) {
32
+ return issue.prReviewState === "changes_requested"
33
+ && arePrChecksCompleteAndGreen(issue)
34
+ && !issue.activeRunType;
35
+ }
36
+ export function prChecksFact(issue) {
37
+ const summary = issue.prChecksSummary;
38
+ if (hasFailedPrChecks(issue)) {
39
+ const failedNames = summary?.failedNames ?? [];
40
+ const checkInfo = issue.latestFailureCheckName
41
+ ?? (failedNames.length > 0 ? failedNames.slice(0, 2).join(", ") : "checks");
42
+ return { text: `${checkInfo} failed`, color: "red" };
43
+ }
44
+ if (summary?.total) {
45
+ if (summary.pending > 0 || summary.completed < summary.total) {
46
+ return { text: `checks ${summary.completed}/${summary.total}`, color: "yellow" };
47
+ }
48
+ if (summary.failed === 0) {
49
+ return { text: "checks passed", color: "green" };
50
+ }
51
+ }
52
+ if (isPassingCheckStatus(issue.prCheckStatus)) {
53
+ return { text: "checks passed", color: "green" };
54
+ }
55
+ if (isPendingCheckStatus(issue.prCheckStatus)) {
56
+ return { text: "checks running", color: "yellow" };
57
+ }
58
+ return undefined;
59
+ }
60
+ export function hasDisplayPrBlocker(issue) {
61
+ if (issue.prNumber === undefined || issue.activeRunType) {
62
+ return false;
63
+ }
64
+ if (hasPendingPrChecks(issue) || hasFailedPrChecks(issue)) {
65
+ return true;
66
+ }
67
+ if (issue.prReviewState === "changes_requested" && !isRereviewNeeded(issue)) {
68
+ return true;
69
+ }
70
+ if (!issue.prReviewState && issue.factoryState === "pr_open") {
71
+ return true;
72
+ }
73
+ return false;
74
+ }
@@ -114,7 +114,6 @@ function parseInlineMarkdown(text, style) {
114
114
  }
115
115
  if (match[1] && match[2]) {
116
116
  segments.push({ text: match[1], color: "cyan", bold: true });
117
- segments.push({ text: ` (${match[2]})`, dimColor: true });
118
117
  }
119
118
  else if (match[3]) {
120
119
  segments.push({ text: match[3], color: "yellow", bold: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.35.13",
3
+ "version": "0.35.14",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {