pr-shepherd 0.59.2 → 0.60.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 +3 -3
- package/bin/api.d.mts +1 -1
- package/bin/classify/types.d.mts +1 -1
- package/bin/cli/edited-comments-format.d.mts +14 -0
- package/bin/cli/edited-comments-format.mjs +39 -0
- package/bin/cli/fix-formatter.mjs +5 -2
- package/bin/cli/iterate-activity-formatter.d.mts +1 -1
- package/bin/cli/iterate-activity-formatter.mjs +4 -5
- package/bin/cli/iterate-check-names.d.mts +5 -0
- package/bin/cli/iterate-check-names.mjs +17 -0
- package/bin/cli/iterate-formatter.mjs +15 -19
- package/bin/cli/iterate-lean.mjs +19 -11
- package/bin/cli/iterate-verbose.mjs +7 -2
- package/bin/cli/list-formatters.d.mts +0 -3
- package/bin/cli/list-formatters.mjs +1 -5
- package/bin/commands/check-review-budget.d.mts +39 -0
- package/bin/commands/check-review-budget.mjs +78 -0
- package/bin/commands/check-wait-detectors.d.mts +6 -1
- package/bin/commands/check-wait-detectors.mjs +2 -1
- package/bin/commands/check.d.mts +2 -0
- package/bin/commands/check.mjs +46 -27
- package/bin/commands/iterate/check-instructions.d.mts +5 -0
- package/bin/commands/iterate/check-instructions.mjs +4 -0
- package/bin/commands/iterate/classify.d.mts +1 -1
- package/bin/commands/iterate/edited-comment-markers.d.mts +3 -0
- package/bin/commands/iterate/edited-comment-markers.mjs +10 -0
- package/bin/commands/iterate/fix-code.mjs +17 -1
- package/bin/commands/iterate/fix-loop-instruction.d.mts +2 -8
- package/bin/commands/iterate/fix-loop-instruction.mjs +8 -29
- package/bin/commands/iterate/helpers.mjs +9 -5
- package/bin/commands/iterate/index.mjs +44 -17
- package/bin/commands/iterate/render.d.mts +1 -1
- package/bin/commands/iterate/render.mjs +18 -17
- package/bin/commands/poll-rate-limit-cancel.mjs +1 -1
- package/bin/commands/poll-seen-options.d.mts +8 -0
- package/bin/commands/poll-seen-options.mjs +7 -0
- package/bin/commands/poll.mjs +7 -5
- package/bin/commands/rule-auto-resolve-format.d.mts +0 -12
- package/bin/commands/rule-auto-resolve-format.mjs +2 -11
- package/bin/commands/rule-auto-resolve.d.mts +0 -5
- package/bin/commands/rule-auto-resolve.mjs +1 -42
- package/bin/commands/shepherd-journal.d.mts +0 -9
- package/bin/commands/shepherd-journal.mjs +0 -15
- package/bin/comments/review-output-budget.d.mts +32 -0
- package/bin/comments/review-output-budget.mjs +55 -0
- package/bin/comments/visible-comments.d.mts +3 -1
- package/bin/comments/visible-comments.mjs +10 -5
- package/bin/config/load.d.mts +8 -2
- package/bin/config/load.mjs +7 -0
- package/bin/config.json +2 -1
- package/bin/github/poll-summary-fingerprint.mjs +5 -1
- package/bin/github/poll-summary-review.mjs +2 -1
- package/bin/mcp/server.mjs +1 -1
- package/bin/reporters/agent.d.mts +1 -3
- package/bin/reporters/agent.mjs +0 -1
- package/bin/types/escalate.d.mts +9 -0
- package/bin/types/iterate.d.mts +19 -1
- package/bin/types/report.d.mts +23 -4
- package/bin/util/cap-list.d.mts +7 -0
- package/bin/util/cap-list.mjs +13 -0
- package/package.json +2 -2
- package/plugins/pr-shepherd/.claude-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/references/fix-code-loop.md +1 -2
- package/plugins/pr-shepherd/skills/pr-shepherd/references/merge-queue-ejection.md +1 -1
- package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +1 -1
- package/src/classify/types.mts +1 -1
package/bin/commands/check.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import { threadTranscriptBody } from "../threads/transcript.mjs";
|
|
|
28
28
|
import { classifyThreadVisibility } from "../comments/thread-visibility.mjs";
|
|
29
29
|
import { classifyReviewsForDisplay, classifyChangesRequestedReviewsForDisplay, } from "../comments/review-visibility.mjs";
|
|
30
30
|
import { applySuppressedRuleAutoResolve } from "./rule-auto-resolve.mjs";
|
|
31
|
+
import { applyReviewOutputBudget } from "./check-review-budget.mjs";
|
|
31
32
|
import { markReviewInlineThreadMarkers } from "../comments/review-thread-markers.mjs";
|
|
32
33
|
import { isConfiguredBotAuthor, isHumanAuthor, normalizeBotUsernames, } from "../comments/authors.mjs";
|
|
33
34
|
import { buildThreadMutationRouting, threadHasAuthorizedMutation, } from "./iterate/thread-mutation-routing.mjs";
|
|
@@ -70,7 +71,11 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
70
71
|
: undefined;
|
|
71
72
|
if (detectors && "reused" in detectors)
|
|
72
73
|
return refreshReused(detectors.reused, detectors.baseTipOid);
|
|
73
|
-
|
|
74
|
+
// A detector change, or a first seeding read, means the stored report may predate what the
|
|
75
|
+
// detectors saw; an unchanged first-page fingerprint cannot vouch for it (it omits
|
|
76
|
+
// per-check-run fields), so take a full snapshot instead.
|
|
77
|
+
const detectorsChanged = detectors !== undefined && "changed" in detectors;
|
|
78
|
+
const reuse = reuseFingerprint && !detectorsChanged
|
|
74
79
|
? await fingerprintReuser(prNumber, repo, stateKey, config)
|
|
75
80
|
: undefined;
|
|
76
81
|
const batchOptions = {
|
|
@@ -215,13 +220,17 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
215
220
|
threadHasAuthorizedMutation(thread, replyThreadIds, resolveThreadIds))
|
|
216
221
|
.map((thread) => thread.id));
|
|
217
222
|
const threadVisibility = classifyThreadVisibility(visibleThreadCandidates, seenMap, botUsernames, repeatableThreadIds, resolveOtherHumanThreads);
|
|
218
|
-
const firstLookComments = minimizedCommentCandidates
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
const firstLookComments = minimizedCommentCandidates
|
|
224
|
+
.filter((c) => classifyItem(c.id, c.body, seenMap) === "new")
|
|
225
|
+
.map((c) => ({ ...c, firstLookStatus: "minimized" }));
|
|
226
|
+
// Edited PR comments never block readiness. Iterate shows them on the next action that is not
|
|
227
|
+
// wait/ready and writes their markers then, so an edit is never marked seen while hidden.
|
|
228
|
+
const editedComments = [
|
|
229
|
+
...minimizedCommentCandidates
|
|
230
|
+
.filter((c) => classifyItem(c.id, c.body, seenMap) === "edited")
|
|
231
|
+
.map((c) => ({ ...c, edited: true })),
|
|
232
|
+
...visibleCommentClassification.edited,
|
|
233
|
+
];
|
|
225
234
|
const firstLookSummaries = [];
|
|
226
235
|
const editedSummaries = [];
|
|
227
236
|
const seenSummaries = [];
|
|
@@ -251,7 +260,6 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
251
260
|
canGenerateItemMutation(review.id, review.body, batchData.viewerAuthorization?.viewerCanAdminister, "dismiss") ||
|
|
252
261
|
visibleChangesRequestedIds.has(review.id));
|
|
253
262
|
}).length;
|
|
254
|
-
const approvedReviews = approvedReviewVisibility.visible;
|
|
255
263
|
let status = computeStatus(verdict, threadVisibility.activeThreads.length + threadVisibility.resolutionOnlyThreads.length, visibleCommentClassification.actionable.length, mergeStatus, changesRequestedReviewCount, unreported.hasUnreportedRequired);
|
|
256
264
|
status = enforceTransportReadiness(status, batchData);
|
|
257
265
|
// Resolve any pending mergeability refresh (and the resulting MERGED/CLOSED short-circuit)
|
|
@@ -292,6 +300,16 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
292
300
|
filtered: merged.filtered,
|
|
293
301
|
ignored: merged.ignored,
|
|
294
302
|
}, mergeStatus.status);
|
|
303
|
+
// A deferred tick shows no review items, so it is never capped; its seen markers stay unwritten.
|
|
304
|
+
const { buckets: budgeted, rollup: reviewItems, heldThreadReviewIds, } = applyReviewOutputBudget({
|
|
305
|
+
changesRequested: changesRequestedReviewVisibility,
|
|
306
|
+
threads: threadVisibility,
|
|
307
|
+
comments: visibleCommentClassification,
|
|
308
|
+
firstLookComments,
|
|
309
|
+
firstLookSummaries,
|
|
310
|
+
editedSummaries,
|
|
311
|
+
editedComments,
|
|
312
|
+
}, deferWhileQueued ? 0 : (opts.reviewOutputMaxChars ?? 0));
|
|
295
313
|
if (opts.persistSeen !== false) {
|
|
296
314
|
const successfulAnnotations = [
|
|
297
315
|
...merged.passing,
|
|
@@ -304,11 +322,11 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
304
322
|
const deferredMarkSeen = deferWhileQueued
|
|
305
323
|
? []
|
|
306
324
|
: [
|
|
307
|
-
...firstLookComments.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
308
|
-
...
|
|
309
|
-
...
|
|
310
|
-
...[...firstLookSummaries, ...editedSummaries].map((r) => markSeen(stateKey, r.id, r.body)),
|
|
311
|
-
...
|
|
325
|
+
...budgeted.firstLookComments.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
326
|
+
...budgeted.threads.toMarkSeen.map((t) => markSeen(stateKey, t.id, threadTranscriptBody(t))),
|
|
327
|
+
...budgeted.comments.toMarkSeen.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
328
|
+
...[...budgeted.firstLookSummaries, ...budgeted.editedSummaries].map((r) => markSeen(stateKey, r.id, r.body)),
|
|
329
|
+
...budgeted.changesRequested.toMarkSeen.map((r) => markSeen(stateKey, r.id, r.body)),
|
|
312
330
|
...approvedReviewVisibility.toMarkSeen.map((r) => markSeen(stateKey, r.id, r.body)),
|
|
313
331
|
];
|
|
314
332
|
await Promise.allSettled([
|
|
@@ -350,10 +368,8 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
350
368
|
enabled: opts.autoMinimizeSuppressed === true,
|
|
351
369
|
partition: authorizedPartition,
|
|
352
370
|
batch: batchData,
|
|
353
|
-
prNumber,
|
|
354
|
-
repo,
|
|
355
371
|
});
|
|
356
|
-
const visibleMutationThreadIds = new Set([...
|
|
372
|
+
const visibleMutationThreadIds = new Set([...budgeted.threads.activeThreads, ...budgeted.threads.resolutionOnlyThreads].map((thread) => thread.id));
|
|
357
373
|
const ruleAutoResolveThreadIds = [
|
|
358
374
|
...authorizedRuleAutoResolveThreadIds,
|
|
359
375
|
...[...deniedRuleAutoResolveThreadIds].filter((id) => visibleMutationThreadIds.has(id)),
|
|
@@ -402,27 +418,30 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
402
418
|
}),
|
|
403
419
|
},
|
|
404
420
|
threads: {
|
|
405
|
-
actionable:
|
|
406
|
-
resolutionOnly:
|
|
421
|
+
actionable: budgeted.threads.activeThreads,
|
|
422
|
+
resolutionOnly: budgeted.threads.resolutionOnlyThreads,
|
|
407
423
|
autoResolved,
|
|
408
424
|
autoResolveErrors,
|
|
409
425
|
...(errorReasons.length > 0 && { autoResolveErrorReasons: errorReasons }),
|
|
410
|
-
firstLook:
|
|
426
|
+
firstLook: budgeted.threads.firstLookThreads,
|
|
411
427
|
...(ruleAutoResolveThreadIds.length > 0
|
|
412
428
|
? { ruleAutoResolveIds: ruleAutoResolveThreadIds }
|
|
413
429
|
: undefined),
|
|
414
430
|
},
|
|
415
431
|
comments: {
|
|
416
|
-
actionable:
|
|
417
|
-
minimizeIds: [...
|
|
418
|
-
firstLook: firstLookComments,
|
|
432
|
+
actionable: budgeted.comments.actionable,
|
|
433
|
+
minimizeIds: [...budgeted.comments.minimizeIds, ...ruleAutoResolveCommentIds],
|
|
434
|
+
firstLook: budgeted.firstLookComments,
|
|
435
|
+
...(budgeted.editedComments.length > 0 && { edited: budgeted.editedComments }),
|
|
419
436
|
...(autoMinimized.length > 0 && { autoMinimized }),
|
|
420
437
|
},
|
|
421
|
-
changesRequestedReviews,
|
|
438
|
+
changesRequestedReviews: budgeted.changesRequested.visible,
|
|
422
439
|
reviewSummaries: seenSummaries,
|
|
423
|
-
firstLookSummaries,
|
|
424
|
-
editedSummaries,
|
|
425
|
-
approvedReviews,
|
|
440
|
+
firstLookSummaries: budgeted.firstLookSummaries,
|
|
441
|
+
editedSummaries: budgeted.editedSummaries,
|
|
442
|
+
approvedReviews: approvedReviewVisibility.visible,
|
|
443
|
+
...(reviewItems && { reviewItems }),
|
|
444
|
+
...(heldThreadReviewIds && { heldThreadReviewIds }),
|
|
426
445
|
...(ruleAutoResolveReviewSummaryIds.length > 0
|
|
427
446
|
? { ruleAutoResolveReviewSummaryIds }
|
|
428
447
|
: undefined),
|
|
@@ -37,5 +37,10 @@ export declare function buildResolveCommandInstruction(resolveCommand: ResolveCo
|
|
|
37
37
|
export declare function buildFailingCheckInstructions(checks: AgentCheck[]): string[];
|
|
38
38
|
/** Update the PR branch after an external blocker merges or closes. Never rerun that job. */
|
|
39
39
|
export declare function buildReleasedBlockerInstruction(prNumber: number): string;
|
|
40
|
+
/** `iterate.reviewOutputMaxChars` held review items back; the rerun step that follows shows them. */
|
|
41
|
+
export declare function buildReviewSubsetInstruction(reviewItems: {
|
|
42
|
+
shown: number;
|
|
43
|
+
total: number;
|
|
44
|
+
}): string;
|
|
40
45
|
/** Recurrence only. Commit, push, rerun, and SHA steps are earlier instructions. */
|
|
41
46
|
export declare function buildFixCompletionInstruction(): string;
|
|
@@ -74,6 +74,10 @@ export function buildReleasedBlockerInstruction(prNumber) {
|
|
|
74
74
|
? "Update this PR branch from its base using the repository's branch-update procedure. Do not rerun the job; a rerun retests the old merge ref."
|
|
75
75
|
: `Update this PR branch from its base with \`gh pr update-branch ${prNumber} --rebase\`. Do not rerun the job; a rerun retests the old merge ref.`;
|
|
76
76
|
}
|
|
77
|
+
/** `iterate.reviewOutputMaxChars` held review items back; the rerun step that follows shows them. */
|
|
78
|
+
export function buildReviewSubsetInstruction(reviewItems) {
|
|
79
|
+
return `This is a subset: ${reviewItems.shown} of ${reviewItems.total} review items are shown, and only their IDs are in the commands above. After handling these items, rerun Shepherd for the next batch; do not fetch the held items yourself.`;
|
|
80
|
+
}
|
|
77
81
|
/** Recurrence only. Commit, push, rerun, and SHA steps are earlier instructions. */
|
|
78
82
|
export function buildFixCompletionInstruction() {
|
|
79
83
|
return FIX_CODE_CONTINUATION;
|
|
@@ -5,7 +5,7 @@ export declare function classifyReviewSummaries(summaries: {
|
|
|
5
5
|
firstLook: Review[];
|
|
6
6
|
seen: Review[];
|
|
7
7
|
edited: Review[];
|
|
8
|
-
}, approvals: Review[], minimizeApprovals: boolean, minimizeComments?: MinimizeCommentsPolicy | undefined, botUsernames?: NormalizedBotUsernames, unresolvedThreads?: ReviewThread
|
|
8
|
+
}, approvals: Review[], minimizeApprovals: boolean, minimizeComments?: MinimizeCommentsPolicy | undefined, botUsernames?: NormalizedBotUsernames, unresolvedThreads?: ReadonlyArray<Pick<ReviewThread, "reviewId">>, ruleAutoResolveIds?: string[]): {
|
|
9
9
|
minimizeIds: string[];
|
|
10
10
|
selfMinimizeIds: string[];
|
|
11
11
|
firstLookSummaries: Review[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { markSeen } from "../../state/seen-comments.mjs";
|
|
2
|
+
/** Records the edited comments a result shows, so each edit is shown once. Returns the result. */
|
|
3
|
+
export async function markEditedCommentsSeen(result) {
|
|
4
|
+
const [owner, repo] = result.repo.split("/");
|
|
5
|
+
if (!result.editedComments?.length || !owner || !repo)
|
|
6
|
+
return result;
|
|
7
|
+
const key = { owner, repo, pr: result.pr };
|
|
8
|
+
await Promise.allSettled(result.editedComments.map((c) => markSeen(key, c.id, c.body)));
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
@@ -9,7 +9,7 @@ import { buildFixInstructions } from "./render.mjs";
|
|
|
9
9
|
import { buildRemovedQueueRecovery, buildStackQueueRemovalAcknowledgment } from "./merge.mjs";
|
|
10
10
|
import { hasLogEvidence } from "./check-evidence.mjs";
|
|
11
11
|
import { currentEjectionCommit, } from "./queue-recovery-instructions.mjs";
|
|
12
|
-
import { buildReleasedBlockerInstruction } from "./check-instructions.mjs";
|
|
12
|
+
import { buildReleasedBlockerInstruction, buildReviewSubsetInstruction, } from "./check-instructions.mjs";
|
|
13
13
|
import { buildNativeStackLayerRebase } from "./native-stack-rebase.mjs";
|
|
14
14
|
import { lookupUpperLayerTrunkConflict } from "./stack-trunk-conflict.mjs";
|
|
15
15
|
import { lookupMergedBasePullRequests } from "./merged-base-pull-requests.mjs";
|
|
@@ -78,6 +78,14 @@ function pendingReviewCommands(resolveCommand, resolveOnlyCommand) {
|
|
|
78
78
|
return Object.keys(pending).length > 0 ? pending : undefined;
|
|
79
79
|
}
|
|
80
80
|
export async function handleFixCode(ctx) {
|
|
81
|
+
const result = await handleFixCodeTick(ctx);
|
|
82
|
+
// Every escalation from a capped tick carries the rollup: the reader sees a subset.
|
|
83
|
+
const { reviewItems } = ctx.report;
|
|
84
|
+
if (result.action !== "escalate" || reviewItems === undefined)
|
|
85
|
+
return result;
|
|
86
|
+
return { ...result, escalate: { ...result.escalate, reviewItems } };
|
|
87
|
+
}
|
|
88
|
+
async function handleFixCodeTick(ctx) {
|
|
81
89
|
const { base, report, opts, headSha, stallKey, prNumber, stallTimeoutSeconds, repoOwner, repoName, reviewSummaryIds, firstLookSummaries, editedSummaries, surfacedApprovals, botUsernames, releasedCheckNames = EMPTY_RELEASED, ruleAutoResolveThreadIds, repairInstructions, } = ctx;
|
|
82
90
|
const prReference = formatPrUrl(report.repo, prNumber);
|
|
83
91
|
const failingChecks = report.checks.failing;
|
|
@@ -221,6 +229,7 @@ export async function handleFixCode(ctx) {
|
|
|
221
229
|
editedSummaries.length > 0 ||
|
|
222
230
|
report.threads.firstLook.length > 0 ||
|
|
223
231
|
report.comments.firstLook.length > 0 ||
|
|
232
|
+
report.reviewItems !== undefined ||
|
|
224
233
|
checks.some((check) => (check.annotations?.length ?? 0) > 0) ||
|
|
225
234
|
failingAgentChecks.some((check) => releasedCheckNames.has(check.name) ||
|
|
226
235
|
belongsToActiveWorkflowRun(check) ||
|
|
@@ -348,6 +357,12 @@ export async function handleFixCode(ctx) {
|
|
|
348
357
|
if (completion !== undefined)
|
|
349
358
|
instructions.push(completion);
|
|
350
359
|
}
|
|
360
|
+
if (report.reviewItems) {
|
|
361
|
+
const completion = instructions.pop();
|
|
362
|
+
instructions.push(buildReviewSubsetInstruction(report.reviewItems));
|
|
363
|
+
if (completion !== undefined)
|
|
364
|
+
instructions.push(completion);
|
|
365
|
+
}
|
|
351
366
|
if (repairInstructions && repairInstructions.length > 0) {
|
|
352
367
|
instructions.unshift(...repairInstructions);
|
|
353
368
|
}
|
|
@@ -396,6 +411,7 @@ export async function handleFixCode(ctx) {
|
|
|
396
411
|
firstLookComments,
|
|
397
412
|
inProgressRunIds,
|
|
398
413
|
protectedRuns,
|
|
414
|
+
...(report.reviewItems && { reviewItems: report.reviewItems }),
|
|
399
415
|
},
|
|
400
416
|
cancelled,
|
|
401
417
|
};
|
|
@@ -1,9 +1,3 @@
|
|
|
1
1
|
import type { InstructionStyle } from "../../config/load.mts";
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
* only when the generic push applies: the "Fix-code loop" playbook tells the caller to push to the
|
|
5
|
-
* PR head branch, which would conflict with a conflict or queue-recovery push step. Without the
|
|
6
|
-
* generic push, the journal step stays inline with its citation clause. The rerun
|
|
7
|
-
* step stays inline because it is the one step a halted loop cannot recover from.
|
|
8
|
-
*/
|
|
9
|
-
export declare function buildPushJournalSteps(style: InstructionStyle, prReference: string | number, genericPush: boolean, wantsJournal: boolean): string[];
|
|
2
|
+
/** The generic push stays separate from conflict and queue-recovery push instructions. */
|
|
3
|
+
export declare function buildPushSteps(style: InstructionStyle, genericPush: boolean): string[];
|
|
@@ -1,35 +1,14 @@
|
|
|
1
1
|
import { renderShellCommand } from "../../cli/runner.mjs";
|
|
2
2
|
import { loadConfig } from "../../config/load.mjs";
|
|
3
|
-
import { buildShepherdJournalCommand, buildShepherdJournalInstruction, } from "../shepherd-journal.mjs";
|
|
4
3
|
import { playbookPointer } from "../playbook-pointer.mjs";
|
|
5
4
|
const FIX_CODE_LOOP_PLAYBOOK = "Fix-code loop";
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (journal)
|
|
14
|
-
parts.push(`Journal substantial decisions or rejections with \`${buildShepherdJournalCommand(prReference)}\`.`);
|
|
5
|
+
/** The generic push stays separate from conflict and queue-recovery push instructions. */
|
|
6
|
+
export function buildPushSteps(style, genericPush) {
|
|
7
|
+
if (!genericPush)
|
|
8
|
+
return [];
|
|
9
|
+
const push = "Commit and push any changes.";
|
|
10
|
+
if (style === "inline")
|
|
11
|
+
return [push];
|
|
15
12
|
const cli = renderShellCommand([...loadConfig().cliCommand, "playbook", FIX_CODE_LOOP_PLAYBOOK]);
|
|
16
|
-
|
|
17
|
-
return parts.join(" ");
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* The generic commit/push step and the journal step. Playbook style folds both into one pointer
|
|
21
|
-
* only when the generic push applies: the "Fix-code loop" playbook tells the caller to push to the
|
|
22
|
-
* PR head branch, which would conflict with a conflict or queue-recovery push step. Without the
|
|
23
|
-
* generic push, the journal step stays inline with its citation clause. The rerun
|
|
24
|
-
* step stays inline because it is the one step a halted loop cannot recover from.
|
|
25
|
-
*/
|
|
26
|
-
export function buildPushJournalSteps(style, prReference, genericPush, wantsJournal) {
|
|
27
|
-
if (style === "playbook" && genericPush)
|
|
28
|
-
return [buildFixLoopInstruction(prReference, wantsJournal)];
|
|
29
|
-
const steps = [];
|
|
30
|
-
if (genericPush)
|
|
31
|
-
steps.push("Commit and push any changes.");
|
|
32
|
-
if (wantsJournal)
|
|
33
|
-
steps.push(buildShepherdJournalInstruction(prReference));
|
|
34
|
-
return steps;
|
|
13
|
+
return [`${push} ${playbookPointer(FIX_CODE_LOOP_PLAYBOOK)} CLI: \`${cli}\`.`];
|
|
35
14
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { blockedReasonFromRequirements } from "../../merge-status/requirements-format.mjs";
|
|
2
|
+
import { capList, moreSuffix } from "../../util/cap-list.mjs";
|
|
3
|
+
/** GitHub statuses for a check that has not started running yet. */
|
|
4
|
+
const QUEUED_STATUSES = new Set(["QUEUED", "WAITING", "PENDING", "REQUESTED"]);
|
|
2
5
|
export function buildSummary(report) {
|
|
6
|
+
const queued = report.checks.inProgress.filter((c) => QUEUED_STATUSES.has(c.status)).length;
|
|
3
7
|
return {
|
|
4
8
|
passing: report.checks.passing.length,
|
|
5
9
|
skipped: report.checks.skipped.length,
|
|
6
10
|
filtered: report.checks.filtered.length,
|
|
7
|
-
inProgress: report.checks.inProgress.length,
|
|
11
|
+
inProgress: report.checks.inProgress.length - queued,
|
|
12
|
+
queued,
|
|
8
13
|
superseded: report.checks.supersededNames?.length ?? 0,
|
|
9
14
|
};
|
|
10
15
|
}
|
|
@@ -111,13 +116,12 @@ export function buildWaitLog(base) {
|
|
|
111
116
|
const counts = [
|
|
112
117
|
...(summary.passing > 0 ? [`${summary.passing} passing`] : []),
|
|
113
118
|
...(summary.inProgress > 0 ? [`${summary.inProgress} in-progress`] : []),
|
|
119
|
+
...(summary.queued > 0 ? [`${summary.queued} queued`] : []),
|
|
114
120
|
];
|
|
115
121
|
const parts = counts.length > 0 ? [counts.join(", ")] : [];
|
|
116
122
|
if ((base.inProgressChecks?.length ?? 0) > 0) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.map((c) => c.name)
|
|
120
|
-
.join(", ")}`);
|
|
123
|
+
const { shown, hidden } = capList(base.inProgressChecks);
|
|
124
|
+
parts.push(`active checks: ${shown.map((c) => c.name).join(", ")}${moreSuffix(hidden)}`);
|
|
121
125
|
}
|
|
122
126
|
switch (base.mergeStatus) {
|
|
123
127
|
case "BLOCKED": {
|
|
@@ -9,12 +9,13 @@ import { classifyReviewSummaries } from "./classify.mjs";
|
|
|
9
9
|
import { applyStallGuard } from "./stall.mjs";
|
|
10
10
|
import { clearStallState } from "../../state/iterate-stall.mjs";
|
|
11
11
|
import { handleFixCode } from "./fix-code.mjs";
|
|
12
|
-
import {
|
|
12
|
+
import { normalizeBotUsernames } from "../../comments/authors.mjs";
|
|
13
13
|
import { autoMinimizeComments } from "../../comments/resolve.mjs";
|
|
14
14
|
import { hasCheckDrivenActionableWork } from "../check-annotations.mjs";
|
|
15
15
|
import { buildReadyMergeOutcome, handleActiveMergeState } from "./merge-state.mjs";
|
|
16
16
|
import { buildIterateBase } from "./base.mjs";
|
|
17
17
|
import { markReadyIfAuthorized } from "./mark-ready.mjs";
|
|
18
|
+
import { classifyItem, loadSeenMap } from "../../state/seen-comments.mjs";
|
|
18
19
|
import { withIterateApiUsage } from "./run.mjs";
|
|
19
20
|
import { fetchRawSummaryPr } from "../../github/poll-summary.mjs";
|
|
20
21
|
import { summarizePollSummaryPr } from "../../github/poll-summary-projector.mjs";
|
|
@@ -29,10 +30,35 @@ import { buildUnreportedFixResult, planUnreportedRequired } from "./unreported-r
|
|
|
29
30
|
import { createCheckExecutionContext } from "../check-execution-context.mjs";
|
|
30
31
|
import { transportReadinessGaps } from "../../github/transport-evidence.mjs";
|
|
31
32
|
import { buildEscalateHumanMessage } from "./escalate.mjs";
|
|
33
|
+
import { markEditedCommentsSeen } from "./edited-comment-markers.mjs";
|
|
32
34
|
export function runIterate(opts) {
|
|
33
|
-
return withIterateApiUsage(opts, () =>
|
|
35
|
+
return withIterateApiUsage(opts, async () => {
|
|
36
|
+
const tick = {};
|
|
37
|
+
return attachEditedComments(await runIterateCore(opts, tick), tick, opts);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Edited PR comments are not readiness work. Hold them while the PR waits or counts down, and show
|
|
41
|
+
// each once on any other action (including MERGE after the ready delay), writing its marker then.
|
|
42
|
+
// The poll loop defers the write to the result it returns, since it can discard or refresh a tick.
|
|
43
|
+
// A replayed cached report can carry edits already shown, so re-check the current markers.
|
|
44
|
+
async function attachEditedComments(result, tick, opts) {
|
|
45
|
+
const candidates = tick.report?.comments.edited ?? [];
|
|
46
|
+
if (candidates.length === 0 || result.action === "wait" || result.action === "ready")
|
|
47
|
+
return result;
|
|
48
|
+
if (result.action === "cancel" && (result.reason === "merged" || result.reason === "closed"))
|
|
49
|
+
return result;
|
|
50
|
+
const [owner, repo] = result.repo.split("/");
|
|
51
|
+
if (!owner || !repo)
|
|
52
|
+
return result;
|
|
53
|
+
const key = { owner, repo, pr: result.pr };
|
|
54
|
+
const seen = await loadSeenMap(key);
|
|
55
|
+
const edited = candidates.filter((c) => classifyItem(c.id, c.body, seen) === "edited");
|
|
56
|
+
if (edited.length === 0)
|
|
57
|
+
return result;
|
|
58
|
+
const withEdits = { ...result, editedComments: edited };
|
|
59
|
+
return opts.deferEditedCommentMarkers ? withEdits : markEditedCommentsSeen(withEdits);
|
|
34
60
|
}
|
|
35
|
-
async function runIterateCore(opts) {
|
|
61
|
+
async function runIterateCore(opts, tick) {
|
|
36
62
|
const config = loadConfig();
|
|
37
63
|
const botUsernames = normalizeBotUsernames(config.botUsernames);
|
|
38
64
|
const readyDelaySeconds = opts.readyDelaySeconds ?? config.watch.readyDelayMinutes * 60;
|
|
@@ -51,7 +77,9 @@ async function runIterateCore(opts) {
|
|
|
51
77
|
// compatibility key in the config loader.
|
|
52
78
|
autoResolve: true,
|
|
53
79
|
autoMinimizeSuppressed: config.actions.autoMinimizeSuppressed,
|
|
80
|
+
reviewOutputMaxChars: config.iterate.reviewOutputMaxChars,
|
|
54
81
|
}, checkContext);
|
|
82
|
+
tick.report = report;
|
|
55
83
|
const [repoOwner, repoName] = report.repo.split("/");
|
|
56
84
|
if (!repoOwner || !repoName) {
|
|
57
85
|
throw new ShepherdError(`Unexpected repo format: "${report.repo}" (expected "owner/name")`, EXIT.DATAERR);
|
|
@@ -79,7 +107,11 @@ async function runIterateCore(opts) {
|
|
|
79
107
|
firstLook: report.firstLookSummaries,
|
|
80
108
|
seen: report.reviewSummaries,
|
|
81
109
|
edited: report.editedSummaries,
|
|
82
|
-
}, report.approvedReviews, config.iterate.minimizeApprovals, config.iterate.minimizeComments, botUsernames, [
|
|
110
|
+
}, report.approvedReviews, config.iterate.minimizeApprovals, config.iterate.minimizeComments, botUsernames, [
|
|
111
|
+
...report.threads.actionable,
|
|
112
|
+
...report.threads.resolutionOnly,
|
|
113
|
+
...(report.heldThreadReviewIds ?? []).map((reviewId) => ({ reviewId })),
|
|
114
|
+
], report.ruleAutoResolveReviewSummaryIds);
|
|
83
115
|
// Minimize already-seen review summaries in-process so they never become agent-facing work.
|
|
84
116
|
// GitHub can still return a null/error/rate-limit result per ID without
|
|
85
117
|
// throwing (autoMinimizeComments reports this via `errors`, not a rejection);
|
|
@@ -94,20 +126,16 @@ async function runIterateCore(opts) {
|
|
|
94
126
|
if (unminimized.length > 0)
|
|
95
127
|
reviewSummaryIds = [...reviewSummaryIds, ...unminimized];
|
|
96
128
|
}
|
|
97
|
-
// A first look at an already-minimized comment is only an acknowledgment.
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
comment.edited === true &&
|
|
104
|
-
!isConfiguredBotAuthor(comment, botUsernames));
|
|
105
|
-
const hasReadinessWork = report.threads.actionable.length > 0 ||
|
|
129
|
+
// A first look at an already-minimized comment is only an acknowledgment. Edited PR comments
|
|
130
|
+
// (report.comments.edited) are neither: they never block readiness or restart the ready delay.
|
|
131
|
+
// Review items held back by iterate.reviewOutputMaxChars keep the PR out of ready/merge: a
|
|
132
|
+
// capped tick that shows only first-look comments still counts, since held work follows.
|
|
133
|
+
const hasReadinessWork = (report.reviewItems !== undefined && report.comments.firstLook.length > 0) ||
|
|
134
|
+
report.threads.actionable.length > 0 ||
|
|
106
135
|
report.threads.resolutionOnly.length > 0 ||
|
|
107
136
|
report.threads.firstLook.length > 0 ||
|
|
108
137
|
(report.threads.ruleAutoResolveIds?.length ?? 0) > 0 ||
|
|
109
138
|
report.comments.actionable.length > 0 ||
|
|
110
|
-
editedMinimizedFeedback ||
|
|
111
139
|
(report.comments.minimizeIds?.length ?? 0) > 0 ||
|
|
112
140
|
report.changesRequestedReviews.length > 0 ||
|
|
113
141
|
hasCheckDrivenActionableWork(reportForWork.checks, report.mergeStatus.status) ||
|
|
@@ -128,7 +156,7 @@ async function runIterateCore(opts) {
|
|
|
128
156
|
!hasReadinessWork &&
|
|
129
157
|
!activeMerge &&
|
|
130
158
|
staleAncestry === null;
|
|
131
|
-
const receiptCurrent = await revalidateReadyReceipt(receiptKey, report, hasReadinessWork,
|
|
159
|
+
const receiptCurrent = await revalidateReadyReceipt(receiptKey, report, hasReadinessWork, checkContext);
|
|
132
160
|
const headSha = report.headSha ?? "unknown";
|
|
133
161
|
// The elapsed marker survives a hidden-comment acknowledgement tick and is
|
|
134
162
|
// consumed only when this tick cancels or merges. A receipt that is still
|
|
@@ -400,7 +428,7 @@ async function recordReadyReceipt(key, report, context) {
|
|
|
400
428
|
}
|
|
401
429
|
}
|
|
402
430
|
/** Clear a stale READY receipt; return whether a current receipt remains. */
|
|
403
|
-
async function revalidateReadyReceipt(key, report, hasReadinessWork,
|
|
431
|
+
async function revalidateReadyReceipt(key, report, hasReadinessWork, context) {
|
|
404
432
|
const receipt = await readReadyReceipt(key);
|
|
405
433
|
if (!receipt)
|
|
406
434
|
return false;
|
|
@@ -413,7 +441,6 @@ async function revalidateReadyReceipt(key, report, hasReadinessWork, editedMinim
|
|
|
413
441
|
!report.headSha ||
|
|
414
442
|
!report.baseRefOid ||
|
|
415
443
|
transportReadinessGaps(report, report.mergeStatus.mergeStateStatus).length > 0 ||
|
|
416
|
-
editedMinimizedFeedback ||
|
|
417
444
|
(!retainQueuedReceipt && (report.status !== "READY" || hasReadinessWork))) {
|
|
418
445
|
await clearReadyReceipt(key);
|
|
419
446
|
return false;
|
|
@@ -8,6 +8,6 @@ import type { InstructionStyle } from "../../config/load.mts";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function renderResolveCommand(rc: ResolveCommand): string;
|
|
10
10
|
export declare function buildFixInstructions(threads: AgentThread[], actionableComments: AgentComment[], checks: AgentCheck[], changesRequestedReviews: Review[], baseBranch: string, resolveCommand: ResolveCommand, hasConflicts: boolean, prReference: string | number, _cancelledCount: number, firstLookThreads?: FirstLookThread[], firstLookComments?: FirstLookComment[], firstLookSummaries?: Review[], editedSummaries?: Review[], _inProgressRunIds?: string[], resolutionOnlyThreads?: ReviewThread[], resolveOnlyCommand?: ResolveCommand, behindBaseHint?: string, // iterate.behindBaseHint — see buildBehindBaseHintInstruction
|
|
11
|
-
isBehind?: boolean,
|
|
11
|
+
isBehind?: boolean, _viewerCanUpdate?: boolean, hasExhaustedWorkflowRerun?: boolean, stackRebase?: string, // native stack layers rebase with gh-stack, not branch by branch
|
|
12
12
|
queueEjection?: QueueEjectionRecovery, // current merge-queue removal with failing queue checks
|
|
13
13
|
instructionStyle?: InstructionStyle): string[];
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { renderShellCommand } from "../../cli/runner.mjs";
|
|
1
|
+
import { buildPrShepherdCommand, renderShellCommand } from "../../cli/runner.mjs";
|
|
2
2
|
import { buildFailingCheckInstructions, buildCrStaleClause, buildBehindBaseHintInstruction, buildRepeatedWorkflowBranchRecoveryInstructions, buildResolveCommandInstruction, buildFixCompletionInstruction, } from "./check-instructions.mjs";
|
|
3
|
-
import { SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE } from "../shepherd-journal.mjs";
|
|
4
3
|
import { isFailingAgentCheck } from "../../checks/conclusions.mjs";
|
|
5
4
|
import { buildCommitSuggestionInstruction } from "../commit-suggestion-instruction.mjs";
|
|
6
5
|
import { partitionFixThreads } from "./fix-instruction-threads.mjs";
|
|
7
6
|
import { buildBranchPushInstruction, buildConflictInstruction } from "./native-stack-rebase.mjs";
|
|
8
7
|
import { queueEjectionSteps } from "./queue-recovery-instructions.mjs";
|
|
9
|
-
import {
|
|
8
|
+
import { buildPushSteps } from "./fix-loop-instruction.mjs";
|
|
10
9
|
/**
|
|
11
10
|
* Render a resolve command as a shell snippet. Appends `--require-sha HEAD` when set: run after
|
|
12
11
|
* the push step, the local HEAD is the pushed PR head, and `apply review` rejects the mutations
|
|
@@ -18,7 +17,7 @@ export function renderResolveCommand(rc) {
|
|
|
18
17
|
}
|
|
19
18
|
const REQUIRE_HEAD_SHA = "--require-sha HEAD";
|
|
20
19
|
export function buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseBranch, resolveCommand, hasConflicts, prReference, _cancelledCount, firstLookThreads = [], firstLookComments = [], firstLookSummaries = [], editedSummaries = [], _inProgressRunIds = [], resolutionOnlyThreads = [], resolveOnlyCommand, behindBaseHint = "", // iterate.behindBaseHint — see buildBehindBaseHintInstruction
|
|
21
|
-
isBehind = false,
|
|
20
|
+
isBehind = false, _viewerCanUpdate = false, hasExhaustedWorkflowRerun = false, stackRebase, // native stack layers rebase with gh-stack, not branch by branch
|
|
22
21
|
queueEjection, // current merge-queue removal with failing queue checks
|
|
23
22
|
instructionStyle = "inline") {
|
|
24
23
|
const instructions = [];
|
|
@@ -48,12 +47,9 @@ instructionStyle = "inline") {
|
|
|
48
47
|
const firstLookTotal = firstLookThreads.length + firstLookComments.length;
|
|
49
48
|
if (firstLookTotal > 0)
|
|
50
49
|
instructions.push("Review every item under `## First-look items` before acting.");
|
|
51
|
-
if (firstLookSummaries.length > 0
|
|
52
|
-
instructions.push(
|
|
53
|
-
const editedTotal = editedSummaries.length +
|
|
54
|
-
actionableComments.filter((c) => c.edited).length +
|
|
55
|
-
firstLookThreads.filter((t) => t.edited).length +
|
|
56
|
-
firstLookComments.filter((c) => c.edited).length;
|
|
50
|
+
if (firstLookSummaries.length > 0)
|
|
51
|
+
instructions.push("Read each body under `## Review summaries (first look)` before acting.");
|
|
52
|
+
const editedTotal = editedSummaries.length + firstLookThreads.filter((t) => t.edited).length;
|
|
57
53
|
if (editedTotal > 0)
|
|
58
54
|
instructions.push("Read every item marked `[edited since first look]`, including edited summaries and edited first-look bullets, before deciding whether to resolve a matching thread.");
|
|
59
55
|
if (unlocatedThreads.length > 0)
|
|
@@ -72,19 +68,24 @@ instructionStyle = "inline") {
|
|
|
72
68
|
instructions.push(staleClause);
|
|
73
69
|
if (!hintWithConflictStep)
|
|
74
70
|
instructions.push(...branchUpdateHint);
|
|
71
|
+
const hasReviewFeedback = threads.length > 0 ||
|
|
72
|
+
resolutionOnlyThreads.length > 0 ||
|
|
73
|
+
firstLookThreads.length > 0 ||
|
|
74
|
+
changesRequestedReviews.length > 0 ||
|
|
75
|
+
actionableComments.length > 0 ||
|
|
76
|
+
firstLookSummaries.length > 0 ||
|
|
77
|
+
editedSummaries.length > 0;
|
|
78
|
+
if (hasReviewFeedback) {
|
|
79
|
+
const journalCommand = buildPrShepherdCommand(["apply", "journal", String(prReference)]).text;
|
|
80
|
+
instructions.push(`Optional: If you reject a code review suggestion, journal why: \`${journalCommand} '- <reason>'\`.`);
|
|
81
|
+
}
|
|
75
82
|
const hasReviewMutations = resolveCommand.hasMutations || resolveOnlyCommand?.hasMutations === true;
|
|
76
83
|
const mutationSuffix = hasReviewMutations ? " before review mutations" : "";
|
|
77
84
|
const genericPush = !(branchRecovery || queueEjection) && hasNonConflictHints;
|
|
78
|
-
const wantsJournal = viewerCanUpdate &&
|
|
79
|
-
(hasReviewMutations ||
|
|
80
|
-
hasNonConflictHints ||
|
|
81
|
-
firstLookTotal > 0 ||
|
|
82
|
-
firstLookSummaries.length > 0 ||
|
|
83
|
-
editedTotal > 0);
|
|
84
85
|
if (branchRecovery || queueEjection) {
|
|
85
86
|
instructions.push(buildBranchPushInstruction(stackRebase, hasConflicts, mutationSuffix, !branchRecovery));
|
|
86
87
|
}
|
|
87
|
-
instructions.push(...
|
|
88
|
+
instructions.push(...buildPushSteps(instructionStyle, genericPush));
|
|
88
89
|
if (resolveOnlyCommand?.hasMutations)
|
|
89
90
|
instructions.push(`Run: \`${renderResolveCommand(resolveOnlyCommand)}\``);
|
|
90
91
|
instructions.push(...buildResolveCommandInstruction(resolveCommand, renderResolveCommand(resolveCommand)), buildFixCompletionInstruction());
|
|
@@ -27,7 +27,7 @@ export function onePrCancelFromPulls(explicit, repoSlug, pulls) {
|
|
|
27
27
|
isDraft: pull.draft,
|
|
28
28
|
shouldCancel: true,
|
|
29
29
|
remainingSeconds: 0,
|
|
30
|
-
summary: { passing: 0, skipped: 0, filtered: 0, inProgress: 0, superseded: 0 },
|
|
30
|
+
summary: { passing: 0, skipped: 0, filtered: 0, inProgress: 0, queued: 0, superseded: 0 },
|
|
31
31
|
baseBranch: pull.baseRefName,
|
|
32
32
|
branchProtection: null,
|
|
33
33
|
checks: [],
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seen-state options for one poll tick. The loop can discard or refresh a tick's result, so
|
|
3
|
+
* edited-comment markers are written only for the result it returns.
|
|
4
|
+
*/
|
|
5
|
+
export declare function pollTickSeenOptions(debounceSeconds: number, pastDebounce: boolean): {
|
|
6
|
+
persistSeen: boolean;
|
|
7
|
+
deferEditedCommentMarkers: boolean;
|
|
8
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seen-state options for one poll tick. The loop can discard or refresh a tick's result, so
|
|
3
|
+
* edited-comment markers are written only for the result it returns.
|
|
4
|
+
*/
|
|
5
|
+
export function pollTickSeenOptions(debounceSeconds, pastDebounce) {
|
|
6
|
+
return { persistSeen: debounceSeconds === 0 || pastDebounce, deferEditedCommentMarkers: true };
|
|
7
|
+
}
|
package/bin/commands/poll.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { runIterate } from "./iterate/index.mjs";
|
|
2
|
+
import { pollTickSeenOptions } from "./poll-seen-options.mjs";
|
|
3
|
+
import { markEditedCommentsSeen } from "./iterate/edited-comment-markers.mjs";
|
|
2
4
|
import { sleep } from "../util/sleep.mjs";
|
|
3
5
|
import { withPollApiUsage } from "./poll-run.mjs";
|
|
4
6
|
import { loadConfig } from "../config/load.mjs";
|
|
@@ -26,6 +28,7 @@ async function runPollCore(opts) {
|
|
|
26
28
|
const verbose = opts.verbose === true;
|
|
27
29
|
const quietStatus = quietStatusOpt === true;
|
|
28
30
|
const untilTerminal = untilTerminalOpt === true;
|
|
31
|
+
const swallowsMarkReady = untilTerminal || iterateOpts.merge === true;
|
|
29
32
|
let lastWaitSignature = null;
|
|
30
33
|
let pendingQuotaWarning;
|
|
31
34
|
let prNumber = opts.prNumber;
|
|
@@ -34,6 +37,7 @@ async function runPollCore(opts) {
|
|
|
34
37
|
while (true) {
|
|
35
38
|
tick += 1;
|
|
36
39
|
const pastDebounce = debounceUntil !== null && Date.now() >= debounceUntil;
|
|
40
|
+
const seenOptions = pollTickSeenOptions(debounceSeconds, pastDebounce);
|
|
37
41
|
const remainingBefore = untilTerminal
|
|
38
42
|
? Number.POSITIVE_INFINITY
|
|
39
43
|
: timeoutMs - (Date.now() - start);
|
|
@@ -41,7 +45,7 @@ async function runPollCore(opts) {
|
|
|
41
45
|
const iterateTick = (fingerprintCache) => runIterate({
|
|
42
46
|
...iterateOpts,
|
|
43
47
|
prNumber,
|
|
44
|
-
|
|
48
|
+
...seenOptions,
|
|
45
49
|
fingerprintCache,
|
|
46
50
|
deferQuotaWarning: !untilTerminal,
|
|
47
51
|
quotaWarningMinimumPollIntervalMinutes: intervalSeconds / 60,
|
|
@@ -123,9 +127,7 @@ async function runPollCore(opts) {
|
|
|
123
127
|
await sleep(sleepMs);
|
|
124
128
|
continue;
|
|
125
129
|
}
|
|
126
|
-
if (
|
|
127
|
-
lastResult.action === "mark_ready" &&
|
|
128
|
-
!pastDebounce) {
|
|
130
|
+
if (swallowsMarkReady && lastResult.action === "mark_ready" && !pastDebounce) {
|
|
129
131
|
debounceUntil = null;
|
|
130
132
|
const elapsedMs = Date.now() - start;
|
|
131
133
|
const sleepMs = quotaPollIntervalMs(quotaBands, lastResult.apiUsage, intervalMs, MAX_TIMER_MS, getGithubTransport());
|
|
@@ -153,5 +155,5 @@ async function runPollCore(opts) {
|
|
|
153
155
|
await refreshIfReturning();
|
|
154
156
|
break;
|
|
155
157
|
}
|
|
156
|
-
return lastResult;
|
|
158
|
+
return markEditedCommentsSeen(lastResult);
|
|
157
159
|
}
|