pr-shepherd 1.0.0 → 1.1.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/bin/checks/classify.d.mts +3 -1
- package/bin/checks/classify.mjs +8 -1
- package/bin/cli/poll-summary-formatter.mjs +3 -0
- package/bin/cli/rule-auto-resolve-lines.d.mts +3 -0
- package/bin/cli/rule-auto-resolve-lines.mjs +12 -0
- package/bin/cli/stack-layer-view.d.mts +2 -1
- package/bin/cli/stack-overview.mjs +3 -0
- package/bin/cli/triage-formatter.mjs +5 -1
- package/bin/commands/check-activity.d.mts +8 -0
- package/bin/commands/check-activity.mjs +26 -0
- package/bin/commands/check-annotations.mjs +2 -1
- package/bin/commands/check-status.d.mts +6 -1
- package/bin/commands/check-status.mjs +9 -4
- package/bin/commands/check-wait-detectors.mjs +11 -0
- package/bin/commands/check.mjs +10 -3
- package/bin/commands/iterate/escalate.mjs +4 -2
- package/bin/commands/poll-summary-explicit-instructions.d.mts +1 -1
- package/bin/commands/poll-summary-explicit-instructions.mjs +9 -7
- package/bin/commands/poll-summary-instructions.d.mts +2 -2
- package/bin/commands/poll-summary-instructions.mjs +16 -15
- package/bin/commands/poll-summary-signature.d.mts +6 -0
- package/bin/commands/poll-summary-signature.mjs +19 -2
- package/bin/commands/poll-summary.mjs +22 -14
- package/bin/commands/ready-delay.d.mts +1 -0
- package/bin/commands/ready-delay.mjs +8 -3
- 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 +7 -0
- package/bin/commands/rule-auto-resolve-format.mjs +11 -4
- package/bin/commands/rule-auto-resolve.d.mts +14 -2
- package/bin/commands/stack-drain.d.mts +1 -1
- package/bin/commands/stack-drain.mjs +3 -3
- package/bin/commands/stack-work.d.mts +4 -1
- package/bin/commands/stack-work.mjs +10 -2
- package/bin/commands/tick.mjs +1 -0
- package/bin/comments/thread-visibility.d.mts +6 -0
- package/bin/comments/thread-visibility.mjs +30 -15
- package/bin/github/batch-parse-suites.d.mts +14 -0
- package/bin/github/batch-parse-suites.mjs +4 -1
- package/bin/github/batch-parser-helpers.mjs +5 -0
- package/bin/github/batch-raw-types.d.mts +1 -0
- package/bin/github/check-annotation-cache.d.mts +5 -0
- package/bin/github/check-annotation-cache.mjs +6 -3
- package/bin/github/fingerprint.mjs +4 -0
- package/bin/github/github-fetch.d.mts +6 -2
- package/bin/github/github-fetch.mjs +37 -2
- package/bin/github/github-timeouts.d.mts +8 -0
- package/bin/github/github-timeouts.mjs +8 -0
- package/bin/github/gql/batch-pr-page.gql +1 -0
- package/bin/github/gql/batch-pr.gql +1 -0
- package/bin/github/gql/commit-check-contexts.gql +1 -0
- package/bin/github/gql/poll-summary-check-contexts.gql +1 -0
- package/bin/github/http-utils.mjs +3 -0
- package/bin/github/poll-summary-annotation-probe.d.mts +5 -0
- package/bin/github/poll-summary-annotation-probe.mjs +17 -3
- package/bin/github/poll-summary-annotations.d.mts +14 -0
- package/bin/github/poll-summary-annotations.mjs +75 -0
- package/bin/github/poll-summary-blocking-reviewer.d.mts +2 -0
- package/bin/github/poll-summary-blocking-reviewer.mjs +9 -0
- package/bin/github/poll-summary-checks.d.mts +1 -0
- package/bin/github/poll-summary-checks.mjs +21 -2
- package/bin/github/poll-summary-command.d.mts +7 -1
- package/bin/github/poll-summary-command.mjs +29 -0
- package/bin/github/poll-summary-fingerprint.d.mts +7 -0
- package/bin/github/poll-summary-fingerprint.mjs +7 -0
- package/bin/github/poll-summary-projector.d.mts +3 -1
- package/bin/github/poll-summary-projector.mjs +15 -25
- package/bin/github/poll-summary-queue-removal.d.mts +2 -0
- package/bin/github/poll-summary-queue-removal.mjs +11 -0
- package/bin/github/poll-summary-raw.d.mts +1 -0
- package/bin/github/poll-summary-readiness.mjs +3 -0
- package/bin/github/poll-summary-review-parity.d.mts +24 -0
- package/bin/github/poll-summary-review-parity.mjs +82 -0
- package/bin/github/poll-summary-review.d.mts +11 -2
- package/bin/github/poll-summary-review.mjs +87 -59
- package/bin/github/poll-summary-route.mjs +2 -0
- package/bin/github/poll-summary-row-error.d.mts +16 -0
- package/bin/github/poll-summary-row-error.mjs +74 -0
- package/bin/github/poll-summary-rule-auto-resolve.d.mts +32 -0
- package/bin/github/poll-summary-rule-auto-resolve.mjs +119 -0
- package/bin/github/poll-summary-rule-handoff.d.mts +7 -0
- package/bin/github/poll-summary-rule-handoff.mjs +20 -0
- package/bin/github/poll-summary-rule-items.d.mts +20 -0
- package/bin/github/poll-summary-rule-items.mjs +28 -0
- package/bin/github/poll-summary-unreported.d.mts +7 -0
- package/bin/github/poll-summary-unreported.mjs +16 -0
- package/bin/github/poll-summary.mjs +15 -7
- package/bin/github/rest-check-read.mjs +3 -1
- package/bin/github/rest-reader-core.mjs +13 -2
- package/bin/github/rest-summary-read.mjs +12 -1
- package/bin/github/rest-text.mjs +3 -2
- package/bin/github/stack-handoff-page.mjs +1 -0
- package/bin/reporters/agent.mjs +2 -0
- package/bin/types/github.d.mts +8 -0
- package/bin/types/poll-summary.d.mts +23 -0
- package/bin/types/report.d.mts +5 -1
- package/package.json +2 -2
- package/plugins/pr-shepherd/.claude-plugin/plugin.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/references/ci-failure-triage.md +1 -0
|
@@ -4,6 +4,7 @@ import { ShepherdError } from "../exit-codes.mjs";
|
|
|
4
4
|
import { getRepoInfo } from "../github/client.mjs";
|
|
5
5
|
import { getGithubTransport } from "../github/transport.mjs";
|
|
6
6
|
import { withApiTelemetryScope, summarizeApiTelemetry } from "../github/api-telemetry.mjs";
|
|
7
|
+
import { aggregateRerunCommand } from "../github/poll-summary-command.mjs";
|
|
7
8
|
import { fetchPollSummary } from "../github/poll-summary.mjs";
|
|
8
9
|
import { writeStackHandoffs } from "../github/stack-handoff.mjs";
|
|
9
10
|
import { sleep } from "../util/sleep.mjs";
|
|
@@ -19,7 +20,7 @@ export function runPollSummary(opts) {
|
|
|
19
20
|
return withApiTelemetryScope(async () => {
|
|
20
21
|
const tick = await readPollSummaryTick(opts);
|
|
21
22
|
await handOffRoutedLayers(tick);
|
|
22
|
-
return attachUsage(tick.result, opts
|
|
23
|
+
return attachUsage(tick.result, opts);
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
export function runAggregatePoll(opts) {
|
|
@@ -60,7 +61,7 @@ async function readPollSummaryTick(opts) {
|
|
|
60
61
|
prs: fetched.prs,
|
|
61
62
|
...(fetched.stackAncestry?.length && { stackAncestry: fetched.stackAncestry }),
|
|
62
63
|
...(fetched.allowedMergeMethods && { allowedMergeMethods: fetched.allowedMergeMethods }),
|
|
63
|
-
}, opts.merge === true);
|
|
64
|
+
}, opts.merge === true, aggregateRerunCommand(`${repo.owner}/${repo.name}`, fetched.selection, opts));
|
|
64
65
|
const result = await applyStackStallGuard(planned, repo, opts.stallTimeoutSeconds ?? loadConfig().iterate["stall-timeout-minutes"] * 60);
|
|
65
66
|
return { result, repo, ...(fetched.handoffSource && { handoffSource: fetched.handoffSource }) };
|
|
66
67
|
}
|
|
@@ -82,6 +83,7 @@ async function runAggregatePollCore(opts) {
|
|
|
82
83
|
try {
|
|
83
84
|
lastTick = await readPollSummaryTick(opts);
|
|
84
85
|
last = lastTick.result;
|
|
86
|
+
writeAggregateRuleAutoResolve(last);
|
|
85
87
|
rateLimitRetry.reset();
|
|
86
88
|
}
|
|
87
89
|
catch (error) {
|
|
@@ -103,7 +105,7 @@ async function runAggregatePollCore(opts) {
|
|
|
103
105
|
stackMergeable: true,
|
|
104
106
|
instructions: ["1. Stop — every stack layer is merged."],
|
|
105
107
|
...(pendingQuotaWarning && { quotaWarning: pendingQuotaWarning }),
|
|
106
|
-
}, opts
|
|
108
|
+
}, opts);
|
|
107
109
|
}
|
|
108
110
|
return attachUsage({
|
|
109
111
|
...explicit,
|
|
@@ -115,7 +117,7 @@ async function runAggregatePollCore(opts) {
|
|
|
115
117
|
`1. Native stack #${last.selection.stackNumber} disappeared before every tracked layer merged. Stop and ask the stack owner to reconcile the remaining PRs; do not claim the stack is complete.`,
|
|
116
118
|
],
|
|
117
119
|
...(pendingQuotaWarning && { quotaWarning: pendingQuotaWarning }),
|
|
118
|
-
}, opts
|
|
120
|
+
}, opts);
|
|
119
121
|
}
|
|
120
122
|
const early = await rateLimitRetry.wait(error, {
|
|
121
123
|
untilTerminal: opts.untilTerminal === true,
|
|
@@ -126,7 +128,7 @@ async function runAggregatePollCore(opts) {
|
|
|
126
128
|
onAllTerminal: (pulls) => aggregateCancelFromPulls(last, opts.targetRepository, pulls),
|
|
127
129
|
});
|
|
128
130
|
if (early) {
|
|
129
|
-
return presentProbedCancel(pendingQuotaWarning ? { ...early, quotaWarning: pendingQuotaWarning } : early, opts
|
|
131
|
+
return presentProbedCancel(pendingQuotaWarning ? { ...early, quotaWarning: pendingQuotaWarning } : early, opts);
|
|
130
132
|
}
|
|
131
133
|
continue;
|
|
132
134
|
}
|
|
@@ -147,14 +149,14 @@ async function runAggregatePollCore(opts) {
|
|
|
147
149
|
...last,
|
|
148
150
|
reason: "all_terminal",
|
|
149
151
|
...(pendingQuotaWarning && { quotaWarning: pendingQuotaWarning }),
|
|
150
|
-
}, opts
|
|
152
|
+
}, opts);
|
|
151
153
|
}
|
|
152
154
|
if (immediate) {
|
|
153
155
|
return attachUsage({
|
|
154
156
|
...last,
|
|
155
157
|
reason: "actionable",
|
|
156
158
|
...(pendingQuotaWarning && { quotaWarning: pendingQuotaWarning }),
|
|
157
|
-
}, opts
|
|
159
|
+
}, opts);
|
|
158
160
|
}
|
|
159
161
|
if (hasFix) {
|
|
160
162
|
if (debounceMs === 0 || Date.now() >= (debounceUntil ??= Date.now() + debounceMs)) {
|
|
@@ -163,13 +165,13 @@ async function runAggregatePollCore(opts) {
|
|
|
163
165
|
...last,
|
|
164
166
|
reason: "actionable",
|
|
165
167
|
...(pendingQuotaWarning && { quotaWarning: pendingQuotaWarning }),
|
|
166
|
-
}, opts
|
|
168
|
+
}, opts);
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
171
|
else {
|
|
170
172
|
debounceUntil = null;
|
|
171
173
|
if (opts.untilTerminal && pendingQuotaWarning) {
|
|
172
|
-
return attachUsage({ ...last, quotaWarning: pendingQuotaWarning }, opts
|
|
174
|
+
return attachUsage({ ...last, quotaWarning: pendingQuotaWarning }, opts);
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
const elapsedMs = Date.now() - start;
|
|
@@ -179,7 +181,7 @@ async function runAggregatePollCore(opts) {
|
|
|
179
181
|
if (!opts.untilTerminal && debounceUntil === null) {
|
|
180
182
|
const remainingMs = timeoutMs - elapsedMs;
|
|
181
183
|
if (remainingMs <= 0 || remainingMs + TIMER_DRIFT_TOLERANCE_MS < sleepMs) {
|
|
182
|
-
return attachUsage({ ...last, reason: "timeout" }, opts
|
|
184
|
+
return attachUsage({ ...last, reason: "timeout" }, opts);
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
const statusSignature = summaryStatusSignature(last);
|
|
@@ -192,15 +194,21 @@ async function runAggregatePollCore(opts) {
|
|
|
192
194
|
await sleep(sleepMs);
|
|
193
195
|
}
|
|
194
196
|
}
|
|
195
|
-
|
|
197
|
+
/** An auto-resolve is a one-tick event; forward it so a swallowed tick still records it. */
|
|
198
|
+
function writeAggregateRuleAutoResolve(result) {
|
|
199
|
+
for (const item of result.prs)
|
|
200
|
+
if (item.ruleAutoResolve?.summary)
|
|
201
|
+
process.stderr.write(`#${item.pr} ${item.ruleAutoResolve.summary}\n`);
|
|
202
|
+
}
|
|
203
|
+
function presentProbedCancel(result, opts) {
|
|
196
204
|
// The stack planner already cancels an all-merged stack and escalates a
|
|
197
205
|
// closed-unmerged layer. Do not rewrite that into a successful stop.
|
|
198
|
-
return attachUsage({ ...result, reason: "all_terminal" },
|
|
206
|
+
return attachUsage({ ...result, reason: "all_terminal" }, opts);
|
|
199
207
|
}
|
|
200
208
|
function isMissingStack(error) {
|
|
201
209
|
return (error instanceof ShepherdError && error.message.includes("not part of a native GitHub stack"));
|
|
202
210
|
}
|
|
203
|
-
function attachUsage(result,
|
|
211
|
+
function attachUsage(result, opts) {
|
|
204
212
|
const apiUsage = summarizeApiTelemetry();
|
|
205
213
|
const enriched = apiUsage ? { ...result, apiUsage } : result;
|
|
206
214
|
// A disappeared native stack is an aggregate-level terminal handoff. Keep
|
|
@@ -209,5 +217,5 @@ function attachUsage(result, mergeRequested) {
|
|
|
209
217
|
if (enriched.selection.kind === "stack" && enriched.nextAction === "escalate") {
|
|
210
218
|
return enriched;
|
|
211
219
|
}
|
|
212
|
-
return withPollSummaryInstructions(enriched,
|
|
220
|
+
return withPollSummaryInstructions(enriched, opts.merge === true, aggregateRerunCommand(enriched.repo, enriched.selection, opts));
|
|
213
221
|
}
|
|
@@ -30,6 +30,7 @@ interface ReadyDelayState {
|
|
|
30
30
|
export declare function updateReadyDelay(prNumber: number, isReady: boolean, readyDelaySeconds: number, owner: string, repo: string, options: {
|
|
31
31
|
headSha: string;
|
|
32
32
|
alreadyElapsed?: boolean;
|
|
33
|
+
summary?: boolean;
|
|
33
34
|
}): Promise<ReadyDelayState>;
|
|
34
35
|
/** Delete the ready-delay marker once an elapsed delay has been consumed. */
|
|
35
36
|
export declare function clearReadyDelay(prNumber: number, owner: string, repo: string): Promise<void>;
|
|
@@ -21,7 +21,7 @@ import { resolvePrStatePath } from "../state/base.mjs";
|
|
|
21
21
|
* the loop and tells one-shot agents to stop.
|
|
22
22
|
*/
|
|
23
23
|
export async function updateReadyDelay(prNumber, isReady, readyDelaySeconds, owner, repo, options) {
|
|
24
|
-
const markerPath = readySincePath(prNumber, owner, repo);
|
|
24
|
+
const markerPath = readySincePath(prNumber, owner, repo, options.summary);
|
|
25
25
|
if (!isReady) {
|
|
26
26
|
// Reset the timer.
|
|
27
27
|
await safeUnlink(markerPath);
|
|
@@ -75,8 +75,13 @@ export async function readyDelayElapsed(prNumber, owner, repo, readyDelaySeconds
|
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
76
|
// Helpers
|
|
77
77
|
// ---------------------------------------------------------------------------
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
/**
|
|
79
|
+
* The one-PR session owns `ready-since.txt`. The aggregate summary's coarser
|
|
80
|
+
* view keeps its own `ready-since-summary.txt` so it can never reset or start
|
|
81
|
+
* the authoritative timer.
|
|
82
|
+
*/
|
|
83
|
+
function readySincePath(pr, owner, repo, summary = false) {
|
|
84
|
+
return resolvePrStatePath({ owner, repo, pr }, summary ? "ready-since-summary.txt" : "ready-since.txt");
|
|
80
85
|
}
|
|
81
86
|
/** The marker's start time, or null when it is missing, malformed, or bound to another head. */
|
|
82
87
|
async function readReadySince(path, headSha) {
|
|
@@ -10,6 +10,6 @@ export declare function refreshUnknownMergeability(prNumber: number, repo: RepoI
|
|
|
10
10
|
batchData: BatchPrData;
|
|
11
11
|
didRefresh: boolean;
|
|
12
12
|
}>;
|
|
13
|
-
export declare function refreshReadyMergeability(prNumber: number, repo: RepoInfo, batchData: BatchPrData, verdict: CiVerdict, unresolvedThreads: number, unresolvedComments: number, changesRequestedCount: number,
|
|
13
|
+
export declare function refreshReadyMergeability(prNumber: number, repo: RepoInfo, batchData: BatchPrData, verdict: CiVerdict, unresolvedThreads: number, unresolvedComments: number, changesRequestedCount: number, awaitingChecks?: boolean): Promise<ReadyMergeabilityRefresh>;
|
|
14
14
|
export declare function isBlockedByFilteredCheck(mergeStatus: MergeStatusResult, verdict: CiVerdict): boolean;
|
|
15
15
|
export {};
|
|
@@ -8,10 +8,10 @@ export async function refreshUnknownMergeability(prNumber, repo, batchData) {
|
|
|
8
8
|
}
|
|
9
9
|
return { batchData: await readMergeability(prNumber, repo, batchData), didRefresh: true };
|
|
10
10
|
}
|
|
11
|
-
export async function refreshReadyMergeability(prNumber, repo, batchData, verdict, unresolvedThreads, unresolvedComments, changesRequestedCount,
|
|
11
|
+
export async function refreshReadyMergeability(prNumber, repo, batchData, verdict, unresolvedThreads, unresolvedComments, changesRequestedCount, awaitingChecks = false) {
|
|
12
12
|
const refreshedBatchData = await readMergeability(prNumber, repo, batchData);
|
|
13
13
|
const mergeStatus = deriveMergeStatus(refreshedBatchData);
|
|
14
|
-
const status = computeStatus(verdict, unresolvedThreads, unresolvedComments, mergeStatus, changesRequestedCount,
|
|
14
|
+
const status = computeStatus(verdict, unresolvedThreads, unresolvedComments, mergeStatus, changesRequestedCount, awaitingChecks);
|
|
15
15
|
return { batchData: refreshedBatchData, mergeStatus, status };
|
|
16
16
|
}
|
|
17
17
|
export function isBlockedByFilteredCheck(mergeStatus, verdict) {
|
|
@@ -3,6 +3,13 @@ export declare function uniqueReasons(reasons: readonly string[]): string[];
|
|
|
3
3
|
export declare function decorateAutoResolveError(error: string, reasons: readonly string[]): string;
|
|
4
4
|
export declare function reasonsForError(error: string, ids: readonly string[], ruleReasons: ReadonlyMap<string, readonly string[]>): string[];
|
|
5
5
|
export declare function ruleAutoResolveFromReport(report: ShepherdReport): RuleAutoResolveReport | undefined;
|
|
6
|
+
/** The one report shape iterate and the aggregate summary both surface. */
|
|
7
|
+
export declare function buildRuleAutoResolveReport(input: {
|
|
8
|
+
threads: AutoResolvedThread[];
|
|
9
|
+
minimized: AutoMinimizedItem[];
|
|
10
|
+
errors: string[];
|
|
11
|
+
errorReasons: string[];
|
|
12
|
+
}): RuleAutoResolveReport | undefined;
|
|
6
13
|
export declare function projectRuleAutoResolve(event: RuleAutoResolveReport): Record<string, unknown>;
|
|
7
14
|
export declare function insertRuleAutoResolveSection(text: string, event: RuleAutoResolveReport | undefined): string;
|
|
8
15
|
export declare function stripReplayedRuleAutoResolve(report: ShepherdReport): ShepherdReport;
|
|
@@ -57,9 +57,16 @@ function splitJoined(reason) {
|
|
|
57
57
|
return reason.split("; ").filter(Boolean);
|
|
58
58
|
}
|
|
59
59
|
export function ruleAutoResolveFromReport(report) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
return buildRuleAutoResolveReport({
|
|
61
|
+
threads: report.threads.autoResolved,
|
|
62
|
+
minimized: report.comments.autoMinimized ?? [],
|
|
63
|
+
errors: report.threads.autoResolveErrors,
|
|
64
|
+
errorReasons: report.threads.autoResolveErrorReasons ?? [],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** The one report shape iterate and the aggregate summary both surface. */
|
|
68
|
+
export function buildRuleAutoResolveReport(input) {
|
|
69
|
+
const { threads, minimized, errors } = input;
|
|
63
70
|
if (threads.length === 0 && minimized.length === 0 && errors.length === 0)
|
|
64
71
|
return undefined;
|
|
65
72
|
const comments = minimized.filter((item) => item.kind === "pr-comment").length;
|
|
@@ -70,7 +77,7 @@ export function ruleAutoResolveFromReport(report) {
|
|
|
70
77
|
...threads.flatMap((thread) => splitJoined(thread.ruleReason)),
|
|
71
78
|
...minimized.flatMap((item) => splitJoined(item.ruleReason)),
|
|
72
79
|
])
|
|
73
|
-
: uniqueReasons(
|
|
80
|
+
: uniqueReasons(input.errorReasons);
|
|
74
81
|
const body = ruleAutoResolveBody({ threads: threads.length, comments, reviewSummaries }) ??
|
|
75
82
|
`auto-resolve failed for ${plural(errors.length, "mutation")}`;
|
|
76
83
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BatchPartition } from "../classify/apply.mts";
|
|
2
|
-
import type { AutoMinimizedItem, AutoResolvedThread,
|
|
2
|
+
import type { AutoMinimizedItem, AutoResolvedThread, ReviewThread } from "../types.mts";
|
|
3
3
|
export interface RuleAutoResolveApplied {
|
|
4
4
|
threadIds: string[];
|
|
5
5
|
commentIds: string[];
|
|
@@ -9,8 +9,20 @@ export interface RuleAutoResolveApplied {
|
|
|
9
9
|
autoResolveErrors: string[];
|
|
10
10
|
errorReasons: string[];
|
|
11
11
|
}
|
|
12
|
+
interface MinimizableItem {
|
|
13
|
+
id: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
viewerCanMinimize?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** The part of a PR read the mutations need. A full batch and a compact summary both satisfy it. */
|
|
18
|
+
export interface RuleAutoResolveBatch {
|
|
19
|
+
comments: readonly MinimizableItem[];
|
|
20
|
+
reviewSummaries: readonly MinimizableItem[];
|
|
21
|
+
reviewThreads: readonly ReviewThread[];
|
|
22
|
+
}
|
|
12
23
|
export declare function applySuppressedRuleAutoResolve(input: {
|
|
13
24
|
enabled: boolean;
|
|
14
25
|
partition: BatchPartition;
|
|
15
|
-
batch:
|
|
26
|
+
batch: RuleAutoResolveBatch;
|
|
16
27
|
}): Promise<RuleAutoResolveApplied>;
|
|
28
|
+
export {};
|
|
@@ -8,7 +8,7 @@ export interface StackPlan {
|
|
|
8
8
|
idle?: PollSummaryItem[];
|
|
9
9
|
instructions: string[];
|
|
10
10
|
}
|
|
11
|
-
export declare function planPrefixDrain(result: PollSummaryResult, mergeRequested: boolean): StackPlan | undefined;
|
|
11
|
+
export declare function planPrefixDrain(result: PollSummaryResult, mergeRequested: boolean, rerunCommand?: string): StackPlan | undefined;
|
|
12
12
|
/**
|
|
13
13
|
* A merge-requested, fully ready stack whose bottom open layer still targets
|
|
14
14
|
* the merged layer below it until GitHub retargets it onto the stack base.
|
|
@@ -2,7 +2,7 @@ import { findHumanHandoffs, appendHumanHandoffInstructions } from "./stack-hando
|
|
|
2
2
|
export { findHumanHandoffs, appendHumanHandoffInstructions } from "./stack-handoffs.mjs";
|
|
3
3
|
import { stackMergeFlag } from "./stack-merge-flag.mjs";
|
|
4
4
|
import { stackLayerBlockReason } from "./stack-layer-readiness.mjs";
|
|
5
|
-
import { splitStackWork } from "./stack-work.mjs";
|
|
5
|
+
import { splitStackWork, stackRerunTarget } from "./stack-work.mjs";
|
|
6
6
|
import { getGithubTransport } from "../github/transport.mjs";
|
|
7
7
|
import { buildMergeCommandPlan, renderMergeCommand } from "./iterate/merge.mjs";
|
|
8
8
|
function readyPrefixTop(result) {
|
|
@@ -28,7 +28,7 @@ function readyPrefixTop(result) {
|
|
|
28
28
|
}
|
|
29
29
|
return top;
|
|
30
30
|
}
|
|
31
|
-
export function planPrefixDrain(result, mergeRequested) {
|
|
31
|
+
export function planPrefixDrain(result, mergeRequested, rerunCommand) {
|
|
32
32
|
if (!mergeRequested)
|
|
33
33
|
return undefined;
|
|
34
34
|
const top = readyPrefixTop(result);
|
|
@@ -92,7 +92,7 @@ export function planPrefixDrain(result, mergeRequested) {
|
|
|
92
92
|
const handoffs = findHumanHandoffs(result);
|
|
93
93
|
if (handoffs)
|
|
94
94
|
appendHumanHandoffInstructions(instructions, handoffs, false);
|
|
95
|
-
instructions.push(`${instructions.length + 1}. After the merge attempt, rerun this same
|
|
95
|
+
instructions.push(`${instructions.length + 1}. After the merge attempt, rerun ${rerunCommand ? stackRerunTarget(rerunCommand) : "this same `stack --merge` command"}; GitHub retargets the next layer onto \`${top.stack.baseRefName}\`. Shepherd any layer that GitHub rejects or ejects.`);
|
|
96
96
|
return {
|
|
97
97
|
action: "merge",
|
|
98
98
|
stackMergeable: gaps.length === 0 && open.every(isStackLayerReady),
|
|
@@ -17,8 +17,11 @@ export declare function onlyDraftReadinessVerification(sessions: PollSummaryItem
|
|
|
17
17
|
* A stale-ancestry child keeps its session, which returns the ancestry repair.
|
|
18
18
|
*/
|
|
19
19
|
export declare function splitStackWork(candidates: PollSummaryItem[], staleChildren: ReadonlySet<number>): StackWork;
|
|
20
|
+
/** The literal stack command to rerun, or the generic name when the caller's command is unknown. */
|
|
21
|
+
export declare function stackRerunTarget(command?: string): string;
|
|
20
22
|
/**
|
|
21
23
|
* The closing step after routed sessions: rerun the stack command, or stop when nothing is owned.
|
|
22
24
|
* Returns the plan action: a stop is a handoff, so no rerun, quota cadence, or wake-up follows it.
|
|
25
|
+
* `keepWatching` is false when a human handoff may end the run before every layer is terminal.
|
|
23
26
|
*/
|
|
24
|
-
export declare function appendStackRerunInstruction(instructions: string[], layers: PollSummaryItem[], rerun: string): "shepherd" | "escalate";
|
|
27
|
+
export declare function appendStackRerunInstruction(instructions: string[], layers: PollSummaryItem[], rerun: string, keepWatching?: boolean): "shepherd" | "escalate";
|
|
@@ -35,12 +35,20 @@ function isProbed(item) {
|
|
|
35
35
|
function onlyUnownedWork(layers) {
|
|
36
36
|
return layers.length > 0 && layers.every((item) => !item.owned);
|
|
37
37
|
}
|
|
38
|
+
/** The turn must outlive every non-terminal layer; a one-PR session ending does not end the stack. */
|
|
39
|
+
const STACK_KEEP_WATCHING = "Keep watching until every layer is terminal.";
|
|
40
|
+
/** The literal stack command to rerun, or the generic name when the caller's command is unknown. */
|
|
41
|
+
export function stackRerunTarget(command) {
|
|
42
|
+
return command ? `\`${command}\`` : "this same `stack` command";
|
|
43
|
+
}
|
|
38
44
|
/**
|
|
39
45
|
* The closing step after routed sessions: rerun the stack command, or stop when nothing is owned.
|
|
40
46
|
* Returns the plan action: a stop is a handoff, so no rerun, quota cadence, or wake-up follows it.
|
|
47
|
+
* `keepWatching` is false when a human handoff may end the run before every layer is terminal.
|
|
41
48
|
*/
|
|
42
|
-
export function appendStackRerunInstruction(instructions, layers, rerun) {
|
|
49
|
+
export function appendStackRerunInstruction(instructions, layers, rerun, keepWatching = true) {
|
|
43
50
|
const stop = onlyUnownedWork(layers);
|
|
44
|
-
|
|
51
|
+
const step = keepWatching ? `${rerun} ${STACK_KEEP_WATCHING}` : rerun;
|
|
52
|
+
instructions.push(`${instructions.length + 1}. ${stop ? "No owned layer needs a session. Report this overview and stop." : step}`);
|
|
45
53
|
return stop ? "escalate" : "shepherd";
|
|
46
54
|
}
|
package/bin/commands/tick.mjs
CHANGED
|
@@ -86,6 +86,7 @@ async function withAggregateNextCheck(result, opts) {
|
|
|
86
86
|
function withoutPollingCadence(step) {
|
|
87
87
|
return step
|
|
88
88
|
.replace(/ Recheck(?: them)? at the configured polling cadence\./g, "")
|
|
89
|
+
.replace(/Do not end the turn while any selected row is non-terminal: /g, "Keep watching every non-terminal selected row through the wake-up below: ")
|
|
89
90
|
.replace("rerun that command at the configured cadence to resume its UUID", "rerun that command after the wake-up below to resume its UUID");
|
|
90
91
|
}
|
|
91
92
|
function aggregateNextCheck(result, stallDeadlineSeconds) {
|
|
@@ -8,5 +8,11 @@ interface ThreadVisibility {
|
|
|
8
8
|
firstLookThreads: FirstLookThread[];
|
|
9
9
|
toMarkSeen: ReviewThread[];
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Whether an unresolved thread with a known outdated state keeps returning every tick once seen,
|
|
13
|
+
* given an authorized mutation. The compact summary calls this so its count cannot drift from
|
|
14
|
+
* `classifyThreadVisibility`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function unresolvedThreadRepeats(thread: ReviewThread, ended: boolean, botUsernames: NormalizedBotUsernames, policy: ResolveOtherHumanThreads): boolean;
|
|
11
17
|
export declare function classifyThreadVisibility(threads: ReviewThread[], seenMap: Map<string, SeenMarker>, botUsernames?: NormalizedBotUsernames, repeatableThreadIds?: ReadonlySet<string>, resolveOtherHumanThreads?: ResolveOtherHumanThreads): ThreadVisibility;
|
|
12
18
|
export {};
|
|
@@ -3,6 +3,9 @@ import { threadTranscriptBody } from "../threads/transcript.mjs";
|
|
|
3
3
|
import { isConfiguredBotAuthor, isHumanAuthor, isViewerAuthoredHuman, } from "./authors.mjs";
|
|
4
4
|
import { threadEndedByShepherd } from "./marker.mjs";
|
|
5
5
|
import { shouldResolveOtherHumanThread } from "./thread-resolve-policy.mjs";
|
|
6
|
+
function isOrdinaryHuman(thread, botUsernames) {
|
|
7
|
+
return isHumanAuthor(thread) && !isConfiguredBotAuthor(thread, botUsernames);
|
|
8
|
+
}
|
|
6
9
|
function withEdited(thread, edited) {
|
|
7
10
|
return edited ? { ...thread, edited: true } : thread;
|
|
8
11
|
}
|
|
@@ -20,9 +23,33 @@ function classifyFirstLookThread(thread, seenMap, firstLookStatus) {
|
|
|
20
23
|
return null;
|
|
21
24
|
return { ...visible, firstLookStatus };
|
|
22
25
|
}
|
|
26
|
+
/** An unresolved, current thread whose author class makes it repeat while a mutation is authorized. */
|
|
27
|
+
function repeatsAsActive(thread, ended, botUsernames, policy) {
|
|
28
|
+
if (thread.isOutdated !== false || thread.isMinimized === true || ended)
|
|
29
|
+
return false;
|
|
30
|
+
return (isConfiguredBotAuthor(thread, botUsernames) ||
|
|
31
|
+
isViewerAuthoredHuman(thread, botUsernames) ||
|
|
32
|
+
(isOrdinaryHuman(thread, botUsernames) && policy === "always"));
|
|
33
|
+
}
|
|
34
|
+
/** An unresolved thread that is resolution-only work: outdated, minimized or ended by Shepherd. */
|
|
35
|
+
function isResolutionOnlyCandidate(thread, ended, botUsernames, policy) {
|
|
36
|
+
if (!ended)
|
|
37
|
+
return thread.isOutdated === true || thread.isMinimized === true;
|
|
38
|
+
if (!isOrdinaryHuman(thread, botUsernames))
|
|
39
|
+
return true;
|
|
40
|
+
return (isViewerAuthoredHuman(thread, botUsernames) || shouldResolveOtherHumanThread(thread, policy));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether an unresolved thread with a known outdated state keeps returning every tick once seen,
|
|
44
|
+
* given an authorized mutation. The compact summary calls this so its count cannot drift from
|
|
45
|
+
* `classifyThreadVisibility`.
|
|
46
|
+
*/
|
|
47
|
+
export function unresolvedThreadRepeats(thread, ended, botUsernames, policy) {
|
|
48
|
+
return (repeatsAsActive(thread, ended, botUsernames, policy) ||
|
|
49
|
+
isResolutionOnlyCandidate(thread, ended, botUsernames, policy));
|
|
50
|
+
}
|
|
23
51
|
export function classifyThreadVisibility(threads, seenMap, botUsernames = new Set(), repeatableThreadIds, resolveOtherHumanThreads = "none") {
|
|
24
52
|
const shouldRepeat = (thread) => repeatableThreadIds?.has(thread.id) ?? true;
|
|
25
|
-
const isOrdinaryHuman = (thread) => isHumanAuthor(thread) && !isConfiguredBotAuthor(thread, botUsernames);
|
|
26
53
|
const unknownStatusThreads = threads.filter((thread) => thread.isResolved === undefined || thread.isOutdated === undefined);
|
|
27
54
|
const unresolvedThreads = threads.filter((thread) => thread.isResolved === false && thread.isOutdated !== undefined);
|
|
28
55
|
const activeThreads = unresolvedThreads
|
|
@@ -30,25 +57,13 @@ export function classifyThreadVisibility(threads, seenMap, botUsernames = new Se
|
|
|
30
57
|
.flatMap((t) => {
|
|
31
58
|
if (threadEndedByShepherd(t))
|
|
32
59
|
return [];
|
|
33
|
-
|
|
34
|
-
isViewerAuthoredHuman(t, botUsernames) ||
|
|
35
|
-
(isOrdinaryHuman(t) && resolveOtherHumanThreads === "always");
|
|
36
|
-
if (repeatAuthor && shouldRepeat(t))
|
|
60
|
+
if (repeatsAsActive(t, false, botUsernames, resolveOtherHumanThreads) && shouldRepeat(t))
|
|
37
61
|
return [t];
|
|
38
62
|
const visible = classifyVisibleThread(t, seenMap);
|
|
39
63
|
return visible ? [visible] : [];
|
|
40
64
|
});
|
|
41
65
|
const resolutionOnlyThreads = unresolvedThreads
|
|
42
|
-
.filter((t) =>
|
|
43
|
-
const endedByShepherd = threadEndedByShepherd(t);
|
|
44
|
-
if (endedByShepherd) {
|
|
45
|
-
if (!isOrdinaryHuman(t))
|
|
46
|
-
return true;
|
|
47
|
-
return (isViewerAuthoredHuman(t, botUsernames) ||
|
|
48
|
-
shouldResolveOtherHumanThread(t, resolveOtherHumanThreads));
|
|
49
|
-
}
|
|
50
|
-
return t.isOutdated || t.isMinimized;
|
|
51
|
-
})
|
|
66
|
+
.filter((t) => isResolutionOnlyCandidate(t, threadEndedByShepherd(t), botUsernames, resolveOtherHumanThreads))
|
|
52
67
|
.flatMap((t) => {
|
|
53
68
|
const visible = classifyVisibleThread(t, seenMap);
|
|
54
69
|
if (visible)
|
|
@@ -5,6 +5,20 @@ export declare function parseCheckSuitesComplete(raw: RawPr): boolean;
|
|
|
5
5
|
/** True only when the head commit's suite page finished and listed nothing. */
|
|
6
6
|
export declare function parseHeadCheckSuitesEmpty(raw: RawPr): boolean;
|
|
7
7
|
export declare function parseSuiteStartupFailures(raw: RawPr): CheckRun[];
|
|
8
|
+
/** One failing check per STARTUP_FAILURE workflow-run suite; shared by the one-PR and summary reads. */
|
|
9
|
+
export declare function startupFailureChecksFromSuites(suites: {
|
|
10
|
+
nodes: Array<{
|
|
11
|
+
conclusion: string | null;
|
|
12
|
+
workflowRun: {
|
|
13
|
+
databaseId?: number | null;
|
|
14
|
+
event: string | null;
|
|
15
|
+
url?: string | null;
|
|
16
|
+
workflow?: {
|
|
17
|
+
name: string | null;
|
|
18
|
+
} | null;
|
|
19
|
+
} | null;
|
|
20
|
+
}>;
|
|
21
|
+
} | null | undefined): CheckRun[];
|
|
8
22
|
/** `headWorkflowSuites`, omitted when the head has none. */
|
|
9
23
|
export declare function workflowSuites(raw: RawPr): {
|
|
10
24
|
headWorkflowSuites?: WorkflowSuiteSnapshot[];
|
|
@@ -22,7 +22,10 @@ function parseHeadWorkflowSuites(raw) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
export function parseSuiteStartupFailures(raw) {
|
|
25
|
-
|
|
25
|
+
return startupFailureChecksFromSuites(raw.commits.nodes[0]?.commit.checkSuites);
|
|
26
|
+
}
|
|
27
|
+
/** One failing check per STARTUP_FAILURE workflow-run suite; shared by the one-PR and summary reads. */
|
|
28
|
+
export function startupFailureChecksFromSuites(suites) {
|
|
26
29
|
if (!suites)
|
|
27
30
|
return [];
|
|
28
31
|
return suites.nodes.flatMap((node) => {
|
|
@@ -28,6 +28,7 @@ export function mapCheckRunNode(node) {
|
|
|
28
28
|
const summary = extractCheckRunSummary(node.title, node.summary);
|
|
29
29
|
const rawCreatedAt = node.checkSuite?.workflowRun?.createdAt ?? node.checkSuite?.createdAt;
|
|
30
30
|
const rawUpdatedAt = node.checkSuite?.workflowRun?.updatedAt ?? node.checkSuite?.updatedAt;
|
|
31
|
+
const suiteStatus = node.checkSuite?.status || undefined;
|
|
31
32
|
const createdAtUnix = rawCreatedAt ? parseCreatedAt(rawCreatedAt) : undefined;
|
|
32
33
|
const startedAtUnix = node.startedAt ? parseCreatedAt(node.startedAt) : undefined;
|
|
33
34
|
const completedAtUnix = node.completedAt ? parseCreatedAt(node.completedAt) : undefined;
|
|
@@ -43,11 +44,15 @@ export function mapCheckRunNode(node) {
|
|
|
43
44
|
runId,
|
|
44
45
|
...(workflowName !== undefined && { workflowName }),
|
|
45
46
|
...(workflowId !== undefined && { workflowId }),
|
|
47
|
+
...(suiteStatus !== undefined && { suiteStatus }),
|
|
46
48
|
...(createdAtUnix !== undefined && { createdAtUnix }),
|
|
47
49
|
...(startedAtUnix !== undefined && { startedAtUnix }),
|
|
48
50
|
...(completedAtUnix !== undefined && { completedAtUnix }),
|
|
49
51
|
...(updatedAtUnix !== undefined && { updatedAtUnix }),
|
|
50
52
|
...(summary !== undefined && { summary }),
|
|
53
|
+
...((node.annotations?.totalCount ?? 0) > 0 && {
|
|
54
|
+
annotationCount: node.annotations.totalCount,
|
|
55
|
+
}),
|
|
51
56
|
...(((node.annotations?.totalCount ?? 0) > 0 || (node.annotations?.nodes.length ?? 0) > 0) && {
|
|
52
57
|
hasAnnotations: true,
|
|
53
58
|
}),
|
|
@@ -3,6 +3,11 @@ import type { CheckAnnotation } from "../types.mts";
|
|
|
3
3
|
export interface AnnotationCacheOptions {
|
|
4
4
|
stateKey: StateKey;
|
|
5
5
|
headSha?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Annotation total GitHub reports per check run right now. A cached list whose length
|
|
8
|
+
* differs is stale (a publisher added annotations to a completed run) and is not reused.
|
|
9
|
+
*/
|
|
10
|
+
expectedCounts?: ReadonlyMap<string, number>;
|
|
6
11
|
}
|
|
7
12
|
export declare function readFreshCheckAnnotations(checkRunId: string, cacheOpts: AnnotationCacheOptions | undefined): Promise<CheckAnnotation[] | undefined>;
|
|
8
13
|
export declare function storeCheckAnnotations(checkRunId: string, annotations: CheckAnnotation[], cacheOpts: AnnotationCacheOptions | undefined): Promise<void>;
|
|
@@ -12,9 +12,12 @@ export async function readFreshCheckAnnotations(checkRunId, cacheOpts) {
|
|
|
12
12
|
if (!cacheOpts)
|
|
13
13
|
return undefined;
|
|
14
14
|
const cached = await loadDerived(cacheOpts.stateKey, cacheName(checkRunId));
|
|
15
|
-
if (cached
|
|
16
|
-
return
|
|
17
|
-
|
|
15
|
+
if (!cached || Date.now() - cached.storedAt >= ANNOTATION_CACHE_MAX_AGE_MS)
|
|
16
|
+
return undefined;
|
|
17
|
+
const expected = cacheOpts.expectedCounts?.get(checkRunId);
|
|
18
|
+
if (expected !== undefined && cached.value.length !== expected)
|
|
19
|
+
return undefined;
|
|
20
|
+
return cached.value;
|
|
18
21
|
}
|
|
19
22
|
export async function storeCheckAnnotations(checkRunId, annotations, cacheOpts) {
|
|
20
23
|
if (!cacheOpts)
|
|
@@ -12,6 +12,10 @@ export function fingerprintFromRaw(raw, viewerPermission = null, viewerLogin = n
|
|
|
12
12
|
isMergeQueueEnabled: Boolean(raw.isMergeQueueEnabled),
|
|
13
13
|
mergePolicy: mergePolicyFingerprint(raw),
|
|
14
14
|
commentCount: raw.comments.totalCount ?? raw.comments.nodes.length,
|
|
15
|
+
// Raw `updatedAt` on purpose, even for bot or rule-suppressed comments (#635 gap 7). A miss
|
|
16
|
+
// continues the same BatchPr request into the full snapshot, so an edited bot comment costs
|
|
17
|
+
// no extra request over a hit, while excluding it would need rule evaluation here and would
|
|
18
|
+
// risk hiding an edit that un-suppresses the comment or edits a minimized human comment.
|
|
15
19
|
commentRevisions: commentRevisions(raw.comments.nodes),
|
|
16
20
|
threadCount: raw.reviewThreads.totalCount ?? raw.reviewThreads.nodes.length,
|
|
17
21
|
reviewCount: raw.allReviews?.totalCount ?? 0,
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Attach proxy routing to GitHub requests without changing the host's dispatcher.
|
|
3
|
+
* The request, including reading its body, is bounded by `timeoutMs`; a stall
|
|
4
|
+
* rejects with an ETIMEDOUT error that the transport retry treats as transient.
|
|
5
|
+
*/
|
|
6
|
+
export declare function githubFetch(input: string | URL, init?: RequestInit, timeoutMs?: number): Promise<Response>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { createCapturedProxyAgent } from "./captured-proxy-agent.mjs";
|
|
3
|
+
import { GITHUB_API_TIMEOUT_MS } from "./github-timeouts.mjs";
|
|
4
|
+
import { redactUrl } from "./http-utils.mjs";
|
|
3
5
|
import { loadUndiciKeepingLegacyDispatcher } from "./legacy-dispatcher.mjs";
|
|
4
6
|
const require = createRequire(import.meta.url);
|
|
5
7
|
let proxyAgent;
|
|
@@ -9,8 +11,41 @@ function loadUndici() {
|
|
|
9
11
|
undiciModule ??= loadUndiciKeepingLegacyDispatcher(() => require("undici"));
|
|
10
12
|
return undiciModule;
|
|
11
13
|
}
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Attach proxy routing to GitHub requests without changing the host's dispatcher.
|
|
16
|
+
* The request, including reading its body, is bounded by `timeoutMs`; a stall
|
|
17
|
+
* rejects with an ETIMEDOUT error that the transport retry treats as transient.
|
|
18
|
+
*/
|
|
19
|
+
export async function githubFetch(input, init, timeoutMs = GITHUB_API_TIMEOUT_MS) {
|
|
20
|
+
const deadline = AbortSignal.timeout(timeoutMs);
|
|
21
|
+
const signal = init?.signal ? AbortSignal.any([init.signal, deadline]) : deadline;
|
|
22
|
+
try {
|
|
23
|
+
return await bufferBody(await routedFetch(input, { ...init, signal }));
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (deadline.aborted && !init?.signal?.aborted) {
|
|
27
|
+
const method = init?.method ?? "GET";
|
|
28
|
+
throw Object.assign(new Error(`GitHub request timed out after ${timeoutMs / 1000}s: ${method} ${redactUrl(String(input))}`, { cause: err }), { code: "ETIMEDOUT" });
|
|
29
|
+
}
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Read a streamed body inside the deadline and the callers' transport retry, so a
|
|
35
|
+
* body that stalls after the headers is retried like a stalled request. Callers
|
|
36
|
+
* read every body in full anyway.
|
|
37
|
+
*/
|
|
38
|
+
async function bufferBody(response) {
|
|
39
|
+
if (typeof response.body?.getReader !== "function")
|
|
40
|
+
return response;
|
|
41
|
+
const bytes = await response.arrayBuffer();
|
|
42
|
+
return new Response(bytes.byteLength > 0 ? bytes : null, {
|
|
43
|
+
status: response.status,
|
|
44
|
+
statusText: response.statusText,
|
|
45
|
+
headers: response.headers,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function routedFetch(input, init) {
|
|
14
49
|
const httpProxy = process.env["http_proxy"] ?? process.env["HTTP_PROXY"] ?? "";
|
|
15
50
|
const httpsProxy = process.env["https_proxy"] ?? process.env["HTTPS_PROXY"] ?? "";
|
|
16
51
|
if (!httpProxy && !httpsProxy)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deadline for one GitHub REST or GraphQL request, headers and body included.
|
|
3
|
+
* Without it a half-open connection waits for undici's 300s headers/body
|
|
4
|
+
* defaults (10s for connect) and the tick prints nothing meanwhile.
|
|
5
|
+
*/
|
|
6
|
+
export declare const GITHUB_API_TIMEOUT_MS = 60000;
|
|
7
|
+
/** Deadline for Actions log downloads, which can be several megabytes. */
|
|
8
|
+
export declare const GITHUB_LOG_TIMEOUT_MS = 180000;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deadline for one GitHub REST or GraphQL request, headers and body included.
|
|
3
|
+
* Without it a half-open connection waits for undici's 300s headers/body
|
|
4
|
+
* defaults (10s for connect) and the tick prints nothing meanwhile.
|
|
5
|
+
*/
|
|
6
|
+
export const GITHUB_API_TIMEOUT_MS = 60_000;
|
|
7
|
+
/** Deadline for Actions log downloads, which can be several megabytes. */
|
|
8
|
+
export const GITHUB_LOG_TIMEOUT_MS = 180_000;
|