pr-shepherd 0.60.0 → 1.0.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/plugin.json +1 -1
- package/README.md +7 -5
- package/bin/api.d.mts +35 -24
- package/bin/api.mjs +68 -48
- package/bin/checks/classify.d.mts +1 -1
- package/bin/checks/classify.mjs +4 -4
- package/bin/checks/log-excerpt.mjs +1 -1
- package/bin/classify/standard/codecov-report.d.mts +1 -1
- package/bin/classify/standard/codecov-report.mjs +7 -2
- package/bin/classify/standard/coderabbit-notices.d.mts +4 -3
- package/bin/classify/standard/coderabbit-notices.mjs +29 -47
- package/bin/classify/standard/codex-notices.mjs +34 -7
- package/bin/classify/standard/dependabot-notices.mjs +5 -1
- package/bin/classify/standard/gemini-notices.d.mts +1 -1
- package/bin/classify/standard/gemini-notices.mjs +16 -5
- package/bin/classify/standard/kilo-no-issues.d.mts +1 -1
- package/bin/classify/standard/kilo-no-issues.mjs +37 -10
- package/bin/classify/standard/sourcery-notices.mjs +1 -1
- package/bin/cli/arg-flags.mjs +0 -3
- package/bin/cli/clean-handler.d.mts +1 -1
- package/bin/cli/clean-handler.mjs +5 -15
- package/bin/cli/default-poll.d.mts +2 -0
- package/bin/cli/default-poll.mjs +9 -6
- package/bin/cli/formatters.d.mts +2 -3
- package/bin/cli/formatters.mjs +0 -26
- package/bin/cli/handlers.d.mts +3 -3
- package/bin/cli/handlers.mjs +45 -35
- package/bin/cli/help-command-pages.d.mts +13 -158
- package/bin/cli/help-command-pages.mjs +12 -157
- package/bin/cli/help-iterate-poll-pages.d.mts +4 -3
- package/bin/cli/help-iterate-poll-pages.mjs +76 -36
- package/bin/cli/help-log-file-page.d.mts +1 -1
- package/bin/cli/help-log-file-page.mjs +3 -5
- package/bin/cli/help-top-page.d.mts +1 -1
- package/bin/cli/help-top-page.mjs +19 -20
- package/bin/cli/help.d.mts +14 -159
- package/bin/cli/iterate-checks-formatter.mjs +1 -1
- package/bin/cli/iterate-flags.d.mts +2 -4
- package/bin/cli/iterate-flags.mjs +4 -20
- package/bin/cli/iterate-formatter.d.mts +1 -1
- package/bin/cli/iterate-formatter.mjs +6 -6
- package/bin/cli/iterate-instructions.d.mts +1 -1
- package/bin/cli/iterate-instructions.mjs +5 -6
- package/bin/cli/iterate-lean.mjs +32 -40
- package/bin/cli/iterate-verbose.mjs +1 -1
- package/bin/cli/journal-formatter.mjs +0 -3
- package/bin/cli/journal-handler.d.mts +1 -1
- package/bin/cli/journal-handler.mjs +2 -1
- package/bin/cli/mutate-formatter.mjs +0 -9
- package/bin/cli/next-check-format.d.mts +0 -4
- package/bin/cli/next-check-format.mjs +0 -4
- package/bin/cli/poll-handler.d.mts +3 -0
- package/bin/cli/poll-handler.mjs +88 -47
- package/bin/cli/poll-summary-emitter.mjs +2 -2
- package/bin/cli/poll-summary-formatter.mjs +2 -2
- package/bin/cli/poll-targets.d.mts +2 -2
- package/bin/cli/poll-targets.mjs +15 -35
- package/bin/cli/removed-flags.d.mts +3 -0
- package/bin/cli/removed-flags.mjs +24 -0
- package/bin/cli/runner.d.mts +1 -1
- package/bin/cli/runner.mjs +2 -2
- package/bin/cli/stack-overview.d.mts +0 -1
- package/bin/cli/stack-overview.mjs +1 -2
- package/bin/cli/{fix-formatter-extra.d.mts → triage-formatter-extra.d.mts} +1 -2
- package/bin/cli/{fix-formatter-extra.mjs → triage-formatter-extra.mjs} +0 -6
- package/bin/cli/triage-formatter.d.mts +4 -0
- package/bin/cli/{fix-formatter.mjs → triage-formatter.mjs} +31 -42
- package/bin/cli/unknown-flags.d.mts +6 -0
- package/bin/cli/unknown-flags.mjs +29 -0
- package/bin/cli-parser.mjs +41 -62
- package/bin/commands/check-annotations.d.mts +1 -1
- package/bin/commands/check-annotations.mjs +1 -1
- package/bin/commands/check-review-budget.d.mts +1 -1
- package/bin/commands/check-review-budget.mjs +1 -1
- package/bin/commands/check-wait-detectors.d.mts +1 -1
- package/bin/commands/check-wait-detectors.mjs +1 -1
- package/bin/commands/check.d.mts +1 -1
- package/bin/commands/check.mjs +7 -7
- package/bin/commands/commit-suggestion-instruction.d.mts +2 -2
- package/bin/commands/commit-suggestion-instruction.mjs +3 -3
- package/bin/commands/iterate/api-usage.mjs +8 -8
- package/bin/commands/iterate/check-instructions.d.mts +4 -4
- package/bin/commands/iterate/check-instructions.mjs +8 -8
- package/bin/commands/iterate/classify.mjs +2 -2
- package/bin/commands/iterate/escalate.mjs +6 -6
- package/bin/commands/iterate/index.mjs +20 -23
- package/bin/commands/iterate/merge-plan.mjs +3 -3
- package/bin/commands/iterate/merge-state.mjs +4 -7
- package/bin/commands/iterate/native-stack-rebase.mjs +1 -1
- package/bin/commands/iterate/render.d.mts +1 -1
- package/bin/commands/iterate/render.mjs +2 -2
- package/bin/commands/iterate/stall.mjs +11 -9
- package/bin/commands/iterate/{fix-loop-instruction.mjs → triage-loop-instruction.mjs} +7 -3
- package/bin/commands/iterate/{fix-code.d.mts → triage.d.mts} +2 -2
- package/bin/commands/iterate/{fix-code.mjs → triage.mjs} +14 -23
- package/bin/commands/iterate/unreported-required.mjs +2 -5
- package/bin/commands/journal/index.d.mts +1 -3
- package/bin/commands/journal/index.mjs +1 -1
- package/bin/commands/mark-files-as-viewed.d.mts +1 -3
- package/bin/commands/mark-files-as-viewed.mjs +1 -1
- package/bin/commands/next-check.d.mts +2 -2
- package/bin/commands/next-check.mjs +4 -2
- package/bin/commands/poll-progress.mjs +1 -1
- package/bin/commands/poll-summary-explicit-instructions.mjs +1 -1
- package/bin/commands/poll-summary-instructions.mjs +7 -7
- package/bin/commands/poll-summary.mjs +4 -4
- package/bin/commands/poll.d.mts +1 -1
- package/bin/commands/poll.mjs +4 -4
- package/bin/commands/resolve-mutate.d.mts +1 -1
- package/bin/commands/resolve-mutate.mjs +1 -1
- package/bin/commands/rest-stack-merge-readiness.mjs +1 -1
- package/bin/commands/single-tick.d.mts +5 -0
- package/bin/commands/single-tick.mjs +7 -0
- package/bin/commands/stack-drain.mjs +1 -1
- package/bin/commands/stack-merge-flag.mjs +1 -1
- package/bin/commands/stack-stall.d.mts +2 -2
- package/bin/commands/stack-stall.mjs +2 -2
- package/bin/commands/stack-work.d.mts +2 -2
- package/bin/commands/stack-work.mjs +2 -2
- package/bin/commands/suggestion-patch-item.d.mts +0 -1
- package/bin/commands/suggestion-patch-item.mjs +0 -12
- package/bin/commands/suggestion-patches.d.mts +2 -2
- package/bin/commands/suggestion-patches.mjs +1 -1
- package/bin/commands/tick-instructions.d.mts +9 -0
- package/bin/commands/tick-instructions.mjs +23 -0
- package/bin/commands/tick.d.mts +15 -0
- package/bin/commands/{event-mode.mjs → tick.mjs} +39 -55
- package/bin/comments/minimize-policy.mjs +1 -3
- package/bin/comments/resolve.d.mts +0 -23
- package/bin/comments/resolve.mjs +0 -4
- package/bin/comments/review-output-budget.d.mts +1 -1
- package/bin/comments/review-output-budget.mjs +1 -1
- package/bin/comments/sha-poll.mjs +3 -1
- package/bin/config/default-bot-usernames.d.mts +2 -0
- package/bin/config/default-bot-usernames.mjs +15 -0
- package/bin/config/load.d.mts +4 -138
- package/bin/config/load.mjs +88 -443
- package/bin/config/merge-command-args.mjs +2 -2
- package/bin/config/merge-method.d.mts +1 -2
- package/bin/config/merge-method.mjs +1 -8
- package/bin/config/renames.d.mts +8 -0
- package/bin/config/renames.mjs +87 -0
- package/bin/config/resolve.d.mts +31 -0
- package/bin/config/resolve.mjs +89 -0
- package/bin/config/schema.d.mts +161 -0
- package/bin/config/schema.mjs +168 -0
- package/bin/exit-codes.d.mts +1 -1
- package/bin/exit-codes.mjs +3 -3
- package/bin/github/poll-summary-check-blockers.mjs +1 -1
- package/bin/github/poll-summary-command.mjs +8 -6
- package/bin/github/poll-summary-projector.mjs +2 -2
- package/bin/github/poll-summary-review.mjs +1 -1
- package/bin/github/poll-summary-route.mjs +9 -9
- package/bin/github/poll-summary-unreported.mjs +1 -1
- package/bin/github/poll-summary.mjs +1 -1
- package/bin/github/queries.d.mts +1 -1
- package/bin/github/queries.mjs +1 -1
- package/bin/mcp/server.mjs +48 -54
- package/bin/merge-status/derive.mjs +1 -1
- package/bin/state/durable-state.d.mts +1 -3
- package/bin/state/durable-state.mjs +2 -8
- package/bin/state/fix-attempts.d.mts +2 -2
- package/bin/state/fix-attempts.mjs +1 -1
- package/bin/state/merge-request.mjs +1 -1
- package/bin/state/pr-fingerprint.mjs +4 -11
- package/bin/state/seen-comments.d.mts +4 -5
- package/bin/state/seen-comments.mjs +8 -14
- package/bin/state/stack-stall.d.mts +1 -1
- package/bin/state/stack-stall.mjs +1 -1
- package/bin/state/stall-state-store.d.mts +1 -1
- package/bin/state/stall-state-store.mjs +1 -1
- package/bin/types/escalate.d.mts +2 -2
- package/bin/types/iterate.d.mts +12 -26
- package/bin/types/merge-queue.d.mts +2 -2
- package/bin/types/next-check.d.mts +1 -1
- package/bin/types/poll-summary.d.mts +3 -6
- package/bin/types/report.d.mts +2 -2
- package/bin/types/suggestion-patch.d.mts +1 -8
- package/package.json +2 -1
- package/plugins/pr-shepherd/.claude-plugin/plugin.json +1 -1
- 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/pr-shepherd/SKILL.md +6 -6
- package/plugins/pr-shepherd/skills/pr-shepherd/references/ci-failure-triage.md +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/references/{cloud-event-loop.md → event-loop.md} +5 -5
- package/plugins/pr-shepherd/skills/pr-shepherd/references/mcp-fallback.md +3 -3
- package/plugins/pr-shepherd/skills/pr-shepherd/references/merge-queue-ejection.md +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/references/suggestion-patches.md +1 -1
- package/plugins/pr-shepherd/skills/pr-shepherd/references/{fix-code-loop.md → triage-loop.md} +3 -3
- package/plugins/pr-shepherd/skills/reduce-pr-noise/SKILL.md +1 -1
- package/plugins/pr-shepherd/skills/reduce-pr-noise/references/classifiers.md +2 -2
- package/plugins/pr-shepherd/skills/reduce-pr-noise/references/settings.md +11 -11
- package/pr-shepherd.schema.json +361 -0
- package/bin/cli/event-state-scope.d.mts +0 -19
- package/bin/cli/event-state-scope.mjs +0 -60
- package/bin/cli/fix-formatter.d.mts +0 -4
- package/bin/commands/commit-suggestion.d.mts +0 -8
- package/bin/commands/commit-suggestion.mjs +0 -26
- package/bin/commands/event-instructions.d.mts +0 -11
- package/bin/commands/event-instructions.mjs +0 -27
- package/bin/commands/event-mode.d.mts +0 -14
- package/bin/commands/poll-mode.d.mts +0 -8
- package/bin/commands/poll-mode.mjs +0 -14
- package/bin/config.json +0 -68
- package/bin/types/protected-run.d.mts +0 -6
- package/bin/types/protected-run.mjs +0 -1
- /package/bin/commands/iterate/{fix-loop-instruction.d.mts → triage-loop-instruction.d.mts} +0 -0
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
import { isBotItem } from "./author.mjs";
|
|
2
2
|
const QUOTA_NOTICES = [
|
|
3
3
|
/^You have reached your Codex usage limits for security reviews\. Please try again later\.$/,
|
|
4
|
-
/^You have reached your Codex usage limits for code reviews\. You can see your limits in the \[Codex usage dashboard\]\(https:\/\/chatgpt
|
|
5
|
-
/^Codex usage limits have been reached for code reviews\. Please check with the admins of this repo to increase the limits by adding credits\.\nRepo admins can enable using credits for code reviews in their \[settings\]\(https:\/\/chatgpt
|
|
4
|
+
/^You have reached your Codex usage limits for code reviews\. You can see your limits in the \[Codex usage dashboard\]\(https:\/\/chatgpt\.com\/[^\s)]*\)\.(?:\nTo continue using code reviews, add credits to your account and enable them for code reviews in your \[settings\]\(https:\/\/chatgpt\.com\/[^\s)]*\)\.)?$/,
|
|
5
|
+
/^Codex usage limits have been reached for code reviews\. Please check with the admins of this repo to increase the limits by adding credits\.\nRepo admins can enable using credits for code reviews in their \[settings\]\(https:\/\/chatgpt\.com\/[^\s)]*\)\.$/,
|
|
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-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const ACTIVITY_SUMMARY = /^<!-- codex-pull-request-review-summary -->\n(?:<!-- codex-[a-z0-9-]+:[^\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
|
+
/**
|
|
11
|
+
* Codex's rotating clean-review sign-offs, as observed. Prose is matched only from this list so a
|
|
12
|
+
* short piece of advice in that position stays visible; a new sign-off is shown until added here.
|
|
13
|
+
*/
|
|
14
|
+
const SIGN_OFFS = [
|
|
15
|
+
"Already looking forward to the next diff.",
|
|
16
|
+
"Another round soon, please!",
|
|
17
|
+
"Bravo.",
|
|
18
|
+
"Breezy!",
|
|
19
|
+
"Can't wait for the next one!",
|
|
20
|
+
"Chef's kiss.",
|
|
21
|
+
"Delightful!",
|
|
22
|
+
"Hooray!",
|
|
23
|
+
"Keep it up!",
|
|
24
|
+
"Keep them coming!",
|
|
25
|
+
"More of your lovely PRs please.",
|
|
26
|
+
"Nice work!",
|
|
27
|
+
"Swish!",
|
|
28
|
+
"What shall we delve into next?",
|
|
29
|
+
"You're on a roll.",
|
|
30
|
+
];
|
|
31
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
32
|
+
/** A clean review closes with a known sign-off, a `:shortcode:`, or a single emoji. */
|
|
33
|
+
const CLEAN_REVIEW = new RegExp(`^Codex Review: Didn't find any major issues\\. (?:${SIGN_OFFS.map(escapeRegExp).join("|")}|:[a-z0-9_+-]+:|\\p{Extended_Pictographic}\\uFE0F?)\\n\\n\\*\\*Reviewed commit:\\*\\* \`[a-f0-9]+\`$`, "u");
|
|
34
|
+
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\/[^\s)]*\)\n\n_Only the user who started this review can view the report in Codex\._$/;
|
|
12
35
|
/**
|
|
13
36
|
* Codex quota notices, the review-activity status table, and no-findings notices on conversation
|
|
14
37
|
* comments. Each pattern matches the whole comment, so a notice with any appended finding stays
|
|
@@ -21,8 +44,12 @@ const rule = (item) => {
|
|
|
21
44
|
if (QUOTA_NOTICES.some((notice) => notice.test(body))) {
|
|
22
45
|
return { autoResolve: true, suppress: true, reason: "Codex usage-limit notice" };
|
|
23
46
|
}
|
|
24
|
-
// Strip only the trailing About block; extra findings
|
|
25
|
-
|
|
47
|
+
// Strip only leading codex-* metadata markers and the trailing About block; extra findings stay
|
|
48
|
+
// visible. The activity table's own marker has no colon, so it is kept for ACTIVITY_SUMMARY.
|
|
49
|
+
const content = body
|
|
50
|
+
.replace(ABOUT_BLOCK, "")
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/^(?:<!-- codex-[a-z0-9-]+:[^\n]* -->\s*)+/, "");
|
|
26
53
|
if (ACTIVITY_SUMMARY.test(content)) {
|
|
27
54
|
return { autoResolve: true, suppress: true, reason: "Codex review-activity table" };
|
|
28
55
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { isBotItem } from "./author.mjs";
|
|
2
2
|
const NOTICES = [
|
|
3
3
|
/^Looks like this PR is already up-to-date with [^\s!]+! If you'd still like to recreate it from scratch, overwriting any edits, you can request `@dependabot recreate`\.$/,
|
|
4
|
-
/^Looks like these dependencies are updatable in another way, so this is no longer needed\.$/,
|
|
4
|
+
/^Looks like (?:these dependencies are|\S+ is) updatable in another way, so this is no longer needed\.$/,
|
|
5
|
+
/^Superseded by #\d+\.$/,
|
|
6
|
+
/^OK, I won't notify you again about this release, but will get in touch when a new version is available\. If you'd rather skip all updates until the next major or minor version, let me know by commenting `@dependabot ignore this major version` or `@dependabot ignore this minor version`\. You can also ignore all major, minor, or patch releases for a dependency by adding an \[`ignore` condition\]\(https:\/\/docs\.github\.com\/[^\s)]*\) with the desired `update_types` to your config file\.\n\nIf you change your mind, just re-open this PR and I'll resolve any conflicts on it\.$/,
|
|
7
|
+
/^This pull request was built based on a group rule\. Closing it will not ignore any of these versions in future pull requests\.\n\nTo ignore these dependencies, configure \[ignore rules\]\(https:\/\/docs\.github\.com\/[^\s)]*\) in dependabot\.yml$/,
|
|
8
|
+
/^Looks like this PR has been edited by someone other than Dependabot\. That means Dependabot can't rebase it - sorry!\n\nIf you're happy for Dependabot to recreate it from scratch, overwriting any edits, you can request `@dependabot recreate`\.$/,
|
|
5
9
|
];
|
|
6
10
|
/** Dependabot's replies that report no change to make. */
|
|
7
11
|
const rule = (item) => {
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { isBotItem } from "./author.mjs";
|
|
2
|
-
const
|
|
2
|
+
const QUOTA_NOTICES = [
|
|
3
3
|
"You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!",
|
|
4
4
|
"Review limit reached. I'll be able to review this pull request again tomorrow.",
|
|
5
5
|
];
|
|
6
|
-
|
|
6
|
+
const UNSUPPORTED_NOTICES = [
|
|
7
|
+
"Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.",
|
|
8
|
+
"Gemini is unable to generate a summary for this pull request due to the file types involved not being currently supported.",
|
|
9
|
+
];
|
|
10
|
+
/** A GitHub alert prefix such as `> [!WARNING]`, with the notice on the following quoted line. */
|
|
11
|
+
const ALERT_PREFIX = /^> \[!(?:NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\n> /;
|
|
12
|
+
/** Complete Gemini Code Assist quota, review-limit, and unsupported-file notices. */
|
|
7
13
|
const rule = (item) => {
|
|
8
14
|
if (!isBotItem(item, ["gemini-code-assist"], ["pr-comment"]))
|
|
9
15
|
return null;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
const notice = item.body.trim().replace(ALERT_PREFIX, "");
|
|
17
|
+
if (QUOTA_NOTICES.includes(notice)) {
|
|
18
|
+
return { autoResolve: true, suppress: true, reason: "Gemini quota or review-limit notice" };
|
|
19
|
+
}
|
|
20
|
+
if (UNSUPPORTED_NOTICES.includes(notice)) {
|
|
21
|
+
return { autoResolve: true, suppress: true, reason: "Gemini unsupported-file-type notice" };
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
13
24
|
};
|
|
14
25
|
export default rule;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ClassifyRule } from "../types.mts";
|
|
2
|
-
/** Complete Kilo Code no-issues
|
|
2
|
+
/** Complete Kilo Code no-issues reports and its out-of-credits notice, with no extra findings. */
|
|
3
3
|
declare const rule: ClassifyRule;
|
|
4
4
|
export default rule;
|
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
import { isBotItem } from "./author.mjs";
|
|
2
2
|
const PLAIN_STATUS = /^Status: ✅ No Issues Found \| Recommendation: Merge$/;
|
|
3
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
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The marked report: a no-issues status, an optional plain overview (see `isPlainOverview`), the
|
|
6
|
+
* collapsed file list, and `kilo-*` usage footers. Any other status, such as "N Issues Found",
|
|
7
|
+
* does not match, and Kilo rewrites this same comment when a later push finds issues. A stray
|
|
8
|
+
* `EOF` line, left by some models before the footer, is tolerated.
|
|
9
|
+
*/
|
|
10
|
+
const MARKED_REPORT = /^<!-- kilo-review -->\n\n?## Code Review Summary\n\n\*\*Status:\*\* No (?:New )?Issues Found \| \*\*Recommendation:\*\* Merge(?: \([^)\n]*\))?\n\n([\s\S]*?\n)?<details>\n<summary><b>Files Reviewed \([^)\n]*\)<\/b><\/summary>\n(?:(?!<details>)[\s\S])*?<\/details>\n+(?:EOF\n+)?---\n(?:<!-- kilo-[a-z-]+ -->\n<sub>[^\n]*<\/sub>(?:\n|$))+$/;
|
|
11
|
+
const BILLING = /^<!-- kilo-billing-notice -->\n\*\*Kilo Code Review could not run — your account is out of credits\.\*\*\n\n\[Add credits\]\(https:\/\/app\.kilo\.ai\/[^\s)]*\) or \[switch to a free model\]\(https:\/\/app\.kilo\.ai\/[^\s)]*\) to enable reviews on this change\.$/;
|
|
12
|
+
/**
|
|
13
|
+
* True when the overview is only prose and lists, optionally under `### Overview`. Any other
|
|
14
|
+
* heading, HTML block, or table could carry a finding, so it keeps the report visible.
|
|
15
|
+
*/
|
|
16
|
+
function isPlainOverview(overview) {
|
|
17
|
+
return (overview ?? "")
|
|
18
|
+
.split("\n")
|
|
19
|
+
.every((line) => line === "### Overview" || !/^\s*(?:#|<|\|)/.test(line));
|
|
20
|
+
}
|
|
21
|
+
/** True for the marked report when its overview is plain. */
|
|
22
|
+
function isMarkedReport(body) {
|
|
23
|
+
const report = body.match(MARKED_REPORT);
|
|
24
|
+
return report != null && isPlainOverview(report[1]);
|
|
25
|
+
}
|
|
26
|
+
/** True for the unmarked report when its file count agrees with the listed files. */
|
|
27
|
+
function isConsistentReport(body) {
|
|
28
|
+
const report = body.match(REVIEW_REPORT);
|
|
29
|
+
if (!report)
|
|
30
|
+
return false;
|
|
31
|
+
if (report[1] === undefined)
|
|
32
|
+
return true;
|
|
33
|
+
return report[2].trimEnd().split("\n").length === Number(report[1]);
|
|
34
|
+
}
|
|
35
|
+
/** Complete Kilo Code no-issues reports and its out-of-credits notice, with no extra findings. */
|
|
5
36
|
const rule = (item) => {
|
|
6
37
|
if (!isBotItem(item, ["kilo-code-bot"], ["pr-comment"]))
|
|
7
38
|
return null;
|
|
8
39
|
const body = item.body.trim();
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const listed = report[2].trimEnd().split("\n").length;
|
|
15
|
-
if (listed !== Number(report[1]))
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
40
|
+
if (BILLING.test(body)) {
|
|
41
|
+
return { autoResolve: true, suppress: true, reason: "Kilo Code out-of-credits notice" };
|
|
42
|
+
}
|
|
43
|
+
if (!PLAIN_STATUS.test(body) && !isMarkedReport(body) && !isConsistentReport(body)) {
|
|
44
|
+
return null;
|
|
18
45
|
}
|
|
19
46
|
return { autoResolve: true, suppress: true, reason: "Kilo Code no-issues report" };
|
|
20
47
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isBotItem } from "./author.mjs";
|
|
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\/
|
|
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\/[^\s)]*\) to continue using Sourcery ✨$/;
|
|
3
3
|
const RATE_LIMIT = /^Sorry @[A-Za-z0-9-]+, you have reached your weekly rate limit for Sourcery\. Please try again later\.$/;
|
|
4
4
|
/** Complete Sourcery private-repo upsell and weekly rate-limit notices. */
|
|
5
5
|
const rule = (item) => {
|
package/bin/cli/arg-flags.mjs
CHANGED
|
@@ -8,13 +8,11 @@ export const FLAGS_WITH_VALUES = new Set([
|
|
|
8
8
|
"--ready-delay",
|
|
9
9
|
"--stall-timeout",
|
|
10
10
|
"--instructions",
|
|
11
|
-
"--poll-mode",
|
|
12
11
|
"--require-sha",
|
|
13
12
|
"--message",
|
|
14
13
|
"--description",
|
|
15
14
|
"--body-file",
|
|
16
15
|
"--file",
|
|
17
|
-
"--stack",
|
|
18
16
|
"--thread-id",
|
|
19
17
|
"--resolve-thread-ids",
|
|
20
18
|
"--reply-thread-ids",
|
|
@@ -35,7 +33,6 @@ export const FLAGS_WITH_VALUES = new Set([
|
|
|
35
33
|
// numeric value to be misidentified as the PR number.
|
|
36
34
|
export const BOOLEAN_FLAGS = new Set([
|
|
37
35
|
"--no-auto-mark-ready",
|
|
38
|
-
"--no-auto-cancel-actionable",
|
|
39
36
|
"--quiet-status",
|
|
40
37
|
"--no-quiet-status",
|
|
41
38
|
"--until-terminal",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function handleClean(args: string[]
|
|
1
|
+
export declare function handleClean(args: string[]): Promise<void>;
|
|
@@ -2,10 +2,10 @@ import { runClean } from "../commands/clean.mjs";
|
|
|
2
2
|
import { EXIT } from "../exit-codes.mjs";
|
|
3
3
|
import { USAGE } from "./help.mjs";
|
|
4
4
|
import { formatCleanResult } from "./formatters.mjs";
|
|
5
|
-
import { rejectInvalidPollModeFlag, runInPollModeScope } from "./event-state-scope.mjs";
|
|
6
5
|
const CLEAN_VARIANTS = new Set(["pr", "branch", "current", "repo", "all"]);
|
|
7
|
-
export async function handleClean(args
|
|
8
|
-
const
|
|
6
|
+
export async function handleClean(args) {
|
|
7
|
+
const command = "admin clean";
|
|
8
|
+
const usage = USAGE["admin clean"];
|
|
9
9
|
const variant = args[0];
|
|
10
10
|
if (!variant || !CLEAN_VARIANTS.has(variant)) {
|
|
11
11
|
process.stderr.write(`${usage}\n`);
|
|
@@ -18,8 +18,6 @@ export async function handleClean(args, command = "admin clean") {
|
|
|
18
18
|
continue;
|
|
19
19
|
if (a === "--dry-run" || a === "--format" || a.startsWith("--format="))
|
|
20
20
|
continue;
|
|
21
|
-
if (a === "--poll-mode" || a.startsWith("--poll-mode="))
|
|
22
|
-
continue;
|
|
23
21
|
process.stderr.write(`pr-shepherd: ${command}: unknown flag: "${a}"\n`);
|
|
24
22
|
process.exitCode = EXIT.USAGE;
|
|
25
23
|
return;
|
|
@@ -38,21 +36,14 @@ export async function handleClean(args, command = "admin clean") {
|
|
|
38
36
|
process.exitCode = EXIT.USAGE;
|
|
39
37
|
return;
|
|
40
38
|
}
|
|
41
|
-
if (rejectInvalidPollModeFlag(rest, command))
|
|
42
|
-
return;
|
|
43
39
|
const jsonOut = formatValue === "json";
|
|
44
40
|
const dryRun = rest.includes("--dry-run");
|
|
45
|
-
// Skip values consumed by --format <value>
|
|
41
|
+
// Skip values consumed by --format <value> so they aren't positionals.
|
|
46
42
|
const flagConsumedIndices = new Set();
|
|
47
43
|
if (fmtIdx !== -1 && fmtIdx + 1 < rest.length && !rest[fmtIdx + 1].startsWith("--")) {
|
|
48
44
|
flagConsumedIndices.add(fmtIdx);
|
|
49
45
|
flagConsumedIndices.add(fmtIdx + 1);
|
|
50
46
|
}
|
|
51
|
-
const pollModeIdx = rest.indexOf("--poll-mode");
|
|
52
|
-
if (pollModeIdx !== -1) {
|
|
53
|
-
flagConsumedIndices.add(pollModeIdx);
|
|
54
|
-
flagConsumedIndices.add(pollModeIdx + 1);
|
|
55
|
-
}
|
|
56
47
|
const positionals = rest.filter((a, i) => !flagConsumedIndices.has(i) && !a.startsWith("--"));
|
|
57
48
|
if (positionals.length > 1) {
|
|
58
49
|
process.stderr.write(`pr-shepherd: ${command}: too many positional arguments (expected at most 1, got ${positionals.length})\n`);
|
|
@@ -60,8 +51,7 @@ export async function handleClean(args, command = "admin clean") {
|
|
|
60
51
|
return;
|
|
61
52
|
}
|
|
62
53
|
const value = positionals[0];
|
|
63
|
-
|
|
64
|
-
const result = await runInPollModeScope(rest, () => runClean({ variant: variant, value, dryRun }));
|
|
54
|
+
const result = await runClean({ variant: variant, value, dryRun });
|
|
65
55
|
if (!result.ok) {
|
|
66
56
|
process.stderr.write(`pr-shepherd: ${command}: ${result.error}\n`);
|
|
67
57
|
process.exitCode = EXIT.SOFTWARE;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
export declare const DEFAULT_POLL_FLAGS_WITH_VALUES: Set<string>;
|
|
2
|
+
export declare const DEFAULT_POLL_BOOLEAN_FLAGS: Set<string>;
|
|
1
3
|
export declare function isDefaultPollInvocation(subcommand: string | undefined): boolean;
|
|
2
4
|
export declare function validateDefaultPollArgs(args: string[]): boolean;
|
package/bin/cli/default-poll.mjs
CHANGED
|
@@ -2,22 +2,20 @@ import { EXIT } from "../exit-codes.mjs";
|
|
|
2
2
|
import { parsePrNumber } from "./args.mjs";
|
|
3
3
|
import { validateDefaultArgs } from "./validate-default-args.mjs";
|
|
4
4
|
import { USAGE } from "./help.mjs";
|
|
5
|
-
|
|
5
|
+
import { isRemovedFlag, rejectRemovedFlags } from "./removed-flags.mjs";
|
|
6
|
+
export const DEFAULT_POLL_FLAGS_WITH_VALUES = new Set([
|
|
6
7
|
"--transport",
|
|
7
8
|
"--format",
|
|
8
9
|
"--ready-delay",
|
|
9
10
|
"--stall-timeout",
|
|
10
11
|
"--instructions",
|
|
11
|
-
"--poll-mode",
|
|
12
12
|
"--interval",
|
|
13
13
|
"--timeout",
|
|
14
14
|
"--debounce",
|
|
15
|
-
"--stack",
|
|
16
15
|
]);
|
|
17
|
-
const DEFAULT_POLL_BOOLEAN_FLAGS = new Set([
|
|
16
|
+
export const DEFAULT_POLL_BOOLEAN_FLAGS = new Set([
|
|
18
17
|
"--verbose",
|
|
19
18
|
"--no-auto-mark-ready",
|
|
20
|
-
"--no-auto-cancel-actionable",
|
|
21
19
|
"--quiet-status",
|
|
22
20
|
"--no-quiet-status",
|
|
23
21
|
"--until-terminal",
|
|
@@ -29,11 +27,16 @@ export function isDefaultPollInvocation(subcommand) {
|
|
|
29
27
|
return (subcommand === undefined || parsePrNumber(subcommand) !== null || isDefaultPollFlag(subcommand));
|
|
30
28
|
}
|
|
31
29
|
export function validateDefaultPollArgs(args) {
|
|
30
|
+
if (rejectRemovedFlags(args))
|
|
31
|
+
return false;
|
|
32
32
|
return validateDefaultArgs(args, DEFAULT_POLL_FLAGS_WITH_VALUES, DEFAULT_POLL_BOOLEAN_FLAGS, writeDefaultUsageError);
|
|
33
33
|
}
|
|
34
34
|
function isDefaultPollFlag(arg) {
|
|
35
35
|
const name = arg.split("=", 1)[0];
|
|
36
|
-
|
|
36
|
+
// A removed flag routes here so its usage error names the replacement.
|
|
37
|
+
return (DEFAULT_POLL_FLAGS_WITH_VALUES.has(name) ||
|
|
38
|
+
DEFAULT_POLL_BOOLEAN_FLAGS.has(arg) ||
|
|
39
|
+
isRemovedFlag(arg));
|
|
37
40
|
}
|
|
38
41
|
function writeDefaultUsageError(arg) {
|
|
39
42
|
process.stderr.write(`Unknown subcommand: ${arg}\n`);
|
package/bin/cli/formatters.d.mts
CHANGED
|
@@ -3,6 +3,5 @@ export { projectIterateLean, projectIterateVerbose } from "./iterate-lean.mts";
|
|
|
3
3
|
export { formatCleanResult } from "./clean-formatter.mts";
|
|
4
4
|
export { formatMarkFilesAsViewedResult } from "./mark-files-as-viewed-formatter.mts";
|
|
5
5
|
export { formatMutateResult } from "./mutate-formatter.mts";
|
|
6
|
-
import type {
|
|
7
|
-
export declare function formatSuggestionPatchesResult(result:
|
|
8
|
-
export declare function formatCommitSuggestionResult(result: CommitSuggestionResult): string;
|
|
6
|
+
import type { SuggestResult } from "../types.mts";
|
|
7
|
+
export declare function formatSuggestionPatchesResult(result: SuggestResult): string;
|
package/bin/cli/formatters.mjs
CHANGED
|
@@ -12,32 +12,6 @@ export function formatSuggestionPatchesResult(result) {
|
|
|
12
12
|
appendInstructions(lines, result.postActionInstructions);
|
|
13
13
|
return lines.join("\n");
|
|
14
14
|
}
|
|
15
|
-
export function formatCommitSuggestionResult(result) {
|
|
16
|
-
const lines = [];
|
|
17
|
-
const range = result.startLine === result.endLine
|
|
18
|
-
? `line ${result.startLine}`
|
|
19
|
-
: `lines ${result.startLine}–${result.endLine}`;
|
|
20
|
-
lines.push(`Suggestion from @${result.author} for PR #${result.pr} — thread ${result.threadId}:`);
|
|
21
|
-
lines.push(` repo: ${result.repo}`);
|
|
22
|
-
lines.push(` ${result.path} (${range})`);
|
|
23
|
-
if (result.patch) {
|
|
24
|
-
const fence = safeFence(result.patch);
|
|
25
|
-
lines.push("");
|
|
26
|
-
lines.push(`${fence}diff`);
|
|
27
|
-
lines.push(result.patch.trimEnd());
|
|
28
|
-
lines.push(fence);
|
|
29
|
-
}
|
|
30
|
-
lines.push("", "## Suggested commit message", "", result.commitMessage, "", result.commitBody);
|
|
31
|
-
if (result.postActionInstructions.length > 0) {
|
|
32
|
-
lines.push("");
|
|
33
|
-
lines.push("## Instructions");
|
|
34
|
-
lines.push("");
|
|
35
|
-
result.postActionInstructions.forEach((inst, i) => {
|
|
36
|
-
lines.push(`${i + 1}. ${inst}`);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return lines.join("\n");
|
|
40
|
-
}
|
|
41
15
|
function formatPatch(patch, heading) {
|
|
42
16
|
const range = patch.startLine === patch.endLine
|
|
43
17
|
? `line ${patch.startLine}`
|
package/bin/cli/handlers.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare function
|
|
2
|
-
|
|
1
|
+
export declare function handleSuggest(args: string[]): Promise<void>;
|
|
2
|
+
/** `iterate [PR...]`: one tick for one PR, or one aggregate tick for several. */
|
|
3
3
|
export declare function handleIterate(args: string[]): Promise<void>;
|
|
4
|
-
export declare function handleMarkFilesAsViewed(args: string[]
|
|
4
|
+
export declare function handleMarkFilesAsViewed(args: string[]): Promise<void>;
|
package/bin/cli/handlers.mjs
CHANGED
|
@@ -1,47 +1,27 @@
|
|
|
1
|
-
import { runCommitSuggestion } from "../commands/commit-suggestion.mjs";
|
|
2
1
|
import { runSuggestionPatches } from "../commands/suggestion-patches.mjs";
|
|
3
2
|
import { runMarkFilesAsViewed } from "../commands/mark-files-as-viewed.mjs";
|
|
4
|
-
import {
|
|
3
|
+
import { runIterateTick, runPollSummaryTick } from "../commands/tick.mjs";
|
|
5
4
|
import { loadConfig } from "../config/load.mjs";
|
|
6
5
|
import { EXIT } from "../exit-codes.mjs";
|
|
7
|
-
import { parseCommonArgs
|
|
6
|
+
import { parseCommonArgs } from "./args.mjs";
|
|
7
|
+
import { rejectUnknownFlags } from "./unknown-flags.mjs";
|
|
8
|
+
import { rejectRemovedFlags } from "./removed-flags.mjs";
|
|
8
9
|
import { USAGE } from "./help.mjs";
|
|
9
|
-
import {
|
|
10
|
+
import { formatSuggestionPatchesResult, formatMarkFilesAsViewedResult } from "./formatters.mjs";
|
|
10
11
|
import { parseSuggestionPatchGroups } from "./suggestion-patch-flags.mjs";
|
|
11
12
|
import { hasInvalidIterateFlags, parseIterateFlags } from "./iterate-flags.mjs";
|
|
12
13
|
import { emitIterateResult } from "./iterate-emitter.mjs";
|
|
14
|
+
import { emitPollSummaryResult } from "./poll-summary-emitter.mjs";
|
|
15
|
+
import { parsePollTargets, resolvePollTargets } from "./poll-targets.mjs";
|
|
13
16
|
import { parseMarkFilesAsViewedArgs } from "./mark-files-as-viewed-flags.mjs";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!threadId) {
|
|
18
|
-
process.stderr.write(`${USAGE[command]}\n`);
|
|
19
|
-
process.exitCode = EXIT.USAGE;
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const message = getFlag(extra, "--message") ?? undefined;
|
|
23
|
-
if (!message || message.trim() === "") {
|
|
24
|
-
process.stderr.write("--message is required and must be non-empty\n");
|
|
25
|
-
process.exitCode = EXIT.USAGE;
|
|
17
|
+
const SUGGEST_FLAGS = ["--thread-id", "--message", "--description"];
|
|
18
|
+
export async function handleSuggest(args) {
|
|
19
|
+
if (rejectUnknownFlags(args, "suggest", SUGGEST_FLAGS))
|
|
26
20
|
return;
|
|
27
|
-
}
|
|
28
|
-
const description = getFlag(extra, "--description") ?? undefined;
|
|
29
|
-
const result = await runCommitSuggestion({
|
|
30
|
-
...globalOpts,
|
|
31
|
-
prNumber,
|
|
32
|
-
threadId,
|
|
33
|
-
message,
|
|
34
|
-
description,
|
|
35
|
-
});
|
|
36
|
-
process.stdout.write(globalOpts.format === "json"
|
|
37
|
-
? `${JSON.stringify(result, null, 2)}\n`
|
|
38
|
-
: `${formatCommitSuggestionResult(result)}\n`);
|
|
39
|
-
}
|
|
40
|
-
export async function handleSuggestionPatches(args) {
|
|
41
21
|
const { prNumber, global: globalOpts, extra } = parseCommonArgs(args);
|
|
42
22
|
const parsed = parseSuggestionPatchGroups(extra);
|
|
43
23
|
if (!parsed.ok) {
|
|
44
|
-
process.stderr.write(`${parsed.error}\n${USAGE
|
|
24
|
+
process.stderr.write(`${parsed.error}\n${USAGE.suggest}\n`);
|
|
45
25
|
process.exitCode = EXIT.USAGE;
|
|
46
26
|
return;
|
|
47
27
|
}
|
|
@@ -54,21 +34,50 @@ export async function handleSuggestionPatches(args) {
|
|
|
54
34
|
? `${JSON.stringify(result, null, 2)}\n`
|
|
55
35
|
: `${formatSuggestionPatchesResult(result)}\n`);
|
|
56
36
|
}
|
|
37
|
+
const ITERATE_FLAGS = [
|
|
38
|
+
"--ready-delay",
|
|
39
|
+
"--stall-timeout",
|
|
40
|
+
"--no-auto-mark-ready",
|
|
41
|
+
"--merge",
|
|
42
|
+
"--instructions",
|
|
43
|
+
];
|
|
44
|
+
/** `iterate [PR...]`: one tick for one PR, or one aggregate tick for several. */
|
|
57
45
|
export async function handleIterate(args) {
|
|
46
|
+
if (rejectRemovedFlags(args) || rejectUnknownFlags(args, "iterate", ITERATE_FLAGS))
|
|
47
|
+
return;
|
|
48
|
+
const targets = parsePollTargets(args);
|
|
49
|
+
if (!targets)
|
|
50
|
+
return;
|
|
51
|
+
const resolved = targets.refs.length > 1 ? await resolvePollTargets(targets) : { prNumbers: [] };
|
|
52
|
+
// Duplicate references collapse to one PR, which takes the single-PR tick.
|
|
53
|
+
if (resolved.prNumbers.length > 1) {
|
|
54
|
+
const flags = parseIterateFlags(targets.extra, loadConfig());
|
|
55
|
+
if (hasInvalidIterateFlags(flags))
|
|
56
|
+
return;
|
|
57
|
+
const result = await runPollSummaryTick({
|
|
58
|
+
...targets.global,
|
|
59
|
+
...resolved,
|
|
60
|
+
readyDelaySeconds: flags.readyDelaySeconds,
|
|
61
|
+
stallTimeoutSeconds: flags.stallTimeoutSeconds,
|
|
62
|
+
noAutoMarkReady: flags.noAutoMarkReady,
|
|
63
|
+
merge: flags.merge,
|
|
64
|
+
instructions: flags.instructions ?? undefined,
|
|
65
|
+
});
|
|
66
|
+
emitPollSummaryResult(result, { format: targets.global.format });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
58
69
|
const { prNumber, global: globalOpts, extra } = parseCommonArgs(args);
|
|
59
70
|
const flags = parseIterateFlags(extra, loadConfig());
|
|
60
71
|
if (hasInvalidIterateFlags(flags))
|
|
61
72
|
return;
|
|
62
|
-
const result = await
|
|
73
|
+
const result = await runIterateTick({
|
|
63
74
|
...globalOpts,
|
|
64
75
|
prNumber,
|
|
65
76
|
readyDelaySeconds: flags.readyDelaySeconds,
|
|
66
77
|
stallTimeoutSeconds: flags.stallTimeoutSeconds,
|
|
67
78
|
noAutoMarkReady: flags.noAutoMarkReady,
|
|
68
|
-
noAutoCancelActionable: flags.noAutoCancelActionable,
|
|
69
79
|
merge: flags.merge,
|
|
70
80
|
instructions: flags.instructions ?? undefined,
|
|
71
|
-
pollMode: flags.pollMode ?? undefined,
|
|
72
81
|
});
|
|
73
82
|
emitIterateResult(result, {
|
|
74
83
|
format: globalOpts.format,
|
|
@@ -76,7 +85,8 @@ export async function handleIterate(args) {
|
|
|
76
85
|
readyDelaySuffix: flags.readyDelaySuffix ?? undefined,
|
|
77
86
|
});
|
|
78
87
|
}
|
|
79
|
-
export async function handleMarkFilesAsViewed(args
|
|
88
|
+
export async function handleMarkFilesAsViewed(args) {
|
|
89
|
+
const command = "apply files";
|
|
80
90
|
const { prNumber, global: globalOpts, extra } = parseCommonArgs(args);
|
|
81
91
|
const parsed = parseMarkFilesAsViewedArgs(extra);
|
|
82
92
|
if (!parsed.ok) {
|