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
package/bin/cli-parser.mjs
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
2
|
/** CLI argument parsing and subcommand dispatch for pr-shepherd. See --help for usage. */
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
|
-
import { EXIT, applyReviewResultToExitCode, errorToExitCode } from "./exit-codes.mjs";
|
|
4
|
+
import { EXIT, ShepherdError, applyReviewResultToExitCode, errorToExitCode, } from "./exit-codes.mjs";
|
|
5
|
+
import { loadConfig } from "./config/load.mjs";
|
|
5
6
|
import { runResolveMutate } from "./commands/resolve.mjs";
|
|
6
7
|
import { runLogFile } from "./commands/log-file.mjs";
|
|
7
8
|
import { handleClean } from "./cli/clean-handler.mjs";
|
|
8
9
|
import { parseCommonArgs, getFlag, hasFlag, parseList } from "./cli/args.mjs";
|
|
10
|
+
import { rejectUnknownFlags } from "./cli/unknown-flags.mjs";
|
|
9
11
|
import { isDefaultPollInvocation, validateDefaultPollArgs } from "./cli/default-poll.mjs";
|
|
10
12
|
import { USAGE, helpKeyForArgs, maybePrintHelp } from "./cli/help.mjs";
|
|
11
13
|
import { formatMutateResult } from "./cli/formatters.mjs";
|
|
12
|
-
import {
|
|
14
|
+
import { handleSuggest, handleIterate, handleMarkFilesAsViewed } from "./cli/handlers.mjs";
|
|
13
15
|
import { handleJournal } from "./cli/journal-handler.mjs";
|
|
14
16
|
import { handleJournalExtract } from "./cli/journal-extract-handler.mjs";
|
|
15
17
|
import { handlePlaybook } from "./cli/playbook-handler.mjs";
|
|
16
|
-
import { handlePoll } from "./cli/poll-handler.mjs";
|
|
18
|
+
import { handlePoll, handleStack } from "./cli/poll-handler.mjs";
|
|
17
19
|
import { warnPrrcThreadIds, validateRequireSha, rejectPrrcMinimizeIds, } from "./cli/resolve-validators.mjs";
|
|
18
20
|
import { handleCheckBlocker } from "./cli/check-blocker-handler.mjs";
|
|
19
21
|
import { handleQueueRemoval } from "./cli/queue-removal-handler.mjs";
|
|
20
22
|
import { setupLog } from "./log/setup.mjs";
|
|
21
|
-
import { rejectInvalidPollModeFlag, runInEventStateScope, runInPollModeScope, } from "./cli/event-state-scope.mjs";
|
|
22
23
|
import { handleApplyMerge } from "./cli/apply-merge-handler.mjs";
|
|
23
24
|
import { getLocalHeadSha } from "./commands/suggestion-patch-git.mjs";
|
|
24
25
|
import { extractTransportArgs } from "./cli/transport-args.mjs";
|
|
@@ -34,7 +35,6 @@ export async function main(argv) {
|
|
|
34
35
|
if (extracted.args.some((arg) => arg === "--help" || arg === "-h") ||
|
|
35
36
|
extracted.args[0] === "--version" ||
|
|
36
37
|
extracted.args[0] === "-v" ||
|
|
37
|
-
extracted.args[0] === "log-file" ||
|
|
38
38
|
extracted.args[0] === "playbook" ||
|
|
39
39
|
(extracted.args[0] === "admin" && extracted.args[1] === "log-file") ||
|
|
40
40
|
(extracted.args[0] === "journal" && extracted.args[1] === "extract")) {
|
|
@@ -50,6 +50,17 @@ export async function main(argv) {
|
|
|
50
50
|
process.exitCode = EXIT.USAGE;
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
+
// Validate config before any GitHub work, so a bad or pre-v1 config file exits 78 up front.
|
|
54
|
+
try {
|
|
55
|
+
loadConfig();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (!(error instanceof ShepherdError))
|
|
59
|
+
throw error;
|
|
60
|
+
process.stderr.write(`pr-shepherd: ${error.message}\n`);
|
|
61
|
+
process.exitCode = error.exitCode;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
53
64
|
return runWithGithubTransport(mode, () => dispatch(dispatched), {
|
|
54
65
|
verbose: extracted.args.includes("--verbose"),
|
|
55
66
|
});
|
|
@@ -65,12 +76,7 @@ async function dispatch(argv) {
|
|
|
65
76
|
process.stdout.write(`${USAGE.top}\n`);
|
|
66
77
|
return;
|
|
67
78
|
}
|
|
68
|
-
//
|
|
69
|
-
if (subcommand === "log-file") {
|
|
70
|
-
await handleLogFile(args.slice(1));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
// Extraction deliberately precedes legacy warnings and logging: it is a local,
|
|
79
|
+
// Extraction deliberately precedes logging: it is a local,
|
|
74
80
|
// GitHub/config/log-free read path for automation that already has a PR body.
|
|
75
81
|
if (subcommand === "journal" && args[1] === "extract") {
|
|
76
82
|
await handleJournalExtract(args.slice(2));
|
|
@@ -80,7 +86,7 @@ async function dispatch(argv) {
|
|
|
80
86
|
// command namespace. Keep this before setupLog because log-file reads the
|
|
81
87
|
// log path and must not initialize/tee the log it is reporting.
|
|
82
88
|
if (subcommand === "admin" && args[1] === "log-file") {
|
|
83
|
-
await handleLogFile(args.slice(2)
|
|
89
|
+
await handleLogFile(args.slice(2));
|
|
84
90
|
return;
|
|
85
91
|
}
|
|
86
92
|
// Short-circuit all --help/-h before any I/O or logging.
|
|
@@ -93,24 +99,12 @@ async function dispatch(argv) {
|
|
|
93
99
|
}
|
|
94
100
|
return;
|
|
95
101
|
}
|
|
96
|
-
const legacyReplacement = {
|
|
97
|
-
poll: "pr-shepherd [PR] (the default PR polling command)",
|
|
98
|
-
resolve: "apply review",
|
|
99
|
-
"mark-files-as-viewed": "apply files",
|
|
100
|
-
journal: "apply journal",
|
|
101
|
-
"build-suggestion-patch": "build-suggestion-patches",
|
|
102
|
-
"commit-suggestion": "build-suggestion-patches",
|
|
103
|
-
};
|
|
104
|
-
if (subcommand !== undefined && legacyReplacement[subcommand] !== undefined) {
|
|
105
|
-
warnLegacyAlias(subcommand, legacyReplacement[subcommand]);
|
|
106
|
-
}
|
|
107
102
|
// A local read of shipped files: no GitHub, git, or log I/O.
|
|
108
103
|
if (subcommand === "playbook") {
|
|
109
104
|
handlePlaybook(args.slice(1));
|
|
110
105
|
return;
|
|
111
106
|
}
|
|
112
|
-
|
|
113
|
-
return runInEventStateScope(args, () => dispatchLogged(argv));
|
|
107
|
+
return dispatchLogged(argv);
|
|
114
108
|
}
|
|
115
109
|
async function dispatchLogged(argv) {
|
|
116
110
|
const args = argv.slice(2);
|
|
@@ -127,36 +121,18 @@ async function dispatchLogged(argv) {
|
|
|
127
121
|
case "apply":
|
|
128
122
|
await handleApply(args.slice(1));
|
|
129
123
|
break;
|
|
130
|
-
case "
|
|
131
|
-
await
|
|
124
|
+
case "suggest":
|
|
125
|
+
await handleSuggest(args.slice(1));
|
|
132
126
|
break;
|
|
133
|
-
case "
|
|
134
|
-
await
|
|
127
|
+
case "stack":
|
|
128
|
+
await handleStack(args.slice(1));
|
|
135
129
|
break;
|
|
136
130
|
case "admin":
|
|
137
131
|
await handleAdmin(args.slice(1));
|
|
138
132
|
break;
|
|
139
|
-
case "resolve":
|
|
140
|
-
await handleResolve(args.slice(1), "resolve");
|
|
141
|
-
break;
|
|
142
|
-
case "commit-suggestion":
|
|
143
|
-
await handleCommitSuggestion(args.slice(1), "commit-suggestion");
|
|
144
|
-
break;
|
|
145
|
-
case "mark-files-as-viewed":
|
|
146
|
-
await handleMarkFilesAsViewed(args.slice(1), "mark-files-as-viewed");
|
|
147
|
-
break;
|
|
148
133
|
case "iterate":
|
|
149
134
|
await handleIterate(args.slice(1));
|
|
150
135
|
break;
|
|
151
|
-
case "poll":
|
|
152
|
-
await handlePoll(args.slice(1));
|
|
153
|
-
break;
|
|
154
|
-
case "clean":
|
|
155
|
-
await handleClean(args.slice(1), "clean");
|
|
156
|
-
break;
|
|
157
|
-
case "journal":
|
|
158
|
-
await handleJournal(args.slice(1), "journal");
|
|
159
|
-
break;
|
|
160
136
|
default:
|
|
161
137
|
process.stderr.write(`Unknown subcommand: ${subcommand ?? "(none)"}\n`);
|
|
162
138
|
process.stderr.write(`${USAGE.top}\n`);
|
|
@@ -164,9 +140,6 @@ async function dispatchLogged(argv) {
|
|
|
164
140
|
return;
|
|
165
141
|
}
|
|
166
142
|
}
|
|
167
|
-
function warnLegacyAlias(alias, replacement) {
|
|
168
|
-
process.stderr.write(`pr-shepherd: ${alias} is deprecated; use ${replacement.startsWith("pr-shepherd") ? replacement : `pr-shepherd ${replacement}`} instead.\n`);
|
|
169
|
-
}
|
|
170
143
|
async function handleApply(args) {
|
|
171
144
|
const action = args[0];
|
|
172
145
|
switch (action) {
|
|
@@ -213,10 +186,10 @@ function readVersion() {
|
|
|
213
186
|
// ---------------------------------------------------------------------------
|
|
214
187
|
// Subcommand handlers
|
|
215
188
|
// ---------------------------------------------------------------------------
|
|
216
|
-
async function handleLogFile(args
|
|
217
|
-
if (maybePrintHelp(args,
|
|
189
|
+
async function handleLogFile(args) {
|
|
190
|
+
if (maybePrintHelp(args, "admin log-file"))
|
|
218
191
|
return;
|
|
219
|
-
if (
|
|
192
|
+
if (rejectUnknownFlags(args, "admin log-file", []))
|
|
220
193
|
return;
|
|
221
194
|
const jsonOut = args.some((a) => a === "--format=json") ||
|
|
222
195
|
(() => {
|
|
@@ -224,16 +197,27 @@ async function handleLogFile(args, usageKey = "log-file") {
|
|
|
224
197
|
return idx !== -1 && args[idx + 1] === "json";
|
|
225
198
|
})();
|
|
226
199
|
try {
|
|
227
|
-
|
|
228
|
-
const result = await runInPollModeScope(args, () => runLogFile());
|
|
200
|
+
const result = await runLogFile();
|
|
229
201
|
process.stdout.write(jsonOut ? `${JSON.stringify(result, null, 2)}\n` : `${result.path}\n`);
|
|
230
202
|
}
|
|
231
203
|
catch (e) {
|
|
232
|
-
process.stderr.write(`pr-shepherd: log-file: ${String(e)}\n`);
|
|
204
|
+
process.stderr.write(`pr-shepherd: admin log-file: ${String(e)}\n`);
|
|
233
205
|
process.exitCode = errorToExitCode(e);
|
|
234
206
|
}
|
|
235
207
|
}
|
|
236
|
-
|
|
208
|
+
const APPLY_REVIEW_FLAGS = [
|
|
209
|
+
"--resolve-thread-ids",
|
|
210
|
+
"--reply-thread-ids",
|
|
211
|
+
"--minimize-comment-ids",
|
|
212
|
+
"--dismiss-review-ids",
|
|
213
|
+
"--message",
|
|
214
|
+
"--require-sha",
|
|
215
|
+
"--adopt-existing-replies",
|
|
216
|
+
];
|
|
217
|
+
async function handleResolve(args) {
|
|
218
|
+
const command = "apply review";
|
|
219
|
+
if (rejectUnknownFlags(args, command, APPLY_REVIEW_FLAGS))
|
|
220
|
+
return;
|
|
237
221
|
const { prNumber, global: globalOpts, extra } = parseCommonArgs(args);
|
|
238
222
|
const resolveThreadIds = parseList(getFlag(extra, "--resolve-thread-ids"));
|
|
239
223
|
const replyThreadIds = parseList(getFlag(extra, "--reply-thread-ids"));
|
|
@@ -247,11 +231,6 @@ async function handleResolve(args, command = "apply review") {
|
|
|
247
231
|
return;
|
|
248
232
|
if (rejectPrrcMinimizeIds(minimizeCommentIds).length > 0)
|
|
249
233
|
return;
|
|
250
|
-
if (hasFlag(extra, "--fetch")) {
|
|
251
|
-
process.stderr.write(`pr-shepherd: ${command}: --fetch has been removed; run pr-shepherd iterate to fetch the next action.\n`);
|
|
252
|
-
process.exitCode = EXIT.USAGE;
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
234
|
const hasAction = resolveThreadIds.length > 0 ||
|
|
256
235
|
replyThreadIds.length > 0 ||
|
|
257
236
|
minimizeCommentIds.length > 0 ||
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BatchPartition } from "../classify/apply.mts";
|
|
2
|
+
import type { PrActivitySummary } from "../types.mts";
|
|
3
|
+
/**
|
|
4
|
+
* Drop rule-suppressed items from the activity digest, matching what `check` hides from the
|
|
5
|
+
* agent. IDs whose automatic minimize/resolve was denied stay visible: `check` surfaces those
|
|
6
|
+
* once so the caller can act. `reviewRoundCount` is raw review history and stays unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export declare function filterSuppressedActivity(activity: PrActivitySummary | undefined, partition: BatchPartition, denied: Iterable<string>): PrActivitySummary | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop rule-suppressed items from the activity digest, matching what `check` hides from the
|
|
3
|
+
* agent. IDs whose automatic minimize/resolve was denied stay visible: `check` surfaces those
|
|
4
|
+
* once so the caller can act. `reviewRoundCount` is raw review history and stays unchanged.
|
|
5
|
+
*/
|
|
6
|
+
export function filterSuppressedActivity(activity, partition, denied) {
|
|
7
|
+
if (!activity)
|
|
8
|
+
return activity;
|
|
9
|
+
const deniedIds = new Set(denied);
|
|
10
|
+
const hidden = (id, suppressed) => suppressed.has(id) && !deniedIds.has(id);
|
|
11
|
+
const reviewItemsSinceLatestCommit = activity.reviewItemsSinceLatestCommit.filter((item) => {
|
|
12
|
+
switch (item.kind) {
|
|
13
|
+
case "pr-comment":
|
|
14
|
+
return !hidden(item.id, partition.suppressedCommentIds);
|
|
15
|
+
case "review-thread-comment":
|
|
16
|
+
return !item.threadId || !hidden(item.threadId, partition.suppressedThreadIds);
|
|
17
|
+
case "review-summary":
|
|
18
|
+
return !hidden(item.id, partition.suppressedReviewSummaryIds);
|
|
19
|
+
case "changes-requested-review":
|
|
20
|
+
return !partition.suppressedChangesRequestedIds.has(item.id);
|
|
21
|
+
default:
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return { ...activity, reviewItemsSinceLatestCommit };
|
|
26
|
+
}
|
|
@@ -6,7 +6,7 @@ export declare function checksWithActionableAnnotations(report: ShepherdReport):
|
|
|
6
6
|
* check, an unseen check-run annotation, or a hard merge conflict) — the categories that
|
|
7
7
|
* `commands/iterate/index.mts` never defers while a PR is queued. Shared with `check.mts` so
|
|
8
8
|
* the seen-marker suppression gate there can't drift from the actual iterate dispatch
|
|
9
|
-
* decision (a queued PR with a failing check still renders review items via `
|
|
9
|
+
* decision (a queued PR with a failing check still renders review items via `triage`; their
|
|
10
10
|
* seen markers must not be suppressed just because the PR happens to be queued).
|
|
11
11
|
*/
|
|
12
12
|
export declare function hasCheckDrivenActionableWork(checks: {
|
|
@@ -20,7 +20,7 @@ export function checksWithActionableAnnotations(report) {
|
|
|
20
20
|
* check, an unseen check-run annotation, or a hard merge conflict) — the categories that
|
|
21
21
|
* `commands/iterate/index.mts` never defers while a PR is queued. Shared with `check.mts` so
|
|
22
22
|
* the seen-marker suppression gate there can't drift from the actual iterate dispatch
|
|
23
|
-
* decision (a queued PR with a failing check still renders review items via `
|
|
23
|
+
* decision (a queued PR with a failing check still renders review items via `triage`; their
|
|
24
24
|
* seen markers must not be suppressed just because the PR happens to be queued).
|
|
25
25
|
*/
|
|
26
26
|
export function hasCheckDrivenActionableWork(checks, mergeStatusValue) {
|
|
@@ -42,7 +42,8 @@ export async function attachAndMergeCheckAnnotations(buckets, seenMap, prNumber,
|
|
|
42
42
|
...buckets.filtered,
|
|
43
43
|
...buckets.ignored,
|
|
44
44
|
].filter(shouldFetchCheckAnnotations);
|
|
45
|
-
const
|
|
45
|
+
const expectedCounts = new Map(candidates.flatMap((c) => c.id != null && c.annotationCount ? [[c.id, c.annotationCount]] : []));
|
|
46
|
+
const annotated = await attachUnseenCheckAnnotations(candidates, seenMap, prNumber, cacheOpts && { ...cacheOpts, expectedCounts });
|
|
46
47
|
const byId = new Map(annotated.flatMap((c) => (c.id != null ? [[c.id, c]] : [])));
|
|
47
48
|
const apply = (list) => list.map((c) => {
|
|
48
49
|
const next = (c.id == null ? undefined : byId.get(c.id)) ?? c;
|
|
@@ -23,7 +23,7 @@ export interface ReviewOutputBuckets {
|
|
|
23
23
|
editedComments: EditedComment[];
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Applies `iterate.
|
|
26
|
+
* Applies `iterate.review-output-max-chars` to one tick's review buckets. Priority: changes-requested
|
|
27
27
|
* reviews, active threads and comments, first-look items, review summaries, edited comments.
|
|
28
28
|
* Approved reviews are opt-in minimize targets, not review work, and are never held.
|
|
29
29
|
* Held items leave every list — the report, the generated mutation IDs, and the seen-marker
|
|
@@ -10,7 +10,7 @@ const threadItem = (thread) => ({
|
|
|
10
10
|
cost: threadTranscriptBody(thread).length,
|
|
11
11
|
});
|
|
12
12
|
/**
|
|
13
|
-
* Applies `iterate.
|
|
13
|
+
* Applies `iterate.review-output-max-chars` to one tick's review buckets. Priority: changes-requested
|
|
14
14
|
* reviews, active threads and comments, first-look items, review summaries, edited comments.
|
|
15
15
|
* Approved reviews are opt-in minimize targets, not review work, and are never held.
|
|
16
16
|
* Held items leave every list — the report, the generated mutation IDs, and the seen-marker
|
|
@@ -14,7 +14,7 @@ type StateKey = {
|
|
|
14
14
|
* when this read differs from the digest stored beside the report, or no digest is stored yet
|
|
15
15
|
* (the seeding tick): the stored report may predate what the detectors saw, so the tick must
|
|
16
16
|
* not reuse it through the GraphQL fingerprint either. A same-digest read past
|
|
17
|
-
* `poll.
|
|
17
|
+
* `poll.reconcile-seconds` leaves `changed` unset.
|
|
18
18
|
*/
|
|
19
19
|
export type WaitDetectorResult = {
|
|
20
20
|
reused: ShepherdReport;
|
|
@@ -30,6 +30,17 @@ function detectorsCover(stored) {
|
|
|
30
30
|
* Read the PR's REST change detectors: the pull, the head's check runs, check suites and
|
|
31
31
|
* statuses, the three feedback lists, and the base branch. Every read is conditional, so an
|
|
32
32
|
* unchanged resource answers a free 304.
|
|
33
|
+
*
|
|
34
|
+
* The feedback lists are deliberately not rule-aware (#635 gap 7). An edited bot comment, even
|
|
35
|
+
* a rule-suppressed or minimized one, answers 200 and wakes the tick. The wake costs one
|
|
36
|
+
* charged list read plus one `BatchPr` first page (a complete fingerprint needs no more),
|
|
37
|
+
* the same point a fingerprint hit spends. Once the digest is re-recorded, a later eligible
|
|
38
|
+
* tick can replay for free again, but only if every detector answers 304 with that digest and
|
|
39
|
+
* the stored snapshot is within `poll.reconcile-seconds`; otherwise it goes to `BatchPr`. Being
|
|
40
|
+
* rule-aware would need the loaded rules, a persisted set of hidden comment ids (REST has no
|
|
41
|
+
* `isMinimized`), and a content digest in place of the ETag. That is more code, and more risk
|
|
42
|
+
* of sleeping through a real change (a rule-suppressed comment edited so it no longer matches,
|
|
43
|
+
* an edited minimized human comment, which `check` surfaces), than the one point it saves.
|
|
33
44
|
*/
|
|
34
45
|
async function readDetectors(prNumber, repo, report) {
|
|
35
46
|
const prefix = restRepoPath(repo);
|
|
@@ -51,7 +62,7 @@ async function readDetectors(prNumber, repo, report) {
|
|
|
51
62
|
* is rethrown so the tick aborts and polling backs off.
|
|
52
63
|
*/
|
|
53
64
|
export async function readWaitDetectors(prNumber, repo, stateKey, config, now = Date.now()) {
|
|
54
|
-
const reconcileMs = config.poll
|
|
65
|
+
const reconcileMs = config.poll["reconcile-seconds"] * 1000;
|
|
55
66
|
if (reconcileMs === 0 || getGithubTransport() === "rest")
|
|
56
67
|
return undefined;
|
|
57
68
|
const stored = await loadPrFingerprint(stateKey);
|
package/bin/commands/check.d.mts
CHANGED
|
@@ -7,7 +7,7 @@ type CheckOptions = GlobalOptions & {
|
|
|
7
7
|
persistSeen?: boolean;
|
|
8
8
|
fingerprintCache?: boolean;
|
|
9
9
|
merge?: boolean;
|
|
10
|
-
/** Per-tick review-item body budget (`iterate.
|
|
10
|
+
/** Per-tick review-item body budget (`iterate.review-output-max-chars`). Omitted or 0: no cap. */
|
|
11
11
|
reviewOutputMaxChars?: number;
|
|
12
12
|
};
|
|
13
13
|
export declare function runCheck(opts: CheckOptions, context?: CheckExecutionContext): Promise<ShepherdReport>;
|
package/bin/commands/check.mjs
CHANGED
|
@@ -33,6 +33,7 @@ import { markReviewInlineThreadMarkers } from "../comments/review-thread-markers
|
|
|
33
33
|
import { isConfiguredBotAuthor, isHumanAuthor, normalizeBotUsernames, } from "../comments/authors.mjs";
|
|
34
34
|
import { buildThreadMutationRouting, threadHasAuthorizedMutation, } from "./iterate/thread-mutation-routing.mjs";
|
|
35
35
|
import { loadClassifyRules } from "../classify/loader.mjs";
|
|
36
|
+
import { filterSuppressedActivity } from "./check-activity.mjs";
|
|
36
37
|
import { buildClassifyIndex, partitionBatch } from "../classify/apply.mjs";
|
|
37
38
|
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
38
39
|
import { getEffectiveCwd } from "../execution-context.mjs";
|
|
@@ -60,7 +61,7 @@ export async function runCheck(opts, context) {
|
|
|
60
61
|
async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
61
62
|
const config = loadConfig();
|
|
62
63
|
const reuseFingerprint = opts.fingerprintCache === true;
|
|
63
|
-
const paginateApprovedReviews = config.iterate
|
|
64
|
+
const paginateApprovedReviews = config.iterate["minimize-approvals"];
|
|
64
65
|
const includeReceiptSummary = (await context?.wantsReceiptSummary(prNumber, repo)) ?? false;
|
|
65
66
|
// GraphQL transport: conditional REST change detectors can replay an idle report for free.
|
|
66
67
|
// Changed trunk rules invalidate a cached report; check again without reuse.
|
|
@@ -125,7 +126,7 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
125
126
|
owner: repo.owner,
|
|
126
127
|
name: repo.name,
|
|
127
128
|
pr: prNumber,
|
|
128
|
-
relevantEvents: config.checks
|
|
129
|
+
relevantEvents: config.checks["ci-trigger-events"],
|
|
129
130
|
}, context);
|
|
130
131
|
const classifiedPrChecks = classifyChecks(allChecks, {
|
|
131
132
|
requiredContexts: unreported.requiredContexts,
|
|
@@ -194,7 +195,7 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
194
195
|
const seenMap = await loadSeenMap(stateKey);
|
|
195
196
|
await aliasThreadSeenMarkers(repo, prNumber, batchData.reviewThreads.map((thread) => thread.id), seenMap);
|
|
196
197
|
const canGenerateItemMutation = (id, body, capability, operation) => !mutationWasDenied(id, body, seenMap) && canGenerateGithubMutation(capability, operation);
|
|
197
|
-
const botUsernames = normalizeBotUsernames(config
|
|
198
|
+
const botUsernames = normalizeBotUsernames(config["bot-usernames"]);
|
|
198
199
|
const ruleSet = await loadClassifyRules(getEffectiveCwd(), config.standardRules);
|
|
199
200
|
const classifyIndex = buildClassifyIndex(ruleSet, batchData);
|
|
200
201
|
const partition = partitionBatch(classifyIndex, batchData);
|
|
@@ -202,7 +203,7 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
202
203
|
const ignoredAnnotated = merged.ignored.filter((c) => (c.annotations?.length ?? 0) > 0);
|
|
203
204
|
const minimizedCommentCandidates = batchData.comments.filter((c) => c.isMinimized && !partition.suppressedCommentIds.has(c.id));
|
|
204
205
|
const deniedRuleAutoResolveCommentIds = new Set(partition.ruleAutoResolveCommentIds.filter((id) => !canGenerateItemMutation(id, batchData.comments.find((comment) => comment.id === id)?.body ?? "", batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize, "minimize")));
|
|
205
|
-
const visibleCommentClassification = classifyVisibleComments(batchData.comments.filter((c) => !partition.suppressedCommentIds.has(c.id) || deniedRuleAutoResolveCommentIds.has(c.id)), seenMap, config.iterate
|
|
206
|
+
const visibleCommentClassification = classifyVisibleComments(batchData.comments.filter((c) => !partition.suppressedCommentIds.has(c.id) || deniedRuleAutoResolveCommentIds.has(c.id)), seenMap, config.iterate["minimize-comments"], botUsernames, new Set(batchData.comments
|
|
206
207
|
.filter((comment) => mutationWasDenied(comment.id, comment.body, seenMap))
|
|
207
208
|
.map((comment) => comment.id)));
|
|
208
209
|
const deniedRuleAutoResolveThreadIds = new Set(partition.ruleAutoResolveThreadIds.filter((id) => {
|
|
@@ -211,7 +212,7 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
211
212
|
!canGenerateItemMutation(thread.id, threadTranscriptBody(thread), thread.viewerCanResolve, "resolve"));
|
|
212
213
|
}));
|
|
213
214
|
const visibleThreadCandidates = batchData.reviewThreads.filter((t) => !partition.suppressedThreadIds.has(t.id) || deniedRuleAutoResolveThreadIds.has(t.id));
|
|
214
|
-
const resolveOtherHumanThreads = config.iterate
|
|
215
|
+
const resolveOtherHumanThreads = config.iterate["resolve-other-human-threads"] ?? "none";
|
|
215
216
|
const threadMutationRouting = buildThreadMutationRouting(visibleThreadCandidates, botUsernames, partition.ruleAutoResolveThreadIds, resolveOtherHumanThreads);
|
|
216
217
|
const replyThreadIds = new Set(threadMutationRouting.replyThreadIds);
|
|
217
218
|
const resolveThreadIds = new Set(threadMutationRouting.resolveThreadIds);
|
|
@@ -288,11 +289,11 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
288
289
|
// persisting seen markers for it now would make it silently vanish from every later tick
|
|
289
290
|
// once it's no longer "new" or "edited", even after the PR leaves the queue. A queued PR
|
|
290
291
|
// that also has check-driven work (failing checks, actionable annotations, conflicts) is
|
|
291
|
-
// NOT deferred — index.mts still renders these items via
|
|
292
|
+
// NOT deferred — index.mts still renders these items via triage — so this must stay false
|
|
292
293
|
// in that case too, or their seen markers would be suppressed while actually being shown.
|
|
293
294
|
const deferWhileQueued = opts.merge === true &&
|
|
294
295
|
batchData.isInMergeQueue === true &&
|
|
295
|
-
config.actions
|
|
296
|
+
config.actions["work-while-queued"] !== true &&
|
|
296
297
|
!hasCheckDrivenActionableWork({
|
|
297
298
|
failing: merged.failing,
|
|
298
299
|
passing: merged.passing,
|
|
@@ -449,7 +450,11 @@ async function runScopedCheck(opts, context, repo, prNumber, stateKey) {
|
|
|
449
450
|
...(batchData.allowedMergeMethods && {
|
|
450
451
|
allowedMergeMethods: batchData.allowedMergeMethods,
|
|
451
452
|
}),
|
|
452
|
-
activity: batchData.activity,
|
|
453
|
+
activity: filterSuppressedActivity(batchData.activity, partition, [
|
|
454
|
+
...deniedRuleAutoResolveCommentIds,
|
|
455
|
+
...deniedRuleAutoResolveThreadIds,
|
|
456
|
+
...deniedRuleAutoResolveReviewSummaryIds,
|
|
457
|
+
]),
|
|
453
458
|
...(hasQueueState && {
|
|
454
459
|
mergeQueue: {
|
|
455
460
|
enabled: Boolean(batchData.isMergeQueueEnabled),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build the `
|
|
3
|
-
* Currently emitted by iterate `
|
|
2
|
+
* Build the `suggest` instruction step for agent consumers.
|
|
3
|
+
* Currently emitted by iterate `triage` for suggestion review threads. The CLI keeps
|
|
4
4
|
* only the trigger and the concrete command; refusal/drift handling is invariant across
|
|
5
5
|
* every invocation, so it lives in the pr-shepherd skill's "Suggestion patches" playbook
|
|
6
6
|
* instead of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
2
2
|
import { playbookPointer } from "./playbook-pointer.mjs";
|
|
3
3
|
/**
|
|
4
|
-
* Build the `
|
|
5
|
-
* Currently emitted by iterate `
|
|
4
|
+
* Build the `suggest` instruction step for agent consumers.
|
|
5
|
+
* Currently emitted by iterate `triage` for suggestion review threads. The CLI keeps
|
|
6
6
|
* only the trigger and the concrete command; refusal/drift handling is invariant across
|
|
7
7
|
* every invocation, so it lives in the pr-shepherd skill's "Suggestion patches" playbook
|
|
8
8
|
* instead of being re-emitted every tick (see AGENTS.md "Keep skills and loop prompts
|
|
@@ -12,7 +12,7 @@ import { playbookPointer } from "./playbook-pointer.mjs";
|
|
|
12
12
|
*/
|
|
13
13
|
export function buildCommitSuggestionInstruction(prReference, sectionName) {
|
|
14
14
|
const command = buildPrShepherdCommand([
|
|
15
|
-
"
|
|
15
|
+
"suggest",
|
|
16
16
|
String(prReference),
|
|
17
17
|
"--thread-id",
|
|
18
18
|
"<id>",
|
|
@@ -3,9 +3,9 @@ import { summarizeApiTelemetry } from "../../github/api-telemetry.mjs";
|
|
|
3
3
|
import { selectQuotaWarning } from "../quota-selection.mjs";
|
|
4
4
|
import { buildQuotaAwareContinuation } from "../../quota-warning.mjs";
|
|
5
5
|
import { getGithubTransport } from "../../github/transport.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { isTriageContinuation } from "./check-instructions.mjs";
|
|
7
7
|
function shouldWarn(result) {
|
|
8
|
-
return ["wait", "mark_ready", "merge", "
|
|
8
|
+
return ["wait", "mark_ready", "merge", "triage"].includes(result.action);
|
|
9
9
|
}
|
|
10
10
|
export async function attachApiUsage(result, persistWarning, preservePersistedWarning = false, minimumPollIntervalMinutes = 0) {
|
|
11
11
|
const apiUsage = summarizeApiTelemetry();
|
|
@@ -16,7 +16,7 @@ export async function attachApiUsage(result, persistWarning, preservePersistedWa
|
|
|
16
16
|
if (quotaWarning === undefined && shouldWarn(result) && (apiUsage.graphql || core)) {
|
|
17
17
|
const [owner, repo] = result.repo.split("/");
|
|
18
18
|
if (owner && repo) {
|
|
19
|
-
const bands = loadConfig().
|
|
19
|
+
const bands = loadConfig().quotaWarningBands.map((band) => ({
|
|
20
20
|
...band,
|
|
21
21
|
pollIntervalMinutes: Math.max(band.pollIntervalMinutes, minimumPollIntervalMinutes),
|
|
22
22
|
}));
|
|
@@ -29,14 +29,14 @@ export async function attachApiUsage(result, persistWarning, preservePersistedWa
|
|
|
29
29
|
apiUsage,
|
|
30
30
|
...(quotaWarning !== undefined && { quotaWarning }),
|
|
31
31
|
};
|
|
32
|
-
if (quotaWarning === undefined || withWarning.action !== "
|
|
32
|
+
if (quotaWarning === undefined || withWarning.action !== "triage")
|
|
33
33
|
return withWarning;
|
|
34
|
-
const instructions = [...withWarning.
|
|
34
|
+
const instructions = [...withWarning.triage.instructions];
|
|
35
35
|
const completion = instructions.at(-1);
|
|
36
36
|
if (completion !== undefined) {
|
|
37
|
-
if (
|
|
38
|
-
instructions[instructions.length - 1] = buildQuotaAwareContinuation(quotaWarning, "`[
|
|
37
|
+
if (isTriageContinuation(completion)) {
|
|
38
|
+
instructions[instructions.length - 1] = buildQuotaAwareContinuation(quotaWarning, "`[TRIAGE]` is non-terminal. After completing these steps,");
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
return { ...withWarning,
|
|
41
|
+
return { ...withWarning, triage: { ...withWarning.triage, instructions } };
|
|
42
42
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { AgentCheck, ResolveCommand, Review } from "../../types.mts";
|
|
2
|
-
/** The `[
|
|
3
|
-
export declare function
|
|
2
|
+
/** The `[TRIAGE]` recurrence step, whether plain or rewritten by the quota warning. */
|
|
3
|
+
export declare function isTriageContinuation(step: string): boolean;
|
|
4
4
|
/** Build the stale-CR step for `## Changes-requested reviews`. Empty when no human CR is stale. */
|
|
5
5
|
export declare function buildCrStaleClause(reviews: Review[]): string;
|
|
6
6
|
/**
|
|
7
7
|
* Build the optional branch-update hint. Empty unless the branch is behind or conflicts with its
|
|
8
|
-
* base and the user configured a non-blank `iterate.
|
|
8
|
+
* base and the user configured a non-blank `iterate.behind-base-hint` — the CLI never prescribes
|
|
9
9
|
* rebase/merge mechanics itself (see "Keep skills and loop prompts minimal" in AGENTS.md); this
|
|
10
10
|
* only echoes back the caller's own configured pointer. `hint` is trimmed and type-checked at the
|
|
11
11
|
* point of use (rather than at config load) so a malformed rc file value (non-string, or
|
|
@@ -37,7 +37,7 @@ 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.
|
|
40
|
+
/** `iterate.review-output-max-chars` held review items back; the rerun step that follows shows them. */
|
|
41
41
|
export declare function buildReviewSubsetInstruction(reviewItems: {
|
|
42
42
|
shown: number;
|
|
43
43
|
total: number;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { playbookPointer } from "../playbook-pointer.mjs";
|
|
2
|
-
// The `[
|
|
3
|
-
const
|
|
4
|
-
/** The `[
|
|
5
|
-
export function
|
|
6
|
-
return step ===
|
|
2
|
+
// The `[TRIAGE]` heading and the skill already say the action is non-terminal.
|
|
3
|
+
const TRIAGE_CONTINUATION = "Rerun Shepherd now.";
|
|
4
|
+
/** The `[TRIAGE]` recurrence step, whether plain or rewritten by the quota warning. */
|
|
5
|
+
export function isTriageContinuation(step) {
|
|
6
|
+
return step === TRIAGE_CONTINUATION || /\[TRIAGE\].*non-terminal/i.test(step);
|
|
7
7
|
}
|
|
8
8
|
/** Build the stale-CR step for `## Changes-requested reviews`. Empty when no human CR is stale. */
|
|
9
9
|
export function buildCrStaleClause(reviews) {
|
|
@@ -13,7 +13,7 @@ export function buildCrStaleClause(reviews) {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Build the optional branch-update hint. Empty unless the branch is behind or conflicts with its
|
|
16
|
-
* base and the user configured a non-blank `iterate.
|
|
16
|
+
* base and the user configured a non-blank `iterate.behind-base-hint` — the CLI never prescribes
|
|
17
17
|
* rebase/merge mechanics itself (see "Keep skills and loop prompts minimal" in AGENTS.md); this
|
|
18
18
|
* only echoes back the caller's own configured pointer. `hint` is trimmed and type-checked at the
|
|
19
19
|
* point of use (rather than at config load) so a malformed rc file value (non-string, or
|
|
@@ -74,12 +74,12 @@ 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.
|
|
77
|
+
/** `iterate.review-output-max-chars` held review items back; the rerun step that follows shows them. */
|
|
78
78
|
export function buildReviewSubsetInstruction(reviewItems) {
|
|
79
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
80
|
}
|
|
81
81
|
/** Recurrence only. Commit, push, rerun, and SHA steps are earlier instructions. */
|
|
82
82
|
export function buildFixCompletionInstruction() {
|
|
83
|
-
return
|
|
83
|
+
return TRIAGE_CONTINUATION;
|
|
84
84
|
}
|
|
85
85
|
import { getGithubTransport } from "../../github/transport.mjs";
|
|
@@ -24,7 +24,7 @@ export function classifyReviewSummaries(summaries, approvals, minimizeApprovals,
|
|
|
24
24
|
// so their minimize IDs ride in the agent-facing apply command. Seen summaries
|
|
25
25
|
// (already surfaced in a prior tick) have no new content to show — the CLI
|
|
26
26
|
// self-minimizes them in-process (selfMinimizeIds) instead of routing a
|
|
27
|
-
// cosmetic-only mutation through
|
|
27
|
+
// cosmetic-only mutation through triage (issue #313). Edited summaries are
|
|
28
28
|
// excluded entirely: they are already minimized server-side (body changed after
|
|
29
29
|
// minimize was applied).
|
|
30
30
|
const minimizeIds = summaries.firstLook.filter(eligible).map((r) => r.id);
|
|
@@ -61,7 +61,7 @@ export function classifyReviewSummaries(summaries, approvals, minimizeApprovals,
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
* A durable-state
|
|
64
|
+
* A durable-state (cloud) session can lose its uncertain-reply records
|
|
65
65
|
* between ticks. Its generated reply command opts in to adopting a reply GitHub already shows,
|
|
66
66
|
* so rerunning the same printed command cannot double-post. Direct apply requests never do.
|
|
67
67
|
*/
|
|
@@ -64,11 +64,11 @@ function renderEscalateCheck(check) {
|
|
|
64
64
|
}
|
|
65
65
|
export function checkEscalateTriggers(actionableThreads, threadAttempts) {
|
|
66
66
|
const triggers = [];
|
|
67
|
-
const maxAttempts = loadConfig().iterate
|
|
68
|
-
// Trigger 1:
|
|
67
|
+
const maxAttempts = loadConfig().iterate["triage-attempts-per-thread"];
|
|
68
|
+
// Trigger 1: triage thrash — same thread dispatched too many times without resolving.
|
|
69
69
|
const thrashThreads = actionableThreads.filter((t) => (threadAttempts[t.id] ?? 0) >= maxAttempts);
|
|
70
70
|
if (thrashThreads.length > 0) {
|
|
71
|
-
triggers.push("
|
|
71
|
+
triggers.push("triage-thrash");
|
|
72
72
|
}
|
|
73
73
|
return {
|
|
74
74
|
triggers,
|
|
@@ -265,9 +265,9 @@ export function buildEscalateSuggestion(triggers, detail) {
|
|
|
265
265
|
const reason = detail ? ` (${detail})` : "";
|
|
266
266
|
return `Could not determine the PR's base branch${reason} — automated rebases are paused because branch safety is unclear. Run the rebase manually against the PR's real target branch.`;
|
|
267
267
|
}
|
|
268
|
-
if (triggers.includes("
|
|
269
|
-
const attempts = loadConfig().iterate
|
|
270
|
-
return `The same thread(s) remain unresolved after their pending review commands were returned for ${attempts}
|
|
268
|
+
if (triggers.includes("triage-thrash")) {
|
|
269
|
+
const attempts = loadConfig().iterate["triage-attempts-per-thread"];
|
|
270
|
+
return `The same thread(s) remain unresolved after their pending review commands were returned for ${attempts} TRIAGE ticks. Automated iteration is paused for a manual decision.`;
|
|
271
271
|
}
|
|
272
272
|
return "Ambiguous state — automated handling cannot proceed safely. Inspect the PR and act manually.";
|
|
273
273
|
}
|