pr-shepherd 0.46.1 → 0.46.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/README.md +1 -1
- package/bin/checks/triage.mjs +17 -4
- package/bin/cli/fix-formatter.mjs +2 -1
- package/bin/commands/check.mjs +6 -4
- package/bin/commands/iterate/escalate.mjs +2 -1
- package/bin/commands/iterate/fix-code.mjs +24 -6
- package/bin/commands/iterate/stall.mjs +1 -1
- package/bin/commands/iterate/thread-mutation-routing.d.mts +1 -0
- package/bin/commands/iterate/thread-mutation-routing.mjs +7 -0
- package/bin/reporters/agent.mjs +1 -0
- package/bin/types/github.d.mts +2 -0
- package/bin/types/report.d.mts +2 -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 +2 -2
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/checks/triage.mjs
CHANGED
|
@@ -12,18 +12,18 @@ export function triageFailingChecks(failingChecks, repo, stateKey) {
|
|
|
12
12
|
return Promise.all(failingChecks.map((c) => triageCheck(c, repo, jobsCache, stateKey)));
|
|
13
13
|
}
|
|
14
14
|
async function triageCheck(check, repo, jobsCache, stateKey) {
|
|
15
|
-
if (check.runId === null ||
|
|
16
|
-
check.conclusion === "CANCELLED" ||
|
|
17
|
-
check.conclusion === "STARTUP_FAILURE") {
|
|
15
|
+
if (check.runId === null || check.conclusion === "STARTUP_FAILURE") {
|
|
18
16
|
return { ...check };
|
|
19
17
|
}
|
|
20
18
|
const jobs = await fetchJobs(check.runId, repo, jobsCache, stateKey);
|
|
21
19
|
const jobInfo = jobs ? pickJobInfo(jobs, check.name) : undefined;
|
|
22
|
-
const
|
|
20
|
+
const runAttempt = jobs ? pickRunAttempt(jobs) : undefined;
|
|
21
|
+
const logExcerpt = check.conclusion !== "CANCELLED" && jobInfo?.jobId
|
|
23
22
|
? await fetchJobLogExcerpt(jobInfo.jobId, repo, stateKey, jobInfo.jobConclusion != null)
|
|
24
23
|
: undefined;
|
|
25
24
|
return {
|
|
26
25
|
...check,
|
|
26
|
+
...(runAttempt !== undefined && { runAttempt }),
|
|
27
27
|
...(jobInfo?.workflowName !== undefined && { workflowName: jobInfo.workflowName }),
|
|
28
28
|
...(jobInfo?.jobName !== undefined && { jobName: jobInfo.jobName }),
|
|
29
29
|
...(jobInfo?.failedStep !== undefined && { failedStep: jobInfo.failedStep }),
|
|
@@ -72,6 +72,7 @@ async function fetchStartupFailureChecksUncached(repo, headSha, prNumber, stateK
|
|
|
72
72
|
}
|
|
73
73
|
function workflowRunToCheckRun(run) {
|
|
74
74
|
const summary = run.display_title?.trim() || undefined;
|
|
75
|
+
const runAttempt = normalizeRunAttempt(run.run_attempt);
|
|
75
76
|
return {
|
|
76
77
|
name: run.name?.trim() || `workflow run ${run.id}`,
|
|
77
78
|
status: "COMPLETED",
|
|
@@ -80,9 +81,21 @@ function workflowRunToCheckRun(run) {
|
|
|
80
81
|
detailsUrl: run.html_url,
|
|
81
82
|
event: run.event,
|
|
82
83
|
runId: String(run.id),
|
|
84
|
+
...(runAttempt !== undefined && { runAttempt }),
|
|
83
85
|
...(summary !== undefined && { summary }),
|
|
84
86
|
};
|
|
85
87
|
}
|
|
88
|
+
function normalizeRunAttempt(value) {
|
|
89
|
+
return Number.isSafeInteger(value) && (value ?? 0) > 0 ? value : undefined;
|
|
90
|
+
}
|
|
91
|
+
function pickRunAttempt(jobs) {
|
|
92
|
+
for (const job of jobs) {
|
|
93
|
+
const attempt = normalizeRunAttempt(job.run_attempt);
|
|
94
|
+
if (attempt !== undefined)
|
|
95
|
+
return attempt;
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
86
99
|
function runBelongsToPr(run, prNumber, headSha) {
|
|
87
100
|
return (run.pull_requests ?? []).some((pr) => pr.number === prNumber && (pr.head?.sha ?? headSha) === headSha);
|
|
88
101
|
}
|
|
@@ -63,12 +63,13 @@ export function formatFixCodeResult(header, result) {
|
|
|
63
63
|
? `external \`${ch.detailsUrl}\``
|
|
64
64
|
: "(no runId)";
|
|
65
65
|
const conclusionTag = ch.conclusion !== null ? ` [conclusion: ${ch.conclusion}]` : "";
|
|
66
|
+
const attemptTag = ch.runAttempt !== undefined ? ` [attempt: ${ch.runAttempt}]` : "";
|
|
66
67
|
const scopeTag = ch.scope
|
|
67
68
|
? ` [scope: ${ch.scope}${ch.commitOid ? `, commit: ${ch.commitOid}` : ""}]`
|
|
68
69
|
: "";
|
|
69
70
|
const rerunTag = ch.rerunCommand ? " [rerun authorized]" : "";
|
|
70
71
|
const lines = [
|
|
71
|
-
`- ${locator} — \`${workflowPrefix}${jobLabel}\`${conclusionTag}${scopeTag}${rerunTag}`,
|
|
72
|
+
`- ${locator} — \`${workflowPrefix}${jobLabel}\`${conclusionTag}${attemptTag}${scopeTag}${rerunTag}`,
|
|
72
73
|
];
|
|
73
74
|
if (ch.conclusion !== "CANCELLED") {
|
|
74
75
|
if (ch.failedStep)
|
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";
|
|
@@ -40,7 +40,8 @@ export async function runCheck(opts) {
|
|
|
40
40
|
if (mergeStatus.state === "MERGED" || mergeStatus.state === "CLOSED") {
|
|
41
41
|
return buildTerminalReport(prNumber, repo, batchData, mergeStatus, mergeStatus.state);
|
|
42
42
|
}
|
|
43
|
-
const
|
|
43
|
+
const startupFailuresNeedAttempt = batchData.checks.some((check) => check.source === "startup_failure" && check.runAttempt === undefined);
|
|
44
|
+
const startupFailureChecks = result.checkSuitesComplete && !startupFailuresNeedAttempt
|
|
44
45
|
? []
|
|
45
46
|
: await fetchStartupFailureChecks(repo, batchData.headRefOid, prNumber, stateKey);
|
|
46
47
|
const allChecks = mergeStartupFailureChecks(batchData.checks, startupFailureChecks);
|
|
@@ -86,10 +87,11 @@ export async function runCheck(opts) {
|
|
|
86
87
|
const replyThreadIds = new Set(threadMutationRouting.replyThreadIds);
|
|
87
88
|
const resolveThreadIds = new Set(threadMutationRouting.resolveThreadIds);
|
|
88
89
|
const repeatableThreadIds = new Set(visibleThreadCandidates
|
|
89
|
-
.filter((thread) => thread.path !== null &&
|
|
90
|
+
.filter((thread) => (thread.path !== null &&
|
|
90
91
|
thread.line !== null &&
|
|
91
92
|
(!replyThreadIds.has(thread.id) || thread.viewerCanReply === true) &&
|
|
92
|
-
(!resolveThreadIds.has(thread.id) || thread.viewerCanResolve === true))
|
|
93
|
+
(!resolveThreadIds.has(thread.id) || thread.viewerCanResolve === true)) ||
|
|
94
|
+
canResolveOutdatedBotWithoutLocation(thread, botUsernames))
|
|
93
95
|
.map((thread) => thread.id));
|
|
94
96
|
const threadVisibility = classifyThreadVisibility(visibleThreadCandidates, seenMap, botUsernames, repeatableThreadIds);
|
|
95
97
|
const firstLookComments = minimizedCommentCandidates.flatMap((c) => {
|
|
@@ -41,8 +41,9 @@ function renderEscalateCheck(check) {
|
|
|
41
41
|
const workflowPrefix = check.workflowName ? `${check.workflowName} › ` : "";
|
|
42
42
|
const jobLabel = check.jobName ?? check.name;
|
|
43
43
|
const conclusion = check.conclusion ?? "UNKNOWN";
|
|
44
|
+
const attempt = check.runAttempt !== undefined ? ` [attempt: ${check.runAttempt}]` : "";
|
|
44
45
|
const lines = [
|
|
45
|
-
`- ${renderCheckTarget(check)} — \`${workflowPrefix}${jobLabel}\` [conclusion: ${conclusion}]${renderCheckScope(check)}`,
|
|
46
|
+
`- ${renderCheckTarget(check)} — \`${workflowPrefix}${jobLabel}\` [conclusion: ${conclusion}]${attempt}${renderCheckScope(check)}`,
|
|
46
47
|
];
|
|
47
48
|
if (check.failedStep)
|
|
48
49
|
lines.push(` > failed step: ${check.failedStep}`);
|
|
@@ -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";
|
|
@@ -17,6 +17,11 @@ import { formatPrUrl } from "../../pr-reference.mjs";
|
|
|
17
17
|
function checkRequiresHumanFollowUp(check) {
|
|
18
18
|
if (check.rerunCommand)
|
|
19
19
|
return false;
|
|
20
|
+
// Once GitHub advances beyond the original attempt, Shepherd's one autonomous rerun has
|
|
21
|
+
// already been consumed. Hand the repeated failure off even when logs are available; the
|
|
22
|
+
// human still receives that evidence in the escalation payload.
|
|
23
|
+
if (check.runAttempt !== undefined && check.runAttempt > 1)
|
|
24
|
+
return true;
|
|
20
25
|
if (check.conclusion === "ACTION_REQUIRED" ||
|
|
21
26
|
check.conclusion === "CANCELLED" ||
|
|
22
27
|
check.conclusion === "STARTUP_FAILURE")
|
|
@@ -138,9 +143,13 @@ export async function handleFixCode(ctx) {
|
|
|
138
143
|
const actionsRunIds = new Set(failingChecks.flatMap((c) => c.runId !== null && (c.source === "startup_failure" || c.workflowName !== undefined)
|
|
139
144
|
? [c.runId]
|
|
140
145
|
: []));
|
|
146
|
+
const initialAttemptRunIds = new Set(failingChecks.flatMap((c) => (c.runId !== null && c.runAttempt === 1 ? [c.runId] : [])));
|
|
141
147
|
const failingAgentChecks = toAgentChecks(failingChecks).map((c) => rerunAuthorized &&
|
|
142
148
|
c.runId &&
|
|
143
149
|
actionsRunIds.has(c.runId) &&
|
|
150
|
+
// GitHub increments run_attempt after every rerun. Recommend at most one rerun by limiting
|
|
151
|
+
// the command to the original attempt; missing attempt metadata is denied conservatively.
|
|
152
|
+
initialAttemptRunIds.has(c.runId) &&
|
|
144
153
|
// ACTION_REQUIRED means the run is paused pending manual workflow approval; rerunning does
|
|
145
154
|
// not grant that approval, so no rerun command applies.
|
|
146
155
|
c.conclusion !== "ACTION_REQUIRED" &&
|
|
@@ -157,7 +166,9 @@ export async function handleFixCode(ctx) {
|
|
|
157
166
|
isConfiguredBotAuthor(review, botUsernames));
|
|
158
167
|
const skippedDismissalIds = new Set(unauthorizedDismissals.map((review) => review.id));
|
|
159
168
|
const changesRequestedReviewsForWork = actionableChangesRequestedReviews.filter((review) => !skippedDismissalIds.has(review.id));
|
|
160
|
-
const resolutionOnlyThreadsForWork = resolutionOnlyThreads.filter((thread) => !skippedThreadIds.has(thread.id) &&
|
|
169
|
+
const resolutionOnlyThreadsForWork = resolutionOnlyThreads.filter((thread) => !skippedThreadIds.has(thread.id) &&
|
|
170
|
+
((thread.path !== null && thread.line !== null) ||
|
|
171
|
+
canResolveOutdatedBotWithoutLocation(thread, botUsernames)));
|
|
161
172
|
const hasConflicts = report.mergeStatus.status === "CONFLICTS";
|
|
162
173
|
const isBehind = report.mergeStatus.status === "BEHIND";
|
|
163
174
|
const { behindBaseHint } = loadConfig().iterate;
|
|
@@ -183,13 +194,19 @@ export async function handleFixCode(ctx) {
|
|
|
183
194
|
checks.some((check) => (check.annotations?.length ?? 0) > 0) ||
|
|
184
195
|
failingAgentChecks.some((check) => belongsToActiveWorkflowRun(check) || !checkRequiresHumanFollowUp(check));
|
|
185
196
|
if (manualFollowUpChecks.length > 0 && !hasAutonomousWork) {
|
|
197
|
+
const exhaustedAttempts = manualFollowUpChecks.filter((check) => check.runAttempt !== undefined && check.runAttempt > 1);
|
|
198
|
+
const checkSuggestion = exhaustedAttempts.length > 0
|
|
199
|
+
? `GitHub reports a later workflow attempt (${exhaustedAttempts
|
|
200
|
+
.map((check) => `${check.runId ?? check.name}: attempt ${check.runAttempt}`)
|
|
201
|
+
.join(", ")}), so Shepherd's single rerun allowance is exhausted. Use the included evidence to handle the repeated failure manually before resuming.`
|
|
202
|
+
: buildEscalateSuggestion(["check-follow-up-unavailable"]);
|
|
186
203
|
const checkEscalateBase = {
|
|
187
204
|
triggers: ["check-follow-up-unavailable"],
|
|
188
205
|
unresolvedThreads: [],
|
|
189
206
|
ambiguousComments: [],
|
|
190
207
|
changesRequestedReviews,
|
|
191
208
|
checks: manualFollowUpChecks,
|
|
192
|
-
suggestion:
|
|
209
|
+
suggestion: checkSuggestion,
|
|
193
210
|
};
|
|
194
211
|
return {
|
|
195
212
|
...base,
|
|
@@ -209,15 +226,16 @@ export async function handleFixCode(ctx) {
|
|
|
209
226
|
const { resolveCommand, resolveOnlyCommand } = buildResolveCommand(retryableAgentThreads, resolutionOnlyThreadsForWork, allCommentIds, changesRequestedReviewsForWork, failingAgentChecks, prReference, botUsernames, ruleAutoResolveThreadIds, report.viewerAuthorization, allThreads);
|
|
210
227
|
// Safety: if the base branch is unknown, escalate when a push is plausible — the agent
|
|
211
228
|
// would need the correct base to rebase safely. This is a conservative guard, not a
|
|
212
|
-
// prediction that the agent *will* push.
|
|
213
|
-
//
|
|
229
|
+
// prediction that the agent *will* push. Located resolution-only threads retain that guard;
|
|
230
|
+
// an outdated bot thread resolved only by ID has no code or push path.
|
|
231
|
+
const locatedResolutionOnlyThreadsForWork = resolutionOnlyThreadsForWork.filter((thread) => thread.path !== null && thread.line !== null);
|
|
214
232
|
const pushIsPlausible = retryableActionableThreads.length > 0 ||
|
|
215
233
|
failingAgentChecks.length > 0 ||
|
|
216
234
|
annotatedExtra.length > 0 ||
|
|
217
235
|
hasConflicts ||
|
|
218
236
|
changesRequestedReviewsForWork.length > 0 ||
|
|
219
237
|
actionableComments.length > 0 ||
|
|
220
|
-
|
|
238
|
+
locatedResolutionOnlyThreadsForWork.length > 0;
|
|
221
239
|
if (baseLookup.isFallback && pushIsPlausible) {
|
|
222
240
|
const fallbackEscalateBase = {
|
|
223
241
|
triggers: ["base-branch-unknown"],
|
|
@@ -5,7 +5,7 @@ import { checksWithActionableAnnotations } from "../check-annotations.mjs";
|
|
|
5
5
|
import { formatPrUrl } from "../../pr-reference.mjs";
|
|
6
6
|
function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds) {
|
|
7
7
|
const checks = [
|
|
8
|
-
...report.checks.failing.map((f) => `failing:${f.name}:${f.conclusion}`),
|
|
8
|
+
...report.checks.failing.map((f) => `failing:${f.name}:${f.conclusion}:${f.runId ?? "no-run"}:${f.runAttempt ?? "unknown"}`),
|
|
9
9
|
...report.checks.inProgress.map((p) => `inProgress:${p.name}`),
|
|
10
10
|
].sort();
|
|
11
11
|
const threads = report.threads.actionable.map((t) => t.id).sort();
|
|
@@ -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/bin/reporters/agent.mjs
CHANGED
|
@@ -60,6 +60,7 @@ export function toAgentCheck(c) {
|
|
|
60
60
|
runId: c.runId,
|
|
61
61
|
detailsUrl: c.detailsUrl,
|
|
62
62
|
conclusion: c.conclusion,
|
|
63
|
+
...(c.runAttempt !== undefined && c.runAttempt > 1 && { runAttempt: c.runAttempt }),
|
|
63
64
|
...(c.workflowName !== undefined && { workflowName: c.workflowName }),
|
|
64
65
|
...(c.jobName !== undefined && { jobName: c.jobName }),
|
|
65
66
|
...(c.failedStep !== undefined && { failedStep: c.failedStep }),
|
package/bin/types/github.d.mts
CHANGED
|
@@ -30,6 +30,8 @@ export interface CheckRun {
|
|
|
30
30
|
detailsUrl: string;
|
|
31
31
|
event: string | null;
|
|
32
32
|
runId: string | null;
|
|
33
|
+
/** GitHub Actions workflow-run attempt number. Omitted for non-Actions or unavailable metadata. */
|
|
34
|
+
runAttempt?: number;
|
|
33
35
|
/** Commit scope that supplied this check. Omitted for ordinary PR-head checks. */
|
|
34
36
|
scope?: "merge_group";
|
|
35
37
|
/** Synthetic merge-group commit OID, when `scope` is `merge_group`. */
|
package/bin/types/report.d.mts
CHANGED
|
@@ -138,6 +138,8 @@ export interface AgentCheck {
|
|
|
138
138
|
logExcerpt?: string;
|
|
139
139
|
/** `gh run rerun` command, present only when the check has a runId and the viewer's repository role grants Actions rerun capability (WRITE+). */
|
|
140
140
|
rerunCommand?: string;
|
|
141
|
+
/** Workflow-run attempt number, surfaced only after the initial attempt. */
|
|
142
|
+
runAttempt?: number;
|
|
141
143
|
annotations?: CheckAnnotation[];
|
|
142
144
|
annotationOnly?: true;
|
|
143
145
|
scope?: "merge_group";
|
package/package.json
CHANGED
|
@@ -46,7 +46,7 @@ Match each failure's `[conclusion: …]` tag under `## Failing checks` to a rule
|
|
|
46
46
|
|
|
47
47
|
More specific rows win over the general "GitHub Actions failure" row — check conclusion first.
|
|
48
48
|
|
|
49
|
-
A `[rerun authorized]` tag with a `rerun:` command means the viewer's repository role grants GitHub's Actions rerun capability (WRITE+) — Shepherd verified
|
|
49
|
+
A `[rerun authorized]` tag with a `rerun:` command means the viewer's repository role grants GitHub's Actions rerun capability (WRITE+) and GitHub reports the original workflow attempt — Shepherd verified these from `repositoryPermission` and `run_attempt`. Run the printed command at most once. Later attempts carry an `[attempt: N]` tag, never get another command, and return `[ESCALATE]` when no other autonomous work remains, even when a log excerpt exists. A run still in progress, an `ACTION_REQUIRED` run (paused pending manual workflow approval — a rerun cannot grant that approval), a check whose runId does not resolve to a GitHub Actions workflow, or a run whose attempt metadata is unavailable never gets `[rerun authorized]`. When a check has no autonomous follow-up and no other agent work remains, Shepherd returns `[ESCALATE]`; do not invent a handoff from a `[FIX_CODE]` result.
|
|
50
50
|
|
|
51
51
|
When several bullets share one runId (matrix jobs from the same run), the `rerun:` command is printed once, on the first bullet; every bullet for that runId still carries `[rerun authorized]` and is covered by that single command — do not run it more than once.
|
|
52
52
|
|
|
@@ -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.
|