pullfrog 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -100664,7 +100664,7 @@ var import_arg2 = __toESM(require_arg(), 1);
100664
100664
  import { dirname as dirname6 } from "node:path";
100665
100665
 
100666
100666
  // main.ts
100667
- import { existsSync as existsSync7, readdirSync } from "node:fs";
100667
+ import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
100668
100668
  import { readFile as readFile4 } from "node:fs/promises";
100669
100669
  import { join as join20 } from "node:path";
100670
100670
 
@@ -109727,6 +109727,11 @@ var providers = {
109727
109727
  resolve: "opencode/kimi-k2.6",
109728
109728
  openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
109729
109729
  },
109730
+ "minimax-m2.5": {
109731
+ displayName: "MiniMax M2.5",
109732
+ resolve: "opencode/minimax-m2.5",
109733
+ openRouterResolve: "openrouter/minimax/minimax-m2.5"
109734
+ },
109730
109735
  "gpt-5-nano": {
109731
109736
  displayName: "GPT Nano",
109732
109737
  resolve: "opencode/gpt-5-nano",
@@ -109743,7 +109748,9 @@ var providers = {
109743
109748
  displayName: "MiniMax M2.5",
109744
109749
  resolve: "opencode/minimax-m2.5-free",
109745
109750
  envVars: [],
109746
- isFree: true
109751
+ isFree: true,
109752
+ fallback: "opencode/big-pickle",
109753
+ hidden: true
109747
109754
  }
109748
109755
  }
109749
109756
  }),
@@ -109881,6 +109888,11 @@ var providers = {
109881
109888
  displayName: "Kimi K2",
109882
109889
  resolve: "openrouter/moonshotai/kimi-k2.6",
109883
109890
  openRouterResolve: "openrouter/moonshotai/kimi-k2.6"
109891
+ },
109892
+ "minimax-m2.5": {
109893
+ displayName: "MiniMax M2.5",
109894
+ resolve: "openrouter/minimax/minimax-m2.5",
109895
+ openRouterResolve: "openrouter/minimax/minimax-m2.5"
109884
109896
  }
109885
109897
  }
109886
109898
  })
@@ -109925,6 +109937,11 @@ var modelAliases = Object.entries(providers).flatMap(
109925
109937
  hidden: def.hidden ?? false
109926
109938
  }))
109927
109939
  );
109940
+ var defaultProxyAlias = modelAliases.find((a) => a.slug === "moonshotai/kimi-k2");
109941
+ if (!defaultProxyAlias?.openRouterResolve) {
109942
+ throw new Error("DEFAULT_PROXY_MODEL: moonshotai/kimi-k2 missing openRouterResolve");
109943
+ }
109944
+ var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
109928
109945
  var MAX_FALLBACK_DEPTH = 10;
109929
109946
  function resolveDisplayAlias(slug2) {
109930
109947
  let current = slug2;
@@ -144232,7 +144249,7 @@ var import_semver = __toESM(require_semver2(), 1);
144232
144249
  // package.json
144233
144250
  var package_default = {
144234
144251
  name: "pullfrog",
144235
- version: "0.1.11",
144252
+ version: "0.1.12",
144236
144253
  type: "module",
144237
144254
  bin: {
144238
144255
  pullfrog: "dist/cli.mjs",
@@ -144430,8 +144447,8 @@ function closeBrowserDaemon(toolState) {
144430
144447
 
144431
144448
  // mcp/checkout.ts
144432
144449
  import { createHash as createHash2 } from "node:crypto";
144433
- import { statSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
144434
- import { join as join4 } from "node:path";
144450
+ import { statSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
144451
+ import { join as join5 } from "node:path";
144435
144452
 
144436
144453
  // utils/diffCoverage.ts
144437
144454
  import { isAbsolute, normalize as normalize2, resolve } from "node:path";
@@ -145943,6 +145960,183 @@ async function reportReviewNodeId(ctx, params) {
145943
145960
  await patchWorkflowRunFields(ctx, { reviewNodeId: params.nodeId });
145944
145961
  }
145945
145962
 
145963
+ // utils/setup.ts
145964
+ import { execFileSync as execFileSync4, execSync as execSync2 } from "node:child_process";
145965
+ import { mkdtempSync as mkdtempSync2, readdirSync, realpathSync as realpathSync2, unlinkSync as unlinkSync2 } from "node:fs";
145966
+ import { tmpdir as tmpdir2 } from "node:os";
145967
+ import { join as join4 } from "node:path";
145968
+ function createTempDirectory() {
145969
+ const sharedTempDir = mkdtempSync2(join4(tmpdir2(), "pullfrog-"));
145970
+ process.env.PULLFROG_TEMP_DIR = sharedTempDir;
145971
+ log.info(`\xBB created temp dir at ${sharedTempDir}`);
145972
+ return sharedTempDir;
145973
+ }
145974
+ function wipeRunnerLeakSurface() {
145975
+ const runnerTemp = process.env.RUNNER_TEMP;
145976
+ if (!runnerTemp) return;
145977
+ const preserve = /* @__PURE__ */ new Set();
145978
+ for (const envVar of [
145979
+ "GITHUB_OUTPUT",
145980
+ "GITHUB_ENV",
145981
+ "GITHUB_PATH",
145982
+ "GITHUB_STATE",
145983
+ "GITHUB_STEP_SUMMARY"
145984
+ ]) {
145985
+ const path3 = process.env[envVar];
145986
+ if (!path3) continue;
145987
+ try {
145988
+ preserve.add(realpathSync2(path3));
145989
+ } catch {
145990
+ preserve.add(path3);
145991
+ }
145992
+ }
145993
+ const wiped = [];
145994
+ const tryUnlink = (path3) => {
145995
+ let resolved = path3;
145996
+ try {
145997
+ resolved = realpathSync2(path3);
145998
+ } catch {
145999
+ }
146000
+ if (preserve.has(resolved) || preserve.has(path3)) return;
146001
+ try {
146002
+ unlinkSync2(path3);
146003
+ wiped.push(path3);
146004
+ } catch {
146005
+ }
146006
+ };
146007
+ const listDir = (dir) => {
146008
+ try {
146009
+ return readdirSync(dir);
146010
+ } catch {
146011
+ return [];
146012
+ }
146013
+ };
146014
+ const fileCommandsDir = join4(runnerTemp, "_runner_file_commands");
146015
+ for (const entry of listDir(fileCommandsDir)) {
146016
+ tryUnlink(join4(fileCommandsDir, entry));
146017
+ }
146018
+ for (const entry of listDir(runnerTemp)) {
146019
+ if (entry.endsWith(".sh") || /^git-credentials-.*\.config$/.test(entry)) {
146020
+ tryUnlink(join4(runnerTemp, entry));
146021
+ }
146022
+ }
146023
+ if (wiped.length > 0) {
146024
+ log.info(`\xBB wiped ${wiped.length} leak-surface file(s) from $RUNNER_TEMP`);
146025
+ log.debug(`\xBB wiped paths: ${wiped.join(", ")}`);
146026
+ }
146027
+ }
146028
+ function envScopedToRepo() {
146029
+ const scoped = { ...process.env };
146030
+ for (const key of Object.keys(scoped)) {
146031
+ if (key.startsWith("GIT_")) delete scoped[key];
146032
+ }
146033
+ return scoped;
146034
+ }
146035
+ function removeIncludeIfEntries(repoDir) {
146036
+ const env2 = envScopedToRepo();
146037
+ let configOutput;
146038
+ try {
146039
+ configOutput = execSync2("git config --local --get-regexp -z ^includeif\\.", {
146040
+ cwd: repoDir,
146041
+ encoding: "utf-8",
146042
+ stdio: "pipe",
146043
+ env: env2
146044
+ });
146045
+ } catch {
146046
+ log.debug("\xBB no includeIf credential entries to remove");
146047
+ return;
146048
+ }
146049
+ const seen = /* @__PURE__ */ new Set();
146050
+ for (const entry of configOutput.split("\0")) {
146051
+ if (!entry) continue;
146052
+ const nl = entry.indexOf("\n");
146053
+ const key = nl === -1 ? entry : entry.slice(0, nl);
146054
+ if (!key || seen.has(key)) continue;
146055
+ seen.add(key);
146056
+ try {
146057
+ execFileSync4("git", ["config", "--local", "--unset-all", key], {
146058
+ cwd: repoDir,
146059
+ stdio: "pipe",
146060
+ env: env2
146061
+ });
146062
+ } catch (error49) {
146063
+ log.debug(
146064
+ `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
146065
+ );
146066
+ }
146067
+ }
146068
+ if (seen.size > 0)
146069
+ log.info(
146070
+ `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
146071
+ );
146072
+ }
146073
+ async function setupGit(params) {
146074
+ const repoDir = process.cwd();
146075
+ log.info("\xBB setting up git configuration...");
146076
+ try {
146077
+ let currentEmail = "";
146078
+ try {
146079
+ currentEmail = execSync2("git config user.email", {
146080
+ cwd: repoDir,
146081
+ stdio: "pipe",
146082
+ encoding: "utf-8"
146083
+ }).trim();
146084
+ } catch {
146085
+ }
146086
+ const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
146087
+ if (shouldSetDefaults) {
146088
+ execSync2('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
146089
+ cwd: repoDir,
146090
+ stdio: "pipe"
146091
+ });
146092
+ execSync2('git config --local user.name "pullfrog[bot]"', {
146093
+ cwd: repoDir,
146094
+ stdio: "pipe"
146095
+ });
146096
+ log.debug("\xBB git user configured (using defaults)");
146097
+ } else {
146098
+ log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
146099
+ }
146100
+ if (params.shell === "disabled") {
146101
+ execSync2("git config --local core.hooksPath /dev/null", {
146102
+ cwd: repoDir,
146103
+ stdio: "pipe"
146104
+ });
146105
+ log.debug("\xBB git hooks disabled (shell=disabled)");
146106
+ }
146107
+ } catch (error49) {
146108
+ log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
146109
+ }
146110
+ try {
146111
+ execSync2("git config --local --unset-all http.https://github.com/.extraheader", {
146112
+ cwd: repoDir,
146113
+ stdio: "pipe"
146114
+ });
146115
+ log.info("\xBB removed existing authentication headers");
146116
+ } catch {
146117
+ log.debug("\xBB no existing authentication headers to remove");
146118
+ }
146119
+ removeIncludeIfEntries(repoDir);
146120
+ const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
146121
+ $2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
146122
+ params.toolState.pushUrl = originUrl;
146123
+ $2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
146124
+ params.toolState.initialHead = captureInitialHead(repoDir);
146125
+ log.info("\xBB git authentication configured");
146126
+ }
146127
+ function captureInitialHead(repoDir) {
146128
+ try {
146129
+ const name = $2("git", ["symbolic-ref", "--short", "HEAD"], {
146130
+ cwd: repoDir,
146131
+ log: false
146132
+ }).trim();
146133
+ if (name) return { kind: "branch", name };
146134
+ } catch {
146135
+ }
146136
+ const sha = $2("git", ["rev-parse", "HEAD"], { cwd: repoDir, log: false }).trim();
146137
+ return { kind: "detached", sha };
146138
+ }
146139
+
145946
146140
  // mcp/checkout.ts
145947
146141
  function formatFilesWithLineNumbers(files) {
145948
146142
  const output = [];
@@ -146117,7 +146311,7 @@ function cleanupStaleGitLocks() {
146117
146311
  }
146118
146312
  if (now - mtimeMs < STALE_LOCK_AGE_MS) continue;
146119
146313
  try {
146120
- unlinkSync2(relPath);
146314
+ unlinkSync3(relPath);
146121
146315
  log.warning(`\xBB removed stale ${relPath} from prior run`);
146122
146316
  } catch (e) {
146123
146317
  log.debug(
@@ -146276,6 +146470,15 @@ async function checkoutPrBranch(pr, params) {
146276
146470
  return { hookWarning: postCheckoutHook.warning };
146277
146471
  }
146278
146472
  var inFlightCheckouts = /* @__PURE__ */ new Map();
146473
+ function headsEqual(a, b) {
146474
+ if (a.kind === "branch" && b.kind === "branch") return a.name === b.name;
146475
+ if (a.kind === "detached" && b.kind === "detached") return a.sha === b.sha;
146476
+ return false;
146477
+ }
146478
+ function describeHead(h) {
146479
+ if (h.kind === "branch") return `branch \`${h.name}\``;
146480
+ return `detached HEAD \`${h.sha}\``;
146481
+ }
146279
146482
  function CheckoutPrTool(ctx) {
146280
146483
  const runCheckout = async (pull_number) => {
146281
146484
  const prResponse = await ctx.octokit.rest.pulls.get({
@@ -146322,7 +146525,7 @@ function CheckoutPrTool(ctx) {
146322
146525
  headSha: ctx.toolState.checkoutSha
146323
146526
  });
146324
146527
  if (incremental) {
146325
- incrementalDiffPath = join4(
146528
+ incrementalDiffPath = join5(
146326
146529
  tempDir,
146327
146530
  `pr-${pull_number}-${beforeShort}-${headShort}-incremental.diff`
146328
146531
  );
@@ -146336,7 +146539,7 @@ function CheckoutPrTool(ctx) {
146336
146539
  const diffPreview = formatResult.content.split("\n").slice(0, 100).join("\n");
146337
146540
  log.debug(`formatted diff preview (first 100 lines):
146338
146541
  ${diffPreview}`);
146339
- const diffPath = join4(tempDir, `pr-${pull_number}-${headShort}.diff`);
146542
+ const diffPath = join5(tempDir, `pr-${pull_number}-${headShort}.diff`);
146340
146543
  writeFileSync2(diffPath, formatResult.content);
146341
146544
  log.debug(`wrote diff to ${diffPath} (${formatResult.content.length} bytes)`);
146342
146545
  ctx.toolState.diffCoverage = createDiffCoverageState({
@@ -146403,7 +146606,8 @@ ${diffPreview}`);
146403
146606
  };
146404
146607
  return tool({
146405
146608
  name: "checkout_pr",
146406
- description: "Checkout a pull request branch locally. This fetches the PR branch and sets up push configuration for fork PRs. Returns diffPath pointing to the formatted diff file. Example: `checkout_pr({ pull_number: 1234 })`. Transient fetch timeouts are common \u2014 retry the same call up to a few times before treating the failure as terminal. If the error mentions `.git/shallow.lock: File exists` or `.git/index.lock: File exists`, that's a stale lock from a prior timed-out fetch \u2014 remove it via the shell tool (`rm -f .git/shallow.lock .git/index.lock`) and retry.",
146609
+ timeoutMs: 6e5,
146610
+ description: "Checkout a pull request branch locally. This fetches the PR branch and sets up push configuration for fork PRs. Returns diffPath pointing to the formatted diff file. Example: `checkout_pr({ pull_number: 1234 })`. Large repos can take several minutes \u2014 wait for the call to finish; do not treat a slow response as failure. If you see `MCP error -32001: Request timed out`, retry the same call without touching git lock files first \u2014 that error is a client-side abort. If the retry then reports `.git/shallow.lock: File exists` or `.git/index.lock: File exists`, remove those lock files via the shell tool and retry again.",
146407
146611
  parameters: CheckoutPr,
146408
146612
  execute: execute(async ({ pull_number }) => {
146409
146613
  const inFlight = inFlightCheckouts.get(pull_number);
@@ -146411,13 +146615,23 @@ ${diffPreview}`);
146411
146615
  log.info(`\xBB checkout_pr({pull_number:${pull_number}}) already in flight \u2014 sharing result`);
146412
146616
  return inFlight;
146413
146617
  }
146414
- const currentBranch = $2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false }).trim();
146415
- if (currentBranch !== `pr-${pull_number}`) {
146416
- const dirty = $2("git", ["status", "--porcelain"], { log: false }).trim();
146417
- if (dirty) {
146418
- throw new Error(
146419
- `cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit, push, or discard them before switching. dirty paths:
146618
+ const dirty = $2("git", ["status", "--porcelain"], { log: false }).trim();
146619
+ if (dirty) {
146620
+ throw new Error(
146621
+ `cannot checkout PR #${pull_number} while the working tree has uncommitted changes. commit (then push if needed), or discard with \`git restore --staged --worktree .\` / \`git clean -fd\` before retrying. this refusal is unconditional \u2014 even re-checking-out the PR you're already on is refused, because shared-working-tree subagents make carry-forward edits unsafe. dirty paths:
146420
146622
  ${dirty}`
146623
+ );
146624
+ }
146625
+ const initialHead = ctx.toolState.initialHead;
146626
+ if (initialHead) {
146627
+ const currentHead = captureInitialHead(process.cwd());
146628
+ const targetBranch = `pr-${pull_number}`;
146629
+ const onTarget = currentHead.kind === "branch" && currentHead.name === targetBranch;
146630
+ const onInitial = headsEqual(currentHead, initialHead);
146631
+ if (!onTarget && !onInitial) {
146632
+ const recoverCmd = initialHead.kind === "branch" ? `git checkout ${initialHead.name}` : `git checkout ${initialHead.sha}`;
146633
+ throw new Error(
146634
+ `cannot checkout PR #${pull_number} from ${describeHead(currentHead)}. the only sanctioned HEAD positions for checkout_pr are the run-entry HEAD (${describeHead(initialHead)}) or the target PR's branch (\`${targetBranch}\`, idempotent re-checkout). recover with \`${recoverCmd}\` first \u2014 if that would carry uncommitted work along, commit or discard it (\`git restore --staged --worktree .\` / \`git clean -fd\`) before switching. routing around this via the \`git\` tool's \`checkout\`/\`switch\` subcommands is not sanctioned: this guard exists to prevent the shared-working-tree cross-PR clobber pattern from the zed-industries/cloud (2026-05-18) incident.`
146421
146635
  );
146422
146636
  }
146423
146637
  }
@@ -146434,7 +146648,7 @@ ${dirty}`
146434
146648
 
146435
146649
  // mcp/checkSuite.ts
146436
146650
  import { mkdirSync, writeFileSync as writeFileSync3 } from "node:fs";
146437
- import { join as join5 } from "node:path";
146651
+ import { join as join6 } from "node:path";
146438
146652
  var GetCheckSuiteLogs = type({
146439
146653
  check_suite_id: type.number.describe("the id from check_suite.id")
146440
146654
  });
@@ -146530,7 +146744,7 @@ function GetCheckSuiteLogsTool(ctx) {
146530
146744
  if (!tempDir) {
146531
146745
  throw new Error("PULLFROG_TEMP_DIR not set");
146532
146746
  }
146533
- const logsDir = join5(tempDir, "ci-logs");
146747
+ const logsDir = join6(tempDir, "ci-logs");
146534
146748
  mkdirSync(logsDir, { recursive: true });
146535
146749
  const jobResults = [];
146536
146750
  for (const run4 of failedRuns) {
@@ -146557,7 +146771,7 @@ function GetCheckSuiteLogsTool(ctx) {
146557
146771
  );
146558
146772
  }
146559
146773
  const logsText = await logsResult.text();
146560
- const logPath = join5(logsDir, `job-${job.id}.log`);
146774
+ const logPath = join6(logsDir, `job-${job.id}.log`);
146561
146775
  writeFileSync3(logPath, logsText);
146562
146776
  const analysis = analyzeLog(logsText, 80);
146563
146777
  const failedSteps = job.steps?.filter((s) => s.conclusion === "failure").map((s) => `Step ${s.number}: ${s.name}`) ?? [];
@@ -146607,7 +146821,7 @@ function GetCheckSuiteLogsTool(ctx) {
146607
146821
 
146608
146822
  // mcp/commitInfo.ts
146609
146823
  import { writeFileSync as writeFileSync4 } from "node:fs";
146610
- import { join as join6 } from "node:path";
146824
+ import { join as join7 } from "node:path";
146611
146825
  var CommitInfo = type({
146612
146826
  sha: type.string.describe("the commit SHA (full or abbreviated) to fetch")
146613
146827
  });
@@ -146631,7 +146845,7 @@ function CommitInfoTool(ctx) {
146631
146845
  "PULLFROG_TEMP_DIR not set - get_commit_info must run in pullfrog action context"
146632
146846
  );
146633
146847
  }
146634
- const diffFile = join6(tempDir, `commit-${sha.slice(0, 7)}.diff`);
146848
+ const diffFile = join7(tempDir, `commit-${sha.slice(0, 7)}.diff`);
146635
146849
  writeFileSync4(diffFile, formatResult.content);
146636
146850
  log.debug(`wrote commit diff to ${diffFile} (${formatResult.content.length} bytes)`);
146637
146851
  return {
@@ -147089,7 +147303,7 @@ function PullRequestInfoTool(ctx) {
147089
147303
 
147090
147304
  // mcp/reviewComments.ts
147091
147305
  import { writeFileSync as writeFileSync5 } from "node:fs";
147092
- import { join as join7 } from "node:path";
147306
+ import { join as join8 } from "node:path";
147093
147307
  var REVIEW_THREADS_QUERY = `
147094
147308
  query ($owner: String!, $name: String!, $prNumber: Int!) {
147095
147309
  repository(owner: $owner, name: $name) {
@@ -147481,7 +147695,7 @@ function GetReviewCommentsTool(ctx) {
147481
147695
  throw new Error("PULLFROG_TEMP_DIR not set");
147482
147696
  }
147483
147697
  const filename = `review-${params.review_id}-threads.md`;
147484
- const commentsPath = join7(tempDir, filename);
147698
+ const commentsPath = join8(tempDir, filename);
147485
147699
  writeFileSync5(commentsPath, formatted.content);
147486
147700
  log.debug(`wrote ${threadBlocks.length} threads to ${commentsPath}`);
147487
147701
  return {
@@ -147710,7 +147924,7 @@ import { spawn as spawn4, spawnSync as spawnSync3 } from "node:child_process";
147710
147924
  import { randomUUID as randomUUID2 } from "node:crypto";
147711
147925
  import { closeSync, openSync, writeFileSync as writeFileSync6 } from "node:fs";
147712
147926
  import { userInfo } from "node:os";
147713
- import { join as join8 } from "node:path";
147927
+ import { join as join9 } from "node:path";
147714
147928
  import { setTimeout as sleep2 } from "node:timers/promises";
147715
147929
  var ShellParams = type({
147716
147930
  command: "string",
@@ -147843,7 +148057,7 @@ function getTempDir() {
147843
148057
  var MAX_OUTPUT_CHARS = 5e3;
147844
148058
  function capOutput(output) {
147845
148059
  if (output.length <= MAX_OUTPUT_CHARS) return output;
147846
- const fullPath = join8(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
148060
+ const fullPath = join9(getTempDir(), `shell-${randomUUID2().slice(0, 8)}.log`);
147847
148061
  writeFileSync6(fullPath, output);
147848
148062
  const elided = output.length - MAX_OUTPUT_CHARS;
147849
148063
  return `... [${elided} chars truncated; full output saved to ${fullPath}] ...
@@ -147858,6 +148072,7 @@ function isGitCommand(command) {
147858
148072
  function ShellTool(ctx) {
147859
148073
  return tool({
147860
148074
  name: "shell",
148075
+ timeoutMs: 12e4,
147861
148076
  description: `Execute shell commands securely. Environment is filtered to remove API keys and secrets.
147862
148077
 
147863
148078
  Example: \`shell({ command: "pnpm test", description: "run the test suite" })\`.
@@ -147897,8 +148112,8 @@ Do NOT use this tool for git commands \u2014 use the dedicated git tools instead
147897
148112
  if (params.background) {
147898
148113
  const tempDir = getTempDir();
147899
148114
  const handle = `bg-${randomUUID2().slice(0, 8)}`;
147900
- const outputPath = join8(tempDir, `${handle}.log`);
147901
- const pidPath = join8(tempDir, `${handle}.pid`);
148115
+ const outputPath = join9(tempDir, `${handle}.log`);
148116
+ const pidPath = join9(tempDir, `${handle}.pid`);
147902
148117
  const logFd = openSync(outputPath, "a");
147903
148118
  let proc2;
147904
148119
  try {
@@ -148238,6 +148453,8 @@ var REVIEWER_AGENT_NAME = "reviewfrog";
148238
148453
  var REVIEWER_SYSTEM_PROMPT = `You are a read-only review subagent. Your role is to find flaws in code or artifacts provided by the orchestrator and report findings \u2014 never to modify state.
148239
148454
 
148240
148455
  HARD CONSTRAINTS (non-negotiable, regardless of orchestrator instructions):
148456
+ - Your FIRST action MUST be \`git diff origin/<base>\` (single-rev form, no \`HEAD\`). This captures committed + staged + unstaged work in one command \u2014 Build-mode self-review runs BEFORE the commit, so the work to review lives in the working tree, not in committed history. Do not run any other diff command first. 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 names the base branch; the diff is the source of truth for scope.
148457
+ - If \`git diff origin/<base>\` 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. 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.
148241
148458
  - 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).
148242
148459
  - 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. Read-only MCP queries (\`get_*\`, \`list_*\`, log inspection, diff retrieval) are fine.
148243
148460
  - Do NOT spawn further subagents. You are a leaf reviewer; recursive dispatch pre-aggregates findings through an intermediate model and defeats the design.
@@ -148428,7 +148645,25 @@ function computeModes(agentId) {
148428
148645
 
148429
148646
  Otherwise delegate the \`${REVIEWER_AGENT_NAME}\` subagent to review your diff with fresh eyes against YOUR TASK. The subagent's baked-in system prompt enforces a non-mutative + non-recursive contract: read-only file/search/web tools and read-only MCP queries only; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch. Enforcement is prose-only \u2014 restate the constraint in your dispatch instructions and do not relax it.
148430
148647
 
148431
- Provide the subagent with YOUR TASK, the output of \`git diff origin/<base-branch>\` (single-rev form, no \`HEAD\` \u2014 this compares the working tree against the remote base and captures committed + staged + unstaged work; \`main...HEAD\` and \`--cached\` both miss the uncommitted edits Build self-review runs on, since self-review happens BEFORE the commit), and a tight summary (not raw output) of any lint/typecheck/test failures you fixed during build \u2014 what broke, root cause, the fix \u2014 so it can check that fixes addressed root causes rather than suppressed symptoms; say "no build-phase failures" if the build path was clean. Instruct it to flag bugs, logic errors, missing edge cases, gaps between request and diff, and unintended changes.
148648
+ Compose your \`${REVIEWER_AGENT_NAME}\` dispatch prompt using this template verbatim, substituting the \`<...>\` placeholders. The preamble aligns the orchestrator side of the dispatch contract with the reviewer's baked-in system prompt \u2014 both ends say the same thing about where the work lives and what to do on an empty diff.
148649
+
148650
+ \`\`\`
148651
+ ## What you're reviewing
148652
+ This is a PRE-COMMIT Build-mode self-review. The work to review lives in the working tree (uncommitted), NOT in committed history.
148653
+
148654
+ Branch: <branch> (off <base>)
148655
+ Canonical diff command: git diff origin/<base>
148656
+
148657
+ If that command returns empty, treat it as "no changes \u2014 nothing to review" and stop per your system prompt. Do not search for the work elsewhere.
148658
+
148659
+ ## Your task
148660
+ <YOUR TASK content>
148661
+
148662
+ ## Build-phase failures
148663
+ <tight summary \u2014 what broke, root cause, the fix \u2014 or "no build-phase failures">
148664
+ \`\`\`
148665
+
148666
+ Follow the template with the diff content (\`git diff origin/<base-branch>\`, single-rev form \u2014 \`main...HEAD\` and \`--cached\` both miss the uncommitted edits self-review runs on) and your task brief. Instruct the subagent to flag bugs, logic errors, missing edge cases, gaps between request and diff, and unintended changes.
148432
148667
 
148433
148668
  Delegation + research discipline (distilled from \`/anneal\` canonical \u2014 these are codified learnings from many review rounds, not theoretical best practices):
148434
148669
  - Do NOT summarize what you implemented \u2014 that biases the subagent toward validating the shape of your solution rather than questioning it.
@@ -148827,21 +149062,21 @@ function initToolState(params) {
148827
149062
  }
148828
149063
 
148829
149064
  // agents/claude.ts
148830
- import { execFileSync as execFileSync4 } from "node:child_process";
149065
+ import { execFileSync as execFileSync5 } from "node:child_process";
148831
149066
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
148832
- import { join as join12 } from "node:path";
149067
+ import { join as join13 } from "node:path";
148833
149068
  import { performance as performance6 } from "node:perf_hooks";
148834
149069
 
148835
149070
  // utils/install.ts
148836
149071
  import { spawnSync as spawnSync4 } from "node:child_process";
148837
149072
  import { chmodSync, createWriteStream, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "node:fs";
148838
- import { join as join9 } from "node:path";
149073
+ import { join as join10 } from "node:path";
148839
149074
  import { pipeline } from "node:stream/promises";
148840
149075
  async function installFromNpmTarball(params) {
148841
149076
  const tempDir = process.env.PULLFROG_TEMP_DIR;
148842
149077
  if (!tempDir) throw new Error("PULLFROG_TEMP_DIR is not set");
148843
- const extractedDir = join9(tempDir, "package");
148844
- const cliPath = join9(extractedDir, params.executablePath);
149078
+ const extractedDir = join10(tempDir, "package");
149079
+ const cliPath = join10(extractedDir, params.executablePath);
148845
149080
  if (existsSync5(cliPath)) {
148846
149081
  log.debug(`\xBB using cached binary at ${cliPath}`);
148847
149082
  return cliPath;
@@ -148866,7 +149101,7 @@ async function installFromNpmTarball(params) {
148866
149101
  }
148867
149102
  }
148868
149103
  log.debug(`\xBB installing ${params.packageName}@${resolvedVersion}...`);
148869
- const tarballPath = join9(tempDir, "package.tgz");
149104
+ const tarballPath = join10(tempDir, "package.tgz");
148870
149105
  const npmRegistry = process.env.NPM_REGISTRY || "https://registry.npmjs.org";
148871
149106
  let tarballUrl;
148872
149107
  if (params.packageName.startsWith("@")) {
@@ -149005,16 +149240,16 @@ function isRouterKeylimitExhaustedError(text) {
149005
149240
  // utils/skills.ts
149006
149241
  import { spawnSync as spawnSync5 } from "node:child_process";
149007
149242
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync7 } from "node:fs";
149008
- import { tmpdir as tmpdir2 } from "node:os";
149009
- import { dirname as dirname2, join as join10 } from "node:path";
149243
+ import { tmpdir as tmpdir3 } from "node:os";
149244
+ import { dirname as dirname2, join as join11 } from "node:path";
149010
149245
  import { fileURLToPath } from "node:url";
149011
149246
  var skillsVersion = getDevDependencyVersion("skills");
149012
149247
  var BUNDLED_SKILL_NAMES = ["git-archaeology"];
149013
149248
  function resolveSkillPath(name) {
149014
149249
  const here = dirname2(fileURLToPath(import.meta.url));
149015
149250
  const candidates = [
149016
- join10(here, "..", "skills", name, "SKILL.md"),
149017
- join10(here, "skills", name, "SKILL.md")
149251
+ join11(here, "..", "skills", name, "SKILL.md"),
149252
+ join11(here, "skills", name, "SKILL.md")
149018
149253
  ];
149019
149254
  for (const candidate of candidates) {
149020
149255
  if (existsSync6(candidate)) return candidate;
@@ -149026,9 +149261,9 @@ function installBundledSkills(params) {
149026
149261
  for (const name of BUNDLED_SKILL_NAMES) {
149027
149262
  const content = readFileSync5(resolveSkillPath(name), "utf8");
149028
149263
  for (const targetDir of SKILL_TARGET_DIRS) {
149029
- const skillDir = join10(params.home, targetDir, name);
149264
+ const skillDir = join11(params.home, targetDir, name);
149030
149265
  mkdirSync3(skillDir, { recursive: true });
149031
- writeFileSync7(join10(skillDir, "SKILL.md"), content);
149266
+ writeFileSync7(join11(skillDir, "SKILL.md"), content);
149032
149267
  }
149033
149268
  }
149034
149269
  log.success(`installed bundled skills: ${BUNDLED_SKILL_NAMES.join(", ")}`);
@@ -149049,7 +149284,7 @@ function addSkill(params) {
149049
149284
  "-y"
149050
149285
  ],
149051
149286
  {
149052
- cwd: tmpdir2(),
149287
+ cwd: tmpdir3(),
149053
149288
  env: { ...process.env, ...params.env },
149054
149289
  stdio: "pipe",
149055
149290
  timeout: 3e4
@@ -149134,7 +149369,7 @@ var ThinkingTimer = class {
149134
149369
  import { randomUUID as randomUUID3 } from "node:crypto";
149135
149370
  import { mkdirSync as mkdirSync4, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "node:fs";
149136
149371
  import { homedir } from "node:os";
149137
- import { join as join11 } from "node:path";
149372
+ import { join as join12 } from "node:path";
149138
149373
  var VERTEX_SERVICE_ACCOUNT_JSON_ENV = "VERTEX_SERVICE_ACCOUNT_JSON";
149139
149374
  var GOOGLE_APPLICATION_CREDENTIALS_ENV = "GOOGLE_APPLICATION_CREDENTIALS";
149140
149375
  var GOOGLE_CLOUD_PROJECT_ENV = "GOOGLE_CLOUD_PROJECT";
@@ -149163,7 +149398,7 @@ function readProjectIdFromVertexServiceAccountJson() {
149163
149398
  }
149164
149399
  function createSecretDir() {
149165
149400
  const base = process.env.PULLFROG_SECRET_HOME || process.env.HOME || homedir();
149166
- const secretDir = join11(base, ".pullfrog", "secrets", randomUUID3());
149401
+ const secretDir = join12(base, ".pullfrog", "secrets", randomUUID3());
149167
149402
  mkdirSync4(secretDir, { recursive: true, mode: 448 });
149168
149403
  return secretDir;
149169
149404
  }
@@ -149172,7 +149407,7 @@ function materializeVertexCredentials(params) {
149172
149407
  const blob = process.env[VERTEX_SERVICE_ACCOUNT_JSON_ENV];
149173
149408
  if (!blob) return void 0;
149174
149409
  const secretDir = createSecretDir();
149175
- const credentialsPath = join11(secretDir, "vertex-sa.json");
149410
+ const credentialsPath = join12(secretDir, "vertex-sa.json");
149176
149411
  writeFileSync8(credentialsPath, blob, { mode: 384 });
149177
149412
  process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV] = credentialsPath;
149178
149413
  const projectId = readProjectIdFromVertexServiceAccountJson();
@@ -149184,6 +149419,7 @@ function materializeVertexCredentials(params) {
149184
149419
  function cleanupVertexCredentials(credentials) {
149185
149420
  if (!credentials) return;
149186
149421
  rmSync2(credentials.secretDir, { recursive: true, force: true });
149422
+ delete process.env[GOOGLE_APPLICATION_CREDENTIALS_ENV];
149187
149423
  }
149188
149424
  function applyClaudeVertexEnv(env2) {
149189
149425
  env2.CLAUDE_CODE_USE_VERTEX = "1";
@@ -149497,9 +149733,9 @@ async function installClaudeCli() {
149497
149733
  });
149498
149734
  }
149499
149735
  function writeMcpConfig(ctx) {
149500
- const configDir = join12(ctx.tmpdir, ".claude");
149736
+ const configDir = join13(ctx.tmpdir, ".claude");
149501
149737
  mkdirSync5(configDir, { recursive: true });
149502
- const configPath = join12(configDir, "mcp.json");
149738
+ const configPath = join13(configDir, "mcp.json");
149503
149739
  writeFileSync9(
149504
149740
  configPath,
149505
149741
  JSON.stringify({
@@ -149925,8 +150161,8 @@ function installManagedSettings(ctx) {
149925
150161
  if (process.env.CI !== "true") return;
149926
150162
  const content = JSON.stringify(buildManagedSettings(ctx), null, 2);
149927
150163
  try {
149928
- execFileSync4("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
149929
- execFileSync4("sudo", ["tee", MANAGED_SETTINGS_PATH], {
150164
+ execFileSync5("sudo", ["mkdir", "-p", MANAGED_SETTINGS_DIR]);
150165
+ execFileSync5("sudo", ["tee", MANAGED_SETTINGS_PATH], {
149930
150166
  input: content,
149931
150167
  stdio: ["pipe", "ignore", "pipe"]
149932
150168
  });
@@ -149948,15 +150184,15 @@ var claude = agent({
149948
150184
  const model = !specifier ? void 0 : isBedrockRoute ? specifier : isVertexRoute2 ? void 0 : stripProviderPrefix(specifier);
149949
150185
  const homeEnv = {
149950
150186
  HOME: ctx.tmpdir,
149951
- XDG_CONFIG_HOME: join12(ctx.tmpdir, ".config")
150187
+ XDG_CONFIG_HOME: join13(ctx.tmpdir, ".config")
149952
150188
  };
149953
- mkdirSync5(join12(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
150189
+ mkdirSync5(join13(homeEnv.XDG_CONFIG_HOME, "claude"), { recursive: true });
149954
150190
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
149955
150191
  addSkill({
149956
150192
  ref: `vercel-labs/agent-browser@v${agentBrowserVersion}`,
149957
150193
  skill: "agent-browser",
149958
150194
  env: homeEnv,
149959
- agent: "claude"
150195
+ agent: "claude-code"
149960
150196
  });
149961
150197
  installBundledSkills({ home: homeEnv.HOME });
149962
150198
  const mcpConfigPath = writeMcpConfig(ctx);
@@ -150035,7 +150271,7 @@ var claude = agent({
150035
150271
  // agents/opencode_v2.ts
150036
150272
  var core2 = __toESM(require_core(), 1);
150037
150273
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
150038
- import { join as join14 } from "node:path";
150274
+ import { join as join15 } from "node:path";
150039
150275
  import { performance as performance7 } from "node:perf_hooks";
150040
150276
 
150041
150277
  // utils/agentHangReport.ts
@@ -150136,7 +150372,7 @@ function formatBillingExhaustedBody(diagnostic) {
150136
150372
  // utils/codexHome.ts
150137
150373
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
150138
150374
  import { homedir as homedir2 } from "node:os";
150139
- import { join as join13 } from "node:path";
150375
+ import { join as join14 } from "node:path";
150140
150376
  var CODEX_AUTH_ENV = "CODEX_AUTH_JSON";
150141
150377
  function installCodexAuth() {
150142
150378
  const raw2 = process.env[CODEX_AUTH_ENV];
@@ -150146,9 +150382,9 @@ function installCodexAuth() {
150146
150382
  log.warning(`\xBB ${CODEX_AUTH_ENV} present but malformed; ignoring`);
150147
150383
  return null;
150148
150384
  }
150149
- const xdgDataHome = join13(homedir2(), ".local", "share");
150150
- const opencodeDir = join13(xdgDataHome, "opencode");
150151
- const authPath = join13(opencodeDir, "auth.json");
150385
+ const xdgDataHome = join14(homedir2(), ".local", "share");
150386
+ const opencodeDir = join14(xdgDataHome, "opencode");
150387
+ const authPath = join14(opencodeDir, "auth.json");
150152
150388
  const opencodeAuth = {
150153
150389
  openai: {
150154
150390
  type: "oauth",
@@ -150276,7 +150512,7 @@ export default async function pullfrogEventsPlugin() {
150276
150512
  `;
150277
150513
 
150278
150514
  // agents/opencodeShared.ts
150279
- import { execFileSync as execFileSync5 } from "node:child_process";
150515
+ import { execFileSync as execFileSync6 } from "node:child_process";
150280
150516
 
150281
150517
  // agents/subagentModels.ts
150282
150518
  function deriveSubagentModels(orchestratorSpec) {
@@ -150325,7 +150561,7 @@ async function installOpencodeCli(params) {
150325
150561
  var AUTO_SELECT_WARNING = "select a model explicitly in the Pullfrog console (https://pullfrog.com/console) to avoid this.";
150326
150562
  function getOpenCodeModels(cliPath) {
150327
150563
  try {
150328
- const output = execFileSync5(cliPath, ["models"], {
150564
+ const output = execFileSync6(cliPath, ["models"], {
150329
150565
  encoding: "utf-8",
150330
150566
  timeout: 3e4,
150331
150567
  env: process.env
@@ -150822,13 +151058,13 @@ var opencode = agent({
150822
151058
  const model = vertexModel ?? (isBedrockRoute ? `amazon-bedrock/${rawModel}` : rawModel);
150823
151059
  const homeEnv = {
150824
151060
  HOME: ctx.tmpdir,
150825
- XDG_CONFIG_HOME: join14(ctx.tmpdir, ".config")
151061
+ XDG_CONFIG_HOME: join15(ctx.tmpdir, ".config")
150826
151062
  };
150827
- mkdirSync7(join14(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
150828
- const opencodePluginDir = join14(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
151063
+ mkdirSync7(join15(homeEnv.XDG_CONFIG_HOME, "opencode"), { recursive: true });
151064
+ const opencodePluginDir = join15(homeEnv.XDG_CONFIG_HOME, "opencode", "plugin");
150829
151065
  mkdirSync7(opencodePluginDir, { recursive: true });
150830
151066
  writeFileSync11(
150831
- join14(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
151067
+ join15(opencodePluginDir, PULLFROG_OPENCODE_PLUGIN_FILENAME),
150832
151068
  PULLFROG_OPENCODE_PLUGIN_SOURCE
150833
151069
  );
150834
151070
  const agentBrowserVersion = getDevDependencyVersion("agent-browser");
@@ -151190,7 +151426,7 @@ async function fetchBodyHtml(ctx) {
151190
151426
  }
151191
151427
 
151192
151428
  // utils/byokFallback.ts
151193
- var FREE_FALLBACK_SLUG = "opencode/minimax-m2.5-free";
151429
+ var FREE_FALLBACK_SLUG = "opencode/big-pickle";
151194
151430
  function selectFallbackModelIfNeeded(input) {
151195
151431
  if (input.proxyModel) return { fallback: false };
151196
151432
  if (!input.resolvedModel) return { fallback: false };
@@ -151208,7 +151444,7 @@ function selectFallbackModelIfNeeded(input) {
151208
151444
  import { randomUUID as randomUUID4 } from "node:crypto";
151209
151445
  import { writeFileSync as writeFileSync12 } from "node:fs";
151210
151446
  import { createServer as createServer2 } from "node:http";
151211
- import { join as join15 } from "node:path";
151447
+ import { join as join16 } from "node:path";
151212
151448
  var CODE_TTL_MS = 5 * 60 * 1e3;
151213
151449
  var TAMPER_WINDOW_MS = 6e4;
151214
151450
  function revokeGitHubToken(token) {
@@ -151280,7 +151516,7 @@ async function startGitAuthServer(tmpdir4) {
151280
151516
  function writeAskpassScript(code) {
151281
151517
  const scriptId = randomUUID4();
151282
151518
  const scriptName = `askpass-${scriptId}.js`;
151283
- const scriptPath = join15(tmpdir4, scriptName);
151519
+ const scriptPath = join16(tmpdir4, scriptName);
151284
151520
  const content = [
151285
151521
  `#!/usr/bin/env node`,
151286
151522
  `var a=process.argv[2]||"";`,
@@ -151319,7 +151555,7 @@ async function startGitAuthServer(tmpdir4) {
151319
151555
  var core3 = __toESM(require_core(), 1);
151320
151556
  import { createSign } from "node:crypto";
151321
151557
  import { rename, writeFile } from "node:fs/promises";
151322
- import { dirname as dirname3, join as join16 } from "node:path";
151558
+ import { dirname as dirname3, join as join17 } from "node:path";
151323
151559
 
151324
151560
  // node_modules/.pnpm/@octokit+plugin-throttling@11.0.3_@octokit+core@7.0.5/node_modules/@octokit/plugin-throttling/dist-bundle/index.js
151325
151561
  var import_light = __toESM(require_light(), 1);
@@ -155177,7 +155413,7 @@ function getGitHubUsageSummary() {
155177
155413
  }
155178
155414
  async function writeGitHubUsageSummaryToFile(path3) {
155179
155415
  const summary2 = getGitHubUsageSummary();
155180
- const tmpPath = join16(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
155416
+ const tmpPath = join17(dirname3(path3), `.usage-summary-${process.pid}.tmp`);
155181
155417
  await writeFile(tmpPath, JSON.stringify(summary2));
155182
155418
  await rename(tmpPath, path3);
155183
155419
  }
@@ -155228,7 +155464,7 @@ function createOctokit(token) {
155228
155464
  }
155229
155465
 
155230
155466
  // utils/instructions.ts
155231
- import { execSync as execSync2 } from "node:child_process";
155467
+ import { execSync as execSync3 } from "node:child_process";
155232
155468
  function buildRuntimeContext(ctx) {
155233
155469
  const {
155234
155470
  "~pullfrog": _2,
@@ -155240,7 +155476,7 @@ function buildRuntimeContext(ctx) {
155240
155476
  } = ctx.payload;
155241
155477
  let gitStatus;
155242
155478
  try {
155243
- gitStatus = execSync2("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
155479
+ gitStatus = execSync3("git status --short", { encoding: "utf-8", stdio: "pipe" }).trim() || "(clean)";
155244
155480
  } catch {
155245
155481
  }
155246
155482
  const data = {
@@ -155577,7 +155813,7 @@ function resolveInstructions(ctx) {
155577
155813
 
155578
155814
  // utils/learnings.ts
155579
155815
  import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
155580
- import { dirname as dirname4, join as join17 } from "node:path";
155816
+ import { dirname as dirname4, join as join18 } from "node:path";
155581
155817
 
155582
155818
  // utils/learningsTruncate.ts
155583
155819
  var MAX_LEARNINGS_LENGTH = 1e5;
@@ -155594,7 +155830,7 @@ function truncateAtLineBoundary(body, cap) {
155594
155830
  // utils/learnings.ts
155595
155831
  var LEARNINGS_FILE_NAME = "pullfrog-learnings.md";
155596
155832
  function learningsFilePath(tmpdir4) {
155597
- return join17(tmpdir4, LEARNINGS_FILE_NAME);
155833
+ return join18(tmpdir4, LEARNINGS_FILE_NAME);
155598
155834
  }
155599
155835
  async function seedLearningsFile(params) {
155600
155836
  const path3 = learningsFilePath(params.tmpdir);
@@ -156274,7 +156510,7 @@ async function runProxyResolution(ctx) {
156274
156510
 
156275
156511
  // utils/prSummary.ts
156276
156512
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
156277
- import { dirname as dirname5, join as join18 } from "node:path";
156513
+ import { dirname as dirname5, join as join19 } from "node:path";
156278
156514
  var SUMMARY_FILE_NAME = "pullfrog-summary.md";
156279
156515
  var SUMMARY_SCAFFOLD = `# PR summary
156280
156516
 
@@ -156284,7 +156520,7 @@ var SUMMARY_SCAFFOLD = `# PR summary
156284
156520
  var MIN_SNAPSHOT_LENGTH = 60;
156285
156521
  var MAX_SNAPSHOT_LENGTH = 32768;
156286
156522
  function summaryFilePath(tmpdir4) {
156287
- return join18(tmpdir4, SUMMARY_FILE_NAME);
156523
+ return join19(tmpdir4, SUMMARY_FILE_NAME);
156288
156524
  }
156289
156525
  async function seedSummaryFile(params) {
156290
156526
  const path3 = summaryFilePath(params.tmpdir);
@@ -156478,6 +156714,16 @@ async function resolveRunContextData(params) {
156478
156714
  }
156479
156715
 
156480
156716
  // utils/runErrorRenderer.ts
156717
+ function isProviderModelNotFoundError(message) {
156718
+ return message.includes("ProviderModelNotFoundError");
156719
+ }
156720
+ function formatProviderModelNotFoundSummary(input) {
156721
+ return `Pullfrog's free fallback model is no longer available in OpenCode's catalog. Add an API key for your configured model in the Pullfrog console for \`${input.owner}/${input.name}\`, or contact support if this persists.
156722
+
156723
+ \`\`\`
156724
+ ${input.raw}
156725
+ \`\`\``;
156726
+ }
156481
156727
  function renderRunError(input) {
156482
156728
  const billingError = isRouterKeylimitExhaustedError(input.errorMessage) ? new BillingError(input.errorMessage, { code: "router_keylimit_exhausted" }) : null;
156483
156729
  if (billingError) {
@@ -156499,6 +156745,14 @@ function renderRunError(input) {
156499
156745
  if (apiKeyErrorSummary) {
156500
156746
  return { summary: apiKeyErrorSummary, comment: apiKeyErrorSummary };
156501
156747
  }
156748
+ if (isProviderModelNotFoundError(input.errorMessage)) {
156749
+ const body = formatProviderModelNotFoundSummary({
156750
+ owner: input.repo.owner,
156751
+ name: input.repo.name,
156752
+ raw: input.errorMessage
156753
+ });
156754
+ return { summary: body, comment: body };
156755
+ }
156502
156756
  if (hangBody) {
156503
156757
  return {
156504
156758
  summary: `### \u274C Pullfrog failed
@@ -156600,16 +156854,17 @@ async function persistRunArtifacts(toolContext) {
156600
156854
  }
156601
156855
  async function finalizeSuccessRun(input) {
156602
156856
  await persistRunArtifacts(input.toolContext);
156603
- if (!input.result.success && input.toolState.progressComment) {
156604
- const rawError = input.result.error || "agent run failed";
156605
- const errorBody = isApiKeyAuthError(rawError) ? formatApiKeyErrorSummary({
156606
- owner: input.repo.owner,
156607
- name: input.repo.name,
156608
- raw: rawError
156609
- }) : rawError;
156610
- await reportErrorToComment({ toolState: input.toolState, error: errorBody }).catch((error49) => {
156611
- log.debug(`failure error report failed: ${error49}`);
156612
- });
156857
+ const rendered = !input.result.success ? renderRunError({
156858
+ errorMessage: input.result.error || "agent run failed",
156859
+ repo: input.repo,
156860
+ agentDiagnostic: input.toolState.agentDiagnostic
156861
+ }) : null;
156862
+ if (rendered && input.toolState.progressComment) {
156863
+ await reportErrorToComment({ toolState: input.toolState, error: rendered.comment }).catch(
156864
+ (error49) => {
156865
+ log.debug(`failure error report failed: ${error49}`);
156866
+ }
156867
+ );
156613
156868
  }
156614
156869
  if (input.result.success && input.toolState.progressComment && !input.toolState.finalSummaryWritten) {
156615
156870
  await deleteProgressComment(input.toolContext).catch((error49) => {
@@ -156619,7 +156874,7 @@ async function finalizeSuccessRun(input) {
156619
156874
  try {
156620
156875
  const usageSummary = formatUsageSummary(input.toolState.usageEntries);
156621
156876
  const body = input.toolState.lastProgressBody || input.result.output;
156622
- const parts = [body, usageSummary].filter(Boolean);
156877
+ const parts = [rendered?.summary, body, usageSummary].filter(Boolean);
156623
156878
  if (parts.length > 0) {
156624
156879
  await writeSummary(parts.join("\n\n"));
156625
156880
  }
@@ -156704,116 +156959,6 @@ function logRunStartup(ctx) {
156704
156959
  log.info(`\xBB timeout: ${resolveTimeoutForLog(ctx.payload.timeout)}`);
156705
156960
  }
156706
156961
 
156707
- // utils/setup.ts
156708
- import { execFileSync as execFileSync6, execSync as execSync3 } from "node:child_process";
156709
- import { mkdtempSync as mkdtempSync2 } from "node:fs";
156710
- import { tmpdir as tmpdir3 } from "node:os";
156711
- import { join as join19 } from "node:path";
156712
- function createTempDirectory() {
156713
- const sharedTempDir = mkdtempSync2(join19(tmpdir3(), "pullfrog-"));
156714
- process.env.PULLFROG_TEMP_DIR = sharedTempDir;
156715
- log.info(`\xBB created temp dir at ${sharedTempDir}`);
156716
- return sharedTempDir;
156717
- }
156718
- function envScopedToRepo() {
156719
- const scoped = { ...process.env };
156720
- for (const key of Object.keys(scoped)) {
156721
- if (key.startsWith("GIT_")) delete scoped[key];
156722
- }
156723
- return scoped;
156724
- }
156725
- function removeIncludeIfEntries(repoDir) {
156726
- const env2 = envScopedToRepo();
156727
- let configOutput;
156728
- try {
156729
- configOutput = execSync3("git config --local --get-regexp -z ^includeif\\.", {
156730
- cwd: repoDir,
156731
- encoding: "utf-8",
156732
- stdio: "pipe",
156733
- env: env2
156734
- });
156735
- } catch {
156736
- log.debug("\xBB no includeIf credential entries to remove");
156737
- return;
156738
- }
156739
- const seen = /* @__PURE__ */ new Set();
156740
- for (const entry of configOutput.split("\0")) {
156741
- if (!entry) continue;
156742
- const nl = entry.indexOf("\n");
156743
- const key = nl === -1 ? entry : entry.slice(0, nl);
156744
- if (!key || seen.has(key)) continue;
156745
- seen.add(key);
156746
- try {
156747
- execFileSync6("git", ["config", "--local", "--unset-all", key], {
156748
- cwd: repoDir,
156749
- stdio: "pipe",
156750
- env: env2
156751
- });
156752
- } catch (error49) {
156753
- log.debug(
156754
- `\xBB failed to unset ${key}: ${error49 instanceof Error ? error49.message : String(error49)}`
156755
- );
156756
- }
156757
- }
156758
- if (seen.size > 0)
156759
- log.info(
156760
- `\xBB removed ${seen.size} includeIf credential ${seen.size === 1 ? "entry" : "entries"}`
156761
- );
156762
- }
156763
- async function setupGit(params) {
156764
- const repoDir = process.cwd();
156765
- log.info("\xBB setting up git configuration...");
156766
- try {
156767
- let currentEmail = "";
156768
- try {
156769
- currentEmail = execSync3("git config user.email", {
156770
- cwd: repoDir,
156771
- stdio: "pipe",
156772
- encoding: "utf-8"
156773
- }).trim();
156774
- } catch {
156775
- }
156776
- const shouldSetDefaults = !currentEmail || currentEmail === "github-actions[bot]@users.noreply.github.com";
156777
- if (shouldSetDefaults) {
156778
- execSync3('git config --local user.email "226033991+pullfrog[bot]@users.noreply.github.com"', {
156779
- cwd: repoDir,
156780
- stdio: "pipe"
156781
- });
156782
- execSync3('git config --local user.name "pullfrog[bot]"', {
156783
- cwd: repoDir,
156784
- stdio: "pipe"
156785
- });
156786
- log.debug("\xBB git user configured (using defaults)");
156787
- } else {
156788
- log.debug(`\xBB git user already configured (${currentEmail}), skipping`);
156789
- }
156790
- if (params.shell === "disabled") {
156791
- execSync3("git config --local core.hooksPath /dev/null", {
156792
- cwd: repoDir,
156793
- stdio: "pipe"
156794
- });
156795
- log.debug("\xBB git hooks disabled (shell=disabled)");
156796
- }
156797
- } catch (error49) {
156798
- log.info(`Failed to set git config: ${error49 instanceof Error ? error49.message : String(error49)}`);
156799
- }
156800
- try {
156801
- execSync3("git config --local --unset-all http.https://github.com/.extraheader", {
156802
- cwd: repoDir,
156803
- stdio: "pipe"
156804
- });
156805
- log.info("\xBB removed existing authentication headers");
156806
- } catch {
156807
- log.debug("\xBB no existing authentication headers to remove");
156808
- }
156809
- removeIncludeIfEntries(repoDir);
156810
- const originUrl = `https://github.com/${params.owner}/${params.name}.git`;
156811
- $2("git", ["remote", "set-url", "origin", originUrl], { cwd: repoDir });
156812
- params.toolState.pushUrl = originUrl;
156813
- $2("git", ["config", "--local", "credential.helper", ""], { cwd: repoDir });
156814
- log.info("\xBB git authentication configured");
156815
- }
156816
-
156817
156962
  // utils/todoTracking.ts
156818
156963
  function isValidTodoStatus(value2) {
156819
156964
  return value2 === "pending" || value2 === "in_progress" || value2 === "completed" || value2 === "cancelled";
@@ -157021,6 +157166,7 @@ async function main() {
157021
157166
  toolState.beforeSha = payload.event.before_sha;
157022
157167
  }
157023
157168
  const tokenRef = __using(_stack2, await resolveTokens({ push: payload.push }), true);
157169
+ wipeRunnerLeakSurface();
157024
157170
  const oidcCredentials = process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN ? {
157025
157171
  requestUrl: process.env.ACTIONS_ID_TOKEN_REQUEST_URL,
157026
157172
  requestToken: process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN
@@ -157190,7 +157336,7 @@ ${instructions.user}` : null,
157190
157336
  });
157191
157337
  if (agentId === "opencode") {
157192
157338
  const pluginDir = join20(process.cwd(), ".opencode", "plugin");
157193
- const hasPlugins = existsSync7(pluginDir) && readdirSync(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
157339
+ const hasPlugins = existsSync7(pluginDir) && readdirSync2(pluginDir).some((f) => /\.[jt]sx?$/.test(f));
157194
157340
  if (hasPlugins && toolState.dependencyInstallation?.promise) {
157195
157341
  log.info(
157196
157342
  "\xBB .opencode/plugin/ detected \u2014 awaiting dependency installation before agent start"
@@ -158238,7 +158384,7 @@ async function run2() {
158238
158384
  }
158239
158385
 
158240
158386
  // cli.ts
158241
- var VERSION10 = "0.1.11";
158387
+ var VERSION10 = "0.1.12";
158242
158388
  var bin = basename2(process.argv[1] || "");
158243
158389
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
158244
158390
  var rawArgs = process.argv.slice(2);