pr-shepherd 0.54.1 → 0.55.1
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 +3 -3
- package/bin/checks/unreported-required.d.mts +29 -0
- package/bin/checks/unreported-required.mjs +52 -0
- package/bin/classify/apply.d.mts +2 -0
- package/bin/classify/apply.mjs +31 -31
- package/bin/classify/rule-action.d.mts +8 -0
- package/bin/classify/rule-action.mjs +29 -0
- package/bin/classify/types.d.mts +1 -1
- 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 +6 -5
- package/bin/cli/help-top-page.d.mts +1 -1
- package/bin/cli/help-top-page.mjs +2 -2
- package/bin/cli/help.d.mts +3 -3
- package/bin/cli/iterate-formatter.mjs +32 -9
- package/bin/cli/iterate-instructions.mjs +6 -0
- package/bin/cli/iterate-lean.mjs +17 -0
- package/bin/cli/poll-summary-emitter.mjs +4 -1
- package/bin/cli/poll-summary-formatter.mjs +10 -3
- package/bin/cli/stack-overview.d.mts +48 -0
- package/bin/cli/stack-overview.mjs +138 -0
- package/bin/commands/check-fingerprint.mjs +2 -1
- package/bin/commands/check-status.d.mts +1 -1
- package/bin/commands/check-status.mjs +8 -3
- package/bin/commands/check-unreported.d.mts +28 -0
- package/bin/commands/check-unreported.mjs +101 -0
- package/bin/commands/check.mjs +45 -38
- package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
- package/bin/commands/commit-suggestion-instruction.mjs +1 -1
- package/bin/commands/iterate/base.mjs +11 -0
- package/bin/commands/iterate/check-instructions.d.mts +1 -1
- package/bin/commands/iterate/check-instructions.mjs +1 -1
- package/bin/commands/iterate/escalate.mjs +4 -0
- package/bin/commands/iterate/index.mjs +69 -3
- package/bin/commands/iterate/merge-state.mjs +12 -12
- package/bin/commands/iterate/merge.d.mts +12 -2
- package/bin/commands/iterate/merge.mjs +32 -2
- package/bin/commands/iterate/unreported-required.d.mts +18 -0
- package/bin/commands/iterate/unreported-required.mjs +136 -0
- package/bin/commands/poll-summary.mjs +1 -0
- package/bin/commands/poll.mjs +9 -2
- package/bin/commands/ready-mergeability.d.mts +1 -1
- package/bin/commands/ready-mergeability.mjs +2 -2
- package/bin/commands/rule-auto-resolve-format.d.mts +22 -0
- package/bin/commands/rule-auto-resolve-format.mjs +151 -0
- package/bin/commands/rule-auto-resolve.d.mts +21 -0
- package/bin/commands/rule-auto-resolve.mjs +113 -0
- package/bin/commands/shepherd-journal.d.mts +1 -1
- package/bin/commands/shepherd-journal.mjs +1 -1
- package/bin/commands/stack-drain.d.mts +0 -5
- package/bin/commands/stack-drain.mjs +26 -16
- package/bin/commands/stack-merge-flag.d.mts +7 -0
- package/bin/commands/stack-merge-flag.mjs +6 -0
- package/bin/config/load.d.mts +3 -0
- package/bin/config/load.mjs +9 -2
- package/bin/config/merge-method.d.mts +27 -0
- package/bin/config/merge-method.mjs +56 -0
- package/bin/exit-codes.d.mts +1 -1
- package/bin/exit-codes.mjs +2 -1
- package/bin/github/batch-parse-suites.d.mts +3 -0
- package/bin/github/batch-parse-suites.mjs +15 -0
- package/bin/github/batch-parsers.d.mts +1 -1
- package/bin/github/batch-parsers.mjs +7 -0
- package/bin/github/batch-raw-types.d.mts +5 -0
- package/bin/github/batch.d.mts +3 -0
- package/bin/github/batch.mjs +9 -1
- package/bin/github/errors.d.mts +6 -0
- package/bin/github/errors.mjs +23 -5
- package/bin/github/fingerprint-fields.d.mts +1 -0
- package/bin/github/fingerprint-fields.mjs +1 -1
- package/bin/github/gql/base-behind.gql +20 -0
- package/bin/github/gql/batch-pr-page.gql +2 -0
- package/bin/github/gql/batch-pr.gql +5 -0
- package/bin/github/gql/commit-check-suites.gql +1 -0
- package/bin/github/gql/poll-stack-summary.gql +6 -0
- package/bin/github/gql/poll-summary-annotation-probe.gql +32 -0
- package/bin/github/gql/poll-summary-check-contexts.gql +2 -3
- package/bin/github/gql/poll-summary-fragment.gql +16 -0
- package/bin/github/gql/pr-merge-policy.gql +1 -44
- package/bin/github/gql/ref-rules-query.gql +18 -0
- package/bin/github/gql/ref-rules.gql +46 -0
- package/bin/github/graphql-internal-retry.d.mts +1 -1
- package/bin/github/graphql-internal-retry.mjs +4 -3
- package/bin/github/merge-queue-checks.mjs +7 -3
- package/bin/github/merge-target-rules.d.mts +28 -0
- package/bin/github/merge-target-rules.mjs +77 -0
- package/bin/github/poll-summary-annotation-probe.d.mts +19 -0
- package/bin/github/poll-summary-annotation-probe.mjs +88 -0
- package/bin/github/poll-summary-fingerprint.mjs +1 -1
- package/bin/github/poll-summary-projector.d.mts +1 -1
- package/bin/github/poll-summary-projector.mjs +19 -12
- package/bin/github/poll-summary-queue-removal.mjs +7 -3
- package/bin/github/poll-summary-raw.d.mts +16 -0
- package/bin/github/poll-summary-readiness.mjs +1 -0
- package/bin/github/poll-summary-route.mjs +5 -0
- package/bin/github/poll-summary-unreported.d.mts +6 -0
- package/bin/github/poll-summary-unreported.mjs +51 -0
- package/bin/github/poll-summary.d.mts +1 -0
- package/bin/github/poll-summary.mjs +28 -3
- package/bin/github/queries.d.mts +9 -0
- package/bin/github/queries.mjs +11 -2
- package/bin/github/queue-removal-freshness.d.mts +20 -5
- package/bin/github/queue-removal-freshness.mjs +37 -10
- package/bin/github/stack-read.d.mts +4 -0
- package/bin/github/stack-read.mjs +13 -1
- package/bin/mcp/server.mjs +6 -1
- package/bin/state/ci-retrigger.d.mts +25 -0
- package/bin/state/ci-retrigger.mjs +48 -0
- package/bin/types/escalate.d.mts +1 -1
- package/bin/types/github.d.mts +11 -0
- package/bin/types/iterate.d.mts +16 -3
- package/bin/types/poll-summary.d.mts +13 -0
- package/bin/types/report.d.mts +34 -1
- 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
- package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +1 -1
- package/src/classify/types.mts +1 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export function uniqueReasons(reasons) {
|
|
2
|
+
const unique = [];
|
|
3
|
+
for (const reason of reasons) {
|
|
4
|
+
const cleaned = reason.trim();
|
|
5
|
+
if (cleaned && !unique.includes(cleaned))
|
|
6
|
+
unique.push(cleaned);
|
|
7
|
+
}
|
|
8
|
+
return unique;
|
|
9
|
+
}
|
|
10
|
+
export function reasonClause(reasons) {
|
|
11
|
+
const unique = uniqueReasons(reasons);
|
|
12
|
+
if (unique.length === 0)
|
|
13
|
+
return undefined;
|
|
14
|
+
const label = unique.length === 1 ? "rule" : "rules";
|
|
15
|
+
return `(${label}: ${unique.join("; ")})`;
|
|
16
|
+
}
|
|
17
|
+
function joinedReason(reasons) {
|
|
18
|
+
const unique = uniqueReasons(reasons);
|
|
19
|
+
return unique.length > 0 ? unique.join("; ") : undefined;
|
|
20
|
+
}
|
|
21
|
+
function plural(count, singular, pluralForm = `${singular}s`) {
|
|
22
|
+
return `${count} ${count === 1 ? singular : pluralForm}`;
|
|
23
|
+
}
|
|
24
|
+
export function ruleAutoResolveBody(counts) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
if (counts.threads > 0)
|
|
27
|
+
parts.push(`auto-resolved ${plural(counts.threads, "thread")}`);
|
|
28
|
+
const minimized = [];
|
|
29
|
+
if (counts.comments > 0)
|
|
30
|
+
minimized.push(plural(counts.comments, "comment"));
|
|
31
|
+
if (counts.reviewSummaries > 0) {
|
|
32
|
+
minimized.push(plural(counts.reviewSummaries, "review summary", "review summaries"));
|
|
33
|
+
}
|
|
34
|
+
if (minimized.length > 0)
|
|
35
|
+
parts.push(`minimized ${minimized.join(" and ")}`);
|
|
36
|
+
return parts.length > 0 ? parts.join(", ") : undefined;
|
|
37
|
+
}
|
|
38
|
+
function withReasonClause(body, reasons) {
|
|
39
|
+
const clause = reasonClause(reasons);
|
|
40
|
+
return clause ? `${body} ${clause}` : body;
|
|
41
|
+
}
|
|
42
|
+
export function formatRuleAutoResolveJournalItem(input) {
|
|
43
|
+
const login = input.viewerLogin?.trim();
|
|
44
|
+
const actor = login ? `as @${login}` : "(token login unavailable)";
|
|
45
|
+
const clause = reasonClause(input.reasons);
|
|
46
|
+
const links = input.urls.map((url) => url.trim()).filter(Boolean);
|
|
47
|
+
const reasonSuffix = clause ? ` ${clause}` : "";
|
|
48
|
+
const linkSuffix = links.length > 0 ? `: ${links.join(", ")}` : "";
|
|
49
|
+
return `- ${input.body} ${actor}${reasonSuffix}${linkSuffix}`;
|
|
50
|
+
}
|
|
51
|
+
export function decorateAutoResolveError(error, reasons) {
|
|
52
|
+
const clause = reasonClause(reasons);
|
|
53
|
+
return clause ? `${error} ${clause}` : error;
|
|
54
|
+
}
|
|
55
|
+
export function reasonsForError(error, ids, ruleReasons) {
|
|
56
|
+
const id = ids.find((candidate) => error.startsWith(`${candidate}:`));
|
|
57
|
+
if (id)
|
|
58
|
+
return [...(ruleReasons.get(id) ?? [])];
|
|
59
|
+
if (!error.startsWith("rate limit:"))
|
|
60
|
+
return [];
|
|
61
|
+
return uniqueReasons(ids.flatMap((candidate) => [...(ruleReasons.get(candidate) ?? [])]));
|
|
62
|
+
}
|
|
63
|
+
function splitJoined(reason) {
|
|
64
|
+
if (!reason)
|
|
65
|
+
return [];
|
|
66
|
+
return reason.split("; ").filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
export function ruleAutoResolveFromReport(report) {
|
|
69
|
+
const threads = report.threads.autoResolved;
|
|
70
|
+
const minimized = report.comments.autoMinimized ?? [];
|
|
71
|
+
const errors = report.threads.autoResolveErrors;
|
|
72
|
+
if (threads.length === 0 && minimized.length === 0 && errors.length === 0)
|
|
73
|
+
return undefined;
|
|
74
|
+
const comments = minimized.filter((item) => item.kind === "pr-comment").length;
|
|
75
|
+
const reviewSummaries = minimized.filter((item) => item.kind === "review-summary").length;
|
|
76
|
+
const success = threads.length + minimized.length > 0;
|
|
77
|
+
const reasons = success
|
|
78
|
+
? uniqueReasons([
|
|
79
|
+
...threads.flatMap((thread) => splitJoined(thread.ruleReason)),
|
|
80
|
+
...minimized.flatMap((item) => splitJoined(item.ruleReason)),
|
|
81
|
+
])
|
|
82
|
+
: uniqueReasons(report.threads.autoResolveErrorReasons ?? []);
|
|
83
|
+
const body = ruleAutoResolveBody({ threads: threads.length, comments, reviewSummaries }) ??
|
|
84
|
+
`auto-resolve failed for ${plural(errors.length, "mutation")}`;
|
|
85
|
+
return {
|
|
86
|
+
summary: withReasonClause(body, reasons),
|
|
87
|
+
...(threads.length > 0 && { threads }),
|
|
88
|
+
...(minimized.length > 0 && { minimized }),
|
|
89
|
+
...(errors.length > 0 && { errors }),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function formatRuleAutoResolveSection(event) {
|
|
93
|
+
if (!event)
|
|
94
|
+
return null;
|
|
95
|
+
const lines = ["## Classification auto-resolve", "", event.summary];
|
|
96
|
+
const links = [
|
|
97
|
+
...(event.threads ?? []).map((thread) => thread.url.trim() || `\`${thread.id}\``),
|
|
98
|
+
...(event.minimized ?? []).map((item) => item.url?.trim() || `\`${item.id}\``),
|
|
99
|
+
];
|
|
100
|
+
if (links.length > 0)
|
|
101
|
+
lines.push("", ...links.map((link) => `- ${link}`));
|
|
102
|
+
if ((event.errors?.length ?? 0) > 0) {
|
|
103
|
+
lines.push("", ...(event.errors ?? []).map((error) => `- ${error}`));
|
|
104
|
+
}
|
|
105
|
+
return lines.join("\n");
|
|
106
|
+
}
|
|
107
|
+
export function projectRuleAutoResolve(event) {
|
|
108
|
+
return {
|
|
109
|
+
summary: event.summary,
|
|
110
|
+
...((event.threads?.length ?? 0) > 0 && { threads: event.threads }),
|
|
111
|
+
...((event.minimized?.length ?? 0) > 0 && { minimized: event.minimized }),
|
|
112
|
+
...((event.errors?.length ?? 0) > 0 && { errors: event.errors }),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function insertRuleAutoResolveSection(text, event) {
|
|
116
|
+
const section = formatRuleAutoResolveSection(event);
|
|
117
|
+
if (!section)
|
|
118
|
+
return text;
|
|
119
|
+
const index = text.lastIndexOf("## Instructions");
|
|
120
|
+
if (index === -1)
|
|
121
|
+
return `${text}\n\n${section}`;
|
|
122
|
+
return `${text.slice(0, index)}${section}\n\n${text.slice(index)}`;
|
|
123
|
+
}
|
|
124
|
+
export function stripReplayedRuleAutoResolve(report) {
|
|
125
|
+
const hasEvent = report.threads.autoResolved.length > 0 ||
|
|
126
|
+
report.threads.autoResolveErrors.length > 0 ||
|
|
127
|
+
(report.threads.autoResolveErrorReasons?.length ?? 0) > 0 ||
|
|
128
|
+
(report.comments.autoMinimized?.length ?? 0) > 0;
|
|
129
|
+
if (!hasEvent)
|
|
130
|
+
return report;
|
|
131
|
+
const { autoMinimized: _autoMinimized, ...comments } = report.comments;
|
|
132
|
+
const { autoResolveErrorReasons: _autoResolveErrorReasons, ...threads } = report.threads;
|
|
133
|
+
return {
|
|
134
|
+
...report,
|
|
135
|
+
threads: { ...threads, autoResolved: [], autoResolveErrors: [] },
|
|
136
|
+
comments,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function minimizedItem(kind, id, url, reasons) {
|
|
140
|
+
const ruleReason = joinedReason(reasons);
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
kind,
|
|
144
|
+
...(url?.trim() ? { url: url.trim() } : {}),
|
|
145
|
+
...(ruleReason ? { ruleReason } : {}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export function resolvedThread(thread, reasons) {
|
|
149
|
+
const ruleReason = joinedReason(reasons);
|
|
150
|
+
return { ...thread, isResolved: true, ...(ruleReason ? { ruleReason } : {}) };
|
|
151
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BatchPartition } from "../classify/apply.mts";
|
|
2
|
+
import type { AutoMinimizedItem, AutoResolvedThread, BatchPrData } from "../types.mts";
|
|
3
|
+
export interface RuleAutoResolveApplied {
|
|
4
|
+
threadIds: string[];
|
|
5
|
+
commentIds: string[];
|
|
6
|
+
reviewSummaryIds: string[];
|
|
7
|
+
autoResolved: AutoResolvedThread[];
|
|
8
|
+
autoMinimized: AutoMinimizedItem[];
|
|
9
|
+
autoResolveErrors: string[];
|
|
10
|
+
errorReasons: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function applySuppressedRuleAutoResolve(input: {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
partition: BatchPartition;
|
|
15
|
+
batch: BatchPrData;
|
|
16
|
+
prNumber: number;
|
|
17
|
+
repo: {
|
|
18
|
+
owner: string;
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
}): Promise<RuleAutoResolveApplied>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { autoMinimizeComments, autoResolveThreads } from "../comments/resolve.mjs";
|
|
2
|
+
import { runJournal } from "./journal/index.mjs";
|
|
3
|
+
import { decorateAutoResolveError, formatRuleAutoResolveJournalItem, minimizedItem, reasonClause, reasonsForError, resolvedThread, ruleAutoResolveBody, uniqueReasons, } from "./rule-auto-resolve-format.mjs";
|
|
4
|
+
const EMPTY = {
|
|
5
|
+
threadIds: [],
|
|
6
|
+
commentIds: [],
|
|
7
|
+
reviewSummaryIds: [],
|
|
8
|
+
autoResolved: [],
|
|
9
|
+
autoMinimized: [],
|
|
10
|
+
autoResolveErrors: [],
|
|
11
|
+
errorReasons: [],
|
|
12
|
+
};
|
|
13
|
+
function idsIn(ids, confirmed) {
|
|
14
|
+
return ids.filter((id) => confirmed.has(id));
|
|
15
|
+
}
|
|
16
|
+
function decorateErrors(errors, ids, ruleReasons) {
|
|
17
|
+
return errors.map((error) => decorateAutoResolveError(error, reasonsForError(error, ids, ruleReasons)));
|
|
18
|
+
}
|
|
19
|
+
function successReasons(ids, ruleReasons) {
|
|
20
|
+
return uniqueReasons(ids.flatMap((id) => [...(ruleReasons.get(id) ?? [])]));
|
|
21
|
+
}
|
|
22
|
+
export async function applySuppressedRuleAutoResolve(input) {
|
|
23
|
+
if (!input.enabled) {
|
|
24
|
+
return {
|
|
25
|
+
...EMPTY,
|
|
26
|
+
threadIds: input.partition.ruleAutoResolveThreadIds,
|
|
27
|
+
commentIds: input.partition.ruleAutoResolveCommentIds,
|
|
28
|
+
reviewSummaryIds: input.partition.ruleAutoResolveReviewSummaryIds,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const commentIds = input.partition.ruleAutoResolveCommentIds.filter((id) => input.partition.suppressedCommentIds.has(id));
|
|
32
|
+
const reviewSummaryIds = input.partition.ruleAutoResolveReviewSummaryIds.filter((id) => input.partition.suppressedReviewSummaryIds.has(id));
|
|
33
|
+
const threadIds = input.partition.ruleAutoResolveThreadIds.filter((id) => input.partition.suppressedThreadIds.has(id));
|
|
34
|
+
const minimizeIds = [...commentIds, ...reviewSummaryIds];
|
|
35
|
+
const [minimized, resolved] = await Promise.all([
|
|
36
|
+
minimizeIds.length > 0
|
|
37
|
+
? autoMinimizeComments(minimizeIds)
|
|
38
|
+
: Promise.resolve({ minimized: [], errors: [] }),
|
|
39
|
+
threadIds.length > 0
|
|
40
|
+
? autoResolveThreads(threadIds)
|
|
41
|
+
: Promise.resolve({ resolved: [], errors: [] }),
|
|
42
|
+
]);
|
|
43
|
+
const minimizedSet = new Set(minimized.minimized);
|
|
44
|
+
const resolvedSet = new Set(resolved.resolved);
|
|
45
|
+
const resolvedIds = idsIn(threadIds, resolvedSet);
|
|
46
|
+
const minimizedCommentIds = idsIn(commentIds, minimizedSet);
|
|
47
|
+
const minimizedSummaryIds = idsIn(reviewSummaryIds, minimizedSet);
|
|
48
|
+
const threadsById = new Map(input.batch.reviewThreads.map((thread) => [thread.id, thread]));
|
|
49
|
+
const commentsById = new Map(input.batch.comments.map((comment) => [comment.id, comment]));
|
|
50
|
+
const summariesById = new Map(input.batch.reviewSummaries.map((review) => [review.id, review]));
|
|
51
|
+
const autoResolved = resolvedIds.flatMap((id) => {
|
|
52
|
+
const thread = threadsById.get(id);
|
|
53
|
+
return thread ? [resolvedThread(thread, input.partition.ruleReasons.get(id) ?? [])] : [];
|
|
54
|
+
});
|
|
55
|
+
const autoMinimized = [
|
|
56
|
+
...minimizedCommentIds.map((id) => minimizedItem("pr-comment", id, commentsById.get(id)?.url, input.partition.ruleReasons.get(id) ?? [])),
|
|
57
|
+
...minimizedSummaryIds.map((id) => minimizedItem("review-summary", id, summariesById.get(id)?.url, input.partition.ruleReasons.get(id) ?? [])),
|
|
58
|
+
];
|
|
59
|
+
const autoResolveErrors = [
|
|
60
|
+
...decorateErrors(minimized.errors, minimizeIds, input.partition.ruleReasons),
|
|
61
|
+
...decorateErrors(resolved.errors, threadIds, input.partition.ruleReasons),
|
|
62
|
+
];
|
|
63
|
+
const errorReasons = uniqueReasons([
|
|
64
|
+
...minimized.errors.flatMap((error) => reasonsForError(error, minimizeIds, input.partition.ruleReasons)),
|
|
65
|
+
...resolved.errors.flatMap((error) => reasonsForError(error, threadIds, input.partition.ruleReasons)),
|
|
66
|
+
]);
|
|
67
|
+
const journalError = await journalSuccesses(input, autoResolved, autoMinimized);
|
|
68
|
+
if (journalError)
|
|
69
|
+
autoResolveErrors.push(journalError);
|
|
70
|
+
return {
|
|
71
|
+
threadIds: input.partition.ruleAutoResolveThreadIds.filter((id) => !resolvedSet.has(id)),
|
|
72
|
+
commentIds: input.partition.ruleAutoResolveCommentIds.filter((id) => !minimizedSet.has(id)),
|
|
73
|
+
reviewSummaryIds: input.partition.ruleAutoResolveReviewSummaryIds.filter((id) => !minimizedSet.has(id)),
|
|
74
|
+
autoResolved,
|
|
75
|
+
autoMinimized,
|
|
76
|
+
autoResolveErrors,
|
|
77
|
+
errorReasons,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function journalSuccesses(input, autoResolved, autoMinimized) {
|
|
81
|
+
const body = ruleAutoResolveBody({
|
|
82
|
+
threads: autoResolved.length,
|
|
83
|
+
comments: autoMinimized.filter((item) => item.kind === "pr-comment").length,
|
|
84
|
+
reviewSummaries: autoMinimized.filter((item) => item.kind === "review-summary").length,
|
|
85
|
+
});
|
|
86
|
+
if (!body)
|
|
87
|
+
return undefined;
|
|
88
|
+
const ids = [...autoResolved.map((thread) => thread.id), ...autoMinimized.map((item) => item.id)];
|
|
89
|
+
const reasons = successReasons(ids, input.partition.ruleReasons);
|
|
90
|
+
const item = formatRuleAutoResolveJournalItem({
|
|
91
|
+
body,
|
|
92
|
+
reasons,
|
|
93
|
+
...(input.batch.viewerLogin ? { viewerLogin: input.batch.viewerLogin } : {}),
|
|
94
|
+
urls: [
|
|
95
|
+
...autoResolved.map((thread) => thread.url),
|
|
96
|
+
...autoMinimized.map((item) => item.url ?? ""),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
await runJournal({
|
|
101
|
+
prNumber: input.prNumber,
|
|
102
|
+
targetRepository: { owner: input.repo.owner, name: input.repo.name },
|
|
103
|
+
rawItem: item,
|
|
104
|
+
dryRun: false,
|
|
105
|
+
});
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
110
|
+
const clause = reasonClause(reasons);
|
|
111
|
+
return clause ? `journal: ${message} ${clause}` : `journal: ${message}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -9,6 +9,6 @@ export declare const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review each body un
|
|
|
9
9
|
* Build the Shepherd Journal instruction step. The reference-citation convention (link
|
|
10
10
|
* threads/comments from their headings, cite reviews by ID) is invariant across every
|
|
11
11
|
* invocation, so it lives in the pr-shepherd skill's "Shepherd Journal" playbook instead
|
|
12
|
-
* of being re-emitted every tick (see
|
|
12
|
+
* of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts minimal").
|
|
13
13
|
*/
|
|
14
14
|
export declare function buildShepherdJournalInstruction(prReference: string | number): string;
|
|
@@ -10,7 +10,7 @@ export const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review each body under `##
|
|
|
10
10
|
* Build the Shepherd Journal instruction step. The reference-citation convention (link
|
|
11
11
|
* threads/comments from their headings, cite reviews by ID) is invariant across every
|
|
12
12
|
* invocation, so it lives in the pr-shepherd skill's "Shepherd Journal" playbook instead
|
|
13
|
-
* of being re-emitted every tick (see
|
|
13
|
+
* of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts minimal").
|
|
14
14
|
*/
|
|
15
15
|
export function buildShepherdJournalInstruction(prReference) {
|
|
16
16
|
// Single-quote the placeholder so a substituted decision stays literal in the shell.
|
|
@@ -7,11 +7,6 @@ export interface StackPlan {
|
|
|
7
7
|
idle?: PollSummaryItem[];
|
|
8
8
|
instructions: string[];
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Merge the ready prefix by its highest PR number. `gh stack merge <n>` tries a
|
|
12
|
-
* stack number first, but stack numbers come from the repository's issue and
|
|
13
|
-
* pull request sequence, so a PR number never names a stack.
|
|
14
|
-
*/
|
|
15
10
|
export declare function planPrefixDrain(result: PollSummaryResult, mergeRequested: boolean): StackPlan | undefined;
|
|
16
11
|
/**
|
|
17
12
|
* A merge-requested, fully ready stack whose bottom open layer still targets
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
+
import { stackMergeFlag } from "./stack-merge-flag.mjs";
|
|
1
2
|
import { stackLayerBlockReason } from "./stack-layer-readiness.mjs";
|
|
2
3
|
import { appendMarkReadyInstructions, splitStackWork } from "./stack-work.mjs";
|
|
3
|
-
/**
|
|
4
|
-
* Highest open layer such that it and every open layer below it are ready to
|
|
5
|
-
* merge together. `gh stack merge <PR>` lands that PR and every unmerged layer
|
|
6
|
-
* below it. A merge queue accepts the same prefix and evaluates each layer
|
|
7
|
-
* from the bottom; a failure ejects that layer and those above it.
|
|
8
|
-
*/
|
|
9
4
|
function readyPrefixTop(result) {
|
|
10
5
|
const layers = [...result.prs].sort((left, right) => stackPosition(left) - stackPosition(right));
|
|
11
6
|
if (layers.some((item) => item.state === "OPEN" && item.isInMergeQueue))
|
|
@@ -29,11 +24,6 @@ function readyPrefixTop(result) {
|
|
|
29
24
|
}
|
|
30
25
|
return top;
|
|
31
26
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Merge the ready prefix by its highest PR number. `gh stack merge <n>` tries a
|
|
34
|
-
* stack number first, but stack numbers come from the repository's issue and
|
|
35
|
-
* pull request sequence, so a PR number never names a stack.
|
|
36
|
-
*/
|
|
37
27
|
export function planPrefixDrain(result, mergeRequested) {
|
|
38
28
|
if (!mergeRequested)
|
|
39
29
|
return undefined;
|
|
@@ -49,8 +39,16 @@ export function planPrefixDrain(result, mergeRequested) {
|
|
|
49
39
|
item.action !== "escalate" &&
|
|
50
40
|
(!isStackLayerReady(item) || staleChildren.has(item.pr))), staleChildren);
|
|
51
41
|
const span = open[0]?.pr === top.pr ? "that layer alone" : `PR #${top.pr} and every unmerged layer below it`;
|
|
42
|
+
const method = stackMergeFlag(result.allowedMergeMethods);
|
|
43
|
+
if ("unavailable" in method) {
|
|
44
|
+
return {
|
|
45
|
+
action: "escalate",
|
|
46
|
+
stackMergeable: false,
|
|
47
|
+
instructions: [`1. ${method.unavailable}`],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
52
50
|
const instructions = [
|
|
53
|
-
`1. PR #${top.pr} is the highest open layer of stack #${top.stack.number} in \`${result.repo}\` whose open lower layers are all ready. Run \`GH_REPO=${result.repo} gh stack merge ${top.pr} --yes
|
|
51
|
+
`1. PR #${top.pr} is the highest open layer of stack #${top.stack.number} in \`${result.repo}\` whose open lower layers are all ready. Run \`GH_REPO=${result.repo} gh stack merge ${top.pr} --yes ${method.flag}\` to merge ${span}. When the base uses a merge queue, the same command queues that prefix together and GitHub evaluates each layer from the bottom; a failure ejects that layer and the layers above it. If \`gh stack\` is an unknown command, run \`gh extension install github/gh-stack\` first.`,
|
|
54
52
|
];
|
|
55
53
|
appendAutonomousInstructions(instructions, above.sessions);
|
|
56
54
|
appendMarkReadyInstructions(instructions, above.markReady);
|
|
@@ -90,17 +88,29 @@ export function idleWaitPlan(idle) {
|
|
|
90
88
|
],
|
|
91
89
|
};
|
|
92
90
|
}
|
|
91
|
+
function sessionInstruction(item) {
|
|
92
|
+
if (!item.owned)
|
|
93
|
+
return ` PR #${item.pr} is not owned. Do not run a session for it.`;
|
|
94
|
+
const removal = item.queueRemoval
|
|
95
|
+
? ` PR #${item.pr} was removed from the merge queue (\`${item.queueRemoval.reason ?? "unknown reason"}\`${item.queueRemoval.actor ? ` by @${item.queueRemoval.actor}` : ""}).`
|
|
96
|
+
: "";
|
|
97
|
+
if (!item.pollCommand) {
|
|
98
|
+
return ` PR #${item.pr} needs a one-PR Shepherd session, but no command was available.`;
|
|
99
|
+
}
|
|
100
|
+
const queueNote = item.queueRemoval
|
|
101
|
+
? " That session fixes failing queue CI, or escalates when the removal has no concrete fix."
|
|
102
|
+
: "";
|
|
103
|
+
return `${removal} Run \`${item.pollCommand}\` for PR #${item.pr}.${queueNote}`;
|
|
104
|
+
}
|
|
93
105
|
export function describeIdleLayers(idle) {
|
|
94
106
|
return idle.map((item) => `PR #${item.pr} (${item.reasons.join(", ")})`).join("; ");
|
|
95
107
|
}
|
|
96
108
|
export function appendAutonomousInstructions(instructions, candidates) {
|
|
97
109
|
if (candidates.length === 0)
|
|
98
110
|
return;
|
|
99
|
-
instructions.push(`${instructions.length + 1}. Start or delegate
|
|
111
|
+
instructions.push(`${instructions.length + 1}. Start or delegate one-PR sessions only for rows marked \`owned\`. Leave every other author's layer untouched. Owned layers can proceed concurrently.`);
|
|
100
112
|
for (const item of candidates) {
|
|
101
|
-
instructions.push(item
|
|
102
|
-
? `${instructions.length + 1}. Run \`${item.pollCommand}\` for PR #${item.pr}${item.queueRemoval ? `; GitHub removed it from the merge queue (${item.queueRemoval.reason ?? "unknown reason"})` : ""}.`
|
|
103
|
-
: `${instructions.length + 1}. PR #${item.pr} needs a one-PR Shepherd session, but no command was available.`);
|
|
113
|
+
instructions.push(`${instructions.length + 1}.${sessionInstruction(item)}`);
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
export function findHumanHandoffs(result) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type MergeMethod } from "../config/merge-method.mts";
|
|
2
|
+
/** Stack commands prefer squash unless config or the repository selects another method. */
|
|
3
|
+
export declare function stackMergeFlag(allowed: readonly MergeMethod[] | undefined): {
|
|
4
|
+
flag: string;
|
|
5
|
+
} | {
|
|
6
|
+
unavailable: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { loadConfig } from "../config/load.mjs";
|
|
2
|
+
import { configuredMergeMethod, mergeMethodFlag, } from "../config/merge-method.mjs";
|
|
3
|
+
/** Stack commands prefer squash unless config or the repository selects another method. */
|
|
4
|
+
export function stackMergeFlag(allowed) {
|
|
5
|
+
return mergeMethodFlag(allowed, "squash", configuredMergeMethod(loadConfig().merge ?? {}));
|
|
6
|
+
}
|
package/bin/config/load.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MergeMethod } from "./merge-method.mts";
|
|
1
2
|
declare const MINIMIZE_COMMENTS_POLICIES: readonly ["all", "bots", "users", "none"];
|
|
2
3
|
export type MinimizeCommentsPolicy = (typeof MINIMIZE_COMMENTS_POLICIES)[number];
|
|
3
4
|
declare const RESOLVE_OTHER_HUMAN_THREADS: readonly ["none", "outdated", "always"];
|
|
@@ -70,6 +71,8 @@ export interface PrShepherdConfig {
|
|
|
70
71
|
blockingReviewerLogins: string[];
|
|
71
72
|
};
|
|
72
73
|
merge?: {
|
|
74
|
+
/** Preferred merge method. Used only when the repository allows it. */
|
|
75
|
+
method?: MergeMethod;
|
|
73
76
|
/** Options added to ordinary `gh pr merge` commands. Queue commands never use these. */
|
|
74
77
|
commandArgs: string[];
|
|
75
78
|
};
|
package/bin/config/load.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { parse } from "yaml";
|
|
|
6
6
|
import builtins from "../config.json" with { type: "json" };
|
|
7
7
|
import { getEffectiveCwd } from "../execution-context.mjs";
|
|
8
8
|
import { findMergeStrategies } from "./merge-command-args.mjs";
|
|
9
|
+
import { parseMergeMethod } from "./merge-method.mjs";
|
|
9
10
|
const MINIMIZE_COMMENTS_POLICIES = ["all", "bots", "users", "none"];
|
|
10
11
|
const RESOLVE_OTHER_HUMAN_THREADS = ["none", "outdated", "always"];
|
|
11
12
|
const RC_FILENAME = ".pr-shepherdrc.yml";
|
|
@@ -143,7 +144,7 @@ function parseMergeCommandArgs(value) {
|
|
|
143
144
|
if (strategies.length > 1) {
|
|
144
145
|
throw new Error(`Invalid config: merge.commandArgs includes multiple merge strategies: ${strategies.join(", ")}`);
|
|
145
146
|
}
|
|
146
|
-
return
|
|
147
|
+
return [...value];
|
|
147
148
|
}
|
|
148
149
|
function parsePollConfig(value) {
|
|
149
150
|
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -279,7 +280,7 @@ const KNOWN_NESTED_KEYS = {
|
|
|
279
280
|
resolve: new Set(["shaPoll"]),
|
|
280
281
|
checks: new Set(["ciTriggerEvents", "ignoreLogLines"]),
|
|
281
282
|
mergeStatus: new Set(["blockingReviewerLogins"]),
|
|
282
|
-
merge: new Set(["commandArgs"]),
|
|
283
|
+
merge: new Set(["commandArgs", "method"]),
|
|
283
284
|
actions: new Set([
|
|
284
285
|
"autoMinimizeSuppressed",
|
|
285
286
|
"autoMarkReady",
|
|
@@ -321,6 +322,12 @@ function parseConfig(value, normalizeMergeArgs = true) {
|
|
|
321
322
|
config.actions.neverCancelRuns = parseNeverCancelRuns(config.actions.neverCancelRuns);
|
|
322
323
|
if (config.merge && normalizeMergeArgs) {
|
|
323
324
|
config.merge.commandArgs = parseMergeCommandArgs(config.merge.commandArgs);
|
|
325
|
+
if (config.merge.method !== undefined)
|
|
326
|
+
config.merge.method = parseMergeMethod(config.merge.method);
|
|
327
|
+
const fromArgs = findMergeStrategies(config.merge.commandArgs)[0];
|
|
328
|
+
if (config.merge.method && fromArgs && fromArgs !== config.merge.method) {
|
|
329
|
+
throw new Error(`Invalid config: merge.method ${config.merge.method} conflicts with merge.commandArgs strategy ${fromArgs}`);
|
|
330
|
+
}
|
|
324
331
|
}
|
|
325
332
|
config.poll = parsePollConfig(config.poll);
|
|
326
333
|
config.watch.graphqlQuotaWarnings = parseGraphqlQuotaWarnings(config.watch.graphqlQuotaWarnings, config.poll.intervalSeconds);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type MergeMethod = "merge" | "squash" | "rebase";
|
|
2
|
+
export interface RepositoryMergeSettings {
|
|
3
|
+
mergeCommitAllowed?: boolean;
|
|
4
|
+
squashMergeAllowed?: boolean;
|
|
5
|
+
rebaseMergeAllowed?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** `undefined` when the query did not select repository merge settings. */
|
|
8
|
+
export declare function readAllowedMergeMethods(repository: RepositoryMergeSettings | null | undefined): MergeMethod[] | undefined;
|
|
9
|
+
export declare function parseMergeMethod(value: unknown): MergeMethod;
|
|
10
|
+
export declare function configuredMergeMethod(merge: {
|
|
11
|
+
method?: MergeMethod;
|
|
12
|
+
commandArgs?: string[];
|
|
13
|
+
}): MergeMethod | null;
|
|
14
|
+
export declare function chooseMergeMethod(input: {
|
|
15
|
+
allowed: readonly MergeMethod[] | undefined;
|
|
16
|
+
configured: MergeMethod | null;
|
|
17
|
+
fallback: MergeMethod;
|
|
18
|
+
}): {
|
|
19
|
+
method: MergeMethod;
|
|
20
|
+
} | {
|
|
21
|
+
unavailable: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function mergeMethodFlag(allowed: readonly MergeMethod[] | undefined, fallback: MergeMethod, configured: MergeMethod | null): {
|
|
24
|
+
flag: string;
|
|
25
|
+
} | {
|
|
26
|
+
unavailable: string;
|
|
27
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { findMergeStrategies } from "./merge-command-args.mjs";
|
|
2
|
+
const MERGE_METHODS = new Set(["merge", "squash", "rebase"]);
|
|
3
|
+
const METHOD_ORDER = ["merge", "squash", "rebase"];
|
|
4
|
+
/** `undefined` when the query did not select repository merge settings. */
|
|
5
|
+
export function readAllowedMergeMethods(repository) {
|
|
6
|
+
if (!repository ||
|
|
7
|
+
(repository.mergeCommitAllowed === undefined &&
|
|
8
|
+
repository.squashMergeAllowed === undefined &&
|
|
9
|
+
repository.rebaseMergeAllowed === undefined)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const allowed = [];
|
|
13
|
+
if (repository.mergeCommitAllowed)
|
|
14
|
+
allowed.push("merge");
|
|
15
|
+
if (repository.squashMergeAllowed)
|
|
16
|
+
allowed.push("squash");
|
|
17
|
+
if (repository.rebaseMergeAllowed)
|
|
18
|
+
allowed.push("rebase");
|
|
19
|
+
return allowed;
|
|
20
|
+
}
|
|
21
|
+
export function parseMergeMethod(value) {
|
|
22
|
+
if (typeof value === "string" && MERGE_METHODS.has(value)) {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
throw new Error("Invalid config: merge.method must be merge, squash, or rebase");
|
|
26
|
+
}
|
|
27
|
+
export function configuredMergeMethod(merge) {
|
|
28
|
+
const fromArgs = findMergeStrategies(merge.commandArgs ?? [])[0];
|
|
29
|
+
return merge.method ?? fromArgs ?? null;
|
|
30
|
+
}
|
|
31
|
+
export function chooseMergeMethod(input) {
|
|
32
|
+
if (input.allowed === undefined)
|
|
33
|
+
return { method: input.configured ?? input.fallback };
|
|
34
|
+
if (input.configured) {
|
|
35
|
+
if (input.allowed.includes(input.configured))
|
|
36
|
+
return { method: input.configured };
|
|
37
|
+
const allowedText = input.allowed.length > 0 ? input.allowed.join(", ") : "none";
|
|
38
|
+
return {
|
|
39
|
+
unavailable: `Configured merge method \`${input.configured}\` is not allowed by this repository. Allowed methods: ${allowedText}.`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (input.allowed.includes(input.fallback))
|
|
43
|
+
return { method: input.fallback };
|
|
44
|
+
const method = METHOD_ORDER.find((item) => input.allowed?.includes(item));
|
|
45
|
+
if (method)
|
|
46
|
+
return { method };
|
|
47
|
+
return {
|
|
48
|
+
unavailable: "This repository allows no merge method (merge commits, squash, and rebase are all disabled).",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function mergeMethodFlag(allowed, fallback, configured) {
|
|
52
|
+
const decision = chooseMergeMethod({ allowed, configured, fallback });
|
|
53
|
+
if ("unavailable" in decision)
|
|
54
|
+
return decision;
|
|
55
|
+
return { flag: `--${decision.method}` };
|
|
56
|
+
}
|
package/bin/exit-codes.d.mts
CHANGED
|
@@ -13,7 +13,7 @@ import type { IterateResult } from "./types.mts";
|
|
|
13
13
|
export declare const EXIT: Readonly<{
|
|
14
14
|
/** `cancel` + `merged` or `ready-delay-elapsed` — shepherd finished cleanly. */
|
|
15
15
|
readonly OK: 0;
|
|
16
|
-
/** Nothing to do yet
|
|
16
|
+
/** Nothing to do yet, or a clean PR whose ready-delay is still counting. */
|
|
17
17
|
readonly WAIT: 10;
|
|
18
18
|
/** Draft PR converted to ready for review. */
|
|
19
19
|
readonly MARK_READY: 11;
|
package/bin/exit-codes.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
export const EXIT = Object.freeze({
|
|
13
13
|
/** `cancel` + `merged` or `ready-delay-elapsed` — shepherd finished cleanly. */
|
|
14
14
|
OK: 0,
|
|
15
|
-
/** Nothing to do yet
|
|
15
|
+
/** Nothing to do yet, or a clean PR whose ready-delay is still counting. */
|
|
16
16
|
WAIT: 10,
|
|
17
17
|
/** Draft PR converted to ready for review. */
|
|
18
18
|
MARK_READY: 11,
|
|
@@ -62,6 +62,7 @@ export function iterateResultToExitCode(result) {
|
|
|
62
62
|
case "cancel":
|
|
63
63
|
return CANCEL_REASON_EXIT_CODE[result.reason];
|
|
64
64
|
case "wait":
|
|
65
|
+
case "ready":
|
|
65
66
|
return EXIT.WAIT;
|
|
66
67
|
case "mark_ready":
|
|
67
68
|
return EXIT.MARK_READY;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { WorkflowSuiteSnapshot } from "../checks/unreported-required.mts";
|
|
1
2
|
import type { CheckRun } from "../types.mts";
|
|
2
3
|
import type { RawPr } from "./batch-raw-types.mts";
|
|
3
4
|
export declare function parseCheckSuitesComplete(raw: RawPr): boolean;
|
|
4
5
|
/** True only when the head commit's suite page finished and listed nothing. */
|
|
5
6
|
export declare function parseHeadCheckSuitesEmpty(raw: RawPr): boolean;
|
|
7
|
+
/** Actions suites on the head. Third-party suites with no workflow run are omitted. */
|
|
8
|
+
export declare function parseHeadWorkflowSuites(raw: RawPr): WorkflowSuiteSnapshot[];
|
|
6
9
|
export declare function parseSuiteStartupFailures(raw: RawPr): CheckRun[];
|
|
@@ -6,6 +6,21 @@ export function parseHeadCheckSuitesEmpty(raw) {
|
|
|
6
6
|
const suites = raw.commits.nodes[0]?.commit.checkSuites;
|
|
7
7
|
return suites?.pageInfo.hasNextPage === false && suites.nodes.length === 0;
|
|
8
8
|
}
|
|
9
|
+
/** Actions suites on the head. Third-party suites with no workflow run are omitted. */
|
|
10
|
+
export function parseHeadWorkflowSuites(raw) {
|
|
11
|
+
const nodes = raw.commits.nodes[0]?.commit.checkSuites?.nodes ?? [];
|
|
12
|
+
return nodes.flatMap((node) => {
|
|
13
|
+
if (!node.workflowRun)
|
|
14
|
+
return [];
|
|
15
|
+
return [
|
|
16
|
+
{
|
|
17
|
+
status: node.status ?? null,
|
|
18
|
+
conclusion: node.conclusion,
|
|
19
|
+
workflowRun: { event: node.workflowRun.event },
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
});
|
|
23
|
+
}
|
|
9
24
|
export function parseSuiteStartupFailures(raw) {
|
|
10
25
|
const suites = raw.commits.nodes[0]?.commit.checkSuites;
|
|
11
26
|
if (!suites)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { BatchPrData } from "../types.mts";
|
|
2
2
|
import type { RawBatchResponse, RawPr, RawThread, RawComment, RawReview, RawReviewSummary, RawContextNode } from "./batch-raw-types.mts";
|
|
3
|
-
export declare function parseRawPr(raw: RawPr, rawThreadPages: RawThread[], rawCommentNodes: RawComment[], rawReviewNodes: RawReview[], rawReviewSummaryNodes: RawReviewSummary[], rawApprovedReviewNodes: RawReviewSummary[], rawCheckNodes: RawContextNode[], repository?: Pick<NonNullable<RawBatchResponse["repository"]>, "viewerPermission" | "viewerCanAdminister">): BatchPrData;
|
|
3
|
+
export declare function parseRawPr(raw: RawPr, rawThreadPages: RawThread[], rawCommentNodes: RawComment[], rawReviewNodes: RawReview[], rawReviewSummaryNodes: RawReviewSummary[], rawApprovedReviewNodes: RawReviewSummary[], rawCheckNodes: RawContextNode[], repository?: Pick<NonNullable<RawBatchResponse["repository"]>, "viewerPermission" | "viewerCanAdminister" | "mergeCommitAllowed" | "squashMergeAllowed" | "rebaseMergeAllowed">): BatchPrData;
|