pr-shepherd 0.59.3 → 0.60.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 (54) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/api.d.mts +1 -1
  3. package/bin/cli/edited-comments-format.d.mts +14 -0
  4. package/bin/cli/edited-comments-format.mjs +39 -0
  5. package/bin/cli/fix-formatter.mjs +5 -2
  6. package/bin/cli/iterate-activity-formatter.d.mts +1 -1
  7. package/bin/cli/iterate-activity-formatter.mjs +4 -5
  8. package/bin/cli/iterate-check-names.d.mts +5 -0
  9. package/bin/cli/iterate-check-names.mjs +17 -0
  10. package/bin/cli/iterate-formatter.mjs +15 -19
  11. package/bin/cli/iterate-lean.mjs +19 -11
  12. package/bin/cli/iterate-verbose.mjs +7 -2
  13. package/bin/cli/list-formatters.d.mts +0 -3
  14. package/bin/cli/list-formatters.mjs +1 -5
  15. package/bin/commands/check-review-budget.d.mts +39 -0
  16. package/bin/commands/check-review-budget.mjs +78 -0
  17. package/bin/commands/check-wait-detectors.d.mts +6 -1
  18. package/bin/commands/check-wait-detectors.mjs +2 -1
  19. package/bin/commands/check.d.mts +2 -0
  20. package/bin/commands/check.mjs +46 -25
  21. package/bin/commands/iterate/check-instructions.d.mts +5 -0
  22. package/bin/commands/iterate/check-instructions.mjs +4 -0
  23. package/bin/commands/iterate/classify.d.mts +1 -1
  24. package/bin/commands/iterate/edited-comment-markers.d.mts +3 -0
  25. package/bin/commands/iterate/edited-comment-markers.mjs +10 -0
  26. package/bin/commands/iterate/fix-code.mjs +17 -1
  27. package/bin/commands/iterate/helpers.mjs +9 -5
  28. package/bin/commands/iterate/index.mjs +44 -17
  29. package/bin/commands/iterate/render.mjs +1 -4
  30. package/bin/commands/poll-rate-limit-cancel.mjs +1 -1
  31. package/bin/commands/poll-seen-options.d.mts +8 -0
  32. package/bin/commands/poll-seen-options.mjs +7 -0
  33. package/bin/commands/poll.mjs +7 -5
  34. package/bin/comments/review-output-budget.d.mts +32 -0
  35. package/bin/comments/review-output-budget.mjs +55 -0
  36. package/bin/comments/visible-comments.d.mts +3 -1
  37. package/bin/comments/visible-comments.mjs +10 -5
  38. package/bin/config/load.d.mts +6 -0
  39. package/bin/config/load.mjs +7 -0
  40. package/bin/config.json +2 -1
  41. package/bin/github/poll-summary-fingerprint.mjs +5 -1
  42. package/bin/github/poll-summary-review.mjs +2 -1
  43. package/bin/reporters/agent.d.mts +1 -3
  44. package/bin/reporters/agent.mjs +0 -1
  45. package/bin/types/escalate.d.mts +9 -0
  46. package/bin/types/iterate.d.mts +19 -1
  47. package/bin/types/report.d.mts +23 -4
  48. package/bin/util/cap-list.d.mts +7 -0
  49. package/bin/util/cap-list.mjs +13 -0
  50. package/package.json +2 -2
  51. package/plugins/pr-shepherd/.claude-plugin/plugin.json +1 -1
  52. package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
  53. package/plugins/pr-shepherd/.codex.mcp.json +1 -1
  54. package/plugins/pr-shepherd/.mcp.json +1 -1
@@ -74,6 +74,12 @@ export interface PrShepherdConfig {
74
74
  * (default) omits the hint entirely; the CLI never prescribes rebase/merge mechanics itself.
75
75
  */
76
76
  behindBaseHint: string;
77
+ /**
78
+ * Per-tick budget for review-item bodies in `fix_code` output, in characters. Over budget,
79
+ * iterate shows a priority-ordered subset and holds the rest (unmarked) for later ticks.
80
+ * `0` disables the cap.
81
+ */
82
+ reviewOutputMaxChars: number;
77
83
  /**
78
84
  * When to resolve other-human inline threads after Shepherd replies. Own and bot threads
79
85
  * always reply-and-resolve. `none` (default) keeps other humans reply-only; `outdated`
@@ -92,6 +92,11 @@ function parseInstructionStyle(value) {
92
92
  return value;
93
93
  throw new Error(`Invalid config: iterate.instructions must be "playbook" or "inline", got ${JSON.stringify(value)}`);
94
94
  }
95
+ function parseReviewOutputMaxChars(value) {
96
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0)
97
+ return value;
98
+ throw new Error(`Invalid config: iterate.reviewOutputMaxChars must be a non-negative integer, got ${JSON.stringify(value)}`);
99
+ }
95
100
  function parseCliCommand(value) {
96
101
  if (!Array.isArray(value) ||
97
102
  value.length === 0 ||
@@ -299,6 +304,7 @@ const KNOWN_NESTED_KEYS = {
299
304
  "behindBaseHint",
300
305
  "resolveOtherHumanThreads",
301
306
  "instructions",
307
+ "reviewOutputMaxChars",
302
308
  ]),
303
309
  poll: new Set([
304
310
  "intervalSeconds",
@@ -404,6 +410,7 @@ function parseConfig(value, normalizeMergeArgs = true) {
404
410
  config.iterate.minimizeComments = parseMinimizeCommentsPolicy(config.iterate.minimizeComments);
405
411
  config.iterate.resolveOtherHumanThreads = parseResolveOtherHumanThreads(config.iterate.resolveOtherHumanThreads);
406
412
  config.iterate.instructions = parseInstructionStyle(config.iterate.instructions);
413
+ config.iterate.reviewOutputMaxChars = parseReviewOutputMaxChars(config.iterate.reviewOutputMaxChars);
407
414
  config.checks.ignoreLogLines = parseIgnoreLogLines(config.checks.ignoreLogLines);
408
415
  config.standardRules = parseStandardRules(config.classify);
409
416
  return config;
package/bin/config.json CHANGED
@@ -23,7 +23,8 @@
23
23
  "minimizeComments": "all",
24
24
  "behindBaseHint": "",
25
25
  "resolveOtherHumanThreads": "none",
26
- "instructions": "inline"
26
+ "instructions": "inline",
27
+ "reviewOutputMaxChars": 50000
27
28
  },
28
29
  "poll": {
29
30
  "intervalSeconds": 60,
@@ -29,7 +29,11 @@ export function fingerprintRawSummaryPr(summary) {
29
29
  // Omitted bodies cannot participate in the hash. Bind truncated evidence
30
30
  // to the PR revision instead, so updates require a fresh full review poll.
31
31
  ...(reviewTruncated && { reviewUpdatedAt: raw.updatedAt }),
32
- comments: hideBodies(raw.comments),
32
+ // A PR comment edit is not readiness evidence; its ID still binds new comments.
33
+ comments: {
34
+ ...raw.comments,
35
+ nodes: raw.comments.nodes.map(({ body: _body, ...node }) => node),
36
+ },
33
37
  reviews: hideBodies(raw.reviews),
34
38
  reviewThreads: {
35
39
  ...raw.reviewThreads,
@@ -19,7 +19,8 @@ export async function summarizePollSummaryReview(raw, seen, viewerCanAdminister)
19
19
  raw.reviewThreads.pageInfo.hasPreviousPage;
20
20
  for (const comment of raw.comments.nodes) {
21
21
  if (!isSuppressed(rules, "pr-comment", comment, comment.id, undefined, comment.body, comment.isMinimized) &&
22
- classifyItem(comment.id, comment.body, seen) !== "unchanged")
22
+ // An edited PR comment is shown once by iterate but never blocks readiness.
23
+ classifyItem(comment.id, comment.body, seen) === "new")
23
24
  actionable += 1;
24
25
  }
25
26
  const latestStateByAuthor = new Map((raw.latestReviews?.nodes ?? []).flatMap((review) => review.author ? [[review.author.login.toLowerCase(), review.state]] : []));
@@ -10,9 +10,7 @@
10
10
  */
11
11
  import type { ReviewThread, PrComment, TriagedCheck, ClassifiedCheck, AgentThread, AgentComment, AgentCheck, AgentStalledCheck } from "../types.mts";
12
12
  export declare function toAgentThread(t: ReviewThread): AgentThread;
13
- export declare function toAgentComment(c: PrComment & {
14
- edited?: boolean;
15
- }): AgentComment;
13
+ export declare function toAgentComment(c: PrComment): AgentComment;
16
14
  export declare function toAgentCheck(c: TriagedCheck): AgentCheck;
17
15
  export declare function toAgentStalledCheck(c: ClassifiedCheck, nowSeconds: number): AgentStalledCheck;
18
16
  /**
@@ -51,7 +51,6 @@ export function toAgentComment(c) {
51
51
  ...(c.authorAssociation !== undefined && { authorAssociation: c.authorAssociation }),
52
52
  body: c.body,
53
53
  url: c.url,
54
- ...(c.edited === true && { edited: true }),
55
54
  };
56
55
  }
57
56
  export function toAgentCheck(c) {
@@ -31,6 +31,15 @@ export interface EscalateDetails {
31
31
  threadId: string;
32
32
  attempts: number;
33
33
  }>;
34
+ /**
35
+ * Set only when `iterate.reviewOutputMaxChars` held review items back on the tick that
36
+ * escalated: `shown` of `total` review items appear here. The held items stay unmarked, so the
37
+ * next run shows them.
38
+ */
39
+ reviewItems?: {
40
+ shown: number;
41
+ total: number;
42
+ };
34
43
  /** Review mutations generated for this tick, retained so an escalation cannot strand them. */
35
44
  pendingReviewCommands?: {
36
45
  resolveOnlyCommand?: ResolveCommand;
@@ -1,4 +1,4 @@
1
- import type { AgentThread, AgentComment, AgentCheck, GlobalOptions, RelevantCheck, ShepherdStatus, FirstLookThread, FirstLookComment, RuleAutoResolveReport } from "./report.mts";
1
+ import type { AgentThread, AgentComment, AgentCheck, GlobalOptions, RelevantCheck, ShepherdStatus, FirstLookThread, FirstLookComment, EditedComment, RuleAutoResolveReport } from "./report.mts";
2
2
  import type { ActiveCheck, PrActivitySummary } from "./activity.mts";
3
3
  import type { BranchProtection, MergeStateStatus, MergedBasePullRequest, Review, ReviewDecision, ReviewThread, ShepherdMergeStatus } from "./github.mts";
4
4
  import type { MergeRequirements } from "./merge-requirements.mts";
@@ -12,7 +12,10 @@ export interface IterateResultSummary {
12
12
  passing: number;
13
13
  skipped: number;
14
14
  filtered: number;
15
+ /** Non-completed checks GitHub reports as `IN_PROGRESS`. */
15
16
  inProgress: number;
17
+ /** Non-completed checks GitHub reports as `QUEUED`, `WAITING`, `PENDING`, or `REQUESTED`. */
18
+ queued: number;
16
19
  superseded: number;
17
20
  }
18
21
  export interface IterateResultBase {
@@ -70,6 +73,11 @@ export interface IterateResultBase {
70
73
  nextCheck?: NextCheck;
71
74
  /** Classification-rule resolve/minimize applied during this fetch. Omitted when nothing happened. */
72
75
  ruleAutoResolve?: RuleAutoResolveReport;
76
+ /**
77
+ * PR comments edited since Shepherd last showed them. Edits never block readiness, so they are
78
+ * held during `wait`/`ready` and shown once on the next other action. Omitted when empty.
79
+ */
80
+ editedComments?: EditedComment[];
73
81
  }
74
82
  interface IterateResultWait extends IterateResultBase {
75
83
  action: "wait";
@@ -143,6 +151,11 @@ interface FixRebaseAndPush {
143
151
  firstLookThreads: FirstLookThread[];
144
152
  /** First-look comments — previously hidden, surfaced for acknowledgment only. */
145
153
  firstLookComments: FirstLookComment[];
154
+ /** Set only when `iterate.reviewOutputMaxChars` held review items back: `shown` of `total`. */
155
+ reviewItems?: {
156
+ shown: number;
157
+ total: number;
158
+ };
146
159
  }
147
160
  export interface IterateResultFixCode extends IterateResultBase {
148
161
  action: "fix_code";
@@ -176,6 +189,11 @@ export interface IterateCommandOptions extends GlobalOptions {
176
189
  * only by `runPollCore`; excluded from the public `IterateInput` in api.mts.
177
190
  */
178
191
  persistSeen?: boolean;
192
+ /**
193
+ * Internal. `true` attaches edited comments without writing their markers; the poll loop
194
+ * writes them for the one result it returns. Set only by `runPollCore`.
195
+ */
196
+ deferEditedCommentMarkers?: boolean;
179
197
  /**
180
198
  * Internal. `true` allows fingerprint reuse of a previous WAIT-shaped report.
181
199
  * Poll sets this only for internal WAIT/MARK_READY continuation ticks — never the
@@ -26,10 +26,14 @@ export interface FirstLookThread extends ReviewThread {
26
26
  }
27
27
  export interface FirstLookComment extends PrComment {
28
28
  firstLookStatus: "minimized";
29
- edited?: boolean;
30
29
  }
31
- export type ActionableComment = PrComment & {
32
- edited?: boolean;
30
+ export type ActionableComment = PrComment;
31
+ /**
32
+ * A seen PR comment whose body changed since Shepherd last showed it. An edit is not readiness
33
+ * work: iterate shows it once on the next action that is not `wait` or `ready`.
34
+ */
35
+ export type EditedComment = PrComment & {
36
+ edited: true;
33
37
  };
34
38
  export type ShepherdStatus = "MERGED" | "CLOSED" | "READY" | "FAILING" | "PENDING" | "IN_PROGRESS" | "UNRESOLVED_COMMENTS" | "UNKNOWN";
35
39
  export interface ShepherdReport {
@@ -91,6 +95,8 @@ export interface ShepherdReport {
91
95
  minimizeIds?: string[];
92
96
  /** First-look items — minimized comments not yet seen by the agent. */
93
97
  firstLook: FirstLookComment[];
98
+ /** Seen PR comments (minimized or not) whose body changed. Not marked seen by check. Omitted when empty. */
99
+ edited?: EditedComment[];
94
100
  /** PR comments and review summaries minimized by a suppress+autoResolve rule this fetch. */
95
101
  autoMinimized?: AutoMinimizedItem[];
96
102
  };
@@ -103,6 +109,20 @@ export interface ShepherdReport {
103
109
  editedSummaries: Review[];
104
110
  /** APPROVED reviews not yet minimized — opt-in minimize target. */
105
111
  approvedReviews: Review[];
112
+ /**
113
+ * Set only when `iterate.reviewOutputMaxChars` held review items back this tick: `shown` of
114
+ * `total` review items appear in this report. Held items are absent from every review list,
115
+ * from the generated mutation IDs, and from the seen markers written this tick.
116
+ */
117
+ reviewItems?: {
118
+ shown: number;
119
+ total: number;
120
+ };
121
+ /**
122
+ * Internal, iterate-only: parent review IDs of unresolved threads held by the budget. Their
123
+ * summaries are not minimized this tick. Not rendered.
124
+ */
125
+ heldThreadReviewIds?: string[];
106
126
  /** COMMENTED review summary IDs matched by user rules with autoResolve:true — minimized without surfacing to agent. */
107
127
  ruleAutoResolveReviewSummaryIds?: string[];
108
128
  /** Branch protection rule for the PR's base branch. Null when no rule exists or the base ref is unavailable. */
@@ -167,7 +187,6 @@ export interface AgentComment {
167
187
  authorAssociation?: import("./github.mts").CommentAuthorAssociation;
168
188
  body: string;
169
189
  url: string;
170
- edited?: boolean;
171
190
  }
172
191
  /** Check shape emitted to the iterate agent under `fix_code` or check escalation. */
173
192
  export interface AgentCheck {
@@ -0,0 +1,7 @@
1
+ /** First `LEAN_LIST_CAP` items plus the count of items left out. */
2
+ export declare function capList<T>(items: readonly T[]): {
3
+ shown: T[];
4
+ hidden: number;
5
+ };
6
+ /** `", +N more"` suffix for a capped inline list; empty when nothing was left out. */
7
+ export declare function moreSuffix(hidden: number): string;
@@ -0,0 +1,13 @@
1
+ /** Lean list cap shared by Markdown lines, lean JSON, and wait logs. */
2
+ const LEAN_LIST_CAP = 5;
3
+ /** First `LEAN_LIST_CAP` items plus the count of items left out. */
4
+ export function capList(items) {
5
+ return {
6
+ shown: items.slice(0, LEAN_LIST_CAP),
7
+ hidden: Math.max(0, items.length - LEAN_LIST_CAP),
8
+ };
9
+ }
10
+ /** `", +N more"` suffix for a capped inline list; empty when nothing was left out. */
11
+ export function moreSuffix(hidden) {
12
+ return hidden > 0 ? `, +${hidden} more` : "";
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.59.3",
3
+ "version": "0.60.0",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "keywords": [
6
6
  "automation",
@@ -90,7 +90,7 @@
90
90
  "husky": "^9.1.7",
91
91
  "knip": "^6.14.1",
92
92
  "marked": "^18.0.11",
93
- "oxfmt": "^0.71.0",
93
+ "oxfmt": "^0.72.0",
94
94
  "oxlint": "^1.60.0",
95
95
  "typescript": "^7.0.2",
96
96
  "vitest": "^5.0.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.59.3",
4
+ "version": "0.60.0",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.59.3",
3
+ "version": "0.60.0",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for Codex.",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "pr-shepherd": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "pr-shepherd@0.59.3", "pr-shepherd-mcp"]
5
+ "args": ["--yes", "--package", "pr-shepherd@0.60.0", "pr-shepherd-mcp"]
6
6
  }
7
7
  }
8
8
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "pr-shepherd": {
3
3
  "command": "npx",
4
- "args": ["--yes", "--package", "pr-shepherd@0.59.3", "pr-shepherd-mcp"]
4
+ "args": ["--yes", "--package", "pr-shepherd@0.60.0", "pr-shepherd-mcp"]
5
5
  }
6
6
  }