pr-shepherd 0.57.1 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/bin/classify/standard/coderabbit-notices.d.mts +2 -2
- package/bin/classify/standard/coderabbit-notices.mjs +50 -6
- package/bin/classify/standard/codex-notices.mjs +1 -1
- package/bin/classify/standard/gemini-notices.d.mts +1 -1
- package/bin/classify/standard/gemini-notices.mjs +6 -5
- package/bin/classify/standard/kilo-no-issues.d.mts +1 -1
- package/bin/classify/standard/kilo-no-issues.mjs +12 -8
- package/bin/classify/standard/sourcery-notices.d.mts +1 -5
- package/bin/classify/standard/sourcery-notices.mjs +6 -9
- package/bin/commands/iterate/index.mjs +6 -3
- package/bin/github/poll-summary-review.mjs +1 -2
- package/package.json +1 -1
- package/plugins/pr-shepherd/.claude-plugin/plugin.json +15 -0
- package/plugins/pr-shepherd/.codex-plugin/plugin.json +1 -1
- package/plugins/pr-shepherd/.codex.mcp.json +1 -1
- package/plugins/pr-shepherd/.mcp.json +1 -1
- package/plugins/pr-shepherd/skills/mark-files-as-viewed/SKILL.md +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/SKILL.md +1 -1
- package/plugins/pr-shepherd/skills/reduce-pr-noise/SKILL.md +1 -1
|
@@ -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.
|
|
5
|
-
*
|
|
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
|
-
|
|
42
|
+
const withoutFooter = body
|
|
7
43
|
.trim()
|
|
8
44
|
.replace(/\n<!-- tips_start -->[\s\S]*?<!-- tips_end -->$/, "")
|
|
9
45
|
.trim()
|
|
10
|
-
.replace(
|
|
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.
|
|
27
|
-
*
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (!
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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 (
|
|
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
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
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
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pr-shepherd",
|
|
3
|
+
"description": "Autonomous PR CI monitor and review-comment resolver for agentic coding tools",
|
|
4
|
+
"version": "0.59.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Jonathan Ong",
|
|
7
|
+
"email": "jonathanrichardong@gmail.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/jonathanong/pr-shepherd",
|
|
10
|
+
"repository": "https://github.com/jonathanong/pr-shepherd",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["github", "pull-request", "ci", "code-review", "automation"],
|
|
13
|
+
"skills": "./skills/",
|
|
14
|
+
"mcpServers": "./.mcp.json"
|
|
15
|
+
}
|
|
@@ -3,7 +3,7 @@ name: mark-files-as-viewed
|
|
|
3
3
|
description: "Mark selected changed pull-request files as viewed with pr-shepherd (MCP or CLI)."
|
|
4
4
|
user-invocable: true
|
|
5
5
|
argument-hint: "[PR number or URL] [files|tests|--tests|--match REGEX]"
|
|
6
|
-
allowed-tools:
|
|
6
|
+
allowed-tools: mcp__plugin_pr-shepherd_pr-shepherd, Bash(npx pr-shepherd:*), Bash(pnpm exec pr-shepherd:*), Bash(pnpm dlx pr-shepherd:*), Bash(bunx pr-shepherd:*), Bash(pr-shepherd:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# mark-files-as-viewed
|
|
@@ -3,7 +3,7 @@ name: pr-shepherd
|
|
|
3
3
|
description: 'Create or iterate a GitHub pull request with pr-shepherd (MCP or CLI). Use for requests like "make a PR and use pr-shepherd", "iterate PR #123", or "run pr-shepherd until this PR is ready".'
|
|
4
4
|
user-invocable: true
|
|
5
5
|
argument-hint: "[PR number or URL | --stack PR] [--merge]"
|
|
6
|
-
allowed-tools:
|
|
6
|
+
allowed-tools: mcp__plugin_pr-shepherd_pr-shepherd, Bash(npx pr-shepherd:*), Bash(pnpm exec pr-shepherd:*), Bash(pnpm dlx pr-shepherd:*), Bash(bunx pr-shepherd:*), Bash(pr-shepherd:*)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# pr-shepherd
|
|
@@ -3,7 +3,7 @@ name: reduce-pr-noise
|
|
|
3
3
|
description: Reduce repetitive pr-shepherd output by configuring bot-comment classification rules or existing noise settings. Use for requests to silence a specific bot notice, quiet polling, trim CI logs, or tune comment visibility; use pr-shepherd for PR iteration itself.
|
|
4
4
|
user-invocable: true
|
|
5
5
|
argument-hint: "[noisy bot comment or output]"
|
|
6
|
-
allowed-tools:
|
|
6
|
+
allowed-tools: Bash(npx pr-shepherd:*), Bash(pnpm exec pr-shepherd:*), Bash(pnpm dlx pr-shepherd:*), Bash(bunx pr-shepherd:*), Bash(pr-shepherd:*), Edit(**/.pr-shepherdrc.yml), Edit(~/.pr-shepherdrc.yml)
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Reduce pr-shepherd noise
|