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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +8 -3
- package/bin/checks/classify.d.mts +3 -1
- package/bin/checks/classify.mjs +5 -2
- package/bin/cli/args.mjs +1 -0
- package/bin/cli/default-poll.mjs +1 -0
- package/bin/cli/fix-formatter.mjs +12 -2
- package/bin/cli/handlers.mjs +1 -0
- package/bin/cli/help-command-pages.d.mts +2 -2
- package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
- package/bin/cli/help-iterate-poll-pages.mjs +7 -2
- package/bin/cli/help.d.mts +2 -2
- package/bin/cli/iterate-flags.d.mts +1 -0
- package/bin/cli/iterate-flags.mjs +2 -0
- package/bin/cli/iterate-formatter.mjs +4 -1
- package/bin/cli/iterate-instructions.mjs +15 -0
- package/bin/cli/iterate-lean.d.mts +1 -2
- package/bin/cli/iterate-lean.mjs +12 -17
- package/bin/cli/iterate-merge-formatter.d.mts +3 -0
- package/bin/cli/iterate-merge-formatter.mjs +52 -0
- package/bin/cli/iterate-verbose.d.mts +6 -0
- package/bin/cli/iterate-verbose.mjs +14 -0
- package/bin/cli/poll-handler.mjs +1 -0
- package/bin/cli/runner.mjs +4 -3
- package/bin/commands/check-terminal-report.mjs +1 -0
- package/bin/commands/check.mjs +42 -1
- package/bin/commands/iterate/base.d.mts +5 -0
- package/bin/commands/iterate/base.mjs +25 -0
- package/bin/commands/iterate/escalate.d.mts +3 -1
- package/bin/commands/iterate/escalate.mjs +18 -2
- package/bin/commands/iterate/fix-code.mjs +16 -0
- package/bin/commands/iterate/helpers.mjs +6 -2
- package/bin/commands/iterate/index.mjs +29 -35
- package/bin/commands/iterate/merge-state.d.mts +15 -0
- package/bin/commands/iterate/merge-state.mjs +53 -0
- package/bin/commands/iterate/merge.d.mts +13 -0
- package/bin/commands/iterate/merge.mjs +46 -0
- package/bin/commands/poll.mjs +3 -1
- package/bin/config/load.d.mts +4 -0
- package/bin/config/load.mjs +34 -0
- package/bin/config/merge-command-args.d.mts +2 -0
- package/bin/config/merge-command-args.mjs +44 -0
- package/bin/config.json +3 -0
- package/bin/exit-codes.d.mts +2 -0
- package/bin/exit-codes.mjs +4 -0
- package/bin/github/batch-parse-checks.d.mts +3 -0
- package/bin/github/batch-parse-checks.mjs +29 -0
- package/bin/github/batch-parsers-rules.d.mts +3 -1
- package/bin/github/batch-parsers-rules.mjs +33 -0
- package/bin/github/batch-parsers.mjs +23 -26
- package/bin/github/batch-raw-rules.d.mts +48 -0
- package/bin/github/batch-raw-types.mjs +0 -1
- package/bin/github/batch.mjs +2 -0
- package/bin/github/gql/batch-pr.gql +92 -0
- package/bin/github/gql/commit-check-contexts.gql +58 -0
- package/bin/github/merge-queue-checks.d.mts +4 -0
- package/bin/github/merge-queue-checks.mjs +48 -0
- package/bin/github/queries.d.mts +2 -0
- package/bin/github/queries.mjs +2 -0
- package/bin/mcp/server.mjs +1 -0
- package/bin/reporters/agent.mjs +6 -3
- package/bin/types/activity.d.mts +2 -0
- package/bin/types/escalate.d.mts +30 -0
- package/bin/types/escalate.mjs +1 -0
- package/bin/types/github.d.mts +8 -0
- package/bin/types/github.mjs +0 -3
- package/bin/types/iterate.d.mts +14 -31
- package/bin/types/merge-action.d.mts +12 -0
- package/bin/types/merge-action.mjs +1 -0
- package/bin/types/merge-queue.d.mts +13 -0
- package/bin/types/merge-queue.mjs +1 -0
- package/bin/types/merge-requirements.d.mts +17 -0
- package/bin/types/report.d.mts +8 -0
- package/bin/types.d.mts +3 -0
- package/bin/types.mjs +3 -0
- package/bin/util/markdown.d.mts +2 -0
- package/bin/util/markdown.mjs +7 -0
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
- 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,
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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;
|
package/bin/github/batch.mjs
CHANGED
|
@@ -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
|
+
}
|
package/bin/github/queries.d.mts
CHANGED
|
@@ -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). */
|
package/bin/github/queries.mjs
CHANGED
|
@@ -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). */
|
package/bin/mcp/server.mjs
CHANGED
|
@@ -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
|
});
|
package/bin/reporters/agent.mjs
CHANGED
|
@@ -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
|
|
90
|
+
const seenKeys = new Set();
|
|
89
91
|
const result = [];
|
|
90
92
|
for (const c of checks) {
|
|
91
93
|
if (c.runId === null) {
|
|
92
|
-
|
|
94
|
+
const key = `${c.scope ?? "pr"}:${c.commitOid ?? "head"}:${c.name}`;
|
|
95
|
+
if (seenKeys.has(key))
|
|
93
96
|
continue;
|
|
94
|
-
|
|
97
|
+
seenKeys.add(key);
|
|
95
98
|
}
|
|
96
99
|
result.push(toAgentCheck(c));
|
|
97
100
|
}
|
package/bin/types/activity.d.mts
CHANGED
|
@@ -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 {};
|
package/bin/types/github.d.mts
CHANGED
|
@@ -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
|
}
|
package/bin/types/github.mjs
CHANGED
package/bin/types/iterate.d.mts
CHANGED
|
@@ -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,
|
|
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 {};
|