pr-shepherd 0.43.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +18 -17
- package/bin/api.d.mts +3 -2
- package/bin/api.mjs +13 -18
- package/bin/cli/args.mjs +13 -9
- package/bin/cli/fix-formatter.mjs +8 -6
- package/bin/cli/help-command-pages.d.mts +9 -6
- package/bin/cli/help-command-pages.mjs +7 -4
- package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
- package/bin/cli/help-iterate-poll-pages.mjs +2 -2
- package/bin/cli/help-top-page.d.mts +1 -1
- package/bin/cli/help-top-page.mjs +2 -2
- package/bin/cli/help.d.mts +10 -7
- package/bin/cli/iterate-formatter.d.mts +1 -1
- package/bin/cli/iterate-formatter.mjs +5 -5
- package/bin/cli/iterate-lean.mjs +4 -0
- package/bin/cli/journal-formatter.mjs +3 -0
- package/bin/cli/journal-handler.mjs +11 -7
- package/bin/cli/list-formatters.d.mts +3 -1
- package/bin/cli/list-formatters.mjs +9 -7
- package/bin/cli/mark-files-as-viewed-formatter.mjs +7 -1
- package/bin/cli/mutate-formatter.mjs +4 -0
- package/bin/commands/check.mjs +22 -6
- package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
- package/bin/commands/commit-suggestion-instruction.mjs +2 -2
- package/bin/commands/commit-suggestion.mjs +3 -1
- package/bin/commands/iterate/check-instructions.d.mts +5 -19
- package/bin/commands/iterate/check-instructions.mjs +24 -25
- package/bin/commands/iterate/classify.d.mts +2 -2
- package/bin/commands/iterate/classify.mjs +43 -25
- package/bin/commands/iterate/escalate.d.mts +1 -1
- package/bin/commands/iterate/escalate.mjs +11 -0
- package/bin/commands/iterate/fix-code.mjs +81 -42
- package/bin/commands/iterate/helpers.d.mts +0 -2
- package/bin/commands/iterate/helpers.mjs +0 -15
- package/bin/commands/iterate/index.mjs +6 -12
- package/bin/commands/iterate/mark-ready.d.mts +2 -0
- package/bin/commands/iterate/mark-ready.mjs +38 -0
- package/bin/commands/iterate/merge-state.mjs +38 -9
- package/bin/commands/iterate/merge.mjs +0 -1
- package/bin/commands/iterate/render.d.mts +2 -2
- package/bin/commands/iterate/render.mjs +19 -20
- package/bin/commands/iterate/stall.mjs +4 -2
- package/bin/commands/iterate/thread-mutation-routing.d.mts +9 -0
- package/bin/commands/iterate/thread-mutation-routing.mjs +29 -0
- package/bin/commands/journal/index.d.mts +5 -0
- package/bin/commands/journal/index.mjs +11 -2
- package/bin/commands/mark-files-as-viewed.d.mts +2 -1
- package/bin/commands/mark-files-as-viewed.mjs +5 -92
- package/bin/commands/resolve-mutate.mjs +51 -9
- package/bin/commands/shepherd-journal.d.mts +1 -1
- package/bin/commands/shepherd-journal.mjs +2 -2
- package/bin/commands/suggestion-patch-item.d.mts +1 -1
- package/bin/commands/suggestion-patch-item.mjs +3 -11
- package/bin/commands/suggestion-patches.mjs +3 -3
- package/bin/comments/authors.d.mts +6 -0
- package/bin/comments/authors.mjs +5 -0
- package/bin/comments/marker.d.mts +6 -1
- package/bin/comments/marker.mjs +8 -1
- package/bin/comments/resolve.d.mts +4 -0
- package/bin/comments/thread-visibility.mjs +10 -10
- package/bin/comments/visible-comments.mjs +2 -1
- package/bin/config/load.d.mts +1 -1
- package/bin/github/batch-parsers.d.mts +2 -2
- package/bin/github/batch-parsers.mjs +17 -1
- package/bin/github/batch-raw-types.d.mts +12 -0
- package/bin/github/batch.mjs +1 -1
- package/bin/github/client.d.mts +1 -0
- package/bin/github/client.mjs +8 -1
- package/bin/github/gql/batch-pr-page.gql +6 -0
- package/bin/github/gql/batch-pr.gql +13 -0
- package/bin/github/gql/get-pr-body.gql +1 -0
- package/bin/github/gql/review-thread-comments.gql +1 -0
- package/bin/github/gql/suggestion-threads.gql +1 -0
- package/bin/github/suggestion-thread.mjs +1 -0
- package/bin/mcp/server.mjs +12 -6
- package/bin/pr-reference.d.mts +13 -0
- package/bin/pr-reference.mjs +23 -1
- package/bin/reporters/agent.mjs +5 -0
- package/bin/threads/transcript.d.mts +2 -0
- package/bin/threads/transcript.mjs +2 -0
- package/bin/types/agent-thread.d.mts +1 -0
- package/bin/types/escalate.d.mts +6 -1
- package/bin/types/github.d.mts +19 -0
- package/bin/types/iterate.d.mts +9 -8
- package/bin/types/report.d.mts +14 -1
- package/bin/types/review-thread.d.mts +1 -0
- package/bin/types/suggestion-patch.d.mts +1 -1
- 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/mark-files-as-viewed/SKILL.md +3 -3
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +15 -13
- package/bin/commands/iterate/reruns.d.mts +0 -20
- package/bin/commands/iterate/reruns.mjs +0 -105
package/bin/cli/help.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ export declare const USAGE: {
|
|
|
2
2
|
readonly default: string;
|
|
3
3
|
readonly apply: `pr-shepherd apply
|
|
4
4
|
|
|
5
|
-
Apply a review mutation,
|
|
5
|
+
Apply a review mutation, select changed files for authorization diagnostics, or append a PR journal item.
|
|
6
6
|
|
|
7
7
|
Usage:
|
|
8
8
|
pr-shepherd apply review [PR] [review-flags]
|
|
@@ -36,7 +36,8 @@ At least one action flag is required.
|
|
|
36
36
|
--help, -h Print this help and exit before GitHub I/O.`;
|
|
37
37
|
readonly "apply files": `pr-shepherd apply files
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
Select changed files and report that viewed-state authorization cannot be verified.
|
|
40
|
+
No file viewed-state mutation is attempted.
|
|
40
41
|
|
|
41
42
|
Usage:
|
|
42
43
|
pr-shepherd apply files [PR] [files...] [--tests] [--match REGEX]
|
|
@@ -139,7 +140,8 @@ Flags:
|
|
|
139
140
|
--message <text> Reply/dismiss message. Required with --reply-thread-ids
|
|
140
141
|
or --dismiss-review-ids.
|
|
141
142
|
--require-sha <sha> Wait until GitHub reports this PR head SHA before mutating.
|
|
142
|
-
Must be a full 40-character lowercase hex SHA.
|
|
143
|
+
Must be a full 40-character lowercase hex SHA. Local HEAD is valid
|
|
144
|
+
only when it equals the current remote PR head.
|
|
143
145
|
--format text|json Output format. Default: text.
|
|
144
146
|
--help, -h Print this help and exit before GitHub I/O.
|
|
145
147
|
|
|
@@ -174,7 +176,8 @@ Exit codes:
|
|
|
174
176
|
See docs/exit-codes.md for the full sysexits.h table.`;
|
|
175
177
|
readonly "mark-files-as-viewed": `pr-shepherd mark-files-as-viewed
|
|
176
178
|
|
|
177
|
-
|
|
179
|
+
Deprecated compatibility alias for selection-only file-view authorization diagnostics.
|
|
180
|
+
No file viewed-state mutation is attempted.
|
|
178
181
|
|
|
179
182
|
Usage:
|
|
180
183
|
pr-shepherd mark-files-as-viewed [PR] [files...] [--tests] [--match REGEX]
|
|
@@ -192,8 +195,8 @@ Flags:
|
|
|
192
195
|
|
|
193
196
|
PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
|
|
194
197
|
Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
195
|
-
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
|
|
196
|
-
readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default 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\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\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
|
|
198
|
+
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 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\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).";
|
|
199
|
+
readonly poll: "pr-shepherd poll\n\nRun iterate repeatedly for WAIT ticks and during the FIX_CODE debounce window. Print only the\nfinal tick to stdout.\nPoll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout\nreturns the last WAIT result. FIX_CODE starts a --debounce settle window (default 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\nPoll flags:\n --interval <duration> Sleep between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Maximum wall-clock wait for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\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 an explicit still-running line to stderr by default; --quiet-status prints only changed WAIT snapshots, 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 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 (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 A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
197
200
|
readonly clean: `pr-shepherd clean
|
|
198
201
|
|
|
199
202
|
Remove pr-shepherd state files from PR_SHEPHERD_STATE_DIR.
|
|
@@ -244,7 +247,7 @@ Flags:
|
|
|
244
247
|
|
|
245
248
|
Exit code: 0 on success (including no-change no-op); nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
246
249
|
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.";
|
|
247
|
-
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 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 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 until non-WAIT or timeout. This is the default command.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files
|
|
250
|
+
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 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 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 until non-WAIT or timeout. This is the default command.\n iterate Run one iterate tick (single-tick alias).\n apply review Apply review-state mutations after fixes.\n apply files Select changed files; no viewed-state mutation is attempted.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\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 such as 42 or a GitHub pull request URL.\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\nPolling flags:\n --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: 60s.\n --timeout <duration> Poll wall-clock cap for WAIT ticks. Bare number = seconds. Default: 4.5m.\n --debounce <duration> Settle window after first FIX_CODE before returning. Bare number = seconds. Default: 60s. 0 disables.\n --quiet-status During WAIT polling, print only changed status snapshots.\n --until-terminal Continue through WAIT/MARK_READY until FIX_CODE/CANCEL/ESCALATE.\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\n 11 MARK_READY\n 12 FIX_CODE\n 13 ESCALATE\n 14 CANCEL (closed without merging)\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.";
|
|
248
251
|
};
|
|
249
252
|
/** Resolve help keys for nested public commands before any command I/O. */
|
|
250
253
|
export declare function helpKeyForArgs(args: string[]): keyof typeof USAGE;
|
|
@@ -6,7 +6,7 @@ import type { IterateResult } from "../types.mts";
|
|
|
6
6
|
* 1. The H1 heading on line 1 contains `[<ACTION>]` — the action tag identifies
|
|
7
7
|
* the output for logging and validation. Behavior is driven by `## Instructions`,
|
|
8
8
|
* not by dispatching on the tag.
|
|
9
|
-
* 2. `[FIX_CODE]` wraps the `resolve` command under `## Post-fix
|
|
9
|
+
* 2. `[FIX_CODE]` wraps the `resolve` command under `## Post-fix actions` in
|
|
10
10
|
* backticks — the skill extracts the backticked content for execution.
|
|
11
11
|
* 3. Every action ends with a `## Instructions` section — numbered `1.`, `2.`, … —
|
|
12
12
|
* that tells the agent exactly what to do with this output. The section is
|
|
@@ -36,7 +36,7 @@ function formatActivityLine(result) {
|
|
|
36
36
|
* 1. The H1 heading on line 1 contains `[<ACTION>]` — the action tag identifies
|
|
37
37
|
* the output for logging and validation. Behavior is driven by `## Instructions`,
|
|
38
38
|
* not by dispatching on the tag.
|
|
39
|
-
* 2. `[FIX_CODE]` wraps the `resolve` command under `## Post-fix
|
|
39
|
+
* 2. `[FIX_CODE]` wraps the `resolve` command under `## Post-fix actions` in
|
|
40
40
|
* backticks — the skill extracts the backticked content for execution.
|
|
41
41
|
* 3. Every action ends with a `## Instructions` section — numbered `1.`, `2.`, … —
|
|
42
42
|
* that tells the agent exactly what to do with this output. The section is
|
|
@@ -55,10 +55,10 @@ export function formatIterateResult(result, opts) {
|
|
|
55
55
|
if (verbose) {
|
|
56
56
|
let verboseBranch = "";
|
|
57
57
|
if (result.mergeStatus === "BEHIND" && result.baseBranch) {
|
|
58
|
-
verboseBranch = ` · **branch** behind
|
|
58
|
+
verboseBranch = ` · **branch** behind PR base \`${result.baseBranch}\``;
|
|
59
59
|
}
|
|
60
60
|
else if (result.mergeStatus === "CONFLICTS" && result.baseBranch) {
|
|
61
|
-
verboseBranch = ` · **branch** conflicts with
|
|
61
|
+
verboseBranch = ` · **branch** conflicts with PR base \`${result.baseBranch}\``;
|
|
62
62
|
}
|
|
63
63
|
summaryLine = `**summary** ${result.summary.passing} passing, ${result.summary.skipped} skipped, ${result.summary.filtered} filtered, ${result.summary.inProgress} inProgress, ${result.summary.superseded} superseded · **remainingSeconds** ${result.remainingSeconds} · **blockingBotReviewInProgress** ${result.blockingBotReviewInProgress} · **isDraft** ${result.isDraft} · **shouldCancel** ${result.shouldCancel}${verboseBranch}`;
|
|
64
64
|
}
|
|
@@ -81,10 +81,10 @@ export function formatIterateResult(result, opts) {
|
|
|
81
81
|
if (result.isDraft)
|
|
82
82
|
segs.push(`**isDraft**`);
|
|
83
83
|
if (result.mergeStatus === "BEHIND" && result.baseBranch) {
|
|
84
|
-
segs.push(`**branch** behind
|
|
84
|
+
segs.push(`**branch** behind PR base \`${result.baseBranch}\``);
|
|
85
85
|
}
|
|
86
86
|
else if (result.mergeStatus === "CONFLICTS" && result.baseBranch) {
|
|
87
|
-
segs.push(`**branch** conflicts with
|
|
87
|
+
segs.push(`**branch** conflicts with PR base \`${result.baseBranch}\``);
|
|
88
88
|
}
|
|
89
89
|
summaryLine = segs.join(" · ");
|
|
90
90
|
}
|
package/bin/cli/iterate-lean.mjs
CHANGED
|
@@ -168,6 +168,10 @@ export function projectIterateLean(result, opts) {
|
|
|
168
168
|
...(result.escalate.mergeQueueRemoval && {
|
|
169
169
|
mergeQueueRemoval: result.escalate.mergeQueueRemoval,
|
|
170
170
|
}),
|
|
171
|
+
...(result.escalate.authorization &&
|
|
172
|
+
result.escalate.authorization.length > 0 && {
|
|
173
|
+
authorization: result.escalate.authorization,
|
|
174
|
+
}),
|
|
171
175
|
suggestion: result.escalate.suggestion,
|
|
172
176
|
humanMessage: result.escalate.humanMessage,
|
|
173
177
|
},
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export function formatJournalResult(result) {
|
|
2
|
+
if (result.authorizationSkipped) {
|
|
3
|
+
return `Authorization denied or unverifiable — Shepherd Journal in PR #${result.prNumber} was not updated.`;
|
|
4
|
+
}
|
|
2
5
|
if (result.dryRun) {
|
|
3
6
|
const lines = ["Dry run — no body change written."];
|
|
4
7
|
if (result.previewBody !== undefined) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { EXIT, errorToExitCode } from "../exit-codes.mjs";
|
|
3
3
|
import { runJournal } from "../commands/journal/index.mjs";
|
|
4
|
-
import { getFlag
|
|
4
|
+
import { getFlag } from "./args.mjs";
|
|
5
|
+
import { parseCliPrReference, resolveParsedPrTarget } from "../pr-reference.mjs";
|
|
5
6
|
import { USAGE } from "./help.mjs";
|
|
6
7
|
import { formatJournalResult } from "./journal-formatter.mjs";
|
|
7
8
|
export async function handleJournal(args, command = "apply journal") {
|
|
@@ -16,7 +17,7 @@ export async function handleJournal(args, command = "apply journal") {
|
|
|
16
17
|
process.exitCode = EXIT.USAGE;
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
|
-
const { prNumber, extra } = parseJournalArgs(args);
|
|
20
|
+
const { prNumber, targetRepository, extra } = parseJournalArgs(args);
|
|
20
21
|
const filePath = getFlag(args, "--file");
|
|
21
22
|
if (filePath !== null && extra[0]) {
|
|
22
23
|
process.stderr.write(`pr-shepherd: ${command}: provide the entry as a positional argument or via --file, not both\n`);
|
|
@@ -41,7 +42,7 @@ export async function handleJournal(args, command = "apply journal") {
|
|
|
41
42
|
const jsonOut = args.some((a) => a === "--format=json") ||
|
|
42
43
|
args.some((a, i) => a === "--format" && args[i + 1] === "json");
|
|
43
44
|
try {
|
|
44
|
-
const result = await runJournal({ prNumber, rawItem, dryRun });
|
|
45
|
+
const result = await runJournal({ prNumber, targetRepository, rawItem, dryRun });
|
|
45
46
|
if (jsonOut) {
|
|
46
47
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
47
48
|
}
|
|
@@ -79,6 +80,7 @@ function parseJournalArgs(args) {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
let prNumber;
|
|
83
|
+
let targetRepository;
|
|
82
84
|
const extra = [];
|
|
83
85
|
for (let i = 0; i < args.length; i++) {
|
|
84
86
|
if (flagConsumedIndices.has(i))
|
|
@@ -87,13 +89,15 @@ function parseJournalArgs(args) {
|
|
|
87
89
|
if (a.startsWith("--"))
|
|
88
90
|
continue;
|
|
89
91
|
if (prNumber === undefined) {
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
|
|
92
|
+
const parsed = parseCliPrReference(a);
|
|
93
|
+
if (parsed !== null) {
|
|
94
|
+
const target = resolveParsedPrTarget(parsed);
|
|
95
|
+
prNumber = target.prNumber;
|
|
96
|
+
targetRepository = target.targetRepository;
|
|
93
97
|
continue;
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
extra.push(a);
|
|
97
101
|
}
|
|
98
|
-
return { prNumber, extra };
|
|
102
|
+
return { prNumber, targetRepository, extra };
|
|
99
103
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AuthorType, CommentAuthorAssociation, SuggestionBlock } from "../types.mts";
|
|
2
2
|
import type { FirstLookThread, FirstLookComment } from "../types/report.mts";
|
|
3
|
-
export declare function renderAuthor(author: string, authorType?: AuthorType, authorAssociation?: CommentAuthorAssociation): string;
|
|
3
|
+
export declare function renderAuthor(author: string, authorType?: AuthorType, authorAssociation?: CommentAuthorAssociation, viewerDidAuthor?: boolean): string;
|
|
4
4
|
export declare function renderBodyPreview(body: string): string;
|
|
5
5
|
export declare function renderThreadResolutionStatusTag(t: {
|
|
6
6
|
isOutdated?: boolean;
|
|
@@ -16,12 +16,14 @@ interface ThreadBulletInput {
|
|
|
16
16
|
author: string;
|
|
17
17
|
authorType?: AuthorType;
|
|
18
18
|
authorAssociation?: CommentAuthorAssociation;
|
|
19
|
+
viewerDidAuthor?: true;
|
|
19
20
|
body: string;
|
|
20
21
|
comments?: Array<{
|
|
21
22
|
id: string;
|
|
22
23
|
author: string;
|
|
23
24
|
authorType?: AuthorType;
|
|
24
25
|
authorAssociation?: CommentAuthorAssociation;
|
|
26
|
+
viewerDidAuthor?: true;
|
|
25
27
|
body: string;
|
|
26
28
|
url: string;
|
|
27
29
|
}>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { renderLineRange, renderSuggestionBlock } from "./suggestion-renderer.mjs";
|
|
2
2
|
import { threadComments } from "../threads/transcript.mjs";
|
|
3
3
|
const BODY_PREVIEW_MAX = 100;
|
|
4
|
-
export function renderAuthor(author, authorType, authorAssociation) {
|
|
5
|
-
return [`@${author}`, authorType, authorAssociation
|
|
4
|
+
export function renderAuthor(author, authorType, authorAssociation, viewerDidAuthor) {
|
|
5
|
+
return [`@${author}`, authorType, authorAssociation, viewerDidAuthor ? "viewer-authored" : null]
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
.join(" · ");
|
|
6
8
|
}
|
|
7
9
|
export function renderBodyPreview(body) {
|
|
8
10
|
const normalizedBody = body.replace(/\r\n?/g, "\n");
|
|
@@ -30,7 +32,7 @@ export function renderThreadBullet(t, opts = {}) {
|
|
|
30
32
|
const editedMarker = t.edited && !opts.suppressEditedMarker ? " [edited since first look]" : "";
|
|
31
33
|
const reviewMarker = t.reviewId ? ` [reviewId=${t.reviewId}]` : "";
|
|
32
34
|
const statusSuffix = opts.statusTag ? ` ${opts.statusTag}` : "";
|
|
33
|
-
const bulletLine = `- \`threadId=${t.id}\`${link} ${loc} (${renderAuthor(t.author, t.authorType, t.authorAssociation)})${reviewMarker}${suggestionMarker}${editedMarker}${statusSuffix}`;
|
|
35
|
+
const bulletLine = `- \`threadId=${t.id}\`${link} ${loc} (${renderAuthor(t.author, t.authorType, t.authorAssociation, t.viewerDidAuthor)})${reviewMarker}${suggestionMarker}${editedMarker}${statusSuffix}`;
|
|
34
36
|
if (!opts.noBody && (!t.comments || t.comments.length === 0)) {
|
|
35
37
|
const legacyLine = `${bulletLine}: ${renderBodyPreview(t.body)}`;
|
|
36
38
|
return t.suggestion && opts.renderSuggestion
|
|
@@ -53,9 +55,9 @@ export function renderThreadConversation(t) {
|
|
|
53
55
|
.map((c) => {
|
|
54
56
|
const heading = c.id
|
|
55
57
|
? c.url
|
|
56
|
-
? `#### [commentId=${c.id}](${c.url}) (${renderAuthor(c.author, c.authorType, c.authorAssociation)})`
|
|
57
|
-
: `#### \`commentId=${c.id}\` (${renderAuthor(c.author, c.authorType, c.authorAssociation)})`
|
|
58
|
-
: `#### (${renderAuthor(c.author, c.authorType, c.authorAssociation)})`;
|
|
58
|
+
? `#### [commentId=${c.id}](${c.url}) (${renderAuthor(c.author, c.authorType, c.authorAssociation, c.viewerDidAuthor)})`
|
|
59
|
+
: `#### \`commentId=${c.id}\` (${renderAuthor(c.author, c.authorType, c.authorAssociation, c.viewerDidAuthor)})`
|
|
60
|
+
: `#### (${renderAuthor(c.author, c.authorType, c.authorAssociation, c.viewerDidAuthor)})`;
|
|
59
61
|
return `${heading}\n\n${blockquote(c.body)}`;
|
|
60
62
|
})
|
|
61
63
|
.join("\n\n");
|
|
@@ -73,7 +75,7 @@ function renderThreadCommentBullets(t) {
|
|
|
73
75
|
const link = c.url ? ` [↗](${c.url})` : "";
|
|
74
76
|
const id = c.id ? `\`commentId=${c.id}\`` : "comment";
|
|
75
77
|
return [
|
|
76
|
-
` - ${id}${link} (${renderAuthor(c.author, c.authorType, c.authorAssociation)})`,
|
|
78
|
+
` - ${id}${link} (${renderAuthor(c.author, c.authorType, c.authorAssociation, c.viewerDidAuthor)})`,
|
|
77
79
|
indentBlockquote(c.body, " "),
|
|
78
80
|
].join("\n");
|
|
79
81
|
})
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
export function formatMarkFilesAsViewedResult(result) {
|
|
2
2
|
const lines = [];
|
|
3
|
-
lines.push(`# PR #${result.prNumber} —
|
|
3
|
+
lines.push(`# PR #${result.prNumber} — File-view selection (${result.matchedPaths.length} selected)`);
|
|
4
4
|
lines.push("");
|
|
5
5
|
lines.push(`repo: ${result.repo}`);
|
|
6
|
+
if (result.authorizationSkipped) {
|
|
7
|
+
lines.push("");
|
|
8
|
+
lines.push("## Authorization");
|
|
9
|
+
lines.push("");
|
|
10
|
+
lines.push("- Not marked: GitHub does not expose a capability that confirms the current viewer may mark PR files as viewed.");
|
|
11
|
+
}
|
|
6
12
|
appendPathSection(lines, "Matched files", result.matchedPaths);
|
|
7
13
|
appendPathSection(lines, "Marked viewed", result.markedPaths);
|
|
8
14
|
appendPathSection(lines, "Already viewed", result.alreadyViewedPaths);
|
|
@@ -34,6 +34,10 @@ export function formatMutateResult(result) {
|
|
|
34
34
|
pushIds(lines, "Skipped human minimizes", result.skippedHumanMinimizes);
|
|
35
35
|
pushIds(lines, "Skipped human review dismissals", result.skippedHumanDismissals);
|
|
36
36
|
pushIds(lines, "Skipped non-human/unknown thread replies", result.skippedNonHumanReplies);
|
|
37
|
+
pushIds(lines, "Authorization denied/unverifiable for thread replies", result.skippedUnauthorizedReplies);
|
|
38
|
+
pushIds(lines, "Authorization denied/unverifiable for thread resolves", result.skippedUnauthorizedResolves);
|
|
39
|
+
pushIds(lines, "Authorization denied/unverifiable for minimizes", result.skippedUnauthorizedMinimizes);
|
|
40
|
+
pushIds(lines, "Authorization denied/unverifiable for review dismissals", result.skippedUnauthorizedDismissals);
|
|
37
41
|
const rateLimit = formatRateLimit(result);
|
|
38
42
|
if (rateLimit)
|
|
39
43
|
lines.push(rateLimit);
|
package/bin/commands/check.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { buildClassifyIndex, partitionBatch } from "../classify/apply.mjs";
|
|
|
22
22
|
import { EXIT, ShepherdError } from "../exit-codes.mjs";
|
|
23
23
|
import { getEffectiveCwd } from "../execution-context.mjs";
|
|
24
24
|
export async function runCheck(opts) {
|
|
25
|
-
const repo = await getRepoInfo();
|
|
25
|
+
const repo = opts.targetRepository ?? (await getRepoInfo());
|
|
26
26
|
const prNumber = opts.prNumber ?? (await getCurrentPrNumber());
|
|
27
27
|
if (prNumber === null) {
|
|
28
28
|
throw new ShepherdError("No open PR found for current branch. Pass a PR number explicitly.", EXIT.UNAVAILABLE);
|
|
@@ -75,8 +75,10 @@ export async function runCheck(opts) {
|
|
|
75
75
|
const merged = await attachAndMergeCheckAnnotations({ passing, failing: triagedBase, skipped, filtered, ignored }, seenMap, prNumber);
|
|
76
76
|
const ignoredAnnotated = merged.ignored.filter((c) => (c.annotations?.length ?? 0) > 0);
|
|
77
77
|
const minimizedCommentCandidates = batchData.comments.filter((c) => c.isMinimized && !partition.suppressedCommentIds.has(c.id));
|
|
78
|
-
const
|
|
79
|
-
const
|
|
78
|
+
const deniedRuleAutoResolveCommentIds = new Set(partition.ruleAutoResolveCommentIds.filter((id) => batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize !== true));
|
|
79
|
+
const visibleCommentClassification = classifyVisibleComments(batchData.comments.filter((c) => !partition.suppressedCommentIds.has(c.id) || deniedRuleAutoResolveCommentIds.has(c.id)), seenMap, config.iterate.minimizeComments, botUsernames);
|
|
80
|
+
const deniedRuleAutoResolveThreadIds = new Set(partition.ruleAutoResolveThreadIds.filter((id) => batchData.reviewThreads.find((thread) => thread.id === id)?.viewerCanResolve !== true));
|
|
81
|
+
const threadVisibility = classifyThreadVisibility(batchData.reviewThreads.filter((t) => !partition.suppressedThreadIds.has(t.id) || deniedRuleAutoResolveThreadIds.has(t.id)), seenMap, botUsernames);
|
|
80
82
|
const firstLookComments = minimizedCommentCandidates.flatMap((c) => {
|
|
81
83
|
const cls = classifyItem(c.id, c.body, seenMap);
|
|
82
84
|
if (cls === "unchanged")
|
|
@@ -87,7 +89,9 @@ export async function runCheck(opts) {
|
|
|
87
89
|
const firstLookSummaries = [];
|
|
88
90
|
const editedSummaries = [];
|
|
89
91
|
const seenSummaries = [];
|
|
90
|
-
const
|
|
92
|
+
const deniedRuleAutoResolveReviewSummaryIds = new Set(partition.ruleAutoResolveReviewSummaryIds.filter((id) => batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize !== true));
|
|
93
|
+
const unseenReviewSummaries = batchData.reviewSummaries.filter((r) => !partition.suppressedReviewSummaryIds.has(r.id) ||
|
|
94
|
+
deniedRuleAutoResolveReviewSummaryIds.has(r.id));
|
|
91
95
|
for (const r of unseenReviewSummaries) {
|
|
92
96
|
const cls = classifyItem(r.id, r.body, seenMap);
|
|
93
97
|
if (cls === "new")
|
|
@@ -123,7 +127,8 @@ export async function runCheck(opts) {
|
|
|
123
127
|
.filter((t) => partition.suppressedThreadIds.has(t.id))
|
|
124
128
|
.map((t) => markSeen(stateKey, t.id, threadTranscriptBody(t))),
|
|
125
129
|
...batchData.reviewSummaries
|
|
126
|
-
.filter((r) => partition.suppressedReviewSummaryIds.has(r.id)
|
|
130
|
+
.filter((r) => partition.suppressedReviewSummaryIds.has(r.id) &&
|
|
131
|
+
!deniedRuleAutoResolveReviewSummaryIds.has(r.id))
|
|
127
132
|
.map((r) => markSeen(stateKey, r.id, r.body)),
|
|
128
133
|
...batchData.changesRequestedReviews
|
|
129
134
|
.filter((r) => partition.suppressedChangesRequestedIds.has(r.id))
|
|
@@ -131,7 +136,17 @@ export async function runCheck(opts) {
|
|
|
131
136
|
]);
|
|
132
137
|
await markReviewInlineThreadMarkers(stateKey, batchData.reviewThreads);
|
|
133
138
|
}
|
|
134
|
-
const
|
|
139
|
+
const authorizedPartition = {
|
|
140
|
+
...partition,
|
|
141
|
+
ruleAutoResolveThreadIds: partition.ruleAutoResolveThreadIds.filter((id) => batchData.reviewThreads.find((thread) => thread.id === id)?.viewerCanResolve === true),
|
|
142
|
+
ruleAutoResolveCommentIds: partition.ruleAutoResolveCommentIds.filter((id) => batchData.comments.find((comment) => comment.id === id)?.viewerCanMinimize === true),
|
|
143
|
+
ruleAutoResolveReviewSummaryIds: partition.ruleAutoResolveReviewSummaryIds.filter((id) => batchData.reviewSummaries.find((review) => review.id === id)?.viewerCanMinimize === true),
|
|
144
|
+
};
|
|
145
|
+
const { threadIds: authorizedRuleAutoResolveThreadIds, commentIds: ruleAutoResolveCommentIds, reviewSummaryIds: ruleAutoResolveReviewSummaryIds, } = await remainingRuleAutoResolveIds(authorizedPartition, opts.autoMinimizeSuppressed);
|
|
146
|
+
const ruleAutoResolveThreadIds = [
|
|
147
|
+
...authorizedRuleAutoResolveThreadIds,
|
|
148
|
+
...deniedRuleAutoResolveThreadIds,
|
|
149
|
+
];
|
|
135
150
|
const changesRequestedReviews = changesRequestedReviewVisibility.visible;
|
|
136
151
|
const changesRequestedReviewCount = batchData.changesRequestedReviews.filter((r) => !partition.suppressedChangesRequestedIds.has(r.id)).length;
|
|
137
152
|
const approvedReviews = approvedReviewVisibility.visible;
|
|
@@ -158,6 +173,7 @@ export async function runCheck(opts) {
|
|
|
158
173
|
nodeId: batchData.nodeId,
|
|
159
174
|
headSha: batchData.headRefOid,
|
|
160
175
|
repo: `${repo.owner}/${repo.name}`,
|
|
176
|
+
...(batchData.viewerAuthorization && { viewerAuthorization: batchData.viewerAuthorization }),
|
|
161
177
|
status,
|
|
162
178
|
baseBranch: batchData.baseRefName,
|
|
163
179
|
mergeStatus,
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
9
9
|
* e.g. `"## Review threads"`.
|
|
10
10
|
*/
|
|
11
|
-
export declare function buildCommitSuggestionInstruction(
|
|
11
|
+
export declare function buildCommitSuggestionInstruction(prReference: string | number, sectionName: string): string;
|
|
@@ -9,10 +9,10 @@ import { buildPrShepherdCommand } from "../cli/runner.mjs";
|
|
|
9
9
|
* @param sectionName - The markdown section heading where suggestion threads appear,
|
|
10
10
|
* e.g. `"## Review threads"`.
|
|
11
11
|
*/
|
|
12
|
-
export function buildCommitSuggestionInstruction(
|
|
12
|
+
export function buildCommitSuggestionInstruction(prReference, sectionName) {
|
|
13
13
|
const command = buildPrShepherdCommand([
|
|
14
14
|
"build-suggestion-patches",
|
|
15
|
-
String(
|
|
15
|
+
String(prReference),
|
|
16
16
|
"--thread-id",
|
|
17
17
|
"<id>",
|
|
18
18
|
"--message",
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { buildSingularInstructions } from "./suggestion-patch-item.mjs";
|
|
2
2
|
import { runSuggestionPatches } from "./suggestion-patches.mjs";
|
|
3
|
+
import { formatPrUrl } from "../pr-reference.mjs";
|
|
3
4
|
/** @deprecated Use runSuggestionPatches. */
|
|
4
5
|
export async function runCommitSuggestion(opts) {
|
|
5
6
|
const result = await runSuggestionPatches({
|
|
6
7
|
prNumber: opts.prNumber,
|
|
8
|
+
targetRepository: opts.targetRepository,
|
|
7
9
|
format: opts.format,
|
|
8
10
|
verbose: opts.verbose,
|
|
9
11
|
suggestions: [
|
|
@@ -19,6 +21,6 @@ export async function runCommitSuggestion(opts) {
|
|
|
19
21
|
...patch,
|
|
20
22
|
pr: result.pr,
|
|
21
23
|
repo: result.repo,
|
|
22
|
-
postActionInstructions: buildSingularInstructions(patch, result.pr),
|
|
24
|
+
postActionInstructions: buildSingularInstructions(patch, formatPrUrl(result.repo, result.pr)),
|
|
23
25
|
};
|
|
24
26
|
}
|
|
@@ -17,11 +17,9 @@ export declare function buildBehindBaseHintInstruction(baseBranch: string, hint:
|
|
|
17
17
|
*
|
|
18
18
|
* - `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution: without it, the printed command has an
|
|
19
19
|
* empty `--message`/invalid `--require-sha` and `apply review` rejects the mutation.
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* and produce a self-perpetuating reply loop — worse than a rejected mutation, and not
|
|
24
|
-
* something a caller can discover by inspecting the command alone.
|
|
20
|
+
* Marker-based self-reply routing is already reflected in the generated IDs. The instruction
|
|
21
|
+
* below makes that behavior explicit so an authenticated viewer's unmarked human feedback is
|
|
22
|
+
* not mistaken for an automated reply merely because the GitHub login matches.
|
|
25
23
|
*
|
|
26
24
|
* Contrast with what *does* stay in the skill's "Review-mutation mechanics" playbook —
|
|
27
25
|
* dismiss-ID retention and the first-look/annotation ID-exclusion rules. Those only matter
|
|
@@ -30,18 +28,6 @@ export declare function buildBehindBaseHintInstruction(baseBranch: string, hint:
|
|
|
30
28
|
* without it, nothing in CLI output tells the agent that playbook exists.
|
|
31
29
|
*/
|
|
32
30
|
export declare function buildResolveCommandInstruction(resolveCommand: ResolveCommand): string[];
|
|
33
|
-
/**
|
|
34
|
-
* Build the CI-triage instruction. The per-conclusion rerun policy (GitHub Actions log
|
|
35
|
-
* excerpts, `gh run view`/`gh run rerun` rules for CANCELLED/STARTUP_FAILURE/external
|
|
36
|
-
* failures) is invariant text keyed on the `[conclusion: …]` tags already rendered in
|
|
37
|
-
* `## Failing checks` — it lives in the pr-shepherd skill's "CI failure triage" playbook
|
|
38
|
-
* instead of being re-emitted every tick. This supersedes the "CI budget rules" example in
|
|
39
|
-
* CLAUDE.md's "Keep skills and loop prompts minimal" section (see that section's amendment
|
|
40
|
-
* note). The `(no runId)` case stays here because it flips `buildFixCompletionInstruction`
|
|
41
|
-
* to a human-handoff terminal state — that trigger, unlike the others, is CLI-decided. The
|
|
42
|
-
* CLI sentence does not claim every failure has a log excerpt to read (only GitHub Actions
|
|
43
|
-
* checks with a runId do — CANCELLED, STARTUP_FAILURE, and external checks may not); that
|
|
44
|
-
* per-kind detail is exactly what the skill playbook table disambiguates.
|
|
45
|
-
*/
|
|
31
|
+
/** Build the CI-triage pointer; the skill limits follow-up actions to included evidence. */
|
|
46
32
|
export declare function buildFailingCheckInstructions(checks: AgentCheck[]): string[];
|
|
47
|
-
export declare function buildFixCompletionInstruction(checks: AgentCheck[]): string;
|
|
33
|
+
export declare function buildFixCompletionInstruction(checks: AgentCheck[], requiresRemoteUpdateAuthorization?: boolean, hasShaGatedReviewMutations?: boolean): string;
|
|
@@ -18,7 +18,7 @@ export function buildBehindBaseHintInstruction(baseBranch, hint, isBehind) {
|
|
|
18
18
|
const trimmedHint = typeof hint === "string" ? hint.trim() : "";
|
|
19
19
|
if (!isBehind || trimmedHint === "")
|
|
20
20
|
return [];
|
|
21
|
-
return [`The branch is behind
|
|
21
|
+
return [`The branch is behind PR base branch \`${baseBranch}\`. ${trimmedHint} before pushing.`];
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Build the `Run the apply review: command` instruction. Steps stay here (not in the skill)
|
|
@@ -26,11 +26,9 @@ export function buildBehindBaseHintInstruction(baseBranch, hint, isBehind) {
|
|
|
26
26
|
*
|
|
27
27
|
* - `$HEAD_SHA`/`$DISMISS_MESSAGE` substitution: without it, the printed command has an
|
|
28
28
|
* empty `--message`/invalid `--require-sha` and `apply review` rejects the mutation.
|
|
29
|
-
* -
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* and produce a self-perpetuating reply loop — worse than a rejected mutation, and not
|
|
33
|
-
* something a caller can discover by inspecting the command alone.
|
|
29
|
+
* Marker-based self-reply routing is already reflected in the generated IDs. The instruction
|
|
30
|
+
* below makes that behavior explicit so an authenticated viewer's unmarked human feedback is
|
|
31
|
+
* not mistaken for an automated reply merely because the GitHub login matches.
|
|
34
32
|
*
|
|
35
33
|
* Contrast with what *does* stay in the skill's "Review-mutation mechanics" playbook —
|
|
36
34
|
* dismiss-ID retention and the first-look/annotation ID-exclusion rules. Those only matter
|
|
@@ -43,10 +41,10 @@ export function buildResolveCommandInstruction(resolveCommand) {
|
|
|
43
41
|
return [];
|
|
44
42
|
const instructions = [];
|
|
45
43
|
if ((resolveCommand.replyThreadIds?.length ?? 0) > 0) {
|
|
46
|
-
instructions.push("
|
|
44
|
+
instructions.push("Run the generated thread IDs unchanged. A latest comment beginning `<!-- pr-shepherd -->` is an established Shepherd reply; a marked viewer-authored human thread is emitted resolve-only, not for another reply.");
|
|
47
45
|
}
|
|
48
46
|
if (resolveCommand.requiresHeadSha) {
|
|
49
|
-
instructions.push("
|
|
47
|
+
instructions.push("If you did not change code, replace `$HEAD_SHA` with `$(git rev-parse HEAD)`, which must equal the current remote PR head. If you changed code, do not run this command until an authorized push updates the remote PR head; then replace `$HEAD_SHA` with that pushed commit SHA.");
|
|
50
48
|
}
|
|
51
49
|
if (resolveCommand.requiresDismissMessage) {
|
|
52
50
|
instructions.push("Replace `$DISMISS_MESSAGE` with one sentence describing what changed.");
|
|
@@ -54,19 +52,7 @@ export function buildResolveCommandInstruction(resolveCommand) {
|
|
|
54
52
|
instructions.push('Run the `apply review:` command shown above. See "Review-mutation mechanics" in the pr-shepherd skill for dismiss-ID retention.');
|
|
55
53
|
return instructions;
|
|
56
54
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Build the CI-triage instruction. The per-conclusion rerun policy (GitHub Actions log
|
|
59
|
-
* excerpts, `gh run view`/`gh run rerun` rules for CANCELLED/STARTUP_FAILURE/external
|
|
60
|
-
* failures) is invariant text keyed on the `[conclusion: …]` tags already rendered in
|
|
61
|
-
* `## Failing checks` — it lives in the pr-shepherd skill's "CI failure triage" playbook
|
|
62
|
-
* instead of being re-emitted every tick. This supersedes the "CI budget rules" example in
|
|
63
|
-
* CLAUDE.md's "Keep skills and loop prompts minimal" section (see that section's amendment
|
|
64
|
-
* note). The `(no runId)` case stays here because it flips `buildFixCompletionInstruction`
|
|
65
|
-
* to a human-handoff terminal state — that trigger, unlike the others, is CLI-decided. The
|
|
66
|
-
* CLI sentence does not claim every failure has a log excerpt to read (only GitHub Actions
|
|
67
|
-
* checks with a runId do — CANCELLED, STARTUP_FAILURE, and external checks may not); that
|
|
68
|
-
* per-kind detail is exactly what the skill playbook table disambiguates.
|
|
69
|
-
*/
|
|
55
|
+
/** Build the CI-triage pointer; the skill limits follow-up actions to included evidence. */
|
|
70
56
|
export function buildFailingCheckInstructions(checks) {
|
|
71
57
|
if (checks.length === 0)
|
|
72
58
|
return [];
|
|
@@ -74,17 +60,30 @@ export function buildFailingCheckInstructions(checks) {
|
|
|
74
60
|
const hasTriageable = checks.some((c) => c.runId || c.detailsUrl);
|
|
75
61
|
const instructions = [];
|
|
76
62
|
if (hasTriageable) {
|
|
77
|
-
instructions.push('Triage every failure under `## Failing checks`. See "CI failure triage" in the pr-shepherd skill for
|
|
63
|
+
instructions.push('Triage every failure under `## Failing checks`. See "CI failure triage" in the pr-shepherd skill for read-only inspection rules.');
|
|
78
64
|
}
|
|
79
65
|
if (hasBare) {
|
|
80
66
|
instructions.push("For each `(no runId)` failure, escalate to a human because no log or URL is available.");
|
|
81
67
|
}
|
|
82
68
|
return instructions;
|
|
83
69
|
}
|
|
84
|
-
export function buildFixCompletionInstruction(checks) {
|
|
85
|
-
|
|
86
|
-
|
|
70
|
+
export function buildFixCompletionInstruction(checks, requiresRemoteUpdateAuthorization = false, hasShaGatedReviewMutations = false) {
|
|
71
|
+
if (requiresRemoteUpdateAuthorization) {
|
|
72
|
+
return "`[FIX_CODE]` requires a human handoff for an authorized push after conflict resolution. Shepherd cannot verify the Git credential's push authorization. Stop polling after committing, and resume only after the remote PR head changes.";
|
|
73
|
+
}
|
|
74
|
+
const hasUninspectableFailure = checks.some((check) => !check.runId && !check.detailsUrl);
|
|
75
|
+
const hasCiAuthorizationHandoff = checks.some((check) => check.conclusion === "CANCELLED" ||
|
|
76
|
+
check.conclusion === "STARTUP_FAILURE" ||
|
|
77
|
+
(check.runId === null && Boolean(check.detailsUrl)) ||
|
|
78
|
+
(check.runId !== null && !check.logExcerpt?.trim()));
|
|
79
|
+
if (hasUninspectableFailure) {
|
|
87
80
|
return "`[FIX_CODE]` requires a human handoff for an uninspectable failing check. Stop polling after escalating, and resume only after human direction.";
|
|
88
81
|
}
|
|
82
|
+
if (hasCiAuthorizationHandoff) {
|
|
83
|
+
return "`[FIX_CODE]` requires a human handoff for a failing check with no authorized follow-up action. Stop polling after escalating, and resume only after human direction.";
|
|
84
|
+
}
|
|
85
|
+
if (hasShaGatedReviewMutations) {
|
|
86
|
+
return "`[FIX_CODE]` is conditional: if you changed code, stop after committing and resume only after an authorized push changes the remote PR head; if you did not change code, complete the authorized review mutations and iterate again with the same options.";
|
|
87
|
+
}
|
|
89
88
|
return "`[FIX_CODE]` is non-terminal. After completing these steps, iterate again with the same options to continue.";
|
|
90
89
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentThread, Review, ResolveCommand, AgentCheck, ReviewThread } from "../../types.mts";
|
|
1
|
+
import type { AgentThread, Review, ResolveCommand, AgentCheck, ReviewThread, ViewerAuthorization } from "../../types.mts";
|
|
2
2
|
import { type NormalizedBotUsernames } from "../../comments/authors.mts";
|
|
3
3
|
import type { MinimizeCommentsPolicy } from "../../config/load.mts";
|
|
4
4
|
export declare function classifyReviewSummaries(summaries: {
|
|
@@ -12,7 +12,7 @@ export declare function classifyReviewSummaries(summaries: {
|
|
|
12
12
|
editedSummaries: Review[];
|
|
13
13
|
surfacedApprovals: Review[];
|
|
14
14
|
};
|
|
15
|
-
export declare function buildResolveCommand(threads: AgentThread[], resolutionOnlyThreads: ReviewThread[], allCommentIds: string[], reviews: Review[], checks: AgentCheck[],
|
|
15
|
+
export declare function buildResolveCommand(threads: AgentThread[], resolutionOnlyThreads: ReviewThread[], allCommentIds: string[], reviews: Review[], checks: AgentCheck[], prReference: string | number, botUsernames?: NormalizedBotUsernames, ruleAutoResolveThreadIds?: string[], viewerAuthorization?: ViewerAuthorization, authorizationThreads?: ReviewThread[]): {
|
|
16
16
|
resolveCommand: ResolveCommand;
|
|
17
17
|
resolveOnlyCommand?: ResolveCommand;
|
|
18
18
|
};
|