pr-shepherd 0.34.0 → 0.36.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 (231) hide show
  1. package/.claude-plugin/plugin.json +3 -2
  2. package/.grok-plugin/marketplace.json +17 -0
  3. package/README.md +80 -43
  4. package/bin/api.d.mts +80 -0
  5. package/bin/api.mjs +236 -0
  6. package/bin/checks/classify.d.mts +41 -0
  7. package/bin/checks/startup-failures.d.mts +2 -0
  8. package/bin/checks/superseded.d.mts +21 -0
  9. package/bin/checks/triage.d.mts +4 -0
  10. package/bin/checks/triage.mjs +11 -3
  11. package/bin/classify/apply.d.mts +18 -0
  12. package/bin/classify/apply.mjs +4 -0
  13. package/bin/classify/loader.d.mts +10 -0
  14. package/bin/classify/types.d.mts +35 -0
  15. package/bin/cli/args.d.mts +18 -0
  16. package/bin/cli/args.mjs +1 -0
  17. package/bin/cli/clean-formatter.d.mts +2 -0
  18. package/bin/cli/default-poll.d.mts +2 -0
  19. package/bin/cli/default-poll.mjs +1 -0
  20. package/bin/cli/duration-flag.d.mts +2 -0
  21. package/bin/cli/duration.d.mts +13 -0
  22. package/bin/cli/fence.d.mts +1 -0
  23. package/bin/cli/fix-formatter-extra.d.mts +3 -0
  24. package/bin/cli/fix-formatter.d.mts +2 -0
  25. package/bin/cli/fix-formatter.mjs +6 -6
  26. package/bin/cli/formatters.d.mts +7 -0
  27. package/bin/cli/handlers.d.mts +4 -0
  28. package/bin/cli/handlers.mjs +11 -10
  29. package/bin/cli/help-command-pages.d.mts +231 -0
  30. package/bin/cli/help-command-pages.mjs +106 -1
  31. package/bin/cli/help-iterate-poll-pages.d.mts +4 -0
  32. package/bin/cli/help-iterate-poll-pages.mjs +14 -7
  33. package/bin/cli/help-log-file-page.d.mts +1 -0
  34. package/bin/cli/help-top-page.d.mts +1 -0
  35. package/bin/cli/help-top-page.mjs +16 -16
  36. package/bin/cli/help.d.mts +236 -0
  37. package/bin/cli/help.mjs +17 -0
  38. package/bin/cli/iterate-emitter.d.mts +8 -0
  39. package/bin/cli/iterate-flags.d.mts +11 -0
  40. package/bin/cli/iterate-formatter.d.mts +19 -0
  41. package/bin/cli/iterate-formatter.mjs +11 -2
  42. package/bin/cli/iterate-instructions.d.mts +6 -0
  43. package/bin/cli/iterate-lean.d.mts +12 -0
  44. package/bin/cli/iterate-lean.mjs +1 -0
  45. package/bin/cli/journal-formatter.d.mts +2 -0
  46. package/bin/cli/journal-formatter.mjs +15 -0
  47. package/bin/cli/journal-handler.d.mts +1 -0
  48. package/bin/cli/journal-handler.mjs +7 -20
  49. package/bin/cli/list-formatters.d.mts +76 -0
  50. package/bin/cli/list-formatters.mjs +9 -9
  51. package/bin/cli/mark-files-as-viewed-flags.d.mts +11 -0
  52. package/bin/cli/mark-files-as-viewed-formatter.d.mts +2 -0
  53. package/bin/cli/mutate-formatter.d.mts +2 -0
  54. package/bin/cli/poll-handler.d.mts +1 -0
  55. package/bin/cli/poll-handler.mjs +9 -2
  56. package/bin/cli/resolve-validators.d.mts +3 -0
  57. package/bin/cli/resolve-validators.mjs +3 -3
  58. package/bin/cli/runner.d.mts +7 -0
  59. package/bin/cli/suggestion-renderer.d.mts +3 -0
  60. package/bin/cli/validate-default-args.d.mts +6 -0
  61. package/bin/cli-parser.d.mts +2 -0
  62. package/bin/cli-parser.mjs +72 -16
  63. package/bin/commands/check-annotations.d.mts +5 -0
  64. package/bin/commands/check-status.d.mts +3 -0
  65. package/bin/commands/check-terminal-report.d.mts +5 -0
  66. package/bin/commands/check.d.mts +7 -0
  67. package/bin/commands/check.mjs +28 -23
  68. package/bin/commands/clean.d.mts +21 -0
  69. package/bin/commands/commit-suggestion-instruction.d.mts +8 -0
  70. package/bin/commands/commit-suggestion-instruction.mjs +3 -3
  71. package/bin/commands/commit-suggestion.d.mts +8 -0
  72. package/bin/commands/commit-suggestion.mjs +20 -13
  73. package/bin/commands/iterate/check-instructions.d.mts +16 -0
  74. package/bin/commands/iterate/check-instructions.mjs +3 -3
  75. package/bin/commands/iterate/classify.d.mts +18 -0
  76. package/bin/commands/iterate/classify.mjs +4 -4
  77. package/bin/commands/iterate/escalate.d.mts +31 -0
  78. package/bin/commands/iterate/escalate.mjs +7 -4
  79. package/bin/commands/iterate/fix-code.d.mts +25 -0
  80. package/bin/commands/iterate/fix-code.mjs +1 -1
  81. package/bin/commands/iterate/helpers.d.mts +14 -0
  82. package/bin/commands/iterate/helpers.mjs +19 -7
  83. package/bin/commands/iterate/index.d.mts +2 -0
  84. package/bin/commands/iterate/index.mjs +8 -12
  85. package/bin/commands/iterate/render.d.mts +5 -0
  86. package/bin/commands/iterate/render.mjs +8 -6
  87. package/bin/commands/iterate/reruns.d.mts +20 -0
  88. package/bin/commands/iterate/stall.d.mts +6 -0
  89. package/bin/commands/journal/index.d.mts +14 -0
  90. package/bin/commands/journal/index.mjs +1 -0
  91. package/bin/commands/journal/transform.d.mts +22 -0
  92. package/bin/commands/log-file.d.mts +5 -0
  93. package/bin/commands/mark-files-as-viewed.d.mts +26 -0
  94. package/bin/commands/mark-files-as-viewed.mjs +1 -0
  95. package/bin/commands/poll.d.mts +12 -0
  96. package/bin/commands/poll.mjs +52 -17
  97. package/bin/commands/ready-delay.d.mts +29 -0
  98. package/bin/commands/ready-delay.mjs +3 -13
  99. package/bin/commands/ready-mergeability.d.mts +15 -0
  100. package/bin/commands/resolve-mutate.d.mts +4 -0
  101. package/bin/commands/resolve-mutate.mjs +1 -0
  102. package/bin/commands/resolve.d.mts +4 -0
  103. package/bin/commands/shepherd-journal.d.mts +7 -0
  104. package/bin/commands/shepherd-journal.mjs +2 -2
  105. package/bin/comments/authors.d.mts +14 -0
  106. package/bin/comments/marker.d.mts +2 -0
  107. package/bin/comments/minimize-policy.d.mts +4 -0
  108. package/bin/comments/pending-ops.d.mts +15 -0
  109. package/bin/comments/rate-limit.d.mts +18 -0
  110. package/bin/comments/resolve.d.mts +34 -0
  111. package/bin/comments/resolve.mjs +1 -0
  112. package/bin/comments/review-thread-markers.d.mts +8 -0
  113. package/bin/comments/review-visibility.d.mts +28 -0
  114. package/bin/comments/sha-poll.d.mts +2 -0
  115. package/bin/comments/thread-visibility.d.mts +11 -0
  116. package/bin/comments/visible-comments.d.mts +11 -0
  117. package/bin/config/load.d.mts +60 -0
  118. package/bin/config/load.mjs +129 -16
  119. package/bin/config.json +0 -2
  120. package/bin/execution-context.d.mts +9 -0
  121. package/bin/execution-context.mjs +19 -0
  122. package/bin/exit-codes.d.mts +51 -0
  123. package/bin/github/activity.d.mts +3 -0
  124. package/bin/github/activity.mjs +7 -0
  125. package/bin/github/batch-page-helpers.d.mts +45 -0
  126. package/bin/github/batch-page-helpers.mjs +63 -0
  127. package/bin/github/batch-page.d.mts +14 -0
  128. package/bin/github/batch-page.mjs +62 -0
  129. package/bin/github/batch-parse-suites.d.mts +4 -0
  130. package/bin/github/batch-parse-suites.mjs +25 -0
  131. package/bin/github/batch-parser-helpers.d.mts +22 -0
  132. package/bin/github/batch-parsers-rules.d.mts +6 -0
  133. package/bin/github/batch-parsers-rules.mjs +122 -0
  134. package/bin/github/batch-parsers.d.mts +3 -0
  135. package/bin/github/batch-parsers.mjs +12 -0
  136. package/bin/github/batch-raw-rules.d.mts +59 -0
  137. package/bin/github/batch-raw-rules.mjs +1 -0
  138. package/bin/github/batch-raw-types.d.mts +216 -0
  139. package/bin/github/batch-response.d.mts +4 -0
  140. package/bin/github/batch.d.mts +24 -0
  141. package/bin/github/batch.mjs +14 -120
  142. package/bin/github/branch-protection.d.mts +3 -0
  143. package/bin/github/check-annotations.d.mts +2 -0
  144. package/bin/github/client.d.mts +46 -0
  145. package/bin/github/client.mjs +7 -2
  146. package/bin/github/errors.d.mts +25 -0
  147. package/bin/github/gql/batch-pr-page.gql +189 -0
  148. package/bin/github/gql/batch-pr.gql +79 -21
  149. package/bin/github/gql/commit-suggestion-thread.gql +40 -0
  150. package/bin/github/gql/review-thread-comments.gql +1 -0
  151. package/bin/github/graphql-http.d.mts +19 -0
  152. package/bin/github/graphql-response.d.mts +7 -0
  153. package/bin/github/http-auth.d.mts +4 -0
  154. package/bin/github/http-request.d.mts +7 -0
  155. package/bin/github/http-utils.d.mts +11 -0
  156. package/bin/github/http.d.mts +6 -0
  157. package/bin/github/http.mjs +2 -1
  158. package/bin/github/pagination.d.mts +46 -0
  159. package/bin/github/pagination.mjs +3 -2
  160. package/bin/github/queries.d.mts +28 -0
  161. package/bin/github/queries.mjs +4 -0
  162. package/bin/github/rest-http.d.mts +7 -0
  163. package/bin/github/rest-http.mjs +25 -86
  164. package/bin/github/rest-text.d.mts +1 -0
  165. package/bin/github/rest-text.mjs +88 -0
  166. package/bin/github/suggestion-thread.d.mts +9 -0
  167. package/bin/github/suggestion-thread.mjs +45 -0
  168. package/bin/github/thread-comments.d.mts +2 -0
  169. package/bin/github/thread-comments.mjs +12 -8
  170. package/bin/index.d.mts +10 -0
  171. package/bin/index.mjs +1 -1
  172. package/bin/log/log-file.d.mts +28 -0
  173. package/bin/log/session.d.mts +31 -0
  174. package/bin/log/setup.d.mts +6 -0
  175. package/bin/mcp/index.d.mts +5 -0
  176. package/bin/mcp/index.mjs +8 -0
  177. package/bin/mcp/server.d.mts +8 -0
  178. package/bin/mcp/server.mjs +157 -0
  179. package/bin/mcp-stdio.d.mts +2 -0
  180. package/bin/mcp-stdio.mjs +7 -0
  181. package/bin/merge-status/derive.d.mts +19 -0
  182. package/bin/merge-status/derive.mjs +2 -0
  183. package/bin/merge-status/requirements-format.d.mts +3 -0
  184. package/bin/merge-status/requirements-format.mjs +88 -0
  185. package/bin/merge-status/requirements.d.mts +2 -0
  186. package/bin/merge-status/requirements.mjs +51 -0
  187. package/bin/reporters/agent.d.mts +23 -0
  188. package/bin/reporters/agent.mjs +3 -0
  189. package/bin/state/base.d.mts +10 -0
  190. package/bin/state/base.mjs +23 -0
  191. package/bin/state/bot-cr-seen.d.mts +51 -0
  192. package/bin/state/bot-cr-seen.mjs +4 -14
  193. package/bin/state/fix-attempts.d.mts +27 -0
  194. package/bin/state/fix-attempts.mjs +3 -13
  195. package/bin/state/iterate-stall.d.mts +27 -0
  196. package/bin/state/iterate-stall.mjs +3 -13
  197. package/bin/state/seen-comments.d.mts +62 -0
  198. package/bin/state/seen-comments.mjs +6 -13
  199. package/bin/suggestions/extract.d.mts +8 -0
  200. package/bin/suggestions/parse.d.mts +48 -0
  201. package/bin/suggestions/patch.d.mts +14 -0
  202. package/bin/threads/transcript.d.mts +14 -0
  203. package/bin/threads/transcript.mjs +4 -0
  204. package/bin/types/activity.d.mts +30 -0
  205. package/bin/types/agent-thread.d.mts +9 -0
  206. package/bin/types/check-annotations.d.mts +14 -0
  207. package/bin/types/check-classification.d.mts +19 -0
  208. package/bin/types/github.d.mts +136 -0
  209. package/bin/types/iterate.d.mts +156 -0
  210. package/bin/types/merge-requirements.d.mts +82 -0
  211. package/bin/types/merge-requirements.mjs +2 -0
  212. package/bin/types/protected-run.d.mts +6 -0
  213. package/bin/types/report.d.mts +176 -0
  214. package/bin/types/review-thread.d.mts +12 -0
  215. package/bin/types.d.mts +10 -0
  216. package/bin/types.mjs +1 -0
  217. package/bin/util/markdown.d.mts +1 -0
  218. package/bin/util/path-segment.d.mts +4 -0
  219. package/bin/util/path-segment.mjs +2 -0
  220. package/bin/util/pool.d.mts +2 -0
  221. package/bin/util/pool.mjs +18 -0
  222. package/bin/util/sleep.d.mts +1 -0
  223. package/bin/util/worktree.d.mts +9 -0
  224. package/bin/util/worktree.mjs +4 -1
  225. package/package.json +51 -37
  226. package/plugins/pr-shepherd/.codex-plugin/plugin.json +3 -2
  227. package/plugins/pr-shepherd/.codex.mcp.json +8 -0
  228. package/plugins/pr-shepherd/.mcp.json +6 -0
  229. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +5 -19
  230. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +6 -15
  231. package/src/classify/types.mts +12 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
3
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
4
- "version": "0.34.0",
4
+ "version": "0.36.0",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
@@ -10,5 +10,6 @@
10
10
  "repository": "https://github.com/jonathanong/pr-shepherd",
11
11
  "license": "MIT",
12
12
  "keywords": ["github", "pull-request", "ci", "code-review", "automation"],
13
- "skills": "./plugins/pr-shepherd/skills/"
13
+ "skills": "./plugins/pr-shepherd/skills/",
14
+ "mcpServers": "./plugins/pr-shepherd/.mcp.json"
14
15
  }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "jonathanong",
3
+ "description": "Jonathan Ong's Grok plugins",
4
+ "owner": {
5
+ "name": "Jonathan Ong",
6
+ "email": "jonathanrichardong@gmail.com"
7
+ },
8
+ "plugins": [
9
+ {
10
+ "name": "pr-shepherd",
11
+ "description": "Autonomous PR CI monitor and review-comment resolver",
12
+ "source": { "type": "local", "path": "./plugins/pr-shepherd" },
13
+ "category": "productivity",
14
+ "homepage": "https://github.com/jonathanong/pr-shepherd"
15
+ }
16
+ ]
17
+ }
package/README.md CHANGED
@@ -2,13 +2,30 @@
2
2
 
3
3
  Autonomous PR CI monitor and review-comment resolver for agentic coding tools, including Claude Code and Codex.
4
4
 
5
- The goal is to help an agent carry a planned change to a human-reviewable PR: passing CI, no unresolved Shepherd-visible work, and a useful PR description/journal.
5
+ ## Why
6
+
7
+ An agent finishing a PR should think about code, not reconstruct GitHub state or invent a next-step policy each tick. Without Shepherd it fans out across GitHub MCP, `gh`, and GraphQL, then guesses what to do with the result.
8
+
9
+ ## What it does
10
+
11
+ 1. **Gather all context for a PR** in one invocation: review threads, comments, replies, summaries, CI, mergeability, merge requirements, first-look / outdated / edited items, and author provenance.
12
+ 2. **Provide deterministic actions for the agent**: exactly one of `WAIT`, `MARK_READY`, `FIX_CODE`, `CANCEL`, or `ESCALATE`, plus numbered `## Instructions` and explicit `apply` / `build_suggestion_patch` mutations. The agent still decides whether a comment or CI failure needs a code change. Shepherd does not classify signal vs noise and does not mutate git.
13
+
14
+ Highlights:
15
+
16
+ - Batched GraphQL reads and writes (plus REST where GraphQL cannot) so one poll replaces a tool-call fan-out. MCP `iterate` is one tick and the client owns recurrence; `--debounce` is a poll-dispatcher settle window, not an MCP tool.
17
+ - CI summaries include failed checks, and the failed job/step plus a log excerpt when triage can fetch them. Job and log details are omitted for `STARTUP_FAILURE` and `CANCELLED`; agents may still inspect logs.
18
+ - Handles GitHub comment types (comments, threads, replies) and their states, including first-look, outdated, resolved, minimized, and edited.
19
+ - `apply` batches resolve / reply / minimize / dismiss. `build_suggestion_patch` emits a unified diff in output, not a patch file, and does not mutate git.
20
+ - `BEHIND` is mergeability information, not a rebase or a guarantee that the next push is at the default-branch tip. The agent can update the branch before pushing.
21
+
22
+ Full reference: [docs/README.md](docs/README.md). Feature matrix: [docs/features.md](docs/features.md).
6
23
 
7
24
  ## How It Works
8
25
 
9
- `pr-shepherd` moves deterministic PR orchestration into a CLI. The CLI fetches GitHub state, emits raw-enough context, and prints a numbered `## Instructions` section for the calling agent to follow. The agent still decides whether a comment or CI failure requires a code change.
26
+ `pr-shepherd` moves deterministic PR orchestration into a local MCP server, with a CLI for shells and CI. Both interfaces fetch the same GitHub state, emit raw-enough context, and return a numbered plan for the calling agent to follow.
10
27
 
11
- The shipped skills invoke the default poll dispatcher (`pr-shepherd <PR>`, equivalent to `pr-shepherd poll <PR>`). Use `pr-shepherd iterate <PR>` for one single tick.
28
+ The MCP server exposes three tools: `iterate`, `apply`, and `build_suggestion_patch`. `apply` accepts ordered review mutations, file-view mutations, and journal entries. The shipped skills are thin dispatchers for those tools.
12
29
 
13
30
  Each tick returns exactly one action:
14
31
 
@@ -25,12 +42,14 @@ Example shape:
25
42
 
26
43
  # PR #123 [FIX_CODE]
27
44
 
28
- **status** `UNRESOLVED_COMMENTS` · **merge** `BLOCKED` · **state** `OPEN` · **repo** `owner/repo`
45
+ **status** `UNRESOLVED_COMMENTS` · **merge** `CLEAN` · **state** `OPEN` · **repo** `owner/repo`
29
46
  **summary** 3 passing
47
+ Approvals: None [Not Required]
48
+ Conversations Resolved: No [Not Required]
30
49
 
31
50
  ## Review threads
32
51
 
33
- ### `threadId=PRRT_kwDOSGizTs58XB1L` — `src/commands/iterate/index.mts:42` (@alice)
52
+ ### `threadId=PRRT_kwDOSGizTs58XB1L` — `src/commands/iterate/index.mts:42` (@alice · User · MEMBER)
34
53
 
35
54
  > The variable name is misleading.
36
55
 
@@ -42,17 +61,17 @@ Example shape:
42
61
  ## Post-fix push
43
62
 
44
63
  - base: `main`
45
- - resolve: `pr-shepherd resolve 123 --reply-thread-ids PRRT_kwDOSGizTs58XB1L --message "$DISMISS_MESSAGE" --require-sha "$HEAD_SHA"`
64
+ - apply review: `pr-shepherd apply review 123 --reply-thread-ids PRRT_kwDOSGizTs58XB1L --message "$DISMISS_MESSAGE" --require-sha "$HEAD_SHA"`
46
65
 
47
66
  ## Instructions
48
67
 
49
- 1. Decide for each item under `## Review threads` and `## Failing checks` whether a code change is warranted. If code changes are needed, apply edits, commit, rebase/push according to the repository's conventions, then run the `resolve:` command.
68
+ 1. Decide for each item under `## Review threads` and `## Failing checks` whether a code change is warranted. If code changes are needed, apply edits, commit, push, then run the `apply review:` command.
50
69
  2. For each failing check under `## Failing checks`: fetch logs when needed and decide whether to rerun or fix.
51
- 3. Run the `resolve:` command shown above with a specific `$DISMISS_MESSAGE` and the relevant `$HEAD_SHA`.
70
+ 3. Run the `apply review:` command shown above, substituting `$HEAD_SHA` and `$DISMISS_MESSAGE`.
52
71
  4. Stop this iteration.
53
72
  ```
54
73
 
55
- See [docs/actions.md](docs/actions.md) for the complete output contract.
74
+ See [docs/actions.md](docs/actions.md) for the complete output contract. Iterate/poll PR outcomes use exit codes `0` and `10`–`14`; command and GitHub failures use `sysexits.h` codes — [docs/exit-codes.md](docs/exit-codes.md).
56
75
 
57
76
  ## Workflow Assumptions
58
77
 
@@ -64,7 +83,7 @@ This system is opinionated and works best with PRs that use required status chec
64
83
  - Every review thread/comment/review summary is surfaced at least once, even if already outdated, resolved, or minimized; edited items re-surface through seen markers.
65
84
  - Draft PRs can be marked ready automatically when clean; disable with `actions.autoMarkReady: false` or `--no-auto-mark-ready`.
66
85
  - The CLI never performs git mutations. It emits instructions; the caller commits, rebases, pushes, and handles repository hooks.
67
- - `commit-suggestion` turns one GitHub suggestion thread into a patch and commit instructions, but still does not edit the working tree or git history.
86
+ - `build_suggestion_patch` turns one GitHub suggestion thread into a patch and commit metadata, but never edits the working tree or git history.
68
87
 
69
88
  ## Usage
70
89
 
@@ -84,57 +103,47 @@ Codex:
84
103
  /goal $pr-shepherd 42
85
104
  ```
86
105
 
87
- Direct CLI:
106
+ Grok:
107
+
108
+ ```text
109
+ /pr-shepherd # infer PR from current branch
110
+ /pr-shepherd 42
111
+ ```
112
+
113
+ MCP clients call `iterate` once per tick, then use `apply` for review/file/journal mutations and `build_suggestion_patch` for an anchored suggestion. `iterate` returns the same structured action data as the CLI, including its review mutation arguments. The client owns recurrence, so this works consistently in Codex, Claude Code, Grok, and any other stdio MCP client.
114
+
115
+ The CLI remains useful for shell workflows. Its canonical polling form is:
88
116
 
89
117
  ```sh
90
118
  pr-shepherd 42 # poll until non-WAIT or timeout
91
119
  pr-shepherd 42 --interval 60s --timeout 270s
92
120
  pr-shepherd 42 --quiet-status # print only changed WAIT status snapshots
93
121
  pr-shepherd 42 --until-terminal # continue through WAIT/MARK_READY until work or terminal state
122
+ pr-shepherd 42 --debounce 5m # wait 5m after first FIX_CODE, then return one batched tick
94
123
  pr-shepherd 42 --ready-delay 15m
95
124
  pr-shepherd iterate 42 # single tick
96
- pr-shepherd poll 42 # explicit poll command
97
- ```
98
-
99
- ### Resolve Review Items
100
-
101
- ```sh
102
- pr-shepherd resolve 42 --reply-thread-ids PRRT_abc --message "Renamed the variable for clarity." --require-sha "$(git rev-parse HEAD)"
103
- ```
104
-
105
- Use `pr-shepherd iterate 42` or `pr-shepherd 42` to fetch the next PR action. `resolve` requires at least one action flag and only applies explicit review-state mutations.
106
-
107
- ### Apply One Suggestion Thread
108
-
109
- ```sh
110
- pr-shepherd commit-suggestion 42 --thread-id PRRT_abc --message "rename value for clarity"
111
125
  ```
112
126
 
113
- The output contains a diff and numbered instructions for applying, staging, committing, resolving, and pushing.
114
-
115
- ### Mark Files As Viewed
116
-
117
- ```sh
118
- pr-shepherd mark-files-as-viewed 42 --tests
119
- pr-shepherd mark-files-as-viewed 42 src/a.ts --match '^docs/'
120
- ```
127
+ ### Apply Review, File, And Journal Changes
121
128
 
122
- The shipped `mark-files-as-viewed` skill maps a standalone `tests` argument to `--tests`.
129
+ Use `apply` with ordered operations to reply/resolve/minimize/dismiss review items, mark changed files viewed, or append an idempotent Shepherd Journal item. Use `build_suggestion_patch` to turn one review suggestion into a validated patch and commit metadata; it never changes the worktree or git history.
123
130
 
124
131
  ### Clean Local State
125
132
 
126
133
  `pr-shepherd` stores seen markers, fix-attempt counters, stall fingerprints, ready-delay markers, and logs under `$PR_SHEPHERD_STATE_DIR` (default `$TMPDIR/pr-shepherd-state`).
127
134
 
128
135
  ```sh
129
- pr-shepherd clean current
130
- pr-shepherd clean repo
131
- pr-shepherd clean all --dry-run
132
- pr-shepherd log-file
136
+ pr-shepherd admin clean current
137
+ pr-shepherd admin clean repo
138
+ pr-shepherd admin clean all --dry-run
139
+ pr-shepherd admin log-file
133
140
  ```
134
141
 
135
142
  ## Install
136
143
 
137
- Skill and plugin install methods add the skill definitions only. Install the `pr-shepherd` CLI separately wherever the skill runs.
144
+ The plugin launches the version-matched `pr-shepherd-mcp` binary from the `pr-shepherd` npm package automatically. Install the `pr-shepherd` CLI separately only when you want the shell interface.
145
+
146
+ To register the MCP server without the plugin, or to wire a local checkout, see [docs/mcp.md](docs/mcp.md).
138
147
 
139
148
  ### Claude Code
140
149
 
@@ -164,9 +173,33 @@ codex plugin marketplace add ~/.codex/plugin-sources/pr-shepherd
164
173
 
165
174
  After adding the marketplace, install/enable the `pr-shepherd` plugin from Codex. The marketplace root must contain `.agents/plugins/marketplace.json` and `plugins/pr-shepherd/`.
166
175
 
176
+ ### Grok
177
+
178
+ ```bash
179
+ grok plugin marketplace add jonathanong/pr-shepherd
180
+ grok plugin install pr-shepherd --trust
181
+ ```
182
+
183
+ Grok starts a plugin's MCP server only after the plugin is trusted. Confirm with `grok mcp list` or `/mcps`.
184
+
185
+ ### MCP server only
186
+
187
+ Any stdio MCP client can run the published binary without installing the plugin:
188
+
189
+ ```bash
190
+ claude mcp add --transport stdio --scope user pr-shepherd -- \
191
+ npx --yes --package pr-shepherd@<version> pr-shepherd-mcp
192
+ codex mcp add pr-shepherd -- \
193
+ npx --yes --package pr-shepherd@<version> pr-shepherd-mcp
194
+ grok mcp add pr-shepherd -- \
195
+ npx --yes --package pr-shepherd@<version> pr-shepherd-mcp
196
+ ```
197
+
198
+ Replace `<version>` with a published version. Full config-file examples, tool schemas, and local-checkout wiring are in [docs/mcp.md](docs/mcp.md).
199
+
167
200
  ## Configuration
168
201
 
169
- Create `.pr-shepherdrc.yml` in your project root or an ancestor directory.
202
+ Create `.pr-shepherdrc.yml` in your project root, an ancestor directory, or `$HOME`. Every file on the walk is deep-merged; closer directories override farther ones.
170
203
 
171
204
  ```yaml
172
205
  ignoreChecks:
@@ -175,7 +208,7 @@ iterate:
175
208
  fixAttemptsPerThread: 5
176
209
  stallTimeoutMinutes: 60
177
210
  minimizeApprovals: false
178
- minimizeComments: all # all | bots | users | none
211
+ minimizeComments: all # all | bots | none
179
212
  checks:
180
213
  ciTriggerEvents:
181
214
  - pull_request
@@ -218,6 +251,10 @@ TypeScript rules are loaded by the runtime's native TypeScript support; keep the
218
251
 
219
252
  Ready-to-use examples for common patterns are in [`examples/classification/`](examples/classification/).
220
253
 
254
+ ## CLI aliases
255
+
256
+ `poll`, `resolve`, `commit-suggestion`, `mark-files-as-viewed`, `journal`, `clean`, and `log-file` are CLI aliases. Prefer default polling/`iterate` in a shell and the MCP `iterate`, `apply`, and `build_suggestion_patch` tools in an agent client.
257
+
221
258
  ## Requirements
222
259
 
223
260
  - Node.js >= 22.18.0, Bun, or Deno
@@ -226,7 +263,7 @@ Ready-to-use examples for common patterns are in [`examples/classification/`](ex
226
263
 
227
264
  ## Docs
228
265
 
229
- Full reference: [docs/README.md](docs/README.md).
266
+ Full reference, grouped by the two jobs (gather context / emit actions): [docs/README.md](docs/README.md).
230
267
 
231
268
  ## Harness Ecosystem
232
269
 
package/bin/api.d.mts ADDED
@@ -0,0 +1,80 @@
1
+ import { type JournalResult } from "./commands/journal/index.mts";
2
+ import { type MarkFilesAsViewedResult } from "./commands/mark-files-as-viewed.mts";
3
+ import type { ResolveResult } from "./comments/resolve.mts";
4
+ import type { CommitSuggestionResult, IterateCommandOptions, IterateResult } from "./types.mts";
5
+ export interface CreatePrShepherdOptions {
6
+ /** Working directory used for git, config, and classification-rule lookups. */
7
+ cwd?: string;
8
+ }
9
+ /** A positive PR number or canonical GitHub pull-request URL. */
10
+ export type PrReference = number | string;
11
+ export type IterateInput = Omit<IterateCommandOptions, "format" | "prNumber"> & {
12
+ pr?: PrReference;
13
+ };
14
+ export interface ReviewMutationsOperation {
15
+ type: "review_mutations";
16
+ resolveThreadIds?: string[];
17
+ replyThreadIds?: string[];
18
+ minimizeCommentIds?: string[];
19
+ dismissReviewIds?: string[];
20
+ /** Required when replying to a thread or dismissing a review. */
21
+ message?: string;
22
+ requireSha?: string;
23
+ }
24
+ export interface MarkFilesViewedOperation {
25
+ type: "mark_files_viewed";
26
+ files?: string[];
27
+ tests?: boolean;
28
+ matchPatterns?: string[];
29
+ }
30
+ export interface AppendJournalOperation {
31
+ type: "append_journal";
32
+ item: string;
33
+ dryRun?: boolean;
34
+ }
35
+ /** Mutations are run in this exact list order after every operation is validated. */
36
+ export type ApplyOperation = ReviewMutationsOperation | MarkFilesViewedOperation | AppendJournalOperation;
37
+ export interface ApplyInput {
38
+ /** PR shared by every operation in this ordered apply request. */
39
+ pr?: PrReference;
40
+ operations: ApplyOperation[];
41
+ }
42
+ export type ApplyOperationResult = {
43
+ type: "review_mutations";
44
+ result: ResolveResult;
45
+ } | {
46
+ type: "mark_files_viewed";
47
+ result: MarkFilesAsViewedResult;
48
+ } | {
49
+ type: "append_journal";
50
+ result: JournalResult;
51
+ };
52
+ export interface ApplyResult {
53
+ operations: ApplyOperationResult[];
54
+ }
55
+ export interface BuildSuggestionPatchInput {
56
+ pr?: PrReference;
57
+ threadId: string;
58
+ message: string;
59
+ description?: string;
60
+ }
61
+ export interface PrShepherd {
62
+ iterate(input?: IterateInput): Promise<IterateResult>;
63
+ apply(input: ApplyInput): Promise<ApplyResult>;
64
+ buildSuggestionPatch(input: BuildSuggestionPatchInput): Promise<CommitSuggestionResult>;
65
+ }
66
+ /** Raised before any API mutation when an input cannot be validated. */
67
+ export declare class PrShepherdValidationError extends Error {
68
+ constructor(message: string);
69
+ }
70
+ /** Raised when a later ordered apply operation fails after earlier operations completed. */
71
+ export declare class PartialApplyError extends Error {
72
+ readonly failedIndex: number;
73
+ readonly completed: ApplyOperationResult[];
74
+ constructor(failedIndex: number, completed: ApplyOperationResult[], cause: unknown);
75
+ }
76
+ /**
77
+ * Creates the programmatic API. The returned object intentionally exposes only
78
+ * read/plan, ordered apply, and suggestion-patch operations.
79
+ */
80
+ export declare function createPrShepherd(options?: CreatePrShepherdOptions): PrShepherd;
package/bin/api.mjs ADDED
@@ -0,0 +1,236 @@
1
+ /* eslint-disable max-lines */
2
+ import { resolve } from "node:path";
3
+ import { runCommitSuggestion } from "./commands/commit-suggestion.mjs";
4
+ import { runIterate } from "./commands/iterate/index.mjs";
5
+ import { runJournal } from "./commands/journal/index.mjs";
6
+ import { validateJournalItem } from "./commands/journal/transform.mjs";
7
+ import { runMarkFilesAsViewed, } from "./commands/mark-files-as-viewed.mjs";
8
+ import { runResolveMutate } from "./commands/resolve-mutate.mjs";
9
+ import { runWithExecutionCwd } from "./execution-context.mjs";
10
+ import { getRepoInfo } from "./github/client.mjs";
11
+ /** Raised before any API mutation when an input cannot be validated. */
12
+ export class PrShepherdValidationError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "PrShepherdValidationError";
16
+ }
17
+ }
18
+ /** Raised when a later ordered apply operation fails after earlier operations completed. */
19
+ export class PartialApplyError extends Error {
20
+ failedIndex;
21
+ completed;
22
+ constructor(failedIndex, completed, cause) {
23
+ const message = cause instanceof Error ? cause.message : String(cause);
24
+ super(`apply operation ${failedIndex} failed: ${message}`, { cause });
25
+ this.name = "PartialApplyError";
26
+ this.failedIndex = failedIndex;
27
+ this.completed = completed;
28
+ }
29
+ }
30
+ /**
31
+ * Creates the programmatic API. The returned object intentionally exposes only
32
+ * read/plan, ordered apply, and suggestion-patch operations.
33
+ */
34
+ export function createPrShepherd(options = {}) {
35
+ const cwd = options.cwd === undefined ? undefined : resolve(options.cwd);
36
+ return Object.freeze({
37
+ iterate(input = {}) {
38
+ const { pr: _pr, ...options } = input;
39
+ return runWithExecutionCwd(cwd, async () => {
40
+ const prNumber = await resolvePrReference(input.pr);
41
+ return runIterate({ ...options, prNumber, format: "json" });
42
+ });
43
+ },
44
+ apply(input) {
45
+ return runWithExecutionCwd(cwd, async () => {
46
+ validateApplyInput(input);
47
+ const prNumber = await resolvePrReference(input.pr);
48
+ const results = [];
49
+ for (let index = 0; index < input.operations.length; index += 1) {
50
+ const operation = input.operations[index];
51
+ try {
52
+ switch (operation.type) {
53
+ case "review_mutations": {
54
+ const { type: _type, message, ...options } = operation;
55
+ const result = await runResolveMutate({
56
+ ...options,
57
+ prNumber,
58
+ dismissMessage: message,
59
+ format: "json",
60
+ });
61
+ results.push({ type: operation.type, result });
62
+ break;
63
+ }
64
+ case "mark_files_viewed": {
65
+ const result = await runMarkFilesAsViewed({
66
+ prNumber,
67
+ files: operation.files ?? [],
68
+ tests: operation.tests,
69
+ matchPatterns: operation.matchPatterns,
70
+ format: "json",
71
+ });
72
+ results.push({ type: operation.type, result });
73
+ break;
74
+ }
75
+ case "append_journal": {
76
+ const result = await runJournal({
77
+ prNumber,
78
+ rawItem: operation.item,
79
+ dryRun: operation.dryRun ?? false,
80
+ });
81
+ results.push({ type: operation.type, result });
82
+ break;
83
+ }
84
+ }
85
+ }
86
+ catch (error) {
87
+ if (results.length === 0)
88
+ throw error;
89
+ throw new PartialApplyError(index, results, error);
90
+ }
91
+ }
92
+ return { operations: results };
93
+ });
94
+ },
95
+ buildSuggestionPatch(input) {
96
+ validateSuggestionPatchInput(input);
97
+ const { pr: _pr, ...options } = input;
98
+ return runWithExecutionCwd(cwd, async () => {
99
+ const prNumber = await resolvePrReference(input.pr);
100
+ return runCommitSuggestion({ ...options, prNumber, format: "json" });
101
+ });
102
+ },
103
+ });
104
+ }
105
+ function validateApplyInput(input) {
106
+ if (!input || !Array.isArray(input.operations) || input.operations.length === 0) {
107
+ throw new PrShepherdValidationError("apply requires a non-empty operations array");
108
+ }
109
+ for (const operation of input.operations)
110
+ validateOperation(operation);
111
+ parsePrReference(input.pr);
112
+ }
113
+ function validateOperation(operation) {
114
+ if (!operation || typeof operation !== "object") {
115
+ throw new PrShepherdValidationError("apply operation must be an object");
116
+ }
117
+ switch (operation.type) {
118
+ case "review_mutations":
119
+ validateReviewMutations(operation);
120
+ return;
121
+ case "mark_files_viewed":
122
+ validateMarkFilesViewed(operation);
123
+ return;
124
+ case "append_journal":
125
+ if (typeof operation.item !== "string") {
126
+ throw new PrShepherdValidationError("append_journal.item must be a string");
127
+ }
128
+ if (operation.dryRun !== undefined && typeof operation.dryRun !== "boolean") {
129
+ throw new PrShepherdValidationError("append_journal.dryRun must be a boolean");
130
+ }
131
+ {
132
+ const validation = validateJournalItem(operation.item);
133
+ if (!validation.ok)
134
+ throw new PrShepherdValidationError(validation.error);
135
+ }
136
+ return;
137
+ default:
138
+ throw new PrShepherdValidationError(`Unsupported apply operation: ${JSON.stringify(operation.type)}`);
139
+ }
140
+ }
141
+ function validateReviewMutations(operation) {
142
+ const ids = [
143
+ operation.resolveThreadIds,
144
+ operation.replyThreadIds,
145
+ operation.minimizeCommentIds,
146
+ operation.dismissReviewIds,
147
+ ];
148
+ if (!ids.some((value) => value !== undefined && value.length > 0)) {
149
+ throw new PrShepherdValidationError("review_mutations requires at least one mutation ID");
150
+ }
151
+ for (const value of ids)
152
+ validateStringArray(value, "review mutation IDs");
153
+ const needsMessage = (operation.replyThreadIds?.length ?? 0) > 0 || (operation.dismissReviewIds?.length ?? 0) > 0;
154
+ if (needsMessage && (!operation.message || operation.message.trim() === "")) {
155
+ throw new PrShepherdValidationError("review_mutations.message is required for replies or review dismissals");
156
+ }
157
+ if (operation.message !== undefined && typeof operation.message !== "string") {
158
+ throw new PrShepherdValidationError("review_mutations.message must be a string");
159
+ }
160
+ if (operation.requireSha !== undefined && !/^[0-9a-f]{40}$/.test(operation.requireSha)) {
161
+ throw new PrShepherdValidationError("review_mutations.requireSha must be a full 40-character lowercase hex SHA");
162
+ }
163
+ }
164
+ function validateMarkFilesViewed(operation) {
165
+ validateStringArray(operation.files, "mark_files_viewed.files");
166
+ validateStringArray(operation.matchPatterns, "mark_files_viewed.matchPatterns");
167
+ if (operation.tests !== undefined && typeof operation.tests !== "boolean") {
168
+ throw new PrShepherdValidationError("mark_files_viewed.tests must be a boolean");
169
+ }
170
+ if ((operation.files?.length ?? 0) === 0 &&
171
+ (operation.matchPatterns?.length ?? 0) === 0 &&
172
+ operation.tests !== true) {
173
+ throw new PrShepherdValidationError("mark_files_viewed requires files, matchPatterns, or tests: true");
174
+ }
175
+ for (const pattern of operation.matchPatterns ?? []) {
176
+ try {
177
+ new RegExp(pattern, "i");
178
+ }
179
+ catch (error) {
180
+ const message = error instanceof Error ? error.message : String(error);
181
+ throw new PrShepherdValidationError(`Invalid mark_files_viewed match pattern ${JSON.stringify(pattern)}: ${message}`);
182
+ }
183
+ }
184
+ }
185
+ function validateSuggestionPatchInput(input) {
186
+ if (!input || typeof input.threadId !== "string" || input.threadId === "") {
187
+ throw new PrShepherdValidationError("buildSuggestionPatch.threadId is required");
188
+ }
189
+ if (typeof input.message !== "string" || input.message.trim() === "") {
190
+ throw new PrShepherdValidationError("buildSuggestionPatch.message is required and must be non-empty");
191
+ }
192
+ if (input.description !== undefined && typeof input.description !== "string") {
193
+ throw new PrShepherdValidationError("buildSuggestionPatch.description must be a string");
194
+ }
195
+ parsePrReference(input.pr);
196
+ }
197
+ function parsePrReference(pr) {
198
+ if (pr === undefined)
199
+ return {};
200
+ if (typeof pr === "number" && Number.isInteger(pr) && pr > 0)
201
+ return { number: pr };
202
+ if (typeof pr === "string") {
203
+ try {
204
+ const url = new URL(pr);
205
+ const parts = url.pathname.split("/").filter(Boolean);
206
+ if ((url.protocol === "https:" || url.protocol === "http:") &&
207
+ (url.hostname === "github.com" || url.hostname === "www.github.com") &&
208
+ parts.length === 4 &&
209
+ parts[2] === "pull" &&
210
+ /^[1-9][0-9]*$/.test(parts[3])) {
211
+ return { number: Number(parts[3]), repository: `${parts[0]}/${parts[1]}` };
212
+ }
213
+ }
214
+ catch {
215
+ // Construct the uniform public validation error below.
216
+ }
217
+ }
218
+ throw new PrShepherdValidationError("pr must be a positive number or a GitHub pull-request URL");
219
+ }
220
+ async function resolvePrReference(pr) {
221
+ const parsed = parsePrReference(pr);
222
+ if (parsed.repository !== undefined) {
223
+ const repo = await getRepoInfo();
224
+ const currentRepository = `${repo.owner}/${repo.name}`;
225
+ if (parsed.repository.toLowerCase() !== currentRepository.toLowerCase()) {
226
+ throw new PrShepherdValidationError(`PR URL repository ${parsed.repository} does not match the configured repository ${currentRepository}`);
227
+ }
228
+ }
229
+ return parsed.number;
230
+ }
231
+ function validateStringArray(value, label) {
232
+ if (value !== undefined &&
233
+ (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item === ""))) {
234
+ throw new PrShepherdValidationError(`${label} must be an array of non-empty strings`);
235
+ }
236
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Classifies check runs into shepherd categories and filters out irrelevant ones.
3
+ *
4
+ * Rules:
5
+ * 1. Skip checks whose workflow event is NOT `pull_request` or `pull_request_target`.
6
+ * Push-triggered, merge-queue, schedule, and workflow-dispatch runs are irrelevant
7
+ * to PR readiness.
8
+ * 2. Drop checks with `conclusion == SKIPPED` or `conclusion == NEUTRAL` from the
9
+ * pass/fail tally. Report them as "skipped" for transparency but don't block on them.
10
+ * 3. Reclassify `CANCELLED` checks as "superseded" (non-blocking) when a newer run of
11
+ * the same workflow exists on the same commit — this is GitHub's concurrency-group
12
+ * eviction behavior, not a real failure. GitHub branch protection itself resolves
13
+ * required status checks by latest-run-per-name and merges past these; mirroring
14
+ * that here keeps shepherd's verdict aligned with what GitHub will actually allow.
15
+ */
16
+ import type { CheckRun, ClassifiedCheck } from "../types.mts";
17
+ /**
18
+ * Classify a list of raw check runs into shepherd categories.
19
+ *
20
+ * @param checks Raw check runs from the batch query.
21
+ * @returns Classified checks. "filtered" items were excluded from the tally.
22
+ */
23
+ export declare function classifyChecks(checks: CheckRun[]): ClassifiedCheck[];
24
+ export interface CiVerdict {
25
+ /** True when all relevant (non-filtered, non-skipped, non-ignored) checks passed. */
26
+ allPassed: boolean;
27
+ /** True when at least one relevant (non-filtered, non-skipped, non-ignored) check exists. */
28
+ hasChecks: boolean;
29
+ /** True when at least one check is still running/queued. */
30
+ anyInProgress: boolean;
31
+ /** True when at least one check failed. */
32
+ anyFailing: boolean;
33
+ /** Names of checks that were filtered out (triggered by non-PR events). */
34
+ filteredNames: string[];
35
+ /** Names of checks suppressed by the user's ignoreChecks config. */
36
+ ignoredNames: string[];
37
+ /** Names of CANCELLED checks superseded by a newer run of the same workflow (concurrency-group eviction). */
38
+ supersededNames: string[];
39
+ }
40
+ /** Compute a high-level CI verdict from a list of classified checks. */
41
+ export declare function getCiVerdict(classified: ClassifiedCheck[]): CiVerdict;
@@ -0,0 +1,2 @@
1
+ import type { CheckRun } from "../types.mts";
2
+ export declare function mergeStartupFailureChecks(checks: CheckRun[], startupFailureChecks: CheckRun[]): CheckRun[];
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Detects check runs that are `CANCELLED` because a newer run of the *same workflow*
3
+ * superseded them on the same commit (concurrency-group eviction), rather than a genuine
4
+ * cancellation. Split out of classify.mts to stay under the file-length cap.
5
+ */
6
+ import type { CheckRun } from "../types.mts";
7
+ /**
8
+ * Grouping key is `workflowId ?? workflowName` — the numeric GitHub Actions workflow database
9
+ * ID when available, falling back to the display name. Checks with neither a workflow identity
10
+ * nor a numeric `runId` (status contexts, startup-failure synthetics) never participate: they
11
+ * can neither be marked superseded nor count as evidence of a newer run.
12
+ *
13
+ * A check is superseded iff its own conclusion is `CANCELLED` and some other check sharing its
14
+ * workflow key has a strictly greater `runId`. The newest run for a workflow is therefore never
15
+ * superseded, even if it is itself cancelled — that case stays "failing" so the agent can decide
16
+ * whether to rerun it.
17
+ *
18
+ * @returns Indices into `checks` (not object identities, since check-run objects are not
19
+ * deduplicated by reference elsewhere) that should be reclassified as "superseded".
20
+ */
21
+ export declare function buildSupersededIndices(checks: CheckRun[]): Set<number>;
@@ -0,0 +1,4 @@
1
+ import type { CheckRun, ClassifiedCheck, TriagedCheck } from "../types.mts";
2
+ import type { RepoInfo } from "../github/client.mts";
3
+ export declare function triageFailingChecks(failingChecks: ClassifiedCheck[], repo: RepoInfo): Promise<TriagedCheck[]>;
4
+ export declare function fetchStartupFailureChecks(repo: RepoInfo, headSha: string, prNumber: number): Promise<CheckRun[]>;