merge-steward 0.22.5 → 0.24.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/config.d.ts CHANGED
@@ -30,6 +30,9 @@ export declare const stewardConfigSchema: z.ZodObject<{
30
30
  admissionLabel: z.ZodDefault<z.ZodString>;
31
31
  priorityQueueLabel: z.ZodDefault<z.ZodString>;
32
32
  mergeQueueCheckName: z.ZodDefault<z.ZodString>;
33
+ evictionCheckName: z.ZodDefault<z.ZodString>;
34
+ specReadyCheckName: z.ZodDefault<z.ZodString>;
35
+ specBranchPrefix: z.ZodDefault<z.ZodString>;
33
36
  excludeBranches: z.ZodDefault<z.ZodArray<z.ZodString>>;
34
37
  autoResolvePatterns: z.ZodDefault<z.ZodArray<z.ZodObject<{
35
38
  glob: z.ZodString;
package/dist/config.js CHANGED
@@ -31,6 +31,21 @@ export const stewardConfigSchema = z.object({
31
31
  admissionLabel: z.string().default("queue"),
32
32
  priorityQueueLabel: z.string().default("queue:priority"),
33
33
  mergeQueueCheckName: z.string().default(DEFAULT_MERGE_QUEUE_CHECK_NAME),
34
+ // Plan §2.4 — bus-contract artifact names exposed for cross-service
35
+ // alignment. Defaults preserve current behavior. The steward writes
36
+ // `evictionCheckName` (today: same as mergeQueueCheckName, kept for
37
+ // contract clarity) and `specReadyCheckName`; consumers read them.
38
+ /** Eviction check run name. Synonym for mergeQueueCheckName, surfaced
39
+ * under the bus-contract name so consumer code can be naming-aligned
40
+ * with patchrelay's resolver. */
41
+ evictionCheckName: z.string().default(DEFAULT_MERGE_QUEUE_CHECK_NAME),
42
+ /** Spec-ready check run name (default: "merge-steward/spec-ready").
43
+ * Read by review-quill in integration_tree mode (plan §3.5). */
44
+ specReadyCheckName: z.string().default("merge-steward/spec-ready"),
45
+ /** Prefix for spec branch names (default: "mq-spec-"); matches the
46
+ * existing SPEC_BRANCH_PREFIX in reconciler-core.ts. The pattern
47
+ * review-quill matches against is `${prefix}*`. */
48
+ specBranchPrefix: z.string().default("mq-spec-"),
34
49
  /** Branch name patterns to exclude from admission (glob-style). */
35
50
  excludeBranches: z.array(z.string()).default([]),
36
51
  /**
@@ -12,6 +12,8 @@ export declare class GitHubCheckRunReporter implements EvictionReporter {
12
12
  private readonly publicBaseUrl?;
13
13
  private readonly admissionLabel;
14
14
  private readonly mergeQueueCheckName;
15
- constructor(repoFullName: string, serverHost: string, serverPort: number, publicBaseUrl?: string | undefined, admissionLabel?: string, mergeQueueCheckName?: string);
15
+ private readonly specReadyCheckName;
16
+ constructor(repoFullName: string, serverHost: string, serverPort: number, publicBaseUrl?: string | undefined, admissionLabel?: string, mergeQueueCheckName?: string, specReadyCheckName?: string);
16
17
  reportEviction(entry: QueueEntry, incident: IncidentRecord): Promise<void>;
18
+ reportSpecReady(entry: QueueEntry, specBranch: string, specSha: string): Promise<void>;
17
19
  }
@@ -4,6 +4,9 @@ import { tmpdir } from "node:os";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { exec } from "../exec.js";
6
6
  import { DEFAULT_MERGE_QUEUE_CHECK_NAME } from "../config.js";
7
+ // Plan §5.2 default — review-quill subscribes by default; configurable
8
+ // per project via the bus contract added in PR 1.
9
+ const DEFAULT_SPEC_READY_CHECK_NAME = "merge-steward/spec-ready";
7
10
  /**
8
11
  * Reports evictions by creating a GitHub check run on the PR's head SHA.
9
12
  * The check run output contains the incident context as structured JSON.
@@ -16,13 +19,15 @@ export class GitHubCheckRunReporter {
16
19
  publicBaseUrl;
17
20
  admissionLabel;
18
21
  mergeQueueCheckName;
19
- constructor(repoFullName, serverHost, serverPort, publicBaseUrl, admissionLabel = "queue", mergeQueueCheckName = DEFAULT_MERGE_QUEUE_CHECK_NAME) {
22
+ specReadyCheckName;
23
+ constructor(repoFullName, serverHost, serverPort, publicBaseUrl, admissionLabel = "queue", mergeQueueCheckName = DEFAULT_MERGE_QUEUE_CHECK_NAME, specReadyCheckName = DEFAULT_SPEC_READY_CHECK_NAME) {
20
24
  this.repoFullName = repoFullName;
21
25
  this.serverHost = serverHost;
22
26
  this.serverPort = serverPort;
23
27
  this.publicBaseUrl = publicBaseUrl;
24
28
  this.admissionLabel = admissionLabel;
25
29
  this.mergeQueueCheckName = mergeQueueCheckName;
30
+ this.specReadyCheckName = specReadyCheckName;
26
31
  }
27
32
  async reportEviction(entry, incident) {
28
33
  const title = formatTitle(incident);
@@ -70,6 +75,52 @@ export class GitHubCheckRunReporter {
70
75
  "--remove-label", this.admissionLabel,
71
76
  ], { timeoutMs: 15_000, githubRepoFullName: this.repoFullName }).catch(() => { });
72
77
  }
78
+ // Plan §5.2: a "spec is ready" event on the PR head — not a verdict.
79
+ // Conclusion is `neutral` so branch-protection rules treat it as a
80
+ // signal rather than a gate. Best-effort: failures are logged in the
81
+ // exec layer and not propagated.
82
+ async reportSpecReady(entry, specBranch, specSha) {
83
+ const targetUrl = `https://github.com/${this.repoFullName}/commit/${specSha}`;
84
+ const summary = [
85
+ `Spec branch \`${specBranch}\` is at \`${specSha.slice(0, 12)}\`.`,
86
+ ``,
87
+ `This is the integration tree the lander will run CI on. Reviewers`,
88
+ `subscribed to integration_tree mode use this commit as the head`,
89
+ `they review.`,
90
+ ].join("\n");
91
+ const body = JSON.stringify({
92
+ name: this.specReadyCheckName,
93
+ head_sha: entry.headSha,
94
+ status: "completed",
95
+ conclusion: "neutral",
96
+ details_url: targetUrl,
97
+ output: {
98
+ title: `Spec ready (${specBranch})`,
99
+ summary,
100
+ text: JSON.stringify({ specBranch, specSha, prNumber: entry.prNumber, queueEntryId: entry.id }),
101
+ },
102
+ });
103
+ const tmpPath = join(tmpdir(), `steward-specready-${randomUUID()}.json`);
104
+ try {
105
+ writeFileSync(tmpPath, body);
106
+ await exec("gh", [
107
+ "api",
108
+ `repos/${this.repoFullName}/check-runs`,
109
+ "--method", "POST",
110
+ "--input", tmpPath,
111
+ ], { timeoutMs: 30_000, githubRepoFullName: this.repoFullName });
112
+ }
113
+ catch {
114
+ // Best-effort — review-quill can also fall back to polling the
115
+ // spec branch directly if it never sees this check_run.
116
+ }
117
+ finally {
118
+ try {
119
+ unlinkSync(tmpPath);
120
+ }
121
+ catch { }
122
+ }
123
+ }
73
124
  }
74
125
  function buildIncidentProjection(incident, detailsUrl) {
75
126
  return {
@@ -64,4 +64,13 @@ export interface GitHubPRApi {
64
64
  */
65
65
  export interface EvictionReporter {
66
66
  reportEviction(entry: QueueEntry, incident: IncidentRecord): Promise<void>;
67
+ /**
68
+ * Plan §5.2: emit a "spec ready" check_run on the PR's head SHA when
69
+ * a fresh spec branch has been pushed. Lets review-quill (and other
70
+ * consumers) subscribe to integration-tree availability via the
71
+ * GitHub bus instead of polling. Optional — implementations that
72
+ * cannot or should not write this check (sim, in-memory, dry-run)
73
+ * may omit or no-op.
74
+ */
75
+ reportSpecReady?(entry: QueueEntry, specBranch: string, specSha: string): Promise<void>;
67
76
  }
@@ -161,6 +161,13 @@ export async function prepareEntry(ctx, entry, isHead, prevEntry) {
161
161
  const specSha = result.sha ?? entry.headSha;
162
162
  emit(ctx, entry, "spec_build_succeeded", { specBranch: specName, ...(prevEntry ? { dependsOn: prevEntry.id } : {}) });
163
163
  await ctx.git.push(specName, true);
164
+ // Plan §5.2: emit `merge-steward/spec-ready` on the PR head so
165
+ // review-quill (and any other consumer) can react to spec
166
+ // availability via the GitHub bus instead of polling. Best-effort —
167
+ // failures are caught inside the reporter; CI still triggers.
168
+ if (ctx.eviction.reportSpecReady) {
169
+ await ctx.eviction.reportSpecReady(entry, specName, specSha).catch(() => undefined);
170
+ }
164
171
  const runId = await ctx.ci.triggerRun(specName, specSha);
165
172
  emit(ctx, entry, "ci_triggered", { ciRunId: runId, specBranch: specName });
166
173
  ctx.store.transition(entry.id, "validating", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "merge-steward",
3
- "version": "0.22.5",
3
+ "version": "0.24.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": {