pr-shepherd 0.25.2 → 0.25.3
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/bin/cli/fix-formatter.mjs +2 -0
- package/bin/cli/iterate-lean.mjs +3 -0
- package/bin/cli/resolve-validators.mjs +8 -0
- package/bin/cli-parser.mjs +3 -1
- package/bin/commands/iterate/classify.mjs +41 -11
- package/bin/commands/iterate/fix-code.mjs +3 -2
- package/bin/commands/iterate/render.mjs +6 -7
- package/bin/commands/shepherd-journal.mjs +1 -1
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
|
@@ -121,6 +121,8 @@ export function formatFixCodeResult(header, result, opts) {
|
|
|
121
121
|
}
|
|
122
122
|
sections.push("## Post-fix push");
|
|
123
123
|
const postFixLines = [`- base: \`${result.baseBranch}\``];
|
|
124
|
+
if (result.fix.resolveOnlyCommand?.hasMutations)
|
|
125
|
+
postFixLines.push(`- resolve-only: \`${renderResolveCommand(result.fix.resolveOnlyCommand)}\``);
|
|
124
126
|
if (result.fix.resolveCommand.hasMutations) {
|
|
125
127
|
postFixLines.push(`- resolve: \`${renderResolveCommand(result.fix.resolveCommand)}\``);
|
|
126
128
|
}
|
package/bin/cli/iterate-lean.mjs
CHANGED
|
@@ -92,6 +92,9 @@ export function projectIterateLean(result, opts) {
|
|
|
92
92
|
changesRequestedReviews: result.fix.changesRequestedReviews,
|
|
93
93
|
}),
|
|
94
94
|
resolveCommand: result.fix.resolveCommand,
|
|
95
|
+
...(result.fix.resolveOnlyCommand && {
|
|
96
|
+
resolveOnlyCommand: result.fix.resolveOnlyCommand,
|
|
97
|
+
}),
|
|
95
98
|
...(result.fix.instructions.length > 0 && {
|
|
96
99
|
instructions: adaptFixCodeInstructions(result.fix.instructions, result.pr, readyDelaySuffix),
|
|
97
100
|
}),
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export function rejectPrrcMinimizeIds(ids) {
|
|
2
|
+
const prrcIds = ids.filter((id) => id.startsWith("PRRC_"));
|
|
3
|
+
if (prrcIds.length > 0) {
|
|
4
|
+
process.stderr.write(`pr-shepherd: resolve: --minimize-comment-ids contains thread comment IDs (PRRC_*): ${prrcIds.join(", ")}. Thread comments cannot be minimized individually — resolve the parent thread using --resolve-thread-ids with the PRRT_* thread ID instead.\n`);
|
|
5
|
+
process.exitCode = 1;
|
|
6
|
+
}
|
|
7
|
+
return prrcIds;
|
|
8
|
+
}
|
|
1
9
|
export function warnPrrcThreadIds(ids) {
|
|
2
10
|
const prrcIds = ids.filter((id) => id.startsWith("PRRC_"));
|
|
3
11
|
if (prrcIds.length > 0) {
|
package/bin/cli-parser.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { USAGE, maybePrintHelp } from "./cli/help.mjs";
|
|
|
30
30
|
import { formatMutateResult } from "./cli/formatters.mjs";
|
|
31
31
|
import { handleClean, handleCommitSuggestion, handleIterate, handleMarkFilesAsViewed, } from "./cli/handlers.mjs";
|
|
32
32
|
import { handlePoll } from "./cli/poll-handler.mjs";
|
|
33
|
-
import { warnPrrcThreadIds, validateRequireSha } from "./cli/resolve-validators.mjs";
|
|
33
|
+
import { warnPrrcThreadIds, validateRequireSha, rejectPrrcMinimizeIds, } from "./cli/resolve-validators.mjs";
|
|
34
34
|
import { setupLog } from "./log/setup.mjs";
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
36
|
// Entry
|
|
@@ -134,6 +134,8 @@ async function handleResolve(args) {
|
|
|
134
134
|
warnPrrcThreadIds(resolveThreadIds);
|
|
135
135
|
if (!validateRequireSha(requireSha))
|
|
136
136
|
return;
|
|
137
|
+
if (rejectPrrcMinimizeIds(minimizeCommentIds).length > 0)
|
|
138
|
+
return;
|
|
137
139
|
if (hasFlag(extra, "--fetch")) {
|
|
138
140
|
process.stderr.write("pr-shepherd: resolve: --fetch has been removed; run pr-shepherd iterate or poll to fetch the next action.\n");
|
|
139
141
|
process.exitCode = 1;
|
|
@@ -44,7 +44,6 @@ export function classifyReviewSummaries(summaries, approvals, minimizeApprovals,
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, _reviews, checks, prNumber, botUsernames = new Set()) {
|
|
47
|
-
const argv = buildPrShepherdCommand(["resolve", String(prNumber)]).argv;
|
|
48
47
|
const allThreads = [...threads, ...resolutionOnlyThreads];
|
|
49
48
|
const replyThreadIds = dedupeIds(allThreads
|
|
50
49
|
.filter((t) => isHumanAuthor(t) && !isConfiguredBotAuthor(t, botUsernames))
|
|
@@ -52,6 +51,41 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
|
|
|
52
51
|
const resolveThreadIds = dedupeIds(allThreads
|
|
53
52
|
.filter((t) => !isHumanAuthor(t) || isConfiguredBotAuthor(t, botUsernames))
|
|
54
53
|
.map((t) => t.id));
|
|
54
|
+
const hasReply = replyThreadIds.length > 0;
|
|
55
|
+
const hasResolveOrMinimize = resolveThreadIds.length > 0 || allCommentIds.length > 0;
|
|
56
|
+
if (hasReply && hasResolveOrMinimize) {
|
|
57
|
+
// Split: reply command needs SHA; resolve/minimize command does not.
|
|
58
|
+
const resolveArgv = buildPrShepherdCommand(["resolve", String(prNumber)]).argv;
|
|
59
|
+
resolveArgv.push("--reply-thread-ids", replyThreadIds.join(","));
|
|
60
|
+
resolveArgv.push("--message", "$DISMISS_MESSAGE");
|
|
61
|
+
// `requiresHeadSha` is only true when actionable thread fixes or failing
|
|
62
|
+
// checks are being addressed — mutations that can race with a moving HEAD.
|
|
63
|
+
const requiresHeadSha = threads.length > 0 || checks.length > 0;
|
|
64
|
+
const resolveCommand = {
|
|
65
|
+
argv: resolveArgv,
|
|
66
|
+
requiresHeadSha,
|
|
67
|
+
requiresDismissMessage: true,
|
|
68
|
+
replyThreadIds,
|
|
69
|
+
hasMutations: true,
|
|
70
|
+
};
|
|
71
|
+
const resolveOnlyArgv = buildPrShepherdCommand(["resolve", String(prNumber)]).argv;
|
|
72
|
+
if (resolveThreadIds.length > 0) {
|
|
73
|
+
resolveOnlyArgv.push("--resolve-thread-ids", resolveThreadIds.join(","));
|
|
74
|
+
}
|
|
75
|
+
if (allCommentIds.length > 0) {
|
|
76
|
+
resolveOnlyArgv.push("--minimize-comment-ids", allCommentIds.join(","));
|
|
77
|
+
}
|
|
78
|
+
const resolveOnlyCommand = {
|
|
79
|
+
argv: resolveOnlyArgv,
|
|
80
|
+
requiresHeadSha: false,
|
|
81
|
+
requiresDismissMessage: false,
|
|
82
|
+
...(resolveThreadIds.length > 0 ? { resolveThreadIds } : undefined),
|
|
83
|
+
hasMutations: true,
|
|
84
|
+
};
|
|
85
|
+
return { resolveCommand, resolveOnlyCommand };
|
|
86
|
+
}
|
|
87
|
+
// Single command: either reply-only or resolve/minimize-only (no split needed).
|
|
88
|
+
const argv = buildPrShepherdCommand(["resolve", String(prNumber)]).argv;
|
|
55
89
|
if (replyThreadIds.length > 0) {
|
|
56
90
|
argv.push("--reply-thread-ids", replyThreadIds.join(","));
|
|
57
91
|
argv.push("--message", "$DISMISS_MESSAGE");
|
|
@@ -62,16 +96,11 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
|
|
|
62
96
|
if (allCommentIds.length > 0) {
|
|
63
97
|
argv.push("--minimize-comment-ids", allCommentIds.join(","));
|
|
64
98
|
}
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
// mutation that can race with a moving HEAD: replying after actionable
|
|
71
|
-
// thread fixes or addressing failing checks.
|
|
72
|
-
const hasCodeMutations = hasMutations && (threads.length > 0 || checks.length > 0);
|
|
73
|
-
const requiresHeadSha = hasCodeMutations;
|
|
74
|
-
return {
|
|
99
|
+
const hasMutations = hasReply || hasResolveOrMinimize;
|
|
100
|
+
// SHA is only required when replying after actionable fixes or failing checks.
|
|
101
|
+
// Resolve/minimize-only mutations never need SHA.
|
|
102
|
+
const requiresHeadSha = hasReply && (threads.length > 0 || checks.length > 0);
|
|
103
|
+
const resolveCommand = {
|
|
75
104
|
argv,
|
|
76
105
|
requiresHeadSha,
|
|
77
106
|
requiresDismissMessage: replyThreadIds.length > 0,
|
|
@@ -79,4 +108,5 @@ export function buildResolveCommand(threads, resolutionOnlyThreads, allCommentId
|
|
|
79
108
|
...(resolveThreadIds.length > 0 ? { resolveThreadIds } : undefined),
|
|
80
109
|
hasMutations,
|
|
81
110
|
};
|
|
111
|
+
return { resolveCommand };
|
|
82
112
|
}
|
|
@@ -76,7 +76,7 @@ export async function handleFixCode(ctx) {
|
|
|
76
76
|
const inProgressRunIds = pushLikely ? buildInProgressRunIds(report, cancelledSet) : [];
|
|
77
77
|
const commentMinimizeIds = report.comments.minimizeIds ?? actionableComments.map((c) => c.id);
|
|
78
78
|
const allCommentIds = [...commentMinimizeIds, ...reviewSummaryIds];
|
|
79
|
-
const resolveCommand = buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, changesRequestedReviews, checks, prNumber, botUsernames);
|
|
79
|
+
const { resolveCommand, resolveOnlyCommand } = buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, changesRequestedReviews, checks, prNumber, botUsernames);
|
|
80
80
|
// Safety: if the base branch is unknown, escalate when a push is plausible — the agent
|
|
81
81
|
// would need the correct base to rebase safely. This is a conservative guard, not a
|
|
82
82
|
// prediction that the agent *will* push. Intentionally broader than `pushLikely` above:
|
|
@@ -106,7 +106,7 @@ export async function handleFixCode(ctx) {
|
|
|
106
106
|
}
|
|
107
107
|
const firstLookThreads = report.threads.firstLook;
|
|
108
108
|
const firstLookComments = report.comments.firstLook;
|
|
109
|
-
const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prNumber, cancelled.length, firstLookThreads, firstLookComments, firstLookSummaries, editedSummaries, inProgressRunIds, resolutionOnlyThreads);
|
|
109
|
+
const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prNumber, cancelled.length, firstLookThreads, firstLookComments, firstLookSummaries, editedSummaries, inProgressRunIds, resolutionOnlyThreads, resolveOnlyCommand);
|
|
110
110
|
const prospectiveResult = {
|
|
111
111
|
...base,
|
|
112
112
|
baseBranch: baseLookup.branch,
|
|
@@ -122,6 +122,7 @@ export async function handleFixCode(ctx) {
|
|
|
122
122
|
checks,
|
|
123
123
|
changesRequestedReviews,
|
|
124
124
|
resolveCommand,
|
|
125
|
+
...(resolveOnlyCommand !== undefined ? { resolveOnlyCommand } : undefined),
|
|
125
126
|
instructions,
|
|
126
127
|
firstLookThreads,
|
|
127
128
|
firstLookComments,
|
|
@@ -15,7 +15,7 @@ export function renderResolveCommand(rc) {
|
|
|
15
15
|
}
|
|
16
16
|
return renderShellCommand(parts);
|
|
17
17
|
}
|
|
18
|
-
export function buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseBranch, resolveCommand, hasConflicts, prNumber, cancelledCount, firstLookThreads = [], firstLookComments = [], firstLookSummaries = [], editedSummaries = [], inProgressRunIds = [], resolutionOnlyThreads = []) {
|
|
18
|
+
export function buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseBranch, resolveCommand, hasConflicts, prNumber, cancelledCount, firstLookThreads = [], firstLookComments = [], firstLookSummaries = [], editedSummaries = [], inProgressRunIds = [], resolutionOnlyThreads = [], resolveOnlyCommand) {
|
|
19
19
|
const instructions = [];
|
|
20
20
|
const hasNonConflictHints = threads.length > 0 ||
|
|
21
21
|
checks.length > 0 ||
|
|
@@ -55,9 +55,8 @@ export function buildFixInstructions(threads, actionableComments, checks, change
|
|
|
55
55
|
instructions.push(`If you decide to push new commits: cancel each in-progress run listed under \`## In-progress runs\` before applying code fixes (e.g. \`gh run cancel <id>\`). Runs may complete between the tick and your action; treat cancellation errors on already-finished runs as non-fatal. Skip this step if you are only resolving threads without pushing — the existing runs remain relevant.`);
|
|
56
56
|
}
|
|
57
57
|
const hasSuggestions = threads.some((t) => t.suggestion);
|
|
58
|
-
if (hasSuggestions)
|
|
58
|
+
if (hasSuggestions)
|
|
59
59
|
instructions.push(buildCommitSuggestionInstruction(prNumber, "## Review threads", false));
|
|
60
|
-
}
|
|
61
60
|
if (threads.length > 0 || actionableComments.length > 0) {
|
|
62
61
|
const suggestionFallback = hasSuggestions
|
|
63
62
|
? ` When applying a \`[suggestion]\` thread manually (e.g. after a failed \`commit-suggestion\` run), replace the exact line range shown in the heading (\`path:startLine-endLine\`) with the replacement shown in its \`Replaces lines …\` block verbatim — an empty replacement deletes those lines, a single blank line replaces the range with one blank line.`
|
|
@@ -74,9 +73,10 @@ export function buildFixInstructions(threads, actionableComments, checks, change
|
|
|
74
73
|
if (changesRequestedReviews.length > 0) {
|
|
75
74
|
instructions.push(`For each bullet under \`## Changes-requested reviews\` above: read the review body and apply the requested changes.`);
|
|
76
75
|
}
|
|
77
|
-
if (hasNonConflictHints)
|
|
76
|
+
if (hasNonConflictHints)
|
|
78
77
|
instructions.push(`If you applied code edits: commit them with a descriptive message, then rebase onto \`origin/${baseBranch}\` per your repository's conventions before pushing.`);
|
|
79
|
-
|
|
78
|
+
if (resolveOnlyCommand?.hasMutations)
|
|
79
|
+
instructions.push(`Run the \`resolve-only:\` command shown above — no substitutions needed.`);
|
|
80
80
|
if (resolveCommand.hasMutations) {
|
|
81
81
|
if ((resolveCommand.replyThreadIds?.length ?? 0) > 0) {
|
|
82
82
|
instructions.push(`Before running the \`resolve:\` command, remove any thread from \`--reply-thread-ids\` if the latest visible comment in that thread is your own prior Shepherd reply. Do not reply to your own comments.`);
|
|
@@ -98,9 +98,8 @@ export function buildFixInstructions(threads, actionableComments, checks, change
|
|
|
98
98
|
if (firstLookTotal > 0) {
|
|
99
99
|
instructions.push(`Items in \`## First-look items\` are shown so you can acknowledge their current status before acting. If a first-look thread also appears under \`## Review threads to resolve\`, its ID is already included in the \`resolve:\` command; otherwise do not pass first-look-only IDs to mutation flags.`);
|
|
100
100
|
}
|
|
101
|
-
if (firstLookSummaries.length > 0)
|
|
101
|
+
if (firstLookSummaries.length > 0)
|
|
102
102
|
instructions.push(SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE);
|
|
103
|
-
}
|
|
104
103
|
const editedTotal = editedSummaries.length +
|
|
105
104
|
actionableComments.filter((c) => c.edited).length +
|
|
106
105
|
firstLookThreads.filter((t) => t.edited).length +
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const SHEPHERD_JOURNAL_SECTION = "## Shepherd Journal";
|
|
2
2
|
export const SHEPHERD_JOURNAL_SECTION_PATTERN = /^##\s+Shepherd\s+Journal$/;
|
|
3
3
|
export const SHEPHERD_JOURNAL_APPEND_HINT = "If this section already exists, append your entries under it instead of creating a duplicate heading.";
|
|
4
|
-
export const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review the bodies shown under `## Review summaries (first look)` — you are seeing these for the first time. Eligible non-human IDs, when present, are already included in
|
|
4
|
+
export const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review the bodies shown under `## Review summaries (first look)` — you are seeing these for the first time. Eligible non-human IDs, when present, are already included in `--minimize-comment-ids` in the resolve or resolve-only command above; if any warrants a Shepherd Journal note, append it before running resolve.";
|
|
5
5
|
export function buildShepherdJournalInstruction(prNumber, itemReferenceGuidance) {
|
|
6
6
|
return [
|
|
7
7
|
`For any large decisions or rejections you made this iteration, add or update a \`${SHEPHERD_JOURNAL_SECTION}\` section in the PR description (\`gh pr edit ${prNumber} --body …\`) summarizing each decision.`,
|
package/package.json
CHANGED