pullfrog 0.1.75 → 0.1.77

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/internal.js CHANGED
@@ -8,7 +8,7 @@ var __export = (target, all) => {
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "pullfrog",
11
- version: "0.1.75",
11
+ version: "0.1.77",
12
12
  cliContractVersion: "1",
13
13
  type: "module",
14
14
  bin: {
@@ -628,7 +628,7 @@ __export(core_exports2, {
628
628
  parse: () => parse,
629
629
  parseAsync: () => parseAsync,
630
630
  prettifyError: () => prettifyError,
631
- process: () => process,
631
+ process: () => process2,
632
632
  regexes: () => regexes_exports,
633
633
  registry: () => registry,
634
634
  safeDecode: () => safeDecode,
@@ -11029,7 +11029,7 @@ function initializeContext(params) {
11029
11029
  external: params?.external ?? void 0
11030
11030
  };
11031
11031
  }
11032
- function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11032
+ function process2(schema, ctx, _params = { path: [], schemaPath: [] }) {
11033
11033
  var _a2;
11034
11034
  const def = schema._zod.def;
11035
11035
  const seen = ctx.seen.get(schema);
@@ -11066,7 +11066,7 @@ function process(schema, ctx, _params = { path: [], schemaPath: [] }) {
11066
11066
  if (parent) {
11067
11067
  if (!result.ref)
11068
11068
  result.ref = parent;
11069
- process(parent, ctx, params);
11069
+ process2(parent, ctx, params);
11070
11070
  ctx.seen.get(parent).isParent = true;
11071
11071
  }
11072
11072
  }
@@ -11347,14 +11347,14 @@ function isTransforming(_schema, _ctx) {
11347
11347
  }
11348
11348
  var createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
11349
11349
  const ctx = initializeContext({ ...params, processors });
11350
- process(schema, ctx);
11350
+ process2(schema, ctx);
11351
11351
  extractDefs(ctx, schema);
11352
11352
  return finalize(ctx, schema);
11353
11353
  };
11354
11354
  var createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
11355
11355
  const { libraryOptions, target } = params ?? {};
11356
11356
  const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
11357
- process(schema, ctx);
11357
+ process2(schema, ctx);
11358
11358
  extractDefs(ctx, schema);
11359
11359
  return finalize(ctx, schema);
11360
11360
  };
@@ -11611,7 +11611,7 @@ var arrayProcessor = (schema, ctx, _json, params) => {
11611
11611
  if (typeof maximum === "number")
11612
11612
  json2.maxItems = maximum;
11613
11613
  json2.type = "array";
11614
- json2.items = process(def.element, ctx, { ...params, path: [...params.path, "items"] });
11614
+ json2.items = process2(def.element, ctx, { ...params, path: [...params.path, "items"] });
11615
11615
  };
11616
11616
  var objectProcessor = (schema, ctx, _json, params) => {
11617
11617
  const json2 = _json;
@@ -11620,7 +11620,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11620
11620
  json2.properties = {};
11621
11621
  const shape = def.shape;
11622
11622
  for (const key in shape) {
11623
- json2.properties[key] = process(shape[key], ctx, {
11623
+ json2.properties[key] = process2(shape[key], ctx, {
11624
11624
  ...params,
11625
11625
  path: [...params.path, "properties", key]
11626
11626
  });
@@ -11643,7 +11643,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11643
11643
  if (ctx.io === "output")
11644
11644
  json2.additionalProperties = false;
11645
11645
  } else if (def.catchall) {
11646
- json2.additionalProperties = process(def.catchall, ctx, {
11646
+ json2.additionalProperties = process2(def.catchall, ctx, {
11647
11647
  ...params,
11648
11648
  path: [...params.path, "additionalProperties"]
11649
11649
  });
@@ -11652,7 +11652,7 @@ var objectProcessor = (schema, ctx, _json, params) => {
11652
11652
  var unionProcessor = (schema, ctx, json2, params) => {
11653
11653
  const def = schema._zod.def;
11654
11654
  const isExclusive = def.inclusive === false;
11655
- const options = def.options.map((x, i) => process(x, ctx, {
11655
+ const options = def.options.map((x, i) => process2(x, ctx, {
11656
11656
  ...params,
11657
11657
  path: [...params.path, isExclusive ? "oneOf" : "anyOf", i]
11658
11658
  }));
@@ -11664,11 +11664,11 @@ var unionProcessor = (schema, ctx, json2, params) => {
11664
11664
  };
11665
11665
  var intersectionProcessor = (schema, ctx, json2, params) => {
11666
11666
  const def = schema._zod.def;
11667
- const a = process(def.left, ctx, {
11667
+ const a = process2(def.left, ctx, {
11668
11668
  ...params,
11669
11669
  path: [...params.path, "allOf", 0]
11670
11670
  });
11671
- const b = process(def.right, ctx, {
11671
+ const b = process2(def.right, ctx, {
11672
11672
  ...params,
11673
11673
  path: [...params.path, "allOf", 1]
11674
11674
  });
@@ -11685,11 +11685,11 @@ var tupleProcessor = (schema, ctx, _json, params) => {
11685
11685
  json2.type = "array";
11686
11686
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
11687
11687
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
11688
- const prefixItems = def.items.map((x, i) => process(x, ctx, {
11688
+ const prefixItems = def.items.map((x, i) => process2(x, ctx, {
11689
11689
  ...params,
11690
11690
  path: [...params.path, prefixPath, i]
11691
11691
  }));
11692
- const rest = def.rest ? process(def.rest, ctx, {
11692
+ const rest = def.rest ? process2(def.rest, ctx, {
11693
11693
  ...params,
11694
11694
  path: [...params.path, restPath, ...ctx.target === "openapi-3.0" ? [def.items.length] : []]
11695
11695
  }) : null;
@@ -11729,7 +11729,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
11729
11729
  const keyBag = keyType._zod.bag;
11730
11730
  const patterns = keyBag?.patterns;
11731
11731
  if (def.mode === "loose" && patterns && patterns.size > 0) {
11732
- const valueSchema = process(def.valueType, ctx, {
11732
+ const valueSchema = process2(def.valueType, ctx, {
11733
11733
  ...params,
11734
11734
  path: [...params.path, "patternProperties", "*"]
11735
11735
  });
@@ -11739,12 +11739,12 @@ var recordProcessor = (schema, ctx, _json, params) => {
11739
11739
  }
11740
11740
  } else {
11741
11741
  if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
11742
- json2.propertyNames = process(def.keyType, ctx, {
11742
+ json2.propertyNames = process2(def.keyType, ctx, {
11743
11743
  ...params,
11744
11744
  path: [...params.path, "propertyNames"]
11745
11745
  });
11746
11746
  }
11747
- json2.additionalProperties = process(def.valueType, ctx, {
11747
+ json2.additionalProperties = process2(def.valueType, ctx, {
11748
11748
  ...params,
11749
11749
  path: [...params.path, "additionalProperties"]
11750
11750
  });
@@ -11759,7 +11759,7 @@ var recordProcessor = (schema, ctx, _json, params) => {
11759
11759
  };
11760
11760
  var nullableProcessor = (schema, ctx, json2, params) => {
11761
11761
  const def = schema._zod.def;
11762
- const inner = process(def.innerType, ctx, params);
11762
+ const inner = process2(def.innerType, ctx, params);
11763
11763
  const seen = ctx.seen.get(schema);
11764
11764
  if (ctx.target === "openapi-3.0") {
11765
11765
  seen.ref = def.innerType;
@@ -11770,20 +11770,20 @@ var nullableProcessor = (schema, ctx, json2, params) => {
11770
11770
  };
11771
11771
  var nonoptionalProcessor = (schema, ctx, _json, params) => {
11772
11772
  const def = schema._zod.def;
11773
- process(def.innerType, ctx, params);
11773
+ process2(def.innerType, ctx, params);
11774
11774
  const seen = ctx.seen.get(schema);
11775
11775
  seen.ref = def.innerType;
11776
11776
  };
11777
11777
  var defaultProcessor = (schema, ctx, json2, params) => {
11778
11778
  const def = schema._zod.def;
11779
- process(def.innerType, ctx, params);
11779
+ process2(def.innerType, ctx, params);
11780
11780
  const seen = ctx.seen.get(schema);
11781
11781
  seen.ref = def.innerType;
11782
11782
  json2.default = JSON.parse(JSON.stringify(def.defaultValue));
11783
11783
  };
11784
11784
  var prefaultProcessor = (schema, ctx, json2, params) => {
11785
11785
  const def = schema._zod.def;
11786
- process(def.innerType, ctx, params);
11786
+ process2(def.innerType, ctx, params);
11787
11787
  const seen = ctx.seen.get(schema);
11788
11788
  seen.ref = def.innerType;
11789
11789
  if (ctx.io === "input")
@@ -11791,7 +11791,7 @@ var prefaultProcessor = (schema, ctx, json2, params) => {
11791
11791
  };
11792
11792
  var catchProcessor = (schema, ctx, json2, params) => {
11793
11793
  const def = schema._zod.def;
11794
- process(def.innerType, ctx, params);
11794
+ process2(def.innerType, ctx, params);
11795
11795
  const seen = ctx.seen.get(schema);
11796
11796
  seen.ref = def.innerType;
11797
11797
  let catchValue;
@@ -11805,32 +11805,32 @@ var catchProcessor = (schema, ctx, json2, params) => {
11805
11805
  var pipeProcessor = (schema, ctx, _json, params) => {
11806
11806
  const def = schema._zod.def;
11807
11807
  const innerType = ctx.io === "input" ? def.in._zod.def.type === "transform" ? def.out : def.in : def.out;
11808
- process(innerType, ctx, params);
11808
+ process2(innerType, ctx, params);
11809
11809
  const seen = ctx.seen.get(schema);
11810
11810
  seen.ref = innerType;
11811
11811
  };
11812
11812
  var readonlyProcessor = (schema, ctx, json2, params) => {
11813
11813
  const def = schema._zod.def;
11814
- process(def.innerType, ctx, params);
11814
+ process2(def.innerType, ctx, params);
11815
11815
  const seen = ctx.seen.get(schema);
11816
11816
  seen.ref = def.innerType;
11817
11817
  json2.readOnly = true;
11818
11818
  };
11819
11819
  var promiseProcessor = (schema, ctx, _json, params) => {
11820
11820
  const def = schema._zod.def;
11821
- process(def.innerType, ctx, params);
11821
+ process2(def.innerType, ctx, params);
11822
11822
  const seen = ctx.seen.get(schema);
11823
11823
  seen.ref = def.innerType;
11824
11824
  };
11825
11825
  var optionalProcessor = (schema, ctx, _json, params) => {
11826
11826
  const def = schema._zod.def;
11827
- process(def.innerType, ctx, params);
11827
+ process2(def.innerType, ctx, params);
11828
11828
  const seen = ctx.seen.get(schema);
11829
11829
  seen.ref = def.innerType;
11830
11830
  };
11831
11831
  var lazyProcessor = (schema, ctx, _json, params) => {
11832
11832
  const innerType = schema._zod.innerType;
11833
- process(innerType, ctx, params);
11833
+ process2(innerType, ctx, params);
11834
11834
  const seen = ctx.seen.get(schema);
11835
11835
  seen.ref = innerType;
11836
11836
  };
@@ -11882,7 +11882,7 @@ function toJSONSchema(input, params) {
11882
11882
  const defs = {};
11883
11883
  for (const entry of registry2._idmap.entries()) {
11884
11884
  const [_, schema] = entry;
11885
- process(schema, ctx2);
11885
+ process2(schema, ctx2);
11886
11886
  }
11887
11887
  const schemas = {};
11888
11888
  const external = {
@@ -11905,7 +11905,7 @@ function toJSONSchema(input, params) {
11905
11905
  return { schemas };
11906
11906
  }
11907
11907
  const ctx = initializeContext({ ...params, processors: allProcessors });
11908
- process(input, ctx);
11908
+ process2(input, ctx);
11909
11909
  extractDefs(ctx, input);
11910
11910
  return finalize(ctx, input);
11911
11911
  }
@@ -11963,7 +11963,7 @@ var JSONSchemaGenerator = class {
11963
11963
  * This must be called before emit().
11964
11964
  */
11965
11965
  process(schema, _params = { path: [], schemaPath: [] }) {
11966
- return process(schema, this.ctx, _params);
11966
+ return process2(schema, this.ctx, _params);
11967
11967
  }
11968
11968
  /**
11969
11969
  * Emit the final JSON Schema after processing.
@@ -14023,6 +14023,20 @@ var repoConfigFields = [
14023
14023
  nullable: true,
14024
14024
  description: "additional instructions for planning"
14025
14025
  },
14026
+ {
14027
+ key: "prompts.address-reviews",
14028
+ field: "AddressReviews",
14029
+ kind: "prompt",
14030
+ nullable: true,
14031
+ description: "additional instructions for addressing review feedback"
14032
+ },
14033
+ {
14034
+ key: "prompts.fix-ci",
14035
+ field: "Fix",
14036
+ kind: "prompt",
14037
+ nullable: true,
14038
+ description: "additional instructions for fixing CI failures"
14039
+ },
14026
14040
  { key: "mention.enabled", field: "mention", kind: "toggle", description: "respond to mentions" },
14027
14041
  {
14028
14042
  key: "mention.instructions",
@@ -15461,6 +15475,97 @@ function resolveAutoTier(params) {
15461
15475
  if (!params.hasCard) return AUTO_EFFICIENT;
15462
15476
  return isAutoTier(params.model) ? params.model : defaultAutoTier(params.hasCard);
15463
15477
  }
15478
+ var ROUTER_TIERS = ["minimal", "light", "standard", "deep"];
15479
+ function isRouterTier(value) {
15480
+ return typeof value === "string" && ROUTER_TIERS.some((tier) => tier === value);
15481
+ }
15482
+ var PROVIDER_LADDERS = {
15483
+ anthropic: {
15484
+ minimal: "anthropic/claude-haiku",
15485
+ light: "anthropic/claude-sonnet",
15486
+ standard: "anthropic/claude-opus",
15487
+ deep: "anthropic/claude-opus"
15488
+ },
15489
+ openai: {
15490
+ minimal: "openai/gpt-luna",
15491
+ light: "openai/gpt-terra",
15492
+ standard: "openai/gpt-sol",
15493
+ deep: "openai/gpt-astra"
15494
+ },
15495
+ google: {
15496
+ minimal: "google/gemini-flash",
15497
+ light: "google/gemini-flash",
15498
+ standard: "google/gemini-pro",
15499
+ deep: "google/gemini-pro"
15500
+ },
15501
+ deepseek: {
15502
+ minimal: "deepseek/deepseek-flash",
15503
+ light: "deepseek/deepseek-flash",
15504
+ standard: "deepseek/deepseek-pro",
15505
+ deep: "deepseek/deepseek-pro"
15506
+ },
15507
+ xai: {
15508
+ minimal: "xai/grok-fast",
15509
+ light: "xai/grok-fast",
15510
+ standard: "xai/grok",
15511
+ deep: "xai/grok"
15512
+ },
15513
+ moonshotai: {
15514
+ minimal: "moonshotai/kimi-k2",
15515
+ light: "moonshotai/kimi-k2",
15516
+ standard: "moonshotai/kimi-k3",
15517
+ deep: "moonshotai/kimi-k3"
15518
+ },
15519
+ openrouter: {
15520
+ minimal: "openrouter/deepseek-flash",
15521
+ light: "openrouter/gpt-luna",
15522
+ standard: "openrouter/gpt-sol",
15523
+ deep: "openrouter/claude-opus"
15524
+ },
15525
+ opencode: {
15526
+ minimal: "opencode/gpt-luna",
15527
+ light: "opencode/gpt-luna",
15528
+ standard: "opencode/gpt-sol",
15529
+ deep: "opencode/claude-opus"
15530
+ }
15531
+ };
15532
+ var ROUTER_LADDER = {
15533
+ minimal: "deepseek/deepseek-flash",
15534
+ light: "openai/gpt-luna",
15535
+ standard: "openai/gpt-sol",
15536
+ deep: "anthropic/claude-opus"
15537
+ };
15538
+ var AUTO_ROUTER = "auto/router";
15539
+ var AUTO_PREFIX = "auto/";
15540
+ function autoLadder(slug) {
15541
+ if (!slug || !slug.startsWith(AUTO_PREFIX) || isAutoTier(slug)) return void 0;
15542
+ const target = slug.slice(AUTO_PREFIX.length);
15543
+ return target === "router" ? ROUTER_LADDER : PROVIDER_LADDERS[target];
15544
+ }
15545
+ function isAutoRouted(slug) {
15546
+ return autoLadder(slug) !== void 0;
15547
+ }
15548
+ function isAutoSlug(slug) {
15549
+ return isAutoTier(slug) || isAutoRouted(slug);
15550
+ }
15551
+ function autoRoutedSlug(provider2) {
15552
+ return `${AUTO_PREFIX}${provider2}`;
15553
+ }
15554
+ var ROUTED_PROVIDERS = Object.keys(
15555
+ PROVIDER_LADDERS
15556
+ ).map((key) => ({ key, displayName: providers[key].displayName }));
15557
+ function ladderRungs(ladder) {
15558
+ const rungs = [];
15559
+ for (const tier of ROUTER_TIERS) {
15560
+ const last = rungs[rungs.length - 1];
15561
+ if (last?.slug === ladder[tier]) last.tiers.push(tier);
15562
+ else rungs.push({ slug: ladder[tier], tiers: [tier] });
15563
+ }
15564
+ return rungs;
15565
+ }
15566
+ function resolveRoutedModel(params) {
15567
+ return autoLadder(params.configured)?.[params.tier];
15568
+ }
15464
15569
  function isCardGatedModel(slug) {
15465
15570
  return resolveOpenRouterModel(slug) !== void 0;
15466
15571
  }
@@ -15469,7 +15574,7 @@ function resolveModelSlug(slug) {
15469
15574
  }
15470
15575
  var MAX_FALLBACK_DEPTH = 10;
15471
15576
  function resolveDisplayAlias(slug) {
15472
- let current = isAutoTier(slug) ? AUTO_TIER_TARGET[slug] : slug;
15577
+ let current = isAutoTier(slug) ? AUTO_TIER_TARGET[slug] : autoLadder(slug)?.standard ?? slug;
15473
15578
  const visited = /* @__PURE__ */ new Set();
15474
15579
  for (let i = 0; i < MAX_FALLBACK_DEPTH; i++) {
15475
15580
  if (visited.has(current)) return void 0;
@@ -15504,6 +15609,11 @@ if (!defaultProxyAlias?.openRouterResolve) {
15504
15609
  }
15505
15610
  var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
15506
15611
  var defaultProxyDisplayName = defaultProxyAlias.displayName;
15612
+ for (const [name, ladder] of Object.entries({ ...PROVIDER_LADDERS, router: ROUTER_LADDER })) {
15613
+ for (const slug of Object.values(ladder)) {
15614
+ if (!resolveDisplayAlias(slug)) throw new Error(`${name} ladder names unknown alias ${slug}`);
15615
+ }
15616
+ }
15507
15617
  var OSS_MODEL_ALLOWLIST = [
15508
15618
  "deepseek/deepseek-flash",
15509
15619
  "deepseek/deepseek-pro",
@@ -15529,6 +15639,10 @@ function isOssAllowedModel(slug) {
15529
15639
  function getAutoSelectHintModel() {
15530
15640
  return defaultProxyDisplayName;
15531
15641
  }
15642
+ var PROVIDER_GATEWAY_URL_ENV = {
15643
+ anthropic: "ANTHROPIC_BASE_URL",
15644
+ openai: "OPENAI_BASE_URL"
15645
+ };
15532
15646
 
15533
15647
  // external.ts
15534
15648
  var pullfrogMcpName = "pullfrog";
@@ -15661,26 +15775,24 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
15661
15775
 
15662
15776
  2. Checkout the PR branch via \`${t("checkout_pr")}\`.
15663
15777
 
15664
- 3. Fetch review comments via \`${t("get_review_comments")}\`.
15778
+ 3. Fetch review comments via \`${t("get_review_comments")}\` and read the full conversation in each relevant thread.
15665
15779
 
15666
- 4. For each comment:
15667
- - understand the feedback
15780
+ 4. For each thread:
15781
+ - **check intent first**: questions, explanations, and disagreement call for a reply, not a code change. Change code only when explicitly requested. Automatic review feedback on Pullfrog-authored PRs (\`address_scope: all\`) remains actionable without a separate request; later replies can narrow or withdraw that permission.
15782
+ - under \`address_scope: mentions\`, unmentioned threads are context only unless the user's review-body request explicitly includes them
15668
15783
  - **verify the finding yourself** against the actual code before deciding whether to apply \u2014 every comment (human or agent) is a hypothesis, not a directive. agent reviewers especially are fallible.
15669
- - you are searching for a solution that is **complete, minimal, and elegant** \u2014 you may need to think hard to find it. do not over-engineer, do not be over-defensive, **do not write AI slop**. reviewers bias toward *recommending additions*, and that bias has a recognizable slop texture: defensive checks for impossible cases, extra abstractions used once, comments restating obvious code, tests asserting tautologies, "just-in-case" guards, error handlers for cases the type system already rules out. reject those. evaluate whether applying the finding would leave the code more **sound, correct, AND elegant**; two-out-of-three is a signal to look harder for a fix that gets all three. if a request would add bloat \u2014 ceremony without commensurate correctness benefit \u2014 push back in your reply rather than mechanically applying it.
15670
- - if the request stands, make the code change using your native tools; otherwise reply explaining why
15784
+ - keep requested changes **sound, minimal, and elegant**. Reject defensive checks for impossible cases, single-use abstractions, redundant comments, and tautological tests rather than mechanically applying reviewer suggestions.
15785
+ - if intent permits a code change and the finding stands, implement it using your native tools; otherwise reply without editing
15671
15786
  - record what was done (or why nothing was done)
15672
15787
 
15673
15788
  5. Quality check:
15674
- - re-call \`${t("get_review_comments")}\` with \`fresh: true\` before committing: reviewers keep commenting while you work, and any thread it now returns that you have not addressed is in scope for this run.
15675
- - test changes, then review the diff before committing \u2014 verify only intended changes are present, no debug artifacts remain, no fix turned out to be bloat in context (revert any that did), and the changes are clean enough that a senior engineer would approve without hesitation
15676
- - ${commitStep}
15789
+ - re-call \`${t("get_review_comments")}\` with \`fresh: true\` before finishing, even when only replying. Apply the same scope and intent checks to new comments; respect requests to stop or change direction before committing.
15790
+ - if code changed, test and review the diff, remove unintended changes or bloat, then ${commitStep}
15677
15791
 
15678
- 6. Finalize. Reply + resolve are paired write actions: do BOTH or NEITHER for each thread.
15679
- - ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*)
15680
- - **if the push/commit fails**, call \`${t("report_progress")}\` with the exact error and STOP \u2014 do NOT reply or resolve any thread until the fix is live on the remote. Resolving a thread without the fix landing misleads the reviewer.
15681
- - **once the fix is live on the remote**, for each thread you acted on:
15682
- - reply ONCE via \`${t("reply_to_review_comment")}\`. The \`comment_id\` parameter takes the root comment's numeric \`id=\` (from the first \`comment author=...\` tag in the \`${t("get_review_comments")}\` output) \u2014 NOT the \`thread=\` value; that's a separate GraphQL ID used by resolve. The runtime dedupes identical bodies within a session.
15683
- - **immediately** call \`${t("resolve_review_thread")}\` with that thread's \`thread=\` value as \`thread_id\`. Resolve every thread where you (a) made the requested code change in full \u2014 partial fixes leave the thread open \u2014 OR (b) replied with a substantive answer the user explicitly asked for. Do NOT resolve threads where you pushed back on the request and the disagreement is unresolved; leave those open for the human to mediate.
15792
+ 6. Finalize:
15793
+ - **only if code changed**, ${finalizeStep} (same push/prepush guidance as Build mode in *SYSTEM*). If push/commit fails, report the exact error and STOP without claiming a fix or resolving its threads.
15794
+ - reply ONCE per relevant thread via \`${t("reply_to_review_comment")}\`, including when no code changed. The \`comment_id\` is the root comment's numeric \`id=\`, NOT the \`thread=\` value from \`${t("get_review_comments")}\`. The runtime dedupes identical bodies within a session.
15795
+ - resolve a thread via \`${t("resolve_review_thread")}\` (using \`thread=\` as \`thread_id\`) only after its requested fix is fully on the remote, or the user explicitly asks to resolve it. Leave discussion-only replies, partial fixes, and disagreements open.
15684
15796
  - call \`${t("report_progress")}\` with a brief summary. If your last \`${t("get_review_comments")}\` turned up threads you did not address, name them there \u2014 a review arriving this late is suppressed as a duplicate and nothing else will pick it up.`
15685
15797
  },
15686
15798
  // Review and IncrementalReview route the minimum reviewfrog specialists
@@ -16583,6 +16695,7 @@ export {
16583
16695
  APPROVAL_CHECK_NAME,
16584
16696
  AUTO_EFFICIENT,
16585
16697
  AUTO_INTELLIGENT,
16698
+ AUTO_ROUTER,
16586
16699
  CLI_CONTRACT_HEADER,
16587
16700
  CLI_CONTRACT_VERSION,
16588
16701
  CLI_UPGRADE_MESSAGE,
@@ -16594,10 +16707,16 @@ export {
16594
16707
  OAuthInvalidGrantError,
16595
16708
  OSS_MODEL_ALLOWLIST,
16596
16709
  OSS_RECOMMENDED_MODEL,
16710
+ PROVIDER_GATEWAY_URL_ENV,
16597
16711
  PULLFROG_DIVIDER,
16712
+ ROUTED_PROVIDERS,
16713
+ ROUTER_LADDER,
16714
+ ROUTER_TIERS,
16598
16715
  RUN_STATUS_CHECK_NAME,
16599
16716
  SUBSIDY_RUNG,
16600
16717
  TIMEOUT_DISABLED,
16718
+ autoLadder,
16719
+ autoRoutedSlug,
16601
16720
  buildPullfrogFooter,
16602
16721
  commercialPaywallBody,
16603
16722
  configEntrySchema,
@@ -16617,6 +16736,8 @@ export {
16617
16736
  getModelProvider,
16618
16737
  getProgressComment,
16619
16738
  getProviderDisplayName,
16739
+ isAutoRouted,
16740
+ isAutoSlug,
16620
16741
  isAutoTier,
16621
16742
  isCardGatedModel,
16622
16743
  isCredentialProbeable,
@@ -16624,7 +16745,9 @@ export {
16624
16745
  isLeapingIntoActionCommentBody,
16625
16746
  isOssAllowedModel,
16626
16747
  isPullfrog,
16748
+ isRouterTier,
16627
16749
  isValidTimeString,
16750
+ ladderRungs,
16628
16751
  modelAliases,
16629
16752
  modelHasStoredAuth,
16630
16753
  modes,
@@ -16645,6 +16768,7 @@ export {
16645
16768
  resolveModelRung,
16646
16769
  resolveModelSlug,
16647
16770
  resolveOpenRouterModel,
16771
+ resolveRoutedModel,
16648
16772
  resolveRung,
16649
16773
  runStatusCheckNeedsFinalizing,
16650
16774
  rungLabel,
package/dist/models.d.ts CHANGED
@@ -218,6 +218,58 @@ export declare function resolveAutoTier(params: {
218
218
  model: string | null;
219
219
  hasCard: boolean;
220
220
  }): AutoTier;
221
+ /**
222
+ * the four rungs the model router scores a review into, ascending. `minimal` is
223
+ * a PR with no behavioural surface, `deep` one whose defects are absences the
224
+ * diff does not show. see wiki/router.md for how a tier is earned.
225
+ */
226
+ export declare const ROUTER_TIERS: readonly ["minimal", "light", "standard", "deep"];
227
+ export type RouterTier = (typeof ROUTER_TIERS)[number];
228
+ export declare function isRouterTier(value: unknown): value is RouterTier;
229
+ type Ladder = Record<RouterTier, string>;
230
+ /**
231
+ * the Router's own ladder for an account on `auto/router` with a card on
232
+ * file. an account without a card is never routed onto this:
233
+ * `resolveAutoTier` clamps it to the efficient default at every tier, exactly
234
+ * as before the router existed.
235
+ */
236
+ export declare const ROUTER_LADDER: Ladder;
237
+ /**
238
+ * the `repo.model` sentinel that turns the router on: `auto/<provider>` runs
239
+ * each piece of work on that provider's ladder, `auto/router` on the Pullfrog
240
+ * Router's. distinct from the two managed tiers above, which each name ONE
241
+ * model. a Pro feature — outside Pro the sentinel simply runs its ladder's
242
+ * `standard` rung, which is what `resolveDisplayAlias` resolves it to.
243
+ */
244
+ export declare const AUTO_ROUTER = "auto/router";
245
+ /** the ladder an Auto sentinel routes on; undefined for a tier or a pin */
246
+ export declare function autoLadder(slug: string | null | undefined): Ladder | undefined;
247
+ export declare function isAutoRouted(slug: string | null | undefined): boolean;
248
+ /** any `auto/*` sentinel — a managed tier or the router — as opposed to a concrete pin */
249
+ export declare function isAutoSlug(slug: string | null | undefined): boolean;
250
+ export declare function autoRoutedSlug(provider: string): string;
251
+ /** the providers a BYOK repo can put on Auto — exactly the ones with a ladder */
252
+ export declare const ROUTED_PROVIDERS: readonly {
253
+ key: string;
254
+ displayName: string;
255
+ }[];
256
+ /**
257
+ * a ladder's distinct models in ascending order, each with the tiers it
258
+ * serves — the console's "what Auto runs on" list, where a model that covers
259
+ * two tiers is one row.
260
+ */
261
+ export declare function ladderRungs(ladder: Ladder): {
262
+ slug: string;
263
+ tiers: RouterTier[];
264
+ }[];
265
+ /**
266
+ * the model a routed run takes: the tier's rung on the ladder the configured
267
+ * Auto sentinel names. undefined for a pin — a pin opts out of routing.
268
+ */
269
+ export declare function resolveRoutedModel(params: {
270
+ configured: string | null | undefined;
271
+ tier: RouterTier;
272
+ }): string | undefined;
221
273
  /**
222
274
  * Router-resolvable — the set a card on file unlocks. custom (non-Auto) picks
223
275
  * are card-gated wholesale on the Router: the console locks the Custom tab
@@ -376,6 +428,27 @@ export declare const OPENAI_COMPATIBLE_CONTEXT_ENV = "OPENAI_COMPATIBLE_CONTEXT"
376
428
  * `context` + `output` together, so the pair is validated and emitted as a unit.
377
429
  */
378
430
  export declare const OPENAI_COMPATIBLE_MAX_OUTPUT_ENV = "OPENAI_COMPATIBLE_MAX_OUTPUT";
431
+ /**
432
+ * Providers whose endpoint can be re-pointed at a customer gateway, and the env
433
+ * var that does it. Deliberately a short explicit map rather than a derived
434
+ * `<PROVIDER>_BASE_URL` rule: only a provider we have confirmed reachable
435
+ * through a proxy belongs here, and three of them must never be.
436
+ * `openai-compatible` already owns its endpoint via OPENAI_COMPATIBLE_BASE_URL,
437
+ * `azure` derives one from AZURE_RESOURCE_NAME, and bedrock/vertex authenticate
438
+ * against a cloud SDK rather than a URL.
439
+ *
440
+ * models.dev declares no base-URL env var for any catalogued provider (2 of 213
441
+ * do, and neither is one of these), so nothing reads these names on its own —
442
+ * `providerGatewayOverride` is what makes them mean anything to opencode and
443
+ * `gatewayProvider` to codex, while claude-code reads ANTHROPIC_BASE_URL itself.
444
+ *
445
+ * Adding a provider that ALREADY has a block in `buildSecurityConfig` needs a
446
+ * merge, not an entry here: that spread replaces the whole key, so listing
447
+ * `openrouter` would silently drop `kimiOpenRouterProviderOverrides()`.
448
+ */
449
+ export declare const PROVIDER_GATEWAY_URL_ENV: Record<string, string>;
450
+ /** the customer gateway a model's provider is re-pointed at, if any. */
451
+ export declare function getProviderGatewayUrl(specifier: string | undefined): string | undefined;
379
452
  /**
380
453
  * the Bedrock model ID passed to claude-code or opencode is whatever the
381
454
  * user set in `BEDROCK_MODEL_ID` — Pullfrog never resolves or upgrades it.
@@ -7,6 +7,13 @@ export declare const JsonPayload: import("arktype/internal/variants/object.ts").
7
7
  type?: string | undefined;
8
8
  model?: string | undefined;
9
9
  modelExplicit?: boolean | undefined;
10
+ routing?: {
11
+ tier: "minimal" | "light" | "standard" | "deep";
12
+ stakes: number;
13
+ workload: number;
14
+ source: "scorer" | "heuristic" | "fixed";
15
+ rationale: string;
16
+ } | undefined;
10
17
  effort?: string | number | undefined;
11
18
  debug?: boolean | undefined;
12
19
  triggerer?: string | undefined;
@@ -54,6 +61,13 @@ export declare function resolvePayload(resolvedPromptInput: ResolvedPromptInput,
54
61
  version: string;
55
62
  model: string | undefined;
56
63
  modelExplicit: boolean;
64
+ routing: {
65
+ tier: "minimal" | "light" | "standard" | "deep";
66
+ stakes: number;
67
+ workload: number;
68
+ source: "scorer" | "heuristic" | "fixed";
69
+ rationale: string;
70
+ } | undefined;
57
71
  effort: number | undefined;
58
72
  debug: true | undefined;
59
73
  prompt: string;
@@ -1,4 +1,5 @@
1
1
  import type { PushPermission, ShellPermission } from "../external.ts";
2
+ import type { RouterTier } from "../models.ts";
2
3
  import type { RepoContext } from "./github.ts";
3
4
  export interface Mode {
4
5
  id: string;
@@ -104,4 +105,7 @@ export declare function fetchRunContext(params: {
104
105
  /** `payload.type` — lets the server apply this repo's per-trigger model
105
106
  * override, which it cannot derive from owner/repo alone. */
106
107
  runType?: string | undefined;
108
+ /** `payload.routing.tier` — the model router's tier, which run-context
109
+ * applies to the Router proxy mint the same way it applies `runType`. */
110
+ routedTier?: RouterTier | undefined;
107
111
  }): Promise<RunContext>;
@@ -1,4 +1,5 @@
1
1
  import type { Octokit } from "@octokit/rest";
2
+ import type { RouterTier } from "../models.ts";
2
3
  import { type OctokitWithPlugins } from "./github.ts";
3
4
  import { type AccountPlan, type CommercialRefusal, type RepoSettings } from "./runContext.ts";
4
5
  export interface RunContextData {
@@ -33,6 +34,8 @@ interface ResolveRunContextDataParams {
33
34
  * because run-context is fetched first and needs it to pick this trigger's
34
35
  * model override. */
35
36
  runType?: string | undefined;
37
+ /** the model router's tier from the payload, forwarded so run-context applies it to the proxy mint. */
38
+ routedTier?: RouterTier | undefined;
36
39
  }
37
40
  /**
38
41
  * initialize run context data: parse context, fetch repo info and settings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
4
4
  "cliContractVersion": "1",
5
5
  "type": "module",
6
6
  "bin": {