ralphctl 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -390,7 +390,7 @@ import { z } from "zod";
390
390
  var CLAUDE_MODELS = [
391
391
  "claude-haiku-4-5",
392
392
  "claude-sonnet-4-6",
393
- "claude-opus-4-7"
393
+ "claude-opus-4-8"
394
394
  ];
395
395
  var isClaudeModel = (s) => CLAUDE_MODELS.includes(s);
396
396
 
@@ -481,7 +481,35 @@ var seedLegacyCreatePrRow = (ai) => {
481
481
  if (!("provider" in refine)) return ai;
482
482
  return { ...aiObj, createPr: { ...refine } };
483
483
  };
484
- var promoteLegacyAiRows = (ai) => seedLegacyCreatePrRow(promoteLegacyImplementRow(ai));
484
+ var RETIRED_CLAUDE_OPUS = "claude-opus-4-7";
485
+ var SUCCESSOR_CLAUDE_OPUS = "claude-opus-4-8";
486
+ var migrateRetiredOpusRow = (row) => {
487
+ if (typeof row !== "object" || row === null) return row;
488
+ const rowObj = row;
489
+ if (rowObj["provider"] === "claude-code" && rowObj["model"] === RETIRED_CLAUDE_OPUS) {
490
+ return { ...rowObj, model: SUCCESSOR_CLAUDE_OPUS };
491
+ }
492
+ return row;
493
+ };
494
+ var migrateRetiredClaudeOpus = (ai) => {
495
+ if (typeof ai !== "object" || ai === null) return ai;
496
+ const aiObj = ai;
497
+ const next = { ...aiObj };
498
+ for (const flow of ["refine", "plan", "readiness", "ideate", "createPr"]) {
499
+ if (flow in next) next[flow] = migrateRetiredOpusRow(next[flow]);
500
+ }
501
+ const implement = next["implement"];
502
+ if (typeof implement === "object" && implement !== null) {
503
+ const implObj = implement;
504
+ next["implement"] = {
505
+ ...implObj,
506
+ generator: migrateRetiredOpusRow(implObj["generator"]),
507
+ evaluator: migrateRetiredOpusRow(implObj["evaluator"])
508
+ };
509
+ }
510
+ return next;
511
+ };
512
+ var promoteLegacyAiRows = (ai) => migrateRetiredClaudeOpus(seedLegacyCreatePrRow(promoteLegacyImplementRow(ai)));
485
513
  var AiSettingsSchema = z.preprocess(
486
514
  promoteLegacyAiRows,
487
515
  z.object({
@@ -575,10 +603,10 @@ var primaryFlowRow = (ai, flow) => {
575
603
  var DEFAULT_MODELS_BY_PROVIDER = {
576
604
  "claude-code": {
577
605
  refine: "claude-sonnet-4-6",
578
- plan: "claude-opus-4-7",
579
- implement: "claude-opus-4-7",
606
+ plan: "claude-opus-4-8",
607
+ implement: "claude-opus-4-8",
580
608
  readiness: "claude-sonnet-4-6",
581
- ideate: "claude-opus-4-7",
609
+ ideate: "claude-opus-4-8",
582
610
  // PR-content drafting is a single-shot summarisation task — Sonnet matches refine's
583
611
  // light reasoning profile and avoids the Opus premium for a few-paragraph diff write-up.
584
612
  createPr: "claude-sonnet-4-6"
@@ -617,7 +645,7 @@ var DEFAULT_SETTINGS = {
617
645
  ai: {
618
646
  ...defaultAiSettingsForProvider("claude-code"),
619
647
  implement: {
620
- generator: { provider: "claude-code", model: "claude-opus-4-7" },
648
+ generator: { provider: "claude-code", model: "claude-opus-4-8" },
621
649
  evaluator: { provider: "openai-codex", model: "gpt-5.5" }
622
650
  }
623
651
  },
@@ -754,13 +782,13 @@ var createJsonSettingsRepository = (deps) => {
754
782
  import { spawn as nodeSpawn } from "child_process";
755
783
  var DEFAULT_GIT_TIMEOUT_MS = 3e4;
756
784
  var createGitRunner = (deps = {}) => {
757
- const spawn3 = deps.spawn ?? defaultSpawn;
785
+ const spawn2 = deps.spawn ?? defaultSpawn;
758
786
  const defaultTimeoutMs = deps.defaultTimeoutMs ?? DEFAULT_GIT_TIMEOUT_MS;
759
787
  const run = (cwd, args, opts = {}) => new Promise((resolve) => {
760
788
  const timeoutMs = opts.timeoutMs ?? defaultTimeoutMs;
761
789
  let child;
762
790
  try {
763
- child = spawn3("git", args, {
791
+ child = spawn2("git", args, {
764
792
  stdio: ["pipe", "pipe", "pipe"],
765
793
  cwd: String(cwd)
766
794
  });
@@ -837,7 +865,7 @@ import { spawn as nodeSpawn2 } from "child_process";
837
865
  var DEFAULT_SHELL_TIMEOUT_MS = 5 * 6e4;
838
866
  var MAX_OUTPUT_BYTES = 50 * 1024 * 1024;
839
867
  var createShellScriptRunner = (deps = {}) => {
840
- const spawn3 = deps.spawn ?? defaultSpawn2;
868
+ const spawn2 = deps.spawn ?? defaultSpawn2;
841
869
  const defaultTimeoutMs = deps.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
842
870
  const now = deps.now ?? Date.now;
843
871
  const run = (cwd, script, opts = {}) => new Promise((resolve) => {
@@ -845,7 +873,7 @@ var createShellScriptRunner = (deps = {}) => {
845
873
  const timeoutMs = opts.timeoutMs ?? defaultTimeoutMs;
846
874
  let child;
847
875
  try {
848
- child = spawn3(script, [], {
876
+ child = spawn2(script, [], {
849
877
  stdio: ["pipe", "pipe", "pipe"],
850
878
  cwd: String(cwd),
851
879
  shell: true,
@@ -2475,13 +2503,21 @@ var CONTEXT_WINDOW = {
2475
2503
  // Claude (claude-code adapter — direct from Anthropic)
2476
2504
  "claude-haiku-4-5": 2e5,
2477
2505
  "claude-sonnet-4-6": 2e5,
2478
- "claude-opus-4-7": 2e5
2506
+ "claude-opus-4-8": 2e5
2479
2507
  };
2480
2508
  var contextWindowFor = (model) => {
2481
2509
  if (model === void 0) return void 0;
2482
2510
  return CONTEXT_WINDOW[model];
2483
2511
  };
2484
2512
 
2513
+ // src/integration/ai/providers/_engine/truncate-debug-field.ts
2514
+ var truncateField = (value, max = 120) => {
2515
+ if (value === void 0 || value === null) return void 0;
2516
+ if (value.length === 0) return void 0;
2517
+ if (value.length <= max) return value;
2518
+ return `${value.slice(0, max)}\u2026`;
2519
+ };
2520
+
2485
2521
  // src/domain/value/error/abort-error.ts
2486
2522
  var AbortError = class extends Error {
2487
2523
  code = ErrorCode.Aborted;
@@ -2561,6 +2597,101 @@ var classifySpawnExit = async (input) => {
2561
2597
 
2562
2598
  // src/integration/ai/providers/claude/headless.ts
2563
2599
  var RATE_LIMIT_RE = /rate.?limit/i;
2600
+ var isRecord2 = (v) => typeof v === "object" && v !== null;
2601
+ var asString = (v) => typeof v === "string" ? v : void 0;
2602
+ var previewArgs = (input) => {
2603
+ if (input === void 0 || input === null) return void 0;
2604
+ if (typeof input === "string") return input;
2605
+ try {
2606
+ const json = JSON.stringify(input);
2607
+ return json === void 0 || json === "{}" || json === "[]" ? void 0 : json;
2608
+ } catch {
2609
+ return void 0;
2610
+ }
2611
+ };
2612
+ var previewToolResult = (content) => {
2613
+ if (typeof content === "string") return content;
2614
+ if (Array.isArray(content)) {
2615
+ const texts = [];
2616
+ for (const part of content) {
2617
+ if (isRecord2(part)) {
2618
+ const t = asString(part["text"]);
2619
+ if (t !== void 0) texts.push(t);
2620
+ }
2621
+ }
2622
+ if (texts.length > 0) return texts.join("\n");
2623
+ }
2624
+ return void 0;
2625
+ };
2626
+ var publishStreamLineEvents = (eventBus, line) => {
2627
+ const json = line.json;
2628
+ if (json === void 0) return;
2629
+ const type = asString(json["type"]);
2630
+ if (type !== "assistant" && type !== "user") return;
2631
+ const message = json["message"];
2632
+ if (!isRecord2(message)) return;
2633
+ const content = message["content"];
2634
+ if (!Array.isArray(content)) return;
2635
+ if (type === "assistant") {
2636
+ const texts = [];
2637
+ const toolUses = [];
2638
+ for (const block of content) {
2639
+ if (!isRecord2(block)) continue;
2640
+ const blockType = asString(block["type"]);
2641
+ if (blockType === "text") {
2642
+ const t = asString(block["text"]);
2643
+ if (t !== void 0) texts.push(t);
2644
+ } else if (blockType === "tool_use") {
2645
+ const name = asString(block["name"]) ?? "";
2646
+ toolUses.push({ name, input: block["input"] });
2647
+ }
2648
+ }
2649
+ if (texts.length > 0) {
2650
+ const text = truncateField(texts.join("\n"));
2651
+ if (text !== void 0) {
2652
+ eventBus.publish({
2653
+ type: "log",
2654
+ level: "debug",
2655
+ message: "claude-provider: assistant",
2656
+ meta: { text },
2657
+ at: IsoTimestamp.now()
2658
+ });
2659
+ }
2660
+ }
2661
+ for (const tool of toolUses) {
2662
+ const args = truncateField(previewArgs(tool.input));
2663
+ eventBus.publish({
2664
+ type: "log",
2665
+ level: "debug",
2666
+ message: "claude-provider: tool_use",
2667
+ meta: {
2668
+ tool: tool.name,
2669
+ ...args !== void 0 ? { args } : {}
2670
+ },
2671
+ at: IsoTimestamp.now()
2672
+ });
2673
+ }
2674
+ return;
2675
+ }
2676
+ for (const block of content) {
2677
+ if (!isRecord2(block)) continue;
2678
+ if (asString(block["type"]) !== "tool_result") continue;
2679
+ const tool = asString(block["name"]) ?? asString(block["tool_use_id"]) ?? "";
2680
+ const status = block["is_error"] === true ? "error" : "ok";
2681
+ const preview = truncateField(previewToolResult(block["content"]));
2682
+ eventBus.publish({
2683
+ type: "log",
2684
+ level: "debug",
2685
+ message: "claude-provider: tool_result",
2686
+ meta: {
2687
+ tool,
2688
+ status,
2689
+ ...preview !== void 0 ? { preview } : {}
2690
+ },
2691
+ at: IsoTimestamp.now()
2692
+ });
2693
+ }
2694
+ };
2564
2695
  var TOOL_EDIT = ["Edit", "MultiEdit", "NotebookEdit"];
2565
2696
  var TOOL_SHELL = ["Bash"];
2566
2697
  var TOOL_NETWORK = ["WebFetch", "WebSearch"];
@@ -2677,6 +2808,7 @@ var spawnAttempt = async (input) => {
2677
2808
  let stderrBuf = "";
2678
2809
  const onLine = (line) => {
2679
2810
  parser.ingest(line);
2811
+ publishStreamLineEvents(deps.eventBus, line);
2680
2812
  };
2681
2813
  const watchdogBannerId = `watchdog-claude-${String(child.pid ?? "unknown")}`;
2682
2814
  const { code, signal } = await runHeadlessSpawn({
@@ -2906,7 +3038,7 @@ var createCodexProvider = (deps) => {
2906
3038
  }
2907
3039
  };
2908
3040
  };
2909
- var consumeMetaLines = (buffer, onMeta) => {
3041
+ var consumeMetaLines = (buffer, onMeta, onLine) => {
2910
3042
  let remaining = buffer;
2911
3043
  while (true) {
2912
3044
  const nl = remaining.indexOf("\n");
@@ -2915,23 +3047,101 @@ var consumeMetaLines = (buffer, onMeta) => {
2915
3047
  remaining = remaining.slice(nl + 1);
2916
3048
  const trimmed = line.trim();
2917
3049
  if (trimmed.length === 0 || !trimmed.startsWith("{")) continue;
3050
+ let obj;
2918
3051
  try {
2919
- const obj = JSON.parse(trimmed);
2920
- const id = stringField2(obj, "thread_id", "session_id", "sessionId");
2921
- const model = stringField2(obj, "model");
2922
- const usageObj = obj["usage"];
2923
- const source = isRecord2(usageObj) ? usageObj : obj;
2924
- const i = numberField2(source, "input_tokens", "inputTokens", "prompt_tokens");
2925
- const o = numberField2(source, "output_tokens", "outputTokens", "completion_tokens");
2926
- if (id === void 0 && model === void 0 && i === void 0 && o === void 0) continue;
2927
- onMeta({
2928
- ...id !== void 0 ? { sessionId: id } : {},
2929
- ...model !== void 0 ? { model } : {},
2930
- ...i !== void 0 ? { inputTokens: i } : {},
2931
- ...o !== void 0 ? { outputTokens: o } : {}
2932
- });
3052
+ obj = JSON.parse(trimmed);
2933
3053
  } catch {
3054
+ continue;
2934
3055
  }
3056
+ if (onLine !== void 0) onLine(obj);
3057
+ const id = stringField2(obj, "thread_id", "session_id", "sessionId");
3058
+ const model = stringField2(obj, "model");
3059
+ const usageObj = obj["usage"];
3060
+ const source = isRecord3(usageObj) ? usageObj : obj;
3061
+ const i = numberField2(source, "input_tokens", "inputTokens", "prompt_tokens");
3062
+ const o = numberField2(source, "output_tokens", "outputTokens", "completion_tokens");
3063
+ if (id === void 0 && model === void 0 && i === void 0 && o === void 0) continue;
3064
+ onMeta({
3065
+ ...id !== void 0 ? { sessionId: id } : {},
3066
+ ...model !== void 0 ? { model } : {},
3067
+ ...i !== void 0 ? { inputTokens: i } : {},
3068
+ ...o !== void 0 ? { outputTokens: o } : {}
3069
+ });
3070
+ }
3071
+ };
3072
+ var publishCodexStreamLineEvents = (eventBus, obj) => {
3073
+ if (stringField2(obj, "type") !== "item.completed") return;
3074
+ const item = obj["item"];
3075
+ if (!isRecord3(item)) return;
3076
+ const itemType = stringField2(item, "type");
3077
+ if (itemType === "agent_message") {
3078
+ const text = truncateField(stringField2(item, "text"));
3079
+ if (text !== void 0) {
3080
+ eventBus.publish({
3081
+ type: "log",
3082
+ level: "debug",
3083
+ message: "codex-provider: assistant",
3084
+ meta: { text },
3085
+ at: IsoTimestamp.now()
3086
+ });
3087
+ }
3088
+ return;
3089
+ }
3090
+ if (itemType === "command_execution") {
3091
+ const args = truncateField(stringField2(item, "command"));
3092
+ eventBus.publish({
3093
+ type: "log",
3094
+ level: "debug",
3095
+ message: "codex-provider: tool_use",
3096
+ meta: {
3097
+ tool: "command_execution",
3098
+ ...args !== void 0 ? { args } : {}
3099
+ },
3100
+ at: IsoTimestamp.now()
3101
+ });
3102
+ return;
3103
+ }
3104
+ if (itemType === "function_call") {
3105
+ const tool = stringField2(item, "name") ?? "";
3106
+ const rawArgs = item["arguments"];
3107
+ const argsPreview = typeof rawArgs === "string" ? rawArgs : safeJson(rawArgs);
3108
+ const args = truncateField(argsPreview);
3109
+ eventBus.publish({
3110
+ type: "log",
3111
+ level: "debug",
3112
+ message: "codex-provider: tool_use",
3113
+ meta: {
3114
+ tool,
3115
+ ...args !== void 0 ? { args } : {}
3116
+ },
3117
+ at: IsoTimestamp.now()
3118
+ });
3119
+ return;
3120
+ }
3121
+ if (itemType === "function_call_output") {
3122
+ const tool = stringField2(item, "name") ?? stringField2(item, "call_id") ?? "";
3123
+ const status = item["is_error"] === true || stringField2(item, "status") === "error" ? "error" : "ok";
3124
+ const preview = truncateField(stringField2(item, "output"));
3125
+ eventBus.publish({
3126
+ type: "log",
3127
+ level: "debug",
3128
+ message: "codex-provider: tool_result",
3129
+ meta: {
3130
+ tool,
3131
+ status,
3132
+ ...preview !== void 0 ? { preview } : {}
3133
+ },
3134
+ at: IsoTimestamp.now()
3135
+ });
3136
+ }
3137
+ };
3138
+ var safeJson = (v) => {
3139
+ if (v === void 0 || v === null) return void 0;
3140
+ try {
3141
+ const s = JSON.stringify(v);
3142
+ return s === "{}" || s === "[]" ? void 0 : s;
3143
+ } catch {
3144
+ return void 0;
2935
3145
  }
2936
3146
  };
2937
3147
  var stringField2 = (obj, ...names) => {
@@ -2948,7 +3158,7 @@ var numberField2 = (obj, ...names) => {
2948
3158
  }
2949
3159
  return void 0;
2950
3160
  };
2951
- var isRecord2 = (v) => typeof v === "object" && v !== null;
3161
+ var isRecord3 = (v) => typeof v === "object" && v !== null;
2952
3162
  var spawnAttempt2 = async (input) => {
2953
3163
  const { deps, spawnFn, command, args, session, readFile: readFile2, outputFile } = input;
2954
3164
  const child = spawnFn(command, args, { stdio: ["pipe", "pipe", "pipe"], cwd: String(session.cwd) });
@@ -2962,23 +3172,27 @@ var spawnAttempt2 = async (input) => {
2962
3172
  const { code, signal } = await runHeadlessSpawn({
2963
3173
  child,
2964
3174
  onStdout: (chunk) => {
2965
- stdoutLineBuf = consumeMetaLines(stdoutLineBuf + chunk, (update) => {
2966
- if (update.sessionId !== void 0 && sessionId2 === void 0) {
2967
- sessionId2 = update.sessionId;
2968
- deps.eventBus.publish({
2969
- type: "log",
2970
- level: "debug",
2971
- message: "codex-provider: session id captured",
2972
- meta: { sessionId: update.sessionId },
2973
- at: IsoTimestamp.now()
2974
- });
2975
- }
2976
- if (update.model !== void 0 && model === void 0) {
2977
- model = update.model;
2978
- }
2979
- if (update.inputTokens !== void 0) inputTokens = update.inputTokens;
2980
- if (update.outputTokens !== void 0) outputTokens = update.outputTokens;
2981
- });
3175
+ stdoutLineBuf = consumeMetaLines(
3176
+ stdoutLineBuf + chunk,
3177
+ (update) => {
3178
+ if (update.sessionId !== void 0 && sessionId2 === void 0) {
3179
+ sessionId2 = update.sessionId;
3180
+ deps.eventBus.publish({
3181
+ type: "log",
3182
+ level: "debug",
3183
+ message: "codex-provider: session id captured",
3184
+ meta: { sessionId: update.sessionId },
3185
+ at: IsoTimestamp.now()
3186
+ });
3187
+ }
3188
+ if (update.model !== void 0 && model === void 0) {
3189
+ model = update.model;
3190
+ }
3191
+ if (update.inputTokens !== void 0) inputTokens = update.inputTokens;
3192
+ if (update.outputTokens !== void 0) outputTokens = update.outputTokens;
3193
+ },
3194
+ (obj) => publishCodexStreamLineEvents(deps.eventBus, obj)
3195
+ );
2982
3196
  },
2983
3197
  onStderr: (chunk) => {
2984
3198
  stderrBuf += chunk;
@@ -3100,7 +3314,7 @@ var numberField3 = (obj, ...names) => {
3100
3314
  }
3101
3315
  return void 0;
3102
3316
  };
3103
- var isRecord3 = (v) => typeof v === "object" && v !== null;
3317
+ var isRecord4 = (v) => typeof v === "object" && v !== null;
3104
3318
  var extractUsage = (json) => {
3105
3319
  const ti = numberField3(json, "input_tokens", "inputTokens", "prompt_tokens");
3106
3320
  const to = numberField3(json, "output_tokens", "outputTokens", "completion_tokens");
@@ -3111,7 +3325,7 @@ var extractUsage = (json) => {
3111
3325
  };
3112
3326
  }
3113
3327
  const u = json["usage"];
3114
- if (!isRecord3(u)) return void 0;
3328
+ if (!isRecord4(u)) return void 0;
3115
3329
  const ni = numberField3(u, "input_tokens", "inputTokens", "prompt_tokens");
3116
3330
  const no = numberField3(u, "output_tokens", "outputTokens", "completion_tokens");
3117
3331
  if (ni === void 0 && no === void 0) return void 0;
@@ -3123,10 +3337,10 @@ var extractUsage = (json) => {
3123
3337
  var extractBodyText = (json) => {
3124
3338
  const eventType = stringField3(json, "type");
3125
3339
  const data = json["data"];
3126
- if (eventType === "assistant.message_delta" && isRecord3(data)) {
3340
+ if (eventType === "assistant.message_delta" && isRecord4(data)) {
3127
3341
  return stringField3(data, "deltaContent");
3128
3342
  }
3129
- if (eventType === "assistant.message" && isRecord3(data)) {
3343
+ if (eventType === "assistant.message" && isRecord4(data)) {
3130
3344
  return stringField3(data, "content");
3131
3345
  }
3132
3346
  if (eventType === "response.output_text.delta") {
@@ -3134,7 +3348,7 @@ var extractBodyText = (json) => {
3134
3348
  }
3135
3349
  if (eventType === "content_block_delta") {
3136
3350
  const delta = json["delta"];
3137
- if (isRecord3(delta)) return stringField3(delta, "text");
3351
+ if (isRecord4(delta)) return stringField3(delta, "text");
3138
3352
  }
3139
3353
  if (eventType === "message") {
3140
3354
  const c = stringField3(json, "content");
@@ -3325,6 +3539,16 @@ var spawnAttempt3 = async (input) => {
3325
3539
  }
3326
3540
  if (line.bodyText !== void 0 && line.bodyText.length > 0) {
3327
3541
  events.push({ assistant: true, text: line.bodyText });
3542
+ const text = truncateField(line.bodyText);
3543
+ if (text !== void 0) {
3544
+ deps.eventBus.publish({
3545
+ type: "log",
3546
+ level: "debug",
3547
+ message: "copilot-provider: assistant",
3548
+ meta: { text },
3549
+ at: IsoTimestamp.now()
3550
+ });
3551
+ }
3328
3552
  } else if (line.sessionId === void 0 && line.model === void 0 && line.usage === void 0) {
3329
3553
  events.push({ assistant: false, text: line.raw });
3330
3554
  }
@@ -3781,10 +4005,10 @@ var createInteractiveAiProvider = (deps) => {
3781
4005
  };
3782
4006
 
3783
4007
  // src/integration/io/run-cli.ts
3784
- var runCli = (spawn3, command, args, opts) => new Promise((resolve) => {
4008
+ var runCli = (spawn2, command, args, opts) => new Promise((resolve) => {
3785
4009
  let child;
3786
4010
  try {
3787
- child = spawn3(command, args, {
4011
+ child = spawn2(command, args, {
3788
4012
  stdio: ["pipe", "pipe", "pipe"],
3789
4013
  ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {}
3790
4014
  });
@@ -3922,9 +4146,9 @@ var looksLikeNotFound = (stderr) => {
3922
4146
  const s = stderr.toLowerCase();
3923
4147
  return s.includes("not found") || s.includes("404") || s.includes("could not resolve") || s.includes("does not exist");
3924
4148
  };
3925
- var fetchGitHub = async (spawn3, parsed, url) => {
4149
+ var fetchGitHub = async (spawn2, parsed, url) => {
3926
4150
  const result = await runCli(
3927
- spawn3,
4151
+ spawn2,
3928
4152
  "gh",
3929
4153
  [
3930
4154
  "issue",
@@ -3971,9 +4195,9 @@ var fetchGitHub = async (spawn3, parsed, url) => {
3971
4195
  comments
3972
4196
  });
3973
4197
  };
3974
- var fetchGitLabNotes = async (spawn3, parsed) => {
4198
+ var fetchGitLabNotes = async (spawn2, parsed) => {
3975
4199
  const result = await runCli(
3976
- spawn3,
4200
+ spawn2,
3977
4201
  "glab",
3978
4202
  ["issue", "note", "list", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--output", "json"],
3979
4203
  { timeoutMs: CLI_TIMEOUT_MS }
@@ -4002,9 +4226,9 @@ var fetchGitLabNotes = async (spawn3, parsed) => {
4002
4226
  }));
4003
4227
  return { comments };
4004
4228
  };
4005
- var fetchGitLab = async (spawn3, parsed, url, logger) => {
4229
+ var fetchGitLab = async (spawn2, parsed, url, logger) => {
4006
4230
  const result = await runCli(
4007
- spawn3,
4231
+ spawn2,
4008
4232
  "glab",
4009
4233
  ["issue", "view", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--output", "json"],
4010
4234
  { timeoutMs: CLI_TIMEOUT_MS }
@@ -4031,7 +4255,7 @@ var fetchGitLab = async (spawn3, parsed, url, logger) => {
4031
4255
  })
4032
4256
  );
4033
4257
  }
4034
- const notes = await fetchGitLabNotes(spawn3, parsed);
4258
+ const notes = await fetchGitLabNotes(spawn2, parsed);
4035
4259
  if (notes.failure !== void 0) {
4036
4260
  logger?.warn(`glab issue note list failed for ${url}: ${notes.failure} \u2014 proceeding without comments`);
4037
4261
  }
@@ -4052,9 +4276,9 @@ var createIssueFetcher = (deps) => async (url) => {
4052
4276
 
4053
4277
  // src/integration/scm/issue-pusher.ts
4054
4278
  var CLI_TIMEOUT_MS2 = 3e4;
4055
- var updateGitHub = async (spawn3, url, body, parsed) => {
4279
+ var updateGitHub = async (spawn2, url, body, parsed) => {
4056
4280
  const r = await runCli(
4057
- spawn3,
4281
+ spawn2,
4058
4282
  "gh",
4059
4283
  ["issue", "edit", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--body-file", "-"],
4060
4284
  { stdin: body, timeoutMs: CLI_TIMEOUT_MS2 }
@@ -4070,9 +4294,9 @@ var updateGitHub = async (spawn3, url, body, parsed) => {
4070
4294
  }
4071
4295
  return Result.ok(void 0);
4072
4296
  };
4073
- var updateGitLab = async (spawn3, url, body, parsed) => {
4297
+ var updateGitLab = async (spawn2, url, body, parsed) => {
4074
4298
  const r = await runCli(
4075
- spawn3,
4299
+ spawn2,
4076
4300
  "glab",
4077
4301
  ["issue", "update", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--description", body],
4078
4302
  { timeoutMs: CLI_TIMEOUT_MS2 }
@@ -4088,9 +4312,9 @@ var updateGitLab = async (spawn3, url, body, parsed) => {
4088
4312
  }
4089
4313
  return Result.ok(void 0);
4090
4314
  };
4091
- var createGitHub = async (spawn3, origin, title, body) => {
4315
+ var createGitHub = async (spawn2, origin, title, body) => {
4092
4316
  const r = await runCli(
4093
- spawn3,
4317
+ spawn2,
4094
4318
  "gh",
4095
4319
  ["issue", "create", "--repo", `${origin.owner}/${origin.repo}`, "--title", title, "--body-file", "-"],
4096
4320
  { stdin: body, timeoutMs: CLI_TIMEOUT_MS2 }
@@ -4115,9 +4339,9 @@ var createGitHub = async (spawn3, origin, title, body) => {
4115
4339
  }
4116
4340
  return Result.ok({ url });
4117
4341
  };
4118
- var createGitLab = async (spawn3, origin, title, body) => {
4342
+ var createGitLab = async (spawn2, origin, title, body) => {
4119
4343
  const r = await runCli(
4120
- spawn3,
4344
+ spawn2,
4121
4345
  "glab",
4122
4346
  ["issue", "create", "--repo", `${origin.owner}/${origin.repo}`, "--title", title, "--description", body],
4123
4347
  { timeoutMs: CLI_TIMEOUT_MS2 }
@@ -4251,11 +4475,11 @@ var buildGlabArgs = (input) => {
4251
4475
  if (input.draft) args.push("--draft");
4252
4476
  return args;
4253
4477
  };
4254
- var runPlatformCli = async (spawn3, platform, input) => {
4478
+ var runPlatformCli = async (spawn2, platform, input) => {
4255
4479
  const command = platform === "github" ? "gh" : "glab";
4256
4480
  const args = platform === "github" ? buildGhArgs(input) : buildGlabArgs(input);
4257
4481
  const noun = platform === "github" ? "gh pr create" : "glab mr create";
4258
- const result = await runCli(spawn3, command, args, { cwd: String(input.cwd), timeoutMs: CLI_TIMEOUT_MS3 });
4482
+ const result = await runCli(spawn2, command, args, { cwd: String(input.cwd), timeoutMs: CLI_TIMEOUT_MS3 });
4259
4483
  if (!result.ok) return Result.error(result.error);
4260
4484
  if (result.value.exitCode !== 0) {
4261
4485
  const stderr = result.value.stderr.trim();
@@ -4734,7 +4958,7 @@ var createNpmVersionChecker = (deps) => {
4734
4958
  // package.json
4735
4959
  var package_default = {
4736
4960
  name: "ralphctl",
4737
- version: "0.8.3",
4961
+ version: "0.8.5",
4738
4962
  description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code, GitHub Copilot, and OpenAI Codex across repositories",
4739
4963
  homepage: "https://github.com/lukas-grigis/ralphctl",
4740
4964
  type: "module",
@@ -4840,7 +5064,7 @@ var CLI_METADATA = {
4840
5064
  var DEFAULT_ESCALATION_MAP = {
4841
5065
  // Claude — Sonnet escalates to Opus; Haiku escalates to Sonnet.
4842
5066
  "claude-haiku-4-5": "claude-sonnet-4-6",
4843
- "claude-sonnet-4-6": "claude-opus-4-7",
5067
+ "claude-sonnet-4-6": "claude-opus-4-8",
4844
5068
  // Copilot/Codex — mini variants step up to their full-tier frontier.
4845
5069
  "gpt-5-mini": "gpt-5.5",
4846
5070
  "gpt-5.4-mini": "gpt-5.5"
@@ -5372,7 +5596,7 @@ var noopNotificationDispatcher = {
5372
5596
  }
5373
5597
  };
5374
5598
  var wire = (opts) => {
5375
- const spawn3 = opts.spawn ?? defaultPipeSpawn;
5599
+ const spawn2 = opts.spawn ?? defaultPipeSpawn;
5376
5600
  const env = opts.env ?? process.env;
5377
5601
  const debugTrace = isTruthyEnvFlag(env[RALPHCTL_DEBUG_TRACE_ENV]);
5378
5602
  const appendFile = createAppendFile();
@@ -5422,9 +5646,9 @@ var wire = (opts) => {
5422
5646
  probes: PROBES,
5423
5647
  eventBus,
5424
5648
  logger,
5425
- pullRequestCreator: createPullRequestCreator({ gitRunner: createGitRunner(), spawn: spawn3 }),
5426
- issueFetcher: createIssueFetcher({ spawn: spawn3, logger }),
5427
- issuePusher: createIssuePusher({ spawn: spawn3 }),
5649
+ pullRequestCreator: createPullRequestCreator({ gitRunner: createGitRunner(), spawn: spawn2 }),
5650
+ issueFetcher: createIssueFetcher({ spawn: spawn2, logger }),
5651
+ issuePusher: createIssuePusher({ spawn: spawn2 }),
5428
5652
  versionChecker: createNpmVersionChecker({
5429
5653
  stateRoot: opts.storage.stateRoot,
5430
5654
  currentVersion: CLI_METADATA.currentVersion,
@@ -6163,7 +6387,12 @@ var SelectionProvider = ({
6163
6387
  onChangeRef.current = onChange;
6164
6388
  const projectIdRef = useRef3(projectId);
6165
6389
  projectIdRef.current = projectId;
6390
+ const isFirstPersist = useRef3(true);
6166
6391
  useEffect3(() => {
6392
+ if (isFirstPersist.current) {
6393
+ isFirstPersist.current = false;
6394
+ return;
6395
+ }
6167
6396
  onChangeRef.current?.({
6168
6397
  ...projectId !== void 0 ? { projectId } : {},
6169
6398
  ...projectLabel !== void 0 ? { projectLabel } : {},
@@ -6840,7 +7069,7 @@ var probeSprintExecutionPairing = async (sprints, sprintExecutionRepo) => {
6840
7069
  // src/integration/io/command-exists.ts
6841
7070
  import { spawn } from "child_process";
6842
7071
  var commandExists = (name) => new Promise((resolve) => {
6843
- const child = spawn(name, ["--version"], { stdio: "ignore" });
7072
+ const child = process.platform === "win32" ? spawn("where", [name], { stdio: "ignore" }) : spawn("command", ["-v", name], { stdio: "ignore", shell: true });
6844
7073
  let settled = false;
6845
7074
  const settle = (value) => {
6846
7075
  if (settled) return;
@@ -6848,7 +7077,7 @@ var commandExists = (name) => new Promise((resolve) => {
6848
7077
  resolve(value);
6849
7078
  };
6850
7079
  child.on("error", () => settle(false));
6851
- child.on("exit", () => settle(true));
7080
+ child.on("exit", (code) => settle(code === 0));
6852
7081
  });
6853
7082
 
6854
7083
  // src/integration/io/run-command.ts
@@ -8635,7 +8864,6 @@ var signalReference = {
8635
8864
  { keys: [], label: "blocked", description: "task halted \u2014 check gate failed or AI self-reported stuck" },
8636
8865
  { keys: [], label: "commit", description: "proposed commit message for the task" },
8637
8866
  { keys: [], label: "note", description: "general annotation" },
8638
- { keys: [], label: "progress", description: "milestone marker from the AI" },
8639
8867
  { keys: [], label: "script", description: "setup or check script discovered or run" },
8640
8868
  { keys: [], label: "proposal", description: "AI-authored context file or skill draft" },
8641
8869
  { keys: [], label: "skills", description: "skill suggestions surfaced for this run" }
@@ -8767,8 +8995,6 @@ var SIGNAL_LABEL_COLOR = {
8767
8995
  decision: inkColors.highlight,
8768
8996
  commit: inkColors.info,
8769
8997
  note: inkColors.muted,
8770
- progress: inkColors.info,
8771
- "progress-entry": inkColors.info,
8772
8998
  done: inkColors.success,
8773
8999
  verified: inkColors.success,
8774
9000
  blocked: inkColors.error,
@@ -8789,10 +9015,6 @@ var rowForSignal = (sig) => {
8789
9015
  }
8790
9016
  case "note":
8791
9017
  return { label: "note", text: sig.text };
8792
- case "progress":
8793
- return { label: "progress", text: sig.summary };
8794
- case "progress-entry":
8795
- return { label: "progress-entry", text: sig.task };
8796
9018
  case "task-complete":
8797
9019
  return { label: "done", text: "task complete" };
8798
9020
  case "task-verified":
@@ -11350,6 +11572,11 @@ var refineExampleSignals = [
11350
11572
  type: "refined-ticket",
11351
11573
  body: "# Export to CSV\n\n## Problem\n\nUsers cannot move their data out of the app.\n\n## Acceptance criteria\n\n### AC1 \u2014 CSV export\n\n- **Given** a logged-in user, **When** they click Export, **Then** a CSV download starts.",
11352
11574
  timestamp: EXAMPLE_TS
11575
+ },
11576
+ {
11577
+ type: "decision",
11578
+ text: "Scoped export to CSV only for v1; XLSX deferred to a follow-up ticket.",
11579
+ timestamp: EXAMPLE_TS
11353
11580
  }
11354
11581
  ];
11355
11582
  var refineOutputContract = {
@@ -11622,7 +11849,7 @@ var refinePromptDef = {
11622
11849
  partials: {
11623
11850
  HARNESS_CONTEXT: "harness-context"
11624
11851
  },
11625
- expectedSignals: ["refined-ticket"]
11852
+ expectedSignals: ["refined-ticket", "note", "learning", "decision"]
11626
11853
  };
11627
11854
  var renderTicket = (ticket) => {
11628
11855
  const lines = [`**Title:** ${ticket.title}`, `**ID:** ${String(ticket.id)}`];
@@ -11679,8 +11906,8 @@ var renderContractSection = (params) => {
11679
11906
  lines.push("other files \u2014 the harness renders every operator-readable sidecar from the validated");
11680
11907
  lines.push("signals.");
11681
11908
  lines.push("");
11682
- lines.push("Use the `Write` tool with the absolute path above \u2014 your cwd is the project repo, not");
11683
- lines.push("the spawn output directory, so a relative `signals.json` would land in the wrong place.");
11909
+ lines.push("Always write to the **absolute path** shown above \u2014 do not use a relative path, which");
11910
+ lines.push("would resolve against your working directory and may land in the wrong place.");
11684
11911
  lines.push("");
11685
11912
  if (params.sidecars.length > 0) {
11686
11913
  lines.push("Files the harness will render from your signals (you must NOT write these):");
@@ -11912,7 +12139,6 @@ var createRefineFlow = (deps, opts) => {
11912
12139
  };
11913
12140
 
11914
12141
  // src/integration/system/detect-cli.ts
11915
- import { spawn as spawn2 } from "child_process";
11916
12142
  var PROVIDER_BINARY2 = {
11917
12143
  "claude-code": "claude",
11918
12144
  "github-copilot": "gh",
@@ -11989,17 +12215,7 @@ Install options (${os}):
11989
12215
  ${bullets}
11990
12216
  Docs: ${guidance.docsUrl}`;
11991
12217
  };
11992
- var defaultWhich = (binary) => new Promise((resolve) => {
11993
- const child = spawn2("command", ["-v", binary], { stdio: "pipe", shell: true });
11994
- let settled = false;
11995
- const settle = (value) => {
11996
- if (settled) return;
11997
- settled = true;
11998
- resolve(value);
11999
- };
12000
- child.on("error", () => settle(false));
12001
- child.on("exit", (code) => settle(code === 0));
12002
- });
12218
+ var defaultWhich = commandExists;
12003
12219
  var detectInstalledProviders = async (options = {}) => {
12004
12220
  const which = options.which ?? defaultWhich;
12005
12221
  const providers = Object.keys(PROVIDER_BINARY2);
@@ -13637,7 +13853,18 @@ var implementPromptDef = {
13637
13853
  },
13638
13854
  // Documents the harness signals the implement response is expected to carry. Validation is
13639
13855
  // not enforced at parse time — this list drives test authors and future scoped parsers.
13640
- expectedSignals: ["progress", "note", "task-verified", "task-complete", "task-blocked", "commit-message"]
13856
+ // Aligned with generator.contract.ts: narrative fan-out (change, decision, learning, note)
13857
+ // plus lifecycle signals (task-verified, task-complete, task-blocked, commit-message).
13858
+ expectedSignals: [
13859
+ "change",
13860
+ "decision",
13861
+ "learning",
13862
+ "note",
13863
+ "task-verified",
13864
+ "task-complete",
13865
+ "task-blocked",
13866
+ "commit-message"
13867
+ ]
13641
13868
  };
13642
13869
  var buildImplementPrompt = async (deps, input) => buildPrompt(deps, implementPromptDef, {
13643
13870
  taskName: input.task.name,
@@ -13656,7 +13883,7 @@ var buildImplementPrompt = async (deps, input) => buildPrompt(deps, implementPro
13656
13883
  });
13657
13884
 
13658
13885
  // src/application/flows/implement/leaves/generator.contract.ts
13659
- import { z as z31 } from "zod";
13886
+ import { z as z30 } from "zod";
13660
13887
 
13661
13888
  // src/integration/ai/contract/_engine/signals/change/schema.ts
13662
13889
  import { z as z25 } from "zod";
@@ -13675,44 +13902,33 @@ var commitMessageSignalSchema = z26.object({
13675
13902
  timestamp: IsoTimestampSchema
13676
13903
  });
13677
13904
 
13678
- // src/integration/ai/contract/_engine/signals/progress-entry/schema.ts
13679
- import { z as z27 } from "zod";
13680
- var progressEntrySignalSchema = z27.object({
13681
- type: z27.literal("progress-entry"),
13682
- task: z27.string(),
13683
- filesChanged: z27.array(z27.string()).readonly(),
13684
- learnings: z27.string(),
13685
- notesForNext: z27.string(),
13686
- timestamp: IsoTimestampSchema
13687
- });
13688
-
13689
13905
  // src/integration/ai/contract/_engine/signals/task-blocked/schema.ts
13690
- import { z as z28 } from "zod";
13691
- var taskBlockedSignalSchema = z28.object({
13692
- type: z28.literal("task-blocked"),
13693
- reason: z28.string(),
13906
+ import { z as z27 } from "zod";
13907
+ var taskBlockedSignalSchema = z27.object({
13908
+ type: z27.literal("task-blocked"),
13909
+ reason: z27.string(),
13694
13910
  timestamp: IsoTimestampSchema
13695
13911
  });
13696
13912
 
13697
13913
  // src/integration/ai/contract/_engine/signals/task-complete/schema.ts
13698
- import { z as z29 } from "zod";
13699
- var taskCompleteSignalSchema = z29.object({
13700
- type: z29.literal("task-complete"),
13914
+ import { z as z28 } from "zod";
13915
+ var taskCompleteSignalSchema = z28.object({
13916
+ type: z28.literal("task-complete"),
13701
13917
  timestamp: IsoTimestampSchema
13702
13918
  });
13703
13919
 
13704
13920
  // src/integration/ai/contract/_engine/signals/task-verified/schema.ts
13705
- import { z as z30 } from "zod";
13706
- var taskVerifiedSignalSchema = z30.object({
13707
- type: z30.literal("task-verified"),
13708
- output: z30.string(),
13921
+ import { z as z29 } from "zod";
13922
+ var taskVerifiedSignalSchema = z29.object({
13923
+ type: z29.literal("task-verified"),
13924
+ output: z29.string(),
13709
13925
  timestamp: IsoTimestampSchema
13710
13926
  });
13711
13927
 
13712
13928
  // src/application/flows/implement/leaves/generator.contract.ts
13713
13929
  var atMostOneCommitMessage = (signals) => signals.filter((s) => s.type === "commit-message").length <= 1;
13714
- var signalsArraySchemaRaw3 = z31.array(
13715
- z31.union([
13930
+ var signalsArraySchemaRaw3 = z30.array(
13931
+ z30.union([
13716
13932
  changeSignalSchema,
13717
13933
  learningSignalSchema,
13718
13934
  noteSignalSchema,
@@ -13720,8 +13936,7 @@ var signalsArraySchemaRaw3 = z31.array(
13720
13936
  taskVerifiedSignalSchema,
13721
13937
  taskCompleteSignalSchema,
13722
13938
  taskBlockedSignalSchema,
13723
- commitMessageSignalSchema,
13724
- progressEntrySignalSchema
13939
+ commitMessageSignalSchema
13725
13940
  ])
13726
13941
  ).refine(atMostOneCommitMessage, "at most one commit-message signal per generator spawn");
13727
13942
  var signalsArraySchema3 = brandSignalArray(signalsArraySchemaRaw3);
@@ -14812,15 +15027,15 @@ var implementSession = (sandboxCwd, repoPath, sprintDir2, prompt, model, signals
14812
15027
  };
14813
15028
 
14814
15029
  // src/application/flows/implement/leaves/evaluator.contract.ts
14815
- import { z as z33 } from "zod";
15030
+ import { z as z32 } from "zod";
14816
15031
 
14817
15032
  // src/integration/ai/contract/_engine/signals/evaluation/schema.ts
14818
- import { z as z32 } from "zod";
14819
- var dimensionScoreSchema = z32.object({
14820
- dimension: z32.string(),
14821
- passed: z32.boolean(),
14822
- finding: z32.string(),
14823
- executionEvidence: z32.string().optional()
15033
+ import { z as z31 } from "zod";
15034
+ var dimensionScoreSchema = z31.object({
15035
+ dimension: z31.string(),
15036
+ passed: z31.boolean(),
15037
+ finding: z31.string(),
15038
+ executionEvidence: z31.string().optional()
14824
15039
  }).superRefine((d, ctx) => {
14825
15040
  if (!d.passed && d.finding.trim().length === 0) {
14826
15041
  ctx.addIssue({
@@ -14830,11 +15045,11 @@ var dimensionScoreSchema = z32.object({
14830
15045
  });
14831
15046
  }
14832
15047
  });
14833
- var evaluationSignalSchema = z32.object({
14834
- type: z32.literal("evaluation"),
14835
- status: z32.union([z32.literal("passed"), z32.literal("failed"), z32.literal("malformed")]),
14836
- dimensions: z32.array(dimensionScoreSchema).readonly(),
14837
- critique: z32.string().optional(),
15048
+ var evaluationSignalSchema = z31.object({
15049
+ type: z31.literal("evaluation"),
15050
+ status: z31.union([z31.literal("passed"), z31.literal("failed"), z31.literal("malformed")]),
15051
+ dimensions: z31.array(dimensionScoreSchema).readonly(),
15052
+ critique: z31.string().optional(),
14838
15053
  timestamp: IsoTimestampSchema
14839
15054
  }).superRefine((s, ctx) => {
14840
15055
  if (s.status === "passed") {
@@ -14894,8 +15109,8 @@ var renderEvaluationMarkdown = (signal) => {
14894
15109
 
14895
15110
  // src/application/flows/implement/leaves/evaluator.contract.ts
14896
15111
  var exactlyOneEvaluation = (signals) => signals.filter((s) => s.type === "evaluation").length === 1;
14897
- var signalsArraySchemaRaw4 = z33.array(
14898
- z33.union([
15112
+ var signalsArraySchemaRaw4 = z32.array(
15113
+ z32.union([
14899
15114
  changeSignalSchema,
14900
15115
  learningSignalSchema,
14901
15116
  noteSignalSchema,
@@ -15011,7 +15226,7 @@ var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
15011
15226
  });
15012
15227
  if (!prompt.ok) return Result.error(prompt.error);
15013
15228
  await writeRoundPrompt(input.workspaceRoot, input.roundNum, "evaluator", String(prompt.value), deps.logger);
15014
- const spawn3 = await deps.provider.generate(
15229
+ const spawn2 = await deps.provider.generate(
15015
15230
  implementSession(
15016
15231
  input.workspaceRoot,
15017
15232
  deps.cwd,
@@ -15024,7 +15239,7 @@ var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
15024
15239
  deps.effort
15025
15240
  )
15026
15241
  );
15027
- if (!spawn3.ok) return Result.error(spawn3.error);
15242
+ if (!spawn2.ok) return Result.error(spawn2.error);
15028
15243
  const validated = await validateSignalsFile(outputDir, evaluatorOutputContract);
15029
15244
  if (!validated.ok) return Result.error(validated.error);
15030
15245
  const signals = validated.value;
@@ -15230,7 +15445,7 @@ var generatorLeaf = (deps, taskId) => leaf(`generator-${String(taskId)}`, {
15230
15445
  if (!prompt.ok) return Result.error(prompt.error);
15231
15446
  await writeRoundPrompt(input.workspaceRoot, roundNum, "generator", String(prompt.value), deps.logger);
15232
15447
  const effectiveModel = task.escalatedToModel ?? deps.model;
15233
- const spawn3 = await deps.provider.generate(
15448
+ const spawn2 = await deps.provider.generate(
15234
15449
  implementSession(
15235
15450
  input.workspaceRoot,
15236
15451
  deps.cwd,
@@ -15243,7 +15458,7 @@ var generatorLeaf = (deps, taskId) => leaf(`generator-${String(taskId)}`, {
15243
15458
  deps.effort
15244
15459
  )
15245
15460
  );
15246
- if (!spawn3.ok) return Result.error(spawn3.error);
15461
+ if (!spawn2.ok) return Result.error(spawn2.error);
15247
15462
  const validated = await validateSignalsFile(outputDir, generatorOutputContract);
15248
15463
  if (!validated.ok) return Result.error(validated.error);
15249
15464
  const signals = validated.value;
@@ -18055,12 +18270,12 @@ var buildApplyFeedbackPrompt = async (deps, input) => buildPrompt(deps, applyFee
18055
18270
  });
18056
18271
 
18057
18272
  // src/application/flows/review/leaves/review-round.contract.ts
18058
- import { z as z34 } from "zod";
18273
+ import { z as z33 } from "zod";
18059
18274
  var hasExactlyOneTerminal = (signals) => {
18060
18275
  const terminals = signals.filter((s) => s.type === "task-complete" || s.type === "task-blocked");
18061
18276
  return terminals.length === 1;
18062
18277
  };
18063
- var signalsArraySchemaRaw5 = z34.array(z34.union([taskCompleteSignalSchema, taskBlockedSignalSchema])).refine(hasExactlyOneTerminal, "exactly one of `task-complete` or `task-blocked` is required per round");
18278
+ var signalsArraySchemaRaw5 = z33.array(z33.union([taskCompleteSignalSchema, taskBlockedSignalSchema])).refine(hasExactlyOneTerminal, "exactly one of `task-complete` or `task-blocked` is required per round");
18064
18279
  var signalsArraySchema5 = brandSignalArray(signalsArraySchemaRaw5);
18065
18280
  var wrapLegacyArray5 = (raw) => {
18066
18281
  if (Array.isArray(raw)) return { schemaVersion: 1, signals: raw };
@@ -18198,7 +18413,7 @@ var reviewRoundLeaf = (deps, opts) => leaf("review-round", {
18198
18413
  }
18199
18414
  const promptWrote = await writeTextAtomic(String(paths.value.promptFile), String(prompt));
18200
18415
  if (!promptWrote.ok) return Result.error(promptWrote.error);
18201
- const spawn3 = await deps.provider.generate({
18416
+ const spawn2 = await deps.provider.generate({
18202
18417
  prompt,
18203
18418
  cwd: paths.value.outputDir,
18204
18419
  additionalRoots: opts.additionalRoots,
@@ -18207,7 +18422,7 @@ var reviewRoundLeaf = (deps, opts) => leaf("review-round", {
18207
18422
  signalsFile: paths.value.signalsFile,
18208
18423
  outputDir: paths.value.outputDir
18209
18424
  });
18210
- if (!spawn3.ok) return Result.error(spawn3.error);
18425
+ if (!spawn2.ok) return Result.error(spawn2.error);
18211
18426
  const validated = await validateSignalsFile(paths.value.outputDir, reviewRoundOutputContract);
18212
18427
  if (!validated.ok) return Result.error(validated.error);
18213
18428
  for (const sig of validated.value) {
@@ -18718,6 +18933,16 @@ var wireTagFor = (tool) => {
18718
18933
  return "agents-md";
18719
18934
  }
18720
18935
  };
18936
+ var conventionsPartialName = (tool) => {
18937
+ switch (tool) {
18938
+ case "claude-code":
18939
+ return "conventions-claude-md";
18940
+ case "copilot":
18941
+ return "conventions-copilot-instructions";
18942
+ case "codex":
18943
+ return "conventions-agents-md";
18944
+ }
18945
+ };
18721
18946
  var readinessPromptDef = {
18722
18947
  templateName: "readiness",
18723
18948
  description: "One-shot read-only repo inventory. The AI proposes a project context file body the harness writes to the tool-native target path.",
@@ -18749,6 +18974,10 @@ var readinessPromptDef = {
18749
18974
  placeholder: "DETECTED_ARTEFACTS",
18750
18975
  description: 'Bullet list of artefact paths discovered by the probe, or "no artefacts detected".'
18751
18976
  },
18977
+ targetFileConventions: {
18978
+ placeholder: "TARGET_FILE_CONVENTIONS",
18979
+ description: "Per-provider style guide for the target context file (CLAUDE.md / .github/copilot-instructions.md / AGENTS.md). Loaded from the matching conventions partial."
18980
+ },
18752
18981
  outputContractSection: {
18753
18982
  placeholder: "OUTPUT_CONTRACT_SECTION",
18754
18983
  description: "Audit-[09] output contract block rendered from the readiness contract \u2014 instructs the AI to write `signals.json` directly with the proposal signals.",
@@ -18799,54 +19028,60 @@ var collectArtefactPaths = (state) => {
18799
19028
  }
18800
19029
  return paths;
18801
19030
  };
18802
- var buildReadinessPrompt = async (deps, input) => buildPrompt(deps, readinessPromptDef, {
18803
- repositoryPath: input.repositoryPath,
18804
- currentTool: renderCurrentTool(input.currentTool),
18805
- wireTag: wireTagFor(input.currentTool),
18806
- existingContextFile: renderExistingContextFile(input.existingContextFile),
18807
- detectedArtefacts: renderDetectedArtefacts(collectArtefactPaths(input.probedState)),
18808
- outputContractSection: input.outputContractSection
18809
- });
19031
+ var buildReadinessPrompt = async (deps, input) => {
19032
+ const partialName = conventionsPartialName(input.currentTool);
19033
+ const conventionsResult = await deps.load(partialName);
19034
+ if (!conventionsResult.ok) return Result.error(conventionsResult.error);
19035
+ return buildPrompt(deps, readinessPromptDef, {
19036
+ repositoryPath: input.repositoryPath,
19037
+ currentTool: renderCurrentTool(input.currentTool),
19038
+ wireTag: wireTagFor(input.currentTool),
19039
+ existingContextFile: renderExistingContextFile(input.existingContextFile),
19040
+ detectedArtefacts: renderDetectedArtefacts(collectArtefactPaths(input.probedState)),
19041
+ targetFileConventions: conventionsResult.value.trim(),
19042
+ outputContractSection: input.outputContractSection
19043
+ });
19044
+ };
18810
19045
 
18811
19046
  // src/application/flows/readiness/leaves/readiness.contract.ts
18812
- import { z as z39 } from "zod";
19047
+ import { z as z38 } from "zod";
18813
19048
 
18814
19049
  // src/integration/ai/contract/_engine/signals/agents-md-proposal/schema.ts
18815
- import { z as z35 } from "zod";
18816
- var agentsMdProposalSignalSchema = z35.object({
18817
- type: z35.literal("agents-md-proposal"),
18818
- tag: z35.union([z35.literal("claude-md"), z35.literal("copilot-instructions"), z35.literal("agents-md")]),
18819
- content: z35.string(),
19050
+ import { z as z34 } from "zod";
19051
+ var agentsMdProposalSignalSchema = z34.object({
19052
+ type: z34.literal("agents-md-proposal"),
19053
+ tag: z34.union([z34.literal("claude-md"), z34.literal("copilot-instructions"), z34.literal("agents-md")]),
19054
+ content: z34.string(),
18820
19055
  timestamp: IsoTimestampSchema
18821
19056
  });
18822
19057
 
18823
19058
  // src/integration/ai/contract/_engine/signals/setup-skill-proposal/schema.ts
18824
- import { z as z36 } from "zod";
18825
- var setupSkillProposalSignalSchema = z36.object({
18826
- type: z36.literal("setup-skill-proposal"),
18827
- content: z36.string(),
19059
+ import { z as z35 } from "zod";
19060
+ var setupSkillProposalSignalSchema = z35.object({
19061
+ type: z35.literal("setup-skill-proposal"),
19062
+ content: z35.string(),
18828
19063
  timestamp: IsoTimestampSchema
18829
19064
  });
18830
19065
 
18831
19066
  // src/integration/ai/contract/_engine/signals/skill-suggestions/schema.ts
18832
- import { z as z37 } from "zod";
18833
- var skillSuggestionsSignalSchema = z37.object({
18834
- type: z37.literal("skill-suggestions"),
18835
- names: z37.array(z37.string()).readonly(),
19067
+ import { z as z36 } from "zod";
19068
+ var skillSuggestionsSignalSchema = z36.object({
19069
+ type: z36.literal("skill-suggestions"),
19070
+ names: z36.array(z36.string()).readonly(),
18836
19071
  timestamp: IsoTimestampSchema
18837
19072
  });
18838
19073
 
18839
19074
  // src/integration/ai/contract/_engine/signals/verify-skill-proposal/schema.ts
18840
- import { z as z38 } from "zod";
18841
- var verifySkillProposalSignalSchema = z38.object({
18842
- type: z38.literal("verify-skill-proposal"),
18843
- content: z38.string(),
19075
+ import { z as z37 } from "zod";
19076
+ var verifySkillProposalSignalSchema = z37.object({
19077
+ type: z37.literal("verify-skill-proposal"),
19078
+ content: z37.string(),
18844
19079
  timestamp: IsoTimestampSchema
18845
19080
  });
18846
19081
 
18847
19082
  // src/application/flows/readiness/leaves/readiness.contract.ts
18848
- var signalsArraySchemaRaw6 = z39.array(
18849
- z39.union([
19083
+ var signalsArraySchemaRaw6 = z38.array(
19084
+ z38.union([
18850
19085
  learningSignalSchema,
18851
19086
  noteSignalSchema,
18852
19087
  agentsMdProposalSignalSchema,
@@ -19411,9 +19646,9 @@ var buildDetectSkillsPrompt = async (loader, input) => buildPrompt(loader, detec
19411
19646
  });
19412
19647
 
19413
19648
  // src/application/flows/detect-skills/leaves/propose.contract.ts
19414
- import { z as z40 } from "zod";
19649
+ import { z as z39 } from "zod";
19415
19650
  var atMostOneOf = (kind) => (signals) => signals.filter((s) => s.type === kind).length <= 1;
19416
- var signalsArraySchemaRaw7 = z40.array(z40.union([setupSkillProposalSignalSchema, verifySkillProposalSignalSchema, noteSignalSchema])).refine(atMostOneOf("setup-skill-proposal"), "at most one setup-skill-proposal per detect-skills spawn").refine(atMostOneOf("verify-skill-proposal"), "at most one verify-skill-proposal per detect-skills spawn");
19651
+ var signalsArraySchemaRaw7 = z39.array(z39.union([setupSkillProposalSignalSchema, verifySkillProposalSignalSchema, noteSignalSchema])).refine(atMostOneOf("setup-skill-proposal"), "at most one setup-skill-proposal per detect-skills spawn").refine(atMostOneOf("verify-skill-proposal"), "at most one verify-skill-proposal per detect-skills spawn");
19417
19652
  var signalsArraySchema7 = brandSignalArray(signalsArraySchemaRaw7);
19418
19653
  var wrapLegacyArray7 = (raw) => {
19419
19654
  if (Array.isArray(raw)) return { schemaVersion: 1, signals: raw };
@@ -19501,7 +19736,7 @@ var proposeUseCase = async (deps, input) => {
19501
19736
  if (!prompt.ok) return Result.error(prompt.error);
19502
19737
  const promptWrote = await writeTextAtomic(String(paths.value.promptFile), String(prompt.value));
19503
19738
  if (!promptWrote.ok) return Result.error(promptWrote.error);
19504
- const spawn3 = await deps.provider.generate(
19739
+ const spawn2 = await deps.provider.generate(
19505
19740
  detectSkillsSession(
19506
19741
  input.repository,
19507
19742
  prompt.value,
@@ -19512,13 +19747,13 @@ var proposeUseCase = async (deps, input) => {
19512
19747
  deps.effort
19513
19748
  )
19514
19749
  );
19515
- if (!spawn3.ok) {
19750
+ if (!spawn2.ok) {
19516
19751
  log.error(`provider failed for repo ${input.repository.name}`, {
19517
19752
  repositoryId: String(input.repository.id),
19518
- error: spawn3.error.message,
19753
+ error: spawn2.error.message,
19519
19754
  runDir: String(paths.value.runDir)
19520
19755
  });
19521
- return Result.error(spawn3.error);
19756
+ return Result.error(spawn2.error);
19522
19757
  }
19523
19758
  const validated = await validateSignalsFile(paths.value.runDir, detectSkillsOutputContract);
19524
19759
  if (!validated.ok) {
@@ -20168,27 +20403,27 @@ var buildDetectScriptsPrompt = async (loader, input) => buildPrompt(loader, dete
20168
20403
  });
20169
20404
 
20170
20405
  // src/application/flows/detect-scripts/leaves/propose.contract.ts
20171
- import { z as z43 } from "zod";
20406
+ import { z as z42 } from "zod";
20172
20407
 
20173
20408
  // src/integration/ai/contract/_engine/signals/setup-script/schema.ts
20174
- import { z as z41 } from "zod";
20175
- var setupScriptSignalSchema = z41.object({
20176
- type: z41.literal("setup-script"),
20177
- command: z41.string(),
20409
+ import { z as z40 } from "zod";
20410
+ var setupScriptSignalSchema = z40.object({
20411
+ type: z40.literal("setup-script"),
20412
+ command: z40.string(),
20178
20413
  timestamp: IsoTimestampSchema
20179
20414
  });
20180
20415
 
20181
20416
  // src/integration/ai/contract/_engine/signals/verify-script/schema.ts
20182
- import { z as z42 } from "zod";
20183
- var verifyScriptSignalSchema = z42.object({
20184
- type: z42.literal("verify-script"),
20185
- command: z42.string(),
20417
+ import { z as z41 } from "zod";
20418
+ var verifyScriptSignalSchema = z41.object({
20419
+ type: z41.literal("verify-script"),
20420
+ command: z41.string(),
20186
20421
  timestamp: IsoTimestampSchema
20187
20422
  });
20188
20423
 
20189
20424
  // src/application/flows/detect-scripts/leaves/propose.contract.ts
20190
20425
  var atMostOneOf2 = (kind) => (signals) => signals.filter((s) => s.type === kind).length <= 1;
20191
- var signalsArraySchemaRaw8 = z43.array(z43.union([setupScriptSignalSchema, verifyScriptSignalSchema, noteSignalSchema])).refine(atMostOneOf2("setup-script"), "at most one setup-script signal per detect-scripts spawn").refine(atMostOneOf2("verify-script"), "at most one verify-script signal per detect-scripts spawn");
20426
+ var signalsArraySchemaRaw8 = z42.array(z42.union([setupScriptSignalSchema, verifyScriptSignalSchema, noteSignalSchema])).refine(atMostOneOf2("setup-script"), "at most one setup-script signal per detect-scripts spawn").refine(atMostOneOf2("verify-script"), "at most one verify-script signal per detect-scripts spawn");
20192
20427
  var signalsArraySchema8 = brandSignalArray(signalsArraySchemaRaw8);
20193
20428
  var wrapLegacyArray8 = (raw) => {
20194
20429
  if (Array.isArray(raw)) return { schemaVersion: 1, signals: raw };
@@ -20259,7 +20494,7 @@ var proposeUseCase2 = async (deps, input) => {
20259
20494
  if (!prompt.ok) return Result.error(prompt.error);
20260
20495
  const promptWrote = await writeTextAtomic(String(paths.value.promptFile), String(prompt.value));
20261
20496
  if (!promptWrote.ok) return Result.error(promptWrote.error);
20262
- const spawn3 = await deps.provider.generate(
20497
+ const spawn2 = await deps.provider.generate(
20263
20498
  detectScriptsSession(
20264
20499
  input.repository,
20265
20500
  prompt.value,
@@ -20270,13 +20505,13 @@ var proposeUseCase2 = async (deps, input) => {
20270
20505
  deps.effort
20271
20506
  )
20272
20507
  );
20273
- if (!spawn3.ok) {
20508
+ if (!spawn2.ok) {
20274
20509
  log.error(`provider failed for repo ${input.repository.name}`, {
20275
20510
  repositoryId: String(input.repository.id),
20276
- error: spawn3.error.message,
20511
+ error: spawn2.error.message,
20277
20512
  runDir: String(paths.value.runDir)
20278
20513
  });
20279
- return Result.error(spawn3.error);
20514
+ return Result.error(spawn2.error);
20280
20515
  }
20281
20516
  const validated = await validateSignalsFile(paths.value.runDir, detectScriptsOutputContract);
20282
20517
  if (!validated.ok) {
@@ -20664,7 +20899,7 @@ var ideatePromptDef = {
20664
20899
  partials: {
20665
20900
  HARNESS_CONTEXT: "harness-context"
20666
20901
  },
20667
- expectedSignals: ["ideated-tickets"]
20902
+ expectedSignals: ["ideated-tickets", "note", "learning", "decision"]
20668
20903
  };
20669
20904
  var renderRepositories2 = (project) => {
20670
20905
  if (project.repositories.length === 0) return "_no repositories configured_";
@@ -20682,19 +20917,19 @@ var buildIdeatePrompt = async (deps, input) => buildPrompt(deps, ideatePromptDef
20682
20917
  var project_name = (project) => project.displayName;
20683
20918
 
20684
20919
  // src/application/flows/ideate/leaves/ideate.contract.ts
20685
- import { z as z45 } from "zod";
20920
+ import { z as z44 } from "zod";
20686
20921
 
20687
20922
  // src/integration/ai/contract/_engine/signals/ideated-tickets/schema.ts
20688
- import { z as z44 } from "zod";
20689
- var ideatedTicketsSignalSchema = z44.object({
20690
- type: z44.literal("ideated-tickets"),
20691
- outputJson: z44.string(),
20923
+ import { z as z43 } from "zod";
20924
+ var ideatedTicketsSignalSchema = z43.object({
20925
+ type: z43.literal("ideated-tickets"),
20926
+ outputJson: z43.string(),
20692
20927
  timestamp: IsoTimestampSchema
20693
20928
  });
20694
20929
 
20695
20930
  // src/application/flows/ideate/leaves/ideate.contract.ts
20696
20931
  var exactlyOneIdeatedTickets = (signals) => signals.filter((s) => s.type === "ideated-tickets").length === 1;
20697
- var signalsArraySchemaRaw9 = z45.array(z45.union([learningSignalSchema, noteSignalSchema, decisionSignalSchema, ideatedTicketsSignalSchema])).refine(exactlyOneIdeatedTickets, "exactly one ideated-tickets signal per ideate spawn");
20932
+ var signalsArraySchemaRaw9 = z44.array(z44.union([learningSignalSchema, noteSignalSchema, decisionSignalSchema, ideatedTicketsSignalSchema])).refine(exactlyOneIdeatedTickets, "exactly one ideated-tickets signal per ideate spawn");
20698
20933
  var signalsArraySchema9 = brandSignalArray(signalsArraySchemaRaw9);
20699
20934
  var wrapLegacyArray9 = (raw) => {
20700
20935
  if (Array.isArray(raw)) return { schemaVersion: 1, signals: raw };
@@ -22767,7 +23002,7 @@ var ProjectsView = () => {
22767
23002
  };
22768
23003
 
22769
23004
  // src/application/ui/tui/views/project-detail-view.tsx
22770
- import React49, { useEffect as useEffect19, useState as useState28 } from "react";
23005
+ import React49, { useEffect as useEffect19, useMemo as useMemo13, useState as useState28 } from "react";
22771
23006
  import { Box as Box35, Text as Text37, useInput as useInput16 } from "ink";
22772
23007
 
22773
23008
  // src/application/ui/tui/components/field-list.tsx
@@ -22806,7 +23041,6 @@ var ProjectDetailView = () => {
22806
23041
  useViewHints([
22807
23042
  { keys: "r", label: "sprints" },
22808
23043
  { keys: "a", label: "add repo" },
22809
- { keys: "e", label: "edit project / repo field" },
22810
23044
  { keys: "d", label: "remove repo" },
22811
23045
  { keys: "c", label: "detect scripts" },
22812
23046
  { keys: "S", label: "detect skills" }
@@ -22830,7 +23064,21 @@ var ProjectDetailView = () => {
22830
23064
  const [confirmRemove, setConfirmRemove] = useState28(void 0);
22831
23065
  const [feedback, setFeedback] = useState28(void 0);
22832
23066
  const project = state.kind === "ok" ? state.value : void 0;
22833
- const repos = project?.repositories ?? [];
23067
+ const fields = useMemo13(() => {
23068
+ if (project === void 0) return [];
23069
+ return [
23070
+ { kind: "project", field: "displayName" },
23071
+ ...project.repositories.flatMap((r) => [
23072
+ { kind: "repo", field: "name", repo: r },
23073
+ { kind: "repo", field: "setupScript", repo: r },
23074
+ { kind: "repo", field: "verifyScript", repo: r }
23075
+ ])
23076
+ ];
23077
+ }, [project]);
23078
+ const focused = fields[Math.min(cursorIdx, Math.max(0, fields.length - 1))];
23079
+ useEffect19(() => {
23080
+ if (state.kind === "ok") setCursorIdx(0);
23081
+ }, [state.kind, projectId]);
22834
23082
  const launchPerRepoFlow = async (flowId, target) => {
22835
23083
  if (project === void 0) return;
22836
23084
  setFeedback(void 0);
@@ -22904,30 +23152,11 @@ var ProjectDetailView = () => {
22904
23152
  };
22905
23153
  };
22906
23154
  const handleEdit = () => {
22907
- if (project === void 0) return;
23155
+ if (project === void 0 || focused === void 0) return;
22908
23156
  setFeedback(void 0);
22909
- const focusedRepo = repos[Math.min(cursorIdx, Math.max(0, repos.length - 1))];
22910
- const options = [
22911
- { label: `Project: displayName (${project.displayName})`, value: { kind: "project" } },
22912
- ...focusedRepo !== void 0 ? [
22913
- { label: `Repo "${focusedRepo.name}": name`, value: { kind: "repo", field: "name", repo: focusedRepo } },
22914
- {
22915
- label: `Repo "${focusedRepo.name}": setupScript`,
22916
- value: { kind: "repo", field: "setupScript", repo: focusedRepo }
22917
- },
22918
- {
22919
- label: `Repo "${focusedRepo.name}": verifyScript`,
22920
- value: { kind: "repo", field: "verifyScript", repo: focusedRepo }
22921
- }
22922
- ] : []
22923
- ];
22924
- new Promise((resolve, reject) => {
22925
- queue.enqueue({ kind: "choice", message: "Edit which field?", options, resolve, reject });
22926
- }).then((target) => {
22927
- const cfg = renderEditPrompt(target);
22928
- if (cfg !== void 0) void edit.openEditPrompt(cfg);
22929
- }).catch(() => {
22930
- });
23157
+ const target = focused.kind === "project" ? { kind: "project" } : { kind: "repo", field: focused.field, repo: focused.repo };
23158
+ const cfg = renderEditPrompt(target);
23159
+ if (cfg !== void 0) void edit.openEditPrompt(cfg);
22931
23160
  };
22932
23161
  useInput16((input, key) => {
22933
23162
  if (ui.helpOpen || ui.promptActive || confirmRemove !== void 0 || project === void 0) return;
@@ -22935,31 +23164,28 @@ var ProjectDetailView = () => {
22935
23164
  router.push({ id: "add-repository", props: { projectId: project.id } });
22936
23165
  return;
22937
23166
  }
22938
- if (input === "e") {
23167
+ if (input === "e" || key.return) {
22939
23168
  handleEdit();
22940
23169
  return;
22941
23170
  }
22942
- if ((key.downArrow || input === "j") && repos.length > 0) {
22943
- setCursorIdx((c) => Math.min(repos.length - 1, c + 1));
23171
+ if (key.downArrow || input === "j") {
23172
+ setCursorIdx((c) => Math.min(Math.max(0, fields.length - 1), c + 1));
22944
23173
  return;
22945
23174
  }
22946
- if ((key.upArrow || input === "k") && repos.length > 0) {
23175
+ if (key.upArrow || input === "k") {
22947
23176
  setCursorIdx((c) => Math.max(0, c - 1));
22948
23177
  return;
22949
23178
  }
22950
- if (input === "d" && repos.length > 0) {
22951
- const target = repos[Math.min(cursorIdx, repos.length - 1)];
22952
- if (target !== void 0) setConfirmRemove(target);
23179
+ if (input === "d" && focused?.kind === "repo") {
23180
+ setConfirmRemove(focused.repo);
22953
23181
  return;
22954
23182
  }
22955
- if (input === "c" && repos.length > 0) {
22956
- const target = repos[Math.min(cursorIdx, repos.length - 1)];
22957
- if (target !== void 0) void launchPerRepoFlow("detect-scripts", target);
23183
+ if (input === "c" && focused?.kind === "repo") {
23184
+ void launchPerRepoFlow("detect-scripts", focused.repo);
22958
23185
  return;
22959
23186
  }
22960
- if (input === "S" && repos.length > 0) {
22961
- const target = repos[Math.min(cursorIdx, repos.length - 1)];
22962
- if (target !== void 0) void launchPerRepoFlow("detect-skills", target);
23187
+ if (input === "S" && focused?.kind === "repo") {
23188
+ void launchPerRepoFlow("detect-skills", focused.repo);
22963
23189
  }
22964
23190
  });
22965
23191
  const claimPrompt = ui.claimPrompt;
@@ -22991,14 +23217,7 @@ var ProjectDetailView = () => {
22991
23217
  onCancel: () => setConfirmRemove(void 0)
22992
23218
  }
22993
23219
  ) })
22994
- ] }) : /* @__PURE__ */ jsx47(
22995
- Body,
22996
- {
22997
- project: state.value,
22998
- cursorIdx: Math.min(cursorIdx, Math.max(0, repos.length - 1)),
22999
- feedback: feedback ?? edit.feedback
23000
- }
23001
- ) });
23220
+ ] }) : /* @__PURE__ */ jsx47(Body, { project: state.value, focused, feedback: feedback ?? edit.feedback }) });
23002
23221
  };
23003
23222
  var removeRepoFromProject = async (project, repoId, projectRepo) => {
23004
23223
  const updated = removeRepository(project, repoId);
@@ -23007,85 +23226,91 @@ var removeRepoFromProject = async (project, repoId, projectRepo) => {
23007
23226
  if (!saved.ok) return { ok: false, error: saved.error.message };
23008
23227
  return { ok: true };
23009
23228
  };
23010
- var Body = ({ project, cursorIdx, feedback }) => /* @__PURE__ */ jsxs36(Box35, { flexDirection: "column", children: [
23011
- /* @__PURE__ */ jsx47(Card, { title: "Project", tone: "primary", children: /* @__PURE__ */ jsx47(
23012
- FieldList,
23229
+ var focusable = (focused, node) => /* @__PURE__ */ jsxs36(Text37, { ...focused ? { color: inkColors.primary } : {}, bold: focused, children: [
23230
+ focused ? `${glyphs.actionCursor} ` : " ",
23231
+ node
23232
+ ] });
23233
+ var noneText = /* @__PURE__ */ jsx47(Text37, { dimColor: true, italic: true, children: "(none)" });
23234
+ var RepoCard = ({ repo, focused }) => {
23235
+ const repoFocused = focused?.kind === "repo" && focused.repo.id === repo.id;
23236
+ const nameFocused = repoFocused && focused.field === "name";
23237
+ const setupFocused = repoFocused && focused.field === "setupScript";
23238
+ const verifyFocused = repoFocused && focused.field === "verifyScript";
23239
+ return /* @__PURE__ */ jsxs36(
23240
+ Box35,
23013
23241
  {
23014
- fields: [
23015
- { label: "Name", value: /* @__PURE__ */ jsx47(Text37, { bold: true, children: project.displayName }) },
23016
- { label: "Slug", value: project.slug },
23017
- { label: "Id", value: /* @__PURE__ */ jsx47(Text37, { dimColor: true, children: project.id }) },
23018
- ...project.description !== void 0 ? [{ label: "Description", value: project.description }] : [],
23019
- { label: "Repositories", value: String(project.repositories.length) }
23242
+ flexDirection: "column",
23243
+ borderStyle: "round",
23244
+ borderColor: inkColors.rule,
23245
+ paddingX: spacing.cardPadX,
23246
+ marginTop: 1,
23247
+ children: [
23248
+ /* @__PURE__ */ jsxs36(Text37, { bold: true, ...nameFocused ? { color: inkColors.primary } : {}, children: [
23249
+ nameFocused ? `${glyphs.actionCursor} ` : " ",
23250
+ repo.name,
23251
+ " ",
23252
+ /* @__PURE__ */ jsxs36(Text37, { dimColor: true, children: [
23253
+ "(",
23254
+ repo.slug,
23255
+ ")"
23256
+ ] })
23257
+ ] }),
23258
+ /* @__PURE__ */ jsx47(
23259
+ FieldList,
23260
+ {
23261
+ fields: [
23262
+ { label: "Path", value: /* @__PURE__ */ jsx47(Text37, { dimColor: true, children: repo.path }) },
23263
+ { label: "Setup", value: focusable(setupFocused, repo.setupScript ?? noneText) },
23264
+ { label: "Verify", value: focusable(verifyFocused, repo.verifyScript ?? noneText) }
23265
+ ]
23266
+ }
23267
+ )
23020
23268
  ]
23021
23269
  }
23022
- ) }),
23023
- /* @__PURE__ */ jsxs36(Box35, { marginTop: spacing.section, flexDirection: "column", children: [
23024
- /* @__PURE__ */ jsxs36(Text37, { bold: true, children: [
23025
- glyphs.badge,
23026
- " Repositories"
23027
- ] }),
23028
- project.repositories.map((repo, idx) => {
23029
- const focused = idx === cursorIdx;
23030
- return /* @__PURE__ */ jsxs36(
23031
- Box35,
23032
- {
23033
- flexDirection: "column",
23034
- borderStyle: "round",
23035
- borderColor: focused ? inkColors.primary : inkColors.rule,
23036
- borderDimColor: !focused,
23037
- paddingX: spacing.cardPadX,
23038
- marginTop: 1,
23039
- children: [
23040
- /* @__PURE__ */ jsxs36(Text37, { bold: true, ...focused ? { color: inkColors.primary } : {}, children: [
23041
- focused ? `${glyphs.actionCursor} ` : " ",
23042
- repo.name,
23043
- " ",
23044
- /* @__PURE__ */ jsxs36(Text37, { dimColor: true, children: [
23045
- "(",
23046
- repo.slug,
23047
- ")"
23048
- ] })
23049
- ] }),
23050
- /* @__PURE__ */ jsx47(
23051
- FieldList,
23052
- {
23053
- fields: [
23054
- { label: "Path", value: /* @__PURE__ */ jsx47(Text37, { dimColor: true, children: repo.path }) },
23055
- {
23056
- label: "Setup",
23057
- value: repo.setupScript ?? /* @__PURE__ */ jsx47(Text37, { dimColor: true, italic: true, children: "(none)" })
23058
- },
23059
- {
23060
- label: "Verify",
23061
- value: repo.verifyScript ?? /* @__PURE__ */ jsx47(Text37, { dimColor: true, italic: true, children: "(none)" })
23062
- }
23063
- ]
23064
- }
23065
- )
23066
- ]
23067
- },
23068
- repo.id
23069
- );
23070
- }),
23071
- /* @__PURE__ */ jsx47(Box35, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs36(Text37, { dimColor: true, children: [
23072
- glyphs.bullet,
23073
- " a add ",
23074
- glyphs.bullet,
23075
- " \u2191/\u2193 select ",
23076
- glyphs.bullet,
23077
- " e edit field ",
23078
- glyphs.bullet,
23079
- " c detect scripts",
23080
- " ",
23081
- glyphs.bullet,
23082
- " S detect skills ",
23083
- glyphs.bullet,
23084
- " d remove (keeps \u2265 1)"
23085
- ] }) }),
23086
- feedback !== void 0 && /* @__PURE__ */ jsx47(Box35, { paddingX: spacing.indent, marginTop: 1, children: /* @__PURE__ */ jsx47(Text37, { color: feedback.startsWith("\u2717") ? inkColors.error : inkColors.primary, children: feedback }) })
23087
- ] })
23088
- ] });
23270
+ );
23271
+ };
23272
+ var Body = ({ project, focused, feedback }) => {
23273
+ const projectNameFocused = focused?.kind === "project";
23274
+ return /* @__PURE__ */ jsxs36(Box35, { flexDirection: "column", children: [
23275
+ /* @__PURE__ */ jsx47(Card, { title: "Project", tone: "primary", children: /* @__PURE__ */ jsx47(
23276
+ FieldList,
23277
+ {
23278
+ fields: [
23279
+ {
23280
+ label: "Name",
23281
+ value: focusable(projectNameFocused, /* @__PURE__ */ jsx47(Text37, { bold: true, children: project.displayName }))
23282
+ },
23283
+ { label: "Slug", value: project.slug },
23284
+ { label: "Id", value: /* @__PURE__ */ jsx47(Text37, { dimColor: true, children: project.id }) },
23285
+ ...project.description !== void 0 ? [{ label: "Description", value: project.description }] : [],
23286
+ { label: "Repositories", value: String(project.repositories.length) }
23287
+ ]
23288
+ }
23289
+ ) }),
23290
+ /* @__PURE__ */ jsxs36(Box35, { marginTop: spacing.section, flexDirection: "column", children: [
23291
+ /* @__PURE__ */ jsxs36(Text37, { bold: true, children: [
23292
+ glyphs.badge,
23293
+ " Repositories"
23294
+ ] }),
23295
+ project.repositories.map((repo) => /* @__PURE__ */ jsx47(RepoCard, { repo, focused }, repo.id)),
23296
+ /* @__PURE__ */ jsx47(Box35, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs36(Text37, { dimColor: true, children: [
23297
+ "a add ",
23298
+ glyphs.bullet,
23299
+ " \u2191/\u2193 navigate ",
23300
+ glyphs.bullet,
23301
+ " e/\u21B5 edit field ",
23302
+ glyphs.bullet,
23303
+ " c detect scripts",
23304
+ " ",
23305
+ glyphs.bullet,
23306
+ " S detect skills ",
23307
+ glyphs.bullet,
23308
+ " d remove (keeps \u2265 1)"
23309
+ ] }) }),
23310
+ feedback !== void 0 && /* @__PURE__ */ jsx47(Box35, { paddingX: spacing.indent, marginTop: 1, children: /* @__PURE__ */ jsx47(Text37, { color: feedback.startsWith("\u2717") ? inkColors.error : inkColors.primary, children: feedback }) })
23311
+ ] })
23312
+ ] });
23313
+ };
23089
23314
 
23090
23315
  // src/application/ui/tui/views/sprints-view.tsx
23091
23316
  import { useEffect as useEffect20, useState as useState29 } from "react";
@@ -23138,8 +23363,8 @@ var SprintsView = () => {
23138
23363
  const { state, reload } = useAsyncLoad(async () => {
23139
23364
  const r = await deps.sprintRepo.list();
23140
23365
  if (!r.ok) throw new Error(r.error.message);
23141
- const all = r.value;
23142
- return selection.projectId !== void 0 ? all.filter((s) => s.projectId === selection.projectId) : all;
23366
+ const scoped = selection.projectId !== void 0 ? r.value.filter((s) => s.projectId === selection.projectId) : r.value;
23367
+ return [...scoped].sort((a, b) => a.id < b.id ? 1 : a.id > b.id ? -1 : 0);
23143
23368
  }, [selection.projectId]);
23144
23369
  const items = state.kind === "ok" ? state.value : [];
23145
23370
  const [cursorId, setCursorId] = useState29(void 0);
@@ -23392,7 +23617,7 @@ var SprintsView = () => {
23392
23617
  };
23393
23618
 
23394
23619
  // src/application/ui/tui/views/sprint-detail-view.tsx
23395
- import { useEffect as useEffect22, useMemo as useMemo13, useState as useState31 } from "react";
23620
+ import { useEffect as useEffect22, useMemo as useMemo14, useState as useState31 } from "react";
23396
23621
  import { Box as Box44, Text as Text46 } from "ink";
23397
23622
 
23398
23623
  // src/application/flows/ticket-remove/flow.ts
@@ -24308,8 +24533,8 @@ var SprintDetailView = () => {
24308
24533
  const selection = useSelection();
24309
24534
  const { state, project, reload } = useSprintBundle({ sprintId, deps });
24310
24535
  const sprint = state.kind === "ok" ? state.value.sprint : void 0;
24311
- const tasks = useMemo13(() => state.kind === "ok" ? state.value.tasks : [], [state]);
24312
- const focusList = useMemo13(() => sprint !== void 0 ? buildFocusList(sprint, tasks) : [], [sprint, tasks]);
24536
+ const tasks = useMemo14(() => state.kind === "ok" ? state.value.tasks : [], [state]);
24537
+ const focusList = useMemo14(() => sprint !== void 0 ? buildFocusList(sprint, tasks) : [], [sprint, tasks]);
24313
24538
  const [cursorIdx, setCursorIdx] = useState31(0);
24314
24539
  const [openIds, setOpenIds] = useState31(() => /* @__PURE__ */ new Set());
24315
24540
  const [confirmRemove, setConfirmRemove] = useState31(void 0);
@@ -24527,13 +24752,12 @@ var useSinkStream = (bus, opts = {}) => {
24527
24752
  const [items, setItems] = useState33(() => replay ? bus.entries.slice(-limit) : []);
24528
24753
  useEffect24(() => {
24529
24754
  if (replay) setItems(bus.entries.slice(-limit));
24530
- const unsub = bus.subscribe((value) => {
24755
+ return bus.subscribe((value) => {
24531
24756
  setItems((prev) => {
24532
24757
  const next = [...prev, value];
24533
24758
  return next.length > limit ? next.slice(next.length - limit) : next;
24534
24759
  });
24535
24760
  });
24536
- return unsub;
24537
24761
  }, [bus, limit, replay]);
24538
24762
  return items;
24539
24763
  };
@@ -24546,14 +24770,13 @@ var useEventBusBuffer = (bus, opts) => {
24546
24770
  const filterRef = useRef11(opts.filter);
24547
24771
  filterRef.current = opts.filter;
24548
24772
  useEffect25(() => {
24549
- const unsub = bus.subscribe((event) => {
24773
+ return bus.subscribe((event) => {
24550
24774
  if (!filterRef.current(event)) return;
24551
24775
  setItems((prev) => {
24552
24776
  const next = [...prev, event];
24553
24777
  return next.length > limit ? next.slice(next.length - limit) : next;
24554
24778
  });
24555
24779
  });
24556
- return unsub;
24557
24780
  }, [bus, limit]);
24558
24781
  return items;
24559
24782
  };
@@ -24888,7 +25111,7 @@ import "react";
24888
25111
  import { Box as Box53 } from "ink";
24889
25112
 
24890
25113
  // src/application/ui/tui/components/baseline-health-card.tsx
24891
- import { useMemo as useMemo14 } from "react";
25114
+ import { useMemo as useMemo15 } from "react";
24892
25115
  import { Box as Box49, Text as Text51 } from "ink";
24893
25116
  import { jsx as jsx61, jsxs as jsxs50 } from "react/jsx-runtime";
24894
25117
  var tierColor3 = (tier) => {
@@ -25026,14 +25249,14 @@ var CompactCleanRow = ({ rows }) => {
25026
25249
  };
25027
25250
  var BaselineHealthCard = ({ execution, tasks, now, width }) => {
25028
25251
  const tNow = now ?? Date.now();
25029
- const taskList = useMemo14(() => tasks ?? [], [tasks]);
25252
+ const taskList = useMemo15(() => tasks ?? [], [tasks]);
25030
25253
  const cardWidth = width ?? CONTEXT_WIDTH;
25031
- const setupData = useMemo14(() => setupRowData(execution, tNow), [execution, tNow]);
25032
- const preRun = useMemo14(() => latestVerifyRun(taskList, "pre"), [taskList]);
25033
- const postRun = useMemo14(() => latestVerifyRun(taskList, "post"), [taskList]);
25254
+ const setupData = useMemo15(() => setupRowData(execution, tNow), [execution, tNow]);
25255
+ const preRun = useMemo15(() => latestVerifyRun(taskList, "pre"), [taskList]);
25256
+ const postRun = useMemo15(() => latestVerifyRun(taskList, "post"), [taskList]);
25034
25257
  const preData = verifyRowData(preRun, tNow, "Pre verify");
25035
25258
  const postData = verifyRowData(postRun, tNow, "Post verify");
25036
- const counts = useMemo14(() => countAttributions(taskList), [taskList]);
25259
+ const counts = useMemo15(() => countAttributions(taskList), [taskList]);
25037
25260
  const attribData = attributionRowData(counts);
25038
25261
  const rows = [setupData, preData, postData, attribData];
25039
25262
  const health = synthesiseBaselineHealth({
@@ -25155,7 +25378,7 @@ var Section2 = ({
25155
25378
  import "react";
25156
25379
 
25157
25380
  // src/application/ui/tui/components/step-trace.tsx
25158
- import { useMemo as useMemo15 } from "react";
25381
+ import { useMemo as useMemo16 } from "react";
25159
25382
  import { Box as Box52, Text as Text54 } from "ink";
25160
25383
  import { Fragment as Fragment5, jsx as jsx64, jsxs as jsxs53 } from "react/jsx-runtime";
25161
25384
  var glyphFor2 = (status) => {
@@ -25237,12 +25460,12 @@ var StepTrace = ({
25237
25460
  railWidth
25238
25461
  }) => {
25239
25462
  const traceLastEntry = trace[trace.length - 1];
25240
- const merged = useMemo15(
25463
+ const merged = useMemo16(
25241
25464
  () => plan !== void 0 ? mergePlanWithTrace(plan, trace, running, labelByName) : traceToRows(trace),
25242
25465
  // eslint-disable-next-line react-hooks/exhaustive-deps -- in-place ring-buffer mutation; see comment above
25243
25466
  [plan, labelByName, trace, trace.length, traceLastEntry, running]
25244
25467
  );
25245
- const filtered = useMemo15(
25468
+ const filtered = useMemo16(
25246
25469
  () => filter !== void 0 ? merged.filter((r) => filter(r.name)) : merged,
25247
25470
  [merged, filter]
25248
25471
  );
@@ -25791,7 +26014,7 @@ var ExecuteBody = ({
25791
26014
  ] });
25792
26015
 
25793
26016
  // src/application/ui/tui/views/execute-view-internals/tasks-panel-host.tsx
25794
- import { useMemo as useMemo16 } from "react";
26017
+ import { useMemo as useMemo17 } from "react";
25795
26018
  import { jsx as jsx72 } from "react/jsx-runtime";
25796
26019
  var TasksPanelHost = ({
25797
26020
  bucketed,
@@ -25802,7 +26025,7 @@ var TasksPanelHost = ({
25802
26025
  now,
25803
26026
  taskState
25804
26027
  }) => {
25805
- const taskCriteriaById = useMemo16(() => {
26028
+ const taskCriteriaById = useMemo17(() => {
25806
26029
  if (taskState === void 0) return void 0;
25807
26030
  const m = /* @__PURE__ */ new Map();
25808
26031
  for (const t of taskState) {
@@ -25951,25 +26174,31 @@ var useBaselineHealthData = ({
25951
26174
  };
25952
26175
 
25953
26176
  // src/application/ui/tui/views/execute-view-internals/use-bucketed-tasks.ts
25954
- import { useMemo as useMemo17 } from "react";
26177
+ import { useMemo as useMemo18 } from "react";
25955
26178
 
25956
26179
  // src/application/ui/tui/runtime/use-task-round-tracker.ts
25957
26180
  import { useEffect as useEffect28, useState as useState35 } from "react";
26181
+ var TASK_ROUND_CAP = 500;
25958
26182
  var isTaskRoundStarted = (e) => e.type === "task-round-started";
25959
26183
  var useTaskRoundTracker = (bus) => {
25960
26184
  const [rounds, setRounds] = useState35(() => /* @__PURE__ */ new Map());
25961
26185
  useEffect28(() => {
25962
- const unsub = bus.subscribe((event) => {
26186
+ return bus.subscribe((event) => {
25963
26187
  if (!isTaskRoundStarted(event)) return;
25964
26188
  setRounds((prev) => {
25965
26189
  const existing = prev.get(event.taskId);
25966
26190
  if (existing !== void 0 && existing.roundN >= event.roundN) return prev;
25967
26191
  const next = new Map(prev);
26192
+ next.delete(event.taskId);
25968
26193
  next.set(event.taskId, { roundN: event.roundN, totalCap: event.totalCap });
26194
+ while (next.size > TASK_ROUND_CAP) {
26195
+ const oldest = next.keys().next().value;
26196
+ if (oldest === void 0) break;
26197
+ next.delete(oldest);
26198
+ }
25969
26199
  return next;
25970
26200
  });
25971
26201
  });
25972
- return unsub;
25973
26202
  }, [bus]);
25974
26203
  return rounds;
25975
26204
  };
@@ -25981,7 +26210,7 @@ var useBucketedTasks = ({
25981
26210
  signals,
25982
26211
  eventBus
25983
26212
  }) => {
25984
- const rawBucketed = useMemo17(
26213
+ const rawBucketed = useMemo18(
25985
26214
  () => descriptor ? bucketTaskSignals(descriptor.trace, chainEvents, signals, {
25986
26215
  ...descriptor.maxTurns !== void 0 ? { maxTurns: descriptor.maxTurns } : {},
25987
26216
  ...descriptor.terminalSubstepName !== void 0 ? { terminalSubstepName: descriptor.terminalSubstepName } : {},
@@ -25994,7 +26223,7 @@ var useBucketedTasks = ({
25994
26223
  [descriptor, chainEvents, signals]
25995
26224
  );
25996
26225
  const taskRounds = useTaskRoundTracker(eventBus);
25997
- const bucketed = useMemo17(() => {
26226
+ const bucketed = useMemo18(() => {
25998
26227
  if (rawBucketed === void 0) return void 0;
25999
26228
  const tasks = rawBucketed.tasks.map((t) => {
26000
26229
  const tracked = taskRounds.get(t.id);
@@ -26102,14 +26331,14 @@ var useCancelHandlers = ({
26102
26331
  };
26103
26332
 
26104
26333
  // src/application/ui/tui/views/execute-view-internals/use-cancel-scope-stats.ts
26105
- import { useMemo as useMemo18 } from "react";
26334
+ import { useMemo as useMemo19 } from "react";
26106
26335
  var useCancelScopeStats = ({
26107
26336
  chainEvents,
26108
26337
  currentTask,
26109
26338
  bucketed,
26110
26339
  now
26111
26340
  }) => {
26112
- const attemptElapsedMs2 = useMemo18(() => {
26341
+ const attemptElapsedMs2 = useMemo19(() => {
26113
26342
  if (currentTask === void 0) return void 0;
26114
26343
  let latestStartMs;
26115
26344
  for (const ev of chainEvents) {
@@ -26120,7 +26349,7 @@ var useCancelScopeStats = ({
26120
26349
  }
26121
26350
  return latestStartMs !== void 0 ? Math.max(0, now - latestStartMs) : void 0;
26122
26351
  }, [chainEvents, currentTask, now]);
26123
- const remainingTaskCount = useMemo18(() => {
26352
+ const remainingTaskCount = useMemo19(() => {
26124
26353
  if (bucketed === void 0) return 0;
26125
26354
  return bucketed.tasks.reduce((n, t) => t.status === "completed" ? n : n + 1, 0);
26126
26355
  }, [bucketed]);
@@ -26341,7 +26570,7 @@ import { useEffect as useEffect31, useState as useState38 } from "react";
26341
26570
  import { Box as Box60, Text as Text59, useInput as useInput22 } from "ink";
26342
26571
 
26343
26572
  // src/application/ui/tui/components/list-view.tsx
26344
- import { useEffect as useEffect30, useMemo as useMemo19, useState as useState37 } from "react";
26573
+ import { useEffect as useEffect30, useMemo as useMemo20, useState as useState37 } from "react";
26345
26574
  import { Box as Box59, Text as Text58, useInput as useInput21 } from "ink";
26346
26575
  import { jsx as jsx74, jsxs as jsxs58 } from "react/jsx-runtime";
26347
26576
  var clamp4 = (n, min, max) => Math.max(min, Math.min(max, n));
@@ -26380,7 +26609,7 @@ function ListView({
26380
26609
  },
26381
26610
  { isActive: active }
26382
26611
  );
26383
- const window = useMemo19(() => {
26612
+ const window = useMemo20(() => {
26384
26613
  const half = Math.floor(visibleRows / 2);
26385
26614
  const start = clamp4(cursor - half, 0, Math.max(0, items.length - visibleRows));
26386
26615
  const end = Math.min(items.length, start + visibleRows);
@@ -26529,7 +26758,7 @@ var SessionsView = () => {
26529
26758
  };
26530
26759
 
26531
26760
  // src/application/ui/tui/views/settings-view.tsx
26532
- import React86, { useEffect as useEffect32, useMemo as useMemo20, useState as useState39 } from "react";
26761
+ import React86, { useEffect as useEffect32, useMemo as useMemo21, useState as useState39 } from "react";
26533
26762
  import { Box as Box64, Text as Text63, useInput as useInput23 } from "ink";
26534
26763
 
26535
26764
  // src/application/flows/settings-show/flow.ts
@@ -26554,11 +26783,11 @@ var MIXED = {
26554
26783
  refine: { provider: "openai-codex", model: "gpt-5.5" },
26555
26784
  plan: { provider: "github-copilot", model: "claude-sonnet-4.6", effort: "xhigh" },
26556
26785
  implement: {
26557
- generator: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" },
26558
- evaluator: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" }
26786
+ generator: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" },
26787
+ evaluator: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" }
26559
26788
  },
26560
26789
  readiness: { provider: "github-copilot", model: "gpt-5-mini", effort: "medium" },
26561
- ideate: { provider: "claude-code", model: "claude-opus-4-7" },
26790
+ ideate: { provider: "claude-code", model: "claude-opus-4-8" },
26562
26791
  // PR content drafting mirrors refine's "light summary" reasoning profile — a fast Codex
26563
26792
  // model is fine, no need to pay for Opus tokens just to summarise a diff.
26564
26793
  createPr: { provider: "openai-codex", model: "gpt-5.4-mini" }
@@ -26566,13 +26795,13 @@ var MIXED = {
26566
26795
  var CLAUDE_ONLY = {
26567
26796
  effort: "high",
26568
26797
  refine: { provider: "claude-code", model: "claude-sonnet-4-6" },
26569
- plan: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" },
26798
+ plan: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" },
26570
26799
  implement: {
26571
- generator: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" },
26572
- evaluator: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" }
26800
+ generator: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" },
26801
+ evaluator: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" }
26573
26802
  },
26574
26803
  readiness: { provider: "claude-code", model: "claude-haiku-4-5", effort: "medium" },
26575
- ideate: { provider: "claude-code", model: "claude-opus-4-7" },
26804
+ ideate: { provider: "claude-code", model: "claude-opus-4-8" },
26576
26805
  createPr: { provider: "claude-code", model: "claude-sonnet-4-6" }
26577
26806
  };
26578
26807
  var COPILOT_ONLY = {
@@ -27247,12 +27476,12 @@ var SettingsView = () => {
27247
27476
  cancelled = true;
27248
27477
  };
27249
27478
  }, []);
27250
- const sections = useMemo20(
27479
+ const sections = useMemo21(
27251
27480
  () => settings === void 0 ? [] : buildSections(settings),
27252
27481
  [settings]
27253
27482
  );
27254
27483
  const activeSection = sections[sectionIdx];
27255
- const activeFields = useMemo20(() => activeSection?.fields ?? [], [activeSection]);
27484
+ const activeFields = useMemo21(() => activeSection?.fields ?? [], [activeSection]);
27256
27485
  useEffect32(() => {
27257
27486
  if (cursor >= activeFields.length && activeFields.length > 0) setCursor(activeFields.length - 1);
27258
27487
  }, [activeFields, cursor]);
@@ -28091,20 +28320,20 @@ var renderIssueRefs = (refs) => {
28091
28320
  var buildCreatePrPrompt = async (deps, input) => buildPrompt(deps, createPrPromptDef, input);
28092
28321
 
28093
28322
  // src/application/flows/create-pr/leaves/generate-pr-content.contract.ts
28094
- import { z as z47 } from "zod";
28323
+ import { z as z46 } from "zod";
28095
28324
 
28096
28325
  // src/integration/ai/contract/_engine/signals/pr-content/schema.ts
28097
- import { z as z46 } from "zod";
28098
- var prContentSignalSchema = z46.object({
28099
- type: z46.literal("pr-content"),
28100
- title: z46.string(),
28101
- body: z46.string(),
28326
+ import { z as z45 } from "zod";
28327
+ var prContentSignalSchema = z45.object({
28328
+ type: z45.literal("pr-content"),
28329
+ title: z45.string(),
28330
+ body: z45.string(),
28102
28331
  timestamp: IsoTimestampSchema
28103
28332
  });
28104
28333
 
28105
28334
  // src/application/flows/create-pr/leaves/generate-pr-content.contract.ts
28106
28335
  var exactlyOnePrContent = (signals) => signals.filter((s) => s.type === "pr-content").length === 1;
28107
- var signalsArraySchemaRaw10 = z47.array(prContentSignalSchema).refine(exactlyOnePrContent, "exactly one pr-content signal per create-pr spawn");
28336
+ var signalsArraySchemaRaw10 = z46.array(prContentSignalSchema).refine(exactlyOnePrContent, "exactly one pr-content signal per create-pr spawn");
28108
28337
  var signalsArraySchema10 = brandSignalArray(signalsArraySchemaRaw10);
28109
28338
  var prContentSidecar = {
28110
28339
  signalKind: "pr-content",
@@ -28189,9 +28418,9 @@ var generatePrContentLeaf = (deps) => leaf("generate-pr-content", {
28189
28418
  outputDir: input.unitRoot
28190
28419
  };
28191
28420
  try {
28192
- const spawn3 = await deps.provider.generate(session);
28193
- if (!spawn3.ok) {
28194
- deps.logger.named("create-pr.ai").warn(`create-pr: AI authoring failed, falling back to template (${spawn3.error.message})`);
28421
+ const spawn2 = await deps.provider.generate(session);
28422
+ if (!spawn2.ok) {
28423
+ deps.logger.named("create-pr.ai").warn(`create-pr: AI authoring failed, falling back to template (${spawn2.error.message})`);
28195
28424
  return Result.ok({});
28196
28425
  }
28197
28426
  const validated = await validateSignalsFile(input.unitRoot, generatePrContentOutputContract);
@@ -29193,14 +29422,14 @@ var backStep3 = (step) => {
29193
29422
  };
29194
29423
 
29195
29424
  // src/application/ui/tui/views/add-ticket-internals/review-scrollable-description.tsx
29196
- import { useEffect as useEffect41, useMemo as useMemo21, useState as useState47 } from "react";
29425
+ import { useEffect as useEffect41, useMemo as useMemo22, useState as useState47 } from "react";
29197
29426
  import { Box as Box74, Text as Text73, useInput as useInput29 } from "ink";
29198
29427
  import { jsx as jsx91, jsxs as jsxs72 } from "react/jsx-runtime";
29199
29428
  var REVIEW_CHROME_ROWS = 14;
29200
29429
  var REVIEW_MIN_VIEWPORT = 4;
29201
29430
  var ReviewScrollableDescription = ({ text }) => {
29202
29431
  const term = useTerminalSize();
29203
- const lines = useMemo21(() => text.split("\n"), [text]);
29432
+ const lines = useMemo22(() => text.split("\n"), [text]);
29204
29433
  const viewport = Math.max(REVIEW_MIN_VIEWPORT, term.rows - REVIEW_CHROME_ROWS);
29205
29434
  const overflows = lines.length > viewport;
29206
29435
  const maxOffset = Math.max(0, lines.length - viewport);
@@ -29440,7 +29669,7 @@ var AddTicketView = () => {
29440
29669
  };
29441
29670
 
29442
29671
  // src/application/ui/tui/views/pick-project-view.tsx
29443
- import { useEffect as useEffect43, useMemo as useMemo22, useState as useState49 } from "react";
29672
+ import { useEffect as useEffect43, useMemo as useMemo23, useState as useState49 } from "react";
29444
29673
  import { Box as Box77, Text as Text75, useInput as useInput30 } from "ink";
29445
29674
  import { jsx as jsx94, jsxs as jsxs75 } from "react/jsx-runtime";
29446
29675
  var PickProjectView = () => {
@@ -29458,8 +29687,8 @@ var PickProjectView = () => {
29458
29687
  if (!r.ok) throw new Error(r.error.message);
29459
29688
  return r.value;
29460
29689
  }, []);
29461
- const projects = useMemo22(() => state.kind === "ok" ? state.value : [], [state]);
29462
- const initialIdx = useMemo22(() => {
29690
+ const projects = useMemo23(() => state.kind === "ok" ? state.value : [], [state]);
29691
+ const initialIdx = useMemo23(() => {
29463
29692
  if (selection.projectId === void 0) return 0;
29464
29693
  const i = projects.findIndex((p) => p.id === selection.projectId);
29465
29694
  return i === -1 ? 0 : i;
@@ -29549,7 +29778,7 @@ var PickProjectView = () => {
29549
29778
  };
29550
29779
 
29551
29780
  // src/application/ui/tui/views/pick-sprint-view.tsx
29552
- import { useEffect as useEffect44, useMemo as useMemo24, useState as useState50 } from "react";
29781
+ import { useEffect as useEffect44, useMemo as useMemo25, useState as useState50 } from "react";
29553
29782
  import { Box as Box79, Text as Text77, useInput as useInput31 } from "ink";
29554
29783
 
29555
29784
  // src/application/ui/tui/views/pick-sprint-internals/types.ts
@@ -29653,7 +29882,7 @@ var computeWindow = (totalRows, cursor, visible) => {
29653
29882
  };
29654
29883
 
29655
29884
  // src/application/ui/tui/views/pick-sprint-internals/row-views.tsx
29656
- import { useMemo as useMemo23 } from "react";
29885
+ import { useMemo as useMemo24 } from "react";
29657
29886
  import { Box as Box78, Text as Text76 } from "ink";
29658
29887
  import { jsx as jsx95, jsxs as jsxs76 } from "react/jsx-runtime";
29659
29888
  var RowWindowView = ({
@@ -29662,7 +29891,7 @@ var RowWindowView = ({
29662
29891
  visibleRows,
29663
29892
  currentSprintId
29664
29893
  }) => {
29665
- const window = useMemo23(() => computeWindow(rows.length, cursor, visibleRows), [rows.length, cursor, visibleRows]);
29894
+ const window = useMemo24(() => computeWindow(rows.length, cursor, visibleRows), [rows.length, cursor, visibleRows]);
29666
29895
  const slice = rows.slice(window.start, window.end);
29667
29896
  return /* @__PURE__ */ jsxs76(Box78, { flexDirection: "column", children: [
29668
29897
  window.hiddenAbove > 0 && /* @__PURE__ */ jsx95(Box78, { paddingX: spacing.indent, children: /* @__PURE__ */ jsxs76(Text76, { dimColor: true, children: [
@@ -29782,17 +30011,17 @@ var PickSprintView = () => {
29782
30011
  },
29783
30012
  [deps.sprintRepo, deps.projectRepo]
29784
30013
  );
29785
- const data = useMemo24(
30014
+ const data = useMemo25(
29786
30015
  () => state.kind === "ok" ? state.value : { sprints: [], projectsById: /* @__PURE__ */ new Map() },
29787
30016
  [state]
29788
30017
  );
29789
- const groups = useMemo24(() => buildGroups(data, selection.projectId, scopeAll), [data, selection.projectId, scopeAll]);
30018
+ const groups = useMemo25(() => buildGroups(data, selection.projectId, scopeAll), [data, selection.projectId, scopeAll]);
29790
30019
  const includeCreate = selection.projectId !== void 0;
29791
- const rows = useMemo24(() => flatten(groups, includeCreate), [groups, includeCreate]);
29792
- const sprintCount = useMemo24(() => rows.reduce((acc, r) => r.kind === "sprint" ? acc + 1 : acc, 0), [rows]);
30020
+ const rows = useMemo25(() => flatten(groups, includeCreate), [groups, includeCreate]);
30021
+ const sprintCount = useMemo25(() => rows.reduce((acc, r) => r.kind === "sprint" ? acc + 1 : acc, 0), [rows]);
29793
30022
  const bp = useBreakpoint();
29794
30023
  const visibleRows = Math.max(MIN_VISIBLE_ROWS, bp.rows - VERTICAL_CHROME_ROWS);
29795
- const initialIdx = useMemo24(() => {
30024
+ const initialIdx = useMemo25(() => {
29796
30025
  if (selection.sprintId !== void 0) {
29797
30026
  const i = rows.findIndex((r) => r.kind === "sprint" && r.sprint.id === selection.sprintId);
29798
30027
  if (i !== -1) return i;
@@ -29981,7 +30210,7 @@ var renderView = (entry) => {
29981
30210
  };
29982
30211
 
29983
30212
  // src/application/ui/tui/runtime/use-global-keys.ts
29984
- import { useEffect as useEffect45, useMemo as useMemo25, useRef as useRef13 } from "react";
30213
+ import { useEffect as useEffect45, useMemo as useMemo26, useRef as useRef13 } from "react";
29985
30214
  import { useApp, useInput as useInput32 } from "ink";
29986
30215
 
29987
30216
  // src/integration/io/clipboard.ts
@@ -30003,10 +30232,10 @@ var resolveHelpers = ({ platform, env }) => {
30003
30232
  }
30004
30233
  return [];
30005
30234
  };
30006
- var runHelper = (spawn3, helper, text) => new Promise((resolve) => {
30235
+ var runHelper = (spawn2, helper, text) => new Promise((resolve) => {
30007
30236
  let child;
30008
30237
  try {
30009
- child = spawn3(helper.cmd, helper.args, { stdio: ["pipe", "pipe", "pipe"] });
30238
+ child = spawn2(helper.cmd, helper.args, { stdio: ["pipe", "pipe", "pipe"] });
30010
30239
  } catch (cause) {
30011
30240
  resolve(
30012
30241
  Result.error({
@@ -30050,7 +30279,7 @@ var runHelper = (spawn3, helper, text) => new Promise((resolve) => {
30050
30279
  }
30051
30280
  });
30052
30281
  var createCopyToClipboard = (opts = {}) => {
30053
- const spawn3 = opts.spawn ?? nodeSpawn10;
30282
+ const spawn2 = opts.spawn ?? nodeSpawn10;
30054
30283
  const platform = opts.platform ?? process.platform;
30055
30284
  const env = opts.env ?? process.env;
30056
30285
  const helpers = resolveHelpers({ platform, env });
@@ -30063,7 +30292,7 @@ var createCopyToClipboard = (opts = {}) => {
30063
30292
  }
30064
30293
  let lastError;
30065
30294
  for (const helper of helpers) {
30066
- const result = await runHelper(spawn3, helper, text);
30295
+ const result = await runHelper(spawn2, helper, text);
30067
30296
  if (result.ok) return result;
30068
30297
  lastError = result.error;
30069
30298
  if (result.error.code !== "no-helper") return result;
@@ -30086,7 +30315,7 @@ var useGlobalKeys = (opts = {}) => {
30086
30315
  const ui = useUiState();
30087
30316
  const selection = useSelection();
30088
30317
  const deps = useDeps();
30089
- const copyToClipboard = useMemo25(
30318
+ const copyToClipboard = useMemo26(
30090
30319
  () => opts.copyToClipboard ?? createCopyToClipboard(),
30091
30320
  [opts.copyToClipboard]
30092
30321
  );
@@ -30259,7 +30488,7 @@ var ChainLogDegradedBanner = () => {
30259
30488
  // src/application/ui/tui/components/progress-overlay.tsx
30260
30489
  import { promises as fs29 } from "fs";
30261
30490
  import { join as join54 } from "path";
30262
- import { useEffect as useEffect48, useMemo as useMemo26, useState as useState53 } from "react";
30491
+ import { useEffect as useEffect48, useMemo as useMemo27, useState as useState53 } from "react";
30263
30492
  import { Box as Box82, Text as Text80, useInput as useInput33 } from "ink";
30264
30493
  import { jsx as jsx99, jsxs as jsxs80 } from "react/jsx-runtime";
30265
30494
  var CHROME_ROWS = 10;
@@ -30276,7 +30505,7 @@ var ProgressOverlay = () => {
30276
30505
  const [offset, setOffset] = useState53(0);
30277
30506
  const [now] = useState53(() => Date.now());
30278
30507
  const sprintId = selection.sprintId;
30279
- const progressPath = useMemo26(() => {
30508
+ const progressPath = useMemo27(() => {
30280
30509
  if (sprintId === void 0) return void 0;
30281
30510
  return join54(String(storage2.dataRoot), "sprints", String(sprintId), "progress.md");
30282
30511
  }, [sprintId, storage2.dataRoot]);
@@ -30471,20 +30700,23 @@ var resolveInitialState = ({
30471
30700
  settingsExist,
30472
30701
  projects,
30473
30702
  lastProjectId,
30474
- lastSprintId
30703
+ lastSprintId,
30704
+ sprints
30475
30705
  }) => {
30476
30706
  if (!settingsExist) return { initialView: { id: "welcome" } };
30477
- if (projects.length === 0) return { initialView: { id: "create-project" } };
30707
+ const [first] = projects;
30708
+ if (first === void 0) return { initialView: { id: "create-project" } };
30478
30709
  const restored = lastProjectId !== void 0 ? projects.find((p) => p.id === lastProjectId) : void 0;
30479
- const preselected = restored ?? (projects.length === 1 ? projects[0] : void 0);
30480
- if (preselected === void 0) return { initialView: { id: "home" } };
30481
- const carrySprintId = lastSprintId !== void 0 && preselected.id === lastProjectId ? lastSprintId : void 0;
30710
+ const resolvedProject = restored ?? first;
30711
+ const projectSprints = (sprints ?? []).filter((s) => s.projectId === resolvedProject.id).slice().sort((a, b) => a.id < b.id ? 1 : a.id > b.id ? -1 : 0);
30712
+ const persistedSprintValid = restored !== void 0 && lastSprintId !== void 0 && projectSprints.some((s) => s.id === lastSprintId);
30713
+ const seededSprintId = persistedSprintValid ? lastSprintId : projectSprints[0]?.id;
30482
30714
  return {
30483
30715
  initialView: { id: "home" },
30484
30716
  initialSelection: {
30485
- projectId: preselected.id,
30486
- projectLabel: preselected.displayName,
30487
- ...carrySprintId !== void 0 ? { sprintId: carrySprintId } : {}
30717
+ projectId: resolvedProject.id,
30718
+ projectLabel: resolvedProject.displayName,
30719
+ ...seededSprintId !== void 0 ? { sprintId: seededSprintId } : {}
30488
30720
  }
30489
30721
  };
30490
30722
  };
@@ -30783,11 +31015,13 @@ var bootstrap = async () => {
30783
31015
  void createInkInteractivePrompt(queue);
30784
31016
  const settingsExists = await deps.settingsRepo.exists();
30785
31017
  const projectsList = await deps.projectRepo.list();
31018
+ const sprintsResult = await deps.sprintRepo.list();
30786
31019
  const lastSelectionStore = createLastSelectionStore(paths.value.stateRoot);
30787
31020
  const lastSelection = await lastSelectionStore.read();
30788
31021
  const { initialView, initialSelection } = resolveInitialState({
30789
31022
  settingsExist: settingsExists.ok ? settingsExists.value : false,
30790
31023
  projects: projectsList.ok ? projectsList.value : [],
31024
+ sprints: sprintsResult.ok ? sprintsResult.value : [],
30791
31025
  ...lastSelection !== void 0 ? { lastProjectId: lastSelection.projectId } : {},
30792
31026
  ...lastSelection?.sprintId !== void 0 ? { lastSprintId: lastSelection.sprintId } : {}
30793
31027
  });