patchrelay 0.35.13 → 0.35.15

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.15",
4
+ "commit": "da544a1edbf6",
5
+ "builtAt": "2026-04-08T09:10:23.366Z"
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,10 +118,9 @@ 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
- style: { dimColor: true },
124
124
  }));
125
125
  }
126
126
  if (input.issueContext?.latestFailureSummary) {
@@ -396,9 +396,7 @@ function renderSideTripLines(trip, runOffset, width) {
396
396
  }
397
397
  function buildFacts(issue, issueContext) {
398
398
  const facts = [];
399
- const rereviewNeeded = issue.prReviewState === "changes_requested"
400
- && (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
401
- && !issue.activeRunType;
399
+ const rereviewNeeded = isRereviewNeeded(issue);
402
400
  if (issue.prNumber !== undefined)
403
401
  facts.push(`PR #${issue.prNumber}`);
404
402
  if (issue.prReviewState === "approved")
@@ -409,14 +407,12 @@ function buildFacts(issue, issueContext) {
409
407
  facts.push("changes requested");
410
408
  if (issue.waitingReason && issue.sessionState === "waiting_input")
411
409
  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}`);
410
+ const checks = prChecksFact({
411
+ ...issue,
412
+ latestFailureCheckName: issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName,
413
+ });
414
+ if (checks) {
415
+ facts.push(checks.text);
420
416
  }
421
417
  return facts;
422
418
  }
@@ -503,16 +499,14 @@ function effectiveState(issue) {
503
499
  return "failed";
504
500
  if (issue.blockedByCount > 0 && !issue.activeRunType)
505
501
  return "blocked";
506
- if (issue.readyForExecution && !issue.activeRunType)
507
- return "ready";
508
502
  if (issue.sessionState === "waiting_input")
509
503
  return "awaiting_input";
504
+ if (issue.readyForExecution && !issue.activeRunType && !hasDisplayPrBlocker(issue))
505
+ return "ready";
510
506
  return issue.factoryState;
511
507
  }
512
508
  function blockerText(issue, issueContext) {
513
- const rereviewNeeded = issue.prReviewState === "changes_requested"
514
- && (issue.prCheckStatus === "passed" || issue.prCheckStatus === "success")
515
- && !issue.activeRunType;
509
+ const rereviewNeeded = isRereviewNeeded(issue);
516
510
  if (issue.sessionState === "waiting_input")
517
511
  return issue.waitingReason ?? "Waiting for input";
518
512
  if (issue.sessionState === "failed" || issue.factoryState === "failed" || issue.factoryState === "escalated") {
@@ -528,9 +522,15 @@ function blockerText(issue, issueContext) {
528
522
  const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "CI";
529
523
  return `Repairing ${check}`;
530
524
  }
531
- if (issue.prCheckStatus === "failed" || issue.prCheckStatus === "failure") {
532
- const check = issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName ?? "checks";
533
- return `${check} failed`;
525
+ const checks = prChecksFact({
526
+ ...issue,
527
+ latestFailureCheckName: issueContext?.latestFailureCheckName ?? issue.latestFailureCheckName,
528
+ });
529
+ if (checks?.color === "red") {
530
+ return checks.text;
531
+ }
532
+ if (checks?.color === "yellow" && checks.text.startsWith("checks ")) {
533
+ return `${checks.text} still running`;
534
534
  }
535
535
  if (rereviewNeeded)
536
536
  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
+ }
@@ -113,11 +113,10 @@ function parseInlineMarkdown(text, style) {
113
113
  segments.push({ text: text.slice(lastIndex, index), ...(style ?? {}) });
114
114
  }
115
115
  if (match[1] && match[2]) {
116
- segments.push({ text: match[1], color: "cyan", bold: true });
117
- segments.push({ text: ` (${match[2]})`, dimColor: true });
116
+ segments.push({ text: match[1], color: "cyan" });
118
117
  }
119
118
  else if (match[3]) {
120
- segments.push({ text: match[3], color: "yellow", bold: true });
119
+ segments.push({ text: match[3], color: "yellow" });
121
120
  }
122
121
  else if (match[4]) {
123
122
  segments.push({ text: match[4], ...(style ?? {}), 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.15",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {