pr-shepherd 0.15.2 → 0.16.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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +23 -89
- package/bin/cli/args.mjs +0 -1
- package/bin/cli/default-iterate.mjs +2 -7
- package/bin/cli/exit-codes.mjs +0 -4
- package/bin/cli/fix-formatter.mjs +8 -8
- package/bin/cli/handlers.mjs +3 -72
- package/bin/cli/iterate-formatter.mjs +8 -17
- package/bin/cli/iterate-instructions.mjs +13 -47
- package/bin/cli/iterate-lean.mjs +4 -11
- package/bin/cli/list-formatters.mjs +6 -3
- package/bin/cli-parser.iterate-fixtures.mjs +4 -7
- package/bin/cli-parser.mjs +4 -33
- package/bin/commands/check-status.mjs +2 -2
- package/bin/commands/check.mjs +7 -6
- package/bin/commands/commit-suggestion-instruction.mjs +23 -0
- package/bin/commands/iterate/check-instructions.mjs +1 -1
- package/bin/commands/iterate/classify.mjs +9 -4
- package/bin/commands/iterate/escalate.mjs +5 -6
- package/bin/commands/iterate/fix-code.mjs +3 -3
- package/bin/commands/iterate/helpers.mjs +0 -29
- package/bin/commands/iterate/index.mjs +18 -19
- package/bin/commands/iterate/render.mjs +5 -17
- package/bin/commands/resolve-instructions.mjs +2 -10
- package/bin/commands/resolve-mutate.mjs +16 -0
- package/bin/commands/resolve.mjs +6 -17
- package/bin/commands/shepherd-journal.mjs +1 -1
- package/bin/comments/minimize-policy.mjs +15 -0
- package/bin/comments/visible-comments.mjs +20 -0
- package/bin/config/load.mjs +10 -3
- package/bin/config.json +2 -3
- package/bin/github/batch-parsers.mjs +10 -0
- package/bin/github/gql/batch-pr.gql +6 -0
- package/bin/github/queries.mjs +0 -2
- package/bin/index.mjs +0 -2
- package/bin/merge-status/derive.mjs +6 -6
- package/bin/reporters/agent.mjs +10 -3
- package/bin/state/iterate-stall.mjs +9 -0
- package/package.json +5 -6
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +31 -26
- package/bin/commands/iterate.mjs +0 -2
- package/bin/commands/monitor.mjs +0 -139
- package/bin/commands/status.mjs +0 -126
- package/bin/github/gql/multi-pr-status-paged.gql +0 -31
- package/bin/reporters/check-instructions.mjs +0 -69
- package/bin/reporters/json.mjs +0 -10
- package/bin/reporters/text.mjs +0 -156
- package/plugin/skills/check/SKILL.md +0 -37
- package/plugin/skills/monitor/SKILL.md +0 -40
- package/plugin/skills/resolve/SKILL.md +0 -38
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { renderLineRange, renderSuggestionBlock } from "./suggestion-renderer.mjs";
|
|
2
2
|
const BODY_PREVIEW_MAX = 100;
|
|
3
|
+
export function renderAuthor(author, authorType) {
|
|
4
|
+
return authorType ? `@${author} · ${authorType}` : `@${author}`;
|
|
5
|
+
}
|
|
3
6
|
export function renderBodyPreview(body) {
|
|
4
7
|
const normalizedBody = body.replace(/\r\n?/g, "\n");
|
|
5
8
|
const firstLine = normalizedBody.split("\n")[0]?.trim() ?? "";
|
|
@@ -24,7 +27,7 @@ export function renderThreadBullet(t, opts = {}) {
|
|
|
24
27
|
: "`(no location)`";
|
|
25
28
|
const suggestionMarker = t.suggestion ? " [suggestion]" : "";
|
|
26
29
|
const statusSuffix = opts.statusTag ? ` ${opts.statusTag}` : "";
|
|
27
|
-
const bulletLine = `- \`threadId=${t.id}\`${link} ${loc} (
|
|
30
|
+
const bulletLine = `- \`threadId=${t.id}\`${link} ${loc} (${renderAuthor(t.author, t.authorType)})${suggestionMarker}${statusSuffix}: ${renderBodyPreview(t.body)}`;
|
|
28
31
|
if (t.suggestion && opts.renderSuggestion) {
|
|
29
32
|
return `${bulletLine}\n${renderSuggestionBlock(t.suggestion)}`;
|
|
30
33
|
}
|
|
@@ -33,11 +36,11 @@ export function renderThreadBullet(t, opts = {}) {
|
|
|
33
36
|
export function renderCommentBullet(c, opts = {}) {
|
|
34
37
|
const link = c.url ? ` [↗](${c.url})` : "";
|
|
35
38
|
const statusSuffix = opts.statusTag ? ` ${opts.statusTag}` : "";
|
|
36
|
-
return `- \`commentId=${c.id}\`${link} (
|
|
39
|
+
return `- \`commentId=${c.id}\`${link} (${renderAuthor(c.author, c.authorType)})${statusSuffix}: ${renderBodyPreview(c.body)}`;
|
|
37
40
|
}
|
|
38
41
|
export function renderReviewBullet(r, opts = {}) {
|
|
39
42
|
const bodySuffix = opts.includeBody && r.body != null && r.body !== "" ? `: ${renderBodyPreview(r.body)}` : "";
|
|
40
|
-
return `- \`reviewId=${r.id}\` (
|
|
43
|
+
return `- \`reviewId=${r.id}\` (${renderAuthor(r.author, r.authorType)})${bodySuffix}`;
|
|
41
44
|
}
|
|
42
45
|
export function renderReviewListSection(heading, items) {
|
|
43
46
|
if (items.length === 0)
|
|
@@ -7,7 +7,7 @@ export function makeIterateResult(action = "wait") {
|
|
|
7
7
|
mergeStateStatus: "BLOCKED",
|
|
8
8
|
mergeStatus: "BLOCKED",
|
|
9
9
|
reviewDecision: null,
|
|
10
|
-
|
|
10
|
+
blockingBotReviewInProgress: false,
|
|
11
11
|
isDraft: false,
|
|
12
12
|
shouldCancel: false,
|
|
13
13
|
remainingSeconds: 60,
|
|
@@ -15,8 +15,6 @@ export function makeIterateResult(action = "wait") {
|
|
|
15
15
|
baseBranch: "main",
|
|
16
16
|
checks: [],
|
|
17
17
|
};
|
|
18
|
-
if (action === "cooldown")
|
|
19
|
-
return { ...base, action: "cooldown", log: "SKIP: CI still starting" };
|
|
20
18
|
if (action === "wait")
|
|
21
19
|
return { ...base, action: "wait", log: "WAIT: 0 passing, 1 in-progress" };
|
|
22
20
|
if (action === "mark_ready")
|
|
@@ -26,7 +24,6 @@ export function makeIterateResult(action = "wait") {
|
|
|
26
24
|
...base,
|
|
27
25
|
action: "fix_code",
|
|
28
26
|
fix: {
|
|
29
|
-
mode: "rebase-and-push",
|
|
30
27
|
threads: [],
|
|
31
28
|
resolutionOnlyThreads: [],
|
|
32
29
|
actionableComments: [],
|
|
@@ -42,7 +39,7 @@ export function makeIterateResult(action = "wait") {
|
|
|
42
39
|
requiresDismissMessage: false,
|
|
43
40
|
hasMutations: false,
|
|
44
41
|
},
|
|
45
|
-
instructions: ["
|
|
42
|
+
instructions: ["Stop this iteration before the next tick."],
|
|
46
43
|
firstLookThreads: [],
|
|
47
44
|
firstLookComments: [],
|
|
48
45
|
inProgressRunIds: [],
|
|
@@ -55,7 +52,7 @@ export function makeIterateResult(action = "wait") {
|
|
|
55
52
|
...base,
|
|
56
53
|
action: "cancel",
|
|
57
54
|
reason: "ready-delay-elapsed",
|
|
58
|
-
log: "CANCEL: PR #42 — stopping
|
|
55
|
+
log: "CANCEL: PR #42 — stopping",
|
|
59
56
|
};
|
|
60
57
|
if (action === "escalate") {
|
|
61
58
|
return {
|
|
@@ -67,7 +64,7 @@ export function makeIterateResult(action = "wait") {
|
|
|
67
64
|
ambiguousComments: [],
|
|
68
65
|
changesRequestedReviews: [],
|
|
69
66
|
suggestion: "check manually",
|
|
70
|
-
humanMessage: "⚠️ /pr-shepherd:
|
|
67
|
+
humanMessage: "⚠️ /pr-shepherd:pr-shepherd paused — needs human direction",
|
|
71
68
|
},
|
|
72
69
|
};
|
|
73
70
|
}
|
package/bin/cli-parser.mjs
CHANGED
|
@@ -3,35 +3,26 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* pr-shepherd --version
|
|
6
|
-
* pr-shepherd [PR] [--format text|json] [--
|
|
6
|
+
* pr-shepherd [PR] [--format text|json] [--ready-delay Nm]
|
|
7
7
|
* [--stall-timeout <duration>] [--no-auto-mark-ready]
|
|
8
8
|
* [--no-auto-cancel-actionable]
|
|
9
|
-
* pr-shepherd check [PR] [--format text|json]
|
|
10
9
|
* pr-shepherd resolve [PR] [--fetch] [--resolve-thread-ids A,B] [--minimize-comment-ids X,Y]
|
|
11
10
|
* [--dismiss-review-ids Q] [--message MSG] [--require-sha SHA]
|
|
12
11
|
* pr-shepherd commit-suggestion [PR] --thread-id ID [--message MSG] [--description DESC]
|
|
13
12
|
* [--dry-run] [--format text|json]
|
|
14
13
|
* (--message is required unless --dry-run is set)
|
|
15
|
-
* pr-shepherd iterate [PR] [--format text|json] [--
|
|
14
|
+
* pr-shepherd iterate [PR] [--format text|json] [--ready-delay Nm]
|
|
16
15
|
* [--stall-timeout <duration>] [--no-auto-mark-ready]
|
|
17
16
|
* [--no-auto-cancel-actionable]
|
|
18
|
-
* pr-shepherd monitor [PR] [--format text|json]
|
|
19
|
-
* pr-shepherd status PR1 [PR2 …]
|
|
20
17
|
*/
|
|
21
18
|
import { readFileSync } from "node:fs";
|
|
22
|
-
import { runCheck } from "./commands/check.mjs";
|
|
23
19
|
import { runResolveFetch, runResolveMutate } from "./commands/resolve.mjs";
|
|
24
20
|
import { runLogFile } from "./commands/log-file.mjs";
|
|
25
|
-
import { formatJson } from "./reporters/json.mjs";
|
|
26
|
-
import { formatText } from "./reporters/text.mjs";
|
|
27
21
|
import { parseCommonArgs, getFlag, hasFlag, parseList } from "./cli/args.mjs";
|
|
28
22
|
import { isDefaultIterateInvocation, validateDefaultIterateArgs } from "./cli/default-iterate.mjs";
|
|
29
|
-
import { statusToExitCode } from "./cli/exit-codes.mjs";
|
|
30
23
|
import { formatFetchResult, formatMutateResult } from "./cli/formatters.mjs";
|
|
31
|
-
import { handleCommitSuggestion, handleIterate
|
|
24
|
+
import { handleCommitSuggestion, handleIterate } from "./cli/handlers.mjs";
|
|
32
25
|
import { setupLog } from "./log/setup.mjs";
|
|
33
|
-
import { detectAgentRuntime } from "./agent-runtime.mjs";
|
|
34
|
-
import { loadConfig } from "./config/load.mjs";
|
|
35
26
|
// ---------------------------------------------------------------------------
|
|
36
27
|
// Entry
|
|
37
28
|
// ---------------------------------------------------------------------------
|
|
@@ -56,9 +47,6 @@ export async function main(argv) {
|
|
|
56
47
|
return;
|
|
57
48
|
}
|
|
58
49
|
switch (subcommand) {
|
|
59
|
-
case "check":
|
|
60
|
-
await handleCheck(args.slice(1));
|
|
61
|
-
break;
|
|
62
50
|
case "resolve":
|
|
63
51
|
await handleResolve(args.slice(1));
|
|
64
52
|
break;
|
|
@@ -68,15 +56,9 @@ export async function main(argv) {
|
|
|
68
56
|
case "iterate":
|
|
69
57
|
await handleIterate(args.slice(1));
|
|
70
58
|
break;
|
|
71
|
-
case "monitor":
|
|
72
|
-
await handleMonitor(args.slice(1));
|
|
73
|
-
break;
|
|
74
|
-
case "status":
|
|
75
|
-
await handleStatus(args.slice(1));
|
|
76
|
-
break;
|
|
77
59
|
default:
|
|
78
60
|
process.stderr.write(`Unknown subcommand: ${subcommand ?? "(none)"}\n`);
|
|
79
|
-
process.stderr.write("Usage: pr-shepherd <
|
|
61
|
+
process.stderr.write("Usage: pr-shepherd <resolve|commit-suggestion|iterate|log-file> [options]\n" +
|
|
80
62
|
" pr-shepherd --version | -v\n");
|
|
81
63
|
process.exitCode = 1;
|
|
82
64
|
return;
|
|
@@ -90,17 +72,6 @@ function readVersion() {
|
|
|
90
72
|
// ---------------------------------------------------------------------------
|
|
91
73
|
// Subcommand handlers
|
|
92
74
|
// ---------------------------------------------------------------------------
|
|
93
|
-
async function handleCheck(args) {
|
|
94
|
-
const { prNumber, global: globalOpts } = parseCommonArgs(args);
|
|
95
|
-
const runtime = detectAgentRuntime();
|
|
96
|
-
const cfg = loadConfig();
|
|
97
|
-
const report = await runCheck({ ...globalOpts, prNumber, autoResolve: false });
|
|
98
|
-
const output = globalOpts.format === "json"
|
|
99
|
-
? formatJson(report, { runtime, runner: cfg.cli?.runner })
|
|
100
|
-
: formatText(report, { runtime, runner: cfg.cli?.runner });
|
|
101
|
-
process.stdout.write(`${output}\n`);
|
|
102
|
-
process.exitCode = statusToExitCode(report.status);
|
|
103
|
-
}
|
|
104
75
|
async function handleLogFile(args) {
|
|
105
76
|
const jsonOut = args.some((a) => a === "--format=json") ||
|
|
106
77
|
(() => {
|
|
@@ -13,14 +13,14 @@ export function computeStatus(verdict, unresolvedThreads, unresolvedComments, me
|
|
|
13
13
|
// is BLOCKED (review pending, insufficient approvals, branch-protection rule, etc.).
|
|
14
14
|
// Requires hasChecks so that a PR with zero relevant checks (CI never started, or all
|
|
15
15
|
// filtered/skipped) doesn't prematurely trigger READY before any check has reported.
|
|
16
|
-
//
|
|
16
|
+
// blockingBotReviewInProgress is still excluded — a bot review is shepherd's problem, not a hand-off.
|
|
17
17
|
if (verdict.allPassed &&
|
|
18
18
|
verdict.hasChecks &&
|
|
19
19
|
unresolvedThreads === 0 &&
|
|
20
20
|
unresolvedComments === 0 &&
|
|
21
21
|
changesRequestedReviews === 0 &&
|
|
22
22
|
mergeStatus.status === "BLOCKED" &&
|
|
23
|
-
!mergeStatus.
|
|
23
|
+
!mergeStatus.blockingBotReviewInProgress) {
|
|
24
24
|
return "READY";
|
|
25
25
|
}
|
|
26
26
|
if (mergeStatus.status === "BLOCKED" ||
|
package/bin/commands/check.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { getOutdatedThreads } from "../comments/outdated.mjs";
|
|
|
7
7
|
import { autoResolveOutdated } from "../comments/resolve.mjs";
|
|
8
8
|
import { deriveMergeStatus } from "../merge-status/derive.mjs";
|
|
9
9
|
import { loadConfig } from "../config/load.mjs";
|
|
10
|
+
import { classifyVisibleComments } from "../comments/visible-comments.mjs";
|
|
10
11
|
import { computeStatus } from "./check-status.mjs";
|
|
11
12
|
import { loadSeenMap, markSeen, classifyItem } from "../state/seen-comments.mjs";
|
|
12
13
|
export async function runCheck(opts) {
|
|
@@ -41,7 +42,6 @@ export async function runCheck(opts) {
|
|
|
41
42
|
const triaged = failing.length > 0 && !opts.skipTriage ? await triageFailingChecks(failing, repo) : failing;
|
|
42
43
|
const stateKey = { owner: repo.owner, repo: repo.name, pr: prNumber };
|
|
43
44
|
const unresolvedThreads = batchData.reviewThreads.filter((t) => !t.isResolved);
|
|
44
|
-
const visibleComments = batchData.comments.filter((c) => !c.isMinimized);
|
|
45
45
|
const outdated = getOutdatedThreads(unresolvedThreads);
|
|
46
46
|
let autoResolved = [];
|
|
47
47
|
let autoResolveErrors = [];
|
|
@@ -56,6 +56,7 @@ export async function runCheck(opts) {
|
|
|
56
56
|
const minimizedThreadCandidates = batchData.reviewThreads.filter((t) => t.isMinimized && !t.isResolved && !t.isOutdated);
|
|
57
57
|
const minimizedCommentCandidates = batchData.comments.filter((c) => c.isMinimized);
|
|
58
58
|
const seenMap = await loadSeenMap(stateKey);
|
|
59
|
+
const visibleCommentClassification = classifyVisibleComments(batchData.comments, seenMap, config.iterate.minimizeComments);
|
|
59
60
|
const autoResolvedIds = new Set(autoResolved.map((t) => t.id));
|
|
60
61
|
const firstLookThreads = [
|
|
61
62
|
...outdatedCandidates.flatMap((t) => {
|
|
@@ -106,17 +107,16 @@ export async function runCheck(opts) {
|
|
|
106
107
|
await Promise.allSettled([
|
|
107
108
|
...firstLookThreads.map((t) => markSeen(stateKey, t.id, t.body)),
|
|
108
109
|
...firstLookComments.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
110
|
+
...visibleCommentClassification.toMarkSeen.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
109
111
|
...[...firstLookSummaries, ...editedSummaries].map((r) => markSeen(stateKey, r.id, r.body)),
|
|
110
112
|
]);
|
|
111
|
-
const actionableThreads = activeThreads;
|
|
112
113
|
const resolutionOnlyThreads = unresolvedThreads.filter((t) => !autoResolvedIds.has(t.id) && (t.isOutdated || t.isMinimized));
|
|
113
|
-
const actionableComments = visibleComments;
|
|
114
114
|
const mergeStatus = deriveMergeStatus(batchData);
|
|
115
115
|
const blockedByFilteredCheck = mergeStatus.status === "BLOCKED" &&
|
|
116
116
|
!verdict.anyFailing &&
|
|
117
117
|
!verdict.anyInProgress &&
|
|
118
118
|
verdict.filteredNames.length > 0;
|
|
119
|
-
const status = computeStatus(verdict,
|
|
119
|
+
const status = computeStatus(verdict, activeThreads.length + resolutionOnlyThreads.length, visibleCommentClassification.actionable.length, mergeStatus, batchData.changesRequestedReviews.length);
|
|
120
120
|
return {
|
|
121
121
|
pr: prNumber,
|
|
122
122
|
nodeId: batchData.nodeId,
|
|
@@ -134,14 +134,15 @@ export async function runCheck(opts) {
|
|
|
134
134
|
blockedByFilteredCheck,
|
|
135
135
|
},
|
|
136
136
|
threads: {
|
|
137
|
-
actionable:
|
|
137
|
+
actionable: activeThreads,
|
|
138
138
|
resolutionOnly: resolutionOnlyThreads,
|
|
139
139
|
autoResolved,
|
|
140
140
|
autoResolveErrors,
|
|
141
141
|
firstLook: firstLookThreads,
|
|
142
142
|
},
|
|
143
143
|
comments: {
|
|
144
|
-
actionable:
|
|
144
|
+
actionable: visibleCommentClassification.actionable,
|
|
145
|
+
minimizeIds: visibleCommentClassification.minimizeIds,
|
|
145
146
|
firstLook: firstLookComments,
|
|
146
147
|
},
|
|
147
148
|
changesRequestedReviews: batchData.changesRequestedReviews,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Build the `commit-suggestion` instruction step for agent consumers.
|
|
4
|
+
* Shared between `fix_code` (iterate) and `resolve --fetch` paths.
|
|
5
|
+
* @param sectionName - The markdown section heading where suggestion threads appear
|
|
6
|
+
* (e.g. `"## Review threads"` for iterate, `"## Actionable Review Threads"` for resolve).
|
|
7
|
+
* @param includeDriftHint - Whether to add the trailing note about drift on failed apply.
|
|
8
|
+
*/
|
|
9
|
+
export function buildCommitSuggestionInstruction(prNumber, sectionName, includeDriftHint, runner) {
|
|
10
|
+
const command = buildPrShepherdCommand([
|
|
11
|
+
"commit-suggestion",
|
|
12
|
+
String(prNumber),
|
|
13
|
+
"--thread-id",
|
|
14
|
+
"<id>",
|
|
15
|
+
"--message",
|
|
16
|
+
"<one-sentence headline>",
|
|
17
|
+
"--format=json",
|
|
18
|
+
], { runner }).text;
|
|
19
|
+
const driftHint = includeDriftHint
|
|
20
|
+
? " If the patch fails to apply (drift since the suggestion was written), fall through to the manual fix step."
|
|
21
|
+
: " If the patch fails to apply, fall through to the manual-edit step.";
|
|
22
|
+
return `For each thread marked \`[suggestion]\` under \`${sectionName}\`: run \`${command}\` to retrieve the patch and suggested commit. The CLI does not mutate the working tree — apply the patch yourself (run \`git apply\` with the diff shown, or edit the file directly using the line range), then stage the listed file and run the suggested \`git commit\` from the \`## Instructions\` section. Include the thread ID in \`--resolve-thread-ids\` in the resolve command below (the thread is not auto-resolved).${driftHint} Do not retry the same command.`;
|
|
23
|
+
}
|
|
@@ -6,7 +6,7 @@ export function buildFailingCheckInstructions(checks) {
|
|
|
6
6
|
const externalChecks = checks.filter((c) => !c.runId && c.detailsUrl);
|
|
7
7
|
const bareChecks = checks.filter((c) => !c.runId && !c.detailsUrl);
|
|
8
8
|
if (failedRunIdChecks.length > 0) {
|
|
9
|
-
instructions.push(`For each failing check under \`## Failing checks\` with a run ID and no \`[conclusion: CANCELLED]\` or \`[conclusion: STARTUP_FAILURE]\` tag: run \`gh run view <runId> --log-failed\` to fetch the failing job's log
|
|
9
|
+
instructions.push(`For each failing check under \`## Failing checks\` with a run ID and no \`[conclusion: CANCELLED]\` or \`[conclusion: STARTUP_FAILURE]\` tag: run \`gh run view <runId> --log-failed\` to fetch the failing job's log. If the log shows a transient infrastructure failure (network timeout, runner setup crash, OOM kill), run \`gh run rerun <runId> --failed\`. If the log shows a real test/build failure, apply a code fix.`);
|
|
10
10
|
}
|
|
11
11
|
if (cancelledRunIdChecks.length > 0) {
|
|
12
12
|
instructions.push(`For each \`[conclusion: CANCELLED]\` bullet under \`## Failing checks\`: the run was cancelled outside Shepherd's control (manual cancel, newer push, concurrency-group eviction). Run \`gh run rerun <runId>\` only if the cancellation looks unintended; otherwise treat it as resolved by the superseding run. Do NOT confuse these with IDs under \`## Cancelled runs\` — those were cancelled by Shepherd itself.`);
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { buildPrShepherdCommand } from "../../cli/runner.mjs";
|
|
2
|
-
|
|
2
|
+
import { shouldMinimizeAuthor } from "../../comments/minimize-policy.mjs";
|
|
3
|
+
export function classifyReviewSummaries(summaries, approvals, minimizeApprovals, minimizeComments = "all") {
|
|
3
4
|
// First-look and seen summaries go into the minimize mutation; edited summaries do NOT —
|
|
4
5
|
// they are already minimized server-side (body changed after minimize was applied).
|
|
5
6
|
// First-look bodies are rendered so the agent sees them before the minimize happens.
|
|
6
|
-
const minimizeIds = [...summaries.firstLook, ...summaries.seen]
|
|
7
|
+
const minimizeIds = [...summaries.firstLook, ...summaries.seen]
|
|
8
|
+
.filter((r) => shouldMinimizeAuthor(r.authorType, minimizeComments))
|
|
9
|
+
.map((r) => r.id);
|
|
7
10
|
if (minimizeApprovals) {
|
|
8
|
-
for (const r of approvals)
|
|
9
|
-
|
|
11
|
+
for (const r of approvals) {
|
|
12
|
+
if (shouldMinimizeAuthor(r.authorType, minimizeComments))
|
|
13
|
+
minimizeIds.push(r.id);
|
|
14
|
+
}
|
|
10
15
|
return {
|
|
11
16
|
minimizeIds,
|
|
12
17
|
firstLookSummaries: summaries.firstLook,
|
|
@@ -55,7 +55,7 @@ export function validateBaseBranch(raw) {
|
|
|
55
55
|
}
|
|
56
56
|
export function buildEscalateHumanMessage(escalate, pr) {
|
|
57
57
|
const lines = [];
|
|
58
|
-
lines.push("⚠️ /pr-shepherd:
|
|
58
|
+
lines.push("⚠️ /pr-shepherd:pr-shepherd paused — needs human direction");
|
|
59
59
|
lines.push("");
|
|
60
60
|
lines.push(`**Triggers:** ${escalate.triggers.map((t) => `\`${t}\``).join(", ")}`);
|
|
61
61
|
lines.push("");
|
|
@@ -80,18 +80,17 @@ export function buildEscalateHumanMessage(escalate, pr) {
|
|
|
80
80
|
lines.push(`- comment \`${c.id}\` (@${c.author}): ${firstLine}`);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
if (escalate.
|
|
83
|
+
if (escalate.thrashHistory && escalate.thrashHistory.length > 0) {
|
|
84
84
|
lines.push("");
|
|
85
85
|
lines.push("## Fix attempts");
|
|
86
|
-
for (const a of escalate.
|
|
86
|
+
for (const a of escalate.thrashHistory) {
|
|
87
87
|
lines.push(`- thread \`${a.threadId}\` attempted ${a.attempts} times`);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
lines.push("");
|
|
91
91
|
lines.push("---");
|
|
92
92
|
lines.push("");
|
|
93
|
-
lines.push(`
|
|
94
|
-
lines.push(`After fixing manually, rerun \`/pr-shepherd:monitor ${pr}\` to resume.`);
|
|
93
|
+
lines.push(`After fixing manually, rerun \`/pr-shepherd:pr-shepherd ${pr}\` to resume.`);
|
|
95
94
|
return lines.join("\n");
|
|
96
95
|
}
|
|
97
96
|
export function buildEscalateSuggestion(triggers, detail) {
|
|
@@ -104,7 +103,7 @@ export function buildEscalateSuggestion(triggers, detail) {
|
|
|
104
103
|
return `Could not determine the PR's base branch${reason} — refusing to emit a rebase that could force-push onto the wrong base. Run the rebase manually against the PR's real target branch.`;
|
|
105
104
|
}
|
|
106
105
|
if (triggers.includes("fix-thrash")) {
|
|
107
|
-
return "Same thread(s) attempted multiple times without resolution — fix manually then rerun /pr-shepherd:
|
|
106
|
+
return "Same thread(s) attempted multiple times without resolution — fix manually then rerun /pr-shepherd:pr-shepherd";
|
|
108
107
|
}
|
|
109
108
|
if (triggers.includes("pr-level-changes-requested")) {
|
|
110
109
|
return "Reviewer requested changes but left no inline comments — read the review and act manually";
|
|
@@ -23,7 +23,7 @@ export async function handleFixCode(ctx) {
|
|
|
23
23
|
unresolvedThreads: [...report.threads.actionable, ...report.threads.resolutionOnly].map(toAgentThread),
|
|
24
24
|
ambiguousComments: report.comments.actionable.map(toAgentComment),
|
|
25
25
|
changesRequestedReviews: report.changesRequestedReviews,
|
|
26
|
-
|
|
26
|
+
thrashHistory: escalateTriggers.thrashHistory,
|
|
27
27
|
suggestion: buildEscalateSuggestion(escalateTriggers.triggers),
|
|
28
28
|
};
|
|
29
29
|
return {
|
|
@@ -56,7 +56,8 @@ export async function handleFixCode(ctx) {
|
|
|
56
56
|
const inProgressRunIds = hasGuaranteedSupersedingPush
|
|
57
57
|
? buildInProgressRunIds(report, cancelledSet)
|
|
58
58
|
: [];
|
|
59
|
-
const
|
|
59
|
+
const commentMinimizeIds = report.comments.minimizeIds ?? actionableComments.map((c) => c.id);
|
|
60
|
+
const allCommentIds = [...commentMinimizeIds, ...reviewSummaryIds];
|
|
60
61
|
const resolveCommand = buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, changesRequestedReviews, checks, prNumber, cliRunner);
|
|
61
62
|
if (baseLookup.isFallback && (resolveCommand.requiresHeadSha || hasConflicts)) {
|
|
62
63
|
const fallbackEscalateBase = {
|
|
@@ -83,7 +84,6 @@ export async function handleFixCode(ctx) {
|
|
|
83
84
|
baseBranch: baseLookup.branch,
|
|
84
85
|
action: "fix_code",
|
|
85
86
|
fix: {
|
|
86
|
-
mode: "rebase-and-push",
|
|
87
87
|
threads,
|
|
88
88
|
resolutionOnlyThreads,
|
|
89
89
|
actionableComments,
|
|
@@ -58,15 +58,6 @@ export function buildRelevantChecks(report) {
|
|
|
58
58
|
});
|
|
59
59
|
return [...passing, ...failing];
|
|
60
60
|
}
|
|
61
|
-
export async function getLastCommitTime() {
|
|
62
|
-
try {
|
|
63
|
-
const { stdout } = await execFile("git", ["log", "-1", "--format=%ct", "HEAD"]);
|
|
64
|
-
return parseInt(stdout.trim(), 10);
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
61
|
// Best-effort: cancelling a completed run is a no-op, not an error.
|
|
71
62
|
export async function tryCancelRun(runId, owner, repo) {
|
|
72
63
|
try {
|
|
@@ -118,23 +109,3 @@ export function buildWaitLog(base) {
|
|
|
118
109
|
}
|
|
119
110
|
return parts.join(" — ");
|
|
120
111
|
}
|
|
121
|
-
export function buildCooldownResult(prNumber, readyDelaySeconds) {
|
|
122
|
-
return {
|
|
123
|
-
action: "cooldown",
|
|
124
|
-
pr: prNumber,
|
|
125
|
-
repo: "",
|
|
126
|
-
status: "UNKNOWN",
|
|
127
|
-
state: "UNKNOWN",
|
|
128
|
-
mergeStateStatus: "UNKNOWN",
|
|
129
|
-
mergeStatus: "UNKNOWN",
|
|
130
|
-
reviewDecision: null,
|
|
131
|
-
copilotReviewInProgress: false,
|
|
132
|
-
isDraft: false,
|
|
133
|
-
shouldCancel: false,
|
|
134
|
-
remainingSeconds: readyDelaySeconds,
|
|
135
|
-
summary: { passing: 0, skipped: 0, filtered: 0, inProgress: 0 },
|
|
136
|
-
baseBranch: "",
|
|
137
|
-
checks: [],
|
|
138
|
-
log: "SKIP: CI still starting — waiting for first check to appear",
|
|
139
|
-
};
|
|
140
|
-
}
|
|
@@ -4,13 +4,13 @@ import { getCurrentPrNumber } from "../../github/client.mjs";
|
|
|
4
4
|
import { graphql } from "../../github/http.mjs";
|
|
5
5
|
import { MARK_PR_READY_MUTATION } from "../../github/queries.mjs";
|
|
6
6
|
import { loadConfig } from "../../config/load.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { getCurrentHeadSha, buildSummary, buildRelevantChecks, buildWaitLog } from "./helpers.mjs";
|
|
8
8
|
import { classifyReviewSummaries } from "./classify.mjs";
|
|
9
9
|
import { applyStallGuard } from "./stall.mjs";
|
|
10
|
+
import { clearStallState } from "../../state/iterate-stall.mjs";
|
|
10
11
|
import { handleFixCode } from "./fix-code.mjs";
|
|
11
12
|
export async function runIterate(opts) {
|
|
12
13
|
const config = loadConfig();
|
|
13
|
-
const cooldownSeconds = opts.cooldownSeconds ?? config.iterate.cooldownSeconds;
|
|
14
14
|
const readyDelaySeconds = opts.readyDelaySeconds ?? config.watch.readyDelayMinutes * 60;
|
|
15
15
|
const stallTimeoutSeconds = opts.stallTimeoutSeconds ?? config.iterate.stallTimeoutMinutes * 60;
|
|
16
16
|
const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
|
|
@@ -18,17 +18,18 @@ export async function runIterate(opts) {
|
|
|
18
18
|
throw new Error("No open PR found for current branch. Pass a PR number explicitly.");
|
|
19
19
|
}
|
|
20
20
|
const optsWithPr = { ...opts, prNumber };
|
|
21
|
-
const lastCommitTime = await getLastCommitTime();
|
|
22
|
-
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
23
|
-
if (lastCommitTime !== null && nowSeconds - lastCommitTime < cooldownSeconds) {
|
|
24
|
-
return buildCooldownResult(prNumber, readyDelaySeconds);
|
|
25
|
-
}
|
|
26
21
|
const report = await runCheck({
|
|
27
22
|
...optsWithPr,
|
|
28
23
|
autoResolve: config.actions.autoResolveOutdated,
|
|
29
24
|
});
|
|
25
|
+
const [repoOwner, repoName] = report.repo.split("/");
|
|
26
|
+
if (!repoOwner || !repoName) {
|
|
27
|
+
throw new Error(`Unexpected repo format: "${report.repo}" (expected "owner/name")`);
|
|
28
|
+
}
|
|
29
|
+
const stallKey = { owner: repoOwner, repo: repoName, pr: prNumber };
|
|
30
30
|
if (report.mergeStatus.state !== "OPEN") {
|
|
31
31
|
const state = report.mergeStatus.state.toLowerCase();
|
|
32
|
+
await clearStallState(stallKey);
|
|
32
33
|
return {
|
|
33
34
|
pr: report.pr,
|
|
34
35
|
repo: report.repo,
|
|
@@ -36,7 +37,7 @@ export async function runIterate(opts) {
|
|
|
36
37
|
mergeStateStatus: report.mergeStatus.mergeStateStatus,
|
|
37
38
|
mergeStatus: report.mergeStatus.status,
|
|
38
39
|
reviewDecision: report.mergeStatus.reviewDecision,
|
|
39
|
-
|
|
40
|
+
blockingBotReviewInProgress: report.mergeStatus.blockingBotReviewInProgress,
|
|
40
41
|
isDraft: report.mergeStatus.isDraft,
|
|
41
42
|
shouldCancel: true,
|
|
42
43
|
remainingSeconds: 0,
|
|
@@ -46,13 +47,9 @@ export async function runIterate(opts) {
|
|
|
46
47
|
checks: buildRelevantChecks(report),
|
|
47
48
|
action: "cancel",
|
|
48
49
|
reason: report.mergeStatus.state === "MERGED" ? "merged" : "closed",
|
|
49
|
-
log: `CANCEL: PR #${report.pr} is ${state} — stopping
|
|
50
|
+
log: `CANCEL: PR #${report.pr} is ${state} — stopping`,
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
|
-
const [repoOwner, repoName] = report.repo.split("/");
|
|
53
|
-
if (!repoOwner || !repoName) {
|
|
54
|
-
throw new Error(`Unexpected repo format: "${report.repo}" (expected "owner/name")`);
|
|
55
|
-
}
|
|
56
53
|
const isReady = report.status === "READY";
|
|
57
54
|
const readyState = await updateReadyDelay(report.pr, isReady, readyDelaySeconds, repoOwner, repoName);
|
|
58
55
|
const base = {
|
|
@@ -63,7 +60,7 @@ export async function runIterate(opts) {
|
|
|
63
60
|
mergeStateStatus: report.mergeStatus.mergeStateStatus,
|
|
64
61
|
mergeStatus: report.mergeStatus.status,
|
|
65
62
|
reviewDecision: report.mergeStatus.reviewDecision,
|
|
66
|
-
|
|
63
|
+
blockingBotReviewInProgress: report.mergeStatus.blockingBotReviewInProgress,
|
|
67
64
|
isDraft: report.mergeStatus.isDraft,
|
|
68
65
|
shouldCancel: readyState.shouldCancel,
|
|
69
66
|
remainingSeconds: readyState.remainingSeconds,
|
|
@@ -72,6 +69,7 @@ export async function runIterate(opts) {
|
|
|
72
69
|
checks: buildRelevantChecks(report),
|
|
73
70
|
};
|
|
74
71
|
if (readyState.shouldCancel) {
|
|
72
|
+
await clearStallState(stallKey);
|
|
75
73
|
let cancelNote;
|
|
76
74
|
if (base.mergeStatus !== "BLOCKED")
|
|
77
75
|
cancelNote = "has been ready for review";
|
|
@@ -85,23 +83,24 @@ export async function runIterate(opts) {
|
|
|
85
83
|
...base,
|
|
86
84
|
action: "cancel",
|
|
87
85
|
reason: "ready-delay-elapsed",
|
|
88
|
-
log: `CANCEL: PR #${base.pr} ${cancelNote} — ready-delay elapsed, stopping
|
|
86
|
+
log: `CANCEL: PR #${base.pr} ${cancelNote} — ready-delay elapsed, stopping`,
|
|
89
87
|
};
|
|
90
88
|
}
|
|
91
89
|
const headSha = (await getCurrentHeadSha()) ?? "unknown";
|
|
92
|
-
const stallKey = { owner: repoOwner, repo: repoName, pr: prNumber };
|
|
93
90
|
const { minimizeIds: reviewSummaryIds, firstLookSummaries, editedSummaries, surfacedApprovals, } = classifyReviewSummaries({
|
|
94
91
|
firstLook: report.firstLookSummaries,
|
|
95
92
|
seen: report.reviewSummaries,
|
|
96
93
|
edited: report.editedSummaries,
|
|
97
|
-
}, report.approvedReviews, config.iterate.minimizeApprovals);
|
|
94
|
+
}, report.approvedReviews, config.iterate.minimizeApprovals, config.iterate.minimizeComments);
|
|
98
95
|
const hasActionableWork = report.threads.actionable.length > 0 ||
|
|
99
96
|
report.threads.resolutionOnly.length > 0 ||
|
|
100
97
|
report.comments.actionable.length > 0 ||
|
|
101
98
|
report.changesRequestedReviews.length > 0 ||
|
|
102
99
|
report.checks.failing.length > 0 ||
|
|
103
100
|
report.mergeStatus.status === "CONFLICTS" ||
|
|
104
|
-
reviewSummaryIds.length > 0
|
|
101
|
+
reviewSummaryIds.length > 0 ||
|
|
102
|
+
firstLookSummaries.length > 0 ||
|
|
103
|
+
editedSummaries.length > 0;
|
|
105
104
|
if (hasActionableWork) {
|
|
106
105
|
return handleFixCode({
|
|
107
106
|
base,
|
|
@@ -122,7 +121,7 @@ export async function runIterate(opts) {
|
|
|
122
121
|
}
|
|
123
122
|
const canMarkReady = report.status === "READY" &&
|
|
124
123
|
report.mergeStatus.isDraft &&
|
|
125
|
-
!report.mergeStatus.
|
|
124
|
+
!report.mergeStatus.blockingBotReviewInProgress &&
|
|
126
125
|
!readyState.shouldCancel;
|
|
127
126
|
if (canMarkReady && !opts.noAutoMarkReady && config.actions.autoMarkReady) {
|
|
128
127
|
await graphql(MARK_PR_READY_MUTATION, { pullRequestId: report.nodeId });
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { renderShellCommand } from "../../cli/runner.mjs";
|
|
2
2
|
import { buildFailingCheckInstructions } from "./check-instructions.mjs";
|
|
3
3
|
import { SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE, SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS, buildShepherdJournalInstruction, } from "../shepherd-journal.mjs";
|
|
4
|
+
import { buildCommitSuggestionInstruction } from "../commit-suggestion-instruction.mjs";
|
|
4
5
|
export const FIX_INSTRUCTION_STOP_AFTER_PUSH = "Stop this iteration — CI needs time to run on the new push before the next tick.";
|
|
5
6
|
export const FIX_INSTRUCTION_STOP_BEFORE_NEXT_TICK = "Stop this iteration before the next tick.";
|
|
6
|
-
export const FIX_INSTRUCTION_END_ITERATION = "End this iteration.";
|
|
7
7
|
/**
|
|
8
8
|
* Render a resolve command as a shell snippet. Wraps `$DISMISS_MESSAGE`, `$HEAD_SHA`, and
|
|
9
9
|
* whitespace-bearing argv entries for placeholder substitution. `$HEAD_SHA` is appended separately
|
|
@@ -19,20 +19,11 @@ export function renderResolveCommand(rc) {
|
|
|
19
19
|
export function buildFixInstructions(threads, actionableComments, checks, reviews, baseBranch, resolveCommand, hasConflicts, prNumber, cancelledCount, firstLookThreads = [], firstLookComments = [], firstLookSummaries = [], editedSummaries = [], inProgressRunIds = [], resolutionOnlyThreads = [], runner) {
|
|
20
20
|
const instructions = [];
|
|
21
21
|
if (inProgressRunIds.length > 0) {
|
|
22
|
-
instructions.push(`Cancel in-progress CI runs first: for each ID under \`## In-progress runs\`, run \`gh run cancel <id
|
|
22
|
+
instructions.push(`Cancel in-progress CI runs first: for each ID under \`## In-progress runs\`, run \`gh run cancel <id>\` before applying code fixes. If \`gh\` reports a run is already completed, ignore it and continue with the next ID.`);
|
|
23
23
|
}
|
|
24
24
|
const hasSuggestions = threads.some((t) => t.suggestion);
|
|
25
25
|
if (hasSuggestions) {
|
|
26
|
-
|
|
27
|
-
"commit-suggestion",
|
|
28
|
-
String(prNumber),
|
|
29
|
-
"--thread-id",
|
|
30
|
-
"<id>",
|
|
31
|
-
"--message",
|
|
32
|
-
"<one-sentence headline>",
|
|
33
|
-
"--format=json",
|
|
34
|
-
], { runner }).text;
|
|
35
|
-
instructions.push(`For each thread marked \`[suggestion]\` under \`## Review threads\`: run \`${commitSuggestionCommand}\` to retrieve the patch and suggested commit. The CLI does not mutate the working tree — apply the patch yourself (run \`git apply\` with the diff shown, or edit the file directly using the line range), then stage the listed file and run the suggested \`git commit\` from the \`## Instructions\` section. Include the thread ID in \`--resolve-thread-ids\` in the \`resolve:\` command below (the thread is not auto-resolved). If the patch fails to apply, fall through to the manual-edit step. Do not retry the same command.`);
|
|
26
|
+
instructions.push(buildCommitSuggestionInstruction(prNumber, "## Review threads", false, runner));
|
|
36
27
|
}
|
|
37
28
|
if (threads.length > 0 || actionableComments.length > 0) {
|
|
38
29
|
const suggestionFallback = hasSuggestions
|
|
@@ -97,11 +88,8 @@ export function buildFixInstructions(threads, actionableComments, checks, review
|
|
|
97
88
|
if (needsPush) {
|
|
98
89
|
instructions.push(FIX_INSTRUCTION_STOP_AFTER_PUSH);
|
|
99
90
|
}
|
|
100
|
-
else if (resolveCommand.hasMutations) {
|
|
101
|
-
instructions.push(FIX_INSTRUCTION_STOP_BEFORE_NEXT_TICK);
|
|
102
|
-
}
|
|
103
91
|
else {
|
|
104
|
-
instructions.push(
|
|
92
|
+
instructions.push(FIX_INSTRUCTION_STOP_BEFORE_NEXT_TICK);
|
|
105
93
|
}
|
|
106
94
|
return instructions;
|
|
107
95
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
2
2
|
import { SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEMS, buildShepherdJournalInstruction, } from "./shepherd-journal.mjs";
|
|
3
|
+
import { buildCommitSuggestionInstruction } from "./commit-suggestion-instruction.mjs";
|
|
3
4
|
/**
|
|
4
5
|
* Build the numbered triage/fix/resolve instruction steps for the agent to follow.
|
|
5
6
|
* Steps are conditionally emitted based on what the fetch returned (mirrors
|
|
@@ -32,16 +33,7 @@ export function buildFetchInstructions(prNumber, result, runner) {
|
|
|
32
33
|
instructions.push(`First-look bullets tagged \`, edited\` were updated by their author after you previously acknowledged them. Read the updated body before deciding whether any matching \`## Review threads to resolve\` item should be resolved.`);
|
|
33
34
|
}
|
|
34
35
|
if (hasSuggestions) {
|
|
35
|
-
|
|
36
|
-
"commit-suggestion",
|
|
37
|
-
String(prNumber),
|
|
38
|
-
"--thread-id",
|
|
39
|
-
"<id>",
|
|
40
|
-
"--message",
|
|
41
|
-
"<one-sentence headline>",
|
|
42
|
-
"--format=json",
|
|
43
|
-
], { runner }).text;
|
|
44
|
-
instructions.push(`For each Actionable thread marked \`[suggestion]\` in \`## Actionable Review Threads\` above: run \`${commitSuggestionCommand}\` to retrieve the patch and suggested commit. The CLI does not mutate the working tree — apply the patch yourself (run \`git apply\` with the diff shown, or edit the file directly using the line range), then stage the listed file and run the suggested \`git commit\` from the \`## Instructions\` section. Include the thread ID in \`--resolve-thread-ids\` in the resolve command below (the thread is not auto-resolved). If the patch fails to apply (drift since the suggestion was written), fall through to the manual fix step. Do not retry the same \`commit-suggestion\` invocation.`);
|
|
36
|
+
instructions.push(buildCommitSuggestionInstruction(prNumber, "## Actionable Review Threads", true, runner));
|
|
45
37
|
}
|
|
46
38
|
if (hasCodeItems) {
|
|
47
39
|
instructions.push(`Read and edit each file referenced under \`## Actionable Review Threads\`, \`## Actionable PR Comments\`, and \`## Pending CHANGES_REQUESTED reviews\` above. Reclassify each fixed item as Fixed. If an item is too complex to address, leave it as Actionable for the final report.`);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getRepoInfo, getCurrentPrNumber } from "../github/client.mjs";
|
|
2
|
+
import { applyResolveOptions } from "../comments/resolve.mjs";
|
|
3
|
+
export async function runResolveMutate(opts) {
|
|
4
|
+
const repo = await getRepoInfo();
|
|
5
|
+
const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
|
|
6
|
+
if (prNumber === null) {
|
|
7
|
+
throw new Error("No open PR found for current branch. Pass a PR number explicitly.");
|
|
8
|
+
}
|
|
9
|
+
return applyResolveOptions(prNumber, repo, {
|
|
10
|
+
resolveThreadIds: opts.resolveThreadIds,
|
|
11
|
+
minimizeCommentIds: opts.minimizeCommentIds,
|
|
12
|
+
dismissReviewIds: opts.dismissReviewIds,
|
|
13
|
+
dismissMessage: opts.dismissMessage,
|
|
14
|
+
requireSha: opts.requireSha,
|
|
15
|
+
});
|
|
16
|
+
}
|