pr-shepherd 0.58.0 → 0.59.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.
@@ -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.58.0",
4
+ "version": "0.59.0",
5
5
  "author": {
6
6
  "name": "Jonathan Ong",
7
7
  "email": "jonathanrichardong@gmail.com"
@@ -1,8 +1,8 @@
1
1
  import type { ClassifyRule } from "../types.mts";
2
2
  /**
3
3
  * CodeRabbit progress, rate-limit, review-skipped and draft notices, plus its "Action not
4
- * completed" command replies. The summarize comment is later edited in place into the PR
5
- * walkthrough, so the rule only hides it from agent output and never minimizes it.
4
+ * completed" command replies. If the comment is later edited into a PR walkthrough, its new
5
+ * body no longer matches and Shepherd surfaces the edited minimized comment to the agent.
6
6
  */
7
7
  declare const rule: ClassifyRule;
8
8
  export default rule;
@@ -1,14 +1,54 @@
1
1
  import { isBotItem } from "./author.mjs";
2
2
  const NOTICE_ENVELOPE = /^<!-- This is an auto-generated comment: (rate limited|skip review|review in progress) by coderabbit.ai -->\n([\s\S]*?)\n<!-- end of auto-generated comment: \1 by coderabbit.ai -->$/;
3
3
  const COMMAND_REPLY = /^<!-- This is an auto-generated reply by CodeRabbit -->\n<!-- CodeRabbit review command invocation: v2:[a-f0-9]+ -->\n<details>\n<summary>⚠️ Action not completed<\/summary>\n\n(?:Review rate limited\.|Review skipped: \d+ files exceed the limit of \d+\.)\n\n(?:> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits\. This command is applicable only when automatic reviews are paused\.|---\n\nYour included review limit is currently reached under our \[Fair Usage Limits Policy\]\(https:\/\/docs.coderabbit.ai\/management\/plans#fair-usage-limits-policy\)\. This review may still proceed through usage-based billing if eligible\. Your next included review will be available in \d+ minutes?\.)\n\n<\/details>$/;
4
+ const FINISHING_TOUCH_BLOCK = `<!-- finishing_touch_checkbox_start -->
5
+
6
+ <details>
7
+ <summary><strong>✨ Finishing Touches</strong></summary>
8
+ <dl>
9
+ <dd>
10
+
11
+ <details open>
12
+ <summary><strong>📝 Generate docstrings</strong></summary>
13
+ <dl>
14
+ <dd>
15
+
16
+ - [ ] <!-- {"checkboxId":"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98"} --> Commit to this branch
17
+ - [ ] <!-- {"checkboxId":"7962f53c-55bc-4827-bfbf-6a18da830691"} --> Create a new PR
18
+
19
+ </dd>
20
+ </dl>
21
+ </details>
22
+ <details open>
23
+ <summary><strong>🧪 Generate unit tests (beta)</strong></summary>
24
+ <dl>
25
+ <dd>
26
+
27
+ - [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit to this branch
28
+ - [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create a new PR
29
+
30
+ </dd>
31
+ </dl>
32
+ </details>
33
+
34
+ </dd>
35
+ </dl>
36
+ </details>
37
+
38
+ <!-- finishing_touch_checkbox_end -->`;
39
+ const AUTOPILOT_BLOCK = /\n<!-- autopilot:start -->\n- \[ \] <!-- \{"checkboxId":"2708ad07-9f24-4260-9c11-7dc76a49f2e3"\} --> <strong title="Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts">Autofix<\/strong> · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts\n<!-- autopilot:end -->$/;
4
40
  /** Strips CodeRabbit's footer and header blocks so only the notice envelope remains. */
5
41
  function stripChrome(body) {
6
- return body
42
+ const withoutFooter = body
7
43
  .trim()
8
44
  .replace(/\n<!-- tips_start -->[\s\S]*?<!-- tips_end -->$/, "")
9
45
  .trim()
10
- .replace(/\n<!-- autopilot:start -->[\s\S]*?<!-- autopilot:end -->$/, "")
11
- .trim()
46
+ .replace(AUTOPILOT_BLOCK, "")
47
+ .trim();
48
+ const withoutFinishingTouch = withoutFooter.endsWith(`\n\n${FINISHING_TOUCH_BLOCK}`)
49
+ ? withoutFooter.slice(0, -FINISHING_TOUCH_BLOCK.length).trim()
50
+ : withoutFooter;
51
+ return withoutFinishingTouch
12
52
  .replace(/^<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\s*/, "")
13
53
  .replace(/^<!-- review_stack_entry_start -->[\s\S]*?<!-- review_stack_entry_end -->\s*/, "");
14
54
  }
@@ -23,8 +63,8 @@ function isQuotedNotice(quoted) {
23
63
  }
24
64
  /**
25
65
  * CodeRabbit progress, rate-limit, review-skipped and draft notices, plus its "Action not
26
- * completed" command replies. The summarize comment is later edited in place into the PR
27
- * walkthrough, so the rule only hides it from agent output and never minimizes it.
66
+ * completed" command replies. If the comment is later edited into a PR walkthrough, its new
67
+ * body no longer matches and Shepherd surfaces the edited minimized comment to the agent.
28
68
  */
29
69
  const rule = (item) => {
30
70
  if (!isBotItem(item, ["coderabbitai"], ["pr-comment"]))
@@ -33,6 +73,10 @@ const rule = (item) => {
33
73
  const quoted = body.match(NOTICE_ENVELOPE)?.[2]?.trim();
34
74
  if (!(quoted && isQuotedNotice(quoted)) && !COMMAND_REPLY.test(body))
35
75
  return null;
36
- return { suppress: true, reason: "CodeRabbit progress, review-limit, or skipped-review notice" };
76
+ return {
77
+ autoResolve: true,
78
+ suppress: true,
79
+ reason: "CodeRabbit progress, review-limit, or skipped-review notice",
80
+ };
37
81
  };
38
82
  export default rule;
@@ -6,7 +6,7 @@ const QUOTA_NOTICES = [
6
6
  /^Codex usage limits have been reached\. Please try again later\.$/,
7
7
  ];
8
8
  const ABOUT_BLOCK = /\n\n<details> <summary>ℹ️ About Codex (?:in GitHub|security reviews in GitHub)<\/summary>[\s\S]*?<\/details>$/;
9
- const ACTIVITY_SUMMARY = /^<!-- codex-pull-request-review-summary -->\n(?:<!-- codex-security-review:v1 \{[^\n]*\} -->\n)?\n?## Codex Review Summary\n\nThis comment shows the latest Codex review activity on this pull request\.\n\n\| Review \| Status \| Commit \| Review trigger \|\n\| --- \| --- \| --- \| --- \|\n(?:\|[^\n]+\|\n?)+$/;
9
+ const ACTIVITY_SUMMARY = /^<!-- codex-pull-request-review-summary -->\n(?:<!-- codex-review-state:v1 \{[^\n]*\} -->\n)?(?:<!-- codex-security-review:v1 \{[^\n]*\} -->\n)?\n?## Codex Review Summary\n\nThis comment shows the latest Codex review activity on this pull request\.\n\n\| Review \| Status \| Commit \| Review trigger \|\n\| --- \| --- \| --- \| --- \|\n(?:\|[^\n]+\|\n?)+$/;
10
10
  const CLEAN_REVIEW = /^Codex Review: Didn't find any major issues\. (?:Bravo\.|Breezy!|Hooray!|:\+1:)\n\n\*\*Reviewed commit:\*\* `[a-f0-9]+`$/;
11
11
  const CLEAN_SECURITY = /^### 🛡️ Codex Security Review(?: · _Automatically triggered_)?\n\nSecurity review completed\. No security issues were found in this pull request\.\n\n\*\*Reviewed commit:\*\* `[a-f0-9]+`\n\n\[View security finding report\]\(https:\/\/chatgpt.com\/codex\/cloud\/tasks\/[a-zA-Z0-9_]+\)\n\n_Only the user who started this review can view the report in Codex\._$/;
12
12
  /**
@@ -1,4 +1,4 @@
1
1
  import type { ClassifyRule } from "../types.mts";
2
- /** Gemini Code Assist daily-quota and review-limit notices on conversation comments. A heading means a real review. */
2
+ /** Complete Gemini Code Assist quota and review-limit notices. */
3
3
  declare const rule: ClassifyRule;
4
4
  export default rule;
@@ -1,13 +1,14 @@
1
1
  import { isBotItem } from "./author.mjs";
2
- /** Gemini Code Assist daily-quota and review-limit notices on conversation comments. A heading means a real review. */
2
+ const NOTICES = [
3
+ "You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!",
4
+ "Review limit reached. I'll be able to review this pull request again tomorrow.",
5
+ ];
6
+ /** Complete Gemini Code Assist quota and review-limit notices. */
3
7
  const rule = (item) => {
4
8
  if (!isBotItem(item, ["gemini-code-assist"], ["pr-comment"]))
5
9
  return null;
6
- if (/^#{1,6}\s/m.test(item.body))
10
+ if (!NOTICES.includes(item.body.trim()))
7
11
  return null;
8
- if (!/You have reached your daily quota limit|Review limit reached/i.test(item.body)) {
9
- return null;
10
- }
11
12
  return { autoResolve: true, suppress: true, reason: "Gemini quota or review-limit notice" };
12
13
  };
13
14
  export default rule;
@@ -1,4 +1,4 @@
1
1
  import type { ClassifyRule } from "../types.mts";
2
- /** Kilo Code conversation comment whose status line reads "No Issues Found" and that carries no findings. */
2
+ /** Complete Kilo Code no-issues status or report with a file list and no extra prose. */
3
3
  declare const rule: ClassifyRule;
4
4
  export default rule;
@@ -1,16 +1,20 @@
1
1
  import { isBotItem } from "./author.mjs";
2
- const STATUS_LINE = /^[^\S\n]*(?:\*\*)?Status:?(?:\*\*)?:?[^\S\n]*(?:\p{Extended_Pictographic}️?[^\S\n]*)?No Issues Found\b/imu;
3
- /** Headings a no-issues report carries; any other heading means real feedback. */
4
- const REPORT_HEADING = /^(?:Code Review Summary|Files Reviewed\b.*)$/i;
5
- /** Kilo Code conversation comment whose status line reads "No Issues Found" and that carries no findings. */
2
+ const PLAIN_STATUS = /^Status: ✅ No Issues Found \| Recommendation: Merge$/;
3
+ const REVIEW_REPORT = /^## Code Review Summary\n\n\*\*Status:\*\* No Issues Found \| \*\*Recommendation:\*\* Merge(?:\n\n### Files Reviewed \((\d+) files\)\n((?:- `[^`\n]+`(?:\n|$))+))?$/;
4
+ /** Complete Kilo Code no-issues status or report with a file list and no extra prose. */
6
5
  const rule = (item) => {
7
6
  if (!isBotItem(item, ["kilo-code-bot"], ["pr-comment"]))
8
7
  return null;
9
- if (!STATUS_LINE.test(item.body))
10
- return null;
11
- for (const [, heading] of item.body.matchAll(/^#{1,6}\s+(.*)$/gm)) {
12
- if (!REPORT_HEADING.test(heading.trim()))
8
+ const body = item.body.trim();
9
+ if (!PLAIN_STATUS.test(body)) {
10
+ const report = body.match(REVIEW_REPORT);
11
+ if (!report)
13
12
  return null;
13
+ if (report[1] !== undefined) {
14
+ const listed = report[2].trimEnd().split("\n").length;
15
+ if (listed !== Number(report[1]))
16
+ return null;
17
+ }
14
18
  }
15
19
  return { autoResolve: true, suppress: true, reason: "Kilo Code no-issues report" };
16
20
  };
@@ -1,8 +1,4 @@
1
1
  import type { ClassifyRule } from "../types.mts";
2
- /**
3
- * Sourcery's private-repo upsell and weekly rate-limit notices on conversation comments. A real review opens
4
- * with a Markdown heading ("Reviewer's Guide", "Summary by Sourcery"); the notices carry none,
5
- * so a heading means genuine feedback that merely quotes the phrase.
6
- */
2
+ /** Complete Sourcery private-repo upsell and weekly rate-limit notices. */
7
3
  declare const rule: ClassifyRule;
8
4
  export default rule;
@@ -1,18 +1,15 @@
1
1
  import { isBotItem } from "./author.mjs";
2
- /**
3
- * Sourcery's private-repo upsell and weekly rate-limit notices on conversation comments. A real review opens
4
- * with a Markdown heading ("Reviewer's Guide", "Summary by Sourcery"); the notices carry none,
5
- * so a heading means genuine feedback that merely quotes the phrase.
6
- */
2
+ const NO_ACCESS = /^Hi @[A-Za-z0-9-]+! 👋\n\nYour private repo does not have access to Sourcery\.\n\nPlease \[upgrade\]\(https:\/\/app\.sourcery\.ai\/login\?connection=github(?:&git_namespace_id=\d+)?&from_surface=github_bot&intent=private_repo_upsell\) to continue using Sourcery ✨$/;
3
+ const RATE_LIMIT = /^Sorry @[A-Za-z0-9-]+, you have reached your weekly rate limit for Sourcery\. Please try again later\.$/;
4
+ /** Complete Sourcery private-repo upsell and weekly rate-limit notices. */
7
5
  const rule = (item) => {
8
6
  if (!isBotItem(item, ["sourcery-ai"], ["pr-comment"]))
9
7
  return null;
10
- if (/^#{1,6}\s/m.test(item.body))
11
- return null;
12
- if (/does not have access to Sourcery/i.test(item.body)) {
8
+ const body = item.body.trim();
9
+ if (NO_ACCESS.test(body)) {
13
10
  return { autoResolve: true, suppress: true, reason: "Sourcery no-access upsell" };
14
11
  }
15
- if (/reached your weekly rate limit/i.test(item.body)) {
12
+ if (RATE_LIMIT.test(body)) {
16
13
  return { autoResolve: true, suppress: true, reason: "Sourcery rate-limit notice" };
17
14
  }
18
15
  return null;
@@ -95,14 +95,15 @@ async function runIterateCore(opts) {
95
95
  if (unminimized.length > 0)
96
96
  reviewSummaryIds = [...reviewSummaryIds, ...unminimized];
97
97
  }
98
- // Hidden PR comments surface once so the agent can acknowledge them, but
99
- // they are not readiness evidence: bots keep editing hidden notices after a
100
- // PR settles. They never restart the ready-delay or void a READY receipt.
98
+ // A first look at an already-minimized comment is only an acknowledgment.
99
+ // An edited body that no longer matches a noise rule may carry new feedback;
100
+ // it must restart the ready delay and void an existing READY receipt.
101
101
  const hasReadinessWork = report.threads.actionable.length > 0 ||
102
102
  report.threads.resolutionOnly.length > 0 ||
103
103
  report.threads.firstLook.length > 0 ||
104
104
  (report.threads.ruleAutoResolveIds?.length ?? 0) > 0 ||
105
105
  report.comments.actionable.length > 0 ||
106
+ report.comments.firstLook.some((comment) => comment.edited === true) ||
106
107
  (report.comments.minimizeIds?.length ?? 0) > 0 ||
107
108
  report.changesRequestedReviews.length > 0 ||
108
109
  hasCheckDrivenActionableWork(reportForWork.checks, report.mergeStatus.status) ||
@@ -393,11 +394,13 @@ async function revalidateReadyReceipt(key, report, hasReadinessWork, context) {
393
394
  report.mergeStatus.state === "OPEN" &&
394
395
  !report.mergeStatus.isDraft &&
395
396
  Boolean(report.headSha && report.baseRefOid);
397
+ const editedMinimizedFeedback = report.comments.firstLook.some((comment) => comment.isMinimized && comment.edited === true);
396
398
  if (report.mergeStatus.state !== "OPEN" ||
397
399
  report.mergeStatus.isDraft ||
398
400
  !report.headSha ||
399
401
  !report.baseRefOid ||
400
402
  transportReadinessGaps(report, report.mergeStatus.mergeStateStatus).length > 0 ||
403
+ editedMinimizedFeedback ||
401
404
  (!retainQueuedReceipt && (report.status !== "READY" || hasReadinessWork))) {
402
405
  await clearReadyReceipt(key);
403
406
  return false;
@@ -18,8 +18,7 @@ export async function summarizePollSummaryReview(raw, seen, viewerCanAdminister)
18
18
  raw.reviews.pageInfo.hasPreviousPage ||
19
19
  raw.reviewThreads.pageInfo.hasPreviousPage;
20
20
  for (const comment of raw.comments.nodes) {
21
- if (!comment.isMinimized &&
22
- !isSuppressed(rules, "pr-comment", comment) &&
21
+ if (!isSuppressed(rules, "pr-comment", comment, comment.id, undefined, comment.body, comment.isMinimized) &&
23
22
  classifyItem(comment.id, comment.body, seen) !== "unchanged")
24
23
  actionable += 1;
25
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-shepherd",
3
- "version": "0.58.0",
3
+ "version": "0.59.0",
4
4
  "description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
5
5
  "keywords": [
6
6
  "automation",
@@ -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.58.0",
4
+ "version": "0.59.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.58.0",
3
+ "version": "0.59.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.58.0", "pr-shepherd-mcp"]
5
+ "args": ["--yes", "--package", "pr-shepherd@0.59.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.58.0", "pr-shepherd-mcp"]
4
+ "args": ["--yes", "--package", "pr-shepherd@0.59.0", "pr-shepherd-mcp"]
5
5
  }
6
6
  }