pr-shepherd 0.60.1 → 1.1.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/README.md +7 -5
- package/bin/api.d.mts +35 -24
- package/bin/api.mjs +68 -48
- package/bin/checks/classify.d.mts +1 -1
- package/bin/checks/classify.mjs +4 -4
- package/bin/checks/log-excerpt.mjs +1 -1
- package/bin/cli/arg-flags.mjs +0 -3
- package/bin/cli/clean-handler.d.mts +1 -1
- package/bin/cli/clean-handler.mjs +5 -15
- package/bin/cli/default-poll.d.mts +2 -0
- package/bin/cli/default-poll.mjs +9 -6
- package/bin/cli/formatters.d.mts +2 -3
- package/bin/cli/formatters.mjs +0 -26
- package/bin/cli/handlers.d.mts +3 -3
- package/bin/cli/handlers.mjs +45 -35
- package/bin/cli/help-command-pages.d.mts +13 -158
- package/bin/cli/help-command-pages.mjs +12 -157
- package/bin/cli/help-iterate-poll-pages.d.mts +4 -3
- package/bin/cli/help-iterate-poll-pages.mjs +76 -36
- package/bin/cli/help-log-file-page.d.mts +1 -1
- package/bin/cli/help-log-file-page.mjs +3 -5
- package/bin/cli/help-top-page.d.mts +1 -1
- package/bin/cli/help-top-page.mjs +19 -20
- package/bin/cli/help.d.mts +14 -159
- package/bin/cli/iterate-checks-formatter.mjs +1 -1
- package/bin/cli/iterate-flags.d.mts +2 -4
- package/bin/cli/iterate-flags.mjs +4 -20
- package/bin/cli/iterate-formatter.d.mts +1 -1
- package/bin/cli/iterate-formatter.mjs +6 -6
- package/bin/cli/iterate-instructions.d.mts +1 -1
- package/bin/cli/iterate-instructions.mjs +5 -6
- package/bin/cli/iterate-lean.mjs +32 -40
- package/bin/cli/iterate-verbose.mjs +1 -1
- package/bin/cli/journal-formatter.mjs +0 -3
- package/bin/cli/journal-handler.d.mts +1 -1
- package/bin/cli/journal-handler.mjs +2 -1
- package/bin/cli/mutate-formatter.mjs +0 -9
- package/bin/cli/next-check-format.d.mts +0 -4
- package/bin/cli/next-check-format.mjs +0 -4
- package/bin/cli/poll-handler.d.mts +3 -0
- package/bin/cli/poll-handler.mjs +88 -47
- package/bin/cli/poll-summary-emitter.mjs +2 -2
- package/bin/cli/poll-summary-formatter.mjs +5 -2
- package/bin/cli/poll-targets.d.mts +2 -2
- package/bin/cli/poll-targets.mjs +15 -35
- package/bin/cli/removed-flags.d.mts +3 -0
- package/bin/cli/removed-flags.mjs +24 -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/runner.d.mts +1 -1
- package/bin/cli/runner.mjs +2 -2
- package/bin/cli/stack-layer-view.d.mts +2 -1
- package/bin/cli/stack-overview.d.mts +0 -1
- package/bin/cli/stack-overview.mjs +4 -2
- package/bin/cli/{fix-formatter-extra.d.mts → triage-formatter-extra.d.mts} +1 -2
- package/bin/cli/{fix-formatter-extra.mjs → triage-formatter-extra.mjs} +0 -6
- package/bin/cli/triage-formatter.d.mts +4 -0
- package/bin/cli/{fix-formatter.mjs → triage-formatter.mjs} +31 -42
- package/bin/cli/unknown-flags.d.mts +6 -0
- package/bin/cli/unknown-flags.mjs +29 -0
- package/bin/cli-parser.mjs +41 -62
- package/bin/commands/check-activity.d.mts +8 -0
- package/bin/commands/check-activity.mjs +26 -0
- package/bin/commands/check-annotations.d.mts +1 -1
- package/bin/commands/check-annotations.mjs +3 -2
- package/bin/commands/check-review-budget.d.mts +1 -1
- package/bin/commands/check-review-budget.mjs +1 -1
- package/bin/commands/check-wait-detectors.d.mts +1 -1
- package/bin/commands/check-wait-detectors.mjs +12 -1
- package/bin/commands/check.d.mts +1 -1
- package/bin/commands/check.mjs +13 -8
- package/bin/commands/commit-suggestion-instruction.d.mts +2 -2
- package/bin/commands/commit-suggestion-instruction.mjs +3 -3
- package/bin/commands/iterate/api-usage.mjs +8 -8
- package/bin/commands/iterate/check-instructions.d.mts +4 -4
- package/bin/commands/iterate/check-instructions.mjs +8 -8
- package/bin/commands/iterate/classify.mjs +2 -2
- package/bin/commands/iterate/escalate.mjs +6 -6
- package/bin/commands/iterate/index.mjs +20 -23
- package/bin/commands/iterate/merge-plan.mjs +3 -3
- package/bin/commands/iterate/merge-state.mjs +4 -7
- package/bin/commands/iterate/native-stack-rebase.mjs +1 -1
- package/bin/commands/iterate/render.d.mts +1 -1
- package/bin/commands/iterate/render.mjs +2 -2
- package/bin/commands/iterate/stall.mjs +11 -9
- package/bin/commands/iterate/{fix-loop-instruction.mjs → triage-loop-instruction.mjs} +7 -3
- package/bin/commands/iterate/{fix-code.d.mts → triage.d.mts} +2 -2
- package/bin/commands/iterate/{fix-code.mjs → triage.mjs} +14 -23
- package/bin/commands/iterate/unreported-required.mjs +2 -5
- package/bin/commands/journal/index.d.mts +1 -3
- package/bin/commands/journal/index.mjs +1 -1
- package/bin/commands/mark-files-as-viewed.d.mts +1 -3
- package/bin/commands/mark-files-as-viewed.mjs +1 -1
- package/bin/commands/next-check.d.mts +2 -2
- package/bin/commands/next-check.mjs +4 -2
- package/bin/commands/poll-progress.mjs +1 -1
- package/bin/commands/poll-summary-explicit-instructions.mjs +1 -1
- package/bin/commands/poll-summary-instructions.mjs +7 -7
- 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 +11 -4
- package/bin/commands/poll.d.mts +1 -1
- package/bin/commands/poll.mjs +4 -4
- package/bin/commands/ready-delay.d.mts +1 -0
- package/bin/commands/ready-delay.mjs +8 -3
- package/bin/commands/resolve-mutate.d.mts +1 -1
- package/bin/commands/resolve-mutate.mjs +1 -1
- package/bin/commands/rest-stack-merge-readiness.mjs +1 -1
- 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/single-tick.d.mts +5 -0
- package/bin/commands/single-tick.mjs +7 -0
- package/bin/commands/stack-drain.mjs +1 -1
- package/bin/commands/stack-merge-flag.mjs +1 -1
- package/bin/commands/stack-stall.d.mts +2 -2
- package/bin/commands/stack-stall.mjs +2 -2
- package/bin/commands/stack-work.d.mts +2 -2
- package/bin/commands/stack-work.mjs +2 -2
- package/bin/commands/suggestion-patch-item.d.mts +0 -1
- package/bin/commands/suggestion-patch-item.mjs +0 -12
- package/bin/commands/suggestion-patches.d.mts +2 -2
- package/bin/commands/suggestion-patches.mjs +1 -1
- package/bin/commands/tick-instructions.d.mts +9 -0
- package/bin/commands/tick-instructions.mjs +23 -0
- package/bin/commands/tick.d.mts +15 -0
- package/bin/commands/{event-mode.mjs → tick.mjs} +39 -55
- package/bin/comments/minimize-policy.mjs +1 -3
- package/bin/comments/resolve.d.mts +0 -23
- package/bin/comments/resolve.mjs +0 -4
- package/bin/comments/review-output-budget.d.mts +1 -1
- package/bin/comments/review-output-budget.mjs +1 -1
- package/bin/comments/sha-poll.mjs +3 -1
- package/bin/comments/thread-visibility.d.mts +6 -0
- package/bin/comments/thread-visibility.mjs +30 -15
- package/bin/config/default-bot-usernames.d.mts +2 -0
- package/bin/config/default-bot-usernames.mjs +15 -0
- package/bin/config/load.d.mts +4 -138
- package/bin/config/load.mjs +88 -443
- package/bin/config/merge-command-args.mjs +2 -2
- package/bin/config/merge-method.d.mts +1 -2
- package/bin/config/merge-method.mjs +1 -8
- package/bin/config/renames.d.mts +8 -0
- package/bin/config/renames.mjs +87 -0
- package/bin/config/resolve.d.mts +31 -0
- package/bin/config/resolve.mjs +89 -0
- package/bin/config/schema.d.mts +161 -0
- package/bin/config/schema.mjs +168 -0
- package/bin/exit-codes.d.mts +1 -1
- package/bin/exit-codes.mjs +3 -3
- 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 +3 -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/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-check-blockers.mjs +1 -1
- package/bin/github/poll-summary-checks.d.mts +1 -0
- package/bin/github/poll-summary-checks.mjs +20 -2
- package/bin/github/poll-summary-command.mjs +8 -6
- 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 +14 -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-readiness.mjs +1 -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 +88 -60
- package/bin/github/poll-summary-route.mjs +9 -9
- 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.mjs +1 -1
- package/bin/github/poll-summary.mjs +16 -8
- package/bin/github/queries.d.mts +1 -1
- package/bin/github/queries.mjs +1 -1
- package/bin/github/rest-reader-core.mjs +13 -2
- package/bin/github/rest-summary-read.mjs +12 -1
- package/bin/mcp/server.mjs +48 -54
- package/bin/merge-status/derive.mjs +1 -1
- package/bin/state/durable-state.d.mts +1 -3
- package/bin/state/durable-state.mjs +2 -8
- package/bin/state/fix-attempts.d.mts +2 -2
- package/bin/state/fix-attempts.mjs +1 -1
- package/bin/state/merge-request.mjs +1 -1
- package/bin/state/pr-fingerprint.mjs +4 -11
- package/bin/state/seen-comments.d.mts +4 -5
- package/bin/state/seen-comments.mjs +8 -14
- package/bin/state/stack-stall.d.mts +1 -1
- package/bin/state/stack-stall.mjs +1 -1
- package/bin/state/stall-state-store.d.mts +1 -1
- package/bin/state/stall-state-store.mjs +1 -1
- package/bin/types/escalate.d.mts +2 -2
- package/bin/types/github.d.mts +2 -0
- package/bin/types/iterate.d.mts +12 -26
- package/bin/types/merge-queue.d.mts +2 -2
- package/bin/types/next-check.d.mts +1 -1
- package/bin/types/poll-summary.d.mts +26 -6
- package/bin/types/report.d.mts +2 -2
- package/bin/types/suggestion-patch.d.mts +1 -8
- package/package.json +3 -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/SKILL.md +6 -6
- package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/references/{cloud-event-loop.md → event-loop.md} +5 -5
- package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +3 -3
- package/plugins/pr-shepherd/skills/pr-shepherd/references/merge-queue-ejection.md +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/references/suggestion-patches.md +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/references/{fix-code-loop.md → triage-loop.md} +3 -3
- package/plugins/pr-shepherd/skills/reduce-pr-noise/SKILL.md +1 -1
- package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -2
- package/plugins/pr-shepherd/skills/reduce-pr-noise/references/settings.md +11 -11
- package/pr-shepherd.schema.json +361 -0
- package/bin/cli/event-state-scope.d.mts +0 -19
- package/bin/cli/event-state-scope.mjs +0 -60
- package/bin/cli/fix-formatter.d.mts +0 -4
- package/bin/commands/commit-suggestion.d.mts +0 -8
- package/bin/commands/commit-suggestion.mjs +0 -26
- package/bin/commands/event-instructions.d.mts +0 -11
- package/bin/commands/event-instructions.mjs +0 -27
- package/bin/commands/event-mode.d.mts +0 -14
- package/bin/commands/poll-mode.d.mts +0 -8
- package/bin/commands/poll-mode.mjs +0 -14
- package/bin/config.json +0 -68
- package/bin/types/protected-run.d.mts +0 -6
- package/bin/types/protected-run.mjs +0 -1
- /package/bin/commands/iterate/{fix-loop-instruction.d.mts → triage-loop-instruction.d.mts} +0 -0
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision-relevant state only. Raw review totals and the ignored, skipped, filtered, and
|
|
3
|
+
* superseded check counts move when rules suppress a bot notice or a bot reposts a status
|
|
4
|
+
* check, which would reprint quiet polls and keep an idle stack from reaching `stall-timeout`.
|
|
5
|
+
* Post-rule `actionable` and the check transitions that drive routing stay in.
|
|
6
|
+
*/
|
|
1
7
|
export function summaryStatusSignature(result) {
|
|
2
8
|
return JSON.stringify({
|
|
3
9
|
nextAction: result.nextAction,
|
|
@@ -9,8 +15,19 @@ export function summaryStatusSignature(result) {
|
|
|
9
15
|
mergeable: item.mergeable,
|
|
10
16
|
mergeStateStatus: item.mergeStateStatus,
|
|
11
17
|
reviewDecision: item.reviewDecision,
|
|
12
|
-
checks: item.checks
|
|
13
|
-
|
|
18
|
+
checks: item.checks && {
|
|
19
|
+
passing: item.checks.passing,
|
|
20
|
+
failing: item.checks.failing,
|
|
21
|
+
inProgress: item.checks.inProgress,
|
|
22
|
+
incomplete: item.checks.incomplete,
|
|
23
|
+
annotated: item.checks.annotated,
|
|
24
|
+
unreportedRequired: item.checks.unreportedRequired,
|
|
25
|
+
actionsWorkflowInProgress: item.checks.actionsWorkflowInProgress,
|
|
26
|
+
},
|
|
27
|
+
review: item.review && {
|
|
28
|
+
actionable: item.review.actionable,
|
|
29
|
+
incomplete: item.review.incomplete,
|
|
30
|
+
},
|
|
14
31
|
})),
|
|
15
32
|
});
|
|
16
33
|
}
|
|
@@ -61,14 +61,14 @@ async function readPollSummaryTick(opts) {
|
|
|
61
61
|
...(fetched.stackAncestry?.length && { stackAncestry: fetched.stackAncestry }),
|
|
62
62
|
...(fetched.allowedMergeMethods && { allowedMergeMethods: fetched.allowedMergeMethods }),
|
|
63
63
|
}, opts.merge === true);
|
|
64
|
-
const result = await applyStackStallGuard(planned, repo, opts.stallTimeoutSeconds ?? loadConfig().iterate
|
|
64
|
+
const result = await applyStackStallGuard(planned, repo, opts.stallTimeoutSeconds ?? loadConfig().iterate["stall-timeout-minutes"] * 60);
|
|
65
65
|
return { result, repo, ...(fetched.handoffSource && { handoffSource: fetched.handoffSource }) };
|
|
66
66
|
}
|
|
67
67
|
async function runAggregatePollCore(opts) {
|
|
68
68
|
const intervalMs = Math.min(opts.intervalSeconds * 1000, MAX_TIMER_MS);
|
|
69
69
|
const timeoutMs = Math.min(opts.timeoutSeconds * 1000, MAX_TIMER_MS);
|
|
70
70
|
const debounceMs = Math.min((opts.debounceSeconds ?? 60) * 1000, MAX_TIMER_MS);
|
|
71
|
-
const quotaBands = loadConfig().
|
|
71
|
+
const quotaBands = loadConfig().quotaWarningBands;
|
|
72
72
|
const start = Date.now();
|
|
73
73
|
let tick = 0;
|
|
74
74
|
let debounceUntil = null;
|
|
@@ -82,6 +82,7 @@ async function runAggregatePollCore(opts) {
|
|
|
82
82
|
try {
|
|
83
83
|
lastTick = await readPollSummaryTick(opts);
|
|
84
84
|
last = lastTick.result;
|
|
85
|
+
writeAggregateRuleAutoResolve(last);
|
|
85
86
|
rateLimitRetry.reset();
|
|
86
87
|
}
|
|
87
88
|
catch (error) {
|
|
@@ -139,7 +140,7 @@ async function runAggregatePollCore(opts) {
|
|
|
139
140
|
: last.prs.some((item) => ["escalate", "merge", "mark_ready"].includes(item.action));
|
|
140
141
|
const hasFix = last.selection.kind === "stack"
|
|
141
142
|
? last.nextAction === "shepherd" && last.reason !== "waiting"
|
|
142
|
-
: last.prs.some((item) => item.action === "
|
|
143
|
+
: last.prs.some((item) => item.action === "triage");
|
|
143
144
|
const warning = await aggregateQuotaWarning(last, quotaBands, opts.intervalSeconds);
|
|
144
145
|
pendingQuotaWarning = carryQuotaWarning(pendingQuotaWarning, warning);
|
|
145
146
|
if (allTerminal) {
|
|
@@ -192,6 +193,12 @@ async function runAggregatePollCore(opts) {
|
|
|
192
193
|
await sleep(sleepMs);
|
|
193
194
|
}
|
|
194
195
|
}
|
|
196
|
+
/** An auto-resolve is a one-tick event; forward it so a swallowed tick still records it. */
|
|
197
|
+
function writeAggregateRuleAutoResolve(result) {
|
|
198
|
+
for (const item of result.prs)
|
|
199
|
+
if (item.ruleAutoResolve?.summary)
|
|
200
|
+
process.stderr.write(`#${item.pr} ${item.ruleAutoResolve.summary}\n`);
|
|
201
|
+
}
|
|
195
202
|
function presentProbedCancel(result, merge) {
|
|
196
203
|
// The stack planner already cancels an all-merged stack and escalates a
|
|
197
204
|
// closed-unmerged layer. Do not rewrite that into a successful stop.
|
|
@@ -205,7 +212,7 @@ function attachUsage(result, mergeRequested) {
|
|
|
205
212
|
const enriched = apiUsage ? { ...result, apiUsage } : result;
|
|
206
213
|
// A disappeared native stack is an aggregate-level terminal handoff. Keep
|
|
207
214
|
// that explicit escalation intact even when the one-PR fallback contains
|
|
208
|
-
// open orphan rows that would otherwise be re-planned as
|
|
215
|
+
// open orphan rows that would otherwise be re-planned as triage.
|
|
209
216
|
if (enriched.selection.kind === "stack" && enriched.nextAction === "escalate") {
|
|
210
217
|
return enriched;
|
|
211
218
|
}
|
package/bin/commands/poll.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import type { IterateCommandOptions, IterateResult } from "../types.mts";
|
|
|
2
2
|
export interface PollCommandOptions extends IterateCommandOptions {
|
|
3
3
|
intervalSeconds: number;
|
|
4
4
|
timeoutSeconds: number;
|
|
5
|
-
/** Settle window after first
|
|
5
|
+
/** Settle window after first TRIAGE before returning. Default 60. 0 disables. */
|
|
6
6
|
debounceSeconds?: number;
|
|
7
7
|
quietStatus?: boolean;
|
|
8
8
|
untilTerminal?: boolean;
|
package/bin/commands/poll.mjs
CHANGED
|
@@ -21,7 +21,7 @@ async function runPollCore(opts) {
|
|
|
21
21
|
const timeoutMs = Math.min(timeoutSeconds * 1000, MAX_TIMER_MS);
|
|
22
22
|
const debounceSeconds = debounceSecondsOpt ?? DEFAULT_POLL_DEBOUNCE_SECONDS;
|
|
23
23
|
const debounceMs = Math.min(debounceSeconds * 1000, MAX_TIMER_MS);
|
|
24
|
-
const quotaBands = loadConfig().
|
|
24
|
+
const quotaBands = loadConfig().quotaWarningBands;
|
|
25
25
|
const start = Date.now();
|
|
26
26
|
let tick = 0;
|
|
27
27
|
let lastResult;
|
|
@@ -82,10 +82,10 @@ async function runPollCore(opts) {
|
|
|
82
82
|
};
|
|
83
83
|
if (untilTerminal &&
|
|
84
84
|
pendingQuotaWarning !== undefined &&
|
|
85
|
-
!(lastResult.action === "
|
|
85
|
+
!(lastResult.action === "triage" && debounceSeconds > 0 && !pastDebounce) &&
|
|
86
86
|
!(debounceUntil !== null && !pastDebounce)) {
|
|
87
87
|
await refreshIfReturning();
|
|
88
|
-
if (lastResult.action === "
|
|
88
|
+
if (lastResult.action === "triage" && debounceSeconds > 0 && !pastDebounce) {
|
|
89
89
|
debounceUntil ??= Date.now() + debounceMs;
|
|
90
90
|
const remainingMs = Math.max(debounceUntil - Date.now(), 0);
|
|
91
91
|
writeRuleAutoResolveProgress(lastResult);
|
|
@@ -142,7 +142,7 @@ async function runPollCore(opts) {
|
|
|
142
142
|
await sleep(sleepMs);
|
|
143
143
|
continue;
|
|
144
144
|
}
|
|
145
|
-
if (lastResult.action === "
|
|
145
|
+
if (lastResult.action === "triage" && debounceSeconds > 0 && !pastDebounce) {
|
|
146
146
|
writeRuleAutoResolveProgress(lastResult);
|
|
147
147
|
debounceUntil ??= Date.now() + debounceMs;
|
|
148
148
|
const remainingMs = debounceUntil - Date.now();
|
|
@@ -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) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ResolveOptions } from "../types.mts";
|
|
2
2
|
import type { ResolveCommandOptions } from "./resolve.mts";
|
|
3
|
-
/**
|
|
3
|
+
/** Implementation for `apply review`. */
|
|
4
4
|
export declare function runResolveMutate(opts: ResolveCommandOptions & ResolveOptions): Promise<import("../comments/resolve.mts").ResolveResult>;
|
|
@@ -7,7 +7,7 @@ import { threadTranscriptBodies } from "../threads/transcript.mjs";
|
|
|
7
7
|
import { addPrShepherdMarker } from "../comments/marker.mjs";
|
|
8
8
|
import { adoptedReplyThreads } from "../comments/uncertain-replies.mjs";
|
|
9
9
|
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
10
|
-
/**
|
|
10
|
+
/** Implementation for `apply review`. */
|
|
11
11
|
export async function runResolveMutate(opts) {
|
|
12
12
|
const repo = opts.targetRepository ?? (await getRepoInfo());
|
|
13
13
|
const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
|
|
@@ -70,5 +70,5 @@ function guardMember(member) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
function fail(message) {
|
|
73
|
-
throw new ShepherdError(`${message}; rerun
|
|
73
|
+
throw new ShepherdError(`${message}; rerun \`pr-shepherd stack <PR> --merge\` before applying this request`, EXIT.UNAVAILABLE);
|
|
74
74
|
}
|
|
@@ -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 {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when a looping command (the default command, `stack`) runs one tick instead of
|
|
3
|
+
* sleeping: inside a Claude Code cloud session, where the session owns the next wake-up.
|
|
4
|
+
*/
|
|
5
|
+
export function runsSingleTick() {
|
|
6
|
+
return process.env["CLAUDE_CODE_REMOTE"] === "true";
|
|
7
|
+
}
|
|
@@ -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 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),
|
|
@@ -2,5 +2,5 @@ import { loadConfig } from "../config/load.mjs";
|
|
|
2
2
|
import { configuredMergeMethod, mergeMethodFlag, } from "../config/merge-method.mjs";
|
|
3
3
|
/** Stack commands prefer squash unless config or the repository selects another method. */
|
|
4
4
|
export function stackMergeFlag(allowed) {
|
|
5
|
-
return mergeMethodFlag(allowed, "squash", configuredMergeMethod(loadConfig().merge
|
|
5
|
+
return mergeMethodFlag(allowed, "squash", configuredMergeMethod(loadConfig().merge));
|
|
6
6
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PollSummaryItem, PollSummaryResult } from "../types.mts";
|
|
2
2
|
/**
|
|
3
|
-
* A
|
|
3
|
+
* A `stack` plan that can only wait never reruns its layers' one-PR sessions, so their own
|
|
4
4
|
* stall guards cannot fire. Escalate `stall-timeout` once that idle plan's fingerprint stays
|
|
5
5
|
* unchanged for `stallTimeoutSeconds`; any other plan, or `0`, clears the timer. When the enabled
|
|
6
6
|
* timer cannot be read or written, escalate `stall-state-unavailable` instead of restarting it.
|
|
@@ -14,6 +14,6 @@ export declare function applyStackStallGuard(planned: {
|
|
|
14
14
|
}, stallTimeoutSeconds: number): Promise<PollSummaryResult>;
|
|
15
15
|
/**
|
|
16
16
|
* Unix seconds when an unchanged idle stack plan trips `stall-timeout`, or undefined when no
|
|
17
|
-
* stack timer is running.
|
|
17
|
+
* stack timer is running. A single tick schedules its next tick no later than this.
|
|
18
18
|
*/
|
|
19
19
|
export declare function readStackStallDeadline(result: PollSummaryResult, stallTimeoutSeconds: number): Promise<number | undefined>;
|
|
@@ -3,7 +3,7 @@ import { formatDurationApprox } from "./iterate/escalate.mjs";
|
|
|
3
3
|
import { summaryStatusSignature } from "./poll-summary-signature.mjs";
|
|
4
4
|
import { describeIdleLayers } from "./stack-drain.mjs";
|
|
5
5
|
/**
|
|
6
|
-
* A
|
|
6
|
+
* A `stack` plan that can only wait never reruns its layers' one-PR sessions, so their own
|
|
7
7
|
* stall guards cannot fire. Escalate `stall-timeout` once that idle plan's fingerprint stays
|
|
8
8
|
* unchanged for `stallTimeoutSeconds`; any other plan, or `0`, clears the timer. When the enabled
|
|
9
9
|
* timer cannot be read or written, escalate `stall-state-unavailable` instead of restarting it.
|
|
@@ -44,7 +44,7 @@ export async function applyStackStallGuard(planned, repo, stallTimeoutSeconds) {
|
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
46
|
* Unix seconds when an unchanged idle stack plan trips `stall-timeout`, or undefined when no
|
|
47
|
-
* stack timer is running.
|
|
47
|
+
* stack timer is running. A single tick schedules its next tick no later than this.
|
|
48
48
|
*/
|
|
49
49
|
export async function readStackStallDeadline(result, stallTimeoutSeconds) {
|
|
50
50
|
const [owner, repo] = result.repo.split("/");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PollSummaryItem } from "../types.mts";
|
|
2
|
-
/** How the stack
|
|
2
|
+
/** How the stack command routes the autonomous layers that still need work. */
|
|
3
3
|
export interface StackWork {
|
|
4
4
|
/** Layers that need a one-PR Shepherd session, with or without a known command. */
|
|
5
5
|
sessions: PollSummaryItem[];
|
|
@@ -18,7 +18,7 @@ export declare function onlyDraftReadinessVerification(sessions: PollSummaryItem
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function splitStackWork(candidates: PollSummaryItem[], staleChildren: ReadonlySet<number>): StackWork;
|
|
20
20
|
/**
|
|
21
|
-
* The closing step after routed sessions: rerun the
|
|
21
|
+
* The closing step after routed sessions: rerun the stack command, or stop when nothing is owned.
|
|
22
22
|
* Returns the plan action: a stop is a handoff, so no rerun, quota cadence, or wake-up follows it.
|
|
23
23
|
*/
|
|
24
24
|
export declare function appendStackRerunInstruction(instructions: string[], layers: PollSummaryItem[], rerun: string): "shepherd" | "escalate";
|
|
@@ -29,14 +29,14 @@ function isProbed(item) {
|
|
|
29
29
|
return item.pollProbe === true && item.pollCommand !== undefined;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Work exists, but every routed layer belongs to another author: rerunning the stack
|
|
32
|
+
* Work exists, but every routed layer belongs to another author: rerunning the stack command
|
|
33
33
|
* cannot change anything, so the caller reports the overview and stops instead.
|
|
34
34
|
*/
|
|
35
35
|
function onlyUnownedWork(layers) {
|
|
36
36
|
return layers.length > 0 && layers.every((item) => !item.owned);
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* The closing step after routed sessions: rerun the
|
|
39
|
+
* The closing step after routed sessions: rerun the stack command, or stop when nothing is owned.
|
|
40
40
|
* Returns the plan action: a stop is a handoff, so no rerun, quota cadence, or wake-up follows it.
|
|
41
41
|
*/
|
|
42
42
|
export function appendStackRerunInstruction(instructions, layers, rerun) {
|
|
@@ -10,6 +10,5 @@ export declare function buildSuggestionPatchItem({ thread, request, originalCont
|
|
|
10
10
|
request: SuggestionPatchRequest;
|
|
11
11
|
originalContent: string;
|
|
12
12
|
}): SuggestionPatchResult;
|
|
13
|
-
export declare function buildSingularInstructions(patch: SuggestionPatchResult, prReference: string | number): string[];
|
|
14
13
|
export declare function buildCommitCommand(patch: SuggestionPatchResult): string;
|
|
15
14
|
export declare function quotePath(path: string): string;
|
|
@@ -61,18 +61,6 @@ export function buildSuggestionPatchItem({ thread, request, originalContent, })
|
|
|
61
61
|
filesToStage: [thread.path],
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
-
export function buildSingularInstructions(patch, prReference) {
|
|
65
|
-
const range = patch.startLine === patch.endLine
|
|
66
|
-
? `line ${patch.startLine}`
|
|
67
|
-
: `lines ${patch.startLine}–${patch.endLine}`;
|
|
68
|
-
return [
|
|
69
|
-
`Apply the patch to \`${patch.path}\`: run \`git apply\` with the diff shown above, or edit the file directly using the line range (${range}).`,
|
|
70
|
-
`Stage the file: \`git add -- ${quotePath(patch.path)}\``,
|
|
71
|
-
`Commit: \`${buildCommitCommand(patch)}\``,
|
|
72
|
-
`Use the originating iterate output for any authorization-checked GitHub review action for thread \`${patch.threadId}\` on ${String(prReference)}.`,
|
|
73
|
-
"Shepherd cannot verify authorization for the Git credential that would push this branch, so this output does not recommend a push.",
|
|
74
|
-
];
|
|
75
|
-
}
|
|
76
64
|
export function buildCommitCommand(patch) {
|
|
77
65
|
return [
|
|
78
66
|
"git commit",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SuggestResult, GlobalOptions } from "../types.mts";
|
|
2
2
|
import { type SuggestionPatchRequest } from "./suggestion-patch-item.mts";
|
|
3
3
|
export interface SuggestionPatchesOptions extends GlobalOptions {
|
|
4
4
|
suggestions: readonly SuggestionPatchRequest[];
|
|
5
5
|
}
|
|
6
|
-
export declare function runSuggestionPatches(opts: SuggestionPatchesOptions): Promise<
|
|
6
|
+
export declare function runSuggestionPatches(opts: SuggestionPatchesOptions): Promise<SuggestResult>;
|
|
@@ -21,7 +21,7 @@ export async function runSuggestionPatches(opts) {
|
|
|
21
21
|
const status = await getPathsStatus(paths);
|
|
22
22
|
if (status !== "") {
|
|
23
23
|
throw new ShepherdError(`Suggestion target files have uncommitted changes:\n${status}\n` +
|
|
24
|
-
"Commit or stash them before running
|
|
24
|
+
"Commit or stash them before running suggest.", EXIT.UNAVAILABLE);
|
|
25
25
|
}
|
|
26
26
|
const originals = await readOriginals(data.headRefOid, paths);
|
|
27
27
|
const patches = threads.map((thread, index) => buildSuggestionPatchItem({
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { NextCheck } from "../types/next-check.mts";
|
|
2
|
+
/**
|
|
3
|
+
* Steps for a single tick whose PR is waiting: end the turn, keep one wake-up, and let the
|
|
4
|
+
* next tick come from a PR event or the wake-up. Always printed after any state-specific
|
|
5
|
+
* sentence.
|
|
6
|
+
*/
|
|
7
|
+
export declare function tickWaitSteps(next: NextCheck): string[];
|
|
8
|
+
/** One aggregate step: sessions still run first, and only then does the turn end. */
|
|
9
|
+
export declare function tickAggregateStep(next: NextCheck, waiting: boolean): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { playbookPointer } from "./playbook-pointer.mjs";
|
|
2
|
+
function wakeUpClause(next) {
|
|
3
|
+
const kind = next.reason === "safety-net" ? "safety-net wake-up" : "wake-up";
|
|
4
|
+
return `Keep exactly one ${kind} at \`${next.at}\` (\`${next.reason}\`).`;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Steps for a single tick whose PR is waiting: end the turn, keep one wake-up, and let the
|
|
8
|
+
* next tick come from a PR event or the wake-up. Always printed after any state-specific
|
|
9
|
+
* sentence.
|
|
10
|
+
*/
|
|
11
|
+
export function tickWaitSteps(next) {
|
|
12
|
+
return [
|
|
13
|
+
`End this turn now without sleeping or polling. ${playbookPointer("Event loop")}`,
|
|
14
|
+
`${wakeUpClause(next)} When a PR event or that wake-up arrives, rerun this command with the same options and act only on Shepherd's output, never on the event payload.`,
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
/** One aggregate step: sessions still run first, and only then does the turn end. */
|
|
18
|
+
export function tickAggregateStep(next, waiting) {
|
|
19
|
+
const lead = waiting
|
|
20
|
+
? "End this turn now without sleeping or polling."
|
|
21
|
+
: "Once the steps above are done and the rerun still waits, end the turn without sleeping.";
|
|
22
|
+
return `${lead} ${wakeUpClause(next)} When a PR event or that wake-up arrives, rerun this command with the same options and act only on Shepherd's output, never on the event payload. ${playbookPointer("Event loop")}`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IterateCommandOptions, IterateResult, PollSummaryCommandOptions, PollSummaryResult } from "../types.mts";
|
|
2
|
+
import { type PollCommandOptions } from "./poll.mts";
|
|
3
|
+
import { type AggregatePollCommandOptions } from "./poll-summary.mts";
|
|
4
|
+
/**
|
|
5
|
+
* One iterate tick: no settle window and no sleeping, seen markers persist immediately, and
|
|
6
|
+
* `nextCheck` reports when to come back if no GitHub event arrives first. The tick's result is
|
|
7
|
+
* never discarded, so edited-comment markers are written on the result it returns.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runIterateTick(opts: IterateCommandOptions): Promise<IterateResult>;
|
|
10
|
+
/** One aggregate tick, with `nextCheck` unless nothing remains to wait for. */
|
|
11
|
+
export declare function runPollSummaryTick(opts: PollSummaryCommandOptions): Promise<PollSummaryResult>;
|
|
12
|
+
/** The default command: the sleeping loop, or one tick in a cloud session. */
|
|
13
|
+
export declare function runPollCommand(opts: PollCommandOptions): Promise<IterateResult>;
|
|
14
|
+
/** The aggregate loop (`stack`, several PRs), or one tick in a cloud session. */
|
|
15
|
+
export declare function runAggregatePollCommand(opts: AggregatePollCommandOptions): Promise<PollSummaryResult>;
|
|
@@ -1,54 +1,30 @@
|
|
|
1
1
|
import { loadConfig } from "../config/load.mjs";
|
|
2
2
|
import { readStallState } from "../state/iterate-stall.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { eventAggregateStep, eventFixContinuation } from "./event-instructions.mjs";
|
|
3
|
+
import { tickAggregateStep } from "./tick-instructions.mjs";
|
|
5
4
|
import { earliestNextCheck, MERGE_QUEUE_RECHECK_SECONDS, nextCheckCandidates, SAFETY_NET_SECONDS, } from "./next-check.mjs";
|
|
6
|
-
import { isFixCodeContinuation } from "./iterate/check-instructions.mjs";
|
|
7
5
|
import { isHandedOffSelection, onlyDraftVerificationSelection, } from "./poll-summary-explicit-instructions.mjs";
|
|
8
6
|
import { runIterate } from "./iterate/index.mjs";
|
|
9
|
-
import {
|
|
7
|
+
import { runsSingleTick } from "./single-tick.mjs";
|
|
10
8
|
import { readStackStallDeadline } from "./stack-stall.mjs";
|
|
11
9
|
import { runPoll } from "./poll.mjs";
|
|
12
10
|
import { runAggregatePoll, runPollSummary, } from "./poll-summary.mjs";
|
|
13
|
-
function isEventMode(requested) {
|
|
14
|
-
return resolvePollMode(requested, loadConfig().poll.mode) === "event";
|
|
15
|
-
}
|
|
16
11
|
/**
|
|
17
|
-
* One iterate tick
|
|
18
|
-
*
|
|
12
|
+
* One iterate tick: no settle window and no sleeping, seen markers persist immediately, and
|
|
13
|
+
* `nextCheck` reports when to come back if no GitHub event arrives first. The tick's result is
|
|
14
|
+
* never discarded, so edited-comment markers are written on the result it returns.
|
|
19
15
|
*/
|
|
20
|
-
export function
|
|
21
|
-
if (!isEventMode(opts.pollMode))
|
|
22
|
-
return runIterate(opts);
|
|
23
|
-
return runWithDurableState(() => runIterateEvent(opts));
|
|
24
|
-
}
|
|
25
|
-
/** One aggregate tick; event mode adds `pollMode` and `nextCheck`. */
|
|
26
|
-
export function runPollSummaryForMode(opts) {
|
|
27
|
-
if (!isEventMode(opts.pollMode))
|
|
28
|
-
return runPollSummary(opts);
|
|
29
|
-
const eventOpts = { ...opts, pollMode: "event" };
|
|
30
|
-
return runWithDurableState(async () => withAggregateNextCheck(await runPollSummary(eventOpts), eventOpts));
|
|
31
|
-
}
|
|
32
|
-
/** The default poll command: the sleeping loop in poll mode, one tick in event mode. */
|
|
33
|
-
export function runPollForMode(opts) {
|
|
34
|
-
if (!isEventMode(opts.pollMode))
|
|
35
|
-
return runPoll(opts);
|
|
36
|
-
const { intervalSeconds: _intervalSeconds, timeoutSeconds: _timeoutSeconds, debounceSeconds: _debounceSeconds, quietStatus: _quietStatus, untilTerminal: _untilTerminal, ...tick } = opts;
|
|
37
|
-
return runIterateForMode(tick);
|
|
38
|
-
}
|
|
39
|
-
/** The aggregate poll command: the sleeping loop in poll mode, one tick in event mode. */
|
|
40
|
-
export function runAggregatePollForMode(opts) {
|
|
41
|
-
return isEventMode(opts.pollMode) ? runPollSummaryForMode(opts) : runAggregatePoll(opts);
|
|
42
|
-
}
|
|
43
|
-
async function runIterateEvent(opts) {
|
|
44
|
-
const { pollMode: _pollMode, ...iterateOpts } = opts;
|
|
16
|
+
export async function runIterateTick(opts) {
|
|
45
17
|
const sink = {};
|
|
46
18
|
const result = await runIterate({
|
|
47
|
-
...
|
|
19
|
+
...opts,
|
|
48
20
|
persistSeen: true,
|
|
21
|
+
deferEditedCommentMarkers: false,
|
|
49
22
|
fingerprintCache: false,
|
|
50
23
|
stallDeadlineSink: sink,
|
|
51
24
|
});
|
|
25
|
+
// `[TRIAGE]` already reruns at once, and that rerun reports its own `nextCheck`.
|
|
26
|
+
if (result.action === "triage")
|
|
27
|
+
return result;
|
|
52
28
|
const nowMs = Date.now();
|
|
53
29
|
const stallDeadlineSeconds = earliestDeadline(await readStallDeadline(result, opts.stallTimeoutSeconds), result.action === "wait" ? sink.ciStartDeadlineSeconds : undefined);
|
|
54
30
|
const nextCheck = earliestNextCheck(nextCheckCandidates({
|
|
@@ -57,17 +33,23 @@ async function runIterateEvent(opts) {
|
|
|
57
33
|
queued: result.mergeQueue?.inQueue === true,
|
|
58
34
|
restMerge: result.action === "merge" && result.merge.mode === "rest",
|
|
59
35
|
stallDeadlineSeconds,
|
|
60
|
-
}, nowMs), nowMs);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
36
|
+
}, nowMs), nowMs, quotaFloorSeconds(result.quotaWarning));
|
|
37
|
+
return nextCheck ? { ...result, nextCheck } : result;
|
|
38
|
+
}
|
|
39
|
+
/** One aggregate tick, with `nextCheck` unless nothing remains to wait for. */
|
|
40
|
+
export async function runPollSummaryTick(opts) {
|
|
41
|
+
return withAggregateNextCheck(await runPollSummary({ ...opts, tick: true }), opts);
|
|
42
|
+
}
|
|
43
|
+
/** The default command: the sleeping loop, or one tick in a cloud session. */
|
|
44
|
+
export function runPollCommand(opts) {
|
|
45
|
+
if (!runsSingleTick())
|
|
46
|
+
return runPoll(opts);
|
|
47
|
+
const { intervalSeconds: _intervalSeconds, timeoutSeconds: _timeoutSeconds, debounceSeconds: _debounceSeconds, quietStatus: _quietStatus, untilTerminal: _untilTerminal, ...tick } = opts;
|
|
48
|
+
return runIterateTick(tick);
|
|
49
|
+
}
|
|
50
|
+
/** The aggregate loop (`stack`, several PRs), or one tick in a cloud session. */
|
|
51
|
+
export function runAggregatePollCommand(opts) {
|
|
52
|
+
return runsSingleTick() ? runPollSummaryTick(opts) : runAggregatePoll(opts);
|
|
71
53
|
}
|
|
72
54
|
function earliestDeadline(...deadlines) {
|
|
73
55
|
const known = deadlines.filter((d) => d !== undefined);
|
|
@@ -75,9 +57,9 @@ function earliestDeadline(...deadlines) {
|
|
|
75
57
|
}
|
|
76
58
|
/** When an unchanged state would trip the stall timeout, or undefined when no timer is running. */
|
|
77
59
|
async function readStallDeadline(result, stallTimeoutSeconds) {
|
|
78
|
-
if (
|
|
60
|
+
if (result.action !== "wait")
|
|
79
61
|
return undefined;
|
|
80
|
-
const timeout = stallTimeoutSeconds ?? loadConfig().iterate
|
|
62
|
+
const timeout = stallTimeoutSeconds ?? loadConfig().iterate["stall-timeout-minutes"] * 60;
|
|
81
63
|
const [owner, repo] = result.repo.split("/");
|
|
82
64
|
if (timeout <= 0 || !owner || !repo)
|
|
83
65
|
return undefined;
|
|
@@ -85,23 +67,22 @@ async function readStallDeadline(result, stallTimeoutSeconds) {
|
|
|
85
67
|
return read.ok && read.state ? read.state.firstSeenAt + timeout : undefined;
|
|
86
68
|
}
|
|
87
69
|
async function withAggregateNextCheck(result, opts) {
|
|
88
|
-
const
|
|
89
|
-
const stallDeadlineSeconds = await readStackStallDeadline(result, opts.stallTimeoutSeconds ?? loadConfig().iterate.stallTimeoutMinutes * 60);
|
|
70
|
+
const stallDeadlineSeconds = await readStackStallDeadline(result, opts.stallTimeoutSeconds ?? loadConfig().iterate["stall-timeout-minutes"] * 60);
|
|
90
71
|
const nextCheck = aggregateNextCheck(result, stallDeadlineSeconds);
|
|
91
72
|
if (!nextCheck)
|
|
92
|
-
return
|
|
73
|
+
return result;
|
|
93
74
|
const instructions = (result.instructions ?? []).map(withoutPollingCadence);
|
|
94
75
|
const waiting = result.reason === "waiting" || result.reason === "timeout";
|
|
95
76
|
return {
|
|
96
|
-
...
|
|
77
|
+
...result,
|
|
97
78
|
nextCheck,
|
|
98
79
|
instructions: [
|
|
99
80
|
...instructions,
|
|
100
|
-
`${instructions.length + 1}. ${
|
|
81
|
+
`${instructions.length + 1}. ${tickAggregateStep(nextCheck, waiting)}`,
|
|
101
82
|
],
|
|
102
83
|
};
|
|
103
84
|
}
|
|
104
|
-
/** The appended
|
|
85
|
+
/** The appended tick step owns timing, including pending stack merges. */
|
|
105
86
|
function withoutPollingCadence(step) {
|
|
106
87
|
return step
|
|
107
88
|
.replace(/ Recheck(?: them)? at the configured polling cadence\./g, "")
|
|
@@ -130,5 +111,8 @@ function aggregateNextCheck(result, stallDeadlineSeconds) {
|
|
|
130
111
|
if (stallDeadlineSeconds !== undefined) {
|
|
131
112
|
candidates.push({ reason: "stall-timeout", seconds: stallDeadlineSeconds - nowMs / 1000 });
|
|
132
113
|
}
|
|
133
|
-
return earliestNextCheck(candidates, nowMs);
|
|
114
|
+
return earliestNextCheck(candidates, nowMs, quotaFloorSeconds(result.quotaWarning));
|
|
115
|
+
}
|
|
116
|
+
function quotaFloorSeconds(warning) {
|
|
117
|
+
return warning === undefined ? 0 : warning.pollIntervalMinutes * 60;
|
|
134
118
|
}
|