pr-shepherd 0.42.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.
Files changed (145) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +24 -18
  3. package/bin/api.d.mts +3 -2
  4. package/bin/api.mjs +13 -18
  5. package/bin/checks/classify.d.mts +3 -1
  6. package/bin/checks/classify.mjs +5 -2
  7. package/bin/cli/args.mjs +14 -9
  8. package/bin/cli/default-poll.mjs +1 -0
  9. package/bin/cli/fix-formatter.mjs +20 -8
  10. package/bin/cli/handlers.mjs +1 -0
  11. package/bin/cli/help-command-pages.d.mts +9 -6
  12. package/bin/cli/help-command-pages.mjs +7 -4
  13. package/bin/cli/help-iterate-poll-pages.d.mts +2 -2
  14. package/bin/cli/help-iterate-poll-pages.mjs +9 -4
  15. package/bin/cli/help-top-page.d.mts +1 -1
  16. package/bin/cli/help-top-page.mjs +2 -2
  17. package/bin/cli/help.d.mts +10 -7
  18. package/bin/cli/iterate-flags.d.mts +1 -0
  19. package/bin/cli/iterate-flags.mjs +2 -0
  20. package/bin/cli/iterate-formatter.d.mts +1 -1
  21. package/bin/cli/iterate-formatter.mjs +9 -6
  22. package/bin/cli/iterate-instructions.mjs +15 -0
  23. package/bin/cli/iterate-lean.d.mts +1 -2
  24. package/bin/cli/iterate-lean.mjs +16 -17
  25. package/bin/cli/iterate-merge-formatter.d.mts +3 -0
  26. package/bin/cli/iterate-merge-formatter.mjs +52 -0
  27. package/bin/cli/iterate-verbose.d.mts +6 -0
  28. package/bin/cli/iterate-verbose.mjs +14 -0
  29. package/bin/cli/journal-formatter.mjs +3 -0
  30. package/bin/cli/journal-handler.mjs +11 -7
  31. package/bin/cli/list-formatters.d.mts +3 -1
  32. package/bin/cli/list-formatters.mjs +9 -7
  33. package/bin/cli/mark-files-as-viewed-formatter.mjs +7 -1
  34. package/bin/cli/mutate-formatter.mjs +4 -0
  35. package/bin/cli/poll-handler.mjs +1 -0
  36. package/bin/cli/runner.mjs +4 -3
  37. package/bin/commands/check-terminal-report.mjs +1 -0
  38. package/bin/commands/check.mjs +64 -7
  39. package/bin/commands/commit-suggestion-instruction.d.mts +1 -1
  40. package/bin/commands/commit-suggestion-instruction.mjs +2 -2
  41. package/bin/commands/commit-suggestion.mjs +3 -1
  42. package/bin/commands/iterate/base.d.mts +5 -0
  43. package/bin/commands/iterate/base.mjs +25 -0
  44. package/bin/commands/iterate/check-instructions.d.mts +5 -19
  45. package/bin/commands/iterate/check-instructions.mjs +24 -25
  46. package/bin/commands/iterate/classify.d.mts +2 -2
  47. package/bin/commands/iterate/classify.mjs +43 -25
  48. package/bin/commands/iterate/escalate.d.mts +3 -1
  49. package/bin/commands/iterate/escalate.mjs +29 -2
  50. package/bin/commands/iterate/fix-code.mjs +81 -26
  51. package/bin/commands/iterate/helpers.d.mts +0 -2
  52. package/bin/commands/iterate/helpers.mjs +6 -17
  53. package/bin/commands/iterate/index.mjs +31 -43
  54. package/bin/commands/iterate/mark-ready.d.mts +2 -0
  55. package/bin/commands/iterate/mark-ready.mjs +38 -0
  56. package/bin/commands/iterate/merge-state.d.mts +15 -0
  57. package/bin/commands/iterate/merge-state.mjs +82 -0
  58. package/bin/commands/iterate/merge.d.mts +13 -0
  59. package/bin/commands/iterate/merge.mjs +45 -0
  60. package/bin/commands/iterate/render.d.mts +2 -2
  61. package/bin/commands/iterate/render.mjs +19 -20
  62. package/bin/commands/iterate/stall.mjs +4 -2
  63. package/bin/commands/iterate/thread-mutation-routing.d.mts +9 -0
  64. package/bin/commands/iterate/thread-mutation-routing.mjs +29 -0
  65. package/bin/commands/journal/index.d.mts +5 -0
  66. package/bin/commands/journal/index.mjs +11 -2
  67. package/bin/commands/mark-files-as-viewed.d.mts +2 -1
  68. package/bin/commands/mark-files-as-viewed.mjs +5 -92
  69. package/bin/commands/poll.mjs +3 -1
  70. package/bin/commands/resolve-mutate.mjs +51 -9
  71. package/bin/commands/shepherd-journal.d.mts +1 -1
  72. package/bin/commands/shepherd-journal.mjs +2 -2
  73. package/bin/commands/suggestion-patch-item.d.mts +1 -1
  74. package/bin/commands/suggestion-patch-item.mjs +3 -11
  75. package/bin/commands/suggestion-patches.mjs +3 -3
  76. package/bin/comments/authors.d.mts +6 -0
  77. package/bin/comments/authors.mjs +5 -0
  78. package/bin/comments/marker.d.mts +6 -1
  79. package/bin/comments/marker.mjs +8 -1
  80. package/bin/comments/resolve.d.mts +4 -0
  81. package/bin/comments/thread-visibility.mjs +10 -10
  82. package/bin/comments/visible-comments.mjs +2 -1
  83. package/bin/config/load.d.mts +5 -1
  84. package/bin/config/load.mjs +34 -0
  85. package/bin/config/merge-command-args.d.mts +2 -0
  86. package/bin/config/merge-command-args.mjs +44 -0
  87. package/bin/config.json +3 -0
  88. package/bin/exit-codes.d.mts +2 -0
  89. package/bin/exit-codes.mjs +4 -0
  90. package/bin/github/batch-parse-checks.d.mts +3 -0
  91. package/bin/github/batch-parse-checks.mjs +29 -0
  92. package/bin/github/batch-parsers-rules.d.mts +3 -1
  93. package/bin/github/batch-parsers-rules.mjs +33 -0
  94. package/bin/github/batch-parsers.d.mts +2 -2
  95. package/bin/github/batch-parsers.mjs +40 -27
  96. package/bin/github/batch-raw-rules.d.mts +48 -0
  97. package/bin/github/batch-raw-types.d.mts +12 -0
  98. package/bin/github/batch-raw-types.mjs +0 -1
  99. package/bin/github/batch.mjs +3 -1
  100. package/bin/github/client.d.mts +1 -0
  101. package/bin/github/client.mjs +8 -1
  102. package/bin/github/gql/batch-pr-page.gql +6 -0
  103. package/bin/github/gql/batch-pr.gql +105 -0
  104. package/bin/github/gql/commit-check-contexts.gql +58 -0
  105. package/bin/github/gql/get-pr-body.gql +1 -0
  106. package/bin/github/gql/review-thread-comments.gql +1 -0
  107. package/bin/github/gql/suggestion-threads.gql +1 -0
  108. package/bin/github/merge-queue-checks.d.mts +4 -0
  109. package/bin/github/merge-queue-checks.mjs +48 -0
  110. package/bin/github/queries.d.mts +2 -0
  111. package/bin/github/queries.mjs +2 -0
  112. package/bin/github/suggestion-thread.mjs +1 -0
  113. package/bin/mcp/server.mjs +13 -6
  114. package/bin/pr-reference.d.mts +13 -0
  115. package/bin/pr-reference.mjs +23 -1
  116. package/bin/reporters/agent.mjs +11 -3
  117. package/bin/threads/transcript.d.mts +2 -0
  118. package/bin/threads/transcript.mjs +2 -0
  119. package/bin/types/activity.d.mts +2 -0
  120. package/bin/types/agent-thread.d.mts +1 -0
  121. package/bin/types/escalate.d.mts +35 -0
  122. package/bin/types/escalate.mjs +1 -0
  123. package/bin/types/github.d.mts +27 -0
  124. package/bin/types/github.mjs +0 -3
  125. package/bin/types/iterate.d.mts +23 -39
  126. package/bin/types/merge-action.d.mts +12 -0
  127. package/bin/types/merge-action.mjs +1 -0
  128. package/bin/types/merge-queue.d.mts +13 -0
  129. package/bin/types/merge-queue.mjs +1 -0
  130. package/bin/types/merge-requirements.d.mts +17 -0
  131. package/bin/types/report.d.mts +22 -1
  132. package/bin/types/review-thread.d.mts +1 -0
  133. package/bin/types/suggestion-patch.d.mts +1 -1
  134. package/bin/types.d.mts +3 -0
  135. package/bin/types.mjs +3 -0
  136. package/bin/util/markdown.d.mts +2 -0
  137. package/bin/util/markdown.mjs +7 -0
  138. package/package.json +1 -1
  139. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  140. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  141. package/plugins/pr-shepherd/.mcp.json +1 -1
  142. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +3 -3
  143. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +17 -15
  144. package/bin/commands/iterate/reruns.d.mts +0 -20
  145. package/bin/commands/iterate/reruns.mjs +0 -105
@@ -10,7 +10,8 @@ Iterate flags:
10
10
  --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.
11
11
  --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.
12
12
  --no-auto-mark-ready Do not convert draft PRs to ready for review.
13
- --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.
13
+ --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.
14
+ --merge Shepherd through readiness, then emit a merge or merge-queue command.
14
15
  --format text|json Output Markdown text or JSON. Default: text.
15
16
  --verbose Include verbose iterate fields.
16
17
  --help, -h Print this help and exit before GitHub, git, config, or log I/O.
@@ -23,6 +24,7 @@ Actions:
23
24
  FIX_CODE Agent action is required; follow the instructions, then continue polling.
24
25
  CANCEL Stop polling: merged/closed or ready-delay elapsed.
25
26
  ESCALATE Stop polling until a human provides direction.
27
+ MERGE Run the emitted merge/queue command, then continue monitoring.
26
28
 
27
29
  Exit codes:
28
30
  0 CANCEL (merged or ready-delay elapsed)
@@ -31,6 +33,7 @@ Exit codes:
31
33
  12 FIX_CODE
32
34
  13 ESCALATE
33
35
  14 CANCEL (closed without merging)
36
+ 15 MERGE
34
37
  A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).`;
35
38
  export const POLL_USAGE = `pr-shepherd poll
36
39
 
@@ -39,7 +42,7 @@ final tick to stdout.
39
42
  Poll exits as soon as iterate returns MARK_READY, CANCEL, or ESCALATE, or when timeout
40
43
  returns the last WAIT result. FIX_CODE starts a --debounce settle window (default 1m): poll keeps
41
44
  iterating at --interval, then runs one more tick after the window and returns that result.
42
- With --until-terminal, poll also continues through MARK_READY.
45
+ With --until-terminal or --merge, poll also continues through MARK_READY.
43
46
 
44
47
  Usage:
45
48
  pr-shepherd poll [PR] [poll-flags] [iterate-flags]
@@ -55,7 +58,8 @@ Forwarded iterate flags:
55
58
  --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.
56
59
  --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.
57
60
  --no-auto-mark-ready Do not convert draft PRs to ready for review.
58
- --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.
61
+ --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.
62
+ --merge Shepherd through readiness, then emit a merge or merge-queue command.
59
63
  --format text|json Output Markdown text or JSON. Default: text.
60
64
  --verbose Include verbose iterate fields and detailed per-tick lines.
61
65
  --help, -h Print this help and exit before GitHub, git, config, or log I/O.
@@ -65,7 +69,7 @@ for --interval/--timeout/--debounce, minutes for --ready-delay/--stall-timeout);
65
69
  an explicit unit (4.5m).
66
70
  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
71
  FIX_CODE debounce writes a remaining-seconds line to stderr. --timeout does not cut an in-flight debounce short.
68
- With --until-terminal, --timeout is ignored for WAIT ticks and polling continues until FIX_CODE, CANCEL, or ESCALATE.
72
+ With --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.
69
73
 
70
74
  Exit codes: same as iterate (the final tick's action/reason decides the code).
71
75
  0 CANCEL (merged or ready-delay elapsed)
@@ -74,6 +78,7 @@ Exit codes: same as iterate (the final tick's action/reason decides the code).
74
78
  12 FIX_CODE
75
79
  13 ESCALATE
76
80
  14 CANCEL (closed without merging)
81
+ 15 MERGE
77
82
  A command/validation/GitHub failure exits with a sysexits.h code instead (see docs/exit-codes.md).`;
78
83
  /** Public help page for the default PR polling invocation. */
79
84
  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-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 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-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 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.";
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-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.";
@@ -18,7 +18,7 @@ Commands:
18
18
  [PR] Poll until non-WAIT or timeout. This is the default command.
19
19
  iterate Run one iterate tick (single-tick alias).
20
20
  apply review Apply review-state mutations after fixes.
21
- apply files Mark changed files as viewed in GitHub.
21
+ apply files Select changed files; no viewed-state mutation is attempted.
22
22
  apply journal Append a list item to the Shepherd Journal details block of a PR body.
23
23
  build-suggestion-patches
24
24
  Convert ordered GitHub suggestion threads into patches and commit instructions.
@@ -38,7 +38,7 @@ Iterate flags:
38
38
  --ready-delay <duration> Settle window before a clean PR cancels. Bare number = minutes. Example: 15m.
39
39
  --stall-timeout <duration> Escalate repeated unchanged failures after this duration. Bare number = minutes. 0 disables.
40
40
  --no-auto-mark-ready Do not convert draft PRs to ready for review.
41
- --no-auto-cancel-actionable Do not cancel in-progress runs before actionable fixes.
41
+ --no-auto-cancel-actionable Legacy no-op; workflow runs are never cancelled.
42
42
 
43
43
  Polling flags:
44
44
  --interval <duration> Delay between WAIT ticks. Bare number = seconds. Default: 60s.
@@ -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, mark changed files as viewed, or append a PR journal item.
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
- Mark changed files as viewed in the GitHub pull request diff.
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. Use $(git rev-parse HEAD).
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
- Mark changed files as viewed in the GitHub pull request diff.
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 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).";
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, 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).";
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 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-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 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.";
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,6 +6,7 @@ interface IterateFlags {
6
6
  stallTimeoutSeconds: number;
7
7
  noAutoMarkReady: boolean;
8
8
  noAutoCancelActionable: boolean;
9
+ merge: boolean;
9
10
  }
10
11
  export declare function parseIterateFlags(extra: string[], cfg: ReturnType<typeof loadConfig>): IterateFlags;
11
12
  export {};
@@ -10,6 +10,7 @@ export function parseIterateFlags(extra, cfg) {
10
10
  const readyDelaySeconds = parseDurationToSeconds(readyDelaySuffix ?? "", cfg.watch.readyDelayMinutes * 60, MINUTE_FLAG_OPTS);
11
11
  const noAutoMarkReady = hasFlag(extra, "--no-auto-mark-ready");
12
12
  const noAutoCancelActionable = hasFlag(extra, "--no-auto-cancel-actionable");
13
+ const merge = hasFlag(extra, "--merge");
13
14
  const stallTimeoutStr = getFlag(extra, "--stall-timeout");
14
15
  const stallTimeoutSuffix = validateSecondsDurationFlag("pr-shepherd", "--stall-timeout", stallTimeoutStr, hasFlag(extra, "--stall-timeout"), MINUTE_FLAG_OPTS);
15
16
  const stallTimeoutSeconds = parseDurationToSeconds(stallTimeoutSuffix ?? "", cfg.iterate.stallTimeoutMinutes * 60, MINUTE_FLAG_OPTS);
@@ -20,5 +21,6 @@ export function parseIterateFlags(extra, cfg) {
20
21
  stallTimeoutSeconds,
21
22
  noAutoMarkReady,
22
23
  noAutoCancelActionable,
24
+ merge,
23
25
  };
24
26
  }
@@ -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 push` in
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
@@ -2,6 +2,7 @@ import { formatFixCodeResult } from "./fix-formatter.mjs";
2
2
  import { joinSections } from "../util/markdown.mjs";
3
3
  import { adaptIterateLog, buildSimpleIterateInstructions, numberInstructions, } from "./iterate-instructions.mjs";
4
4
  import { formatMergeRequirementLines } from "../merge-status/requirements-format.mjs";
5
+ import { appendMergeQueueHeader, formatMergeAction } from "./iterate-merge-formatter.mjs";
5
6
  function formatActivityLine(result) {
6
7
  const activity = result.activity ?? {
7
8
  commitCount: 0,
@@ -35,7 +36,7 @@ function formatActivityLine(result) {
35
36
  * 1. The H1 heading on line 1 contains `[<ACTION>]` — the action tag identifies
36
37
  * the output for logging and validation. Behavior is driven by `## Instructions`,
37
38
  * not by dispatching on the tag.
38
- * 2. `[FIX_CODE]` wraps the `resolve` command under `## Post-fix push` in
39
+ * 2. `[FIX_CODE]` wraps the `resolve` command under `## Post-fix actions` in
39
40
  * backticks — the skill extracts the backticked content for execution.
40
41
  * 3. Every action ends with a `## Instructions` section — numbered `1.`, `2.`, … —
41
42
  * that tells the agent exactly what to do with this output. The section is
@@ -54,10 +55,10 @@ export function formatIterateResult(result, opts) {
54
55
  if (verbose) {
55
56
  let verboseBranch = "";
56
57
  if (result.mergeStatus === "BEHIND" && result.baseBranch) {
57
- verboseBranch = ` · **branch** behind \`origin/${result.baseBranch}\``;
58
+ verboseBranch = ` · **branch** behind PR base \`${result.baseBranch}\``;
58
59
  }
59
60
  else if (result.mergeStatus === "CONFLICTS" && result.baseBranch) {
60
- verboseBranch = ` · **branch** conflicts with \`origin/${result.baseBranch}\``;
61
+ verboseBranch = ` · **branch** conflicts with PR base \`${result.baseBranch}\``;
61
62
  }
62
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}`;
63
64
  }
@@ -80,16 +81,15 @@ export function formatIterateResult(result, opts) {
80
81
  if (result.isDraft)
81
82
  segs.push(`**isDraft**`);
82
83
  if (result.mergeStatus === "BEHIND" && result.baseBranch) {
83
- segs.push(`**branch** behind \`origin/${result.baseBranch}\``);
84
+ segs.push(`**branch** behind PR base \`${result.baseBranch}\``);
84
85
  }
85
86
  else if (result.mergeStatus === "CONFLICTS" && result.baseBranch) {
86
- segs.push(`**branch** conflicts with \`origin/${result.baseBranch}\``);
87
+ segs.push(`**branch** conflicts with PR base \`${result.baseBranch}\``);
87
88
  }
88
89
  summaryLine = segs.join(" · ");
89
90
  }
90
91
  // Surface an explicit `--ready-delay` override (set only when the user passed the flag)
91
92
  // so the active settle window stays visible on every tick. Replaces the rerun command
92
- // that previously carried the suffix in the (now removed) recheck instruction.
93
93
  if (readyDelaySuffix) {
94
94
  summaryLine += ` · **ready-delay** \`${readyDelaySuffix}\` (override)`;
95
95
  }
@@ -127,6 +127,7 @@ export function formatIterateResult(result, opts) {
127
127
  const names = result.supersededNames.map((n) => "`" + n + "`").join(", ");
128
128
  headerLines.push(`**superseded** ${names}`);
129
129
  }
130
+ appendMergeQueueHeader(headerLines, result);
130
131
  const activityLine = formatActivityLine(result);
131
132
  if (activityLine)
132
133
  headerLines.push(activityLine);
@@ -144,6 +145,8 @@ export function formatIterateResult(result, opts) {
144
145
  adaptIterateLog(result.log),
145
146
  `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result))}`,
146
147
  ]);
148
+ case "merge":
149
+ return formatMergeAction(header, result);
147
150
  case "cancel": {
148
151
  const cancelHeaderLines = [`${heading} — ${result.reason}`, "", baseLine, summaryLine];
149
152
  if (result.mergeRequirements) {
@@ -1,3 +1,5 @@
1
+ import { renderMergeCommand } from "../commands/iterate/merge.mjs";
2
+ import { inlineCode } from "../util/markdown.mjs";
1
3
  export function buildSimpleIterateInstructions(result) {
2
4
  switch (result.action) {
3
5
  case "wait":
@@ -8,6 +10,19 @@ export function buildSimpleIterateInstructions(result) {
8
10
  return [
9
11
  "The CLI marked the PR ready for review. Iterate again with the same options to continue.",
10
12
  ];
13
+ case "merge": {
14
+ const instructions = [
15
+ `Run the \`${result.merge.mode === "queue" ? "merge queue" : "auto-merge"}\` command shown above exactly as printed.`,
16
+ ];
17
+ if (result.merge.mode === "queue" && result.merge.queueApiFallbackCommand) {
18
+ instructions.push(`If the gh CLI says auto-merge is disabled instead of adding the PR to the queue, run the \`queue API fallback\` command: ${inlineCode(renderMergeCommand(result.merge.queueApiFallbackCommand))}.`);
19
+ }
20
+ else if (result.merge.fallbackCommand) {
21
+ instructions.push(`Only if GitHub reports that auto-merge is unavailable, run the \`plain merge fallback\` command: ${inlineCode(renderMergeCommand(result.merge.fallbackCommand))}.`);
22
+ }
23
+ instructions.push("Then iterate again with the same options to monitor until the PR merges or needs work.");
24
+ return instructions;
25
+ }
11
26
  case "cancel":
12
27
  return ["Stop — the PR loop is complete. No further polling is needed."];
13
28
  case "escalate":
@@ -8,5 +8,4 @@ interface IterateProjectionOptions {
8
8
  * outside the state where they are meaningful.
9
9
  */
10
10
  export declare function projectIterateLean(result: IterateResult, opts?: IterateProjectionOptions): unknown;
11
- export declare function projectIterateVerbose(result: IterateResult, opts?: IterateProjectionOptions): unknown;
12
- export {};
11
+ export { projectIterateVerbose } from "./iterate-verbose.mts";
@@ -42,6 +42,7 @@ export function projectIterateLean(result, opts) {
42
42
  ...(result.baseBranch && { baseBranch: result.baseBranch }),
43
43
  ...(result.branchProtection !== null && { branchProtection: result.branchProtection }),
44
44
  ...(result.mergeRequirements && { mergeRequirements: result.mergeRequirements }),
45
+ ...(result.mergeQueue && { mergeQueue: result.mergeQueue }),
45
46
  ...(hasActivity && {
46
47
  activity: {
47
48
  commitCount: activity.commitCount,
@@ -85,6 +86,12 @@ export function projectIterateLean(result, opts) {
85
86
  log: adaptIterateLog(result.log),
86
87
  instructions: simpleInstructions(result),
87
88
  };
89
+ case "merge":
90
+ return {
91
+ ...base,
92
+ merge: result.merge,
93
+ instructions: simpleInstructions(result),
94
+ };
88
95
  case "fix_code":
89
96
  return {
90
97
  ...base,
@@ -122,6 +129,7 @@ export function projectIterateLean(result, opts) {
122
129
  ...(result.fix.protectedRuns.length > 0 && {
123
130
  protectedRuns: result.fix.protectedRuns,
124
131
  }),
132
+ ...(result.fix.requeue && { requeue: result.fix.requeue }),
125
133
  ...(result.fix.checks.length > 0 && { checks: result.fix.checks }),
126
134
  ...(result.fix.changesRequestedReviews.length > 0 && {
127
135
  changesRequestedReviews: result.fix.changesRequestedReviews,
@@ -157,6 +165,13 @@ export function projectIterateLean(result, opts) {
157
165
  result.escalate.thrashHistory.length > 0 && {
158
166
  thrashHistory: result.escalate.thrashHistory,
159
167
  }),
168
+ ...(result.escalate.mergeQueueRemoval && {
169
+ mergeQueueRemoval: result.escalate.mergeQueueRemoval,
170
+ }),
171
+ ...(result.escalate.authorization &&
172
+ result.escalate.authorization.length > 0 && {
173
+ authorization: result.escalate.authorization,
174
+ }),
160
175
  suggestion: result.escalate.suggestion,
161
176
  humanMessage: result.escalate.humanMessage,
162
177
  },
@@ -164,20 +179,4 @@ export function projectIterateLean(result, opts) {
164
179
  };
165
180
  }
166
181
  }
167
- export function projectIterateVerbose(result, opts) {
168
- const readyDelaySuffix = opts?.readyDelaySuffix;
169
- const readyDelayOverride = readyDelaySuffix ? { readyDelayOverride: readyDelaySuffix } : {};
170
- if (result.action === "fix_code") {
171
- return {
172
- ...result,
173
- ...readyDelayOverride,
174
- };
175
- }
176
- const log = "log" in result && typeof result.log === "string" ? { log: adaptIterateLog(result.log) } : {};
177
- return {
178
- ...result,
179
- ...log,
180
- ...readyDelayOverride,
181
- instructions: buildSimpleIterateInstructions(result),
182
- };
183
- }
182
+ export { projectIterateVerbose } from "./iterate-verbose.mjs";
@@ -0,0 +1,3 @@
1
+ import type { IterateResult, IterateResultMerge } from "../types.mts";
2
+ export declare function appendMergeQueueHeader(lines: string[], result: IterateResult): void;
3
+ export declare function formatMergeAction(header: string, result: IterateResultMerge): string;
@@ -0,0 +1,52 @@
1
+ import { renderMergeCommand } from "../commands/iterate/merge.mjs";
2
+ import { inlineCode, joinSections } from "../util/markdown.mjs";
3
+ import { buildSimpleIterateInstructions, numberInstructions } from "./iterate-instructions.mjs";
4
+ export function appendMergeQueueHeader(lines, result) {
5
+ const queue = result.mergeQueue;
6
+ if (!queue)
7
+ return;
8
+ const parts = [`enabled \`${queue.enabled}\``, `inQueue \`${queue.inQueue}\``];
9
+ if (queue.entry) {
10
+ parts.push(`state \`${queue.entry.state}\``, `position \`${queue.entry.position}\``);
11
+ if (queue.entry.estimatedTimeToMerge !== null) {
12
+ parts.push(`estimatedTimeToMerge \`${queue.entry.estimatedTimeToMerge}\``);
13
+ }
14
+ if (queue.entry.enqueuedAtUnix !== undefined) {
15
+ parts.push(`enqueuedAtUnix \`${queue.entry.enqueuedAtUnix}\``);
16
+ }
17
+ if (queue.entry.enqueuer)
18
+ parts.push(`enqueuer \`@${queue.entry.enqueuer}\``);
19
+ if (queue.entry.headCommitOid)
20
+ parts.push(`headCommit \`${queue.entry.headCommitOid}\``);
21
+ }
22
+ if (queue.checkCommitOid)
23
+ parts.push(`checkCommit \`${queue.checkCommitOid}\``);
24
+ if (queue.checksIncomplete)
25
+ parts.push("checks incomplete (first 100 shown)");
26
+ if (queue.headUpdatedAfterRemoval)
27
+ parts.push("head updated after removal");
28
+ lines.push(`**merge queue** ${parts.join(" · ")}`);
29
+ if (queue.autoMergeRequest) {
30
+ lines.push(`**auto-merge** method \`${queue.autoMergeRequest.mergeMethod}\` · enabledAtUnix \`${queue.autoMergeRequest.enabledAtUnix}\`${queue.autoMergeRequest.enabledBy ? ` · by \`@${queue.autoMergeRequest.enabledBy}\`` : ""}`);
31
+ }
32
+ if (queue.latestRemoval) {
33
+ const removal = queue.latestRemoval;
34
+ lines.push(`**queue removal** reason \`${removal.reason ?? "not provided"}\` · createdAtUnix \`${removal.createdAtUnix}\`${removal.actor ? ` · actor \`@${removal.actor}\`` : ""}${removal.beforeCommitOid ? ` · commit \`${removal.beforeCommitOid}\`` : ""}${removal.beforeCommitParentOids ? ` · parents \`${removal.beforeCommitParentOids.join(",")}\`` : ""}`);
35
+ }
36
+ }
37
+ export function formatMergeAction(header, result) {
38
+ const commandLines = [
39
+ `- ${result.merge.mode === "queue" ? "merge queue" : "auto-merge"}: ${inlineCode(renderMergeCommand(result.merge.command))}`,
40
+ ];
41
+ if (result.merge.fallbackCommand) {
42
+ commandLines.push(`- plain merge fallback: ${inlineCode(renderMergeCommand(result.merge.fallbackCommand))}`);
43
+ }
44
+ if (result.merge.queueApiFallbackCommand) {
45
+ commandLines.push(`- queue API fallback: ${inlineCode(renderMergeCommand(result.merge.queueApiFallbackCommand))}`);
46
+ }
47
+ return joinSections([
48
+ header,
49
+ `## Merge command\n\n${commandLines.join("\n")}`,
50
+ `## Instructions\n\n${numberInstructions(buildSimpleIterateInstructions(result))}`,
51
+ ]);
52
+ }
@@ -0,0 +1,6 @@
1
+ import type { IterateResult } from "../types.mts";
2
+ interface IterateProjectionOptions {
3
+ readyDelaySuffix?: string;
4
+ }
5
+ export declare function projectIterateVerbose(result: IterateResult, opts?: IterateProjectionOptions): unknown;
6
+ export {};
@@ -0,0 +1,14 @@
1
+ import { adaptIterateLog, buildSimpleIterateInstructions } from "./iterate-instructions.mjs";
2
+ export function projectIterateVerbose(result, opts) {
3
+ const readyDelaySuffix = opts?.readyDelaySuffix;
4
+ const readyDelayOverride = readyDelaySuffix ? { readyDelayOverride: readyDelaySuffix } : {};
5
+ if (result.action === "fix_code")
6
+ return { ...result, ...readyDelayOverride };
7
+ const log = "log" in result && typeof result.log === "string" ? { log: adaptIterateLog(result.log) } : {};
8
+ return {
9
+ ...result,
10
+ ...log,
11
+ ...readyDelayOverride,
12
+ instructions: buildSimpleIterateInstructions(result),
13
+ };
14
+ }
@@ -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, parsePrNumber } from "./args.mjs";
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 n = parsePrNumber(a);
91
- if (n !== null) {
92
- prNumber = n;
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].filter(Boolean).join(" · ");
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} — Mark files as viewed (${result.markedPaths.length} marked)`);
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);