pr-shepherd 0.41.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 (109) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +14 -9
  3. package/bin/api.d.mts +12 -1
  4. package/bin/api.mjs +34 -1
  5. package/bin/checks/classify.d.mts +3 -1
  6. package/bin/checks/classify.mjs +5 -2
  7. package/bin/cli/args.mjs +1 -0
  8. package/bin/cli/default-poll.mjs +1 -0
  9. package/bin/cli/fix-formatter.mjs +12 -2
  10. package/bin/cli/formatters.d.mts +2 -1
  11. package/bin/cli/formatters.mjs +33 -0
  12. package/bin/cli/handlers.d.mts +1 -0
  13. package/bin/cli/handlers.mjs +21 -1
  14. package/bin/cli/help-command-pages.d.mts +22 -6
  15. package/bin/cli/help-command-pages.mjs +20 -4
  16. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  17. package/bin/cli/help-iterate-poll-pages.mjs +7 -2
  18. package/bin/cli/help-top-page.d.mts +1 -1
  19. package/bin/cli/help-top-page.mjs +3 -3
  20. package/bin/cli/help.d.mts +23 -7
  21. package/bin/cli/iterate-flags.d.mts +1 -0
  22. package/bin/cli/iterate-flags.mjs +2 -0
  23. package/bin/cli/iterate-formatter.mjs +4 -1
  24. package/bin/cli/iterate-instructions.mjs +15 -0
  25. package/bin/cli/iterate-lean.d.mts +1 -2
  26. package/bin/cli/iterate-lean.mjs +12 -17
  27. package/bin/cli/iterate-merge-formatter.d.mts +3 -0
  28. package/bin/cli/iterate-merge-formatter.mjs +52 -0
  29. package/bin/cli/iterate-verbose.d.mts +6 -0
  30. package/bin/cli/iterate-verbose.mjs +14 -0
  31. package/bin/cli/poll-handler.mjs +1 -0
  32. package/bin/cli/runner.mjs +4 -3
  33. package/bin/cli/suggestion-patch-flags.d.mts +11 -0
  34. package/bin/cli/suggestion-patch-flags.mjs +83 -0
  35. package/bin/cli-parser.mjs +6 -2
  36. package/bin/commands/check-terminal-report.mjs +1 -0
  37. package/bin/commands/check.mjs +42 -1
  38. package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
  39. package/bin/commands/commit-suggestion-instruction.mjs +3 -3
  40. package/bin/commands/commit-suggestion.d.mts +1 -1
  41. package/bin/commands/commit-suggestion.mjs +19 -138
  42. package/bin/commands/iterate/base.d.mts +5 -0
  43. package/bin/commands/iterate/base.mjs +25 -0
  44. package/bin/commands/iterate/escalate.d.mts +3 -1
  45. package/bin/commands/iterate/escalate.mjs +18 -2
  46. package/bin/commands/iterate/fix-code.mjs +16 -0
  47. package/bin/commands/iterate/helpers.mjs +6 -2
  48. package/bin/commands/iterate/index.mjs +29 -35
  49. package/bin/commands/iterate/merge-state.d.mts +15 -0
  50. package/bin/commands/iterate/merge-state.mjs +53 -0
  51. package/bin/commands/iterate/merge.d.mts +13 -0
  52. package/bin/commands/iterate/merge.mjs +46 -0
  53. package/bin/commands/poll.mjs +3 -1
  54. package/bin/commands/suggestion-patch-git.d.mts +6 -0
  55. package/bin/commands/suggestion-patch-git.mjs +54 -0
  56. package/bin/commands/suggestion-patch-item.d.mts +15 -0
  57. package/bin/commands/suggestion-patch-item.mjs +106 -0
  58. package/bin/commands/suggestion-patches.d.mts +6 -0
  59. package/bin/commands/suggestion-patches.mjs +108 -0
  60. package/bin/config/load.d.mts +4 -0
  61. package/bin/config/load.mjs +34 -0
  62. package/bin/config/merge-command-args.d.mts +2 -0
  63. package/bin/config/merge-command-args.mjs +44 -0
  64. package/bin/config.json +3 -0
  65. package/bin/exit-codes.d.mts +2 -0
  66. package/bin/exit-codes.mjs +4 -0
  67. package/bin/github/batch-parse-checks.d.mts +3 -0
  68. package/bin/github/batch-parse-checks.mjs +29 -0
  69. package/bin/github/batch-parsers-rules.d.mts +3 -1
  70. package/bin/github/batch-parsers-rules.mjs +33 -0
  71. package/bin/github/batch-parsers.mjs +23 -26
  72. package/bin/github/batch-raw-rules.d.mts +48 -0
  73. package/bin/github/batch-raw-types.mjs +0 -1
  74. package/bin/github/batch.mjs +2 -0
  75. package/bin/github/gql/batch-pr.gql +92 -0
  76. package/bin/github/gql/commit-check-contexts.gql +58 -0
  77. package/bin/github/gql/{commit-suggestion-thread.gql → suggestion-threads.gql} +2 -2
  78. package/bin/github/merge-queue-checks.d.mts +4 -0
  79. package/bin/github/merge-queue-checks.mjs +48 -0
  80. package/bin/github/queries.d.mts +3 -1
  81. package/bin/github/queries.mjs +3 -1
  82. package/bin/github/suggestion-thread.d.mts +3 -3
  83. package/bin/github/suggestion-thread.mjs +8 -5
  84. package/bin/mcp/server.d.mts +1 -1
  85. package/bin/mcp/server.mjs +24 -3
  86. package/bin/reporters/agent.mjs +6 -3
  87. package/bin/types/activity.d.mts +2 -0
  88. package/bin/types/escalate.d.mts +30 -0
  89. package/bin/types/escalate.mjs +1 -0
  90. package/bin/types/github.d.mts +8 -0
  91. package/bin/types/github.mjs +0 -3
  92. package/bin/types/iterate.d.mts +14 -31
  93. package/bin/types/merge-action.d.mts +12 -0
  94. package/bin/types/merge-action.mjs +1 -0
  95. package/bin/types/merge-queue.d.mts +13 -0
  96. package/bin/types/merge-queue.mjs +1 -0
  97. package/bin/types/merge-requirements.d.mts +17 -0
  98. package/bin/types/report.d.mts +8 -19
  99. package/bin/types/suggestion-patch.d.mts +30 -0
  100. package/bin/types/suggestion-patch.mjs +1 -0
  101. package/bin/types.d.mts +4 -0
  102. package/bin/types.mjs +4 -0
  103. package/bin/util/markdown.d.mts +2 -0
  104. package/bin/util/markdown.mjs +7 -0
  105. package/package.json +1 -1
  106. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  107. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  108. package/plugins/pr-shepherd/.mcp.json +1 -1
  109. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +11 -10
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- query CommitSuggestionThread($owner: String!, $repo: String!, $pr: Int!, $threadId: ID!) {
1
+ query SuggestionThreads($owner: String!, $repo: String!, $pr: Int!, $threadIds: [ID!]!) {
2
2
  repository(owner: $owner, name: $repo) {
3
3
  pullRequest(number: $pr) {
4
4
  headRefOid
@@ -8,7 +8,7 @@ query CommitSuggestionThread($owner: String!, $repo: String!, $pr: Int!, $thread
8
8
  }
9
9
  }
10
10
  }
11
- node(id: $threadId) {
11
+ nodes(ids: $threadIds) {
12
12
  ... on PullRequestReviewThread {
13
13
  id
14
14
  isResolved
@@ -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
+ }
@@ -11,11 +11,13 @@ export declare const BATCH_PR_QUERY: string;
11
11
  /** Slim @include follow-up for outstanding batch-query connections. */
12
12
  export declare const BATCH_PR_PAGE_QUERY: string;
13
13
  /** PR head fields plus a single review thread for `commit-suggestion`. */
14
- export declare const COMMIT_SUGGESTION_THREAD_QUERY: string;
14
+ export declare const SUGGESTION_THREADS_QUERY: string;
15
15
  /** Fetches additional comments for a single review thread when its nested connection paginates. */
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). */
@@ -14,11 +14,13 @@ export const BATCH_PR_QUERY = gql("batch-pr.gql");
14
14
  /** Slim @include follow-up for outstanding batch-query connections. */
15
15
  export const BATCH_PR_PAGE_QUERY = gql("batch-pr-page.gql");
16
16
  /** PR head fields plus a single review thread for `commit-suggestion`. */
17
- export const COMMIT_SUGGESTION_THREAD_QUERY = gql("commit-suggestion-thread.gql");
17
+ export const SUGGESTION_THREADS_QUERY = gql("suggestion-threads.gql");
18
18
  /** Fetches additional comments for a single review thread when its nested connection paginates. */
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). */
@@ -1,9 +1,9 @@
1
1
  import type { ReviewThread } from "../types.mts";
2
2
  import type { RepoInfo } from "./client.mts";
3
- export interface SuggestionThreadResult {
3
+ export interface SuggestionThreadsResult {
4
4
  headRefOid: string;
5
5
  headRefName: string;
6
6
  headRepoWithOwner: string | null;
7
- thread: ReviewThread | null;
7
+ threads: (ReviewThread | null)[];
8
8
  }
9
- export declare function fetchSuggestionThread(pr: number, repo: RepoInfo, threadId: string): Promise<SuggestionThreadResult>;
9
+ export declare function fetchSuggestionThreads(pr: number, repo: RepoInfo, threadIds: readonly string[]): Promise<SuggestionThreadsResult>;
@@ -1,12 +1,12 @@
1
1
  import { graphql } from "./client.mjs";
2
- import { COMMIT_SUGGESTION_THREAD_QUERY } from "./queries.mjs";
2
+ import { SUGGESTION_THREADS_QUERY } from "./queries.mjs";
3
3
  import { mapAuthorType, parseCreatedAt } from "./batch-parser-helpers.mjs";
4
- export async function fetchSuggestionThread(pr, repo, threadId) {
5
- const result = await graphql(COMMIT_SUGGESTION_THREAD_QUERY, {
4
+ export async function fetchSuggestionThreads(pr, repo, threadIds) {
5
+ const result = await graphql(SUGGESTION_THREADS_QUERY, {
6
6
  owner: repo.owner,
7
7
  repo: repo.name,
8
8
  pr,
9
- threadId,
9
+ threadIds,
10
10
  });
11
11
  const pull = result.data.repository.pullRequest;
12
12
  if (!pull) {
@@ -16,7 +16,10 @@ export async function fetchSuggestionThread(pr, repo, threadId) {
16
16
  headRefOid: pull.headRefOid,
17
17
  headRefName: pull.headRefName,
18
18
  headRepoWithOwner: pull.headRepository?.nameWithOwner ?? null,
19
- thread: parseThread(result.data.node, pr, threadId),
19
+ threads: threadIds.map((threadId) => {
20
+ const raw = result.data.nodes.find((candidate) => candidate?.id === threadId) ?? null;
21
+ return parseThread(raw, pr, threadId);
22
+ }),
20
23
  };
21
24
  }
22
25
  function parseThread(raw, pr, threadId) {
@@ -4,5 +4,5 @@ export interface CreatePrShepherdMcpServerOptions extends CreatePrShepherdOption
4
4
  /** Optional injection point for embedding hosts and focused tests. */
5
5
  shepherd?: PrShepherd;
6
6
  }
7
- /** Creates a local-only MCP server with the three public Shepherd operations. */
7
+ /** Creates a local-only MCP server with Shepherd's public operations. */
8
8
  export declare function createPrShepherdMcpServer(options?: CreatePrShepherdMcpServerOptions): McpServer;
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  import { createPrShepherd, PartialApplyError, PrShepherdValidationError, } from "../api.mjs";
6
6
  import { isRepositoryQualifiedPrReference } from "../pr-reference.mjs";
7
7
  import { formatJournalResult } from "../cli/journal-formatter.mjs";
8
- import { formatCommitSuggestionResult, formatIterateResult, formatMarkFilesAsViewedResult, formatMutateResult, } from "../cli/formatters.mjs";
8
+ import { formatCommitSuggestionResult, formatSuggestionPatchesResult, formatIterateResult, formatMarkFilesAsViewedResult, formatMutateResult, } from "../cli/formatters.mjs";
9
9
  import { errorToExitCode, EXIT } from "../exit-codes.mjs";
10
10
  const QUALIFIED_PR_ERROR = "pr must be a GitHub pull-request URL or an owner/repo#number reference";
11
11
  const pr = z
@@ -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
  });
@@ -57,7 +58,17 @@ const suggestionPatchInputSchema = z.object({
57
58
  message: z.string().min(1),
58
59
  description: z.string().optional(),
59
60
  });
60
- /** Creates a local-only MCP server with the three public Shepherd operations. */
61
+ const suggestionPatchesInputSchema = z.object({
62
+ pr,
63
+ suggestions: z
64
+ .array(z.object({
65
+ threadId: z.string().min(1),
66
+ message: z.string().min(1),
67
+ description: z.string().optional(),
68
+ }))
69
+ .min(1),
70
+ });
71
+ /** Creates a local-only MCP server with Shepherd's public operations. */
61
72
  export function createPrShepherdMcpServer(options = {}) {
62
73
  const shepherd = options.shepherd ?? createPrShepherd({ cwd: options.cwd });
63
74
  const server = new McpServer({ name: "pr-shepherd", version: readPackageVersion() });
@@ -81,8 +92,18 @@ export function createPrShepherdMcpServer(options = {}) {
81
92
  openWorldHint: true,
82
93
  },
83
94
  }, async (input) => runTool(() => shepherd.apply(requireRepositoryQualifiedPr(input)), formatApplyResult));
95
+ server.registerTool("build_suggestion_patches", {
96
+ description: "Build, but never apply, an ordered list of eligible review suggestion patches.",
97
+ inputSchema: suggestionPatchesInputSchema,
98
+ annotations: {
99
+ readOnlyHint: true,
100
+ destructiveHint: false,
101
+ idempotentHint: true,
102
+ openWorldHint: true,
103
+ },
104
+ }, async (input) => runTool(() => shepherd.buildSuggestionPatches(requireRepositoryQualifiedPr(input)), formatSuggestionPatchesResult));
84
105
  server.registerTool("build_suggestion_patch", {
85
- description: "Build, but never apply, a patch from an eligible review suggestion.",
106
+ description: "Deprecated: use build_suggestion_patches with a one-item suggestions array.",
86
107
  inputSchema: suggestionPatchInputSchema,
87
108
  annotations: {
88
109
  readOnlyHint: true,
@@ -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 {};
@@ -0,0 +1,12 @@
1
+ interface ShellCommand {
2
+ argv: string[];
3
+ }
4
+ export interface MergeCommandPlan {
5
+ mode: "auto" | "queue";
6
+ command: ShellCommand;
7
+ /** Ordinary merge retry without `--auto`, used only when auto-merge is unavailable. */
8
+ fallbackCommand?: ShellCommand;
9
+ /** Direct enqueuePullRequest fallback for the known gh CLI queue limitation. */
10
+ queueApiFallbackCommand?: ShellCommand;
11
+ }
12
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { AutoMergeRequestStatus, MergeQueueEntryStatus, MergeQueueRemovalStatus } from "./merge-requirements.mts";
2
+ export interface MergeQueueReport {
3
+ enabled: boolean;
4
+ inQueue: boolean;
5
+ autoMergeRequest?: AutoMergeRequestStatus;
6
+ entry?: MergeQueueEntryStatus;
7
+ latestRemoval?: MergeQueueRemovalStatus;
8
+ checkCommitOid?: string;
9
+ /** GitHub reported more than the first 100 contexts on the queue commit. */
10
+ checksIncomplete?: true;
11
+ /** The current PR head is not a parent of the removed synthetic queue commit. */
12
+ headUpdatedAfterRemoval?: true;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -17,6 +17,21 @@ export interface MergeQueueEntryStatus {
17
17
  position: number;
18
18
  state: string;
19
19
  estimatedTimeToMerge: number | null;
20
+ headCommitOid?: string;
21
+ enqueuedAtUnix?: number;
22
+ enqueuer?: string;
23
+ }
24
+ export interface AutoMergeRequestStatus {
25
+ enabledAtUnix: number;
26
+ mergeMethod: string;
27
+ enabledBy?: string;
28
+ }
29
+ export interface MergeQueueRemovalStatus {
30
+ reason: string | null;
31
+ actor?: string;
32
+ createdAtUnix: number;
33
+ beforeCommitOid?: string;
34
+ beforeCommitParentOids?: string[];
20
35
  }
21
36
  export interface StackStatus {
22
37
  number: number;
@@ -30,6 +45,8 @@ export interface BatchPrMergeFields {
30
45
  isInMergeQueue?: boolean;
31
46
  isMergeQueueEnabled?: boolean;
32
47
  mergeQueueEntry?: MergeQueueEntryStatus | null;
48
+ autoMergeRequest?: AutoMergeRequestStatus | null;
49
+ latestMergeQueueRemoval?: MergeQueueRemovalStatus | null;
33
50
  stack?: StackStatus | null;
34
51
  }
35
52
  /** Snapshot of "why can't I merge" requirements vs current PR state. */