pr-shepherd 0.60.0 → 1.0.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/classify/standard/codecov-report.d.mts +1 -1
- package/bin/classify/standard/codecov-report.mjs +7 -2
- package/bin/classify/standard/coderabbit-notices.d.mts +4 -3
- package/bin/classify/standard/coderabbit-notices.mjs +29 -47
- package/bin/classify/standard/codex-notices.mjs +34 -7
- package/bin/classify/standard/dependabot-notices.mjs +5 -1
- package/bin/classify/standard/gemini-notices.d.mts +1 -1
- package/bin/classify/standard/gemini-notices.mjs +16 -5
- package/bin/classify/standard/kilo-no-issues.d.mts +1 -1
- package/bin/classify/standard/kilo-no-issues.mjs +37 -10
- package/bin/classify/standard/sourcery-notices.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 +2 -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/runner.d.mts +1 -1
- package/bin/cli/runner.mjs +2 -2
- package/bin/cli/stack-overview.d.mts +0 -1
- package/bin/cli/stack-overview.mjs +1 -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-annotations.d.mts +1 -1
- package/bin/commands/check-annotations.mjs +1 -1
- 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 +1 -1
- package/bin/commands/check.d.mts +1 -1
- package/bin/commands/check.mjs +7 -7
- 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.mjs +4 -4
- package/bin/commands/poll.d.mts +1 -1
- package/bin/commands/poll.mjs +4 -4
- 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/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/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/poll-summary-check-blockers.mjs +1 -1
- package/bin/github/poll-summary-command.mjs +8 -6
- package/bin/github/poll-summary-projector.mjs +2 -2
- package/bin/github/poll-summary-review.mjs +1 -1
- package/bin/github/poll-summary-route.mjs +9 -9
- package/bin/github/poll-summary-unreported.mjs +1 -1
- package/bin/github/poll-summary.mjs +1 -1
- package/bin/github/queries.d.mts +1 -1
- package/bin/github/queries.mjs +1 -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/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 +3 -6
- package/bin/types/report.d.mts +2 -2
- package/bin/types/suggestion-patch.d.mts +1 -8
- package/package.json +2 -1
- 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
|
@@ -77,22 +77,22 @@ function pendingReviewCommands(resolveCommand, resolveOnlyCommand) {
|
|
|
77
77
|
};
|
|
78
78
|
return Object.keys(pending).length > 0 ? pending : undefined;
|
|
79
79
|
}
|
|
80
|
-
export async function
|
|
81
|
-
const result = await
|
|
80
|
+
export async function handleTriage(ctx) {
|
|
81
|
+
const result = await handleTriageTick(ctx);
|
|
82
82
|
// Every escalation from a capped tick carries the rollup: the reader sees a subset.
|
|
83
83
|
const { reviewItems } = ctx.report;
|
|
84
84
|
if (result.action !== "escalate" || reviewItems === undefined)
|
|
85
85
|
return result;
|
|
86
86
|
return { ...result, escalate: { ...result.escalate, reviewItems } };
|
|
87
87
|
}
|
|
88
|
-
async function
|
|
88
|
+
async function handleTriageTick(ctx) {
|
|
89
89
|
const { base, report, opts, headSha, stallKey, prNumber, stallTimeoutSeconds, repoOwner, repoName, reviewSummaryIds, firstLookSummaries, editedSummaries, surfacedApprovals, botUsernames, releasedCheckNames = EMPTY_RELEASED, ruleAutoResolveThreadIds, repairInstructions, } = ctx;
|
|
90
90
|
const prReference = formatPrUrl(report.repo, prNumber);
|
|
91
91
|
const failingChecks = report.checks.failing;
|
|
92
92
|
const annotatedExtra = checksWithActionableAnnotations(report).filter((c) => c.category !== "failing");
|
|
93
93
|
const allThreads = [...report.threads.actionable, ...report.threads.resolutionOnly];
|
|
94
94
|
const ruleAutoResolveIds = new Set(ruleAutoResolveThreadIds ?? []);
|
|
95
|
-
const resolveOtherHumanThreads = loadConfig().iterate
|
|
95
|
+
const resolveOtherHumanThreads = loadConfig().iterate["resolve-other-human-threads"] ?? "none";
|
|
96
96
|
const routedThreadMutations = buildThreadMutationRouting(allThreads, botUsernames, [...ruleAutoResolveIds], resolveOtherHumanThreads);
|
|
97
97
|
const replyIdSet = new Set(routedThreadMutations.replyThreadIds);
|
|
98
98
|
const resolveIdSet = new Set(routedThreadMutations.resolveThreadIds);
|
|
@@ -107,12 +107,11 @@ async function handleFixCodeTick(ctx) {
|
|
|
107
107
|
((thread.path !== null && thread.line !== null) ||
|
|
108
108
|
threadHasAuthorizedMutation(thread, replyIdSet, resolveIdSet)));
|
|
109
109
|
const retryableActionableThreads = mutationActionableThreads.filter((thread) => thread.path !== null && thread.line !== null);
|
|
110
|
-
const protectedRuns = [];
|
|
111
110
|
const stored = await readFixAttempts({ owner: repoOwner, repo: repoName, pr: prNumber });
|
|
112
111
|
await aliasThreadFixAttempts({ owner: repoOwner, name: repoName }, prNumber, retryableActionableThreads.map(({ id }) => id), stored);
|
|
113
|
-
const
|
|
112
|
+
const countTriageAttempt = opts.persistSeen !== false;
|
|
114
113
|
const priorThreadAttempts = previousFixAttempts(stored, retryableActionableThreads);
|
|
115
|
-
const { threadAttempts, threadBodyHashes } = nextFixAttempts(stored, retryableActionableThreads,
|
|
114
|
+
const { threadAttempts, threadBodyHashes } = nextFixAttempts(stored, retryableActionableThreads, countTriageAttempt);
|
|
116
115
|
const resolutionOnlyThreadsForWork = report.threads.resolutionOnly.filter((thread) => !skippedThreadIds.has(thread.id) &&
|
|
117
116
|
((thread.path !== null && thread.line !== null) ||
|
|
118
117
|
threadHasAuthorizedMutation(thread, replyIdSet, resolveIdSet)));
|
|
@@ -126,7 +125,7 @@ async function handleFixCodeTick(ctx) {
|
|
|
126
125
|
...(report.comments.minimizeIds ?? report.comments.actionable.map((comment) => comment.id)),
|
|
127
126
|
...reviewSummaryIds,
|
|
128
127
|
], changesRequestedReviewsForWork, checks, prReference, botUsernames, ruleAutoResolveThreadIds, report.viewerAuthorization, allThreads, resolveOtherHumanThreads);
|
|
129
|
-
const escalateTriggers =
|
|
128
|
+
const escalateTriggers = countTriageAttempt
|
|
130
129
|
? checkEscalateTriggers(retryableActionableThreads, priorThreadAttempts)
|
|
131
130
|
: { triggers: [], thrashHistory: undefined };
|
|
132
131
|
if (escalateTriggers.triggers.length > 0) {
|
|
@@ -161,7 +160,6 @@ async function handleFixCodeTick(ctx) {
|
|
|
161
160
|
// it does not confirm the credential executing `gh` has that scope; an unauthorized rerun
|
|
162
161
|
// simply fails when the agent runs it, the same residual risk as every other CLI-recommended
|
|
163
162
|
// git/gh mutation in this codebase.
|
|
164
|
-
const cancelled = [];
|
|
165
163
|
const baseLookup = validateBaseBranch(report.baseBranch);
|
|
166
164
|
const threads = report.threads.actionable.map(toAgentThread);
|
|
167
165
|
const resolutionOnlyThreads = report.threads.resolutionOnly;
|
|
@@ -204,11 +202,7 @@ async function handleFixCodeTick(ctx) {
|
|
|
204
202
|
const { changesRequestedReviews } = report;
|
|
205
203
|
const hasConflicts = report.mergeStatus.status === "CONFLICTS";
|
|
206
204
|
const isBehind = report.mergeStatus.status === "BEHIND";
|
|
207
|
-
const { behindBaseHint, instructions: configuredInstructions } = loadConfig().iterate;
|
|
208
|
-
// Only surface in-progress runs when a push is plausible — resolution-only and
|
|
209
|
-
// summary-only iterations have no path to a push, so listing runs would prompt
|
|
210
|
-
// unnecessary cancellation.
|
|
211
|
-
const inProgressRunIds = [];
|
|
205
|
+
const { "behind-base-hint": behindBaseHint, instructions: configuredInstructions } = loadConfig().iterate;
|
|
212
206
|
const commentMinimizeIds = report.comments.minimizeIds ?? actionableComments.map((c) => c.id);
|
|
213
207
|
const belongsToActiveWorkflowRun = (check) => check.runId !== null && inProgressWorkflowRunIds.has(check.runId);
|
|
214
208
|
const manualFollowUpChecks = failingAgentChecks.filter((check) => !releasedCheckNames.has(check.name) &&
|
|
@@ -265,7 +259,7 @@ async function handleFixCodeTick(ctx) {
|
|
|
265
259
|
};
|
|
266
260
|
}
|
|
267
261
|
// Push access to the PR head branch is a usage precondition. Build review mutations for
|
|
268
|
-
// conflict ticks normally so the caller can push and complete the same
|
|
262
|
+
// conflict ticks normally so the caller can push and complete the same triage cycle.
|
|
269
263
|
const { resolveCommand, resolveOnlyCommand } = buildReviewCommands(failingAgentChecks);
|
|
270
264
|
// Safety: if the base branch is unknown, escalate when a push is plausible — the agent
|
|
271
265
|
// would need the correct base to rebase safely. This is a conservative guard, not a
|
|
@@ -346,7 +340,7 @@ async function handleFixCodeTick(ctx) {
|
|
|
346
340
|
const stackRebase = hasConflicts || (isBehind && exhaustedAttempts.length > 0) || queueEjection
|
|
347
341
|
? buildNativeStackLayerRebase(report.repo, { number: prNumber, baseBranch: baseLookup.branch }, stack, trunkConflict, report.stackBottomPr)
|
|
348
342
|
: undefined;
|
|
349
|
-
const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prReference,
|
|
343
|
+
const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prReference, firstLookThreads, firstLookComments, firstLookSummaries, editedSummaries, resolutionOnlyThreads, resolveOnlyCommand, behindBaseHint, isBehind, canGenerateGithubMutation(report.viewerAuthorization?.viewerCanUpdate, "ready"), exhaustedAttempts.length > 0, stackRebase, queueEjection, opts.instructions ?? configuredInstructions);
|
|
350
344
|
if (recovery && "unavailable" in recovery)
|
|
351
345
|
instructions.unshift(recovery.unavailable);
|
|
352
346
|
if (acknowledgmentRecovery && "unavailable" in acknowledgmentRecovery)
|
|
@@ -391,8 +385,8 @@ async function handleFixCodeTick(ctx) {
|
|
|
391
385
|
baseBranch: baseLookup.branch,
|
|
392
386
|
...(mergedBasePullRequests.length > 0 && { mergedBasePullRequests }),
|
|
393
387
|
...(trunkConflict && { stackTrunkConflict: trunkConflict.trunk }),
|
|
394
|
-
action: "
|
|
395
|
-
|
|
388
|
+
action: "triage",
|
|
389
|
+
triage: {
|
|
396
390
|
threads,
|
|
397
391
|
resolutionOnlyThreads,
|
|
398
392
|
actionableComments,
|
|
@@ -409,16 +403,13 @@ async function handleFixCodeTick(ctx) {
|
|
|
409
403
|
instructions,
|
|
410
404
|
firstLookThreads,
|
|
411
405
|
firstLookComments,
|
|
412
|
-
inProgressRunIds,
|
|
413
|
-
protectedRuns,
|
|
414
406
|
...(report.reviewItems && { reviewItems: report.reviewItems }),
|
|
415
407
|
},
|
|
416
|
-
cancelled,
|
|
417
408
|
};
|
|
418
409
|
const result = await applyStallGuard(stallKey, stallTimeoutSeconds, headSha, base, prNumber, prospectiveResult, report, reviewSummaryIds);
|
|
419
|
-
if (result.action === "
|
|
410
|
+
if (result.action === "triage" && opts.persistSeen !== false) {
|
|
420
411
|
await writeFixAttempts({ owner: repoOwner, repo: repoName, pr: prNumber }, { headSha, threadAttempts, threadBodyHashes });
|
|
421
|
-
await Promise.allSettled(result.
|
|
412
|
+
await Promise.allSettled(result.triage.checks.flatMap((ch) => (ch.annotations ?? []).map((a) => markSeen(stallKey, a.id, annotationMarkerBody(a)))));
|
|
422
413
|
}
|
|
423
414
|
return result;
|
|
424
415
|
}
|
|
@@ -84,8 +84,8 @@ export function buildUnreportedFixResult(base, report, instructions) {
|
|
|
84
84
|
const prUrl = formatPrUrl(report.repo, report.pr);
|
|
85
85
|
return {
|
|
86
86
|
...base,
|
|
87
|
-
action: "
|
|
88
|
-
|
|
87
|
+
action: "triage",
|
|
88
|
+
triage: {
|
|
89
89
|
threads: [],
|
|
90
90
|
resolutionOnlyThreads: [],
|
|
91
91
|
actionableComments: [],
|
|
@@ -102,12 +102,9 @@ export function buildUnreportedFixResult(base, report, instructions) {
|
|
|
102
102
|
hasMutations: false,
|
|
103
103
|
},
|
|
104
104
|
instructions: [...instructions, buildFixCompletionInstruction()],
|
|
105
|
-
inProgressRunIds: [],
|
|
106
|
-
protectedRuns: [],
|
|
107
105
|
firstLookThreads: [],
|
|
108
106
|
firstLookComments: [],
|
|
109
107
|
},
|
|
110
|
-
cancelled: [],
|
|
111
108
|
};
|
|
112
109
|
}
|
|
113
110
|
function stackRebase(report) {
|
|
@@ -13,8 +13,6 @@ export interface JournalResult {
|
|
|
13
13
|
sectionExisted: boolean;
|
|
14
14
|
dryRun: boolean;
|
|
15
15
|
previewBody?: string;
|
|
16
|
-
/** @deprecated Explicit journal requests now rely on GitHub's mutation response. */
|
|
17
|
-
authorizationSkipped?: "denied-or-unverifiable";
|
|
18
16
|
}
|
|
19
|
-
/**
|
|
17
|
+
/** Implementation for `apply journal`. */
|
|
20
18
|
export declare function runJournal(opts: RunJournalOptions): Promise<JournalResult>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getRepoInfo, getPullRequestBody, updatePullRequestBody, getCurrentPrNumber, } from "../../github/client.mjs";
|
|
2
2
|
import { validateJournalItem, appendJournalItem } from "./transform.mjs";
|
|
3
|
-
/**
|
|
3
|
+
/** Implementation for `apply journal`. */
|
|
4
4
|
export async function runJournal(opts) {
|
|
5
5
|
const validation = validateJournalItem(opts.rawItem);
|
|
6
6
|
if (!validation.ok) {
|
|
@@ -21,8 +21,6 @@ export interface MarkFilesAsViewedResult {
|
|
|
21
21
|
errors: string[];
|
|
22
22
|
rateLimit?: ResolveRateLimitStop;
|
|
23
23
|
unmarkedPaths?: string[];
|
|
24
|
-
/** @deprecated Explicit file-view requests are attempted; GitHub authorizes the mutation. */
|
|
25
|
-
authorizationSkipped?: "unverifiable";
|
|
26
24
|
}
|
|
27
|
-
/**
|
|
25
|
+
/** Implementation for `apply files`. */
|
|
28
26
|
export declare function runMarkFilesAsViewed(opts: MarkFilesAsViewedOptions): Promise<MarkFilesAsViewedResult>;
|
|
@@ -34,7 +34,7 @@ const FILES_QUERY = `query PullRequestFiles($owner: String!, $repo: String!, $pr
|
|
|
34
34
|
const TEST_FILE_RE = /(^|\/)(tests?|__tests__|spec)(\/|$)|\.(test|spec)\.[cm]?[jt]sx?$|_tests?\.rs$|(^|\/)tests?\.rs$/i;
|
|
35
35
|
// Keep mutation batches small so rate-limit stops leave an ordered pending list.
|
|
36
36
|
const MARK_FILES_CHUNK_SIZE = 10;
|
|
37
|
-
/**
|
|
37
|
+
/** Implementation for `apply files`. */
|
|
38
38
|
export async function runMarkFilesAsViewed(opts) {
|
|
39
39
|
return githubOperation("markFileAsViewed", () => runGraphqlMarkFilesAsViewed(opts), async () => {
|
|
40
40
|
throw new UnsupportedRestOperationError("markFileAsViewed");
|
|
@@ -8,10 +8,10 @@ export interface NextCheckCandidate {
|
|
|
8
8
|
seconds: number;
|
|
9
9
|
}
|
|
10
10
|
/** The earliest candidate as a `NextCheck`, with `at` rounded up to a whole minute. */
|
|
11
|
-
export declare function earliestNextCheck(candidates: NextCheckCandidate[], nowMs?: number): NextCheck | undefined;
|
|
11
|
+
export declare function earliestNextCheck(candidates: NextCheckCandidate[], nowMs?: number, minimumSeconds?: number): NextCheck | undefined;
|
|
12
12
|
interface NextCheckFacts {
|
|
13
13
|
/** The action Shepherd returned for this tick. */
|
|
14
|
-
action: "wait" | "ready" | "mark_ready" | "
|
|
14
|
+
action: "wait" | "ready" | "mark_ready" | "triage" | "merge" | "cancel" | "escalate";
|
|
15
15
|
/** Ready-delay seconds left; only meaningful for `ready`. */
|
|
16
16
|
remainingSeconds?: number;
|
|
17
17
|
queued?: boolean;
|
|
@@ -3,11 +3,13 @@ export const MERGE_QUEUE_RECHECK_SECONDS = 300;
|
|
|
3
3
|
/** Backstop for a missed GitHub event; events normally wake the session long before this. */
|
|
4
4
|
export const SAFETY_NET_SECONDS = 3000;
|
|
5
5
|
/** The earliest candidate as a `NextCheck`, with `at` rounded up to a whole minute. */
|
|
6
|
-
export function earliestNextCheck(candidates, nowMs = Date.now()) {
|
|
6
|
+
export function earliestNextCheck(candidates, nowMs = Date.now(), minimumSeconds = 0) {
|
|
7
7
|
const first = [...candidates].sort((a, b) => a.seconds - b.seconds)[0];
|
|
8
8
|
if (!first)
|
|
9
9
|
return undefined;
|
|
10
|
-
|
|
10
|
+
// A quota warning's interval is a floor: no wake-up may come sooner than it permits.
|
|
11
|
+
const seconds = Math.max(0, first.seconds, minimumSeconds);
|
|
12
|
+
const atMs = Math.ceil((nowMs + seconds * 1000) / 60_000) * 60_000;
|
|
11
13
|
return {
|
|
12
14
|
at: new Date(atMs).toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
13
15
|
inSeconds: Math.max(0, Math.round((atMs - nowMs) / 1000)),
|
|
@@ -53,7 +53,7 @@ export function writeWaitProgress(opts) {
|
|
|
53
53
|
export function writeDebounceProgress(tick, elapsedMs, remainingMs) {
|
|
54
54
|
const elapsedSeconds = Math.round(elapsedMs / 1000);
|
|
55
55
|
const remainingSeconds = Math.round(remainingMs / 1000);
|
|
56
|
-
process.stderr.write(`[poll tick ${tick} / +${elapsedSeconds}s]
|
|
56
|
+
process.stderr.write(`[poll tick ${tick} / +${elapsedSeconds}s] TRIAGE — debounce ${remainingSeconds}s remaining\n`);
|
|
57
57
|
}
|
|
58
58
|
export function writeRuleAutoResolveProgress(result) {
|
|
59
59
|
if (result.ruleAutoResolve?.summary)
|
|
@@ -25,7 +25,7 @@ export function explicitInstructions(result) {
|
|
|
25
25
|
];
|
|
26
26
|
}
|
|
27
27
|
if (result.reason === "waiting" || result.reason === "timeout") {
|
|
28
|
-
return ["1. Run this aggregate
|
|
28
|
+
return ["1. Run this aggregate command again when the caller is ready to recheck."];
|
|
29
29
|
}
|
|
30
30
|
const instructions = [
|
|
31
31
|
"1. Choose each non-WAIT, non-CANCEL row that can proceed independently and run or delegate its exact `pollCommand`.",
|
|
@@ -24,7 +24,7 @@ export function planPollSummary(result, mergeRequested) {
|
|
|
24
24
|
const layer = needsOwnSession
|
|
25
25
|
? {
|
|
26
26
|
...item,
|
|
27
|
-
action: "
|
|
27
|
+
action: "triage",
|
|
28
28
|
reasons: [
|
|
29
29
|
...item.reasons,
|
|
30
30
|
staleChildren.has(item.pr) ? "stale-ancestry" : "ready-receipt-required",
|
|
@@ -108,8 +108,8 @@ function planStack(result, mergeRequested) {
|
|
|
108
108
|
if (ownedWork) {
|
|
109
109
|
const rerun = onlyDraftReadinessVerification(work.sessions, work.idle) &&
|
|
110
110
|
work.sessions.every((item) => item.owned)
|
|
111
|
-
? "If every listed one-PR probe returns `[CANCEL]` with reason `auto-mark-ready-disabled`, report the human handoff above and stop this
|
|
112
|
-
: "After the listed one-PR sessions, rerun this same
|
|
111
|
+
? "If every listed one-PR probe returns `[CANCEL]` with reason `auto-mark-ready-disabled`, report the human handoff above and stop this `stack` command. If any probe returns `[ESCALATE]`, report that handoff and stop after the other probes finish. Otherwise follow each probe's instructions and rerun this same `stack` command when that work completes."
|
|
112
|
+
: "After the listed one-PR sessions, rerun this same `stack` command. Stop for the human handoff only when no autonomous shepherding remains.";
|
|
113
113
|
appendStackRerunInstruction(instructions, runnableCandidates, rerun);
|
|
114
114
|
}
|
|
115
115
|
return { action: stop ? "escalate" : "shepherd", stackMergeable: false, instructions };
|
|
@@ -129,7 +129,7 @@ function planStack(result, mergeRequested) {
|
|
|
129
129
|
instructions.push(`${instructions.length + 1}. PR #${item.pr} needs a one-PR session, but Shepherd could not produce its command. ${ownedWork ? "After autonomous shepherding, ask" : "Stop and ask"} for direction.`);
|
|
130
130
|
}
|
|
131
131
|
if (ownedWork) {
|
|
132
|
-
appendStackRerunInstruction(instructions, runnableCandidates, "After the listed one-PR sessions, rerun this same
|
|
132
|
+
appendStackRerunInstruction(instructions, runnableCandidates, "After the listed one-PR sessions, rerun this same `stack` command. Stop for the human handoff only when no autonomous shepherding remains.");
|
|
133
133
|
}
|
|
134
134
|
return { action: ownedWork ? "shepherd" : "escalate", stackMergeable: false, instructions };
|
|
135
135
|
}
|
|
@@ -149,10 +149,10 @@ function planStack(result, mergeRequested) {
|
|
|
149
149
|
appendAutonomousInstructions(instructions, work.sessions);
|
|
150
150
|
if (onlyDraftReadinessVerification(work.sessions, work.idle) &&
|
|
151
151
|
work.sessions.every((item) => item.owned)) {
|
|
152
|
-
instructions.push(`${instructions.length + 1}. If every listed one-PR probe returns \`[CANCEL]\` with reason \`auto-mark-ready-disabled\`, stop shepherding this stack and leave those drafts unchanged. If any probe returns \`[ESCALATE]\`, report that handoff and stop after the other probes finish. Otherwise follow each probe's instructions and rerun this same
|
|
152
|
+
instructions.push(`${instructions.length + 1}. If every listed one-PR probe returns \`[CANCEL]\` with reason \`auto-mark-ready-disabled\`, stop shepherding this stack and leave those drafts unchanged. If any probe returns \`[ESCALATE]\`, report that handoff and stop after the other probes finish. Otherwise follow each probe's instructions and rerun this same \`stack\` command when that work completes.`);
|
|
153
153
|
return { action: "shepherd", stackMergeable: false, instructions };
|
|
154
154
|
}
|
|
155
|
-
const action = appendStackRerunInstruction(instructions, work.sessions, "After the selected one-PR sessions, rerun this same
|
|
155
|
+
const action = appendStackRerunInstruction(instructions, work.sessions, "After the selected one-PR sessions, rerun this same `stack` command.");
|
|
156
156
|
return { action, stackMergeable: false, instructions };
|
|
157
157
|
}
|
|
158
158
|
if (open.some((item) => item.isInMergeQueue)) {
|
|
@@ -176,7 +176,7 @@ function planStack(result, mergeRequested) {
|
|
|
176
176
|
const instructions = [];
|
|
177
177
|
const sessions = open.filter((item) => item.action !== "cancel");
|
|
178
178
|
appendAutonomousInstructions(instructions, sessions);
|
|
179
|
-
const action = appendStackRerunInstruction(instructions, sessions, "After the selected one-PR sessions, rerun this same
|
|
179
|
+
const action = appendStackRerunInstruction(instructions, sessions, "After the selected one-PR sessions, rerun this same `stack` command.");
|
|
180
180
|
return { action, stackMergeable, instructions };
|
|
181
181
|
}
|
|
182
182
|
return retargetWaitPlan(open[0]);
|
|
@@ -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;
|
|
@@ -139,7 +139,7 @@ async function runAggregatePollCore(opts) {
|
|
|
139
139
|
: last.prs.some((item) => ["escalate", "merge", "mark_ready"].includes(item.action));
|
|
140
140
|
const hasFix = last.selection.kind === "stack"
|
|
141
141
|
? last.nextAction === "shepherd" && last.reason !== "waiting"
|
|
142
|
-
: last.prs.some((item) => item.action === "
|
|
142
|
+
: last.prs.some((item) => item.action === "triage");
|
|
143
143
|
const warning = await aggregateQuotaWarning(last, quotaBands, opts.intervalSeconds);
|
|
144
144
|
pendingQuotaWarning = carryQuotaWarning(pendingQuotaWarning, warning);
|
|
145
145
|
if (allTerminal) {
|
|
@@ -205,7 +205,7 @@ function attachUsage(result, mergeRequested) {
|
|
|
205
205
|
const enriched = apiUsage ? { ...result, apiUsage } : result;
|
|
206
206
|
// A disappeared native stack is an aggregate-level terminal handoff. Keep
|
|
207
207
|
// that explicit escalation intact even when the one-PR fallback contains
|
|
208
|
-
// open orphan rows that would otherwise be re-planned as
|
|
208
|
+
// open orphan rows that would otherwise be re-planned as triage.
|
|
209
209
|
if (enriched.selection.kind === "stack" && enriched.nextAction === "escalate") {
|
|
210
210
|
return enriched;
|
|
211
211
|
}
|
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();
|
|
@@ -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
|
}
|
|
@@ -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>;
|