ralphctl 0.22.0 → 0.23.0

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.
@@ -646,19 +646,22 @@ var CLAUDE_MODELS = [
646
646
  "claude-opus-4-8",
647
647
  "claude-opus-4-8[1m]",
648
648
  "claude-opus-5",
649
+ "claude-opus-5-5",
649
650
  "claude-fable-5",
650
- "claude-fable-5[1m]"
651
+ "claude-fable-5[1m]",
652
+ "claude-fable-5-1"
651
653
  ];
652
654
  var isClaudeModel = (s) => CLAUDE_MODELS.includes(s);
653
655
 
654
656
  // src/domain/value/settings-models/codex.ts
655
657
  var CODEX_MODELS = [
658
+ "gpt-6-astra",
659
+ "gpt-6-sol",
660
+ "gpt-6-luna",
656
661
  "gpt-5.6-sol",
657
662
  "gpt-5.6-terra",
658
663
  "gpt-5.6-luna",
659
664
  "gpt-5.5",
660
- "gpt-5.4",
661
- "gpt-5.4-mini",
662
665
  "codex-auto-review"
663
666
  ];
664
667
  var isCodexModel = (s) => CODEX_MODELS.includes(s);
@@ -675,25 +678,25 @@ var COPILOT_MODELS = [
675
678
  "gpt-5.6-sol",
676
679
  "gpt-5.6-terra",
677
680
  "gpt-5.6-luna",
681
+ "gpt-6-astra",
682
+ "gpt-6-sol",
683
+ "gpt-6-luna",
678
684
  // Anthropic
679
685
  "claude-haiku-4.5",
680
- "claude-opus-4.5",
681
- "claude-opus-4.6",
682
686
  "claude-opus-4.7",
683
687
  "claude-opus-4.8",
684
688
  "claude-opus-4.8-fast",
685
689
  "claude-opus-5",
690
+ "claude-opus-5.5",
686
691
  "claude-fable-5",
687
- "claude-sonnet-4.5",
688
- "claude-sonnet-4.6",
692
+ "claude-fable-5.1",
689
693
  "claude-sonnet-5",
690
694
  // Google
691
- "gemini-3.1-pro",
692
695
  "gemini-3.5-flash",
693
696
  "gemini-3.6-flash",
694
697
  "gemini-3.7-flash",
698
+ "gemini-3.8-flash",
695
699
  // Microsoft
696
- "mai-code-1-flash",
697
700
  "mai-code-1.1-flash",
698
701
  // Moonshot
699
702
  "kimi-k2.7-code",
@@ -701,26 +704,24 @@ var COPILOT_MODELS = [
701
704
  // xAI
702
705
  "grok-4.5",
703
706
  "grok-4.6",
704
- // Fine-tuned
705
- "raptor-mini"
707
+ "grok-4.7"
706
708
  ];
707
709
  var isCopilotModel = (s) => COPILOT_MODELS.includes(s);
708
710
 
709
711
  // src/domain/value/settings-models/opencode.ts
710
712
  var OPENCODE_MODELS = [
711
713
  "opencode/big-pickle",
712
- "opencode/deepseek-v4-flash-free",
713
- "opencode/laguna-s-2.1-free",
714
- "opencode/ling-3.0-tiny-free",
715
- "opencode/longcat-2.0-free",
714
+ "opencode/hy3-free",
715
+ "opencode/ling-3.0-flash-fin-free",
716
716
  "opencode/mimo-v2.5-free",
717
+ "opencode/muse-spark-1.2-contributor-free",
717
718
  "opencode/nemotron-3-ultra-free",
718
- "opencode/north-mini-code-free"
719
+ "opencode/nemotron-3.5-lightning-free"
719
720
  ];
720
721
  var isOpencodeModelIdShape = (s) => /^[^/\s]+(?:\/[^/\s]+)+$/.test(s);
721
722
 
722
723
  // src/domain/value/settings-models/grok.ts
723
- var GROK_MODELS = ["grok-4.6", "grok-4.5"];
724
+ var GROK_MODELS = ["grok-4.7", "grok-4.7-build-fast", "grok-4.6", "grok-4.5"];
724
725
  var isGrokModel = (s) => GROK_MODELS.includes(s);
725
726
 
726
727
  // src/domain/value/flow-id.ts
@@ -818,23 +819,53 @@ var seedLegacyCreatePrRow = (ai) => {
818
819
  if (!("provider" in refine)) return ai;
819
820
  return { ...aiObj, createPr: { ...refine } };
820
821
  };
822
+ var COPILOT_GEMINI_SUCCESSOR = "gemini-3.8-flash";
823
+ var COPILOT_SMALL_CODE_SUCCESSOR = "mai-code-1.1-flash";
821
824
  var RETIRED_MODEL_REMAPS = [
822
825
  { provider: PROVIDER_CLAUDE_CODE, from: "claude-opus-4-7", to: "claude-opus-4-8" },
826
+ { provider: PROVIDER_GITHUB_COPILOT, from: "claude-opus-4.5", to: "claude-opus-4.8" },
827
+ { provider: PROVIDER_GITHUB_COPILOT, from: "claude-opus-4.6", to: "claude-opus-4.8" },
823
828
  { provider: PROVIDER_GITHUB_COPILOT, from: "claude-opus-4.6-fast", to: "claude-opus-4.8-fast" },
824
- { provider: PROVIDER_GITHUB_COPILOT, from: "gemini-3.1-pro-preview", to: "gemini-3.1-pro" },
825
- { provider: PROVIDER_GITHUB_COPILOT, from: "raptor-mini-preview", to: "raptor-mini" },
826
- { provider: PROVIDER_GITHUB_COPILOT, from: "gemini-2.5-pro", to: "gemini-3.1-pro" },
829
+ { provider: PROVIDER_GITHUB_COPILOT, from: "claude-sonnet-4.5", to: "claude-sonnet-5" },
830
+ { provider: PROVIDER_GITHUB_COPILOT, from: "claude-sonnet-4.6", to: "claude-sonnet-5" },
831
+ { provider: PROVIDER_GITHUB_COPILOT, from: "gemini-3.1-pro", to: COPILOT_GEMINI_SUCCESSOR },
832
+ { provider: PROVIDER_GITHUB_COPILOT, from: "gemini-3.1-pro-preview", to: COPILOT_GEMINI_SUCCESSOR },
833
+ { provider: PROVIDER_GITHUB_COPILOT, from: "gemini-2.5-pro", to: COPILOT_GEMINI_SUCCESSOR },
827
834
  { provider: PROVIDER_GITHUB_COPILOT, from: "gemini-3-flash", to: "gemini-3.5-flash" },
835
+ { provider: PROVIDER_GITHUB_COPILOT, from: "raptor-mini", to: COPILOT_SMALL_CODE_SUCCESSOR },
836
+ { provider: PROVIDER_GITHUB_COPILOT, from: "raptor-mini-preview", to: COPILOT_SMALL_CODE_SUCCESSOR },
837
+ { provider: PROVIDER_GITHUB_COPILOT, from: "mai-code-1-flash", to: COPILOT_SMALL_CODE_SUCCESSOR },
838
+ { provider: PROVIDER_OPENAI_CODEX, from: "gpt-5.4", to: "gpt-6-sol" },
839
+ { provider: PROVIDER_OPENAI_CODEX, from: "gpt-5.4-mini", to: "gpt-6-luna" },
828
840
  { provider: PROVIDER_OPENAI_CODEX, from: "gpt-5.2", to: "gpt-5.5" },
829
841
  { provider: PROVIDER_OPENAI_CODEX, from: "gpt-5.3-codex", to: "gpt-5.5" },
830
- { provider: PROVIDER_OPENAI_CODEX, from: "gpt-5.3-codex-spark", to: "gpt-5.5" }
842
+ { provider: PROVIDER_OPENAI_CODEX, from: "gpt-5.3-codex-spark", to: "gpt-5.5" },
843
+ { provider: PROVIDER_OPENCODE, from: "opencode/deepseek-v4-flash-free", to: "opencode/nemotron-3.5-lightning-free" },
844
+ { provider: PROVIDER_OPENCODE, from: "opencode/ling-3.0-tiny-free", to: "opencode/ling-3.0-flash-fin-free" }
831
845
  ];
846
+ var remapRetiredModel = (provider, model) => RETIRED_MODEL_REMAPS.find((r) => r.provider === provider && r.from === model)?.to ?? model;
847
+ var LIVE_CATALOG_IDS = /* @__PURE__ */ new Set([
848
+ ...CLAUDE_MODELS,
849
+ ...COPILOT_MODELS,
850
+ ...CODEX_MODELS,
851
+ ...OPENCODE_MODELS,
852
+ ...GROK_MODELS
853
+ ]);
854
+ var unambiguousRetiredSuccessor = (model) => {
855
+ if (LIVE_CATALOG_IDS.has(model)) return void 0;
856
+ const hits = RETIRED_MODEL_REMAPS.filter((r) => r.from === model);
857
+ return hits.length === 1 ? hits[0]?.to : void 0;
858
+ };
832
859
  var RETIRED_CODEX_EFFORT = "minimal";
833
860
  var migrateStaleRow = (row) => {
834
861
  if (typeof row !== "object" || row === null) return row;
835
862
  let next = row;
836
- const remap = RETIRED_MODEL_REMAPS.find((r) => next["provider"] === r.provider && next["model"] === r.from);
837
- if (remap !== void 0) next = { ...next, model: remap.to };
863
+ const provider = next["provider"];
864
+ const model = next["model"];
865
+ if (typeof provider === "string" && typeof model === "string") {
866
+ const remapped = remapRetiredModel(provider, model);
867
+ if (remapped !== model) next = { ...next, model: remapped };
868
+ }
838
869
  if (next["provider"] === PROVIDER_OPENAI_CODEX && next["effort"] === RETIRED_CODEX_EFFORT) {
839
870
  next = { ...next, effort: "low" };
840
871
  }
@@ -871,6 +902,21 @@ var healHarnessCrossKnobs = (harness) => {
871
902
  const healedTurns = Math.max(turns, healedThreshold);
872
903
  return { ...h, maxTurns: healedTurns, plateauThreshold: healedThreshold };
873
904
  };
905
+ var remapRetiredEscalationTargets = (harness) => {
906
+ if (typeof harness !== "object" || harness === null) return harness;
907
+ const h = harness;
908
+ const map = h["escalationMap"];
909
+ if (typeof map !== "object" || map === null) return harness;
910
+ let changed2 = false;
911
+ const next = {};
912
+ for (const [from, to] of Object.entries(map)) {
913
+ const successor = typeof to === "string" ? unambiguousRetiredSuccessor(to) : void 0;
914
+ if (successor !== void 0) changed2 = true;
915
+ next[from] = successor ?? to;
916
+ }
917
+ return changed2 ? { ...h, escalationMap: next } : harness;
918
+ };
919
+ var healHarness = (harness) => remapRetiredEscalationTargets(healHarnessCrossKnobs(harness));
874
920
  var FlowSkillsRowSchema = z.object({
875
921
  disabled: z.array(z.string().trim().min(1, "skill name must be a non-empty trimmed string")).readonly()
876
922
  });
@@ -904,7 +950,7 @@ var SettingsSchema = z.object({
904
950
  schemaVersion: z.literal(CURRENT_SCHEMA_VERSION).default(CURRENT_SCHEMA_VERSION),
905
951
  ai: AiSettingsSchema,
906
952
  harness: z.preprocess(
907
- healHarnessCrossKnobs,
953
+ healHarness,
908
954
  z.object({
909
955
  /** Generator–evaluator turns budgeted per `Attempt` (1–10). */
910
956
  maxTurns: z.number().int().min(1).max(10),
@@ -954,7 +1000,7 @@ var SettingsSchema = z.object({
954
1000
  * a plain same-model fresh-attempt retry — instead of settling immediately.
955
1001
  *
956
1002
  * On an escalatable exit the generator climbs one rung up the merged ladder ({@link
957
- * escalationMap} over the built-in `DEFAULT_ESCALATION_MAP`) across successive failures, bounded
1003
+ * escalationMap} over the generator provider's built-in ladder) across successive failures, bounded
958
1004
  * by `maxAttempts`; each climb hands the targeted prior critique to the stronger model. The
959
1005
  * "change your approach" directive is NOT injected on a model bump — it fires only once the
960
1006
  * generator reaches the top of the ladder, as a same-model nudge (one more attempt on the same
@@ -968,10 +1014,11 @@ var SettingsSchema = z.object({
968
1014
  */
969
1015
  escalateOnPlateau: z.boolean().default(true),
970
1016
  /**
971
- * User overrides for the built-in `DEFAULT_ESCALATION_MAP` (in
1017
+ * User overrides for the built-in per-provider `DEFAULT_ESCALATION_LADDERS` (in
972
1018
  * `business/task/escalation-map.ts`). Keys are the current model id, values the model id
973
- * to escalate to. Empty by default; merged at read time with user keys winning on
974
- * conflict and extending the default ladder. Non-string entries fail schema validation
1019
+ * to escalate to. Flat on purpose: it applies to whichever provider the generator runs on.
1020
+ * Empty by default; merged at read time over the generator provider's ladder, with user
1021
+ * keys winning on conflict and extending it. Non-string entries fail schema validation
975
1022
  * with a typed Zod error naming the offending field.
976
1023
  */
977
1024
  escalationMap: z.record(z.string(), z.string()).default({}),
@@ -1025,7 +1072,7 @@ var SettingsSchema = z.object({
1025
1072
  * COST CAVEAT: the granted attempt spawns N full generator sessions instead of one,
1026
1073
  * multiplying spend for that one attempt. It is bounded (at most once per task, and only
1027
1074
  * for a task that has already exhausted the whole ladder), but cost-sensitive setups opt
1028
- * out with `0` — which is exactly what the four `*-economic` presets pin.
1075
+ * out with `0` — which is exactly what the five `*-economic` presets pin.
1029
1076
  *
1030
1077
  * Research: arXiv 2604.16529 (RTV) — harness-level N-candidate selection lifted SWE-bench
1031
1078
  * Verified 67.4 → 73.6 at N=16 (RTV measured N=16, not the 2-4 cap used here); the tight
@@ -1064,10 +1111,9 @@ var SettingsSchema = z.object({
1064
1111
  */
1065
1112
  scm: z.object({
1066
1113
  /**
1067
- * Governs the default reviewer choice for the refine flow's "Post as comment" action,
1068
- * and — in non-interactive (CI / headless) runs — whether the refined requirements are
1069
- * posted as a comment on the linked issue at all. Defaults `false`: the original issue
1070
- * description is never touched, and posting a comment is strictly opt-in.
1114
+ * Retained so existing settings files parse. Does not select the refine approval default
1115
+ * and does not post a comment — Approve always leads, and commenting is an explicit
1116
+ * "Post as comment" choice on a linked ticket.
1071
1117
  */
1072
1118
  postRefinementComment: z.boolean().default(false)
1073
1119
  }).default({ postRefinementComment: false }),
@@ -1109,42 +1155,59 @@ var uniqueProvidersFromAi = (ai) => {
1109
1155
  var AI_PROVIDERS = AiProviderSchema.options;
1110
1156
  var AI_PROVIDERS_HINT = AI_PROVIDERS.join(", ");
1111
1157
 
1112
- // src/business/settings/defaults.ts
1113
- var CLAUDE_SONNET = "claude-sonnet-5";
1114
- var CLAUDE_OPUS = "claude-opus-5";
1115
- var GPT_5_MINI = "gpt-5-mini";
1116
- var GPT_5_4_MINI = "gpt-5.4-mini";
1117
- var GPT_5_6_SOL = "gpt-5.6-sol";
1158
+ // src/business/settings/preset-model-ids.ts
1159
+ var CLAUDE = "claude-code";
1160
+ var COPILOT = "github-copilot";
1161
+ var CODEX = "openai-codex";
1162
+ var OPENCODE = "opencode";
1163
+ var GROK = "xai-grok";
1164
+ var OPUS = "claude-opus-5-5";
1165
+ var SONNET = "claude-sonnet-5";
1166
+ var FABLE = "claude-fable-5-1";
1167
+ var COPILOT_OPUS = "claude-opus-4.8";
1168
+ var COPILOT_SONNET = "claude-sonnet-5";
1169
+ var COPILOT_LUNA = "gpt-5.6-luna";
1170
+ var GPT_6_ASTRA = "gpt-6-astra";
1171
+ var GPT_6_SOL = "gpt-6-sol";
1172
+ var GPT_6_LUNA = "gpt-6-luna";
1173
+ var GROK_FLAGSHIP = "grok-4.7";
1174
+ var GROK_MID = "grok-4.6";
1175
+ var GROK_CHEAP = "grok-4.5";
1118
1176
  var OPENCODE_BIG = "opencode/big-pickle";
1119
- var OPENCODE_MINI = "opencode/deepseek-v4-flash-free";
1177
+ var OPENCODE_MINI = "opencode/nemotron-3.5-lightning-free";
1178
+
1179
+ // src/business/settings/defaults.ts
1120
1180
  var DEFAULT_MODELS_BY_PROVIDER = {
1121
1181
  "claude-code": {
1122
- refine: CLAUDE_SONNET,
1123
- plan: CLAUDE_OPUS,
1124
- implement: CLAUDE_OPUS,
1125
- readiness: CLAUDE_SONNET,
1126
- ideate: CLAUDE_OPUS,
1182
+ refine: SONNET,
1183
+ plan: OPUS,
1184
+ implement: OPUS,
1185
+ readiness: SONNET,
1186
+ ideate: OPUS,
1127
1187
  // PR-content drafting is a single-shot summarisation task — Sonnet matches refine's
1128
1188
  // light reasoning profile and avoids the Opus premium for a few-paragraph diff write-up.
1129
- createPr: CLAUDE_SONNET
1189
+ createPr: SONNET
1130
1190
  },
1191
+ // Same tiers as `copilot-only`. Opus 4.8 rather than Opus 5 / 5.5 (plan-gated on Copilot), and
1192
+ // it tops the Copilot escalation ladder, so the reset-to-copilot implement default and the
1193
+ // ladder top stay aligned.
1131
1194
  "github-copilot": {
1132
- refine: GPT_5_MINI,
1133
- plan: "gpt-5.4",
1134
- implement: "gpt-5.4",
1135
- readiness: GPT_5_MINI,
1136
- ideate: "gpt-5.4",
1137
- createPr: GPT_5_MINI
1195
+ refine: COPILOT_SONNET,
1196
+ plan: COPILOT_OPUS,
1197
+ implement: COPILOT_OPUS,
1198
+ readiness: COPILOT_LUNA,
1199
+ ideate: COPILOT_OPUS,
1200
+ createPr: COPILOT_LUNA
1138
1201
  },
1139
1202
  "openai-codex": {
1140
- refine: GPT_5_4_MINI,
1141
- plan: GPT_5_6_SOL,
1142
- // `gpt-5.6-sol` is the codex flagship (codex CLI ≥ 0.145); it tops the Codex escalation
1143
- // ladder, so the reset-to-codex implement default and the ladder top stay aligned.
1144
- implement: GPT_5_6_SOL,
1145
- readiness: GPT_5_4_MINI,
1146
- ideate: GPT_5_6_SOL,
1147
- createPr: GPT_5_4_MINI
1203
+ refine: GPT_6_LUNA,
1204
+ plan: GPT_6_SOL,
1205
+ // `gpt-6-sol` is the codex flagship; it tops the Codex escalation ladder, so the
1206
+ // reset-to-codex implement default and the ladder top stay aligned.
1207
+ implement: GPT_6_SOL,
1208
+ readiness: GPT_6_LUNA,
1209
+ ideate: GPT_6_SOL,
1210
+ createPr: GPT_6_LUNA
1148
1211
  },
1149
1212
  // OpenCode aggregates upstream providers, so there is no vendor flagship to default to. These
1150
1213
  // are the zero-auth free-tier picks — they make a fresh install runnable with no credentials
@@ -1159,13 +1222,14 @@ var DEFAULT_MODELS_BY_PROVIDER = {
1159
1222
  ideate: OPENCODE_BIG,
1160
1223
  createPr: OPENCODE_MINI
1161
1224
  },
1225
+ // Same split as `grok-only`.
1162
1226
  "xai-grok": {
1163
- refine: "grok-4.5",
1164
- plan: "grok-4.6",
1165
- implement: "grok-4.6",
1166
- readiness: "grok-4.5",
1167
- ideate: "grok-4.6",
1168
- createPr: "grok-4.5"
1227
+ refine: GROK_MID,
1228
+ plan: GROK_FLAGSHIP,
1229
+ implement: GROK_FLAGSHIP,
1230
+ readiness: GROK_CHEAP,
1231
+ ideate: GROK_FLAGSHIP,
1232
+ createPr: GROK_CHEAP
1169
1233
  }
1170
1234
  };
1171
1235
  var defaultAiSettingsForProvider = (provider) => {
@@ -1185,8 +1249,8 @@ var DEFAULT_SETTINGS = {
1185
1249
  ai: {
1186
1250
  ...defaultAiSettingsForProvider("claude-code"),
1187
1251
  implement: {
1188
- generator: { provider: "claude-code", model: CLAUDE_OPUS },
1189
- evaluator: { provider: "openai-codex", model: GPT_5_6_SOL }
1252
+ generator: { provider: "claude-code", model: OPUS },
1253
+ evaluator: { provider: "openai-codex", model: GPT_6_SOL }
1190
1254
  }
1191
1255
  },
1192
1256
  harness: {
@@ -3903,10 +3967,15 @@ var CONTEXT_WINDOW = {
3903
3967
  "claude-sonnet-5": 1e6,
3904
3968
  // Opus 5 — natively 1M in Claude Code — no `[1m]` selector; the bare id carries the figure.
3905
3969
  "claude-opus-5": 1e6,
3970
+ // Opus 5.5 and Fable 5.1 — natively 1M (default AND max) in Claude Code, no `[1m]` selector.
3971
+ "claude-opus-5-5": 1e6,
3972
+ "claude-fable-5-1": 1e6,
3906
3973
  // `[1m]` is Claude Code's 1M-token long-context selector — the window IS the id suffix.
3907
3974
  "claude-opus-4-8[1m]": 1e6,
3908
3975
  "claude-fable-5[1m]": 1e6,
3909
- // Grok (xai-grok adapter — docs.x.ai published window for both catalog ids)
3976
+ // Grok (xai-grok adapter — docs.x.ai published window; build-fast is the same model)
3977
+ "grok-4.7": 5e5,
3978
+ "grok-4.7-build-fast": 5e5,
3910
3979
  "grok-4.6": 5e5,
3911
3980
  "grok-4.5": 5e5
3912
3981
  };
@@ -6005,10 +6074,76 @@ var runCli = (spawn2, command, args, opts) => new Promise((resolve2) => {
6005
6074
  writeStdinBody(child, opts.stdin);
6006
6075
  });
6007
6076
 
6008
- // src/integration/scm/issue-fetcher.ts
6077
+ // src/integration/scm/gitlab-notes.ts
6009
6078
  var CLI_TIMEOUT_MS = 3e4;
6010
- var MAX_COMMENTS = 20;
6011
6079
  var UNKNOWN_ERROR = "unknown error";
6080
+ var NOTES_LABEL = "glab api issue notes";
6081
+ var gitLabNotesArgs = (ref3) => [
6082
+ "api",
6083
+ "--hostname",
6084
+ ref3.hostname,
6085
+ "--paginate",
6086
+ "--output",
6087
+ "json",
6088
+ `projects/${encodeURIComponent(`${ref3.owner}/${ref3.repo}`)}/issues/${String(ref3.number)}/notes?per_page=100&sort=asc&order_by=created_at`
6089
+ ];
6090
+ var listGitLabIssueNotes = async (spawn2, ref3) => {
6091
+ const result = await runCli(spawn2, "glab", [...gitLabNotesArgs(ref3)], { timeoutMs: CLI_TIMEOUT_MS });
6092
+ if (!result.ok) return Result.error(result.error);
6093
+ if (result.value.exitCode !== 0) {
6094
+ return Result.error(
6095
+ new StorageError({
6096
+ subCode: "io",
6097
+ message: `${NOTES_LABEL} failed: ${result.value.stderr.trim() || UNKNOWN_ERROR}`
6098
+ })
6099
+ );
6100
+ }
6101
+ let parsed;
6102
+ try {
6103
+ parsed = JSON.parse(result.value.stdout);
6104
+ } catch (cause) {
6105
+ return Result.error(
6106
+ new StorageError({ subCode: "parse", message: `failed to parse ${NOTES_LABEL} response`, cause })
6107
+ );
6108
+ }
6109
+ if (!Array.isArray(parsed)) {
6110
+ return Result.error(
6111
+ new StorageError({ subCode: "parse", message: `failed to parse ${NOTES_LABEL} response: expected a JSON array` })
6112
+ );
6113
+ }
6114
+ const notes = parsed.filter((n) => n.system !== true);
6115
+ const sorted = [...notes].sort((a, b) => (a.created_at ?? "").localeCompare(b.created_at ?? ""));
6116
+ return Result.ok(sorted.map((n) => ({ body: n.body ?? "", author: n.author?.username })));
6117
+ };
6118
+
6119
+ // src/integration/scm/issue-fetcher.ts
6120
+ var CLI_TIMEOUT_MS2 = 3e4;
6121
+ var MAX_COMMENTS = 20;
6122
+ var UNKNOWN_ERROR2 = "unknown error";
6123
+ var parseGitRemoteUrl = (url) => {
6124
+ const trimmed = url.trim();
6125
+ if (trimmed.length === 0) return null;
6126
+ const sshShort = /^[A-Za-z0-9._-]+@([A-Za-z0-9._-]+):([^/]+)\/(.+?)(\.git)?$/u.exec(trimmed);
6127
+ if (sshShort !== null) {
6128
+ const host2 = sshShort[1];
6129
+ const owner2 = sshShort[2];
6130
+ const repo2 = sshShort[3];
6131
+ return { provider: host2.includes("github") ? "github" : "gitlab", owner: owner2, repo: repo2 };
6132
+ }
6133
+ let parsed;
6134
+ try {
6135
+ parsed = new URL(trimmed);
6136
+ } catch {
6137
+ return null;
6138
+ }
6139
+ const segments = parsed.pathname.replace(/^\/+/, "").replace(/\.git$/, "").split("/");
6140
+ if (segments.length < 2) return null;
6141
+ const repo = segments[segments.length - 1];
6142
+ const owner = segments.slice(0, -1).join("/");
6143
+ if (owner.length === 0 || repo === void 0 || repo.length === 0) return null;
6144
+ const host = parsed.hostname;
6145
+ return { provider: host.includes("github") ? "github" : "gitlab", owner, repo };
6146
+ };
6012
6147
  var parseGitHubIssuePath = (segments, hostname) => {
6013
6148
  if (segments.length >= 4 && segments[2] === "issues") {
6014
6149
  const owner = segments[0];
@@ -6063,7 +6198,7 @@ var fetchGitHub = async (spawn2, parsed, url) => {
6063
6198
  "--json",
6064
6199
  "title,body,state,url,comments"
6065
6200
  ],
6066
- { timeoutMs: CLI_TIMEOUT_MS }
6201
+ { timeoutMs: CLI_TIMEOUT_MS2 }
6067
6202
  );
6068
6203
  if (!result.ok) return Result.error(result.error);
6069
6204
  if (result.value.exitCode !== 0) {
@@ -6071,7 +6206,7 @@ var fetchGitHub = async (spawn2, parsed, url) => {
6071
6206
  return Result.error(
6072
6207
  new StorageError({
6073
6208
  subCode: "io",
6074
- message: `gh issue view failed: ${result.value.stderr.trim() || UNKNOWN_ERROR}`
6209
+ message: `gh issue view failed: ${result.value.stderr.trim() || UNKNOWN_ERROR2}`
6075
6210
  })
6076
6211
  );
6077
6212
  }
@@ -6100,33 +6235,11 @@ var fetchGitHub = async (spawn2, parsed, url) => {
6100
6235
  });
6101
6236
  };
6102
6237
  var fetchGitLabNotes = async (spawn2, parsed) => {
6103
- const result = await runCli(
6104
- spawn2,
6105
- "glab",
6106
- ["issue", "note", "list", String(parsed.number), "--repo", glabRepoArg(parsed), "--output", "json"],
6107
- { timeoutMs: CLI_TIMEOUT_MS }
6108
- );
6109
- if (!result.ok) return { comments: [], failure: result.error.message };
6110
- if (result.value.exitCode !== 0) {
6111
- return {
6112
- comments: [],
6113
- failure: `glab issue note list exited ${String(result.value.exitCode)}: ${result.value.stderr.trim() || UNKNOWN_ERROR}`
6114
- };
6115
- }
6116
- let notes;
6117
- try {
6118
- notes = JSON.parse(result.value.stdout);
6119
- } catch (cause) {
6120
- return {
6121
- comments: [],
6122
- failure: `failed to parse glab issue note list response: ${cause instanceof Error ? cause.message : String(cause)}`
6123
- };
6124
- }
6125
- const filtered = notes.filter((n) => n.system !== true);
6126
- const sorted = [...filtered].sort((a, b) => (a.created_at ?? "").localeCompare(b.created_at ?? ""));
6127
- const comments = sorted.slice(-MAX_COMMENTS).map((n) => ({
6128
- author: n.author?.username ?? "unknown",
6129
- body: n.body ?? ""
6238
+ const notes = await listGitLabIssueNotes(spawn2, parsed);
6239
+ if (!notes.ok) return { comments: [], failure: notes.error.message };
6240
+ const comments = notes.value.slice(-MAX_COMMENTS).map((n) => ({
6241
+ author: n.author ?? "unknown",
6242
+ body: n.body
6130
6243
  }));
6131
6244
  return { comments };
6132
6245
  };
@@ -6135,7 +6248,7 @@ var fetchGitLab = async (spawn2, parsed, url, logger) => {
6135
6248
  spawn2,
6136
6249
  "glab",
6137
6250
  ["issue", "view", String(parsed.number), "--repo", glabRepoArg(parsed), "--output", "json"],
6138
- { timeoutMs: CLI_TIMEOUT_MS }
6251
+ { timeoutMs: CLI_TIMEOUT_MS2 }
6139
6252
  );
6140
6253
  if (!result.ok) return Result.error(result.error);
6141
6254
  if (result.value.exitCode !== 0) {
@@ -6143,7 +6256,7 @@ var fetchGitLab = async (spawn2, parsed, url, logger) => {
6143
6256
  return Result.error(
6144
6257
  new StorageError({
6145
6258
  subCode: "io",
6146
- message: `glab issue view failed: ${result.value.stderr.trim() || UNKNOWN_ERROR}`
6259
+ message: `glab issue view failed: ${result.value.stderr.trim() || UNKNOWN_ERROR2}`
6147
6260
  })
6148
6261
  );
6149
6262
  }
@@ -6161,7 +6274,7 @@ var fetchGitLab = async (spawn2, parsed, url, logger) => {
6161
6274
  }
6162
6275
  const notes = await fetchGitLabNotes(spawn2, parsed);
6163
6276
  if (notes.failure !== void 0) {
6164
- logger?.warn(`glab issue note list failed for ${url}: ${notes.failure} \u2014 proceeding without comments`);
6277
+ logger?.warn(`could not list GitLab notes for ${url}: ${notes.failure} \u2014 proceeding without comments`);
6165
6278
  }
6166
6279
  return Result.ok({
6167
6280
  url: parsedJson.web_url ?? url,
@@ -6178,23 +6291,52 @@ var createIssueFetcher = (deps) => async (url) => {
6178
6291
  return fetchGitLab(deps.spawn, parsed, url, deps.logger);
6179
6292
  };
6180
6293
 
6294
+ // src/business/scm/pull-request-creator.ts
6295
+ var HTTP_REMOTE_RE = /^https?:\/\/([^/]+)\//i;
6296
+ var SSH_REMOTE_RE = /^(?:git\+ssh:\/\/)?(?:[^@]+@)?([^:/]+)[:/]/i;
6297
+ var parseRemoteHostname = (remoteUrl) => {
6298
+ const trimmed = remoteUrl.trim();
6299
+ if (trimmed.length === 0) return null;
6300
+ const httpMatch = HTTP_REMOTE_RE.exec(trimmed);
6301
+ if (httpMatch?.[1] !== void 0) return httpMatch[1];
6302
+ const sshMatch = SSH_REMOTE_RE.exec(trimmed);
6303
+ if (sshMatch?.[1] !== void 0) return sshMatch[1];
6304
+ return null;
6305
+ };
6306
+ var detectPullRequestPlatform = (remoteUrl) => {
6307
+ const hostname = parseRemoteHostname(remoteUrl);
6308
+ if (hostname === null) return null;
6309
+ const lower = hostname.toLowerCase();
6310
+ if (lower === "github.com" || lower.endsWith(".github.com")) return "github";
6311
+ if (lower === "gitlab.com" || lower.startsWith("gitlab.") || lower.includes(".gitlab.")) return "gitlab";
6312
+ return null;
6313
+ };
6314
+ var parseUrlFromCliStdout = (stdout) => {
6315
+ const lines = stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
6316
+ for (let i = lines.length - 1; i >= 0; i--) {
6317
+ const line = lines[i];
6318
+ if (line !== void 0 && /^https?:\/\//.test(line)) return line;
6319
+ }
6320
+ return lines.at(-1) ?? null;
6321
+ };
6322
+
6181
6323
  // src/integration/scm/issue-pusher.ts
6182
- var CLI_TIMEOUT_MS2 = 3e4;
6324
+ var CLI_TIMEOUT_MS3 = 3e4;
6325
+ var UNKNOWN_ERROR3 = "unknown error";
6326
+ var cliFailed = (noun, stderr, extra) => new StorageError({
6327
+ subCode: "io",
6328
+ message: extra === void 0 ? `${noun} failed: ${stderr.trim() || UNKNOWN_ERROR3}` : `${noun} failed: ${stderr.trim() || UNKNOWN_ERROR3} (${extra})`
6329
+ });
6183
6330
  var commentGitHub = async (spawn2, url, body, parsed) => {
6184
6331
  const r = await runCli(
6185
6332
  spawn2,
6186
6333
  "gh",
6187
6334
  ["issue", "comment", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--body-file", "-"],
6188
- { stdin: body, timeoutMs: CLI_TIMEOUT_MS2 }
6335
+ { stdin: body, timeoutMs: CLI_TIMEOUT_MS3 }
6189
6336
  );
6190
6337
  if (!r.ok) return Result.error(r.error);
6191
6338
  if (r.value.exitCode !== 0) {
6192
- return Result.error(
6193
- new StorageError({
6194
- subCode: "io",
6195
- message: `gh issue comment failed: ${r.value.stderr.trim() || "unknown error"} (url=${url})`
6196
- })
6197
- );
6339
+ return Result.error(cliFailed("gh issue comment", r.value.stderr, `url=${url}`));
6198
6340
  }
6199
6341
  return Result.ok(void 0);
6200
6342
  };
@@ -6208,23 +6350,148 @@ var commentGitLab = async (spawn2, url, body, parsed) => {
6208
6350
  String(parsed.number),
6209
6351
  "--repo",
6210
6352
  `${parsed.hostname}/${parsed.owner}/${parsed.repo}`,
6211
- "--body",
6353
+ "--message",
6212
6354
  body
6213
6355
  ],
6214
- { timeoutMs: CLI_TIMEOUT_MS2 }
6356
+ { timeoutMs: CLI_TIMEOUT_MS3 }
6357
+ );
6358
+ if (!r.ok) return Result.error(r.error);
6359
+ if (r.value.exitCode !== 0) {
6360
+ return Result.error(cliFailed("glab issue comment", r.value.stderr, `url=${url}`));
6361
+ }
6362
+ return Result.ok(void 0);
6363
+ };
6364
+ var resolveOriginFromGit = async (gitRunner, cwd) => {
6365
+ const result = await gitRunner.run(cwd, ["remote", "get-url", "origin"]);
6366
+ if (!result.ok) return Result.error(result.error);
6367
+ if (result.value.exitCode !== 0) return Result.ok(null);
6368
+ const url = result.value.stdout.trim();
6369
+ if (url.length === 0) return Result.ok(null);
6370
+ const parsed = parseGitRemoteUrl(url);
6371
+ if (parsed === null) return Result.ok(null);
6372
+ const platform = detectPullRequestPlatform(url);
6373
+ if (platform === null) return Result.ok(null);
6374
+ const hostname = parseRemoteHostname(url);
6375
+ if (hostname === null) return Result.ok(null);
6376
+ return Result.ok({
6377
+ provider: platform,
6378
+ hostname,
6379
+ owner: parsed.owner,
6380
+ repo: parsed.repo
6381
+ });
6382
+ };
6383
+ var createGitHub = async (spawn2, origin, title, body) => {
6384
+ const r = await runCli(
6385
+ spawn2,
6386
+ "gh",
6387
+ ["issue", "create", "--repo", `${origin.owner}/${origin.repo}`, "--title", title, "--body-file", "-"],
6388
+ { stdin: body, timeoutMs: CLI_TIMEOUT_MS3 }
6215
6389
  );
6216
6390
  if (!r.ok) return Result.error(r.error);
6217
6391
  if (r.value.exitCode !== 0) {
6392
+ return Result.error(cliFailed("gh issue create", r.value.stderr));
6393
+ }
6394
+ const url = parseUrlFromCliStdout(r.value.stdout);
6395
+ if (url === null) {
6396
+ return Result.error(new StorageError({ subCode: "io", message: "gh issue create succeeded but emitted no URL" }));
6397
+ }
6398
+ return Result.ok({ url });
6399
+ };
6400
+ var createGitLab = async (spawn2, origin, title, body) => {
6401
+ const r = await runCli(
6402
+ spawn2,
6403
+ "glab",
6404
+ [
6405
+ "issue",
6406
+ "create",
6407
+ "--repo",
6408
+ `${origin.hostname}/${origin.owner}/${origin.repo}`,
6409
+ "--title",
6410
+ title,
6411
+ "--description",
6412
+ body
6413
+ ],
6414
+ { timeoutMs: CLI_TIMEOUT_MS3 }
6415
+ );
6416
+ if (!r.ok) return Result.error(r.error);
6417
+ if (r.value.exitCode !== 0) {
6418
+ return Result.error(cliFailed("glab issue create", r.value.stderr));
6419
+ }
6420
+ const url = parseUrlFromCliStdout(r.value.stdout);
6421
+ if (url === null) {
6422
+ return Result.error(new StorageError({ subCode: "io", message: "glab issue create succeeded but emitted no URL" }));
6423
+ }
6424
+ return Result.ok({ url });
6425
+ };
6426
+ var listGitHubComments = async (spawn2, parsed) => {
6427
+ const result = await runCli(
6428
+ spawn2,
6429
+ "gh",
6430
+ [
6431
+ "issue",
6432
+ "view",
6433
+ String(parsed.number),
6434
+ "--repo",
6435
+ `${parsed.owner}/${parsed.repo}`,
6436
+ "--json",
6437
+ "title,body,state,url,comments"
6438
+ ],
6439
+ { timeoutMs: CLI_TIMEOUT_MS3 }
6440
+ );
6441
+ if (!result.ok) return Result.error(result.error);
6442
+ if (result.value.exitCode !== 0) {
6443
+ return Result.error(cliFailed("gh issue view", result.value.stderr));
6444
+ }
6445
+ let parsedJson;
6446
+ try {
6447
+ parsedJson = JSON.parse(result.value.stdout);
6448
+ } catch (cause) {
6218
6449
  return Result.error(
6219
6450
  new StorageError({
6220
- subCode: "io",
6221
- message: `glab issue comment failed: ${r.value.stderr.trim() || "unknown error"} (url=${url})`
6451
+ subCode: "parse",
6452
+ message: "failed to parse gh issue response",
6453
+ cause
6222
6454
  })
6223
6455
  );
6224
6456
  }
6225
- return Result.ok(void 0);
6457
+ return Result.ok((parsedJson.comments ?? []).map((c) => c.body ?? ""));
6458
+ };
6459
+ var listGitLabComments = async (spawn2, parsed) => {
6460
+ const notes = await listGitLabIssueNotes(spawn2, parsed);
6461
+ if (!notes.ok) return Result.error(notes.error);
6462
+ return Result.ok(notes.value.map((n) => n.body));
6226
6463
  };
6227
6464
  var createIssuePusher = (deps) => ({
6465
+ async resolveOrigin(cwd) {
6466
+ return resolveOriginFromGit(deps.gitRunner, cwd);
6467
+ },
6468
+ async create({ cwd, title, body }) {
6469
+ const origin = await resolveOriginFromGit(deps.gitRunner, cwd);
6470
+ if (!origin.ok) return Result.error(origin.error);
6471
+ if (origin.value === null) {
6472
+ return Result.error(
6473
+ new StorageError({
6474
+ subCode: "io",
6475
+ message: `no GitHub or GitLab origin at ${String(cwd)}`
6476
+ })
6477
+ );
6478
+ }
6479
+ if (origin.value.provider === "github") return createGitHub(deps.spawn, origin.value, title, body);
6480
+ return createGitLab(deps.spawn, origin.value, title, body);
6481
+ },
6482
+ async listComments(url) {
6483
+ const parsed = parseIssueUrl(url);
6484
+ if (parsed === null) {
6485
+ return Result.error(
6486
+ new StorageError({
6487
+ subCode: "parse",
6488
+ message: `unsupported issue URL: ${url}`
6489
+ })
6490
+ );
6491
+ }
6492
+ if (parsed.host === "github") return listGitHubComments(deps.spawn, parsed);
6493
+ return listGitLabComments(deps.spawn, parsed);
6494
+ },
6228
6495
  async comment(url, { body }) {
6229
6496
  const parsed = parseIssueUrl(url);
6230
6497
  if (parsed === null) {
@@ -6240,37 +6507,8 @@ var createIssuePusher = (deps) => ({
6240
6507
  }
6241
6508
  });
6242
6509
 
6243
- // src/business/scm/pull-request-creator.ts
6244
- var HTTP_REMOTE_RE = /^https?:\/\/([^/]+)\//i;
6245
- var SSH_REMOTE_RE = /^(?:git\+ssh:\/\/)?(?:[^@]+@)?([^:/]+)[:/]/i;
6246
- var parseRemoteHostname = (remoteUrl) => {
6247
- const trimmed = remoteUrl.trim();
6248
- if (trimmed.length === 0) return null;
6249
- const httpMatch = HTTP_REMOTE_RE.exec(trimmed);
6250
- if (httpMatch?.[1] !== void 0) return httpMatch[1];
6251
- const sshMatch = SSH_REMOTE_RE.exec(trimmed);
6252
- if (sshMatch?.[1] !== void 0) return sshMatch[1];
6253
- return null;
6254
- };
6255
- var detectPullRequestPlatform = (remoteUrl) => {
6256
- const hostname = parseRemoteHostname(remoteUrl);
6257
- if (hostname === null) return null;
6258
- const lower = hostname.toLowerCase();
6259
- if (lower === "github.com" || lower.endsWith(".github.com")) return "github";
6260
- if (lower === "gitlab.com" || lower.startsWith("gitlab.") || lower.includes(".gitlab.")) return "gitlab";
6261
- return null;
6262
- };
6263
- var parseUrlFromCliStdout = (stdout) => {
6264
- const lines = stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
6265
- for (let i = lines.length - 1; i >= 0; i--) {
6266
- const line = lines[i];
6267
- if (line !== void 0 && /^https?:\/\//.test(line)) return line;
6268
- }
6269
- return lines.at(-1) ?? null;
6270
- };
6271
-
6272
6510
  // src/integration/scm/pull-request-creator.ts
6273
- var CLI_TIMEOUT_MS3 = 6e4;
6511
+ var CLI_TIMEOUT_MS4 = 6e4;
6274
6512
  var getOriginRemoteUrl = async (gitRunner, cwd) => {
6275
6513
  const result = await gitRunner.run(cwd, ["remote", "get-url", "origin"]);
6276
6514
  if (!result.ok) return Result.error(result.error);
@@ -6329,7 +6567,7 @@ var runPlatformCli = async (spawn2, platform, input) => {
6329
6567
  const command = platform === "github" ? "gh" : "glab";
6330
6568
  const args = platform === "github" ? buildGhArgs(input) : buildGlabArgs(input);
6331
6569
  const noun = platform === "github" ? "gh pr create" : "glab mr create";
6332
- const result = await runCli(spawn2, command, args, { cwd: String(input.cwd), timeoutMs: CLI_TIMEOUT_MS3 });
6570
+ const result = await runCli(spawn2, command, args, { cwd: String(input.cwd), timeoutMs: CLI_TIMEOUT_MS4 });
6333
6571
  if (!result.ok) return Result.error(result.error);
6334
6572
  if (result.value.exitCode !== 0) {
6335
6573
  const stderr = result.value.stderr.trim();
@@ -7127,7 +7365,7 @@ var createNpmVersionChecker = (deps) => {
7127
7365
  // package.json
7128
7366
  var package_default = {
7129
7367
  name: "ralphctl",
7130
- version: "0.22.0",
7368
+ version: "0.23.0",
7131
7369
  description: "A ralph harness for long-running AI coding tasks \u2014 a generator-evaluator loop that drives Claude Code, GitHub Copilot CLI, OpenAI Codex CLI, OpenCode or Grok Build CLI across one or more repositories",
7132
7370
  homepage: "https://github.com/lukas-grigis/ralphctl",
7133
7371
  type: "module",
@@ -7209,24 +7447,24 @@ var package_default = {
7209
7447
  devDependencies: {
7210
7448
  "@eslint/js": "^10.0.1",
7211
7449
  "@types/cross-spawn": "^6.0.6",
7212
- "@types/node": "^26.5.1",
7450
+ "@types/node": "^26.6.2",
7213
7451
  "@types/proper-lockfile": "^4.1.4",
7214
7452
  "@types/react": "^19.3.0",
7215
7453
  "@vitest/coverage-v8": "^5.0.0",
7216
- eslint: "^10.10.0",
7454
+ eslint: "^10.11.0",
7217
7455
  "eslint-plugin-react-hooks": "^7.1.1",
7218
7456
  "eslint-plugin-sonarjs": "^4.2.0",
7219
7457
  globals: "^17.12.0",
7220
7458
  husky: "^9.1.7",
7221
7459
  "ink-testing-library": "^4.0.0",
7222
7460
  jiti: "^2.7.0",
7223
- knip: "^6.35.1",
7461
+ knip: "^6.37.0",
7224
7462
  "lint-staged": "^17.5.0",
7225
- prettier: "^3.9.6",
7463
+ prettier: "^3.9.8",
7226
7464
  tsup: "^8.5.1",
7227
- tsx: "^4.23.13",
7465
+ tsx: "^4.23.15",
7228
7466
  typescript: "^6.0.3",
7229
- "typescript-eslint": "^8.70.0",
7467
+ "typescript-eslint": "^8.70.1",
7230
7468
  vite: "^8.2.2",
7231
7469
  vitest: "^5.0.0"
7232
7470
  },
@@ -7246,30 +7484,45 @@ var CLI_METADATA = {
7246
7484
  };
7247
7485
 
7248
7486
  // src/business/task/escalation-map.ts
7249
- var DEFAULT_ESCALATION_MAP = {
7250
- // Claude (Claude-Code dash-form) — Haiku → Sonnet 5 → Opus 5; legacy tiers chain through their
7251
- // own generation (Sonnet 4.6 → Opus 4.8 → Opus 5) and converge at the flagship.
7252
- "claude-haiku-4-5": "claude-sonnet-5",
7253
- "claude-sonnet-5": "claude-opus-5",
7254
- "claude-sonnet-4-6": "claude-opus-4-8",
7255
- "claude-opus-4-8": "claude-opus-5",
7256
- // Claude (Copilot dot-form) — Haiku → Sonnet 4.6 → Opus 4.8. Opus 4.8 is deliberately the
7257
- // dot-form top: claude-opus-5 is plan-gated on Copilot, so the default ladder never steers a
7258
- // mid-task spawn into a model many accounts cannot use (add a rung via escalationMap to opt in).
7259
- "claude-haiku-4.5": "claude-sonnet-4.6",
7260
- "claude-sonnet-4.6": "claude-opus-4.8",
7261
- // Copilot/Codex GPT — minis step to the 5.5 full tier; the 5.5/5.4 generation chains into the
7262
- // 5.6 family; within 5.6, luna → terra → sol (sol is the flagship top rung).
7263
- "gpt-5-mini": "gpt-5.5",
7264
- "gpt-5.4-mini": "gpt-5.5",
7265
- "gpt-5.4": "gpt-5.5",
7266
- "gpt-5.5": "gpt-5.6-sol",
7267
- "gpt-5.6-luna": "gpt-5.6-terra",
7268
- "gpt-5.6-terra": "gpt-5.6-sol",
7269
- "grok-4.5": "grok-4.6"
7270
- };
7271
- var mergeEscalationMap = (user) => ({
7272
- ...DEFAULT_ESCALATION_MAP,
7487
+ var CLAUDE_OPUS_5_5 = "claude-opus-5-5";
7488
+ var GPT_5_5 = "gpt-5.5";
7489
+ var GPT_5_6_SOL = "gpt-5.6-sol";
7490
+ var GPT_6_SOL2 = "gpt-6-sol";
7491
+ var DEFAULT_ESCALATION_LADDERS = {
7492
+ "claude-code": {
7493
+ "claude-haiku-4-5": "claude-sonnet-5",
7494
+ "claude-sonnet-5": CLAUDE_OPUS_5_5,
7495
+ "claude-sonnet-4-6": "claude-opus-4-8",
7496
+ "claude-opus-4-8": CLAUDE_OPUS_5_5,
7497
+ "claude-opus-5": CLAUDE_OPUS_5_5
7498
+ },
7499
+ "github-copilot": {
7500
+ "claude-haiku-4.5": "claude-sonnet-5",
7501
+ "claude-sonnet-5": "claude-opus-4.8",
7502
+ "claude-opus-4.7": "claude-opus-4.8",
7503
+ "gpt-5-mini": GPT_5_5,
7504
+ "gpt-5.4-mini": GPT_5_5,
7505
+ "gpt-5.4": GPT_5_5,
7506
+ [GPT_5_5]: GPT_5_6_SOL,
7507
+ "gpt-5.6-luna": "gpt-5.6-terra",
7508
+ "gpt-5.6-terra": GPT_5_6_SOL,
7509
+ "grok-4.5": "grok-4.6"
7510
+ },
7511
+ "openai-codex": {
7512
+ "gpt-6-luna": GPT_6_SOL2,
7513
+ [GPT_5_5]: GPT_5_6_SOL,
7514
+ "gpt-5.6-luna": "gpt-5.6-terra",
7515
+ "gpt-5.6-terra": GPT_5_6_SOL,
7516
+ [GPT_5_6_SOL]: GPT_6_SOL2
7517
+ },
7518
+ "xai-grok": {
7519
+ "grok-4.5": "grok-4.6",
7520
+ "grok-4.6": "grok-4.7"
7521
+ },
7522
+ opencode: {}
7523
+ };
7524
+ var mergeEscalationMap = (user, provider) => ({
7525
+ ...provider === void 0 ? {} : DEFAULT_ESCALATION_LADDERS[provider],
7273
7526
  ...user
7274
7527
  });
7275
7528
  var warnEscalationMapSelfLoops = (escalationMap, logger) => {
@@ -7279,6 +7532,17 @@ var warnEscalationMapSelfLoops = (escalationMap, logger) => {
7279
7532
  }
7280
7533
  }
7281
7534
  };
7535
+ var warnEscalationMapRetiredValues = (escalationMap, logger) => {
7536
+ for (const [from, to] of Object.entries(escalationMap)) {
7537
+ const retirements = RETIRED_MODEL_REMAPS.filter((r) => r.from === to);
7538
+ if (retirements.length === 0) continue;
7539
+ const where = retirements.map((r) => `${r.provider} (successor '${r.to}')`).join(", ");
7540
+ logger.warn(
7541
+ `escalationMap: '${from}' escalates to '${to}', which is retired on ${where} \u2014 left unchanged because the slug is ambiguous; pin the successor if the generator runs there`,
7542
+ { from, to }
7543
+ );
7544
+ }
7545
+ };
7282
7546
  var escalationLadderCyclicFrom = (map, start) => {
7283
7547
  const seen = /* @__PURE__ */ new Set([start]);
7284
7548
  let cur = map[start];
@@ -7300,19 +7564,21 @@ var EFFORT_CAPABLE_PROVIDERS = /* @__PURE__ */ new Set([
7300
7564
  "xai-grok"
7301
7565
  ]);
7302
7566
  var CLAUDE_EFFORT_LADDER = ["low", "medium", "high", "xhigh", "max"];
7303
- var CLAUDE_XHIGH_INDEX = CLAUDE_EFFORT_LADDER.indexOf("xhigh");
7304
- var CLAUDE_MAX_INDEX = CLAUDE_EFFORT_LADDER.indexOf("max");
7305
7567
  var CLAUDE_EFFORTLESS_MODELS = /* @__PURE__ */ new Set(["claude-haiku-4-5", "claude-haiku-4.5"]);
7306
- var CLAUDE_HIGH_DEFAULT_MODELS = /* @__PURE__ */ new Set(["claude-sonnet-4-6", "claude-sonnet-4.6"]);
7568
+ var CLAUDE_NO_XHIGH_MODELS = /* @__PURE__ */ new Set(["claude-sonnet-4-6", "claude-sonnet-4.6"]);
7569
+ var CLAUDE_CLI_DEFAULT_EFFORT = {
7570
+ "claude-opus-5-5": "medium",
7571
+ "claude-opus-5.5": "medium"
7572
+ };
7573
+ var CLAUDE_FALLBACK_CLI_DEFAULT_EFFORT = "high";
7574
+ var isClaudeEffort = (s) => CLAUDE_EFFORT_LADDER.includes(s);
7307
7575
  var claudeEffortRung = (model, currentEffort) => {
7308
7576
  if (CLAUDE_EFFORTLESS_MODELS.has(model)) return void 0;
7309
- const xhighCapable = !CLAUDE_HIGH_DEFAULT_MODELS.has(model);
7310
- const effective = currentEffort ?? (xhighCapable ? "xhigh" : "high");
7577
+ const effective = currentEffort ?? CLAUDE_CLI_DEFAULT_EFFORT[model] ?? CLAUDE_FALLBACK_CLI_DEFAULT_EFFORT;
7578
+ if (!isClaudeEffort(effective)) return void 0;
7311
7579
  const effectiveIndex = CLAUDE_EFFORT_LADDER.indexOf(effective);
7312
- if (effectiveIndex < 0) return void 0;
7313
- if (effectiveIndex >= CLAUDE_MAX_INDEX) return void 0;
7314
- if (xhighCapable && effectiveIndex < CLAUDE_XHIGH_INDEX) return "xhigh";
7315
- return CLAUDE_EFFORT_LADDER[CLAUDE_MAX_INDEX];
7580
+ const modelLadder = CLAUDE_NO_XHIGH_MODELS.has(model) ? CLAUDE_EFFORT_LADDER.filter((level) => level !== "xhigh") : CLAUDE_EFFORT_LADDER;
7581
+ return modelLadder.find((level) => CLAUDE_EFFORT_LADDER.indexOf(level) > effectiveIndex);
7316
7582
  };
7317
7583
  var nextEffortRung = (provider, model, currentEffort) => {
7318
7584
  if (provider === void 0 || !EFFORT_CAPABLE_PROVIDERS.has(provider)) return void 0;
@@ -8990,6 +9256,7 @@ var wire = (opts) => {
8990
9256
  const eventBus = createInMemoryEventBus();
8991
9257
  const logger = createEventBusLogger({ eventBus, clock: IsoTimestamp.now });
8992
9258
  warnEscalationMapSelfLoops(opts.settings.harness.escalationMap, logger);
9259
+ warnEscalationMapRetiredValues(opts.settings.harness.escalationMap, logger);
8993
9260
  const notificationDispatcher = opts.notificationDispatcher ?? noopNotificationDispatcher;
8994
9261
  const fileLocker = createFileLocker({
8995
9262
  // Surface stale `.lock` files via the application logger. The locker is intentionally
@@ -9012,6 +9279,7 @@ var wire = (opts) => {
9012
9279
  return pending;
9013
9280
  };
9014
9281
  const atomicWriteFile = createAtomicWriteFile();
9282
+ const gitRunner = createGitRunner();
9015
9283
  return {
9016
9284
  storage: opts.storage,
9017
9285
  projectRepo: createFsProjectRepository({ root: opts.storage.dataRoot }),
@@ -9022,7 +9290,7 @@ var wire = (opts) => {
9022
9290
  settingsRepo: createJsonSettingsRepository({ configRoot: opts.storage.configRoot }),
9023
9291
  provider: buildWireProvider(opts, eventBus, providerSpawn),
9024
9292
  ...providerSpawn !== void 0 ? { providerSpawn } : {},
9025
- gitRunner: createGitRunner(),
9293
+ gitRunner,
9026
9294
  shellScriptRunner: createShellScriptRunner(),
9027
9295
  fileLocker,
9028
9296
  writeFile: atomicWriteFile,
@@ -9037,7 +9305,7 @@ var wire = (opts) => {
9037
9305
  logger,
9038
9306
  pullRequestCreator: createPullRequestCreator({ gitRunner: createGitRunner(), spawn: spawn2 }),
9039
9307
  issueFetcher: createIssueFetcher({ spawn: spawn2, logger }),
9040
- issuePusher: createIssuePusher({ spawn: spawn2 }),
9308
+ issuePusher: createIssuePusher({ spawn: spawn2, gitRunner }),
9041
9309
  versionChecker: createNpmVersionChecker({
9042
9310
  stateRoot: opts.storage.stateRoot,
9043
9311
  currentVersion: CLI_METADATA.currentVersion,
@@ -15748,25 +16016,24 @@ var warnIfContractViolated = (logger, skillName, content) => {
15748
16016
 
15749
16017
  // src/business/settings/resolve-effort.ts
15750
16018
  var FLOW_DEFAULT_EFFORT = {
16019
+ refine: "medium",
15751
16020
  plan: "high",
15752
- ideate: "high"
15753
- };
15754
- var resolveEffort = (flow, settings) => {
15755
- const row = primaryFlowRow(settings.ai, flow);
15756
- return resolveEffortForRow(row, settings.ai.effort, flow);
16021
+ implement: "high",
16022
+ readiness: "medium",
16023
+ ideate: "high",
16024
+ createPr: "low"
15757
16025
  };
16026
+ var resolveEffort = (flow, settings) => resolveEffortForRow(primaryFlowRow(settings.ai, flow), settings.ai.effort, flow);
15758
16027
  var resolveEffortForRow = (row, globalEffort, flow) => {
15759
16028
  if (row.effort !== void 0) return row.effort;
15760
- if (globalEffort !== void 0) return _floorForProvider(globalEffort, row.provider);
15761
- const flowDefault = flow !== void 0 && row.provider !== "opencode" ? FLOW_DEFAULT_EFFORT[flow] : void 0;
15762
- if (flowDefault !== void 0) return clampEffortToProvider(flowDefault, row.provider);
15763
- return void 0;
16029
+ if (globalEffort !== void 0) return clampEffortToProvider(globalEffort, row.provider);
16030
+ if (row.provider === "opencode") return void 0;
16031
+ return clampEffortToProvider(FLOW_DEFAULT_EFFORT[flow], row.provider);
15764
16032
  };
15765
16033
  var clampEffortToProvider = (effort, provider) => {
15766
16034
  if (provider === "openai-codex" && effort === "max") return "xhigh";
15767
16035
  return effort;
15768
16036
  };
15769
- var _floorForProvider = (effort, provider) => clampEffortToProvider(effort, provider);
15770
16037
 
15771
16038
  // src/application/session/session.ts
15772
16039
  import { AsyncLocalStorage } from "async_hooks";
@@ -16091,6 +16358,142 @@ var setExecutionBaselineBrokenPolicy = (execution, policy) => {
16091
16358
  return { ...execution, baselineBrokenPolicy: policy };
16092
16359
  };
16093
16360
 
16361
+ // src/domain/value/external-ref.ts
16362
+ var toIssueRef = (segment) => {
16363
+ const num2 = Number(segment);
16364
+ return Number.isInteger(num2) && num2 > 0 ? `#${String(num2)}` : void 0;
16365
+ };
16366
+ var parseGithubIssueRef = (segments) => {
16367
+ if (segments.length < 4 || segments[2] !== "issues") return void 0;
16368
+ return toIssueRef(segments[3]);
16369
+ };
16370
+ var parseGitlabIssueRef = (segments) => {
16371
+ const dashIdx = segments.indexOf("-");
16372
+ if (dashIdx < 2 || segments[dashIdx + 1] !== "issues") return void 0;
16373
+ return toIssueRef(segments[dashIdx + 2]);
16374
+ };
16375
+ var parseExternalRefFromUrl = (url) => {
16376
+ let parsed;
16377
+ try {
16378
+ parsed = new URL(url);
16379
+ } catch {
16380
+ return void 0;
16381
+ }
16382
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return void 0;
16383
+ const segments = parsed.pathname.split("/").filter((s) => s.length > 0);
16384
+ return parsed.hostname.includes("github") ? parseGithubIssueRef(segments) : parseGitlabIssueRef(segments);
16385
+ };
16386
+ var normalizeRefs = (refs) => {
16387
+ if (refs === void 0 || refs.length === 0) return [];
16388
+ const seen = /* @__PURE__ */ new Set();
16389
+ const ordered = [];
16390
+ for (const r of refs) {
16391
+ const trimmed = r.trim();
16392
+ if (trimmed.length === 0) continue;
16393
+ if (seen.has(trimmed)) continue;
16394
+ seen.add(trimmed);
16395
+ ordered.push(trimmed);
16396
+ }
16397
+ return ordered;
16398
+ };
16399
+
16400
+ // src/domain/value/parsers/parse-optional-string.ts
16401
+ var parseOptionalString = (field, value) => {
16402
+ if (value === void 0) return Result.ok(void 0);
16403
+ const trimmed = value.trim();
16404
+ if (trimmed.length === 0) {
16405
+ return Result.error(
16406
+ new ValidationError({
16407
+ field,
16408
+ value,
16409
+ message: `${field} must be non-empty when provided`,
16410
+ hint: "omit the field instead of passing an empty string"
16411
+ })
16412
+ );
16413
+ }
16414
+ return Result.ok(trimmed);
16415
+ };
16416
+
16417
+ // src/domain/entity/ticket.ts
16418
+ var createTicket = (input) => {
16419
+ const title = parseRequiredString("ticket.title", input.title);
16420
+ if (!title.ok) return Result.error(title.error);
16421
+ const description = parseOptionalString("ticket.description", input.description);
16422
+ if (!description.ok) return Result.error(description.error);
16423
+ let link;
16424
+ if (input.link !== void 0) {
16425
+ const parsed = parseHttpUrl("ticket.link", input.link);
16426
+ if (!parsed.ok) return Result.error(parsed.error);
16427
+ link = parsed.value;
16428
+ }
16429
+ const trimmedRef = input.externalRef?.trim();
16430
+ const suppliedRef = trimmedRef !== void 0 && trimmedRef.length > 0 ? trimmedRef : void 0;
16431
+ const externalRef = suppliedRef ?? (link !== void 0 ? parseExternalRefFromUrl(link) : void 0);
16432
+ return Result.ok({
16433
+ id: input.id ?? TicketId.generate(),
16434
+ title: title.value,
16435
+ ...description.value !== void 0 ? { description: description.value } : {},
16436
+ ...link !== void 0 ? { link } : {},
16437
+ ...externalRef !== void 0 ? { externalRef } : {},
16438
+ status: "pending",
16439
+ requirements: void 0
16440
+ });
16441
+ };
16442
+ function setTicketLink(ticket, url) {
16443
+ if (url === void 0) {
16444
+ const { link: _drop, ...rest } = ticket;
16445
+ void _drop;
16446
+ return Result.ok(rest);
16447
+ }
16448
+ const parsed = parseHttpUrl("ticket.link", url);
16449
+ if (!parsed.ok) return Result.error(parsed.error);
16450
+ const derivedRef = ticket.externalRef === void 0 ? parseExternalRefFromUrl(parsed.value) : void 0;
16451
+ return Result.ok({
16452
+ ...ticket,
16453
+ link: parsed.value,
16454
+ ...derivedRef !== void 0 ? { externalRef: derivedRef } : {}
16455
+ });
16456
+ }
16457
+ function setTicketTitle(ticket, title) {
16458
+ const parsed = parseRequiredString("ticket.title", title);
16459
+ if (!parsed.ok) return Result.error(parsed.error);
16460
+ return Result.ok({ ...ticket, title: parsed.value });
16461
+ }
16462
+ function setTicketDescription(ticket, description) {
16463
+ const parsed = parseOptionalString("ticket.description", description);
16464
+ if (!parsed.ok) return Result.error(parsed.error);
16465
+ if (parsed.value === void 0) {
16466
+ const { description: _drop, ...rest } = ticket;
16467
+ void _drop;
16468
+ return Result.ok(rest);
16469
+ }
16470
+ return Result.ok({ ...ticket, description: parsed.value });
16471
+ }
16472
+ var setTicketRequirements = (ticket, requirements) => {
16473
+ const parsed = parseRequiredString("ticket.requirements", requirements);
16474
+ if (!parsed.ok) return Result.error(parsed.error);
16475
+ return Result.ok({ ...ticket, requirements: parsed.value });
16476
+ };
16477
+ var approveTicketRequirements = (ticket, text) => {
16478
+ const guard2 = requireStatus(
16479
+ "ticket",
16480
+ ticket,
16481
+ ["pending"],
16482
+ "approve-requirements",
16483
+ "Requirements have already been approved for this ticket."
16484
+ );
16485
+ if (!guard2.ok) return Result.error(guard2.error);
16486
+ return Result.ok({
16487
+ id: ticket.id,
16488
+ title: ticket.title,
16489
+ ...ticket.description !== void 0 ? { description: ticket.description } : {},
16490
+ ...ticket.link !== void 0 ? { link: ticket.link } : {},
16491
+ ...ticket.externalRef !== void 0 ? { externalRef: ticket.externalRef } : {},
16492
+ status: "approved",
16493
+ requirements: text
16494
+ });
16495
+ };
16496
+
16094
16497
  // src/domain/entity/sprint.ts
16095
16498
  var sprintBaseFrom = (sprint) => ({
16096
16499
  id: sprint.id,
@@ -16270,13 +16673,14 @@ var transitionSprintToDone = (sprint, now) => {
16270
16673
  doneAt: now
16271
16674
  });
16272
16675
  };
16676
+ var DONE_IMMUTABLE_HINT = "Done sprints are immutable.";
16273
16677
  var renameSprint = (sprint, newName) => {
16274
16678
  const guard2 = requireStatus(
16275
16679
  "sprint",
16276
16680
  sprint,
16277
16681
  ["draft", "planned", "active", "review"],
16278
16682
  "rename",
16279
- "Done sprints are immutable."
16683
+ DONE_IMMUTABLE_HINT
16280
16684
  );
16281
16685
  if (!guard2.ok) return Result.error(guard2.error);
16282
16686
  const name = parseRequiredString("sprint.name", newName);
@@ -16329,6 +16733,24 @@ var replaceTicket = (sprint, id, updated) => {
16329
16733
  const draft = guard2.value;
16330
16734
  return Result.ok({ ...draft, tickets: draft.tickets.map((t) => t.id === id ? updated : t) });
16331
16735
  };
16736
+ var attachTicketLink = (sprint, ticketId, url) => {
16737
+ const guard2 = requireStatus(
16738
+ "sprint",
16739
+ sprint,
16740
+ ["draft", "planned", "active", "review"],
16741
+ "attach-ticket-link",
16742
+ DONE_IMMUTABLE_HINT
16743
+ );
16744
+ if (!guard2.ok) return Result.error(guard2.error);
16745
+ const open = guard2.value;
16746
+ const ticket = open.tickets.find((t) => t.id === ticketId);
16747
+ if (ticket === void 0) {
16748
+ return Result.error(new NotFoundError({ entity: "ticket", id: String(ticketId) }));
16749
+ }
16750
+ const updated = ticket.status === "approved" ? setTicketLink(ticket, url) : setTicketLink(ticket, url);
16751
+ if (!updated.ok) return Result.error(updated.error);
16752
+ return Result.ok({ ...open, tickets: open.tickets.map((t) => t.id === ticketId ? updated.value : t) });
16753
+ };
16332
16754
  var assertSprintStatus = (sprint, allowed, attemptedAction) => {
16333
16755
  if (allowed.includes(sprint.status)) return Result.ok(sprint);
16334
16756
  return Result.error(
@@ -16713,127 +17135,6 @@ var launchCreateSprint = (ctx) => {
16713
17135
  // src/application/ui/shared/launch/refine.ts
16714
17136
  import { join as join49 } from "path";
16715
17137
 
16716
- // src/domain/value/external-ref.ts
16717
- var toIssueRef = (segment) => {
16718
- const num2 = Number(segment);
16719
- return Number.isInteger(num2) && num2 > 0 ? `#${String(num2)}` : void 0;
16720
- };
16721
- var parseGithubIssueRef = (segments) => {
16722
- if (segments.length < 4 || segments[2] !== "issues") return void 0;
16723
- return toIssueRef(segments[3]);
16724
- };
16725
- var parseGitlabIssueRef = (segments) => {
16726
- const dashIdx = segments.indexOf("-");
16727
- if (dashIdx < 2 || segments[dashIdx + 1] !== "issues") return void 0;
16728
- return toIssueRef(segments[dashIdx + 2]);
16729
- };
16730
- var parseExternalRefFromUrl = (url) => {
16731
- let parsed;
16732
- try {
16733
- parsed = new URL(url);
16734
- } catch {
16735
- return void 0;
16736
- }
16737
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return void 0;
16738
- const segments = parsed.pathname.split("/").filter((s) => s.length > 0);
16739
- return parsed.hostname.includes("github") ? parseGithubIssueRef(segments) : parseGitlabIssueRef(segments);
16740
- };
16741
- var normalizeRefs = (refs) => {
16742
- if (refs === void 0 || refs.length === 0) return [];
16743
- const seen = /* @__PURE__ */ new Set();
16744
- const ordered = [];
16745
- for (const r of refs) {
16746
- const trimmed = r.trim();
16747
- if (trimmed.length === 0) continue;
16748
- if (seen.has(trimmed)) continue;
16749
- seen.add(trimmed);
16750
- ordered.push(trimmed);
16751
- }
16752
- return ordered;
16753
- };
16754
-
16755
- // src/domain/value/parsers/parse-optional-string.ts
16756
- var parseOptionalString = (field, value) => {
16757
- if (value === void 0) return Result.ok(void 0);
16758
- const trimmed = value.trim();
16759
- if (trimmed.length === 0) {
16760
- return Result.error(
16761
- new ValidationError({
16762
- field,
16763
- value,
16764
- message: `${field} must be non-empty when provided`,
16765
- hint: "omit the field instead of passing an empty string"
16766
- })
16767
- );
16768
- }
16769
- return Result.ok(trimmed);
16770
- };
16771
-
16772
- // src/domain/entity/ticket.ts
16773
- var createTicket = (input) => {
16774
- const title = parseRequiredString("ticket.title", input.title);
16775
- if (!title.ok) return Result.error(title.error);
16776
- const description = parseOptionalString("ticket.description", input.description);
16777
- if (!description.ok) return Result.error(description.error);
16778
- let link;
16779
- if (input.link !== void 0) {
16780
- const parsed = parseHttpUrl("ticket.link", input.link);
16781
- if (!parsed.ok) return Result.error(parsed.error);
16782
- link = parsed.value;
16783
- }
16784
- const trimmedRef = input.externalRef?.trim();
16785
- const suppliedRef = trimmedRef !== void 0 && trimmedRef.length > 0 ? trimmedRef : void 0;
16786
- const externalRef = suppliedRef ?? (link !== void 0 ? parseExternalRefFromUrl(link) : void 0);
16787
- return Result.ok({
16788
- id: input.id ?? TicketId.generate(),
16789
- title: title.value,
16790
- ...description.value !== void 0 ? { description: description.value } : {},
16791
- ...link !== void 0 ? { link } : {},
16792
- ...externalRef !== void 0 ? { externalRef } : {},
16793
- status: "pending",
16794
- requirements: void 0
16795
- });
16796
- };
16797
- function setTicketTitle(ticket, title) {
16798
- const parsed = parseRequiredString("ticket.title", title);
16799
- if (!parsed.ok) return Result.error(parsed.error);
16800
- return Result.ok({ ...ticket, title: parsed.value });
16801
- }
16802
- function setTicketDescription(ticket, description) {
16803
- const parsed = parseOptionalString("ticket.description", description);
16804
- if (!parsed.ok) return Result.error(parsed.error);
16805
- if (parsed.value === void 0) {
16806
- const { description: _drop, ...rest } = ticket;
16807
- void _drop;
16808
- return Result.ok(rest);
16809
- }
16810
- return Result.ok({ ...ticket, description: parsed.value });
16811
- }
16812
- var setTicketRequirements = (ticket, requirements) => {
16813
- const parsed = parseRequiredString("ticket.requirements", requirements);
16814
- if (!parsed.ok) return Result.error(parsed.error);
16815
- return Result.ok({ ...ticket, requirements: parsed.value });
16816
- };
16817
- var approveTicketRequirements = (ticket, text) => {
16818
- const guard2 = requireStatus(
16819
- "ticket",
16820
- ticket,
16821
- ["pending"],
16822
- "approve-requirements",
16823
- "Requirements have already been approved for this ticket."
16824
- );
16825
- if (!guard2.ok) return Result.error(guard2.error);
16826
- return Result.ok({
16827
- id: ticket.id,
16828
- title: ticket.title,
16829
- ...ticket.description !== void 0 ? { description: ticket.description } : {},
16830
- ...ticket.link !== void 0 ? { link: ticket.link } : {},
16831
- ...ticket.externalRef !== void 0 ? { externalRef: ticket.externalRef } : {},
16832
- status: "approved",
16833
- requirements: text
16834
- });
16835
- };
16836
-
16837
17138
  // src/application/flows/_shared/sprint/load.ts
16838
17139
  var loadSprintLeaf = (deps, name = "load-sprint") => leaf(name, {
16839
17140
  useCase: {
@@ -17297,11 +17598,13 @@ var refineOutputContract = {
17297
17598
  exampleSignals: refineExampleSignals
17298
17599
  };
17299
17600
 
17300
- // src/application/flows/refine/leaves/refine-ticket-interactive.ts
17301
- var REFINEMENT_COMMENT_SIGNATURE = (sprintId, timestamp) => `
17601
+ // src/business/scm/refinement-comment.ts
17602
+ var REFINEMENT_MARKER = "<!-- ralphctl:refined-requirements -->";
17603
+ var refinementCommentBody = (requirements) => `${requirements}
17302
17604
 
17303
- ---
17304
- _\u{1F916} Posted by [ralphctl](https://github.com/lukas-grigis/ralphctl) \xB7 Sprint ${sprintId} \xB7 ${timestamp}_`;
17605
+ ${REFINEMENT_MARKER}`;
17606
+
17607
+ // src/application/flows/refine/leaves/refine-ticket-interactive.ts
17305
17608
  var remapRefineSignalsError = (error) => {
17306
17609
  if (error instanceof InvalidStateError && error.message.includes("signals-missing")) {
17307
17610
  return new InvalidStateError({
@@ -17335,8 +17638,20 @@ var warnDroppedSignals = async (deps, outputDir) => {
17335
17638
  } catch {
17336
17639
  }
17337
17640
  };
17338
- var maybeCommentOnOrigin = async (deps, ticket, body) => {
17641
+ var commentBannerId = (ticketId) => `refine-comment-${String(ticketId)}`;
17642
+ var publishCommentBanner = (deps, id, tier, message, cause) => {
17643
+ deps.eventBus.publish({
17644
+ type: "banner-show",
17645
+ id,
17646
+ tier,
17647
+ message,
17648
+ ...cause !== void 0 ? { cause } : {},
17649
+ at: IsoTimestamp.now()
17650
+ });
17651
+ };
17652
+ var maybeCommentOnOrigin = async (deps, ticket) => {
17339
17653
  const PUSH_LOGGER = "refine.push";
17654
+ const bannerId = commentBannerId(ticket.id);
17340
17655
  if (deps.issuePusher === void 0) {
17341
17656
  deps.logger.named(PUSH_LOGGER).warn("no IssuePusher wired \u2014 skipping issue comment");
17342
17657
  return;
@@ -17345,15 +17660,26 @@ var maybeCommentOnOrigin = async (deps, ticket, body) => {
17345
17660
  deps.logger.named(PUSH_LOGGER).warn("comment requested but ticket has no link \u2014 skipping");
17346
17661
  return;
17347
17662
  }
17348
- const now = (/* @__PURE__ */ new Date()).toISOString();
17349
- const fullBody = `${body}${REFINEMENT_COMMENT_SIGNATURE(deps.sprintId, now)}`;
17663
+ const body = refinementCommentBody(ticket.requirements);
17350
17664
  const url = String(ticket.link);
17351
- const result = await deps.issuePusher.comment(url, { body: fullBody });
17665
+ const listed = await deps.issuePusher.listComments(url);
17666
+ if (!listed.ok) {
17667
+ deps.logger.named(PUSH_LOGGER).warn(`list comments failed (${url}): ${listed.error.message}`);
17668
+ publishCommentBanner(deps, bannerId, "error", "Could not post the refinement comment", listed.error.message);
17669
+ return;
17670
+ }
17671
+ if (listed.value.includes(body)) {
17672
+ deps.logger.named(PUSH_LOGGER).info(`tracker already up to date for ${url}`);
17673
+ publishCommentBanner(deps, bannerId, "info", "Tracker is already up to date");
17674
+ return;
17675
+ }
17676
+ const result = await deps.issuePusher.comment(url, { body });
17352
17677
  if (!result.ok) {
17353
17678
  deps.logger.named(PUSH_LOGGER).warn(`issue comment failed (${url}): ${result.error.message}`);
17354
- } else {
17355
- deps.logger.named(PUSH_LOGGER).info(`posted comment on issue ${url}`);
17679
+ publishCommentBanner(deps, bannerId, "error", "Could not post the refinement comment", result.error.message);
17680
+ return;
17356
17681
  }
17682
+ deps.logger.named(PUSH_LOGGER).info(`posted comment on issue ${url}`);
17357
17683
  };
17358
17684
  var runInteractiveSession = async (deps, input, signal) => {
17359
17685
  const session = await deps.runInTerminal(
@@ -17406,10 +17732,9 @@ var applyResult = async (deps, input, refinedSignal) => {
17406
17732
  if (!useCaseResult.ok) return Result.error(useCaseResult.error);
17407
17733
  const out = useCaseResult.value;
17408
17734
  if (!out.accepted) return Result.ok(out);
17409
- const shouldComment = deps.reviewBeforeApprove !== void 0 ? out.alsoUpdateOrigin : deps.postRefinementComment === true;
17410
- if (!shouldComment) return Result.ok(out);
17735
+ if (out.alsoUpdateOrigin !== true) return Result.ok(out);
17411
17736
  const approved = out.ticket;
17412
- await maybeCommentOnOrigin(deps, approved, approved.requirements);
17737
+ await maybeCommentOnOrigin(deps, approved);
17413
17738
  return Result.ok(out);
17414
17739
  };
17415
17740
  var refineTicketInteractiveLeaf = (deps, ticket) => leaf(`refine-ticket-${String(ticket.id)}`, {
@@ -18015,9 +18340,7 @@ var createRefineFlow = (deps, opts) => {
18015
18340
  writeFile: deps.writeFile,
18016
18341
  eventBus: deps.eventBus,
18017
18342
  model: opts.model,
18018
- sprintId: String(opts.sprintId),
18019
18343
  ...opts.effort !== void 0 ? { effort: opts.effort } : {},
18020
- ...deps.postRefinementComment !== void 0 ? { postRefinementComment: deps.postRefinementComment } : {},
18021
18344
  ...deps.reviewBeforeApprove !== void 0 ? { reviewBeforeApprove: deps.reviewBeforeApprove } : {},
18022
18345
  ...deps.issuePusher !== void 0 ? { issuePusher: deps.issuePusher } : {}
18023
18346
  },
@@ -18102,7 +18425,7 @@ var checkCli = async (flowId, settings, options = {}) => {
18102
18425
  };
18103
18426
 
18104
18427
  // src/application/ui/shared/launch/refine.ts
18105
- var buildRefineOptions = (hasLink, postRefinementComment) => {
18428
+ var buildRefineOptions = (hasLink) => {
18106
18429
  const approveOption = {
18107
18430
  label: "Approve",
18108
18431
  value: "approve",
@@ -18113,11 +18436,10 @@ var buildRefineOptions = (hasLink, postRefinementComment) => {
18113
18436
  value: "post_comment",
18114
18437
  description: "Save locally AND post the body as a comment on the linked issue."
18115
18438
  };
18116
- const leadOptions = hasLink && postRefinementComment ? [commentOption, approveOption] : [approveOption];
18117
18439
  return [
18118
- ...leadOptions,
18440
+ approveOption,
18119
18441
  { label: "Edit", value: "edit", description: "Open the body in an editor; come back here to decide." },
18120
- ...hasLink && !postRefinementComment ? [commentOption] : [],
18442
+ ...hasLink ? [commentOption] : [],
18121
18443
  { label: "Reject", value: "reject", description: "Leave the ticket pending \u2014 no changes." }
18122
18444
  ];
18123
18445
  };
@@ -18137,8 +18459,8 @@ var applyRefineDecision = async (interactive, ticket, decision, body) => {
18137
18459
  }
18138
18460
  }
18139
18461
  };
18140
- var buildReviewBeforeApprove = (interactive, postRefinementComment) => async (proposed, ticket) => {
18141
- const options = buildRefineOptions(ticket.link !== void 0, postRefinementComment);
18462
+ var buildReviewBeforeApprove = (interactive) => async (proposed, ticket) => {
18463
+ const options = buildRefineOptions(ticket.link !== void 0);
18142
18464
  let body = proposed;
18143
18465
  while (true) {
18144
18466
  const message = `Approve refined requirements for "${ticket.title}"?
@@ -18161,8 +18483,7 @@ var launchRefine = async (ctx) => {
18161
18483
  join49(sprintDir(deps.storage.dataRoot, snapshot.sprint.id, snapshot.sprint.slug), "refinement")
18162
18484
  );
18163
18485
  if (!refinementRoot.ok) return { ok: false, reason: refinementRoot.error.message };
18164
- const postRefinementComment = settings.scm.postRefinementComment;
18165
- const reviewBeforeApprove = buildReviewBeforeApprove(deps.interactive, postRefinementComment);
18486
+ const reviewBeforeApprove = buildReviewBeforeApprove(deps.interactive);
18166
18487
  const element = createRefineFlow(
18167
18488
  {
18168
18489
  sprintRepo: deps.app.sprintRepo,
@@ -18176,7 +18497,6 @@ var launchRefine = async (ctx) => {
18176
18497
  skillsAdapter,
18177
18498
  skillSource,
18178
18499
  reviewBeforeApprove,
18179
- postRefinementComment,
18180
18500
  ...deps.app.issueFetcher !== void 0 ? { issueFetcher: deps.app.issueFetcher } : {},
18181
18501
  ...deps.app.issuePusher !== void 0 ? { issuePusher: deps.app.issuePusher } : {}
18182
18502
  },
@@ -23651,7 +23971,7 @@ var decideEscalation = (props) => {
23651
23971
  };
23652
23972
  if (!props.flagOn) return { kind: "flag-off" };
23653
23973
  if (budgetExhausted) return budgetExhaustedDecision;
23654
- const merged = mergeEscalationMap(props.userMap);
23974
+ const merged = mergeEscalationMap(props.userMap, props.generatorProvider);
23655
23975
  const next = merged[props.generatorModel];
23656
23976
  if (hasStrongerModelRung(merged, props.generatorModel, next)) {
23657
23977
  return { kind: "escalate", from: props.generatorModel, to: next, ...computeEvaluatorEffortCarry(props) };
@@ -23671,6 +23991,11 @@ var decideEscalation = (props) => {
23671
23991
  }
23672
23992
  return { kind: "nudge", currentModel: props.generatorModel };
23673
23993
  };
23994
+ var effectiveGeneratorModel = (task, configuredModel, provider) => {
23995
+ const override = task.escalatedToModel;
23996
+ if (override === void 0) return configuredModel;
23997
+ return provider === void 0 ? override : remapRetiredModel(provider, override);
23998
+ };
23674
23999
  var escalationBannerId = (taskId) => `model-escalation-${taskId}`;
23675
24000
  var computeActionEntropy = (actionCounts) => {
23676
24001
  const K = actionCounts.size;
@@ -24162,8 +24487,9 @@ var makeGeneratorCallImplement = (deps, taskId, args) => async (task) => {
24162
24487
  // Per-task generator-model escalation: when the task carries an `escalatedToModel`
24163
24488
  // (stamped by the prior plateau's escalation policy), spawn the generator on that
24164
24489
  // upgraded model instead of the configured row. Evaluator model is intentionally
24165
- // unaffected — escalation only touches the generator role.
24166
- model: task.escalatedToModel ?? deps.model,
24490
+ // unaffected — escalation only touches the generator role. A persisted override naming a
24491
+ // since-retired slug is remapped to its live successor rather than spawned as-is.
24492
+ model: effectiveGeneratorModel(task, deps.model, deps.providerId),
24167
24493
  // Per-task generator-EFFORT escalation: the same-model effort rung stamps `escalatedToEffort`
24168
24494
  // (default → high) when the generator topped out on the model ladder but still had effort
24169
24495
  // headroom. Prefer it over the configured `deps.effort` at spawn — mirrors the model override
@@ -24508,7 +24834,7 @@ var resolveImplementMetaInput = (ctx, taskId, role, providerId, model, effort) =
24508
24834
  const outputDir = AbsolutePath.parse(dirname20(signalsPath));
24509
24835
  if (!outputDir.ok) throw outputDir.error;
24510
24836
  const task = ctx.currentTask;
24511
- const effectiveModel = role === "generator" && task.escalatedToModel !== void 0 ? task.escalatedToModel : model;
24837
+ const effectiveModel = role === "generator" ? effectiveGeneratorModel(task, model, providerId) : model;
24512
24838
  return {
24513
24839
  outputDir: outputDir.value,
24514
24840
  flow: `implement-${role}`,
@@ -24540,6 +24866,7 @@ var stampImplementEvaluatorSessionMetaLeaf = (deps, opts, taskId) => stampSessio
24540
24866
  var withRoleSpawn = (shared, provider, role) => ({
24541
24867
  ...shared,
24542
24868
  provider,
24869
+ providerId: role.providerId,
24543
24870
  model: role.model,
24544
24871
  ...role.effort !== void 0 ? { effort: role.effort } : {},
24545
24872
  ...role.agentDefinitionSection !== void 0 ? { agentDefinition: role.agentDefinitionSection } : {},
@@ -26145,7 +26472,11 @@ var finalizeGenEvalLeaf = (deps, taskId) => leaf(`finalize-gen-eval-${String(tas
26145
26472
  message: `finalize-gen-eval-${String(taskId)}: expected in_progress task`
26146
26473
  });
26147
26474
  }
26148
- const generatorModel = ctx.currentTask.escalatedToModel ?? deps.configuredGeneratorModel;
26475
+ const generatorModel = effectiveGeneratorModel(
26476
+ ctx.currentTask,
26477
+ deps.configuredGeneratorModel,
26478
+ deps.configuredGeneratorProvider
26479
+ );
26149
26480
  const generatorEffort = ctx.currentTask.escalatedToEffort ?? deps.configuredGeneratorEffort;
26150
26481
  const evaluatorEffort = ctx.currentTask.escalatedToEvaluatorEffort ?? deps.configuredEvaluatorEffort;
26151
26482
  return {
@@ -30109,9 +30440,9 @@ var resolveImplementAgentBindings = async (deps, implementPair) => {
30109
30440
  };
30110
30441
 
30111
30442
  // src/business/settings/resolve-agent-override.ts
30112
- var resolveAgentOverride = (row, globalEffort, binding) => ({
30443
+ var resolveAgentOverride = (row, globalEffort, binding, flow) => ({
30113
30444
  model: binding?.model ?? row.model,
30114
- effort: binding?.effort !== void 0 ? clampEffortToProvider(binding.effort, row.provider) : resolveEffortForRow(row, globalEffort)
30445
+ effort: binding?.effort !== void 0 ? clampEffortToProvider(binding.effort, row.provider) : resolveEffortForRow(row, globalEffort, flow)
30115
30446
  });
30116
30447
 
30117
30448
  // src/application/ui/shared/launch/implement-bags.ts
@@ -30143,12 +30474,14 @@ var buildImplementProviders = (implementPair, effectiveSettings, deps, agentDefi
30143
30474
  const generatorResolved = resolveAgentOverride(
30144
30475
  implementPair.generator,
30145
30476
  effectiveSettings.ai.effort,
30146
- agentDefinitions.generator
30477
+ agentDefinitions.generator,
30478
+ "implement"
30147
30479
  );
30148
30480
  const evaluatorResolved = resolveAgentOverride(
30149
30481
  implementPair.evaluator,
30150
30482
  effectiveSettings.ai.effort,
30151
- agentDefinitions.evaluator
30483
+ agentDefinitions.evaluator,
30484
+ "implement"
30152
30485
  );
30153
30486
  return {
30154
30487
  generatorProvider,
@@ -30909,6 +31242,7 @@ var callApplyFeedbackPhase = (deps, opts, paths, promptRef, signal) => async ()
30909
31242
  cwd: paths.outputDir,
30910
31243
  additionalRoots: opts.additionalRoots,
30911
31244
  model: deps.model,
31245
+ ...deps.effort !== void 0 ? { effort: deps.effort } : {},
30912
31246
  permissions: FULL_AUTO,
30913
31247
  signalsFile: paths.signalsFile,
30914
31248
  outputDir: paths.outputDir,
@@ -31500,17 +31834,17 @@ var distillWriteLeaf = (deps, tool) => leaf(`distill-write-${tool}`, {
31500
31834
  // src/application/flows/_shared/memory/distill-learnings.ts
31501
31835
  var pickModelEffortForProvider = (ai, provider) => {
31502
31836
  const rows = [
31503
- ai.refine,
31504
- ai.plan,
31505
- ai.implement.generator,
31506
- ai.implement.evaluator,
31507
- ai.readiness,
31508
- ai.ideate,
31509
- ai.createPr
31837
+ ["refine", ai.refine],
31838
+ ["plan", ai.plan],
31839
+ ["implement", ai.implement.generator],
31840
+ ["implement", ai.implement.evaluator],
31841
+ ["readiness", ai.readiness],
31842
+ ["ideate", ai.ideate],
31843
+ ["createPr", ai.createPr]
31510
31844
  ];
31511
- for (const row of rows) {
31845
+ for (const [flow, row] of rows) {
31512
31846
  if (row.provider === provider) {
31513
- const effort = row.effort ?? ai.effort;
31847
+ const effort = resolveEffortForRow(row, ai.effort, flow);
31514
31848
  return effort !== void 0 ? { model: row.model, effort } : { model: row.model };
31515
31849
  }
31516
31850
  }
@@ -31656,7 +31990,8 @@ var createReviewFlow = (deps, opts) => {
31656
31990
  gitRunner: deps.gitRunner,
31657
31991
  shellScriptRunner: deps.shellScriptRunner,
31658
31992
  appendFile: deps.appendFile,
31659
- model: deps.model
31993
+ model: deps.model,
31994
+ ...deps.effort !== void 0 ? { effort: deps.effort } : {}
31660
31995
  },
31661
31996
  {
31662
31997
  reviewRoot: opts.reviewRoot,
@@ -31772,7 +32107,7 @@ var sprintAffectedRepositories = (repositories, taskRepositoryIds) => {
31772
32107
  };
31773
32108
  var renderRepositoriesBlock = (affected) => affected.map((r) => `- \`${String(r.path)}\` (${r.name})`).join("\n");
31774
32109
  var launchReview = async (ctx) => {
31775
- const { deps, snapshot, settings, provider, bridge, sessionId: sessionId3 } = ctx;
32110
+ const { deps, snapshot, settings, provider, bridge, sessionId: sessionId3, effort } = ctx;
31776
32111
  if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
31777
32112
  if (!snapshot.project) return { ok: false, reason: "No project loaded for the selected sprint." };
31778
32113
  if (snapshot.project.repositories.length === 0) {
@@ -31817,11 +32152,13 @@ var launchReview = async (ctx) => {
31817
32152
  fileLocker: deps.app.fileLocker,
31818
32153
  locksRoot: deps.storage.locksRoot,
31819
32154
  appendFile: deps.app.appendFile,
31820
- // Review uses the implement generator model — same code-mutation profile, same
32155
+ // Review uses the implement generator model + effort — same code-mutation profile, same
31821
32156
  // accuracy expectations. No per-flow `review` row in settings today. Override flows in
31822
32157
  // through ctx.settings (launcher applied it to ai.implement.generator when the picker
31823
- // emitted a non-empty override), so per-field fallback is automatic.
32158
+ // emitted a non-empty override), so per-field fallback is automatic; `ctx.effort` is the
32159
+ // launcher's `resolveEffort('implement', settings)` over those same settings.
31824
32160
  model: settings.ai.implement.generator.model,
32161
+ ...effort !== void 0 ? { effort } : {},
31825
32162
  ...distill !== void 0 ? { distill } : {}
31826
32163
  },
31827
32164
  {
@@ -33156,13 +33493,6 @@ var pickRowForProvider = (ai, provider) => {
33156
33493
  }
33157
33494
  throw new Error(`pickRowForProvider: provider ${provider} not referenced in ai settings`);
33158
33495
  };
33159
- var resolveEffortForRow2 = (ai, flow) => {
33160
- const row = primaryFlowRow(ai, flow);
33161
- if (row.effort !== void 0) return row.effort;
33162
- const globalEffort = ai.effort;
33163
- if (globalEffort === void 0) return void 0;
33164
- return clampEffortToProvider(globalEffort, row.provider);
33165
- };
33166
33496
  var PROVIDER_INFRASTRUCTURE_ERROR_CODES = /* @__PURE__ */ new Set([ErrorCode.Probe, ErrorCode.RateLimit]);
33167
33497
  var wrapProviderContinue = (eventBus, provider, inner) => tolerateErrors(
33168
33498
  {
@@ -33181,7 +33511,7 @@ var wrapProviderContinue = (eventBus, provider, inner) => tolerateErrors(
33181
33511
  var buildPerToolSubchain = (deps, opts, provider, tool) => {
33182
33512
  const rowFlow = pickRowForProvider(opts.ai, provider);
33183
33513
  const row = primaryFlowRow(opts.ai, rowFlow);
33184
- const effort = resolveEffortForRow2(opts.ai, rowFlow);
33514
+ const effort = resolveEffortForRow(row, opts.ai.effort, "readiness");
33185
33515
  const provideAi = deps.providerFor(provider);
33186
33516
  const skillsAdapter = deps.skillsAdapterFor(provider);
33187
33517
  return sequential(`tool-${tool}`, [
@@ -37938,7 +38268,7 @@ var SprintsView = () => {
37938
38268
  import "react";
37939
38269
 
37940
38270
  // src/application/ui/tui/views/sprint-detail-internals/detail-body.tsx
37941
- import { useEffect as useEffect26, useMemo as useMemo17, useState as useState31 } from "react";
38271
+ import { useEffect as useEffect26, useMemo as useMemo17, useRef as useRef14, useState as useState31 } from "react";
37942
38272
 
37943
38273
  // src/business/task/evaluation-artifact.ts
37944
38274
  import { isAbsolute as isAbsolute4, normalize } from "path";
@@ -38020,6 +38350,18 @@ var SHORTCUT_ROWS = [
38020
38350
  if (focused?.kind === "ticket") args.beginRemove(focused.ticket);
38021
38351
  }
38022
38352
  },
38353
+ {
38354
+ // `p` is unused globally (`P` is pick-project) and unused elsewhere in this view. Fires
38355
+ // on any focused ticket row of an open sprint — draft or not — so the comment path is
38356
+ // reachable after the sprint leaves draft; inert once `done` (done sprints are immutable).
38357
+ // No prompt; the flow writes or surfaces the tracker error.
38358
+ key: (input) => input === "p",
38359
+ guard: (args, sprint) => sprint.status !== "done" && focusedItem(args)?.kind === "ticket",
38360
+ action: (args) => {
38361
+ const focused = focusedItem(args);
38362
+ if (focused?.kind === "ticket") args.handlePublish(focused.ticket);
38363
+ }
38364
+ },
38023
38365
  {
38024
38366
  key: (input) => input === "u",
38025
38367
  guard: (args) => args.focusedStuckTask !== void 0,
@@ -38160,6 +38502,107 @@ var useSprintBundle = (args) => {
38160
38502
  return { state, project, reload };
38161
38503
  };
38162
38504
 
38505
+ // src/business/ticket/publish-to-tracker.ts
38506
+ var REFINEMENT_MARKER2 = "<!-- ralphctl:refined-requirements -->";
38507
+ var refinementBody = (requirements) => `${requirements}
38508
+
38509
+ ${REFINEMENT_MARKER2}`;
38510
+ var publishTicketToTracker = async (props) => {
38511
+ const open = requireStatus(
38512
+ "sprint",
38513
+ props.sprint,
38514
+ ["draft", "planned", "active", "review"],
38515
+ "publish-ticket",
38516
+ "Done sprints are immutable."
38517
+ );
38518
+ if (!open.ok) return Result.error(open.error);
38519
+ const ticket = props.sprint.tickets.find((t) => t.id === props.ticketId);
38520
+ if (ticket === void 0) {
38521
+ return Result.error(new NotFoundError({ entity: "ticket", id: String(props.ticketId) }));
38522
+ }
38523
+ if (ticket.link !== void 0) {
38524
+ return publishExistingLink(props, ticket.link, ticket.status === "approved" ? ticket.requirements : void 0);
38525
+ }
38526
+ const title = parseRequiredString("ticket.title", ticket.title);
38527
+ if (!title.ok) return Result.error(title.error);
38528
+ const origin = await props.issuePusher.resolveOrigin(props.cwd);
38529
+ if (!origin.ok) return Result.error(origin.error);
38530
+ if (origin.value === null) {
38531
+ return Result.error(
38532
+ new StorageError({
38533
+ subCode: "io",
38534
+ message: `no GitHub or GitLab origin at ${String(props.cwd)}`
38535
+ })
38536
+ );
38537
+ }
38538
+ const created = await props.issuePusher.create({
38539
+ cwd: props.cwd,
38540
+ title: title.value,
38541
+ body: ticket.description ?? ""
38542
+ });
38543
+ if (!created.ok) return Result.error(created.error);
38544
+ const attached = attachTicketLink(props.sprint, props.ticketId, created.value.url);
38545
+ if (!attached.ok) return Result.error(attached.error);
38546
+ return Result.ok({ sprint: attached.value, outcome: "created" });
38547
+ };
38548
+ var publishExistingLink = async (props, link, requirements) => {
38549
+ if (requirements === void 0) {
38550
+ return Result.ok({ sprint: props.sprint, outcome: "nothing-to-post" });
38551
+ }
38552
+ const body = refinementBody(requirements);
38553
+ const listed = await props.issuePusher.listComments(link);
38554
+ if (!listed.ok) return Result.error(listed.error);
38555
+ if (listed.value.includes(body)) {
38556
+ return Result.ok({ sprint: props.sprint, outcome: "already-up-to-date" });
38557
+ }
38558
+ const posted = await props.issuePusher.comment(link, { body });
38559
+ if (!posted.ok) return Result.error(posted.error);
38560
+ return Result.ok({ sprint: props.sprint, outcome: "commented" });
38561
+ };
38562
+ var publishSaveFailedError = (output, ticketId, saveError) => {
38563
+ if (output.outcome !== "created") return saveError;
38564
+ const url = output.sprint.tickets.find((t) => t.id === ticketId)?.link;
38565
+ if (url === void 0) return saveError;
38566
+ return new StorageError({
38567
+ subCode: "io",
38568
+ message: `Issue created at ${url} but saving the link failed \u2014 set the ticket link to this URL manually; re-publishing would open a duplicate.`,
38569
+ cause: saveError
38570
+ });
38571
+ };
38572
+
38573
+ // src/application/flows/publish-ticket/flow.ts
38574
+ var createTicketPublishFlow = (deps) => leaf("publish-ticket", {
38575
+ useCase: {
38576
+ async execute(input) {
38577
+ const sprint = await deps.sprintRepo.findById(input.sprintId);
38578
+ if (!sprint.ok) return Result.error(sprint.error);
38579
+ const project = await deps.projectRepo.findById(sprint.value.projectId);
38580
+ if (!project.ok) return Result.error(project.error);
38581
+ const originCwd = project.value.repositories[0]?.path;
38582
+ if (originCwd === void 0) {
38583
+ return Result.error(
38584
+ new StorageError({
38585
+ subCode: "io",
38586
+ message: "Project has no repositories \u2014 add one first."
38587
+ })
38588
+ );
38589
+ }
38590
+ const published = await publishTicketToTracker({
38591
+ sprint: sprint.value,
38592
+ ticketId: input.ticketId,
38593
+ cwd: originCwd,
38594
+ issuePusher: deps.issuePusher
38595
+ });
38596
+ if (!published.ok) return Result.error(published.error);
38597
+ const saved = await deps.sprintRepo.save(published.value.sprint);
38598
+ if (!saved.ok) return Result.error(publishSaveFailedError(published.value, input.ticketId, saved.error));
38599
+ return Result.ok(published.value);
38600
+ }
38601
+ },
38602
+ input: (c) => c.input,
38603
+ output: (c, o) => ({ ...c, output: o })
38604
+ });
38605
+
38163
38606
  // src/application/flows/remove-ticket/flow.ts
38164
38607
  var createTicketRemoveFlow = (deps) => leaf("remove-ticket", {
38165
38608
  useCase: {
@@ -38303,8 +38746,37 @@ var runRemoveTicket = async (args) => {
38303
38746
  setFeedback(`${glyphs.check} removed "${target.title}"`);
38304
38747
  reload();
38305
38748
  };
38749
+ var runPublishTicket = async (args) => {
38750
+ const { target, sprintId, sprintRepo, projectRepo, issuePusher, mountedRef, setFeedback, reload } = args;
38751
+ if (issuePusher === void 0) {
38752
+ if (mountedRef.current) setFeedback(`${glyphs.cross} issue tracker is unavailable`);
38753
+ return;
38754
+ }
38755
+ const flow = createTicketPublishFlow({ sprintRepo, projectRepo, issuePusher });
38756
+ const r = await flow.execute({ input: { sprintId, ticketId: target.id } });
38757
+ if (!r.ok) {
38758
+ if (mountedRef.current) setFeedback(`${glyphs.cross} ${r.error.error.message}`);
38759
+ return;
38760
+ }
38761
+ if (!mountedRef.current) return;
38762
+ const out = r.value.ctx.output;
38763
+ setFeedback(`${glyphs.check} ${out.outcome} "${target.title}"`);
38764
+ reload();
38765
+ };
38306
38766
  var buildSprintDetailHandlers = (args) => {
38307
- const { sprint, deps, focus, queue, edit, reload, mountedRef, setFeedback, unblockTask: unblockTask2, setConfirmRemove } = args;
38767
+ const {
38768
+ sprint,
38769
+ deps,
38770
+ focus,
38771
+ queue,
38772
+ edit,
38773
+ reload,
38774
+ mountedRef,
38775
+ setFeedback,
38776
+ unblockTask: unblockTask2,
38777
+ setConfirmRemove,
38778
+ publishInFlightRef
38779
+ } = args;
38308
38780
  const handleEdit = () => {
38309
38781
  if (sprint === void 0) return;
38310
38782
  runEdit({
@@ -38322,6 +38794,24 @@ var buildSprintDetailHandlers = (args) => {
38322
38794
  if (sprint === void 0) return;
38323
38795
  await runUnblock({ target, sprintId: sprint.id, unblockTask: unblockTask2, mountedRef, setFeedback, reload });
38324
38796
  };
38797
+ const handlePublish = async (target) => {
38798
+ if (sprint === void 0 || publishInFlightRef.current) return;
38799
+ publishInFlightRef.current = true;
38800
+ try {
38801
+ await runPublishTicket({
38802
+ target,
38803
+ sprintId: sprint.id,
38804
+ sprintRepo: deps.sprintRepo,
38805
+ projectRepo: deps.projectRepo,
38806
+ issuePusher: deps.issuePusher,
38807
+ mountedRef,
38808
+ setFeedback,
38809
+ reload
38810
+ });
38811
+ } finally {
38812
+ publishInFlightRef.current = false;
38813
+ }
38814
+ };
38325
38815
  const handleRemoveConfirmed2 = async (target, confirmed) => {
38326
38816
  setConfirmRemove(void 0);
38327
38817
  if (!confirmed || sprint === void 0) return;
@@ -38334,7 +38824,7 @@ var buildSprintDetailHandlers = (args) => {
38334
38824
  reload
38335
38825
  });
38336
38826
  };
38337
- return { handleEdit, handleUnblock: handleUnblock2, handleRemoveConfirmed: handleRemoveConfirmed2 };
38827
+ return { handleEdit, handleUnblock: handleUnblock2, handlePublish, handleRemoveConfirmed: handleRemoveConfirmed2 };
38338
38828
  };
38339
38829
 
38340
38830
  // src/application/ui/tui/views/sprint-detail-internals/detail-shortcuts-actions.ts
@@ -38346,6 +38836,7 @@ var buildShortcutsActions = (args) => {
38346
38836
  setConfirmRemove,
38347
38837
  setFeedback,
38348
38838
  onUnblock,
38839
+ onPublish,
38349
38840
  sprintId,
38350
38841
  openEvaluationOverlay,
38351
38842
  reload
@@ -38367,6 +38858,9 @@ var buildShortcutsActions = (args) => {
38367
38858
  handleUnblock: (task) => {
38368
38859
  void onUnblock(task);
38369
38860
  },
38861
+ handlePublish: (ticket) => {
38862
+ void onPublish(ticket);
38863
+ },
38370
38864
  // The full target is assembled here (not inside the overlay) so its degrade arms never need a
38371
38865
  // second repository read — see `runtime/evaluation-target.ts`.
38372
38866
  openEvaluation: (task) => {
@@ -38397,11 +38891,12 @@ var deriveFocusedSelection = (focusList, cursorIdx, ticketsEditable, sprintStatu
38397
38891
  const focusedNow = focusList[Math.min(cursorIdx, Math.max(0, focusList.length - 1))];
38398
38892
  const focusedNowTask = focusedNow?.kind === "task" ? focusedNow.task : void 0;
38399
38893
  const focusedStuckTask = focusedNowTask !== void 0 && isStuckTaskStatus(focusedNowTask.status, sprintStatus) ? focusedNowTask : void 0;
38400
- const focusedTicket = focusedNow?.kind === "ticket" && ticketsEditable ? focusedNow.ticket : void 0;
38894
+ const focusedTicketRow = focusedNow?.kind === "ticket" ? focusedNow.ticket : void 0;
38895
+ const focusedTicket = ticketsEditable ? focusedTicketRow : void 0;
38401
38896
  const focusedTodoTask = focusedNow?.kind === "task" && focusedNow.task.status === "todo" ? focusedNow.task : void 0;
38402
38897
  const focusedEvaluatedTask = focusedNow?.kind === "task" && latestRecordedEvaluation(focusedNow.task) !== void 0 ? focusedNow.task : void 0;
38403
38898
  const canEdit = focusedTicket !== void 0 || focusedTodoTask !== void 0;
38404
- return { focusedTicket, focusedTodoTask, focusedStuckTask, focusedEvaluatedTask, canEdit };
38899
+ return { focusedTicket, focusedTicketRow, focusedTodoTask, focusedStuckTask, focusedEvaluatedTask, canEdit };
38405
38900
  };
38406
38901
  var focusItemId = (item) => item.kind === "ticket" ? `ticket:${String(item.ticket.id)}` : `task:${String(item.task.id)}`;
38407
38902
  var useFocusModel = (args) => {
@@ -38416,7 +38911,7 @@ var useFocusModel = (args) => {
38416
38911
  visibleRows: focusVisibleRows,
38417
38912
  // Navigation keys (↑↓ j/k PgUp/PgDn Home/End) are owned by the hook — except once a jump
38418
38913
  // override is active, when `shortcuts.ts`'s `jump.moveBy` / `moveToEdge` take over instead.
38419
- // The shortcuts hook also provides the other view-local keys (a/e/m/d/u/B/↵/q).
38914
+ // The shortcuts hook also provides the other view-local keys (a/e/m/d/p/u/B/↵/q).
38420
38915
  active: modalOpen === false && loaded && jumpOverrideIdx === void 0
38421
38916
  });
38422
38917
  const cursorIdx = jumpOverrideIdx !== void 0 ? clampFocusIndex(jumpOverrideIdx, focusList.length) : listCursorIdx;
@@ -38443,16 +38938,9 @@ var useFocusModel = (args) => {
38443
38938
  };
38444
38939
  };
38445
38940
  var buildDetailHints = (args) => {
38446
- const {
38447
- inDetail,
38448
- ticketsEditable,
38449
- canEdit,
38450
- sprint,
38451
- currentSprintId,
38452
- focusedStuckTask,
38453
- focusedEvaluatedTask,
38454
- blockedCount
38455
- } = args;
38941
+ const { inDetail, ticketsEditable, sprint, currentSprintId, focus } = args;
38942
+ const { canEdit, focusedTicketRow, focusedStuckTask, focusedEvaluatedTask, blockedCount } = focus;
38943
+ const canPublish = focusedTicketRow !== void 0 && sprint?.status !== "done";
38456
38944
  return [
38457
38945
  { keys: "\u2191/\u2193", label: "move" },
38458
38946
  { keys: "n", label: "flows" },
@@ -38463,14 +38951,15 @@ var buildDetailHints = (args) => {
38463
38951
  { keys: "a", label: "add", enabledWhen: ticketsEditable },
38464
38952
  { keys: "e", label: "edit", enabledWhen: canEdit },
38465
38953
  { keys: "d", label: "remove", enabledWhen: ticketsEditable },
38954
+ { keys: "p", label: "publish", enabledWhen: canPublish },
38466
38955
  // Surface the `m` chord only when this sprint is not already the current one — once
38467
38956
  // they match, the action is a no-op and the hint adds noise. Suppressed while a
38468
- // stuck task is focused so the `u unblock` hint (a more urgent operator action)
38469
- // stays prominent in the footer without competing for horizontal space.
38957
+ // stuck task or ticket is focused so `u unblock` / `p publish` stay on one 100-column
38958
+ // line without competing for horizontal space.
38470
38959
  {
38471
38960
  keys: "m",
38472
38961
  label: "current",
38473
- enabledWhen: sprint !== void 0 && currentSprintId !== sprint.id && focusedStuckTask === void 0
38962
+ enabledWhen: sprint !== void 0 && currentSprintId !== sprint.id && focusedStuckTask === void 0 && !canPublish
38474
38963
  },
38475
38964
  { keys: "u", label: "unblock", enabledWhen: focusedStuckTask !== void 0 },
38476
38965
  { keys: "B", label: "next blocked", enabledWhen: blockedCount > 0 },
@@ -38564,6 +39053,7 @@ var useSprintDetailBody = () => {
38564
39053
  const [feedback2, setFeedback] = useState31(void 0);
38565
39054
  const inDetail = openIds.size > 0;
38566
39055
  const mountedRef = useIsMounted();
39056
+ const publishInFlightRef = useRef14(false);
38567
39057
  const edit = useEditField();
38568
39058
  const queue = usePromptQueue();
38569
39059
  useSprintStatusChipSync(sprint, selection);
@@ -38571,12 +39061,9 @@ var useSprintDetailBody = () => {
38571
39061
  buildDetailHints({
38572
39062
  inDetail,
38573
39063
  ticketsEditable,
38574
- canEdit: focus.canEdit,
38575
39064
  sprint,
38576
39065
  currentSprintId: selection.sprintId,
38577
- focusedStuckTask: focus.focusedStuckTask,
38578
- focusedEvaluatedTask: focus.focusedEvaluatedTask,
38579
- blockedCount: focus.blockedCount
39066
+ focus
38580
39067
  })
38581
39068
  );
38582
39069
  const unblockTask2 = useUnblockTask();
@@ -38590,7 +39077,8 @@ var useSprintDetailBody = () => {
38590
39077
  mountedRef,
38591
39078
  setFeedback,
38592
39079
  unblockTask: unblockTask2,
38593
- setConfirmRemove
39080
+ setConfirmRemove,
39081
+ publishInFlightRef
38594
39082
  });
38595
39083
  useSprintDetailShortcuts({
38596
39084
  modalOpen: ui.modalOpen,
@@ -38612,6 +39100,7 @@ var useSprintDetailBody = () => {
38612
39100
  setConfirmRemove,
38613
39101
  setFeedback,
38614
39102
  onUnblock: handlers.handleUnblock,
39103
+ onPublish: handlers.handlePublish,
38615
39104
  sprintId: sprint?.id,
38616
39105
  openEvaluationOverlay: ui.openEvaluation,
38617
39106
  reload
@@ -39885,15 +40374,15 @@ var useTokenUsage = (bus, opts = {}) => useCoalescedMap(bus, {
39885
40374
  });
39886
40375
 
39887
40376
  // src/application/ui/tui/runtime/use-coalesced-buffer.ts
39888
- import { useEffect as useEffect28, useRef as useRef14, useState as useState33 } from "react";
40377
+ import { useEffect as useEffect28, useRef as useRef15, useState as useState33 } from "react";
39889
40378
  var useCoalescedBuffer = (opts) => {
39890
40379
  const limit = opts.limit ?? 100;
39891
40380
  const [items, setItems] = useState33(() => opts.initial ? opts.initial.slice(-limit) : []);
39892
- const subscribeRef = useRef14(opts.subscribe);
40381
+ const subscribeRef = useRef15(opts.subscribe);
39893
40382
  subscribeRef.current = opts.subscribe;
39894
- const initialRef = useRef14(opts.initial);
40383
+ const initialRef = useRef15(opts.initial);
39895
40384
  initialRef.current = opts.initial;
39896
- const mountedRef = useRef14(false);
40385
+ const mountedRef = useRef15(false);
39897
40386
  useEffect28(() => {
39898
40387
  const buf = createCoalescedBuffer({
39899
40388
  limit,
@@ -39928,10 +40417,10 @@ var useSinkStream = (bus, opts = {}) => {
39928
40417
  };
39929
40418
 
39930
40419
  // src/application/ui/tui/runtime/use-event-bus.ts
39931
- import { useRef as useRef15 } from "react";
40420
+ import { useRef as useRef16 } from "react";
39932
40421
  var useEventBusBuffer = (bus, opts) => {
39933
40422
  const limit = opts.limit ?? 100;
39934
- const filterRef = useRef15(opts.filter);
40423
+ const filterRef = useRef16(opts.filter);
39935
40424
  filterRef.current = opts.filter;
39936
40425
  return useCoalescedBuffer({
39937
40426
  limit,
@@ -42954,6 +43443,330 @@ var createSettingsShowFlow = (deps) => leaf("settings-show", {
42954
43443
  // src/application/ui/tui/views/settings-editor.tsx
42955
43444
  import { useState as useState38 } from "react";
42956
43445
 
43446
+ // src/business/settings/claude-preset-matrices.ts
43447
+ var CLAUDE_ONLY = {
43448
+ effort: "high",
43449
+ refine: { provider: CLAUDE, model: SONNET, effort: "medium" },
43450
+ plan: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43451
+ implement: {
43452
+ generator: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43453
+ evaluator: { provider: CLAUDE, model: OPUS, effort: "xhigh" }
43454
+ },
43455
+ readiness: { provider: CLAUDE, model: SONNET, effort: "medium" },
43456
+ ideate: { provider: CLAUDE, model: OPUS, effort: "high" },
43457
+ createPr: { provider: CLAUDE, model: SONNET, effort: "low" }
43458
+ };
43459
+ var CLAUDE_ECONOMIC = {
43460
+ effort: "high",
43461
+ refine: { provider: CLAUDE, model: SONNET, effort: "low" },
43462
+ plan: { provider: CLAUDE, model: SONNET, effort: "high" },
43463
+ implement: {
43464
+ generator: { provider: CLAUDE, model: SONNET, effort: "high" },
43465
+ evaluator: { provider: CLAUDE, model: SONNET, effort: "high" }
43466
+ },
43467
+ readiness: { provider: CLAUDE, model: SONNET, effort: "low" },
43468
+ ideate: { provider: CLAUDE, model: SONNET, effort: "medium" },
43469
+ createPr: { provider: CLAUDE, model: SONNET, effort: "low" }
43470
+ };
43471
+ var CLAUDE_STRONG_GATE = {
43472
+ effort: "high",
43473
+ refine: { provider: CLAUDE, model: SONNET, effort: "medium" },
43474
+ plan: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43475
+ implement: {
43476
+ generator: { provider: CLAUDE, model: SONNET, effort: "high" },
43477
+ evaluator: { provider: CLAUDE, model: OPUS, effort: "xhigh" }
43478
+ },
43479
+ readiness: { provider: CLAUDE, model: SONNET, effort: "low" },
43480
+ ideate: { provider: CLAUDE, model: SONNET, effort: "high" },
43481
+ createPr: { provider: CLAUDE, model: SONNET, effort: "low" }
43482
+ };
43483
+ var CLAUDE_FAST = {
43484
+ effort: "low",
43485
+ refine: { provider: CLAUDE, model: SONNET, effort: "low" },
43486
+ plan: { provider: CLAUDE, model: SONNET, effort: "low" },
43487
+ implement: {
43488
+ generator: { provider: CLAUDE, model: SONNET, effort: "low" },
43489
+ evaluator: { provider: CLAUDE, model: SONNET, effort: "low" }
43490
+ },
43491
+ readiness: { provider: CLAUDE, model: SONNET, effort: "low" },
43492
+ ideate: { provider: CLAUDE, model: SONNET, effort: "low" },
43493
+ createPr: { provider: CLAUDE, model: SONNET, effort: "low" }
43494
+ };
43495
+ var CLAUDE_FRONTIER = {
43496
+ effort: "max",
43497
+ refine: { provider: CLAUDE, model: OPUS, effort: "high" },
43498
+ plan: { provider: CLAUDE, model: FABLE, effort: "max" },
43499
+ implement: {
43500
+ generator: { provider: CLAUDE, model: FABLE, effort: "max" },
43501
+ evaluator: { provider: CLAUDE, model: FABLE, effort: "max" }
43502
+ },
43503
+ readiness: { provider: CLAUDE, model: OPUS, effort: "high" },
43504
+ ideate: { provider: CLAUDE, model: FABLE, effort: "high" },
43505
+ createPr: { provider: CLAUDE, model: OPUS, effort: "medium" }
43506
+ };
43507
+
43508
+ // src/business/settings/codex-preset-matrices.ts
43509
+ var CODEX_ONLY = {
43510
+ effort: "high",
43511
+ refine: { provider: CODEX, model: GPT_6_LUNA, effort: "high" },
43512
+ plan: { provider: CODEX, model: GPT_6_SOL, effort: "xhigh" },
43513
+ implement: {
43514
+ generator: { provider: CODEX, model: GPT_6_SOL, effort: "xhigh" },
43515
+ evaluator: { provider: CODEX, model: GPT_6_SOL, effort: "xhigh" }
43516
+ },
43517
+ readiness: { provider: CODEX, model: GPT_6_LUNA, effort: "medium" },
43518
+ ideate: { provider: CODEX, model: GPT_6_SOL, effort: "high" },
43519
+ createPr: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43520
+ };
43521
+ var CODEX_ECONOMIC = {
43522
+ effort: "high",
43523
+ refine: { provider: CODEX, model: GPT_6_LUNA, effort: "medium" },
43524
+ plan: { provider: CODEX, model: GPT_6_LUNA, effort: "xhigh" },
43525
+ implement: {
43526
+ generator: { provider: CODEX, model: GPT_6_LUNA, effort: "xhigh" },
43527
+ evaluator: { provider: CODEX, model: GPT_6_LUNA, effort: "xhigh" }
43528
+ },
43529
+ readiness: { provider: CODEX, model: GPT_6_LUNA, effort: "low" },
43530
+ ideate: { provider: CODEX, model: GPT_6_LUNA, effort: "high" },
43531
+ createPr: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43532
+ };
43533
+ var CODEX_STRONG_GATE = {
43534
+ effort: "high",
43535
+ refine: { provider: CODEX, model: GPT_6_LUNA, effort: "medium" },
43536
+ plan: { provider: CODEX, model: GPT_6_SOL, effort: "xhigh" },
43537
+ implement: {
43538
+ generator: { provider: CODEX, model: GPT_6_LUNA, effort: "xhigh" },
43539
+ evaluator: { provider: CODEX, model: GPT_6_SOL, effort: "xhigh" }
43540
+ },
43541
+ readiness: { provider: CODEX, model: GPT_6_LUNA, effort: "medium" },
43542
+ ideate: { provider: CODEX, model: GPT_6_SOL, effort: "high" },
43543
+ createPr: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43544
+ };
43545
+ var CODEX_FAST = {
43546
+ effort: "low",
43547
+ refine: { provider: CODEX, model: GPT_6_LUNA, effort: "low" },
43548
+ plan: { provider: CODEX, model: GPT_6_LUNA, effort: "low" },
43549
+ implement: {
43550
+ generator: { provider: CODEX, model: GPT_6_LUNA, effort: "low" },
43551
+ evaluator: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43552
+ },
43553
+ readiness: { provider: CODEX, model: GPT_6_LUNA, effort: "low" },
43554
+ ideate: { provider: CODEX, model: GPT_6_LUNA, effort: "low" },
43555
+ createPr: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43556
+ };
43557
+ var CODEX_FRONTIER = {
43558
+ effort: "max",
43559
+ refine: { provider: CODEX, model: GPT_6_SOL, effort: "high" },
43560
+ plan: { provider: CODEX, model: GPT_6_ASTRA, effort: "max" },
43561
+ implement: {
43562
+ generator: { provider: CODEX, model: GPT_6_ASTRA, effort: "max" },
43563
+ evaluator: { provider: CODEX, model: GPT_6_ASTRA, effort: "max" }
43564
+ },
43565
+ readiness: { provider: CODEX, model: GPT_6_SOL, effort: "high" },
43566
+ ideate: { provider: CODEX, model: GPT_6_ASTRA, effort: "high" },
43567
+ createPr: { provider: CODEX, model: GPT_6_SOL, effort: "medium" }
43568
+ };
43569
+
43570
+ // src/business/settings/copilot-preset-matrices.ts
43571
+ var COPILOT_ONLY = {
43572
+ effort: "high",
43573
+ refine: { provider: COPILOT, model: COPILOT_SONNET, effort: "medium" },
43574
+ plan: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" },
43575
+ implement: {
43576
+ generator: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" },
43577
+ evaluator: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" }
43578
+ },
43579
+ readiness: { provider: COPILOT, model: COPILOT_LUNA, effort: "medium" },
43580
+ ideate: { provider: COPILOT, model: COPILOT_OPUS, effort: "high" },
43581
+ createPr: { provider: COPILOT, model: COPILOT_LUNA, effort: "low" }
43582
+ };
43583
+ var COPILOT_ECONOMIC = {
43584
+ effort: "high",
43585
+ refine: { provider: COPILOT, model: COPILOT_LUNA, effort: "medium" },
43586
+ plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43587
+ implement: {
43588
+ generator: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43589
+ evaluator: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" }
43590
+ },
43591
+ readiness: { provider: COPILOT, model: COPILOT_LUNA, effort: "medium" },
43592
+ ideate: { provider: COPILOT, model: COPILOT_SONNET, effort: "medium" },
43593
+ createPr: { provider: COPILOT, model: COPILOT_LUNA, effort: "low" }
43594
+ };
43595
+ var COPILOT_STRONG_GATE = {
43596
+ effort: "high",
43597
+ refine: { provider: COPILOT, model: COPILOT_SONNET, effort: "medium" },
43598
+ plan: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" },
43599
+ implement: {
43600
+ generator: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43601
+ evaluator: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" }
43602
+ },
43603
+ readiness: { provider: COPILOT, model: COPILOT_LUNA, effort: "medium" },
43604
+ ideate: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43605
+ createPr: { provider: COPILOT, model: COPILOT_LUNA, effort: "low" }
43606
+ };
43607
+ var COPILOT_FAST = {
43608
+ effort: "low",
43609
+ refine: { provider: COPILOT, model: COPILOT_LUNA, effort: "low" },
43610
+ plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "low" },
43611
+ implement: {
43612
+ generator: { provider: COPILOT, model: COPILOT_SONNET, effort: "low" },
43613
+ evaluator: { provider: COPILOT, model: COPILOT_SONNET, effort: "low" }
43614
+ },
43615
+ readiness: { provider: COPILOT, model: COPILOT_LUNA, effort: "low" },
43616
+ ideate: { provider: COPILOT, model: COPILOT_LUNA, effort: "low" },
43617
+ createPr: { provider: COPILOT, model: COPILOT_LUNA, effort: "low" }
43618
+ };
43619
+ var COPILOT_FRONTIER = {
43620
+ effort: "max",
43621
+ refine: { provider: COPILOT, model: COPILOT_OPUS, effort: "high" },
43622
+ plan: { provider: COPILOT, model: COPILOT_OPUS, effort: "max" },
43623
+ implement: {
43624
+ generator: { provider: COPILOT, model: COPILOT_OPUS, effort: "max" },
43625
+ evaluator: { provider: COPILOT, model: COPILOT_OPUS, effort: "max" }
43626
+ },
43627
+ readiness: { provider: COPILOT, model: COPILOT_OPUS, effort: "high" },
43628
+ ideate: { provider: COPILOT, model: COPILOT_OPUS, effort: "high" },
43629
+ createPr: { provider: COPILOT, model: COPILOT_OPUS, effort: "medium" }
43630
+ };
43631
+
43632
+ // src/business/settings/grok-preset-matrices.ts
43633
+ var GROK_ONLY = {
43634
+ effort: "high",
43635
+ refine: { provider: GROK, model: GROK_MID, effort: "medium" },
43636
+ plan: { provider: GROK, model: GROK_FLAGSHIP, effort: "xhigh" },
43637
+ implement: {
43638
+ generator: { provider: GROK, model: GROK_FLAGSHIP, effort: "xhigh" },
43639
+ evaluator: { provider: GROK, model: GROK_FLAGSHIP, effort: "xhigh" }
43640
+ },
43641
+ readiness: { provider: GROK, model: GROK_CHEAP, effort: "medium" },
43642
+ ideate: { provider: GROK, model: GROK_FLAGSHIP, effort: "high" },
43643
+ createPr: { provider: GROK, model: GROK_CHEAP, effort: "low" }
43644
+ };
43645
+ var GROK_ECONOMIC = {
43646
+ effort: "high",
43647
+ refine: { provider: GROK, model: GROK_CHEAP, effort: "medium" },
43648
+ plan: { provider: GROK, model: GROK_MID, effort: "high" },
43649
+ implement: {
43650
+ generator: { provider: GROK, model: GROK_MID, effort: "high" },
43651
+ evaluator: { provider: GROK, model: GROK_MID, effort: "high" }
43652
+ },
43653
+ readiness: { provider: GROK, model: GROK_CHEAP, effort: "medium" },
43654
+ ideate: { provider: GROK, model: GROK_MID, effort: "medium" },
43655
+ createPr: { provider: GROK, model: GROK_CHEAP, effort: "low" }
43656
+ };
43657
+ var GROK_STRONG_GATE = {
43658
+ effort: "high",
43659
+ refine: { provider: GROK, model: GROK_CHEAP, effort: "medium" },
43660
+ plan: { provider: GROK, model: GROK_FLAGSHIP, effort: "xhigh" },
43661
+ implement: {
43662
+ generator: { provider: GROK, model: GROK_MID, effort: "high" },
43663
+ evaluator: { provider: GROK, model: GROK_FLAGSHIP, effort: "xhigh" }
43664
+ },
43665
+ readiness: { provider: GROK, model: GROK_CHEAP, effort: "medium" },
43666
+ ideate: { provider: GROK, model: GROK_FLAGSHIP, effort: "high" },
43667
+ createPr: { provider: GROK, model: GROK_CHEAP, effort: "low" }
43668
+ };
43669
+ var GROK_FAST = {
43670
+ effort: "low",
43671
+ refine: { provider: GROK, model: GROK_CHEAP, effort: "low" },
43672
+ plan: { provider: GROK, model: GROK_CHEAP, effort: "low" },
43673
+ implement: {
43674
+ generator: { provider: GROK, model: GROK_CHEAP, effort: "low" },
43675
+ evaluator: { provider: GROK, model: GROK_CHEAP, effort: "low" }
43676
+ },
43677
+ readiness: { provider: GROK, model: GROK_CHEAP, effort: "low" },
43678
+ ideate: { provider: GROK, model: GROK_CHEAP, effort: "low" },
43679
+ createPr: { provider: GROK, model: GROK_CHEAP, effort: "low" }
43680
+ };
43681
+ var GROK_FRONTIER = {
43682
+ effort: "max",
43683
+ refine: { provider: GROK, model: GROK_FLAGSHIP, effort: "high" },
43684
+ plan: { provider: GROK, model: GROK_FLAGSHIP, effort: "max" },
43685
+ implement: {
43686
+ generator: { provider: GROK, model: GROK_FLAGSHIP, effort: "max" },
43687
+ evaluator: { provider: GROK, model: GROK_FLAGSHIP, effort: "max" }
43688
+ },
43689
+ readiness: { provider: GROK, model: GROK_FLAGSHIP, effort: "high" },
43690
+ ideate: { provider: GROK, model: GROK_FLAGSHIP, effort: "high" },
43691
+ createPr: { provider: GROK, model: GROK_FLAGSHIP, effort: "medium" }
43692
+ };
43693
+
43694
+ // src/business/settings/mixed-preset-matrices.ts
43695
+ var MIXED = {
43696
+ effort: "high",
43697
+ refine: { provider: CODEX, model: GPT_6_LUNA, effort: "high" },
43698
+ plan: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43699
+ implement: {
43700
+ generator: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43701
+ evaluator: { provider: CODEX, model: GPT_6_SOL, effort: "xhigh" }
43702
+ },
43703
+ readiness: { provider: COPILOT, model: COPILOT_LUNA, effort: "medium" },
43704
+ ideate: { provider: CLAUDE, model: OPUS, effort: "high" },
43705
+ // Summarising a diff does not need Opus tokens.
43706
+ createPr: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43707
+ };
43708
+ var MIXED_ECONOMIC = {
43709
+ effort: "high",
43710
+ refine: { provider: CODEX, model: GPT_6_LUNA, effort: "medium" },
43711
+ plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43712
+ implement: {
43713
+ generator: { provider: CLAUDE, model: SONNET, effort: "high" },
43714
+ evaluator: { provider: CODEX, model: GPT_6_LUNA, effort: "xhigh" }
43715
+ },
43716
+ readiness: { provider: COPILOT, model: COPILOT_LUNA, effort: "medium" },
43717
+ ideate: { provider: CLAUDE, model: SONNET, effort: "medium" },
43718
+ createPr: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43719
+ };
43720
+ var MIXED_STRONG_GATE = {
43721
+ effort: "high",
43722
+ refine: { provider: CODEX, model: GPT_6_LUNA, effort: "medium" },
43723
+ plan: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43724
+ implement: {
43725
+ generator: { provider: CLAUDE, model: SONNET, effort: "high" },
43726
+ evaluator: { provider: CLAUDE, model: OPUS, effort: "xhigh" }
43727
+ },
43728
+ readiness: { provider: COPILOT, model: COPILOT_LUNA, effort: "medium" },
43729
+ ideate: { provider: CLAUDE, model: SONNET, effort: "high" },
43730
+ createPr: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43731
+ };
43732
+ var MIXED_FAST = {
43733
+ effort: "low",
43734
+ refine: { provider: CODEX, model: GPT_6_LUNA, effort: "low" },
43735
+ plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "low" },
43736
+ implement: {
43737
+ generator: { provider: CLAUDE, model: SONNET, effort: "low" },
43738
+ evaluator: { provider: CLAUDE, model: SONNET, effort: "low" }
43739
+ },
43740
+ readiness: { provider: COPILOT, model: COPILOT_LUNA, effort: "low" },
43741
+ ideate: { provider: CLAUDE, model: SONNET, effort: "low" },
43742
+ createPr: { provider: CODEX, model: GPT_6_LUNA, effort: "low" }
43743
+ };
43744
+ var MIXED_FRONTIER = {
43745
+ effort: "max",
43746
+ refine: { provider: CODEX, model: GPT_6_SOL, effort: "high" },
43747
+ plan: { provider: CLAUDE, model: FABLE, effort: "max" },
43748
+ implement: {
43749
+ generator: { provider: CLAUDE, model: FABLE, effort: "max" },
43750
+ evaluator: { provider: CODEX, model: GPT_6_ASTRA, effort: "max" }
43751
+ },
43752
+ readiness: { provider: CLAUDE, model: OPUS, effort: "high" },
43753
+ ideate: { provider: CLAUDE, model: FABLE, effort: "high" },
43754
+ createPr: { provider: CODEX, model: GPT_6_SOL, effort: "medium" }
43755
+ };
43756
+
43757
+ // src/business/settings/opencode-preset-matrices.ts
43758
+ var OPENCODE_ONLY = {
43759
+ refine: { provider: OPENCODE, model: OPENCODE_MINI },
43760
+ plan: { provider: OPENCODE, model: OPENCODE_BIG },
43761
+ implement: {
43762
+ generator: { provider: OPENCODE, model: OPENCODE_BIG },
43763
+ evaluator: { provider: OPENCODE, model: OPENCODE_BIG }
43764
+ },
43765
+ readiness: { provider: OPENCODE, model: OPENCODE_MINI },
43766
+ ideate: { provider: OPENCODE, model: OPENCODE_BIG },
43767
+ createPr: { provider: OPENCODE, model: OPENCODE_MINI }
43768
+ };
43769
+
42957
43770
  // src/business/settings/presets.ts
42958
43771
  var PRESET_NAMES = [
42959
43772
  "mixed",
@@ -42966,311 +43779,24 @@ var PRESET_NAMES = [
42966
43779
  "claude-economic",
42967
43780
  "copilot-economic",
42968
43781
  "codex-economic",
43782
+ "grok-economic",
42969
43783
  "mixed-strong-gate",
42970
43784
  "claude-strong-gate",
42971
43785
  "copilot-strong-gate",
42972
43786
  "codex-strong-gate",
43787
+ "grok-strong-gate",
42973
43788
  "mixed-fast",
42974
43789
  "claude-fast",
42975
43790
  "copilot-fast",
42976
43791
  "codex-fast",
43792
+ "grok-fast",
42977
43793
  "mixed-frontier",
42978
43794
  "claude-frontier",
42979
43795
  "copilot-frontier",
42980
- "codex-frontier"
43796
+ "codex-frontier",
43797
+ "grok-frontier"
42981
43798
  ];
42982
43799
  var isPresetName = (raw) => PRESET_NAMES.includes(raw);
42983
- var CLAUDE = "claude-code";
42984
- var COPILOT = "github-copilot";
42985
- var CODEX = "openai-codex";
42986
- var OPENCODE = "opencode";
42987
- var GROK = "xai-grok";
42988
- var GROK_FLAGSHIP = "grok-4.6";
42989
- var GROK_PREV = "grok-4.5";
42990
- var OPENCODE_BIG2 = "opencode/big-pickle";
42991
- var OPENCODE_MINI2 = "opencode/deepseek-v4-flash-free";
42992
- var OPUS = "claude-opus-5";
42993
- var SONNET = "claude-sonnet-5";
42994
- var COPILOT_OPUS = "claude-opus-4.8";
42995
- var COPILOT_SONNET = "claude-sonnet-4.6";
42996
- var GPT_5_6_SOL2 = "gpt-5.6-sol";
42997
- var GPT_5_6_TERRA = "gpt-5.6-terra";
42998
- var GPT_5_6_LUNA = "gpt-5.6-luna";
42999
- var MIXED = {
43000
- effort: "high",
43001
- refine: { provider: CODEX, model: GPT_5_6_TERRA },
43002
- plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "xhigh" },
43003
- implement: {
43004
- generator: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43005
- // Independent gate: a different provider's flagship grades the Opus author's work.
43006
- evaluator: { provider: CODEX, model: GPT_5_6_SOL2, effort: "xhigh" }
43007
- },
43008
- readiness: { provider: COPILOT, model: GPT_5_6_LUNA, effort: "medium" },
43009
- ideate: { provider: CLAUDE, model: OPUS },
43010
- // PR content drafting mirrors refine's "light summary" reasoning profile — a fast Codex
43011
- // model is fine, no need to pay for Opus tokens just to summarise a diff.
43012
- createPr: { provider: CODEX, model: GPT_5_6_LUNA }
43013
- };
43014
- var CLAUDE_ONLY = {
43015
- effort: "high",
43016
- refine: { provider: CLAUDE, model: SONNET },
43017
- plan: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43018
- implement: {
43019
- generator: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43020
- evaluator: { provider: CLAUDE, model: OPUS, effort: "xhigh" }
43021
- },
43022
- readiness: { provider: CLAUDE, model: SONNET, effort: "medium" },
43023
- ideate: { provider: CLAUDE, model: OPUS },
43024
- createPr: { provider: CLAUDE, model: SONNET }
43025
- };
43026
- var COPILOT_ONLY = {
43027
- effort: "high",
43028
- refine: { provider: COPILOT, model: COPILOT_SONNET },
43029
- plan: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" },
43030
- implement: {
43031
- generator: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" },
43032
- evaluator: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" }
43033
- },
43034
- readiness: { provider: COPILOT, model: GPT_5_6_LUNA, effort: "medium" },
43035
- ideate: { provider: COPILOT, model: COPILOT_OPUS },
43036
- createPr: { provider: COPILOT, model: GPT_5_6_LUNA }
43037
- };
43038
- var OPENCODE_ONLY = {
43039
- refine: { provider: OPENCODE, model: OPENCODE_MINI2 },
43040
- plan: { provider: OPENCODE, model: OPENCODE_BIG2 },
43041
- implement: {
43042
- generator: { provider: OPENCODE, model: OPENCODE_BIG2 },
43043
- evaluator: { provider: OPENCODE, model: OPENCODE_BIG2 }
43044
- },
43045
- readiness: { provider: OPENCODE, model: OPENCODE_MINI2 },
43046
- ideate: { provider: OPENCODE, model: OPENCODE_BIG2 },
43047
- createPr: { provider: OPENCODE, model: OPENCODE_MINI2 }
43048
- };
43049
- var GROK_ONLY = {
43050
- effort: "high",
43051
- refine: { provider: GROK, model: GROK_PREV },
43052
- plan: { provider: GROK, model: GROK_FLAGSHIP, effort: "xhigh" },
43053
- implement: {
43054
- generator: { provider: GROK, model: GROK_FLAGSHIP, effort: "xhigh" },
43055
- evaluator: { provider: GROK, model: GROK_FLAGSHIP, effort: "xhigh" }
43056
- },
43057
- readiness: { provider: GROK, model: GROK_PREV, effort: "medium" },
43058
- ideate: { provider: GROK, model: GROK_FLAGSHIP },
43059
- createPr: { provider: GROK, model: GROK_PREV }
43060
- };
43061
- var CODEX_ONLY = {
43062
- effort: "high",
43063
- refine: { provider: CODEX, model: GPT_5_6_TERRA },
43064
- plan: { provider: CODEX, model: GPT_5_6_SOL2, effort: "xhigh" },
43065
- implement: {
43066
- generator: { provider: CODEX, model: GPT_5_6_SOL2, effort: "xhigh" },
43067
- evaluator: { provider: CODEX, model: GPT_5_6_SOL2, effort: "xhigh" }
43068
- },
43069
- readiness: { provider: CODEX, model: GPT_5_6_LUNA, effort: "medium" },
43070
- ideate: { provider: CODEX, model: GPT_5_6_SOL2 },
43071
- createPr: { provider: CODEX, model: GPT_5_6_LUNA }
43072
- };
43073
- var MIXED_ECONOMIC = {
43074
- effort: "high",
43075
- refine: { provider: CODEX, model: GPT_5_6_LUNA },
43076
- plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43077
- implement: {
43078
- generator: { provider: CLAUDE, model: SONNET, effort: "high" },
43079
- evaluator: { provider: CLAUDE, model: SONNET, effort: "high" }
43080
- },
43081
- readiness: { provider: COPILOT, model: GPT_5_6_LUNA, effort: "medium" },
43082
- ideate: { provider: CLAUDE, model: SONNET },
43083
- createPr: { provider: CODEX, model: GPT_5_6_LUNA }
43084
- };
43085
- var CLAUDE_ECONOMIC = {
43086
- effort: "high",
43087
- // Claude's cheap tier is sonnet-at-`low`, not a smaller model — see the note on SONNET. Effort
43088
- // is pinned on every light row: unset would inherit this preset's global `high` and undo the
43089
- // saving the row exists for.
43090
- refine: { provider: CLAUDE, model: SONNET, effort: "low" },
43091
- plan: { provider: CLAUDE, model: SONNET, effort: "high" },
43092
- implement: {
43093
- generator: { provider: CLAUDE, model: SONNET, effort: "high" },
43094
- evaluator: { provider: CLAUDE, model: SONNET, effort: "high" }
43095
- },
43096
- readiness: { provider: CLAUDE, model: SONNET, effort: "low" },
43097
- ideate: { provider: CLAUDE, model: SONNET },
43098
- createPr: { provider: CLAUDE, model: SONNET, effort: "low" }
43099
- };
43100
- var COPILOT_ECONOMIC = {
43101
- effort: "high",
43102
- refine: { provider: COPILOT, model: GPT_5_6_LUNA },
43103
- plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43104
- implement: {
43105
- generator: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43106
- evaluator: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" }
43107
- },
43108
- readiness: { provider: COPILOT, model: GPT_5_6_LUNA, effort: "medium" },
43109
- ideate: { provider: COPILOT, model: COPILOT_SONNET },
43110
- createPr: { provider: COPILOT, model: GPT_5_6_LUNA }
43111
- };
43112
- var CODEX_ECONOMIC = {
43113
- effort: "high",
43114
- refine: { provider: CODEX, model: GPT_5_6_LUNA },
43115
- plan: { provider: CODEX, model: GPT_5_6_TERRA, effort: "high" },
43116
- implement: {
43117
- generator: { provider: CODEX, model: GPT_5_6_TERRA, effort: "high" },
43118
- evaluator: { provider: CODEX, model: GPT_5_6_TERRA, effort: "high" }
43119
- },
43120
- readiness: { provider: CODEX, model: GPT_5_6_LUNA, effort: "medium" },
43121
- ideate: { provider: CODEX, model: GPT_5_6_TERRA },
43122
- createPr: { provider: CODEX, model: GPT_5_6_LUNA }
43123
- };
43124
- var CLAUDE_STRONG_GATE = {
43125
- effort: "high",
43126
- refine: { provider: CLAUDE, model: SONNET },
43127
- plan: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43128
- implement: {
43129
- // Cheap author: sonnet at high effort, climbs to opus on plateau via the default ladder.
43130
- generator: { provider: CLAUDE, model: SONNET, effort: "high" },
43131
- // Strong gate: opus from the first round, never cheapened.
43132
- evaluator: { provider: CLAUDE, model: OPUS, effort: "xhigh" }
43133
- },
43134
- // Cheap light flows: sonnet pinned at `low` (see CLAUDE_ECONOMIC) — never inheriting global `high`.
43135
- readiness: { provider: CLAUDE, model: SONNET, effort: "low" },
43136
- ideate: { provider: CLAUDE, model: SONNET },
43137
- createPr: { provider: CLAUDE, model: SONNET, effort: "low" }
43138
- };
43139
- var MIXED_STRONG_GATE = {
43140
- effort: "high",
43141
- refine: { provider: CODEX, model: GPT_5_6_LUNA },
43142
- plan: { provider: CLAUDE, model: OPUS, effort: "xhigh" },
43143
- implement: {
43144
- generator: { provider: CLAUDE, model: SONNET, effort: "high" },
43145
- evaluator: { provider: CLAUDE, model: OPUS, effort: "xhigh" }
43146
- },
43147
- readiness: { provider: COPILOT, model: GPT_5_6_LUNA, effort: "medium" },
43148
- ideate: { provider: CLAUDE, model: SONNET },
43149
- createPr: { provider: CODEX, model: GPT_5_6_LUNA }
43150
- };
43151
- var COPILOT_STRONG_GATE = {
43152
- effort: "high",
43153
- refine: { provider: COPILOT, model: COPILOT_SONNET },
43154
- plan: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" },
43155
- implement: {
43156
- generator: { provider: COPILOT, model: COPILOT_SONNET, effort: "high" },
43157
- evaluator: { provider: COPILOT, model: COPILOT_OPUS, effort: "xhigh" }
43158
- },
43159
- readiness: { provider: COPILOT, model: GPT_5_6_LUNA, effort: "medium" },
43160
- ideate: { provider: COPILOT, model: COPILOT_SONNET },
43161
- createPr: { provider: COPILOT, model: GPT_5_6_LUNA }
43162
- };
43163
- var CODEX_STRONG_GATE = {
43164
- effort: "high",
43165
- refine: { provider: CODEX, model: GPT_5_6_LUNA },
43166
- plan: { provider: CODEX, model: GPT_5_6_SOL2, effort: "xhigh" },
43167
- implement: {
43168
- // Narrowest gate of the family: terra author climbs the single default-ladder rung to the
43169
- // sol gate.
43170
- generator: { provider: CODEX, model: GPT_5_6_TERRA, effort: "high" },
43171
- evaluator: { provider: CODEX, model: GPT_5_6_SOL2, effort: "xhigh" }
43172
- },
43173
- readiness: { provider: CODEX, model: GPT_5_6_LUNA, effort: "medium" },
43174
- ideate: { provider: CODEX, model: GPT_5_6_SOL2 },
43175
- createPr: { provider: CODEX, model: GPT_5_6_LUNA }
43176
- };
43177
- var MIXED_FAST = {
43178
- effort: "low",
43179
- refine: { provider: CODEX, model: GPT_5_6_LUNA },
43180
- plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "low" },
43181
- implement: {
43182
- generator: { provider: CLAUDE, model: SONNET, effort: "low" },
43183
- evaluator: { provider: CLAUDE, model: SONNET, effort: "low" }
43184
- },
43185
- readiness: { provider: COPILOT, model: GPT_5_6_LUNA, effort: "low" },
43186
- ideate: { provider: CLAUDE, model: SONNET, effort: "low" },
43187
- createPr: { provider: CODEX, model: GPT_5_6_LUNA }
43188
- };
43189
- var CLAUDE_FAST = {
43190
- effort: "low",
43191
- refine: { provider: CLAUDE, model: SONNET, effort: "low" },
43192
- plan: { provider: CLAUDE, model: SONNET, effort: "low" },
43193
- implement: {
43194
- generator: { provider: CLAUDE, model: SONNET, effort: "low" },
43195
- evaluator: { provider: CLAUDE, model: SONNET, effort: "low" }
43196
- },
43197
- readiness: { provider: CLAUDE, model: SONNET, effort: "low" },
43198
- ideate: { provider: CLAUDE, model: SONNET, effort: "low" },
43199
- createPr: { provider: CLAUDE, model: SONNET, effort: "low" }
43200
- };
43201
- var COPILOT_FAST = {
43202
- effort: "low",
43203
- refine: { provider: COPILOT, model: GPT_5_6_LUNA },
43204
- plan: { provider: COPILOT, model: COPILOT_SONNET, effort: "low" },
43205
- implement: {
43206
- generator: { provider: COPILOT, model: COPILOT_SONNET, effort: "low" },
43207
- evaluator: { provider: COPILOT, model: COPILOT_SONNET, effort: "low" }
43208
- },
43209
- readiness: { provider: COPILOT, model: GPT_5_6_LUNA, effort: "low" },
43210
- ideate: { provider: COPILOT, model: GPT_5_6_LUNA },
43211
- createPr: { provider: COPILOT, model: GPT_5_6_LUNA }
43212
- };
43213
- var CODEX_FAST = {
43214
- effort: "low",
43215
- refine: { provider: CODEX, model: GPT_5_6_LUNA },
43216
- plan: { provider: CODEX, model: GPT_5_6_LUNA, effort: "low" },
43217
- implement: {
43218
- generator: { provider: CODEX, model: GPT_5_6_LUNA, effort: "low" },
43219
- evaluator: { provider: CODEX, model: GPT_5_6_LUNA, effort: "low" }
43220
- },
43221
- readiness: { provider: CODEX, model: GPT_5_6_LUNA },
43222
- ideate: { provider: CODEX, model: GPT_5_6_LUNA },
43223
- createPr: { provider: CODEX, model: GPT_5_6_LUNA }
43224
- };
43225
- var MIXED_FRONTIER = {
43226
- effort: "max",
43227
- refine: { provider: CODEX, model: GPT_5_6_SOL2 },
43228
- plan: { provider: CLAUDE, model: OPUS, effort: "max" },
43229
- implement: {
43230
- generator: { provider: CLAUDE, model: OPUS, effort: "max" },
43231
- // Same cross-provider gate as `mixed`, at the frontier tier.
43232
- evaluator: { provider: CODEX, model: GPT_5_6_SOL2, effort: "max" }
43233
- },
43234
- readiness: { provider: CLAUDE, model: OPUS, effort: "high" },
43235
- ideate: { provider: CLAUDE, model: OPUS },
43236
- createPr: { provider: CODEX, model: GPT_5_6_SOL2 }
43237
- };
43238
- var CLAUDE_FRONTIER = {
43239
- effort: "max",
43240
- refine: { provider: CLAUDE, model: OPUS },
43241
- plan: { provider: CLAUDE, model: OPUS, effort: "max" },
43242
- implement: {
43243
- generator: { provider: CLAUDE, model: OPUS, effort: "max" },
43244
- evaluator: { provider: CLAUDE, model: OPUS, effort: "max" }
43245
- },
43246
- readiness: { provider: CLAUDE, model: OPUS, effort: "high" },
43247
- ideate: { provider: CLAUDE, model: OPUS },
43248
- createPr: { provider: CLAUDE, model: OPUS }
43249
- };
43250
- var COPILOT_FRONTIER = {
43251
- effort: "max",
43252
- refine: { provider: COPILOT, model: COPILOT_OPUS },
43253
- plan: { provider: COPILOT, model: COPILOT_OPUS, effort: "max" },
43254
- implement: {
43255
- generator: { provider: COPILOT, model: COPILOT_OPUS, effort: "max" },
43256
- evaluator: { provider: COPILOT, model: COPILOT_OPUS, effort: "max" }
43257
- },
43258
- readiness: { provider: COPILOT, model: COPILOT_OPUS, effort: "high" },
43259
- ideate: { provider: COPILOT, model: COPILOT_OPUS },
43260
- createPr: { provider: COPILOT, model: COPILOT_OPUS }
43261
- };
43262
- var CODEX_FRONTIER = {
43263
- effort: "max",
43264
- refine: { provider: CODEX, model: GPT_5_6_SOL2 },
43265
- plan: { provider: CODEX, model: GPT_5_6_SOL2, effort: "max" },
43266
- implement: {
43267
- generator: { provider: CODEX, model: GPT_5_6_SOL2, effort: "max" },
43268
- evaluator: { provider: CODEX, model: GPT_5_6_SOL2, effort: "max" }
43269
- },
43270
- readiness: { provider: CODEX, model: GPT_5_6_SOL2, effort: "high" },
43271
- ideate: { provider: CODEX, model: GPT_5_6_SOL2 },
43272
- createPr: { provider: CODEX, model: GPT_5_6_SOL2 }
43273
- };
43274
43800
  var PRESETS = {
43275
43801
  mixed: { ai: MIXED, escalateOnPlateau: true },
43276
43802
  "claude-only": { ai: CLAUDE_ONLY, escalateOnPlateau: true },
@@ -43282,18 +43808,22 @@ var PRESETS = {
43282
43808
  "claude-economic": { ai: CLAUDE_ECONOMIC, escalateOnPlateau: true, bestOfNCandidates: 0 },
43283
43809
  "copilot-economic": { ai: COPILOT_ECONOMIC, escalateOnPlateau: true, bestOfNCandidates: 0 },
43284
43810
  "codex-economic": { ai: CODEX_ECONOMIC, escalateOnPlateau: true, bestOfNCandidates: 0 },
43811
+ "grok-economic": { ai: GROK_ECONOMIC, escalateOnPlateau: true, bestOfNCandidates: 0 },
43285
43812
  "mixed-strong-gate": { ai: MIXED_STRONG_GATE, escalateOnPlateau: true },
43286
43813
  "claude-strong-gate": { ai: CLAUDE_STRONG_GATE, escalateOnPlateau: true },
43287
43814
  "copilot-strong-gate": { ai: COPILOT_STRONG_GATE, escalateOnPlateau: true },
43288
43815
  "codex-strong-gate": { ai: CODEX_STRONG_GATE, escalateOnPlateau: true },
43816
+ "grok-strong-gate": { ai: GROK_STRONG_GATE, escalateOnPlateau: true },
43289
43817
  "mixed-fast": { ai: MIXED_FAST, escalateOnPlateau: false },
43290
43818
  "claude-fast": { ai: CLAUDE_FAST, escalateOnPlateau: false },
43291
43819
  "copilot-fast": { ai: COPILOT_FAST, escalateOnPlateau: false },
43292
43820
  "codex-fast": { ai: CODEX_FAST, escalateOnPlateau: false },
43821
+ "grok-fast": { ai: GROK_FAST, escalateOnPlateau: false },
43293
43822
  "mixed-frontier": { ai: MIXED_FRONTIER, escalateOnPlateau: true },
43294
43823
  "claude-frontier": { ai: CLAUDE_FRONTIER, escalateOnPlateau: true },
43295
43824
  "copilot-frontier": { ai: COPILOT_FRONTIER, escalateOnPlateau: true },
43296
- "codex-frontier": { ai: CODEX_FRONTIER, escalateOnPlateau: true }
43825
+ "codex-frontier": { ai: CODEX_FRONTIER, escalateOnPlateau: true },
43826
+ "grok-frontier": { ai: GROK_FRONTIER, escalateOnPlateau: true }
43297
43827
  };
43298
43828
  var applyPreset = (name, current) => {
43299
43829
  const preset = PRESETS[name];
@@ -43321,18 +43851,22 @@ var PRESET_LABEL = {
43321
43851
  "claude-economic": "Apply: Claude (economic)",
43322
43852
  "copilot-economic": "Apply: Copilot (economic)",
43323
43853
  "codex-economic": "Apply: Codex (economic)",
43854
+ "grok-economic": "Apply: Grok (economic)",
43324
43855
  "mixed-strong-gate": "Apply: Mixed strong-gate",
43325
43856
  "claude-strong-gate": "Apply: Claude strong-gate",
43326
43857
  "copilot-strong-gate": "Apply: Copilot strong-gate",
43327
43858
  "codex-strong-gate": "Apply: Codex strong-gate",
43859
+ "grok-strong-gate": "Apply: Grok strong-gate",
43328
43860
  "mixed-fast": "Apply: Mixed (fast)",
43329
43861
  "claude-fast": "Apply: Claude (fast)",
43330
43862
  "copilot-fast": "Apply: Copilot (fast)",
43331
43863
  "codex-fast": "Apply: Codex (fast)",
43864
+ "grok-fast": "Apply: Grok (fast)",
43332
43865
  "mixed-frontier": "Apply: Mixed (frontier)",
43333
43866
  "claude-frontier": "Apply: Claude (frontier)",
43334
43867
  "copilot-frontier": "Apply: Copilot (frontier)",
43335
- "codex-frontier": "Apply: Codex (frontier)"
43868
+ "codex-frontier": "Apply: Codex (frontier)",
43869
+ "grok-frontier": "Apply: Grok (frontier)"
43336
43870
  };
43337
43871
  var PRESET_FAMILY_LABEL = {
43338
43872
  standard: "Standard",
@@ -43353,18 +43887,22 @@ var PRESET_FAMILY = {
43353
43887
  "claude-economic": "economic",
43354
43888
  "copilot-economic": "economic",
43355
43889
  "codex-economic": "economic",
43890
+ "grok-economic": "economic",
43356
43891
  "mixed-strong-gate": STRONG_GATE,
43357
43892
  "claude-strong-gate": STRONG_GATE,
43358
43893
  "copilot-strong-gate": STRONG_GATE,
43359
43894
  "codex-strong-gate": STRONG_GATE,
43895
+ "grok-strong-gate": STRONG_GATE,
43360
43896
  "mixed-fast": "fast",
43361
43897
  "claude-fast": "fast",
43362
43898
  "copilot-fast": "fast",
43363
43899
  "codex-fast": "fast",
43900
+ "grok-fast": "fast",
43364
43901
  "mixed-frontier": "frontier",
43365
43902
  "claude-frontier": "frontier",
43366
43903
  "copilot-frontier": "frontier",
43367
- "codex-frontier": "frontier"
43904
+ "codex-frontier": "frontier",
43905
+ "grok-frontier": "frontier"
43368
43906
  };
43369
43907
  var LOG_LEVELS = ["silent", "debug", "info", "warn", "error"];
43370
43908
  var DEFAULT_TOKEN = "Default";
@@ -43390,12 +43928,11 @@ var escalationTargetsFor = (from) => {
43390
43928
  const pool = owning.length > 0 ? owning.flat() : MODEL_CATALOGS.flat();
43391
43929
  return [...new Set(pool)].filter((m) => m !== from);
43392
43930
  };
43393
- var effectiveEscalationChains = (user) => {
43394
- const merged = mergeEscalationMap(user);
43931
+ var chainsOf = (provider, merged, user, isRoot) => {
43395
43932
  const targets = new Set(Object.values(merged));
43396
43933
  const chains = [];
43397
43934
  for (const root of Object.keys(merged)) {
43398
- if (targets.has(root)) continue;
43935
+ if (targets.has(root) || !isRoot(root)) continue;
43399
43936
  const models = [root];
43400
43937
  const seen = /* @__PURE__ */ new Set([root]);
43401
43938
  let customised = root in user;
@@ -43408,10 +43945,19 @@ var effectiveEscalationChains = (user) => {
43408
43945
  prev = cur;
43409
43946
  cur = merged[cur];
43410
43947
  }
43411
- chains.push({ models, customised });
43948
+ chains.push({ provider, models, customised });
43412
43949
  }
43413
43950
  return chains;
43414
43951
  };
43952
+ var effectiveEscalationChains = (user) => {
43953
+ const perProvider = AI_PROVIDERS2.flatMap((provider) => {
43954
+ const catalog = new Set(PROVIDER_TRAITS[provider].modelCatalog);
43955
+ return chainsOf(provider, mergeEscalationMap(user, provider), user, (m) => catalog.has(m));
43956
+ });
43957
+ const catalogued = new Set(MODEL_CATALOGS.flat());
43958
+ const custom = chainsOf(void 0, mergeEscalationMap(user, void 0), user, (m) => !catalogued.has(m));
43959
+ return [...perProvider, ...custom];
43960
+ };
43415
43961
  var modelOptionsFor = (provider) => PROVIDER_TRAITS[provider].modelCatalog;
43416
43962
  var capitalize = (s) => s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
43417
43963
  var FLOW_DISPLAY_LABEL = { createPr: "Create-PR" };
@@ -43971,6 +44517,8 @@ var applyFixedSettingsKey = (current, key2, raw) => {
43971
44517
  return Result.ok({ ...current, logging: { level: raw } });
43972
44518
  case "concurrency.maxParallelTasks":
43973
44519
  return applyNumberField(current, key2, raw, (c, n) => ({ ...c, concurrency: { maxParallelTasks: n } }));
44520
+ // Key is still addressable so existing settings files and `settings set` keep parsing;
44521
+ // the flag does not post a comment.
43974
44522
  case "scm.postRefinementComment":
43975
44523
  return applyBooleanField(current, key2, raw, (c, b) => ({
43976
44524
  ...c,
@@ -44176,9 +44724,11 @@ var HarnessRow = ({ title, fields, valueFor }) => {
44176
44724
  ] }),
44177
44725
  chains.map((chain) => /* @__PURE__ */ jsxs69(Text68, { dimColor: true, children: [
44178
44726
  " ",
44727
+ chain.provider ?? "any provider",
44728
+ ": ",
44179
44729
  chain.models.join(` ${glyphs.arrowRight} `),
44180
44730
  chain.customised ? " (customised)" : ""
44181
- ] }, chain.models[0]))
44731
+ ] }, `${chain.provider ?? "custom"}:${chain.models[0] ?? ""}`))
44182
44732
  ] })
44183
44733
  ] });
44184
44734
  };
@@ -45133,7 +45683,7 @@ import "react";
45133
45683
  import { Box as Box78, Text as Text74 } from "ink";
45134
45684
 
45135
45685
  // src/application/ui/tui/runtime/use-markdown-export.ts
45136
- import { useCallback as useCallback18, useEffect as useEffect37, useRef as useRef16, useState as useState41 } from "react";
45686
+ import { useCallback as useCallback18, useEffect as useEffect37, useRef as useRef17, useState as useState41 } from "react";
45137
45687
  import { join as join81 } from "path";
45138
45688
  import { useInput as useInput22 } from "ink";
45139
45689
  var useMarkdownExport = (config) => {
@@ -45141,10 +45691,10 @@ var useMarkdownExport = (config) => {
45141
45691
  const selection = useSelection();
45142
45692
  const ui = useUiState();
45143
45693
  const [run, setRun] = useState41({ kind: "idle" });
45144
- const runGenRef = useRef16(0);
45145
- const runFnRef = useRef16(config.run);
45694
+ const runGenRef = useRef17(0);
45695
+ const runFnRef = useRef17(config.run);
45146
45696
  runFnRef.current = config.run;
45147
- const filenameRef = useRef16(config.filename);
45697
+ const filenameRef = useRef17(config.filename);
45148
45698
  filenameRef.current = config.filename;
45149
45699
  useViewHints([{ keys: "r", label: "rerun" }]);
45150
45700
  const runExport = useCallback18(async () => {
@@ -45945,6 +46495,7 @@ var generatePrContentLeaf = (deps) => leaf(LEAF_NAME11, {
45945
46495
  cwd: input.unitRoot,
45946
46496
  additionalRoots: [input.repoPath],
45947
46497
  model: deps.model,
46498
+ ...deps.effort !== void 0 ? { effort: deps.effort } : {},
45948
46499
  permissions: PR_AUTHORING_PERMISSIONS,
45949
46500
  signalsFile: signalsFilePathResult.value,
45950
46501
  outputDir: input.unitRoot,
@@ -45996,11 +46547,11 @@ var createCreatePrFlow = (deps, opts = {}) => {
45996
46547
  outputContractSection: renderContractSectionFor(generatePrContentOutputContract, currentUnitRoot)
45997
46548
  });
45998
46549
  },
45999
- // create-pr does not yet thread `settings.ai.createPr.provider` through as a plain string
46000
- // (see `CreateCreatePrFlowOpts.providerId` doc) — falls back to a placeholder rather than
46001
- // failing the sidecar write.
46550
+ // The CLI / TUI surfaces thread `settings.ai.createPr.provider`; a caller that omits it
46551
+ // (see `CreateCreatePrFlowOpts.providerId`) gets a placeholder rather than a failed write.
46002
46552
  providerId: opts.providerId ?? "unknown",
46003
- model: deps.model
46553
+ model: deps.model,
46554
+ ...deps.effort !== void 0 ? { effort: deps.effort } : {}
46004
46555
  };
46005
46556
  children.push(
46006
46557
  createLoadCreatePrContextLeaf(deps),
@@ -46019,7 +46570,8 @@ var createCreatePrFlow = (deps, opts = {}) => {
46019
46570
  writeFile: deps.writeFile,
46020
46571
  eventBus: deps.eventBus,
46021
46572
  logger: deps.logger,
46022
- model: deps.model
46573
+ model: deps.model,
46574
+ ...deps.effort !== void 0 ? { effort: deps.effort } : {}
46023
46575
  }),
46024
46576
  ...aiUnitEpilogue({ skillsAdapter: deps.skillsAdapter }, unitOpts)
46025
46577
  );
@@ -46131,6 +46683,7 @@ var resolveCreatePrInputs = async (deps, sprintId, useAi) => {
46131
46683
  var executeCreatePrFlow = async (args) => {
46132
46684
  const { deps, sprintId, sprintDir: sprintDir2, cwd, useAi } = args;
46133
46685
  const resolvedProvider = deps.settings.ai.createPr.provider;
46686
+ const effort = resolveEffort("createPr", deps.settings);
46134
46687
  const provider = createAiProvider({
46135
46688
  flow: "createPr",
46136
46689
  ai: deps.settings.ai,
@@ -46160,10 +46713,11 @@ var executeCreatePrFlow = async (args) => {
46160
46713
  writeFile: deps.writeFile,
46161
46714
  logger: deps.logger,
46162
46715
  model: deps.settings.ai.createPr.model,
46716
+ ...effort !== void 0 ? { effort } : {},
46163
46717
  skillSource,
46164
46718
  skillsAdapter
46165
46719
  },
46166
- { useAi }
46720
+ { useAi, providerId: resolvedProvider }
46167
46721
  );
46168
46722
  const result = await flow.execute({
46169
46723
  input: {
@@ -46243,7 +46797,7 @@ var renderBody2 = (prep, run, useAi) => {
46243
46797
  };
46244
46798
 
46245
46799
  // src/application/ui/tui/views/welcome-view.tsx
46246
- import { useCallback as useCallback20, useEffect as useEffect39, useRef as useRef17, useState as useState43 } from "react";
46800
+ import { useCallback as useCallback20, useEffect as useEffect39, useRef as useRef18, useState as useState43 } from "react";
46247
46801
  import { Box as Box80, Text as Text76, useInput as useInput24 } from "ink";
46248
46802
  import { jsx as jsx101, jsxs as jsxs76 } from "react/jsx-runtime";
46249
46803
  var PRESET_FOR_PROVIDER = {
@@ -46275,7 +46829,7 @@ var useWelcomeSeeding = () => {
46275
46829
  const [noCliDetected, setNoCliDetected] = useState43(false);
46276
46830
  const [errorMsg, setErrorMsg] = useState43(void 0);
46277
46831
  const [pendingRoute, setPendingRoute] = useState43(void 0);
46278
- const seededRef = useRef17(false);
46832
+ const seededRef = useRef18(false);
46279
46833
  useEffect39(() => {
46280
46834
  if (seededRef.current) return;
46281
46835
  seededRef.current = true;
@@ -46967,12 +47521,67 @@ var createTicketAddFlow = (deps) => leaf("add-ticket", {
46967
47521
  if (!updated.ok) return Result.error(updated.error);
46968
47522
  const saved = await deps.sprintRepo.save(updated.value);
46969
47523
  if (!saved.ok) return Result.error(saved.error);
46970
- return Result.ok(ticket.value);
47524
+ if (input.createTrackerIssue !== true) {
47525
+ return Result.ok({ ticket: ticket.value });
47526
+ }
47527
+ const published = await publishAfterSave(deps, updated.value, ticket.value);
47528
+ return Result.ok(published);
46971
47529
  }
46972
47530
  },
46973
47531
  input: (c) => c.input,
46974
- output: (c, o) => ({ ...c, output: o })
47532
+ output: (c, o) => ({
47533
+ ...c,
47534
+ output: o.ticket,
47535
+ ...o.trackerError !== void 0 ? { trackerError: o.trackerError } : {},
47536
+ ...o.trackerIssueOrphaned === true ? { trackerIssueOrphaned: true } : {}
47537
+ })
46975
47538
  });
47539
+ var publishAfterSave = async (deps, sprint, ticket) => {
47540
+ const tracker = deps.issuePusher;
47541
+ const projectRepo = deps.projectRepo;
47542
+ if (tracker === void 0 || projectRepo === void 0) {
47543
+ return {
47544
+ ticket,
47545
+ trackerError: new StorageError({
47546
+ subCode: "io",
47547
+ message: "issue tracker is unavailable"
47548
+ })
47549
+ };
47550
+ }
47551
+ const project = await projectRepo.findById(sprint.projectId);
47552
+ if (!project.ok) {
47553
+ return { ticket, trackerError: project.error };
47554
+ }
47555
+ const cwd = project.value.repositories[0]?.path;
47556
+ if (cwd === void 0) {
47557
+ return {
47558
+ ticket,
47559
+ trackerError: new StorageError({
47560
+ subCode: "io",
47561
+ message: "Project has no repositories \u2014 add one first."
47562
+ })
47563
+ };
47564
+ }
47565
+ const published = await publishTicketToTracker({
47566
+ sprint,
47567
+ ticketId: ticket.id,
47568
+ cwd,
47569
+ issuePusher: tracker
47570
+ });
47571
+ if (!published.ok) {
47572
+ return { ticket, trackerError: published.error };
47573
+ }
47574
+ const relinked = await deps.sprintRepo.save(published.value.sprint);
47575
+ if (!relinked.ok) {
47576
+ return {
47577
+ ticket,
47578
+ trackerError: publishSaveFailedError(published.value, ticket.id, relinked.error),
47579
+ ...published.value.outcome === "created" ? { trackerIssueOrphaned: true } : {}
47580
+ };
47581
+ }
47582
+ const withLink = published.value.sprint.tickets.find((t) => t.id === ticket.id) ?? ticket;
47583
+ return { ticket: withLink };
47584
+ };
46976
47585
 
46977
47586
  // src/application/ui/tui/views/add-ticket-internals/header-card.tsx
46978
47587
  import "react";
@@ -47010,7 +47619,7 @@ var HeaderCard2 = ({
47010
47619
  return /* @__PURE__ */ jsx105(Card, { title: "Progress", tone: "rule", children: /* @__PURE__ */ jsx105(Box84, { flexDirection: "column", paddingX: spacing.indent, children: /* @__PURE__ */ jsx105(FieldList, { fields: collected }) }) });
47011
47620
  };
47012
47621
  var collectedFields = (step) => {
47013
- if (step.kind === "confirm") return [];
47622
+ if (step.kind === "confirm" || step.kind === "ask-create") return [];
47014
47623
  const fields = [];
47015
47624
  const linkFor = (s) => {
47016
47625
  if (s.kind === "fetching" || s.kind === "fetch-failed" || s.kind === "title" || s.kind === "description") {
@@ -47057,9 +47666,17 @@ var backStep3 = (step) => {
47057
47666
  title: step.title,
47058
47667
  descriptionInitial: step.description
47059
47668
  };
47669
+ case "ask-create":
47670
+ return {
47671
+ kind: "confirm",
47672
+ link: step.link,
47673
+ title: step.title,
47674
+ description: step.description
47675
+ };
47060
47676
  case "saving":
47061
47677
  case "added":
47062
47678
  case "error":
47679
+ case "create-failed":
47063
47680
  return void 0;
47064
47681
  }
47065
47682
  };
@@ -47228,6 +47845,17 @@ var renderConfirmStep2 = (step, ctx, onSubmit) => {
47228
47845
  ) })
47229
47846
  ] });
47230
47847
  };
47848
+ var renderAskCreateStep = (step, ctx, onSubmit) => /* @__PURE__ */ jsx107(Box86, { flexDirection: "column", paddingX: spacing.indent, children: /* @__PURE__ */ jsx107(
47849
+ ConfirmPrompt,
47850
+ {
47851
+ message: "Create a tracker issue?",
47852
+ defaultYes: false,
47853
+ onSubmit: (value) => {
47854
+ void onSubmit({ ...step, createTrackerIssue: value });
47855
+ },
47856
+ onCancel: ctx.cancelOrBack
47857
+ }
47858
+ ) });
47231
47859
  var renderSavingStep = () => /* @__PURE__ */ jsx107(Spinner, { label: "saving sprint\u2026" });
47232
47860
  var renderAddedStep = (step, onChange, onCancel) => {
47233
47861
  const plural4 = step.count === 1 ? "ticket" : "tickets";
@@ -47255,12 +47883,43 @@ var renderAddedStep = (step, onChange, onCancel) => {
47255
47883
  ) })
47256
47884
  ] });
47257
47885
  };
47258
- var renderErrorStep2 = (step) => /* @__PURE__ */ jsxs82(Box86, { flexDirection: "column", paddingX: spacing.indent, children: [
47886
+ var renderErrorStep2 = (step, onChange, onCancel) => /* @__PURE__ */ jsxs82(Box86, { flexDirection: "column", paddingX: spacing.indent, children: [
47259
47887
  /* @__PURE__ */ jsxs82(Text82, { color: inkColors.error, children: [
47260
- "\u2717 ",
47888
+ glyphs.cross,
47889
+ " ",
47890
+ step.message
47891
+ ] }),
47892
+ /* @__PURE__ */ jsx107(Text82, { dimColor: true, children: "Nothing was saved." }),
47893
+ /* @__PURE__ */ jsx107(Box86, { marginTop: spacing.section, children: /* @__PURE__ */ jsx107(
47894
+ ConfirmPrompt,
47895
+ {
47896
+ message: "Start over with a new ticket?",
47897
+ onSubmit: (value) => {
47898
+ if (value) onChange({ kind: "link" });
47899
+ else onCancel();
47900
+ },
47901
+ onCancel
47902
+ }
47903
+ ) })
47904
+ ] });
47905
+ var renderCreateFailedStep = (step, onChange, onCancel) => /* @__PURE__ */ jsxs82(Box86, { flexDirection: "column", paddingX: spacing.indent, children: [
47906
+ /* @__PURE__ */ jsxs82(Text82, { color: inkColors.error, children: [
47907
+ glyphs.cross,
47908
+ " ",
47261
47909
  step.message
47262
47910
  ] }),
47263
- /* @__PURE__ */ jsx107(Text82, { dimColor: true, children: "Press esc to go back." })
47911
+ /* @__PURE__ */ jsx107(Text82, { dimColor: true, children: step.hint }),
47912
+ /* @__PURE__ */ jsx107(Box86, { marginTop: spacing.section, children: /* @__PURE__ */ jsx107(
47913
+ ConfirmPrompt,
47914
+ {
47915
+ message: "Add another ticket?",
47916
+ onSubmit: (value) => {
47917
+ if (value) onChange({ kind: "link" });
47918
+ else onCancel();
47919
+ },
47920
+ onCancel
47921
+ }
47922
+ ) })
47264
47923
  ] });
47265
47924
  var StepView3 = ({ step, onChange, onCancel, onSubmit }) => {
47266
47925
  const prev = backStep3(step);
@@ -47280,12 +47939,16 @@ var StepView3 = ({ step, onChange, onCancel, onSubmit }) => {
47280
47939
  return renderDescriptionStep2(step, ctx);
47281
47940
  case "confirm":
47282
47941
  return renderConfirmStep2(step, ctx, onSubmit);
47942
+ case "ask-create":
47943
+ return renderAskCreateStep(step, ctx, onSubmit);
47283
47944
  case "saving":
47284
47945
  return renderSavingStep();
47285
47946
  case "added":
47286
47947
  return renderAddedStep(step, onChange, onCancel);
47287
47948
  case "error":
47288
- return renderErrorStep2(step);
47949
+ return renderErrorStep2(step, onChange, onCancel);
47950
+ case "create-failed":
47951
+ return renderCreateFailedStep(step, onChange, onCancel);
47289
47952
  }
47290
47953
  };
47291
47954
  var runFetch = async (fetcher, url) => {
@@ -47304,6 +47967,58 @@ var runFetch = async (fetcher, url) => {
47304
47967
 
47305
47968
  // src/application/ui/tui/views/add-ticket-view.tsx
47306
47969
  import { jsx as jsx108, jsxs as jsxs83 } from "react/jsx-runtime";
47970
+ var originResolves = async (deps, sprintId) => {
47971
+ const pusher = deps.issuePusher;
47972
+ if (pusher === void 0) return false;
47973
+ const sprint = await deps.sprintRepo.findById(sprintId);
47974
+ if (!sprint.ok) return false;
47975
+ const project = await deps.projectRepo?.findById(sprint.value.projectId);
47976
+ if (project === void 0 || !project.ok) return false;
47977
+ const cwd = project.value.repositories[0]?.path;
47978
+ if (cwd === void 0) return false;
47979
+ const origin = await pusher.resolveOrigin(cwd);
47980
+ return origin.ok && origin.value !== null;
47981
+ };
47982
+ var createFailedHint = (sprintId, ticketId, orphaned) => {
47983
+ if (orphaned) return "The ticket was saved locally without the link \u2014 do not re-publish it.";
47984
+ const target = ticketId ?? "<ticket-id>";
47985
+ return `The ticket was saved locally \u2014 retry with: ralphctl ticket publish --sprint ${String(sprintId)} ${target}`;
47986
+ };
47987
+ var persistTicket = async (deps, sprintId, draft) => {
47988
+ const description = draft.description.trim();
47989
+ const link = draft.link.trim();
47990
+ const flow = createTicketAddFlow({
47991
+ sprintRepo: deps.sprintRepo,
47992
+ ...deps.projectRepo !== void 0 ? { projectRepo: deps.projectRepo } : {},
47993
+ ...deps.issuePusher !== void 0 ? { issuePusher: deps.issuePusher } : {}
47994
+ });
47995
+ const result = await flow.execute({
47996
+ input: {
47997
+ sprintId,
47998
+ title: draft.title.trim(),
47999
+ ...description.length > 0 ? { description } : {},
48000
+ ...link.length > 0 ? { link } : {},
48001
+ ...draft.createTrackerIssue === true ? { createTrackerIssue: true } : {}
48002
+ }
48003
+ });
48004
+ if (!result.ok) {
48005
+ return { kind: "error", message: result.error.error.message };
48006
+ }
48007
+ const trackerError = result.value.ctx.trackerError;
48008
+ if (trackerError !== void 0) {
48009
+ const ticketId = result.value.ctx.output?.id;
48010
+ return {
48011
+ kind: "create-failed",
48012
+ message: trackerError.message,
48013
+ hint: createFailedHint(
48014
+ sprintId,
48015
+ ticketId !== void 0 ? String(ticketId) : void 0,
48016
+ result.value.ctx.trackerIssueOrphaned === true
48017
+ )
48018
+ };
48019
+ }
48020
+ return { kind: "added" };
48021
+ };
47307
48022
  var AddTicketView = () => {
47308
48023
  const deps = useDeps();
47309
48024
  const router = useRouter();
@@ -47330,28 +48045,36 @@ var AddTicketView = () => {
47330
48045
  cancelled = true;
47331
48046
  };
47332
48047
  }, [step, deps.issueFetcher]);
47333
- const submit = async (s) => {
48048
+ const persist = async (draft) => {
47334
48049
  setStep({ kind: "saving" });
47335
- const description = s.description.trim();
47336
- const link = s.link.trim();
47337
- const flow = createTicketAddFlow({ sprintRepo: deps.sprintRepo });
47338
- const result = await flow.execute({
47339
- input: {
47340
- sprintId,
47341
- title: s.title.trim(),
47342
- ...description.length > 0 ? { description } : {},
47343
- ...link.length > 0 ? { link } : {}
47344
- }
47345
- });
47346
- if (!result.ok) {
47347
- setStep({ kind: "error", message: result.error.error.message });
48050
+ const outcome = await persistTicket(deps, sprintId, draft);
48051
+ if (outcome.kind === "create-failed") {
48052
+ setAddedCount((prev) => prev + 1);
48053
+ setStep(outcome);
47348
48054
  return;
47349
48055
  }
47350
- setAddedCount((prev) => {
47351
- const next = prev + 1;
47352
- setStep({ kind: "added", title: s.title.trim(), count: next });
47353
- return next;
47354
- });
48056
+ if (outcome.kind === "added") {
48057
+ setAddedCount((prev) => {
48058
+ const count2 = prev + 1;
48059
+ setStep({ kind: "added", title: draft.title.trim(), count: count2 });
48060
+ return count2;
48061
+ });
48062
+ return;
48063
+ }
48064
+ setStep(outcome);
48065
+ };
48066
+ const submit = async (draft) => {
48067
+ const shouldAsk = draft.createTrackerIssue === void 0 && draft.link.trim().length === 0 && await originResolves(deps, sprintId);
48068
+ if (shouldAsk) {
48069
+ setStep({
48070
+ kind: "ask-create",
48071
+ link: draft.link,
48072
+ title: draft.title,
48073
+ description: draft.description
48074
+ });
48075
+ return;
48076
+ }
48077
+ await persist(draft);
47355
48078
  };
47356
48079
  return /* @__PURE__ */ jsx108(ViewShell, { title: "Add ticket", subtitle: "Append a pending ticket to the current sprint.", children: /* @__PURE__ */ jsxs83(Box87, { flexDirection: "column", children: [
47357
48080
  /* @__PURE__ */ jsx108(HeaderCard2, { step, addedCount }),
@@ -47949,7 +48672,7 @@ var renderView = (entry) => {
47949
48672
  };
47950
48673
 
47951
48674
  // src/application/ui/tui/runtime/use-global-keys.ts
47952
- import { useEffect as useEffect45, useMemo as useMemo33, useRef as useRef18 } from "react";
48675
+ import { useEffect as useEffect45, useMemo as useMemo33, useRef as useRef19 } from "react";
47953
48676
  import { useApp, useInput as useInput29 } from "ink";
47954
48677
 
47955
48678
  // src/integration/io/clipboard.ts
@@ -48059,7 +48782,7 @@ var useGlobalKeys = (opts = {}) => {
48059
48782
  () => opts.copyToClipboard ?? createCopyToClipboard(),
48060
48783
  [opts.copyToClipboard]
48061
48784
  );
48062
- const clearTimerRef = useRef18(void 0);
48785
+ const clearTimerRef = useRef19(void 0);
48063
48786
  useEffect45(
48064
48787
  () => () => {
48065
48788
  if (clearTimerRef.current !== void 0) clearTimeout(clearTimerRef.current);
@@ -48971,7 +49694,7 @@ import { useState as useState57 } from "react";
48971
49694
  import { useApp as useApp2 } from "ink";
48972
49695
 
48973
49696
  // src/application/ui/tui/migration/migration-gate.tsx
48974
- import { useEffect as useEffect51, useRef as useRef19, useState as useState56 } from "react";
49697
+ import { useEffect as useEffect51, useRef as useRef20, useState as useState56 } from "react";
48975
49698
  import { Box as Box97, Text as Text91, useInput as useInput31 } from "ink";
48976
49699
 
48977
49700
  // src/application/ui/tui/migration/learnings-backfill-adapter.ts
@@ -49018,7 +49741,7 @@ var summarize = (report) => {
49018
49741
  var useMigrationScan = (args) => {
49019
49742
  const { engine, dataRoot, stateRoot, appVersion, now, writeFile: writeFile4, onResolve } = args;
49020
49743
  const [state, setState] = useState56({ kind: "scanning" });
49021
- const scannedRef = useRef19(false);
49744
+ const scannedRef = useRef20(false);
49022
49745
  useEffect51(() => {
49023
49746
  if (scannedRef.current) return;
49024
49747
  scannedRef.current = true;
@@ -50590,6 +51313,7 @@ var registerExportContextCommand = (program) => {
50590
51313
  // src/application/ui/cli/commands/create-pr.ts
50591
51314
  var buildCreatePrFlow = (deps, storage2, useAi) => {
50592
51315
  const resolvedProvider = deps.settings.ai.createPr.provider;
51316
+ const effort = resolveEffort("createPr", deps.settings);
50593
51317
  const provider = createAiProvider({
50594
51318
  flow: "createPr",
50595
51319
  ai: deps.settings.ai,
@@ -50619,10 +51343,11 @@ var buildCreatePrFlow = (deps, storage2, useAi) => {
50619
51343
  writeFile: deps.writeFile,
50620
51344
  logger: deps.logger,
50621
51345
  model: deps.settings.ai.createPr.model,
51346
+ ...effort !== void 0 ? { effort } : {},
50622
51347
  skillSource,
50623
51348
  skillsAdapter
50624
51349
  },
50625
- { useAi }
51350
+ { useAi, providerId: resolvedProvider }
50626
51351
  );
50627
51352
  };
50628
51353
  var createPrAction = async (opts) => {
@@ -52109,11 +52834,47 @@ var removeTicketAction = async (rawTicketId, opts) => {
52109
52834
  `
52110
52835
  );
52111
52836
  };
52837
+ var publishTicketAction = async (rawTicketId, opts) => {
52838
+ const { deps, storage: storage2 } = await bootstrapCli();
52839
+ const sprintId = await resolveSprintId(opts.sprint, storage2.stateRoot);
52840
+ if (!sprintId.ok) {
52841
+ fail(sprintId.error.message);
52842
+ return;
52843
+ }
52844
+ const ticketId = TicketId.parse(rawTicketId);
52845
+ if (!ticketId.ok) {
52846
+ fail(`invalid ticket id: ${ticketId.error.message}`);
52847
+ return;
52848
+ }
52849
+ if (sprintId.value.fromPin) process.stderr.write(pinFallbackNotice(sprintId.value.sprintId));
52850
+ if (deps.issuePusher === void 0) {
52851
+ fail("issue tracker is unavailable");
52852
+ return;
52853
+ }
52854
+ const flow = createTicketPublishFlow({
52855
+ sprintRepo: deps.sprintRepo,
52856
+ projectRepo: deps.projectRepo,
52857
+ issuePusher: deps.issuePusher
52858
+ });
52859
+ const result = await flow.execute({
52860
+ input: { sprintId: sprintId.value.sprintId, ticketId: ticketId.value }
52861
+ });
52862
+ if (!result.ok) {
52863
+ fail(result.error.error.message);
52864
+ return;
52865
+ }
52866
+ const out = result.value.ctx.output;
52867
+ process.stdout.write(
52868
+ `published ticket ${rawTicketId} on sprint ${String(sprintId.value.sprintId)} \u2014 ${out.outcome}
52869
+ `
52870
+ );
52871
+ };
52112
52872
  var registerTicketCommand = (program) => {
52113
52873
  const ticketCmd = program.command("ticket").description("inspect and manage tickets within a sprint");
52114
52874
  ticketCmd.command("list").description("list every ticket on the sprint").option(SPRINT_OPTION_FLAGS, SPRINT_OPTION_DESC).action(listTicketsAction);
52115
52875
  ticketCmd.command("show <ticketId>").description("print a single ticket as JSON").option(SPRINT_OPTION_FLAGS, SPRINT_OPTION_DESC).action(showTicketAction);
52116
52876
  ticketCmd.command("add").description("append a pending ticket to a draft sprint").option(SPRINT_OPTION_FLAGS, SPRINT_OPTION_DESC).requiredOption("-t, --title <title>", "ticket title").option("-d, --description <text>", "optional description").option("-l, --link <url>", "optional issue link (http/https)").action(addTicketAction);
52877
+ ticketCmd.command("publish <ticketId>").description("create a tracker issue or post an approved-requirements comment").option(SPRINT_OPTION_FLAGS, SPRINT_OPTION_DESC).action(publishTicketAction);
52117
52878
  ticketCmd.command("remove <ticketId>").description("drop a ticket from a draft sprint").option(SPRINT_OPTION_FLAGS, SPRINT_OPTION_DESC).option("-y, --yes", "skip the interactive y/N confirmation").action(removeTicketAction);
52118
52879
  };
52119
52880
  var formatTicketLine = (t) => {