merge-steward 0.17.0 → 0.18.0

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/db/schema.js CHANGED
@@ -75,6 +75,7 @@ export function ensureSchema(connection) {
75
75
  ensureColumn(connection, "queue_entries", "post_merge_summary", "TEXT");
76
76
  ensureColumn(connection, "queue_entries", "post_merge_checked_at", "TEXT");
77
77
  ensureColumn(connection, "queue_entries", "wait_detail", "TEXT");
78
+ ensureColumn(connection, "queue_entries", "pr_title", "TEXT");
78
79
  connection.exec(`
79
80
  UPDATE queue_entries
80
81
  SET post_merge_status = 'pending'
@@ -29,6 +29,7 @@ function mapEntry(row) {
29
29
  postMergeSha: row.post_merge_sha === null || row.post_merge_sha === undefined ? null : String(row.post_merge_sha),
30
30
  postMergeSummary: row.post_merge_summary === null || row.post_merge_summary === undefined ? null : String(row.post_merge_summary),
31
31
  postMergeCheckedAt: row.post_merge_checked_at === null || row.post_merge_checked_at === undefined ? null : String(row.post_merge_checked_at),
32
+ prTitle: row.pr_title === null || row.pr_title === undefined ? null : String(row.pr_title),
32
33
  enqueuedAt: String(row.enqueued_at),
33
34
  updatedAt: String(row.updated_at),
34
35
  };
@@ -125,8 +126,9 @@ export class SqliteStore {
125
126
  priority, generation, ci_run_id, ci_retries, retry_attempts,
126
127
  max_retries, last_failed_base_sha, issue_key, wait_detail,
127
128
  post_merge_status, post_merge_sha, post_merge_summary, post_merge_checked_at,
129
+ pr_title,
128
130
  enqueued_at, updated_at)
129
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(entry.id, entry.repoId, entry.prNumber, entry.branch, entry.headSha, entry.baseSha, entry.status, entry.position, entry.priority, entry.generation, entry.ciRunId, entry.ciRetries, entry.retryAttempts, entry.maxRetries, entry.lastFailedBaseSha, entry.issueKey ?? null, entry.waitDetail ?? null, entry.postMergeStatus ?? null, entry.postMergeSha ?? null, entry.postMergeSummary ?? null, entry.postMergeCheckedAt ?? null, entry.enqueuedAt, entry.updatedAt);
131
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(entry.id, entry.repoId, entry.prNumber, entry.branch, entry.headSha, entry.baseSha, entry.status, entry.position, entry.priority, entry.generation, entry.ciRunId, entry.ciRetries, entry.retryAttempts, entry.maxRetries, entry.lastFailedBaseSha, entry.issueKey ?? null, entry.waitDetail ?? null, entry.postMergeStatus ?? null, entry.postMergeSha ?? null, entry.postMergeSummary ?? null, entry.postMergeCheckedAt ?? null, entry.prTitle ?? null, entry.enqueuedAt, entry.updatedAt);
130
132
  this.writeEvent(entry.id, null, entry.status);
131
133
  })();
132
134
  }
@@ -55,7 +55,14 @@ export class GitHubActionsRunner {
55
55
  // (e.g. deploy-stage on main), even though listChecksForRef treats
56
56
  // those same checks as success, producing a "main_broken" block with
57
57
  // an empty failing-check list and stalling the queue.
58
- const acceptSkipped = !hasRequired && !requireAllChecks;
58
+ // Accept "skipped" whenever there are no named required checks,
59
+ // including strict-protection-with-empty-contexts mode. A skipped
60
+ // job is an `if:` or `on:` gate by the workflow author declaring
61
+ // the job does not apply here; it is not a failure. The bypass
62
+ // concern (a gate job reporting success while a required job was
63
+ // skipped) only matters when we have a named required-check set
64
+ // to gate against, i.e. `hasRequired === true`.
65
+ const acceptSkipped = !hasRequired;
59
66
  if (relevant.some((c) => {
60
67
  if (c.conclusion === "success" || c.conclusion === "neutral")
61
68
  return false;
@@ -24,13 +24,14 @@ export class GitHubPRClient {
24
24
  const result = await exec("gh", [
25
25
  "pr", "view", String(prNumber),
26
26
  "--repo", this.repoFullName,
27
- "--json", "number,headRefName,headRefOid,reviewDecision,state,mergeStateStatus",
27
+ "--json", "number,title,headRefName,headRefOid,reviewDecision,state,mergeStateStatus",
28
28
  ], { githubRepoFullName: this.repoFullName });
29
29
  const data = JSON.parse(result.stdout);
30
30
  return {
31
31
  number: data.number,
32
32
  branch: data.headRefName,
33
33
  headSha: data.headRefOid,
34
+ ...(data.title ? { title: data.title } : {}),
34
35
  mergeable: data.state === "OPEN",
35
36
  mergeStateStatus: data.mergeStateStatus,
36
37
  reviewDecision: data.reviewDecision,
@@ -18,6 +18,7 @@ export declare class MergeStewardQueueCommands {
18
18
  headSha: string;
19
19
  issueKey?: string;
20
20
  priority?: number;
21
+ prTitle?: string;
21
22
  }): QueueEntry | undefined;
22
23
  scanStartupAdmissions(): Promise<void>;
23
24
  scanEligibleOpenPrs(): Promise<{
@@ -63,6 +63,7 @@ export class MergeStewardQueueCommands {
63
63
  postMergeSha: null,
64
64
  postMergeSummary: null,
65
65
  postMergeCheckedAt: null,
66
+ prTitle: params.prTitle ?? null,
66
67
  enqueuedAt: new Date().toISOString(),
67
68
  updatedAt: new Date().toISOString(),
68
69
  };
@@ -161,7 +162,7 @@ export class MergeStewardQueueCommands {
161
162
  return false;
162
163
  }
163
164
  }
164
- this.enqueue({ prNumber, branch, headSha });
165
+ this.enqueue({ prNumber, branch, headSha, ...(status.title ? { prTitle: status.title } : {}) });
165
166
  return true;
166
167
  }
167
168
  catch (error) {
package/dist/types.d.ts CHANGED
@@ -42,6 +42,8 @@ export interface QueueEntry {
42
42
  waitDetail?: string | null;
43
43
  /** Post-merge verification status for the landed commit on main. */
44
44
  postMergeStatus: PostMergeStatus | null;
45
+ /** PR title captured at admission. Falls back to branch name in the UI. */
46
+ prTitle?: string | null;
45
47
  /** Commit SHA used when checking post-merge status. */
46
48
  postMergeSha: string | null;
47
49
  /** Optional one-line summary of post-merge checks. */
@@ -180,6 +182,7 @@ export interface PRStatus {
180
182
  number: number;
181
183
  branch: string;
182
184
  headSha: string;
185
+ title?: string | undefined;
183
186
  mergeable: boolean;
184
187
  mergeStateStatus?: string | undefined;
185
188
  reviewDecision?: string | undefined;
@@ -3,13 +3,26 @@ import { Box, Text, useStdout } from "ink";
3
3
  import { RepoRow } from "./OverviewView.js";
4
4
  import { clipSummary } from "./dashboard-model.js";
5
5
  const PR_ID_WIDTH = 7;
6
+ const PR_PHRASE_WIDTH = 20;
6
7
  const SUMMARY_INDENT = 9;
7
8
  function PrEntryRow({ entry, width, includeSummary, }) {
8
9
  const idText = `#${entry.prNumber}`.padEnd(PR_ID_WIDTH, " ");
10
+ const paddedPhrase = entry.phrase.padEnd(PR_PHRASE_WIDTH, " ");
9
11
  const summaryText = includeSummary
10
12
  ? clipSummary(entry.summary, { maxLines: 3, width: Math.max(20, width - SUMMARY_INDENT) })
11
13
  : "";
12
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: entry.color, children: idText }), _jsx(Text, { color: entry.color, children: entry.glyph }), _jsx(Text, { children: ` ${entry.phrase}` })] }), summaryText ? (_jsxs(Box, { children: [_jsx(Text, { children: " ".repeat(SUMMARY_INDENT) }), _jsx(Text, { dimColor: true, children: summaryText })] })) : null] }));
14
+ const titleSpace = Math.max(0, width - (PR_ID_WIDTH + 3 + PR_PHRASE_WIDTH + 1));
15
+ const title = entry.title && entry.title !== entry.phrase && titleSpace >= 8
16
+ ? truncate(entry.title, titleSpace)
17
+ : "";
18
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: entry.color, children: idText }), _jsx(Text, { color: entry.color, children: entry.glyph }), _jsx(Text, { children: ` ${paddedPhrase}` }), title ? _jsx(Text, { dimColor: true, children: ` ${title}` }) : null] }), summaryText ? (_jsxs(Box, { children: [_jsx(Text, { children: " ".repeat(SUMMARY_INDENT) }), _jsx(Text, { dimColor: true, children: summaryText })] })) : null] }));
19
+ }
20
+ function truncate(value, maxWidth) {
21
+ if (value.length <= maxWidth)
22
+ return value;
23
+ if (maxWidth <= 1)
24
+ return value.slice(0, maxWidth);
25
+ return `${value.slice(0, Math.max(0, maxWidth - 1))}\u2026`;
13
26
  }
14
27
  export function ProjectDetailView({ model, selectedRepoId }) {
15
28
  const { stdout } = useStdout();
@@ -23,6 +23,7 @@ export interface DashboardToken {
23
23
  export interface DashboardPrEntry extends DashboardToken {
24
24
  phrase: string;
25
25
  summary?: string;
26
+ title?: string;
26
27
  eventAt: number;
27
28
  }
28
29
  export interface DashboardRepo {
@@ -151,6 +151,9 @@ function repoEntriesFromSnapshot(snapshot, cutoff) {
151
151
  const summary = entrySummary(entry);
152
152
  if (summary)
153
153
  item.summary = summary;
154
+ const title = entry.prTitle?.trim() || entry.branch;
155
+ if (title)
156
+ item.title = title;
154
157
  byPr.set(entry.prNumber, item);
155
158
  }
156
159
  return [...byPr.values()].sort((left, right) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "merge-steward",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "Serial merge queue for GitHub — rebase, CI-gate, and merge PRs one at a time",
5
5
  "type": "module",
6
6
  "repository": {