pr-shepherd 0.33.0 → 0.35.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 (198) hide show
  1. package/.claude-plugin/plugin.json +3 -2
  2. package/.grok-plugin/marketplace.json +17 -0
  3. package/README.md +63 -41
  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/classify/apply.d.mts +18 -0
  11. package/bin/classify/apply.mjs +4 -0
  12. package/bin/classify/loader.d.mts +10 -0
  13. package/bin/classify/types.d.mts +35 -0
  14. package/bin/cli/args.d.mts +18 -0
  15. package/bin/cli/clean-formatter.d.mts +2 -0
  16. package/bin/cli/default-poll.d.mts +2 -0
  17. package/bin/cli/default-poll.mjs +2 -1
  18. package/bin/cli/duration-flag.d.mts +2 -0
  19. package/bin/cli/duration-flag.mjs +5 -4
  20. package/bin/cli/duration.d.mts +13 -0
  21. package/bin/cli/{exit-codes.mjs → duration.mjs} +0 -26
  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 +20 -18
  29. package/bin/cli/help-command-pages.d.mts +231 -0
  30. package/bin/cli/help-command-pages.mjs +116 -72
  31. package/bin/cli/help-iterate-poll-pages.d.mts +4 -0
  32. package/bin/cli/help-iterate-poll-pages.mjs +74 -0
  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 +22 -20
  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-emitter.mjs +2 -2
  40. package/bin/cli/iterate-flags.d.mts +11 -0
  41. package/bin/cli/iterate-flags.mjs +1 -1
  42. package/bin/cli/iterate-formatter.d.mts +19 -0
  43. package/bin/cli/iterate-instructions.d.mts +6 -0
  44. package/bin/cli/iterate-lean.d.mts +12 -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 +13 -25
  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 +3 -3
  56. package/bin/cli/resolve-validators.d.mts +3 -0
  57. package/bin/cli/resolve-validators.mjs +6 -5
  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 +77 -20
  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 +6 -0
  67. package/bin/commands/check.mjs +4 -2
  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 +28 -26
  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/helpers.d.mts +13 -0
  81. package/bin/commands/iterate/helpers.mjs +4 -1
  82. package/bin/commands/iterate/index.d.mts +2 -0
  83. package/bin/commands/iterate/index.mjs +10 -5
  84. package/bin/commands/iterate/render.d.mts +5 -0
  85. package/bin/commands/iterate/render.mjs +8 -6
  86. package/bin/commands/iterate/reruns.d.mts +20 -0
  87. package/bin/commands/iterate/stall.d.mts +6 -0
  88. package/bin/commands/journal/index.d.mts +14 -0
  89. package/bin/commands/journal/index.mjs +1 -0
  90. package/bin/commands/journal/transform.d.mts +22 -0
  91. package/bin/commands/log-file.d.mts +5 -0
  92. package/bin/commands/mark-files-as-viewed.d.mts +26 -0
  93. package/bin/commands/mark-files-as-viewed.mjs +8 -5
  94. package/bin/commands/poll.d.mts +10 -0
  95. package/bin/commands/poll.mjs +1 -0
  96. package/bin/commands/ready-delay.d.mts +29 -0
  97. package/bin/commands/ready-mergeability.d.mts +15 -0
  98. package/bin/commands/resolve-mutate.d.mts +4 -0
  99. package/bin/commands/resolve-mutate.mjs +3 -1
  100. package/bin/commands/resolve.d.mts +4 -0
  101. package/bin/commands/shepherd-journal.d.mts +7 -0
  102. package/bin/commands/shepherd-journal.mjs +2 -2
  103. package/bin/comments/authors.d.mts +14 -0
  104. package/bin/comments/marker.d.mts +2 -0
  105. package/bin/comments/minimize-policy.d.mts +4 -0
  106. package/bin/comments/pending-ops.d.mts +15 -0
  107. package/bin/comments/rate-limit.d.mts +18 -0
  108. package/bin/comments/resolve.d.mts +34 -0
  109. package/bin/comments/resolve.mjs +1 -0
  110. package/bin/comments/review-thread-markers.d.mts +8 -0
  111. package/bin/comments/review-visibility.d.mts +28 -0
  112. package/bin/comments/sha-poll.d.mts +2 -0
  113. package/bin/comments/thread-visibility.d.mts +11 -0
  114. package/bin/comments/visible-comments.d.mts +11 -0
  115. package/bin/config/load.d.mts +60 -0
  116. package/bin/config/load.mjs +72 -1
  117. package/bin/config.json +0 -2
  118. package/bin/execution-context.d.mts +9 -0
  119. package/bin/execution-context.mjs +19 -0
  120. package/bin/exit-codes.d.mts +51 -0
  121. package/bin/exit-codes.mjs +74 -0
  122. package/bin/github/activity.d.mts +3 -0
  123. package/bin/github/activity.mjs +7 -0
  124. package/bin/github/batch-parser-helpers.d.mts +22 -0
  125. package/bin/github/batch-parsers.d.mts +3 -0
  126. package/bin/github/batch-parsers.mjs +6 -0
  127. package/bin/github/batch-raw-types.d.mts +207 -0
  128. package/bin/github/batch-response.d.mts +4 -0
  129. package/bin/github/batch-response.mjs +8 -3
  130. package/bin/github/batch.d.mts +22 -0
  131. package/bin/github/branch-protection.d.mts +3 -0
  132. package/bin/github/check-annotations.d.mts +2 -0
  133. package/bin/github/client.d.mts +46 -0
  134. package/bin/github/client.mjs +7 -2
  135. package/bin/github/errors.d.mts +25 -0
  136. package/bin/github/errors.mjs +26 -2
  137. package/bin/github/gql/batch-pr.gql +5 -0
  138. package/bin/github/gql/review-thread-comments.gql +1 -0
  139. package/bin/github/graphql-http.d.mts +19 -0
  140. package/bin/github/graphql-response.d.mts +7 -0
  141. package/bin/github/graphql-response.mjs +5 -0
  142. package/bin/github/http-auth.d.mts +4 -0
  143. package/bin/github/http-auth.mjs +2 -1
  144. package/bin/github/http-request.d.mts +7 -0
  145. package/bin/github/http-utils.d.mts +11 -0
  146. package/bin/github/http.d.mts +5 -0
  147. package/bin/github/pagination.d.mts +45 -0
  148. package/bin/github/queries.d.mts +24 -0
  149. package/bin/github/rest-http.d.mts +2 -0
  150. package/bin/github/rest-http.mjs +19 -5
  151. package/bin/github/thread-comments.d.mts +2 -0
  152. package/bin/index.d.mts +10 -0
  153. package/bin/index.mjs +3 -2
  154. package/bin/log/log-file.d.mts +28 -0
  155. package/bin/log/session.d.mts +31 -0
  156. package/bin/log/setup.d.mts +6 -0
  157. package/bin/mcp/index.d.mts +5 -0
  158. package/bin/mcp/index.mjs +8 -0
  159. package/bin/mcp/server.d.mts +8 -0
  160. package/bin/mcp/server.mjs +157 -0
  161. package/bin/mcp-stdio.d.mts +2 -0
  162. package/bin/mcp-stdio.mjs +7 -0
  163. package/bin/merge-status/derive.d.mts +19 -0
  164. package/bin/reporters/agent.d.mts +23 -0
  165. package/bin/reporters/agent.mjs +3 -0
  166. package/bin/state/base.d.mts +1 -0
  167. package/bin/state/bot-cr-seen.d.mts +51 -0
  168. package/bin/state/bot-cr-seen.mjs +1 -1
  169. package/bin/state/fix-attempts.d.mts +27 -0
  170. package/bin/state/iterate-stall.d.mts +27 -0
  171. package/bin/state/seen-comments.d.mts +62 -0
  172. package/bin/suggestions/extract.d.mts +8 -0
  173. package/bin/suggestions/parse.d.mts +48 -0
  174. package/bin/suggestions/patch.d.mts +14 -0
  175. package/bin/threads/transcript.d.mts +14 -0
  176. package/bin/threads/transcript.mjs +4 -0
  177. package/bin/types/activity.d.mts +30 -0
  178. package/bin/types/agent-thread.d.mts +9 -0
  179. package/bin/types/check-annotations.d.mts +14 -0
  180. package/bin/types/check-classification.d.mts +19 -0
  181. package/bin/types/github.d.mts +139 -0
  182. package/bin/types/iterate.d.mts +157 -0
  183. package/bin/types/protected-run.d.mts +6 -0
  184. package/bin/types/report.d.mts +176 -0
  185. package/bin/types/review-thread.d.mts +12 -0
  186. package/bin/types.d.mts +9 -0
  187. package/bin/util/markdown.d.mts +1 -0
  188. package/bin/util/path-segment.d.mts +2 -0
  189. package/bin/util/sleep.d.mts +1 -0
  190. package/bin/util/worktree.d.mts +9 -0
  191. package/bin/util/worktree.mjs +4 -1
  192. package/package.json +51 -37
  193. package/plugins/pr-shepherd/.codex-plugin/plugin.json +3 -2
  194. package/plugins/pr-shepherd/.codex.mcp.json +8 -0
  195. package/plugins/pr-shepherd/.mcp.json +6 -0
  196. package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +5 -19
  197. package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +6 -15
  198. 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.33.0",
4
+ "version": "0.35.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
@@ -6,9 +6,9 @@ The goal is to help an agent carry a planned change to a human-reviewable PR: pa
6
6
 
7
7
  ## How It Works
8
8
 
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.
9
+ `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. The agent still decides whether a comment or CI failure requires a code change.
10
10
 
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.
11
+ 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
12
 
13
13
  Each tick returns exactly one action:
14
14
 
@@ -30,7 +30,7 @@ Example shape:
30
30
 
31
31
  ## Review threads
32
32
 
33
- ### `threadId=PRRT_kwDOSGizTs58XB1L` — `src/commands/iterate/index.mts:42` (@alice)
33
+ ### `threadId=PRRT_kwDOSGizTs58XB1L` — `src/commands/iterate/index.mts:42` (@alice · User · MEMBER)
34
34
 
35
35
  > The variable name is misleading.
36
36
 
@@ -39,16 +39,16 @@ Example shape:
39
39
  - `24697658766` — `CI › lint / typecheck / test (22.x)` [conclusion: FAILURE]
40
40
  > oxfmt
41
41
 
42
- ## Post-fix push
42
+ ## Post-fix plan
43
43
 
44
44
  - base: `main`
45
- - resolve: `pr-shepherd resolve 123 --reply-thread-ids PRRT_kwDOSGizTs58XB1L --message "$DISMISS_MESSAGE" --require-sha "$HEAD_SHA"`
45
+ - apply: reply to `PRRT_kwDOSGizTs58XB1L` after the relevant HEAD SHA is visible
46
46
 
47
47
  ## Instructions
48
48
 
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.
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, and push according to the repository's conventions.
50
50
  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`.
51
+ 3. Use `apply` with a specific reply/dismiss message and the relevant HEAD SHA.
52
52
  4. Stop this iteration.
53
53
  ```
54
54
 
@@ -64,7 +64,7 @@ This system is opinionated and works best with PRs that use required status chec
64
64
  - 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
65
  - Draft PRs can be marked ready automatically when clean; disable with `actions.autoMarkReady: false` or `--no-auto-mark-ready`.
66
66
  - 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.
67
+ - `build_suggestion_patch` turns one GitHub suggestion thread into a patch and commit metadata, but never edits the working tree or git history.
68
68
 
69
69
  ## Usage
70
70
 
@@ -84,7 +84,16 @@ Codex:
84
84
  /goal $pr-shepherd 42
85
85
  ```
86
86
 
87
- Direct CLI:
87
+ Grok:
88
+
89
+ ```text
90
+ /pr-shepherd # infer PR from current branch
91
+ /pr-shepherd 42
92
+ ```
93
+
94
+ 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.
95
+
96
+ The CLI remains useful for shell workflows. Its canonical polling form is:
88
97
 
89
98
  ```sh
90
99
  pr-shepherd 42 # poll until non-WAIT or timeout
@@ -93,48 +102,28 @@ pr-shepherd 42 --quiet-status # print only changed WAIT status snapshot
93
102
  pr-shepherd 42 --until-terminal # continue through WAIT/MARK_READY until work or terminal state
94
103
  pr-shepherd 42 --ready-delay 15m
95
104
  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
105
  ```
104
106
 
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.
107
+ ### Apply Review, File, And Journal Changes
106
108
 
107
- ### Apply One Suggestion Thread
108
-
109
- ```sh
110
- pr-shepherd commit-suggestion 42 --thread-id PRRT_abc --message "rename value for clarity"
111
- ```
112
-
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
- ```
121
-
122
- The shipped `mark-files-as-viewed` skill maps a standalone `tests` argument to `--tests`.
109
+ 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
110
 
124
111
  ### Clean Local State
125
112
 
126
113
  `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
114
 
128
115
  ```sh
129
- pr-shepherd clean current
130
- pr-shepherd clean repo
131
- pr-shepherd clean all --dry-run
132
- pr-shepherd log-file
116
+ pr-shepherd admin clean current
117
+ pr-shepherd admin clean repo
118
+ pr-shepherd admin clean all --dry-run
119
+ pr-shepherd admin log-file
133
120
  ```
134
121
 
135
122
  ## Install
136
123
 
137
- Skill and plugin install methods add the skill definitions only. Install the `pr-shepherd` CLI separately wherever the skill runs.
124
+ 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.
125
+
126
+ To register the MCP server without the plugin, or to wire a local checkout, see [docs/mcp.md](docs/mcp.md).
138
127
 
139
128
  ### Claude Code
140
129
 
@@ -164,6 +153,30 @@ codex plugin marketplace add ~/.codex/plugin-sources/pr-shepherd
164
153
 
165
154
  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
155
 
156
+ ### Grok
157
+
158
+ ```bash
159
+ grok plugin marketplace add jonathanong/pr-shepherd
160
+ grok plugin install pr-shepherd --trust
161
+ ```
162
+
163
+ Grok starts a plugin's MCP server only after the plugin is trusted. Confirm with `grok mcp list` or `/mcps`.
164
+
165
+ ### MCP server only
166
+
167
+ Any stdio MCP client can run the published binary without installing the plugin:
168
+
169
+ ```bash
170
+ claude mcp add --transport stdio --scope user pr-shepherd -- \
171
+ npx --yes --package pr-shepherd@<version> pr-shepherd-mcp
172
+ codex mcp add pr-shepherd -- \
173
+ npx --yes --package pr-shepherd@<version> pr-shepherd-mcp
174
+ grok mcp add pr-shepherd -- \
175
+ npx --yes --package pr-shepherd@<version> pr-shepherd-mcp
176
+ ```
177
+
178
+ Replace `<version>` with a published version. Full config-file examples, tool schemas, and local-checkout wiring are in [docs/mcp.md](docs/mcp.md).
179
+
167
180
  ## Configuration
168
181
 
169
182
  Create `.pr-shepherdrc.yml` in your project root or an ancestor directory.
@@ -175,7 +188,7 @@ iterate:
175
188
  fixAttemptsPerThread: 5
176
189
  stallTimeoutMinutes: 60
177
190
  minimizeApprovals: false
178
- minimizeComments: all # all | bots | users | none
191
+ minimizeComments: all # all | bots | none
179
192
  checks:
180
193
  ciTriggerEvents:
181
194
  - pull_request
@@ -218,6 +231,10 @@ TypeScript rules are loaded by the runtime's native TypeScript support; keep the
218
231
 
219
232
  Ready-to-use examples for common patterns are in [`examples/classification/`](examples/classification/).
220
233
 
234
+ ## Compatibility aliases
235
+
236
+ The legacy CLI subcommands `poll`, `resolve`, `commit-suggestion`, and `mark-files-as-viewed` remain available for compatibility but are no longer advertised as the primary integration. Prefer canonical default polling/`iterate` in a shell and the MCP `iterate`, `apply`, and `build_suggestion_patch` tools in an agent client.
237
+
221
238
  ## Requirements
222
239
 
223
240
  - Node.js >= 22.18.0, Bun, or Deno
@@ -228,6 +245,11 @@ Ready-to-use examples for common patterns are in [`examples/classification/`](ex
228
245
 
229
246
  Full reference: [docs/README.md](docs/README.md).
230
247
 
231
- ## License
248
+ ## Harness Ecosystem
249
+
250
+ This is part of the following harness ecosystem:
232
251
 
233
- [MIT](LICENSE)
252
+ - [auto-harness](https://github.com/jonathanong/auto-harness) - non-interactive agent CLI orchestration across sandboxes
253
+ - [agent-blackboard](https://github.com/jonathanong/agent-blackboard) - session-scoped telemetry for autonomous agents
254
+ - [pr-shepherd](https://github.com/jonathanong/pr-shepherd) - autonomous pull request shepherd
255
+ - [no-mistakes](https://github.com/jonathanong/no-mistakes) - deterministic AST-based codebase intelligence, test selection, and linting for agents
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[]>;
@@ -0,0 +1,18 @@
1
+ import type { BatchPrData } from "../types.mts";
2
+ import type { LoadedRule } from "./loader.mts";
3
+ export interface ClassifyIndex {
4
+ suppressedIds: Set<string>;
5
+ autoResolveIds: Set<string>;
6
+ }
7
+ export interface BatchPartition {
8
+ suppressedCommentIds: Set<string>;
9
+ suppressedThreadIds: Set<string>;
10
+ suppressedReviewSummaryIds: Set<string>;
11
+ suppressedChangesRequestedIds: Set<string>;
12
+ ruleAutoResolveCommentIds: string[];
13
+ ruleAutoResolveThreadIds: string[];
14
+ /** COMMENTED review summary IDs — minimized without surfacing to the agent. */
15
+ ruleAutoResolveReviewSummaryIds: string[];
16
+ }
17
+ export declare function buildClassifyIndex(rules: LoadedRule[], batch: BatchPrData): ClassifyIndex;
18
+ export declare function partitionBatch(index: ClassifyIndex, batch: BatchPrData): BatchPartition;
@@ -26,6 +26,7 @@ function threadToItem(t) {
26
26
  id: t.id,
27
27
  author: t.author,
28
28
  authorType: t.authorType,
29
+ ...(t.authorAssociation !== undefined && { authorAssociation: t.authorAssociation }),
29
30
  body: t.body,
30
31
  url: t.url,
31
32
  path: t.path,
@@ -37,6 +38,7 @@ function commentToItem(c) {
37
38
  id: c.id,
38
39
  author: c.author,
39
40
  authorType: c.authorType,
41
+ ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
40
42
  body: c.body,
41
43
  url: c.url,
42
44
  };
@@ -47,6 +49,7 @@ function reviewSummaryToItem(r) {
47
49
  id: r.id,
48
50
  author: r.author,
49
51
  authorType: r.authorType,
52
+ ...(r.authorAssociation !== undefined && { authorAssociation: r.authorAssociation }),
50
53
  body: r.body,
51
54
  };
52
55
  }
@@ -56,6 +59,7 @@ function changesRequestedToItem(r) {
56
59
  id: r.id,
57
60
  author: r.author,
58
61
  authorType: r.authorType,
62
+ ...(r.authorAssociation !== undefined && { authorAssociation: r.authorAssociation }),
59
63
  body: r.body,
60
64
  };
61
65
  }