pr-shepherd 0.37.2 → 0.38.1
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/bin/cli/help-command-pages.d.mts +3 -3
- package/bin/cli/help-command-pages.mjs +2 -2
- package/bin/cli/help-iterate-poll-pages.d.mts +1 -1
- 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 +1 -1
- package/bin/cli/help.d.mts +4 -4
- package/bin/cli/journal-formatter.mjs +2 -2
- package/bin/commands/check-annotations.d.mts +1 -1
- package/bin/commands/check-annotations.mjs +2 -2
- package/bin/commands/check.mjs +13 -1
- package/bin/commands/iterate/fix-code.mjs +2 -2
- package/bin/commands/iterate/index.mjs +2 -2
- package/bin/commands/iterate/stall.mjs +2 -2
- package/bin/commands/journal/journal-item.d.mts +11 -0
- package/bin/commands/journal/journal-item.mjs +29 -0
- package/bin/commands/journal/journal-markdown.d.mts +13 -0
- package/bin/commands/journal/journal-markdown.mjs +66 -0
- package/bin/commands/journal/transform.d.mts +1 -16
- package/bin/commands/journal/transform.mjs +118 -95
- package/bin/commands/poll.mjs +1 -10
- package/bin/commands/ready-mergeability.mjs +1 -0
- package/bin/commands/shepherd-journal.d.mts +5 -2
- package/bin/commands/shepherd-journal.mjs +6 -3
- package/bin/github/client.d.mts +5 -3
- package/bin/github/client.mjs +11 -4
- 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
|
@@ -50,7 +50,7 @@ Selectors:
|
|
|
50
50
|
--help, -h Print this help and exit before GitHub I/O.`;
|
|
51
51
|
readonly "apply journal": `pr-shepherd apply journal
|
|
52
52
|
|
|
53
|
-
Append a list item to the
|
|
53
|
+
Append a list item to the Shepherd Journal details block of a PR body.
|
|
54
54
|
|
|
55
55
|
Usage:
|
|
56
56
|
pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]
|
|
@@ -177,7 +177,7 @@ Flags:
|
|
|
177
177
|
PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
|
|
178
178
|
Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
179
179
|
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 Do not cancel in-progress runs before actionable fixes.\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\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 A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
180
|
-
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, 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 Do not cancel in-progress runs before actionable fixes.\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
|
|
180
|
+
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, 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 Do not cancel in-progress runs before actionable fixes.\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, CANCEL, or ESCALATE.\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 A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
181
181
|
readonly clean: `pr-shepherd clean
|
|
182
182
|
|
|
183
183
|
Remove pr-shepherd state files from PR_SHEPHERD_STATE_DIR.
|
|
@@ -205,7 +205,7 @@ Flags:
|
|
|
205
205
|
Exit code: 0 on success (including a no-op --dry-run on a nonexistent target); nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
206
206
|
readonly journal: `pr-shepherd journal
|
|
207
207
|
|
|
208
|
-
Append a list item to the
|
|
208
|
+
Append a list item to the Shepherd Journal details block of a PR body.
|
|
209
209
|
Creates the section at the end if absent. Idempotent — duplicate items are skipped.
|
|
210
210
|
|
|
211
211
|
Usage:
|
|
@@ -53,7 +53,7 @@ Selectors:
|
|
|
53
53
|
--help, -h Print this help and exit before GitHub I/O.`,
|
|
54
54
|
"apply journal": `pr-shepherd apply journal
|
|
55
55
|
|
|
56
|
-
Append a list item to the
|
|
56
|
+
Append a list item to the Shepherd Journal details block of a PR body.
|
|
57
57
|
|
|
58
58
|
Usage:
|
|
59
59
|
pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]
|
|
@@ -208,7 +208,7 @@ Flags:
|
|
|
208
208
|
Exit code: 0 on success (including a no-op --dry-run on a nonexistent target); nonzero on failure (sysexits.h — see docs/exit-codes.md).`,
|
|
209
209
|
journal: `pr-shepherd journal
|
|
210
210
|
|
|
211
|
-
Append a list item to the
|
|
211
|
+
Append a list item to the Shepherd Journal details block of a PR body.
|
|
212
212
|
Creates the section at the end if absent. Idempotent — duplicate items are skipped.
|
|
213
213
|
|
|
214
214
|
Usage:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
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 Do not cancel in-progress runs before actionable fixes.\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\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 A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
2
|
-
export declare const POLL_USAGE = "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, 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 Do not cancel in-progress runs before actionable fixes.\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
|
|
2
|
+
export declare const POLL_USAGE = "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, 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 Do not cancel in-progress runs before actionable fixes.\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, CANCEL, or ESCALATE.\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 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;
|
|
@@ -63,7 +63,7 @@ Forwarded iterate flags:
|
|
|
63
63
|
Durations accept s/m/h suffixes: 30s, 4.5m, 1h. A bare number uses each flag's default unit (seconds
|
|
64
64
|
for --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout); decimals are allowed only with
|
|
65
65
|
an explicit unit (4.5m).
|
|
66
|
-
Each WAIT tick writes
|
|
66
|
+
Each 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.
|
|
67
67
|
FIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.
|
|
68
68
|
With --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, CANCEL, or ESCALATE.
|
|
69
69
|
|
|
@@ -76,4 +76,4 @@ Exit codes: same as iterate (the final tick's action/reason decides the code).
|
|
|
76
76
|
14 CANCEL (closed without merging)
|
|
77
77
|
A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).`;
|
|
78
78
|
/** Public help page for the default PR polling invocation. */
|
|
79
|
-
export const DEFAULT_USAGE = POLL_USAGE.
|
|
79
|
+
export const DEFAULT_USAGE = POLL_USAGE.replace(/^pr-shepherd poll$/m, "pr-shepherd [PR]").replace(/^ {2}pr-shepherd poll \[PR\]/m, " pr-shepherd [PR]");
|
|
@@ -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 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-patch [PR] --thread-id ID --message MSG [flags]\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 Mark changed files as viewed in GitHub.\n apply journal Append a list item to the
|
|
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 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-patch [PR] --thread-id ID --message MSG [flags]\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 Mark changed files as viewed in GitHub.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n build-suggestion-patch\n Convert one GitHub suggestion thread into a patch 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 Do not cancel in-progress runs before actionable fixes.\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.";
|
|
@@ -19,7 +19,7 @@ Commands:
|
|
|
19
19
|
iterate Run one iterate tick (single-tick alias).
|
|
20
20
|
apply review Apply review-state mutations after fixes.
|
|
21
21
|
apply files Mark changed files as viewed in GitHub.
|
|
22
|
-
apply journal Append a list item to the
|
|
22
|
+
apply journal Append a list item to the Shepherd Journal details block of a PR body.
|
|
23
23
|
build-suggestion-patch
|
|
24
24
|
Convert one GitHub suggestion thread into a patch and commit instructions.
|
|
25
25
|
admin clean Remove pr-shepherd state files.
|
package/bin/cli/help.d.mts
CHANGED
|
@@ -50,7 +50,7 @@ Selectors:
|
|
|
50
50
|
--help, -h Print this help and exit before GitHub I/O.`;
|
|
51
51
|
readonly "apply journal": `pr-shepherd apply journal
|
|
52
52
|
|
|
53
|
-
Append a list item to the
|
|
53
|
+
Append a list item to the Shepherd Journal details block of a PR body.
|
|
54
54
|
|
|
55
55
|
Usage:
|
|
56
56
|
pr-shepherd apply journal [PR] <item> [--dry-run] [--format text|json]
|
|
@@ -177,7 +177,7 @@ Flags:
|
|
|
177
177
|
PR may be a number or GitHub pull request URL. When omitted, the current branch PR is inferred.
|
|
178
178
|
Exit code: 0 on success; nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
179
179
|
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 Do not cancel in-progress runs before actionable fixes.\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\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 A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
180
|
-
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, 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 Do not cancel in-progress runs before actionable fixes.\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
|
|
180
|
+
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, 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 Do not cancel in-progress runs before actionable fixes.\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, CANCEL, or ESCALATE.\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 A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).";
|
|
181
181
|
readonly clean: `pr-shepherd clean
|
|
182
182
|
|
|
183
183
|
Remove pr-shepherd state files from PR_SHEPHERD_STATE_DIR.
|
|
@@ -205,7 +205,7 @@ Flags:
|
|
|
205
205
|
Exit code: 0 on success (including a no-op --dry-run on a nonexistent target); nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
206
206
|
readonly journal: `pr-shepherd journal
|
|
207
207
|
|
|
208
|
-
Append a list item to the
|
|
208
|
+
Append a list item to the Shepherd Journal details block of a PR body.
|
|
209
209
|
Creates the section at the end if absent. Idempotent — duplicate items are skipped.
|
|
210
210
|
|
|
211
211
|
Usage:
|
|
@@ -228,7 +228,7 @@ Flags:
|
|
|
228
228
|
|
|
229
229
|
Exit code: 0 on success (including no-change no-op); nonzero on failure (sysexits.h — see docs/exit-codes.md).`;
|
|
230
230
|
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.";
|
|
231
|
-
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-patch [PR] --thread-id ID --message MSG [flags]\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 Mark changed files as viewed in GitHub.\n apply journal Append a list item to the
|
|
231
|
+
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-patch [PR] --thread-id ID --message MSG [flags]\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 Mark changed files as viewed in GitHub.\n apply journal Append a list item to the Shepherd Journal details block of a PR body.\n build-suggestion-patch\n Convert one GitHub suggestion thread into a patch 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 Do not cancel in-progress runs before actionable fixes.\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.";
|
|
232
232
|
};
|
|
233
233
|
/** Resolve help keys for nested public commands before any command I/O. */
|
|
234
234
|
export declare function helpKeyForArgs(args: string[]): keyof typeof USAGE;
|
|
@@ -9,7 +9,7 @@ export function formatJournalResult(result) {
|
|
|
9
9
|
if (!result.mutated)
|
|
10
10
|
return "No change — entry already present.";
|
|
11
11
|
if (!result.sectionExisted) {
|
|
12
|
-
return `Created
|
|
12
|
+
return `Created Shepherd Journal details in PR #${result.prNumber}.`;
|
|
13
13
|
}
|
|
14
|
-
return `Appended to
|
|
14
|
+
return `Appended to Shepherd Journal details in PR #${result.prNumber}.`;
|
|
15
15
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CheckAnnotation, ClassifiedCheck, ShepherdReport, TriagedCheck } from "../types.mts";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function checksWithActionableAnnotations(report: ShepherdReport): TriagedCheck[];
|
|
3
3
|
export declare function attachAndMergeCheckAnnotations(buckets: {
|
|
4
4
|
passing: ClassifiedCheck[];
|
|
5
5
|
failing: TriagedCheck[];
|
|
@@ -2,14 +2,14 @@ import { fetchCheckRunAnnotations } from "../github/check-annotations.mjs";
|
|
|
2
2
|
function shouldFetchCheckAnnotations(check) {
|
|
3
3
|
return check.id != null && check.status === "COMPLETED" && check.hasAnnotations === true;
|
|
4
4
|
}
|
|
5
|
-
export function
|
|
5
|
+
export function checksWithActionableAnnotations(report) {
|
|
6
6
|
return [
|
|
7
7
|
...report.checks.failing,
|
|
8
8
|
...report.checks.passing,
|
|
9
9
|
...report.checks.skipped,
|
|
10
10
|
...report.checks.filtered,
|
|
11
11
|
...(report.checks.ignored ?? []),
|
|
12
|
-
].filter((c) => (c.annotations?.length ?? 0) > 0);
|
|
12
|
+
].filter((c) => c.conclusion !== "SUCCESS" && (c.annotations?.length ?? 0) > 0);
|
|
13
13
|
}
|
|
14
14
|
export async function attachAndMergeCheckAnnotations(buckets, seenMap, prNumber) {
|
|
15
15
|
const candidates = [
|
package/bin/commands/check.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { deriveMergeStatus } from "../merge-status/derive.mjs";
|
|
|
7
7
|
import { loadConfig } from "../config/load.mjs";
|
|
8
8
|
import { classifyVisibleComments } from "../comments/visible-comments.mjs";
|
|
9
9
|
import { computeStatus } from "./check-status.mjs";
|
|
10
|
-
import { attachAndMergeCheckAnnotations } from "./check-annotations.mjs";
|
|
10
|
+
import { annotationMarkerBody, attachAndMergeCheckAnnotations } from "./check-annotations.mjs";
|
|
11
11
|
import { buildTerminalReport } from "./check-terminal-report.mjs";
|
|
12
12
|
import { isBlockedByFilteredCheck, refreshReadyMergeability, refreshUnknownMergeability, } from "./ready-mergeability.mjs";
|
|
13
13
|
import { loadSeenMap, markSeen, classifyItem } from "../state/seen-comments.mjs";
|
|
@@ -85,7 +85,16 @@ export async function runCheck(opts) {
|
|
|
85
85
|
const changesRequestedReviewVisibility = classifyChangesRequestedReviewsForDisplay(batchData.changesRequestedReviews.filter((r) => !partition.suppressedChangesRequestedIds.has(r.id)), seenMap, botUsernames);
|
|
86
86
|
const approvedReviewVisibility = classifyReviewsForDisplay(batchData.approvedReviews, seenMap);
|
|
87
87
|
if (opts.persistSeen !== false) {
|
|
88
|
+
const successfulAnnotations = [
|
|
89
|
+
...merged.passing,
|
|
90
|
+
...merged.skipped,
|
|
91
|
+
...merged.filtered,
|
|
92
|
+
...merged.ignored,
|
|
93
|
+
]
|
|
94
|
+
.filter((check) => check.conclusion === "SUCCESS")
|
|
95
|
+
.flatMap((check) => check.annotations ?? []);
|
|
88
96
|
await Promise.allSettled([
|
|
97
|
+
...successfulAnnotations.map((a) => markSeen(stateKey, a.id, annotationMarkerBody(a))),
|
|
89
98
|
...firstLookComments.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
90
99
|
...threadVisibility.toMarkSeen.map((t) => markSeen(stateKey, t.id, threadTranscriptBody(t))),
|
|
91
100
|
...visibleCommentClassification.toMarkSeen.map((c) => markSeen(stateKey, c.id, c.body)),
|
|
@@ -117,6 +126,9 @@ export async function runCheck(opts) {
|
|
|
117
126
|
batchData = refreshed.batchData;
|
|
118
127
|
mergeStatus = refreshed.mergeStatus;
|
|
119
128
|
status = refreshed.status;
|
|
129
|
+
if (mergeStatus.state === "MERGED" || mergeStatus.state === "CLOSED") {
|
|
130
|
+
return buildTerminalReport(prNumber, repo, batchData, mergeStatus, mergeStatus.state);
|
|
131
|
+
}
|
|
120
132
|
}
|
|
121
133
|
const blockedByFilteredCheck = isBlockedByFilteredCheck(mergeStatus, verdict);
|
|
122
134
|
return {
|
|
@@ -8,7 +8,7 @@ import { buildResolveCommand } from "./classify.mjs";
|
|
|
8
8
|
import { buildFixInstructions } from "./render.mjs";
|
|
9
9
|
import { applyStallGuard } from "./stall.mjs";
|
|
10
10
|
import { tryCancelRun, buildAutoCancelRunIdsWithOptions, buildInProgressRunIds, buildRunProtection, } from "./helpers.mjs";
|
|
11
|
-
import { annotationMarkerBody,
|
|
11
|
+
import { annotationMarkerBody, checksWithActionableAnnotations } from "../check-annotations.mjs";
|
|
12
12
|
import { threadTranscriptBody } from "../../threads/transcript.mjs";
|
|
13
13
|
import { isHumanAuthor, isConfiguredBotAuthor } from "../../comments/authors.mjs";
|
|
14
14
|
import { loadConfig } from "../../config/load.mjs";
|
|
@@ -30,7 +30,7 @@ function nextFixAttempts(stored, headSha, threads) {
|
|
|
30
30
|
export async function handleFixCode(ctx) {
|
|
31
31
|
const { base, report, opts, headSha, stallKey, prNumber, stallTimeoutSeconds, repoOwner, repoName, reviewSummaryIds, firstLookSummaries, editedSummaries, surfacedApprovals, botUsernames, ruleAutoResolveThreadIds, } = ctx;
|
|
32
32
|
const failingChecks = report.checks.failing;
|
|
33
|
-
const annotatedExtra =
|
|
33
|
+
const annotatedExtra = checksWithActionableAnnotations(report).filter((c) => c.category !== "failing");
|
|
34
34
|
const { protectedRunIds, protectedRuns } = buildRunProtection([...failingChecks, ...report.checks.inProgress], opts.neverCancelRuns);
|
|
35
35
|
const stored = await readFixAttempts({ owner: repoOwner, repo: repoName, pr: prNumber });
|
|
36
36
|
const { threadAttempts, threadBodyHashes } = nextFixAttempts(stored, headSha, report.threads.actionable);
|
|
@@ -12,7 +12,7 @@ import { clearStallState } from "../../state/iterate-stall.mjs";
|
|
|
12
12
|
import { handleFixCode } from "./fix-code.mjs";
|
|
13
13
|
import { normalizeBotUsernames } from "../../comments/authors.mjs";
|
|
14
14
|
import { autoMinimizeComments } from "../../comments/resolve.mjs";
|
|
15
|
-
import {
|
|
15
|
+
import { checksWithActionableAnnotations } from "../check-annotations.mjs";
|
|
16
16
|
export async function runIterate(opts) {
|
|
17
17
|
const config = loadConfig();
|
|
18
18
|
const botUsernames = normalizeBotUsernames(config.botUsernames);
|
|
@@ -70,7 +70,7 @@ export async function runIterate(opts) {
|
|
|
70
70
|
report.comments.firstLook.length > 0 ||
|
|
71
71
|
report.changesRequestedReviews.length > 0 ||
|
|
72
72
|
report.checks.failing.length > 0 ||
|
|
73
|
-
|
|
73
|
+
checksWithActionableAnnotations(report).length > 0 ||
|
|
74
74
|
report.mergeStatus.status === "CONFLICTS" ||
|
|
75
75
|
reviewSummaryIds.length > 0 ||
|
|
76
76
|
firstLookSummaries.length > 0 ||
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readStallState, writeStallState } from "../../state/iterate-stall.mjs";
|
|
2
2
|
import { toAgentThread, toAgentComment, toAgentStalledCheck } from "../../reporters/agent.mjs";
|
|
3
3
|
import { buildEscalateSuggestion, buildEscalateHumanMessage, formatDurationApprox, } from "./escalate.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { checksWithActionableAnnotations } from "../check-annotations.mjs";
|
|
5
5
|
function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds) {
|
|
6
6
|
const checks = [
|
|
7
7
|
...report.checks.failing.map((f) => `failing:${f.name}:${f.conclusion}`),
|
|
@@ -14,7 +14,7 @@ function computeStallFingerprint(action, headSha, base, report, reviewSummaryIds
|
|
|
14
14
|
const reviews = report.changesRequestedReviews.map((r) => r.id).sort();
|
|
15
15
|
const summaries = [...reviewSummaryIds].sort();
|
|
16
16
|
const ruleAutoResolveSummaries = (report.ruleAutoResolveReviewSummaryIds ?? []).sort((a, b) => a.localeCompare(b));
|
|
17
|
-
const annotations =
|
|
17
|
+
const annotations = checksWithActionableAnnotations(report)
|
|
18
18
|
.flatMap((c) => (c.annotations ?? []).map((a) => a.id))
|
|
19
19
|
.sort((a, b) => a.localeCompare(b));
|
|
20
20
|
return JSON.stringify({
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type ValidationOk = {
|
|
2
|
+
ok: true;
|
|
3
|
+
item: string;
|
|
4
|
+
};
|
|
5
|
+
type ValidationError = {
|
|
6
|
+
ok: false;
|
|
7
|
+
error: string;
|
|
8
|
+
};
|
|
9
|
+
export type ValidationResult = ValidationOk | ValidationError;
|
|
10
|
+
export declare function validateJournalItem(input: string): ValidationResult;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isJournalLikeSummary, isReservedJournalMarker } from "./journal-markdown.mjs";
|
|
2
|
+
export function validateJournalItem(input) {
|
|
3
|
+
const lines = input.split("\n").map((line) => line.trimEnd());
|
|
4
|
+
const nonBlank = lines.filter((line) => line.trim() !== "");
|
|
5
|
+
if (nonBlank.length === 0) {
|
|
6
|
+
return { ok: false, error: 'journal item must not be empty; expected a "- <text>" list item' };
|
|
7
|
+
}
|
|
8
|
+
if (!/^- \S/.test(nonBlank[0])) {
|
|
9
|
+
return {
|
|
10
|
+
ok: false,
|
|
11
|
+
error: `journal item must start with "- <text>"; got: ${JSON.stringify(nonBlank[0].slice(0, 40))}`,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
for (const line of nonBlank.slice(1)) {
|
|
15
|
+
if (line.startsWith("#")) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
error: "journal item lines must not start with # (would break section structure)",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (isReservedJournalMarker(line.trim()) || isJournalLikeSummary(line.trim())) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
error: `journal item must not contain standalone journal container marker ${JSON.stringify(line.trim())}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { ok: true, item: lines.join("\n").trim() };
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type Fence = {
|
|
2
|
+
marker: "`" | "~";
|
|
3
|
+
length: number;
|
|
4
|
+
};
|
|
5
|
+
export type MarkdownScanState = {
|
|
6
|
+
comment: boolean;
|
|
7
|
+
fence: Fence | null;
|
|
8
|
+
};
|
|
9
|
+
export declare function findDetailsClose(lines: string[], startIdx: number): number;
|
|
10
|
+
export declare function isJournalLikeSummary(line: string): boolean;
|
|
11
|
+
export declare function isReservedJournalMarker(line: string): boolean;
|
|
12
|
+
export declare function skipMarkdownLine(state: MarkdownScanState, line: string): boolean;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SHEPHERD_JOURNAL_DETAILS_CLOSE, SHEPHERD_JOURNAL_DETAILS_SUMMARY, } from "../shepherd-journal.mjs";
|
|
2
|
+
export function findDetailsClose(lines, startIdx) {
|
|
3
|
+
let depth = 1;
|
|
4
|
+
const state = { fence: null, comment: false };
|
|
5
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
6
|
+
if (skipMarkdownLine(state, lines[i]))
|
|
7
|
+
continue;
|
|
8
|
+
if (isJournalLikeSummary(lines[i].trimStart())) {
|
|
9
|
+
throw new Error("duplicate Shepherd Journal details summary inside canonical container");
|
|
10
|
+
}
|
|
11
|
+
if (isDetailsOpening(lines[i]))
|
|
12
|
+
depth++;
|
|
13
|
+
if (lines[i].trim() === SHEPHERD_JOURNAL_DETAILS_CLOSE) {
|
|
14
|
+
if (lines[i] !== SHEPHERD_JOURNAL_DETAILS_CLOSE) {
|
|
15
|
+
throw new Error("malformed Shepherd Journal details container: closing marker must be unindented");
|
|
16
|
+
}
|
|
17
|
+
if (--depth === 0)
|
|
18
|
+
return i;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
throw new Error("unterminated or unsafe nested Shepherd Journal details container");
|
|
22
|
+
}
|
|
23
|
+
export function isJournalLikeSummary(line) {
|
|
24
|
+
return /^<summary>\s*Shepherd\s+Journal\b/i.test(line);
|
|
25
|
+
}
|
|
26
|
+
export function isReservedJournalMarker(line) {
|
|
27
|
+
return (isDetailsOpening(line) ||
|
|
28
|
+
line === SHEPHERD_JOURNAL_DETAILS_SUMMARY ||
|
|
29
|
+
line === SHEPHERD_JOURNAL_DETAILS_CLOSE);
|
|
30
|
+
}
|
|
31
|
+
function isDetailsOpening(line) {
|
|
32
|
+
return /^<details(?:\s+[^>]*)?>$/.test(line);
|
|
33
|
+
}
|
|
34
|
+
function advanceHtmlComment(active, line) {
|
|
35
|
+
return active ? !line.includes("-->") : line.includes("<!--") && !line.includes("-->");
|
|
36
|
+
}
|
|
37
|
+
export function skipMarkdownLine(state, line) {
|
|
38
|
+
if (state.comment) {
|
|
39
|
+
state.comment = advanceHtmlComment(true, line);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (state.fence) {
|
|
43
|
+
state.fence = advanceFence(state.fence, line);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (line.includes("<!--")) {
|
|
47
|
+
state.comment = advanceHtmlComment(false, line);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
state.fence = advanceFence(null, line);
|
|
51
|
+
return state.fence !== null;
|
|
52
|
+
}
|
|
53
|
+
function advanceFence(activeFence, line) {
|
|
54
|
+
const match = line.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
55
|
+
if (!activeFence) {
|
|
56
|
+
if (match?.[1][0] === "`" && match[2].includes("`"))
|
|
57
|
+
return null;
|
|
58
|
+
return match ? { marker: match[1][0], length: match[1].length } : null;
|
|
59
|
+
}
|
|
60
|
+
return match &&
|
|
61
|
+
match[1][0] === activeFence.marker &&
|
|
62
|
+
match[1].length >= activeFence.length &&
|
|
63
|
+
/^[ \t]*$/.test(match[2])
|
|
64
|
+
? null
|
|
65
|
+
: activeFence;
|
|
66
|
+
}
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
+
export { validateJournalItem } from "./journal-item.mts";
|
|
1
2
|
export interface AppendResult {
|
|
2
3
|
body: string;
|
|
3
4
|
mutated: boolean;
|
|
4
5
|
sectionExisted: boolean;
|
|
5
6
|
}
|
|
6
|
-
type ValidationOk = {
|
|
7
|
-
ok: true;
|
|
8
|
-
item: string;
|
|
9
|
-
};
|
|
10
|
-
type ValidationError = {
|
|
11
|
-
ok: false;
|
|
12
|
-
error: string;
|
|
13
|
-
};
|
|
14
|
-
export type ValidationResult = ValidationOk | ValidationError;
|
|
15
|
-
/** Validates that the input is a properly formed markdown list item. */
|
|
16
|
-
export declare function validateJournalItem(input: string): ValidationResult;
|
|
17
|
-
/**
|
|
18
|
-
* Appends a validated list item to the ## Shepherd Journal section of a PR body.
|
|
19
|
-
* Creates the section at the end if absent. Skips if the exact item is already present (idempotent).
|
|
20
|
-
*/
|
|
21
7
|
export declare function appendJournalItem(body: string, item: string): AppendResult;
|
|
22
|
-
export {};
|
|
@@ -1,112 +1,135 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
const lines = input.split("\n").map((l) => l.trimEnd());
|
|
5
|
-
const nonBlank = lines.filter((l) => l.trim() !== "");
|
|
6
|
-
if (nonBlank.length === 0) {
|
|
7
|
-
return { ok: false, error: 'journal item must not be empty; expected a "- <text>" list item' };
|
|
8
|
-
}
|
|
9
|
-
if (!/^- \S/.test(nonBlank[0])) {
|
|
10
|
-
return {
|
|
11
|
-
ok: false,
|
|
12
|
-
error: `journal item must start with "- <text>"; got: ${JSON.stringify(nonBlank[0].slice(0, 40))}`,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
for (const line of nonBlank.slice(1)) {
|
|
16
|
-
if (line.startsWith("#")) {
|
|
17
|
-
return {
|
|
18
|
-
ok: false,
|
|
19
|
-
error: "journal item lines must not start with # (would break section structure)",
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
const trimmed = lines
|
|
24
|
-
.map((l) => l.trimEnd())
|
|
25
|
-
.join("\n")
|
|
26
|
-
.trim();
|
|
27
|
-
return { ok: true, item: trimmed };
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Appends a validated list item to the ## Shepherd Journal section of a PR body.
|
|
31
|
-
* Creates the section at the end if absent. Skips if the exact item is already present (idempotent).
|
|
32
|
-
*/
|
|
1
|
+
import { SHEPHERD_JOURNAL_DETAILS_OPEN, SHEPHERD_JOURNAL_DETAILS_CLOSE, SHEPHERD_JOURNAL_DETAILS_SUMMARY, SHEPHERD_JOURNAL_SECTION_PATTERN, } from "../shepherd-journal.mjs";
|
|
2
|
+
import { findDetailsClose, isJournalLikeSummary, skipMarkdownLine, } from "./journal-markdown.mjs";
|
|
3
|
+
export { validateJournalItem } from "./journal-item.mjs";
|
|
33
4
|
export function appendJournalItem(body, item) {
|
|
34
|
-
const lines = body.split("\n");
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
5
|
+
const lines = body.replaceAll("\r\n", "\n").split("\n");
|
|
6
|
+
const canonical = findCanonicalJournal(lines);
|
|
7
|
+
const legacy = findLegacyJournal(lines);
|
|
8
|
+
if (canonical && legacy)
|
|
9
|
+
throw new Error("ambiguous Shepherd Journal: both canonical details and legacy ## section exist");
|
|
10
|
+
if (canonical)
|
|
11
|
+
return appendToCanonical(lines, canonical, item, body);
|
|
12
|
+
if (legacy)
|
|
13
|
+
return migrateLegacyJournal(lines, legacy, item);
|
|
14
|
+
return createCanonicalJournal(lines, item);
|
|
15
|
+
}
|
|
16
|
+
function findCanonicalJournal(lines) {
|
|
17
|
+
const journals = [];
|
|
18
|
+
const state = { fence: null, comment: false };
|
|
19
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20
|
+
if (skipMarkdownLine(state, lines[i]) || !isJournalLikeSummary(lines[i].trimStart()))
|
|
21
|
+
continue;
|
|
22
|
+
if (lines[i] !== SHEPHERD_JOURNAL_DETAILS_SUMMARY) {
|
|
23
|
+
throw new Error("malformed Shepherd Journal details container: expected exact summary line");
|
|
24
|
+
}
|
|
25
|
+
if (i === 0 || lines[i - 1] !== SHEPHERD_JOURNAL_DETAILS_OPEN) {
|
|
26
|
+
throw new Error("malformed Shepherd Journal details container: summary must immediately follow <details>");
|
|
27
|
+
}
|
|
28
|
+
if (lines[i + 1] !== "") {
|
|
29
|
+
throw new Error("malformed Shepherd Journal details container: expected blank line after summary");
|
|
30
|
+
}
|
|
31
|
+
const closeIdx = findDetailsClose(lines, i + 1);
|
|
32
|
+
journals.push({ summaryIdx: i, closeIdx });
|
|
33
|
+
i = closeIdx;
|
|
43
34
|
}
|
|
44
|
-
|
|
35
|
+
if (journals.length > 1)
|
|
36
|
+
throw new Error("duplicate Shepherd Journal details containers");
|
|
37
|
+
return journals[0] ?? null;
|
|
45
38
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
39
|
+
function findLegacyJournal(lines) {
|
|
40
|
+
const state = { fence: null, comment: false };
|
|
48
41
|
let headingIdx = -1;
|
|
42
|
+
let endIdx = null;
|
|
49
43
|
for (let i = 0; i < lines.length; i++) {
|
|
50
|
-
|
|
51
|
-
if (trimmed.startsWith("```") || trimmed.startsWith("~~~")) {
|
|
52
|
-
inFence = !inFence;
|
|
44
|
+
if (skipMarkdownLine(state, lines[i]))
|
|
53
45
|
continue;
|
|
46
|
+
if (SHEPHERD_JOURNAL_SECTION_PATTERN.test(lines[i].trimEnd())) {
|
|
47
|
+
if (headingIdx !== -1)
|
|
48
|
+
throw new Error("duplicate legacy Shepherd Journal sections");
|
|
49
|
+
headingIdx = i;
|
|
50
|
+
}
|
|
51
|
+
else if (headingIdx !== -1 &&
|
|
52
|
+
endIdx === null &&
|
|
53
|
+
lines[i].trim() === SHEPHERD_JOURNAL_DETAILS_CLOSE) {
|
|
54
|
+
throw new Error("unsafe legacy Shepherd Journal section: standalone </details> line");
|
|
54
55
|
}
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
if (SHEPHERD_JOURNAL_SECTION_PATTERN.test(lines[i].trimEnd())) {
|
|
58
|
-
headingIdx = i;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
else if (/^#{1,2} /.test(lines[i])) {
|
|
62
|
-
return { headingIdx, endIdx: i };
|
|
63
|
-
}
|
|
56
|
+
else if (headingIdx !== -1 && endIdx === null && /^#{1,2} /.test(lines[i])) {
|
|
57
|
+
endIdx = i;
|
|
64
58
|
}
|
|
65
59
|
}
|
|
66
|
-
|
|
67
|
-
return null;
|
|
68
|
-
return { headingIdx, endIdx: lines.length };
|
|
60
|
+
return headingIdx === -1 ? null : { headingIdx, endIdx: endIdx ?? lines.length };
|
|
69
61
|
}
|
|
70
|
-
function
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
62
|
+
function appendToCanonical(lines, bounds, item, originalBody) {
|
|
63
|
+
const journalLines = lines.slice(bounds.summaryIdx + 1, bounds.closeIdx);
|
|
64
|
+
if (itemAlreadyPresent(journalLines, item))
|
|
65
|
+
return { body: originalBody, mutated: false, sectionExisted: true };
|
|
66
|
+
const trimmed = trimTrailingBlankLines(journalLines);
|
|
67
|
+
const content = trimmed.length === 0 ? ["", ...item.split("\n")] : [...trimmed, ...item.split("\n")];
|
|
68
|
+
return {
|
|
69
|
+
body: [
|
|
70
|
+
...lines.slice(0, bounds.summaryIdx + 1),
|
|
71
|
+
...content,
|
|
72
|
+
...lines.slice(bounds.closeIdx),
|
|
73
|
+
].join("\n"),
|
|
74
|
+
mutated: true,
|
|
75
|
+
sectionExisted: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function migrateLegacyJournal(lines, bounds, item) {
|
|
79
|
+
const journalLines = trimBlankLines(lines.slice(bounds.headingIdx + 1, bounds.endIdx));
|
|
80
|
+
const content = itemAlreadyPresent(journalLines, item)
|
|
81
|
+
? journalLines
|
|
82
|
+
: journalLines.length === 0
|
|
83
|
+
? ["", ...item.split("\n")]
|
|
84
|
+
: [...journalLines, ...item.split("\n")];
|
|
85
|
+
const canonical = [
|
|
86
|
+
SHEPHERD_JOURNAL_DETAILS_OPEN,
|
|
87
|
+
SHEPHERD_JOURNAL_DETAILS_SUMMARY,
|
|
88
|
+
"",
|
|
89
|
+
...content,
|
|
90
|
+
SHEPHERD_JOURNAL_DETAILS_CLOSE,
|
|
91
|
+
];
|
|
92
|
+
const after = lines.slice(bounds.endIdx);
|
|
93
|
+
return {
|
|
94
|
+
body: [
|
|
95
|
+
...lines.slice(0, bounds.headingIdx),
|
|
96
|
+
...canonical,
|
|
97
|
+
...(after.length > 0 ? ["", ...after] : []),
|
|
98
|
+
].join("\n"),
|
|
99
|
+
mutated: true,
|
|
100
|
+
sectionExisted: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function createCanonicalJournal(lines, item) {
|
|
104
|
+
const existing = trimTrailingBlankLines(lines);
|
|
105
|
+
const canonical = [
|
|
106
|
+
SHEPHERD_JOURNAL_DETAILS_OPEN,
|
|
107
|
+
SHEPHERD_JOURNAL_DETAILS_SUMMARY,
|
|
108
|
+
"",
|
|
109
|
+
...item.split("\n"),
|
|
110
|
+
SHEPHERD_JOURNAL_DETAILS_CLOSE,
|
|
111
|
+
];
|
|
112
|
+
return {
|
|
113
|
+
body: [...existing, ...(existing.length > 0 ? [""] : []), ...canonical].join("\n"),
|
|
114
|
+
mutated: true,
|
|
115
|
+
sectionExisted: false,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function itemAlreadyPresent(journalLines, item) {
|
|
119
|
+
const itemLines = item.split("\n").map((line) => line.trimEnd());
|
|
120
|
+
for (let i = 0; i <= journalLines.length - itemLines.length; i++) {
|
|
121
|
+
if (itemLines.every((line, offset) => journalLines[i + offset].trimEnd() === line))
|
|
82
122
|
return true;
|
|
83
123
|
}
|
|
84
124
|
return false;
|
|
85
125
|
}
|
|
86
|
-
function
|
|
87
|
-
const before = lines.slice(0, headingIdx + 1);
|
|
88
|
-
const after = lines.slice(endIdx);
|
|
89
|
-
// Strip trailing blank lines from section body.
|
|
90
|
-
let sectionEnd = sectionLines.length;
|
|
91
|
-
while (sectionEnd > 0 && sectionLines[sectionEnd - 1].trim() === "") {
|
|
92
|
-
sectionEnd--;
|
|
93
|
-
}
|
|
94
|
-
const trimmedSection = sectionLines.slice(0, sectionEnd);
|
|
95
|
-
// Insert blank line after heading when section was empty, then the item.
|
|
96
|
-
const newSection = trimmedSection.length === 0
|
|
97
|
-
? ["", ...item.split("\n")]
|
|
98
|
-
: [...trimmedSection, ...item.split("\n")];
|
|
99
|
-
// One blank line before the next section (or trailing newline at EOF).
|
|
100
|
-
const newBody = [...before, ...newSection, ...(after.length > 0 ? ["", ...after] : [])].join("\n");
|
|
101
|
-
return { body: newBody, mutated: true, sectionExisted: true };
|
|
102
|
-
}
|
|
103
|
-
function createSection(lines, item) {
|
|
104
|
-
// Strip trailing blank lines from the existing body.
|
|
126
|
+
function trimTrailingBlankLines(lines) {
|
|
105
127
|
let end = lines.length;
|
|
106
|
-
while (end > 0 && lines[end - 1].trim() === "")
|
|
128
|
+
while (end > 0 && lines[end - 1].trim() === "")
|
|
107
129
|
end--;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
return lines.slice(0, end);
|
|
131
|
+
}
|
|
132
|
+
function trimBlankLines(lines) {
|
|
133
|
+
const first = lines.findIndex((line) => line.trim() !== "");
|
|
134
|
+
return first === -1 ? [] : trimTrailingBlankLines(lines.slice(first));
|
|
112
135
|
}
|
package/bin/commands/poll.mjs
CHANGED
|
@@ -6,7 +6,7 @@ function writeTickProgress(tick, elapsedSeconds, sleepSeconds, verbose) {
|
|
|
6
6
|
process.stderr.write(`[poll tick ${tick} / +${elapsedSeconds}s] WAIT — sleeping ${sleepSeconds}s\n`);
|
|
7
7
|
}
|
|
8
8
|
else {
|
|
9
|
-
process.stderr.write(
|
|
9
|
+
process.stderr.write(`[poll tick ${tick} / +${elapsedSeconds}s] WAIT — still running; next tick in ${sleepSeconds}s\n`);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
function waitSignature(result) {
|
|
@@ -73,7 +73,6 @@ export async function runPoll(opts) {
|
|
|
73
73
|
const verbose = opts.verbose === true;
|
|
74
74
|
const quietStatus = quietStatusOpt === true;
|
|
75
75
|
const untilTerminal = untilTerminalOpt === true;
|
|
76
|
-
let dotsPrinted = false;
|
|
77
76
|
let lastWaitSignature = null;
|
|
78
77
|
// When prNumber is omitted, iterateOpts.prNumber starts undefined and each tick would otherwise
|
|
79
78
|
// re-infer the PR from the current branch. That inference query only matches OPEN PRs, so once
|
|
@@ -110,8 +109,6 @@ export async function runPoll(opts) {
|
|
|
110
109
|
verbose,
|
|
111
110
|
lastWaitSignature,
|
|
112
111
|
});
|
|
113
|
-
if (!quietStatus && !verbose)
|
|
114
|
-
dotsPrinted = true;
|
|
115
112
|
await sleep(intervalMs);
|
|
116
113
|
continue;
|
|
117
114
|
}
|
|
@@ -124,10 +121,6 @@ export async function runPoll(opts) {
|
|
|
124
121
|
debounceUntil ??= Date.now() + debounceMs;
|
|
125
122
|
const remainingMs = debounceUntil - Date.now();
|
|
126
123
|
if (remainingMs > 0) {
|
|
127
|
-
if (dotsPrinted) {
|
|
128
|
-
process.stderr.write("\n");
|
|
129
|
-
dotsPrinted = false;
|
|
130
|
-
}
|
|
131
124
|
writeDebounceProgress(tick, Date.now() - start, remainingMs);
|
|
132
125
|
await sleep(Math.min(intervalMs, remainingMs));
|
|
133
126
|
}
|
|
@@ -138,7 +131,5 @@ export async function runPoll(opts) {
|
|
|
138
131
|
}
|
|
139
132
|
break;
|
|
140
133
|
}
|
|
141
|
-
if (dotsPrinted)
|
|
142
|
-
process.stderr.write("\n");
|
|
143
134
|
return lastResult;
|
|
144
135
|
}
|
|
@@ -24,6 +24,7 @@ async function readMergeability(prNumber, repo, batchData) {
|
|
|
24
24
|
const restState = await getMergeableState(prNumber, repo.owner, repo.name);
|
|
25
25
|
return {
|
|
26
26
|
...batchData,
|
|
27
|
+
state: restState.state ?? batchData.state,
|
|
27
28
|
mergeable: restState.mergeable ?? batchData.mergeable,
|
|
28
29
|
mergeStateStatus: restState.mergeStateStatus ?? batchData.mergeStateStatus,
|
|
29
30
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
export declare const SHEPHERD_JOURNAL_SECTION = "
|
|
1
|
+
export declare const SHEPHERD_JOURNAL_SECTION = "Shepherd Journal";
|
|
2
2
|
export declare const SHEPHERD_JOURNAL_SECTION_PATTERN: RegExp;
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const SHEPHERD_JOURNAL_DETAILS_OPEN = "<details>";
|
|
4
|
+
export declare const SHEPHERD_JOURNAL_DETAILS_SUMMARY = "<summary>Shepherd Journal</summary>";
|
|
5
|
+
export declare const SHEPHERD_JOURNAL_DETAILS_CLOSE = "</details>";
|
|
6
|
+
export declare const SHEPHERD_JOURNAL_APPEND_HINT = "If Shepherd Journal details already exist, append entries inside them instead of creating another container.";
|
|
4
7
|
export declare const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review each body under `## Review summaries (first look)`. Eligible non-human IDs are already in `--minimize-comment-ids`. Record any warranted Shepherd Journal note before review mutations.";
|
|
5
8
|
export declare function buildShepherdJournalInstruction(prNumber: number, itemReferenceGuidance: string): string[];
|
|
6
9
|
export declare const SHEPHERD_JOURNAL_REFERENCE_GUIDANCE_THREADS_AND_COMMENTS_IN_ITEM_HEADINGS = "Link threads and comments from their headings. Cite reviews by ID.";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
export const SHEPHERD_JOURNAL_SECTION = "
|
|
1
|
+
export const SHEPHERD_JOURNAL_SECTION = "Shepherd Journal";
|
|
2
2
|
export const SHEPHERD_JOURNAL_SECTION_PATTERN = /^##\s+Shepherd\s+Journal$/;
|
|
3
|
-
export const
|
|
3
|
+
export const SHEPHERD_JOURNAL_DETAILS_OPEN = "<details>";
|
|
4
|
+
export const SHEPHERD_JOURNAL_DETAILS_SUMMARY = "<summary>Shepherd Journal</summary>";
|
|
5
|
+
export const SHEPHERD_JOURNAL_DETAILS_CLOSE = "</details>";
|
|
6
|
+
export const SHEPHERD_JOURNAL_APPEND_HINT = "If Shepherd Journal details already exist, append entries inside them instead of creating another container.";
|
|
4
7
|
export const SHEPHERD_JOURNAL_FIRST_LOOK_GUIDANCE = "Review each body under `## Review summaries (first look)`. Eligible non-human IDs are already in `--minimize-comment-ids`. Record any warranted Shepherd Journal note before review mutations.";
|
|
5
8
|
export function buildShepherdJournalInstruction(prNumber, itemReferenceGuidance) {
|
|
6
9
|
return [
|
|
7
|
-
`For any substantial decision or rejection, append \`- <decision>\` to
|
|
10
|
+
`For any substantial decision or rejection, append \`- <decision>\` to Shepherd Journal with \`pr-shepherd apply journal ${prNumber} '- <decision>'\`.`,
|
|
8
11
|
itemReferenceGuidance,
|
|
9
12
|
];
|
|
10
13
|
}
|
package/bin/github/client.d.mts
CHANGED
|
@@ -34,13 +34,15 @@ export declare function getPullRequestBody(pr: number, owner: string, name: stri
|
|
|
34
34
|
/** Overwrites the PR body. */
|
|
35
35
|
export declare function updatePullRequestBody(pullRequestId: string, body: string): Promise<void>;
|
|
36
36
|
/**
|
|
37
|
-
* Fetches `mergeable
|
|
37
|
+
* Fetches PR state, `mergeable`, and `mergeStateStatus` via the REST API.
|
|
38
38
|
*
|
|
39
|
-
* Used
|
|
40
|
-
*
|
|
39
|
+
* Used when the GraphQL API returns `UNKNOWN` for mergeability or before a
|
|
40
|
+
* READY handoff. The same response carries state so a concurrent merge or
|
|
41
|
+
* close can supersede the earlier GraphQL snapshot without another request.
|
|
41
42
|
*/
|
|
42
43
|
export declare function getMergeableState(pr: number, owner: string, repo: string): Promise<{
|
|
43
44
|
mergeable: MergeableState;
|
|
44
45
|
mergeStateStatus: MergeStateStatus;
|
|
46
|
+
state?: "OPEN" | "CLOSED" | "MERGED";
|
|
45
47
|
}>;
|
|
46
48
|
export declare function getCurrentBranch(): Promise<string>;
|
package/bin/github/client.mjs
CHANGED
|
@@ -83,16 +83,23 @@ export async function updatePullRequestBody(pullRequestId, body) {
|
|
|
83
83
|
await httpGraphql(UPDATE_PR_BODY_MUTATION, { pullRequestId, body });
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Fetches `mergeable
|
|
86
|
+
* Fetches PR state, `mergeable`, and `mergeStateStatus` via the REST API.
|
|
87
87
|
*
|
|
88
|
-
* Used
|
|
89
|
-
*
|
|
88
|
+
* Used when the GraphQL API returns `UNKNOWN` for mergeability or before a
|
|
89
|
+
* READY handoff. The same response carries state so a concurrent merge or
|
|
90
|
+
* close can supersede the earlier GraphQL snapshot without another request.
|
|
90
91
|
*/
|
|
91
92
|
export async function getMergeableState(pr, owner, repo) {
|
|
92
93
|
const data = await rest("GET", `/repos/${owner}/${repo}/pulls/${pr}`);
|
|
93
94
|
const mergeable = data.mergeable === true ? "MERGEABLE" : data.mergeable === false ? "CONFLICTING" : "UNKNOWN";
|
|
94
95
|
const mergeStateStatus = data.mergeable_state.toUpperCase();
|
|
95
|
-
|
|
96
|
+
const rawState = data.state?.toUpperCase();
|
|
97
|
+
const state = data.merged_at != null
|
|
98
|
+
? "MERGED"
|
|
99
|
+
: rawState === "OPEN" || rawState === "CLOSED"
|
|
100
|
+
? rawState
|
|
101
|
+
: undefined;
|
|
102
|
+
return { mergeable, mergeStateStatus, ...(state !== undefined && { state }) };
|
|
96
103
|
}
|
|
97
104
|
// ---------------------------------------------------------------------------
|
|
98
105
|
// Internal helpers
|
package/package.json
CHANGED