pr-shepherd 0.42.0 → 0.43.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.
Files changed (82) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +8 -3
  3. package/bin/checks/classify.d.mts +3 -1
  4. package/bin/checks/classify.mjs +5 -2
  5. package/bin/cli/args.mjs +1 -0
  6. package/bin/cli/default-poll.mjs +1 -0
  7. package/bin/cli/fix-formatter.mjs +12 -2
  8. package/bin/cli/handlers.mjs +1 -0
  9. package/bin/cli/help-command-pages.d.mts +2 -2
  10. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  11. package/bin/cli/help-iterate-poll-pages.mjs +7 -2
  12. package/bin/cli/help.d.mts +2 -2
  13. package/bin/cli/iterate-flags.d.mts +1 -0
  14. package/bin/cli/iterate-flags.mjs +2 -0
  15. package/bin/cli/iterate-formatter.mjs +4 -1
  16. package/bin/cli/iterate-instructions.mjs +15 -0
  17. package/bin/cli/iterate-lean.d.mts +1 -2
  18. package/bin/cli/iterate-lean.mjs +12 -17
  19. package/bin/cli/iterate-merge-formatter.d.mts +3 -0
  20. package/bin/cli/iterate-merge-formatter.mjs +52 -0
  21. package/bin/cli/iterate-verbose.d.mts +6 -0
  22. package/bin/cli/iterate-verbose.mjs +14 -0
  23. package/bin/cli/poll-handler.mjs +1 -0
  24. package/bin/cli/runner.mjs +4 -3
  25. package/bin/commands/check-terminal-report.mjs +1 -0
  26. package/bin/commands/check.mjs +42 -1
  27. package/bin/commands/iterate/base.d.mts +5 -0
  28. package/bin/commands/iterate/base.mjs +25 -0
  29. package/bin/commands/iterate/escalate.d.mts +3 -1
  30. package/bin/commands/iterate/escalate.mjs +18 -2
  31. package/bin/commands/iterate/fix-code.mjs +16 -0
  32. package/bin/commands/iterate/helpers.mjs +6 -2
  33. package/bin/commands/iterate/index.mjs +29 -35
  34. package/bin/commands/iterate/merge-state.d.mts +15 -0
  35. package/bin/commands/iterate/merge-state.mjs +53 -0
  36. package/bin/commands/iterate/merge.d.mts +13 -0
  37. package/bin/commands/iterate/merge.mjs +46 -0
  38. package/bin/commands/poll.mjs +3 -1
  39. package/bin/config/load.d.mts +4 -0
  40. package/bin/config/load.mjs +34 -0
  41. package/bin/config/merge-command-args.d.mts +2 -0
  42. package/bin/config/merge-command-args.mjs +44 -0
  43. package/bin/config.json +3 -0
  44. package/bin/exit-codes.d.mts +2 -0
  45. package/bin/exit-codes.mjs +4 -0
  46. package/bin/github/batch-parse-checks.d.mts +3 -0
  47. package/bin/github/batch-parse-checks.mjs +29 -0
  48. package/bin/github/batch-parsers-rules.d.mts +3 -1
  49. package/bin/github/batch-parsers-rules.mjs +33 -0
  50. package/bin/github/batch-parsers.mjs +23 -26
  51. package/bin/github/batch-raw-rules.d.mts +48 -0
  52. package/bin/github/batch-raw-types.mjs +0 -1
  53. package/bin/github/batch.mjs +2 -0
  54. package/bin/github/gql/batch-pr.gql +92 -0
  55. package/bin/github/gql/commit-check-contexts.gql +58 -0
  56. package/bin/github/merge-queue-checks.d.mts +4 -0
  57. package/bin/github/merge-queue-checks.mjs +48 -0
  58. package/bin/github/queries.d.mts +2 -0
  59. package/bin/github/queries.mjs +2 -0
  60. package/bin/mcp/server.mjs +1 -0
  61. package/bin/reporters/agent.mjs +6 -3
  62. package/bin/types/activity.d.mts +2 -0
  63. package/bin/types/escalate.d.mts +30 -0
  64. package/bin/types/escalate.mjs +1 -0
  65. package/bin/types/github.d.mts +8 -0
  66. package/bin/types/github.mjs +0 -3
  67. package/bin/types/iterate.d.mts +14 -31
  68. package/bin/types/merge-action.d.mts +12 -0
  69. package/bin/types/merge-action.mjs +1 -0
  70. package/bin/types/merge-queue.d.mts +13 -0
  71. package/bin/types/merge-queue.mjs +1 -0
  72. package/bin/types/merge-requirements.d.mts +17 -0
  73. package/bin/types/report.d.mts +8 -0
  74. package/bin/types.d.mts +3 -0
  75. package/bin/types.mjs +3 -0
  76. package/bin/util/markdown.d.mts +2 -0
  77. package/bin/util/markdown.mjs +7 -0
  78. package/package.json +1 -1
  79. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  80. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  81. package/plugins/pr-shepherd/.mcp.json +1 -1
  82. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +3 -3
@@ -0,0 +1,29 @@
1
+ import { mapCheckRunNode, mapStatusContextState, parseCreatedAt } from "./batch-parser-helpers.mjs";
2
+ export function parseCheckNodes(nodes, mergeCommitOid) {
3
+ return (nodes ?? []).flatMap((node) => {
4
+ const scope = mergeCommitOid
5
+ ? { scope: "merge_group", commitOid: mergeCommitOid }
6
+ : {};
7
+ if (node?.__typename === "CheckRun")
8
+ return [{ ...mapCheckRunNode(node), ...scope }];
9
+ if (node?.__typename !== "StatusContext")
10
+ return [];
11
+ const { status, conclusion } = mapStatusContextState(node.state);
12
+ const summary = node.description?.trim() || undefined;
13
+ const createdAtUnix = node.createdAt ? parseCreatedAt(node.createdAt) : undefined;
14
+ return [
15
+ {
16
+ name: node.context,
17
+ status,
18
+ conclusion,
19
+ source: "status_context",
20
+ detailsUrl: node.targetUrl ?? "",
21
+ event: null,
22
+ runId: null,
23
+ ...scope,
24
+ ...(createdAtUnix !== undefined && { createdAtUnix }),
25
+ ...(summary !== undefined && { summary }),
26
+ },
27
+ ];
28
+ });
29
+ }
@@ -1,6 +1,8 @@
1
- import type { BranchRules, MergeQueueEntryStatus, StackStatus } from "../types.mts";
1
+ import type { AutoMergeRequestStatus, BranchRules, MergeQueueEntryStatus, MergeQueueRemovalStatus, StackStatus } from "../types.mts";
2
2
  import type { RawBaseRef, RawPrMergeFields } from "./batch-raw-rules.mts";
3
3
  export declare const EMPTY_BRANCH_RULES: BranchRules;
4
4
  export declare function parseBranchRules(baseRef: RawBaseRef | null | undefined): BranchRules;
5
5
  export declare function parseMergeQueueEntry(raw: RawPrMergeFields["mergeQueueEntry"]): MergeQueueEntryStatus | null;
6
+ export declare function parseAutoMergeRequest(raw: RawPrMergeFields["autoMergeRequest"]): AutoMergeRequestStatus | null;
7
+ export declare function parseLatestMergeQueueRemoval(raw: RawPrMergeFields): MergeQueueRemovalStatus | null;
6
8
  export declare function parseStack(raw: RawPrMergeFields): StackStatus | null;
@@ -32,8 +32,41 @@ export function parseMergeQueueEntry(raw) {
32
32
  position: raw.position,
33
33
  state: raw.state,
34
34
  estimatedTimeToMerge: raw.estimatedTimeToMerge,
35
+ ...(raw.headCommit?.oid && { headCommitOid: raw.headCommit.oid }),
36
+ ...(raw.enqueuedAt && { enqueuedAtUnix: parseUnix(raw.enqueuedAt) }),
37
+ ...(raw.enqueuer?.login && { enqueuer: raw.enqueuer.login }),
35
38
  };
36
39
  }
40
+ export function parseAutoMergeRequest(raw) {
41
+ if (!raw)
42
+ return null;
43
+ return {
44
+ enabledAtUnix: parseUnix(raw.enabledAt),
45
+ mergeMethod: raw.mergeMethod,
46
+ ...(raw.enabledBy?.login && { enabledBy: raw.enabledBy.login }),
47
+ };
48
+ }
49
+ export function parseLatestMergeQueueRemoval(raw) {
50
+ const removal = raw.mergeQueueRemovals?.nodes[0];
51
+ if (!removal)
52
+ return null;
53
+ const lastAddedAt = raw.mergeQueueAdditions?.nodes[0]?.createdAt;
54
+ if (lastAddedAt && Date.parse(lastAddedAt) > Date.parse(removal.createdAt))
55
+ return null;
56
+ return {
57
+ reason: removal.reason,
58
+ createdAtUnix: parseUnix(removal.createdAt),
59
+ ...(removal.actor?.login && { actor: removal.actor.login }),
60
+ ...(removal.beforeCommit?.oid && { beforeCommitOid: removal.beforeCommit.oid }),
61
+ ...(removal.beforeCommit?.parents && {
62
+ beforeCommitParentOids: removal.beforeCommit.parents.nodes.map((parent) => parent.oid),
63
+ }),
64
+ };
65
+ }
66
+ function parseUnix(value) {
67
+ const ms = Date.parse(value);
68
+ return Number.isFinite(ms) ? Math.floor(ms / 1000) : 0;
69
+ }
37
70
  export function parseStack(raw) {
38
71
  if (!raw.stack)
39
72
  return null;
@@ -1,7 +1,9 @@
1
- import { mapAuthorType, parseCreatedAt, mapStatusContextState, latestApprovedLogins, isReviewStale, mapCheckRunNode, } from "./batch-parser-helpers.mjs";
1
+ import { mapAuthorType, parseCreatedAt, latestApprovedLogins, isReviewStale, } from "./batch-parser-helpers.mjs";
2
+ import { parseCheckNodes } from "./batch-parse-checks.mjs";
3
+ import { requireContextNodes } from "./batch-response.mjs";
2
4
  import { buildPrActivitySummary } from "./activity.mjs";
3
5
  import { parseBranchProtection } from "./branch-protection.mjs";
4
- import { parseBranchRules, parseMergeQueueEntry, parseStack } from "./batch-parsers-rules.mjs";
6
+ import { parseAutoMergeRequest, parseBranchRules, parseLatestMergeQueueRemoval, parseMergeQueueEntry, parseStack, } from "./batch-parsers-rules.mjs";
5
7
  function parseReviewNode(r) {
6
8
  const base = {
7
9
  id: r.id,
@@ -86,30 +88,15 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
86
88
  const approvedReviews = rawApprovedReviewNodes
87
89
  .filter((r) => !r.isMinimized)
88
90
  .map((r) => parseReviewNode(r));
89
- const checks = rawCheckNodes.flatMap((node) => {
90
- if (node.__typename === "CheckRun") {
91
- return [mapCheckRunNode(node)];
92
- }
93
- if (node.__typename === "StatusContext") {
94
- const { status, conclusion } = mapStatusContextState(node.state);
95
- const summary = node.description?.trim() || undefined;
96
- const createdAtUnix = node.createdAt ? parseCreatedAt(node.createdAt) : undefined;
97
- return [
98
- {
99
- name: node.context,
100
- status,
101
- conclusion,
102
- source: "status_context",
103
- detailsUrl: node.targetUrl ?? "",
104
- event: null,
105
- runId: null,
106
- ...(createdAtUnix !== undefined && { createdAtUnix }),
107
- ...(summary !== undefined && { summary }),
108
- },
109
- ];
110
- }
111
- return [];
112
- });
91
+ const checks = parseCheckNodes(rawCheckNodes);
92
+ const queueHead = raw.mergeQueueEntry?.headCommit;
93
+ const removedQueueHead = raw.mergeQueueRemovals?.nodes[0]?.beforeCommit;
94
+ const mergeQueueChecks = parseCheckNodes(queueHead?.statusCheckRollup
95
+ ? requireContextNodes(queueHead.statusCheckRollup.contexts.nodes)
96
+ : undefined, queueHead?.oid);
97
+ const removedMergeQueueChecks = parseCheckNodes(removedQueueHead?.statusCheckRollup
98
+ ? requireContextNodes(removedQueueHead.statusCheckRollup.contexts.nodes)
99
+ : undefined, removedQueueHead?.oid);
113
100
  return {
114
101
  nodeId: raw.id,
115
102
  number: raw.number,
@@ -135,6 +122,16 @@ export function parseRawPr(raw, rawThreadPages, rawCommentNodes, rawReviewNodes,
135
122
  isInMergeQueue: raw.isInMergeQueue ?? false,
136
123
  isMergeQueueEnabled: raw.isMergeQueueEnabled ?? false,
137
124
  mergeQueueEntry: parseMergeQueueEntry(raw.mergeQueueEntry),
125
+ autoMergeRequest: parseAutoMergeRequest(raw.autoMergeRequest),
126
+ latestMergeQueueRemoval: parseLatestMergeQueueRemoval(raw),
127
+ ...(mergeQueueChecks.length > 0 && { mergeQueueChecks }),
128
+ ...(queueHead?.statusCheckRollup?.contexts.pageInfo.hasNextPage && {
129
+ mergeQueueChecksIncomplete: true,
130
+ }),
131
+ ...(removedMergeQueueChecks.length > 0 && { removedMergeQueueChecks }),
132
+ ...(removedQueueHead?.statusCheckRollup?.contexts.pageInfo.hasNextPage && {
133
+ removedMergeQueueChecksIncomplete: true,
134
+ }),
138
135
  stack: parseStack(raw),
139
136
  activity: buildPrActivitySummary(raw, comments, reviewThreads, reviewSummaries, allChangesRequestedReviews, approvedReviews),
140
137
  };
@@ -12,10 +12,49 @@ export interface RawBranchProtectionRule {
12
12
  requiresDeployments?: boolean;
13
13
  requiredDeploymentEnvironments?: string[] | null;
14
14
  }
15
+ import type { RawContextNode } from "./batch-raw-types.mts";
16
+ interface RawCheckCommit {
17
+ oid: string;
18
+ committedDate?: string;
19
+ parents?: {
20
+ nodes: Array<{
21
+ oid: string;
22
+ }>;
23
+ };
24
+ statusCheckRollup: {
25
+ contexts: {
26
+ pageInfo: {
27
+ hasNextPage: boolean;
28
+ endCursor: string | null;
29
+ };
30
+ nodes: Array<RawContextNode | null>;
31
+ };
32
+ } | null;
33
+ }
15
34
  interface RawMergeQueueEntry {
16
35
  position: number;
17
36
  state: string;
18
37
  estimatedTimeToMerge: number | null;
38
+ headCommit?: RawCheckCommit | null;
39
+ enqueuedAt?: string;
40
+ enqueuer?: {
41
+ login: string;
42
+ } | null;
43
+ }
44
+ interface RawAutoMergeRequest {
45
+ enabledAt: string;
46
+ mergeMethod: string;
47
+ enabledBy?: {
48
+ login: string;
49
+ } | null;
50
+ }
51
+ interface RawMergeQueueRemoval {
52
+ reason: string | null;
53
+ actor?: {
54
+ login: string;
55
+ } | null;
56
+ createdAt: string;
57
+ beforeCommit?: RawCheckCommit | null;
19
58
  }
20
59
  interface RawStack {
21
60
  number: number;
@@ -50,6 +89,15 @@ export interface RawPrMergeFields {
50
89
  isInMergeQueue?: boolean;
51
90
  isMergeQueueEnabled?: boolean;
52
91
  mergeQueueEntry?: RawMergeQueueEntry | null;
92
+ autoMergeRequest?: RawAutoMergeRequest | null;
93
+ mergeQueueRemovals?: {
94
+ nodes: RawMergeQueueRemoval[];
95
+ } | null;
96
+ mergeQueueAdditions?: {
97
+ nodes: Array<{
98
+ createdAt: string;
99
+ }>;
100
+ } | null;
53
101
  stack?: RawStack | null;
54
102
  stackEntry?: {
55
103
  position: number;
@@ -1,2 +1 @@
1
- // Private raw GraphQL response types for the batch PR query.
2
1
  export {};
@@ -6,6 +6,7 @@ import { parseCheckSuitesComplete, parseSuiteStartupFailures } from "./batch-par
6
6
  import { mergeStartupFailureChecks } from "../checks/startup-failures.mjs";
7
7
  import { paginateBatchConnections } from "./batch-page.mjs";
8
8
  import { requireRawPr } from "./batch-response.mjs";
9
+ import { hydrateMergeQueueChecks } from "./merge-queue-checks.mjs";
9
10
  /**
10
11
  * Fetch all PR data needed for a `shepherd check` in one (or a few, if paginating) GraphQL requests.
11
12
  */
@@ -16,6 +17,7 @@ export async function fetchPrBatch(pr, repo, opts = {}) {
16
17
  pr,
17
18
  });
18
19
  const raw = requireRawPr(result.data, pr, repo);
20
+ await hydrateMergeQueueChecks(raw, repo);
19
21
  const paged = await paginateBatchConnections(pr, repo, raw, opts, result.rateLimit);
20
22
  const rawThreadPages = await hydrateThreadCommentPages(paged.threads);
21
23
  const data = parseRawPr(raw, rawThreadPages, paged.comments, paged.changesRequested, paged.reviewSummaries, paged.approvedReviews, paged.checks);
@@ -18,10 +18,45 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
18
18
  baseRefName
19
19
  isInMergeQueue
20
20
  isMergeQueueEnabled
21
+ autoMergeRequest {
22
+ enabledAt
23
+ mergeMethod
24
+ enabledBy {
25
+ login
26
+ }
27
+ }
21
28
  mergeQueueEntry {
22
29
  position
23
30
  state
24
31
  estimatedTimeToMerge
32
+ enqueuedAt
33
+ enqueuer {
34
+ login
35
+ }
36
+ headCommit {
37
+ ...QueueCheckCommit
38
+ }
39
+ }
40
+ mergeQueueAdditions: timelineItems(last: 1, itemTypes: [ADDED_TO_MERGE_QUEUE_EVENT]) {
41
+ nodes {
42
+ ... on AddedToMergeQueueEvent {
43
+ createdAt
44
+ }
45
+ }
46
+ }
47
+ mergeQueueRemovals: timelineItems(last: 1, itemTypes: [REMOVED_FROM_MERGE_QUEUE_EVENT]) {
48
+ nodes {
49
+ ... on RemovedFromMergeQueueEvent {
50
+ reason
51
+ createdAt
52
+ actor {
53
+ login
54
+ }
55
+ beforeCommit {
56
+ ...QueueCheckCommit
57
+ }
58
+ }
59
+ }
25
60
  }
26
61
  stack {
27
62
  number
@@ -289,3 +324,60 @@ query BatchPr($owner: String!, $repo: String!, $pr: Int!) {
289
324
  }
290
325
  }
291
326
  }
327
+
328
+ fragment QueueCheckCommit on Commit {
329
+ oid
330
+ committedDate
331
+ parents(first: 100) {
332
+ nodes {
333
+ oid
334
+ }
335
+ }
336
+ statusCheckRollup {
337
+ contexts(first: 100) {
338
+ pageInfo {
339
+ hasNextPage
340
+ endCursor
341
+ }
342
+ nodes {
343
+ __typename
344
+ ... on CheckRun {
345
+ id
346
+ name
347
+ status
348
+ conclusion
349
+ detailsUrl
350
+ completedAt
351
+ startedAt
352
+ title
353
+ summary
354
+ annotations(first: 1) {
355
+ nodes {
356
+ message
357
+ }
358
+ }
359
+ checkSuite {
360
+ createdAt
361
+ updatedAt
362
+ workflowRun {
363
+ event
364
+ createdAt
365
+ updatedAt
366
+ workflow {
367
+ name
368
+ databaseId
369
+ }
370
+ }
371
+ }
372
+ }
373
+ ... on StatusContext {
374
+ context
375
+ state
376
+ createdAt
377
+ targetUrl
378
+ description
379
+ }
380
+ }
381
+ }
382
+ }
383
+ }
@@ -0,0 +1,58 @@
1
+ query CommitCheckContexts($owner: String!, $repo: String!, $oid: String!, $cursor: String) {
2
+ repository(owner: $owner, name: $repo) {
3
+ object(expression: $oid) {
4
+ __typename
5
+ ... on Commit {
6
+ oid
7
+ statusCheckRollup {
8
+ contexts(first: 100, after: $cursor) {
9
+ pageInfo {
10
+ hasNextPage
11
+ endCursor
12
+ }
13
+ nodes {
14
+ __typename
15
+ ... on CheckRun {
16
+ id
17
+ name
18
+ status
19
+ conclusion
20
+ detailsUrl
21
+ completedAt
22
+ startedAt
23
+ title
24
+ summary
25
+ annotations(first: 1) {
26
+ nodes {
27
+ message
28
+ }
29
+ }
30
+ checkSuite {
31
+ createdAt
32
+ updatedAt
33
+ workflowRun {
34
+ event
35
+ createdAt
36
+ updatedAt
37
+ workflow {
38
+ name
39
+ databaseId
40
+ }
41
+ databaseId
42
+ }
43
+ }
44
+ }
45
+ ... on StatusContext {
46
+ context
47
+ state
48
+ description
49
+ targetUrl
50
+ createdAt
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,4 @@
1
+ import type { RepoInfo } from "./client.mts";
2
+ import type { RawPr } from "./batch-raw-types.mts";
3
+ /** Hydrate all status contexts for the active or most recently removed queue commit. */
4
+ export declare function hydrateMergeQueueChecks(raw: RawPr, repo: RepoInfo): Promise<void>;
@@ -0,0 +1,48 @@
1
+ import { graphql } from "./client.mjs";
2
+ import { requireContextNodes } from "./batch-response.mjs";
3
+ import { COMMIT_CHECK_CONTEXTS_QUERY } from "./queries.mjs";
4
+ async function hydrateCommitContexts(commit, repo) {
5
+ const contexts = commit.statusCheckRollup?.contexts;
6
+ if (!contexts)
7
+ return;
8
+ contexts.nodes = requireContextNodes(contexts.nodes);
9
+ if (contexts.pageInfo.hasNextPage && !contexts.pageInfo.endCursor) {
10
+ throw new Error(`Merge queue check pagination interrupted: GitHub omitted the next cursor for ${commit.oid}. Retry.`);
11
+ }
12
+ let cursor = contexts.pageInfo.hasNextPage ? contexts.pageInfo.endCursor : null;
13
+ while (cursor) {
14
+ // eslint-disable-next-line no-await-in-loop
15
+ const result = await graphql(COMMIT_CHECK_CONTEXTS_QUERY, {
16
+ owner: repo.owner,
17
+ repo: repo.name,
18
+ oid: commit.oid,
19
+ cursor,
20
+ });
21
+ const object = result.data.repository?.object;
22
+ if (object?.__typename !== "Commit" || object.oid !== commit.oid) {
23
+ throw new Error(`Merge queue check pagination interrupted: commit ${commit.oid} disappeared or changed. Retry.`);
24
+ }
25
+ const next = object.statusCheckRollup?.contexts;
26
+ if (!next) {
27
+ throw new Error(`Merge queue check pagination interrupted: statusCheckRollup disappeared for ${commit.oid}. Retry.`);
28
+ }
29
+ contexts.nodes.push(...requireContextNodes(next.nodes));
30
+ contexts.pageInfo = next.pageInfo;
31
+ cursor = next.pageInfo.hasNextPage ? next.pageInfo.endCursor : null;
32
+ if (next.pageInfo.hasNextPage && !cursor) {
33
+ throw new Error(`Merge queue check pagination interrupted: GitHub omitted the next cursor for ${commit.oid}. Retry.`);
34
+ }
35
+ }
36
+ }
37
+ /** Hydrate all status contexts for the active or most recently removed queue commit. */
38
+ export async function hydrateMergeQueueChecks(raw, repo) {
39
+ const active = raw.mergeQueueEntry?.headCommit;
40
+ const removal = raw.mergeQueueRemovals?.nodes[0];
41
+ const addition = raw.mergeQueueAdditions?.nodes[0];
42
+ const removalIsCurrent = Boolean(removal && (!addition || Date.parse(removal.createdAt) >= Date.parse(addition.createdAt)));
43
+ const removed = removalIsCurrent ? removal?.beforeCommit : undefined;
44
+ if (active)
45
+ await hydrateCommitContexts(active, repo);
46
+ if (removed && removed.oid !== active?.oid)
47
+ await hydrateCommitContexts(removed, repo);
48
+ }
@@ -16,6 +16,8 @@ export declare const SUGGESTION_THREADS_QUERY: string;
16
16
  export declare const REVIEW_THREAD_COMMENTS_QUERY: string;
17
17
  /** Fetch inline annotations for a single CheckRun by node ID. */
18
18
  export declare const CHECK_RUN_ANNOTATIONS_QUERY: string;
19
+ /** Fetches additional status contexts for a merge queue synthetic commit. */
20
+ export declare const COMMIT_CHECK_CONTEXTS_QUERY: string;
19
21
  /** Returns the current head commit SHA for a PR. Used by waitForSha polling. */
20
22
  export declare const GET_PR_HEAD_SHA_QUERY: string;
21
23
  /** Look up PR number by branch name (for getCurrentPrNumber). */
@@ -19,6 +19,8 @@ export const SUGGESTION_THREADS_QUERY = gql("suggestion-threads.gql");
19
19
  export const REVIEW_THREAD_COMMENTS_QUERY = gql("review-thread-comments.gql");
20
20
  /** Fetch inline annotations for a single CheckRun by node ID. */
21
21
  export const CHECK_RUN_ANNOTATIONS_QUERY = gql("check-run-annotations.gql");
22
+ /** Fetches additional status contexts for a merge queue synthetic commit. */
23
+ export const COMMIT_CHECK_CONTEXTS_QUERY = gql("commit-check-contexts.gql");
22
24
  /** Returns the current head commit SHA for a PR. Used by waitForSha polling. */
23
25
  export const GET_PR_HEAD_SHA_QUERY = gql("get-pr-head-sha.gql");
24
26
  /** Look up PR number by branch name (for getCurrentPrNumber). */
@@ -18,6 +18,7 @@ const iterateInputSchema = z.object({
18
18
  readyDelaySeconds: z.number().nonnegative().optional(),
19
19
  stallTimeoutSeconds: z.number().nonnegative().optional(),
20
20
  noAutoMarkReady: z.boolean().optional(),
21
+ merge: z.boolean().optional(),
21
22
  noAutoCancelActionable: z.boolean().optional(),
22
23
  neverCancelRuns: z.array(z.string()).optional(),
23
24
  });
@@ -61,6 +61,8 @@ export function toAgentCheck(c) {
61
61
  ...(c.summary !== undefined && { summary: c.summary }),
62
62
  ...(c.logExcerpt !== undefined && { logExcerpt: c.logExcerpt }),
63
63
  ...(c.annotations !== undefined && { annotations: c.annotations }),
64
+ ...(c.scope !== undefined && { scope: c.scope }),
65
+ ...(c.commitOid !== undefined && { commitOid: c.commitOid }),
64
66
  };
65
67
  }
66
68
  export function toAgentStalledCheck(c, nowSeconds) {
@@ -85,13 +87,14 @@ export function toAgentStalledCheck(c, nowSeconds) {
85
87
  * log tail, so they are all kept.
86
88
  */
87
89
  export function toAgentChecks(checks) {
88
- const seenNames = new Set();
90
+ const seenKeys = new Set();
89
91
  const result = [];
90
92
  for (const c of checks) {
91
93
  if (c.runId === null) {
92
- if (seenNames.has(c.name))
94
+ const key = `${c.scope ?? "pr"}:${c.commitOid ?? "head"}:${c.name}`;
95
+ if (seenKeys.has(key))
93
96
  continue;
94
- seenNames.add(c.name);
97
+ seenKeys.add(key);
95
98
  }
96
99
  result.push(toAgentCheck(c));
97
100
  }
@@ -26,5 +26,7 @@ export interface ActiveCheck {
26
26
  detailsUrl: string | null;
27
27
  workflowName?: string;
28
28
  summary?: string;
29
+ scope?: "merge_group";
30
+ commitOid?: string;
29
31
  }
30
32
  export {};
@@ -0,0 +1,30 @@
1
+ import type { AgentComment, AgentThread } from "./report.mts";
2
+ import type { CheckStatus, Review } from "./github.mts";
3
+ import type { MergeQueueRemovalStatus } from "./merge-requirements.mts";
4
+ export type EscalateTrigger = "fix-thrash" | "base-branch-unknown" | "stall-timeout" | "thread-missing-location" | "bot-cr-not-dismissed" | "merge-queue-removed";
5
+ export interface AgentStalledCheck {
6
+ name: string;
7
+ status: CheckStatus;
8
+ source: "check_run" | "status_context" | "startup_failure";
9
+ runId: string | null;
10
+ detailsUrl: string | null;
11
+ createdAtUnix?: number;
12
+ startedAtUnix?: number;
13
+ updatedAtUnix?: number;
14
+ ageSeconds: number;
15
+ summary?: string;
16
+ }
17
+ export interface EscalateDetails {
18
+ triggers: EscalateTrigger[];
19
+ unresolvedThreads: AgentThread[];
20
+ ambiguousComments: AgentComment[];
21
+ changesRequestedReviews: Review[];
22
+ stalledChecks?: AgentStalledCheck[];
23
+ thrashHistory?: Array<{
24
+ threadId: string;
25
+ attempts: number;
26
+ }>;
27
+ suggestion: string;
28
+ humanMessage: string;
29
+ mergeQueueRemoval?: MergeQueueRemovalStatus;
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -19,6 +19,10 @@ export interface CheckRun {
19
19
  detailsUrl: string;
20
20
  event: string | null;
21
21
  runId: string | null;
22
+ /** Commit scope that supplied this check. Omitted for ordinary PR-head checks. */
23
+ scope?: "merge_group";
24
+ /** Synthetic merge-group commit OID, when `scope` is `merge_group`. */
25
+ commitOid?: string;
22
26
  createdAtUnix?: number;
23
27
  startedAtUnix?: number;
24
28
  completedAtUnix?: number;
@@ -133,6 +137,10 @@ export interface BatchPrData extends BatchPrMergeFields {
133
137
  reviewSummaries: Review[];
134
138
  approvedReviews: Review[];
135
139
  checks: CheckRun[];
140
+ mergeQueueChecks?: CheckRun[];
141
+ mergeQueueChecksIncomplete?: true;
142
+ removedMergeQueueChecks?: CheckRun[];
143
+ removedMergeQueueChecksIncomplete?: true;
136
144
  branchProtection: BranchProtection | null;
137
145
  activity?: PrActivitySummary;
138
146
  }
@@ -1,4 +1 @@
1
- // GitHub primitives, check runs, review threads, and batch PR data types.
2
- // Check classification types (CheckCategory/ClassifiedCheck/TriagedCheck) live in
3
- // check-classification.mts to stay under the file-length cap.
4
1
  export {};
@@ -1,37 +1,11 @@
1
1
  import type { AgentThread, AgentComment, AgentCheck, GlobalOptions, RelevantCheck, ShepherdStatus, FirstLookThread, FirstLookComment } from "./report.mts";
2
2
  import type { ActiveCheck, PrActivitySummary } from "./activity.mts";
3
- import type { BranchProtection, CheckStatus, MergeStateStatus, Review, ReviewDecision, ReviewThread, ShepherdMergeStatus } from "./github.mts";
3
+ import type { BranchProtection, MergeStateStatus, Review, ReviewDecision, ReviewThread, ShepherdMergeStatus } from "./github.mts";
4
4
  import type { MergeRequirements } from "./merge-requirements.mts";
5
+ import type { EscalateDetails } from "./escalate.mts";
6
+ import type { MergeCommandPlan } from "./merge-action.mts";
5
7
  import type { ProtectedRun } from "./protected-run.mts";
6
- export type ShepherdAction = "wait" | "fix_code" | "mark_ready" | "cancel" | "escalate";
7
- export type EscalateTrigger = "fix-thrash" | "base-branch-unknown" | "stall-timeout" | "thread-missing-location" | "bot-cr-not-dismissed";
8
- export interface AgentStalledCheck {
9
- name: string;
10
- status: CheckStatus;
11
- source: "check_run" | "status_context" | "startup_failure";
12
- runId: string | null;
13
- detailsUrl: string | null;
14
- createdAtUnix?: number;
15
- startedAtUnix?: number;
16
- updatedAtUnix?: number;
17
- ageSeconds: number;
18
- summary?: string;
19
- }
20
- export interface EscalateDetails {
21
- triggers: EscalateTrigger[];
22
- unresolvedThreads: AgentThread[];
23
- ambiguousComments: AgentComment[];
24
- changesRequestedReviews: Review[];
25
- /** Pending/unstarted CI checks that exceeded the stall timeout. */
26
- stalledChecks?: AgentStalledCheck[];
27
- /** Populated when fix-thrash triggered — threads that have been attempted too many times. */
28
- thrashHistory?: Array<{
29
- threadId: string;
30
- attempts: number;
31
- }>;
32
- suggestion: string;
33
- humanMessage: string;
34
- }
8
+ export type ShepherdAction = "wait" | "fix_code" | "mark_ready" | "merge" | "cancel" | "escalate";
35
9
  export interface IterateResultSummary {
36
10
  passing: number;
37
11
  skipped: number;
@@ -66,6 +40,7 @@ export interface IterateResultBase {
66
40
  ignoredNames?: string[];
67
41
  supersededNames?: string[];
68
42
  activity?: PrActivitySummary;
43
+ mergeQueue?: import("./merge-queue.mts").MergeQueueReport;
69
44
  }
70
45
  interface IterateResultWait extends IterateResultBase {
71
46
  action: "wait";
@@ -123,6 +98,8 @@ interface FixRebaseAndPush {
123
98
  inProgressRunIds: string[];
124
99
  /** Workflow runs deliberately excluded from cancellation by actions.neverCancelRuns. */
125
100
  protectedRuns: ProtectedRun[];
101
+ /** Requeue command emitted after merge-group remediation. */
102
+ requeue?: MergeCommandPlan;
126
103
  /** First-look threads — previously hidden, surfaced for acknowledgment only. */
127
104
  firstLookThreads: FirstLookThread[];
128
105
  /** First-look comments — previously hidden, surfaced for acknowledgment only. */
@@ -138,11 +115,15 @@ interface IterateResultMarkReady extends IterateResultBase {
138
115
  markedReady: boolean;
139
116
  log: string;
140
117
  }
118
+ export interface IterateResultMerge extends IterateResultBase {
119
+ action: "merge";
120
+ merge: MergeCommandPlan;
121
+ }
141
122
  interface IterateResultEscalate extends IterateResultBase {
142
123
  action: "escalate";
143
124
  escalate: EscalateDetails;
144
125
  }
145
- export type IterateResult = IterateResultWait | IterateResultCancel | IterateResultFixCode | IterateResultMarkReady | IterateResultEscalate;
126
+ export type IterateResult = IterateResultWait | IterateResultCancel | IterateResultFixCode | IterateResultMarkReady | IterateResultMerge | IterateResultEscalate;
146
127
  export interface IterateCommandOptions extends GlobalOptions {
147
128
  readyDelaySeconds?: number;
148
129
  noAutoMarkReady?: boolean;
@@ -152,5 +133,7 @@ export interface IterateCommandOptions extends GlobalOptions {
152
133
  /** Case-insensitive workflow/check glob patterns Shepherd must not cancel. */
153
134
  neverCancelRuns?: string[];
154
135
  persistSeen?: boolean;
136
+ /** Shepherd through readiness and emit the exact merge/queue command when ready. */
137
+ merge?: boolean;
155
138
  }
156
139
  export {};