pr-shepherd 0.54.1 → 0.55.1
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 +3 -3
- package/bin/checks/unreported-required.d.mts +29 -0
- package/bin/checks/unreported-required.mjs +52 -0
- package/bin/classify/apply.d.mts +2 -0
- package/bin/classify/apply.mjs +31 -31
- package/bin/classify/rule-action.d.mts +8 -0
- package/bin/classify/rule-action.mjs +29 -0
- package/bin/classify/types.d.mts +1 -1
- package/bin/cli/help-command-pages.d.mts +2 -2
- package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
- package/bin/cli/help-iterate-poll-pages.mjs +6 -5
- package/bin/cli/help-top-page.d.mts +1 -1
- package/bin/cli/help-top-page.mjs +2 -2
- package/bin/cli/help.d.mts +3 -3
- package/bin/cli/iterate-formatter.mjs +32 -9
- package/bin/cli/iterate-instructions.mjs +6 -0
- package/bin/cli/iterate-lean.mjs +17 -0
- package/bin/cli/poll-summary-emitter.mjs +4 -1
- package/bin/cli/poll-summary-formatter.mjs +10 -3
- package/bin/cli/stack-overview.d.mts +48 -0
- package/bin/cli/stack-overview.mjs +138 -0
- package/bin/commands/check-fingerprint.mjs +2 -1
- package/bin/commands/check-status.d.mts +1 -1
- package/bin/commands/check-status.mjs +8 -3
- package/bin/commands/check-unreported.d.mts +28 -0
- package/bin/commands/check-unreported.mjs +101 -0
- package/bin/commands/check.mjs +45 -38
- package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
- package/bin/commands/commit-suggestion-instruction.mjs +1 -1
- package/bin/commands/iterate/base.mjs +11 -0
- package/bin/commands/iterate/check-instructions.d.mts +1 -1
- package/bin/commands/iterate/check-instructions.mjs +1 -1
- package/bin/commands/iterate/escalate.mjs +4 -0
- package/bin/commands/iterate/index.mjs +69 -3
- package/bin/commands/iterate/merge-state.mjs +12 -12
- package/bin/commands/iterate/merge.d.mts +12 -2
- package/bin/commands/iterate/merge.mjs +32 -2
- package/bin/commands/iterate/unreported-required.d.mts +18 -0
- package/bin/commands/iterate/unreported-required.mjs +136 -0
- package/bin/commands/poll-summary.mjs +1 -0
- package/bin/commands/poll.mjs +9 -2
- package/bin/commands/ready-mergeability.d.mts +1 -1
- package/bin/commands/ready-mergeability.mjs +2 -2
- package/bin/commands/rule-auto-resolve-format.d.mts +22 -0
- package/bin/commands/rule-auto-resolve-format.mjs +151 -0
- package/bin/commands/rule-auto-resolve.d.mts +21 -0
- package/bin/commands/rule-auto-resolve.mjs +113 -0
- package/bin/commands/shepherd-journal.d.mts +1 -1
- package/bin/commands/shepherd-journal.mjs +1 -1
- package/bin/commands/stack-drain.d.mts +0 -5
- package/bin/commands/stack-drain.mjs +26 -16
- package/bin/commands/stack-merge-flag.d.mts +7 -0
- package/bin/commands/stack-merge-flag.mjs +6 -0
- package/bin/config/load.d.mts +3 -0
- package/bin/config/load.mjs +9 -2
- package/bin/config/merge-method.d.mts +27 -0
- package/bin/config/merge-method.mjs +56 -0
- package/bin/exit-codes.d.mts +1 -1
- package/bin/exit-codes.mjs +2 -1
- package/bin/github/batch-parse-suites.d.mts +3 -0
- package/bin/github/batch-parse-suites.mjs +15 -0
- package/bin/github/batch-parsers.d.mts +1 -1
- package/bin/github/batch-parsers.mjs +7 -0
- package/bin/github/batch-raw-types.d.mts +5 -0
- package/bin/github/batch.d.mts +3 -0
- package/bin/github/batch.mjs +9 -1
- package/bin/github/errors.d.mts +6 -0
- package/bin/github/errors.mjs +23 -5
- package/bin/github/fingerprint-fields.d.mts +1 -0
- package/bin/github/fingerprint-fields.mjs +1 -1
- package/bin/github/gql/base-behind.gql +20 -0
- package/bin/github/gql/batch-pr-page.gql +2 -0
- package/bin/github/gql/batch-pr.gql +5 -0
- package/bin/github/gql/commit-check-suites.gql +1 -0
- package/bin/github/gql/poll-stack-summary.gql +6 -0
- package/bin/github/gql/poll-summary-annotation-probe.gql +32 -0
- package/bin/github/gql/poll-summary-check-contexts.gql +2 -3
- package/bin/github/gql/poll-summary-fragment.gql +16 -0
- package/bin/github/gql/pr-merge-policy.gql +1 -44
- package/bin/github/gql/ref-rules-query.gql +18 -0
- package/bin/github/gql/ref-rules.gql +46 -0
- package/bin/github/graphql-internal-retry.d.mts +1 -1
- package/bin/github/graphql-internal-retry.mjs +4 -3
- package/bin/github/merge-queue-checks.mjs +7 -3
- package/bin/github/merge-target-rules.d.mts +28 -0
- package/bin/github/merge-target-rules.mjs +77 -0
- package/bin/github/poll-summary-annotation-probe.d.mts +19 -0
- package/bin/github/poll-summary-annotation-probe.mjs +88 -0
- package/bin/github/poll-summary-fingerprint.mjs +1 -1
- package/bin/github/poll-summary-projector.d.mts +1 -1
- package/bin/github/poll-summary-projector.mjs +19 -12
- package/bin/github/poll-summary-queue-removal.mjs +7 -3
- package/bin/github/poll-summary-raw.d.mts +16 -0
- package/bin/github/poll-summary-readiness.mjs +1 -0
- package/bin/github/poll-summary-route.mjs +5 -0
- package/bin/github/poll-summary-unreported.d.mts +6 -0
- package/bin/github/poll-summary-unreported.mjs +51 -0
- package/bin/github/poll-summary.d.mts +1 -0
- package/bin/github/poll-summary.mjs +28 -3
- package/bin/github/queries.d.mts +9 -0
- package/bin/github/queries.mjs +11 -2
- package/bin/github/queue-removal-freshness.d.mts +20 -5
- package/bin/github/queue-removal-freshness.mjs +37 -10
- package/bin/github/stack-read.d.mts +4 -0
- package/bin/github/stack-read.mjs +13 -1
- package/bin/mcp/server.mjs +6 -1
- package/bin/state/ci-retrigger.d.mts +25 -0
- package/bin/state/ci-retrigger.mjs +48 -0
- package/bin/types/escalate.d.mts +1 -1
- package/bin/types/github.d.mts +11 -0
- package/bin/types/iterate.d.mts +16 -3
- package/bin/types/poll-summary.d.mts +13 -0
- package/bin/types/report.d.mts +34 -1
- 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 +3 -3
- package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +1 -1
- package/src/classify/types.mts +1 -1
|
@@ -6,6 +6,12 @@ import { AUTO_MARK_READY_DISABLED_HOLD } from "../commands/stack-work.mjs";
|
|
|
6
6
|
import { buildPrShepherdCommand } from "./runner.mjs";
|
|
7
7
|
export function buildSimpleIterateInstructions(result) {
|
|
8
8
|
switch (result.action) {
|
|
9
|
+
case "ready": {
|
|
10
|
+
const sentence = `PR #${result.pr} is ready. Ready-delay has ${result.remainingSeconds}s left. Run this same command again when the timer elapses. Do not start other work.`;
|
|
11
|
+
return [
|
|
12
|
+
result.quotaWarning ? buildQuotaAwareContinuation(result.quotaWarning, sentence) : sentence,
|
|
13
|
+
];
|
|
14
|
+
}
|
|
9
15
|
case "wait":
|
|
10
16
|
if (result.stackDraftHold)
|
|
11
17
|
return [buildStackDraftHoldInstruction(result, result.stackDraftHold)];
|
package/bin/cli/iterate-lean.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { adaptIterateLog, buildSimpleIterateInstructions } from "./iterate-instructions.mjs";
|
|
2
|
+
import { projectRuleAutoResolve } from "../commands/rule-auto-resolve-format.mjs";
|
|
2
3
|
/**
|
|
3
4
|
* Project an IterateResult to a lean JSON shape for the default (non-verbose) output.
|
|
4
5
|
* Omits fields that are the trivial default (false, 0, empty) or state-gated fields
|
|
@@ -66,9 +67,25 @@ export function projectIterateLean(result, opts) {
|
|
|
66
67
|
...((result.supersededNames?.length ?? 0) > 0 && {
|
|
67
68
|
supersededNames: result.supersededNames,
|
|
68
69
|
}),
|
|
70
|
+
...((result.unreportedRequiredChecks?.length ?? 0) > 0 && {
|
|
71
|
+
unreportedRequiredChecks: result.unreportedRequiredChecks,
|
|
72
|
+
}),
|
|
73
|
+
...(result.trunkBehindBy !== undefined &&
|
|
74
|
+
result.trunkBehindBy > 0 && { trunkBehindBy: result.trunkBehindBy }),
|
|
75
|
+
...(result.baseBehindBy !== undefined &&
|
|
76
|
+
result.baseBehindBy > 0 && { baseBehindBy: result.baseBehindBy }),
|
|
69
77
|
...(result.quotaWarning && { quotaWarning: result.quotaWarning }),
|
|
78
|
+
...(result.ruleAutoResolve && {
|
|
79
|
+
ruleAutoResolve: projectRuleAutoResolve(result.ruleAutoResolve),
|
|
80
|
+
}),
|
|
70
81
|
};
|
|
71
82
|
switch (result.action) {
|
|
83
|
+
case "ready":
|
|
84
|
+
return {
|
|
85
|
+
...base,
|
|
86
|
+
log: adaptIterateLog(result.log),
|
|
87
|
+
instructions: simpleInstructions(result),
|
|
88
|
+
};
|
|
72
89
|
case "wait":
|
|
73
90
|
return {
|
|
74
91
|
...base,
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { EXIT } from "../exit-codes.mjs";
|
|
2
2
|
import { formatPollSummaryResult } from "./poll-summary-formatter.mjs";
|
|
3
|
+
import { projectStackOverview } from "./stack-overview.mjs";
|
|
3
4
|
export function emitPollSummaryResult(result, opts) {
|
|
4
|
-
process.stdout.write(opts.format === "json"
|
|
5
|
+
process.stdout.write(opts.format === "json"
|
|
6
|
+
? `${JSON.stringify(result.selection.kind === "stack" ? projectStackOverview(result) : result)}\n`
|
|
7
|
+
: `${formatPollSummaryResult(result)}\n`);
|
|
5
8
|
process.exitCode = pollSummaryExitCode(result);
|
|
6
9
|
}
|
|
7
10
|
function pollSummaryExitCode(result) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { formatApiUsage, formatQuotaWarning } from "./api-usage-formatter.mjs";
|
|
2
2
|
import { withPollSummaryInstructions } from "../commands/poll-summary-instructions.mjs";
|
|
3
|
+
import { formatStackOverview, projectStackOverview } from "./stack-overview.mjs";
|
|
3
4
|
export function formatPollSummaryResult(result) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
if (result.selection.kind === "stack")
|
|
6
|
+
return formatStackOverview(projectStackOverview(result));
|
|
7
|
+
const selection = `PRs ${result.selection.requested.map((pr) => `#${pr}`).join(", ")}`;
|
|
7
8
|
const lines = [
|
|
8
9
|
`# Poll summary [${result.reason.toUpperCase()}]`,
|
|
9
10
|
"",
|
|
@@ -52,6 +53,12 @@ function formatItem(item) {
|
|
|
52
53
|
...(checks
|
|
53
54
|
? [` - checks: ${formatCounts(checks, checks.incomplete ? ", incomplete" : "")}`]
|
|
54
55
|
: []),
|
|
56
|
+
...(checks?.unreportedRequired?.length
|
|
57
|
+
? [
|
|
58
|
+
` - unreported required: ${checks.unreportedRequired.map((name) => `\`${name}\``).join(", ")}`,
|
|
59
|
+
]
|
|
60
|
+
: []),
|
|
61
|
+
...(checks?.actionsWorkflowInProgress ? [" - actions workflow in progress"] : []),
|
|
55
62
|
...(review
|
|
56
63
|
? [` - review: ${formatCounts(review, review.incomplete ? ", incomplete" : "")}`]
|
|
57
64
|
: []),
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PollSummaryItem, PollSummaryResult, PollSummaryStackAncestry, StackNextAction } from "../types.mts";
|
|
2
|
+
import type { ApiUsage, GraphqlQuotaWarning } from "../types/api-usage.mts";
|
|
3
|
+
/** Agent-facing stack row. Planning keeps the richer PollSummaryItem. */
|
|
4
|
+
interface StackLayerView {
|
|
5
|
+
pr: number;
|
|
6
|
+
title: string;
|
|
7
|
+
url: string;
|
|
8
|
+
state: PollSummaryItem["state"];
|
|
9
|
+
shepherded: boolean;
|
|
10
|
+
mergeable: boolean;
|
|
11
|
+
blocker?: string;
|
|
12
|
+
author?: string;
|
|
13
|
+
owned?: true;
|
|
14
|
+
isDraft?: true;
|
|
15
|
+
isInMergeQueue?: true;
|
|
16
|
+
position?: number;
|
|
17
|
+
stackSize?: number;
|
|
18
|
+
baseRefName: string;
|
|
19
|
+
failing?: number;
|
|
20
|
+
inProgress?: number;
|
|
21
|
+
actionable?: number;
|
|
22
|
+
checksIncomplete?: true;
|
|
23
|
+
reviewIncomplete?: true;
|
|
24
|
+
queueRemoval?: {
|
|
25
|
+
reason: string | null;
|
|
26
|
+
actor?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface StackOverview {
|
|
30
|
+
mode: "summary";
|
|
31
|
+
repo: string;
|
|
32
|
+
selection: Extract<PollSummaryResult["selection"], {
|
|
33
|
+
kind: "stack";
|
|
34
|
+
}>;
|
|
35
|
+
reason: PollSummaryResult["reason"];
|
|
36
|
+
stackMergeable?: boolean;
|
|
37
|
+
nextAction?: StackNextAction;
|
|
38
|
+
prs: StackLayerView[];
|
|
39
|
+
stackAncestry?: PollSummaryStackAncestry[];
|
|
40
|
+
instructions?: string[];
|
|
41
|
+
apiUsage?: ApiUsage;
|
|
42
|
+
quotaWarning?: GraphqlQuotaWarning;
|
|
43
|
+
}
|
|
44
|
+
/** Lean stack payload shared by Markdown, JSON, and MCP. */
|
|
45
|
+
export declare function projectStackOverview(result: PollSummaryResult): StackOverview;
|
|
46
|
+
/** Markdown for a stack overview. */
|
|
47
|
+
export declare function formatStackOverview(overview: StackOverview): string;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { stackLayerBlockReason } from "../commands/stack-layer-readiness.mjs";
|
|
2
|
+
import { formatApiUsage, formatQuotaWarning } from "./api-usage-formatter.mjs";
|
|
3
|
+
/** Lean stack payload shared by Markdown, JSON, and MCP. */
|
|
4
|
+
export function projectStackOverview(result) {
|
|
5
|
+
const selection = result.selection;
|
|
6
|
+
if (selection.kind !== "stack") {
|
|
7
|
+
throw new Error("projectStackOverview requires a stack selection");
|
|
8
|
+
}
|
|
9
|
+
const stale = new Set(result.stackAncestry?.map((gap) => gap.childPr) ?? []);
|
|
10
|
+
return {
|
|
11
|
+
mode: "summary",
|
|
12
|
+
repo: result.repo,
|
|
13
|
+
selection,
|
|
14
|
+
reason: result.reason,
|
|
15
|
+
...(result.stackMergeable !== undefined && { stackMergeable: result.stackMergeable }),
|
|
16
|
+
...(result.nextAction && { nextAction: result.nextAction }),
|
|
17
|
+
prs: result.prs.map((item) => projectLayer(item, stale.has(item.pr))),
|
|
18
|
+
...(result.stackAncestry?.length && { stackAncestry: result.stackAncestry }),
|
|
19
|
+
...(result.instructions?.length && { instructions: result.instructions }),
|
|
20
|
+
...(result.apiUsage && { apiUsage: result.apiUsage }),
|
|
21
|
+
...(result.quotaWarning && { quotaWarning: result.quotaWarning }),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function projectLayer(item, stale) {
|
|
25
|
+
const blocker = layerBlocker(item, stale);
|
|
26
|
+
const removal = item.queueRemoval;
|
|
27
|
+
return {
|
|
28
|
+
pr: item.pr,
|
|
29
|
+
title: item.title,
|
|
30
|
+
url: item.url,
|
|
31
|
+
state: item.state,
|
|
32
|
+
shepherded: item.readyReceipt === true,
|
|
33
|
+
mergeable: blocker === undefined,
|
|
34
|
+
...(blocker && { blocker }),
|
|
35
|
+
...(item.authorLogin && { author: item.authorLogin }),
|
|
36
|
+
...(item.owned && { owned: true }),
|
|
37
|
+
...(item.isDraft && { isDraft: true }),
|
|
38
|
+
...(item.isInMergeQueue && { isInMergeQueue: true }),
|
|
39
|
+
...(item.stack && { position: item.stack.position, stackSize: item.stack.size }),
|
|
40
|
+
baseRefName: item.baseRefName,
|
|
41
|
+
...(blocker === "failing-checks" &&
|
|
42
|
+
(item.checks?.failing ?? 0) > 0 && { failing: item.checks?.failing }),
|
|
43
|
+
...(blocker === "checks-in-progress" &&
|
|
44
|
+
(item.checks?.inProgress ?? 0) > 0 && { inProgress: item.checks?.inProgress }),
|
|
45
|
+
...(blocker === "review-work" &&
|
|
46
|
+
(item.review?.actionable ?? 0) > 0 && { actionable: item.review?.actionable }),
|
|
47
|
+
...(item.checks?.incomplete && { checksIncomplete: true }),
|
|
48
|
+
...(item.review?.incomplete && { reviewIncomplete: true }),
|
|
49
|
+
...(removal && {
|
|
50
|
+
queueRemoval: {
|
|
51
|
+
reason: removal.reason,
|
|
52
|
+
...(removal.actor && { actor: removal.actor }),
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Mergeability blocker. A missing receipt is "not shepherded", not a mergeability reason. */
|
|
58
|
+
function layerBlocker(item, stale) {
|
|
59
|
+
if (item.action === "escalate") {
|
|
60
|
+
return item.reasons.find((reason) => reason !== "appears-ready") ?? "escalate";
|
|
61
|
+
}
|
|
62
|
+
if (stale)
|
|
63
|
+
return "stale-ancestry";
|
|
64
|
+
const reason = stackLayerBlockReason(item);
|
|
65
|
+
if (reason === undefined || reason === "no-ready-receipt")
|
|
66
|
+
return undefined;
|
|
67
|
+
return reason;
|
|
68
|
+
}
|
|
69
|
+
/** Skill stop tokens. Other stack actions stay untagged so the loop keeps running. */
|
|
70
|
+
function stackTerminalTag(nextAction) {
|
|
71
|
+
if (nextAction === "cancel")
|
|
72
|
+
return " [CANCEL]";
|
|
73
|
+
if (nextAction === "escalate")
|
|
74
|
+
return " [ESCALATE]";
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
/** Markdown for a stack overview. */
|
|
78
|
+
export function formatStackOverview(overview) {
|
|
79
|
+
const stack = overview.selection;
|
|
80
|
+
const lines = [
|
|
81
|
+
`# ${overview.repo} stack #${stack.stackNumber}${stackTerminalTag(overview.nextAction)} — ${overview.reason}`,
|
|
82
|
+
"",
|
|
83
|
+
`Stack: #${stack.stackNumber} · anchor PR #${stack.anchor} · ${stack.stackSize} layers · mode \`${overview.mode}\``,
|
|
84
|
+
...(overview.stackMergeable !== undefined
|
|
85
|
+
? [`stackMergeable: ${overview.stackMergeable}`]
|
|
86
|
+
: []),
|
|
87
|
+
...(overview.nextAction ? [`nextAction: ${overview.nextAction}`] : []),
|
|
88
|
+
"",
|
|
89
|
+
"## Layers",
|
|
90
|
+
"",
|
|
91
|
+
...overview.prs.flatMap(formatLayerLines),
|
|
92
|
+
];
|
|
93
|
+
if (overview.stackAncestry?.length) {
|
|
94
|
+
lines.push("", "## Stack ancestry", "");
|
|
95
|
+
for (const gap of overview.stackAncestry) {
|
|
96
|
+
lines.push(`- PR #${gap.childPr} base \`${gap.childBaseRefName}\` at \`${gap.childBaseRefOid}\` differs from parent PR #${gap.parentPr} head \`${gap.parentHeadRefName}\` at \`${gap.parentHeadRefOid}\`.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (overview.instructions?.length) {
|
|
100
|
+
lines.push("", "## Instructions", "", ...overview.instructions);
|
|
101
|
+
}
|
|
102
|
+
const quota = formatQuotaWarning(overview.quotaWarning);
|
|
103
|
+
const usage = formatApiUsage(overview.apiUsage);
|
|
104
|
+
if (quota)
|
|
105
|
+
lines.push("", quota);
|
|
106
|
+
if (usage)
|
|
107
|
+
lines.push("", usage);
|
|
108
|
+
return lines.join("\n");
|
|
109
|
+
}
|
|
110
|
+
function formatLayerLines(layer) {
|
|
111
|
+
const facts = [
|
|
112
|
+
layer.shepherded ? "shepherded" : "not shepherded",
|
|
113
|
+
layer.mergeable ? "mergeable" : `not mergeable (\`${layer.blocker ?? "unknown"}\`)`,
|
|
114
|
+
layer.author ? `owner \`@${layer.author}\`` : undefined,
|
|
115
|
+
layer.owned ? "owned" : undefined,
|
|
116
|
+
].filter((fact) => fact !== undefined);
|
|
117
|
+
const details = [
|
|
118
|
+
layer.state,
|
|
119
|
+
layer.isDraft ? "draft" : undefined,
|
|
120
|
+
layer.isInMergeQueue ? "in merge queue" : undefined,
|
|
121
|
+
layer.position !== undefined && layer.stackSize !== undefined
|
|
122
|
+
? `position ${layer.position}/${layer.stackSize}`
|
|
123
|
+
: undefined,
|
|
124
|
+
`base \`${layer.baseRefName}\``,
|
|
125
|
+
layer.failing ? `${layer.failing} failing` : undefined,
|
|
126
|
+
layer.inProgress ? `${layer.inProgress} in progress` : undefined,
|
|
127
|
+
layer.actionable ? `${layer.actionable} actionable` : undefined,
|
|
128
|
+
layer.checksIncomplete ? "checks incomplete" : undefined,
|
|
129
|
+
layer.reviewIncomplete ? "review incomplete" : undefined,
|
|
130
|
+
layer.queueRemoval
|
|
131
|
+
? `removed from merge queue (${layer.queueRemoval.reason ?? "unknown reason"}${layer.queueRemoval.actor ? ` by @${layer.queueRemoval.actor}` : ""})`
|
|
132
|
+
: undefined,
|
|
133
|
+
].filter((detail) => detail !== undefined);
|
|
134
|
+
return [
|
|
135
|
+
`- [PR #${layer.pr}: ${layer.title}](${layer.url}) — ${facts.join(" · ")}`,
|
|
136
|
+
` - ${details.join(" · ")}`,
|
|
137
|
+
];
|
|
138
|
+
}
|
|
@@ -2,6 +2,7 @@ import { getMergeableState } from "../github/client.mjs";
|
|
|
2
2
|
import { fetchPrFingerprint, fingerprintsEqual, } from "../github/fingerprint.mjs";
|
|
3
3
|
import { fingerprintInputDigest, loadPrFingerprint } from "../state/pr-fingerprint.mjs";
|
|
4
4
|
import { hasCheckDrivenActionableWork } from "./check-annotations.mjs";
|
|
5
|
+
import { stripReplayedRuleAutoResolve } from "./rule-auto-resolve-format.mjs";
|
|
5
6
|
function reportAllowsFingerprintSkip(report) {
|
|
6
7
|
if (report.status === "READY")
|
|
7
8
|
return false;
|
|
@@ -50,7 +51,7 @@ export async function tryReuseFingerprintReport(prNumber, repo, stateKey, config
|
|
|
50
51
|
if (!(await cachedReportSurvivesMergeabilityRefresh(prNumber, repo, cached.report, cached.fingerprint))) {
|
|
51
52
|
return null;
|
|
52
53
|
}
|
|
53
|
-
return { ...cached.report, fingerprintReused: true };
|
|
54
|
+
return { ...stripReplayedRuleAutoResolve(cached.report), fingerprintReused: true };
|
|
54
55
|
}
|
|
55
56
|
async function cachedReportSurvivesMergeabilityRefresh(prNumber, repo, report, fingerprint) {
|
|
56
57
|
const restDerived = fingerprint.mergeable !== report.mergeStatus.mergeable ||
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { MergeStatusResult, ShepherdStatus } from "../types.mts";
|
|
2
2
|
import type { CiVerdict } from "../checks/classify.mts";
|
|
3
|
-
export declare function computeStatus(verdict: CiVerdict, unresolvedThreads: number, unresolvedComments: number, mergeStatus: MergeStatusResult, changesRequestedReviews: number): ShepherdStatus;
|
|
3
|
+
export declare function computeStatus(verdict: CiVerdict, unresolvedThreads: number, unresolvedComments: number, mergeStatus: MergeStatusResult, changesRequestedReviews: number, hasUnreportedRequired?: boolean): ShepherdStatus;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function computeStatus(verdict, unresolvedThreads, unresolvedComments, mergeStatus, changesRequestedReviews) {
|
|
1
|
+
export function computeStatus(verdict, unresolvedThreads, unresolvedComments, mergeStatus, changesRequestedReviews, hasUnreportedRequired = false) {
|
|
2
2
|
// Merge conflicts are always terminal regardless of CI state.
|
|
3
3
|
if (mergeStatus.status === "CONFLICTS")
|
|
4
4
|
return "FAILING";
|
|
@@ -25,7 +25,8 @@ export function computeStatus(verdict, unresolvedThreads, unresolvedComments, me
|
|
|
25
25
|
unresolvedComments === 0 &&
|
|
26
26
|
changesRequestedReviews === 0 &&
|
|
27
27
|
(mergeStatus.status === "BLOCKED" || mergeStatus.status === "UNSTABLE") &&
|
|
28
|
-
!mergeStatus.blockingBotReviewInProgress
|
|
28
|
+
!mergeStatus.blockingBotReviewInProgress &&
|
|
29
|
+
!hasUnreportedRequired) {
|
|
29
30
|
return "READY";
|
|
30
31
|
}
|
|
31
32
|
if (mergeStatus.status === "BLOCKED" ||
|
|
@@ -37,7 +38,11 @@ export function computeStatus(verdict, unresolvedThreads, unresolvedComments, me
|
|
|
37
38
|
if (changesRequestedReviews > 0 || unresolvedThreads > 0 || unresolvedComments > 0)
|
|
38
39
|
return "UNRESOLVED_COMMENTS";
|
|
39
40
|
// DRAFT is treated the same as CLEAN for readiness — marking the PR ready resolves it.
|
|
40
|
-
if ((mergeStatus.status === "CLEAN" || mergeStatus.status === "DRAFT") &&
|
|
41
|
+
if ((mergeStatus.status === "CLEAN" || mergeStatus.status === "DRAFT") &&
|
|
42
|
+
verdict.allPassed &&
|
|
43
|
+
!hasUnreportedRequired)
|
|
41
44
|
return "READY";
|
|
45
|
+
if (hasUnreportedRequired && (mergeStatus.status === "CLEAN" || mergeStatus.status === "DRAFT"))
|
|
46
|
+
return "PENDING";
|
|
42
47
|
return "UNKNOWN";
|
|
43
48
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type WorkflowSuiteSnapshot } from "../checks/unreported-required.mts";
|
|
2
|
+
import type { BatchPrData, CheckRun, ShepherdReport } from "../types.mts";
|
|
3
|
+
import type { RepoInfo } from "../github/client.mts";
|
|
4
|
+
export interface UnreportedRequiredFields {
|
|
5
|
+
unreportedRequiredChecks?: string[];
|
|
6
|
+
trunkBehindBy?: number;
|
|
7
|
+
/** Commits on the PR base that this head does not contain. Omitted when zero or a stack. */
|
|
8
|
+
baseBehindBy?: number;
|
|
9
|
+
actionsWorkflowInProgress?: true;
|
|
10
|
+
stackBottomPr?: number;
|
|
11
|
+
headRefName: string;
|
|
12
|
+
hasUnreportedRequired: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Load trunk rules for a stack and diff them against the head's check names. */
|
|
15
|
+
export declare function collectUnreportedRequired(input: {
|
|
16
|
+
batchData: BatchPrData;
|
|
17
|
+
checks: readonly CheckRun[];
|
|
18
|
+
suites: readonly WorkflowSuiteSnapshot[];
|
|
19
|
+
owner: string;
|
|
20
|
+
name: string;
|
|
21
|
+
pr: number;
|
|
22
|
+
relevantEvents: readonly string[];
|
|
23
|
+
}): Promise<UnreportedRequiredFields>;
|
|
24
|
+
/**
|
|
25
|
+
* A fingerprint hit can keep a stale trunk `behindBy`. Refresh the trunk compare
|
|
26
|
+
* and the required-context diff without refetching the whole batch.
|
|
27
|
+
*/
|
|
28
|
+
export declare function refreshCachedUnreported(report: ShepherdReport, repo: RepoInfo): Promise<ShepherdReport>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { actionsWorkflowInProgress, reportedCheckNames, unreportedRequiredContexts, } from "../checks/unreported-required.mjs";
|
|
2
|
+
import { loadBaseBehindBy, loadMergeTargetStatus } from "../github/merge-target-rules.mjs";
|
|
3
|
+
/** Load trunk rules for a stack and diff them against the head's check names. */
|
|
4
|
+
export async function collectUnreportedRequired(input) {
|
|
5
|
+
const localContexts = input.batchData.branchRules?.requiredStatusCheckContexts ??
|
|
6
|
+
input.batchData.branchProtection?.requiredStatusCheckContexts ??
|
|
7
|
+
[];
|
|
8
|
+
const target = await loadMergeTargetStatus({
|
|
9
|
+
owner: input.owner,
|
|
10
|
+
name: input.name,
|
|
11
|
+
pr: input.pr,
|
|
12
|
+
baseRefName: input.batchData.baseRefName,
|
|
13
|
+
headRefName: input.batchData.headRefName,
|
|
14
|
+
localContexts,
|
|
15
|
+
stack: input.batchData.stack,
|
|
16
|
+
});
|
|
17
|
+
const unreported = unreportedRequiredContexts(target.contexts, reportedCheckNames(input.checks));
|
|
18
|
+
const actionsRunning = actionsWorkflowInProgress(input.suites, new Set(input.relevantEvents));
|
|
19
|
+
const baseBehindBy = unreported.length > 0 && !input.batchData.stack
|
|
20
|
+
? await loadBaseBehindBy(input.owner, input.name, input.batchData.baseRefName, input.batchData.headRefOid)
|
|
21
|
+
: 0;
|
|
22
|
+
return {
|
|
23
|
+
...(unreported.length > 0 && { unreportedRequiredChecks: unreported }),
|
|
24
|
+
...(target.trunkBehindBy !== undefined &&
|
|
25
|
+
target.trunkBehindBy > 0 && { trunkBehindBy: target.trunkBehindBy }),
|
|
26
|
+
...(baseBehindBy > 0 && { baseBehindBy }),
|
|
27
|
+
...(actionsRunning && { actionsWorkflowInProgress: true }),
|
|
28
|
+
...(target.stackBottomPr !== undefined && { stackBottomPr: target.stackBottomPr }),
|
|
29
|
+
headRefName: input.batchData.headRefName,
|
|
30
|
+
hasUnreportedRequired: unreported.length > 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A fingerprint hit can keep a stale trunk `behindBy`. Refresh the trunk compare
|
|
35
|
+
* and the required-context diff without refetching the whole batch.
|
|
36
|
+
*/
|
|
37
|
+
export async function refreshCachedUnreported(report, repo) {
|
|
38
|
+
const stack = report.mergeStatus?.mergeRequirements?.stack;
|
|
39
|
+
if (!report.headRefName)
|
|
40
|
+
return report;
|
|
41
|
+
if (!stack)
|
|
42
|
+
return refreshBaseBehind(report, repo);
|
|
43
|
+
const target = await loadMergeTargetStatus({
|
|
44
|
+
owner: repo.owner,
|
|
45
|
+
name: repo.name,
|
|
46
|
+
pr: report.pr,
|
|
47
|
+
baseRefName: report.baseBranch,
|
|
48
|
+
headRefName: report.headRefName,
|
|
49
|
+
localContexts: report.mergeStatus.mergeRequirements?.requiredStatusChecks?.contexts ?? [],
|
|
50
|
+
stack,
|
|
51
|
+
});
|
|
52
|
+
const unreported = unreportedRequiredContexts(target.contexts, reportedCheckNames(reportChecks(report)));
|
|
53
|
+
const next = { ...report };
|
|
54
|
+
if (unreported.length > 0)
|
|
55
|
+
next.unreportedRequiredChecks = unreported;
|
|
56
|
+
else
|
|
57
|
+
delete next.unreportedRequiredChecks;
|
|
58
|
+
if (target.trunkBehindBy !== undefined && target.trunkBehindBy > 0) {
|
|
59
|
+
next.trunkBehindBy = target.trunkBehindBy;
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
delete next.trunkBehindBy;
|
|
63
|
+
if (target.stackBottomPr !== undefined)
|
|
64
|
+
next.stackBottomPr = target.stackBottomPr;
|
|
65
|
+
if (unreported.length > 0 && next.status === "READY")
|
|
66
|
+
next.status = "PENDING";
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
/** A fingerprint hit can keep a stale base compare after main moves. */
|
|
70
|
+
async function refreshBaseBehind(report, repo) {
|
|
71
|
+
if ((report.unreportedRequiredChecks?.length ?? 0) === 0) {
|
|
72
|
+
if (report.baseBehindBy === undefined)
|
|
73
|
+
return report;
|
|
74
|
+
const cleared = { ...report };
|
|
75
|
+
delete cleared.baseBehindBy;
|
|
76
|
+
return cleared;
|
|
77
|
+
}
|
|
78
|
+
// A missing head OID must not fall back to the branch name. That name can
|
|
79
|
+
// resolve inside the base repository when the PR comes from a fork.
|
|
80
|
+
const headOid = report.headSha;
|
|
81
|
+
if (!headOid)
|
|
82
|
+
return report;
|
|
83
|
+
const behind = await loadBaseBehindBy(repo.owner, repo.name, report.baseBranch, headOid);
|
|
84
|
+
const next = { ...report };
|
|
85
|
+
if (behind > 0)
|
|
86
|
+
next.baseBehindBy = behind;
|
|
87
|
+
else
|
|
88
|
+
delete next.baseBehindBy;
|
|
89
|
+
return next;
|
|
90
|
+
}
|
|
91
|
+
function reportChecks(report) {
|
|
92
|
+
return [
|
|
93
|
+
...report.checks.passing,
|
|
94
|
+
...report.checks.failing,
|
|
95
|
+
...report.checks.inProgress,
|
|
96
|
+
...report.checks.skipped,
|
|
97
|
+
...report.checks.filtered,
|
|
98
|
+
...(report.checks.ignored ?? []),
|
|
99
|
+
...(report.checks.supersededNames ?? []).map((name) => ({ name })),
|
|
100
|
+
];
|
|
101
|
+
}
|
package/bin/commands/check.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { fetchPrBatch } from "../github/batch.mjs";
|
|
|
2
2
|
import { queueRemovalAppliesToHead } from "../github/queue-removal-freshness.mjs";
|
|
3
3
|
import { storePrFingerprint } from "../state/pr-fingerprint.mjs";
|
|
4
4
|
import { tryReuseFingerprintReport } from "./check-fingerprint.mjs";
|
|
5
|
+
import { collectUnreportedRequired, refreshCachedUnreported } from "./check-unreported.mjs";
|
|
5
6
|
import { getRepoInfo, getCurrentPrNumber } from "../github/client.mjs";
|
|
6
7
|
import { classifyChecks, getCiVerdict } from "../checks/classify.mjs";
|
|
7
8
|
import { mergeStartupFailureChecks } from "../checks/startup-failures.mjs";
|
|
@@ -17,7 +18,7 @@ import { loadSeenMap, markSeen, classifyItem } from "../state/seen-comments.mjs"
|
|
|
17
18
|
import { threadTranscriptBody } from "../threads/transcript.mjs";
|
|
18
19
|
import { classifyThreadVisibility } from "../comments/thread-visibility.mjs";
|
|
19
20
|
import { classifyReviewsForDisplay, classifyChangesRequestedReviewsForDisplay, } from "../comments/review-visibility.mjs";
|
|
20
|
-
import {
|
|
21
|
+
import { applySuppressedRuleAutoResolve } from "./rule-auto-resolve.mjs";
|
|
21
22
|
import { markReviewInlineThreadMarkers } from "../comments/review-thread-markers.mjs";
|
|
22
23
|
import { isConfiguredBotAuthor, isHumanAuthor, normalizeBotUsernames, } from "../comments/authors.mjs";
|
|
23
24
|
import { buildThreadMutationRouting, threadHasAuthorizedMutation, } from "./iterate/thread-mutation-routing.mjs";
|
|
@@ -37,7 +38,7 @@ export async function runCheck(opts) {
|
|
|
37
38
|
if (reuseFingerprint) {
|
|
38
39
|
const cached = await tryReuseFingerprintReport(prNumber, repo, stateKey, config);
|
|
39
40
|
if (cached)
|
|
40
|
-
return cached;
|
|
41
|
+
return refreshCachedUnreported(cached, repo);
|
|
41
42
|
}
|
|
42
43
|
const paginateApprovedReviews = config.iterate.minimizeApprovals;
|
|
43
44
|
const result = await fetchPrBatch(prNumber, repo, { paginateApprovedReviews });
|
|
@@ -67,8 +68,10 @@ export async function runCheck(opts) {
|
|
|
67
68
|
// it as stale/updated rather than as still current, so Shepherd doesn't escalate
|
|
68
69
|
// `merge-queue-removed` permanently on data it can no longer check. A squash or rebase
|
|
69
70
|
// queue commit has one parent and does not list the PR head; that removal stays current
|
|
70
|
-
// until
|
|
71
|
-
//
|
|
71
|
+
// until this head reached the PR after it. The push time is the earliest pull_request
|
|
72
|
+
// check on the head, with committer time as the fallback when no check time is
|
|
73
|
+
// available. The raw removal fields still render in the merge-queue header regardless
|
|
74
|
+
// of this flag.
|
|
72
75
|
const headCommittedAtUnix = batchData.activity?.latestCommitCommittedAtUnix;
|
|
73
76
|
const headUpdatedAfterRemoval = Boolean(latestRemoval &&
|
|
74
77
|
!queueRemovalAppliesToHead({
|
|
@@ -76,6 +79,9 @@ export async function runCheck(opts) {
|
|
|
76
79
|
headOid: batchData.headRefOid,
|
|
77
80
|
...(headCommittedAtUnix !== undefined &&
|
|
78
81
|
headCommittedAtUnix !== null && { headCommittedAtUnix }),
|
|
82
|
+
...(batchData.headPushedAtUnix !== undefined && {
|
|
83
|
+
headPushedAtUnix: batchData.headPushedAtUnix,
|
|
84
|
+
}),
|
|
79
85
|
removedAtUnix: latestRemoval.createdAtUnix,
|
|
80
86
|
}));
|
|
81
87
|
const queueRawChecks = batchData.isInMergeQueue
|
|
@@ -154,14 +160,23 @@ export async function runCheck(opts) {
|
|
|
154
160
|
visibleChangesRequestedIds.has(review.id));
|
|
155
161
|
}).length;
|
|
156
162
|
const approvedReviews = approvedReviewVisibility.visible;
|
|
157
|
-
|
|
163
|
+
const unreported = await collectUnreportedRequired({
|
|
164
|
+
batchData,
|
|
165
|
+
checks: allChecks,
|
|
166
|
+
suites: result.headWorkflowSuites ?? [],
|
|
167
|
+
owner: repo.owner,
|
|
168
|
+
name: repo.name,
|
|
169
|
+
pr: prNumber,
|
|
170
|
+
relevantEvents: config.checks.ciTriggerEvents,
|
|
171
|
+
});
|
|
172
|
+
let status = computeStatus(verdict, threadVisibility.activeThreads.length + threadVisibility.resolutionOnlyThreads.length, visibleCommentClassification.actionable.length, mergeStatus, changesRequestedReviewCount, unreported.hasUnreportedRequired);
|
|
158
173
|
// Resolve any pending mergeability refresh (and the resulting MERGED/CLOSED short-circuit)
|
|
159
174
|
// before deciding what to persist below — deferWhileQueued must see the same final,
|
|
160
175
|
// possibly-refreshed mergeStatus that commands/iterate/index.mts acts on, not the pre-refresh
|
|
161
176
|
// snapshot. A conflict newly discovered by this REST read is exactly the kind of check-driven
|
|
162
177
|
// signal that keeps a queued PR out of the deferral path.
|
|
163
178
|
if (status === "READY" && !didRefreshMergeability) {
|
|
164
|
-
const refreshed = await refreshReadyMergeability(prNumber, repo, batchData, verdict, threadVisibility.activeThreads.length + threadVisibility.resolutionOnlyThreads.length, visibleCommentClassification.actionable.length, changesRequestedReviewCount);
|
|
179
|
+
const refreshed = await refreshReadyMergeability(prNumber, repo, batchData, verdict, threadVisibility.activeThreads.length + threadVisibility.resolutionOnlyThreads.length, visibleCommentClassification.actionable.length, changesRequestedReviewCount, unreported.hasUnreportedRequired);
|
|
165
180
|
batchData = refreshed.batchData;
|
|
166
181
|
mergeStatus = refreshed.mergeStatus;
|
|
167
182
|
status = refreshed.status;
|
|
@@ -236,7 +251,13 @@ export async function runCheck(opts) {
|
|
|
236
251
|
ruleAutoResolveCommentIds: partition.ruleAutoResolveCommentIds.filter((id) => batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize === true),
|
|
237
252
|
ruleAutoResolveReviewSummaryIds: partition.ruleAutoResolveReviewSummaryIds.filter((id) => batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize === true),
|
|
238
253
|
};
|
|
239
|
-
const { threadIds: authorizedRuleAutoResolveThreadIds, commentIds: ruleAutoResolveCommentIds, reviewSummaryIds: ruleAutoResolveReviewSummaryIds, } = await
|
|
254
|
+
const { threadIds: authorizedRuleAutoResolveThreadIds, commentIds: ruleAutoResolveCommentIds, reviewSummaryIds: ruleAutoResolveReviewSummaryIds, autoResolved, autoMinimized, autoResolveErrors, errorReasons, } = await applySuppressedRuleAutoResolve({
|
|
255
|
+
enabled: opts.autoMinimizeSuppressed === true,
|
|
256
|
+
partition: authorizedPartition,
|
|
257
|
+
batch: batchData,
|
|
258
|
+
prNumber,
|
|
259
|
+
repo,
|
|
260
|
+
});
|
|
240
261
|
const visibleMutationThreadIds = new Set([...threadVisibility.activeThreads, ...threadVisibility.resolutionOnlyThreads].map((thread) => thread.id));
|
|
241
262
|
const ruleAutoResolveThreadIds = [
|
|
242
263
|
...authorizedRuleAutoResolveThreadIds,
|
|
@@ -254,6 +275,7 @@ export async function runCheck(opts) {
|
|
|
254
275
|
pr: prNumber,
|
|
255
276
|
nodeId: batchData.nodeId,
|
|
256
277
|
headSha: batchData.headRefOid,
|
|
278
|
+
headRefName: unreported.headRefName,
|
|
257
279
|
repo: `${repo.owner}/${repo.name}`,
|
|
258
280
|
...(batchData.viewerAuthorization && { viewerAuthorization: batchData.viewerAuthorization }),
|
|
259
281
|
status,
|
|
@@ -276,8 +298,9 @@ export async function runCheck(opts) {
|
|
|
276
298
|
threads: {
|
|
277
299
|
actionable: threadVisibility.activeThreads,
|
|
278
300
|
resolutionOnly: threadVisibility.resolutionOnlyThreads,
|
|
279
|
-
autoResolved
|
|
280
|
-
autoResolveErrors
|
|
301
|
+
autoResolved,
|
|
302
|
+
autoResolveErrors,
|
|
303
|
+
...(errorReasons.length > 0 && { autoResolveErrorReasons: errorReasons }),
|
|
281
304
|
firstLook: threadVisibility.firstLookThreads,
|
|
282
305
|
...(ruleAutoResolveThreadIds.length > 0
|
|
283
306
|
? { ruleAutoResolveIds: ruleAutoResolveThreadIds }
|
|
@@ -287,6 +310,7 @@ export async function runCheck(opts) {
|
|
|
287
310
|
actionable: visibleCommentClassification.actionable,
|
|
288
311
|
minimizeIds: [...visibleCommentClassification.minimizeIds, ...ruleAutoResolveCommentIds],
|
|
289
312
|
firstLook: firstLookComments,
|
|
313
|
+
...(autoMinimized.length > 0 && { autoMinimized }),
|
|
290
314
|
},
|
|
291
315
|
changesRequestedReviews,
|
|
292
316
|
reviewSummaries: seenSummaries,
|
|
@@ -297,6 +321,9 @@ export async function runCheck(opts) {
|
|
|
297
321
|
? { ruleAutoResolveReviewSummaryIds }
|
|
298
322
|
: undefined),
|
|
299
323
|
branchProtection: batchData.branchProtection,
|
|
324
|
+
...(batchData.allowedMergeMethods && {
|
|
325
|
+
allowedMergeMethods: batchData.allowedMergeMethods,
|
|
326
|
+
}),
|
|
300
327
|
activity: batchData.activity,
|
|
301
328
|
...(hasQueueState && {
|
|
302
329
|
mergeQueue: {
|
|
@@ -316,38 +343,18 @@ export async function runCheck(opts) {
|
|
|
316
343
|
...(headUpdatedAfterRemoval && { headUpdatedAfterRemoval: true }),
|
|
317
344
|
},
|
|
318
345
|
}),
|
|
346
|
+
...(unreported.unreportedRequiredChecks && {
|
|
347
|
+
unreportedRequiredChecks: unreported.unreportedRequiredChecks,
|
|
348
|
+
}),
|
|
349
|
+
...(unreported.trunkBehindBy !== undefined && { trunkBehindBy: unreported.trunkBehindBy }),
|
|
350
|
+
...(unreported.baseBehindBy !== undefined && { baseBehindBy: unreported.baseBehindBy }),
|
|
351
|
+
...(unreported.actionsWorkflowInProgress && {
|
|
352
|
+
actionsWorkflowInProgress: true,
|
|
353
|
+
}),
|
|
354
|
+
...(unreported.stackBottomPr !== undefined && { stackBottomPr: unreported.stackBottomPr }),
|
|
319
355
|
};
|
|
320
356
|
if (result.fingerprint) {
|
|
321
357
|
await storePrFingerprint(stateKey, result.fingerprint, report, config);
|
|
322
358
|
}
|
|
323
359
|
return report;
|
|
324
360
|
}
|
|
325
|
-
async function remainingRuleAutoResolveIds(partition, autoMinimizeSuppressed = false) {
|
|
326
|
-
const consumedIds = autoMinimizeSuppressed
|
|
327
|
-
? await selfApplySuppressedRuleAutoResolve(partition)
|
|
328
|
-
: { minimized: new Set(), resolvedThreads: new Set() };
|
|
329
|
-
return {
|
|
330
|
-
threadIds: partition.ruleAutoResolveThreadIds.filter((id) => !consumedIds.resolvedThreads.has(id)),
|
|
331
|
-
commentIds: partition.ruleAutoResolveCommentIds.filter((id) => !consumedIds.minimized.has(id)),
|
|
332
|
-
reviewSummaryIds: partition.ruleAutoResolveReviewSummaryIds.filter((id) => !consumedIds.minimized.has(id)),
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
async function selfApplySuppressedRuleAutoResolve(partition) {
|
|
336
|
-
const minimizeIds = [
|
|
337
|
-
...partition.ruleAutoResolveCommentIds.filter((id) => partition.suppressedCommentIds.has(id)),
|
|
338
|
-
...partition.ruleAutoResolveReviewSummaryIds.filter((id) => partition.suppressedReviewSummaryIds.has(id)),
|
|
339
|
-
];
|
|
340
|
-
const threadIds = partition.ruleAutoResolveThreadIds.filter((id) => partition.suppressedThreadIds.has(id));
|
|
341
|
-
const [minimized, resolved] = await Promise.all([
|
|
342
|
-
minimizeIds.length > 0
|
|
343
|
-
? autoMinimizeComments(minimizeIds)
|
|
344
|
-
: Promise.resolve({ minimized: [], errors: [] }),
|
|
345
|
-
threadIds.length > 0
|
|
346
|
-
? autoResolveThreads(threadIds)
|
|
347
|
-
: Promise.resolve({ resolved: [], errors: [] }),
|
|
348
|
-
]);
|
|
349
|
-
return {
|
|
350
|
-
minimized: new Set(minimized.minimized),
|
|
351
|
-
resolvedThreads: new Set(resolved.resolved),
|
|
352
|
-
};
|
|
353
|
-
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Currently emitted by iterate `fix_code` for suggestion review threads. The CLI keeps
|
|
4
4
|
* only the trigger and the concrete command; refusal/drift handling is invariant across
|
|
5
5
|
* every invocation, so it lives in the pr-shepherd skill's "Suggestion patches" playbook
|
|
6
|
-
* instead of being re-emitted every tick (see
|
|
6
|
+
* instead of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts
|
|
7
7
|
* minimal").
|
|
8
8
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
9
9
|
* e.g. `"## Review threads"`.
|
|
@@ -4,7 +4,7 @@ import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
|
4
4
|
* Currently emitted by iterate `fix_code` for suggestion review threads. The CLI keeps
|
|
5
5
|
* only the trigger and the concrete command; refusal/drift handling is invariant across
|
|
6
6
|
* every invocation, so it lives in the pr-shepherd skill's "Suggestion patches" playbook
|
|
7
|
-
* instead of being re-emitted every tick (see
|
|
7
|
+
* instead of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts
|
|
8
8
|
* minimal").
|
|
9
9
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
10
10
|
* e.g. `"## Review threads"`.
|