pr-shepherd 0.42.0 → 0.44.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 +24 -18
- package/bin/api.d.mts +3 -2
- package/bin/api.mjs +13 -18
- package/bin/checks/classify.d.mts +3 -1
- package/bin/checks/classify.mjs +5 -2
- package/bin/cli/args.mjs +14 -9
- package/bin/cli/default-poll.mjs +1 -0
- package/bin/cli/fix-formatter.mjs +20 -8
- package/bin/cli/handlers.mjs +1 -0
- package/bin/cli/help-command-pages.d.mts +9 -6
- package/bin/cli/help-command-pages.mjs +7 -4
- package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
- package/bin/cli/help-iterate-poll-pages.mjs +9 -4
- 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 +10 -7
- package/bin/cli/iterate-flags.d.mts +1 -0
- package/bin/cli/iterate-flags.mjs +2 -0
- package/bin/cli/iterate-formatter.d.mts +1 -1
- package/bin/cli/iterate-formatter.mjs +9 -6
- package/bin/cli/iterate-instructions.mjs +15 -0
- package/bin/cli/iterate-lean.d.mts +1 -2
- package/bin/cli/iterate-lean.mjs +16 -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/journal-formatter.mjs +3 -0
- package/bin/cli/journal-handler.mjs +11 -7
- package/bin/cli/list-formatters.d.mts +3 -1
- package/bin/cli/list-formatters.mjs +9 -7
- package/bin/cli/mark-files-as-viewed-formatter.mjs +7 -1
- package/bin/cli/mutate-formatter.mjs +4 -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 +64 -7
- package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
- package/bin/commands/commit-suggestion-instruction.mjs +2 -2
- package/bin/commands/commit-suggestion.mjs +3 -1
- package/bin/commands/iterate/base.d.mts +5 -0
- package/bin/commands/iterate/base.mjs +25 -0
- package/bin/commands/iterate/check-instructions.d.mts +5 -19
- package/bin/commands/iterate/check-instructions.mjs +24 -25
- package/bin/commands/iterate/classify.d.mts +2 -2
- package/bin/commands/iterate/classify.mjs +43 -25
- package/bin/commands/iterate/escalate.d.mts +3 -1
- package/bin/commands/iterate/escalate.mjs +29 -2
- package/bin/commands/iterate/fix-code.mjs +81 -26
- package/bin/commands/iterate/helpers.d.mts +0 -2
- package/bin/commands/iterate/helpers.mjs +6 -17
- package/bin/commands/iterate/index.mjs +31 -43
- package/bin/commands/iterate/mark-ready.d.mts +2 -0
- package/bin/commands/iterate/mark-ready.mjs +38 -0
- package/bin/commands/iterate/merge-state.d.mts +15 -0
- package/bin/commands/iterate/merge-state.mjs +82 -0
- package/bin/commands/iterate/merge.d.mts +13 -0
- package/bin/commands/iterate/merge.mjs +45 -0
- package/bin/commands/iterate/render.d.mts +2 -2
- package/bin/commands/iterate/render.mjs +19 -20
- package/bin/commands/iterate/stall.mjs +4 -2
- package/bin/commands/iterate/thread-mutation-routing.d.mts +9 -0
- package/bin/commands/iterate/thread-mutation-routing.mjs +29 -0
- package/bin/commands/journal/index.d.mts +5 -0
- package/bin/commands/journal/index.mjs +11 -2
- package/bin/commands/mark-files-as-viewed.d.mts +2 -1
- package/bin/commands/mark-files-as-viewed.mjs +5 -92
- package/bin/commands/poll.mjs +3 -1
- package/bin/commands/resolve-mutate.mjs +51 -9
- package/bin/commands/shepherd-journal.d.mts +1 -1
- package/bin/commands/shepherd-journal.mjs +2 -2
- package/bin/commands/suggestion-patch-item.d.mts +1 -1
- package/bin/commands/suggestion-patch-item.mjs +3 -11
- package/bin/commands/suggestion-patches.mjs +3 -3
- package/bin/comments/authors.d.mts +6 -0
- package/bin/comments/authors.mjs +5 -0
- package/bin/comments/marker.d.mts +6 -1
- package/bin/comments/marker.mjs +8 -1
- package/bin/comments/resolve.d.mts +4 -0
- package/bin/comments/thread-visibility.mjs +10 -10
- package/bin/comments/visible-comments.mjs +2 -1
- package/bin/config/load.d.mts +5 -1
- 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.d.mts +2 -2
- package/bin/github/batch-parsers.mjs +40 -27
- package/bin/github/batch-raw-rules.d.mts +48 -0
- package/bin/github/batch-raw-types.d.mts +12 -0
- package/bin/github/batch-raw-types.mjs +0 -1
- package/bin/github/batch.mjs +3 -1
- package/bin/github/client.d.mts +1 -0
- package/bin/github/client.mjs +8 -1
- package/bin/github/gql/batch-pr-page.gql +6 -0
- package/bin/github/gql/batch-pr.gql +105 -0
- package/bin/github/gql/commit-check-contexts.gql +58 -0
- package/bin/github/gql/get-pr-body.gql +1 -0
- package/bin/github/gql/review-thread-comments.gql +1 -0
- package/bin/github/gql/suggestion-threads.gql +1 -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/github/suggestion-thread.mjs +1 -0
- package/bin/mcp/server.mjs +13 -6
- package/bin/pr-reference.d.mts +13 -0
- package/bin/pr-reference.mjs +23 -1
- package/bin/reporters/agent.mjs +11 -3
- package/bin/threads/transcript.d.mts +2 -0
- package/bin/threads/transcript.mjs +2 -0
- package/bin/types/activity.d.mts +2 -0
- package/bin/types/agent-thread.d.mts +1 -0
- package/bin/types/escalate.d.mts +35 -0
- package/bin/types/escalate.mjs +1 -0
- package/bin/types/github.d.mts +27 -0
- package/bin/types/github.mjs +0 -3
- package/bin/types/iterate.d.mts +23 -39
- 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 +22 -1
- package/bin/types/review-thread.d.mts +1 -0
- package/bin/types/suggestion-patch.d.mts +1 -1
- 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/mark-files-as-viewed/SKILL.md +3 -3
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +17 -15
- package/bin/commands/iterate/reruns.d.mts +0 -20
- package/bin/commands/iterate/reruns.mjs +0 -105
|
@@ -34,6 +34,10 @@ export function formatMutateResult(result) {
|
|
|
34
34
|
pushIds(lines, "Skipped human minimizes", result.skippedHumanMinimizes);
|
|
35
35
|
pushIds(lines, "Skipped human review dismissals", result.skippedHumanDismissals);
|
|
36
36
|
pushIds(lines, "Skipped non-human/unknown thread replies", result.skippedNonHumanReplies);
|
|
37
|
+
pushIds(lines, "Authorization denied/unverifiable for thread replies", result.skippedUnauthorizedReplies);
|
|
38
|
+
pushIds(lines, "Authorization denied/unverifiable for thread resolves", result.skippedUnauthorizedResolves);
|
|
39
|
+
pushIds(lines, "Authorization denied/unverifiable for minimizes", result.skippedUnauthorizedMinimizes);
|
|
40
|
+
pushIds(lines, "Authorization denied/unverifiable for review dismissals", result.skippedUnauthorizedDismissals);
|
|
37
41
|
const rateLimit = formatRateLimit(result);
|
|
38
42
|
if (rateLimit)
|
|
39
43
|
lines.push(rateLimit);
|
package/bin/cli/poll-handler.mjs
CHANGED
|
@@ -36,6 +36,7 @@ export async function handlePoll(args) {
|
|
|
36
36
|
stallTimeoutSeconds: flags.stallTimeoutSeconds,
|
|
37
37
|
noAutoMarkReady: flags.noAutoMarkReady,
|
|
38
38
|
noAutoCancelActionable: flags.noAutoCancelActionable,
|
|
39
|
+
merge: flags.merge,
|
|
39
40
|
intervalSeconds,
|
|
40
41
|
timeoutSeconds,
|
|
41
42
|
debounceSeconds,
|
package/bin/cli/runner.mjs
CHANGED
|
@@ -8,11 +8,12 @@ export function renderShellCommand(argv) {
|
|
|
8
8
|
function renderShellArg(arg) {
|
|
9
9
|
if (/^\$[A-Z_][A-Z0-9_]*$/.test(arg))
|
|
10
10
|
return `"${arg}"`;
|
|
11
|
+
const assignment = /^([A-Za-z_][A-Za-z0-9_]*)=(\$[A-Z_][A-Z0-9_]*)$/.exec(arg);
|
|
12
|
+
if (assignment)
|
|
13
|
+
return `${assignment[1]}="${assignment[2]}"`;
|
|
11
14
|
if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg))
|
|
12
15
|
return arg;
|
|
13
16
|
if (!/["$`\\]/.test(arg))
|
|
14
17
|
return `"${arg}"`;
|
|
15
|
-
|
|
16
|
-
return `'${arg}'`;
|
|
17
|
-
throw new Error(`Unexpected character in shell arg: ${JSON.stringify(arg)}`);
|
|
18
|
+
return `'${arg.replaceAll("'", `'"'"'`)}'`;
|
|
18
19
|
}
|
package/bin/commands/check.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { buildClassifyIndex, partitionBatch } from "../classify/apply.mjs";
|
|
|
22
22
|
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
23
23
|
import { getEffectiveCwd } from "../execution-context.mjs";
|
|
24
24
|
export async function runCheck(opts) {
|
|
25
|
-
const repo = await getRepoInfo();
|
|
25
|
+
const repo = opts.targetRepository ?? (await getRepoInfo());
|
|
26
26
|
const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
|
|
27
27
|
if (prNumber === null) {
|
|
28
28
|
throw new ShepherdError("No open PR found for current branch. Pass a PR number explicitly.", EXIT.UNAVAILABLE);
|
|
@@ -42,7 +42,22 @@ export async function runCheck(opts) {
|
|
|
42
42
|
? []
|
|
43
43
|
: await fetchStartupFailureChecks(repo, batchData.headRefOid, prNumber);
|
|
44
44
|
const allChecks = mergeStartupFailureChecks(batchData.checks, startupFailureChecks);
|
|
45
|
-
const
|
|
45
|
+
const classifiedPrChecks = classifyChecks(allChecks);
|
|
46
|
+
const latestRemoval = batchData.latestMergeQueueRemoval;
|
|
47
|
+
const headUpdatedAfterRemoval = Boolean(latestRemoval &&
|
|
48
|
+
latestRemoval.beforeCommitParentOids &&
|
|
49
|
+
!latestRemoval.beforeCommitParentOids.includes(batchData.headRefOid));
|
|
50
|
+
const queueRawChecks = batchData.isInMergeQueue
|
|
51
|
+
? (batchData.mergeQueueChecks ?? [])
|
|
52
|
+
: latestRemoval && !headUpdatedAfterRemoval
|
|
53
|
+
? (batchData.removedMergeQueueChecks ?? [])
|
|
54
|
+
: [];
|
|
55
|
+
// Keep supersession grouping commit-local, but accept merge_group only for the
|
|
56
|
+
// synthetic queue-commit source.
|
|
57
|
+
const classifiedQueueChecks = classifyChecks(queueRawChecks, {
|
|
58
|
+
additionalRelevantEvents: ["merge_group"],
|
|
59
|
+
});
|
|
60
|
+
const classifiedChecks = [...classifiedPrChecks, ...classifiedQueueChecks];
|
|
46
61
|
const verdict = getCiVerdict(classifiedChecks);
|
|
47
62
|
const passing = classifiedChecks.filter((c) => c.category === "passed");
|
|
48
63
|
const failing = classifiedChecks.filter((c) => c.category === "failing");
|
|
@@ -60,8 +75,10 @@ export async function runCheck(opts) {
|
|
|
60
75
|
const merged = await attachAndMergeCheckAnnotations({ passing, failing: triagedBase, skipped, filtered, ignored }, seenMap, prNumber);
|
|
61
76
|
const ignoredAnnotated = merged.ignored.filter((c) => (c.annotations?.length ?? 0) > 0);
|
|
62
77
|
const minimizedCommentCandidates = batchData.comments.filter((c) => c.isMinimized && !partition.suppressedCommentIds.has(c.id));
|
|
63
|
-
const
|
|
64
|
-
const
|
|
78
|
+
const deniedRuleAutoResolveCommentIds = new Set(partition.ruleAutoResolveCommentIds.filter((id) => batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize !== true));
|
|
79
|
+
const visibleCommentClassification = classifyVisibleComments(batchData.comments.filter((c) => !partition.suppressedCommentIds.has(c.id) || deniedRuleAutoResolveCommentIds.has(c.id)), seenMap, config.iterate.minimizeComments, botUsernames);
|
|
80
|
+
const deniedRuleAutoResolveThreadIds = new Set(partition.ruleAutoResolveThreadIds.filter((id) => batchData.reviewThreads.find((thread) => thread.id === id)?.viewerCanResolve !== true));
|
|
81
|
+
const threadVisibility = classifyThreadVisibility(batchData.reviewThreads.filter((t) => !partition.suppressedThreadIds.has(t.id) || deniedRuleAutoResolveThreadIds.has(t.id)), seenMap, botUsernames);
|
|
65
82
|
const firstLookComments = minimizedCommentCandidates.flatMap((c) => {
|
|
66
83
|
const cls = classifyItem(c.id, c.body, seenMap);
|
|
67
84
|
if (cls === "unchanged")
|
|
@@ -72,7 +89,9 @@ export async function runCheck(opts) {
|
|
|
72
89
|
const firstLookSummaries = [];
|
|
73
90
|
const editedSummaries = [];
|
|
74
91
|
const seenSummaries = [];
|
|
75
|
-
const
|
|
92
|
+
const deniedRuleAutoResolveReviewSummaryIds = new Set(partition.ruleAutoResolveReviewSummaryIds.filter((id) => batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize !== true));
|
|
93
|
+
const unseenReviewSummaries = batchData.reviewSummaries.filter((r) => !partition.suppressedReviewSummaryIds.has(r.id) ||
|
|
94
|
+
deniedRuleAutoResolveReviewSummaryIds.has(r.id));
|
|
76
95
|
for (const r of unseenReviewSummaries) {
|
|
77
96
|
const cls = classifyItem(r.id, r.body, seenMap);
|
|
78
97
|
if (cls === "new")
|
|
@@ -108,7 +127,8 @@ export async function runCheck(opts) {
|
|
|
108
127
|
.filter((t) => partition.suppressedThreadIds.has(t.id))
|
|
109
128
|
.map((t) => markSeen(stateKey, t.id, threadTranscriptBody(t))),
|
|
110
129
|
...batchData.reviewSummaries
|
|
111
|
-
.filter((r) => partition.suppressedReviewSummaryIds.has(r.id)
|
|
130
|
+
.filter((r) => partition.suppressedReviewSummaryIds.has(r.id) &&
|
|
131
|
+
!deniedRuleAutoResolveReviewSummaryIds.has(r.id))
|
|
112
132
|
.map((r) => markSeen(stateKey, r.id, r.body)),
|
|
113
133
|
...batchData.changesRequestedReviews
|
|
114
134
|
.filter((r) => partition.suppressedChangesRequestedIds.has(r.id))
|
|
@@ -116,7 +136,17 @@ export async function runCheck(opts) {
|
|
|
116
136
|
]);
|
|
117
137
|
await markReviewInlineThreadMarkers(stateKey, batchData.reviewThreads);
|
|
118
138
|
}
|
|
119
|
-
const
|
|
139
|
+
const authorizedPartition = {
|
|
140
|
+
...partition,
|
|
141
|
+
ruleAutoResolveThreadIds: partition.ruleAutoResolveThreadIds.filter((id) => batchData.reviewThreads.find((thread) => thread.id === id)?.viewerCanResolve === true),
|
|
142
|
+
ruleAutoResolveCommentIds: partition.ruleAutoResolveCommentIds.filter((id) => batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize === true),
|
|
143
|
+
ruleAutoResolveReviewSummaryIds: partition.ruleAutoResolveReviewSummaryIds.filter((id) => batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize === true),
|
|
144
|
+
};
|
|
145
|
+
const { threadIds: authorizedRuleAutoResolveThreadIds, commentIds: ruleAutoResolveCommentIds, reviewSummaryIds: ruleAutoResolveReviewSummaryIds, } = await remainingRuleAutoResolveIds(authorizedPartition, opts.autoMinimizeSuppressed);
|
|
146
|
+
const ruleAutoResolveThreadIds = [
|
|
147
|
+
...authorizedRuleAutoResolveThreadIds,
|
|
148
|
+
...deniedRuleAutoResolveThreadIds,
|
|
149
|
+
];
|
|
120
150
|
const changesRequestedReviews = changesRequestedReviewVisibility.visible;
|
|
121
151
|
const changesRequestedReviewCount = batchData.changesRequestedReviews.filter((r) => !partition.suppressedChangesRequestedIds.has(r.id)).length;
|
|
122
152
|
const approvedReviews = approvedReviewVisibility.visible;
|
|
@@ -131,10 +161,19 @@ export async function runCheck(opts) {
|
|
|
131
161
|
}
|
|
132
162
|
}
|
|
133
163
|
const blockedByFilteredCheck = isBlockedByFilteredCheck(mergeStatus, verdict);
|
|
164
|
+
const queueCommit = batchData.isInMergeQueue
|
|
165
|
+
? batchData.mergeQueueEntry?.headCommitOid
|
|
166
|
+
: batchData.latestMergeQueueRemoval?.beforeCommitOid;
|
|
167
|
+
const hasQueueState = Boolean(batchData.isMergeQueueEnabled ||
|
|
168
|
+
batchData.isInMergeQueue ||
|
|
169
|
+
batchData.autoMergeRequest ||
|
|
170
|
+
batchData.latestMergeQueueRemoval);
|
|
134
171
|
return {
|
|
135
172
|
pr: prNumber,
|
|
136
173
|
nodeId: batchData.nodeId,
|
|
174
|
+
headSha: batchData.headRefOid,
|
|
137
175
|
repo: `${repo.owner}/${repo.name}`,
|
|
176
|
+
...(batchData.viewerAuthorization && { viewerAuthorization: batchData.viewerAuthorization }),
|
|
138
177
|
status,
|
|
139
178
|
baseBranch: batchData.baseRefName,
|
|
140
179
|
mergeStatus,
|
|
@@ -175,6 +214,24 @@ export async function runCheck(opts) {
|
|
|
175
214
|
: undefined),
|
|
176
215
|
branchProtection: batchData.branchProtection,
|
|
177
216
|
activity: batchData.activity,
|
|
217
|
+
...(hasQueueState && {
|
|
218
|
+
mergeQueue: {
|
|
219
|
+
enabled: Boolean(batchData.isMergeQueueEnabled),
|
|
220
|
+
inQueue: Boolean(batchData.isInMergeQueue),
|
|
221
|
+
...(batchData.autoMergeRequest && { autoMergeRequest: batchData.autoMergeRequest }),
|
|
222
|
+
...(batchData.mergeQueueEntry && { entry: batchData.mergeQueueEntry }),
|
|
223
|
+
...(batchData.latestMergeQueueRemoval && {
|
|
224
|
+
latestRemoval: batchData.latestMergeQueueRemoval,
|
|
225
|
+
}),
|
|
226
|
+
...(queueCommit && { checkCommitOid: queueCommit }),
|
|
227
|
+
...((batchData.isInMergeQueue
|
|
228
|
+
? batchData.mergeQueueChecksIncomplete
|
|
229
|
+
: batchData.removedMergeQueueChecksIncomplete) && {
|
|
230
|
+
checksIncomplete: true,
|
|
231
|
+
}),
|
|
232
|
+
...(headUpdatedAfterRemoval && { headUpdatedAfterRemoval: true }),
|
|
233
|
+
},
|
|
234
|
+
}),
|
|
178
235
|
};
|
|
179
236
|
}
|
|
180
237
|
async function remainingRuleAutoResolveIds(partition, autoMinimizeSuppressed = false) {
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
9
9
|
* e.g. `"## Review threads"`.
|
|
10
10
|
*/
|
|
11
|
-
export declare function buildCommitSuggestionInstruction(
|
|
11
|
+
export declare function buildCommitSuggestionInstruction(prReference: string | number, sectionName: string): string;
|
|
@@ -9,10 +9,10 @@ import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
|
9
9
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
10
10
|
* e.g. `"## Review threads"`.
|
|
11
11
|
*/
|
|
12
|
-
export function buildCommitSuggestionInstruction(
|
|
12
|
+
export function buildCommitSuggestionInstruction(prReference, sectionName) {
|
|
13
13
|
const command = buildPrShepherdCommand([
|
|
14
14
|
"build-suggestion-patches",
|
|
15
|
-
String(
|
|
15
|
+
String(prReference),
|
|
16
16
|
"--thread-id",
|
|
17
17
|
"<id>",
|
|
18
18
|
"--message",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { buildSingularInstructions } from "./suggestion-patch-item.mjs";
|
|
2
2
|
import { runSuggestionPatches } from "./suggestion-patches.mjs";
|
|
3
|
+
import { formatPrUrl } from "../pr-reference.mjs";
|
|
3
4
|
/** @deprecated Use runSuggestionPatches. */
|
|
4
5
|
export async function runCommitSuggestion(opts) {
|
|
5
6
|
const result = await runSuggestionPatches({
|
|
6
7
|
prNumber: opts.prNumber,
|
|
8
|
+
targetRepository: opts.targetRepository,
|
|
7
9
|
format: opts.format,
|
|
8
10
|
verbose: opts.verbose,
|
|
9
11
|
suggestions: [
|
|
@@ -19,6 +21,6 @@ export async function runCommitSuggestion(opts) {
|
|
|
19
21
|
...patch,
|
|
20
22
|
pr: result.pr,
|
|
21
23
|
repo: result.repo,
|
|
22
|
-
postActionInstructions: buildSingularInstructions(patch, result.pr),
|
|
24
|
+
postActionInstructions: buildSingularInstructions(patch, formatPrUrl(result.repo, result.pr)),
|
|
23
25
|
};
|
|
24
26
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { buildActiveChecks, buildRelevantChecks, buildSummary, buildSuppressedCheckFields, } from "./helpers.mjs";
|
|
2
|
+
export function buildIterateBase(report, readyState) {
|
|
3
|
+
return {
|
|
4
|
+
pr: report.pr,
|
|
5
|
+
repo: report.repo,
|
|
6
|
+
status: report.status,
|
|
7
|
+
state: report.mergeStatus.state,
|
|
8
|
+
mergeStateStatus: report.mergeStatus.mergeStateStatus,
|
|
9
|
+
mergeStatus: report.mergeStatus.status,
|
|
10
|
+
reviewDecision: report.mergeStatus.reviewDecision,
|
|
11
|
+
blockingBotReviewInProgress: report.mergeStatus.blockingBotReviewInProgress,
|
|
12
|
+
isDraft: report.mergeStatus.isDraft,
|
|
13
|
+
shouldCancel: readyState.shouldCancel,
|
|
14
|
+
remainingSeconds: readyState.remainingSeconds,
|
|
15
|
+
summary: buildSummary(report),
|
|
16
|
+
baseBranch: report.baseBranch,
|
|
17
|
+
branchProtection: report.branchProtection,
|
|
18
|
+
mergeRequirements: report.mergeStatus.mergeRequirements,
|
|
19
|
+
checks: buildRelevantChecks(report),
|
|
20
|
+
inProgressChecks: buildActiveChecks(report),
|
|
21
|
+
...buildSuppressedCheckFields(report),
|
|
22
|
+
activity: report.activity,
|
|
23
|
+
mergeQueue: report.mergeQueue,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -17,11 +17,9 @@ export declare function buildBehindBaseHintInstruction(baseBranch: string, hint:
|
|
|
17
17
|
*
|
|
18
18
|
* - `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution: without it, the printed command has an
|
|
19
19
|
* empty `--message`/invalid `--require-sha` and `apply review` rejects the mutation.
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* and produce a self-perpetuating reply loop — worse than a rejected mutation, and not
|
|
24
|
-
* something a caller can discover by inspecting the command alone.
|
|
20
|
+
* Marker-based self-reply routing is already reflected in the generated IDs. The instruction
|
|
21
|
+
* below makes that behavior explicit so an authenticated viewer's unmarked human feedback is
|
|
22
|
+
* not mistaken for an automated reply merely because the GitHub login matches.
|
|
25
23
|
*
|
|
26
24
|
* Contrast with what *does* stay in the skill's "Review-mutation mechanics" playbook —
|
|
27
25
|
* dismiss-ID retention and the first-look/annotation ID-exclusion rules. Those only matter
|
|
@@ -30,18 +28,6 @@ export declare function buildBehindBaseHintInstruction(baseBranch: string, hint:
|
|
|
30
28
|
* without it, nothing in CLI output tells the agent that playbook exists.
|
|
31
29
|
*/
|
|
32
30
|
export declare function buildResolveCommandInstruction(resolveCommand: ResolveCommand): string[];
|
|
33
|
-
/**
|
|
34
|
-
* Build the CI-triage instruction. The per-conclusion rerun policy (GitHub Actions log
|
|
35
|
-
* excerpts, `gh run view`/`gh run rerun` rules for CANCELLED/STARTUP_FAILURE/external
|
|
36
|
-
* failures) is invariant text keyed on the `[conclusion: …]` tags already rendered in
|
|
37
|
-
* `## Failing checks` — it lives in the pr-shepherd skill's "CI failure triage" playbook
|
|
38
|
-
* instead of being re-emitted every tick. This supersedes the "CI budget rules" example in
|
|
39
|
-
* CLAUDE.md's "Keep skills and loop prompts minimal" section (see that section's amendment
|
|
40
|
-
* note). The `(no runId)` case stays here because it flips `buildFixCompletionInstruction`
|
|
41
|
-
* to a human-handoff terminal state — that trigger, unlike the others, is CLI-decided. The
|
|
42
|
-
* CLI sentence does not claim every failure has a log excerpt to read (only GitHub Actions
|
|
43
|
-
* checks with a runId do — CANCELLED, STARTUP_FAILURE, and external checks may not); that
|
|
44
|
-
* per-kind detail is exactly what the skill playbook table disambiguates.
|
|
45
|
-
*/
|
|
31
|
+
/** Build the CI-triage pointer; the skill limits follow-up actions to included evidence. */
|
|
46
32
|
export declare function buildFailingCheckInstructions(checks: AgentCheck[]): string[];
|
|
47
|
-
export declare function buildFixCompletionInstruction(checks: AgentCheck[]): string;
|
|
33
|
+
export declare function buildFixCompletionInstruction(checks: AgentCheck[], requiresRemoteUpdateAuthorization?: boolean, hasShaGatedReviewMutations?: boolean): string;
|
|
@@ -18,7 +18,7 @@ export function buildBehindBaseHintInstruction(baseBranch, hint, isBehind) {
|
|
|
18
18
|
const trimmedHint = typeof hint === "string" ? hint.trim() : "";
|
|
19
19
|
if (!isBehind || trimmedHint === "")
|
|
20
20
|
return [];
|
|
21
|
-
return [`The branch is behind
|
|
21
|
+
return [`The branch is behind PR base branch \`${baseBranch}\`. ${trimmedHint} before pushing.`];
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
|
|
@@ -26,11 +26,9 @@ export function buildBehindBaseHintInstruction(baseBranch, hint, isBehind) {
|
|
|
26
26
|
*
|
|
27
27
|
* - `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution: without it, the printed command has an
|
|
28
28
|
* empty `--message`/invalid `--require-sha` and `apply review` rejects the mutation.
|
|
29
|
-
* -
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* and produce a self-perpetuating reply loop — worse than a rejected mutation, and not
|
|
33
|
-
* something a caller can discover by inspecting the command alone.
|
|
29
|
+
* Marker-based self-reply routing is already reflected in the generated IDs. The instruction
|
|
30
|
+
* below makes that behavior explicit so an authenticated viewer's unmarked human feedback is
|
|
31
|
+
* not mistaken for an automated reply merely because the GitHub login matches.
|
|
34
32
|
*
|
|
35
33
|
* Contrast with what *does* stay in the skill's "Review-mutation mechanics" playbook —
|
|
36
34
|
* dismiss-ID retention and the first-look/annotation ID-exclusion rules. Those only matter
|
|
@@ -43,10 +41,10 @@ export function buildResolveCommandInstruction(resolveCommand) {
|
|
|
43
41
|
return [];
|
|
44
42
|
const instructions = [];
|
|
45
43
|
if ((resolveCommand.replyThreadIds?.length ?? 0) > 0) {
|
|
46
|
-
instructions.push("
|
|
44
|
+
instructions.push("Run the generated thread IDs unchanged. A latest comment beginning `<!-- pr-shepherd -->` is an established Shepherd reply; a marked viewer-authored human thread is emitted resolve-only, not for another reply.");
|
|
47
45
|
}
|
|
48
46
|
if (resolveCommand.requiresHeadSha) {
|
|
49
|
-
instructions.push("
|
|
47
|
+
instructions.push("If you did not change code, replace `$HEAD_SHA` with `$(git rev-parse HEAD)`, which must equal the current remote PR head. If you changed code, do not run this command until an authorized push updates the remote PR head; then replace `$HEAD_SHA` with that pushed commit SHA.");
|
|
50
48
|
}
|
|
51
49
|
if (resolveCommand.requiresDismissMessage) {
|
|
52
50
|
instructions.push("Replace `$DISMISS_MESSAGE` with one sentence describing what changed.");
|
|
@@ -54,19 +52,7 @@ export function buildResolveCommandInstruction(resolveCommand) {
|
|
|
54
52
|
instructions.push('Run the `apply review:` command shown above. See "Review-mutation mechanics" in the pr-shepherd skill for dismiss-ID retention.');
|
|
55
53
|
return instructions;
|
|
56
54
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Build the CI-triage instruction. The per-conclusion rerun policy (GitHub Actions log
|
|
59
|
-
* excerpts, `gh run view`/`gh run rerun` rules for CANCELLED/STARTUP_FAILURE/external
|
|
60
|
-
* failures) is invariant text keyed on the `[conclusion: …]` tags already rendered in
|
|
61
|
-
* `## Failing checks` — it lives in the pr-shepherd skill's "CI failure triage" playbook
|
|
62
|
-
* instead of being re-emitted every tick. This supersedes the "CI budget rules" example in
|
|
63
|
-
* CLAUDE.md's "Keep skills and loop prompts minimal" section (see that section's amendment
|
|
64
|
-
* note). The `(no runId)` case stays here because it flips `buildFixCompletionInstruction`
|
|
65
|
-
* to a human-handoff terminal state — that trigger, unlike the others, is CLI-decided. The
|
|
66
|
-
* CLI sentence does not claim every failure has a log excerpt to read (only GitHub Actions
|
|
67
|
-
* checks with a runId do — CANCELLED, STARTUP_FAILURE, and external checks may not); that
|
|
68
|
-
* per-kind detail is exactly what the skill playbook table disambiguates.
|
|
69
|
-
*/
|
|
55
|
+
/** Build the CI-triage pointer; the skill limits follow-up actions to included evidence. */
|
|
70
56
|
export function buildFailingCheckInstructions(checks) {
|
|
71
57
|
if (checks.length === 0)
|
|
72
58
|
return [];
|
|
@@ -74,17 +60,30 @@ export function buildFailingCheckInstructions(checks) {
|
|
|
74
60
|
const hasTriageable = checks.some((c) => c.runId || c.detailsUrl);
|
|
75
61
|
const instructions = [];
|
|
76
62
|
if (hasTriageable) {
|
|
77
|
-
instructions.push('Triage every failure under `## Failing checks`. See "CI failure triage" in the pr-shepherd skill for
|
|
63
|
+
instructions.push('Triage every failure under `## Failing checks`. See "CI failure triage" in the pr-shepherd skill for read-only inspection rules.');
|
|
78
64
|
}
|
|
79
65
|
if (hasBare) {
|
|
80
66
|
instructions.push("For each `(no runId)` failure, escalate to a human because no log or URL is available.");
|
|
81
67
|
}
|
|
82
68
|
return instructions;
|
|
83
69
|
}
|
|
84
|
-
export function buildFixCompletionInstruction(checks) {
|
|
85
|
-
|
|
86
|
-
|
|
70
|
+
export function buildFixCompletionInstruction(checks, requiresRemoteUpdateAuthorization = false, hasShaGatedReviewMutations = false) {
|
|
71
|
+
if (requiresRemoteUpdateAuthorization) {
|
|
72
|
+
return "`[FIX_CODE]` requires a human handoff for an authorized push after conflict resolution. Shepherd cannot verify the Git credential's push authorization. Stop polling after committing, and resume only after the remote PR head changes.";
|
|
73
|
+
}
|
|
74
|
+
const hasUninspectableFailure = checks.some((check) => !check.runId && !check.detailsUrl);
|
|
75
|
+
const hasCiAuthorizationHandoff = checks.some((check) => check.conclusion === "CANCELLED" ||
|
|
76
|
+
check.conclusion === "STARTUP_FAILURE" ||
|
|
77
|
+
(check.runId === null && Boolean(check.detailsUrl)) ||
|
|
78
|
+
(check.runId !== null && !check.logExcerpt?.trim()));
|
|
79
|
+
if (hasUninspectableFailure) {
|
|
87
80
|
return "`[FIX_CODE]` requires a human handoff for an uninspectable failing check. Stop polling after escalating, and resume only after human direction.";
|
|
88
81
|
}
|
|
82
|
+
if (hasCiAuthorizationHandoff) {
|
|
83
|
+
return "`[FIX_CODE]` requires a human handoff for a failing check with no authorized follow-up action. Stop polling after escalating, and resume only after human direction.";
|
|
84
|
+
}
|
|
85
|
+
if (hasShaGatedReviewMutations) {
|
|
86
|
+
return "`[FIX_CODE]` is conditional: if you changed code, stop after committing and resume only after an authorized push changes the remote PR head; if you did not change code, complete the authorized review mutations and iterate again with the same options.";
|
|
87
|
+
}
|
|
89
88
|
return "`[FIX_CODE]` is non-terminal. After completing these steps, iterate again with the same options to continue.";
|
|
90
89
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentThread, Review, ResolveCommand, AgentCheck, ReviewThread } from "../../types.mts";
|
|
1
|
+
import type { AgentThread, Review, ResolveCommand, AgentCheck, ReviewThread, ViewerAuthorization } from "../../types.mts";
|
|
2
2
|
import { type NormalizedBotUsernames } from "../../comments/authors.mts";
|
|
3
3
|
import type { MinimizeCommentsPolicy } from "../../config/load.mts";
|
|
4
4
|
export declare function classifyReviewSummaries(summaries: {
|
|
@@ -12,7 +12,7 @@ export declare function classifyReviewSummaries(summaries: {
|
|
|
12
12
|
editedSummaries: Review[];
|
|
13
13
|
surfacedApprovals: Review[];
|
|
14
14
|
};
|
|
15
|
-
export declare function buildResolveCommand(threads: AgentThread[], resolutionOnlyThreads: ReviewThread[], allCommentIds: string[], reviews: Review[], checks: AgentCheck[],
|
|
15
|
+
export declare function buildResolveCommand(threads: AgentThread[], resolutionOnlyThreads: ReviewThread[], allCommentIds: string[], reviews: Review[], checks: AgentCheck[], prReference: string | number, botUsernames?: NormalizedBotUsernames, ruleAutoResolveThreadIds?: string[], viewerAuthorization?: ViewerAuthorization, authorizationThreads?: ReviewThread[]): {
|
|
16
16
|
resolveCommand: ResolveCommand;
|
|
17
17
|
resolveOnlyCommand?: ResolveCommand;
|
|
18
18
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildPrShepherdCommand } from "../../cli/runner.mjs";
|
|
2
2
|
import { shouldMinimizeAuthor } from "../../comments/minimize-policy.mjs";
|
|
3
3
|
import { isConfiguredBotAuthor, isHumanAuthor, } from "../../comments/authors.mjs";
|
|
4
|
+
import { buildThreadMutationRouting } from "./thread-mutation-routing.mjs";
|
|
4
5
|
function dedupeIds(ids) {
|
|
5
6
|
const seen = new Set();
|
|
6
7
|
const out = [];
|
|
@@ -14,7 +15,8 @@ function dedupeIds(ids) {
|
|
|
14
15
|
}
|
|
15
16
|
export function classifyReviewSummaries(summaries, approvals, minimizeApprovals, minimizeComments = "all", botUsernames = new Set(), unresolvedThreads = [], ruleAutoResolveIds = []) {
|
|
16
17
|
const blockedReviewIds = new Set(unresolvedThreads.flatMap((t) => (t.reviewId !== undefined ? [t.reviewId] : [])));
|
|
17
|
-
const eligible = (r) =>
|
|
18
|
+
const eligible = (r) => r.viewerCanMinimize === true &&
|
|
19
|
+
shouldMinimizeAuthor(r.authorType, minimizeComments, r.author, botUsernames) &&
|
|
18
20
|
!blockedReviewIds.has(r.id);
|
|
19
21
|
// First-look summaries still need one tick to surface their body to the agent,
|
|
20
22
|
// so their minimize IDs ride in the agent-facing apply command. Seen summaries
|
|
@@ -34,7 +36,8 @@ export function classifyReviewSummaries(summaries, approvals, minimizeApprovals,
|
|
|
34
36
|
if (minimizeApprovals) {
|
|
35
37
|
const surfacedApprovals = [];
|
|
36
38
|
for (const r of approvals) {
|
|
37
|
-
if (
|
|
39
|
+
if (r.viewerCanMinimize === true &&
|
|
40
|
+
shouldMinimizeAuthor(r.authorType, minimizeComments, r.author, botUsernames))
|
|
38
41
|
minimizeIds.push(r.id);
|
|
39
42
|
else
|
|
40
43
|
surfacedApprovals.push(r);
|
|
@@ -55,35 +58,45 @@ export function classifyReviewSummaries(summaries, approvals, minimizeApprovals,
|
|
|
55
58
|
surfacedApprovals: approvals,
|
|
56
59
|
};
|
|
57
60
|
}
|
|
58
|
-
export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, reviews, checks,
|
|
61
|
+
export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, reviews, checks, prReference, botUsernames = new Set(), ruleAutoResolveThreadIds = [], viewerAuthorization, authorizationThreads = []) {
|
|
59
62
|
const allThreads = [...threads, ...resolutionOnlyThreads];
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
const routed = buildThreadMutationRouting(allThreads, botUsernames, ruleAutoResolveThreadIds);
|
|
64
|
+
const canReply = new Set(authorizationThreads
|
|
65
|
+
.filter((thread) => thread.viewerCanReply === true)
|
|
66
|
+
.map((thread) => thread.id));
|
|
67
|
+
const canResolve = new Set(authorizationThreads
|
|
68
|
+
.filter((thread) => thread.viewerCanResolve === true)
|
|
69
|
+
.map((thread) => thread.id));
|
|
70
|
+
const replyThreadIds = routed.replyThreadIds.filter((id) => canReply.has(id));
|
|
71
|
+
// Viewer-authored human resolves stay paired with an authorized reply. Marker-ended
|
|
72
|
+
// viewer-authored retries and bot/non-human resolves need only resolve authorization.
|
|
73
|
+
const pairedResolveThreadIds = routed.pairedResolveThreadIds.filter((id) => canReply.has(id) && canResolve.has(id));
|
|
74
|
+
const standaloneResolveThreadIds = routed.standaloneResolveThreadIds.filter((id) => canResolve.has(id));
|
|
75
|
+
const resolveThreadIds = dedupeIds([...pairedResolveThreadIds, ...standaloneResolveThreadIds]);
|
|
70
76
|
// Bot/non-human CHANGES_REQUESTED reviews are auto-dismissed after the agent pushes a fix.
|
|
71
77
|
// Human reviews are left for the reviewer to re-review or dismiss themselves.
|
|
72
|
-
const dismissReviewIds = dedupeIds(
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
const dismissReviewIds = dedupeIds(viewerAuthorization?.viewerCanAdminister === true
|
|
79
|
+
? reviews
|
|
80
|
+
.filter((r) => !isHumanAuthor(r) || isConfiguredBotAuthor(r, botUsernames))
|
|
81
|
+
.map((r) => r.id)
|
|
82
|
+
: []);
|
|
75
83
|
const hasReply = replyThreadIds.length > 0;
|
|
76
84
|
const hasDismiss = dismissReviewIds.length > 0;
|
|
77
85
|
// Mutations that require --message: replies (to human threads) and dismissals (of bot CR reviews).
|
|
78
86
|
const hasMessageMutations = hasReply || hasDismiss;
|
|
79
87
|
const hasResolveOrMinimize = resolveThreadIds.length > 0 || allCommentIds.length > 0;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
88
|
+
const hasStandaloneResolveOrMinimize = standaloneResolveThreadIds.length > 0 || allCommentIds.length > 0;
|
|
89
|
+
if (hasMessageMutations && hasStandaloneResolveOrMinimize) {
|
|
90
|
+
// Split: message-bearing mutations plus their paired viewer-authored resolves ride in
|
|
91
|
+
// resolveArgv; standalone resolve/minimize mutations go in resolveOnlyArgv so they can
|
|
92
|
+
// run without SHA or message.
|
|
93
|
+
const resolveArgv = buildPrShepherdCommand(["apply", "review", String(prReference)]).argv;
|
|
84
94
|
if (replyThreadIds.length > 0) {
|
|
85
95
|
resolveArgv.push("--reply-thread-ids", replyThreadIds.join(","));
|
|
86
96
|
}
|
|
97
|
+
if (pairedResolveThreadIds.length > 0) {
|
|
98
|
+
resolveArgv.push("--resolve-thread-ids", pairedResolveThreadIds.join(","));
|
|
99
|
+
}
|
|
87
100
|
resolveArgv.push("--message", "$DISMISS_MESSAGE");
|
|
88
101
|
if (hasDismiss) {
|
|
89
102
|
resolveArgv.push("--dismiss-review-ids", dismissReviewIds.join(","));
|
|
@@ -96,12 +109,15 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
|
|
|
96
109
|
requiresHeadSha,
|
|
97
110
|
requiresDismissMessage: true,
|
|
98
111
|
...(replyThreadIds.length > 0 ? { replyThreadIds } : undefined),
|
|
112
|
+
...(pairedResolveThreadIds.length > 0
|
|
113
|
+
? { resolveThreadIds: pairedResolveThreadIds }
|
|
114
|
+
: undefined),
|
|
99
115
|
...(hasDismiss ? { dismissReviewIds } : undefined),
|
|
100
116
|
hasMutations: true,
|
|
101
117
|
};
|
|
102
|
-
const resolveOnlyArgv = buildPrShepherdCommand(["apply", "review", String(
|
|
103
|
-
if (
|
|
104
|
-
resolveOnlyArgv.push("--resolve-thread-ids",
|
|
118
|
+
const resolveOnlyArgv = buildPrShepherdCommand(["apply", "review", String(prReference)]).argv;
|
|
119
|
+
if (standaloneResolveThreadIds.length > 0) {
|
|
120
|
+
resolveOnlyArgv.push("--resolve-thread-ids", standaloneResolveThreadIds.join(","));
|
|
105
121
|
}
|
|
106
122
|
if (allCommentIds.length > 0) {
|
|
107
123
|
resolveOnlyArgv.push("--minimize-comment-ids", allCommentIds.join(","));
|
|
@@ -110,13 +126,15 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
|
|
|
110
126
|
argv: resolveOnlyArgv,
|
|
111
127
|
requiresHeadSha: false,
|
|
112
128
|
requiresDismissMessage: false,
|
|
113
|
-
...(
|
|
129
|
+
...(standaloneResolveThreadIds.length > 0
|
|
130
|
+
? { resolveThreadIds: standaloneResolveThreadIds }
|
|
131
|
+
: undefined),
|
|
114
132
|
hasMutations: true,
|
|
115
133
|
};
|
|
116
134
|
return { resolveCommand, resolveOnlyCommand };
|
|
117
135
|
}
|
|
118
136
|
// Single command: all mutations combined (or only one category present).
|
|
119
|
-
const argv = buildPrShepherdCommand(["apply", "review", String(
|
|
137
|
+
const argv = buildPrShepherdCommand(["apply", "review", String(prReference)]).argv;
|
|
120
138
|
if (replyThreadIds.length > 0) {
|
|
121
139
|
argv.push("--reply-thread-ids", replyThreadIds.join(","));
|
|
122
140
|
argv.push("--message", "$DISMISS_MESSAGE");
|
|
@@ -26,6 +26,8 @@ interface BaseBranchLookup {
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function formatDurationApprox(seconds: number): string;
|
|
28
28
|
export declare function validateBaseBranch(raw: string): BaseBranchLookup;
|
|
29
|
-
export declare function buildEscalateHumanMessage(escalate: Omit<EscalateDetails, "humanMessage">, pr: number
|
|
29
|
+
export declare function buildEscalateHumanMessage(escalate: Omit<EscalateDetails, "humanMessage">, pr: string | number, opts?: {
|
|
30
|
+
merge?: boolean;
|
|
31
|
+
}): string;
|
|
30
32
|
export declare function buildEscalateSuggestion(triggers: EscalateTrigger[], detail?: string): string;
|
|
31
33
|
export {};
|
|
@@ -59,7 +59,7 @@ export function validateBaseBranch(raw) {
|
|
|
59
59
|
}
|
|
60
60
|
return { branch: trimmed, isFallback: false };
|
|
61
61
|
}
|
|
62
|
-
export function buildEscalateHumanMessage(escalate, pr) {
|
|
62
|
+
export function buildEscalateHumanMessage(escalate, pr, opts) {
|
|
63
63
|
const lines = [];
|
|
64
64
|
lines.push("⚠️ /pr-shepherd:pr-shepherd paused — manual intervention required");
|
|
65
65
|
lines.push("");
|
|
@@ -109,6 +109,26 @@ export function buildEscalateHumanMessage(escalate, pr) {
|
|
|
109
109
|
lines.push("");
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
+
if (escalate.mergeQueueRemoval) {
|
|
113
|
+
const removal = escalate.mergeQueueRemoval;
|
|
114
|
+
lines.push("");
|
|
115
|
+
lines.push("## Merge queue removal");
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push(`- reason: \`${removal.reason ?? "not provided"}\``);
|
|
118
|
+
if (removal.actor)
|
|
119
|
+
lines.push(`- actor: \`@${removal.actor}\``);
|
|
120
|
+
lines.push(`- createdAtUnix: \`${removal.createdAtUnix}\``);
|
|
121
|
+
if (removal.beforeCommitOid)
|
|
122
|
+
lines.push(`- queue commit: \`${removal.beforeCommitOid}\``);
|
|
123
|
+
}
|
|
124
|
+
if (escalate.authorization && escalate.authorization.length > 0) {
|
|
125
|
+
lines.push("");
|
|
126
|
+
lines.push("## Authorization");
|
|
127
|
+
lines.push("");
|
|
128
|
+
for (const item of escalate.authorization) {
|
|
129
|
+
lines.push(`- ${item.action}: ${item.targetIds.map((id) => `\`${id}\``).join(", ")} — GitHub denied the action or did not expose a confirming capability`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
112
132
|
if (escalate.thrashHistory && escalate.thrashHistory.length > 0) {
|
|
113
133
|
lines.push("");
|
|
114
134
|
lines.push("## Fix attempts");
|
|
@@ -120,10 +140,17 @@ export function buildEscalateHumanMessage(escalate, pr) {
|
|
|
120
140
|
lines.push("");
|
|
121
141
|
lines.push("---");
|
|
122
142
|
lines.push("");
|
|
123
|
-
lines.push(`After completing manual fixes (and pushing if required), rerun \`/pr-shepherd:pr-shepherd ${pr}\` to resume.`);
|
|
143
|
+
lines.push(`After completing manual fixes (and pushing if required), rerun \`/pr-shepherd:pr-shepherd ${pr}${opts?.merge ? " --merge" : ""}\` to resume.`);
|
|
124
144
|
return lines.join("\n");
|
|
125
145
|
}
|
|
126
146
|
export function buildEscalateSuggestion(triggers, detail) {
|
|
147
|
+
if (triggers.includes("authorization-required")) {
|
|
148
|
+
return "GitHub did not confirm that the current viewer may perform one or more required actions. Ask a repository maintainer to handle the listed items; Shepherd will not recommend commands that would be denied.";
|
|
149
|
+
}
|
|
150
|
+
if (triggers.includes("merge-queue-removed")) {
|
|
151
|
+
const reason = detail ? ` GitHub reason: ${detail}.` : "";
|
|
152
|
+
return `The PR left the merge queue without an actionable check failure.${reason} Confirm the removal was not intentional before adding it again.`;
|
|
153
|
+
}
|
|
127
154
|
if (triggers.includes("stall-timeout")) {
|
|
128
155
|
const duration = detail ?? "60 minutes";
|
|
129
156
|
return `No progress detected for ${duration} — state has not changed. This is a manual checkpoint: inspect the PR and apply a manual fix before resuming.`;
|