pr-shepherd 0.36.0 → 0.37.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/plugin.json +1 -1
- package/bin/checks/conclusions.d.mts +6 -0
- package/bin/checks/conclusions.mjs +9 -0
- package/bin/cli/fix-formatter.mjs +4 -2
- package/bin/commands/check-annotations.d.mts +16 -3
- package/bin/commands/check-annotations.mjs +37 -1
- package/bin/commands/check.mjs +9 -6
- package/bin/commands/iterate/fix-code.mjs +12 -5
- package/bin/commands/iterate/index.mjs +2 -0
- package/bin/commands/iterate/render.mjs +9 -5
- package/bin/commands/iterate/stall.mjs +5 -0
- package/bin/github/batch-parser-helpers.mjs +1 -0
- package/bin/github/batch-raw-types.d.mts +5 -0
- package/bin/github/gql/batch-pr-page.gql +5 -0
- package/bin/github/gql/batch-pr.gql +5 -0
- package/bin/reporters/agent.mjs +0 -3
- package/bin/types/check-classification.d.mts +2 -2
- package/bin/types/github.d.mts +2 -0
- package/bin/types/report.d.mts +5 -5
- 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
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CheckConclusion } from "../types.mts";
|
|
2
|
+
/** Failing-check rows for formatter/instructions — excludes annotation-only carriers. */
|
|
3
|
+
export declare function isFailingAgentCheck(check: {
|
|
4
|
+
conclusion: CheckConclusion;
|
|
5
|
+
annotationOnly?: boolean;
|
|
6
|
+
}): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const NON_FAILING_CONCLUSIONS = new Set(["SUCCESS", "SKIPPED", "NEUTRAL"]);
|
|
2
|
+
/** True for conclusions that belong under `## Failing checks` (not success/skipped/neutral). */
|
|
3
|
+
function isFailingCheckConclusion(conclusion) {
|
|
4
|
+
return conclusion == null || !NON_FAILING_CONCLUSIONS.has(conclusion);
|
|
5
|
+
}
|
|
6
|
+
/** Failing-check rows for formatter/instructions — excludes annotation-only carriers. */
|
|
7
|
+
export function isFailingAgentCheck(check) {
|
|
8
|
+
return !check.annotationOnly && isFailingCheckConclusion(check.conclusion);
|
|
9
|
+
}
|
|
@@ -4,6 +4,7 @@ import { renderSuggestionBlock, renderLineRange } from "./suggestion-renderer.mj
|
|
|
4
4
|
import { renderThreadBullet, renderReviewBullet, renderThreadResolutionStatusTag, renderAuthor, buildFirstLookBullets, renderThreadConversation, blockquote, } from "./list-formatters.mjs";
|
|
5
5
|
import { numberInstructions } from "./iterate-instructions.mjs";
|
|
6
6
|
import { renderCheckAnnotation, renderProtectedRun } from "./fix-formatter-extra.mjs";
|
|
7
|
+
import { isFailingAgentCheck } from "../checks/conclusions.mjs";
|
|
7
8
|
export function formatFixCodeResult(header, result) {
|
|
8
9
|
const sections = [header];
|
|
9
10
|
if (result.fix.threads.length > 0) {
|
|
@@ -37,9 +38,10 @@ export function formatFixCodeResult(header, result) {
|
|
|
37
38
|
sections.push(blockquote(c.body));
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
+
const failingChecks = result.fix.checks.filter((ch) => isFailingAgentCheck(ch));
|
|
42
|
+
if (failingChecks.length > 0) {
|
|
41
43
|
sections.push("## Failing checks");
|
|
42
|
-
const bullets =
|
|
44
|
+
const bullets = failingChecks.map((ch) => {
|
|
43
45
|
const workflowPrefix = ch.workflowName ? `${ch.workflowName} › ` : "";
|
|
44
46
|
const jobLabel = ch.jobName ? ch.jobName : ch.name;
|
|
45
47
|
const locator = ch.runId
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import type { CheckAnnotation, TriagedCheck } from "../types.mts";
|
|
2
|
-
export declare function
|
|
1
|
+
import type { CheckAnnotation, ClassifiedCheck, ShepherdReport, TriagedCheck } from "../types.mts";
|
|
2
|
+
export declare function checksWithUnseenAnnotations(report: ShepherdReport): TriagedCheck[];
|
|
3
|
+
export declare function attachAndMergeCheckAnnotations(buckets: {
|
|
4
|
+
passing: ClassifiedCheck[];
|
|
5
|
+
failing: TriagedCheck[];
|
|
6
|
+
skipped: ClassifiedCheck[];
|
|
7
|
+
filtered: ClassifiedCheck[];
|
|
8
|
+
ignored: ClassifiedCheck[];
|
|
9
|
+
}, seenMap: Map<string, {
|
|
3
10
|
seenAt: number;
|
|
4
|
-
}>, prNumber: number): Promise<
|
|
11
|
+
}>, prNumber: number): Promise<{
|
|
12
|
+
passing: ClassifiedCheck[];
|
|
13
|
+
failing: TriagedCheck[];
|
|
14
|
+
skipped: ClassifiedCheck[];
|
|
15
|
+
filtered: ClassifiedCheck[];
|
|
16
|
+
ignored: ClassifiedCheck[];
|
|
17
|
+
}>;
|
|
5
18
|
export declare function annotationMarkerBody(a: CheckAnnotation): string;
|
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
import { fetchCheckRunAnnotations } from "../github/check-annotations.mjs";
|
|
2
|
-
|
|
2
|
+
function shouldFetchCheckAnnotations(check) {
|
|
3
|
+
return check.id != null && check.status === "COMPLETED" && check.hasAnnotations === true;
|
|
4
|
+
}
|
|
5
|
+
export function checksWithUnseenAnnotations(report) {
|
|
6
|
+
return [
|
|
7
|
+
...report.checks.failing,
|
|
8
|
+
...report.checks.passing,
|
|
9
|
+
...report.checks.skipped,
|
|
10
|
+
...report.checks.filtered,
|
|
11
|
+
...(report.checks.ignored ?? []),
|
|
12
|
+
].filter((c) => (c.annotations?.length ?? 0) > 0);
|
|
13
|
+
}
|
|
14
|
+
export async function attachAndMergeCheckAnnotations(buckets, seenMap, prNumber) {
|
|
15
|
+
const candidates = [
|
|
16
|
+
...buckets.failing,
|
|
17
|
+
...buckets.passing,
|
|
18
|
+
...buckets.skipped,
|
|
19
|
+
...buckets.filtered,
|
|
20
|
+
...buckets.ignored,
|
|
21
|
+
].filter(shouldFetchCheckAnnotations);
|
|
22
|
+
const annotated = await attachUnseenCheckAnnotations(candidates, seenMap, prNumber);
|
|
23
|
+
const byId = new Map(annotated.flatMap((c) => (c.id != null ? [[c.id, c]] : [])));
|
|
24
|
+
const apply = (list) => list.map((c) => {
|
|
25
|
+
if (c.id == null)
|
|
26
|
+
return c;
|
|
27
|
+
const next = byId.get(c.id);
|
|
28
|
+
return next !== undefined ? next : c;
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
passing: apply(buckets.passing),
|
|
32
|
+
failing: apply(buckets.failing),
|
|
33
|
+
skipped: apply(buckets.skipped),
|
|
34
|
+
filtered: apply(buckets.filtered),
|
|
35
|
+
ignored: apply(buckets.ignored),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async function attachUnseenCheckAnnotations(checks, seenMap, prNumber) {
|
|
3
39
|
const checksWithAnnotations = [];
|
|
4
40
|
for (const check of checks) {
|
|
5
41
|
// eslint-disable-next-line no-await-in-loop
|
package/bin/commands/check.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { deriveMergeStatus } from "../merge-status/derive.mjs";
|
|
|
7
7
|
import { loadConfig } from "../config/load.mjs";
|
|
8
8
|
import { classifyVisibleComments } from "../comments/visible-comments.mjs";
|
|
9
9
|
import { computeStatus } from "./check-status.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import { attachAndMergeCheckAnnotations } from "./check-annotations.mjs";
|
|
11
11
|
import { buildTerminalReport } from "./check-terminal-report.mjs";
|
|
12
12
|
import { isBlockedByFilteredCheck, refreshReadyMergeability, refreshUnknownMergeability, } from "./ready-mergeability.mjs";
|
|
13
13
|
import { loadSeenMap, markSeen, classifyItem } from "../state/seen-comments.mjs";
|
|
@@ -49,6 +49,7 @@ export async function runCheck(opts) {
|
|
|
49
49
|
const inProgress = classifiedChecks.filter((c) => c.category === "in_progress");
|
|
50
50
|
const skipped = classifiedChecks.filter((c) => c.category === "skipped");
|
|
51
51
|
const filtered = classifiedChecks.filter((c) => c.category === "filtered");
|
|
52
|
+
const ignored = classifiedChecks.filter((c) => c.category === "ignored");
|
|
52
53
|
const triagedBase = failing.length > 0 && !opts.skipTriage ? await triageFailingChecks(failing, repo) : failing;
|
|
53
54
|
const stateKey = { owner: repo.owner, repo: repo.name, pr: prNumber };
|
|
54
55
|
const seenMap = await loadSeenMap(stateKey);
|
|
@@ -56,7 +57,8 @@ export async function runCheck(opts) {
|
|
|
56
57
|
const ruleSet = await loadRules(discoverRuleFiles(getEffectiveCwd()));
|
|
57
58
|
const classifyIndex = buildClassifyIndex(ruleSet, batchData);
|
|
58
59
|
const partition = partitionBatch(classifyIndex, batchData);
|
|
59
|
-
const
|
|
60
|
+
const merged = await attachAndMergeCheckAnnotations({ passing, failing: triagedBase, skipped, filtered, ignored }, seenMap, prNumber);
|
|
61
|
+
const ignoredAnnotated = merged.ignored.filter((c) => (c.annotations?.length ?? 0) > 0);
|
|
60
62
|
const minimizedCommentCandidates = batchData.comments.filter((c) => c.isMinimized && !partition.suppressedCommentIds.has(c.id));
|
|
61
63
|
const visibleCommentClassification = classifyVisibleComments(batchData.comments.filter((c) => !partition.suppressedCommentIds.has(c.id)), seenMap, config.iterate.minimizeComments, botUsernames);
|
|
62
64
|
const threadVisibility = classifyThreadVisibility(batchData.reviewThreads.filter((t) => !partition.suppressedThreadIds.has(t.id)), seenMap, botUsernames);
|
|
@@ -125,11 +127,12 @@ export async function runCheck(opts) {
|
|
|
125
127
|
baseBranch: batchData.baseRefName,
|
|
126
128
|
mergeStatus,
|
|
127
129
|
checks: {
|
|
128
|
-
passing,
|
|
129
|
-
failing:
|
|
130
|
+
passing: merged.passing,
|
|
131
|
+
failing: merged.failing,
|
|
130
132
|
inProgress: inProgress,
|
|
131
|
-
skipped,
|
|
132
|
-
filtered,
|
|
133
|
+
skipped: merged.skipped,
|
|
134
|
+
filtered: merged.filtered,
|
|
135
|
+
...(ignoredAnnotated.length > 0 && { ignored: ignoredAnnotated }),
|
|
133
136
|
filteredNames: verdict.filteredNames,
|
|
134
137
|
blockedByFilteredCheck,
|
|
135
138
|
...(verdict.ignoredNames.length > 0 && { ignoredNames: verdict.ignoredNames }),
|
|
@@ -8,7 +8,7 @@ import { buildResolveCommand } from "./classify.mjs";
|
|
|
8
8
|
import { buildFixInstructions } from "./render.mjs";
|
|
9
9
|
import { applyStallGuard } from "./stall.mjs";
|
|
10
10
|
import { tryCancelRun, buildAutoCancelRunIdsWithOptions, buildInProgressRunIds, buildRunProtection, } from "./helpers.mjs";
|
|
11
|
-
import { annotationMarkerBody } from "../check-annotations.mjs";
|
|
11
|
+
import { annotationMarkerBody, checksWithUnseenAnnotations } from "../check-annotations.mjs";
|
|
12
12
|
import { threadTranscriptBody } from "../../threads/transcript.mjs";
|
|
13
13
|
import { isHumanAuthor, isConfiguredBotAuthor } from "../../comments/authors.mjs";
|
|
14
14
|
import { loadConfig } from "../../config/load.mjs";
|
|
@@ -30,6 +30,7 @@ function nextFixAttempts(stored, headSha, threads) {
|
|
|
30
30
|
export async function handleFixCode(ctx) {
|
|
31
31
|
const { base, report, opts, headSha, stallKey, prNumber, stallTimeoutSeconds, repoOwner, repoName, reviewSummaryIds, firstLookSummaries, editedSummaries, surfacedApprovals, botUsernames, ruleAutoResolveThreadIds, } = ctx;
|
|
32
32
|
const failingChecks = report.checks.failing;
|
|
33
|
+
const annotatedExtra = checksWithUnseenAnnotations(report).filter((c) => c.category !== "failing");
|
|
33
34
|
const { protectedRunIds, protectedRuns } = buildRunProtection([...failingChecks, ...report.checks.inProgress], opts.neverCancelRuns);
|
|
34
35
|
const stored = await readFixAttempts({ owner: repoOwner, repo: repoName, pr: prNumber });
|
|
35
36
|
const { threadAttempts, threadBodyHashes } = nextFixAttempts(stored, headSha, report.threads.actionable);
|
|
@@ -89,7 +90,11 @@ export async function handleFixCode(ctx) {
|
|
|
89
90
|
const threads = report.threads.actionable.map(toAgentThread);
|
|
90
91
|
const resolutionOnlyThreads = report.threads.resolutionOnly;
|
|
91
92
|
const actionableComments = report.comments.actionable.map(toAgentComment);
|
|
92
|
-
const
|
|
93
|
+
const failingAgentChecks = toAgentChecks(failingChecks);
|
|
94
|
+
const checks = [
|
|
95
|
+
...failingAgentChecks,
|
|
96
|
+
...toAgentChecks(annotatedExtra).map((c) => ({ ...c, annotationOnly: true })),
|
|
97
|
+
];
|
|
93
98
|
const { changesRequestedReviews } = report;
|
|
94
99
|
const hasConflicts = report.mergeStatus.status === "CONFLICTS";
|
|
95
100
|
const isBehind = report.mergeStatus.status === "BEHIND";
|
|
@@ -98,7 +103,8 @@ export async function handleFixCode(ctx) {
|
|
|
98
103
|
// summary-only iterations have no path to a push, so listing runs would prompt
|
|
99
104
|
// unnecessary cancellation.
|
|
100
105
|
const pushLikely = threads.length > 0 ||
|
|
101
|
-
|
|
106
|
+
failingAgentChecks.length > 0 ||
|
|
107
|
+
annotatedExtra.length > 0 ||
|
|
102
108
|
hasConflicts ||
|
|
103
109
|
changesRequestedReviews.length > 0 ||
|
|
104
110
|
actionableComments.length > 0;
|
|
@@ -110,13 +116,14 @@ export async function handleFixCode(ctx) {
|
|
|
110
116
|
: [];
|
|
111
117
|
const commentMinimizeIds = report.comments.minimizeIds ?? actionableComments.map((c) => c.id);
|
|
112
118
|
const allCommentIds = [...commentMinimizeIds, ...reviewSummaryIds];
|
|
113
|
-
const { resolveCommand, resolveOnlyCommand } = buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, changesRequestedReviews,
|
|
119
|
+
const { resolveCommand, resolveOnlyCommand } = buildResolveCommand(threads, resolutionOnlyThreads, allCommentIds, changesRequestedReviews, failingAgentChecks, prNumber, botUsernames, ruleAutoResolveThreadIds);
|
|
114
120
|
// Safety: if the base branch is unknown, escalate when a push is plausible — the agent
|
|
115
121
|
// would need the correct base to rebase safely. This is a conservative guard, not a
|
|
116
122
|
// prediction that the agent *will* push. Intentionally broader than `pushLikely` above:
|
|
117
123
|
// resolution-only threads also need a known base in case the agent does push.
|
|
118
124
|
const pushIsPlausible = threads.length > 0 ||
|
|
119
|
-
|
|
125
|
+
failingAgentChecks.length > 0 ||
|
|
126
|
+
annotatedExtra.length > 0 ||
|
|
120
127
|
hasConflicts ||
|
|
121
128
|
changesRequestedReviews.length > 0 ||
|
|
122
129
|
actionableComments.length > 0 ||
|
|
@@ -12,6 +12,7 @@ import { clearStallState } from "../../state/iterate-stall.mjs";
|
|
|
12
12
|
import { handleFixCode } from "./fix-code.mjs";
|
|
13
13
|
import { normalizeBotUsernames } from "../../comments/authors.mjs";
|
|
14
14
|
import { autoMinimizeComments } from "../../comments/resolve.mjs";
|
|
15
|
+
import { checksWithUnseenAnnotations } from "../check-annotations.mjs";
|
|
15
16
|
export async function runIterate(opts) {
|
|
16
17
|
const config = loadConfig();
|
|
17
18
|
const botUsernames = normalizeBotUsernames(config.botUsernames);
|
|
@@ -69,6 +70,7 @@ export async function runIterate(opts) {
|
|
|
69
70
|
report.comments.firstLook.length > 0 ||
|
|
70
71
|
report.changesRequestedReviews.length > 0 ||
|
|
71
72
|
report.checks.failing.length > 0 ||
|
|
73
|
+
checksWithUnseenAnnotations(report).length > 0 ||
|
|
72
74
|
report.mergeStatus.status === "CONFLICTS" ||
|
|
73
75
|
reviewSummaryIds.length > 0 ||
|
|
74
76
|
firstLookSummaries.length > 0 ||
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { renderShellCommand } from "../../cli/runner.mjs";
|
|
2
2
|
import { buildFailingCheckInstructions, buildCrStaleClause, buildBehindBaseHintInstruction, buildResolveCommandInstruction, } 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 { isFailingAgentCheck } from "../../checks/conclusions.mjs";
|
|
4
5
|
import { buildCommitSuggestionInstruction } from "../commit-suggestion-instruction.mjs";
|
|
5
6
|
const FIX_INSTRUCTION_STOP = "Stop this iteration — if you pushed new commits, CI needs time before the next tick; otherwise stop before the next tick.";
|
|
6
7
|
/** Render a resolve command as a shell snippet. Appends `--require-sha "$HEAD_SHA"` when set. */
|
|
@@ -13,8 +14,11 @@ export function renderResolveCommand(rc) {
|
|
|
13
14
|
export function buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseBranch, resolveCommand, hasConflicts, prNumber, cancelledCount, firstLookThreads = [], firstLookComments = [], firstLookSummaries = [], editedSummaries = [], inProgressRunIds = [], resolutionOnlyThreads = [], resolveOnlyCommand, behindBaseHint = "", // iterate.behindBaseHint — see buildBehindBaseHintInstruction
|
|
14
15
|
isBehind = false) {
|
|
15
16
|
const instructions = [];
|
|
17
|
+
const failingChecks = checks.filter((c) => isFailingAgentCheck(c));
|
|
18
|
+
const hasAnnotations = checks.some((c) => (c.annotations?.length ?? 0) > 0);
|
|
16
19
|
const hasNonConflictHints = threads.length > 0 ||
|
|
17
|
-
|
|
20
|
+
failingChecks.length > 0 ||
|
|
21
|
+
hasAnnotations ||
|
|
18
22
|
changesRequestedReviews.length > 0 ||
|
|
19
23
|
actionableComments.length > 0;
|
|
20
24
|
// Leading decision or mandatory instruction depending on what actionable items exist.
|
|
@@ -24,9 +28,9 @@ isBehind = false) {
|
|
|
24
28
|
actionableSections.push("`## Review threads`");
|
|
25
29
|
if (actionableComments.length > 0)
|
|
26
30
|
actionableSections.push("`## Actionable comments`");
|
|
27
|
-
if (
|
|
31
|
+
if (failingChecks.length > 0)
|
|
28
32
|
actionableSections.push("`## Failing checks`");
|
|
29
|
-
if (
|
|
33
|
+
if (hasAnnotations) {
|
|
30
34
|
actionableSections.push("`## Check annotations`");
|
|
31
35
|
}
|
|
32
36
|
if (changesRequestedReviews.length > 0)
|
|
@@ -68,8 +72,8 @@ isBehind = false) {
|
|
|
68
72
|
if (resolutionOnlyThreads.length > 0) {
|
|
69
73
|
instructions.push(`Review the threads under \`## Review threads to resolve\`. Human-authored threads are replied to by the \`apply review:\` command shown below; Shepherd does not resolve them. Bot/non-human threads are included in \`--resolve-thread-ids\`.`);
|
|
70
74
|
}
|
|
71
|
-
instructions.push(...buildFailingCheckInstructions(
|
|
72
|
-
if (
|
|
75
|
+
instructions.push(...buildFailingCheckInstructions(failingChecks));
|
|
76
|
+
if (hasAnnotations) {
|
|
73
77
|
instructions.push(`For each item under \`## Check annotations\`: inspect the referenced file range and decide whether the annotation requires a code change. These annotations are surfaced once per PR and do not need any resolve/minimize mutation.`);
|
|
74
78
|
}
|
|
75
79
|
if (changesRequestedReviews.length > 0) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readStallState, writeStallState } from "../../state/iterate-stall.mjs";
|
|
2
2
|
import { toAgentThread, toAgentComment, toAgentStalledCheck } from "../../reporters/agent.mjs";
|
|
3
3
|
import { buildEscalateSuggestion, buildEscalateHumanMessage, formatDurationApprox, } from "./escalate.mjs";
|
|
4
|
+
import { checksWithUnseenAnnotations } from "../check-annotations.mjs";
|
|
4
5
|
function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds) {
|
|
5
6
|
const checks = [
|
|
6
7
|
...report.checks.failing.map((f) => `failing:${f.name}:${f.conclusion}`),
|
|
@@ -13,6 +14,9 @@ function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds
|
|
|
13
14
|
const reviews = report.changesRequestedReviews.map((r) => r.id).sort();
|
|
14
15
|
const summaries = [...reviewSummaryIds].sort();
|
|
15
16
|
const ruleAutoResolveSummaries = (report.ruleAutoResolveReviewSummaryIds ?? []).sort((a, b) => a.localeCompare(b));
|
|
17
|
+
const annotations = checksWithUnseenAnnotations(report)
|
|
18
|
+
.flatMap((c) => (c.annotations ?? []).map((a) => a.id))
|
|
19
|
+
.sort((a, b) => a.localeCompare(b));
|
|
16
20
|
return JSON.stringify({
|
|
17
21
|
action,
|
|
18
22
|
headSha,
|
|
@@ -28,6 +32,7 @@ function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds
|
|
|
28
32
|
reviews,
|
|
29
33
|
summaries,
|
|
30
34
|
ruleAutoResolveSummaries,
|
|
35
|
+
annotations,
|
|
31
36
|
});
|
|
32
37
|
}
|
|
33
38
|
export async function applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, prospectiveResult, report, reviewSummaryIds) {
|
|
@@ -45,6 +45,7 @@ export function mapCheckRunNode(node) {
|
|
|
45
45
|
...(completedAtUnix !== undefined && { completedAtUnix }),
|
|
46
46
|
...(updatedAtUnix !== undefined && { updatedAtUnix }),
|
|
47
47
|
...(summary !== undefined && { summary }),
|
|
48
|
+
...((node.annotations?.nodes.length ?? 0) > 0 && { hasAnnotations: true }),
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
function extractCheckRunSummary(title, summary) {
|
|
@@ -192,6 +192,11 @@ export type RawContextNode = {
|
|
|
192
192
|
startedAt?: string | null;
|
|
193
193
|
title: string | null;
|
|
194
194
|
summary: string | null;
|
|
195
|
+
annotations?: {
|
|
196
|
+
nodes: Array<{
|
|
197
|
+
message: string;
|
|
198
|
+
}>;
|
|
199
|
+
};
|
|
195
200
|
checkSuite: {
|
|
196
201
|
createdAt?: string;
|
|
197
202
|
updatedAt?: string;
|
package/bin/reporters/agent.mjs
CHANGED
|
@@ -50,9 +50,6 @@ export function toAgentComment(c) {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
export function toAgentCheck(c) {
|
|
53
|
-
if (c.conclusion === "SKIPPED" || c.conclusion === "NEUTRAL") {
|
|
54
|
-
throw new Error(`Unexpected conclusion ${c.conclusion} in toAgentCheck`);
|
|
55
|
-
}
|
|
56
53
|
return {
|
|
57
54
|
name: c.name,
|
|
58
55
|
runId: c.runId,
|
|
@@ -3,6 +3,8 @@ import type { CheckRun } from "./github.mts";
|
|
|
3
3
|
type CheckCategory = "passed" | "failing" | "in_progress" | "skipped" | "filtered" | "ignored" | "superseded";
|
|
4
4
|
export interface ClassifiedCheck extends CheckRun {
|
|
5
5
|
category: CheckCategory;
|
|
6
|
+
/** Inline annotations attached to this check run, surfaced once per PR. */
|
|
7
|
+
annotations?: CheckAnnotation[];
|
|
6
8
|
}
|
|
7
9
|
export interface TriagedCheck extends ClassifiedCheck {
|
|
8
10
|
/** Workflow display name (e.g. `"CI"`). Populated when available from the jobs API; may be `undefined` on fetch failure or when no matching job is found. */
|
|
@@ -13,7 +15,5 @@ export interface TriagedCheck extends ClassifiedCheck {
|
|
|
13
15
|
failedStep?: string;
|
|
14
16
|
/** Bounded raw excerpt from the matched failed job log, when GitHub exposes one. */
|
|
15
17
|
logExcerpt?: string;
|
|
16
|
-
/** Inline annotations attached to this failing check run, surfaced once per PR. */
|
|
17
|
-
annotations?: CheckAnnotation[];
|
|
18
18
|
}
|
|
19
19
|
export {};
|
package/bin/types/github.d.mts
CHANGED
|
@@ -28,6 +28,8 @@ export interface CheckRun {
|
|
|
28
28
|
/** Workflow display name for GitHub Actions check runs, when GraphQL exposes it. */
|
|
29
29
|
workflowName?: string;
|
|
30
30
|
workflowId?: string;
|
|
31
|
+
/** True when GraphQL reported at least one CheckRun annotation. Omitted when false. */
|
|
32
|
+
hasAnnotations?: boolean;
|
|
31
33
|
}
|
|
32
34
|
export interface ReviewThread {
|
|
33
35
|
id: string;
|
package/bin/types/report.d.mts
CHANGED
|
@@ -32,6 +32,8 @@ export interface ShepherdReport {
|
|
|
32
32
|
skipped: ClassifiedCheck[];
|
|
33
33
|
/** Checks filtered out because they were triggered by a non-PR event (push, schedule, etc.). */
|
|
34
34
|
filtered: ClassifiedCheck[];
|
|
35
|
+
/** Ignored checks with unseen annotations; omitted when empty. */
|
|
36
|
+
ignored?: ClassifiedCheck[];
|
|
35
37
|
filteredNames: string[];
|
|
36
38
|
blockedByFilteredCheck: boolean;
|
|
37
39
|
ignoredNames?: string[];
|
|
@@ -104,16 +106,14 @@ export interface AgentComment {
|
|
|
104
106
|
url: string;
|
|
105
107
|
edited?: boolean;
|
|
106
108
|
}
|
|
107
|
-
/** Check shape emitted to the iterate agent under `fix_code`.
|
|
108
|
-
* should be handled from `name`/`runId`/`detailsUrl`/`conclusion`; optional
|
|
109
|
-
* workflow/job/step metadata may still be present when available. */
|
|
109
|
+
/** Check shape emitted to the iterate agent under `fix_code`. */
|
|
110
110
|
export interface AgentCheck {
|
|
111
111
|
name: string;
|
|
112
112
|
runId: string | null;
|
|
113
113
|
/** Fallback for checks where runId is null (e.g. external status checks). */
|
|
114
114
|
detailsUrl: string | null;
|
|
115
115
|
/** Raw GitHub check conclusion; may be null for some completed checks from upstream data. */
|
|
116
|
-
conclusion:
|
|
116
|
+
conclusion: CheckConclusion;
|
|
117
117
|
/** Workflow display name (e.g. `"CI"`). Populated on a best-effort basis when available from the jobs API. */
|
|
118
118
|
workflowName?: string;
|
|
119
119
|
/** Name of the matched job (e.g. `"tests (ubuntu)"`). Distinct from check name for matrix builds. */
|
|
@@ -123,8 +123,8 @@ export interface AgentCheck {
|
|
|
123
123
|
/** One-line status text shown in the GitHub UI (e.g. "67.68% of diff hit (target 85.00%)"). */
|
|
124
124
|
summary?: string;
|
|
125
125
|
logExcerpt?: string;
|
|
126
|
-
/** Marker-gated inline annotations from this failing check. */
|
|
127
126
|
annotations?: CheckAnnotation[];
|
|
127
|
+
annotationOnly?: true;
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
130
|
* A single CI check that is relevant to PR readiness — triggered by a PR event
|
package/package.json
CHANGED