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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
3
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
4
- "version": "0.46.1",
4
+ "version": "0.46.2",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
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`.
@@ -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) && thread.path !== null && thread.line !== null);
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. Intentionally broader than `pushLikely` above:
213
- // resolution-only threads also need a known base in case the agent does push.
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
- resolutionOnlyThreadsForWork.length > 0;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.46.1",
3
+ "version": "0.46.2",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "keywords": [
6
6
  "automation",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.46.1",
3
+ "version": "0.46.2",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "pr-shepherd": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "pr-shepherd@0.46.1", "pr-shepherd-mcp"]
5
+ "args": ["--yes", "--package", "pr-shepherd@0.46.2", "pr-shepherd-mcp"]
6
6
  }
7
7
  }
8
8
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "pr-shepherd": {
3
3
  "command": "npx",
4
- "args": ["--yes", "--package", "pr-shepherd@0.46.1", "pr-shepherd-mcp"]
4
+ "args": ["--yes", "--package", "pr-shepherd@0.46.2", "pr-shepherd-mcp"]
5
5
  }
6
6
  }
@@ -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. Threads without a path or line follow the same skip rule.
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.