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
|
@@ -19,3 +19,8 @@ export declare function hydrateReadyAnnotationProbe(pr: RawSummaryPr, repo: Repo
|
|
|
19
19
|
actionable?: number;
|
|
20
20
|
incomplete?: true;
|
|
21
21
|
}): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Load annotation totals once per summary row, from the same probe the READY receipt
|
|
24
|
+
* hash uses. False when a page could not be read, so no total is trustworthy.
|
|
25
|
+
*/
|
|
26
|
+
export declare function hydrateAnnotationTotals(pr: RawSummaryPr, repo: RepoInfo): Promise<boolean>;
|
|
@@ -7,6 +7,7 @@ import { fingerprintRawSummaryPr } from "./poll-summary-fingerprint.mjs";
|
|
|
7
7
|
import { POLL_SUMMARY_ANNOTATION_PROBE_QUERY } from "./queries.mjs";
|
|
8
8
|
const PROBE_UNAVAILABLE = Symbol.for("prShepherd.annotationProbeUnavailable");
|
|
9
9
|
const COMPLETE_FROM_BATCH = new WeakSet();
|
|
10
|
+
const HYDRATED = new WeakSet();
|
|
10
11
|
/** Annotation totals copied from the same full-snapshot request; no follow-up probe is needed. */
|
|
11
12
|
export function markReadyAnnotationProbeComplete(pr) {
|
|
12
13
|
COMPLETE_FROM_BATCH.add(pr);
|
|
@@ -36,9 +37,22 @@ export async function hydrateReadyAnnotationProbe(pr, repo, review) {
|
|
|
36
37
|
const checks = summarizePollSummaryChecks(pr);
|
|
37
38
|
if (!isCurrentSummaryReady(pr, checks, review))
|
|
38
39
|
return;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
await hydrateAnnotationTotals(pr, repo);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load annotation totals once per summary row, from the same probe the READY receipt
|
|
44
|
+
* hash uses. False when a page could not be read, so no total is trustworthy.
|
|
45
|
+
*/
|
|
46
|
+
export async function hydrateAnnotationTotals(pr, repo) {
|
|
47
|
+
if (COMPLETE_FROM_BATCH.has(pr))
|
|
48
|
+
return true;
|
|
49
|
+
if (HYDRATED.has(pr))
|
|
50
|
+
return !annotationProbeUnavailable(pr);
|
|
51
|
+
HYDRATED.add(pr);
|
|
52
|
+
if (await hydrateCommitAnnotations(pr, repo))
|
|
53
|
+
return true;
|
|
54
|
+
pr[PROBE_UNAVAILABLE] = true;
|
|
55
|
+
return false;
|
|
42
56
|
}
|
|
43
57
|
async function hydrateCommitAnnotations(pr, repo) {
|
|
44
58
|
const commit = pr.commits.nodes[0]?.commit;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PollSummaryChecks, PollSummaryItem } from "../types.mts";
|
|
2
|
+
import type { RepoInfo } from "./client.mts";
|
|
3
|
+
import type { RawSummaryPr } from "./poll-summary-raw.mts";
|
|
4
|
+
type Routed = Pick<PollSummaryItem, "action" | "reasons">;
|
|
5
|
+
/**
|
|
6
|
+
* The one-PR check treats unseen annotations on any non-SUCCESS check (failing, skipped,
|
|
7
|
+
* filtered, ignored or neutral) as actionable work. The compact summary only has annotation
|
|
8
|
+
* totals, and only for a row that otherwise claims readiness, so it loads them then and reads
|
|
9
|
+
* the annotations of just the non-SUCCESS checks that have any. A row with unseen annotations,
|
|
10
|
+
* or whose annotations cannot be read, goes to its one-PR `pollCommand` instead of
|
|
11
|
+
* claiming `appears-ready`, `cancel`, `mark_ready` or `merge`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyUnseenCheckAnnotations(raw: RawSummaryPr, repo: RepoInfo, routed: Routed, checks: PollSummaryChecks, seen: ReadonlyMap<string, unknown>, required: readonly string[]): Promise<Routed>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { pollRateLimitRetryAfterMs } from "../commands/poll-quota.mjs";
|
|
2
|
+
import { fetchCheckRunAnnotationsBatch } from "./check-annotations-batch.mjs";
|
|
3
|
+
import { hydrateAnnotationTotals } from "./poll-summary-annotation-probe.mjs";
|
|
4
|
+
import { classifiedSummaryChecks } from "./poll-summary-checks.mjs";
|
|
5
|
+
/** Routes whose action claims the row has nothing left to do. */
|
|
6
|
+
const READY_CLAIMS = new Set([
|
|
7
|
+
"appears-ready",
|
|
8
|
+
"draft-appears-ready",
|
|
9
|
+
"already-in-merge-queue",
|
|
10
|
+
"already-auto-merging",
|
|
11
|
+
]);
|
|
12
|
+
/**
|
|
13
|
+
* The one-PR check treats unseen annotations on any non-SUCCESS check (failing, skipped,
|
|
14
|
+
* filtered, ignored or neutral) as actionable work. The compact summary only has annotation
|
|
15
|
+
* totals, and only for a row that otherwise claims readiness, so it loads them then and reads
|
|
16
|
+
* the annotations of just the non-SUCCESS checks that have any. A row with unseen annotations,
|
|
17
|
+
* or whose annotations cannot be read, goes to its one-PR `pollCommand` instead of
|
|
18
|
+
* claiming `appears-ready`, `cancel`, `mark_ready` or `merge`.
|
|
19
|
+
*/
|
|
20
|
+
export async function applyUnseenCheckAnnotations(raw, repo, routed, checks, seen, required) {
|
|
21
|
+
if (!routed.reasons.some((reason) => READY_CLAIMS.has(reason)))
|
|
22
|
+
return routed;
|
|
23
|
+
// Successful checks never carry actionable annotations: skip the probe when none can.
|
|
24
|
+
if (nonSuccessCheckTotals(raw, required, false).size === 0)
|
|
25
|
+
return routed;
|
|
26
|
+
const totalsKnown = await hydrateAnnotationTotals(raw, repo);
|
|
27
|
+
const candidates = nonSuccessCheckTotals(raw, required, totalsKnown);
|
|
28
|
+
if (candidates.size === 0)
|
|
29
|
+
return routed;
|
|
30
|
+
const count = totalsKnown
|
|
31
|
+
? await countChecksWithUnseen(raw, repo, candidates, seen)
|
|
32
|
+
: candidates.size;
|
|
33
|
+
if (count === 0)
|
|
34
|
+
return routed;
|
|
35
|
+
checks.annotated = count;
|
|
36
|
+
return { action: "triage", reasons: ["check-annotations"] };
|
|
37
|
+
}
|
|
38
|
+
/** Completed, non-SUCCESS head checks with their annotation totals (all of them when totals are unknown). */
|
|
39
|
+
function nonSuccessCheckTotals(raw, required, totalsKnown) {
|
|
40
|
+
const head = new Map((raw.commits.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? []).flatMap((node) => node.__typename === "CheckRun" && node.id
|
|
41
|
+
? [[node.id, node.annotations?.totalCount]]
|
|
42
|
+
: []));
|
|
43
|
+
const totals = new Map();
|
|
44
|
+
for (const check of classifiedSummaryChecks(raw, required)) {
|
|
45
|
+
if (check.scope || check.id == null || !head.has(check.id))
|
|
46
|
+
continue;
|
|
47
|
+
if (check.category === "in_progress" || check.category === "superseded")
|
|
48
|
+
continue;
|
|
49
|
+
if (check.status !== "COMPLETED" || check.conclusion === "SUCCESS")
|
|
50
|
+
continue;
|
|
51
|
+
const total = head.get(check.id) ?? 0;
|
|
52
|
+
if (!totalsKnown || total > 0)
|
|
53
|
+
totals.set(check.id, total);
|
|
54
|
+
}
|
|
55
|
+
return totals;
|
|
56
|
+
}
|
|
57
|
+
async function countChecksWithUnseen(raw, repo, totals, seen) {
|
|
58
|
+
const ids = [...totals.keys()];
|
|
59
|
+
try {
|
|
60
|
+
const batch = await fetchCheckRunAnnotationsBatch(ids, {
|
|
61
|
+
stateKey: { owner: repo.owner, repo: repo.name, pr: raw.number },
|
|
62
|
+
headSha: raw.headRefOid,
|
|
63
|
+
expectedCounts: totals,
|
|
64
|
+
});
|
|
65
|
+
// A check whose annotations could not be read may still hold unseen work.
|
|
66
|
+
const unread = ids.filter((id) => !batch.annotations.has(id)).length;
|
|
67
|
+
const unseen = [...batch.annotations.values()].filter((list) => list.some((annotation) => !seen.has(annotation.id))).length;
|
|
68
|
+
return unread + unseen;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (pollRateLimitRetryAfterMs(error) !== null)
|
|
72
|
+
throw error;
|
|
73
|
+
return ids.length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { loadConfig } from "../config/load.mjs";
|
|
2
|
+
export function detectBlockingReviewer(raw) {
|
|
3
|
+
const prefixes = loadConfig()["mark-ready"]["blocking-reviewer-logins"].map((login) => login.toLowerCase());
|
|
4
|
+
// A team reviewer has a name, not a login, so it never matches a login prefix.
|
|
5
|
+
const matches = (author) => typeof author?.login === "string" &&
|
|
6
|
+
prefixes.some((prefix) => author.login.toLowerCase().startsWith(prefix));
|
|
7
|
+
return ((raw.reviewRequests?.nodes ?? []).some((request) => matches(request.requestedReviewer)) ||
|
|
8
|
+
(raw.latestReviews?.nodes ?? []).some((review) => review.state === "PENDING" && matches(review.author)));
|
|
9
|
+
}
|
|
@@ -7,7 +7,7 @@ import { failingSummaryCheckNames } from "./poll-summary-checks.mjs";
|
|
|
7
7
|
* does not start a one-PR session for a wait it would otherwise shepherd.
|
|
8
8
|
*/
|
|
9
9
|
export async function applyOpenCheckBlockers(raw, repo, routed, checks, review, stack, requiredContexts) {
|
|
10
|
-
if (routed.action !== "
|
|
10
|
+
if (routed.action !== "triage" || !routed.reasons.includes("failing-checks"))
|
|
11
11
|
return routed;
|
|
12
12
|
if ((review.actionable ?? 0) > 0 || checks.incomplete)
|
|
13
13
|
return routed;
|
|
@@ -3,3 +3,4 @@ import type { RawSummaryPr } from "./poll-summary-raw.mts";
|
|
|
3
3
|
export declare function summarizePollSummaryChecks(raw: RawSummaryPr, requiredContexts?: readonly string[]): PollSummaryChecks;
|
|
4
4
|
/** Names of failing checks in the loaded summary rollup. */
|
|
5
5
|
export declare function failingSummaryCheckNames(raw: RawSummaryPr, requiredContexts?: readonly string[]): string[];
|
|
6
|
+
export declare function classifiedSummaryChecks(raw: RawSummaryPr, requiredContexts?: readonly string[]): import("../types.mts").ClassifiedCheck[];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { classifyChecks } from "../checks/classify.mjs";
|
|
2
|
+
import { mergeStartupFailureChecks } from "../checks/startup-failures.mjs";
|
|
3
|
+
import { startupFailureChecksFromSuites } from "./batch-parse-suites.mjs";
|
|
2
4
|
import { parseCreatedAt } from "./batch-parser-helpers.mjs";
|
|
3
5
|
export function summarizePollSummaryChecks(raw, requiredContexts) {
|
|
4
6
|
const rollups = summaryRollups(raw);
|
|
@@ -19,6 +21,11 @@ export function summarizePollSummaryChecks(raw, requiredContexts) {
|
|
|
19
21
|
if (rollups.some(({ rollup }) => rollup.contexts.pageInfo.hasPreviousPage)) {
|
|
20
22
|
summary.incomplete = true;
|
|
21
23
|
}
|
|
24
|
+
// The first 50 check suites are all the summary reads. The one-PR check lists the
|
|
25
|
+
// remaining workflow runs for startup failures; here an unread suite stays unverified.
|
|
26
|
+
if (raw.commits.nodes[0]?.commit.checkSuites?.pageInfo?.hasNextPage === true) {
|
|
27
|
+
summary.incomplete = true;
|
|
28
|
+
}
|
|
22
29
|
return summary;
|
|
23
30
|
}
|
|
24
31
|
/** Names of failing checks in the loaded summary rollup. */
|
|
@@ -36,13 +43,24 @@ function summaryRollups(raw) {
|
|
|
36
43
|
},
|
|
37
44
|
].flatMap(({ rollup, scope }) => (rollup ? [{ rollup, scope }] : []));
|
|
38
45
|
}
|
|
39
|
-
function classifiedSummaryChecks(raw, requiredContexts) {
|
|
46
|
+
export function classifiedSummaryChecks(raw, requiredContexts) {
|
|
40
47
|
// A successful check on the queue commit cannot cover a PR-head cancellation, or vice versa.
|
|
41
|
-
|
|
48
|
+
const groups = summaryRollups(raw).map(({ rollup, scope }) => ({
|
|
49
|
+
runs: checkRuns(rollup, scope),
|
|
50
|
+
scope,
|
|
51
|
+
}));
|
|
52
|
+
if (!groups.some(({ scope }) => scope === undefined))
|
|
53
|
+
groups.unshift({ runs: [], scope: undefined });
|
|
54
|
+
return groups.flatMap(({ runs, scope }) => classifyChecks(scope ? runs : withStartupFailures(raw, runs), {
|
|
42
55
|
...(scope === "merge_group" && { additionalRelevantEvents: ["merge_group"] }),
|
|
43
56
|
...(requiredContexts && { requiredContexts }),
|
|
44
57
|
}));
|
|
45
58
|
}
|
|
59
|
+
/** A workflow run that never started has no check run; the one-PR check synthesizes it as failing. */
|
|
60
|
+
function withStartupFailures(raw, runs) {
|
|
61
|
+
const suites = raw.commits.nodes[0]?.commit.checkSuites;
|
|
62
|
+
return mergeStartupFailureChecks(runs, startupFailureChecksFromSuites(suites));
|
|
63
|
+
}
|
|
46
64
|
function checkRuns(rollup, scope) {
|
|
47
65
|
return rollup.contexts.nodes.map((context) => {
|
|
48
66
|
if (context.__typename === "StatusContext") {
|
|
@@ -2,11 +2,15 @@ import { loadConfig } from "../config/load.mjs";
|
|
|
2
2
|
import { formatPrUrl } from "../pr-reference.mjs";
|
|
3
3
|
import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
4
4
|
export function pollCommandFields(repo, pr, isDraft, opts) {
|
|
5
|
-
const autoMarkReadyDisabled = opts.noAutoMarkReady || loadConfig().
|
|
5
|
+
const autoMarkReadyDisabled = opts.noAutoMarkReady || loadConfig()["mark-ready"].auto === false;
|
|
6
6
|
const boundedDraft = isDraft && autoMarkReadyDisabled;
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const url = formatPrUrl(repo, pr);
|
|
8
|
+
// A single tick routes each PR to one `iterate` tick rather than a sleeping poll.
|
|
9
|
+
const args = opts.tick
|
|
10
|
+
? ["iterate", url, ...(boundedDraft ? ["--no-auto-mark-ready"] : [])]
|
|
11
|
+
: boundedDraft
|
|
12
|
+
? [url, "--timeout", "1s", "--debounce", "0s", "--no-auto-mark-ready"]
|
|
13
|
+
: [url, "--until-terminal"];
|
|
10
14
|
if (opts.merge && opts.stackPrNumber === undefined)
|
|
11
15
|
args.push("--merge");
|
|
12
16
|
if (opts.readyDelaySeconds !== undefined)
|
|
@@ -15,8 +19,6 @@ export function pollCommandFields(repo, pr, isDraft, opts) {
|
|
|
15
19
|
args.push("--stall-timeout", `${opts.stallTimeoutSeconds}s`);
|
|
16
20
|
if (opts.instructions)
|
|
17
21
|
args.push("--instructions", opts.instructions);
|
|
18
|
-
if (opts.pollMode === "event")
|
|
19
|
-
args.push("--poll-mode", "event");
|
|
20
22
|
if (opts.noAutoMarkReady && !boundedDraft)
|
|
21
23
|
args.push("--no-auto-mark-ready");
|
|
22
24
|
const pollCommand = buildPrShepherdCommand(args).text;
|
|
@@ -4,5 +4,12 @@ import type { RawSummaryPr } from "./poll-summary-raw.mts";
|
|
|
4
4
|
* completion and stack reconciliation. Queue entry, queue checks, and GitHub's
|
|
5
5
|
* computed merge state are checked separately: including them here would
|
|
6
6
|
* invalidate a READY receipt merely because a ready PR entered the queue.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately conservative: every comment, review and thread node and the ignored
|
|
9
|
+
* checks are hashed without applying classification rules. A rule-suppressed bot
|
|
10
|
+
* comment or an ignored-check change therefore invalidates a receipt, but a miss only
|
|
11
|
+
* costs one more one-PR session (`no-ready-receipt`), whereas a hash that skipped
|
|
12
|
+
* evidence could certify a state the one-PR check never examined. Do not narrow this
|
|
13
|
+
* to post-rule state unless the one-PR check hashes the same projection.
|
|
7
14
|
*/
|
|
8
15
|
export declare function fingerprintRawSummaryPr(summary: RawSummaryPr): string | null;
|
|
@@ -5,6 +5,13 @@ import { withoutHandoffFields } from "./poll-summary-handoff-fields.mjs";
|
|
|
5
5
|
* completion and stack reconciliation. Queue entry, queue checks, and GitHub's
|
|
6
6
|
* computed merge state are checked separately: including them here would
|
|
7
7
|
* invalidate a READY receipt merely because a ready PR entered the queue.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately conservative: every comment, review and thread node and the ignored
|
|
10
|
+
* checks are hashed without applying classification rules. A rule-suppressed bot
|
|
11
|
+
* comment or an ignored-check change therefore invalidates a receipt, but a miss only
|
|
12
|
+
* costs one more one-PR session (`no-ready-receipt`), whereas a hash that skipped
|
|
13
|
+
* evidence could certify a state the one-PR check never examined. Do not narrow this
|
|
14
|
+
* to post-rule state unless the one-PR check hashes the same projection.
|
|
8
15
|
*/
|
|
9
16
|
export function fingerprintRawSummaryPr(summary) {
|
|
10
17
|
const raw = withoutHandoffFields(summary);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { PollSummaryCommandOptions, PollSummaryItem } from "../types.mts";
|
|
2
2
|
import type { RepoInfo } from "./client.mts";
|
|
3
3
|
import type { RawSummaryPr } from "./poll-summary-raw.mts";
|
|
4
|
-
export declare function summarizePollSummaryPr(raw: RawSummaryPr, repo: RepoInfo, opts: PollSummaryCommandOptions, viewerCanAdminister?: boolean | undefined, mergeTargetContexts?: readonly string[], viewerLogin?: string | null
|
|
4
|
+
export declare function summarizePollSummaryPr(raw: RawSummaryPr, repo: RepoInfo, opts: PollSummaryCommandOptions, viewerCanAdminister?: boolean | undefined, mergeTargetContexts?: readonly string[], viewerLogin?: string | null,
|
|
5
|
+
/** Aggregate polls only: apply rule-driven auto-resolve. Receipt and merge validation reads never mutate. */
|
|
6
|
+
applyRuleAutoResolve?: boolean): Promise<PollSummaryItem>;
|
|
@@ -5,8 +5,9 @@ import { formatPrUrl } from "../pr-reference.mjs";
|
|
|
5
5
|
import { loadSeenMap } from "../state/seen-comments.mjs";
|
|
6
6
|
import { isReadyReceiptCurrent, readReadyReceipt } from "../state/ready-receipts.mjs";
|
|
7
7
|
import { summarizePollSummaryChecks } from "./poll-summary-checks.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import { currentQueueRemovalEvent } from "./poll-summary-queue-removal.mjs";
|
|
8
|
+
import { evaluatePollSummaryReview } from "./poll-summary-review.mjs";
|
|
9
|
+
import { currentQueueRemovalEvent, projectQueueRemoval } from "./poll-summary-queue-removal.mjs";
|
|
10
|
+
import { applyUnseenCheckAnnotations } from "./poll-summary-annotations.mjs";
|
|
10
11
|
import { isCurrentSummaryReady } from "./poll-summary-readiness.mjs";
|
|
11
12
|
import { applyOpenCheckBlockers } from "./poll-summary-check-blockers.mjs";
|
|
12
13
|
import { normalizePollSummaryState, routePollSummary } from "./poll-summary-route.mjs";
|
|
@@ -14,14 +15,19 @@ import { hydrateReadyAnnotationProbe, readyFingerprint } from "./poll-summary-an
|
|
|
14
15
|
import { applyUnreportedRequiredChecks, summaryRequiredContexts, } from "./poll-summary-unreported.mjs";
|
|
15
16
|
import { aliasThreadSeenMarkers } from "./rest-identities.mjs";
|
|
16
17
|
import { restQueueRequirement } from "./poll-summary-rest-queue.mjs";
|
|
17
|
-
|
|
18
|
+
import { detectBlockingReviewer } from "./poll-summary-blocking-reviewer.mjs";
|
|
19
|
+
export async function summarizePollSummaryPr(raw, repo, opts, viewerCanAdminister = undefined, mergeTargetContexts, viewerLogin = null,
|
|
20
|
+
/** Aggregate polls only: apply rule-driven auto-resolve. Receipt and merge validation reads never mutate. */
|
|
21
|
+
applyRuleAutoResolve = false) {
|
|
18
22
|
const repoName = `${repo.owner}/${repo.name}`;
|
|
19
23
|
const seen = await loadSeenMap({ owner: repo.owner, repo: repo.name, pr: raw.number });
|
|
20
24
|
await aliasThreadSeenMarkers(repo, raw.number, raw.reviewThreads.nodes.map(({ id }) => id), seen);
|
|
21
25
|
const required = summaryRequiredContexts(raw, mergeTargetContexts);
|
|
22
26
|
const checks = summarizePollSummaryChecks(raw, required);
|
|
23
27
|
applyUnreportedRequiredChecks(raw, checks, required);
|
|
24
|
-
const review = await
|
|
28
|
+
const { review, ruleAutoResolve } = await evaluatePollSummaryReview(raw, seen, viewerCanAdminister, applyRuleAutoResolve && normalizePollSummaryState(raw.state) === "OPEN"
|
|
29
|
+
? { owner: repo.owner, repo: repo.name, pr: raw.number }
|
|
30
|
+
: undefined);
|
|
25
31
|
const blockingReviewerInProgress = detectBlockingReviewer(raw);
|
|
26
32
|
const removalEvent = currentQueueRemovalEvent(raw);
|
|
27
33
|
let { action, reasons } = routePollSummary(raw, checks, review, opts);
|
|
@@ -34,10 +40,11 @@ export async function summarizePollSummaryPr(raw, repo, opts, viewerCanAdministe
|
|
|
34
40
|
action = "wait";
|
|
35
41
|
reasons = ["blocking-reviewer-in-progress"];
|
|
36
42
|
}
|
|
43
|
+
({ action, reasons } = await applyUnseenCheckAnnotations(raw, repo, { action, reasons }, checks, seen, required));
|
|
37
44
|
const appearsReady = reasons.includes("appears-ready");
|
|
38
45
|
if (opts.stackPrNumber === undefined) {
|
|
39
|
-
const readyDelaySeconds = opts.readyDelaySeconds ??
|
|
40
|
-
const readyState = await updateReadyDelay(raw.number, appearsReady, readyDelaySeconds, repo.owner, repo.name, { headSha: raw.headRefOid });
|
|
46
|
+
const readyDelaySeconds = opts.readyDelaySeconds ?? loadConfig().iterate["ready-delay-minutes"] * 60;
|
|
47
|
+
const readyState = await updateReadyDelay(raw.number, appearsReady, readyDelaySeconds, repo.owner, repo.name, { headSha: raw.headRefOid, summary: true });
|
|
41
48
|
if (appearsReady && !readyState.shouldCancel) {
|
|
42
49
|
action = "wait";
|
|
43
50
|
reasons = ["ready-delay"];
|
|
@@ -112,6 +119,7 @@ export async function summarizePollSummaryPr(raw, repo, opts, viewerCanAdministe
|
|
|
112
119
|
...(remainingSeconds !== undefined && { remainingSeconds }),
|
|
113
120
|
...(Object.keys(checks).length > 0 && { checks }),
|
|
114
121
|
...(Object.keys(review).length > 0 && { review }),
|
|
122
|
+
...(ruleAutoResolve && { ruleAutoResolve }),
|
|
115
123
|
...(stack && { stack }),
|
|
116
124
|
...(readyReceipt && { readyReceipt: true }),
|
|
117
125
|
...((opts.stackPrNumber !== undefined && raw.state === "OPEN") ||
|
|
@@ -122,22 +130,3 @@ export async function summarizePollSummaryPr(raw, repo, opts, viewerCanAdministe
|
|
|
122
130
|
...(blocked.pollProbe ? { pollProbe: true } : {}),
|
|
123
131
|
};
|
|
124
132
|
}
|
|
125
|
-
function projectQueueRemoval(removal) {
|
|
126
|
-
const removalTime = Date.parse(removal.createdAt);
|
|
127
|
-
const parents = removal.beforeCommit?.parents?.nodes.map((parent) => parent.oid) ?? [];
|
|
128
|
-
return {
|
|
129
|
-
reason: removal.reason,
|
|
130
|
-
createdAtUnix: Math.floor(removalTime / 1000),
|
|
131
|
-
...(removal.actor?.login && { actor: removal.actor.login }),
|
|
132
|
-
...(removal.beforeCommit?.oid && { beforeCommitOid: removal.beforeCommit.oid }),
|
|
133
|
-
beforeCommitParentOids: parents,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
function detectBlockingReviewer(raw) {
|
|
137
|
-
const prefixes = (loadConfig().mergeStatus?.blockingReviewerLogins ?? []).map((login) => login.toLowerCase());
|
|
138
|
-
// A team reviewer has a name, not a login, so it never matches a login prefix.
|
|
139
|
-
const matches = (author) => typeof author?.login === "string" &&
|
|
140
|
-
prefixes.some((prefix) => author.login.toLowerCase().startsWith(prefix));
|
|
141
|
-
return ((raw.reviewRequests?.nodes ?? []).some((request) => matches(request.requestedReviewer)) ||
|
|
142
|
-
(raw.latestReviews?.nodes ?? []).some((review) => review.state === "PENDING" && matches(review.author)));
|
|
143
|
-
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { PollSummaryItem } from "../types.mts";
|
|
1
2
|
import type { RawSummaryPr } from "./poll-summary-raw.mts";
|
|
2
3
|
type QueueRemovalEvent = NonNullable<RawSummaryPr["mergeQueueRemovals"]>["nodes"][number];
|
|
3
4
|
/** Latest queue removal that still applies to this PR head, not an older attempt. */
|
|
4
5
|
export declare function currentQueueRemovalEvent(raw: RawSummaryPr): QueueRemovalEvent | null;
|
|
6
|
+
export declare function projectQueueRemoval(removal: QueueRemovalEvent): PollSummaryItem["queueRemoval"];
|
|
5
7
|
export {};
|
|
@@ -28,3 +28,14 @@ export function currentQueueRemovalEvent(raw) {
|
|
|
28
28
|
}
|
|
29
29
|
return removal;
|
|
30
30
|
}
|
|
31
|
+
export function projectQueueRemoval(removal) {
|
|
32
|
+
const removalTime = Date.parse(removal.createdAt);
|
|
33
|
+
const parents = removal.beforeCommit?.parents?.nodes.map((parent) => parent.oid) ?? [];
|
|
34
|
+
return {
|
|
35
|
+
reason: removal.reason,
|
|
36
|
+
createdAtUnix: Math.floor(removalTime / 1000),
|
|
37
|
+
...(removal.actor?.login && { actor: removal.actor.login }),
|
|
38
|
+
...(removal.beforeCommit?.oid && { beforeCommitOid: removal.beforeCommit.oid }),
|
|
39
|
+
beforeCommitParentOids: parents,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -38,6 +38,7 @@ export function isCurrentSummaryReady(raw, checks, review, options = {}) {
|
|
|
38
38
|
(queued || raw.mergeable === "MERGEABLE") &&
|
|
39
39
|
(queued || !blockedStates.includes(raw.mergeStateStatus)) &&
|
|
40
40
|
(checks.failing ?? 0) === 0 &&
|
|
41
|
+
(checks.annotated ?? 0) === 0 &&
|
|
41
42
|
(sourceChecks.failing ?? 0) === 0 &&
|
|
42
43
|
(sourceChecks.inProgress ?? 0) === 0 &&
|
|
43
44
|
(checks.unreportedRequired?.length ?? 0) === 0 &&
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type NormalizedBotUsernames } from "../comments/authors.mts";
|
|
2
|
+
import type { MinimizeCommentsPolicy, ResolveOtherHumanThreads } from "../config/load.mts";
|
|
3
|
+
import { type SeenMarker } from "../state/seen-comments.mts";
|
|
4
|
+
import type { RawSummaryPr } from "./poll-summary-raw.mts";
|
|
5
|
+
type RawComment = RawSummaryPr["comments"]["nodes"][number];
|
|
6
|
+
type RawThread = RawSummaryPr["reviewThreads"]["nodes"][number];
|
|
7
|
+
/**
|
|
8
|
+
* Count PR comments the full check would route to work, through the same classifier it uses: a new
|
|
9
|
+
* comment, plus any seen comment eligible for minimization under `iterate.minimize-comments`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function countCommentWork(eligible: RawComment[], unconfirmed: ReadonlySet<string>, seen: Map<string, SeenMarker>, minimizeComments: MinimizeCommentsPolicy | undefined, bots: NormalizedBotUsernames): number;
|
|
12
|
+
export interface SummaryThreadView {
|
|
13
|
+
thread: RawThread;
|
|
14
|
+
root: RawThread["comments"]["nodes"][number] | undefined;
|
|
15
|
+
truncated: boolean;
|
|
16
|
+
transcript: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Unresolved thread ids that keep returning every tick once seen. This is the full check's
|
|
20
|
+
* `classifyThreadVisibility` repeat rule, including `iterate.resolve-other-human-threads`,
|
|
21
|
+
* shared rather than re-derived.
|
|
22
|
+
*/
|
|
23
|
+
export declare function repeatingThreadIds(views: SummaryThreadView[], seen: Map<string, SeenMarker>, bots: NormalizedBotUsernames, policy: ResolveOtherHumanThreads): Set<string>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { buildThreadMutationRouting, threadHasAuthorizedMutation, } from "../commands/iterate/thread-mutation-routing.mjs";
|
|
2
|
+
import { normalizeAuthorType } from "../comments/authors.mjs";
|
|
3
|
+
import { threadEndedByShepherd } from "../comments/marker.mjs";
|
|
4
|
+
import { unresolvedThreadRepeats } from "../comments/thread-visibility.mjs";
|
|
5
|
+
import { classifyVisibleComments } from "../comments/visible-comments.mjs";
|
|
6
|
+
import { mutationWasDenied } from "../state/seen-comments.mjs";
|
|
7
|
+
const asPrComment = (c) => ({
|
|
8
|
+
id: c.id,
|
|
9
|
+
isMinimized: c.isMinimized === true,
|
|
10
|
+
...(c.viewerCanMinimize !== undefined && { viewerCanMinimize: c.viewerCanMinimize }),
|
|
11
|
+
author: c.author?.login ?? "unknown",
|
|
12
|
+
authorType: normalizeAuthorType(c.author?.__typename, c.author?.login),
|
|
13
|
+
body: c.body,
|
|
14
|
+
url: c.url ?? "",
|
|
15
|
+
createdAtUnix: 0,
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Count PR comments the full check would route to work, through the same classifier it uses: a new
|
|
19
|
+
* comment, plus any seen comment eligible for minimization under `iterate.minimize-comments`.
|
|
20
|
+
*/
|
|
21
|
+
export function countCommentWork(eligible, unconfirmed, seen, minimizeComments, bots) {
|
|
22
|
+
const denied = new Set(eligible.filter((c) => mutationWasDenied(c.id, c.body, seen)).map((c) => c.id));
|
|
23
|
+
const work = new Set(classifyVisibleComments(eligible.map(asPrComment), seen, minimizeComments, bots, denied).actionable.map((c) => c.id));
|
|
24
|
+
// An edited PR comment is shown once by iterate but never blocks readiness.
|
|
25
|
+
return eligible.filter((c) => work.has(c.id) || unconfirmed.has(c.id)).length;
|
|
26
|
+
}
|
|
27
|
+
const asReviewThread = ({ thread, root, truncated }) => {
|
|
28
|
+
const author = root?.author?.login ?? "unknown";
|
|
29
|
+
const authorType = normalizeAuthorType(root?.author?.__typename, root?.author?.login);
|
|
30
|
+
return {
|
|
31
|
+
id: thread.id,
|
|
32
|
+
...(thread.isResolved !== undefined && { isResolved: thread.isResolved }),
|
|
33
|
+
...(thread.isOutdated !== undefined && { isOutdated: thread.isOutdated }),
|
|
34
|
+
isMinimized: root?.isMinimized === true,
|
|
35
|
+
...(thread.viewerCanReply !== undefined && { viewerCanReply: thread.viewerCanReply }),
|
|
36
|
+
...(thread.viewerCanResolve !== undefined && { viewerCanResolve: thread.viewerCanResolve }),
|
|
37
|
+
path: thread.path,
|
|
38
|
+
line: null,
|
|
39
|
+
startLine: null,
|
|
40
|
+
author,
|
|
41
|
+
authorType,
|
|
42
|
+
...(root?.viewerDidAuthor === true && { viewerDidAuthor: true }),
|
|
43
|
+
body: root?.body ?? "",
|
|
44
|
+
url: "",
|
|
45
|
+
// A window never shows a truncated thread's last comment, so it cannot prove the thread ended.
|
|
46
|
+
comments: truncated
|
|
47
|
+
? []
|
|
48
|
+
: thread.comments.nodes.map((c) => ({
|
|
49
|
+
id: c.id,
|
|
50
|
+
author: c.author?.login ?? "unknown",
|
|
51
|
+
authorType: normalizeAuthorType(c.author?.__typename, c.author?.login),
|
|
52
|
+
body: c.body,
|
|
53
|
+
url: "",
|
|
54
|
+
createdAtUnix: 0,
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Unresolved thread ids that keep returning every tick once seen. This is the full check's
|
|
60
|
+
* `classifyThreadVisibility` repeat rule, including `iterate.resolve-other-human-threads`,
|
|
61
|
+
* shared rather than re-derived.
|
|
62
|
+
*/
|
|
63
|
+
export function repeatingThreadIds(views, seen, bots, policy) {
|
|
64
|
+
const threads = views.map((view) => asReviewThread(view));
|
|
65
|
+
const routing = buildThreadMutationRouting(threads, bots, [], policy);
|
|
66
|
+
const replyIds = new Set(routing.replyThreadIds);
|
|
67
|
+
const resolveIds = new Set(routing.resolveThreadIds);
|
|
68
|
+
const repeating = new Set();
|
|
69
|
+
views.forEach((view, index) => {
|
|
70
|
+
const thread = threads[index];
|
|
71
|
+
if (thread.isResolved !== false || thread.isOutdated === undefined)
|
|
72
|
+
return;
|
|
73
|
+
if (mutationWasDenied(thread.id, view.transcript, seen))
|
|
74
|
+
return;
|
|
75
|
+
if (!threadHasAuthorizedMutation(thread, replyIds, resolveIds))
|
|
76
|
+
return;
|
|
77
|
+
const ended = !view.truncated && threadEndedByShepherd(thread);
|
|
78
|
+
if (unresolvedThreadRepeats(thread, ended, bots, policy))
|
|
79
|
+
repeating.add(thread.id);
|
|
80
|
+
});
|
|
81
|
+
return repeating;
|
|
82
|
+
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { type SeenMarker } from "../state/seen-comments.mts";
|
|
2
|
-
import type { PollSummaryReview } from "../types.mts";
|
|
2
|
+
import type { PollSummaryReview, PollSummaryRuleAutoResolve } from "../types.mts";
|
|
3
3
|
import type { RawSummaryPr } from "./poll-summary-raw.mts";
|
|
4
|
-
|
|
4
|
+
import { type SummaryStateKey } from "./poll-summary-rule-auto-resolve.mts";
|
|
5
|
+
/**
|
|
6
|
+
* Count the review work a compact row routes to the full check. With `autoResolveKey` and
|
|
7
|
+
* `actions.auto-minimize-suppressed` on, rule-driven auto-resolve runs first, exactly as in the
|
|
8
|
+
* one-PR path, so a handled bot notice never sends the row to triage.
|
|
9
|
+
*/
|
|
10
|
+
export declare function evaluatePollSummaryReview(raw: RawSummaryPr, seen: Map<string, SeenMarker>, viewerCanAdminister: boolean | undefined, autoResolveKey?: SummaryStateKey): Promise<{
|
|
11
|
+
review: PollSummaryReview;
|
|
12
|
+
ruleAutoResolve?: PollSummaryRuleAutoResolve;
|
|
13
|
+
}>;
|