pr-shepherd 0.56.1 → 0.56.2
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 -4
- package/bin/api.d.mts +11 -1
- package/bin/api.mjs +25 -0
- package/bin/cli/args.mjs +3 -0
- package/bin/cli/fix-formatter.mjs +3 -0
- package/bin/cli/help-command-pages.d.mts +17 -2
- package/bin/cli/help-command-pages.mjs +16 -1
- package/bin/cli/help-iterate-poll-pages.d.mts +1 -1
- package/bin/cli/help-iterate-poll-pages.mjs +1 -1
- package/bin/cli/help-top-page.d.mts +1 -1
- package/bin/cli/help-top-page.mjs +2 -0
- package/bin/cli/help.d.mts +18 -3
- package/bin/cli/help.mjs +2 -0
- package/bin/cli/iterate-instructions.mjs +4 -1
- package/bin/cli/iterate-lean.mjs +3 -0
- package/bin/cli/iterate-merge-formatter.mjs +2 -0
- package/bin/cli/queue-removal-handler.d.mts +2 -0
- package/bin/cli/queue-removal-handler.mjs +86 -0
- package/bin/cli-parser.mjs +4 -0
- package/bin/commands/apply-queue-removal.d.mts +18 -0
- package/bin/commands/apply-queue-removal.mjs +44 -0
- package/bin/commands/check-fingerprint.mjs +2 -0
- package/bin/commands/check.mjs +13 -1
- package/bin/commands/iterate/check-evidence.d.mts +4 -0
- package/bin/commands/iterate/check-evidence.mjs +8 -0
- package/bin/commands/iterate/fix-code.mjs +20 -4
- package/bin/commands/iterate/index.mjs +15 -1
- package/bin/commands/iterate/merge-state.mjs +4 -1
- package/bin/commands/iterate/merge.d.mts +7 -1
- package/bin/commands/iterate/merge.mjs +59 -1
- package/bin/github/poll-summary-fingerprint.mjs +7 -0
- package/bin/github/poll-summary-readiness.mjs +11 -1
- package/bin/mcp/server.mjs +18 -0
- package/bin/state/queue-removal-ack.d.mts +19 -0
- package/bin/state/queue-removal-ack.mjs +66 -0
- package/bin/types/iterate.d.mts +4 -0
- package/bin/types/merge-queue.d.mts +2 -0
- package/package.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 +3 -2
- package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +1 -0
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Each tick returns exactly one action:
|
|
|
35
35
|
- `MARK_READY` — the CLI converted an eligible draft PR to ready; continue polling.
|
|
36
36
|
- `FIX_CODE` — agent work is required; complete it, push when needed, then continue polling. Push access to the PR head branch is a usage precondition.
|
|
37
37
|
- `MERGE` — run the emitted head-pinned auto-merge or queue command. Ordinary merges include a plain-merge fallback; queue merges include a GraphQL enqueue fallback. GitHub is authoritative for the result and reports any authorization failure.
|
|
38
|
-
- `CANCEL` — stop polling because
|
|
38
|
+
- `CANCEL` — stop polling this pull request because it merged, closed, or completed its ready-delay. Continue any remaining pull requests or issues from the original request.
|
|
39
39
|
- `ESCALATE` — stop polling until a human provides direction. Native stacks reach this only after their autonomous one-PR sessions are exhausted.
|
|
40
40
|
|
|
41
41
|
Native-stack summaries additionally use stack-level `SHEPHERD`: run the listed one-PR sessions,
|
|
@@ -122,7 +122,7 @@ Grok:
|
|
|
122
122
|
/pr-shepherd 42
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
MCP clients call `iterate` once per tick, then use `apply` for review/file/journal mutations and `build_suggestion_patches` for anchored suggestions. To read journal entries, call `extract_journal` with a body already in hand or `get_journal` with a repository-qualified PR reference. `iterate` returns the same structured action data as the CLI, including its review mutation arguments. The client owns recurrence, so this works consistently in Codex, Claude Code, Grok, and any other stdio MCP client.
|
|
125
|
+
MCP clients call `iterate` once per tick, then use `apply` for review/file/journal mutations and validated queue-removal acknowledgments and `build_suggestion_patches` for anchored suggestions. To read journal entries, call `extract_journal` with a body already in hand or `get_journal` with a repository-qualified PR reference. `iterate` returns the same structured action data as the CLI, including its review mutation arguments. The client owns recurrence, so this works consistently in Codex, Claude Code, Grok, and any other stdio MCP client.
|
|
126
126
|
|
|
127
127
|
The CLI remains useful for shell workflows. Its canonical polling form is:
|
|
128
128
|
|
|
@@ -141,6 +141,9 @@ pr-shepherd 42 43 44 # summarize an explicit same-repository s
|
|
|
141
141
|
pr-shepherd --stack 43 # summarize every PR in a native GitHub stack
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
A user-supplied `--merge` authorizes the agent to run the emitted merge/enqueue commands for the
|
|
145
|
+
selected PRs or stack without another conversational confirmation. Host permission checks still apply.
|
|
146
|
+
|
|
144
147
|
Multi-PR and `--stack` polling use compact, read-only GraphQL summaries. They return when work is
|
|
145
148
|
needed, every selected PR is complete, the bounded timeout expires, or `--until-terminal` crosses a
|
|
146
149
|
configured GraphQL quota-warning band. Explicit PR sets give each actionable row an exact single-PR
|
|
@@ -161,7 +164,7 @@ receipts, and whose bottom open layer GitHub has retargeted onto the stack base,
|
|
|
161
164
|
with `gh stack merge <that PR number> --yes` and the allowed method flag (`--squash` unless config or the repository selects another). That lands the named layer and every
|
|
162
165
|
unmerged layer below it. When the base uses a merge queue, the same command queues the prefix
|
|
163
166
|
together and GitHub evaluates each layer from the bottom; a failure ejects that layer and those
|
|
164
|
-
above it. Layers above the prefix keep their one-PR sessions. After the merge, GitHub retargets
|
|
167
|
+
above it. If the evidence shows an unrelated failure and no source changes or other blockers remain, the one-PR session emits a head-, queue-commit-, and timestamp-pinned local acknowledgment command. Fresh source checks and a new READY receipt then let the aggregate selector recover the eligible prefix. Manual or stale removals cannot use this path. Layers above the prefix keep their one-PR sessions. After the merge, GitHub retargets
|
|
165
168
|
the next layer, so the rerun continues until the stack returns `CANCEL`. API and MCP aggregate
|
|
166
169
|
calls perform one summary tick and leave recurrence to the caller.
|
|
167
170
|
|
|
@@ -169,7 +172,7 @@ Polling defaults can be set under `poll` in `.pr-shepherdrc.yml`: `intervalSecon
|
|
|
169
172
|
|
|
170
173
|
### Apply Review And Journal Changes, Or Select Files
|
|
171
174
|
|
|
172
|
-
Use `apply` with ordered operations to reply/resolve/minimize/dismiss review items, mark selected changed files as viewed,
|
|
175
|
+
Use `apply` with ordered operations to reply/resolve/minimize/dismiss review items, mark selected changed files as viewed, append an idempotent Shepherd Journal item, or record a validated native-stack CI queue-removal acknowledgment. Explicit operations are attempted and surface GitHub's per-operation results; generated iterate guidance remains capability-filtered. Use `build_suggestion_patches` to turn ordered review suggestions into checked patches and commit metadata; it never changes the worktree or git history.
|
|
173
176
|
|
|
174
177
|
### Extract Shepherd Journal Entries
|
|
175
178
|
|
package/bin/api.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type JournalResult } from "./commands/journal/index.mts";
|
|
2
2
|
import { type MarkFilesAsViewedResult } from "./commands/mark-files-as-viewed.mts";
|
|
3
|
+
import { type ApplyQueueRemovalResult } from "./commands/apply-queue-removal.mts";
|
|
3
4
|
import type { ResolveResult } from "./comments/resolve.mts";
|
|
4
5
|
import type { BuildSuggestionPatchesResult, CommitSuggestionResult, IterateCommandOptions, IterateResult, PollSummaryResult } from "./types.mts";
|
|
5
6
|
import { type ShepherdJournalExtraction } from "./journal/index.mts";
|
|
@@ -47,8 +48,14 @@ export interface AppendJournalOperation {
|
|
|
47
48
|
item: string;
|
|
48
49
|
dryRun?: boolean;
|
|
49
50
|
}
|
|
51
|
+
export interface AcknowledgeQueueRemovalOperation {
|
|
52
|
+
type: "acknowledge_queue_removal";
|
|
53
|
+
requireSha: string;
|
|
54
|
+
queueCommitOid: string;
|
|
55
|
+
removedAtUnix: number;
|
|
56
|
+
}
|
|
50
57
|
/** Operations run in this exact list order after validation. */
|
|
51
|
-
export type ApplyOperation = ReviewMutationsOperation | MarkFilesViewedOperation | AppendJournalOperation;
|
|
58
|
+
export type ApplyOperation = ReviewMutationsOperation | MarkFilesViewedOperation | AppendJournalOperation | AcknowledgeQueueRemovalOperation;
|
|
52
59
|
export interface ApplyInput {
|
|
53
60
|
/** PR shared by every operation in this ordered apply request. */
|
|
54
61
|
pr?: PrReference;
|
|
@@ -63,6 +70,9 @@ export type ApplyOperationResult = {
|
|
|
63
70
|
} | {
|
|
64
71
|
type: "append_journal";
|
|
65
72
|
result: JournalResult;
|
|
73
|
+
} | {
|
|
74
|
+
type: "acknowledge_queue_removal";
|
|
75
|
+
result: ApplyQueueRemovalResult;
|
|
66
76
|
};
|
|
67
77
|
export interface ApplyResult {
|
|
68
78
|
operations: ApplyOperationResult[];
|
package/bin/api.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { runJournal } from "./commands/journal/index.mjs";
|
|
|
8
8
|
import { validateJournalItem } from "./commands/journal/transform.mjs";
|
|
9
9
|
import { runMarkFilesAsViewed, } from "./commands/mark-files-as-viewed.mjs";
|
|
10
10
|
import { runResolveMutate } from "./commands/resolve-mutate.mjs";
|
|
11
|
+
import { applyQueueRemovalAck, } from "./commands/apply-queue-removal.mjs";
|
|
11
12
|
import { runWithExecutionCwd } from "./execution-context.mjs";
|
|
12
13
|
import { parsePrReference, normalizeRepositoryIdentity, resolveParsedPrTarget, } from "./pr-reference.mjs";
|
|
13
14
|
import { getPullRequestBody, getRepoInfo } from "./github/client.mjs";
|
|
@@ -104,6 +105,17 @@ export function createPrShepherd(options = {}) {
|
|
|
104
105
|
results.push({ type: operation.type, result });
|
|
105
106
|
break;
|
|
106
107
|
}
|
|
108
|
+
case "acknowledge_queue_removal": {
|
|
109
|
+
const result = await applyQueueRemovalAck({
|
|
110
|
+
prNumber,
|
|
111
|
+
targetRepository,
|
|
112
|
+
headSha: operation.requireSha,
|
|
113
|
+
queueCommitOid: operation.queueCommitOid,
|
|
114
|
+
removedAtUnix: operation.removedAtUnix,
|
|
115
|
+
});
|
|
116
|
+
results.push({ type: operation.type, result });
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
107
119
|
}
|
|
108
120
|
}
|
|
109
121
|
catch (error) {
|
|
@@ -206,6 +218,19 @@ function validateOperation(operation) {
|
|
|
206
218
|
throw new PrShepherdValidationError(validation.error);
|
|
207
219
|
}
|
|
208
220
|
return;
|
|
221
|
+
case "acknowledge_queue_removal":
|
|
222
|
+
if (typeof operation.requireSha !== "string" ||
|
|
223
|
+
!/^[0-9a-f]{40}$/.test(operation.requireSha)) {
|
|
224
|
+
throw new PrShepherdValidationError("acknowledge_queue_removal.requireSha must be a full 40-character lowercase hex SHA");
|
|
225
|
+
}
|
|
226
|
+
if (typeof operation.queueCommitOid !== "string" ||
|
|
227
|
+
!/^[0-9a-f]{40}$/.test(operation.queueCommitOid)) {
|
|
228
|
+
throw new PrShepherdValidationError("acknowledge_queue_removal.queueCommitOid must be a full 40-character lowercase hex SHA");
|
|
229
|
+
}
|
|
230
|
+
if (!Number.isSafeInteger(operation.removedAtUnix) || operation.removedAtUnix <= 0) {
|
|
231
|
+
throw new PrShepherdValidationError("acknowledge_queue_removal.removedAtUnix must be a positive Unix timestamp in seconds");
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
209
234
|
default:
|
|
210
235
|
throw new PrShepherdValidationError(`Unsupported apply operation: ${JSON.stringify(operation.type)}`);
|
|
211
236
|
}
|
package/bin/cli/args.mjs
CHANGED
|
@@ -23,6 +23,9 @@ const FLAGS_WITH_VALUES = new Set([
|
|
|
23
23
|
"--match",
|
|
24
24
|
"--check",
|
|
25
25
|
"--blocked-by",
|
|
26
|
+
"--require-sha",
|
|
27
|
+
"--queue-commit",
|
|
28
|
+
"--removed-at",
|
|
26
29
|
]);
|
|
27
30
|
// Boolean flags that do NOT consume the next argument. Any --flag not in this
|
|
28
31
|
// set and not in FLAGS_WITH_VALUES is treated conservatively as value-taking
|
|
@@ -191,6 +191,9 @@ export function formatFixCodeResult(header, result, opts = {}) {
|
|
|
191
191
|
postFixLines.push(`- requeue API fallback: ${inlineCode(renderMergeCommand(result.fix.requeue.queueApiFallbackCommand))}`);
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
+
if (result.fix.queueRemovalAcknowledgment) {
|
|
195
|
+
postFixLines.push(`- acknowledge queue removal: ${inlineCode(renderMergeCommand(result.fix.queueRemovalAcknowledgment))}`);
|
|
196
|
+
}
|
|
194
197
|
sections.push(postFixLines.join("\n"));
|
|
195
198
|
sections.push("## Instructions");
|
|
196
199
|
sections.push(numberInstructions(result.fix.instructions));
|
|
@@ -11,8 +11,9 @@ Usage:
|
|
|
11
11
|
pr-shepherd apply journal [PR] --file <path> [--dry-run] [--format text|json]
|
|
12
12
|
pr-shepherd apply check-blocker [PR] --check <name> --blocked-by <ref>
|
|
13
13
|
pr-shepherd apply check-blocker [PR] --check <name> --clear
|
|
14
|
+
pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>
|
|
14
15
|
|
|
15
|
-
Run 'pr-shepherd apply <review|files|journal|check-blocker> --help' for command-specific details.
|
|
16
|
+
Run 'pr-shepherd apply <review|files|journal|check-blocker|queue-removal> --help' for command-specific details.
|
|
16
17
|
--help, -h Print this help and exit before GitHub I/O.`;
|
|
17
18
|
readonly "apply review": `pr-shepherd apply review
|
|
18
19
|
|
|
@@ -80,6 +81,20 @@ Usage:
|
|
|
80
81
|
|
|
81
82
|
\`--clear\` removes that check's record and leaves other checks alone.
|
|
82
83
|
--help, -h Print this help and exit before any I/O.`;
|
|
84
|
+
readonly "apply queue-removal": `pr-shepherd apply queue-removal
|
|
85
|
+
|
|
86
|
+
Acknowledge one current CI-driven merge-queue removal for a native-stack PR.
|
|
87
|
+
This writes local state only after a fresh GitHub read confirms the supplied head,
|
|
88
|
+
queue commit, and removal timestamp still identify the current removal.
|
|
89
|
+
|
|
90
|
+
Usage:
|
|
91
|
+
pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>
|
|
92
|
+
|
|
93
|
+
--require-sha <sha> Full 40-character lowercase PR head SHA observed with the failure.
|
|
94
|
+
--queue-commit <sha> Full 40-character lowercase synthetic merge-group commit SHA.
|
|
95
|
+
--removed-at <unix> Removal time in Unix seconds.
|
|
96
|
+
--format text|json Output format. Default: text.
|
|
97
|
+
--help, -h Print this help and exit before any I/O.`;
|
|
83
98
|
readonly "build-suggestion-patches": `pr-shepherd build-suggestion-patches
|
|
84
99
|
|
|
85
100
|
Build an ordered list of patches and commit instructions from GitHub review suggestions.
|
|
@@ -212,7 +227,7 @@ Flags:
|
|
|
212
227
|
|
|
213
228
|
PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
|
|
214
229
|
Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
215
|
-
readonly iterate: "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n READY Clean PR is inside the ready-delay. Schedule the rerun for when remainingSeconds elapses. Do not invent unrelated work. Already-owned later work may continue.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
230
|
+
readonly iterate: "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n READY Clean PR is inside the ready-delay. Schedule the rerun for when remainingSeconds elapses. Do not invent unrelated work. Already-owned later work may continue.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling this pull request: merged/closed or ready-delay elapsed. Continue remaining work from the original request.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
216
231
|
readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns READY, MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s). Stack and multi-PR polls multiply that by poll.stackIntervalFactor (built-in 2) unless this flag is set.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until READY/FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes a stderr line naming what it is waiting on by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until READY, FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
217
232
|
readonly clean: `pr-shepherd clean
|
|
218
233
|
|
|
@@ -14,8 +14,9 @@ Usage:
|
|
|
14
14
|
pr-shepherd apply journal [PR] --file <path> [--dry-run] [--format text|json]
|
|
15
15
|
pr-shepherd apply check-blocker [PR] --check <name> --blocked-by <ref>
|
|
16
16
|
pr-shepherd apply check-blocker [PR] --check <name> --clear
|
|
17
|
+
pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>
|
|
17
18
|
|
|
18
|
-
Run 'pr-shepherd apply <review|files|journal|check-blocker> --help' for command-specific details.
|
|
19
|
+
Run 'pr-shepherd apply <review|files|journal|check-blocker|queue-removal> --help' for command-specific details.
|
|
19
20
|
--help, -h Print this help and exit before GitHub I/O.`,
|
|
20
21
|
"apply review": `pr-shepherd apply review
|
|
21
22
|
|
|
@@ -83,6 +84,20 @@ Usage:
|
|
|
83
84
|
|
|
84
85
|
\`--clear\` removes that check's record and leaves other checks alone.
|
|
85
86
|
--help, -h Print this help and exit before any I/O.`,
|
|
87
|
+
"apply queue-removal": `pr-shepherd apply queue-removal
|
|
88
|
+
|
|
89
|
+
Acknowledge one current CI-driven merge-queue removal for a native-stack PR.
|
|
90
|
+
This writes local state only after a fresh GitHub read confirms the supplied head,
|
|
91
|
+
queue commit, and removal timestamp still identify the current removal.
|
|
92
|
+
|
|
93
|
+
Usage:
|
|
94
|
+
pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>
|
|
95
|
+
|
|
96
|
+
--require-sha <sha> Full 40-character lowercase PR head SHA observed with the failure.
|
|
97
|
+
--queue-commit <sha> Full 40-character lowercase synthetic merge-group commit SHA.
|
|
98
|
+
--removed-at <unix> Removal time in Unix seconds.
|
|
99
|
+
--format text|json Output format. Default: text.
|
|
100
|
+
--help, -h Print this help and exit before any I/O.`,
|
|
86
101
|
"build-suggestion-patches": `pr-shepherd build-suggestion-patches
|
|
87
102
|
|
|
88
103
|
Build an ordered list of patches and commit instructions from GitHub review suggestions.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const ITERATE_USAGE = "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n READY Clean PR is inside the ready-delay. Schedule the rerun for when remainingSeconds elapses. Do not invent unrelated work. Already-owned later work may continue.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
1
|
+
export declare const ITERATE_USAGE = "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n READY Clean PR is inside the ready-delay. Schedule the rerun for when remainingSeconds elapses. Do not invent unrelated work. Already-owned later work may continue.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling this pull request: merged/closed or ready-delay elapsed. Continue remaining work from the original request.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
2
2
|
export declare const POLL_USAGE = "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns READY, MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s). Stack and multi-PR polls multiply that by poll.stackIntervalFactor (built-in 2) unless this flag is set.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until READY/FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes a stderr line naming what it is waiting on by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until READY, FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
3
3
|
/** Public help page for the default PR polling invocation. */
|
|
4
4
|
export declare const DEFAULT_USAGE: string;
|
|
@@ -23,7 +23,7 @@ Actions:
|
|
|
23
23
|
MARK_READY Draft PR was marked ready; continue with the next poll.
|
|
24
24
|
READY Clean PR is inside the ready-delay. Schedule the rerun for when remainingSeconds elapses. Do not invent unrelated work. Already-owned later work may continue.
|
|
25
25
|
FIX_CODE Agent action is required; follow the instructions, then continue polling.
|
|
26
|
-
CANCEL Stop polling: merged/closed or ready-delay elapsed.
|
|
26
|
+
CANCEL Stop polling this pull request: merged/closed or ready-delay elapsed. Continue remaining work from the original request.
|
|
27
27
|
ESCALATE Stop polling until a human provides direction.
|
|
28
28
|
MERGE Run the emitted merge/queue command, then continue monitoring.
|
|
29
29
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const TOP_USAGE = "pr-shepherd\n\nAutonomous PR CI monitor and review-comment resolver for agentic coding tools.\n\nUsage:\n pr-shepherd --version | -v\n pr-shepherd --help | -h\n pr-shepherd [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd --stack PR [poll-flags] [iterate-flags]\n pr-shepherd iterate [PR] [iterate-flags]\n pr-shepherd apply review [PR] [review-flags]\n pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]\n pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]\n pr-shepherd apply check-blocker [PR] --check <name> (--blocked-by <ref>|--clear)\n pr-shepherd journal extract --body-file <path>\n pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]\n pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]\n pr-shepherd admin log-file [--format text|json]\n\nCommands:\n [PR ...] Poll one PR, an explicit same-repository set, or a native stack.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Mark selected changed files as viewed.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n apply check-blocker Record that a failing check is blocked on an external PR or issue.\n journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.\n build-suggestion-patches\n Convert ordered GitHub suggestion threads into patches and commit instructions.\n admin clean Remove pr-shepherd state files.\n admin log-file Print the per-worktree debug log path.\n\nPR argument:\n PR may be a number, owner/repo#number, or a GitHub pull request URL.\n Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.\n When omitted, pr-shepherd infers the current branch's pull request.\n\nCommon flags:\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed poll-tick lines.\n --help, -h Print help and exit before any GitHub, git, config, or log I/O.\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s). Stack and multi-PR polls multiply that by poll.stackIntervalFactor (built-in 2) unless this flag is set.\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until READY/FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nClean variants:\n pr [number] Remove state for one PR. Defaults to current branch PR.\n branch [name] Remove state for a branch's PR. Defaults to current branch.\n current Alias for branch against the current branch.\n repo Remove all state for the current repository.\n all Remove all pr-shepherd state.\n\nExit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\nSee docs/exit-codes.md for the full sysexits.h error-code table.\n\nDuration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).\n\nRun 'pr-shepherd <command> --help' for command-specific details.";
|
|
1
|
+
export declare const TOP_USAGE = "pr-shepherd\n\nAutonomous PR CI monitor and review-comment resolver for agentic coding tools.\n\nUsage:\n pr-shepherd --version | -v\n pr-shepherd --help | -h\n pr-shepherd [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd --stack PR [poll-flags] [iterate-flags]\n pr-shepherd iterate [PR] [iterate-flags]\n pr-shepherd apply review [PR] [review-flags]\n pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]\n pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]\n pr-shepherd apply check-blocker [PR] --check <name> (--blocked-by <ref>|--clear)\n pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>\n pr-shepherd journal extract --body-file <path>\n pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]\n pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]\n pr-shepherd admin log-file [--format text|json]\n\nCommands:\n [PR ...] Poll one PR, an explicit same-repository set, or a native stack.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Mark selected changed files as viewed.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n apply check-blocker Record that a failing check is blocked on an external PR or issue.\n apply queue-removal Acknowledge one current CI-driven native-stack queue removal.\n journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.\n build-suggestion-patches\n Convert ordered GitHub suggestion threads into patches and commit instructions.\n admin clean Remove pr-shepherd state files.\n admin log-file Print the per-worktree debug log path.\n\nPR argument:\n PR may be a number, owner/repo#number, or a GitHub pull request URL.\n Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.\n When omitted, pr-shepherd infers the current branch's pull request.\n\nCommon flags:\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed poll-tick lines.\n --help, -h Print help and exit before any GitHub, git, config, or log I/O.\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s). Stack and multi-PR polls multiply that by poll.stackIntervalFactor (built-in 2) unless this flag is set.\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until READY/FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nClean variants:\n pr [number] Remove state for one PR. Defaults to current branch PR.\n branch [name] Remove state for a branch's PR. Defaults to current branch.\n current Alias for branch against the current branch.\n repo Remove all state for the current repository.\n all Remove all pr-shepherd state.\n\nExit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\nSee docs/exit-codes.md for the full sysexits.h error-code table.\n\nDuration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).\n\nRun 'pr-shepherd <command> --help' for command-specific details.";
|
|
@@ -12,6 +12,7 @@ Usage:
|
|
|
12
12
|
pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]
|
|
13
13
|
pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]
|
|
14
14
|
pr-shepherd apply check-blocker [PR] --check <name> (--blocked-by <ref>|--clear)
|
|
15
|
+
pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>
|
|
15
16
|
pr-shepherd journal extract --body-file <path>
|
|
16
17
|
pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]
|
|
17
18
|
pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]
|
|
@@ -24,6 +25,7 @@ Commands:
|
|
|
24
25
|
apply files Mark selected changed files as viewed.
|
|
25
26
|
apply journal Append a list item to the Shepherd Journal details block of a PR body.
|
|
26
27
|
apply check-blocker Record that a failing check is blocked on an external PR or issue.
|
|
28
|
+
apply queue-removal Acknowledge one current CI-driven native-stack queue removal.
|
|
27
29
|
journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.
|
|
28
30
|
build-suggestion-patches
|
|
29
31
|
Convert ordered GitHub suggestion threads into patches and commit instructions.
|
package/bin/cli/help.d.mts
CHANGED
|
@@ -11,8 +11,9 @@ Usage:
|
|
|
11
11
|
pr-shepherd apply journal [PR] --file <path> [--dry-run] [--format text|json]
|
|
12
12
|
pr-shepherd apply check-blocker [PR] --check <name> --blocked-by <ref>
|
|
13
13
|
pr-shepherd apply check-blocker [PR] --check <name> --clear
|
|
14
|
+
pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>
|
|
14
15
|
|
|
15
|
-
Run 'pr-shepherd apply <review|files|journal|check-blocker> --help' for command-specific details.
|
|
16
|
+
Run 'pr-shepherd apply <review|files|journal|check-blocker|queue-removal> --help' for command-specific details.
|
|
16
17
|
--help, -h Print this help and exit before GitHub I/O.`;
|
|
17
18
|
readonly "apply review": `pr-shepherd apply review
|
|
18
19
|
|
|
@@ -80,6 +81,20 @@ Usage:
|
|
|
80
81
|
|
|
81
82
|
\`--clear\` removes that check's record and leaves other checks alone.
|
|
82
83
|
--help, -h Print this help and exit before any I/O.`;
|
|
84
|
+
readonly "apply queue-removal": `pr-shepherd apply queue-removal
|
|
85
|
+
|
|
86
|
+
Acknowledge one current CI-driven merge-queue removal for a native-stack PR.
|
|
87
|
+
This writes local state only after a fresh GitHub read confirms the supplied head,
|
|
88
|
+
queue commit, and removal timestamp still identify the current removal.
|
|
89
|
+
|
|
90
|
+
Usage:
|
|
91
|
+
pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>
|
|
92
|
+
|
|
93
|
+
--require-sha <sha> Full 40-character lowercase PR head SHA observed with the failure.
|
|
94
|
+
--queue-commit <sha> Full 40-character lowercase synthetic merge-group commit SHA.
|
|
95
|
+
--removed-at <unix> Removal time in Unix seconds.
|
|
96
|
+
--format text|json Output format. Default: text.
|
|
97
|
+
--help, -h Print this help and exit before any I/O.`;
|
|
83
98
|
readonly "build-suggestion-patches": `pr-shepherd build-suggestion-patches
|
|
84
99
|
|
|
85
100
|
Build an ordered list of patches and commit instructions from GitHub review suggestions.
|
|
@@ -212,7 +227,7 @@ Flags:
|
|
|
212
227
|
|
|
213
228
|
PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
|
|
214
229
|
Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
215
|
-
readonly iterate: "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n READY Clean PR is inside the ready-delay. Schedule the rerun for when remainingSeconds elapses. Do not invent unrelated work. Already-owned later work may continue.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling: merged/closed or ready-delay elapsed.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
230
|
+
readonly iterate: "pr-shepherd iterate\n\nRun one iterate tick for a pull request. The no-subcommand form polls; use this subcommand for a single tick.\nThe output contains one action and an action-specific ## Instructions section.\n\nUsage:\n pr-shepherd iterate [PR] [iterate-flags]\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number is minutes; decimals are allowed only with an explicit unit (4.5m).\n\nActions:\n WAIT No immediate action; continue with the next poll.\n MARK_READY Draft PR was marked ready; continue with the next poll.\n READY Clean PR is inside the ready-delay. Schedule the rerun for when remainingSeconds elapses. Do not invent unrelated work. Already-owned later work may continue.\n FIX_CODE Agent action is required; follow the instructions, then continue polling.\n CANCEL Stop polling this pull request: merged/closed or ready-delay elapsed. Continue remaining work from the original request.\n ESCALATE Stop polling until a human provides direction.\n MERGE Run the emitted merge/queue command, then continue monitoring.\n\nExit codes:\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
216
231
|
readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for one PR, or read compact summaries for an explicit PR set or native\nGitHub stack. Aggregate mode returns when any row needs work, every row is terminal, or timeout.\nPoll exits as soon as iterate returns READY, MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default:\npoll.debounceSeconds; built-in 1m): poll keeps\niterating at --interval, then runs one more tick after the window and returns that result.\nWith --until-terminal or --merge, poll also continues through MARK_READY.\n\nUsage:\n pr-shepherd poll [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd poll --stack PR [poll-flags] [iterate-flags]\n\nPoll flags:\n --stack PR Select all entries in PR's native GitHub stack, bottom to top.\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s). Stack and multi-PR polls multiply that by poll.stackIntervalFactor (built-in 2) unless this flag is set.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until READY/FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nForwarded iterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed per-tick lines.\n --help, -h Print this help and exit before GitHub, git, config, or log I/O.\n\nDurations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds\nfor --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with\nan explicit unit (4.5m).\nEach WAIT tick writes a stderr line naming what it is waiting on by default; poll.quietStatus can change that default, --quiet-status/--no-quiet-status override it, and --verbose emits detailed per-tick lines.\nFIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.\nWith --until-terminal, --timeout is ignored for WAIT ticks and polling continues until READY, FIX_CODE, MERGE, CANCEL, or ESCALATE. With --merge, --timeout still bounds WAIT ticks; it only continues through MARK_READY while polling remains within that timeout.\n\nExit codes: same as iterate (the final tick's action/reason decides the code).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY (including a WAIT returned by --timeout)\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\n A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
217
232
|
readonly clean: `pr-shepherd clean
|
|
218
233
|
|
|
@@ -277,7 +292,7 @@ On POSIX, the final body-file path entry must be a readable regular file in a tr
|
|
|
277
292
|
symlinks, FIFOs, devices, and unreadable paths exit 66. Unsupported platforms fail closed with exit 66.
|
|
278
293
|
--help, -h Print this help and exit before any I/O.`;
|
|
279
294
|
readonly "log-file": "pr-shepherd log-file\n\nPrint the per-worktree append-only debug log path for the current repository.\nThe log is created by the first non-help pr-shepherd command that initializes logging.\n\nUsage:\n pr-shepherd log-file [--format text|json]\n\nFlags:\n --format text|json Print a raw path or {\"path\": \"...\"} JSON. Default: text.\n --help, -h Print this help and exit before logging setup.\n\nEnvironment:\n PR_SHEPHERD_LOG_DISABLED=1 disables logging.\n PR_SHEPHERD_STATE_DIR overrides the base state directory.\n\nExit code: 0 on success; 1 if repository identity cannot be resolved.";
|
|
280
|
-
readonly top: "pr-shepherd\n\nAutonomous PR CI monitor and review-comment resolver for agentic coding tools.\n\nUsage:\n pr-shepherd --version | -v\n pr-shepherd --help | -h\n pr-shepherd [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd --stack PR [poll-flags] [iterate-flags]\n pr-shepherd iterate [PR] [iterate-flags]\n pr-shepherd apply review [PR] [review-flags]\n pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]\n pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]\n pr-shepherd apply check-blocker [PR] --check <name> (--blocked-by <ref>|--clear)\n pr-shepherd journal extract --body-file <path>\n pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]\n pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]\n pr-shepherd admin log-file [--format text|json]\n\nCommands:\n [PR ...] Poll one PR, an explicit same-repository set, or a native stack.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Mark selected changed files as viewed.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n apply check-blocker Record that a failing check is blocked on an external PR or issue.\n journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.\n build-suggestion-patches\n Convert ordered GitHub suggestion threads into patches and commit instructions.\n admin clean Remove pr-shepherd state files.\n admin log-file Print the per-worktree debug log path.\n\nPR argument:\n PR may be a number, owner/repo#number, or a GitHub pull request URL.\n Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.\n When omitted, pr-shepherd infers the current branch's pull request.\n\nCommon flags:\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed poll-tick lines.\n --help, -h Print help and exit before any GitHub, git, config, or log I/O.\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s). Stack and multi-PR polls multiply that by poll.stackIntervalFactor (built-in 2) unless this flag is set.\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until READY/FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nClean variants:\n pr [number] Remove state for one PR. Defaults to current branch PR.\n branch [name] Remove state for a branch's PR. Defaults to current branch.\n current Alias for branch against the current branch.\n repo Remove all state for the current repository.\n all Remove all pr-shepherd state.\n\nExit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\nSee docs/exit-codes.md for the full sysexits.h error-code table.\n\nDuration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).\n\nRun 'pr-shepherd <command> --help' for command-specific details.";
|
|
295
|
+
readonly top: "pr-shepherd\n\nAutonomous PR CI monitor and review-comment resolver for agentic coding tools.\n\nUsage:\n pr-shepherd --version | -v\n pr-shepherd --help | -h\n pr-shepherd [PR ...] [poll-flags] [iterate-flags]\n pr-shepherd --stack PR [poll-flags] [iterate-flags]\n pr-shepherd iterate [PR] [iterate-flags]\n pr-shepherd apply review [PR] [review-flags]\n pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]\n pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]\n pr-shepherd apply check-blocker [PR] --check <name> (--blocked-by <ref>|--clear)\n pr-shepherd apply queue-removal [PR] --require-sha <head> --queue-commit <commit> --removed-at <unix>\n pr-shepherd journal extract --body-file <path>\n pr-shepherd build-suggestion-patches [PR] --thread-id ID --message MSG [groups...]\n pr-shepherd admin clean <pr|branch|current|repo|all> [value] [flags]\n pr-shepherd admin log-file [--format text|json]\n\nCommands:\n [PR ...] Poll one PR, an explicit same-repository set, or a native stack.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Mark selected changed files as viewed.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n apply check-blocker Record that a failing check is blocked on an external PR or issue.\n apply queue-removal Acknowledge one current CI-driven native-stack queue removal.\n journal extract Extract a validated Shepherd Journal from a local PR-body file as JSON.\n build-suggestion-patches\n Convert ordered GitHub suggestion threads into patches and commit instructions.\n admin clean Remove pr-shepherd state files.\n admin log-file Print the per-worktree debug log path.\n\nPR argument:\n PR may be a number, owner/repo#number, or a GitHub pull request URL.\n Multiple PRs must name one repository. --stack PR selects every entry in PR's native stack.\n When omitted, pr-shepherd infers the current branch's pull request.\n\nCommon flags:\n --format text|json Output Markdown text or JSON. Default: text.\n --verbose Include verbose iterate fields and detailed poll-tick lines.\n --help, -h Print help and exit before any GitHub, git, config, or log I/O.\n\nIterate flags:\n --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.\n --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.\n --no-auto-mark-ready Do not convert draft PRs to ready for review.\n --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.\n --merge Shepherd through readiness, then emit a merge or merge-queue command.\n\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: poll.intervalSeconds (built-in 60s). Stack and multi-PR polls multiply that by poll.stackIntervalFactor (built-in 2) unless this flag is set.\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: poll.timeoutSeconds (built-in 4.5m).\n --debounce <duration> Settle window after first FIX_CODE or stack SHEPHERD before returning. Bare number = seconds. Default: poll.debounceSeconds (built-in 60s). 0 disables.\n --quiet-status Print only changed WAIT snapshots. Overrides poll.quietStatus.\n --no-quiet-status Print every WAIT snapshot. Overrides poll.quietStatus.\n --until-terminal Continue through WAIT/MARK_READY until READY/FIX_CODE/MERGE/CANCEL/ESCALATE or stack SHEPHERD.\n\nClean variants:\n pr [number] Remove state for one PR. Defaults to current branch PR.\n branch [name] Remove state for a branch's PR. Defaults to current branch.\n current Alias for branch against the current branch.\n repo Remove all state for the current repository.\n all Remove all pr-shepherd state.\n\nExit codes: 0 done, 10-19 PR state, 64-78 shepherd failed (sysexits.h).\n 0 CANCEL (merged or ready-delay elapsed)\n 10 WAIT or READY\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\n 15 MERGE\n 16 SHEPHERD (--stack only: run the listed one-PR sessions, then rerun the selector)\nSee docs/exit-codes.md for the full sysexits.h error-code table.\n\nDuration examples: 30s, 4.5m, 1h. A bare number uses each flag's default unit (see above); decimals are allowed with an explicit unit (4.5m).\n\nRun 'pr-shepherd <command> --help' for command-specific details.";
|
|
281
296
|
};
|
|
282
297
|
/** Resolve help keys for nested public commands before any command I/O. */
|
|
283
298
|
export declare function helpKeyForArgs(args: string[]): keyof typeof USAGE;
|
package/bin/cli/help.mjs
CHANGED
|
@@ -15,6 +15,8 @@ export function helpKeyForArgs(args) {
|
|
|
15
15
|
return "apply journal";
|
|
16
16
|
if (args[0] === "apply" && args[1] === "check-blocker")
|
|
17
17
|
return "apply check-blocker";
|
|
18
|
+
if (args[0] === "apply" && args[1] === "queue-removal")
|
|
19
|
+
return "apply queue-removal";
|
|
18
20
|
if (args[0] === "journal" && args[1] === "extract")
|
|
19
21
|
return "journal extract";
|
|
20
22
|
if (args[0] === "admin" && args[1] === "clean")
|
|
@@ -48,7 +48,10 @@ export function buildSimpleIterateInstructions(result) {
|
|
|
48
48
|
return instructions;
|
|
49
49
|
}
|
|
50
50
|
case "cancel":
|
|
51
|
-
return [
|
|
51
|
+
return [
|
|
52
|
+
"Stop polling this pull request — its poll is complete.",
|
|
53
|
+
"Continue any remaining pull requests or issues from the original request.",
|
|
54
|
+
];
|
|
52
55
|
case "escalate": {
|
|
53
56
|
const pending = result.escalate.pendingReviewCommands;
|
|
54
57
|
if (!pending)
|
package/bin/cli/iterate-lean.mjs
CHANGED
|
@@ -152,6 +152,9 @@ export function projectIterateLean(result, opts) {
|
|
|
152
152
|
protectedRuns: result.fix.protectedRuns,
|
|
153
153
|
}),
|
|
154
154
|
...(result.fix.requeue && { requeue: result.fix.requeue }),
|
|
155
|
+
...(result.fix.queueRemovalAcknowledgment && {
|
|
156
|
+
queueRemovalAcknowledgment: result.fix.queueRemovalAcknowledgment,
|
|
157
|
+
}),
|
|
155
158
|
...(result.fix.checks.length > 0 && { checks: result.fix.checks }),
|
|
156
159
|
...(result.fix.changesRequestedReviews.length > 0 && {
|
|
157
160
|
changesRequestedReviews: result.fix.changesRequestedReviews,
|
|
@@ -40,6 +40,8 @@ export function appendMergeQueueHeader(lines, result) {
|
|
|
40
40
|
parts.push("checks incomplete (first 100 shown)");
|
|
41
41
|
if (queue.headUpdatedAfterRemoval)
|
|
42
42
|
parts.push("head updated after removal");
|
|
43
|
+
if (queue.removalAcknowledged)
|
|
44
|
+
parts.push("removal acknowledged");
|
|
43
45
|
lines.push(`**merge queue** ${parts.join(" · ")}`);
|
|
44
46
|
if (queue.autoMergeRequest) {
|
|
45
47
|
lines.push(`**auto-merge** method \`${queue.autoMergeRequest.mergeMethod}\` · enabledAtUnix \`${queue.autoMergeRequest.enabledAtUnix}\`${queue.autoMergeRequest.enabledBy ? ` · by \`@${queue.autoMergeRequest.enabledBy}\`` : ""}`);
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { EXIT, errorToExitCode } from "../exit-codes.mjs";
|
|
2
|
+
import { applyQueueRemovalAck } from "../commands/apply-queue-removal.mjs";
|
|
3
|
+
import { getFlag, parseCommonArgs } from "./args.mjs";
|
|
4
|
+
import { maybePrintHelp, USAGE } from "./help.mjs";
|
|
5
|
+
const FLAGS = new Set(["--require-sha", "--queue-commit", "--removed-at"]);
|
|
6
|
+
/** `apply queue-removal`; help exits before argument validation and GitHub I/O. */
|
|
7
|
+
export async function handleQueueRemoval(args) {
|
|
8
|
+
if (maybePrintHelp(args, "apply queue-removal"))
|
|
9
|
+
return;
|
|
10
|
+
const { prNumber, global, extra } = parseCommonArgs(args);
|
|
11
|
+
const flagError = validateFlags(extra);
|
|
12
|
+
if (flagError) {
|
|
13
|
+
usage(flagError);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const headSha = getFlag(extra, "--require-sha");
|
|
17
|
+
const queueCommitOid = getFlag(extra, "--queue-commit");
|
|
18
|
+
const removedAtText = getFlag(extra, "--removed-at");
|
|
19
|
+
if (headSha === null || !/^[0-9a-f]{40}$/.test(headSha)) {
|
|
20
|
+
usage("--require-sha must be a full 40-character lowercase hex SHA.", EXIT.DATAERR);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (queueCommitOid === null || !/^[0-9a-f]{40}$/.test(queueCommitOid)) {
|
|
24
|
+
usage("--queue-commit must be a full 40-character lowercase hex SHA.", EXIT.DATAERR);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (removedAtText === null || !/^[1-9]\d*$/.test(removedAtText)) {
|
|
28
|
+
usage("--removed-at must be a positive Unix timestamp in seconds.", EXIT.DATAERR);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const removedAtUnix = Number(removedAtText);
|
|
32
|
+
if (!Number.isSafeInteger(removedAtUnix)) {
|
|
33
|
+
usage("--removed-at must be a positive Unix timestamp in seconds.", EXIT.DATAERR);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const result = await applyQueueRemovalAck({
|
|
38
|
+
prNumber,
|
|
39
|
+
targetRepository: global.targetRepository,
|
|
40
|
+
headSha,
|
|
41
|
+
queueCommitOid,
|
|
42
|
+
removedAtUnix,
|
|
43
|
+
});
|
|
44
|
+
const body = global.format === "json"
|
|
45
|
+
? JSON.stringify(result, null, 2)
|
|
46
|
+
: [
|
|
47
|
+
`PR: ${result.repo}#${result.pr}`,
|
|
48
|
+
`headSha: ${result.acknowledgment.headSha}`,
|
|
49
|
+
`queueCommitOid: ${result.acknowledgment.queueCommitOid}`,
|
|
50
|
+
`removedAtUnix: ${result.acknowledgment.removedAtUnix}`,
|
|
51
|
+
].join("\n");
|
|
52
|
+
process.stdout.write(`${body}\n`);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
56
|
+
process.stderr.write(`pr-shepherd: apply queue-removal: ${message}\n`);
|
|
57
|
+
process.exitCode = errorToExitCode(err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function validateFlags(args) {
|
|
61
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
62
|
+
const arg = args[i];
|
|
63
|
+
if (!arg.startsWith("--"))
|
|
64
|
+
return `unexpected argument: "${arg}"`;
|
|
65
|
+
const eq = arg.indexOf("=");
|
|
66
|
+
const name = eq === -1 ? arg : arg.slice(0, eq);
|
|
67
|
+
if (!FLAGS.has(name))
|
|
68
|
+
return `unknown flag: "${name}"`;
|
|
69
|
+
if (eq !== -1) {
|
|
70
|
+
if (arg.slice(eq + 1) === "")
|
|
71
|
+
return `${name} requires a value.`;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const value = args[i + 1];
|
|
75
|
+
if (value === undefined || value.startsWith("--"))
|
|
76
|
+
return `${name} requires a value.`;
|
|
77
|
+
i += 1;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function usage(message, exitCode = EXIT.USAGE) {
|
|
82
|
+
process.stderr.write(`pr-shepherd: apply queue-removal: ${message}\n`);
|
|
83
|
+
if (exitCode === EXIT.USAGE)
|
|
84
|
+
process.stderr.write(`${USAGE["apply queue-removal"]}\n`);
|
|
85
|
+
process.exitCode = exitCode;
|
|
86
|
+
}
|
package/bin/cli-parser.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import { handleJournalExtract } from "./cli/journal-extract-handler.mjs";
|
|
|
14
14
|
import { handlePoll } from "./cli/poll-handler.mjs";
|
|
15
15
|
import { warnPrrcThreadIds, validateRequireSha, rejectPrrcMinimizeIds, } from "./cli/resolve-validators.mjs";
|
|
16
16
|
import { handleCheckBlocker } from "./cli/check-blocker-handler.mjs";
|
|
17
|
+
import { handleQueueRemoval } from "./cli/queue-removal-handler.mjs";
|
|
17
18
|
import { setupLog } from "./log/setup.mjs";
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// Entry
|
|
@@ -135,6 +136,9 @@ async function handleApply(args) {
|
|
|
135
136
|
case "check-blocker":
|
|
136
137
|
await handleCheckBlocker(args.slice(1));
|
|
137
138
|
return;
|
|
139
|
+
case "queue-removal":
|
|
140
|
+
await handleQueueRemoval(args.slice(1));
|
|
141
|
+
return;
|
|
138
142
|
default:
|
|
139
143
|
process.stderr.write(`Unknown apply action: ${action ?? "(none)"}\n`);
|
|
140
144
|
process.stderr.write(`${USAGE.apply}\n`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type QueueRemovalAcknowledgment } from "../state/queue-removal-ack.mts";
|
|
2
|
+
export interface ApplyQueueRemovalInput {
|
|
3
|
+
prNumber?: number;
|
|
4
|
+
targetRepository?: {
|
|
5
|
+
owner: string;
|
|
6
|
+
name: string;
|
|
7
|
+
};
|
|
8
|
+
headSha: string;
|
|
9
|
+
queueCommitOid: string;
|
|
10
|
+
removedAtUnix: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ApplyQueueRemovalResult {
|
|
13
|
+
pr: number;
|
|
14
|
+
repo: string;
|
|
15
|
+
acknowledgment: QueueRemovalAcknowledgment;
|
|
16
|
+
}
|
|
17
|
+
/** Validate an observed native-stack CI queue removal before recording the caller's acknowledgment. */
|
|
18
|
+
export declare function applyQueueRemovalAck(input: ApplyQueueRemovalInput): Promise<ApplyQueueRemovalResult>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
2
|
+
import { fetchPrBatch } from "../github/batch.mjs";
|
|
3
|
+
import { queueRemovalAppliesToHead } from "../github/queue-removal-freshness.mjs";
|
|
4
|
+
import { getCurrentPrNumber, getRepoInfo } from "../github/client.mjs";
|
|
5
|
+
import { isCiQueueRemovalReason, writeQueueRemovalAcknowledgment, } from "../state/queue-removal-ack.mjs";
|
|
6
|
+
/** Validate an observed native-stack CI queue removal before recording the caller's acknowledgment. */
|
|
7
|
+
export async function applyQueueRemovalAck(input) {
|
|
8
|
+
const repo = input.targetRepository ?? (await getRepoInfo());
|
|
9
|
+
const pr = input.prNumber ?? (await getCurrentPrNumber());
|
|
10
|
+
if (pr === null) {
|
|
11
|
+
throw new ShepherdError("No open PR found for current branch. Pass a PR number explicitly.", EXIT.UNAVAILABLE);
|
|
12
|
+
}
|
|
13
|
+
const { data } = await fetchPrBatch(pr, repo);
|
|
14
|
+
const removal = data.latestMergeQueueRemoval;
|
|
15
|
+
if (data.state !== "OPEN" ||
|
|
16
|
+
!data.stack ||
|
|
17
|
+
data.isMergeQueueEnabled !== true ||
|
|
18
|
+
data.isInMergeQueue === true ||
|
|
19
|
+
!removal ||
|
|
20
|
+
!isCiQueueRemovalReason(removal.reason) ||
|
|
21
|
+
data.headRefOid !== input.headSha ||
|
|
22
|
+
removal.beforeCommitOid !== input.queueCommitOid ||
|
|
23
|
+
removal.createdAtUnix !== input.removedAtUnix ||
|
|
24
|
+
!queueRemovalAppliesToHead({
|
|
25
|
+
parentOids: removal.beforeCommitParentOids,
|
|
26
|
+
headOid: data.headRefOid,
|
|
27
|
+
...(data.activity?.latestCommitCommittedAtUnix != null && {
|
|
28
|
+
headCommittedAtUnix: data.activity.latestCommitCommittedAtUnix,
|
|
29
|
+
}),
|
|
30
|
+
...(data.headPushedAtUnix !== undefined && { headPushedAtUnix: data.headPushedAtUnix }),
|
|
31
|
+
removedAtUnix: removal.createdAtUnix,
|
|
32
|
+
})) {
|
|
33
|
+
throw new ShepherdError("The supplied queue-removal evidence is stale or is not a current CI-driven removal for this native-stack PR.", EXIT.UNAVAILABLE);
|
|
34
|
+
}
|
|
35
|
+
const acknowledgment = {
|
|
36
|
+
headSha: input.headSha,
|
|
37
|
+
queueCommitOid: input.queueCommitOid,
|
|
38
|
+
removedAtUnix: input.removedAtUnix,
|
|
39
|
+
};
|
|
40
|
+
if (!(await writeQueueRemovalAcknowledgment({ owner: repo.owner, repo: repo.name, pr }, acknowledgment))) {
|
|
41
|
+
throw new ShepherdError("Could not write queue-removal acknowledgment state.", EXIT.UNAVAILABLE);
|
|
42
|
+
}
|
|
43
|
+
return { pr, repo: `${repo.owner}/${repo.name}`, acknowledgment };
|
|
44
|
+
}
|
|
@@ -10,6 +10,8 @@ function reportAllowsFingerprintSkip(report) {
|
|
|
10
10
|
return false;
|
|
11
11
|
if (report.mergeQueue?.inQueue === true)
|
|
12
12
|
return false;
|
|
13
|
+
if (report.mergeQueue?.removalAcknowledged === true)
|
|
14
|
+
return false;
|
|
13
15
|
return (report.threads.actionable.length === 0 &&
|
|
14
16
|
report.threads.resolutionOnly.length === 0 &&
|
|
15
17
|
report.threads.firstLook.length === 0 &&
|
package/bin/commands/check.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fetchPrBatch } from "../github/batch.mjs";
|
|
2
2
|
import { queueRemovalAppliesToHead } from "../github/queue-removal-freshness.mjs";
|
|
3
|
+
import { readQueueRemovalAcknowledgment, matchesQueueRemovalAcknowledgment, isCiQueueRemovalReason, } from "../state/queue-removal-ack.mjs";
|
|
3
4
|
import { storePrFingerprint } from "../state/pr-fingerprint.mjs";
|
|
4
5
|
import { tryReuseFingerprintReport } from "./check-fingerprint.mjs";
|
|
5
6
|
import { collectUnreportedRequired, refreshCachedUnreported } from "./check-unreported.mjs";
|
|
@@ -91,9 +92,19 @@ export async function runCheck(opts, context) {
|
|
|
91
92
|
}),
|
|
92
93
|
removedAtUnix: latestRemoval.createdAtUnix,
|
|
93
94
|
}));
|
|
95
|
+
const removalAcknowledged = Boolean(batchData.stack &&
|
|
96
|
+
latestRemoval?.beforeCommitOid &&
|
|
97
|
+
isCiQueueRemovalReason(latestRemoval.reason) &&
|
|
98
|
+
!batchData.isInMergeQueue &&
|
|
99
|
+
!headUpdatedAfterRemoval &&
|
|
100
|
+
matchesQueueRemovalAcknowledgment(await readQueueRemovalAcknowledgment(stateKey), {
|
|
101
|
+
headSha: batchData.headRefOid,
|
|
102
|
+
queueCommitOid: latestRemoval.beforeCommitOid,
|
|
103
|
+
removedAtUnix: latestRemoval.createdAtUnix,
|
|
104
|
+
}));
|
|
94
105
|
const queueRawChecks = batchData.isInMergeQueue
|
|
95
106
|
? (batchData.mergeQueueChecks ?? [])
|
|
96
|
-
: latestRemoval && !headUpdatedAfterRemoval
|
|
107
|
+
: latestRemoval && !headUpdatedAfterRemoval && !removalAcknowledged
|
|
97
108
|
? (batchData.removedMergeQueueChecks ?? [])
|
|
98
109
|
: [];
|
|
99
110
|
// Keep supersession grouping commit-local, but accept merge_group only for the
|
|
@@ -356,6 +367,7 @@ export async function runCheck(opts, context) {
|
|
|
356
367
|
checksIncomplete: true,
|
|
357
368
|
}),
|
|
358
369
|
...(headUpdatedAfterRemoval && { headUpdatedAfterRemoval: true }),
|
|
370
|
+
...(removalAcknowledged && { removalAcknowledged: true }),
|
|
359
371
|
},
|
|
360
372
|
}),
|
|
361
373
|
...(unreported.unreportedRequiredChecks && {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AgentCheck } from "../../types.mts";
|
|
2
|
+
export declare function hasLogEvidence(check: AgentCheck): boolean;
|
|
3
|
+
/** An external provider link is inspectable even when Actions log downloads are unavailable. */
|
|
4
|
+
export declare function hasQueueRecoveryEvidence(check: AgentCheck): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function hasLogEvidence(check) {
|
|
2
|
+
return (Boolean(check.logExcerpt?.trim()) ||
|
|
3
|
+
(check.relatedJobs ?? []).some((job) => Boolean(job.logExcerpt?.trim())));
|
|
4
|
+
}
|
|
5
|
+
/** An external provider link is inspectable even when Actions log downloads are unavailable. */
|
|
6
|
+
export function hasQueueRecoveryEvidence(check) {
|
|
7
|
+
return hasLogEvidence(check) || (check.runId === null && Boolean(check.detailsUrl?.trim()));
|
|
8
|
+
}
|
|
@@ -6,6 +6,8 @@ import { checkEscalateTriggers, validateBaseBranch, buildEscalateSuggestion, bui
|
|
|
6
6
|
import { buildResolveCommand } from "./classify.mjs";
|
|
7
7
|
import { buildThreadMutationRouting, threadHasAuthorizedMutation, } from "./thread-mutation-routing.mjs";
|
|
8
8
|
import { buildFixInstructions } from "./render.mjs";
|
|
9
|
+
import { buildRemovedQueueRecovery, buildStackQueueRemovalAcknowledgment } from "./merge.mjs";
|
|
10
|
+
import { hasLogEvidence } from "./check-evidence.mjs";
|
|
9
11
|
import { buildReleasedBlockerInstruction } from "./check-instructions.mjs";
|
|
10
12
|
import { buildNativeStackLayerRebase } from "./native-stack-rebase.mjs";
|
|
11
13
|
import { lookupUpperLayerTrunkConflict } from "./stack-trunk-conflict.mjs";
|
|
@@ -19,10 +21,6 @@ import { canRerunWorkflows } from "../../checks/conclusions.mjs";
|
|
|
19
21
|
import { loadConfig } from "../../config/load.mjs";
|
|
20
22
|
import { formatPrUrl } from "../../pr-reference.mjs";
|
|
21
23
|
const EMPTY_RELEASED = new Set();
|
|
22
|
-
function hasLogEvidence(check) {
|
|
23
|
-
return (Boolean(check.logExcerpt?.trim()) ||
|
|
24
|
-
(check.relatedJobs ?? []).some((job) => Boolean(job.logExcerpt?.trim())));
|
|
25
|
-
}
|
|
26
24
|
function checkRequiresHumanFollowUp(check) {
|
|
27
25
|
if (check.rerunCommand)
|
|
28
26
|
return false;
|
|
@@ -171,6 +169,8 @@ export async function handleFixCode(ctx) {
|
|
|
171
169
|
if (releasedCheckNames.has(c.name))
|
|
172
170
|
return c;
|
|
173
171
|
return rerunAuthorized &&
|
|
172
|
+
// Rerunning queue CI cannot restore a removed entry and overwrites its failure evidence.
|
|
173
|
+
c.scope !== "merge_group" &&
|
|
174
174
|
c.runId &&
|
|
175
175
|
actionsRunIds.has(c.runId) &&
|
|
176
176
|
// GitHub increments run_attempt after every rerun. Recommend at most one rerun by limiting
|
|
@@ -306,6 +306,20 @@ export async function handleFixCode(ctx) {
|
|
|
306
306
|
? buildNativeStackLayerRebase(report.repo, { number: prNumber, baseBranch: baseLookup.branch }, stack, trunkConflict)
|
|
307
307
|
: undefined;
|
|
308
308
|
const instructions = buildFixInstructions(threads, actionableComments, checks, changesRequestedReviews, baseLookup.branch, resolveCommand, hasConflicts, prReference, cancelled.length, firstLookThreads, firstLookComments, firstLookSummaries, editedSummaries, inProgressRunIds, resolutionOnlyThreads, resolveOnlyCommand, behindBaseHint, isBehind, report.viewerAuthorization?.viewerCanUpdate === true, exhaustedAttempts.length > 0, stackRebase);
|
|
309
|
+
const requeue = buildRemovedQueueRecovery(report, failingAgentChecks, opts.merge);
|
|
310
|
+
const queueRemovalAcknowledgment = buildStackQueueRemovalAcknowledgment(report, failingAgentChecks);
|
|
311
|
+
if (queueRemovalAcknowledgment) {
|
|
312
|
+
const completion = instructions.pop();
|
|
313
|
+
instructions.push("If the merge-group failure belongs to this PR, fix and push its head, then iterate. Otherwise, if no code changed and no other blocker remains, run `acknowledge queue removal:` exactly as printed. This records only the disposition of that removed queue commit; finish this one-PR session to validate current source CI and record its READY receipt, then return to the aggregate `--stack` selector with its original options. In merge mode it verifies lower-layer readiness before merging. Do not enqueue or merge this layer directly.");
|
|
314
|
+
if (completion !== undefined)
|
|
315
|
+
instructions.push(completion);
|
|
316
|
+
}
|
|
317
|
+
if (requeue) {
|
|
318
|
+
const completion = instructions.pop();
|
|
319
|
+
instructions.push("If the merge-group failure belongs to this PR, fix and push the PR head, then iterate. Otherwise, if no code changed and no other blocker remains, run the `requeue:` command exactly as printed. If gh reports auto-merge is disabled instead of adding the PR to the queue, run the `requeue API fallback:` command. Both commands require the observed PR head SHA; if the head changed, iterate for a fresh command.");
|
|
320
|
+
if (completion !== undefined)
|
|
321
|
+
instructions.push(completion);
|
|
322
|
+
}
|
|
309
323
|
if (failingAgentChecks.some((check) => releasedCheckNames.has(check.name))) {
|
|
310
324
|
const completion = instructions.pop();
|
|
311
325
|
instructions.push(buildReleasedBlockerInstruction(prNumber));
|
|
@@ -342,6 +356,8 @@ export async function handleFixCode(ctx) {
|
|
|
342
356
|
editedSummaries,
|
|
343
357
|
surfacedApprovals,
|
|
344
358
|
checks,
|
|
359
|
+
...(requeue && { requeue }),
|
|
360
|
+
...(queueRemovalAcknowledgment && { queueRemovalAcknowledgment }),
|
|
345
361
|
changesRequestedReviews,
|
|
346
362
|
resolveCommand,
|
|
347
363
|
...(resolveOnlyCommand !== undefined ? { resolveOnlyCommand } : undefined),
|
|
@@ -109,7 +109,9 @@ async function runIterateCore(opts) {
|
|
|
109
109
|
editedSummaries.length > 0 ||
|
|
110
110
|
(config.iterate.minimizeApprovals && surfacedApprovals.length > 0);
|
|
111
111
|
const hasActionableWork = hasReadinessWork || report.comments.firstLook.length > 0;
|
|
112
|
-
const activeMerge = Boolean(opts.merge &&
|
|
112
|
+
const activeMerge = Boolean(opts.merge &&
|
|
113
|
+
(report.mergeQueue?.inQueue ||
|
|
114
|
+
(report.mergeQueue?.autoMergeRequest && !report.mergeQueue.removalAcknowledged)));
|
|
113
115
|
const staleAncestry = await findStaleNativeStackAncestry(report, {
|
|
114
116
|
owner: repoOwner,
|
|
115
117
|
name: repoName,
|
|
@@ -307,6 +309,18 @@ async function recordReadyReceipt(key, report, context) {
|
|
|
307
309
|
if (!isCurrentSummaryReady(raw, summary.checks ?? {}, summary.review ?? {}))
|
|
308
310
|
return false;
|
|
309
311
|
const removalEvent = currentQueueRemovalEvent(raw);
|
|
312
|
+
// Bind recovery to the exact evidence the one-PR check accepted. A new ejection
|
|
313
|
+
// between that check and this fresh snapshot must get its own acknowledgment.
|
|
314
|
+
if (report.mergeQueue?.removalAcknowledged) {
|
|
315
|
+
const accepted = report.mergeQueue.latestRemoval;
|
|
316
|
+
if (raw.isInMergeQueue ||
|
|
317
|
+
!accepted ||
|
|
318
|
+
!removalEvent ||
|
|
319
|
+
removalEvent.beforeCommit?.oid !== accepted.beforeCommitOid ||
|
|
320
|
+
Math.floor(Date.parse(removalEvent.createdAt) / 1000) !== accepted.createdAtUnix ||
|
|
321
|
+
removalEvent.reason !== accepted.reason)
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
310
324
|
await writeReadyReceipt({
|
|
311
325
|
version: 1,
|
|
312
326
|
...key,
|
|
@@ -115,7 +115,10 @@ export async function handleActiveMergeState(input) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
const removal = report.mergeQueue?.latestRemoval;
|
|
118
|
-
if (!enabled ||
|
|
118
|
+
if (!enabled ||
|
|
119
|
+
!removal ||
|
|
120
|
+
report.mergeQueue?.headUpdatedAfterRemoval ||
|
|
121
|
+
report.mergeQueue?.removalAcknowledged)
|
|
119
122
|
return null;
|
|
120
123
|
await clearStallState(stallKey);
|
|
121
124
|
const escalateBase = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type MergeMethod } from "../../config/merge-method.mts";
|
|
2
|
-
import type { IterateResult, IterateResultBase, MergeCommandPlan } from "../../types.mts";
|
|
2
|
+
import type { AgentCheck, IterateResult, IterateResultBase, MergeCommandPlan, ShepherdReport } from "../../types.mts";
|
|
3
3
|
interface MergePlanInput {
|
|
4
4
|
pr: number;
|
|
5
5
|
repo: string;
|
|
@@ -16,6 +16,12 @@ export declare function buildMergeCommandPlan(input: MergePlanInput): MergeComma
|
|
|
16
16
|
export declare function renderMergeCommand(command: {
|
|
17
17
|
argv: string[];
|
|
18
18
|
}): string;
|
|
19
|
+
/** Offer a guarded queue command; the caller decides whether the failure warrants a PR fix. */
|
|
20
|
+
export declare function buildRemovedQueueRecovery(report: ShepherdReport, checks: AgentCheck[], merge: boolean | undefined): MergeCommandPlan | undefined;
|
|
21
|
+
/** Bind a caller's disposition to one removed stack queue commit, without submitting a merge. */
|
|
22
|
+
export declare function buildStackQueueRemovalAcknowledgment(report: ShepherdReport, checks: AgentCheck[]): {
|
|
23
|
+
argv: string[];
|
|
24
|
+
} | undefined;
|
|
19
25
|
export declare function unavailableMergeResult(base: IterateResultBase, report: {
|
|
20
26
|
pr: number;
|
|
21
27
|
repo: string;
|
|
@@ -2,7 +2,9 @@ import { loadConfig } from "../../config/load.mjs";
|
|
|
2
2
|
import { findMergeStrategies } from "../../config/merge-command-args.mjs";
|
|
3
3
|
import { chooseMergeMethod, configuredMergeMethod, } from "../../config/merge-method.mjs";
|
|
4
4
|
import { formatPrUrl } from "../../pr-reference.mjs";
|
|
5
|
-
import { renderShellCommand } from "../../cli/runner.mjs";
|
|
5
|
+
import { renderShellCommand, buildPrShepherdCommand } from "../../cli/runner.mjs";
|
|
6
|
+
import { hasQueueRecoveryEvidence } from "./check-evidence.mjs";
|
|
7
|
+
import { isCiQueueRemovalReason } from "../../state/queue-removal-ack.mjs";
|
|
6
8
|
import { buildEscalateHumanMessage } from "./escalate.mjs";
|
|
7
9
|
const ENQUEUE_MUTATION = "mutation EnqueuePullRequest($pullRequestId: ID!, $expectedHeadOid: GitObjectID!) { enqueuePullRequest(input: { pullRequestId: $pullRequestId, expectedHeadOid: $expectedHeadOid }) { mergeQueueEntry { id } } }";
|
|
8
10
|
export function buildMergeCommandPlan(input) {
|
|
@@ -55,6 +57,62 @@ export function buildMergeCommandPlan(input) {
|
|
|
55
57
|
export function renderMergeCommand(command) {
|
|
56
58
|
return renderShellCommand(command.argv);
|
|
57
59
|
}
|
|
60
|
+
/** Offer a guarded queue command; the caller decides whether the failure warrants a PR fix. */
|
|
61
|
+
export function buildRemovedQueueRecovery(report, checks, merge) {
|
|
62
|
+
if (report.mergeStatus.mergeRequirements?.stack)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (!removedQueueRecoveryAvailable(report, checks, merge))
|
|
65
|
+
return undefined;
|
|
66
|
+
const plan = buildMergeCommandPlan({
|
|
67
|
+
pr: report.pr,
|
|
68
|
+
repo: report.repo,
|
|
69
|
+
nodeId: report.nodeId,
|
|
70
|
+
headSha: report.headSha,
|
|
71
|
+
queue: true,
|
|
72
|
+
});
|
|
73
|
+
return "unavailable" in plan ? undefined : plan;
|
|
74
|
+
}
|
|
75
|
+
/** Bind a caller's disposition to one removed stack queue commit, without submitting a merge. */
|
|
76
|
+
export function buildStackQueueRemovalAcknowledgment(report, checks) {
|
|
77
|
+
if (!report.mergeStatus.mergeRequirements?.stack)
|
|
78
|
+
return undefined;
|
|
79
|
+
// This records a local disposition, without enqueueing; aggregate child sessions omit --merge.
|
|
80
|
+
if (!removedQueueRecoveryAvailable(report, checks, true))
|
|
81
|
+
return undefined;
|
|
82
|
+
const removal = report.mergeQueue.latestRemoval;
|
|
83
|
+
return buildPrShepherdCommand([
|
|
84
|
+
"apply",
|
|
85
|
+
"queue-removal",
|
|
86
|
+
formatPrUrl(report.repo, report.pr),
|
|
87
|
+
"--require-sha",
|
|
88
|
+
report.headSha,
|
|
89
|
+
"--queue-commit",
|
|
90
|
+
removal.beforeCommitOid,
|
|
91
|
+
"--removed-at",
|
|
92
|
+
String(removal.createdAtUnix),
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
function removedQueueRecoveryAvailable(report, checks, merge) {
|
|
96
|
+
const queue = report.mergeQueue;
|
|
97
|
+
const removedCommit = queue?.latestRemoval?.beforeCommitOid;
|
|
98
|
+
if (!merge ||
|
|
99
|
+
!queue?.enabled ||
|
|
100
|
+
queue.inQueue ||
|
|
101
|
+
queue.headUpdatedAfterRemoval ||
|
|
102
|
+
// GitHub exposes a raw string, not a capability to reverse a human's queue removal.
|
|
103
|
+
// Only known CI-driven reasons authorize offering automated recovery.
|
|
104
|
+
!isCiQueueRemovalReason(queue.latestRemoval?.reason) ||
|
|
105
|
+
!queue.latestRemoval ||
|
|
106
|
+
queue.latestRemoval.createdAtUnix <= 0 ||
|
|
107
|
+
!removedCommit ||
|
|
108
|
+
!report.headSha ||
|
|
109
|
+
!report.nodeId ||
|
|
110
|
+
!checks.some((check) => check.scope === "merge_group" && check.commitOid === removedCommit))
|
|
111
|
+
return false;
|
|
112
|
+
return checks
|
|
113
|
+
.filter((check) => check.scope === "merge_group" && check.commitOid === removedCommit)
|
|
114
|
+
.every(hasQueueRecoveryEvidence);
|
|
115
|
+
}
|
|
58
116
|
export function unavailableMergeResult(base, report, unavailable) {
|
|
59
117
|
const escalateBase = {
|
|
60
118
|
triggers: ["merge-method-unavailable"],
|
|
@@ -8,6 +8,10 @@ import { createHash } from "node:crypto";
|
|
|
8
8
|
export function fingerprintRawSummaryPr(raw) {
|
|
9
9
|
if (!raw.updatedAt || !raw.headRefOid || !raw.baseRefOid)
|
|
10
10
|
return null;
|
|
11
|
+
const reviewTruncated = raw.comments.pageInfo.hasPreviousPage ||
|
|
12
|
+
raw.reviews.pageInfo.hasPreviousPage ||
|
|
13
|
+
raw.reviewThreads.pageInfo.hasPreviousPage ||
|
|
14
|
+
raw.reviewThreads.nodes.some((thread) => thread.comments.pageInfo.hasPreviousPage);
|
|
11
15
|
const evidence = {
|
|
12
16
|
number: raw.number,
|
|
13
17
|
state: raw.state,
|
|
@@ -19,6 +23,9 @@ export function fingerprintRawSummaryPr(raw) {
|
|
|
19
23
|
reviewDecision: raw.reviewDecision,
|
|
20
24
|
reviewRequests: raw.reviewRequests,
|
|
21
25
|
latestReviews: raw.latestReviews,
|
|
26
|
+
// Omitted bodies cannot participate in the hash. Bind truncated evidence
|
|
27
|
+
// to the PR revision instead, so updates require a fresh full review poll.
|
|
28
|
+
...(reviewTruncated && { reviewUpdatedAt: raw.updatedAt }),
|
|
22
29
|
comments: hideBodies(raw.comments),
|
|
23
30
|
reviews: hideBodies(raw.reviews),
|
|
24
31
|
reviewThreads: {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { summarizePollSummaryChecks } from "./poll-summary-checks.mjs";
|
|
2
|
+
import { parseBranchRules } from "./batch-parsers-rules.mjs";
|
|
3
|
+
import { rulesComplete } from "./fingerprint-fields.mjs";
|
|
2
4
|
/** Fresh compact evidence required before a READY receipt can be used. */
|
|
3
5
|
export function isCurrentSummaryReady(raw, checks, review, options = {}) {
|
|
4
6
|
const queued = options.allowQueuedProgress === true && raw.isInMergeQueue;
|
|
@@ -8,9 +10,17 @@ export function isCurrentSummaryReady(raw, checks, review, options = {}) {
|
|
|
8
10
|
const sourceChecks = queued
|
|
9
11
|
? summarizePollSummaryChecks({ ...raw, mergeQueueEntry: null })
|
|
10
12
|
: checks;
|
|
13
|
+
// The full one-PR check already surfaces review feedback. Certification
|
|
14
|
+
// need not reread historical conversations unless their resolution is a
|
|
15
|
+
// merge requirement. GitHub's CLEAN state proves that requirement is
|
|
16
|
+
// satisfied. BLOCKED is not conversation-specific, and queue progress
|
|
17
|
+
// alone is not that proof.
|
|
18
|
+
const requiresConversationResolution = parseBranchRules(raw.baseRef).requiresConversationResolution;
|
|
11
19
|
return (checks.incomplete !== true &&
|
|
12
20
|
sourceChecks.incomplete !== true &&
|
|
13
|
-
review.incomplete !== true
|
|
21
|
+
(review.incomplete !== true ||
|
|
22
|
+
raw.mergeStateStatus === "CLEAN" ||
|
|
23
|
+
(rulesComplete(raw.baseRef) && !requiresConversationResolution)) &&
|
|
14
24
|
raw.state === "OPEN" &&
|
|
15
25
|
!raw.isDraft &&
|
|
16
26
|
raw.mergeable !== "CONFLICTING" &&
|
package/bin/mcp/server.mjs
CHANGED
|
@@ -60,6 +60,12 @@ const appendJournalOperationSchema = z.object({
|
|
|
60
60
|
item: z.string(),
|
|
61
61
|
dryRun: z.boolean().optional(),
|
|
62
62
|
});
|
|
63
|
+
const acknowledgeQueueRemovalOperationSchema = z.object({
|
|
64
|
+
type: z.literal("acknowledge_queue_removal"),
|
|
65
|
+
requireSha: z.string().regex(/^[0-9a-f]{40}$/),
|
|
66
|
+
queueCommitOid: z.string().regex(/^[0-9a-f]{40}$/),
|
|
67
|
+
removedAtUnix: z.number().int().positive().safe(),
|
|
68
|
+
});
|
|
63
69
|
const applyInputSchema = z.object({
|
|
64
70
|
pr,
|
|
65
71
|
operations: z
|
|
@@ -67,6 +73,7 @@ const applyInputSchema = z.object({
|
|
|
67
73
|
reviewMutationsOperationSchema,
|
|
68
74
|
markFilesViewedOperationSchema,
|
|
69
75
|
appendJournalOperationSchema,
|
|
76
|
+
acknowledgeQueueRemovalOperationSchema,
|
|
70
77
|
]))
|
|
71
78
|
.min(1),
|
|
72
79
|
});
|
|
@@ -234,6 +241,17 @@ function formatApplyResult(result) {
|
|
|
234
241
|
return `${heading}\n\n${formatMarkFilesAsViewedResult(operation.result)}`;
|
|
235
242
|
case "append_journal":
|
|
236
243
|
return `${heading}\n\n${formatJournalResult(operation.result)}`;
|
|
244
|
+
case "acknowledge_queue_removal": {
|
|
245
|
+
const result = operation.result;
|
|
246
|
+
return [
|
|
247
|
+
heading,
|
|
248
|
+
"",
|
|
249
|
+
`PR: ${result.repo}#${result.pr}`,
|
|
250
|
+
`headSha: ${result.acknowledgment.headSha}`,
|
|
251
|
+
`queueCommitOid: ${result.acknowledgment.queueCommitOid}`,
|
|
252
|
+
`removedAtUnix: ${result.acknowledgment.removedAtUnix}`,
|
|
253
|
+
].join("\n");
|
|
254
|
+
}
|
|
237
255
|
}
|
|
238
256
|
})
|
|
239
257
|
.join("\n\n");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A caller's acknowledgment of one observed CI-driven native-stack queue removal. */
|
|
2
|
+
export interface QueueRemovalAcknowledgment {
|
|
3
|
+
headSha: string;
|
|
4
|
+
queueCommitOid: string;
|
|
5
|
+
removedAtUnix: number;
|
|
6
|
+
}
|
|
7
|
+
type StateKey = {
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
pr: number;
|
|
11
|
+
};
|
|
12
|
+
/** The only removal reasons that indicate a CI-driven queue ejection. */
|
|
13
|
+
export declare function isCiQueueRemovalReason(reason: string | null | undefined): boolean;
|
|
14
|
+
/** Missing, unreadable, or malformed acknowledgment state is treated as absent. */
|
|
15
|
+
export declare function readQueueRemovalAcknowledgment(key: StateKey): Promise<QueueRemovalAcknowledgment | null>;
|
|
16
|
+
/** Atomically store one acknowledgment. */
|
|
17
|
+
export declare function writeQueueRemovalAcknowledgment(key: StateKey, acknowledgment: QueueRemovalAcknowledgment): Promise<boolean>;
|
|
18
|
+
export declare function matchesQueueRemovalAcknowledgment(acknowledgment: QueueRemovalAcknowledgment | null, expected: Pick<QueueRemovalAcknowledgment, "headSha" | "queueCommitOid" | "removedAtUnix">): acknowledgment is QueueRemovalAcknowledgment;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** A caller's acknowledgment of one observed CI-driven native-stack queue removal. */
|
|
2
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { resolvePrStatePath } from "./base.mjs";
|
|
6
|
+
const FILE = "queue-removal-ack.json";
|
|
7
|
+
/** The only removal reasons that indicate a CI-driven queue ejection. */
|
|
8
|
+
export function isCiQueueRemovalReason(reason) {
|
|
9
|
+
return reason === "CI_FAILURE" || reason === "MERGE_QUEUE_POLICY_CHECK_FAILURE";
|
|
10
|
+
}
|
|
11
|
+
/** Missing, unreadable, or malformed acknowledgment state is treated as absent. */
|
|
12
|
+
export async function readQueueRemovalAcknowledgment(key) {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(await readFile(resolvePrStatePath(key, FILE), "utf8"));
|
|
15
|
+
return isAcknowledgment(parsed) ? parsed : null;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Atomically store one acknowledgment. */
|
|
22
|
+
export async function writeQueueRemovalAcknowledgment(key, acknowledgment) {
|
|
23
|
+
if (!isAcknowledgment(acknowledgment))
|
|
24
|
+
return false;
|
|
25
|
+
let tmp;
|
|
26
|
+
try {
|
|
27
|
+
const path = resolvePrStatePath(key, FILE);
|
|
28
|
+
tmp = `${path}.${randomUUID()}.tmp`;
|
|
29
|
+
await mkdir(dirname(path), { recursive: true });
|
|
30
|
+
await writeFile(tmp, `${JSON.stringify(acknowledgment)}\n`, "utf8");
|
|
31
|
+
await rename(tmp, path);
|
|
32
|
+
tmp = undefined;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
if (tmp !== undefined) {
|
|
40
|
+
try {
|
|
41
|
+
await unlink(tmp);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// A failed write may not have created the temporary file.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function matchesQueueRemovalAcknowledgment(acknowledgment, expected) {
|
|
50
|
+
return (acknowledgment !== null &&
|
|
51
|
+
acknowledgment.headSha === expected.headSha &&
|
|
52
|
+
acknowledgment.queueCommitOid === expected.queueCommitOid &&
|
|
53
|
+
acknowledgment.removedAtUnix === expected.removedAtUnix);
|
|
54
|
+
}
|
|
55
|
+
function isAcknowledgment(value) {
|
|
56
|
+
if (value === null || typeof value !== "object")
|
|
57
|
+
return false;
|
|
58
|
+
const candidate = value;
|
|
59
|
+
return (typeof candidate.headSha === "string" &&
|
|
60
|
+
/^[0-9a-f]{40}$/.test(candidate.headSha) &&
|
|
61
|
+
typeof candidate.queueCommitOid === "string" &&
|
|
62
|
+
/^[0-9a-f]{40}$/.test(candidate.queueCommitOid) &&
|
|
63
|
+
typeof candidate.removedAtUnix === "number" &&
|
|
64
|
+
Number.isSafeInteger(candidate.removedAtUnix) &&
|
|
65
|
+
candidate.removedAtUnix > 0);
|
|
66
|
+
}
|
package/bin/types/iterate.d.mts
CHANGED
|
@@ -124,6 +124,10 @@ interface FixRebaseAndPush {
|
|
|
124
124
|
protectedRuns: ProtectedRun[];
|
|
125
125
|
/** Requeue command emitted after merge-group remediation. */
|
|
126
126
|
requeue?: MergeCommandPlan;
|
|
127
|
+
/** Acknowledge an unrelated native-stack queue failure before fresh READY validation. */
|
|
128
|
+
queueRemovalAcknowledgment?: {
|
|
129
|
+
argv: string[];
|
|
130
|
+
};
|
|
127
131
|
/** First-look threads — previously hidden, surfaced for acknowledgment only. */
|
|
128
132
|
firstLookThreads: FirstLookThread[];
|
|
129
133
|
/** First-look comments — previously hidden, surfaced for acknowledgment only. */
|
|
@@ -10,6 +10,8 @@ export interface MergeQueueReport {
|
|
|
10
10
|
checksIncomplete?: true;
|
|
11
11
|
/** The current PR head is not a parent of the removed synthetic queue commit. */
|
|
12
12
|
headUpdatedAfterRemoval?: true;
|
|
13
|
+
/** The caller acknowledged this exact native-stack removal on the current head. */
|
|
14
|
+
removalAcknowledged?: true;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Raw counts of actionable work held back while the PR sits in the merge queue
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ allowed-tools: ["MCP", "Bash", "Read", "Grep", "Glob", "Edit", "Write"]
|
|
|
8
8
|
|
|
9
9
|
# pr-shepherd
|
|
10
10
|
|
|
11
|
-
Poll with the CLI. Use MCP `iterate` only when the CLI is unavailable. Stop at `[CANCEL]` or `[ESCALATE]`.
|
|
11
|
+
Poll with the CLI. Use MCP `iterate` only when the CLI is unavailable. Stop polling the selected pull request at `[CANCEL]` or `[ESCALATE]`.
|
|
12
12
|
|
|
13
13
|
## Create a PR
|
|
14
14
|
|
|
@@ -21,7 +21,8 @@ Poll with the CLI. Use MCP `iterate` only when the CLI is unavailable. Stop at `
|
|
|
21
21
|
## Dispatch
|
|
22
22
|
|
|
23
23
|
- Parse `$ARGUMENTS` for PR numbers, `owner/repo#N`, GitHub PR URLs, one `--stack PR`, and an optional `--merge`. Reject any other argument.
|
|
24
|
-
- A
|
|
24
|
+
- A user-supplied `--merge` explicitly authorizes merging or enqueueing the selected PR or stack. Run the emitted merge/enqueue commands without asking for another conversational confirmation; request runtime escalation when the host requires it.
|
|
25
|
+
- A request to merge, land, or enqueue the selected PR or stack also sets `--merge`. Creating or opening a PR without merge intent leaves merge mode off.
|
|
25
26
|
- A request to shepherd or merge a native stack, with an anchor PR and no literal `--stack`, uses that PR as the `--stack` selector. Otherwise infer the current branch PR.
|
|
26
27
|
- Follow the target repository's `AGENTS.md` while editing.
|
|
27
28
|
- CLI: turn `owner/repo#N` into `https://github.com/owner/repo/pull/N`. Pass other URLs and bare numbers through.
|
|
@@ -6,6 +6,7 @@ Apply when a step says `Playbook: "CI failure triage"`. For a GitHub Actions row
|
|
|
6
6
|
- `[rerun authorized]` plus a `rerun:` command means the viewer can rerun Actions (WRITE+) and this is the original attempt. Shepherd checked `repositoryPermission` and `run_attempt`.
|
|
7
7
|
- Run that printed command at most once. An `[attempt: N]` check never gets another rerun. A log excerpt on a later attempt is still investigation work. A later attempt with no usable evidence escalates when nothing else remains.
|
|
8
8
|
- A run in progress, `[conclusion: ACTION_REQUIRED]`, a check whose run id is not a GitHub Actions workflow, or a run with no attempt metadata never gets `[rerun authorized]`.
|
|
9
|
+
- A check with `scope: merge_group` never gets a rerun command. Rerunning cannot restore a removed queue entry and overwrites the failure evidence. If the failure belongs to this PR, fix the PR head. If it does not, follow the printed requeue instruction when a plan is present. Without `--merge`, report the failure without enqueueing. For native stacks, child sessions omit `--merge` and may still record the printed local acknowledgment after inspecting logs or an external provider URL. Complete fresh one-PR READY validation before returning to the aggregate selector with its original options; never enqueue a stack layer directly.
|
|
9
10
|
- Do not invent a handoff from `[FIX_CODE]`. Shepherd returns `[ESCALATE]` when no autonomous follow-up remains.
|
|
10
11
|
- Several bullets can share one run id (matrix jobs). The `rerun:` command is printed once, on the first bullet. Run it once.
|
|
11
12
|
|