pullfrog 0.1.102 → 0.1.103
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/cli.mjs +63 -22
- package/dist/index.js +61 -20
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal.js +13 -8
- package/dist/utils/diffCoverage.d.ts +9 -1
- package/dist/utils/generatedFiles.d.ts +10 -0
- package/dist/utils/rangeDiff.d.ts +3 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -112919,7 +112919,7 @@ var import_picocolors = __toESM(require_picocolors(), 1);
|
|
|
112919
112919
|
// package.json
|
|
112920
112920
|
var package_default = {
|
|
112921
112921
|
name: "pullfrog",
|
|
112922
|
-
version: "0.1.
|
|
112922
|
+
version: "0.1.103",
|
|
112923
112923
|
cliContractVersion: "1",
|
|
112924
112924
|
type: "module",
|
|
112925
112925
|
bin: {
|
|
@@ -117052,7 +117052,7 @@ var providers2 = {
|
|
|
117052
117052
|
},
|
|
117053
117053
|
// Zen's FREE open-weights preview (listed 2026-10-05), the eighth promo.
|
|
117054
117054
|
// Zen's docs: no training, 30-day retention for safety. hidden until a
|
|
117055
|
-
// published release carries the alias.
|
|
117055
|
+
// published release carries the alias; 0.1.102 is the first.
|
|
117056
117056
|
beam: {
|
|
117057
117057
|
displayName: "Beam",
|
|
117058
117058
|
description: "Preview, free for a limited time",
|
|
@@ -117060,8 +117060,7 @@ var providers2 = {
|
|
|
117060
117060
|
effort: ["low", "medium", "high", "xhigh", "max"],
|
|
117061
117061
|
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
117062
117062
|
// see the big-pickle note above (#1077).
|
|
117063
|
-
isFree: true
|
|
117064
|
-
hidden: true
|
|
117063
|
+
isFree: true
|
|
117065
117064
|
},
|
|
117066
117065
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
117067
117066
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
@@ -117434,10 +117433,12 @@ var providers2 = {
|
|
|
117434
117433
|
openRouterResolve: "openrouter/openai/gpt-5.4",
|
|
117435
117434
|
fallback: "openrouter/gpt-sol"
|
|
117436
117435
|
},
|
|
117436
|
+
// OpenRouter dropped `openai/o4-mini` on 2026-10-28 (only `:batch` remains) — folds stored pins to Luna.
|
|
117437
117437
|
"o4-mini": {
|
|
117438
117438
|
displayName: "O4 Mini",
|
|
117439
117439
|
resolve: "openrouter/openai/o4-mini",
|
|
117440
|
-
openRouterResolve: "openrouter/openai/o4-mini"
|
|
117440
|
+
openRouterResolve: "openrouter/openai/o4-mini",
|
|
117441
|
+
fallback: "openrouter/gpt-luna"
|
|
117441
117442
|
},
|
|
117442
117443
|
"gemini-pro": {
|
|
117443
117444
|
displayName: "Gemini Pro",
|
|
@@ -119564,7 +119565,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
119564
119565
|
|
|
119565
119566
|
2. **checkout**: call \`${t2("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.
|
|
119566
119567
|
|
|
119567
|
-
3. **incremental scope**: if \`incrementalDiffPath\` is present,
|
|
119568
|
+
3. **incremental scope**: if \`incrementalDiffPath\` is present, it is your coverage checklist: read it FIRST and in full. it is a range-diff of the net changes since the last review, free of base-branch noise. read only the \`diffPath\` ranges the delta needs for context \u2014 do not re-read the whole PR. 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 that, use \`impactPath\` as an explicitly incomplete list of reference leads; it never replaces raw-diff reading or establishes coverage.
|
|
119568
119569
|
|
|
119569
119570
|
4. **prior feedback \u2014 read AND retire it**: fetch previous reviews via \`${t2("list_pull_request_reviews")}\`, then call \`${t2("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:
|
|
119570
119571
|
|
|
@@ -119580,13 +119581,13 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
119580
119581
|
|
|
119581
119582
|
skip the deeper pass and jump to step 10's non-substantive path (do NOT submit a review) only when the incremental changes have **no behavioral surface at all** \u2014 formatting, comment tweaks, import reordering, lockfile regen, a mechanical rename of import paths. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
|
|
119582
119583
|
|
|
119583
|
-
6. **specialist decision**: after covering the
|
|
119584
|
+
6. **specialist decision**: after covering the delta, name the questions about the new changes that you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most incremental reviews need zero or one, especially thread-reply re-reviews; some need several.
|
|
119584
119585
|
|
|
119585
119586
|
**There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
|
|
119586
119587
|
|
|
119587
119588
|
frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent \u2014 the domain framing makes the subagent recall failure modes a generic lens misses.
|
|
119588
119589
|
|
|
119589
|
-
you remain the synthesizer: reading the
|
|
119590
|
+
you remain the synthesizer: reading the delta and the context it needs, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
|
|
119590
119591
|
|
|
119591
119592
|
7. **dispatch specialists (only if step 6 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn.
|
|
119592
119593
|
|
|
@@ -192058,7 +192059,9 @@ function parseDiffTocEntries(params) {
|
|
|
192058
192059
|
const lines = params.toc.split("\n");
|
|
192059
192060
|
const entries = [];
|
|
192060
192061
|
for (const line of lines) {
|
|
192061
|
-
const match3 = line.match(
|
|
192062
|
+
const match3 = line.match(
|
|
192063
|
+
/^- (.+) (?:→|->) lines (\d+)-(\d+)(?: · diff-[0-9a-f]+)?(?: · generated)?$/
|
|
192064
|
+
);
|
|
192062
192065
|
if (!match3) continue;
|
|
192063
192066
|
const startLine = Number.parseInt(match3[2], 10);
|
|
192064
192067
|
const endLine = Number.parseInt(match3[3], 10);
|
|
@@ -192067,11 +192070,24 @@ function parseDiffTocEntries(params) {
|
|
|
192067
192070
|
}
|
|
192068
192071
|
return entries;
|
|
192069
192072
|
}
|
|
192073
|
+
function parseIncrementalDiffEntries(params) {
|
|
192074
|
+
const lines = params.content.split("\n");
|
|
192075
|
+
const entries = [];
|
|
192076
|
+
for (const [index, line] of lines.entries()) {
|
|
192077
|
+
const match3 = line.match(/^@@ (.+)$|^[ +-]## (.+) ##$/);
|
|
192078
|
+
if (!match3) continue;
|
|
192079
|
+
const previous = entries.at(-1);
|
|
192080
|
+
if (previous) previous.endLine = index;
|
|
192081
|
+
const filename = (match3[1] ?? match3[2]).replace(/(: | \().*$/, "");
|
|
192082
|
+
entries.push({ filename, startLine: index + 1, endLine: lines.length });
|
|
192083
|
+
}
|
|
192084
|
+
return entries;
|
|
192085
|
+
}
|
|
192070
192086
|
function createDiffCoverageState(params) {
|
|
192071
192087
|
return {
|
|
192072
192088
|
diffPath: params.diffPath,
|
|
192073
192089
|
totalLines: params.totalLines,
|
|
192074
|
-
tocEntries:
|
|
192090
|
+
tocEntries: params.tocEntries,
|
|
192075
192091
|
coveredRanges: [],
|
|
192076
192092
|
// carry forward across checkout_pr refreshes so the nudge stays "once per
|
|
192077
192093
|
// review session". coveredRanges are intentionally not carried because
|
|
@@ -192286,6 +192302,9 @@ function readNumber(params) {
|
|
|
192286
192302
|
return void 0;
|
|
192287
192303
|
}
|
|
192288
192304
|
|
|
192305
|
+
// utils/generatedFiles.ts
|
|
192306
|
+
var GENERATED = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|Cargo\.lock|go\.sum|poetry\.lock|Pipfile\.lock|Gemfile\.lock|composer\.lock|.*\.snap|.*\.min\.(js|css)|.*\.map|.*\.lock|.*\.(pb\.go|_pb2\.py|g\.dart)|.*\.(generated|gen)\.[^/]+|.*\.(svg|png|jpe?g|gif|ico|webp|woff2?|ttf|pdf))$|(^|\/)(dist|generated|__generated__|node_modules|\.yarn|__snapshots__|coverage|\.next|\.turbo|drizzle\/meta)\//i;
|
|
192307
|
+
|
|
192289
192308
|
// utils/gitAuth.ts
|
|
192290
192309
|
import { execSync } from "node:child_process";
|
|
192291
192310
|
import { createHash as createHash2 } from "node:crypto";
|
|
@@ -193632,7 +193651,7 @@ function computeIncrementalDiff(params) {
|
|
|
193632
193651
|
"sh",
|
|
193633
193652
|
[
|
|
193634
193653
|
"-c",
|
|
193635
|
-
'old_base=$(git merge-base "$1" "origin/$2") && new_base=$(git merge-base "$3" "origin/$2") && git range-diff --no-color "$old_base..$(git commit-tree "$1^{tree}" -p "$old_base" -m x)" "$new_base..$(git commit-tree "$3^{tree}" -p "$new_base" -m x)"',
|
|
193654
|
+
'old_base=$(git merge-base "$1" "origin/$2") && new_base=$(git merge-base "$3" "origin/$2") && git range-diff --no-color --creation-factor=1000 "$old_base..$(git commit-tree "$1^{tree}" -p "$old_base" -m x)" "$new_base..$(git commit-tree "$3^{tree}" -p "$new_base" -m x)"',
|
|
193636
193655
|
"--",
|
|
193637
193656
|
params.beforeSha,
|
|
193638
193657
|
params.baseBranch,
|
|
@@ -193655,6 +193674,8 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
193655
193674
|
if (!raw2.trim()) return null;
|
|
193656
193675
|
if (/^\d+:\s+\w+\s+=\s+\d+:/m.test(raw2)) return null;
|
|
193657
193676
|
const beforeBuf = [];
|
|
193677
|
+
let lastOuterHdr = null;
|
|
193678
|
+
let outerHdrEmitted = true;
|
|
193658
193679
|
let lastFileHdr = null;
|
|
193659
193680
|
let lastHunkHdr = null;
|
|
193660
193681
|
let fileHdrEmitted = true;
|
|
@@ -193668,10 +193689,12 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
193668
193689
|
if (lastEmittedSeq >= 0 && line.seq > lastEmittedSeq + 1) out += (out ? "\n" : "") + "...";
|
|
193669
193690
|
out += (out ? "\n" : "") + line.prefix + raw2.slice(line.from, line.to);
|
|
193670
193691
|
lastEmittedSeq = line.seq;
|
|
193692
|
+
if (lastOuterHdr?.seq === line.seq) outerHdrEmitted = true;
|
|
193671
193693
|
if (lastFileHdr?.seq === line.seq) fileHdrEmitted = true;
|
|
193672
193694
|
if (lastHunkHdr?.seq === line.seq) hunkHdrEmitted = true;
|
|
193673
193695
|
}
|
|
193674
193696
|
function flushBefore() {
|
|
193697
|
+
if (lastOuterHdr && !outerHdrEmitted) emit2(lastOuterHdr);
|
|
193675
193698
|
if (lastFileHdr && !fileHdrEmitted) emit2(lastFileHdr);
|
|
193676
193699
|
if (lastHunkHdr && !hunkHdrEmitted) emit2(lastHunkHdr);
|
|
193677
193700
|
for (const line of beforeBuf) {
|
|
@@ -193687,7 +193710,17 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
193687
193710
|
cursor3 = lineEnd + 1;
|
|
193688
193711
|
continue;
|
|
193689
193712
|
}
|
|
193690
|
-
if (
|
|
193713
|
+
if (raw2.startsWith(" @@", cursor3)) {
|
|
193714
|
+
lastOuterHdr = { prefix: "", from: cursor3 + 4, to: lineEnd, seq };
|
|
193715
|
+
outerHdrEmitted = false;
|
|
193716
|
+
lastFileHdr = null;
|
|
193717
|
+
fileHdrEmitted = true;
|
|
193718
|
+
lastHunkHdr = null;
|
|
193719
|
+
hunkHdrEmitted = true;
|
|
193720
|
+
beforeBuf.length = 0;
|
|
193721
|
+
afterRemaining = 0;
|
|
193722
|
+
seq++;
|
|
193723
|
+
} else if (lineEnd - cursor3 >= 5 && raw2.startsWith(" ", cursor3)) {
|
|
193691
193724
|
const prefix = raw2[cursor3 + 4];
|
|
193692
193725
|
if (isDiffPrefix(prefix)) {
|
|
193693
193726
|
const contentPos = cursor3 + 5;
|
|
@@ -201132,8 +201165,9 @@ function formatFilesWithLineNumbers(files) {
|
|
|
201132
201165
|
const tocLines = [`## Files (${files.length})`];
|
|
201133
201166
|
for (const entry of tocEntries) {
|
|
201134
201167
|
const anchor2 = createHash3("sha256").update(entry.filename).digest("hex");
|
|
201168
|
+
const generated = GENERATED.test(entry.filename) ? " \xB7 generated" : "";
|
|
201135
201169
|
tocLines.push(
|
|
201136
|
-
`- ${entry.filename} \u2192 lines ${entry.startLine}-${entry.endLine} \xB7 diff-${anchor2}`
|
|
201170
|
+
`- ${entry.filename} \u2192 lines ${entry.startLine}-${entry.endLine} \xB7 diff-${anchor2}${generated}`
|
|
201137
201171
|
);
|
|
201138
201172
|
}
|
|
201139
201173
|
tocLines.push("");
|
|
@@ -201498,6 +201532,8 @@ function CheckoutPrTool(ctx) {
|
|
|
201498
201532
|
if (!checkoutSha) throw new Error("checkout completed without a resolved HEAD SHA");
|
|
201499
201533
|
const headShort = checkoutSha.slice(0, 7);
|
|
201500
201534
|
let incrementalDiffPath;
|
|
201535
|
+
let incrementalDiff;
|
|
201536
|
+
let incrementalEmpty = false;
|
|
201501
201537
|
let incrementalUnavailable = false;
|
|
201502
201538
|
let incrementalUnavailableReason;
|
|
201503
201539
|
if (primary.beforeSha) {
|
|
@@ -201513,9 +201549,12 @@ function CheckoutPrTool(ctx) {
|
|
|
201513
201549
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
201514
201550
|
);
|
|
201515
201551
|
writeFileSync11(incrementalDiffPath, incremental.diff);
|
|
201552
|
+
incrementalDiff = incremental.diff;
|
|
201516
201553
|
log2.info(
|
|
201517
201554
|
`\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
201518
201555
|
);
|
|
201556
|
+
} else if (incremental.status === "empty") {
|
|
201557
|
+
incrementalEmpty = true;
|
|
201519
201558
|
} else if (incremental.status === "unavailable") {
|
|
201520
201559
|
incrementalUnavailable = true;
|
|
201521
201560
|
incrementalUnavailableReason = incremental.reason;
|
|
@@ -201528,14 +201567,16 @@ ${diffPreview}`);
|
|
|
201528
201567
|
const diffPath = join17(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
201529
201568
|
writeFileSync11(diffPath, formatResult.content);
|
|
201530
201569
|
log2.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
201531
|
-
|
|
201532
|
-
|
|
201533
|
-
|
|
201534
|
-
|
|
201570
|
+
const coverageEntries = incrementalDiff ? parseIncrementalDiffEntries({ content: incrementalDiff }) : parseDiffTocEntries({ toc: formatResult.toc });
|
|
201571
|
+
primary.diffCoverage = incrementalEmpty ? void 0 : createDiffCoverageState({
|
|
201572
|
+
diffPath: incrementalDiffPath ?? diffPath,
|
|
201573
|
+
totalLines: countLines({ content: incrementalDiff ?? formatResult.content }),
|
|
201574
|
+
tocEntries: coverageEntries.filter((entry) => !GENERATED.test(entry.filename)),
|
|
201535
201575
|
previous: primary.diffCoverage
|
|
201536
201576
|
});
|
|
201577
|
+
const coverage = primary.diffCoverage;
|
|
201537
201578
|
log2.debug(
|
|
201538
|
-
`\xBB diff coverage initialized: diffPath=${diffPath}, totalLines=${
|
|
201579
|
+
coverage ? `\xBB diff coverage initialized: diffPath=${coverage.diffPath}, totalLines=${coverage.totalLines}, tocEntries=${coverage.tocEntries.length}` : "\xBB diff coverage skipped: no changes since the last reviewed version"
|
|
201539
201580
|
);
|
|
201540
201581
|
let impactPath;
|
|
201541
201582
|
if (process.env.PULLFROG_DISABLE_CHANGE_IMPACT === "1") {
|
|
@@ -201586,7 +201627,7 @@ ${diffPreview}`);
|
|
|
201586
201627
|
primary.commentableLinesByFile = cached4;
|
|
201587
201628
|
primary.commentableLinesPullNumber = pull_number;
|
|
201588
201629
|
primary.commentableLinesCheckoutSha = checkoutSha;
|
|
201589
|
-
const incrementalInstructions = incrementalDiffPath ? `IMPORTANT: incrementalDiffPath contains ONLY the changes since the last reviewed version (computed via range-diff)
|
|
201630
|
+
const incrementalInstructions = incrementalDiffPath ? `IMPORTANT: incrementalDiffPath contains ONLY the changes since the last reviewed version (computed via range-diff), and it \u2014 not diffPath's TOC \u2014 is this review's coverage checklist. read it FIRST and in full, then read only the diffPath ranges the delta needs for context. ` : incrementalEmpty ? `NOTE: the PR's net changes are identical to the last reviewed version (e.g. a rebase with no content change), so there is no new diff to cover. ` : incrementalUnavailable ? (
|
|
201590
201631
|
// say it FAILED rather than leaving the agent to infer "nothing changed"
|
|
201591
201632
|
// from an absent field. only suggest diff-tree when the previous
|
|
201592
201633
|
// revision is actually PRESENT locally — if the fetch never landed it,
|
|
@@ -201637,7 +201678,7 @@ ${diffPreview}`);
|
|
|
201637
201678
|
commitLogTruncated,
|
|
201638
201679
|
commitLogUnavailable,
|
|
201639
201680
|
hookWarning: checkoutResult.hookWarning,
|
|
201640
|
-
instructions: incrementalInstructions + `the diff file at diffPath contains a table of contents (TOC) at the top listing every changed file with its line range. use the TOC line ranges as your checklist and read specific files from the diff instead of reading the entire file. for example, if the TOC says "src/foo.ts \u2192 lines 5-42", read lines 5-42 from diffPath to see that file's changes. review files selectively based on relevance rather than reading everything sequentially. to inspect the PR's changed files, use diffPath \u2014 do NOT run \`git diff\` to re-derive what's already in diffPath. the formatted diff with line numbers is authoritative. if you ever do need a branch-vs-base diff via the git tool, use \`git diff --merge-base <base>\` (single call, includes uncommitted edits) or three-dot \`git diff <base>...HEAD\` (committed-only). bare \`<base>\` and two-dot \`<base>..HEAD\` are symmetric and pull in the inverse of every commit landed on \`<base>\` since the branch forked \u2014 the git tool will reject those forms when the divergence is detected. \`$(...)\` subshells are NOT expanded by the git tool. \`git log\` and \`git diff --stat\` are fine for commit-range overview, and \`git diff\` / \`git diff --cached\` are fine for inspecting *your own* uncommitted changes \u2014 but PR review content MUST come from diffPath. before your review is submitted, a one-time coverage pre-flight may error listing unread TOC regions. retry the same create_pull_request_review call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session. the local branch is 'localBranch' (pr-{number}), not the remote branch name. when pushing, omit branchName to use the current branch. do not use remoteBranch as a local branch name.` + impactInstructions + hookWarningInstructions + commitLogInstructions
|
|
201681
|
+
instructions: incrementalInstructions + `the diff file at diffPath contains a table of contents (TOC) at the top listing every changed file with its line range. absent incrementalDiffPath, use the TOC line ranges as your checklist (entries marked \`generated\` are off it) and read specific files from the diff instead of reading the entire file. for example, if the TOC says "src/foo.ts \u2192 lines 5-42", read lines 5-42 from diffPath to see that file's changes. review files selectively based on relevance rather than reading everything sequentially. to inspect the PR's changed files, use diffPath \u2014 do NOT run \`git diff\` to re-derive what's already in diffPath. the formatted diff with line numbers is authoritative. if you ever do need a branch-vs-base diff via the git tool, use \`git diff --merge-base <base>\` (single call, includes uncommitted edits) or three-dot \`git diff <base>...HEAD\` (committed-only). bare \`<base>\` and two-dot \`<base>..HEAD\` are symmetric and pull in the inverse of every commit landed on \`<base>\` since the branch forked \u2014 the git tool will reject those forms when the divergence is detected. \`$(...)\` subshells are NOT expanded by the git tool. \`git log\` and \`git diff --stat\` are fine for commit-range overview, and \`git diff\` / \`git diff --cached\` are fine for inspecting *your own* uncommitted changes \u2014 but PR review content MUST come from diffPath. before your review is submitted, a one-time coverage pre-flight may error listing unread TOC regions. retry the same create_pull_request_review call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session. the local branch is 'localBranch' (pr-{number}), not the remote branch name. when pushing, omit branchName to use the current branch. do not use remoteBranch as a local branch name.` + impactInstructions + hookWarningInstructions + commitLogInstructions
|
|
201641
201682
|
};
|
|
201642
201683
|
};
|
|
201643
201684
|
return tool({
|
|
@@ -209874,7 +209915,7 @@ function resolveTarget2(repo) {
|
|
|
209874
209915
|
return { owner: match3[1], repo: match3[2] };
|
|
209875
209916
|
}
|
|
209876
209917
|
function cliVersion() {
|
|
209877
|
-
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.
|
|
209918
|
+
const parts = /^(\d+)\.(\d+)\.(\d+)$/.exec("0.1.103");
|
|
209878
209919
|
if (!parts) return "0.0.0";
|
|
209879
209920
|
return `${Number(parts[1])}.${Number(parts[2])}.${Number(parts[3])}`;
|
|
209880
209921
|
}
|
|
@@ -210041,7 +210082,7 @@ async function runCli8(input2) {
|
|
|
210041
210082
|
}
|
|
210042
210083
|
|
|
210043
210084
|
// cli.ts
|
|
210044
|
-
var VERSION10 = "0.1.
|
|
210085
|
+
var VERSION10 = "0.1.103";
|
|
210045
210086
|
var bin = basename2(process.argv[1] || "");
|
|
210046
210087
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
210047
210088
|
var rawArgs = process.argv.slice(2);
|
package/dist/index.js
CHANGED
|
@@ -109128,7 +109128,7 @@ var providers = {
|
|
|
109128
109128
|
},
|
|
109129
109129
|
// Zen's FREE open-weights preview (listed 2026-10-05), the eighth promo.
|
|
109130
109130
|
// Zen's docs: no training, 30-day retention for safety. hidden until a
|
|
109131
|
-
// published release carries the alias.
|
|
109131
|
+
// published release carries the alias; 0.1.102 is the first.
|
|
109132
109132
|
beam: {
|
|
109133
109133
|
displayName: "Beam",
|
|
109134
109134
|
description: "Preview, free for a limited time",
|
|
@@ -109136,8 +109136,7 @@ var providers = {
|
|
|
109136
109136
|
effort: ["low", "medium", "high", "xhigh", "max"],
|
|
109137
109137
|
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
109138
109138
|
// see the big-pickle note above (#1077).
|
|
109139
|
-
isFree: true
|
|
109140
|
-
hidden: true
|
|
109139
|
+
isFree: true
|
|
109141
109140
|
},
|
|
109142
109141
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
109143
109142
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
@@ -109510,10 +109509,12 @@ var providers = {
|
|
|
109510
109509
|
openRouterResolve: "openrouter/openai/gpt-5.4",
|
|
109511
109510
|
fallback: "openrouter/gpt-sol"
|
|
109512
109511
|
},
|
|
109512
|
+
// OpenRouter dropped `openai/o4-mini` on 2026-10-28 (only `:batch` remains) — folds stored pins to Luna.
|
|
109513
109513
|
"o4-mini": {
|
|
109514
109514
|
displayName: "O4 Mini",
|
|
109515
109515
|
resolve: "openrouter/openai/o4-mini",
|
|
109516
|
-
openRouterResolve: "openrouter/openai/o4-mini"
|
|
109516
|
+
openRouterResolve: "openrouter/openai/o4-mini",
|
|
109517
|
+
fallback: "openrouter/gpt-luna"
|
|
109517
109518
|
},
|
|
109518
109519
|
"gemini-pro": {
|
|
109519
109520
|
displayName: "Gemini Pro",
|
|
@@ -112140,7 +112141,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
112140
112141
|
// package.json
|
|
112141
112142
|
var package_default = {
|
|
112142
112143
|
name: "pullfrog",
|
|
112143
|
-
version: "0.1.
|
|
112144
|
+
version: "0.1.103",
|
|
112144
112145
|
cliContractVersion: "1",
|
|
112145
112146
|
type: "module",
|
|
112146
112147
|
bin: {
|
|
@@ -113089,7 +113090,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
113089
113090
|
|
|
113090
113091
|
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.
|
|
113091
113092
|
|
|
113092
|
-
3. **incremental scope**: if \`incrementalDiffPath\` is present,
|
|
113093
|
+
3. **incremental scope**: if \`incrementalDiffPath\` is present, it is your coverage checklist: read it FIRST and in full. it is a range-diff of the net changes since the last review, free of base-branch noise. read only the \`diffPath\` ranges the delta needs for context \u2014 do not re-read the whole PR. 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 that, use \`impactPath\` as an explicitly incomplete list of reference leads; it never replaces raw-diff reading or establishes coverage.
|
|
113093
113094
|
|
|
113094
113095
|
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:
|
|
113095
113096
|
|
|
@@ -113105,13 +113106,13 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
113105
113106
|
|
|
113106
113107
|
skip the deeper pass and jump to step 10's non-substantive path (do NOT submit a review) only when the incremental changes have **no behavioral surface at all** \u2014 formatting, comment tweaks, import reordering, lockfile regen, a mechanical rename of import paths. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
|
|
113107
113108
|
|
|
113108
|
-
6. **specialist decision**: after covering the
|
|
113109
|
+
6. **specialist decision**: after covering the delta, name the questions about the new changes that you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most incremental reviews need zero or one, especially thread-reply re-reviews; some need several.
|
|
113109
113110
|
|
|
113110
113111
|
**There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
|
|
113111
113112
|
|
|
113112
113113
|
frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent \u2014 the domain framing makes the subagent recall failure modes a generic lens misses.
|
|
113113
113114
|
|
|
113114
|
-
you remain the synthesizer: reading the
|
|
113115
|
+
you remain the synthesizer: reading the delta and the context it needs, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
|
|
113115
113116
|
|
|
113116
113117
|
7. **dispatch specialists (only if step 6 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn.
|
|
113117
113118
|
|
|
@@ -188540,7 +188541,9 @@ function parseDiffTocEntries(params) {
|
|
|
188540
188541
|
const lines = params.toc.split("\n");
|
|
188541
188542
|
const entries = [];
|
|
188542
188543
|
for (const line of lines) {
|
|
188543
|
-
const match3 = line.match(
|
|
188544
|
+
const match3 = line.match(
|
|
188545
|
+
/^- (.+) (?:→|->) lines (\d+)-(\d+)(?: · diff-[0-9a-f]+)?(?: · generated)?$/
|
|
188546
|
+
);
|
|
188544
188547
|
if (!match3) continue;
|
|
188545
188548
|
const startLine = Number.parseInt(match3[2], 10);
|
|
188546
188549
|
const endLine = Number.parseInt(match3[3], 10);
|
|
@@ -188549,11 +188552,24 @@ function parseDiffTocEntries(params) {
|
|
|
188549
188552
|
}
|
|
188550
188553
|
return entries;
|
|
188551
188554
|
}
|
|
188555
|
+
function parseIncrementalDiffEntries(params) {
|
|
188556
|
+
const lines = params.content.split("\n");
|
|
188557
|
+
const entries = [];
|
|
188558
|
+
for (const [index, line] of lines.entries()) {
|
|
188559
|
+
const match3 = line.match(/^@@ (.+)$|^[ +-]## (.+) ##$/);
|
|
188560
|
+
if (!match3) continue;
|
|
188561
|
+
const previous = entries.at(-1);
|
|
188562
|
+
if (previous) previous.endLine = index;
|
|
188563
|
+
const filename = (match3[1] ?? match3[2]).replace(/(: | \().*$/, "");
|
|
188564
|
+
entries.push({ filename, startLine: index + 1, endLine: lines.length });
|
|
188565
|
+
}
|
|
188566
|
+
return entries;
|
|
188567
|
+
}
|
|
188552
188568
|
function createDiffCoverageState(params) {
|
|
188553
188569
|
return {
|
|
188554
188570
|
diffPath: params.diffPath,
|
|
188555
188571
|
totalLines: params.totalLines,
|
|
188556
|
-
tocEntries:
|
|
188572
|
+
tocEntries: params.tocEntries,
|
|
188557
188573
|
coveredRanges: [],
|
|
188558
188574
|
// carry forward across checkout_pr refreshes so the nudge stays "once per
|
|
188559
188575
|
// review session". coveredRanges are intentionally not carried because
|
|
@@ -188768,6 +188784,9 @@ function readNumber(params) {
|
|
|
188768
188784
|
return void 0;
|
|
188769
188785
|
}
|
|
188770
188786
|
|
|
188787
|
+
// utils/generatedFiles.ts
|
|
188788
|
+
var GENERATED = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|Cargo\.lock|go\.sum|poetry\.lock|Pipfile\.lock|Gemfile\.lock|composer\.lock|.*\.snap|.*\.min\.(js|css)|.*\.map|.*\.lock|.*\.(pb\.go|_pb2\.py|g\.dart)|.*\.(generated|gen)\.[^/]+|.*\.(svg|png|jpe?g|gif|ico|webp|woff2?|ttf|pdf))$|(^|\/)(dist|generated|__generated__|node_modules|\.yarn|__snapshots__|coverage|\.next|\.turbo|drizzle\/meta)\//i;
|
|
188789
|
+
|
|
188771
188790
|
// utils/gitAuth.ts
|
|
188772
188791
|
import { execSync } from "node:child_process";
|
|
188773
188792
|
import { createHash } from "node:crypto";
|
|
@@ -190114,7 +190133,7 @@ function computeIncrementalDiff(params) {
|
|
|
190114
190133
|
"sh",
|
|
190115
190134
|
[
|
|
190116
190135
|
"-c",
|
|
190117
|
-
'old_base=$(git merge-base "$1" "origin/$2") && new_base=$(git merge-base "$3" "origin/$2") && git range-diff --no-color "$old_base..$(git commit-tree "$1^{tree}" -p "$old_base" -m x)" "$new_base..$(git commit-tree "$3^{tree}" -p "$new_base" -m x)"',
|
|
190136
|
+
'old_base=$(git merge-base "$1" "origin/$2") && new_base=$(git merge-base "$3" "origin/$2") && git range-diff --no-color --creation-factor=1000 "$old_base..$(git commit-tree "$1^{tree}" -p "$old_base" -m x)" "$new_base..$(git commit-tree "$3^{tree}" -p "$new_base" -m x)"',
|
|
190118
190137
|
"--",
|
|
190119
190138
|
params.beforeSha,
|
|
190120
190139
|
params.baseBranch,
|
|
@@ -190137,6 +190156,8 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
190137
190156
|
if (!raw2.trim()) return null;
|
|
190138
190157
|
if (/^\d+:\s+\w+\s+=\s+\d+:/m.test(raw2)) return null;
|
|
190139
190158
|
const beforeBuf = [];
|
|
190159
|
+
let lastOuterHdr = null;
|
|
190160
|
+
let outerHdrEmitted = true;
|
|
190140
190161
|
let lastFileHdr = null;
|
|
190141
190162
|
let lastHunkHdr = null;
|
|
190142
190163
|
let fileHdrEmitted = true;
|
|
@@ -190150,10 +190171,12 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
190150
190171
|
if (lastEmittedSeq >= 0 && line.seq > lastEmittedSeq + 1) out += (out ? "\n" : "") + "...";
|
|
190151
190172
|
out += (out ? "\n" : "") + line.prefix + raw2.slice(line.from, line.to);
|
|
190152
190173
|
lastEmittedSeq = line.seq;
|
|
190174
|
+
if (lastOuterHdr?.seq === line.seq) outerHdrEmitted = true;
|
|
190153
190175
|
if (lastFileHdr?.seq === line.seq) fileHdrEmitted = true;
|
|
190154
190176
|
if (lastHunkHdr?.seq === line.seq) hunkHdrEmitted = true;
|
|
190155
190177
|
}
|
|
190156
190178
|
function flushBefore() {
|
|
190179
|
+
if (lastOuterHdr && !outerHdrEmitted) emit(lastOuterHdr);
|
|
190157
190180
|
if (lastFileHdr && !fileHdrEmitted) emit(lastFileHdr);
|
|
190158
190181
|
if (lastHunkHdr && !hunkHdrEmitted) emit(lastHunkHdr);
|
|
190159
190182
|
for (const line of beforeBuf) {
|
|
@@ -190169,7 +190192,17 @@ function postProcessRangeDiff(raw2, contextLines = 3) {
|
|
|
190169
190192
|
cursor = lineEnd + 1;
|
|
190170
190193
|
continue;
|
|
190171
190194
|
}
|
|
190172
|
-
if (
|
|
190195
|
+
if (raw2.startsWith(" @@", cursor)) {
|
|
190196
|
+
lastOuterHdr = { prefix: "", from: cursor + 4, to: lineEnd, seq };
|
|
190197
|
+
outerHdrEmitted = false;
|
|
190198
|
+
lastFileHdr = null;
|
|
190199
|
+
fileHdrEmitted = true;
|
|
190200
|
+
lastHunkHdr = null;
|
|
190201
|
+
hunkHdrEmitted = true;
|
|
190202
|
+
beforeBuf.length = 0;
|
|
190203
|
+
afterRemaining = 0;
|
|
190204
|
+
seq++;
|
|
190205
|
+
} else if (lineEnd - cursor >= 5 && raw2.startsWith(" ", cursor)) {
|
|
190173
190206
|
const prefix = raw2[cursor + 4];
|
|
190174
190207
|
if (isDiffPrefix(prefix)) {
|
|
190175
190208
|
const contentPos = cursor + 5;
|
|
@@ -197614,8 +197647,9 @@ function formatFilesWithLineNumbers(files) {
|
|
|
197614
197647
|
const tocLines = [`## Files (${files.length})`];
|
|
197615
197648
|
for (const entry of tocEntries) {
|
|
197616
197649
|
const anchor2 = createHash2("sha256").update(entry.filename).digest("hex");
|
|
197650
|
+
const generated = GENERATED.test(entry.filename) ? " \xB7 generated" : "";
|
|
197617
197651
|
tocLines.push(
|
|
197618
|
-
`- ${entry.filename} \u2192 lines ${entry.startLine}-${entry.endLine} \xB7 diff-${anchor2}`
|
|
197652
|
+
`- ${entry.filename} \u2192 lines ${entry.startLine}-${entry.endLine} \xB7 diff-${anchor2}${generated}`
|
|
197619
197653
|
);
|
|
197620
197654
|
}
|
|
197621
197655
|
tocLines.push("");
|
|
@@ -197980,6 +198014,8 @@ function CheckoutPrTool(ctx) {
|
|
|
197980
198014
|
if (!checkoutSha) throw new Error("checkout completed without a resolved HEAD SHA");
|
|
197981
198015
|
const headShort = checkoutSha.slice(0, 7);
|
|
197982
198016
|
let incrementalDiffPath;
|
|
198017
|
+
let incrementalDiff;
|
|
198018
|
+
let incrementalEmpty = false;
|
|
197983
198019
|
let incrementalUnavailable = false;
|
|
197984
198020
|
let incrementalUnavailableReason;
|
|
197985
198021
|
if (primary.beforeSha) {
|
|
@@ -197995,9 +198031,12 @@ function CheckoutPrTool(ctx) {
|
|
|
197995
198031
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
197996
198032
|
);
|
|
197997
198033
|
writeFileSync10(incrementalDiffPath, incremental.diff);
|
|
198034
|
+
incrementalDiff = incremental.diff;
|
|
197998
198035
|
log.info(
|
|
197999
198036
|
`\xBB incremental diff computed (${incremental.diff.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
198000
198037
|
);
|
|
198038
|
+
} else if (incremental.status === "empty") {
|
|
198039
|
+
incrementalEmpty = true;
|
|
198001
198040
|
} else if (incremental.status === "unavailable") {
|
|
198002
198041
|
incrementalUnavailable = true;
|
|
198003
198042
|
incrementalUnavailableReason = incremental.reason;
|
|
@@ -198010,14 +198049,16 @@ ${diffPreview}`);
|
|
|
198010
198049
|
const diffPath = join16(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
198011
198050
|
writeFileSync10(diffPath, formatResult.content);
|
|
198012
198051
|
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
198013
|
-
|
|
198014
|
-
|
|
198015
|
-
|
|
198016
|
-
|
|
198052
|
+
const coverageEntries = incrementalDiff ? parseIncrementalDiffEntries({ content: incrementalDiff }) : parseDiffTocEntries({ toc: formatResult.toc });
|
|
198053
|
+
primary.diffCoverage = incrementalEmpty ? void 0 : createDiffCoverageState({
|
|
198054
|
+
diffPath: incrementalDiffPath ?? diffPath,
|
|
198055
|
+
totalLines: countLines({ content: incrementalDiff ?? formatResult.content }),
|
|
198056
|
+
tocEntries: coverageEntries.filter((entry) => !GENERATED.test(entry.filename)),
|
|
198017
198057
|
previous: primary.diffCoverage
|
|
198018
198058
|
});
|
|
198059
|
+
const coverage = primary.diffCoverage;
|
|
198019
198060
|
log.debug(
|
|
198020
|
-
`\xBB diff coverage initialized: diffPath=${diffPath}, totalLines=${
|
|
198061
|
+
coverage ? `\xBB diff coverage initialized: diffPath=${coverage.diffPath}, totalLines=${coverage.totalLines}, tocEntries=${coverage.tocEntries.length}` : "\xBB diff coverage skipped: no changes since the last reviewed version"
|
|
198021
198062
|
);
|
|
198022
198063
|
let impactPath;
|
|
198023
198064
|
if (process.env.PULLFROG_DISABLE_CHANGE_IMPACT === "1") {
|
|
@@ -198068,7 +198109,7 @@ ${diffPreview}`);
|
|
|
198068
198109
|
primary.commentableLinesByFile = cached4;
|
|
198069
198110
|
primary.commentableLinesPullNumber = pull_number;
|
|
198070
198111
|
primary.commentableLinesCheckoutSha = checkoutSha;
|
|
198071
|
-
const incrementalInstructions = incrementalDiffPath ? `IMPORTANT: incrementalDiffPath contains ONLY the changes since the last reviewed version (computed via range-diff)
|
|
198112
|
+
const incrementalInstructions = incrementalDiffPath ? `IMPORTANT: incrementalDiffPath contains ONLY the changes since the last reviewed version (computed via range-diff), and it \u2014 not diffPath's TOC \u2014 is this review's coverage checklist. read it FIRST and in full, then read only the diffPath ranges the delta needs for context. ` : incrementalEmpty ? `NOTE: the PR's net changes are identical to the last reviewed version (e.g. a rebase with no content change), so there is no new diff to cover. ` : incrementalUnavailable ? (
|
|
198072
198113
|
// say it FAILED rather than leaving the agent to infer "nothing changed"
|
|
198073
198114
|
// from an absent field. only suggest diff-tree when the previous
|
|
198074
198115
|
// revision is actually PRESENT locally — if the fetch never landed it,
|
|
@@ -198119,7 +198160,7 @@ ${diffPreview}`);
|
|
|
198119
198160
|
commitLogTruncated,
|
|
198120
198161
|
commitLogUnavailable,
|
|
198121
198162
|
hookWarning: checkoutResult.hookWarning,
|
|
198122
|
-
instructions: incrementalInstructions + `the diff file at diffPath contains a table of contents (TOC) at the top listing every changed file with its line range. use the TOC line ranges as your checklist and read specific files from the diff instead of reading the entire file. for example, if the TOC says "src/foo.ts \u2192 lines 5-42", read lines 5-42 from diffPath to see that file's changes. review files selectively based on relevance rather than reading everything sequentially. to inspect the PR's changed files, use diffPath \u2014 do NOT run \`git diff\` to re-derive what's already in diffPath. the formatted diff with line numbers is authoritative. if you ever do need a branch-vs-base diff via the git tool, use \`git diff --merge-base <base>\` (single call, includes uncommitted edits) or three-dot \`git diff <base>...HEAD\` (committed-only). bare \`<base>\` and two-dot \`<base>..HEAD\` are symmetric and pull in the inverse of every commit landed on \`<base>\` since the branch forked \u2014 the git tool will reject those forms when the divergence is detected. \`$(...)\` subshells are NOT expanded by the git tool. \`git log\` and \`git diff --stat\` are fine for commit-range overview, and \`git diff\` / \`git diff --cached\` are fine for inspecting *your own* uncommitted changes \u2014 but PR review content MUST come from diffPath. before your review is submitted, a one-time coverage pre-flight may error listing unread TOC regions. retry the same create_pull_request_review call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session. the local branch is 'localBranch' (pr-{number}), not the remote branch name. when pushing, omit branchName to use the current branch. do not use remoteBranch as a local branch name.` + impactInstructions + hookWarningInstructions + commitLogInstructions
|
|
198163
|
+
instructions: incrementalInstructions + `the diff file at diffPath contains a table of contents (TOC) at the top listing every changed file with its line range. absent incrementalDiffPath, use the TOC line ranges as your checklist (entries marked \`generated\` are off it) and read specific files from the diff instead of reading the entire file. for example, if the TOC says "src/foo.ts \u2192 lines 5-42", read lines 5-42 from diffPath to see that file's changes. review files selectively based on relevance rather than reading everything sequentially. to inspect the PR's changed files, use diffPath \u2014 do NOT run \`git diff\` to re-derive what's already in diffPath. the formatted diff with line numbers is authoritative. if you ever do need a branch-vs-base diff via the git tool, use \`git diff --merge-base <base>\` (single call, includes uncommitted edits) or three-dot \`git diff <base>...HEAD\` (committed-only). bare \`<base>\` and two-dot \`<base>..HEAD\` are symmetric and pull in the inverse of every commit landed on \`<base>\` since the branch forked \u2014 the git tool will reject those forms when the divergence is detected. \`$(...)\` subshells are NOT expanded by the git tool. \`git log\` and \`git diff --stat\` are fine for commit-range overview, and \`git diff\` / \`git diff --cached\` are fine for inspecting *your own* uncommitted changes \u2014 but PR review content MUST come from diffPath. before your review is submitted, a one-time coverage pre-flight may error listing unread TOC regions. retry the same create_pull_request_review call to proceed \u2014 optionally after reading the listed ranges. the pre-flight will not block again this session. the local branch is 'localBranch' (pr-{number}), not the remote branch name. when pushing, omit branchName to use the current branch. do not use remoteBranch as a local branch name.` + impactInstructions + hookWarningInstructions + commitLogInstructions
|
|
198123
198164
|
};
|
|
198124
198165
|
};
|
|
198125
198166
|
return tool({
|
package/dist/internal/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { decodeJwtExpMs, OAuthInvalidGrantError, parseCodexAuthBody, refreshCode
|
|
|
17
17
|
export type { CredentialVerdict } from "../utils/credentialCheck.ts";
|
|
18
18
|
export { isCredentialProbeable, verifyCredential } from "../utils/credentialCheck.ts";
|
|
19
19
|
export { renderFailure } from "../utils/failureBody.ts";
|
|
20
|
+
export { GENERATED } from "../utils/generatedFiles.ts";
|
|
20
21
|
export type { ResourceUsage, UsageSummary } from "../utils/github.ts";
|
|
21
22
|
export { isPullfrog } from "../utils/isPullfrog.ts";
|
|
22
23
|
export { isLeapingIntoActionCommentBody, isLiveChecklistCommentBody, LEAPING_INTO_ACTION_PREFIX, } from "../utils/leapingComment.ts";
|
package/dist/internal.js
CHANGED
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "pullfrog",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.103",
|
|
12
12
|
cliContractVersion: "1",
|
|
13
13
|
type: "module",
|
|
14
14
|
bin: {
|
|
@@ -21026,7 +21026,7 @@ var providers = {
|
|
|
21026
21026
|
},
|
|
21027
21027
|
// Zen's FREE open-weights preview (listed 2026-10-05), the eighth promo.
|
|
21028
21028
|
// Zen's docs: no training, 30-day retention for safety. hidden until a
|
|
21029
|
-
// published release carries the alias.
|
|
21029
|
+
// published release carries the alias; 0.1.102 is the first.
|
|
21030
21030
|
beam: {
|
|
21031
21031
|
displayName: "Beam",
|
|
21032
21032
|
description: "Preview, free for a limited time",
|
|
@@ -21034,8 +21034,7 @@ var providers = {
|
|
|
21034
21034
|
effort: ["low", "medium", "high", "xhigh", "max"],
|
|
21035
21035
|
// free to run, still gated on the provider's own OPENCODE_API_KEY —
|
|
21036
21036
|
// see the big-pickle note above (#1077).
|
|
21037
|
-
isFree: true
|
|
21038
|
-
hidden: true
|
|
21037
|
+
isFree: true
|
|
21039
21038
|
},
|
|
21040
21039
|
// Zen's live free MiMo, and the second free row in a menu that big-pickle
|
|
21041
21040
|
// was alone in since `mimo-v2-pro-free` lost its model.
|
|
@@ -21408,10 +21407,12 @@ var providers = {
|
|
|
21408
21407
|
openRouterResolve: "openrouter/openai/gpt-5.4",
|
|
21409
21408
|
fallback: "openrouter/gpt-sol"
|
|
21410
21409
|
},
|
|
21410
|
+
// OpenRouter dropped `openai/o4-mini` on 2026-10-28 (only `:batch` remains) — folds stored pins to Luna.
|
|
21411
21411
|
"o4-mini": {
|
|
21412
21412
|
displayName: "O4 Mini",
|
|
21413
21413
|
resolve: "openrouter/openai/o4-mini",
|
|
21414
|
-
openRouterResolve: "openrouter/openai/o4-mini"
|
|
21414
|
+
openRouterResolve: "openrouter/openai/o4-mini",
|
|
21415
|
+
fallback: "openrouter/gpt-luna"
|
|
21415
21416
|
},
|
|
21416
21417
|
"gemini-pro": {
|
|
21417
21418
|
displayName: "Gemini Pro",
|
|
@@ -22133,7 +22134,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
22133
22134
|
|
|
22134
22135
|
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.
|
|
22135
22136
|
|
|
22136
|
-
3. **incremental scope**: if \`incrementalDiffPath\` is present,
|
|
22137
|
+
3. **incremental scope**: if \`incrementalDiffPath\` is present, it is your coverage checklist: read it FIRST and in full. it is a range-diff of the net changes since the last review, free of base-branch noise. read only the \`diffPath\` ranges the delta needs for context \u2014 do not re-read the whole PR. 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 that, use \`impactPath\` as an explicitly incomplete list of reference leads; it never replaces raw-diff reading or establishes coverage.
|
|
22137
22138
|
|
|
22138
22139
|
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:
|
|
22139
22140
|
|
|
@@ -22149,13 +22150,13 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
22149
22150
|
|
|
22150
22151
|
skip the deeper pass and jump to step 10's non-substantive path (do NOT submit a review) only when the incremental changes have **no behavioral surface at all** \u2014 formatting, comment tweaks, import reordering, lockfile regen, a mechanical rename of import paths. line count is not the signal: a one-line change to auth, money, SQL, a comparison operator, a redirect, or a config default is not trivial.
|
|
22151
22152
|
|
|
22152
|
-
6. **specialist decision**: after covering the
|
|
22153
|
+
6. **specialist decision**: after covering the delta, name the questions about the new changes that you still cannot answer confidently yourself, and dispatch one \`${REVIEWER_AGENT_NAME}\` specialist per question. a question qualifies only when a specialist could return evidence that **changes your disposition** on the PR \u2014 generic requests for another look, extra confidence, or polish do not. most incremental reviews need zero or one, especially thread-reply re-reviews; some need several.
|
|
22153
22154
|
|
|
22154
22155
|
**There is NO one-specialist cap or fixed maximum.** cover every orthogonal question that remains; do not collapse several real questions into one broad prompt just to reduce the count. there is no file-count, line-count, or budget threshold either \u2014 diff size is not a proxy for review uncertainty.
|
|
22155
22156
|
|
|
22156
22157
|
frame each question through the lens that primes the right failure modes. for high-stakes subsystems, lead with the **domain** ("the billing lens", "the auth lens", "the schema-migration lens") rather than the generic equivalent \u2014 the domain framing makes the subagent recall failure modes a generic lens misses.
|
|
22157
22158
|
|
|
22158
|
-
you remain the synthesizer: reading the
|
|
22159
|
+
you remain the synthesizer: reading the delta and the context it needs, investigating surrounding code, validating every returned finding, and writing the review are yours. specialist reads supplement that work; they never satisfy your own coverage obligation.
|
|
22159
22160
|
|
|
22160
22161
|
7. **dispatch specialists (only if step 6 found unresolved questions)**: for 2+ questions, emit every Task tool_use block **IN A SINGLE ASSISTANT TURN** before reading any result, so the investigations run in parallel rather than serially. your own \`read\` / \`grep\` / \`webfetch\` calls can ride in that same turn.
|
|
22161
22162
|
|
|
@@ -22633,6 +22634,9 @@ async function verifyCredential(params) {
|
|
|
22633
22634
|
}
|
|
22634
22635
|
}
|
|
22635
22636
|
|
|
22637
|
+
// utils/generatedFiles.ts
|
|
22638
|
+
var GENERATED = /(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|Cargo\.lock|go\.sum|poetry\.lock|Pipfile\.lock|Gemfile\.lock|composer\.lock|.*\.snap|.*\.min\.(js|css)|.*\.map|.*\.lock|.*\.(pb\.go|_pb2\.py|g\.dart)|.*\.(generated|gen)\.[^/]+|.*\.(svg|png|jpe?g|gif|ico|webp|woff2?|ttf|pdf))$|(^|\/)(dist|generated|__generated__|node_modules|\.yarn|__snapshots__|coverage|\.next|\.turbo|drizzle\/meta)\//i;
|
|
22639
|
+
|
|
22636
22640
|
// utils/isPullfrog.ts
|
|
22637
22641
|
function isPullfrog(actor) {
|
|
22638
22642
|
actor = actor?.toLowerCase().replace("[bot]", "");
|
|
@@ -23480,6 +23484,7 @@ export {
|
|
|
23480
23484
|
DEFAULT_EFFORT_POSITION,
|
|
23481
23485
|
DEFAULT_PROXY_MODEL,
|
|
23482
23486
|
EFFORT_ALIASES,
|
|
23487
|
+
GENERATED,
|
|
23483
23488
|
LEAPING_INTO_ACTION_PREFIX,
|
|
23484
23489
|
MAX_LEARNINGS_LENGTH,
|
|
23485
23490
|
OAuthInvalidGrantError,
|
|
@@ -38,10 +38,18 @@ export declare function countLines(params: {
|
|
|
38
38
|
export declare function parseDiffTocEntries(params: {
|
|
39
39
|
toc: string;
|
|
40
40
|
}): DiffTocEntry[];
|
|
41
|
+
/**
|
|
42
|
+
* one entry per section of an incremental (range-diff) diff, which opens on an
|
|
43
|
+
* `@@ <file>[: <function>]` hunk header or a `## <file>[ (new)] ##` file header,
|
|
44
|
+
* either of which may be the only line naming its file.
|
|
45
|
+
*/
|
|
46
|
+
export declare function parseIncrementalDiffEntries(params: {
|
|
47
|
+
content: string;
|
|
48
|
+
}): DiffTocEntry[];
|
|
41
49
|
export declare function createDiffCoverageState(params: {
|
|
42
50
|
diffPath: string;
|
|
43
51
|
totalLines: number;
|
|
44
|
-
|
|
52
|
+
tocEntries: DiffTocEntry[];
|
|
45
53
|
previous?: DiffCoverageState | undefined;
|
|
46
54
|
}): DiffCoverageState;
|
|
47
55
|
export declare function recordDiffReadFromToolUse(params: {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* files whose hunks carry nothing a reviewer weighs — lockfiles, minified and
|
|
3
|
+
* generated output, snapshots, images and fonts, built trees, migration
|
|
4
|
+
* metadata. the router's scorer reads a one-line stub in their place and its
|
|
5
|
+
* heuristic skips their lines; a review's diff keeps them but leaves them off
|
|
6
|
+
* its coverage checklist. `build/` and `vendor/` are deliberately not here: the
|
|
7
|
+
* router eval set has source under both (`src/build/expo-brand.ts`, a vendored
|
|
8
|
+
* crate patched in the PR), and hiding a patch is worse than reading a copy.
|
|
9
|
+
*/
|
|
10
|
+
export declare const GENERATED: RegExp;
|
|
@@ -64,7 +64,9 @@ export declare function computeIncrementalDiff(params: ComputeIncrementalDiffPar
|
|
|
64
64
|
*
|
|
65
65
|
* uses a streaming approach: a ring buffer of before-context lines is flushed when
|
|
66
66
|
* a change is hit, then afterCount lines of after-context are emitted directly.
|
|
67
|
-
* nearest preceding ## / @@ headers are force-included when outside the context window
|
|
67
|
+
* nearest preceding ## / @@ headers are force-included when outside the context window,
|
|
68
|
+
* and so is the outer `@@ <file>[: <function>]` hunk header, which is often the only
|
|
69
|
+
* line naming the file a change belongs to.
|
|
68
70
|
*/
|
|
69
71
|
export declare function postProcessRangeDiff(raw: string, contextLines?: number): string | null;
|
|
70
72
|
export {};
|