ralphctl 0.8.3 → 0.8.4

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
  },
@@ -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;
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
+ });
2934
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
  }
@@ -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.4",
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"
@@ -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 } : {},
@@ -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):");
@@ -13637,7 +13864,18 @@ var implementPromptDef = {
13637
13864
  },
13638
13865
  // Documents the harness signals the implement response is expected to carry. Validation is
13639
13866
  // 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"]
13867
+ // Aligned with generator.contract.ts: narrative fan-out (change, decision, learning, note)
13868
+ // plus lifecycle signals (task-verified, task-complete, task-blocked, commit-message).
13869
+ expectedSignals: [
13870
+ "change",
13871
+ "decision",
13872
+ "learning",
13873
+ "note",
13874
+ "task-verified",
13875
+ "task-complete",
13876
+ "task-blocked",
13877
+ "commit-message"
13878
+ ]
13641
13879
  };
13642
13880
  var buildImplementPrompt = async (deps, input) => buildPrompt(deps, implementPromptDef, {
13643
13881
  taskName: input.task.name,
@@ -13656,7 +13894,7 @@ var buildImplementPrompt = async (deps, input) => buildPrompt(deps, implementPro
13656
13894
  });
13657
13895
 
13658
13896
  // src/application/flows/implement/leaves/generator.contract.ts
13659
- import { z as z31 } from "zod";
13897
+ import { z as z30 } from "zod";
13660
13898
 
13661
13899
  // src/integration/ai/contract/_engine/signals/change/schema.ts
13662
13900
  import { z as z25 } from "zod";
@@ -13675,44 +13913,33 @@ var commitMessageSignalSchema = z26.object({
13675
13913
  timestamp: IsoTimestampSchema
13676
13914
  });
13677
13915
 
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
13916
  // 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(),
13917
+ import { z as z27 } from "zod";
13918
+ var taskBlockedSignalSchema = z27.object({
13919
+ type: z27.literal("task-blocked"),
13920
+ reason: z27.string(),
13694
13921
  timestamp: IsoTimestampSchema
13695
13922
  });
13696
13923
 
13697
13924
  // 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"),
13925
+ import { z as z28 } from "zod";
13926
+ var taskCompleteSignalSchema = z28.object({
13927
+ type: z28.literal("task-complete"),
13701
13928
  timestamp: IsoTimestampSchema
13702
13929
  });
13703
13930
 
13704
13931
  // 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(),
13932
+ import { z as z29 } from "zod";
13933
+ var taskVerifiedSignalSchema = z29.object({
13934
+ type: z29.literal("task-verified"),
13935
+ output: z29.string(),
13709
13936
  timestamp: IsoTimestampSchema
13710
13937
  });
13711
13938
 
13712
13939
  // src/application/flows/implement/leaves/generator.contract.ts
13713
13940
  var atMostOneCommitMessage = (signals) => signals.filter((s) => s.type === "commit-message").length <= 1;
13714
- var signalsArraySchemaRaw3 = z31.array(
13715
- z31.union([
13941
+ var signalsArraySchemaRaw3 = z30.array(
13942
+ z30.union([
13716
13943
  changeSignalSchema,
13717
13944
  learningSignalSchema,
13718
13945
  noteSignalSchema,
@@ -13720,8 +13947,7 @@ var signalsArraySchemaRaw3 = z31.array(
13720
13947
  taskVerifiedSignalSchema,
13721
13948
  taskCompleteSignalSchema,
13722
13949
  taskBlockedSignalSchema,
13723
- commitMessageSignalSchema,
13724
- progressEntrySignalSchema
13950
+ commitMessageSignalSchema
13725
13951
  ])
13726
13952
  ).refine(atMostOneCommitMessage, "at most one commit-message signal per generator spawn");
13727
13953
  var signalsArraySchema3 = brandSignalArray(signalsArraySchemaRaw3);
@@ -14812,15 +15038,15 @@ var implementSession = (sandboxCwd, repoPath, sprintDir2, prompt, model, signals
14812
15038
  };
14813
15039
 
14814
15040
  // src/application/flows/implement/leaves/evaluator.contract.ts
14815
- import { z as z33 } from "zod";
15041
+ import { z as z32 } from "zod";
14816
15042
 
14817
15043
  // 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()
15044
+ import { z as z31 } from "zod";
15045
+ var dimensionScoreSchema = z31.object({
15046
+ dimension: z31.string(),
15047
+ passed: z31.boolean(),
15048
+ finding: z31.string(),
15049
+ executionEvidence: z31.string().optional()
14824
15050
  }).superRefine((d, ctx) => {
14825
15051
  if (!d.passed && d.finding.trim().length === 0) {
14826
15052
  ctx.addIssue({
@@ -14830,11 +15056,11 @@ var dimensionScoreSchema = z32.object({
14830
15056
  });
14831
15057
  }
14832
15058
  });
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(),
15059
+ var evaluationSignalSchema = z31.object({
15060
+ type: z31.literal("evaluation"),
15061
+ status: z31.union([z31.literal("passed"), z31.literal("failed"), z31.literal("malformed")]),
15062
+ dimensions: z31.array(dimensionScoreSchema).readonly(),
15063
+ critique: z31.string().optional(),
14838
15064
  timestamp: IsoTimestampSchema
14839
15065
  }).superRefine((s, ctx) => {
14840
15066
  if (s.status === "passed") {
@@ -14894,8 +15120,8 @@ var renderEvaluationMarkdown = (signal) => {
14894
15120
 
14895
15121
  // src/application/flows/implement/leaves/evaluator.contract.ts
14896
15122
  var exactlyOneEvaluation = (signals) => signals.filter((s) => s.type === "evaluation").length === 1;
14897
- var signalsArraySchemaRaw4 = z33.array(
14898
- z33.union([
15123
+ var signalsArraySchemaRaw4 = z32.array(
15124
+ z32.union([
14899
15125
  changeSignalSchema,
14900
15126
  learningSignalSchema,
14901
15127
  noteSignalSchema,
@@ -18055,12 +18281,12 @@ var buildApplyFeedbackPrompt = async (deps, input) => buildPrompt(deps, applyFee
18055
18281
  });
18056
18282
 
18057
18283
  // src/application/flows/review/leaves/review-round.contract.ts
18058
- import { z as z34 } from "zod";
18284
+ import { z as z33 } from "zod";
18059
18285
  var hasExactlyOneTerminal = (signals) => {
18060
18286
  const terminals = signals.filter((s) => s.type === "task-complete" || s.type === "task-blocked");
18061
18287
  return terminals.length === 1;
18062
18288
  };
18063
- var signalsArraySchemaRaw5 = z34.array(z34.union([taskCompleteSignalSchema, taskBlockedSignalSchema])).refine(hasExactlyOneTerminal, "exactly one of `task-complete` or `task-blocked` is required per round");
18289
+ var signalsArraySchemaRaw5 = z33.array(z33.union([taskCompleteSignalSchema, taskBlockedSignalSchema])).refine(hasExactlyOneTerminal, "exactly one of `task-complete` or `task-blocked` is required per round");
18064
18290
  var signalsArraySchema5 = brandSignalArray(signalsArraySchemaRaw5);
18065
18291
  var wrapLegacyArray5 = (raw) => {
18066
18292
  if (Array.isArray(raw)) return { schemaVersion: 1, signals: raw };
@@ -18718,6 +18944,16 @@ var wireTagFor = (tool) => {
18718
18944
  return "agents-md";
18719
18945
  }
18720
18946
  };
18947
+ var conventionsPartialName = (tool) => {
18948
+ switch (tool) {
18949
+ case "claude-code":
18950
+ return "conventions-claude-md";
18951
+ case "copilot":
18952
+ return "conventions-copilot-instructions";
18953
+ case "codex":
18954
+ return "conventions-agents-md";
18955
+ }
18956
+ };
18721
18957
  var readinessPromptDef = {
18722
18958
  templateName: "readiness",
18723
18959
  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 +18985,10 @@ var readinessPromptDef = {
18749
18985
  placeholder: "DETECTED_ARTEFACTS",
18750
18986
  description: 'Bullet list of artefact paths discovered by the probe, or "no artefacts detected".'
18751
18987
  },
18988
+ targetFileConventions: {
18989
+ placeholder: "TARGET_FILE_CONVENTIONS",
18990
+ description: "Per-provider style guide for the target context file (CLAUDE.md / .github/copilot-instructions.md / AGENTS.md). Loaded from the matching conventions partial."
18991
+ },
18752
18992
  outputContractSection: {
18753
18993
  placeholder: "OUTPUT_CONTRACT_SECTION",
18754
18994
  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 +19039,60 @@ var collectArtefactPaths = (state) => {
18799
19039
  }
18800
19040
  return paths;
18801
19041
  };
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
- });
19042
+ var buildReadinessPrompt = async (deps, input) => {
19043
+ const partialName = conventionsPartialName(input.currentTool);
19044
+ const conventionsResult = await deps.load(partialName);
19045
+ if (!conventionsResult.ok) return Result.error(conventionsResult.error);
19046
+ return buildPrompt(deps, readinessPromptDef, {
19047
+ repositoryPath: input.repositoryPath,
19048
+ currentTool: renderCurrentTool(input.currentTool),
19049
+ wireTag: wireTagFor(input.currentTool),
19050
+ existingContextFile: renderExistingContextFile(input.existingContextFile),
19051
+ detectedArtefacts: renderDetectedArtefacts(collectArtefactPaths(input.probedState)),
19052
+ targetFileConventions: conventionsResult.value.trim(),
19053
+ outputContractSection: input.outputContractSection
19054
+ });
19055
+ };
18810
19056
 
18811
19057
  // src/application/flows/readiness/leaves/readiness.contract.ts
18812
- import { z as z39 } from "zod";
19058
+ import { z as z38 } from "zod";
18813
19059
 
18814
19060
  // 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(),
19061
+ import { z as z34 } from "zod";
19062
+ var agentsMdProposalSignalSchema = z34.object({
19063
+ type: z34.literal("agents-md-proposal"),
19064
+ tag: z34.union([z34.literal("claude-md"), z34.literal("copilot-instructions"), z34.literal("agents-md")]),
19065
+ content: z34.string(),
18820
19066
  timestamp: IsoTimestampSchema
18821
19067
  });
18822
19068
 
18823
19069
  // 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(),
19070
+ import { z as z35 } from "zod";
19071
+ var setupSkillProposalSignalSchema = z35.object({
19072
+ type: z35.literal("setup-skill-proposal"),
19073
+ content: z35.string(),
18828
19074
  timestamp: IsoTimestampSchema
18829
19075
  });
18830
19076
 
18831
19077
  // 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(),
19078
+ import { z as z36 } from "zod";
19079
+ var skillSuggestionsSignalSchema = z36.object({
19080
+ type: z36.literal("skill-suggestions"),
19081
+ names: z36.array(z36.string()).readonly(),
18836
19082
  timestamp: IsoTimestampSchema
18837
19083
  });
18838
19084
 
18839
19085
  // 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(),
19086
+ import { z as z37 } from "zod";
19087
+ var verifySkillProposalSignalSchema = z37.object({
19088
+ type: z37.literal("verify-skill-proposal"),
19089
+ content: z37.string(),
18844
19090
  timestamp: IsoTimestampSchema
18845
19091
  });
18846
19092
 
18847
19093
  // src/application/flows/readiness/leaves/readiness.contract.ts
18848
- var signalsArraySchemaRaw6 = z39.array(
18849
- z39.union([
19094
+ var signalsArraySchemaRaw6 = z38.array(
19095
+ z38.union([
18850
19096
  learningSignalSchema,
18851
19097
  noteSignalSchema,
18852
19098
  agentsMdProposalSignalSchema,
@@ -19411,9 +19657,9 @@ var buildDetectSkillsPrompt = async (loader, input) => buildPrompt(loader, detec
19411
19657
  });
19412
19658
 
19413
19659
  // src/application/flows/detect-skills/leaves/propose.contract.ts
19414
- import { z as z40 } from "zod";
19660
+ import { z as z39 } from "zod";
19415
19661
  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");
19662
+ 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
19663
  var signalsArraySchema7 = brandSignalArray(signalsArraySchemaRaw7);
19418
19664
  var wrapLegacyArray7 = (raw) => {
19419
19665
  if (Array.isArray(raw)) return { schemaVersion: 1, signals: raw };
@@ -20168,27 +20414,27 @@ var buildDetectScriptsPrompt = async (loader, input) => buildPrompt(loader, dete
20168
20414
  });
20169
20415
 
20170
20416
  // src/application/flows/detect-scripts/leaves/propose.contract.ts
20171
- import { z as z43 } from "zod";
20417
+ import { z as z42 } from "zod";
20172
20418
 
20173
20419
  // 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(),
20420
+ import { z as z40 } from "zod";
20421
+ var setupScriptSignalSchema = z40.object({
20422
+ type: z40.literal("setup-script"),
20423
+ command: z40.string(),
20178
20424
  timestamp: IsoTimestampSchema
20179
20425
  });
20180
20426
 
20181
20427
  // 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(),
20428
+ import { z as z41 } from "zod";
20429
+ var verifyScriptSignalSchema = z41.object({
20430
+ type: z41.literal("verify-script"),
20431
+ command: z41.string(),
20186
20432
  timestamp: IsoTimestampSchema
20187
20433
  });
20188
20434
 
20189
20435
  // src/application/flows/detect-scripts/leaves/propose.contract.ts
20190
20436
  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");
20437
+ 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
20438
  var signalsArraySchema8 = brandSignalArray(signalsArraySchemaRaw8);
20193
20439
  var wrapLegacyArray8 = (raw) => {
20194
20440
  if (Array.isArray(raw)) return { schemaVersion: 1, signals: raw };
@@ -20664,7 +20910,7 @@ var ideatePromptDef = {
20664
20910
  partials: {
20665
20911
  HARNESS_CONTEXT: "harness-context"
20666
20912
  },
20667
- expectedSignals: ["ideated-tickets"]
20913
+ expectedSignals: ["ideated-tickets", "note", "learning", "decision"]
20668
20914
  };
20669
20915
  var renderRepositories2 = (project) => {
20670
20916
  if (project.repositories.length === 0) return "_no repositories configured_";
@@ -20682,19 +20928,19 @@ var buildIdeatePrompt = async (deps, input) => buildPrompt(deps, ideatePromptDef
20682
20928
  var project_name = (project) => project.displayName;
20683
20929
 
20684
20930
  // src/application/flows/ideate/leaves/ideate.contract.ts
20685
- import { z as z45 } from "zod";
20931
+ import { z as z44 } from "zod";
20686
20932
 
20687
20933
  // 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(),
20934
+ import { z as z43 } from "zod";
20935
+ var ideatedTicketsSignalSchema = z43.object({
20936
+ type: z43.literal("ideated-tickets"),
20937
+ outputJson: z43.string(),
20692
20938
  timestamp: IsoTimestampSchema
20693
20939
  });
20694
20940
 
20695
20941
  // src/application/flows/ideate/leaves/ideate.contract.ts
20696
20942
  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");
20943
+ var signalsArraySchemaRaw9 = z44.array(z44.union([learningSignalSchema, noteSignalSchema, decisionSignalSchema, ideatedTicketsSignalSchema])).refine(exactlyOneIdeatedTickets, "exactly one ideated-tickets signal per ideate spawn");
20698
20944
  var signalsArraySchema9 = brandSignalArray(signalsArraySchemaRaw9);
20699
20945
  var wrapLegacyArray9 = (raw) => {
20700
20946
  if (Array.isArray(raw)) return { schemaVersion: 1, signals: raw };
@@ -22767,7 +23013,7 @@ var ProjectsView = () => {
22767
23013
  };
22768
23014
 
22769
23015
  // src/application/ui/tui/views/project-detail-view.tsx
22770
- import React49, { useEffect as useEffect19, useState as useState28 } from "react";
23016
+ import React49, { useEffect as useEffect19, useMemo as useMemo13, useState as useState28 } from "react";
22771
23017
  import { Box as Box35, Text as Text37, useInput as useInput16 } from "ink";
22772
23018
 
22773
23019
  // src/application/ui/tui/components/field-list.tsx
@@ -22806,7 +23052,6 @@ var ProjectDetailView = () => {
22806
23052
  useViewHints([
22807
23053
  { keys: "r", label: "sprints" },
22808
23054
  { keys: "a", label: "add repo" },
22809
- { keys: "e", label: "edit project / repo field" },
22810
23055
  { keys: "d", label: "remove repo" },
22811
23056
  { keys: "c", label: "detect scripts" },
22812
23057
  { keys: "S", label: "detect skills" }
@@ -22830,7 +23075,21 @@ var ProjectDetailView = () => {
22830
23075
  const [confirmRemove, setConfirmRemove] = useState28(void 0);
22831
23076
  const [feedback, setFeedback] = useState28(void 0);
22832
23077
  const project = state.kind === "ok" ? state.value : void 0;
22833
- const repos = project?.repositories ?? [];
23078
+ const fields = useMemo13(() => {
23079
+ if (project === void 0) return [];
23080
+ return [
23081
+ { kind: "project", field: "displayName" },
23082
+ ...project.repositories.flatMap((r) => [
23083
+ { kind: "repo", field: "name", repo: r },
23084
+ { kind: "repo", field: "setupScript", repo: r },
23085
+ { kind: "repo", field: "verifyScript", repo: r }
23086
+ ])
23087
+ ];
23088
+ }, [project]);
23089
+ const focused = fields[Math.min(cursorIdx, Math.max(0, fields.length - 1))];
23090
+ useEffect19(() => {
23091
+ if (state.kind === "ok") setCursorIdx(0);
23092
+ }, [state.kind, projectId]);
22834
23093
  const launchPerRepoFlow = async (flowId, target) => {
22835
23094
  if (project === void 0) return;
22836
23095
  setFeedback(void 0);
@@ -22904,30 +23163,11 @@ var ProjectDetailView = () => {
22904
23163
  };
22905
23164
  };
22906
23165
  const handleEdit = () => {
22907
- if (project === void 0) return;
23166
+ if (project === void 0 || focused === void 0) return;
22908
23167
  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
- });
23168
+ const target = focused.kind === "project" ? { kind: "project" } : { kind: "repo", field: focused.field, repo: focused.repo };
23169
+ const cfg = renderEditPrompt(target);
23170
+ if (cfg !== void 0) void edit.openEditPrompt(cfg);
22931
23171
  };
22932
23172
  useInput16((input, key) => {
22933
23173
  if (ui.helpOpen || ui.promptActive || confirmRemove !== void 0 || project === void 0) return;
@@ -22935,31 +23175,28 @@ var ProjectDetailView = () => {
22935
23175
  router.push({ id: "add-repository", props: { projectId: project.id } });
22936
23176
  return;
22937
23177
  }
22938
- if (input === "e") {
23178
+ if (input === "e" || key.return) {
22939
23179
  handleEdit();
22940
23180
  return;
22941
23181
  }
22942
- if ((key.downArrow || input === "j") && repos.length > 0) {
22943
- setCursorIdx((c) => Math.min(repos.length - 1, c + 1));
23182
+ if (key.downArrow || input === "j") {
23183
+ setCursorIdx((c) => Math.min(Math.max(0, fields.length - 1), c + 1));
22944
23184
  return;
22945
23185
  }
22946
- if ((key.upArrow || input === "k") && repos.length > 0) {
23186
+ if (key.upArrow || input === "k") {
22947
23187
  setCursorIdx((c) => Math.max(0, c - 1));
22948
23188
  return;
22949
23189
  }
22950
- if (input === "d" && repos.length > 0) {
22951
- const target = repos[Math.min(cursorIdx, repos.length - 1)];
22952
- if (target !== void 0) setConfirmRemove(target);
23190
+ if (input === "d" && focused?.kind === "repo") {
23191
+ setConfirmRemove(focused.repo);
22953
23192
  return;
22954
23193
  }
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);
23194
+ if (input === "c" && focused?.kind === "repo") {
23195
+ void launchPerRepoFlow("detect-scripts", focused.repo);
22958
23196
  return;
22959
23197
  }
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);
23198
+ if (input === "S" && focused?.kind === "repo") {
23199
+ void launchPerRepoFlow("detect-skills", focused.repo);
22963
23200
  }
22964
23201
  });
22965
23202
  const claimPrompt = ui.claimPrompt;
@@ -22991,14 +23228,7 @@ var ProjectDetailView = () => {
22991
23228
  onCancel: () => setConfirmRemove(void 0)
22992
23229
  }
22993
23230
  ) })
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
- ) });
23231
+ ] }) : /* @__PURE__ */ jsx47(Body, { project: state.value, focused, feedback: feedback ?? edit.feedback }) });
23002
23232
  };
23003
23233
  var removeRepoFromProject = async (project, repoId, projectRepo) => {
23004
23234
  const updated = removeRepository(project, repoId);
@@ -23007,85 +23237,91 @@ var removeRepoFromProject = async (project, repoId, projectRepo) => {
23007
23237
  if (!saved.ok) return { ok: false, error: saved.error.message };
23008
23238
  return { ok: true };
23009
23239
  };
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,
23240
+ var focusable = (focused, node) => /* @__PURE__ */ jsxs36(Text37, { ...focused ? { color: inkColors.primary } : {}, bold: focused, children: [
23241
+ focused ? `${glyphs.actionCursor} ` : " ",
23242
+ node
23243
+ ] });
23244
+ var noneText = /* @__PURE__ */ jsx47(Text37, { dimColor: true, italic: true, children: "(none)" });
23245
+ var RepoCard = ({ repo, focused }) => {
23246
+ const repoFocused = focused?.kind === "repo" && focused.repo.id === repo.id;
23247
+ const nameFocused = repoFocused && focused.field === "name";
23248
+ const setupFocused = repoFocused && focused.field === "setupScript";
23249
+ const verifyFocused = repoFocused && focused.field === "verifyScript";
23250
+ return /* @__PURE__ */ jsxs36(
23251
+ Box35,
23013
23252
  {
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) }
23253
+ flexDirection: "column",
23254
+ borderStyle: "round",
23255
+ borderColor: inkColors.rule,
23256
+ paddingX: spacing.cardPadX,
23257
+ marginTop: 1,
23258
+ children: [
23259
+ /* @__PURE__ */ jsxs36(Text37, { bold: true, ...nameFocused ? { color: inkColors.primary } : {}, children: [
23260
+ nameFocused ? `${glyphs.actionCursor} ` : " ",
23261
+ repo.name,
23262
+ " ",
23263
+ /* @__PURE__ */ jsxs36(Text37, { dimColor: true, children: [
23264
+ "(",
23265
+ repo.slug,
23266
+ ")"
23267
+ ] })
23268
+ ] }),
23269
+ /* @__PURE__ */ jsx47(
23270
+ FieldList,
23271
+ {
23272
+ fields: [
23273
+ { label: "Path", value: /* @__PURE__ */ jsx47(Text37, { dimColor: true, children: repo.path }) },
23274
+ { label: "Setup", value: focusable(setupFocused, repo.setupScript ?? noneText) },
23275
+ { label: "Verify", value: focusable(verifyFocused, repo.verifyScript ?? noneText) }
23276
+ ]
23277
+ }
23278
+ )
23020
23279
  ]
23021
23280
  }
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
- ] });
23281
+ );
23282
+ };
23283
+ var Body = ({ project, focused, feedback }) => {
23284
+ const projectNameFocused = focused?.kind === "project";
23285
+ return /* @__PURE__ */ jsxs36(Box35, { flexDirection: "column", children: [
23286
+ /* @__PURE__ */ jsx47(Card, { title: "Project", tone: "primary", children: /* @__PURE__ */ jsx47(
23287
+ FieldList,
23288
+ {
23289
+ fields: [
23290
+ {
23291
+ label: "Name",
23292
+ value: focusable(projectNameFocused, /* @__PURE__ */ jsx47(Text37, { bold: true, children: project.displayName }))
23293
+ },
23294
+ { label: "Slug", value: project.slug },
23295
+ { label: "Id", value: /* @__PURE__ */ jsx47(Text37, { dimColor: true, children: project.id }) },
23296
+ ...project.description !== void 0 ? [{ label: "Description", value: project.description }] : [],
23297
+ { label: "Repositories", value: String(project.repositories.length) }
23298
+ ]
23299
+ }
23300
+ ) }),
23301
+ /* @__PURE__ */ jsxs36(Box35, { marginTop: spacing.section, flexDirection: "column", children: [
23302
+ /* @__PURE__ */ jsxs36(Text37, { bold: true, children: [
23303
+ glyphs.badge,
23304
+ " Repositories"
23305
+ ] }),
23306
+ project.repositories.map((repo) => /* @__PURE__ */ jsx47(RepoCard, { repo, focused }, repo.id)),
23307
+ /* @__PURE__ */ jsx47(Box35, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs36(Text37, { dimColor: true, children: [
23308
+ "a add ",
23309
+ glyphs.bullet,
23310
+ " \u2191/\u2193 navigate ",
23311
+ glyphs.bullet,
23312
+ " e/\u21B5 edit field ",
23313
+ glyphs.bullet,
23314
+ " c detect scripts",
23315
+ " ",
23316
+ glyphs.bullet,
23317
+ " S detect skills ",
23318
+ glyphs.bullet,
23319
+ " d remove (keeps \u2265 1)"
23320
+ ] }) }),
23321
+ 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 }) })
23322
+ ] })
23323
+ ] });
23324
+ };
23089
23325
 
23090
23326
  // src/application/ui/tui/views/sprints-view.tsx
23091
23327
  import { useEffect as useEffect20, useState as useState29 } from "react";
@@ -23138,8 +23374,8 @@ var SprintsView = () => {
23138
23374
  const { state, reload } = useAsyncLoad(async () => {
23139
23375
  const r = await deps.sprintRepo.list();
23140
23376
  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;
23377
+ const scoped = selection.projectId !== void 0 ? r.value.filter((s) => s.projectId === selection.projectId) : r.value;
23378
+ return [...scoped].sort((a, b) => a.id < b.id ? 1 : a.id > b.id ? -1 : 0);
23143
23379
  }, [selection.projectId]);
23144
23380
  const items = state.kind === "ok" ? state.value : [];
23145
23381
  const [cursorId, setCursorId] = useState29(void 0);
@@ -23392,7 +23628,7 @@ var SprintsView = () => {
23392
23628
  };
23393
23629
 
23394
23630
  // src/application/ui/tui/views/sprint-detail-view.tsx
23395
- import { useEffect as useEffect22, useMemo as useMemo13, useState as useState31 } from "react";
23631
+ import { useEffect as useEffect22, useMemo as useMemo14, useState as useState31 } from "react";
23396
23632
  import { Box as Box44, Text as Text46 } from "ink";
23397
23633
 
23398
23634
  // src/application/flows/ticket-remove/flow.ts
@@ -24308,8 +24544,8 @@ var SprintDetailView = () => {
24308
24544
  const selection = useSelection();
24309
24545
  const { state, project, reload } = useSprintBundle({ sprintId, deps });
24310
24546
  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]);
24547
+ const tasks = useMemo14(() => state.kind === "ok" ? state.value.tasks : [], [state]);
24548
+ const focusList = useMemo14(() => sprint !== void 0 ? buildFocusList(sprint, tasks) : [], [sprint, tasks]);
24313
24549
  const [cursorIdx, setCursorIdx] = useState31(0);
24314
24550
  const [openIds, setOpenIds] = useState31(() => /* @__PURE__ */ new Set());
24315
24551
  const [confirmRemove, setConfirmRemove] = useState31(void 0);
@@ -24527,13 +24763,12 @@ var useSinkStream = (bus, opts = {}) => {
24527
24763
  const [items, setItems] = useState33(() => replay ? bus.entries.slice(-limit) : []);
24528
24764
  useEffect24(() => {
24529
24765
  if (replay) setItems(bus.entries.slice(-limit));
24530
- const unsub = bus.subscribe((value) => {
24766
+ return bus.subscribe((value) => {
24531
24767
  setItems((prev) => {
24532
24768
  const next = [...prev, value];
24533
24769
  return next.length > limit ? next.slice(next.length - limit) : next;
24534
24770
  });
24535
24771
  });
24536
- return unsub;
24537
24772
  }, [bus, limit, replay]);
24538
24773
  return items;
24539
24774
  };
@@ -24546,14 +24781,13 @@ var useEventBusBuffer = (bus, opts) => {
24546
24781
  const filterRef = useRef11(opts.filter);
24547
24782
  filterRef.current = opts.filter;
24548
24783
  useEffect25(() => {
24549
- const unsub = bus.subscribe((event) => {
24784
+ return bus.subscribe((event) => {
24550
24785
  if (!filterRef.current(event)) return;
24551
24786
  setItems((prev) => {
24552
24787
  const next = [...prev, event];
24553
24788
  return next.length > limit ? next.slice(next.length - limit) : next;
24554
24789
  });
24555
24790
  });
24556
- return unsub;
24557
24791
  }, [bus, limit]);
24558
24792
  return items;
24559
24793
  };
@@ -24888,7 +25122,7 @@ import "react";
24888
25122
  import { Box as Box53 } from "ink";
24889
25123
 
24890
25124
  // src/application/ui/tui/components/baseline-health-card.tsx
24891
- import { useMemo as useMemo14 } from "react";
25125
+ import { useMemo as useMemo15 } from "react";
24892
25126
  import { Box as Box49, Text as Text51 } from "ink";
24893
25127
  import { jsx as jsx61, jsxs as jsxs50 } from "react/jsx-runtime";
24894
25128
  var tierColor3 = (tier) => {
@@ -25026,14 +25260,14 @@ var CompactCleanRow = ({ rows }) => {
25026
25260
  };
25027
25261
  var BaselineHealthCard = ({ execution, tasks, now, width }) => {
25028
25262
  const tNow = now ?? Date.now();
25029
- const taskList = useMemo14(() => tasks ?? [], [tasks]);
25263
+ const taskList = useMemo15(() => tasks ?? [], [tasks]);
25030
25264
  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]);
25265
+ const setupData = useMemo15(() => setupRowData(execution, tNow), [execution, tNow]);
25266
+ const preRun = useMemo15(() => latestVerifyRun(taskList, "pre"), [taskList]);
25267
+ const postRun = useMemo15(() => latestVerifyRun(taskList, "post"), [taskList]);
25034
25268
  const preData = verifyRowData(preRun, tNow, "Pre verify");
25035
25269
  const postData = verifyRowData(postRun, tNow, "Post verify");
25036
- const counts = useMemo14(() => countAttributions(taskList), [taskList]);
25270
+ const counts = useMemo15(() => countAttributions(taskList), [taskList]);
25037
25271
  const attribData = attributionRowData(counts);
25038
25272
  const rows = [setupData, preData, postData, attribData];
25039
25273
  const health = synthesiseBaselineHealth({
@@ -25155,7 +25389,7 @@ var Section2 = ({
25155
25389
  import "react";
25156
25390
 
25157
25391
  // src/application/ui/tui/components/step-trace.tsx
25158
- import { useMemo as useMemo15 } from "react";
25392
+ import { useMemo as useMemo16 } from "react";
25159
25393
  import { Box as Box52, Text as Text54 } from "ink";
25160
25394
  import { Fragment as Fragment5, jsx as jsx64, jsxs as jsxs53 } from "react/jsx-runtime";
25161
25395
  var glyphFor2 = (status) => {
@@ -25237,12 +25471,12 @@ var StepTrace = ({
25237
25471
  railWidth
25238
25472
  }) => {
25239
25473
  const traceLastEntry = trace[trace.length - 1];
25240
- const merged = useMemo15(
25474
+ const merged = useMemo16(
25241
25475
  () => plan !== void 0 ? mergePlanWithTrace(plan, trace, running, labelByName) : traceToRows(trace),
25242
25476
  // eslint-disable-next-line react-hooks/exhaustive-deps -- in-place ring-buffer mutation; see comment above
25243
25477
  [plan, labelByName, trace, trace.length, traceLastEntry, running]
25244
25478
  );
25245
- const filtered = useMemo15(
25479
+ const filtered = useMemo16(
25246
25480
  () => filter !== void 0 ? merged.filter((r) => filter(r.name)) : merged,
25247
25481
  [merged, filter]
25248
25482
  );
@@ -25791,7 +26025,7 @@ var ExecuteBody = ({
25791
26025
  ] });
25792
26026
 
25793
26027
  // src/application/ui/tui/views/execute-view-internals/tasks-panel-host.tsx
25794
- import { useMemo as useMemo16 } from "react";
26028
+ import { useMemo as useMemo17 } from "react";
25795
26029
  import { jsx as jsx72 } from "react/jsx-runtime";
25796
26030
  var TasksPanelHost = ({
25797
26031
  bucketed,
@@ -25802,7 +26036,7 @@ var TasksPanelHost = ({
25802
26036
  now,
25803
26037
  taskState
25804
26038
  }) => {
25805
- const taskCriteriaById = useMemo16(() => {
26039
+ const taskCriteriaById = useMemo17(() => {
25806
26040
  if (taskState === void 0) return void 0;
25807
26041
  const m = /* @__PURE__ */ new Map();
25808
26042
  for (const t of taskState) {
@@ -25951,25 +26185,31 @@ var useBaselineHealthData = ({
25951
26185
  };
25952
26186
 
25953
26187
  // src/application/ui/tui/views/execute-view-internals/use-bucketed-tasks.ts
25954
- import { useMemo as useMemo17 } from "react";
26188
+ import { useMemo as useMemo18 } from "react";
25955
26189
 
25956
26190
  // src/application/ui/tui/runtime/use-task-round-tracker.ts
25957
26191
  import { useEffect as useEffect28, useState as useState35 } from "react";
26192
+ var TASK_ROUND_CAP = 500;
25958
26193
  var isTaskRoundStarted = (e) => e.type === "task-round-started";
25959
26194
  var useTaskRoundTracker = (bus) => {
25960
26195
  const [rounds, setRounds] = useState35(() => /* @__PURE__ */ new Map());
25961
26196
  useEffect28(() => {
25962
- const unsub = bus.subscribe((event) => {
26197
+ return bus.subscribe((event) => {
25963
26198
  if (!isTaskRoundStarted(event)) return;
25964
26199
  setRounds((prev) => {
25965
26200
  const existing = prev.get(event.taskId);
25966
26201
  if (existing !== void 0 && existing.roundN >= event.roundN) return prev;
25967
26202
  const next = new Map(prev);
26203
+ next.delete(event.taskId);
25968
26204
  next.set(event.taskId, { roundN: event.roundN, totalCap: event.totalCap });
26205
+ while (next.size > TASK_ROUND_CAP) {
26206
+ const oldest = next.keys().next().value;
26207
+ if (oldest === void 0) break;
26208
+ next.delete(oldest);
26209
+ }
25969
26210
  return next;
25970
26211
  });
25971
26212
  });
25972
- return unsub;
25973
26213
  }, [bus]);
25974
26214
  return rounds;
25975
26215
  };
@@ -25981,7 +26221,7 @@ var useBucketedTasks = ({
25981
26221
  signals,
25982
26222
  eventBus
25983
26223
  }) => {
25984
- const rawBucketed = useMemo17(
26224
+ const rawBucketed = useMemo18(
25985
26225
  () => descriptor ? bucketTaskSignals(descriptor.trace, chainEvents, signals, {
25986
26226
  ...descriptor.maxTurns !== void 0 ? { maxTurns: descriptor.maxTurns } : {},
25987
26227
  ...descriptor.terminalSubstepName !== void 0 ? { terminalSubstepName: descriptor.terminalSubstepName } : {},
@@ -25994,7 +26234,7 @@ var useBucketedTasks = ({
25994
26234
  [descriptor, chainEvents, signals]
25995
26235
  );
25996
26236
  const taskRounds = useTaskRoundTracker(eventBus);
25997
- const bucketed = useMemo17(() => {
26237
+ const bucketed = useMemo18(() => {
25998
26238
  if (rawBucketed === void 0) return void 0;
25999
26239
  const tasks = rawBucketed.tasks.map((t) => {
26000
26240
  const tracked = taskRounds.get(t.id);
@@ -26102,14 +26342,14 @@ var useCancelHandlers = ({
26102
26342
  };
26103
26343
 
26104
26344
  // src/application/ui/tui/views/execute-view-internals/use-cancel-scope-stats.ts
26105
- import { useMemo as useMemo18 } from "react";
26345
+ import { useMemo as useMemo19 } from "react";
26106
26346
  var useCancelScopeStats = ({
26107
26347
  chainEvents,
26108
26348
  currentTask,
26109
26349
  bucketed,
26110
26350
  now
26111
26351
  }) => {
26112
- const attemptElapsedMs2 = useMemo18(() => {
26352
+ const attemptElapsedMs2 = useMemo19(() => {
26113
26353
  if (currentTask === void 0) return void 0;
26114
26354
  let latestStartMs;
26115
26355
  for (const ev of chainEvents) {
@@ -26120,7 +26360,7 @@ var useCancelScopeStats = ({
26120
26360
  }
26121
26361
  return latestStartMs !== void 0 ? Math.max(0, now - latestStartMs) : void 0;
26122
26362
  }, [chainEvents, currentTask, now]);
26123
- const remainingTaskCount = useMemo18(() => {
26363
+ const remainingTaskCount = useMemo19(() => {
26124
26364
  if (bucketed === void 0) return 0;
26125
26365
  return bucketed.tasks.reduce((n, t) => t.status === "completed" ? n : n + 1, 0);
26126
26366
  }, [bucketed]);
@@ -26341,7 +26581,7 @@ import { useEffect as useEffect31, useState as useState38 } from "react";
26341
26581
  import { Box as Box60, Text as Text59, useInput as useInput22 } from "ink";
26342
26582
 
26343
26583
  // src/application/ui/tui/components/list-view.tsx
26344
- import { useEffect as useEffect30, useMemo as useMemo19, useState as useState37 } from "react";
26584
+ import { useEffect as useEffect30, useMemo as useMemo20, useState as useState37 } from "react";
26345
26585
  import { Box as Box59, Text as Text58, useInput as useInput21 } from "ink";
26346
26586
  import { jsx as jsx74, jsxs as jsxs58 } from "react/jsx-runtime";
26347
26587
  var clamp4 = (n, min, max) => Math.max(min, Math.min(max, n));
@@ -26380,7 +26620,7 @@ function ListView({
26380
26620
  },
26381
26621
  { isActive: active }
26382
26622
  );
26383
- const window = useMemo19(() => {
26623
+ const window = useMemo20(() => {
26384
26624
  const half = Math.floor(visibleRows / 2);
26385
26625
  const start = clamp4(cursor - half, 0, Math.max(0, items.length - visibleRows));
26386
26626
  const end = Math.min(items.length, start + visibleRows);
@@ -26529,7 +26769,7 @@ var SessionsView = () => {
26529
26769
  };
26530
26770
 
26531
26771
  // src/application/ui/tui/views/settings-view.tsx
26532
- import React86, { useEffect as useEffect32, useMemo as useMemo20, useState as useState39 } from "react";
26772
+ import React86, { useEffect as useEffect32, useMemo as useMemo21, useState as useState39 } from "react";
26533
26773
  import { Box as Box64, Text as Text63, useInput as useInput23 } from "ink";
26534
26774
 
26535
26775
  // src/application/flows/settings-show/flow.ts
@@ -26554,11 +26794,11 @@ var MIXED = {
26554
26794
  refine: { provider: "openai-codex", model: "gpt-5.5" },
26555
26795
  plan: { provider: "github-copilot", model: "claude-sonnet-4.6", effort: "xhigh" },
26556
26796
  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" }
26797
+ generator: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" },
26798
+ evaluator: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" }
26559
26799
  },
26560
26800
  readiness: { provider: "github-copilot", model: "gpt-5-mini", effort: "medium" },
26561
- ideate: { provider: "claude-code", model: "claude-opus-4-7" },
26801
+ ideate: { provider: "claude-code", model: "claude-opus-4-8" },
26562
26802
  // PR content drafting mirrors refine's "light summary" reasoning profile — a fast Codex
26563
26803
  // model is fine, no need to pay for Opus tokens just to summarise a diff.
26564
26804
  createPr: { provider: "openai-codex", model: "gpt-5.4-mini" }
@@ -26566,13 +26806,13 @@ var MIXED = {
26566
26806
  var CLAUDE_ONLY = {
26567
26807
  effort: "high",
26568
26808
  refine: { provider: "claude-code", model: "claude-sonnet-4-6" },
26569
- plan: { provider: "claude-code", model: "claude-opus-4-7", effort: "xhigh" },
26809
+ plan: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" },
26570
26810
  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" }
26811
+ generator: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" },
26812
+ evaluator: { provider: "claude-code", model: "claude-opus-4-8", effort: "xhigh" }
26573
26813
  },
26574
26814
  readiness: { provider: "claude-code", model: "claude-haiku-4-5", effort: "medium" },
26575
- ideate: { provider: "claude-code", model: "claude-opus-4-7" },
26815
+ ideate: { provider: "claude-code", model: "claude-opus-4-8" },
26576
26816
  createPr: { provider: "claude-code", model: "claude-sonnet-4-6" }
26577
26817
  };
26578
26818
  var COPILOT_ONLY = {
@@ -27247,12 +27487,12 @@ var SettingsView = () => {
27247
27487
  cancelled = true;
27248
27488
  };
27249
27489
  }, []);
27250
- const sections = useMemo20(
27490
+ const sections = useMemo21(
27251
27491
  () => settings === void 0 ? [] : buildSections(settings),
27252
27492
  [settings]
27253
27493
  );
27254
27494
  const activeSection = sections[sectionIdx];
27255
- const activeFields = useMemo20(() => activeSection?.fields ?? [], [activeSection]);
27495
+ const activeFields = useMemo21(() => activeSection?.fields ?? [], [activeSection]);
27256
27496
  useEffect32(() => {
27257
27497
  if (cursor >= activeFields.length && activeFields.length > 0) setCursor(activeFields.length - 1);
27258
27498
  }, [activeFields, cursor]);
@@ -28091,20 +28331,20 @@ var renderIssueRefs = (refs) => {
28091
28331
  var buildCreatePrPrompt = async (deps, input) => buildPrompt(deps, createPrPromptDef, input);
28092
28332
 
28093
28333
  // src/application/flows/create-pr/leaves/generate-pr-content.contract.ts
28094
- import { z as z47 } from "zod";
28334
+ import { z as z46 } from "zod";
28095
28335
 
28096
28336
  // 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(),
28337
+ import { z as z45 } from "zod";
28338
+ var prContentSignalSchema = z45.object({
28339
+ type: z45.literal("pr-content"),
28340
+ title: z45.string(),
28341
+ body: z45.string(),
28102
28342
  timestamp: IsoTimestampSchema
28103
28343
  });
28104
28344
 
28105
28345
  // src/application/flows/create-pr/leaves/generate-pr-content.contract.ts
28106
28346
  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");
28347
+ var signalsArraySchemaRaw10 = z46.array(prContentSignalSchema).refine(exactlyOnePrContent, "exactly one pr-content signal per create-pr spawn");
28108
28348
  var signalsArraySchema10 = brandSignalArray(signalsArraySchemaRaw10);
28109
28349
  var prContentSidecar = {
28110
28350
  signalKind: "pr-content",
@@ -29193,14 +29433,14 @@ var backStep3 = (step) => {
29193
29433
  };
29194
29434
 
29195
29435
  // 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";
29436
+ import { useEffect as useEffect41, useMemo as useMemo22, useState as useState47 } from "react";
29197
29437
  import { Box as Box74, Text as Text73, useInput as useInput29 } from "ink";
29198
29438
  import { jsx as jsx91, jsxs as jsxs72 } from "react/jsx-runtime";
29199
29439
  var REVIEW_CHROME_ROWS = 14;
29200
29440
  var REVIEW_MIN_VIEWPORT = 4;
29201
29441
  var ReviewScrollableDescription = ({ text }) => {
29202
29442
  const term = useTerminalSize();
29203
- const lines = useMemo21(() => text.split("\n"), [text]);
29443
+ const lines = useMemo22(() => text.split("\n"), [text]);
29204
29444
  const viewport = Math.max(REVIEW_MIN_VIEWPORT, term.rows - REVIEW_CHROME_ROWS);
29205
29445
  const overflows = lines.length > viewport;
29206
29446
  const maxOffset = Math.max(0, lines.length - viewport);
@@ -29440,7 +29680,7 @@ var AddTicketView = () => {
29440
29680
  };
29441
29681
 
29442
29682
  // src/application/ui/tui/views/pick-project-view.tsx
29443
- import { useEffect as useEffect43, useMemo as useMemo22, useState as useState49 } from "react";
29683
+ import { useEffect as useEffect43, useMemo as useMemo23, useState as useState49 } from "react";
29444
29684
  import { Box as Box77, Text as Text75, useInput as useInput30 } from "ink";
29445
29685
  import { jsx as jsx94, jsxs as jsxs75 } from "react/jsx-runtime";
29446
29686
  var PickProjectView = () => {
@@ -29458,8 +29698,8 @@ var PickProjectView = () => {
29458
29698
  if (!r.ok) throw new Error(r.error.message);
29459
29699
  return r.value;
29460
29700
  }, []);
29461
- const projects = useMemo22(() => state.kind === "ok" ? state.value : [], [state]);
29462
- const initialIdx = useMemo22(() => {
29701
+ const projects = useMemo23(() => state.kind === "ok" ? state.value : [], [state]);
29702
+ const initialIdx = useMemo23(() => {
29463
29703
  if (selection.projectId === void 0) return 0;
29464
29704
  const i = projects.findIndex((p) => p.id === selection.projectId);
29465
29705
  return i === -1 ? 0 : i;
@@ -29549,7 +29789,7 @@ var PickProjectView = () => {
29549
29789
  };
29550
29790
 
29551
29791
  // src/application/ui/tui/views/pick-sprint-view.tsx
29552
- import { useEffect as useEffect44, useMemo as useMemo24, useState as useState50 } from "react";
29792
+ import { useEffect as useEffect44, useMemo as useMemo25, useState as useState50 } from "react";
29553
29793
  import { Box as Box79, Text as Text77, useInput as useInput31 } from "ink";
29554
29794
 
29555
29795
  // src/application/ui/tui/views/pick-sprint-internals/types.ts
@@ -29653,7 +29893,7 @@ var computeWindow = (totalRows, cursor, visible) => {
29653
29893
  };
29654
29894
 
29655
29895
  // src/application/ui/tui/views/pick-sprint-internals/row-views.tsx
29656
- import { useMemo as useMemo23 } from "react";
29896
+ import { useMemo as useMemo24 } from "react";
29657
29897
  import { Box as Box78, Text as Text76 } from "ink";
29658
29898
  import { jsx as jsx95, jsxs as jsxs76 } from "react/jsx-runtime";
29659
29899
  var RowWindowView = ({
@@ -29662,7 +29902,7 @@ var RowWindowView = ({
29662
29902
  visibleRows,
29663
29903
  currentSprintId
29664
29904
  }) => {
29665
- const window = useMemo23(() => computeWindow(rows.length, cursor, visibleRows), [rows.length, cursor, visibleRows]);
29905
+ const window = useMemo24(() => computeWindow(rows.length, cursor, visibleRows), [rows.length, cursor, visibleRows]);
29666
29906
  const slice = rows.slice(window.start, window.end);
29667
29907
  return /* @__PURE__ */ jsxs76(Box78, { flexDirection: "column", children: [
29668
29908
  window.hiddenAbove > 0 && /* @__PURE__ */ jsx95(Box78, { paddingX: spacing.indent, children: /* @__PURE__ */ jsxs76(Text76, { dimColor: true, children: [
@@ -29782,17 +30022,17 @@ var PickSprintView = () => {
29782
30022
  },
29783
30023
  [deps.sprintRepo, deps.projectRepo]
29784
30024
  );
29785
- const data = useMemo24(
30025
+ const data = useMemo25(
29786
30026
  () => state.kind === "ok" ? state.value : { sprints: [], projectsById: /* @__PURE__ */ new Map() },
29787
30027
  [state]
29788
30028
  );
29789
- const groups = useMemo24(() => buildGroups(data, selection.projectId, scopeAll), [data, selection.projectId, scopeAll]);
30029
+ const groups = useMemo25(() => buildGroups(data, selection.projectId, scopeAll), [data, selection.projectId, scopeAll]);
29790
30030
  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]);
30031
+ const rows = useMemo25(() => flatten(groups, includeCreate), [groups, includeCreate]);
30032
+ const sprintCount = useMemo25(() => rows.reduce((acc, r) => r.kind === "sprint" ? acc + 1 : acc, 0), [rows]);
29793
30033
  const bp = useBreakpoint();
29794
30034
  const visibleRows = Math.max(MIN_VISIBLE_ROWS, bp.rows - VERTICAL_CHROME_ROWS);
29795
- const initialIdx = useMemo24(() => {
30035
+ const initialIdx = useMemo25(() => {
29796
30036
  if (selection.sprintId !== void 0) {
29797
30037
  const i = rows.findIndex((r) => r.kind === "sprint" && r.sprint.id === selection.sprintId);
29798
30038
  if (i !== -1) return i;
@@ -29981,7 +30221,7 @@ var renderView = (entry) => {
29981
30221
  };
29982
30222
 
29983
30223
  // src/application/ui/tui/runtime/use-global-keys.ts
29984
- import { useEffect as useEffect45, useMemo as useMemo25, useRef as useRef13 } from "react";
30224
+ import { useEffect as useEffect45, useMemo as useMemo26, useRef as useRef13 } from "react";
29985
30225
  import { useApp, useInput as useInput32 } from "ink";
29986
30226
 
29987
30227
  // src/integration/io/clipboard.ts
@@ -30086,7 +30326,7 @@ var useGlobalKeys = (opts = {}) => {
30086
30326
  const ui = useUiState();
30087
30327
  const selection = useSelection();
30088
30328
  const deps = useDeps();
30089
- const copyToClipboard = useMemo25(
30329
+ const copyToClipboard = useMemo26(
30090
30330
  () => opts.copyToClipboard ?? createCopyToClipboard(),
30091
30331
  [opts.copyToClipboard]
30092
30332
  );
@@ -30259,7 +30499,7 @@ var ChainLogDegradedBanner = () => {
30259
30499
  // src/application/ui/tui/components/progress-overlay.tsx
30260
30500
  import { promises as fs29 } from "fs";
30261
30501
  import { join as join54 } from "path";
30262
- import { useEffect as useEffect48, useMemo as useMemo26, useState as useState53 } from "react";
30502
+ import { useEffect as useEffect48, useMemo as useMemo27, useState as useState53 } from "react";
30263
30503
  import { Box as Box82, Text as Text80, useInput as useInput33 } from "ink";
30264
30504
  import { jsx as jsx99, jsxs as jsxs80 } from "react/jsx-runtime";
30265
30505
  var CHROME_ROWS = 10;
@@ -30276,7 +30516,7 @@ var ProgressOverlay = () => {
30276
30516
  const [offset, setOffset] = useState53(0);
30277
30517
  const [now] = useState53(() => Date.now());
30278
30518
  const sprintId = selection.sprintId;
30279
- const progressPath = useMemo26(() => {
30519
+ const progressPath = useMemo27(() => {
30280
30520
  if (sprintId === void 0) return void 0;
30281
30521
  return join54(String(storage2.dataRoot), "sprints", String(sprintId), "progress.md");
30282
30522
  }, [sprintId, storage2.dataRoot]);
@@ -30471,20 +30711,23 @@ var resolveInitialState = ({
30471
30711
  settingsExist,
30472
30712
  projects,
30473
30713
  lastProjectId,
30474
- lastSprintId
30714
+ lastSprintId,
30715
+ sprints
30475
30716
  }) => {
30476
30717
  if (!settingsExist) return { initialView: { id: "welcome" } };
30477
- if (projects.length === 0) return { initialView: { id: "create-project" } };
30718
+ const [first] = projects;
30719
+ if (first === void 0) return { initialView: { id: "create-project" } };
30478
30720
  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;
30721
+ const resolvedProject = restored ?? first;
30722
+ const projectSprints = (sprints ?? []).filter((s) => s.projectId === resolvedProject.id).slice().sort((a, b) => a.id < b.id ? 1 : a.id > b.id ? -1 : 0);
30723
+ const persistedSprintValid = restored !== void 0 && lastSprintId !== void 0 && projectSprints.some((s) => s.id === lastSprintId);
30724
+ const seededSprintId = persistedSprintValid ? lastSprintId : projectSprints[0]?.id;
30482
30725
  return {
30483
30726
  initialView: { id: "home" },
30484
30727
  initialSelection: {
30485
- projectId: preselected.id,
30486
- projectLabel: preselected.displayName,
30487
- ...carrySprintId !== void 0 ? { sprintId: carrySprintId } : {}
30728
+ projectId: resolvedProject.id,
30729
+ projectLabel: resolvedProject.displayName,
30730
+ ...seededSprintId !== void 0 ? { sprintId: seededSprintId } : {}
30488
30731
  }
30489
30732
  };
30490
30733
  };
@@ -30783,11 +31026,13 @@ var bootstrap = async () => {
30783
31026
  void createInkInteractivePrompt(queue);
30784
31027
  const settingsExists = await deps.settingsRepo.exists();
30785
31028
  const projectsList = await deps.projectRepo.list();
31029
+ const sprintsResult = await deps.sprintRepo.list();
30786
31030
  const lastSelectionStore = createLastSelectionStore(paths.value.stateRoot);
30787
31031
  const lastSelection = await lastSelectionStore.read();
30788
31032
  const { initialView, initialSelection } = resolveInitialState({
30789
31033
  settingsExist: settingsExists.ok ? settingsExists.value : false,
30790
31034
  projects: projectsList.ok ? projectsList.value : [],
31035
+ sprints: sprintsResult.ok ? sprintsResult.value : [],
30791
31036
  ...lastSelection !== void 0 ? { lastProjectId: lastSelection.projectId } : {},
30792
31037
  ...lastSelection?.sprintId !== void 0 ? { lastSprintId: lastSelection.sprintId } : {}
30793
31038
  });