pullfrog 0.1.35 → 0.1.37
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/dist/agents/reviewer.d.ts +1 -1
- package/dist/cli.mjs +602 -194
- package/dist/index.js +601 -193
- package/dist/internal.js +60 -64
- package/dist/mcp/checkout.d.ts +1 -0
- package/dist/mcp/review.d.ts +37 -3
- package/dist/mcp/shared.d.ts +3 -1
- package/dist/toolState.d.ts +2 -2
- package/dist/utils/buildPullfrogFooter.d.ts +1 -1
- package/dist/utils/changeImpact.d.ts +18 -0
- package/dist/utils/diffCoverage.d.ts +0 -5
- package/package.json +1 -1
package/dist/internal.js
CHANGED
|
@@ -326,7 +326,7 @@ var providers = {
|
|
|
326
326
|
envVars: ["OPENCODE_API_KEY"],
|
|
327
327
|
models: {
|
|
328
328
|
"glm-5.1": {
|
|
329
|
-
displayName: "GLM 5.
|
|
329
|
+
displayName: "GLM 5.2",
|
|
330
330
|
resolve: "opencode-go/glm-5.2",
|
|
331
331
|
openRouterResolve: "openrouter/z-ai/glm-5.2",
|
|
332
332
|
preferred: true
|
|
@@ -868,20 +868,15 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
868
868
|
- **immediately** call \`${t("resolve_review_thread")}\` with that thread's \`thread=\` value as \`thread_id\`. Resolve every thread where you (a) made the requested code change in full \u2014 partial fixes leave the thread open \u2014 OR (b) replied with a substantive answer the user explicitly asked for. Do NOT resolve threads where you pushed back on the request and the disagreement is unresolved; leave those open for the human to mediate.
|
|
869
869
|
- call \`${t("report_progress")}\` with a brief summary`
|
|
870
870
|
},
|
|
871
|
-
// Review and IncrementalReview
|
|
872
|
-
//
|
|
873
|
-
//
|
|
874
|
-
// high-stakes-subsystem touches — and when it fires, ALL lenses must
|
|
875
|
-
// dispatch in a single assistant turn or the parallelism win disappears.
|
|
876
|
-
// We never dispatch exactly one lens: a single lens is just a worse,
|
|
877
|
-
// slower version of doing the work yourself.
|
|
871
|
+
// Review and IncrementalReview route the minimum reviewfrog specialists
|
|
872
|
+
// needed to cover unresolved, disposition-changing hypotheses. Most runs
|
|
873
|
+
// use zero or one; multiple orthogonal hypotheses dispatch in parallel.
|
|
878
874
|
//
|
|
879
875
|
// Build mode self-review is a different problem shape: the orchestrator
|
|
880
876
|
// wrote the code, so bias-mitigation comes from delegating to one
|
|
881
877
|
// fresh-eyes subagent that doesn't share the implementation context. A
|
|
882
|
-
// single subagent there is appropriate
|
|
883
|
-
// the
|
|
884
|
-
// perspectives is what's being purchased.
|
|
878
|
+
// single subagent there is appropriate. Review-mode specialist routing
|
|
879
|
+
// instead scales with the unresolved hypotheses in someone else's diff.
|
|
885
880
|
//
|
|
886
881
|
// Severity categorization is split across two surfaces: the opening
|
|
887
882
|
// callout (CAUTION/IMPORTANT/ℹ️/✅) sets the review's overall tier, and
|
|
@@ -896,11 +891,11 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
896
891
|
|
|
897
892
|
1. **task list**: create your task list for this run as your first action.
|
|
898
893
|
|
|
899
|
-
2. **checkout**: call \`${t("checkout_pr")}\` \u2014 this returns PR metadata and a \`
|
|
894
|
+
2. **checkout**: call \`${t("checkout_pr")}\` \u2014 this returns PR metadata, a \`diffPath\`, and a supplemental \`impactPath\` when change-impact extraction is enabled. read the complete raw diff end-to-end, beginning with the TOC and using its file line ranges as your coverage checklist. only after that, use \`impactPath\` as an explicitly incomplete list of reference leads; it never replaces raw-diff reading or establishes coverage.
|
|
900
895
|
|
|
901
896
|
3. **triage**: orient yourself on the PR \u2014 identify *what kind of thing this is* (domain it touches, seams it crosses, external contracts it depends on, user-facing surfaces it changes). pull as much context as you need to render a confident, well-grounded review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths, fetch related GitHub state. **you are the synthesizer** \u2014 never delegate understanding to subagents.
|
|
902
897
|
|
|
903
|
-
if the PR is **genuinely trivial**, skip
|
|
898
|
+
if the PR is **genuinely trivial**, skip specialists entirely and submit a \`No new issues found.\` review per step 7.
|
|
904
899
|
|
|
905
900
|
"Genuinely trivial" (skip):
|
|
906
901
|
- single-word doc typo, whitespace/format-only, comment-only across any number of files
|
|
@@ -919,22 +914,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
919
914
|
- any "typo fix" in user-facing copy that changes meaning ("approved" \u2192 "denied")
|
|
920
915
|
- mixed diffs where a semantic 1-liner is buried in whitespace/formatting changes
|
|
921
916
|
|
|
922
|
-
4. **
|
|
917
|
+
4. **specialist decision \u2014 minimum hypothesis coverage**.
|
|
923
918
|
|
|
924
|
-
|
|
919
|
+
After full-diff coverage and triage, identify the load-bearing questions you still cannot resolve confidently yourself. A specialist hypothesis is load-bearing only when its answer could yield an actionable finding that changes the review disposition and warrants independent investigation, and falsifiable only when the specialist can return evidence that supports or refutes it. Generic requests for extra confidence, polish, or "another look" do not qualify.
|
|
925
920
|
|
|
926
|
-
|
|
927
|
-
-
|
|
928
|
-
-
|
|
929
|
-
- parallel-
|
|
921
|
+
Route the **minimum number of \`${REVIEWER_AGENT_NAME}\` specialists** needed to cover those unresolved hypotheses. Most reviews need **0 or 1**:
|
|
922
|
+
- dispatch 0 when you can resolve every disposition-changing question directly
|
|
923
|
+
- dispatch 1 when exactly one falsifiable, load-bearing hypothesis warrants independent investigation
|
|
924
|
+
- dispatch 2+ in parallel when multiple orthogonal load-bearing hypotheses remain, or when the user explicitly requests an exhaustive or multi-angle review
|
|
930
925
|
|
|
931
|
-
**
|
|
926
|
+
**There is NO one-specialist cap or fixed maximum.** Cover every orthogonal load-bearing hypothesis that remains; do not collapse multiple real questions into one broad prompt just to reduce the count. There is also no file-count, line-count, schema, quota, or hard-budget threshold \u2014 diff size is not a proxy for review uncertainty.
|
|
932
927
|
|
|
933
|
-
|
|
928
|
+
The primary reviewer remains responsible for reading the complete raw diff, investigating surrounding code, validating every returned finding, and synthesizing the final review. Specialist reads supplement that work; they never replace it or satisfy the primary's diff-coverage obligation.
|
|
929
|
+
|
|
930
|
+
Specialist hypotheses can draw on two kinds of framing:
|
|
934
931
|
- **themed lenses** \u2014 a perspective applied across the whole diff (correctness, security, user-journey, performance, etc.).
|
|
935
932
|
- **subsystem lenses** \u2014 a domain-scoped frame for high-stakes subsystems the PR touches (e.g. "the auth lens", "the billing lens", "the schema-migration lens"). **for high-stakes domains, lead with the subsystem lens rather than the generic themed equivalent** \u2014 "billing-subsystem" outperforms "correctness on billing code" because the framing primes the subagent to remember domain-specific failure modes (double-charges, refund races, currency rounding, dispute flows) the generic lens misses.
|
|
936
933
|
|
|
937
|
-
starter menu (combine, omit, or invent your own):
|
|
934
|
+
starter menu for identifying hypotheses (combine, omit, or invent your own; do not dispatch a bare menu label without a falsifiable question):
|
|
938
935
|
- **correctness & invariants** \u2014 bugs, races, error handling, edge cases, state-machine boundaries
|
|
939
936
|
- **impact** \u2014 stale references in code/tests/docs/configs/UI after rename/remove
|
|
940
937
|
- **research-validated assumptions** \u2014 third-party API contracts, SDK semantics, framework directives, version-gated behavior. **only pick when the PR's correctness depends on the contract behaving a specific way** \u2014 not when the API is merely used. The bar is "if the third-party contract differs from what the diff assumes, the PR is incorrect." When dispatched, the subagent must verify load-bearing claims via web search and quote source URLs.
|
|
@@ -949,30 +946,27 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
949
946
|
|
|
950
947
|
The only subagent type is \`${REVIEWER_AGENT_NAME}\` \u2014 used for lens judgment work ("is this safe / correct / well-tested?"), runs on a mid-tier model.
|
|
951
948
|
|
|
952
|
-
5. **
|
|
953
|
-
|
|
954
|
-
\u26A0\uFE0F CRITICAL \u2014 PARALLELISM IS THE ONLY REASON LENSES EXIST. \u26A0\uFE0F
|
|
955
|
-
The default tool-call behavior of Claude Code (and most agent runtimes) is **serial dispatch**: emit one Task call, await result, emit next, await, etc. This collapses your fan-out into a sequential review where each lens adds N \xD7 (orchestrator-think-time + lens-execution-time) to wall time. **YOU MUST OVERRIDE THIS DEFAULT.** Emit ALL of your Task tool_use blocks in the SAME assistant message, BEFORE you read ANY result from ANY of them. If you find yourself emitting one Task call, then thinking about the result, then emitting another \u2014 STOP and re-issue them all together. The whole point of going multi-lens is the wall-clock speedup from parallel execution; serial dispatch defeats it entirely.
|
|
949
|
+
5. **dispatch specialists (only if step 4 found unresolved hypotheses)**: dispatch one \`${REVIEWER_AGENT_NAME}\` for one hypothesis. For 2+ hypotheses, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result so the investigations run in parallel rather than serially.
|
|
956
950
|
|
|
957
|
-
\u2705 Right pattern: one assistant turn with N Task tool_use blocks \u2192 wait \u2192 N results arrive together \u2192 aggregate.
|
|
958
|
-
\u274C Wrong pattern:
|
|
951
|
+
\u2705 Right multi-specialist pattern: one assistant turn with N Task tool_use blocks \u2192 wait \u2192 N results arrive together \u2192 aggregate.
|
|
952
|
+
\u274C Wrong multi-specialist pattern: Task(hypothesis A) \u2192 wait for A \u2192 Task(hypothesis B).
|
|
959
953
|
|
|
960
954
|
You can also include your own \`read\` / \`grep\` / \`webfetch\` calls in the SAME turn as the parallel \`${REVIEWER_AGENT_NAME}\` dispatches \u2014 concurrent context-pulling on the orchestrator side runs in parallel with the lens fan-out and costs zero extra wall time.
|
|
961
955
|
|
|
962
|
-
if a
|
|
956
|
+
if a specialist errors out, times out, or returns nothing usable, retry it once with the same hypothesis. if it still fails, attempt to resolve the hypothesis yourself; if it remains disposition-changing and unresolved, surface the limitation and do not approve. each specialist gets:
|
|
963
957
|
- **the absolute \`diffPath\` (and \`incrementalDiffPath\` if available) from step 2's \`${t("checkout_pr")}\` return, named verbatim in the dispatch prompt** (e.g. \`diffPath: /tmp/pullfrog-XXXX/pr-NNN-SHA.diff\`). the reviewer's baked-in system prompt selects its FIRST action on this token \u2014 paraphrasing ("review the diff", "look at this PR") sends it down the \`git diff origin/<base>\` fallback, which fails on shallow GHA checkouts. the subagent \`read\`s those files for scope; it must NOT re-derive the diff via \`git diff\` (bare \`git diff origin/<base>\` is symmetric and pulls in the inverse of any commits that landed on \`<base>\` since the branch forked \u2014 pure noise, and the git tool rejects it). reading and codebase exploration are still its job.
|
|
964
|
-
- **
|
|
965
|
-
- **a Task \`description\` set to
|
|
958
|
+
- **exactly one falsifiable hypothesis with explicit scope boundaries** \u2014 ask for evidence that supports or refutes it, never a broad "review for X, Y, and Z" prompt
|
|
959
|
+
- **a Task \`description\` set to a short hypothesis label** (e.g. \`"webhook-replay"\`, \`"billing-rounding"\`) \u2014 the harness reads this field to label the subagent's log lines so parallel runs can be told apart in CI output. without it, every subagent shows up as \`subagent#N\`.
|
|
966
960
|
- if the lens touches external contracts, instruct the subagent to verify load-bearing claims via web search rather than trust training data, and to quote source URLs in its reasoning. action runs are non-interactive \u2014 there's no human in the loop to catch "I'm pretty sure Stripe does X."
|
|
967
|
-
- ask the subagent to report findings with file paths and NEW line numbers from the diff so you can anchor
|
|
961
|
+
- ask the subagent to report findings with file paths and NEW line numbers from the diff so you can validate and anchor them. you must still read the complete diff yourself.
|
|
968
962
|
|
|
969
963
|
delegation discipline:
|
|
970
|
-
- do NOT summarize the PR for them (biases toward a validation frame)
|
|
964
|
+
- do NOT summarize the PR for them (a lossy summary biases toward a validation frame; the raw diff is the source)
|
|
971
965
|
- do NOT hand them a curated reading list (let them discover scope)
|
|
972
966
|
- do NOT pre-shape their output with a finding schema
|
|
973
967
|
- do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)
|
|
974
968
|
|
|
975
|
-
6. **aggregate & draft**: when
|
|
969
|
+
6. **aggregate & draft**: when specialist results land, merge findings; de-dup overlaps (two specialists catching the same issue = higher-confidence signal); trace each finding yourself before accepting it. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the PR (heuristic: if the finding's root cause lives in lines this PR added or modified, it's in scope; otherwise drop unless the PR plausibly introduced or amplified the regression), and anything not actionable. also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or worse, degrades elegance to nominally improve correctness) makes the codebase worse, not better.
|
|
976
970
|
|
|
977
971
|
**Hunt for non-anchored concerns before drafting.** After collecting your anchored findings, deliberately scan for concerns that have no specific line to point at \u2014 typically: deletion / cleanup plans for code the diff replaces or shadows; rollout sequencing (what happens to in-flight state during deploy / revert?); coverage gaps the diff implies but doesn't add; scope questions that only the human can answer (e.g. is the legacy path going away or is this a long-term dual track?); architectural risks the diff opens up that aren't a single-line bug. On substantial PRs (migrations, refactors, multi-file rewrites, version bumps that change runtime semantics), at least one such concern almost always exists; if you can't think of any, your bar is probably too high.
|
|
978
972
|
|
|
@@ -1006,10 +1000,10 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
1006
1000
|
|
|
1007
1001
|
${PR_SUMMARY_FORMAT}`
|
|
1008
1002
|
},
|
|
1009
|
-
// IncrementalReview shares Review's
|
|
1010
|
-
// format
|
|
1011
|
-
// prior
|
|
1012
|
-
// review" filter lives at aggregation time
|
|
1003
|
+
// IncrementalReview shares Review's minimum hypothesis-covering specialist
|
|
1004
|
+
// routing and body format, scoped to the incremental delta against the
|
|
1005
|
+
// prior Pullfrog review. The "issues must be NEW since the last Pullfrog
|
|
1006
|
+
// review" filter lives at aggregation time, NOT in the subagent
|
|
1013
1007
|
// prompt — pushing the filter into subagents matches the canonical anneal
|
|
1014
1008
|
// anti-pattern of "list known pre-existing failures — don't flag these"
|
|
1015
1009
|
// and suppresses signal on regressions the new commits amplified. A
|
|
@@ -1024,9 +1018,9 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
1024
1018
|
|
|
1025
1019
|
1. **task list**: create your task list for this run as your first action.
|
|
1026
1020
|
|
|
1027
|
-
2. **checkout**: call \`${t("checkout_pr")}\` \u2014 this returns PR metadata, \`diffPath\` (full diff),
|
|
1021
|
+
2. **checkout**: call \`${t("checkout_pr")}\` \u2014 this returns PR metadata, \`diffPath\` (full diff), \`incrementalDiffPath\` (changes since last reviewed version, if available), and a supplemental \`impactPath\` when change-impact extraction is enabled.
|
|
1028
1022
|
|
|
1029
|
-
3. **incremental scope**: if \`incrementalDiffPath\` is present, read it to see what changed since the last review. this is a range-diff that isolates the net changes, filtering out base branch noise.
|
|
1023
|
+
3. **incremental scope**: if \`incrementalDiffPath\` is present, read it FIRST to see what changed since the last review. this is a range-diff that isolates the net changes, filtering out base branch noise. then read the authoritative full diff end-to-end, beginning with the TOC and using its line ranges as your coverage checklist. if no incremental diff is present, start with the full-diff TOC, determine what changed since Pullfrog's most recent review, and complete the raw-diff read. only after establishing that authoritative scope and completing raw-diff coverage, use \`impactPath\` as an explicitly incomplete list of reference leads; it never replaces raw-diff reading or establishes coverage.
|
|
1030
1024
|
|
|
1031
1025
|
4. **prior feedback \u2014 read AND retire it**: fetch previous reviews via \`${t("list_pull_request_reviews")}\`, then call \`${t("get_review_comments")}\` on each prior Pullfrog review. Each thread renders as a section whose first line is a fenced tag \`comment author=<login> id=<fullDatabaseId> review=<reviewId> thread=<graphqlId>\`; section headers carry \`[RESOLVED]\` / \`[OUTDATED]\` when relevant. For every **open, Pullfrog-originated** thread, decide and act:
|
|
1032
1026
|
|
|
@@ -1040,49 +1034,48 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
1040
1034
|
|
|
1041
1035
|
5. **triage**: orient on the *incremental* changes \u2014 domain, seams, external contracts, user-facing surfaces. pull as much context as you need to render a confident review: read related files, grep for callers of changed symbols, check tests that exercise the touched paths. **you are the synthesizer.**
|
|
1042
1036
|
|
|
1043
|
-
if the incremental changes are **genuinely trivial**, skip
|
|
1037
|
+
if the incremental changes are **genuinely trivial**, skip specialists entirely and jump to step 10's non-substantive path (do NOT submit a review).
|
|
1044
1038
|
|
|
1045
1039
|
"Genuinely trivial" (skip): formatting/comment tweaks, import reordering, lockfile regen, mechanical rename of import paths, whitespace-only.
|
|
1046
1040
|
"Looks trivial but isn't" (do NOT skip \u2014 same anti-patterns as Review mode): 1-line changes to SQL/regex/auth/billing/permissions/signature-verification code; flipping feature-flag defaults or retry/timeout constants; money/tax/HTTP-method/redirect changes; tightening or loosening a comparison operator; mixed diffs with a semantic line buried in formatting.
|
|
1047
1041
|
When unsure, treat as non-trivial.
|
|
1048
1042
|
|
|
1049
|
-
6. **
|
|
1043
|
+
6. **specialist decision \u2014 minimum hypothesis coverage**.
|
|
1050
1044
|
|
|
1051
|
-
|
|
1045
|
+
After full-diff coverage and triage, identify the load-bearing questions about the incremental changes that you still cannot resolve confidently yourself. A specialist hypothesis is load-bearing only when its answer could yield an actionable new finding that changes the review disposition and warrants independent investigation, and falsifiable only when the specialist can return evidence that supports or refutes it. Generic requests for extra confidence, polish, or "another look" do not qualify.
|
|
1052
1046
|
|
|
1053
|
-
|
|
1054
|
-
-
|
|
1055
|
-
-
|
|
1056
|
-
- parallel-
|
|
1047
|
+
Route the **minimum number of \`${REVIEWER_AGENT_NAME}\` specialists** needed to cover those unresolved hypotheses. Most incremental reviews need **0 or 1**, especially thread-reply re-reviews:
|
|
1048
|
+
- dispatch 0 when you can resolve every disposition-changing question directly
|
|
1049
|
+
- dispatch 1 when exactly one falsifiable, load-bearing hypothesis warrants independent investigation
|
|
1050
|
+
- dispatch 2+ in parallel when multiple orthogonal load-bearing hypotheses remain, or when the user explicitly requests an exhaustive or multi-angle review
|
|
1057
1051
|
|
|
1058
|
-
**
|
|
1052
|
+
**There is NO one-specialist cap or fixed maximum.** Cover every orthogonal load-bearing hypothesis that remains; do not collapse multiple real questions into one broad prompt just to reduce the count. There is also no file-count, line-count, schema, quota, or hard-budget threshold \u2014 diff size is not a proxy for review uncertainty.
|
|
1059
1053
|
|
|
1060
|
-
|
|
1054
|
+
The primary reviewer remains responsible for reading the complete raw full diff plus the incremental diff, investigating surrounding code, validating every returned finding, and synthesizing the final review. Specialist reads supplement that work; they never replace it or satisfy the primary's diff-coverage obligation.
|
|
1061
1055
|
|
|
1062
|
-
|
|
1056
|
+
Specialist hypotheses can draw on Review mode's themed or subsystem framings, but every dispatch must turn the framing into one falsifiable question with explicit scope boundaries.
|
|
1063
1057
|
|
|
1064
|
-
|
|
1065
|
-
Default tool-call behavior is **serial dispatch**: emit one Task call, await result, emit next, await, etc. This collapses your fan-out into a sequential review where each lens adds N \xD7 (orchestrator-think-time + lens-execution-time) to wall time. **YOU MUST OVERRIDE THIS DEFAULT.** Emit ALL of your Task tool_use blocks in the SAME assistant message, BEFORE you read ANY result from ANY of them.
|
|
1058
|
+
7. **dispatch specialists (only if step 6 found unresolved hypotheses)**: dispatch one \`${REVIEWER_AGENT_NAME}\` for one hypothesis. For 2+ hypotheses, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result so the investigations run in parallel rather than serially.
|
|
1066
1059
|
|
|
1067
|
-
\u2705 Right pattern: one assistant turn with N Task tool_use blocks \u2192 wait \u2192 N results arrive together \u2192 aggregate.
|
|
1068
|
-
\u274C Wrong pattern:
|
|
1060
|
+
\u2705 Right multi-specialist pattern: one assistant turn with N Task tool_use blocks \u2192 wait \u2192 N results arrive together \u2192 aggregate.
|
|
1061
|
+
\u274C Wrong multi-specialist pattern: Task(hypothesis A) \u2192 wait for A \u2192 Task(hypothesis B).
|
|
1069
1062
|
|
|
1070
1063
|
You can also include your own \`read\` / \`grep\` / \`webfetch\` calls in the SAME turn as the parallel \`${REVIEWER_AGENT_NAME}\` dispatches.
|
|
1071
1064
|
|
|
1072
|
-
if a
|
|
1065
|
+
if a specialist errors out, times out, or returns nothing usable, retry it once with the same hypothesis. if it still fails, attempt to resolve the hypothesis yourself; if it remains disposition-changing and unresolved, surface the limitation and do not approve. each specialist gets:
|
|
1073
1066
|
- **the absolute diff path(s) from step 2's \`${t("checkout_pr")}\` return, named verbatim in the dispatch prompt.** when \`incrementalDiffPath\` is present, name BOTH (\`incrementalDiffPath: /tmp/.../pr-NNN-SHA-incremental.diff\` then \`diffPath: /tmp/.../pr-NNN-SHA.diff\`) \u2014 the reviewer's baked-in prompt reads incremental first and uses full for context; when only \`diffPath\` exists, name it alone. the subagent \`read\`s those files; it must NOT re-derive via \`git diff\` (bare \`git diff origin/<base>\` is symmetric and pulls in the inverse of base-branch progress \u2014 pure noise, and the git tool rejects it), and paraphrasing ("review the new commits") sends it down that fallback, which also fails on shallow GHA checkouts. do NOT tell them to skip pre-existing issues \u2014 that suppresses regressions the new commits amplified; the "issues must be NEW" filter lives at aggregation time (step 8), not in the subagent prompt.
|
|
1074
|
-
- **
|
|
1075
|
-
- **a Task \`description\` set to
|
|
1067
|
+
- **exactly one falsifiable hypothesis with explicit scope boundaries** \u2014 ask for evidence that supports or refutes it, never a broad "review for X, Y, and Z" prompt
|
|
1068
|
+
- **a Task \`description\` set to a short hypothesis label** \u2014 the harness reads this field to label log lines so parallel runs can be told apart.
|
|
1076
1069
|
- if the lens touches external contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs.
|
|
1077
|
-
- ask the subagent to report findings with file paths and NEW line numbers from the full PR diff so you can anchor
|
|
1070
|
+
- ask the subagent to report findings with file paths and NEW line numbers from the full PR diff so you can validate and anchor them. you must still read the complete incremental and full diff scope yourself.
|
|
1078
1071
|
|
|
1079
1072
|
delegation discipline:
|
|
1080
|
-
- do NOT summarize the changes for them (biases toward validation frame)
|
|
1073
|
+
- do NOT summarize the changes for them (a lossy summary biases toward a validation frame; the raw diff is the source)
|
|
1081
1074
|
- do NOT hand them a curated reading list (let them discover scope)
|
|
1082
1075
|
- do NOT pre-shape their output with a finding schema
|
|
1083
1076
|
- do NOT mention the other lenses (independence is the point)
|
|
1084
1077
|
|
|
1085
|
-
8. **aggregate, draft, self-critique**: merge findings (yours +
|
|
1078
|
+
8. **aggregate, draft, self-critique**: merge findings (yours + output from every specialist you dispatched); de-dup overlaps; trace each finding yourself. drop praise, style preferences, speculative/unverified claims, findings about pre-existing code unrelated to the new commits, anything not actionable, and anything that re-states prior review feedback (heuristic: if the finding's root cause lives in lines the *new commits* added or modified, it's in scope; otherwise drop). also drop **bloat-shaped findings** \u2014 proposed fixes that would add defensive checks for cases that can't happen, abstractions used once, comments restating obvious code, tests asserting tautologies, or "just-in-case" guards. subagents are fallible and bias toward recommending changes; the bar for an actionable inline comment is sound + correct + elegant. recommending a change that improves only one of the three (or degrades elegance to nominally improve correctness) makes the codebase worse, not better. To compute "lines the new commits added or modified": if \`incrementalDiffPath\` from step 2 is present, use it directly. Otherwise, take the prior Pullfrog review's \`commit_id\` (returned alongside each entry from \`${t("list_pull_request_reviews")}\` in step 4) and run \`git diff <prior-review-sha>..HEAD\` to isolate the lines added since that review.
|
|
1086
1079
|
|
|
1087
1080
|
**Hunt for non-anchored concerns before drafting.** After collecting your anchored findings, deliberately scan for concerns that have no specific line to point at \u2014 typically: deletion / cleanup plans for code the new commits replace or shadow; rollout sequencing (what happens to in-flight state during deploy / revert?); coverage gaps the new commits imply but don't add; scope questions that only the human can answer (e.g. is the legacy path going away or is this a long-term dual track?); architectural risks the new commits open up that aren't a single-line bug. On substantial incremental diffs (migrations, refactors, multi-file rewrites, version bumps that change runtime semantics), at least one such concern almost always exists; if you can't think of any, your bar is probably too high.
|
|
1088
1081
|
|
|
@@ -1179,7 +1172,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
1179
1172
|
|
|
1180
1173
|
1. **task list**: create your task list for this run as your first action.
|
|
1181
1174
|
|
|
1182
|
-
2. Analyze the task. For simple operations (labeling,
|
|
1175
|
+
2. Analyze the task. For simple operations (labeling, answering questions, running a single command), handle directly \u2014 but your answer only reaches the user through \`${t("report_progress")}\` (step 4); raw assistant text is discarded. If a standalone comment on the current issue/PR is the task's sole requested deliverable, create that comment directly and skip \`${t("report_progress")}\`.
|
|
1183
1176
|
|
|
1184
1177
|
3. For substantial work \u2014 code changes across multiple files, multi-step investigations:
|
|
1185
1178
|
- plan your approach before starting
|
|
@@ -1189,8 +1182,8 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
1189
1182
|
|
|
1190
1183
|
4. Finalize:
|
|
1191
1184
|
- if code changes were made, get them onto a pull request (new or existing) using ${signedCommits ? `\`${t("commit_changes")}\`` : `\`${t("push_branch")}\``} and \`${t("create_pull_request")}\` as needed. \`git status\` must be clean before you finish (see *SYSTEM* Git rules if this fails).
|
|
1192
|
-
- call \`${t("report_progress")}\` once with results \u2014 include exact tool errors if push or PR creation failed
|
|
1193
|
-
- if the task involved labeling
|
|
1185
|
+
- call \`${t("report_progress")}\` once with results \u2014 include exact tool errors if push or PR creation failed. skip this only when a standalone comment on the current target was the task's sole requested deliverable
|
|
1186
|
+
- if the task involved labeling or other GitHub operations, perform those directly`
|
|
1194
1187
|
}
|
|
1195
1188
|
];
|
|
1196
1189
|
}
|
|
@@ -1216,7 +1209,10 @@ function formatModelLabel(params) {
|
|
|
1216
1209
|
modelAliases.find((a) => a.resolve === params.model || a.openRouterResolve === params.model);
|
|
1217
1210
|
const displayName = alias?.displayName ?? params.model;
|
|
1218
1211
|
if (params.oss) {
|
|
1219
|
-
|
|
1212
|
+
const ossBase = `\`${displayName}\` (free via [Pullfrog for OSS](https://pullfrog.com/for-oss))`;
|
|
1213
|
+
if (params.clamped?.reason !== "oss") return ossBase;
|
|
1214
|
+
const configured = isAutoTier(params.clamped.from) ? "the intelligent tier" : `\`${resolveDisplayAlias(params.clamped.from)?.displayName ?? params.clamped.from}\``;
|
|
1215
|
+
return `${ossBase} (${configured} not used \u2014 the program covers this model; add its [provider key](https://docs.pullfrog.com/models) to run your pick)`;
|
|
1220
1216
|
}
|
|
1221
1217
|
const base = alias?.isFree ? `\`${displayName}\` (free)` : `\`${displayName}\``;
|
|
1222
1218
|
if (params.fallbackFrom) {
|
package/dist/mcp/checkout.d.ts
CHANGED
package/dist/mcp/review.d.ts
CHANGED
|
@@ -117,6 +117,31 @@ export declare function duplicateReviewDecision(params: {
|
|
|
117
117
|
} | undefined;
|
|
118
118
|
currentCheckoutSha: string | undefined;
|
|
119
119
|
}): DuplicateReviewDecision | null;
|
|
120
|
+
/**
|
|
121
|
+
* reject placeholder review bodies at the tool boundary.
|
|
122
|
+
*
|
|
123
|
+
* every successful create_pull_request_review call posts a permanent, publicly
|
|
124
|
+
* visible review that no Pullfrog tool can retract (`edit_issue_comment` 404s
|
|
125
|
+
* on a review id). a weak model that cannot get a real submission through
|
|
126
|
+
* eventually probes the tool with a minimal payload to test whether it works —
|
|
127
|
+
* `test`, `test body`, `placeholder`, `foo` — and that probe lands on a
|
|
128
|
+
* customer's PR. 27 such reviews reached 10 public repos between 2026-06-25 and
|
|
129
|
+
* 2026-07-18. the tool description already says "NEVER submit test or
|
|
130
|
+
* diagnostic reviews"; a prompt instruction is not a control.
|
|
131
|
+
*
|
|
132
|
+
* a body carrying a verdict marker is always accepted — `> ✅ No new issues
|
|
133
|
+
* found.` is a legitimate 24-char review. everything else must clear
|
|
134
|
+
* MIN_UNMARKED_BODY_LENGTH, which every observed placeholder fails and every
|
|
135
|
+
* observed real review passes.
|
|
136
|
+
*/
|
|
137
|
+
export declare function isDegenerateReviewBody(body: string): boolean;
|
|
138
|
+
/**
|
|
139
|
+
* consecutive no-op submissions tolerated before the tool stops explaining and
|
|
140
|
+
* starts refusing. kimi-k2.7-code ran ~15 rounds of empty submissions against a
|
|
141
|
+
* misleading skip reason before probing with a placeholder; a bounded budget
|
|
142
|
+
* converts that unbounded loop into a run-visible failure.
|
|
143
|
+
*/
|
|
144
|
+
export declare const MAX_CONSECUTIVE_NOOP_SUBMISSIONS = 4;
|
|
120
145
|
/**
|
|
121
146
|
* decide whether to skip a review submission before any network call.
|
|
122
147
|
*
|
|
@@ -126,6 +151,15 @@ export declare function duplicateReviewDecision(params: {
|
|
|
126
151
|
* 1. `!approved` + empty body/comments: agent's "no issues found" result.
|
|
127
152
|
* skipping preserves the agent's intent (nothing to post is a fine
|
|
128
153
|
* outcome for a review run) without a spurious 422.
|
|
154
|
+
* this shape is AMBIGUOUS: it is equally the signature of a weak model
|
|
155
|
+
* that meant to submit a real review but dropped `body` from the tool
|
|
156
|
+
* call. the skip reason must therefore describe the empty PAYLOAD, not
|
|
157
|
+
* assert an empty VERDICT — a reason reading "no issues found" reads as
|
|
158
|
+
* success to the agent, so it retries blind. kimi-k2.7-code looped ~15
|
|
159
|
+
* times against the old wording, then probed the tool with
|
|
160
|
+
* `body: "test"` to check whether it worked at all; that probe posted
|
|
161
|
+
* permanently to a customer PR (software-mansion/TypeGPU#2730), and
|
|
162
|
+
* 26 more like it across 10 repos. see wiki/review-approval.md.
|
|
129
163
|
* 2. `approved` + `!prApproveEnabled` + empty body/comments: the runtime
|
|
130
164
|
* downgrades APPROVE to COMMENT when prApproveEnabled is off, and the
|
|
131
165
|
* resulting empty-COMMENT is exactly the shape GitHub 422s. skipping
|
|
@@ -146,7 +180,7 @@ export declare function reviewSkipDecision(params: {
|
|
|
146
180
|
export declare function formatDroppedCommentsNote(dropped: DroppedComment[]): string;
|
|
147
181
|
export declare const CreatePullRequestReview: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
148
182
|
pull_number: number;
|
|
149
|
-
body
|
|
183
|
+
body: string;
|
|
150
184
|
approved?: boolean;
|
|
151
185
|
request_changes?: boolean;
|
|
152
186
|
commit_id?: string;
|
|
@@ -161,7 +195,7 @@ export declare const CreatePullRequestReview: import("arktype/internal/variants/
|
|
|
161
195
|
}, {}>;
|
|
162
196
|
export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("fastmcp").Tool<any, import("@standard-schema/spec").StandardSchemaV1<{
|
|
163
197
|
pull_number: number;
|
|
164
|
-
body
|
|
198
|
+
body: string;
|
|
165
199
|
approved?: boolean;
|
|
166
200
|
request_changes?: boolean;
|
|
167
201
|
commit_id?: string;
|
|
@@ -175,7 +209,7 @@ export declare function CreatePullRequestReviewTool(ctx: ToolContext): import("f
|
|
|
175
209
|
}[];
|
|
176
210
|
}, {
|
|
177
211
|
pull_number: number;
|
|
178
|
-
body
|
|
212
|
+
body: string;
|
|
179
213
|
approved?: boolean;
|
|
180
214
|
request_changes?: boolean;
|
|
181
215
|
commit_id?: string;
|
package/dist/mcp/shared.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ export declare function getHttpStatus(err: unknown): number | undefined;
|
|
|
9
9
|
* the single source of truth the subagent deny list derives from (see
|
|
10
10
|
* action/agents/subagentToolGates.ts). general-purpose execution tools (`git`,
|
|
11
11
|
* `shell`) are deliberately left unmarked: they can mutate but are allowed for
|
|
12
|
-
* subagents and gated by command-arg validation instead.
|
|
12
|
+
* subagents and gated by command-arg validation instead. `readOnlyHint` is
|
|
13
|
+
* independent scheduler metadata and must only follow an implementation audit;
|
|
14
|
+
* the absence of `mutates` does not imply that a tool is read-only.
|
|
13
15
|
*/
|
|
14
16
|
export type PullfrogTool = Tool<any, any> & {
|
|
15
17
|
mutates?: boolean;
|
package/dist/toolState.d.ts
CHANGED
|
@@ -93,6 +93,7 @@ export interface ToolState {
|
|
|
93
93
|
primaryRepoKey: string;
|
|
94
94
|
selectedMode?: string;
|
|
95
95
|
prepushFailureCount: number;
|
|
96
|
+
noopReviewSubmissions: number;
|
|
96
97
|
backgroundProcesses: Map<string, BackgroundProcess>;
|
|
97
98
|
browserDaemon?: BrowserDaemon | undefined;
|
|
98
99
|
review?: {
|
|
@@ -119,7 +120,6 @@ export interface ToolState {
|
|
|
119
120
|
lastProgressBody?: string;
|
|
120
121
|
wasUpdated?: boolean;
|
|
121
122
|
finalSummaryWritten?: boolean;
|
|
122
|
-
answerCommentPosted?: boolean;
|
|
123
123
|
existingPlanCommentId?: number;
|
|
124
124
|
previousPlanBody?: string;
|
|
125
125
|
summaryFilePath?: string;
|
|
@@ -140,7 +140,7 @@ export interface ToolState {
|
|
|
140
140
|
unselectedProxyDefault?: boolean | undefined;
|
|
141
141
|
modelClamped?: {
|
|
142
142
|
from: string;
|
|
143
|
-
reason: "card" | "noRouterPath";
|
|
143
|
+
reason: "card" | "noRouterPath" | "oss";
|
|
144
144
|
} | undefined;
|
|
145
145
|
shaPinned?: boolean | undefined;
|
|
146
146
|
oss?: boolean | undefined;
|
|
@@ -36,7 +36,7 @@ export interface BuildPullfrogFooterParams {
|
|
|
36
36
|
*/
|
|
37
37
|
clamped?: {
|
|
38
38
|
from: string;
|
|
39
|
-
reason: "card" | "noRouterPath";
|
|
39
|
+
reason: "card" | "noRouterPath" | "oss";
|
|
40
40
|
} | undefined;
|
|
41
41
|
/**
|
|
42
42
|
* true when the run used the default proxy model only because no model was
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RestEndpointMethodTypes } from "@octokit/rest";
|
|
2
|
+
import type { ToolContext } from "../mcp/server.ts";
|
|
3
|
+
type PullFile = RestEndpointMethodTypes["pulls"]["listFiles"]["response"]["data"][number];
|
|
4
|
+
type CreateChangeImpactParams = {
|
|
5
|
+
files: PullFile[];
|
|
6
|
+
pullNumber: number;
|
|
7
|
+
baseSha: string;
|
|
8
|
+
headSha: string;
|
|
9
|
+
};
|
|
10
|
+
type ChangeImpactResult = {
|
|
11
|
+
path: string;
|
|
12
|
+
candidateCount: number;
|
|
13
|
+
renderedAtomCount: number;
|
|
14
|
+
referenceCount: number;
|
|
15
|
+
bytes: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function createChangeImpactArtifact(ctx: ToolContext, params: CreateChangeImpactParams): Promise<ChangeImpactResult>;
|
|
18
|
+
export {};
|
|
@@ -31,7 +31,6 @@ export type DiffCoverageState = {
|
|
|
31
31
|
tocEntries: DiffTocEntry[];
|
|
32
32
|
coveredRanges: DiffLineRange[];
|
|
33
33
|
coveragePreflightRan: boolean;
|
|
34
|
-
lastBreakdown?: string | undefined;
|
|
35
34
|
};
|
|
36
35
|
export declare function countLines(params: {
|
|
37
36
|
content: string;
|
|
@@ -54,10 +53,6 @@ export declare function recordDiffReadFromToolUse(params: {
|
|
|
54
53
|
export declare function getDiffCoverageBreakdown(params: {
|
|
55
54
|
state: DiffCoverageState;
|
|
56
55
|
}): DiffCoverageBreakdown;
|
|
57
|
-
export declare function renderDiffCoverageBreakdown(params: {
|
|
58
|
-
diffPath: string;
|
|
59
|
-
breakdown: DiffCoverageBreakdown;
|
|
60
|
-
}): string;
|
|
61
56
|
export declare function countLinesInRanges(params: {
|
|
62
57
|
ranges: DiffLineRange[];
|
|
63
58
|
}): number;
|