pullfrog 0.1.34 → 0.1.36
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/opencodeShared.d.ts +1 -1
- package/dist/agents/reviewer.d.ts +1 -1
- package/dist/cli.mjs +639 -238
- package/dist/index.js +638 -237
- package/dist/internal.js +137 -113
- package/dist/mcp/checkout.d.ts +1 -0
- package/dist/mcp/shared.d.ts +3 -1
- package/dist/models.d.ts +7 -2
- package/dist/toolState.d.ts +0 -1
- package/dist/utils/changeImpact.d.ts +18 -0
- package/dist/utils/diffCoverage.d.ts +0 -5
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -28230,7 +28230,7 @@ var require_cross_spawn = __commonJS({
|
|
|
28230
28230
|
var cp = __require("child_process");
|
|
28231
28231
|
var parse5 = require_parse3();
|
|
28232
28232
|
var enoent = require_enoent();
|
|
28233
|
-
function
|
|
28233
|
+
function spawn6(command, args2, options) {
|
|
28234
28234
|
const parsed2 = parse5(command, args2, options);
|
|
28235
28235
|
const spawned = cp.spawn(parsed2.command, parsed2.args, parsed2.options);
|
|
28236
28236
|
enoent.hookChildProcess(spawned, parsed2);
|
|
@@ -28242,8 +28242,8 @@ var require_cross_spawn = __commonJS({
|
|
|
28242
28242
|
result.error = result.error || enoent.verifyENOENTSync(result.status, parsed2);
|
|
28243
28243
|
return result;
|
|
28244
28244
|
}
|
|
28245
|
-
module.exports =
|
|
28246
|
-
module.exports.spawn =
|
|
28245
|
+
module.exports = spawn6;
|
|
28246
|
+
module.exports.spawn = spawn6;
|
|
28247
28247
|
module.exports.sync = spawnSync6;
|
|
28248
28248
|
module.exports._parse = parse5;
|
|
28249
28249
|
module.exports._enoent = enoent;
|
|
@@ -96055,14 +96055,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
96055
96055
|
} else if (node2.nodeType === 1) {
|
|
96056
96056
|
replacement = replacementForNode.call(self2, node2);
|
|
96057
96057
|
}
|
|
96058
|
-
return
|
|
96058
|
+
return join27(output, replacement);
|
|
96059
96059
|
}, "");
|
|
96060
96060
|
}
|
|
96061
96061
|
function postProcess(output) {
|
|
96062
96062
|
var self2 = this;
|
|
96063
96063
|
this.rules.forEach(function(rule) {
|
|
96064
96064
|
if (typeof rule.append === "function") {
|
|
96065
|
-
output =
|
|
96065
|
+
output = join27(output, rule.append(self2.options));
|
|
96066
96066
|
}
|
|
96067
96067
|
});
|
|
96068
96068
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -96074,7 +96074,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
96074
96074
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
96075
96075
|
return whitespace.leading + rule.replacement(content, node2, this.options) + whitespace.trailing;
|
|
96076
96076
|
}
|
|
96077
|
-
function
|
|
96077
|
+
function join27(output, replacement) {
|
|
96078
96078
|
var s1 = trimTrailingNewlines(output);
|
|
96079
96079
|
var s2 = trimLeadingNewlines(replacement);
|
|
96080
96080
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -101730,7 +101730,7 @@ import { dirname as dirname6 } from "node:path";
|
|
|
101730
101730
|
// main.ts
|
|
101731
101731
|
import { existsSync as existsSync8, readdirSync as readdirSync2 } from "node:fs";
|
|
101732
101732
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
101733
|
-
import { join as
|
|
101733
|
+
import { join as join26 } from "node:path";
|
|
101734
101734
|
|
|
101735
101735
|
// agents/claude.ts
|
|
101736
101736
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
@@ -101788,37 +101788,40 @@ var providers = {
|
|
|
101788
101788
|
managedCredentials: ["CODEX_AUTH_JSON"],
|
|
101789
101789
|
models: {
|
|
101790
101790
|
gpt: {
|
|
101791
|
-
displayName: "GPT",
|
|
101792
|
-
resolve: "openai/gpt-5.
|
|
101793
|
-
openRouterResolve: "openrouter/openai/gpt-5.
|
|
101791
|
+
displayName: "GPT Sol",
|
|
101792
|
+
resolve: "openai/gpt-5.6-sol",
|
|
101793
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-sol",
|
|
101794
101794
|
preferred: true,
|
|
101795
|
-
subagentModel: "gpt-
|
|
101795
|
+
subagentModel: "gpt-terra"
|
|
101796
101796
|
},
|
|
101797
|
+
// Sol served at reasoning.mode=pro — same $/token as Sol, just more tokens
|
|
101798
|
+
// burned; not a pricier premium tier. models.dev has no -pro id, so direct-key
|
|
101799
|
+
// (BYOK) resolves to plain Sol; only the Router/OpenRouter path gets sol-pro.
|
|
101797
101800
|
"gpt-pro": {
|
|
101798
|
-
displayName: "GPT Pro",
|
|
101799
|
-
|
|
101800
|
-
|
|
101801
|
+
displayName: "GPT Sol Pro",
|
|
101802
|
+
description: "Maximum reasoning effort",
|
|
101803
|
+
resolve: "openai/gpt-5.6-sol",
|
|
101804
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-sol-pro",
|
|
101801
101805
|
subagentModel: "gpt"
|
|
101802
101806
|
},
|
|
101803
|
-
//
|
|
101804
|
-
//
|
|
101805
|
-
//
|
|
101806
|
-
"gpt-
|
|
101807
|
-
displayName: "GPT
|
|
101808
|
-
resolve: "openai/gpt-5.
|
|
101809
|
-
openRouterResolve: "openrouter/openai/gpt-5.
|
|
101810
|
-
hidden: true
|
|
101807
|
+
// gpt-5.6's balanced mid-tier (Sol/Terra/Luna are durable capability tiers,
|
|
101808
|
+
// not version bumps). selectable on its own and doubles as `gpt`'s cheaper
|
|
101809
|
+
// lens-fanout subagent — replaces the old hidden gpt-5.4 subagent target.
|
|
101810
|
+
"gpt-terra": {
|
|
101811
|
+
displayName: "GPT Terra",
|
|
101812
|
+
resolve: "openai/gpt-5.6-terra",
|
|
101813
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-terra"
|
|
101811
101814
|
},
|
|
101812
101815
|
"gpt-mini": {
|
|
101813
|
-
displayName: "GPT
|
|
101814
|
-
resolve: "openai/gpt-5.
|
|
101815
|
-
openRouterResolve: "openrouter/openai/gpt-5.
|
|
101816
|
+
displayName: "GPT Luna",
|
|
101817
|
+
resolve: "openai/gpt-5.6-luna",
|
|
101818
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-luna"
|
|
101816
101819
|
},
|
|
101817
101820
|
// legacy aliases — openai unified the codex line into the main GPT family
|
|
101818
101821
|
// and is shutting down every "-codex" snapshot on 2026-07-23. transparently
|
|
101819
101822
|
// upgrade existing users via the fallback chain. UI display sites resolve
|
|
101820
101823
|
// to the terminal alias's label (so dropdown trigger + PR footers show
|
|
101821
|
-
// "GPT" / "GPT
|
|
101824
|
+
// "GPT Sol" / "GPT Luna", not the historical name).
|
|
101822
101825
|
"gpt-codex": {
|
|
101823
101826
|
displayName: "GPT Codex",
|
|
101824
101827
|
resolve: "openai/gpt-5.3-codex",
|
|
@@ -101831,6 +101834,13 @@ var providers = {
|
|
|
101831
101834
|
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
101832
101835
|
fallback: "openai/gpt-mini"
|
|
101833
101836
|
},
|
|
101837
|
+
// dropped hidden subagent tier — folds stored pins forward to gpt (Sol).
|
|
101838
|
+
"gpt-5.4": {
|
|
101839
|
+
displayName: "GPT 5.4",
|
|
101840
|
+
resolve: "openai/gpt-5.4",
|
|
101841
|
+
openRouterResolve: "openrouter/openai/gpt-5.4",
|
|
101842
|
+
fallback: "openai/gpt"
|
|
101843
|
+
},
|
|
101834
101844
|
o3: {
|
|
101835
101845
|
displayName: "O3",
|
|
101836
101846
|
resolve: "openai/o3",
|
|
@@ -101960,28 +101970,29 @@ var providers = {
|
|
|
101960
101970
|
openRouterResolve: "openrouter/anthropic/claude-haiku-4.5"
|
|
101961
101971
|
},
|
|
101962
101972
|
gpt: {
|
|
101963
|
-
displayName: "GPT",
|
|
101964
|
-
resolve: "opencode/gpt-5.
|
|
101965
|
-
openRouterResolve: "openrouter/openai/gpt-5.
|
|
101966
|
-
subagentModel: "gpt-
|
|
101973
|
+
displayName: "GPT Sol",
|
|
101974
|
+
resolve: "opencode/gpt-5.6-sol",
|
|
101975
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-sol",
|
|
101976
|
+
subagentModel: "gpt-terra"
|
|
101967
101977
|
},
|
|
101978
|
+
// see openai/gpt-pro — Zen has no -pro id, so direct resolves to plain Sol.
|
|
101968
101979
|
"gpt-pro": {
|
|
101969
|
-
displayName: "GPT Pro",
|
|
101970
|
-
|
|
101971
|
-
|
|
101980
|
+
displayName: "GPT Sol Pro",
|
|
101981
|
+
description: "Maximum reasoning effort",
|
|
101982
|
+
resolve: "opencode/gpt-5.6-sol",
|
|
101983
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-sol-pro",
|
|
101972
101984
|
subagentModel: "gpt"
|
|
101973
101985
|
},
|
|
101974
|
-
//
|
|
101975
|
-
"gpt-
|
|
101976
|
-
displayName: "GPT
|
|
101977
|
-
resolve: "opencode/gpt-5.
|
|
101978
|
-
openRouterResolve: "openrouter/openai/gpt-5.
|
|
101979
|
-
hidden: true
|
|
101986
|
+
// gpt-5.6 balanced mid-tier — selectable + `gpt`'s subagent. see openai above.
|
|
101987
|
+
"gpt-terra": {
|
|
101988
|
+
displayName: "GPT Terra",
|
|
101989
|
+
resolve: "opencode/gpt-5.6-terra",
|
|
101990
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-terra"
|
|
101980
101991
|
},
|
|
101981
101992
|
"gpt-mini": {
|
|
101982
|
-
displayName: "GPT
|
|
101983
|
-
resolve: "opencode/gpt-5.
|
|
101984
|
-
openRouterResolve: "openrouter/openai/gpt-5.
|
|
101993
|
+
displayName: "GPT Luna",
|
|
101994
|
+
resolve: "opencode/gpt-5.6-luna",
|
|
101995
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-luna"
|
|
101985
101996
|
},
|
|
101986
101997
|
// legacy aliases — see openai provider above for context.
|
|
101987
101998
|
"gpt-codex": {
|
|
@@ -101996,6 +102007,13 @@ var providers = {
|
|
|
101996
102007
|
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
101997
102008
|
fallback: "opencode/gpt-mini"
|
|
101998
102009
|
},
|
|
102010
|
+
// dropped hidden subagent tier — folds stored pins forward to gpt (Sol).
|
|
102011
|
+
"gpt-5.4": {
|
|
102012
|
+
displayName: "GPT 5.4",
|
|
102013
|
+
resolve: "opencode/gpt-5.4",
|
|
102014
|
+
openRouterResolve: "openrouter/openai/gpt-5.4",
|
|
102015
|
+
fallback: "opencode/gpt"
|
|
102016
|
+
},
|
|
101999
102017
|
"gemini-pro": {
|
|
102000
102018
|
displayName: "Gemini Pro",
|
|
102001
102019
|
resolve: "opencode/gemini-3.1-pro",
|
|
@@ -102112,29 +102130,34 @@ var providers = {
|
|
|
102112
102130
|
resolve: "openrouter/~anthropic/claude-haiku-latest",
|
|
102113
102131
|
openRouterResolve: "openrouter/~anthropic/claude-haiku-latest"
|
|
102114
102132
|
},
|
|
102133
|
+
// pinned to the explicit gpt-5.6 tiers (not the ~openai/gpt-latest rolling
|
|
102134
|
+
// alias): after the Sol/Terra/Luna rename, ~gpt-mini-latest no longer maps
|
|
102135
|
+
// to Luna, so rolling aliases would silently diverge `gpt`/`gpt-mini` from
|
|
102136
|
+
// the chosen tiers across funding paths.
|
|
102115
102137
|
gpt: {
|
|
102116
|
-
displayName: "GPT",
|
|
102117
|
-
resolve: "openrouter
|
|
102118
|
-
openRouterResolve: "openrouter
|
|
102119
|
-
subagentModel: "gpt-
|
|
102138
|
+
displayName: "GPT Sol",
|
|
102139
|
+
resolve: "openrouter/openai/gpt-5.6-sol",
|
|
102140
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-sol",
|
|
102141
|
+
subagentModel: "gpt-terra"
|
|
102120
102142
|
},
|
|
102143
|
+
// see openai/gpt-pro. openrouter serves sol-pro directly on both routes.
|
|
102121
102144
|
"gpt-pro": {
|
|
102122
|
-
displayName: "GPT Pro",
|
|
102123
|
-
|
|
102124
|
-
|
|
102145
|
+
displayName: "GPT Sol Pro",
|
|
102146
|
+
description: "Maximum reasoning effort",
|
|
102147
|
+
resolve: "openrouter/openai/gpt-5.6-sol-pro",
|
|
102148
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-sol-pro",
|
|
102125
102149
|
subagentModel: "gpt"
|
|
102126
102150
|
},
|
|
102127
|
-
//
|
|
102128
|
-
"gpt-
|
|
102129
|
-
displayName: "GPT
|
|
102130
|
-
resolve: "openrouter/openai/gpt-5.
|
|
102131
|
-
openRouterResolve: "openrouter/openai/gpt-5.
|
|
102132
|
-
hidden: true
|
|
102151
|
+
// gpt-5.6 balanced mid-tier — selectable + `gpt`'s subagent. see openai above.
|
|
102152
|
+
"gpt-terra": {
|
|
102153
|
+
displayName: "GPT Terra",
|
|
102154
|
+
resolve: "openrouter/openai/gpt-5.6-terra",
|
|
102155
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-terra"
|
|
102133
102156
|
},
|
|
102134
102157
|
"gpt-mini": {
|
|
102135
|
-
displayName: "GPT
|
|
102136
|
-
resolve: "openrouter
|
|
102137
|
-
openRouterResolve: "openrouter
|
|
102158
|
+
displayName: "GPT Luna",
|
|
102159
|
+
resolve: "openrouter/openai/gpt-5.6-luna",
|
|
102160
|
+
openRouterResolve: "openrouter/openai/gpt-5.6-luna"
|
|
102138
102161
|
},
|
|
102139
102162
|
// legacy aliases — see openai provider for context.
|
|
102140
102163
|
"gpt-codex": {
|
|
@@ -102149,6 +102172,13 @@ var providers = {
|
|
|
102149
102172
|
openRouterResolve: "openrouter/openai/gpt-5.1-codex-mini",
|
|
102150
102173
|
fallback: "openrouter/gpt-mini"
|
|
102151
102174
|
},
|
|
102175
|
+
// dropped hidden subagent tier — folds stored pins forward to gpt (Sol).
|
|
102176
|
+
"gpt-5.4": {
|
|
102177
|
+
displayName: "GPT 5.4",
|
|
102178
|
+
resolve: "openrouter/openai/gpt-5.4",
|
|
102179
|
+
openRouterResolve: "openrouter/openai/gpt-5.4",
|
|
102180
|
+
fallback: "openrouter/gpt"
|
|
102181
|
+
},
|
|
102152
102182
|
"o4-mini": {
|
|
102153
102183
|
displayName: "O4 Mini",
|
|
102154
102184
|
resolve: "openrouter/openai/o4-mini",
|
|
@@ -102228,6 +102258,7 @@ var modelAliases = Object.entries(providers).flatMap(
|
|
|
102228
102258
|
slug: `${providerKey}/${modelId}`,
|
|
102229
102259
|
provider: providerKey,
|
|
102230
102260
|
displayName: def.displayName,
|
|
102261
|
+
description: def.description,
|
|
102231
102262
|
resolve: def.resolve,
|
|
102232
102263
|
openRouterResolve: def.openRouterResolve,
|
|
102233
102264
|
preferred: def.preferred ?? false,
|
|
@@ -102970,7 +103001,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
102970
103001
|
// package.json
|
|
102971
103002
|
var package_default = {
|
|
102972
103003
|
name: "pullfrog",
|
|
102973
|
-
version: "0.1.
|
|
103004
|
+
version: "0.1.36",
|
|
102974
103005
|
type: "module",
|
|
102975
103006
|
bin: {
|
|
102976
103007
|
pullfrog: "dist/cli.mjs",
|
|
@@ -103644,8 +103675,9 @@ HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
|
|
|
103644
103675
|
- Your FIRST action MUST source the diff for review. If the orchestrator's dispatch names a diff PATH on disk (e.g. \`diffPath\` / \`incrementalDiffPath\` from a prior \`checkout_pr\` call), \`read\` that path \u2014 do not invoke git at all. The on-disk diff is the authoritative scope, and dispatches almost always include one; recomputing it via git also fails on shallow GitHub Actions checkouts where the base ref may be unfetched. When BOTH a diff path and a base branch appear in your dispatch, path always wins. When the dispatch names an \`incrementalDiffPath\` alongside \`diffPath\`, prefer the incremental path for scope and consult the full diff only for line-number anchoring.
|
|
103645
103676
|
- If (and only if) NO diff path was provided, the dispatch names a base branch. Run \`git diff --merge-base origin/<base>\` (single MCP call, captures committed + staged + unstaged work, excludes commits landed on \`origin/<base>\` since your branch forked). The read-only \`git\` MCP tool is the right surface for this \u2014 \`--merge-base\` is a flag git accepts directly, so no shell substitution is needed. Do NOT run bare \`git diff origin/<base>\` or two-dot \`git diff origin/<base>..HEAD\`: those are symmetric diffs that include the inverse of every commit on \`<base>\` your branch is behind, which is pure noise (and the git tool will reject those forms when the divergence is detected). Do NOT try to expand \`$(...)\` subshell forms via the git tool \u2014 it runs git directly without shell interpolation. If \`git diff --merge-base origin/<base>\` fails with \`ambiguous argument 'origin/<base>'\` or \`no merge base\`, the runner is a shallow single-branch checkout AND the orchestrator failed to fetch the base ref before dispatching you. Surface that in one line (which ref is missing, and that the orchestrator needs to fetch it with \`git fetch --no-tags --deepen=1000 origin <base>:refs/remotes/origin/<base>\` before re-dispatching) and stop. Do NOT run \`git fetch\` yourself \u2014 your read-only contract below forbids mutating shell, and the \`git_fetch\` MCP tool is state-changing and therefore prohibited. Do NOT call \`checkout_pr\`, do NOT fetch alternative refs, do NOT list branches or all-refs looking for the work, do NOT run \`gh pr list\`. The orchestrator's dispatch is the source of truth for scope.
|
|
103646
103677
|
- If the on-disk diff path you were given is empty (or unreadable), that is a checkout / formatting failure on the orchestrator side \u2014 reply EXACTLY: \`no changes in dispatched diff \u2014 scope appears empty; orchestrator should verify checkout_pr output\` (naming the path), do NOT fall through to running \`git diff\` against guessed refs. If the merge-base diff (the fallback path) returns empty AND the orchestrator's dispatch claims there are changes to review, the most likely cause is a pre-commit Build-mode self-review: the orchestrator dispatched you before committing AND there are no uncommitted edits either. Reply EXACTLY: \`no changes detected \u2014 likely pre-commit Build self-review; orchestrator should commit then re-dispatch\` and stop. Do NOT guess PR numbers (e.g. by extrapolating from \`git log\` output), do NOT check out other PRs, do NOT fetch from forks. The empty diff is the diagnosis \u2014 surface it; do not work around it.
|
|
103678
|
+
- Once the mandatory first diff read returns a non-empty scope, batch each next dependency layer: emit all independent read-only file reads, greps, globs, and directory listings together in one assistant turn before awaiting their results. Include MCP queries only when their contract explicitly guarantees they are read-only; a \`get_*\` or \`list_*\` name alone is not proof. Keep dependent calls in later turns, after the results they depend on are available.
|
|
103647
103679
|
- Read-only tools only. Do NOT write or edit files. Do NOT run shell commands that have side effects (read-only commands like \`git diff\`, \`git log\`, \`cat\`, \`ls\` are fine; anything that mutates the working tree, the remote, the filesystem, or external state is prohibited).
|
|
103648
|
-
- Do NOT call any state-changing MCP tool. State-changing means: posts a comment, pushes a branch, creates/updates a PR or issue, changes labels, resolves review threads, persists learnings, sets workflow output, installs dependencies, uploads files, kills processes, etc.
|
|
103680
|
+
- Do NOT call any state-changing MCP tool. State-changing means: posts a comment, pushes a branch, creates/updates a PR or issue, changes labels, resolves review threads, persists learnings, sets workflow output, installs dependencies, uploads files, kills processes, etc. MCP tool names are not evidence of safety: only use queries whose contract explicitly guarantees no side effects. The \`git\` tool is fine only for read-only subcommands like \`diff\`/\`log\`/\`merge-base\`.
|
|
103649
103681
|
- Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
|
|
103650
103682
|
- Test for any tool call before invoking it: would this still be a no-op if reverted? If not, do not call it. Apply this test to tools added after this prompt was written \u2014 the rule is the invariant, not the enumeration.
|
|
103651
103683
|
|
|
@@ -103908,20 +103940,15 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
103908
103940
|
- **immediately** call \`${t2("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.
|
|
103909
103941
|
- call \`${t2("report_progress")}\` with a brief summary`
|
|
103910
103942
|
},
|
|
103911
|
-
// Review and IncrementalReview
|
|
103912
|
-
//
|
|
103913
|
-
//
|
|
103914
|
-
// high-stakes-subsystem touches — and when it fires, ALL lenses must
|
|
103915
|
-
// dispatch in a single assistant turn or the parallelism win disappears.
|
|
103916
|
-
// We never dispatch exactly one lens: a single lens is just a worse,
|
|
103917
|
-
// slower version of doing the work yourself.
|
|
103943
|
+
// Review and IncrementalReview route the minimum reviewfrog specialists
|
|
103944
|
+
// needed to cover unresolved, disposition-changing hypotheses. Most runs
|
|
103945
|
+
// use zero or one; multiple orthogonal hypotheses dispatch in parallel.
|
|
103918
103946
|
//
|
|
103919
103947
|
// Build mode self-review is a different problem shape: the orchestrator
|
|
103920
103948
|
// wrote the code, so bias-mitigation comes from delegating to one
|
|
103921
103949
|
// fresh-eyes subagent that doesn't share the implementation context. A
|
|
103922
|
-
// single subagent there is appropriate
|
|
103923
|
-
// the
|
|
103924
|
-
// perspectives is what's being purchased.
|
|
103950
|
+
// single subagent there is appropriate. Review-mode specialist routing
|
|
103951
|
+
// instead scales with the unresolved hypotheses in someone else's diff.
|
|
103925
103952
|
//
|
|
103926
103953
|
// Severity categorization is split across two surfaces: the opening
|
|
103927
103954
|
// callout (CAUTION/IMPORTANT/ℹ️/✅) sets the review's overall tier, and
|
|
@@ -103936,11 +103963,11 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
103936
103963
|
|
|
103937
103964
|
1. **task list**: create your task list for this run as your first action.
|
|
103938
103965
|
|
|
103939
|
-
2. **checkout**: call \`${t2("checkout_pr")}\` \u2014 this returns PR metadata and a \`
|
|
103966
|
+
2. **checkout**: call \`${t2("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.
|
|
103940
103967
|
|
|
103941
103968
|
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.
|
|
103942
103969
|
|
|
103943
|
-
if the PR is **genuinely trivial**, skip
|
|
103970
|
+
if the PR is **genuinely trivial**, skip specialists entirely and submit a \`No new issues found.\` review per step 7.
|
|
103944
103971
|
|
|
103945
103972
|
"Genuinely trivial" (skip):
|
|
103946
103973
|
- single-word doc typo, whitespace/format-only, comment-only across any number of files
|
|
@@ -103959,22 +103986,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
103959
103986
|
- any "typo fix" in user-facing copy that changes meaning ("approved" \u2192 "denied")
|
|
103960
103987
|
- mixed diffs where a semantic 1-liner is buried in whitespace/formatting changes
|
|
103961
103988
|
|
|
103962
|
-
4. **
|
|
103989
|
+
4. **specialist decision \u2014 minimum hypothesis coverage**.
|
|
103963
103990
|
|
|
103964
|
-
|
|
103991
|
+
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.
|
|
103965
103992
|
|
|
103966
|
-
|
|
103967
|
-
-
|
|
103968
|
-
-
|
|
103969
|
-
- parallel-
|
|
103993
|
+
Route the **minimum number of \`${REVIEWER_AGENT_NAME}\` specialists** needed to cover those unresolved hypotheses. Most reviews need **0 or 1**:
|
|
103994
|
+
- dispatch 0 when you can resolve every disposition-changing question directly
|
|
103995
|
+
- dispatch 1 when exactly one falsifiable, load-bearing hypothesis warrants independent investigation
|
|
103996
|
+
- dispatch 2+ in parallel when multiple orthogonal load-bearing hypotheses remain, or when the user explicitly requests an exhaustive or multi-angle review
|
|
103970
103997
|
|
|
103971
|
-
**
|
|
103998
|
+
**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.
|
|
103972
103999
|
|
|
103973
|
-
|
|
104000
|
+
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.
|
|
104001
|
+
|
|
104002
|
+
Specialist hypotheses can draw on two kinds of framing:
|
|
103974
104003
|
- **themed lenses** \u2014 a perspective applied across the whole diff (correctness, security, user-journey, performance, etc.).
|
|
103975
104004
|
- **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.
|
|
103976
104005
|
|
|
103977
|
-
starter menu (combine, omit, or invent your own):
|
|
104006
|
+
starter menu for identifying hypotheses (combine, omit, or invent your own; do not dispatch a bare menu label without a falsifiable question):
|
|
103978
104007
|
- **correctness & invariants** \u2014 bugs, races, error handling, edge cases, state-machine boundaries
|
|
103979
104008
|
- **impact** \u2014 stale references in code/tests/docs/configs/UI after rename/remove
|
|
103980
104009
|
- **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.
|
|
@@ -103989,30 +104018,27 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
103989
104018
|
|
|
103990
104019
|
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.
|
|
103991
104020
|
|
|
103992
|
-
5. **
|
|
103993
|
-
|
|
103994
|
-
\u26A0\uFE0F CRITICAL \u2014 PARALLELISM IS THE ONLY REASON LENSES EXIST. \u26A0\uFE0F
|
|
103995
|
-
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.
|
|
104021
|
+
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.
|
|
103996
104022
|
|
|
103997
|
-
\u2705 Right pattern: one assistant turn with N Task tool_use blocks \u2192 wait \u2192 N results arrive together \u2192 aggregate.
|
|
103998
|
-
\u274C Wrong pattern:
|
|
104023
|
+
\u2705 Right multi-specialist pattern: one assistant turn with N Task tool_use blocks \u2192 wait \u2192 N results arrive together \u2192 aggregate.
|
|
104024
|
+
\u274C Wrong multi-specialist pattern: Task(hypothesis A) \u2192 wait for A \u2192 Task(hypothesis B).
|
|
103999
104025
|
|
|
104000
104026
|
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.
|
|
104001
104027
|
|
|
104002
|
-
if a
|
|
104028
|
+
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:
|
|
104003
104029
|
- **the absolute \`diffPath\` (and \`incrementalDiffPath\` if available) from step 2's \`${t2("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.
|
|
104004
|
-
- **
|
|
104005
|
-
- **a Task \`description\` set to
|
|
104030
|
+
- **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
|
|
104031
|
+
- **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\`.
|
|
104006
104032
|
- 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."
|
|
104007
|
-
- ask the subagent to report findings with file paths and NEW line numbers from the diff so you can anchor
|
|
104033
|
+
- 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.
|
|
104008
104034
|
|
|
104009
104035
|
delegation discipline:
|
|
104010
|
-
- do NOT summarize the PR for them (biases toward a validation frame)
|
|
104036
|
+
- do NOT summarize the PR for them (a lossy summary biases toward a validation frame; the raw diff is the source)
|
|
104011
104037
|
- do NOT hand them a curated reading list (let them discover scope)
|
|
104012
104038
|
- do NOT pre-shape their output with a finding schema
|
|
104013
104039
|
- do NOT mention the other lenses (independence is the point \u2014 overlapping findings are a strong signal)
|
|
104014
104040
|
|
|
104015
|
-
6. **aggregate & draft**: when
|
|
104041
|
+
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.
|
|
104016
104042
|
|
|
104017
104043
|
**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.
|
|
104018
104044
|
|
|
@@ -104046,10 +104072,10 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
104046
104072
|
|
|
104047
104073
|
${PR_SUMMARY_FORMAT}`
|
|
104048
104074
|
},
|
|
104049
|
-
// IncrementalReview shares Review's
|
|
104050
|
-
// format
|
|
104051
|
-
// prior
|
|
104052
|
-
// review" filter lives at aggregation time
|
|
104075
|
+
// IncrementalReview shares Review's minimum hypothesis-covering specialist
|
|
104076
|
+
// routing and body format, scoped to the incremental delta against the
|
|
104077
|
+
// prior Pullfrog review. The "issues must be NEW since the last Pullfrog
|
|
104078
|
+
// review" filter lives at aggregation time, NOT in the subagent
|
|
104053
104079
|
// prompt — pushing the filter into subagents matches the canonical anneal
|
|
104054
104080
|
// anti-pattern of "list known pre-existing failures — don't flag these"
|
|
104055
104081
|
// and suppresses signal on regressions the new commits amplified. A
|
|
@@ -104064,9 +104090,9 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
104064
104090
|
|
|
104065
104091
|
1. **task list**: create your task list for this run as your first action.
|
|
104066
104092
|
|
|
104067
|
-
2. **checkout**: call \`${t2("checkout_pr")}\` \u2014 this returns PR metadata, \`diffPath\` (full diff),
|
|
104093
|
+
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.
|
|
104068
104094
|
|
|
104069
|
-
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.
|
|
104095
|
+
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.
|
|
104070
104096
|
|
|
104071
104097
|
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:
|
|
104072
104098
|
|
|
@@ -104080,49 +104106,48 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
104080
104106
|
|
|
104081
104107
|
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.**
|
|
104082
104108
|
|
|
104083
|
-
if the incremental changes are **genuinely trivial**, skip
|
|
104109
|
+
if the incremental changes are **genuinely trivial**, skip specialists entirely and jump to step 10's non-substantive path (do NOT submit a review).
|
|
104084
104110
|
|
|
104085
104111
|
"Genuinely trivial" (skip): formatting/comment tweaks, import reordering, lockfile regen, mechanical rename of import paths, whitespace-only.
|
|
104086
104112
|
"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.
|
|
104087
104113
|
When unsure, treat as non-trivial.
|
|
104088
104114
|
|
|
104089
|
-
6. **
|
|
104115
|
+
6. **specialist decision \u2014 minimum hypothesis coverage**.
|
|
104090
104116
|
|
|
104091
|
-
|
|
104117
|
+
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.
|
|
104092
104118
|
|
|
104093
|
-
|
|
104094
|
-
-
|
|
104095
|
-
-
|
|
104096
|
-
- parallel-
|
|
104119
|
+
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:
|
|
104120
|
+
- dispatch 0 when you can resolve every disposition-changing question directly
|
|
104121
|
+
- dispatch 1 when exactly one falsifiable, load-bearing hypothesis warrants independent investigation
|
|
104122
|
+
- dispatch 2+ in parallel when multiple orthogonal load-bearing hypotheses remain, or when the user explicitly requests an exhaustive or multi-angle review
|
|
104097
104123
|
|
|
104098
|
-
**
|
|
104124
|
+
**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.
|
|
104099
104125
|
|
|
104100
|
-
|
|
104126
|
+
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.
|
|
104101
104127
|
|
|
104102
|
-
|
|
104128
|
+
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.
|
|
104103
104129
|
|
|
104104
|
-
|
|
104105
|
-
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.
|
|
104130
|
+
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.
|
|
104106
104131
|
|
|
104107
|
-
\u2705 Right pattern: one assistant turn with N Task tool_use blocks \u2192 wait \u2192 N results arrive together \u2192 aggregate.
|
|
104108
|
-
\u274C Wrong pattern:
|
|
104132
|
+
\u2705 Right multi-specialist pattern: one assistant turn with N Task tool_use blocks \u2192 wait \u2192 N results arrive together \u2192 aggregate.
|
|
104133
|
+
\u274C Wrong multi-specialist pattern: Task(hypothesis A) \u2192 wait for A \u2192 Task(hypothesis B).
|
|
104109
104134
|
|
|
104110
104135
|
You can also include your own \`read\` / \`grep\` / \`webfetch\` calls in the SAME turn as the parallel \`${REVIEWER_AGENT_NAME}\` dispatches.
|
|
104111
104136
|
|
|
104112
|
-
if a
|
|
104137
|
+
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:
|
|
104113
104138
|
- **the absolute diff path(s) from step 2's \`${t2("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.
|
|
104114
|
-
- **
|
|
104115
|
-
- **a Task \`description\` set to
|
|
104139
|
+
- **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
|
|
104140
|
+
- **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.
|
|
104116
104141
|
- if the lens touches external contracts, instruct the subagent to verify load-bearing claims via web search and quote source URLs.
|
|
104117
|
-
- ask the subagent to report findings with file paths and NEW line numbers from the full PR diff so you can anchor
|
|
104142
|
+
- 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.
|
|
104118
104143
|
|
|
104119
104144
|
delegation discipline:
|
|
104120
|
-
- do NOT summarize the changes for them (biases toward validation frame)
|
|
104145
|
+
- do NOT summarize the changes for them (a lossy summary biases toward a validation frame; the raw diff is the source)
|
|
104121
104146
|
- do NOT hand them a curated reading list (let them discover scope)
|
|
104122
104147
|
- do NOT pre-shape their output with a finding schema
|
|
104123
104148
|
- do NOT mention the other lenses (independence is the point)
|
|
104124
104149
|
|
|
104125
|
-
8. **aggregate, draft, self-critique**: merge findings (yours +
|
|
104150
|
+
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 \`${t2("list_pull_request_reviews")}\` in step 4) and run \`git diff <prior-review-sha>..HEAD\` to isolate the lines added since that review.
|
|
104126
104151
|
|
|
104127
104152
|
**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.
|
|
104128
104153
|
|
|
@@ -104219,7 +104244,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
104219
104244
|
|
|
104220
104245
|
1. **task list**: create your task list for this run as your first action.
|
|
104221
104246
|
|
|
104222
|
-
2. Analyze the task. For simple operations (labeling,
|
|
104247
|
+
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 \`${t2("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 \`${t2("report_progress")}\`.
|
|
104223
104248
|
|
|
104224
104249
|
3. For substantial work \u2014 code changes across multiple files, multi-step investigations:
|
|
104225
104250
|
- plan your approach before starting
|
|
@@ -104229,8 +104254,8 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
104229
104254
|
|
|
104230
104255
|
4. Finalize:
|
|
104231
104256
|
- if code changes were made, get them onto a pull request (new or existing) using ${signedCommits ? `\`${t2("commit_changes")}\`` : `\`${t2("push_branch")}\``} and \`${t2("create_pull_request")}\` as needed. \`git status\` must be clean before you finish (see *SYSTEM* Git rules if this fails).
|
|
104232
|
-
- call \`${t2("report_progress")}\` once with results \u2014 include exact tool errors if push or PR creation failed
|
|
104233
|
-
- if the task involved labeling
|
|
104257
|
+
- call \`${t2("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
|
|
104258
|
+
- if the task involved labeling or other GitHub operations, perform those directly`
|
|
104234
104259
|
}
|
|
104235
104260
|
];
|
|
104236
104261
|
}
|
|
@@ -104785,7 +104810,7 @@ async function runClaude2(params) {
|
|
|
104785
104810
|
}
|
|
104786
104811
|
} else if (block.type === "tool_use") {
|
|
104787
104812
|
const toolName = block.name || "unknown";
|
|
104788
|
-
if (params.onToolUse) {
|
|
104813
|
+
if (params.onToolUse && label === ORCHESTRATOR_LABEL) {
|
|
104789
104814
|
params.onToolUse({
|
|
104790
104815
|
toolName,
|
|
104791
104816
|
input: block.input
|
|
@@ -105177,7 +105202,7 @@ var claude = agent({
|
|
|
105177
105202
|
const cliPath = await installClaudeCli();
|
|
105178
105203
|
const specifier = ctx.payload.proxyModel ?? ctx.resolvedModel;
|
|
105179
105204
|
const bedrockModelId = process.env[BEDROCK_MODEL_ID_ENV]?.trim();
|
|
105180
|
-
const isBedrockRoute = specifier !== void 0 && bedrockModelId !== void 0 && bedrockModelId === specifier
|
|
105205
|
+
const isBedrockRoute = specifier !== void 0 && bedrockModelId !== void 0 && bedrockModelId === specifier;
|
|
105181
105206
|
const vertexModelId = process.env[VERTEX_MODEL_ID_ENV]?.trim();
|
|
105182
105207
|
const isVertexRoute2 = specifier !== void 0 && vertexModelId !== void 0 && vertexModelId === specifier && isVertexAnthropicId(specifier);
|
|
105183
105208
|
const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
|
|
@@ -105225,6 +105250,7 @@ var claude = agent({
|
|
|
105225
105250
|
...homeEnv,
|
|
105226
105251
|
PWD: repoDir
|
|
105227
105252
|
};
|
|
105253
|
+
env2.CLAUDE_CODE_AUTO_COMPACT_WINDOW ||= "500000";
|
|
105228
105254
|
if (isBedrockRoute) {
|
|
105229
105255
|
env2.CLAUDE_CODE_USE_BEDROCK = "1";
|
|
105230
105256
|
}
|
|
@@ -144134,8 +144160,8 @@ function closeBrowserDaemon(toolState) {
|
|
|
144134
144160
|
|
|
144135
144161
|
// mcp/checkout.ts
|
|
144136
144162
|
import { createHash as createHash2 } from "node:crypto";
|
|
144137
|
-
import { statSync, unlinkSync as unlinkSync3, writeFileSync as
|
|
144138
|
-
import { join as
|
|
144163
|
+
import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
|
|
144164
|
+
import { join as join14 } from "node:path";
|
|
144139
144165
|
|
|
144140
144166
|
// node_modules/.pnpm/@ark+schema@0.56.0/node_modules/@ark/schema/out/shared/errors.js
|
|
144141
144167
|
var ArkError = class _ArkError extends CastableBase {
|
|
@@ -151604,6 +151630,367 @@ function ensureRepoState(toolState, init) {
|
|
|
151604
151630
|
return created;
|
|
151605
151631
|
}
|
|
151606
151632
|
|
|
151633
|
+
// utils/changeImpact.ts
|
|
151634
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
151635
|
+
import { writeFileSync as writeFileSync7 } from "node:fs";
|
|
151636
|
+
import { join as join8 } from "node:path";
|
|
151637
|
+
var MAX_CANDIDATES = 24;
|
|
151638
|
+
var MAX_ATOM_LENGTH = 128;
|
|
151639
|
+
var MAX_ATOMS = 12;
|
|
151640
|
+
var MAX_REFERENCES = 6;
|
|
151641
|
+
var MAX_EXCERPT = 160;
|
|
151642
|
+
var MAX_BYTES = 16e3;
|
|
151643
|
+
function isSymbolShaped(atom) {
|
|
151644
|
+
return atom.length >= 4 && atom.length <= MAX_ATOM_LENGTH && /[A-Z_]/.test(atom);
|
|
151645
|
+
}
|
|
151646
|
+
function getAtomConfidence(params) {
|
|
151647
|
+
const before = params.text.slice(Math.max(0, params.index - 64), params.index);
|
|
151648
|
+
const after = params.text.slice(
|
|
151649
|
+
params.index + params.atom.length,
|
|
151650
|
+
params.index + params.atom.length + 16
|
|
151651
|
+
);
|
|
151652
|
+
if (/\b(?:class|interface|type|enum|function|def|func|fn|struct|trait)\s+$/.test(before))
|
|
151653
|
+
return 4;
|
|
151654
|
+
if (/\b(?:const|let|var)\s+$/.test(before) || /^\s*\??\s*[:(]/.test(after)) return 3;
|
|
151655
|
+
const quoted = /['"`]$/.test(before) && /^['"`]/.test(after);
|
|
151656
|
+
if (quoted || before.endsWith(".") || after.startsWith(".") || params.atom.includes("_"))
|
|
151657
|
+
return 2;
|
|
151658
|
+
return 1;
|
|
151659
|
+
}
|
|
151660
|
+
function getChangedAtoms(text) {
|
|
151661
|
+
const atoms = /* @__PURE__ */ new Map();
|
|
151662
|
+
for (const match3 of text.matchAll(/[A-Za-z_][A-Za-z0-9_]*/g)) {
|
|
151663
|
+
const atom = match3[0];
|
|
151664
|
+
if (!isSymbolShaped(atom)) continue;
|
|
151665
|
+
const confidence = getAtomConfidence({ text, atom, index: match3.index ?? 0 });
|
|
151666
|
+
atoms.set(atom, Math.max(atoms.get(atom) ?? 0, confidence));
|
|
151667
|
+
}
|
|
151668
|
+
return atoms;
|
|
151669
|
+
}
|
|
151670
|
+
function recordChange(params) {
|
|
151671
|
+
for (const [atom, confidence] of getChangedAtoms(params.text)) {
|
|
151672
|
+
const change = params.changes.get(atom) ?? { atom, confidence, added: [], removed: [] };
|
|
151673
|
+
change.confidence = Math.max(change.confidence, confidence);
|
|
151674
|
+
change[params.location.kind].push(params.location);
|
|
151675
|
+
params.changes.set(atom, change);
|
|
151676
|
+
}
|
|
151677
|
+
}
|
|
151678
|
+
function locationKey(location) {
|
|
151679
|
+
return `${location.path}\0${location.line}`;
|
|
151680
|
+
}
|
|
151681
|
+
function collectFileChanges(collection, file2) {
|
|
151682
|
+
if (!file2.patch) return;
|
|
151683
|
+
let oldLine = 0;
|
|
151684
|
+
let newLine = 0;
|
|
151685
|
+
for (const patchLine of file2.patch.split("\n")) {
|
|
151686
|
+
const hunk = patchLine.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
151687
|
+
if (hunk) {
|
|
151688
|
+
oldLine = Number.parseInt(hunk[1], 10);
|
|
151689
|
+
newLine = Number.parseInt(hunk[2], 10);
|
|
151690
|
+
} else if (patchLine.startsWith("+")) {
|
|
151691
|
+
const location = { path: file2.filename, line: newLine++, kind: "added" };
|
|
151692
|
+
collection.addedLines.add(locationKey(location));
|
|
151693
|
+
recordChange({ changes: collection.changes, location, text: patchLine.slice(1) });
|
|
151694
|
+
} else if (patchLine.startsWith("-")) {
|
|
151695
|
+
const oldPath = file2.previous_filename ?? file2.filename;
|
|
151696
|
+
recordChange({
|
|
151697
|
+
changes: collection.changes,
|
|
151698
|
+
location: { path: oldPath, line: oldLine++, kind: "removed" },
|
|
151699
|
+
text: patchLine.slice(1)
|
|
151700
|
+
});
|
|
151701
|
+
} else if (!patchLine.startsWith("\\")) {
|
|
151702
|
+
oldLine++;
|
|
151703
|
+
newLine++;
|
|
151704
|
+
}
|
|
151705
|
+
}
|
|
151706
|
+
}
|
|
151707
|
+
function compareChanges(a, b) {
|
|
151708
|
+
if (a.confidence !== b.confidence) return b.confidence - a.confidence;
|
|
151709
|
+
const removedDelta = Number(b.removed.length > 0) - Number(a.removed.length > 0);
|
|
151710
|
+
if (removedDelta !== 0) return removedDelta;
|
|
151711
|
+
const aDelta = Math.abs(a.added.length - a.removed.length);
|
|
151712
|
+
const bDelta = Math.abs(b.added.length - b.removed.length);
|
|
151713
|
+
if (aDelta !== bDelta) return bDelta - aDelta;
|
|
151714
|
+
if (a.atom.length !== b.atom.length) return b.atom.length - a.atom.length;
|
|
151715
|
+
return a.atom < b.atom ? -1 : Number(a.atom > b.atom);
|
|
151716
|
+
}
|
|
151717
|
+
function getCandidates(files) {
|
|
151718
|
+
const collection = {
|
|
151719
|
+
changes: /* @__PURE__ */ new Map(),
|
|
151720
|
+
addedLines: /* @__PURE__ */ new Set()
|
|
151721
|
+
};
|
|
151722
|
+
for (const file2 of files) collectFileChanges(collection, file2);
|
|
151723
|
+
const candidates = [...collection.changes.values()].filter((change) => change.added.length !== change.removed.length).sort(compareChanges);
|
|
151724
|
+
return {
|
|
151725
|
+
selected: candidates.slice(0, MAX_CANDIDATES),
|
|
151726
|
+
total: candidates.length,
|
|
151727
|
+
addedLines: collection.addedLines
|
|
151728
|
+
};
|
|
151729
|
+
}
|
|
151730
|
+
function emptyBuckets(candidates) {
|
|
151731
|
+
return new Map(candidates.map((candidate) => [candidate.atom, { count: 0, samples: [] }]));
|
|
151732
|
+
}
|
|
151733
|
+
function createGrepRecordState() {
|
|
151734
|
+
return {
|
|
151735
|
+
phase: "path",
|
|
151736
|
+
path: "",
|
|
151737
|
+
line: "",
|
|
151738
|
+
sample: "",
|
|
151739
|
+
truncated: false,
|
|
151740
|
+
searchTail: "",
|
|
151741
|
+
searchStart: 0,
|
|
151742
|
+
matches: /* @__PURE__ */ new Set()
|
|
151743
|
+
};
|
|
151744
|
+
}
|
|
151745
|
+
function resetGrepRecord(state) {
|
|
151746
|
+
state.phase = "path";
|
|
151747
|
+
state.path = "";
|
|
151748
|
+
state.line = "";
|
|
151749
|
+
state.sample = "";
|
|
151750
|
+
state.truncated = false;
|
|
151751
|
+
state.searchTail = "";
|
|
151752
|
+
state.searchStart = 0;
|
|
151753
|
+
state.matches.clear();
|
|
151754
|
+
}
|
|
151755
|
+
function isWordCharacter(value2) {
|
|
151756
|
+
return value2 !== void 0 && /[A-Za-z0-9_]/.test(value2);
|
|
151757
|
+
}
|
|
151758
|
+
function recordCandidateMatches(params) {
|
|
151759
|
+
for (const candidate of params.candidates) {
|
|
151760
|
+
if (params.matches.has(candidate.atom)) continue;
|
|
151761
|
+
let index = params.text.indexOf(candidate.atom, params.minStart);
|
|
151762
|
+
while (index >= 0 && index < params.maxStart) {
|
|
151763
|
+
const before = params.text[index - 1];
|
|
151764
|
+
const after = params.text[index + candidate.atom.length];
|
|
151765
|
+
if (!isWordCharacter(before) && !isWordCharacter(after)) {
|
|
151766
|
+
params.matches.add(candidate.atom);
|
|
151767
|
+
break;
|
|
151768
|
+
}
|
|
151769
|
+
index = params.text.indexOf(candidate.atom, index + 1);
|
|
151770
|
+
}
|
|
151771
|
+
}
|
|
151772
|
+
}
|
|
151773
|
+
function consumeReferenceText(params) {
|
|
151774
|
+
const remaining = MAX_EXCERPT - params.state.sample.length;
|
|
151775
|
+
params.state.sample += params.text.slice(0, Math.max(remaining, 0));
|
|
151776
|
+
if (params.text.length > remaining) params.state.truncated = true;
|
|
151777
|
+
const searchable = params.state.searchTail + params.text;
|
|
151778
|
+
const consumed = params.final ? searchable.length : Math.max(0, searchable.length - params.maxAtomLength - 2);
|
|
151779
|
+
recordCandidateMatches({
|
|
151780
|
+
text: searchable,
|
|
151781
|
+
minStart: params.state.searchStart,
|
|
151782
|
+
maxStart: consumed,
|
|
151783
|
+
candidates: params.candidates,
|
|
151784
|
+
matches: params.state.matches
|
|
151785
|
+
});
|
|
151786
|
+
if (params.final) {
|
|
151787
|
+
params.state.searchTail = "";
|
|
151788
|
+
params.state.searchStart = 0;
|
|
151789
|
+
return;
|
|
151790
|
+
}
|
|
151791
|
+
const tailStart = Math.max(0, consumed - 1);
|
|
151792
|
+
params.state.searchTail = searchable.slice(tailStart);
|
|
151793
|
+
params.state.searchStart = consumed > 0 ? 1 : params.state.searchStart;
|
|
151794
|
+
}
|
|
151795
|
+
function recordGrepReference(params) {
|
|
151796
|
+
const prefix = `${params.treeish}:`;
|
|
151797
|
+
const path4 = params.state.path.startsWith(prefix) ? params.state.path.slice(prefix.length) : params.state.path;
|
|
151798
|
+
const line = Number.parseInt(params.state.line, 10);
|
|
151799
|
+
if (!Number.isFinite(line)) return;
|
|
151800
|
+
const reference2 = {
|
|
151801
|
+
path: path4,
|
|
151802
|
+
line,
|
|
151803
|
+
text: params.state.sample,
|
|
151804
|
+
truncated: params.state.truncated
|
|
151805
|
+
};
|
|
151806
|
+
if (params.addedLines.has(locationKey(reference2))) return;
|
|
151807
|
+
for (const atom of params.state.matches) {
|
|
151808
|
+
const bucket = params.buckets.get(atom);
|
|
151809
|
+
if (!bucket) continue;
|
|
151810
|
+
bucket.count++;
|
|
151811
|
+
if (bucket.samples.length < MAX_REFERENCES) bucket.samples.push(reference2);
|
|
151812
|
+
}
|
|
151813
|
+
}
|
|
151814
|
+
function consumeGrepChunk(params) {
|
|
151815
|
+
let offset = 0;
|
|
151816
|
+
while (offset < params.chunk.length) {
|
|
151817
|
+
if (params.state.phase !== "text") {
|
|
151818
|
+
const end2 = params.chunk.indexOf("\0", offset);
|
|
151819
|
+
const value2 = end2 < 0 ? params.chunk.slice(offset) : params.chunk.slice(offset, end2);
|
|
151820
|
+
params.state[params.state.phase] += value2;
|
|
151821
|
+
if (end2 < 0) return;
|
|
151822
|
+
params.state.phase = params.state.phase === "path" ? "line" : "text";
|
|
151823
|
+
offset = end2 + 1;
|
|
151824
|
+
continue;
|
|
151825
|
+
}
|
|
151826
|
+
const end = params.chunk.indexOf("\n", offset);
|
|
151827
|
+
const final = end >= 0;
|
|
151828
|
+
const text = final ? params.chunk.slice(offset, end) : params.chunk.slice(offset);
|
|
151829
|
+
consumeReferenceText({
|
|
151830
|
+
state: params.state,
|
|
151831
|
+
text,
|
|
151832
|
+
candidates: params.candidates,
|
|
151833
|
+
maxAtomLength: params.maxAtomLength,
|
|
151834
|
+
final
|
|
151835
|
+
});
|
|
151836
|
+
if (!final) return;
|
|
151837
|
+
recordGrepReference(params);
|
|
151838
|
+
resetGrepRecord(params.state);
|
|
151839
|
+
offset = end + 1;
|
|
151840
|
+
}
|
|
151841
|
+
}
|
|
151842
|
+
async function lookupReferences(params) {
|
|
151843
|
+
const buckets = emptyBuckets(params.candidates);
|
|
151844
|
+
if (params.candidates.length === 0) return { buckets, error: void 0 };
|
|
151845
|
+
const patterns = params.candidates.flatMap((candidate) => ["-e", candidate.atom]);
|
|
151846
|
+
const child = spawn4(
|
|
151847
|
+
"git",
|
|
151848
|
+
[
|
|
151849
|
+
"grep",
|
|
151850
|
+
"--no-color",
|
|
151851
|
+
"--full-name",
|
|
151852
|
+
"-n",
|
|
151853
|
+
"-z",
|
|
151854
|
+
"-I",
|
|
151855
|
+
"-w",
|
|
151856
|
+
"-F",
|
|
151857
|
+
...patterns,
|
|
151858
|
+
params.treeish,
|
|
151859
|
+
"--",
|
|
151860
|
+
":(top)"
|
|
151861
|
+
],
|
|
151862
|
+
{ env: resolveEnv(void 0), stdio: ["ignore", "pipe", "pipe"] }
|
|
151863
|
+
);
|
|
151864
|
+
const state = createGrepRecordState();
|
|
151865
|
+
const maxAtomLength = Math.max(...params.candidates.map((candidate) => candidate.atom.length));
|
|
151866
|
+
let spawnError;
|
|
151867
|
+
child.stdout.setEncoding("utf8");
|
|
151868
|
+
child.stdout.on("data", (chunk) => {
|
|
151869
|
+
consumeGrepChunk({
|
|
151870
|
+
chunk,
|
|
151871
|
+
state,
|
|
151872
|
+
candidates: params.candidates,
|
|
151873
|
+
maxAtomLength,
|
|
151874
|
+
buckets,
|
|
151875
|
+
addedLines: params.addedLines,
|
|
151876
|
+
treeish: params.treeish
|
|
151877
|
+
});
|
|
151878
|
+
});
|
|
151879
|
+
child.stderr.resume();
|
|
151880
|
+
const status = await new Promise((resolve3) => {
|
|
151881
|
+
child.once("error", (error49) => {
|
|
151882
|
+
spawnError = error49.message;
|
|
151883
|
+
resolve3(null);
|
|
151884
|
+
});
|
|
151885
|
+
child.once("close", resolve3);
|
|
151886
|
+
});
|
|
151887
|
+
if (spawnError || status !== 0 && status !== 1) {
|
|
151888
|
+
const error49 = spawnError ?? `git grep exit ${status ?? "unknown"}`;
|
|
151889
|
+
log.warning(`\xBB change impact lookup failed: ${error49}`);
|
|
151890
|
+
return { buckets: emptyBuckets(params.candidates), error: error49 };
|
|
151891
|
+
}
|
|
151892
|
+
if (state.phase === "text") {
|
|
151893
|
+
consumeReferenceText({
|
|
151894
|
+
state,
|
|
151895
|
+
text: "",
|
|
151896
|
+
candidates: params.candidates,
|
|
151897
|
+
maxAtomLength,
|
|
151898
|
+
final: true
|
|
151899
|
+
});
|
|
151900
|
+
recordGrepReference({ state, buckets, addedLines: params.addedLines, treeish: params.treeish });
|
|
151901
|
+
} else if (state.phase !== "path" || state.path) {
|
|
151902
|
+
const error49 = "malformed git grep output";
|
|
151903
|
+
log.warning(`\xBB change impact lookup failed: ${error49}`);
|
|
151904
|
+
return { buckets: emptyBuckets(params.candidates), error: error49 };
|
|
151905
|
+
}
|
|
151906
|
+
return { buckets, error: void 0 };
|
|
151907
|
+
}
|
|
151908
|
+
function formatReference(reference2) {
|
|
151909
|
+
const text = reference2.text.trim();
|
|
151910
|
+
const excerpt = reference2.truncated ? `${text.slice(0, MAX_EXCERPT - 1)}\u2026` : text;
|
|
151911
|
+
return `- ${JSON.stringify(reference2.path)}:${reference2.line} ${JSON.stringify(excerpt)}`;
|
|
151912
|
+
}
|
|
151913
|
+
function formatChangedLocations(change) {
|
|
151914
|
+
const selected = [change.removed[0], change.added[0]].filter(
|
|
151915
|
+
(location) => location !== void 0
|
|
151916
|
+
);
|
|
151917
|
+
const rendered = selected.map((location) => `${location.kind} ${JSON.stringify(location.path)}:${location.line}`).join(", ");
|
|
151918
|
+
const omitted = change.removed.length + change.added.length - selected.length;
|
|
151919
|
+
return omitted > 0 ? `${rendered}; ${omitted} more changed location(s) omitted` : rendered;
|
|
151920
|
+
}
|
|
151921
|
+
function formatEntry(entry) {
|
|
151922
|
+
const count = entry.bucket.count;
|
|
151923
|
+
const sampleNote = count > entry.bucket.samples.length ? `${count} matched line(s); first ${entry.bucket.samples.length} shown.` : `${count} matched line(s).`;
|
|
151924
|
+
return [
|
|
151925
|
+
`## \`${entry.change.atom}\``,
|
|
151926
|
+
"",
|
|
151927
|
+
`Changed at: ${formatChangedLocations(entry.change)}`,
|
|
151928
|
+
`Tracked references: ${sampleNote}`,
|
|
151929
|
+
"",
|
|
151930
|
+
...entry.bucket.samples.map(formatReference)
|
|
151931
|
+
].join("\n");
|
|
151932
|
+
}
|
|
151933
|
+
function formatArtifact(params) {
|
|
151934
|
+
const lines = [
|
|
151935
|
+
"# Change impact leads",
|
|
151936
|
+
"",
|
|
151937
|
+
"> Supplemental and explicitly incomplete. Read the authoritative `diffPath` TOC and raw lines first. Absence here is not evidence of no impact.",
|
|
151938
|
+
"",
|
|
151939
|
+
`Scope: base ${params.baseSha}, head ${params.headSha}; ${params.fileCount} changed file(s).`,
|
|
151940
|
+
"Generated from changed symbol-shaped identifiers, then one exact-word lookup pinned to the checked-out head across Git-tracked text files. Changed added lines are excluded; snippets are capped at 160 characters.",
|
|
151941
|
+
"",
|
|
151942
|
+
`Candidate atoms searched: ${params.candidateCount}/${params.totalCandidateCount}.`
|
|
151943
|
+
];
|
|
151944
|
+
if (params.lookupError) lines.push(`Tracked-file lookup unavailable: ${params.lookupError}.`);
|
|
151945
|
+
let content = `${lines.join("\n")}
|
|
151946
|
+
`;
|
|
151947
|
+
let renderedAtomCount = 0;
|
|
151948
|
+
let referenceCount = 0;
|
|
151949
|
+
for (const entry of params.entries.filter((candidate) => candidate.bucket.count > 0)) {
|
|
151950
|
+
if (renderedAtomCount >= MAX_ATOMS) break;
|
|
151951
|
+
const addition = `
|
|
151952
|
+
${formatEntry(entry)}
|
|
151953
|
+
`;
|
|
151954
|
+
if (Buffer.byteLength(content + addition) > MAX_BYTES) break;
|
|
151955
|
+
content += addition;
|
|
151956
|
+
renderedAtomCount++;
|
|
151957
|
+
referenceCount += entry.bucket.count;
|
|
151958
|
+
}
|
|
151959
|
+
if (renderedAtomCount === 0) {
|
|
151960
|
+
const empty = "\nNo bounded tracked references found. This is not evidence of no wider impact.\n";
|
|
151961
|
+
if (Buffer.byteLength(content + empty) <= MAX_BYTES) content += empty;
|
|
151962
|
+
}
|
|
151963
|
+
return { content, renderedAtomCount, referenceCount };
|
|
151964
|
+
}
|
|
151965
|
+
async function createChangeImpactArtifact(ctx, params) {
|
|
151966
|
+
const candidates = getCandidates(params.files);
|
|
151967
|
+
const lookup2 = await lookupReferences({
|
|
151968
|
+
candidates: candidates.selected,
|
|
151969
|
+
addedLines: candidates.addedLines,
|
|
151970
|
+
treeish: params.headSha
|
|
151971
|
+
});
|
|
151972
|
+
const artifact = formatArtifact({
|
|
151973
|
+
entries: candidates.selected.map((change) => {
|
|
151974
|
+
return { change, bucket: lookup2.buckets.get(change.atom) ?? { count: 0, samples: [] } };
|
|
151975
|
+
}),
|
|
151976
|
+
candidateCount: candidates.selected.length,
|
|
151977
|
+
totalCandidateCount: candidates.total,
|
|
151978
|
+
fileCount: params.files.length,
|
|
151979
|
+
baseSha: params.baseSha,
|
|
151980
|
+
headSha: params.headSha,
|
|
151981
|
+
lookupError: lookup2.error
|
|
151982
|
+
});
|
|
151983
|
+
const path4 = join8(ctx.tmpdir, `pr-${params.pullNumber}-${params.headSha.slice(0, 7)}-impact.md`);
|
|
151984
|
+
writeFileSync7(path4, artifact.content);
|
|
151985
|
+
return {
|
|
151986
|
+
path: path4,
|
|
151987
|
+
candidateCount: candidates.selected.length,
|
|
151988
|
+
renderedAtomCount: artifact.renderedAtomCount,
|
|
151989
|
+
referenceCount: artifact.referenceCount,
|
|
151990
|
+
bytes: Buffer.byteLength(artifact.content)
|
|
151991
|
+
};
|
|
151992
|
+
}
|
|
151993
|
+
|
|
151607
151994
|
// utils/diffCoverage.ts
|
|
151608
151995
|
import { isAbsolute, normalize as normalize2, resolve } from "node:path";
|
|
151609
151996
|
function countLines(params) {
|
|
@@ -151691,27 +152078,6 @@ function getDiffCoverageBreakdown(params) {
|
|
|
151691
152078
|
files
|
|
151692
152079
|
};
|
|
151693
152080
|
}
|
|
151694
|
-
function renderDiffCoverageBreakdown(params) {
|
|
151695
|
-
const breakdown = params.breakdown;
|
|
151696
|
-
const lines = [];
|
|
151697
|
-
lines.push(`diff coverage report for \`${params.diffPath}\``);
|
|
151698
|
-
lines.push(
|
|
151699
|
-
`overall: ${breakdown.coveredLines}/${breakdown.totalLines} lines read (${breakdown.coveragePercent}%), unread: ${breakdown.unreadLines}`
|
|
151700
|
-
);
|
|
151701
|
-
lines.push(`covered ranges: ${formatRanges({ ranges: breakdown.coveredRanges })}`);
|
|
151702
|
-
lines.push(`unread ranges: ${formatRanges({ ranges: breakdown.unreadRanges })}`);
|
|
151703
|
-
lines.push("");
|
|
151704
|
-
lines.push("per-file TOC coverage:");
|
|
151705
|
-
for (const file2 of breakdown.files) {
|
|
151706
|
-
const filePercent = file2.totalLines ? Number((file2.coveredLines / file2.totalLines * 100).toFixed(2)) : 100;
|
|
151707
|
-
lines.push(
|
|
151708
|
-
`- ${file2.filename} (toc lines ${file2.startLine}-${file2.endLine}): ${file2.coveredLines}/${file2.totalLines} lines read (${filePercent}%)`
|
|
151709
|
-
);
|
|
151710
|
-
lines.push(` read: ${formatRanges({ ranges: file2.coveredRanges })}`);
|
|
151711
|
-
lines.push(` unread: ${formatRanges({ ranges: file2.unreadRanges })}`);
|
|
151712
|
-
}
|
|
151713
|
-
return lines.join("\n");
|
|
151714
|
-
}
|
|
151715
152081
|
function resolveOffsetBase(params) {
|
|
151716
152082
|
const lower2 = params.toolName.toLowerCase();
|
|
151717
152083
|
if (lower2 === "readfile" || lower2.endsWith(".readfile")) {
|
|
@@ -151842,10 +152208,6 @@ function countLinesInRanges(params) {
|
|
|
151842
152208
|
}
|
|
151843
152209
|
return total;
|
|
151844
152210
|
}
|
|
151845
|
-
function formatRanges(params) {
|
|
151846
|
-
if (params.ranges.length === 0) return "none";
|
|
151847
|
-
return params.ranges.map((range2) => `${range2.startLine}-${range2.endLine}`).join(", ");
|
|
151848
|
-
}
|
|
151849
152211
|
function clampLine(params) {
|
|
151850
152212
|
if (params.value < 1) return 1;
|
|
151851
152213
|
if (params.value > params.totalLines) return params.totalLines;
|
|
@@ -151872,7 +152234,7 @@ function readNumber(params) {
|
|
|
151872
152234
|
import { execSync } from "node:child_process";
|
|
151873
152235
|
import { createHash } from "node:crypto";
|
|
151874
152236
|
import { readFileSync as readFileSync3, realpathSync, unlinkSync } from "node:fs";
|
|
151875
|
-
import { join as
|
|
152237
|
+
import { join as join9 } from "node:path";
|
|
151876
152238
|
|
|
151877
152239
|
// utils/shell.ts
|
|
151878
152240
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -151950,7 +152312,7 @@ function resolveHooksDir(cwd, gitPath) {
|
|
|
151950
152312
|
cwd,
|
|
151951
152313
|
log: false
|
|
151952
152314
|
}).trim();
|
|
151953
|
-
const hooksDir =
|
|
152315
|
+
const hooksDir = join9(commonDir, "hooks");
|
|
151954
152316
|
hooksDirCache.set(cwd, hooksDir);
|
|
151955
152317
|
return hooksDir;
|
|
151956
152318
|
}
|
|
@@ -153098,13 +153460,13 @@ function _op(fn2, options) {
|
|
|
153098
153460
|
|
|
153099
153461
|
// mcp/git.ts
|
|
153100
153462
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
153101
|
-
import { writeFileSync as
|
|
153102
|
-
import { join as
|
|
153463
|
+
import { writeFileSync as writeFileSync8 } from "node:fs";
|
|
153464
|
+
import { join as join12 } from "node:path";
|
|
153103
153465
|
|
|
153104
153466
|
// utils/apiCommit.ts
|
|
153105
153467
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
153106
153468
|
import { lstat, readlink } from "node:fs/promises";
|
|
153107
|
-
import { join as
|
|
153469
|
+
import { join as join10 } from "node:path";
|
|
153108
153470
|
var GITHUB_API = "https://api.github.com";
|
|
153109
153471
|
var MAX_BLOB_BYTES = 30 * 1024 * 1024;
|
|
153110
153472
|
var BLOB_UPLOAD_CONCURRENCY = 8;
|
|
@@ -153169,7 +153531,7 @@ async function assertApiCommittable(files) {
|
|
|
153169
153531
|
const root = getRepoRoot();
|
|
153170
153532
|
const attrs = $2(
|
|
153171
153533
|
"git",
|
|
153172
|
-
["check-attr", "filter", "-z", "--", ...present.map((p2) =>
|
|
153534
|
+
["check-attr", "filter", "-z", "--", ...present.map((p2) => join10(root, p2))],
|
|
153173
153535
|
{ log: false }
|
|
153174
153536
|
);
|
|
153175
153537
|
const parts = attrs.split("\0");
|
|
@@ -153181,7 +153543,7 @@ async function assertApiCommittable(files) {
|
|
|
153181
153543
|
}
|
|
153182
153544
|
}
|
|
153183
153545
|
for (const path4 of present) {
|
|
153184
|
-
const stat = await lstat(
|
|
153546
|
+
const stat = await lstat(join10(root, path4));
|
|
153185
153547
|
if (stat.isDirectory()) {
|
|
153186
153548
|
throw new Error(
|
|
153187
153549
|
`'${path4}' is a directory (nested repository or submodule?) \u2014 signed commits only support files and symlinks.`
|
|
@@ -153190,7 +153552,7 @@ async function assertApiCommittable(files) {
|
|
|
153190
153552
|
}
|
|
153191
153553
|
}
|
|
153192
153554
|
async function createBlobEntry(params) {
|
|
153193
|
-
const absPath =
|
|
153555
|
+
const absPath = join10(params.repoRoot, params.path);
|
|
153194
153556
|
const stat = await lstat(absPath);
|
|
153195
153557
|
if (stat.size > MAX_BLOB_BYTES) {
|
|
153196
153558
|
throw new Error(
|
|
@@ -153370,7 +153732,7 @@ this usually means your local branch has commits that were never pushed \u2014 s
|
|
|
153370
153732
|
var core3 = __toESM(require_core(), 1);
|
|
153371
153733
|
import { createSign } from "node:crypto";
|
|
153372
153734
|
import { rename, writeFile } from "node:fs/promises";
|
|
153373
|
-
import { dirname as dirname3, join as
|
|
153735
|
+
import { dirname as dirname3, join as join11 } from "node:path";
|
|
153374
153736
|
|
|
153375
153737
|
// node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
|
|
153376
153738
|
var import_light = __toESM(require_light(), 1);
|
|
@@ -157314,7 +157676,7 @@ function getGitHubUsageSummary() {
|
|
|
157314
157676
|
}
|
|
157315
157677
|
async function writeGitHubUsageSummaryToFile(path4) {
|
|
157316
157678
|
const summary2 = getGitHubUsageSummary();
|
|
157317
|
-
const tmpPath =
|
|
157679
|
+
const tmpPath = join11(dirname3(path4), `.usage-summary-${process.pid}.tmp`);
|
|
157318
157680
|
await writeFile(tmpPath, JSON.stringify(summary2));
|
|
157319
157681
|
await rename(tmpPath, path4);
|
|
157320
157682
|
}
|
|
@@ -158441,8 +158803,8 @@ function countAhead(head, base, cwd) {
|
|
|
158441
158803
|
function spillGitOutput(params) {
|
|
158442
158804
|
const tempDir = process.env.PULLFROG_TEMP_DIR;
|
|
158443
158805
|
if (!tempDir) throw new Error("PULLFROG_TEMP_DIR not set");
|
|
158444
|
-
const outputPath =
|
|
158445
|
-
|
|
158806
|
+
const outputPath = join12(tempDir, `git-${params.command}-${randomUUID3().slice(0, 8)}.txt`);
|
|
158807
|
+
writeFileSync8(outputPath, params.output);
|
|
158446
158808
|
const previewByLines = params.output.split("\n").slice(0, OVERFLOW_PREVIEW_LINES).join("\n");
|
|
158447
158809
|
const preview = previewByLines.length <= OVERFLOW_PREVIEW_MAX_CHARS ? previewByLines : `${previewByLines.slice(0, OVERFLOW_PREVIEW_MAX_CHARS)}\u2026`;
|
|
158448
158810
|
log.info(
|
|
@@ -159074,13 +159436,15 @@ function addFooter(ctx, body) {
|
|
|
159074
159436
|
var Comment = type({
|
|
159075
159437
|
issueNumber: type.number.describe("the issue number to comment on"),
|
|
159076
159438
|
body: type.string.describe("the comment body content"),
|
|
159077
|
-
type: type.enumerated("Plan", "Comment").describe(
|
|
159439
|
+
type: type.enumerated("Plan", "Comment").describe(
|
|
159440
|
+
"Plan: standalone plan comment on another target. Comment: regular comment (default)."
|
|
159441
|
+
).optional()
|
|
159078
159442
|
});
|
|
159079
159443
|
function CreateCommentTool(ctx) {
|
|
159080
159444
|
return tool({
|
|
159081
159445
|
name: "create_issue_comment",
|
|
159082
159446
|
mutates: true,
|
|
159083
|
-
description:
|
|
159447
|
+
description: "Create a comment on a GitHub issue or PR. Example: `create_issue_comment({ issueNumber: 1234, body: \"Thanks for the report.\" })`. For the current run's answer, progress, or plan use report_progress instead. Use this on the current target only when the task explicitly requests a standalone comment. Skip report_progress only when that current-target comment is the task's sole requested deliverable.",
|
|
159084
159448
|
parameters: Comment,
|
|
159085
159449
|
execute: execute(async ({ issueNumber, body, type: commentType }) => {
|
|
159086
159450
|
const bodyWithFooter = addFooter(ctx, body);
|
|
@@ -159092,9 +159456,6 @@ function CreateCommentTool(ctx) {
|
|
|
159092
159456
|
});
|
|
159093
159457
|
ctx.toolState.wasUpdated = true;
|
|
159094
159458
|
log.info(`\xBB created comment ${result.data.id}`);
|
|
159095
|
-
if (commentType !== "Plan" && issueNumber === ctx.payload.event.issue_number) {
|
|
159096
|
-
ctx.toolState.answerCommentPosted = true;
|
|
159097
|
-
}
|
|
159098
159459
|
if (commentType === "Plan") {
|
|
159099
159460
|
if (result.data.node_id) {
|
|
159100
159461
|
await patchWorkflowRunFields(ctx, { planCommentNodeId: result.data.node_id });
|
|
@@ -159900,10 +160261,6 @@ function runDiffCoveragePreflight(params) {
|
|
|
159900
160261
|
unread.push({ path: file2.filename, ranges: rangesText, unreadLines: fileUnreadLines });
|
|
159901
160262
|
unreadLines += fileUnreadLines;
|
|
159902
160263
|
}
|
|
159903
|
-
coverageState.lastBreakdown = renderDiffCoverageBreakdown({
|
|
159904
|
-
diffPath: coverageState.diffPath,
|
|
159905
|
-
breakdown
|
|
159906
|
-
});
|
|
159907
160264
|
log.debug(
|
|
159908
160265
|
`diff coverage pre-flight breakdown: coveredLines=${breakdown.coveredLines}, unreadLines=${unreadLines}`
|
|
159909
160266
|
);
|
|
@@ -159919,9 +160276,7 @@ function runDiffCoveragePreflight(params) {
|
|
|
159919
160276
|
`diff coverage pre-flight: some TOC regions were not read before review submission. this is a one-time nudge \u2014 read the ranges below from ${coverageState.diffPath} on a best-effort basis, then call create_pull_request_review again. you are NOT obligated to read generated artifacts (lockfiles like pnpm-lock.yaml / package-lock.json / yarn.lock / Cargo.lock; codegen output like *.gen.*, *.pb.go, *.generated.*; snapshot/fixture dirs like __snapshots__/; migration metadata like drizzle/meta/, prisma migration SQL). if every unread region is generated, retry immediately without reading. this pre-flight will not block again in this review session.
|
|
159920
160277
|
|
|
159921
160278
|
unread TOC regions:
|
|
159922
|
-
${unreadText}
|
|
159923
|
-
|
|
159924
|
-
${coverageState.lastBreakdown}`
|
|
160279
|
+
${unreadText}`
|
|
159925
160280
|
);
|
|
159926
160281
|
}
|
|
159927
160282
|
async function clearStrandedPendingReview(ctx, params) {
|
|
@@ -160043,9 +160398,9 @@ async function reportReviewNodeId(ctx, params) {
|
|
|
160043
160398
|
import { execFileSync as execFileSync8, execSync as execSync2 } from "node:child_process";
|
|
160044
160399
|
import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
160045
160400
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
160046
|
-
import { join as
|
|
160401
|
+
import { join as join13 } from "node:path";
|
|
160047
160402
|
function createTempDirectory() {
|
|
160048
|
-
const sharedTempDir = mkdtempSync2(
|
|
160403
|
+
const sharedTempDir = mkdtempSync2(join13(tmpdir3(), "pullfrog-"));
|
|
160049
160404
|
process.env.PULLFROG_TEMP_DIR = sharedTempDir;
|
|
160050
160405
|
log.info(`\xBB created temp dir at ${sharedTempDir}`);
|
|
160051
160406
|
return sharedTempDir;
|
|
@@ -160090,13 +160445,13 @@ function wipeRunnerLeakSurface() {
|
|
|
160090
160445
|
return [];
|
|
160091
160446
|
}
|
|
160092
160447
|
};
|
|
160093
|
-
const fileCommandsDir =
|
|
160448
|
+
const fileCommandsDir = join13(runnerTemp, "_runner_file_commands");
|
|
160094
160449
|
for (const entry of listDir(fileCommandsDir)) {
|
|
160095
|
-
tryUnlink(
|
|
160450
|
+
tryUnlink(join13(fileCommandsDir, entry));
|
|
160096
160451
|
}
|
|
160097
160452
|
for (const entry of listDir(runnerTemp)) {
|
|
160098
160453
|
if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
|
|
160099
|
-
tryUnlink(
|
|
160454
|
+
tryUnlink(join13(runnerTemp, entry));
|
|
160100
160455
|
}
|
|
160101
160456
|
}
|
|
160102
160457
|
if (wiped.length > 0) {
|
|
@@ -160602,21 +160957,23 @@ function CheckoutPrTool(ctx) {
|
|
|
160602
160957
|
"PULLFROG_TEMP_DIR not set - checkout_pr must run in pullfrog action context"
|
|
160603
160958
|
);
|
|
160604
160959
|
}
|
|
160605
|
-
const
|
|
160960
|
+
const checkoutSha = primary.checkoutSha;
|
|
160961
|
+
if (!checkoutSha) throw new Error("checkout completed without a resolved HEAD SHA");
|
|
160962
|
+
const headShort = checkoutSha.slice(0, 7);
|
|
160606
160963
|
let incrementalDiffPath;
|
|
160607
|
-
if (primary.beforeSha
|
|
160964
|
+
if (primary.beforeSha) {
|
|
160608
160965
|
const beforeShort = primary.beforeSha.slice(0, 7);
|
|
160609
160966
|
const incremental = computeIncrementalDiff({
|
|
160610
160967
|
baseBranch: pr.baseRef,
|
|
160611
160968
|
beforeSha: primary.beforeSha,
|
|
160612
|
-
headSha:
|
|
160969
|
+
headSha: checkoutSha
|
|
160613
160970
|
});
|
|
160614
160971
|
if (incremental) {
|
|
160615
|
-
incrementalDiffPath =
|
|
160972
|
+
incrementalDiffPath = join14(
|
|
160616
160973
|
tempDir,
|
|
160617
160974
|
`pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
|
|
160618
160975
|
);
|
|
160619
|
-
|
|
160976
|
+
writeFileSync9(incrementalDiffPath, incremental);
|
|
160620
160977
|
log.info(
|
|
160621
160978
|
`\xBB incremental diff computed (${incremental.length} bytes) \u2192 ${incrementalDiffPath}`
|
|
160622
160979
|
);
|
|
@@ -160626,8 +160983,8 @@ function CheckoutPrTool(ctx) {
|
|
|
160626
160983
|
const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
|
|
160627
160984
|
log.debug(`formatted diff preview (first 100 lines):
|
|
160628
160985
|
${diffPreview}`);
|
|
160629
|
-
const diffPath =
|
|
160630
|
-
|
|
160986
|
+
const diffPath = join14(tempDir, `pr-${pull_number}-${headShort}.diff`);
|
|
160987
|
+
writeFileSync9(diffPath, formatResult.content);
|
|
160631
160988
|
log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
|
|
160632
160989
|
primary.diffCoverage = createDiffCoverageState({
|
|
160633
160990
|
diffPath,
|
|
@@ -160638,14 +160995,56 @@ ${diffPreview}`);
|
|
|
160638
160995
|
log.debug(
|
|
160639
160996
|
`\xBB diff coverage initialized: diffPath=${diffPath}, totalLines=${primary.diffCoverage.totalLines}, tocEntries=${primary.diffCoverage.tocEntries.length}`
|
|
160640
160997
|
);
|
|
160998
|
+
let impactPath;
|
|
160999
|
+
if (process.env.PULLFROG_DISABLE_CHANGE_IMPACT === "1") {
|
|
161000
|
+
log.info("\xBB change impact disabled by PULLFROG_DISABLE_CHANGE_IMPACT");
|
|
161001
|
+
} else {
|
|
161002
|
+
let revisionVerified = false;
|
|
161003
|
+
let revisionFailure;
|
|
161004
|
+
try {
|
|
161005
|
+
const latest = await ctx.octokit.rest.pulls.get({
|
|
161006
|
+
owner: ctx.repo.owner,
|
|
161007
|
+
repo: ctx.repo.name,
|
|
161008
|
+
pull_number
|
|
161009
|
+
});
|
|
161010
|
+
revisionVerified = latest.data.head.sha === checkoutSha && latest.data.base.sha === prResponse.data.base.sha;
|
|
161011
|
+
} catch (error49) {
|
|
161012
|
+
revisionFailure = error49 instanceof Error ? error49.message : String(error49);
|
|
161013
|
+
}
|
|
161014
|
+
if (revisionFailure !== void 0) {
|
|
161015
|
+
log.warning(
|
|
161016
|
+
`\xBB change impact skipped: PR revision could not be verified (${revisionFailure})`
|
|
161017
|
+
);
|
|
161018
|
+
} else if (!revisionVerified) {
|
|
161019
|
+
log.warning("\xBB change impact skipped: PR revision changed while the diff was fetched");
|
|
161020
|
+
} else {
|
|
161021
|
+
try {
|
|
161022
|
+
const impact = await createChangeImpactArtifact(ctx, {
|
|
161023
|
+
files: formatResult.files,
|
|
161024
|
+
pullNumber: pull_number,
|
|
161025
|
+
baseSha: prResponse.data.base.sha,
|
|
161026
|
+
headSha: checkoutSha
|
|
161027
|
+
});
|
|
161028
|
+
impactPath = impact.path;
|
|
161029
|
+
log.info(
|
|
161030
|
+
`\xBB change impact: ${impact.candidateCount} candidate atom(s), ${impact.renderedAtomCount} detailed atom(s), ${impact.referenceCount} tracked reference(s), ${impact.bytes} bytes \u2192 ${impact.path}`
|
|
161031
|
+
);
|
|
161032
|
+
} catch (error49) {
|
|
161033
|
+
log.warning(
|
|
161034
|
+
`\xBB change impact skipped: generation failed (${error49 instanceof Error ? error49.message : String(error49)})`
|
|
161035
|
+
);
|
|
161036
|
+
}
|
|
161037
|
+
}
|
|
161038
|
+
}
|
|
160641
161039
|
const cached4 = /* @__PURE__ */ new Map();
|
|
160642
161040
|
for (const file2 of formatResult.files) {
|
|
160643
161041
|
cached4.set(file2.filename, commentableLinesForFile(file2.patch));
|
|
160644
161042
|
}
|
|
160645
161043
|
primary.commentableLinesByFile = cached4;
|
|
160646
161044
|
primary.commentableLinesPullNumber = pull_number;
|
|
160647
|
-
primary.commentableLinesCheckoutSha =
|
|
160648
|
-
const incrementalInstructions = incrementalDiffPath ? `
|
|
161045
|
+
primary.commentableLinesCheckoutSha = checkoutSha;
|
|
161046
|
+
const incrementalInstructions = incrementalDiffPath ? `IMPORTANT: incrementalDiffPath contains ONLY the changes since the last reviewed version (computed via range-diff). you MUST read incrementalDiffPath FIRST to understand what changed, then use diffPath for full PR context. do NOT skip the incremental diff. ` : "";
|
|
161047
|
+
const impactInstructions = impactPath ? ` impactPath contains bounded, deterministic reference leads for semantic atoms changed by the PR. read the authoritative diff TOC and relevant raw diff lines FIRST; only then use impactPath as a supplemental, explicitly incomplete lead list. impactPath never establishes review coverage and an empty artifact is not evidence of no wider impact.` : "";
|
|
160649
161048
|
const COMMIT_LOG_MAX = 200;
|
|
160650
161049
|
const baseRange = `origin/${pr.baseRef}..HEAD`;
|
|
160651
161050
|
let commitCount = 0;
|
|
@@ -160681,6 +161080,7 @@ ${diffPreview}`);
|
|
|
160681
161080
|
url: prResponse.data.html_url,
|
|
160682
161081
|
headRepo: pr.headRepoFullName,
|
|
160683
161082
|
diffPath,
|
|
161083
|
+
impactPath,
|
|
160684
161084
|
incrementalDiffPath,
|
|
160685
161085
|
toc: formatResult.toc,
|
|
160686
161086
|
commitCount,
|
|
@@ -160688,7 +161088,7 @@ ${diffPreview}`);
|
|
|
160688
161088
|
commitLogTruncated,
|
|
160689
161089
|
commitLogUnavailable,
|
|
160690
161090
|
hookWarning: checkoutResult.hookWarning,
|
|
160691
|
-
instructions: `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.` +
|
|
161091
|
+
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
|
|
160692
161092
|
};
|
|
160693
161093
|
};
|
|
160694
161094
|
return tool({
|
|
@@ -160735,8 +161135,8 @@ ${dirty}`
|
|
|
160735
161135
|
}
|
|
160736
161136
|
|
|
160737
161137
|
// mcp/checkSuite.ts
|
|
160738
|
-
import { mkdirSync as mkdirSync7, writeFileSync as
|
|
160739
|
-
import { join as
|
|
161138
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
161139
|
+
import { join as join15 } from "node:path";
|
|
160740
161140
|
var GetCheckSuiteLogs = type({
|
|
160741
161141
|
check_suite_id: type.number.describe("the id from check_suite.id")
|
|
160742
161142
|
});
|
|
@@ -160832,7 +161232,7 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
160832
161232
|
if (!tempDir) {
|
|
160833
161233
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
160834
161234
|
}
|
|
160835
|
-
const logsDir =
|
|
161235
|
+
const logsDir = join15(tempDir, "ci-logs");
|
|
160836
161236
|
mkdirSync7(logsDir, { recursive: true });
|
|
160837
161237
|
const jobResults = [];
|
|
160838
161238
|
for (const run4 of failedRuns) {
|
|
@@ -160859,8 +161259,8 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
160859
161259
|
);
|
|
160860
161260
|
}
|
|
160861
161261
|
const logsText = await logsResult.text();
|
|
160862
|
-
const logPath =
|
|
160863
|
-
|
|
161262
|
+
const logPath = join15(logsDir, `job-${job.id}.log`);
|
|
161263
|
+
writeFileSync10(logPath, logsText);
|
|
160864
161264
|
const analysis = analyzeLog(logsText, 80);
|
|
160865
161265
|
const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
|
|
160866
161266
|
jobResults.push({
|
|
@@ -160908,8 +161308,8 @@ function GetCheckSuiteLogsTool(ctx) {
|
|
|
160908
161308
|
}
|
|
160909
161309
|
|
|
160910
161310
|
// mcp/commitInfo.ts
|
|
160911
|
-
import { writeFileSync as
|
|
160912
|
-
import { join as
|
|
161311
|
+
import { writeFileSync as writeFileSync11 } from "node:fs";
|
|
161312
|
+
import { join as join16 } from "node:path";
|
|
160913
161313
|
var CommitInfo = type({
|
|
160914
161314
|
sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
|
|
160915
161315
|
});
|
|
@@ -160933,8 +161333,8 @@ function CommitInfoTool(ctx) {
|
|
|
160933
161333
|
"PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
|
|
160934
161334
|
);
|
|
160935
161335
|
}
|
|
160936
|
-
const diffFile =
|
|
160937
|
-
|
|
161336
|
+
const diffFile = join16(tempDir, `commit-${sha.slice(0, 7)}.diff`);
|
|
161337
|
+
writeFileSync11(diffFile, formatResult.content);
|
|
160938
161338
|
log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
|
|
160939
161339
|
return {
|
|
160940
161340
|
sha: data.sha,
|
|
@@ -160961,7 +161361,7 @@ import { performance as performance8 } from "node:perf_hooks";
|
|
|
160961
161361
|
|
|
160962
161362
|
// prep/installNodeDependencies.ts
|
|
160963
161363
|
import { existsSync as existsSync6 } from "node:fs";
|
|
160964
|
-
import { join as
|
|
161364
|
+
import { join as join18 } from "node:path";
|
|
160965
161365
|
|
|
160966
161366
|
// node_modules/.pnpm/package-manager-detector@1.6.0/node_modules/package-manager-detector/dist/commands.mjs
|
|
160967
161367
|
function dashDashArg(agent2, agentCommand) {
|
|
@@ -161260,7 +161660,7 @@ function isMetadataYarnClassic(metadataPath) {
|
|
|
161260
161660
|
var import_semver3 = __toESM(require_semver2(), 1);
|
|
161261
161661
|
import { existsSync as existsSync5 } from "node:fs";
|
|
161262
161662
|
import { mkdir, readFile as readFile2 } from "node:fs/promises";
|
|
161263
|
-
import { delimiter, join as
|
|
161663
|
+
import { delimiter, join as join17 } from "node:path";
|
|
161264
161664
|
var SUPPORTED_NAMES = ["npm", "pnpm", "yarn", "bun"];
|
|
161265
161665
|
var COREPACK_MANAGED = ["pnpm", "yarn"];
|
|
161266
161666
|
function isSupported(name) {
|
|
@@ -161298,7 +161698,7 @@ function parseDevEnginesField(field) {
|
|
|
161298
161698
|
};
|
|
161299
161699
|
}
|
|
161300
161700
|
async function resolvePackageManagerSpec(cwd) {
|
|
161301
|
-
const pkgPath =
|
|
161701
|
+
const pkgPath = join17(cwd, "package.json");
|
|
161302
161702
|
if (!existsSync5(pkgPath)) return null;
|
|
161303
161703
|
let pkg;
|
|
161304
161704
|
try {
|
|
@@ -161361,7 +161761,7 @@ async function currentVersion(name) {
|
|
|
161361
161761
|
return result.stdout.trim();
|
|
161362
161762
|
}
|
|
161363
161763
|
function packageManagerBinDir(tmpdir4) {
|
|
161364
|
-
return
|
|
161764
|
+
return join17(tmpdir4, "pm-bin");
|
|
161365
161765
|
}
|
|
161366
161766
|
async function ensurePackageManager(params) {
|
|
161367
161767
|
const spec = params.spec;
|
|
@@ -161432,7 +161832,7 @@ async function installFallback(name, installSpec) {
|
|
|
161432
161832
|
return result.stderr || `failed to install ${name}`;
|
|
161433
161833
|
}
|
|
161434
161834
|
if (name === "deno") {
|
|
161435
|
-
const denoPath =
|
|
161835
|
+
const denoPath = join18(process.env.HOME || "", ".deno", "bin");
|
|
161436
161836
|
process.env.PATH = `${denoPath}:${process.env.PATH}`;
|
|
161437
161837
|
}
|
|
161438
161838
|
log.info(`\xBB installed ${name}`);
|
|
@@ -161441,7 +161841,7 @@ async function installFallback(name, installSpec) {
|
|
|
161441
161841
|
var installNodeDependencies = {
|
|
161442
161842
|
name: "installNodeDependencies",
|
|
161443
161843
|
shouldRun: () => {
|
|
161444
|
-
const packageJsonPath =
|
|
161844
|
+
const packageJsonPath = join18(process.cwd(), "package.json");
|
|
161445
161845
|
return existsSync6(packageJsonPath);
|
|
161446
161846
|
},
|
|
161447
161847
|
run: async (options) => {
|
|
@@ -161540,12 +161940,12 @@ ${errorMessage}`]
|
|
|
161540
161940
|
|
|
161541
161941
|
// prep/installPythonDependencies.ts
|
|
161542
161942
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
161543
|
-
import { join as
|
|
161943
|
+
import { join as join19 } from "node:path";
|
|
161544
161944
|
function declaresBuildSystem(path4) {
|
|
161545
161945
|
return /^\s*\[\s*build-system\s*\]/m.test(readFileSync5(path4, "utf8"));
|
|
161546
161946
|
}
|
|
161547
161947
|
function configApplies(config3, cwd) {
|
|
161548
|
-
const path4 =
|
|
161948
|
+
const path4 = join19(cwd, config3.file);
|
|
161549
161949
|
return existsSync7(path4) && (config3.applies?.(path4) ?? true);
|
|
161550
161950
|
}
|
|
161551
161951
|
var PYTHON_CONFIGS = [
|
|
@@ -162681,8 +163081,8 @@ function PullRequestInfoTool(ctx) {
|
|
|
162681
163081
|
}
|
|
162682
163082
|
|
|
162683
163083
|
// mcp/reviewComments.ts
|
|
162684
|
-
import { writeFileSync as
|
|
162685
|
-
import { join as
|
|
163084
|
+
import { writeFileSync as writeFileSync12 } from "node:fs";
|
|
163085
|
+
import { join as join20 } from "node:path";
|
|
162686
163086
|
var REVIEW_THREADS_QUERY = `
|
|
162687
163087
|
query ($owner: String!, $name: String!, $prNumber: Int!) {
|
|
162688
163088
|
repository(owner: $owner, name: $name) {
|
|
@@ -163098,8 +163498,8 @@ function GetReviewCommentsTool(ctx) {
|
|
|
163098
163498
|
throw new Error("PULLFROG_TEMP_DIR not set");
|
|
163099
163499
|
}
|
|
163100
163500
|
const filename = `review-${params.review_id}-threads.md`;
|
|
163101
|
-
const commentsPath =
|
|
163102
|
-
|
|
163501
|
+
const commentsPath = join20(tempDir, filename);
|
|
163502
|
+
writeFileSync12(commentsPath, formatted.content);
|
|
163103
163503
|
log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
|
|
163104
163504
|
return {
|
|
163105
163505
|
review_id: params.review_id,
|
|
@@ -163334,11 +163734,11 @@ ${summaryAddendum}`,
|
|
|
163334
163734
|
}
|
|
163335
163735
|
|
|
163336
163736
|
// mcp/shell.ts
|
|
163337
|
-
import { spawn as
|
|
163737
|
+
import { spawn as spawn5, spawnSync as spawnSync5 } from "node:child_process";
|
|
163338
163738
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
163339
|
-
import { closeSync, openSync, writeFileSync as
|
|
163739
|
+
import { closeSync, openSync, writeFileSync as writeFileSync13 } from "node:fs";
|
|
163340
163740
|
import { userInfo as userInfo2 } from "node:os";
|
|
163341
|
-
import { join as
|
|
163741
|
+
import { join as join21 } from "node:path";
|
|
163342
163742
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
163343
163743
|
var ShellParams = type({
|
|
163344
163744
|
command: "string",
|
|
@@ -163436,7 +163836,7 @@ function spawnShell(params) {
|
|
|
163436
163836
|
const repoRoot = resolveRepoRoot();
|
|
163437
163837
|
const fsMounts = buildFsMounts(repoRoot);
|
|
163438
163838
|
if (sandboxMethod === "unshare") {
|
|
163439
|
-
return
|
|
163839
|
+
return spawn5(
|
|
163440
163840
|
"unshare",
|
|
163441
163841
|
[
|
|
163442
163842
|
"--pid",
|
|
@@ -163460,7 +163860,7 @@ function spawnShell(params) {
|
|
|
163460
163860
|
const pathRestore = 'export PATH="${SANDBOX_PATH:-$PATH}"; ';
|
|
163461
163861
|
const escaped = (pathRestore + params.command).replace(/'/g, "'\\''");
|
|
163462
163862
|
envArgs.push(`SANDBOX_PATH=${params.env.PATH ?? ""}`);
|
|
163463
|
-
return
|
|
163863
|
+
return spawn5(
|
|
163464
163864
|
"sudo",
|
|
163465
163865
|
[
|
|
163466
163866
|
"env",
|
|
@@ -163476,7 +163876,7 @@ function spawnShell(params) {
|
|
|
163476
163876
|
{ ...spawnOpts, env: {} }
|
|
163477
163877
|
);
|
|
163478
163878
|
}
|
|
163479
|
-
return
|
|
163879
|
+
return spawn5("bash", ["-c", params.command], spawnOpts);
|
|
163480
163880
|
}
|
|
163481
163881
|
async function killProcessGroup(proc) {
|
|
163482
163882
|
if (!proc.pid) return;
|
|
@@ -163501,8 +163901,8 @@ function getTempDir() {
|
|
|
163501
163901
|
var MAX_OUTPUT_CHARS = 5e3;
|
|
163502
163902
|
function capOutput(output) {
|
|
163503
163903
|
if (output.length <= MAX_OUTPUT_CHARS) return output;
|
|
163504
|
-
const fullPath =
|
|
163505
|
-
|
|
163904
|
+
const fullPath = join21(getTempDir(), `shell-${randomUUID4().slice(0, 8)}.log`);
|
|
163905
|
+
writeFileSync13(fullPath, output);
|
|
163506
163906
|
const elided = output.length - MAX_OUTPUT_CHARS;
|
|
163507
163907
|
return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
|
|
163508
163908
|
${output.slice(-MAX_OUTPUT_CHARS)}`;
|
|
@@ -163556,8 +163956,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
163556
163956
|
if (params.background) {
|
|
163557
163957
|
const tempDir = getTempDir();
|
|
163558
163958
|
const handle = `bg-${randomUUID4().slice(0, 8)}`;
|
|
163559
|
-
const outputPath =
|
|
163560
|
-
const pidPath =
|
|
163959
|
+
const outputPath = join21(tempDir, `${handle}.log`);
|
|
163960
|
+
const pidPath = join21(tempDir, `${handle}.pid`);
|
|
163561
163961
|
const logFd = openSync(outputPath, "a");
|
|
163562
163962
|
let proc2;
|
|
163563
163963
|
try {
|
|
@@ -163574,7 +163974,7 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
|
|
|
163574
163974
|
throw new Error("failed to start background process");
|
|
163575
163975
|
}
|
|
163576
163976
|
proc2.unref();
|
|
163577
|
-
|
|
163977
|
+
writeFileSync13(pidPath, `${proc2.pid}
|
|
163578
163978
|
`);
|
|
163579
163979
|
ctx.toolState.backgroundProcesses.set(handle, { pid: proc2.pid, outputPath, pidPath });
|
|
163580
163980
|
return {
|
|
@@ -163724,7 +164124,7 @@ function UploadFileTool(ctx) {
|
|
|
163724
164124
|
|
|
163725
164125
|
// mcp/xrepo.ts
|
|
163726
164126
|
import { mkdirSync as mkdirSync8, rmSync as rmSync3 } from "node:fs";
|
|
163727
|
-
import { join as
|
|
164127
|
+
import { join as join22 } from "node:path";
|
|
163728
164128
|
function assertValidRepoName(name) {
|
|
163729
164129
|
if (!/^[A-Za-z0-9._-]+$/.test(name) || name.startsWith("-") || name.includes("..")) {
|
|
163730
164130
|
throw new Error(
|
|
@@ -163742,6 +164142,7 @@ var ListRepos = type({});
|
|
|
163742
164142
|
function ListReposTool(ctx) {
|
|
163743
164143
|
return tool({
|
|
163744
164144
|
name: "list_repos",
|
|
164145
|
+
annotations: { readOnlyHint: true },
|
|
163745
164146
|
description: "List the repositories available for cross-repo (`--xrepo`) work in this run, with each repo's access tier (primary, write, or read) and whether it's already checked out. Use this before `checkout_repo` to discover what you can reference or edit. Returns an empty set on single-repo runs.",
|
|
163746
164147
|
parameters: ListRepos,
|
|
163747
164148
|
execute: execute(async () => {
|
|
@@ -163803,7 +164204,7 @@ function CheckoutRepoTool(ctx) {
|
|
|
163803
164204
|
return { path: existing.dir, access: existing.access, note: "already checked out." };
|
|
163804
164205
|
}
|
|
163805
164206
|
const access = accessFor(ctx, repo);
|
|
163806
|
-
const dir =
|
|
164207
|
+
const dir = join22(ctx.tmpdir, "xrepo", repo);
|
|
163807
164208
|
mkdirSync8(dir, { recursive: true });
|
|
163808
164209
|
const state = ensureRepoState(ctx.toolState, { owner, name: repo, dir, access });
|
|
163809
164210
|
const rc = resolveRepoCtx(ctx, repo);
|
|
@@ -164253,9 +164654,9 @@ function formatApiKeyErrorSummary(params) {
|
|
|
164253
164654
|
|
|
164254
164655
|
// utils/gitAuthServer.ts
|
|
164255
164656
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
164256
|
-
import { writeFileSync as
|
|
164657
|
+
import { writeFileSync as writeFileSync14 } from "node:fs";
|
|
164257
164658
|
import { createServer as createServer3 } from "node:http";
|
|
164258
|
-
import { join as
|
|
164659
|
+
import { join as join23 } from "node:path";
|
|
164259
164660
|
var REVOKED_TRAP_MS = 6e4;
|
|
164260
164661
|
function revokeGitHubToken(token) {
|
|
164261
164662
|
fetch("https://api.github.com/installation/token", {
|
|
@@ -164324,7 +164725,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
164324
164725
|
function writeAskpassScript(code) {
|
|
164325
164726
|
const scriptId = randomUUID5();
|
|
164326
164727
|
const scriptName = `askpass-${scriptId}.js`;
|
|
164327
|
-
const scriptPath =
|
|
164728
|
+
const scriptPath = join23(tmpdir4, scriptName);
|
|
164328
164729
|
const content = [
|
|
164329
164730
|
`#!/usr/bin/env node`,
|
|
164330
164731
|
`var a=process.argv[2]||"";`,
|
|
@@ -164337,7 +164738,7 @@ async function startGitAuthServer(tmpdir4) {
|
|
|
164337
164738
|
`r.on("end",function(){process.stdout.write(d+"\\n")})`,
|
|
164338
164739
|
`}).on("error",function(){process.exit(1)})}`
|
|
164339
164740
|
].join("\n");
|
|
164340
|
-
|
|
164741
|
+
writeFileSync14(scriptPath, content, { mode: 448 });
|
|
164341
164742
|
return scriptPath;
|
|
164342
164743
|
}
|
|
164343
164744
|
async function close() {
|
|
@@ -164658,9 +165059,9 @@ When embedding images (e.g. uploaded screenshots) in comments or PR bodies, alwa
|
|
|
164658
165059
|
|
|
164659
165060
|
**Your raw assistant messages are never delivered** \u2014 they exist only in the run logs. Anything the user is meant to see (an answer to a question, a mention reply, a result) MUST go through \`report_progress\` (or another ${pullfrogMcpName} write tool). Do not rely on returning the answer as plain text \u2014 the harness makes a best-effort attempt to recover it into the progress comment, but that is a safety net, not a substitute for calling \`report_progress\`.
|
|
164660
165061
|
|
|
164661
|
-
**\`report_progress\`**: call this exactly once at the end of every run with a brief final summary (1-3 sentences) unless the mode guidance instructs otherwise. Never call it for intermediate status updates (e.g., "Checking for changes...", "Starting review...") \u2014 the task list handles live progress automatically. Calling \`report_progress\` replaces the task list with your summary and preserves the current task list in a collapsible section. Keep the summary concise \u2014 do not repeat what the task list already shows. Focus on the outcome (what was accomplished, links to artifacts) rather than listing individual steps. If something failed, include the tool's error text even when that makes the summary longer.
|
|
165062
|
+
**\`report_progress\`**: call this exactly once at the end of every run with a brief final summary (1-3 sentences) unless the mode guidance instructs otherwise or a standalone comment on the current target is the task's sole requested deliverable. Never call it for intermediate status updates (e.g., "Checking for changes...", "Starting review...") \u2014 the task list handles live progress automatically. Calling \`report_progress\` replaces the task list with your summary and preserves the current task list in a collapsible section. Keep the summary concise \u2014 do not repeat what the task list already shows. Focus on the outcome (what was accomplished, links to artifacts) rather than listing individual steps. If something failed, include the tool's error text even when that makes the summary longer.
|
|
164662
165063
|
|
|
164663
|
-
Never use \`create_issue_comment\` for task progress
|
|
165064
|
+
Never use \`create_issue_comment\` for task progress or for the answer that \`report_progress\` should deliver. Use it only when the task explicitly requests a separate standalone comment or a comment on a different target. Skip \`report_progress\` only when a standalone comment on the current target is the task's sole requested deliverable; successful-run cleanup then removes the progress chrome. Plan output (initial post AND revisions) goes through \`report_progress\` \u2014 see the Plan mode guidance for details.
|
|
164664
165065
|
|
|
164665
165066
|
### If you get stuck
|
|
164666
165067
|
|
|
@@ -164794,7 +165195,7 @@ function resolveInstructions(ctx) {
|
|
|
164794
165195
|
|
|
164795
165196
|
// utils/learnings.ts
|
|
164796
165197
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
|
|
164797
|
-
import { dirname as dirname4, join as
|
|
165198
|
+
import { dirname as dirname4, join as join24 } from "node:path";
|
|
164798
165199
|
|
|
164799
165200
|
// utils/learningsTruncate.ts
|
|
164800
165201
|
var MAX_LEARNINGS_LENGTH = 1e5;
|
|
@@ -164812,10 +165213,10 @@ function truncateAtLineBoundary(body, cap) {
|
|
|
164812
165213
|
var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
|
|
164813
165214
|
var XREPO_LEARNINGS_FILE_NAME = "pullfrog-xrepo-learnings.md";
|
|
164814
165215
|
function learningsFilePath(tmpdir4) {
|
|
164815
|
-
return
|
|
165216
|
+
return join24(tmpdir4, LEARNINGS_FILE_NAME);
|
|
164816
165217
|
}
|
|
164817
165218
|
function xrepoLearningsFilePath(tmpdir4) {
|
|
164818
|
-
return
|
|
165219
|
+
return join24(tmpdir4, XREPO_LEARNINGS_FILE_NAME);
|
|
164819
165220
|
}
|
|
164820
165221
|
async function seedLearningsFile(params) {
|
|
164821
165222
|
const path4 = learningsFilePath(params.tmpdir);
|
|
@@ -165555,7 +165956,7 @@ async function runProxyResolution(ctx) {
|
|
|
165555
165956
|
|
|
165556
165957
|
// utils/prSummary.ts
|
|
165557
165958
|
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
165558
|
-
import { dirname as dirname5, join as
|
|
165959
|
+
import { dirname as dirname5, join as join25 } from "node:path";
|
|
165559
165960
|
var SUMMARY_FILE_NAME = "pullfrog-summary.md";
|
|
165560
165961
|
var SUMMARY_SCAFFOLD = `# PR summary
|
|
165561
165962
|
|
|
@@ -165565,7 +165966,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
|
|
|
165565
165966
|
var MIN_SNAPSHOT_LENGTH = 60;
|
|
165566
165967
|
var MAX_SNAPSHOT_LENGTH = 32768;
|
|
165567
165968
|
function summaryFilePath(tmpdir4) {
|
|
165568
|
-
return
|
|
165969
|
+
return join25(tmpdir4, SUMMARY_FILE_NAME);
|
|
165569
165970
|
}
|
|
165570
165971
|
async function seedSummaryFile(params) {
|
|
165571
165972
|
const path4 = summaryFilePath(params.tmpdir);
|
|
@@ -166282,7 +166683,7 @@ async function finalizeSuccessRun(input) {
|
|
|
166282
166683
|
log.debug(`failure error report failed: ${error49}`);
|
|
166283
166684
|
});
|
|
166284
166685
|
}
|
|
166285
|
-
if (input.result.success && input.toolState.progressComment && input.toolState.wasUpdated &&
|
|
166686
|
+
if (input.result.success && input.toolState.progressComment && input.toolState.wasUpdated && !input.toolState.finalSummaryWritten) {
|
|
166286
166687
|
await deleteProgressComment(input.toolContext).catch((error49) => {
|
|
166287
166688
|
log.debug(`stranded progress comment cleanup failed: ${error49}`);
|
|
166288
166689
|
});
|
|
@@ -166848,7 +167249,7 @@ ${instructions.user}` : null,
|
|
|
166848
167249
|
log.info(instructions.full);
|
|
166849
167250
|
});
|
|
166850
167251
|
if (agentId === "opencode") {
|
|
166851
|
-
const pluginDir =
|
|
167252
|
+
const pluginDir = join26(process.cwd(), ".opencode", "plugin");
|
|
166852
167253
|
const hasPlugins = existsSync8(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
|
|
166853
167254
|
if (hasPlugins && toolState.dependencyInstallation?.promise) {
|
|
166854
167255
|
log.info(
|
|
@@ -168022,7 +168423,7 @@ async function runCli4(input) {
|
|
|
168022
168423
|
}
|
|
168023
168424
|
|
|
168024
168425
|
// cli.ts
|
|
168025
|
-
var VERSION10 = "0.1.
|
|
168426
|
+
var VERSION10 = "0.1.36";
|
|
168026
168427
|
var bin = basename2(process.argv[1] || "");
|
|
168027
168428
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
168028
168429
|
var rawArgs = process.argv.slice(2);
|