pr-shepherd 0.46.1 → 0.46.2
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 +1 -1
- package/bin/commands/check.mjs +4 -3
- package/bin/commands/iterate/fix-code.mjs +8 -5
- package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
- package/bin/commands/iterate/thread-mutation-routing.mjs +7 -0
- package/package.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ See [docs/actions.md](docs/actions.md) for the complete output contract and [doc
|
|
|
84
84
|
This system is opinionated and works best with PRs that use required status checks and conversation resolution.
|
|
85
85
|
|
|
86
86
|
- A human inline thread whose original comment has `viewerDidAuthor: true` is replied to and resolved when its latest comment is unmarked. An unmarked other-human inline thread remains reply-only; a marker-ended other-human thread is already acknowledged and receives no further mutation. Human items are never minimized.
|
|
87
|
-
- Detected bots and configured `botUsernames` review threads are returned until resolved when the required mutation is authorized and the thread has a source location. Unauthorized or unlocated items are surfaced once and then marker-gated until edited. Bot/non-human threads, PR comments, and review summaries can be resolved or minimized when eligible. Review summaries are not minimized while known inline child threads from that review remain unresolved.
|
|
87
|
+
- Detected bots and configured `botUsernames` review threads are returned until resolved when the required mutation is authorized and the thread has a source location. Authorized outdated bot threads remain resolution-only work even when GitHub clears their source line, because resolving by thread ID does not require that location. Unauthorized or other unlocated items are surfaced once and then marker-gated until edited. Bot/non-human threads, PR comments, and review summaries can be resolved or minimized when eligible. Review summaries are not minimized while known inline child threads from that review remain unresolved.
|
|
88
88
|
- Shepherd identifies its own latest reply only when that comment begins `<!-- pr-shepherd -->`, not from author equality. A marked viewer-authored thread can be resolved without another reply as a retry.
|
|
89
89
|
- Every review thread/comment/review summary is surfaced at least once, even if already outdated, resolved, or minimized; edited items re-surface through seen markers.
|
|
90
90
|
- Draft PRs can be marked ready automatically when clean; disable with `actions.autoMarkReady: false` or `--no-auto-mark-ready`.
|
package/bin/commands/check.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { classifyReviewsForDisplay, classifyChangesRequestedReviewsForDisplay, }
|
|
|
17
17
|
import { autoMinimizeComments, autoResolveThreads } from "../comments/resolve.mjs";
|
|
18
18
|
import { markReviewInlineThreadMarkers } from "../comments/review-thread-markers.mjs";
|
|
19
19
|
import { isConfiguredBotAuthor, isHumanAuthor, normalizeBotUsernames, } from "../comments/authors.mjs";
|
|
20
|
-
import { buildThreadMutationRouting } from "./iterate/thread-mutation-routing.mjs";
|
|
20
|
+
import { buildThreadMutationRouting, canResolveOutdatedBotWithoutLocation, } from "./iterate/thread-mutation-routing.mjs";
|
|
21
21
|
import { discoverRuleFiles, loadRules } from "../classify/loader.mjs";
|
|
22
22
|
import { buildClassifyIndex, partitionBatch } from "../classify/apply.mjs";
|
|
23
23
|
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
@@ -86,10 +86,11 @@ export async function runCheck(opts) {
|
|
|
86
86
|
const replyThreadIds = new Set(threadMutationRouting.replyThreadIds);
|
|
87
87
|
const resolveThreadIds = new Set(threadMutationRouting.resolveThreadIds);
|
|
88
88
|
const repeatableThreadIds = new Set(visibleThreadCandidates
|
|
89
|
-
.filter((thread) => thread.path !== null &&
|
|
89
|
+
.filter((thread) => (thread.path !== null &&
|
|
90
90
|
thread.line !== null &&
|
|
91
91
|
(!replyThreadIds.has(thread.id) || thread.viewerCanReply === true) &&
|
|
92
|
-
(!resolveThreadIds.has(thread.id) || thread.viewerCanResolve === true))
|
|
92
|
+
(!resolveThreadIds.has(thread.id) || thread.viewerCanResolve === true)) ||
|
|
93
|
+
canResolveOutdatedBotWithoutLocation(thread, botUsernames))
|
|
93
94
|
.map((thread) => thread.id));
|
|
94
95
|
const threadVisibility = classifyThreadVisibility(visibleThreadCandidates, seenMap, botUsernames, repeatableThreadIds);
|
|
95
96
|
const firstLookComments = minimizedCommentCandidates.flatMap((c) => {
|
|
@@ -5,7 +5,7 @@ import { toAgentThread, toAgentComment, toAgentChecks } from "../../reporters/ag
|
|
|
5
5
|
import { hashBody, markSeen } from "../../state/seen-comments.mjs";
|
|
6
6
|
import { checkEscalateTriggers, validateBaseBranch, buildEscalateSuggestion, buildEscalateHumanMessage, } from "./escalate.mjs";
|
|
7
7
|
import { buildResolveCommand } from "./classify.mjs";
|
|
8
|
-
import { buildThreadMutationRouting } from "./thread-mutation-routing.mjs";
|
|
8
|
+
import { buildThreadMutationRouting, canResolveOutdatedBotWithoutLocation, } from "./thread-mutation-routing.mjs";
|
|
9
9
|
import { buildFixInstructions } from "./render.mjs";
|
|
10
10
|
import { applyStallGuard } from "./stall.mjs";
|
|
11
11
|
import { annotationMarkerBody, checksWithActionableAnnotations } from "../check-annotations.mjs";
|
|
@@ -157,7 +157,9 @@ export async function handleFixCode(ctx) {
|
|
|
157
157
|
isConfiguredBotAuthor(review, botUsernames));
|
|
158
158
|
const skippedDismissalIds = new Set(unauthorizedDismissals.map((review) => review.id));
|
|
159
159
|
const changesRequestedReviewsForWork = actionableChangesRequestedReviews.filter((review) => !skippedDismissalIds.has(review.id));
|
|
160
|
-
const resolutionOnlyThreadsForWork = resolutionOnlyThreads.filter((thread) => !skippedThreadIds.has(thread.id) &&
|
|
160
|
+
const resolutionOnlyThreadsForWork = resolutionOnlyThreads.filter((thread) => !skippedThreadIds.has(thread.id) &&
|
|
161
|
+
((thread.path !== null && thread.line !== null) ||
|
|
162
|
+
canResolveOutdatedBotWithoutLocation(thread, botUsernames)));
|
|
161
163
|
const hasConflicts = report.mergeStatus.status === "CONFLICTS";
|
|
162
164
|
const isBehind = report.mergeStatus.status === "BEHIND";
|
|
163
165
|
const { behindBaseHint } = loadConfig().iterate;
|
|
@@ -209,15 +211,16 @@ export async function handleFixCode(ctx) {
|
|
|
209
211
|
const { resolveCommand, resolveOnlyCommand } = buildResolveCommand(retryableAgentThreads, resolutionOnlyThreadsForWork, allCommentIds, changesRequestedReviewsForWork, failingAgentChecks, prReference, botUsernames, ruleAutoResolveThreadIds, report.viewerAuthorization, allThreads);
|
|
210
212
|
// Safety: if the base branch is unknown, escalate when a push is plausible — the agent
|
|
211
213
|
// would need the correct base to rebase safely. This is a conservative guard, not a
|
|
212
|
-
// prediction that the agent *will* push.
|
|
213
|
-
//
|
|
214
|
+
// prediction that the agent *will* push. Located resolution-only threads retain that guard;
|
|
215
|
+
// an outdated bot thread resolved only by ID has no code or push path.
|
|
216
|
+
const locatedResolutionOnlyThreadsForWork = resolutionOnlyThreadsForWork.filter((thread) => thread.path !== null && thread.line !== null);
|
|
214
217
|
const pushIsPlausible = retryableActionableThreads.length > 0 ||
|
|
215
218
|
failingAgentChecks.length > 0 ||
|
|
216
219
|
annotatedExtra.length > 0 ||
|
|
217
220
|
hasConflicts ||
|
|
218
221
|
changesRequestedReviewsForWork.length > 0 ||
|
|
219
222
|
actionableComments.length > 0 ||
|
|
220
|
-
|
|
223
|
+
locatedResolutionOnlyThreadsForWork.length > 0;
|
|
221
224
|
if (baseLookup.isFallback && pushIsPlausible) {
|
|
222
225
|
const fallbackEscalateBase = {
|
|
223
226
|
triggers: ["base-branch-unknown"],
|
|
@@ -6,4 +6,5 @@ export interface ThreadMutationRouting {
|
|
|
6
6
|
standaloneResolveThreadIds: string[];
|
|
7
7
|
resolveThreadIds: string[];
|
|
8
8
|
}
|
|
9
|
+
export declare function canResolveOutdatedBotWithoutLocation(thread: ReviewThread, botUsernames: NormalizedBotUsernames): boolean;
|
|
9
10
|
export declare function buildThreadMutationRouting(threads: Array<AgentThread | ReviewThread>, botUsernames: NormalizedBotUsernames, ruleAutoResolveThreadIds: string[]): ThreadMutationRouting;
|
|
@@ -3,6 +3,13 @@ import { threadEndedByShepherd } from "../../comments/marker.mjs";
|
|
|
3
3
|
function dedupeIds(ids) {
|
|
4
4
|
return [...new Set(ids)];
|
|
5
5
|
}
|
|
6
|
+
export function canResolveOutdatedBotWithoutLocation(thread, botUsernames) {
|
|
7
|
+
return (!thread.isResolved &&
|
|
8
|
+
thread.isOutdated &&
|
|
9
|
+
(thread.path === null || thread.line === null) &&
|
|
10
|
+
isConfiguredBotAuthor(thread, botUsernames) &&
|
|
11
|
+
thread.viewerCanResolve === true);
|
|
12
|
+
}
|
|
6
13
|
export function buildThreadMutationRouting(threads, botUsernames, ruleAutoResolveThreadIds) {
|
|
7
14
|
const isOrdinaryHuman = (thread) => isHumanAuthor(thread) && !isConfiguredBotAuthor(thread, botUsernames);
|
|
8
15
|
const replyThreadIds = dedupeIds(threads
|
package/package.json
CHANGED
|
@@ -64,7 +64,7 @@ When several bullets share one runId (matrix jobs from the same run), the `rerun
|
|
|
64
64
|
|
|
65
65
|
Applies to every `apply review:` / `resolve-only:` command the CLI prints. Covers only what stays safe if you run the printed command **unmodified** — `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution remains a separate CLI-printed step because the command is unsafe by default without those placeholders.
|
|
66
66
|
|
|
67
|
-
The CLI only includes IDs whose per-object GitHub viewer capability and semantic routing authorize the corresponding generated action. Direct `apply review` honors those emitted IDs without a second authorization preflight and surfaces GitHub's per-operation result. Do not reconstruct omitted review reply, thread resolution, or bot-review dismissal IDs and do not hand them off: denied or unverifiable generated mutations are one-look skips that Shepherd suppresses until the item is edited.
|
|
67
|
+
The CLI only includes IDs whose per-object GitHub viewer capability and semantic routing authorize the corresponding generated action. Direct `apply review` honors those emitted IDs without a second authorization preflight and surfaces GitHub's per-operation result. Do not reconstruct omitted review reply, thread resolution, or bot-review dismissal IDs and do not hand them off: denied or unverifiable generated mutations are one-look skips that Shepherd suppresses until the item is edited. Active threads without a path or line follow the same skip rule; authorized outdated bot threads are emitted for resolution by thread ID even when GitHub clears their source line.
|
|
68
68
|
|
|
69
69
|
- Run every generated `apply review:` / `resolve-only:` command even when no code change is warranted. The command records the agent's disposition of the included review items; skipping it leaves bot threads active and can eventually trigger `fix-thrash`.
|
|
70
70
|
- Never add first-look-only or check-annotation IDs to `--reply-thread-ids`, `--resolve-thread-ids`, `--dismiss-review-ids`, or `--minimize-comment-ids` — those flags are pre-populated by the CLI.
|