ralphctl 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1229,16 +1229,16 @@ var killWithEscalation = (child, graceMs = DEFAULT_KILL_GRACE_MS) => {
1229
1229
  var DEFAULT_GIT_TIMEOUT_MS = 3e4;
1230
1230
  var GIT_MAX_OUTPUT_BYTES = 50 * 1024 * 1024;
1231
1231
  var createGitRunner = (deps = {}) => {
1232
- const spawn3 = deps.spawn ?? defaultSpawn;
1232
+ const spawn2 = deps.spawn ?? defaultSpawn;
1233
1233
  const defaultTimeoutMs = deps.defaultTimeoutMs ?? DEFAULT_GIT_TIMEOUT_MS;
1234
1234
  const maxOutputBytes = deps.maxOutputBytes ?? GIT_MAX_OUTPUT_BYTES;
1235
- const run = (cwd, args, opts = {}) => runGitOnce(spawn3, cwd, args, opts.timeoutMs ?? defaultTimeoutMs, maxOutputBytes);
1235
+ const run = (cwd, args, opts = {}) => runGitOnce(spawn2, cwd, args, opts.timeoutMs ?? defaultTimeoutMs, maxOutputBytes);
1236
1236
  return { run };
1237
1237
  };
1238
- var spawnGitChild = (spawn3, cwd, args) => {
1238
+ var spawnGitChild = (spawn2, cwd, args) => {
1239
1239
  try {
1240
1240
  return Result.ok(
1241
- spawn3("git", args, {
1241
+ spawn2("git", args, {
1242
1242
  stdio: ["pipe", "pipe", "pipe"],
1243
1243
  cwd: String(cwd)
1244
1244
  })
@@ -1310,8 +1310,8 @@ var buildCloseResult = (collector, timedOut, code, timeoutMs, args, maxOutputByt
1310
1310
  );
1311
1311
  }
1312
1312
  };
1313
- var runGitOnce = (spawn3, cwd, args, timeoutMs, maxOutputBytes) => new Promise((resolve) => {
1314
- const spawnResult = spawnGitChild(spawn3, cwd, args);
1313
+ var runGitOnce = (spawn2, cwd, args, timeoutMs, maxOutputBytes) => new Promise((resolve) => {
1314
+ const spawnResult = spawnGitChild(spawn2, cwd, args);
1315
1315
  if (!spawnResult.ok) {
1316
1316
  resolve(Result.error(spawnResult.error));
1317
1317
  return;
@@ -1398,9 +1398,9 @@ var buildChildEnv = (overrideEnv) => ({
1398
1398
  ...process.env,
1399
1399
  ...overrideEnv
1400
1400
  });
1401
- var trySpawnChild = (spawn3, cwd, script, env) => {
1401
+ var trySpawnChild = (spawn2, cwd, script, env) => {
1402
1402
  try {
1403
- const child = spawn3(script, [], {
1403
+ const child = spawn2(script, [], {
1404
1404
  stdio: ["pipe", "pipe", "pipe"],
1405
1405
  cwd: String(cwd),
1406
1406
  shell: true,
@@ -1462,14 +1462,14 @@ var wireAbortLadder = (child, signal, abortKillGraceMs, isSettled2) => {
1462
1462
  };
1463
1463
  };
1464
1464
  var runChildProcess = (cwd, script, opts, deps, resolve) => {
1465
- const { spawn: spawn3, defaultTimeoutMs, now, abortKillGraceMs } = deps;
1465
+ const { spawn: spawn2, defaultTimeoutMs, now, abortKillGraceMs } = deps;
1466
1466
  const start = now();
1467
1467
  const timeoutMs = opts.timeoutMs ?? defaultTimeoutMs;
1468
1468
  if (opts.signal?.aborted === true) {
1469
1469
  resolve(abortResult());
1470
1470
  return;
1471
1471
  }
1472
- const spawnAttempt = trySpawnChild(spawn3, cwd, script, buildChildEnv(opts.env));
1472
+ const spawnAttempt = trySpawnChild(spawn2, cwd, script, buildChildEnv(opts.env));
1473
1473
  if (!spawnAttempt.ok) {
1474
1474
  resolve(Result.error(spawnAttempt.error));
1475
1475
  return;
@@ -1989,33 +1989,38 @@ var reconcileStaleProjectSiblings = async (root, id, canonicalFile) => {
1989
1989
  await removeFile(fullPath);
1990
1990
  }
1991
1991
  };
1992
- var createFsProjectRepository = (deps) => {
1993
- const list = async () => {
1994
- const dir = projectsDir(deps.root);
1995
- const entries = await listDir(dir);
1996
- if (!entries.ok) return Result.error(entries.error);
1997
- const jsonFiles = entries.value.filter((f) => f.endsWith(".json")).sort();
1998
- const items = [];
1999
- const byId = /* @__PURE__ */ new Map();
2000
- const canonicalIds = /* @__PURE__ */ new Set();
2001
- for (const file of jsonFiles) {
1992
+ var listProjects = async (root) => {
1993
+ const dir = projectsDir(root);
1994
+ const entries = await listDir(dir);
1995
+ if (!entries.ok) return Result.error(entries.error);
1996
+ const jsonFiles = entries.value.filter((f) => f.endsWith(".json")).sort();
1997
+ const reads = await Promise.all(
1998
+ jsonFiles.map(async (file) => {
2002
1999
  const path2 = `${dir}/${file}`;
2003
- const json = await readJson(path2);
2004
- if (!json.ok) {
2005
- if (json.error instanceof NotFoundError) continue;
2006
- return Result.error(json.error);
2007
- }
2008
- const decoded = decode(fromJsonProject, json.value, { entity: "project", path: path2 });
2009
- if (!decoded.ok) return Result.error(decoded.error);
2010
- const id = String(decoded.value.id);
2011
- const isCanonical = file.includes("--");
2012
- if (!isCanonical && canonicalIds.has(id)) continue;
2013
- byId.set(id, decoded.value);
2014
- if (isCanonical) canonicalIds.add(id);
2000
+ return { file, path: path2, json: await readJson(path2) };
2001
+ })
2002
+ );
2003
+ const items = [];
2004
+ const byId = /* @__PURE__ */ new Map();
2005
+ const canonicalIds = /* @__PURE__ */ new Set();
2006
+ for (const { file, path: path2, json } of reads) {
2007
+ if (!json.ok) {
2008
+ if (json.error instanceof NotFoundError) continue;
2009
+ return Result.error(json.error);
2015
2010
  }
2016
- items.push(...byId.values());
2017
- return Result.ok(items);
2018
- };
2011
+ const decoded = decode(fromJsonProject, json.value, { entity: "project", path: path2 });
2012
+ if (!decoded.ok) return Result.error(decoded.error);
2013
+ const id = String(decoded.value.id);
2014
+ const isCanonical = file.includes("--");
2015
+ if (!isCanonical && canonicalIds.has(id)) continue;
2016
+ byId.set(id, decoded.value);
2017
+ if (isCanonical) canonicalIds.add(id);
2018
+ }
2019
+ items.push(...byId.values());
2020
+ return Result.ok(items);
2021
+ };
2022
+ var createFsProjectRepository = (deps) => {
2023
+ const list = () => listProjects(deps.root);
2019
2024
  return {
2020
2025
  async findById(id) {
2021
2026
  const path2 = await resolveProjectPath(deps.root, id);
@@ -2352,33 +2357,38 @@ var reconcileSprintDir = async (root, id, canonicalDir) => {
2352
2357
  reconciledDirs.add(memoKey);
2353
2358
  }
2354
2359
  };
2355
- var createFsSprintRepository = (deps) => {
2356
- const list = async () => {
2357
- const dir = sprintsDir(deps.root);
2358
- const entries = await listDir(dir);
2359
- if (!entries.ok) return Result.error(entries.error);
2360
- const sortedEntries = [...entries.value].sort((a, b) => parseIdFromName(a).localeCompare(parseIdFromName(b)));
2361
- const items = [];
2362
- const byId = /* @__PURE__ */ new Map();
2363
- const canonicalIds = /* @__PURE__ */ new Set();
2364
- for (const entry of sortedEntries) {
2360
+ var listSprints = async (root) => {
2361
+ const dir = sprintsDir(root);
2362
+ const entries = await listDir(dir);
2363
+ if (!entries.ok) return Result.error(entries.error);
2364
+ const sortedEntries = [...entries.value].sort((a, b) => parseIdFromName(a).localeCompare(parseIdFromName(b)));
2365
+ const reads = await Promise.all(
2366
+ sortedEntries.map(async (entry) => {
2365
2367
  const path2 = `${dir}/${entry}/sprint.json`;
2366
- const json = await readJson(path2);
2367
- if (!json.ok) {
2368
- if (json.error instanceof NotFoundError) continue;
2369
- return Result.error(json.error);
2370
- }
2371
- const decoded = decode((input) => fromJsonSprint(input, path2), json.value, { entity: "sprint", path: path2 });
2372
- if (!decoded.ok) return Result.error(decoded.error);
2373
- const id = String(decoded.value.id);
2374
- const isCanonical = entry.includes("--");
2375
- if (!isCanonical && canonicalIds.has(id)) continue;
2376
- byId.set(id, decoded.value);
2377
- if (isCanonical) canonicalIds.add(id);
2368
+ return { entry, path: path2, json: await readJson(path2) };
2369
+ })
2370
+ );
2371
+ const items = [];
2372
+ const byId = /* @__PURE__ */ new Map();
2373
+ const canonicalIds = /* @__PURE__ */ new Set();
2374
+ for (const { entry, path: path2, json } of reads) {
2375
+ if (!json.ok) {
2376
+ if (json.error instanceof NotFoundError) continue;
2377
+ return Result.error(json.error);
2378
2378
  }
2379
- items.push(...byId.values());
2380
- return Result.ok(items);
2381
- };
2379
+ const decoded = decode((input) => fromJsonSprint(input, path2), json.value, { entity: "sprint", path: path2 });
2380
+ if (!decoded.ok) return Result.error(decoded.error);
2381
+ const id = String(decoded.value.id);
2382
+ const isCanonical = entry.includes("--");
2383
+ if (!isCanonical && canonicalIds.has(id)) continue;
2384
+ byId.set(id, decoded.value);
2385
+ if (isCanonical) canonicalIds.add(id);
2386
+ }
2387
+ items.push(...byId.values());
2388
+ return Result.ok(items);
2389
+ };
2390
+ var createFsSprintRepository = (deps) => {
2391
+ const list = () => listSprints(deps.root);
2382
2392
  return {
2383
2393
  async findById(id) {
2384
2394
  const dir = await resolveSprintDir(deps.root, id);
@@ -2495,6 +2505,8 @@ var unblockTask = (task) => {
2495
2505
  // The raised effort is a per-run remedy like the model bump — a clean restart drops it so the
2496
2506
  // fresh run begins on the configured effort again.
2497
2507
  escalatedToEffort: _effort,
2508
+ // Evaluator-side counterpart of the effort remedy above — same clean-restart rationale.
2509
+ escalatedToEvaluatorEffort: _evaluatorEffort,
2498
2510
  // A clean restart drops the prior run's per-criterion verdicts too — a freshly-planned task
2499
2511
  // carries no k-of-N history; stale verdicts would mislead the next run's checklist.
2500
2512
  criteriaVerdicts: _verdicts,
@@ -2505,6 +2517,7 @@ var unblockTask = (task) => {
2505
2517
  void _from;
2506
2518
  void _to;
2507
2519
  void _effort;
2520
+ void _evaluatorEffort;
2508
2521
  void _verdicts;
2509
2522
  return Result.ok({ ...rest, status: "todo", attempts: [] });
2510
2523
  };
@@ -2761,7 +2774,10 @@ var TaskBaseShape = {
2761
2774
  escalatedToModel: z13.string().optional(),
2762
2775
  // Same-model effort-rung override. Optional on read so `tasks.json` files written before the
2763
2776
  // field existed load unchanged (a missing value heals to `undefined`). Never planner-authored.
2764
- escalatedToEffort: z13.string().optional()
2777
+ escalatedToEffort: z13.string().optional(),
2778
+ // Evaluator-side counterpart of escalatedToEffort — same tolerant-read rationale. The evaluator
2779
+ // MODEL never escalates, so there is no evaluator equivalent of escalatedToModel.
2780
+ escalatedToEvaluatorEffort: z13.string().optional()
2765
2781
  };
2766
2782
  var TodoTaskSchema = z13.object({ ...TaskBaseShape, status: z13.literal("todo") });
2767
2783
  var InProgressTaskSchema = z13.object({ ...TaskBaseShape, status: z13.literal("in_progress") });
@@ -3061,6 +3077,31 @@ var isSuspendedModel = (s) => SUSPENDED_MODELS.includes(s);
3061
3077
  var SUSPENSION_NOTE = "suspended";
3062
3078
  var suspendedModelMessage = (model) => `'${model}' is temporarily suspended by its provider \u2014 pick another model until access is restored`;
3063
3079
 
3080
+ // src/integration/ai/providers/_engine/validate-model.ts
3081
+ var validateModel = (model, isKnownModel, opts) => {
3082
+ if (!isKnownModel(model)) {
3083
+ return Result.error(
3084
+ new InvalidStateError({
3085
+ entity: opts.entity,
3086
+ currentState: "model-validation",
3087
+ attemptedAction: opts.attemptedAction,
3088
+ message: opts.notKnownMessage
3089
+ })
3090
+ );
3091
+ }
3092
+ if (isSuspendedModel(model)) {
3093
+ return Result.error(
3094
+ new InvalidStateError({
3095
+ entity: opts.entity,
3096
+ currentState: "model-suspended",
3097
+ attemptedAction: opts.attemptedAction,
3098
+ message: suspendedModelMessage(model)
3099
+ })
3100
+ );
3101
+ }
3102
+ return Result.ok(void 0);
3103
+ };
3104
+
3064
3105
  // src/integration/ai/providers/_engine/bounded-tail.ts
3065
3106
  var STDERR_TAIL_CAP = 16384;
3066
3107
  var RATE_LIMIT_SCAN_TAIL_CAP = 8192;
@@ -3211,7 +3252,7 @@ var defaultProviderSpawn = (command, args, options) => crossPlatformSpawn(comman
3211
3252
  // src/integration/ai/providers/_engine/truncate-debug-field.ts
3212
3253
  var truncateField = (value, max = 120) => {
3213
3254
  if (value === void 0 || value === null) return void 0;
3214
- if (value.length === 0) return void 0;
3255
+ if (value.trim().length === 0) return void 0;
3215
3256
  if (value.length <= max) return value;
3216
3257
  return `${value.slice(0, max)}\u2026`;
3217
3258
  };
@@ -3360,6 +3401,12 @@ var delayForRetry = (retryIndex, schedule = DEFAULT_BACKOFF_SCHEDULE) => {
3360
3401
  const clamped = Math.min(retryIndex - 1, schedule.length - 1);
3361
3402
  return schedule[clamped] ?? 0;
3362
3403
  };
3404
+ var JITTER_RATIO = 0.2;
3405
+ var applyJitter = (delayMs, random = Math.random) => {
3406
+ if (delayMs <= 0) return delayMs;
3407
+ const spread = delayMs * JITTER_RATIO;
3408
+ return Math.round(delayMs - spread + random() * spread * 2);
3409
+ };
3363
3410
  var sleepCancellable = (ms, signal) => new Promise((resolve) => {
3364
3411
  if (signal?.aborted === true) {
3365
3412
  resolve();
@@ -3394,7 +3441,8 @@ var handleRateLimitOutcome = async ({
3394
3441
  attemptIdx,
3395
3442
  maxAttempts,
3396
3443
  schedule,
3397
- error
3444
+ error,
3445
+ random
3398
3446
  }) => {
3399
3447
  const bannerId = `rate-limit-${providerSlug}-${error.sessionId ?? String(attemptIdx + 1)}`;
3400
3448
  eventBus.publish({
@@ -3408,7 +3456,7 @@ var handleRateLimitOutcome = async ({
3408
3456
  if (attemptIdx >= maxAttempts - 1) {
3409
3457
  return { type: "continue", session: nextSession };
3410
3458
  }
3411
- const delayMs = delayForRetry(attemptIdx + 1, schedule);
3459
+ const delayMs = applyJitter(delayForRetry(attemptIdx + 1, schedule), random ?? Math.random);
3412
3460
  if (delayMs <= 0) {
3413
3461
  return { type: "continue", session: nextSession };
3414
3462
  }
@@ -3472,7 +3520,8 @@ var runWithRateLimitRetry = async (opts) => {
3472
3520
  attemptIdx,
3473
3521
  maxAttempts,
3474
3522
  schedule,
3475
- error: outcome.error
3523
+ error: outcome.error,
3524
+ random: opts.random
3476
3525
  });
3477
3526
  if (result.type === "aborted") {
3478
3527
  return Result.error(result.error);
@@ -3918,26 +3967,12 @@ var disallowedToolsFor = (p) => {
3918
3967
  return denied;
3919
3968
  };
3920
3969
  var buildClaudeArgs = (session) => {
3921
- if (!isClaudeModel(session.model)) {
3922
- return Result.error(
3923
- new InvalidStateError({
3924
- entity: PROVIDER_NAME,
3925
- currentState: "model-validation",
3926
- attemptedAction: "build argv",
3927
- message: `claude-provider: '${session.model}' is not a known Claude model`
3928
- })
3929
- );
3930
- }
3931
- if (isSuspendedModel(session.model)) {
3932
- return Result.error(
3933
- new InvalidStateError({
3934
- entity: PROVIDER_NAME,
3935
- currentState: "model-suspended",
3936
- attemptedAction: "build argv",
3937
- message: suspendedModelMessage(session.model)
3938
- })
3939
- );
3940
- }
3970
+ const validated = validateModel(session.model, isClaudeModel, {
3971
+ entity: PROVIDER_NAME,
3972
+ attemptedAction: "build argv",
3973
+ notKnownMessage: `claude-provider: '${session.model}' is not a known Claude model`
3974
+ });
3975
+ if (!validated.ok) return Result.error(validated.error);
3941
3976
  const args = ["-p", "--verbose", "--output-format", "stream-json", "--model", session.model];
3942
3977
  args.push("--permission-mode", "bypassPermissions");
3943
3978
  const denied = disallowedToolsFor(session.permissions);
@@ -4039,16 +4074,12 @@ var sandboxFor = (_p) => {
4039
4074
  return Result.ok({ sandbox: "workspace-write" });
4040
4075
  };
4041
4076
  var buildCodexArgs = (session, opts) => {
4042
- if (!isCodexModel(session.model)) {
4043
- return Result.error(
4044
- new InvalidStateError({
4045
- entity: PROVIDER_NAME2,
4046
- currentState: "model-validation",
4047
- attemptedAction: "build argv",
4048
- message: `codex-provider: '${session.model}' is not a known Codex model`
4049
- })
4050
- );
4051
- }
4077
+ const validated = validateModel(session.model, isCodexModel, {
4078
+ entity: PROVIDER_NAME2,
4079
+ attemptedAction: "build argv",
4080
+ notKnownMessage: `codex-provider: '${session.model}' is not a known Codex model`
4081
+ });
4082
+ if (!validated.ok) return Result.error(validated.error);
4052
4083
  const perms = sandboxFor(session.permissions);
4053
4084
  if (!perms.ok) return Result.error(perms.error);
4054
4085
  const args = ["exec"];
@@ -4409,26 +4440,12 @@ var RESUME_STALE_RE3 = /(session|conversation)[^\n]*not found|no (session|conver
4409
4440
  var isFullAuto = (p) => p.autoApprove && p.canModifyRepoFiles && p.canRunShell;
4410
4441
  var PROVIDER_NAME3 = "copilot-provider";
4411
4442
  var buildCopilotArgs = (session) => {
4412
- if (!isCopilotModel(session.model)) {
4413
- return Result.error(
4414
- new InvalidStateError({
4415
- entity: PROVIDER_NAME3,
4416
- currentState: "model-validation",
4417
- attemptedAction: "build argv",
4418
- message: `copilot-provider: '${session.model}' is not a known Copilot model`
4419
- })
4420
- );
4421
- }
4422
- if (isSuspendedModel(session.model)) {
4423
- return Result.error(
4424
- new InvalidStateError({
4425
- entity: PROVIDER_NAME3,
4426
- currentState: "model-suspended",
4427
- attemptedAction: "build argv",
4428
- message: suspendedModelMessage(session.model)
4429
- })
4430
- );
4431
- }
4443
+ const validated = validateModel(session.model, isCopilotModel, {
4444
+ entity: PROVIDER_NAME3,
4445
+ attemptedAction: "build argv",
4446
+ notKnownMessage: `copilot-provider: '${session.model}' is not a known Copilot model`
4447
+ });
4448
+ if (!validated.ok) return Result.error(validated.error);
4432
4449
  const args = [
4433
4450
  "--output-format=json",
4434
4451
  "--autopilot",
@@ -4657,16 +4674,12 @@ var createInteractiveClaudeProvider = (deps) => {
4657
4674
  const newSessionId = deps.newSessionId ?? uuidv7;
4658
4675
  return {
4659
4676
  async run(input) {
4660
- if (!isClaudeModel(input.model)) {
4661
- return Result.error(
4662
- new InvalidStateError({
4663
- entity: PROVIDER,
4664
- currentState: "model-validation",
4665
- attemptedAction: "run",
4666
- message: `interactive-claude: '${input.model}' is not a known Claude model`
4667
- })
4668
- );
4669
- }
4677
+ const validated = validateModel(input.model, isClaudeModel, {
4678
+ entity: PROVIDER,
4679
+ attemptedAction: "run",
4680
+ notKnownMessage: `interactive-claude: '${input.model}' is not a known Claude model`
4681
+ });
4682
+ if (!validated.ok) return Result.error(validated.error);
4670
4683
  let prompt;
4671
4684
  try {
4672
4685
  prompt = await readFile5(String(input.promptFile));
@@ -4777,16 +4790,12 @@ var createInteractiveCodexProvider = (deps) => {
4777
4790
  const readFile5 = deps.readFile ?? defaultReadFile;
4778
4791
  return {
4779
4792
  async run(input) {
4780
- if (!isCodexModel(input.model)) {
4781
- return Result.error(
4782
- new InvalidStateError({
4783
- entity: PROVIDER2,
4784
- currentState: "model-validation",
4785
- attemptedAction: "run",
4786
- message: `interactive-codex: '${input.model}' is not a known Codex model`
4787
- })
4788
- );
4789
- }
4793
+ const validated = validateModel(input.model, isCodexModel, {
4794
+ entity: PROVIDER2,
4795
+ attemptedAction: "run",
4796
+ notKnownMessage: `interactive-codex: '${input.model}' is not a known Codex model`
4797
+ });
4798
+ if (!validated.ok) return Result.error(validated.error);
4790
4799
  let prompt;
4791
4800
  try {
4792
4801
  prompt = await readFile5(String(input.promptFile));
@@ -4887,16 +4896,12 @@ var createInteractiveCopilotProvider = (deps) => {
4887
4896
  const newSessionId = deps.newSessionId ?? uuidv7;
4888
4897
  return {
4889
4898
  async run(input) {
4890
- if (!isCopilotModel(input.model)) {
4891
- return Result.error(
4892
- new InvalidStateError({
4893
- entity: PROVIDER3,
4894
- currentState: "model-validation",
4895
- attemptedAction: "run",
4896
- message: `interactive-copilot: '${input.model}' is not a known Copilot model`
4897
- })
4898
- );
4899
- }
4899
+ const validated = validateModel(input.model, isCopilotModel, {
4900
+ entity: PROVIDER3,
4901
+ attemptedAction: "run",
4902
+ notKnownMessage: `interactive-copilot: '${input.model}' is not a known Copilot model`
4903
+ });
4904
+ if (!validated.ok) return Result.error(validated.error);
4900
4905
  let prompt;
4901
4906
  try {
4902
4907
  prompt = await readFile5(String(input.promptFile));
@@ -5016,10 +5021,10 @@ var createInteractiveAiProvider = (deps) => {
5016
5021
  };
5017
5022
 
5018
5023
  // src/integration/io/run-cli.ts
5019
- var runCli = (spawn3, command, args, opts) => new Promise((resolve) => {
5024
+ var runCli = (spawn2, command, args, opts) => new Promise((resolve) => {
5020
5025
  let child;
5021
5026
  try {
5022
- child = spawn3(command, args, {
5027
+ child = spawn2(command, args, {
5023
5028
  stdio: ["pipe", "pipe", "pipe"],
5024
5029
  ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {}
5025
5030
  });
@@ -5133,9 +5138,9 @@ var looksLikeNotFound = (stderr) => {
5133
5138
  const s = stderr.toLowerCase();
5134
5139
  return s.includes("not found") || s.includes("404") || s.includes("could not resolve") || s.includes("does not exist");
5135
5140
  };
5136
- var fetchGitHub = async (spawn3, parsed, url) => {
5141
+ var fetchGitHub = async (spawn2, parsed, url) => {
5137
5142
  const result = await runCli(
5138
- spawn3,
5143
+ spawn2,
5139
5144
  "gh",
5140
5145
  [
5141
5146
  "issue",
@@ -5182,9 +5187,9 @@ var fetchGitHub = async (spawn3, parsed, url) => {
5182
5187
  comments
5183
5188
  });
5184
5189
  };
5185
- var fetchGitLabNotes = async (spawn3, parsed) => {
5190
+ var fetchGitLabNotes = async (spawn2, parsed) => {
5186
5191
  const result = await runCli(
5187
- spawn3,
5192
+ spawn2,
5188
5193
  "glab",
5189
5194
  ["issue", "note", "list", String(parsed.number), "--repo", glabRepoArg(parsed), "--output", "json"],
5190
5195
  { timeoutMs: CLI_TIMEOUT_MS }
@@ -5213,9 +5218,9 @@ var fetchGitLabNotes = async (spawn3, parsed) => {
5213
5218
  }));
5214
5219
  return { comments };
5215
5220
  };
5216
- var fetchGitLab = async (spawn3, parsed, url, logger) => {
5221
+ var fetchGitLab = async (spawn2, parsed, url, logger) => {
5217
5222
  const result = await runCli(
5218
- spawn3,
5223
+ spawn2,
5219
5224
  "glab",
5220
5225
  ["issue", "view", String(parsed.number), "--repo", glabRepoArg(parsed), "--output", "json"],
5221
5226
  { timeoutMs: CLI_TIMEOUT_MS }
@@ -5242,7 +5247,7 @@ var fetchGitLab = async (spawn3, parsed, url, logger) => {
5242
5247
  })
5243
5248
  );
5244
5249
  }
5245
- const notes = await fetchGitLabNotes(spawn3, parsed);
5250
+ const notes = await fetchGitLabNotes(spawn2, parsed);
5246
5251
  if (notes.failure !== void 0) {
5247
5252
  logger?.warn(`glab issue note list failed for ${url}: ${notes.failure} \u2014 proceeding without comments`);
5248
5253
  }
@@ -5263,9 +5268,9 @@ var createIssueFetcher = (deps) => async (url) => {
5263
5268
 
5264
5269
  // src/integration/scm/issue-pusher.ts
5265
5270
  var CLI_TIMEOUT_MS2 = 3e4;
5266
- var commentGitHub = async (spawn3, url, body, parsed) => {
5271
+ var commentGitHub = async (spawn2, url, body, parsed) => {
5267
5272
  const r = await runCli(
5268
- spawn3,
5273
+ spawn2,
5269
5274
  "gh",
5270
5275
  ["issue", "comment", String(parsed.number), "--repo", `${parsed.owner}/${parsed.repo}`, "--body-file", "-"],
5271
5276
  { stdin: body, timeoutMs: CLI_TIMEOUT_MS2 }
@@ -5281,9 +5286,9 @@ var commentGitHub = async (spawn3, url, body, parsed) => {
5281
5286
  }
5282
5287
  return Result.ok(void 0);
5283
5288
  };
5284
- var commentGitLab = async (spawn3, url, body, parsed) => {
5289
+ var commentGitLab = async (spawn2, url, body, parsed) => {
5285
5290
  const r = await runCli(
5286
- spawn3,
5291
+ spawn2,
5287
5292
  "glab",
5288
5293
  [
5289
5294
  "issue",
@@ -5408,11 +5413,11 @@ var buildGlabArgs = (input) => {
5408
5413
  if (input.draft) args.push("--draft");
5409
5414
  return args;
5410
5415
  };
5411
- var runPlatformCli = async (spawn3, platform, input) => {
5416
+ var runPlatformCli = async (spawn2, platform, input) => {
5412
5417
  const command = platform === "github" ? "gh" : "glab";
5413
5418
  const args = platform === "github" ? buildGhArgs(input) : buildGlabArgs(input);
5414
5419
  const noun = platform === "github" ? "gh pr create" : "glab mr create";
5415
- const result = await runCli(spawn3, command, args, { cwd: String(input.cwd), timeoutMs: CLI_TIMEOUT_MS3 });
5420
+ const result = await runCli(spawn2, command, args, { cwd: String(input.cwd), timeoutMs: CLI_TIMEOUT_MS3 });
5416
5421
  if (!result.ok) return Result.error(result.error);
5417
5422
  if (result.value.exitCode !== 0) {
5418
5423
  const stderr = result.value.stderr.trim();
@@ -6024,7 +6029,7 @@ var createNpmVersionChecker = (deps) => {
6024
6029
  // package.json
6025
6030
  var package_default = {
6026
6031
  name: "ralphctl",
6027
- version: "0.17.1",
6032
+ version: "0.18.0",
6028
6033
  description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code, GitHub Copilot, and OpenAI Codex across repositories",
6029
6034
  homepage: "https://github.com/lukas-grigis/ralphctl",
6030
6035
  type: "module",
@@ -6526,18 +6531,7 @@ var BUNDLED_SKILLS = [
6526
6531
  ];
6527
6532
  var skillsForFlow = (flowId) => BUNDLED_SKILLS.filter((entry) => entry.defaultFor.includes(flowId)).map((entry) => entry.name);
6528
6533
 
6529
- // src/integration/ai/skills/_engine/skill.ts
6530
- import { z as z16 } from "zod";
6531
- var SkillNameSchema = z16.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u, "lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens");
6532
- var SkillFrontmatterSchema = z16.object({
6533
- name: SkillNameSchema,
6534
- description: z16.string().min(1).max(1024),
6535
- license: z16.string().optional(),
6536
- compatibility: z16.string().max(500).optional(),
6537
- "allowed-tools": z16.string().optional()
6538
- });
6539
-
6540
- // src/integration/ai/skills/_engine/parse-skill.ts
6534
+ // src/integration/ai/skills/_engine/frontmatter.ts
6541
6535
  var splitFrontmatter = (raw) => {
6542
6536
  const trimmed = raw.replace(/^\uFEFF/u, "");
6543
6537
  if (!trimmed.startsWith("---")) return { frontmatter: "", body: trimmed };
@@ -6564,6 +6558,19 @@ var parseSimpleYaml = (input) => {
6564
6558
  return result;
6565
6559
  };
6566
6560
  var errorCode = (cause) => typeof cause === "object" && cause !== null && "code" in cause && typeof cause.code === "string" ? cause.code : void 0;
6561
+
6562
+ // src/integration/ai/skills/_engine/skill.ts
6563
+ import { z as z16 } from "zod";
6564
+ var SkillNameSchema = z16.string().min(1).max(64).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/u, "lowercase alphanumeric with hyphens, no leading/trailing/consecutive hyphens");
6565
+ var SkillFrontmatterSchema = z16.object({
6566
+ name: SkillNameSchema,
6567
+ description: z16.string().min(1).max(1024),
6568
+ license: z16.string().optional(),
6569
+ compatibility: z16.string().max(500).optional(),
6570
+ "allowed-tools": z16.string().optional()
6571
+ });
6572
+
6573
+ // src/integration/ai/skills/_engine/parse-skill.ts
6567
6574
  var parseSkill = (label, path2, name, raw) => {
6568
6575
  const { frontmatter, body } = splitFrontmatter(raw);
6569
6576
  const fm = parseSimpleYaml(frontmatter);
@@ -7293,35 +7300,9 @@ import { readFile as readFile4 } from "fs/promises";
7293
7300
  var BUNDLED_AGENT_DEFINITIONS = ["ralphctl-evaluator", "ralphctl-generator"];
7294
7301
 
7295
7302
  // src/integration/ai/agents/_engine/parse-agent-definition.ts
7296
- var splitFrontmatter2 = (raw) => {
7297
- const trimmed = raw.replace(/^\uFEFF/u, "");
7298
- if (!trimmed.startsWith("---")) return { frontmatter: "", body: trimmed };
7299
- const closing = trimmed.indexOf("\n---", 3);
7300
- if (closing === -1) return { frontmatter: "", body: trimmed };
7301
- const frontmatter = trimmed.slice(3, closing).trim();
7302
- const afterClose = trimmed.slice(closing + 4);
7303
- const body = afterClose.replace(/^(?:\r?\n)+/, "");
7304
- return { frontmatter, body };
7305
- };
7306
- var parseSimpleYaml2 = (input) => {
7307
- const result = {};
7308
- for (const line of input.split("\n")) {
7309
- const stripped = line.trim();
7310
- if (stripped.length === 0 || stripped.startsWith("#")) continue;
7311
- const colon = stripped.indexOf(":");
7312
- if (colon === -1) continue;
7313
- const key = stripped.slice(0, colon).trim();
7314
- let value = stripped.slice(colon + 1).trim();
7315
- if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
7316
- else if (value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
7317
- result[key] = value;
7318
- }
7319
- return result;
7320
- };
7321
- var errorCode2 = (cause) => typeof cause === "object" && cause !== null && "code" in cause && typeof cause.code === "string" ? cause.code : void 0;
7322
7303
  var parseAgentDefinition = (label, path2, name, raw) => {
7323
- const { frontmatter, body } = splitFrontmatter2(raw);
7324
- const fm = parseSimpleYaml2(frontmatter);
7304
+ const { frontmatter, body } = splitFrontmatter(raw);
7305
+ const fm = parseSimpleYaml(frontmatter);
7325
7306
  const parsed = AgentDefinitionFrontmatterSchema.safeParse(fm);
7326
7307
  if (!parsed.success) {
7327
7308
  return Result.error(
@@ -7375,7 +7356,7 @@ var readDefinitionOptional = async (root, name) => {
7375
7356
  try {
7376
7357
  raw = await readFile4(path2, "utf-8");
7377
7358
  } catch (cause) {
7378
- if (errorCode2(cause) === "ENOENT") return Result.ok(void 0);
7359
+ if (errorCode(cause) === "ENOENT") return Result.ok(void 0);
7379
7360
  return Result.error(
7380
7361
  new StorageError({ subCode: "io", message: `bundled agent definition not readable: ${path2}`, path: path2, cause })
7381
7362
  );
@@ -7423,7 +7404,7 @@ var loadOperatorAgentDefinitions = async (deps) => {
7423
7404
  try {
7424
7405
  entries = await fs14.readdir(root, { withFileTypes: true });
7425
7406
  } catch (cause) {
7426
- if (errorCode2(cause) === "ENOENT") return [];
7407
+ if (errorCode(cause) === "ENOENT") return [];
7427
7408
  log.warn("operator agent definitions dir not readable", { path: root, cause });
7428
7409
  return [];
7429
7410
  }
@@ -7656,7 +7637,7 @@ var buildWireAgentDefinitionSource = (storage2, logger) => composeAgentDefinitio
7656
7637
  })
7657
7638
  );
7658
7639
  var wire = (opts) => {
7659
- const spawn3 = opts.spawn ?? defaultPipeSpawn;
7640
+ const spawn2 = opts.spawn ?? defaultPipeSpawn;
7660
7641
  const env = opts.env ?? process.env;
7661
7642
  const debugTrace = isTruthyEnvFlag(env[RALPHCTL_DEBUG_TRACE_ENV]);
7662
7643
  const appendFile = createAppendFile();
@@ -7716,9 +7697,9 @@ var wire = (opts) => {
7716
7697
  availableModelsFor,
7717
7698
  eventBus,
7718
7699
  logger,
7719
- pullRequestCreator: createPullRequestCreator({ gitRunner: createGitRunner(), spawn: spawn3 }),
7720
- issueFetcher: createIssueFetcher({ spawn: spawn3, logger }),
7721
- issuePusher: createIssuePusher({ spawn: spawn3 }),
7700
+ pullRequestCreator: createPullRequestCreator({ gitRunner: createGitRunner(), spawn: spawn2 }),
7701
+ issueFetcher: createIssueFetcher({ spawn: spawn2, logger }),
7702
+ issuePusher: createIssuePusher({ spawn: spawn2 }),
7722
7703
  versionChecker: createNpmVersionChecker({
7723
7704
  stateRoot: opts.storage.stateRoot,
7724
7705
  currentVersion: CLI_METADATA.currentVersion,
@@ -8954,9 +8935,8 @@ var useSelection = () => {
8954
8935
  import { createContext as createContext10, useCallback as useCallback5, useContext as useContext10, useEffect as useEffect4, useState as useState6 } from "react";
8955
8936
 
8956
8937
  // src/integration/io/command-exists.ts
8957
- import { spawn as spawn2 } from "child_process";
8958
8938
  var commandExists = (name) => new Promise((resolve) => {
8959
- const child = process.platform === "win32" ? spawn2("where", [name], { stdio: "ignore" }) : spawn2("sh", ["-c", 'command -v "$0"', name], { stdio: "ignore" });
8939
+ const child = process.platform === "win32" ? crossPlatformSpawn("where", [name], { stdio: "ignore" }) : crossPlatformSpawn("sh", ["-c", 'command -v "$0"', name], { stdio: "ignore" });
8960
8940
  let settled = false;
8961
8941
  const settle = (value) => {
8962
8942
  if (settled) return;
@@ -10033,7 +10013,7 @@ var Breadcrumb = () => {
10033
10013
  /* @__PURE__ */ jsx14(Text5, { color: inkColors.primary, bold: true, children: right[1] }),
10034
10014
  /* @__PURE__ */ jsx14(Text5, { dimColor: true, children: " " }),
10035
10015
  /* @__PURE__ */ jsx14(Text5, { color: inkColors.highlight, bold: true, children: "[S]" }),
10036
- atLeast("md") && effectiveSprintStatus !== void 0 && /* @__PURE__ */ jsx14(Box3, { marginLeft: 1, children: /* @__PURE__ */ jsx14(StatusChip, { label: effectiveSprintStatus, kind: sprintStatusKind(effectiveSprintStatus) }) })
10016
+ atLeast("md") && effectiveSprintStatus !== void 0 && /* @__PURE__ */ jsx14(Box3, { marginLeft: spacing.gutter, children: /* @__PURE__ */ jsx14(StatusChip, { label: effectiveSprintStatus, kind: sprintStatusKind(effectiveSprintStatus) }) })
10037
10017
  ] })
10038
10018
  ] })
10039
10019
  ]
@@ -11104,7 +11084,7 @@ var ScrollableMessage = ({ message, ownsArrows = true }) => {
11104
11084
  " ",
11105
11085
  header
11106
11086
  ] }),
11107
- body.length > 0 && /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", marginTop: 1, children: [
11087
+ body.length > 0 && /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", marginTop: spacing.section, children: [
11108
11088
  /* @__PURE__ */ jsx22(Box11, { flexDirection: "column", borderStyle: "single", borderColor: inkColors.rule, paddingX: spacing.cardPadX, children: visible.map((line, i) => /* @__PURE__ */ jsx22(Text13, { children: line.length === 0 ? " " : line }, `body-${String(offset + i)}`)) }),
11109
11089
  overflows && /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
11110
11090
  "lines ",
@@ -11146,7 +11126,7 @@ var ConfirmPrompt = ({
11146
11126
  const Pill = ({ on, label }) => /* @__PURE__ */ jsx23(Text14, { color: on ? inkColors.primary : inkColors.muted, bold: on, children: on ? `[ ${label} ]` : ` ${label} ` });
11147
11127
  return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", paddingX: spacing.indent, children: [
11148
11128
  /* @__PURE__ */ jsx23(ScrollableMessage, { message }),
11149
- /* @__PURE__ */ jsxs13(Box12, { marginTop: 1, children: [
11129
+ /* @__PURE__ */ jsxs13(Box12, { marginTop: spacing.section, children: [
11150
11130
  /* @__PURE__ */ jsx23(Pill, { on: yes, label: "Yes" }),
11151
11131
  /* @__PURE__ */ jsx23(Text14, { children: " " }),
11152
11132
  /* @__PURE__ */ jsx23(Pill, { on: !yes, label: "No" })
@@ -11209,7 +11189,7 @@ var SelectPrompt = ({
11209
11189
  const end = Math.min(options.length, start + VISIBLE_ROWS);
11210
11190
  return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", paddingX: spacing.indent, children: [
11211
11191
  /* @__PURE__ */ jsx24(ScrollableMessage, { message, ownsArrows: false }),
11212
- /* @__PURE__ */ jsx24(Box13, { flexDirection: "column", marginTop: 1, children: options.slice(start, end).map((opt, localIdx) => {
11192
+ /* @__PURE__ */ jsx24(Box13, { flexDirection: "column", marginTop: spacing.section, children: options.slice(start, end).map((opt, localIdx) => {
11213
11193
  const i = start + localIdx;
11214
11194
  const focused = i === cursor;
11215
11195
  const disabled = opt.disabled === true;
@@ -11352,7 +11332,7 @@ var MultiSelectPrompt = ({
11352
11332
  " ",
11353
11333
  message
11354
11334
  ] }),
11355
- /* @__PURE__ */ jsx25(Box14, { flexDirection: "column", marginTop: 1, children: options.slice(start, end).map((opt, localIdx) => {
11335
+ /* @__PURE__ */ jsx25(Box14, { flexDirection: "column", marginTop: spacing.section, children: options.slice(start, end).map((opt, localIdx) => {
11356
11336
  const i = start + localIdx;
11357
11337
  const focused = i === cursor && opt.disabled !== true;
11358
11338
  return /* @__PURE__ */ jsx25(OptionRow, { opt, focused, checked: picked.has(i) }, `opt-${String(i)}`);
@@ -11585,6 +11565,9 @@ var OverflowRow = ({ direction, count }) => {
11585
11565
  // src/application/ui/tui/components/action-menu.tsx
11586
11566
  import { jsx as jsx29, jsxs as jsxs19 } from "react/jsx-runtime";
11587
11567
  var isEnabled3 = (item) => item.disabledReason === void 0;
11568
+ var RESERVED_NAV_KEYS = new Set(
11569
+ [...listKeys.up.keys, ...listKeys.down.keys].filter((k) => /^[a-z]$/.test(k))
11570
+ );
11588
11571
  var findInitialCursorId = (items, initialIndex, enabledItems) => {
11589
11572
  for (let i = initialIndex; i < items.length; i++) {
11590
11573
  const it = items[i];
@@ -11592,7 +11575,10 @@ var findInitialCursorId = (items, initialIndex, enabledItems) => {
11592
11575
  }
11593
11576
  return enabledItems[0]?.id ?? "";
11594
11577
  };
11595
- var matchHotkey = (items, input) => items.find((it) => it.hotkey === input && it.globalHotkey !== true && isEnabled3(it));
11578
+ var matchHotkey = (items, input) => {
11579
+ if (RESERVED_NAV_KEYS.has(input)) return void 0;
11580
+ return items.find((it) => it.hotkey === input && it.globalHotkey !== true && isEnabled3(it));
11581
+ };
11596
11582
  var isRowVisible = (it, enabled, visibleEnabledIds, windowedEnabled) => {
11597
11583
  if (enabled) return visibleEnabledIds.has(it.id);
11598
11584
  if (windowedEnabled.length === 0) return true;
@@ -14001,14 +13987,20 @@ var warnIfContractViolated = (logger, skillName, content) => {
14001
13987
  };
14002
13988
 
14003
13989
  // src/business/settings/resolve-effort.ts
13990
+ var FLOW_DEFAULT_EFFORT = {
13991
+ plan: "high",
13992
+ ideate: "high"
13993
+ };
14004
13994
  var resolveEffort = (flow, settings) => {
14005
13995
  const row = primaryFlowRow(settings.ai, flow);
14006
- return resolveEffortForRow(row, settings.ai.effort);
13996
+ return resolveEffortForRow(row, settings.ai.effort, flow);
14007
13997
  };
14008
- var resolveEffortForRow = (row, globalEffort) => {
13998
+ var resolveEffortForRow = (row, globalEffort, flow) => {
14009
13999
  if (row.effort !== void 0) return row.effort;
14010
- if (globalEffort === void 0) return void 0;
14011
- return _floorForProvider(globalEffort, row.provider);
14000
+ if (globalEffort !== void 0) return _floorForProvider(globalEffort, row.provider);
14001
+ const flowDefault = flow !== void 0 ? FLOW_DEFAULT_EFFORT[flow] : void 0;
14002
+ if (flowDefault !== void 0) return clampEffortToProvider(flowDefault, row.provider);
14003
+ return void 0;
14012
14004
  };
14013
14005
  var clampEffortToProvider = (effort, provider) => {
14014
14006
  if (provider === "openai-codex" && effort === "max") return "xhigh";
@@ -14585,6 +14577,28 @@ var interactiveSprintNameLeaf = (deps) => leaf("interactive-sprint-name", {
14585
14577
  // src/application/flows/create-sprint/leaves/init-progress-journal.ts
14586
14578
  import { join as join31 } from "path";
14587
14579
 
14580
+ // src/business/_shared/format-duration.ts
14581
+ var EM_DASH = "\u2014";
14582
+ var MS_PER_SECOND = 1e3;
14583
+ var MS_PER_MINUTE = 60 * MS_PER_SECOND;
14584
+ var MS_PER_HOUR = 60 * MS_PER_MINUTE;
14585
+ var formatDuration = (ms) => {
14586
+ if (ms === void 0) return EM_DASH;
14587
+ if (ms < 0) return `${String(ms)}ms`;
14588
+ if (ms < MS_PER_SECOND) return `${String(ms)}ms`;
14589
+ if (ms < MS_PER_MINUTE) {
14590
+ return `${String(Math.floor(ms / MS_PER_SECOND))}s`;
14591
+ }
14592
+ if (ms < MS_PER_HOUR) {
14593
+ const minutes2 = Math.floor(ms / MS_PER_MINUTE);
14594
+ const seconds = Math.floor(ms % MS_PER_MINUTE / MS_PER_SECOND);
14595
+ return seconds > 0 ? `${String(minutes2)}m ${String(seconds)}s` : `${String(minutes2)}m`;
14596
+ }
14597
+ const hours = Math.floor(ms / MS_PER_HOUR);
14598
+ const minutes = Math.floor(ms % MS_PER_HOUR / MS_PER_MINUTE);
14599
+ return minutes > 0 ? `${String(hours)}h ${String(minutes)}m` : `${String(hours)}h`;
14600
+ };
14601
+
14588
14602
  // src/business/sprint/journal-sanitize.ts
14589
14603
  var sanitizeInline = (text) => text.replace(/[\r\n\t\v\f]+/g, " ").trim();
14590
14604
  var neutralizeProseHeadings = (text) => text.split("\n").map((line) => line.startsWith("#") ? ` ${line}` : line).join("\n");
@@ -14628,26 +14642,7 @@ _Task ${sanitizeInline(taskName)}: rejected diff quarantined to git stash \u2014
14628
14642
  `;
14629
14643
 
14630
14644
  // src/business/sprint/render-journal-entry.ts
14631
- var EM_DASH = "\u2014";
14632
- var MS_PER_SECOND = 1e3;
14633
- var MS_PER_MINUTE = 60 * MS_PER_SECOND;
14634
- var MS_PER_HOUR = 60 * MS_PER_MINUTE;
14635
- var formatDuration = (ms) => {
14636
- if (ms === void 0) return EM_DASH;
14637
- if (ms < 0) return `${String(ms)}ms`;
14638
- if (ms < MS_PER_SECOND) return `${String(ms)}ms`;
14639
- if (ms < MS_PER_MINUTE) {
14640
- return `${String(Math.floor(ms / MS_PER_SECOND))}s`;
14641
- }
14642
- if (ms < MS_PER_HOUR) {
14643
- const minutes2 = Math.floor(ms / MS_PER_MINUTE);
14644
- const seconds = Math.floor(ms % MS_PER_MINUTE / MS_PER_SECOND);
14645
- return seconds > 0 ? `${String(minutes2)}m ${String(seconds)}s` : `${String(minutes2)}m`;
14646
- }
14647
- const hours = Math.floor(ms / MS_PER_HOUR);
14648
- const minutes = Math.floor(ms % MS_PER_HOUR / MS_PER_MINUTE);
14649
- return minutes > 0 ? `${String(hours)}h ${String(minutes)}m` : `${String(hours)}h`;
14650
- };
14645
+ var EM_DASH2 = "\u2014";
14651
14646
  var SHA_DISPLAY_LENGTH = 7;
14652
14647
  var appendSubsection = (lines, heading, entries) => {
14653
14648
  if (entries.length === 0) return;
@@ -14734,7 +14729,7 @@ var appendOutcomeDetail = (lines, input) => {
14734
14729
  lines.push("");
14735
14730
  };
14736
14731
  var renderJournalEntry = (input) => {
14737
- const sha = input.commitSha !== void 0 ? input.commitSha.slice(0, SHA_DISPLAY_LENGTH) : EM_DASH;
14732
+ const sha = input.commitSha !== void 0 ? input.commitSha.slice(0, SHA_DISPLAY_LENGTH) : EM_DASH2;
14738
14733
  const outcome = input.outcome.trim().length > 0 ? neutralizeProseHeadings(input.outcome.trim()) : "_(no outcome paragraph)_";
14739
14734
  const lines = [];
14740
14735
  lines.push("");
@@ -15676,11 +15671,29 @@ var assertTemplateKeysFilled = (rendered, template, partialBodies, values, where
15676
15671
  };
15677
15672
 
15678
15673
  // src/integration/ai/prompts/_engine/build-prompt.ts
15674
+ var findPartialParameterCollision = (def, partialPlaceholders) => {
15675
+ for (const [field, rawSpec] of Object.entries(def.parameters)) {
15676
+ if (partialPlaceholders.has(rawSpec.placeholder)) return [field, rawSpec.placeholder];
15677
+ }
15678
+ return void 0;
15679
+ };
15679
15680
  var buildPrompt = async (loader, def, input) => {
15680
15681
  const template = await loader.load(def.templateName);
15681
15682
  if (!template.ok) return Result.error(template.error);
15682
15683
  const values = {};
15683
15684
  const partialBodies = [];
15685
+ const partialPlaceholders = new Set(def.partials !== void 0 ? Object.keys(def.partials) : []);
15686
+ const collision = findPartialParameterCollision(def, partialPlaceholders);
15687
+ if (collision !== void 0) {
15688
+ const [field, placeholder] = collision;
15689
+ return Result.error(
15690
+ new ValidationError({
15691
+ field,
15692
+ value: placeholder,
15693
+ message: `buildPrompt(${def.templateName}): parameter '${field}' declares placeholder {{${placeholder}}}, which collides with an auto-loaded partial slot of the same name \u2014 rename one of them.`
15694
+ })
15695
+ );
15696
+ }
15684
15697
  if (def.partials !== void 0) {
15685
15698
  for (const [placeholder, name] of Object.entries(def.partials)) {
15686
15699
  const partial = await loader.load(name);
@@ -18546,7 +18559,6 @@ var branchPreflightLeaf = (deps, opts, name = "branch-preflight") => leaf(name,
18546
18559
  });
18547
18560
 
18548
18561
  // src/application/flows/implement/leaves/build-task-workspace.ts
18549
- import { promises as fs21 } from "fs";
18550
18562
  import { join as join44 } from "path";
18551
18563
 
18552
18564
  // src/business/task/compose-criteria-history.ts
@@ -18849,22 +18861,6 @@ ${body}
18849
18861
  };
18850
18862
 
18851
18863
  // src/application/flows/implement/leaves/build-task-workspace.ts
18852
- var writeOrError = async (path2, content) => {
18853
- try {
18854
- await fs21.mkdir(path2.slice(0, path2.lastIndexOf("/")), { recursive: true });
18855
- await fs21.writeFile(path2, content, "utf8");
18856
- return Result.ok(void 0);
18857
- } catch (cause) {
18858
- return Result.error(
18859
- new StorageError({
18860
- subCode: "io",
18861
- message: `failed to write task workspace file: ${path2}`,
18862
- path: path2,
18863
- cause
18864
- })
18865
- );
18866
- }
18867
- };
18868
18864
  var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-${String(taskId)}`, {
18869
18865
  useCase: {
18870
18866
  execute: async (input) => {
@@ -18873,6 +18869,10 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
18873
18869
  const previewOutputDir = AbsolutePath.parse(join44(workspaceRoot, "rounds", "1", "generator"));
18874
18870
  if (!previewOutputDir.ok) return Result.error(previewOutputDir.error);
18875
18871
  const contractPath = join44(workspaceRoot, "contract.md");
18872
+ const parsedContractPath = AbsolutePath.parse(contractPath);
18873
+ if (!parsedContractPath.ok) return Result.error(parsedContractPath.error);
18874
+ const parsedPromptPath = AbsolutePath.parse(join44(workspaceRoot, "prompt.md"));
18875
+ if (!parsedPromptPath.ok) return Result.error(parsedPromptPath.error);
18876
18876
  const prompt = await buildImplementPrompt(deps.templateLoader, {
18877
18877
  task: input.task,
18878
18878
  projectPath: String(opts.cwd),
@@ -18883,9 +18883,9 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
18883
18883
  ...opts.verifyScript !== void 0 ? { verifyScript: opts.verifyScript } : {}
18884
18884
  });
18885
18885
  if (!prompt.ok) return Result.error(prompt.error);
18886
- const wrotePrompt = await writeOrError(join44(workspaceRoot, "prompt.md"), String(prompt.value));
18886
+ const wrotePrompt = await deps.writeFile(parsedPromptPath.value, String(prompt.value));
18887
18887
  if (!wrotePrompt.ok) return Result.error(wrotePrompt.error);
18888
- const wroteContract = await writeOrError(contractPath, renderContractMd(input.task));
18888
+ const wroteContract = await deps.writeFile(parsedContractPath.value, renderContractMd(input.task));
18889
18889
  if (!wroteContract.ok) return Result.error(wroteContract.error);
18890
18890
  log.debug("task workspace built", { taskId: input.task.id, workspaceRoot });
18891
18891
  const parsedRoot = AbsolutePath.parse(workspaceRoot);
@@ -19306,6 +19306,7 @@ var markTaskDone = (task, now) => {
19306
19306
  ...guard2.value.escalatedFromModel !== void 0 ? { escalatedFromModel: guard2.value.escalatedFromModel } : {},
19307
19307
  ...guard2.value.escalatedToModel !== void 0 ? { escalatedToModel: guard2.value.escalatedToModel } : {},
19308
19308
  ...guard2.value.escalatedToEffort !== void 0 ? { escalatedToEffort: guard2.value.escalatedToEffort } : {},
19309
+ ...guard2.value.escalatedToEvaluatorEffort !== void 0 ? { escalatedToEvaluatorEffort: guard2.value.escalatedToEvaluatorEffort } : {},
19309
19310
  status: "done",
19310
19311
  attempts,
19311
19312
  finalAttemptN: verified.n
@@ -19350,6 +19351,11 @@ var recordTaskEffortEscalation = (task, toEffort) => {
19350
19351
  if (!to.ok) return Result.error(to.error);
19351
19352
  return Result.ok({ ...task, escalatedToEffort: to.value });
19352
19353
  };
19354
+ var recordTaskEvaluatorEffortEscalation = (task, toEffort) => {
19355
+ const to = parseRequiredString("task.escalatedToEvaluatorEffort", toEffort);
19356
+ if (!to.ok) return Result.error(to.error);
19357
+ return Result.ok({ ...task, escalatedToEvaluatorEffort: to.value });
19358
+ };
19353
19359
 
19354
19360
  // src/business/task/escalation-policy.ts
19355
19361
  var BANNER_SHOW_EVENT = "banner-show";
@@ -19373,11 +19379,14 @@ var decideEscalation = (props) => {
19373
19379
  if (nudgedAtTop) return { kind: "topped-out", model: props.generatorModel };
19374
19380
  const effortTarget = nextEffortRung(props.generatorProvider, props.generatorModel, props.generatorEffort);
19375
19381
  if (effortTarget !== void 0) {
19382
+ const evaluatorEffortTarget = props.evaluatorModel !== void 0 ? nextEffortRung(props.evaluatorProvider, props.evaluatorModel, props.evaluatorEffort) : void 0;
19383
+ const evaluatorCarry = evaluatorEffortTarget !== void 0 ? { evaluator: { from: props.evaluatorEffort ?? "default", to: evaluatorEffortTarget } } : {};
19376
19384
  return {
19377
19385
  kind: "escalate-effort",
19378
19386
  model: props.generatorModel,
19379
19387
  from: props.generatorEffort ?? "default",
19380
- to: effortTarget
19388
+ to: effortTarget,
19389
+ ...evaluatorCarry
19381
19390
  };
19382
19391
  }
19383
19392
  return { kind: "nudge", currentModel: props.generatorModel };
@@ -19449,11 +19458,12 @@ var applyEscalation = (props) => {
19449
19458
  return Result.ok({ task: stamped.value });
19450
19459
  }
19451
19460
  case "escalate-effort": {
19461
+ const evaluatorClause = decision.evaluator !== void 0 ? `; evaluator effort: ${decision.evaluator.from} \u2192 ${decision.evaluator.to}` : "";
19452
19462
  eventBus.publish({
19453
19463
  type: BANNER_SHOW_EVENT,
19454
19464
  id: bannerId,
19455
19465
  tier: "info",
19456
- message: `raised generator effort on '${decision.model}': ${decision.from} \u2192 ${decision.to}`,
19466
+ message: `raised generator effort on '${decision.model}': ${decision.from} \u2192 ${decision.to}${evaluatorClause}`,
19457
19467
  cause,
19458
19468
  at: now
19459
19469
  });
@@ -19462,7 +19472,8 @@ var applyEscalation = (props) => {
19462
19472
  model: decision.model,
19463
19473
  from: decision.from,
19464
19474
  to: decision.to,
19465
- reason: trigger
19475
+ reason: trigger,
19476
+ ...decision.evaluator !== void 0 ? { evaluatorFrom: decision.evaluator.from, evaluatorTo: decision.evaluator.to } : {}
19466
19477
  });
19467
19478
  return Result.ok({ task });
19468
19479
  }
@@ -19563,7 +19574,12 @@ var resolveEscalatableRemedy = (exit, props, cfg) => {
19563
19574
  // provider (or the row carries no effort dimension the caller could resolve) the policy never
19564
19575
  // returns `escalate-effort` and behaves exactly as before.
19565
19576
  generatorProvider: props.generatorProvider,
19566
- generatorEffort: props.generatorEffort
19577
+ generatorEffort: props.generatorEffort,
19578
+ // Evaluator lockstep context — same optionality: absent context means the policy never
19579
+ // returns an `evaluator` field on the decision.
19580
+ evaluatorProvider: props.evaluatorProvider,
19581
+ evaluatorModel: props.evaluatorModel,
19582
+ evaluatorEffort: props.evaluatorEffort
19567
19583
  });
19568
19584
  const applied = applyEscalation({
19569
19585
  task: props.task,
@@ -19583,6 +19599,11 @@ var resolveEscalatableRemedy = (exit, props, cfg) => {
19583
19599
  const stamped = recordTaskEffortEscalation(task, decision.to);
19584
19600
  if (!stamped.ok) return Result.error(stamped.error);
19585
19601
  task = stamped.value;
19602
+ if (decision.evaluator !== void 0) {
19603
+ const evaluatorStamped = recordTaskEvaluatorEffortEscalation(task, decision.evaluator.to);
19604
+ if (!evaluatorStamped.ok) return Result.error(evaluatorStamped.error);
19605
+ task = evaluatorStamped.value;
19606
+ }
19586
19607
  }
19587
19608
  const shouldFailAttempt = decision.kind === "escalate" || decision.kind === "escalate-effort" || decision.kind === "nudge";
19588
19609
  return Result.ok({
@@ -19679,7 +19700,10 @@ var finalizeGenEvalLeaf = (deps, taskId) => leaf(`finalize-gen-eval-${String(tas
19679
19700
  clock: deps.clock,
19680
19701
  generatorModel: input.generatorModel,
19681
19702
  ...input.generatorProvider !== void 0 ? { generatorProvider: input.generatorProvider } : {},
19682
- ...input.generatorEffort !== void 0 ? { generatorEffort: input.generatorEffort } : {}
19703
+ ...input.generatorEffort !== void 0 ? { generatorEffort: input.generatorEffort } : {},
19704
+ ...input.evaluatorProvider !== void 0 ? { evaluatorProvider: input.evaluatorProvider } : {},
19705
+ ...input.evaluatorModel !== void 0 ? { evaluatorModel: input.evaluatorModel } : {},
19706
+ ...input.evaluatorEffort !== void 0 ? { evaluatorEffort: input.evaluatorEffort } : {}
19683
19707
  })
19684
19708
  },
19685
19709
  input: (ctx) => {
@@ -19701,6 +19725,7 @@ var finalizeGenEvalLeaf = (deps, taskId) => leaf(`finalize-gen-eval-${String(tas
19701
19725
  }
19702
19726
  const generatorModel = ctx.currentTask.escalatedToModel ?? deps.configuredGeneratorModel;
19703
19727
  const generatorEffort = ctx.currentTask.escalatedToEffort ?? deps.configuredGeneratorEffort;
19728
+ const evaluatorEffort = ctx.currentTask.escalatedToEvaluatorEffort ?? deps.configuredEvaluatorEffort;
19704
19729
  return {
19705
19730
  task: ctx.currentTask,
19706
19731
  sprintId: ctx.sprintId,
@@ -19708,7 +19733,10 @@ var finalizeGenEvalLeaf = (deps, taskId) => leaf(`finalize-gen-eval-${String(tas
19708
19733
  turnsUsed: ctx.genEvalTurn ?? 0,
19709
19734
  generatorModel,
19710
19735
  ...deps.configuredGeneratorProvider !== void 0 ? { generatorProvider: deps.configuredGeneratorProvider } : {},
19711
- ...generatorEffort !== void 0 ? { generatorEffort } : {}
19736
+ ...generatorEffort !== void 0 ? { generatorEffort } : {},
19737
+ ...deps.configuredEvaluatorProvider !== void 0 ? { evaluatorProvider: deps.configuredEvaluatorProvider } : {},
19738
+ ...deps.configuredEvaluatorModel !== void 0 ? { evaluatorModel: deps.configuredEvaluatorModel } : {},
19739
+ ...evaluatorEffort !== void 0 ? { evaluatorEffort } : {}
19712
19740
  };
19713
19741
  },
19714
19742
  output: (ctx, out) => {
@@ -19846,7 +19874,7 @@ var computePlateauVerdict = (priorTurns, current, options) => {
19846
19874
 
19847
19875
  // src/application/flows/implement/leaves/evaluator.ts
19848
19876
  import { dirname as dirname17, join as join46 } from "path";
19849
- import { promises as fs23 } from "fs";
19877
+ import { promises as fs22 } from "fs";
19850
19878
 
19851
19879
  // src/business/task/turn-error-policy.ts
19852
19880
  var isRecoverableTurnError = (err) => err.code !== ErrorCode.Aborted && err.code !== ErrorCode.RateLimit;
@@ -20184,7 +20212,7 @@ var buildEvaluateContinuationPrompt = async (deps, input) => buildPrompt(deps, e
20184
20212
 
20185
20213
  // src/integration/ai/contract/_engine/corrective-retry.ts
20186
20214
  import { ZodError } from "zod";
20187
- var isCorrectableContractError = (err) => err.code !== ErrorCode.Aborted && err.code !== ErrorCode.RateLimit && err.code !== ErrorCode.MigrationGap;
20215
+ var isCorrectableContractError = (err) => err instanceof InvalidStateError || err instanceof ParseError;
20188
20216
  var MAX_ENUMERATED_ISSUES = 10;
20189
20217
  var correctiveBody = (err, signalsPath, selfContainedContext) => {
20190
20218
  const lines = [];
@@ -20255,6 +20283,14 @@ var validateSignalsFileWithCorrectiveRetry = async (deps, contract) => {
20255
20283
  return Result.ok({ signals: revalidated.value, nudgeCount: attempt });
20256
20284
  }
20257
20285
  lastErr = revalidated.error;
20286
+ if (!isCorrectableContractError(lastErr)) {
20287
+ log.warn("corrective retry produced a non-correctable error \u2014 self-blocking without a further nudge", {
20288
+ outputDir: String(deps.outputDir),
20289
+ attempt,
20290
+ error: lastErr.message
20291
+ });
20292
+ return Result.error(lastErr);
20293
+ }
20258
20294
  }
20259
20295
  log.warn("corrective retries exhausted \u2014 self-blocking", {
20260
20296
  outputDir: String(deps.outputDir),
@@ -20526,7 +20562,7 @@ var evaluatorOutputContract = {
20526
20562
  };
20527
20563
 
20528
20564
  // src/application/flows/implement/leaves/round-artifacts.ts
20529
- import { promises as fs22 } from "fs";
20565
+ import { promises as fs21 } from "fs";
20530
20566
  import { join as join45 } from "path";
20531
20567
  var nextRoundNum = async (workspaceRoot) => {
20532
20568
  const entries = await listDir(join45(String(workspaceRoot), "rounds"));
@@ -20545,7 +20581,7 @@ var readRoundSessionId = async (workspaceRoot, round, role) => {
20545
20581
  const path2 = join45(String(workspaceRoot), "rounds", String(round), role, "session-id.txt");
20546
20582
  let content;
20547
20583
  try {
20548
- content = await fs22.readFile(path2, "utf8");
20584
+ content = await fs21.readFile(path2, "utf8");
20549
20585
  } catch {
20550
20586
  return void 0;
20551
20587
  }
@@ -20617,7 +20653,7 @@ var positiveCountCarry = (field, delta, prior) => delta > 0 ? { [field]: (prior
20617
20653
  // src/application/flows/implement/leaves/evaluator.ts
20618
20654
  var readCappedProgress = async (path2, currentTaskId, model) => {
20619
20655
  try {
20620
- return capProgressBody(await fs23.readFile(path2, "utf8"), {
20656
+ return capProgressBody(await fs22.readFile(path2, "utf8"), {
20621
20657
  currentTaskId,
20622
20658
  recentBudgetTokens: progressCapBudgetForModel(model)
20623
20659
  });
@@ -20676,7 +20712,7 @@ var makeEvaluatorReinvoke = (deps, args) => async (corrective, attempt) => {
20676
20712
  args.signalsFile,
20677
20713
  "evaluator",
20678
20714
  resume,
20679
- deps.effort,
20715
+ args.effectiveEffort,
20680
20716
  args.signal,
20681
20717
  bodyFile.ok ? bodyFile.value : void 0
20682
20718
  )
@@ -20704,7 +20740,8 @@ var makeEvaluatorCallEvaluate = (deps, args) => async (task) => {
20704
20740
  const initialBodyFile = AbsolutePath.parse(
20705
20741
  roundBodyPath(args.input.workspaceRoot, args.input.roundNum, "evaluator")
20706
20742
  );
20707
- const spawn3 = await deps.provider.generate(
20743
+ const effectiveEffort = task.escalatedToEvaluatorEffort ?? deps.effort;
20744
+ const spawn2 = await deps.provider.generate(
20708
20745
  implementSession(
20709
20746
  args.input.workspaceRoot,
20710
20747
  deps.cwd,
@@ -20714,12 +20751,12 @@ var makeEvaluatorCallEvaluate = (deps, args) => async (task) => {
20714
20751
  args.signalsFile,
20715
20752
  "evaluator",
20716
20753
  args.input.priorEvaluatorSessionId,
20717
- deps.effort,
20754
+ effectiveEffort,
20718
20755
  args.signal,
20719
20756
  initialBodyFile.ok ? initialBodyFile.value : void 0
20720
20757
  )
20721
20758
  );
20722
- if (!spawn3.ok) return Result.error(spawn3.error);
20759
+ if (!spawn2.ok) return Result.error(spawn2.error);
20723
20760
  const validated = await validateSignalsFileWithCorrectiveRetry(
20724
20761
  {
20725
20762
  outputDir: args.outputDir,
@@ -20741,6 +20778,7 @@ var makeEvaluatorCallEvaluate = (deps, args) => async (task) => {
20741
20778
  workspaceRoot: args.input.workspaceRoot,
20742
20779
  roundNum: args.input.roundNum,
20743
20780
  signalsFile: args.signalsFile,
20781
+ effectiveEffort,
20744
20782
  priorEvaluatorSessionId: args.input.priorEvaluatorSessionId,
20745
20783
  signal: args.signal
20746
20784
  })
@@ -20860,7 +20898,7 @@ var evaluatorLeaf = (deps, taskId) => leaf(`evaluator-${String(taskId)}`, {
20860
20898
 
20861
20899
  // src/application/flows/implement/leaves/generator.ts
20862
20900
  import { dirname as dirname18, join as join47 } from "path";
20863
- import { promises as fs25 } from "fs";
20901
+ import { promises as fs24 } from "fs";
20864
20902
 
20865
20903
  // src/business/task/run-generator-turn.ts
20866
20904
  var findTaskBlocked = (signals) => signals.find((s) => s.type === "task-blocked")?.reason;
@@ -21027,7 +21065,8 @@ var composeDimensionTrajectory = (input) => {
21027
21065
  const priorFailed = failedDimensions(prior.evaluation);
21028
21066
  const fixed = sortedLimited([...priorFailed].filter((d) => !latestFailed.has(d)));
21029
21067
  const newlyFailing = sortedLimited([...latestFailed].filter((d) => !priorFailed.has(d)));
21030
- const stillFailing = sortedLimited([...latestFailed].filter((d) => priorFailed.has(d)));
21068
+ const stillFailingAll = [...latestFailed].filter((d) => priorFailed.has(d));
21069
+ const stillFailing = sortedLimited(stillFailingAll);
21031
21070
  const lines = [];
21032
21071
  for (const d of fixed) {
21033
21072
  lines.push(`- ${d}: fixed since last round \u2014 keep it passing.`);
@@ -21041,7 +21080,7 @@ var composeDimensionTrajectory = (input) => {
21041
21080
  }
21042
21081
  if (lines.length === 0) return "";
21043
21082
  const threshold = Math.max(2, Math.trunc(input.plateauThreshold));
21044
- const longestStall = stillFailing.reduce((max, d) => Math.max(max, consecutiveFailing(history, d)), 0);
21083
+ const longestStall = stillFailingAll.reduce((max, d) => Math.max(max, consecutiveFailing(history, d)), 0);
21045
21084
  const pressure = longestStall >= threshold - 1 ? [
21046
21085
  "",
21047
21086
  `Round ${String(input.roundNum)} of ${String(input.maxTurns)}; ${String(longestStall)} stalled round(s) \u2014 the harness exits this loop at ${String(threshold)} consecutive stalled rounds and escalates. Do NOT repeat the previous approach on a still-failing dimension; step back and try a fundamentally different fix.`
@@ -21160,7 +21199,7 @@ var formatRetryFeedback = (task, logTail) => {
21160
21199
  };
21161
21200
 
21162
21201
  // src/integration/io/read-log-tail.ts
21163
- import { promises as fs24 } from "fs";
21202
+ import { promises as fs23 } from "fs";
21164
21203
  var DEFAULT_LOG_TAIL_BYTES = 4096;
21165
21204
  var createFsLogTailReader = () => {
21166
21205
  return async (path2, maxBytes = DEFAULT_LOG_TAIL_BYTES) => {
@@ -21168,7 +21207,7 @@ var createFsLogTailReader = () => {
21168
21207
  if (cap === 0) return "";
21169
21208
  let handle;
21170
21209
  try {
21171
- handle = await fs24.open(String(path2), "r");
21210
+ handle = await fs23.open(String(path2), "r");
21172
21211
  const stat = await handle.stat();
21173
21212
  const size = stat.size;
21174
21213
  if (size <= 0) return "";
@@ -21199,7 +21238,7 @@ var countTurnActionKinds = (out) => {
21199
21238
  };
21200
21239
  var readCappedProgress2 = async (path2, currentTaskId, model) => {
21201
21240
  try {
21202
- return capProgressBody(await fs25.readFile(path2, "utf8"), {
21241
+ return capProgressBody(await fs24.readFile(path2, "utf8"), {
21203
21242
  currentTaskId,
21204
21243
  recentBudgetTokens: progressCapBudgetForModel(model)
21205
21244
  });
@@ -21364,7 +21403,7 @@ var makeGeneratorCallImplement = (deps, taskId, args) => async (task) => {
21364
21403
  const effectiveModel = task.escalatedToModel ?? deps.model;
21365
21404
  const effectiveEffort = task.escalatedToEffort ?? deps.effort;
21366
21405
  const initialBodyFile = AbsolutePath.parse(roundBodyPath(args.input.workspaceRoot, args.roundNum, "generator"));
21367
- const spawn3 = await deps.provider.generate(
21406
+ const spawn2 = await deps.provider.generate(
21368
21407
  implementSession(
21369
21408
  args.input.workspaceRoot,
21370
21409
  deps.cwd,
@@ -21379,7 +21418,7 @@ var makeGeneratorCallImplement = (deps, taskId, args) => async (task) => {
21379
21418
  initialBodyFile.ok ? initialBodyFile.value : void 0
21380
21419
  )
21381
21420
  );
21382
- if (!spawn3.ok) return Result.error(spawn3.error);
21421
+ if (!spawn2.ok) return Result.error(spawn2.error);
21383
21422
  const validated = await validateSignalsFileWithCorrectiveRetry(
21384
21423
  {
21385
21424
  outputDir: args.outputDir,
@@ -22725,13 +22764,13 @@ var quarantineRetryDiffLeaf = (deps, opts, taskId) => leaf(`quarantine-retry-dif
22725
22764
  var isRedVerifyRetry = (ctx) => ctx.lastShouldFailAttempt === true && ctx.lastBlockReason !== void 0;
22726
22765
 
22727
22766
  // src/application/flows/implement/leaves/progress-journal.ts
22728
- import { promises as fs26 } from "fs";
22767
+ import { promises as fs25 } from "fs";
22729
22768
 
22730
22769
  // src/business/sprint/render-sprint-state-header.ts
22731
- var EM_DASH2 = "\u2014";
22770
+ var EM_DASH3 = "\u2014";
22732
22771
  var cell = (text) => neutralizeProseHeadings(sanitizeInline(text));
22733
22772
  var isStale = (task) => task.status === "in_progress" && task.attemptCount > 0;
22734
- var orEmDash = (value) => value !== null && value.length > 0 ? value : EM_DASH2;
22773
+ var orEmDash = (value) => value !== null && value.length > 0 ? value : EM_DASH3;
22735
22774
  var renderIdentity = (input) => [
22736
22775
  `# Sprint: ${cell(input.sprintName)}`,
22737
22776
  "",
@@ -22751,7 +22790,7 @@ var renderBlockers = (tasks) => {
22751
22790
  const blockers = tasks.filter((t) => t.status === "blocked");
22752
22791
  if (blockers.length === 0) return [];
22753
22792
  const bullets = blockers.map((t) => {
22754
- const reason = t.blockedReason !== void 0 && t.blockedReason.trim().length > 0 ? cell(t.blockedReason) : EM_DASH2;
22793
+ const reason = t.blockedReason !== void 0 && t.blockedReason.trim().length > 0 ? cell(t.blockedReason) : EM_DASH3;
22755
22794
  return `- ${cell(t.name)}: ${reason}`;
22756
22795
  });
22757
22796
  return ["## Blockers", "", ...bullets, ""];
@@ -22764,7 +22803,7 @@ var renderStale = (tasks) => {
22764
22803
  );
22765
22804
  return ["## Stale tasks", "", ...bullets, ""];
22766
22805
  };
22767
- var passesCell = (task) => task.criteriaTotal === 0 ? EM_DASH2 : `${String(task.criteriaPassed)}/${String(task.criteriaTotal)}`;
22806
+ var passesCell = (task) => task.criteriaTotal === 0 ? EM_DASH3 : `${String(task.criteriaPassed)}/${String(task.criteriaTotal)}`;
22768
22807
  var renderTaskTable = (tasks) => {
22769
22808
  if (tasks.length === 0) return ["## Tasks", "", "_No tasks planned yet._", ""];
22770
22809
  const rows = tasks.map((t) => `| ${cell(t.name)} | ${t.status} | ${passesCell(t)} |`);
@@ -22902,7 +22941,7 @@ var buildAttemptSection = (input, totalRounds, clock) => {
22902
22941
  };
22903
22942
  var readExisting = async (path2) => {
22904
22943
  try {
22905
- return await fs26.readFile(String(path2), "utf8");
22944
+ return await fs25.readFile(String(path2), "utf8");
22906
22945
  } catch {
22907
22946
  return "";
22908
22947
  }
@@ -23142,16 +23181,16 @@ ${output.slice(output.length - tail)}`;
23142
23181
  };
23143
23182
 
23144
23183
  // src/business/task/render-round-outcome.ts
23145
- var EM_DASH3 = "\u2014";
23184
+ var EM_DASH4 = "\u2014";
23146
23185
  var renderRoundOutcome = (input) => {
23147
23186
  const lines = [];
23148
23187
  lines.push(`# Round ${String(input.roundN)} \xB7 attempt ${String(input.attemptN)}`);
23149
23188
  lines.push("");
23150
- lines.push(`- generator session: ${input.generatorSessionId ?? EM_DASH3}`);
23151
- lines.push(`- evaluator session: ${input.evaluatorSessionId ?? EM_DASH3}`);
23152
- lines.push(`- duration: ${formatDuration2(input.durationMs)}`);
23189
+ lines.push(`- generator session: ${input.generatorSessionId ?? EM_DASH4}`);
23190
+ lines.push(`- evaluator session: ${input.evaluatorSessionId ?? EM_DASH4}`);
23191
+ lines.push(`- duration: ${formatDuration(input.durationMs)}`);
23153
23192
  lines.push(`- verdict: ${input.verdict}`);
23154
- lines.push(`- commit: ${input.attempt.commitSha !== void 0 ? String(input.attempt.commitSha) : EM_DASH3}`);
23193
+ lines.push(`- commit: ${input.attempt.commitSha !== void 0 ? String(input.attempt.commitSha) : EM_DASH4}`);
23155
23194
  const dimensionsBlock = renderDimensions(input.evaluation, input.attempt.evaluation);
23156
23195
  if (dimensionsBlock !== void 0) {
23157
23196
  lines.push("");
@@ -23223,25 +23262,6 @@ var formatFailedDimensions = (evaluation) => {
23223
23262
  };
23224
23263
  var SHA_DISPLAY_LENGTH2 = 7;
23225
23264
  var shortSha = (sha) => sha.slice(0, SHA_DISPLAY_LENGTH2);
23226
- var MS_PER_SECOND2 = 1e3;
23227
- var MS_PER_MINUTE2 = 60 * MS_PER_SECOND2;
23228
- var MS_PER_HOUR2 = 60 * MS_PER_MINUTE2;
23229
- var formatDuration2 = (ms) => {
23230
- if (ms === void 0) return EM_DASH3;
23231
- if (ms < 0) return `${String(ms)}ms`;
23232
- if (ms < MS_PER_SECOND2) return `${String(ms)}ms`;
23233
- if (ms < MS_PER_MINUTE2) {
23234
- return `${String(Math.floor(ms / MS_PER_SECOND2))}s`;
23235
- }
23236
- if (ms < MS_PER_HOUR2) {
23237
- const minutes2 = Math.floor(ms / MS_PER_MINUTE2);
23238
- const seconds = Math.floor(ms % MS_PER_MINUTE2 / MS_PER_SECOND2);
23239
- return seconds > 0 ? `${String(minutes2)}m ${String(seconds)}s` : `${String(minutes2)}m`;
23240
- }
23241
- const hours = Math.floor(ms / MS_PER_HOUR2);
23242
- const minutes = Math.floor(ms % MS_PER_HOUR2 / MS_PER_MINUTE2);
23243
- return minutes > 0 ? `${String(hours)}h ${String(minutes)}m` : `${String(hours)}h`;
23244
- };
23245
23265
 
23246
23266
  // src/application/flows/implement/leaves/settle-attempt.ts
23247
23267
  var settleAttemptLeaf = (deps, opts, taskId) => {
@@ -23651,7 +23671,7 @@ var createPerTaskSubchain = (deps, opts, task, repo, readConfig) => {
23651
23671
  )
23652
23672
  ] : [],
23653
23673
  buildTaskWorkspaceLeaf(
23654
- { templateLoader: deps.templateLoader, logger: deps.logger },
23674
+ { templateLoader: deps.templateLoader, logger: deps.logger, writeFile: deps.writeFile },
23655
23675
  {
23656
23676
  sprintDir: opts.sprintDir,
23657
23677
  cwd: repo.path,
@@ -23759,7 +23779,14 @@ var createPerTaskSubchain = (deps, opts, task, repo, readConfig) => {
23759
23779
  // before spending the nudge. `providerId` is the resolved provider enum string;
23760
23780
  // `nextEffortRung` skips gracefully for any non-effort provider or model.
23761
23781
  configuredGeneratorProvider: opts.generator.providerId,
23762
- ...opts.generator.effort !== void 0 ? { configuredGeneratorEffort: opts.generator.effort } : {}
23782
+ ...opts.generator.effort !== void 0 ? { configuredGeneratorEffort: opts.generator.effort } : {},
23783
+ // Same lockstep wiring for the EVALUATOR role: the policy computes its bump
23784
+ // independently against this triple, never copying the generator's target. The
23785
+ // evaluator MODEL is never escalated, but it still rides through so `nextEffortRung`
23786
+ // can classify the evaluator's own effort ladder.
23787
+ configuredEvaluatorProvider: opts.evaluator.providerId,
23788
+ configuredEvaluatorModel: opts.evaluator.model,
23789
+ ...opts.evaluator.effort !== void 0 ? { configuredEvaluatorEffort: opts.evaluator.effort } : {}
23763
23790
  },
23764
23791
  taskId
23765
23792
  ),
@@ -24892,6 +24919,16 @@ var createPublishSignal = (eventBus, source, taskId) => (signal) => {
24892
24919
  eventBus.publish({ type: "ai-signal", signal, source, ...taskId !== void 0 ? { taskId } : {} });
24893
24920
  };
24894
24921
 
24922
+ // src/application/flows/implement/sprint-scoped-projection.ts
24923
+ var projectSprintScopedFields = (ctx) => ({
24924
+ sprintId: ctx.sprintId,
24925
+ sprint: ctx.sprint,
24926
+ execution: ctx.execution,
24927
+ progressFile: ctx.progressFile,
24928
+ setupVerifiedRepoIdsThisRun: ctx.setupVerifiedRepoIdsThisRun,
24929
+ priorLearnings: ctx.priorLearnings
24930
+ });
24931
+
24895
24932
  // src/application/flows/implement/merge-wave.ts
24896
24933
  var branchSettled = (outcome) => outcome.status === "completed" || outcome.error !== void 0;
24897
24934
  var mergeImplementWave = (base, outcomes) => {
@@ -24902,13 +24939,7 @@ var mergeImplementWave = (base, outcomes) => {
24902
24939
  }
24903
24940
  const tasks = base.tasks?.map((t) => byId.get(t.id) ?? t);
24904
24941
  return {
24905
- // sprint-scoped → base
24906
- sprintId: base.sprintId,
24907
- ...base.sprint !== void 0 ? { sprint: base.sprint } : {},
24908
- ...base.execution !== void 0 ? { execution: base.execution } : {},
24909
- ...base.progressFile !== void 0 ? { progressFile: base.progressFile } : {},
24910
- ...base.setupVerifiedRepoIdsThisRun !== void 0 ? { setupVerifiedRepoIdsThisRun: base.setupVerifiedRepoIdsThisRun } : {},
24911
- ...base.priorLearnings !== void 0 ? { priorLearnings: base.priorLearnings } : {},
24942
+ ...projectSprintScopedFields(base),
24912
24943
  // task list → overlay
24913
24944
  ...tasks !== void 0 ? { tasks } : {}
24914
24945
  // per-task + signal-accum classes intentionally omitted → undefined in the merged ctx.
@@ -24916,17 +24947,8 @@ var mergeImplementWave = (base, outcomes) => {
24916
24947
  };
24917
24948
  var forkCtx = (base, repo, worktreePath) => {
24918
24949
  const ctx = {
24919
- // sprint-scoped → base
24920
- sprintId: base.sprintId,
24921
- ...base.sprint !== void 0 ? { sprint: base.sprint } : {},
24922
- ...base.execution !== void 0 ? { execution: base.execution } : {},
24923
- ...base.progressFile !== void 0 ? { progressFile: base.progressFile } : {},
24924
- ...base.tasks !== void 0 ? { tasks: base.tasks } : {},
24925
- // run-scoped (like `execution`): the set of repos this launch's setup verified survives the
24926
- // fork so a parallel branch's first pre-task-verify can also take the fresh-setup skip.
24927
- ...base.setupVerifiedRepoIdsThisRun !== void 0 ? { setupVerifiedRepoIdsThisRun: base.setupVerifiedRepoIdsThisRun } : {},
24928
- // run-scoped cross-sprint memory: every branch's generator reads the same loaded ledger.
24929
- ...base.priorLearnings !== void 0 ? { priorLearnings: base.priorLearnings } : {}
24950
+ ...projectSprintScopedFields(base),
24951
+ ...base.tasks !== void 0 ? { tasks: base.tasks } : {}
24930
24952
  // per-task + signal-accum classes cleared (omitted → undefined); `priorPostVerifyOutcome`
24931
24953
  // dropped (accepted cost). `expectedBranch` is intentionally NOT set here — see the docstring:
24932
24954
  // the branch element omits `branch-preflight`, so leaving it `undefined` is correct.
@@ -25776,7 +25798,7 @@ var launchImplement = async (ctx) => {
25776
25798
  import { join as join60 } from "path";
25777
25799
 
25778
25800
  // src/application/flows/review/leaves/ensure-feedback-file.ts
25779
- import { promises as fs27 } from "fs";
25801
+ import { promises as fs26 } from "fs";
25780
25802
 
25781
25803
  // src/business/feedback/md-parser.ts
25782
25804
  var ROUND_HEADING_RE = /^##\s+Round\s+(\d+)\s*$/;
@@ -25851,12 +25873,12 @@ var ensureFeedbackFileLeaf = (feedbackFile) => leaf("ensure-feedback-file", {
25851
25873
  useCase: {
25852
25874
  execute: async (path2) => {
25853
25875
  try {
25854
- await fs27.access(String(path2));
25876
+ await fs26.access(String(path2));
25855
25877
  return Result.ok(void 0);
25856
25878
  } catch {
25857
25879
  }
25858
25880
  try {
25859
- await fs27.writeFile(String(path2), TEMPLATE, { flag: "wx" });
25881
+ await fs26.writeFile(String(path2), TEMPLATE, { flag: "wx" });
25860
25882
  return Result.ok(void 0);
25861
25883
  } catch (cause) {
25862
25884
  if (typeof cause === "object" && cause !== null && cause.code === "EEXIST") {
@@ -25878,7 +25900,7 @@ var ensureFeedbackFileLeaf = (feedbackFile) => leaf("ensure-feedback-file", {
25878
25900
  });
25879
25901
 
25880
25902
  // src/application/flows/review/leaves/review-round.ts
25881
- import { promises as fs28 } from "fs";
25903
+ import { promises as fs27 } from "fs";
25882
25904
  import { join as join56 } from "path";
25883
25905
 
25884
25906
  // src/business/feedback/apply-feedback.ts
@@ -26061,7 +26083,7 @@ var reviewRoundOutputContract = {
26061
26083
  var readProgressSnippet = async (path2) => {
26062
26084
  if (path2 === void 0) return "_(no progress file)_";
26063
26085
  try {
26064
- const content = await fs28.readFile(String(path2), "utf8");
26086
+ const content = await fs27.readFile(String(path2), "utf8");
26065
26087
  return content.length > 4e3 ? `${content.slice(0, 4e3)}
26066
26088
  [truncated]` : content;
26067
26089
  } catch {
@@ -26077,7 +26099,7 @@ ${renderEmptyRound(nextIndex)}${ROUND_SEPARATOR}
26077
26099
  };
26078
26100
  var deriveActiveRoundIndex = async (feedbackFile) => {
26079
26101
  try {
26080
- const raw = await fs28.readFile(String(feedbackFile), "utf8");
26102
+ const raw = await fs27.readFile(String(feedbackFile), "utf8");
26081
26103
  const rounds = parseFeedbackMd(raw);
26082
26104
  return rounds.at(-1)?.index ?? 1;
26083
26105
  } catch {
@@ -26086,7 +26108,7 @@ var deriveActiveRoundIndex = async (feedbackFile) => {
26086
26108
  };
26087
26109
  var writeRoundBody = async (path2, body) => {
26088
26110
  try {
26089
- const content = await fs28.readFile(String(path2), "utf8");
26111
+ const content = await fs27.readFile(String(path2), "utf8");
26090
26112
  const lastMarker = content.lastIndexOf(MARKER_COMMENT);
26091
26113
  if (lastMarker === -1) {
26092
26114
  return Result.error(
@@ -26126,7 +26148,7 @@ var allocateRoundPaths = (reviewRoot, roundIndex) => {
26126
26148
  };
26127
26149
  var ensureRoundDir = async (dir) => {
26128
26150
  try {
26129
- await fs28.mkdir(String(dir), { recursive: true });
26151
+ await fs27.mkdir(String(dir), { recursive: true });
26130
26152
  return Result.ok(void 0);
26131
26153
  } catch (cause) {
26132
26154
  return Result.error(
@@ -26161,7 +26183,7 @@ var reviewRoundLeaf = (deps, opts) => leaf(LEAF_NAME3, {
26161
26183
  if (!wrote.ok) return Result.error(wrote.error);
26162
26184
  return Result.ok(void 0);
26163
26185
  },
26164
- readFeedbackFile: () => fs28.readFile(String(input.feedbackFile), "utf8"),
26186
+ readFeedbackFile: () => fs27.readFile(String(input.feedbackFile), "utf8"),
26165
26187
  readProgressSnippet: () => readProgressSnippet(input.progressFile),
26166
26188
  buildPrompt: async (params) => {
26167
26189
  const outputContractSection = renderContractSectionFor(reviewRoundOutputContract, paths.value.outputDir);
@@ -26189,7 +26211,7 @@ var reviewRoundLeaf = (deps, opts) => leaf(LEAF_NAME3, {
26189
26211
  const promptWrote = await writeTextAtomic(String(paths.value.promptFile), String(prompt));
26190
26212
  if (!promptWrote.ok) return Result.error(promptWrote.error);
26191
26213
  const chainSessionId = currentSessionId();
26192
- const spawn3 = await deps.provider.generate({
26214
+ const spawn2 = await deps.provider.generate({
26193
26215
  prompt,
26194
26216
  cwd: paths.value.outputDir,
26195
26217
  additionalRoots: opts.additionalRoots,
@@ -26202,7 +26224,7 @@ var reviewRoundLeaf = (deps, opts) => leaf(LEAF_NAME3, {
26202
26224
  // the provider's SIGTERM ladder instead of letting it run to completion.
26203
26225
  ...signal !== void 0 ? { abortSignal: signal } : {}
26204
26226
  });
26205
- if (!spawn3.ok) return Result.error(spawn3.error);
26227
+ if (!spawn2.ok) return Result.error(spawn2.error);
26206
26228
  const validated = await validateSignalsFile(paths.value.outputDir, reviewRoundOutputContract);
26207
26229
  if (!validated.ok) return Result.error(validated.error);
26208
26230
  for (const sig of validated.value) deps.publishSignal(sig);
@@ -26432,7 +26454,7 @@ var stampPass = (collected, accepted, retired, stampTime, path2) => {
26432
26454
  var isLive = (record) => record.retiredAt === void 0 || record.retiredAt === null;
26433
26455
 
26434
26456
  // src/application/flows/_shared/memory/distill-propose.ts
26435
- import { promises as fs29 } from "fs";
26457
+ import { promises as fs28 } from "fs";
26436
26458
  import { join as join58 } from "path";
26437
26459
 
26438
26460
  // src/integration/ai/prompts/distill-learnings/definition.ts
@@ -26544,7 +26566,7 @@ var distillProposeUseCase = async (deps, tool, input, signal) => {
26544
26566
  if (!prompt.ok) return Result.error(prompt.error);
26545
26567
  const toolDir = join58(String(deps.distillRoot), tool);
26546
26568
  try {
26547
- await fs29.mkdir(toolDir, { recursive: true });
26569
+ await fs28.mkdir(toolDir, { recursive: true });
26548
26570
  } catch (cause) {
26549
26571
  return Result.error(
26550
26572
  new StorageError({
@@ -26598,11 +26620,11 @@ var renderProjectTooling = (repository) => {
26598
26620
  const lines = [];
26599
26621
  if (repository.setupScript !== void 0) lines.push(`- Setup: ${repository.setupScript}`);
26600
26622
  if (repository.verifyScript !== void 0) lines.push(`- Verify: ${repository.verifyScript}`);
26601
- return lines.length > 0 ? lines.join("\n") : "(none detected)";
26623
+ return renderProjectToolingSection(lines.length > 0 ? lines.join("\n") : void 0);
26602
26624
  };
26603
26625
  var safeReadText = async (path2) => {
26604
26626
  try {
26605
- return await fs29.readFile(path2, "utf8");
26627
+ return await fs28.readFile(path2, "utf8");
26606
26628
  } catch {
26607
26629
  return void 0;
26608
26630
  }
@@ -26665,7 +26687,7 @@ var distillConfirmLeaf = (deps, tool) => leaf(`distill-confirm-${tool}`, {
26665
26687
  });
26666
26688
 
26667
26689
  // src/application/flows/_shared/memory/distill-write.ts
26668
- import { promises as fs30 } from "fs";
26690
+ import { promises as fs29 } from "fs";
26669
26691
  var distillWriteUseCase = async (deps, tool, input) => {
26670
26692
  const log = deps.logger.named(`memory.distill-write-${tool}`);
26671
26693
  if (!input.accepted || input.proposedContent === void 0 || input.targetPath === void 0) {
@@ -26694,14 +26716,14 @@ var distillWriteUseCase = async (deps, tool, input) => {
26694
26716
  var makeBackupPath = (targetPath, now) => `${String(targetPath)}.bak.${String(now).replace(/:/g, "-")}`;
26695
26717
  var fileExists = async (path2) => {
26696
26718
  try {
26697
- return (await fs30.stat(path2)).isFile();
26719
+ return (await fs29.stat(path2)).isFile();
26698
26720
  } catch {
26699
26721
  return false;
26700
26722
  }
26701
26723
  };
26702
26724
  var safeReadText2 = async (path2) => {
26703
26725
  try {
26704
- return await fs30.readFile(path2, "utf8");
26726
+ return await fs29.readFile(path2, "utf8");
26705
26727
  } catch {
26706
26728
  return void 0;
26707
26729
  }
@@ -27858,7 +27880,7 @@ var probeReadinessLeaf = (deps, tool) => leaf(`probe-${tool}`, {
27858
27880
  });
27859
27881
 
27860
27882
  // src/application/flows/readiness/leaves/propose.ts
27861
- import { promises as fs31 } from "fs";
27883
+ import { promises as fs30 } from "fs";
27862
27884
 
27863
27885
  // src/integration/ai/prompts/readiness/definition.ts
27864
27886
  var CLAUDE_CODE = "claude-code";
@@ -28127,7 +28149,7 @@ var proposeReadinessUseCase = async (deps, tool, input, abortSignal) => {
28127
28149
  let existingBody;
28128
28150
  if (existingPath !== void 0) {
28129
28151
  try {
28130
- existingBody = await fs31.readFile(existingPath, "utf8");
28152
+ existingBody = await fs30.readFile(existingPath, "utf8");
28131
28153
  } catch {
28132
28154
  existingBody = void 0;
28133
28155
  }
@@ -28259,7 +28281,7 @@ var proposeReadinessLeaf = (deps, tool) => leaf(`propose-${tool}`, {
28259
28281
  });
28260
28282
 
28261
28283
  // src/application/flows/readiness/leaves/write.ts
28262
- import { promises as fs32 } from "fs";
28284
+ import { promises as fs31 } from "fs";
28263
28285
  var writeReadinessUseCase = async (deps, tool, input) => {
28264
28286
  const log = deps.logger.named(`readiness.write-${tool}`);
28265
28287
  if (!input.accepted || input.proposal === void 0) {
@@ -28293,7 +28315,7 @@ var makeBackupPath2 = (targetPath, now) => {
28293
28315
  };
28294
28316
  var fileExists2 = async (path2) => {
28295
28317
  try {
28296
- const stat = await fs32.stat(path2);
28318
+ const stat = await fs31.stat(path2);
28297
28319
  return stat.isFile();
28298
28320
  } catch {
28299
28321
  return false;
@@ -28301,7 +28323,7 @@ var fileExists2 = async (path2) => {
28301
28323
  };
28302
28324
  var safeReadText3 = async (path2) => {
28303
28325
  try {
28304
- return await fs32.readFile(path2, "utf8");
28326
+ return await fs31.readFile(path2, "utf8");
28305
28327
  } catch {
28306
28328
  return void 0;
28307
28329
  }
@@ -28326,11 +28348,11 @@ var writeReadinessLeaf = (deps, tool) => leaf(`write-${tool}`, {
28326
28348
  });
28327
28349
 
28328
28350
  // src/application/flows/_shared/allocate-run-dir.ts
28329
- import { promises as fs34 } from "fs";
28351
+ import { promises as fs33 } from "fs";
28330
28352
  import { join as join63 } from "path";
28331
28353
 
28332
28354
  // src/integration/ai/runs/_engine/run-artifacts.ts
28333
- import { promises as fs33 } from "fs";
28355
+ import { promises as fs32 } from "fs";
28334
28356
  import { join as join62 } from "path";
28335
28357
  var BODY_PREVIEW_LIMIT = 800;
28336
28358
  var buildRunDirName = () => {
@@ -28341,7 +28363,7 @@ var buildRunDirName = () => {
28341
28363
  var readRunBodyPreview = async (runDir, options) => {
28342
28364
  let raw;
28343
28365
  try {
28344
- raw = await fs33.readFile(join62(String(runDir), "body.txt"), "utf8");
28366
+ raw = await fs32.readFile(join62(String(runDir), "body.txt"), "utf8");
28345
28367
  } catch (cause) {
28346
28368
  if (isErrnoException(cause) && cause.code === "ENOENT") return void 0;
28347
28369
  const code = isErrnoException(cause) ? cause.code : "unknown";
@@ -28361,7 +28383,7 @@ var allocateRunDirLeaf = (opts) => leaf(opts.name, {
28361
28383
  useCase: {
28362
28384
  execute: async (input) => {
28363
28385
  try {
28364
- await fs34.mkdir(input.path, { recursive: true });
28386
+ await fs33.mkdir(input.path, { recursive: true });
28365
28387
  } catch (cause) {
28366
28388
  return Result.error(
28367
28389
  new StorageError({
@@ -28749,16 +28771,16 @@ var proposeUseCase = async (deps, input, abortSignal) => {
28749
28771
  if (!prompt.ok) return Result.error(prompt.error);
28750
28772
  const promptWrote = await writeTextAtomic(String(paths.value.promptFile), String(prompt.value));
28751
28773
  if (!promptWrote.ok) return Result.error(promptWrote.error);
28752
- const spawn3 = await deps.provider.generate(
28774
+ const spawn2 = await deps.provider.generate(
28753
28775
  readOnlySignalsSession(buildSessionOpts(deps, input, paths.value, prompt.value, abortSignal))
28754
28776
  );
28755
- if (!spawn3.ok) {
28777
+ if (!spawn2.ok) {
28756
28778
  log.error(`provider failed for repo ${input.repository.name}`, {
28757
28779
  repositoryId: String(input.repository.id),
28758
- error: spawn3.error.message,
28780
+ error: spawn2.error.message,
28759
28781
  runDir: String(input.runDir)
28760
28782
  });
28761
- return Result.error(spawn3.error);
28783
+ return Result.error(spawn2.error);
28762
28784
  }
28763
28785
  const validated = await validateSignalsFile(input.runDir, detectSkillsOutputContract);
28764
28786
  if (!validated.ok) {
@@ -29194,16 +29216,16 @@ var proposeUseCase2 = async (deps, input, abortSignal) => {
29194
29216
  if (!prompt.ok) return Result.error(prompt.error);
29195
29217
  const promptWrote = await writeTextAtomic(String(paths.value.promptFile), String(prompt.value));
29196
29218
  if (!promptWrote.ok) return Result.error(promptWrote.error);
29197
- const spawn3 = await deps.provider.generate(
29219
+ const spawn2 = await deps.provider.generate(
29198
29220
  readOnlySignalsSession(buildSessionOpts2(deps, input, paths.value, prompt.value, abortSignal))
29199
29221
  );
29200
- if (!spawn3.ok) {
29222
+ if (!spawn2.ok) {
29201
29223
  log.error(`provider failed for repo ${input.repository.name}`, {
29202
29224
  repositoryId: String(input.repository.id),
29203
- error: spawn3.error.message,
29225
+ error: spawn2.error.message,
29204
29226
  runDir: String(input.runDir)
29205
29227
  });
29206
- return Result.error(spawn3.error);
29228
+ return Result.error(spawn2.error);
29207
29229
  }
29208
29230
  const validated = await validateSignalsFile(input.runDir, detectScriptsOutputContract);
29209
29231
  if (!validated.ok) {
@@ -31025,8 +31047,8 @@ var defaultRowFor = (flowId, settings) => {
31025
31047
  return primaryFlowRow(settings.ai, aiFlow);
31026
31048
  };
31027
31049
  var labelKeepDefault = (value) => `Keep default (${value ?? "unset"})`;
31028
- var formatRow = (row, globalEffort) => {
31029
- const resolved = resolveEffortForRow(row, globalEffort);
31050
+ var formatRow = (row, globalEffort, flow) => {
31051
+ const resolved = resolveEffortForRow(row, globalEffort, flow);
31030
31052
  return `${row.provider} / ${row.model} / ${resolved ?? "auto"}`;
31031
31053
  };
31032
31054
  var pickProviderStep = async (interactive, header, defaultRow) => {
@@ -31060,9 +31082,9 @@ var computeEffortDefaultLabel = (defaultRow, globalEffort, providerChanged, mode
31060
31082
  }
31061
31083
  return labelKeepDefault(resolvedRowEffort ?? "auto");
31062
31084
  };
31063
- var pickEffortStep = async (interactive, header, defaultRow, globalEffort, effectiveProvider, providerChanged, modelChanged) => {
31085
+ var pickEffortStep = async (interactive, header, defaultRow, globalEffort, effectiveProvider, providerChanged, modelChanged, flow) => {
31064
31086
  const effortCatalog = PROVIDER_EFFORT_LEVELS[effectiveProvider];
31065
- const resolvedRowEffort = resolveEffortForRow(defaultRow, globalEffort);
31087
+ const resolvedRowEffort = resolveEffortForRow(defaultRow, globalEffort, flow);
31066
31088
  const effortDefaultLabel = computeEffortDefaultLabel(
31067
31089
  defaultRow,
31068
31090
  globalEffort,
@@ -31087,7 +31109,7 @@ var assembleRowOverride = (defaultRow, providerChanged, providerValue, modelValu
31087
31109
  if (providerChanged && override.model === void 0) override.model = modelValue;
31088
31110
  return override;
31089
31111
  };
31090
- var customizeRow = async (interactive, header, defaultRow, globalEffort, availableModelsFor) => {
31112
+ var customizeRow = async (interactive, header, defaultRow, globalEffort, availableModelsFor, flow) => {
31091
31113
  const providerStep = await pickProviderStep(interactive, header, defaultRow);
31092
31114
  if (providerStep === void 0) return void 0;
31093
31115
  const { providerValue, providerChanged, effectiveProvider } = providerStep;
@@ -31108,14 +31130,15 @@ var customizeRow = async (interactive, header, defaultRow, globalEffort, availab
31108
31130
  globalEffort,
31109
31131
  effectiveProvider,
31110
31132
  providerChanged,
31111
- modelChanged
31133
+ modelChanged,
31134
+ flow
31112
31135
  );
31113
31136
  if (effortValue === void 0) return void 0;
31114
31137
  return assembleRowOverride(defaultRow, providerChanged, providerValue, modelValue, effortValue);
31115
31138
  };
31116
- var buildPickerHeader = (flowId, flowTitle, settings) => flowId === "implement" ? `${flowTitle} \u2014 current defaults:
31117
- generator: ${formatRow(settings.ai.implement.generator, settings.ai.effort)}
31118
- evaluator: ${formatRow(settings.ai.implement.evaluator, settings.ai.effort)}` : `${flowTitle} \u2014 current default: ${formatRow(defaultRowFor(flowId, settings), settings.ai.effort)}`;
31139
+ var buildPickerHeader = (flowId, flowTitle, settings, aiFlow) => flowId === "implement" ? `${flowTitle} \u2014 current defaults:
31140
+ generator: ${formatRow(settings.ai.implement.generator, settings.ai.effort, aiFlow)}
31141
+ evaluator: ${formatRow(settings.ai.implement.evaluator, settings.ai.effort, aiFlow)}` : `${flowTitle} \u2014 current default: ${formatRow(defaultRowFor(flowId, settings), settings.ai.effort, aiFlow)}`;
31119
31142
  var originLabel = (origin) => {
31120
31143
  switch (origin) {
31121
31144
  case "bundled-default":
@@ -31173,7 +31196,7 @@ var runSkillsStep = async (interactive, flowTitle, skillCandidates) => {
31173
31196
  skills: { disabled, saveAsDefault: saveAns.value === "remember" }
31174
31197
  };
31175
31198
  };
31176
- var runImplementCustomize = async (interactive, header, flowTitle, settings, availableModelsFor, skillsStepInput) => {
31199
+ var runImplementCustomize = async (interactive, header, flowTitle, settings, availableModelsFor, skillsStepInput, aiFlow) => {
31177
31200
  const roles = ["generator", "evaluator"];
31178
31201
  const collected = {};
31179
31202
  for (const role of roles) {
@@ -31181,7 +31204,7 @@ var runImplementCustomize = async (interactive, header, flowTitle, settings, ava
31181
31204
  const roleHeader = `${header}
31182
31205
 
31183
31206
  Role: ${role}`;
31184
- const result = await customizeRow(interactive, roleHeader, row, settings.ai.effort, availableModelsFor);
31207
+ const result = await customizeRow(interactive, roleHeader, row, settings.ai.effort, availableModelsFor, aiFlow);
31185
31208
  if (result === void 0) return { kind: "cancel" };
31186
31209
  if (Object.keys(result).length > 0) collected[role] = result;
31187
31210
  }
@@ -31205,10 +31228,10 @@ Role: ${role}`;
31205
31228
  ...skills !== void 0 ? { skills } : {}
31206
31229
  };
31207
31230
  };
31208
- var runSingleRowCustomize = async (interactive, header, flowId, flowTitle, settings, availableModelsFor, skillsStepInput) => {
31231
+ var runSingleRowCustomize = async (interactive, header, flowId, flowTitle, settings, availableModelsFor, skillsStepInput, aiFlow) => {
31209
31232
  const defaultRow = defaultRowFor(flowId, settings);
31210
31233
  if (defaultRow === void 0) return { kind: "defaults" };
31211
- const override = await customizeRow(interactive, header, defaultRow, settings.ai.effort, availableModelsFor);
31234
+ const override = await customizeRow(interactive, header, defaultRow, settings.ai.effort, availableModelsFor, aiFlow);
31212
31235
  if (override === void 0) return { kind: "cancel" };
31213
31236
  const candidates = await effectiveSkillCandidates(
31214
31237
  skillsStepInput.candidates,
@@ -31234,7 +31257,7 @@ var runCustomizePicker = async ({
31234
31257
  }) => {
31235
31258
  const aiFlow = aiFlowIdForPicker(flowId);
31236
31259
  if (aiFlow === void 0) return { kind: "defaults" };
31237
- const header = buildPickerHeader(flowId, flowTitle, settings);
31260
+ const header = buildPickerHeader(flowId, flowTitle, settings, aiFlow);
31238
31261
  const action = await interactive.askChoice(
31239
31262
  `${header}
31240
31263
 
@@ -31252,9 +31275,18 @@ What would you like to do?`,
31252
31275
  ...rebuildSkillCandidates !== void 0 ? { rebuild: rebuildSkillCandidates } : {}
31253
31276
  };
31254
31277
  if (flowId === "implement") {
31255
- return runImplementCustomize(interactive, header, flowTitle, settings, availableModelsFor, skillsStepInput);
31278
+ return runImplementCustomize(interactive, header, flowTitle, settings, availableModelsFor, skillsStepInput, aiFlow);
31256
31279
  }
31257
- return runSingleRowCustomize(interactive, header, flowId, flowTitle, settings, availableModelsFor, skillsStepInput);
31280
+ return runSingleRowCustomize(
31281
+ interactive,
31282
+ header,
31283
+ flowId,
31284
+ flowTitle,
31285
+ settings,
31286
+ availableModelsFor,
31287
+ skillsStepInput,
31288
+ aiFlow
31289
+ );
31258
31290
  };
31259
31291
 
31260
31292
  // src/application/flows/settings-set/flow.ts
@@ -31765,10 +31797,10 @@ var FeedbackLine = ({ text }) => {
31765
31797
  if (text === void 0) return null;
31766
31798
  if (typeof text === "string") {
31767
31799
  const { color: color2, body } = resolveStructured(text);
31768
- return /* @__PURE__ */ jsx45(Box34, { paddingX: spacing.indent, marginTop: 1, children: /* @__PURE__ */ jsx45(Text35, { color: color2, children: body }) });
31800
+ return /* @__PURE__ */ jsx45(Box34, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsx45(Text35, { color: color2, children: body }) });
31769
31801
  }
31770
31802
  const { glyph, color } = toneConfig(text.tone);
31771
- return /* @__PURE__ */ jsx45(Box34, { paddingX: spacing.indent, marginTop: 1, children: /* @__PURE__ */ jsxs34(Text35, { color, children: [
31803
+ return /* @__PURE__ */ jsx45(Box34, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs34(Text35, { color, children: [
31772
31804
  glyph.length > 0 ? `${glyph} ` : "",
31773
31805
  text.text
31774
31806
  ] }) });
@@ -34374,7 +34406,7 @@ var HeaderCardImpl = ({
34374
34406
  String(tasksTotal)
34375
34407
  ] })
34376
34408
  ] }),
34377
- isRunning && /* @__PURE__ */ jsx62(Box50, { marginLeft: 2, children: /* @__PURE__ */ jsx62(Spinner, { active: isRunning, color: inkColors.info, label: "live" }) })
34409
+ isRunning && /* @__PURE__ */ jsx62(Box50, { marginLeft: spacing.indent, children: /* @__PURE__ */ jsx62(Spinner, { active: isRunning, color: inkColors.info, label: "live" }) })
34378
34410
  ] }),
34379
34411
  /* @__PURE__ */ jsx62(
34380
34412
  ModelLines,
@@ -35144,7 +35176,7 @@ var TASK_STATUS_COLOR = {
35144
35176
  aborted: inkColors.warning,
35145
35177
  skipped: inkColors.muted
35146
35178
  };
35147
- var SidebarDivider = ({ width }) => /* @__PURE__ */ jsx69(Box56, { paddingX: 1, marginTop: spacing.gutter, children: /* @__PURE__ */ jsx69(Text56, { color: inkColors.rule, children: glyphs.sectionRule.repeat(Math.max(0, width - 2)) }) });
35179
+ var SidebarDivider = ({ width }) => /* @__PURE__ */ jsx69(Box56, { paddingX: spacing.gutter, marginTop: spacing.gutter, children: /* @__PURE__ */ jsx69(Text56, { color: inkColors.rule, children: glyphs.sectionRule.repeat(Math.max(0, width - 2)) }) });
35148
35180
  var truncateName = (name, maxChars) => name.length > maxChars ? `${name.slice(0, Math.max(0, maxChars - 1))}${glyphs.clipEllipsis}` : name;
35149
35181
  var TaskNavList = ({
35150
35182
  tasks,
@@ -39388,10 +39420,10 @@ var projectInput = (ctx) => {
39388
39420
  };
39389
39421
  var runAuthoringSpawn = async (deps, session, unitRoot, log) => {
39390
39422
  try {
39391
- const spawn3 = await deps.provider.generate(session);
39392
- if (!spawn3.ok) {
39393
- if (spawn3.error.code === ErrorCode.Aborted) return Result.error(spawn3.error);
39394
- log.warn(`create-pr: AI authoring failed, falling back to template (${spawn3.error.message})`);
39423
+ const spawn2 = await deps.provider.generate(session);
39424
+ if (!spawn2.ok) {
39425
+ if (spawn2.error.code === ErrorCode.Aborted) return Result.error(spawn2.error);
39426
+ log.warn(`create-pr: AI authoring failed, falling back to template (${spawn2.error.message})`);
39395
39427
  return Result.ok({});
39396
39428
  }
39397
39429
  const validated = await validateSignalsFile(unitRoot, generatePrContentOutputContract);
@@ -39861,7 +39893,7 @@ import { basename as basename5, join as join69 } from "path";
39861
39893
 
39862
39894
  // src/application/ui/tui/prompts/path-picker-prompt.tsx
39863
39895
  import { useEffect as useEffect40, useState as useState45 } from "react";
39864
- import { promises as fs35 } from "fs";
39896
+ import { promises as fs34 } from "fs";
39865
39897
  import { dirname as dirname21, join as join68 } from "path";
39866
39898
  import { homedir as homedir2 } from "os";
39867
39899
  import { Box as Box76, Text as Text72, useInput as useInput28 } from "ink";
@@ -39880,7 +39912,7 @@ var useDirectoryEntries = (cwd, showHidden) => {
39880
39912
  let cancelled = false;
39881
39913
  const load = async () => {
39882
39914
  try {
39883
- const items = await fs35.readdir(cwd, { withFileTypes: true });
39915
+ const items = await fs34.readdir(cwd, { withFileTypes: true });
39884
39916
  if (cancelled) return;
39885
39917
  const filtered = items.filter((d) => showHidden || !d.name.startsWith(".")).filter((d) => d.isDirectory()).map((d) => ({ name: d.name, isDirectory: true })).sort((a, b) => a.name.localeCompare(b.name));
39886
39918
  setEntries(filtered);
@@ -39962,7 +39994,7 @@ var submitTypedPath = async (raw, { setError, setTyping, onSubmit }) => {
39962
39994
  return;
39963
39995
  }
39964
39996
  try {
39965
- const stat = await fs35.stat(expanded);
39997
+ const stat = await fs34.stat(expanded);
39966
39998
  if (!stat.isDirectory()) {
39967
39999
  setError(`${expanded} is not a directory`);
39968
40000
  setTyping(false);
@@ -40037,8 +40069,8 @@ var PathPickerPrompt = ({
40037
40069
  ] }) }),
40038
40070
  /* @__PURE__ */ jsx94(Box76, { paddingX: spacing.indent, children: /* @__PURE__ */ jsx94(Text72, { dimColor: true, children: cwd }) }),
40039
40071
  error !== void 0 && /* @__PURE__ */ jsx94(Box76, { paddingX: spacing.indent, children: /* @__PURE__ */ jsx94(Text72, { color: inkColors.error, children: error }) }),
40040
- /* @__PURE__ */ jsx94(Box76, { flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx94(PathPickerRows, { rows, start, end, cursor }) }),
40041
- typing ? /* @__PURE__ */ jsxs72(Box76, { flexDirection: "column", marginTop: 1, paddingX: spacing.indent, children: [
40072
+ /* @__PURE__ */ jsx94(Box76, { flexDirection: "column", marginTop: spacing.section, children: /* @__PURE__ */ jsx94(PathPickerRows, { rows, start, end, cursor }) }),
40073
+ typing ? /* @__PURE__ */ jsxs72(Box76, { flexDirection: "column", marginTop: spacing.section, paddingX: spacing.indent, children: [
40042
40074
  /* @__PURE__ */ jsx94(Text72, { dimColor: true, children: "Type an absolute path (~/ allowed). Enter validates; esc returns to the picker." }),
40043
40075
  /* @__PURE__ */ jsx94(
40044
40076
  TextPrompt,
@@ -40049,7 +40081,7 @@ var PathPickerPrompt = ({
40049
40081
  onCancel: () => setTyping(false)
40050
40082
  }
40051
40083
  )
40052
- ] }) : /* @__PURE__ */ jsx94(Box76, { paddingX: spacing.indent, marginTop: 1, children: /* @__PURE__ */ jsxs72(Text72, { dimColor: true, children: [
40084
+ ] }) : /* @__PURE__ */ jsx94(Box76, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsxs72(Text72, { dimColor: true, children: [
40053
40085
  "\u21B5 open/select \xB7 \u232B up \xB7 esc cancel \xB7 ~ home \xB7 t type \xB7 . ",
40054
40086
  showHidden ? "hide" : "show",
40055
40087
  " hidden"
@@ -40967,7 +40999,7 @@ var PickProjectView = () => {
40967
40999
  };
40968
41000
 
40969
41001
  // src/application/ui/tui/views/pick-sprint-view.tsx
40970
- import { useEffect as useEffect45, useMemo as useMemo29, useState as useState50 } from "react";
41002
+ import { useMemo as useMemo29, useState as useState50 } from "react";
40971
41003
  import { Box as Box85, Text as Text80, useInput as useInput31 } from "ink";
40972
41004
 
40973
41005
  // src/application/ui/tui/views/pick-sprint-internals/types.ts
@@ -41032,99 +41064,75 @@ var flatten = (groups, includeCreate) => {
41032
41064
  }
41033
41065
  return rows;
41034
41066
  };
41035
- var cursorableRowIndices = (rows) => {
41036
- const indices = [];
41037
- for (let i = 0; i < rows.length; i += 1) {
41038
- const kind = rows[i]?.kind;
41039
- if (kind === "sprint" || kind === "create") indices.push(i);
41040
- }
41041
- return indices;
41042
- };
41043
- var nextCursorableIndex = (rows, from, direction) => {
41044
- const candidates = cursorableRowIndices(rows);
41045
- if (candidates.length === 0) return from;
41046
- if (direction === 1) {
41047
- const next = candidates.find((i) => i > from);
41048
- return next ?? from;
41049
- }
41050
- let prev = from;
41051
- for (const i of candidates) {
41052
- if (i < from) prev = i;
41053
- else break;
41054
- }
41055
- return prev === from && candidates.includes(from) ? from : prev;
41056
- };
41057
- var cursorableNear = (rows, target, direction) => {
41058
- const candidates = cursorableRowIndices(rows);
41059
- if (candidates.length === 0) return target;
41060
- if (candidates.includes(target)) return target;
41061
- const below = candidates.find((i) => i > target);
41062
- let above;
41063
- for (const i of candidates) {
41064
- if (i < target) above = i;
41065
- else break;
41067
+ var CREATE_ROW_ID = "__create__";
41068
+ var cursorableRows = (rows) => rows.filter((r) => r.kind !== "header");
41069
+ var cursorableRowId = (row) => row.kind === "create" ? CREATE_ROW_ID : row.sprint.id;
41070
+ var preferredCursorId = (rows, preferredSprintId) => {
41071
+ if (preferredSprintId !== void 0) {
41072
+ const match = rows.find((r) => r.kind === "sprint" && r.sprint.id === preferredSprintId);
41073
+ if (match !== void 0) return match.sprint.id;
41066
41074
  }
41067
- const preferred = direction === 1 ? below : above;
41068
- const fallback = direction === 1 ? above : below;
41069
- return preferred ?? fallback ?? target;
41075
+ const firstSprint = rows.find((r) => r.kind === "sprint");
41076
+ if (firstSprint !== void 0) return firstSprint.sprint.id;
41077
+ const firstCreate = rows.find((r) => r.kind === "create");
41078
+ return firstCreate !== void 0 ? CREATE_ROW_ID : "";
41070
41079
  };
41071
41080
 
41072
41081
  // src/application/ui/tui/views/pick-sprint-internals/window.ts
41073
41082
  var VERTICAL_CHROME_ROWS = 12;
41074
41083
  var MIN_VISIBLE_ROWS = 8;
41075
- var computeWindow = (totalRows, cursor, visible) => {
41076
- if (totalRows <= visible) return { start: 0, end: totalRows, hiddenAbove: 0, hiddenBelow: 0 };
41077
- const half = Math.floor(visible / 2);
41078
- let start = Math.max(0, cursor - half);
41079
- let end = start + visible;
41080
- if (end > totalRows) {
41081
- end = totalRows;
41082
- start = Math.max(0, end - visible);
41083
- }
41084
- return { start, end, hiddenAbove: start, hiddenBelow: totalRows - end };
41085
- };
41086
41084
 
41087
41085
  // src/application/ui/tui/views/pick-sprint-internals/row-views.tsx
41088
41086
  import { useMemo as useMemo28 } from "react";
41089
41087
  import { Box as Box84, Text as Text79 } from "ink";
41090
41088
  import { jsx as jsx102, jsxs as jsxs80 } from "react/jsx-runtime";
41091
- var RowWindowView = ({
41089
+ var PickerRowList = ({
41092
41090
  rows,
41093
- cursor,
41094
41091
  visibleRows,
41095
- currentSprintId
41092
+ active,
41093
+ initialCursorId,
41094
+ currentSprintId,
41095
+ onSubmit
41096
41096
  }) => {
41097
- const window = useMemo28(() => computeWindow(rows.length, cursor, visibleRows), [rows.length, cursor, visibleRows]);
41098
- const slice = rows.slice(window.start, window.end);
41097
+ const items = useMemo28(() => cursorableRows(rows), [rows]);
41098
+ const { focusedItem } = useListWindow({
41099
+ items,
41100
+ getId: cursorableRowId,
41101
+ visibleRows,
41102
+ active,
41103
+ initialCursorId,
41104
+ onSubmit
41105
+ });
41106
+ const focusedId = focusedItem !== void 0 ? cursorableRowId(focusedItem) : void 0;
41107
+ const focusedRowIndex = useMemo28(() => {
41108
+ if (focusedId === void 0) return 0;
41109
+ const idx = rows.findIndex((row) => row.kind !== "header" && cursorableRowId(row) === focusedId);
41110
+ return idx < 0 ? 0 : idx;
41111
+ }, [rows, focusedId]);
41112
+ const renderWindow = useMemo28(
41113
+ () => computeListWindow(rows.length, focusedRowIndex, visibleRows),
41114
+ [rows.length, focusedRowIndex, visibleRows]
41115
+ );
41116
+ const renderRows = useMemo28(
41117
+ () => rows.slice(renderWindow.start, renderWindow.end),
41118
+ [rows, renderWindow.start, renderWindow.end]
41119
+ );
41099
41120
  return /* @__PURE__ */ jsxs80(Box84, { flexDirection: "column", children: [
41100
- window.hiddenAbove > 0 && /* @__PURE__ */ jsx102(Box84, { paddingX: spacing.indent, children: /* @__PURE__ */ jsxs80(Text79, { dimColor: true, children: [
41101
- "\u25B2 ",
41102
- String(window.hiddenAbove),
41103
- " more above"
41104
- ] }) }),
41105
- slice.map((row, i) => {
41106
- const absoluteIndex = i + window.start;
41107
- if (row.kind === "header") {
41108
- return /* @__PURE__ */ jsx102(HeaderRowView, { row }, `h-${row.groupKey}-${String(absoluteIndex)}`);
41109
- }
41110
- if (row.kind === "create") {
41111
- return /* @__PURE__ */ jsx102(CreateRowView, { focused: absoluteIndex === cursor }, `create-${String(absoluteIndex)}`);
41112
- }
41121
+ /* @__PURE__ */ jsx102(OverflowRow, { direction: "above", count: renderWindow.start }),
41122
+ renderRows.map((row) => {
41123
+ if (row.kind === "header") return /* @__PURE__ */ jsx102(HeaderRowView, { row }, `h-${row.groupKey}`);
41124
+ if (row.kind === "create") return /* @__PURE__ */ jsx102(CreateRowView, { focused: focusedId === cursorableRowId(row) }, "create");
41113
41125
  return /* @__PURE__ */ jsx102(
41114
41126
  SprintRowView,
41115
41127
  {
41116
41128
  sprint: row.sprint,
41117
- focused: absoluteIndex === cursor,
41129
+ focused: focusedId === row.sprint.id,
41118
41130
  isCurrent: currentSprintId === row.sprint.id
41119
41131
  },
41120
41132
  row.sprint.id
41121
41133
  );
41122
41134
  }),
41123
- window.hiddenBelow > 0 && /* @__PURE__ */ jsx102(Box84, { paddingX: spacing.indent, children: /* @__PURE__ */ jsxs80(Text79, { dimColor: true, children: [
41124
- "\u25BC ",
41125
- String(window.hiddenBelow),
41126
- " more below"
41127
- ] }) })
41135
+ /* @__PURE__ */ jsx102(OverflowRow, { direction: "below", count: rows.length - renderWindow.end })
41128
41136
  ] });
41129
41137
  };
41130
41138
  var CreateRowView = ({ focused }) => /* @__PURE__ */ jsxs80(Box84, { flexDirection: "column", paddingX: spacing.indent, children: [
@@ -41186,15 +41194,6 @@ var SprintRowView = ({
41186
41194
 
41187
41195
  // src/application/ui/tui/views/pick-sprint-view.tsx
41188
41196
  import { Fragment as Fragment12, jsx as jsx103, jsxs as jsxs81 } from "react/jsx-runtime";
41189
- var preferredCursorIndex = (rows, preferredSprintId) => {
41190
- if (preferredSprintId !== void 0) {
41191
- const i = rows.findIndex((r) => r.kind === "sprint" && r.sprint.id === preferredSprintId);
41192
- if (i !== -1) return i;
41193
- }
41194
- const firstSprint = rows.findIndex((r) => r.kind === "sprint");
41195
- if (firstSprint !== -1) return firstSprint;
41196
- return cursorableRowIndices(rows)[0] ?? 0;
41197
- };
41198
41197
  var usePickerRows = (deps, selection) => {
41199
41198
  const [scopeAll, setScopeAll] = useState50(true);
41200
41199
  const [hideDone, setHideDone] = useState50(false);
@@ -41229,28 +41228,7 @@ var usePickerRows = (deps, selection) => {
41229
41228
  if (!hideDone || sprintCount > 0) return false;
41230
41229
  return flatten(buildGroups(data, selection.projectId, scopeAll), false).some((r) => r.kind === "sprint");
41231
41230
  }, [hideDone, sprintCount, data, selection.projectId, scopeAll]);
41232
- const initialIdx = useMemo29(() => preferredCursorIndex(rows, selection.sprintId), [rows, selection.sprintId]);
41233
- const [cursor, setCursor] = useState50(initialIdx);
41234
- useEffect45(() => {
41235
- setCursor((c) => {
41236
- if (rows.length === 0) return 0;
41237
- if (c >= rows.length) return Math.max(0, rows.length - 1);
41238
- const focused = rows[c];
41239
- if (focused?.kind !== "sprint" && focused?.kind !== "create") {
41240
- return cursorableRowIndices(rows)[0] ?? c;
41241
- }
41242
- return c;
41243
- });
41244
- }, [rows]);
41245
- useEffect45(() => {
41246
- setCursor(initialIdx);
41247
- }, [initialIdx]);
41248
- const toggleScope = () => {
41249
- const next = !scopeAll;
41250
- setScopeAll(next);
41251
- const nextRows = flatten(buildGroups(visibleData, selection.projectId, next), includeCreate);
41252
- setCursor(preferredCursorIndex(nextRows, selection.sprintId));
41253
- };
41231
+ const toggleScope = () => setScopeAll((v) => !v);
41254
41232
  return {
41255
41233
  state,
41256
41234
  reload,
@@ -41261,24 +41239,9 @@ var usePickerRows = (deps, selection) => {
41261
41239
  scopeAll,
41262
41240
  hideDone,
41263
41241
  setHideDone,
41264
- toggleScope,
41265
- cursor,
41266
- setCursor
41242
+ toggleScope
41267
41243
  };
41268
41244
  };
41269
- var isNavigationInput = (input, key) => key.upArrow || key.downArrow || key.pageUp || key.pageDown || key.home || key.end || input === "j" || input === "k";
41270
- var resolveArrowMove = (rows, cursor, visibleRows, input, key) => {
41271
- if (key.upArrow || input === "k") return nextCursorableIndex(rows, cursor, -1);
41272
- if (key.downArrow || input === "j") return nextCursorableIndex(rows, cursor, 1);
41273
- if (key.pageUp) return cursorableNear(rows, Math.max(0, cursor - visibleRows), -1);
41274
- if (key.pageDown) return cursorableNear(rows, Math.min(rows.length - 1, cursor + visibleRows), 1);
41275
- if (key.home) return cursorableRowIndices(rows)[0] ?? 0;
41276
- if (key.end) {
41277
- const candidates = cursorableRowIndices(rows);
41278
- return candidates[candidates.length - 1] ?? cursor;
41279
- }
41280
- return void 0;
41281
- };
41282
41245
  var PickerBody = ({
41283
41246
  helpOpen,
41284
41247
  state,
@@ -41287,11 +41250,14 @@ var PickerBody = ({
41287
41250
  scopeAll,
41288
41251
  hideDone,
41289
41252
  rows,
41290
- cursor,
41291
41253
  visibleRows,
41254
+ listActive,
41255
+ initialCursorId,
41256
+ listKey,
41292
41257
  currentSprintId,
41293
41258
  currentSprintLabel,
41294
- feedback: feedback2
41259
+ feedback: feedback2,
41260
+ onRowSubmit
41295
41261
  }) => helpOpen ? /* @__PURE__ */ jsx103(HelpOverlay, {}) : state.kind === "loading" || state.kind === "idle" ? /* @__PURE__ */ jsx103(LoadingRow, { label: "Loading sprints\u2026" }) : state.kind === "error" ? /* @__PURE__ */ jsx103(LoadErrorRow, { message: "Failed to load sprints.", color: inkColors.error }) : sprintCount === 0 ? /* @__PURE__ */ jsx103(Box85, { flexDirection: "column", paddingX: spacing.indent, children: hiddenByDoneFilter ? /* @__PURE__ */ jsxs81(Fragment12, { children: [
41296
41262
  /* @__PURE__ */ jsx103(Text80, { children: "All sprints here are done (hidden)." }),
41297
41263
  /* @__PURE__ */ jsx103(Text80, { dimColor: true, children: "Press f to show them, or + to create a new one." })
@@ -41316,7 +41282,18 @@ var PickerBody = ({
41316
41282
  /* @__PURE__ */ jsx103(Text80, { color: inkColors.primary, bold: true, children: currentSprintLabel })
41317
41283
  ] }) : /* @__PURE__ */ jsx103(Text80, { children: "press \u21B5 to confirm" })
41318
41284
  ] }) }),
41319
- /* @__PURE__ */ jsx103(RowWindowView, { rows, cursor, visibleRows, currentSprintId }),
41285
+ /* @__PURE__ */ jsx103(
41286
+ PickerRowList,
41287
+ {
41288
+ rows,
41289
+ visibleRows,
41290
+ active: listActive,
41291
+ initialCursorId,
41292
+ currentSprintId,
41293
+ onSubmit: onRowSubmit
41294
+ },
41295
+ listKey
41296
+ ),
41320
41297
  /* @__PURE__ */ jsx103(Box85, { marginTop: spacing.section, paddingX: spacing.indent, children: /* @__PURE__ */ jsxs81(Text80, { dimColor: true, children: [
41321
41298
  glyphs.bullet,
41322
41299
  " \u21B5 use the highlighted sprint ",
@@ -41330,67 +41307,15 @@ var PickerBody = ({
41330
41307
  glyphs.bullet,
41331
41308
  " + create a new one"
41332
41309
  ] }) }),
41333
- feedback2 !== void 0 && /* @__PURE__ */ jsx103(Box85, { paddingX: spacing.indent, marginTop: 1, children: /* @__PURE__ */ jsx103(Text80, { color: inkColors.error, children: feedback2 }) })
41310
+ feedback2 !== void 0 && /* @__PURE__ */ jsx103(Box85, { paddingX: spacing.indent, marginTop: spacing.section, children: /* @__PURE__ */ jsx103(Text80, { color: inkColors.error, children: feedback2 }) })
41334
41311
  ] });
41335
- var usePickerInput = ({
41336
- modalOpen,
41337
- rows,
41338
- cursor,
41339
- visibleRows,
41340
- setCursor,
41341
- pick,
41342
- launchCreateSprint: launchCreateSprint2,
41343
- toggleScope,
41344
- setHideDone,
41345
- reload
41346
- }) => {
41347
- useInput31((input, key) => {
41348
- if (modalOpen) return;
41349
- if (isNavigationInput(input, key)) {
41350
- setCursor((c) => resolveArrowMove(rows, c, visibleRows, input, key) ?? c);
41351
- return;
41352
- }
41353
- if (key.return) {
41354
- const row = rows[cursor];
41355
- if (row?.kind === "sprint") pick(row.sprint);
41356
- else if (row?.kind === "create") void launchCreateSprint2();
41357
- return;
41358
- }
41359
- if (input === "t") {
41360
- toggleScope();
41361
- return;
41362
- }
41363
- if (input === "f") {
41364
- setHideDone((v) => !v);
41365
- return;
41366
- }
41367
- if (input === "+" || input === "c") {
41368
- void launchCreateSprint2();
41369
- return;
41370
- }
41371
- if (input === "r") reload();
41372
- });
41373
- };
41374
41312
  var PickSprintView = () => {
41375
41313
  const deps = useDeps();
41376
41314
  const router = useRouter();
41377
41315
  const selection = useSelection();
41378
41316
  const ui = useUiState();
41379
41317
  const [feedback2, setFeedback] = useState50(void 0);
41380
- const {
41381
- state,
41382
- reload,
41383
- data,
41384
- rows,
41385
- sprintCount,
41386
- hiddenByDoneFilter,
41387
- scopeAll,
41388
- hideDone,
41389
- setHideDone,
41390
- toggleScope,
41391
- cursor,
41392
- setCursor
41393
- } = usePickerRows(deps, selection);
41318
+ const { state, reload, data, rows, sprintCount, hiddenByDoneFilter, scopeAll, hideDone, setHideDone, toggleScope } = usePickerRows(deps, selection);
41394
41319
  useViewHints([
41395
41320
  { keys: "\u2191/\u2193/j/k", label: "move" },
41396
41321
  { keys: "\u21B5", label: "use sprint" },
@@ -41417,18 +41342,28 @@ var PickSprintView = () => {
41417
41342
  onError: setFeedback,
41418
41343
  noProjectMessage: `${glyphs.cross} select a project first`
41419
41344
  });
41420
- usePickerInput({
41421
- modalOpen: ui.modalOpen,
41422
- rows,
41423
- cursor,
41424
- visibleRows,
41425
- setCursor,
41426
- pick,
41427
- launchCreateSprint: launchCreateSprint2,
41428
- toggleScope,
41429
- setHideDone,
41430
- reload
41345
+ const onRowSubmit = (row) => {
41346
+ if (row.kind === "sprint") pick(row.sprint);
41347
+ else void launchCreateSprint2();
41348
+ };
41349
+ useInput31((input) => {
41350
+ if (ui.modalOpen) return;
41351
+ if (input === "t") {
41352
+ toggleScope();
41353
+ return;
41354
+ }
41355
+ if (input === "f") {
41356
+ setHideDone((v) => !v);
41357
+ return;
41358
+ }
41359
+ if (input === "+" || input === "c") {
41360
+ void launchCreateSprint2();
41361
+ return;
41362
+ }
41363
+ if (input === "r") reload();
41431
41364
  });
41365
+ const initialCursorId = useMemo29(() => preferredCursorId(rows, selection.sprintId), [rows, selection.sprintId]);
41366
+ const listKey = `${String(scopeAll)}-${selection.sprintId ?? "unseeded"}`;
41432
41367
  return /* @__PURE__ */ jsx103(ViewShell, { title: "Pick a sprint", subtitle: "Switch sprint (and project) in one step", suppressScrollArrows: true, children: /* @__PURE__ */ jsx103(
41433
41368
  PickerBody,
41434
41369
  {
@@ -41439,11 +41374,14 @@ var PickSprintView = () => {
41439
41374
  scopeAll,
41440
41375
  hideDone,
41441
41376
  rows,
41442
- cursor,
41443
41377
  visibleRows,
41378
+ listActive: !ui.modalOpen,
41379
+ initialCursorId,
41380
+ listKey,
41444
41381
  currentSprintId: selection.sprintId,
41445
41382
  currentSprintLabel: selection.sprintLabel,
41446
- feedback: feedback2
41383
+ feedback: feedback2,
41384
+ onRowSubmit
41447
41385
  }
41448
41386
  ) });
41449
41387
  };
@@ -41498,11 +41436,10 @@ var renderView = (entry) => {
41498
41436
  };
41499
41437
 
41500
41438
  // src/application/ui/tui/runtime/use-global-keys.ts
41501
- import { useEffect as useEffect46, useMemo as useMemo30, useRef as useRef18 } from "react";
41439
+ import { useEffect as useEffect45, useMemo as useMemo30, useRef as useRef18 } from "react";
41502
41440
  import { useApp, useInput as useInput32 } from "ink";
41503
41441
 
41504
41442
  // src/integration/io/clipboard.ts
41505
- import { spawn as nodeSpawn2 } from "child_process";
41506
41443
  var resolveHelpers = ({ platform, env }) => {
41507
41444
  if (platform === "darwin") return [{ cmd: "pbcopy", args: [] }];
41508
41445
  if (platform === "win32") return [{ cmd: "clip.exe", args: [] }];
@@ -41521,10 +41458,10 @@ var resolveHelpers = ({ platform, env }) => {
41521
41458
  return [];
41522
41459
  };
41523
41460
  var SPAWN_FAILED = "spawn-failed";
41524
- var runHelper = (spawn3, helper, text) => new Promise((resolve) => {
41461
+ var runHelper = (spawn2, helper, text) => new Promise((resolve) => {
41525
41462
  let child;
41526
41463
  try {
41527
- child = spawn3(helper.cmd, helper.args, { stdio: ["pipe", "pipe", "pipe"] });
41464
+ child = spawn2(helper.cmd, helper.args, { stdio: ["pipe", "pipe", "pipe"] });
41528
41465
  } catch (cause) {
41529
41466
  resolve(
41530
41467
  Result.error({
@@ -41568,7 +41505,7 @@ var runHelper = (spawn3, helper, text) => new Promise((resolve) => {
41568
41505
  }
41569
41506
  });
41570
41507
  var createCopyToClipboard = (opts = {}) => {
41571
- const spawn3 = opts.spawn ?? nodeSpawn2;
41508
+ const spawn2 = opts.spawn ?? crossPlatformSpawn;
41572
41509
  const platform = opts.platform ?? process.platform;
41573
41510
  const env = opts.env ?? process.env;
41574
41511
  const helpers = resolveHelpers({ platform, env });
@@ -41581,7 +41518,7 @@ var createCopyToClipboard = (opts = {}) => {
41581
41518
  }
41582
41519
  let lastError;
41583
41520
  for (const helper of helpers) {
41584
- const result = await runHelper(spawn3, helper, text);
41521
+ const result = await runHelper(spawn2, helper, text);
41585
41522
  if (result.ok) return result;
41586
41523
  lastError = result.error;
41587
41524
  if (result.error.code !== "no-helper") return result;
@@ -41610,7 +41547,7 @@ var useGlobalKeys = (opts = {}) => {
41610
41547
  [opts.copyToClipboard]
41611
41548
  );
41612
41549
  const clearTimerRef = useRef18(void 0);
41613
- useEffect46(
41550
+ useEffect45(
41614
41551
  () => () => {
41615
41552
  if (clearTimerRef.current !== void 0) clearTimeout(clearTimerRef.current);
41616
41553
  },
@@ -41779,7 +41716,7 @@ var scheduleClear = (eventBus, ref3) => {
41779
41716
  };
41780
41717
 
41781
41718
  // src/application/ui/tui/components/memory-pressure-banner.tsx
41782
- import { useEffect as useEffect47, useState as useState51 } from "react";
41719
+ import { useEffect as useEffect46, useState as useState51 } from "react";
41783
41720
  import { Box as Box86, Text as Text81 } from "ink";
41784
41721
  import { jsxs as jsxs82 } from "react/jsx-runtime";
41785
41722
  var formatMb = (bytes) => `${(bytes / 1048576).toFixed(0)} MB`;
@@ -41787,7 +41724,7 @@ var formatPercent = (ratio) => `${Math.round(ratio * 100)}%`;
41787
41724
  var MemoryPressureBanner = () => {
41788
41725
  const deps = useDeps();
41789
41726
  const [latest, setLatest] = useState51(void 0);
41790
- useEffect47(() => {
41727
+ useEffect46(() => {
41791
41728
  return deps.eventBus.subscribe((event) => {
41792
41729
  if (event.type === "memory-pressure") setLatest(event);
41793
41730
  });
@@ -41810,13 +41747,13 @@ var MemoryPressureBanner = () => {
41810
41747
  };
41811
41748
 
41812
41749
  // src/application/ui/tui/components/chain-log-degraded-banner.tsx
41813
- import { useEffect as useEffect48, useState as useState52 } from "react";
41750
+ import { useEffect as useEffect47, useState as useState52 } from "react";
41814
41751
  import { Box as Box87, Text as Text82 } from "ink";
41815
41752
  import { jsx as jsx105, jsxs as jsxs83 } from "react/jsx-runtime";
41816
41753
  var ChainLogDegradedBanner = () => {
41817
41754
  const deps = useDeps();
41818
41755
  const [degraded, setDegraded] = useState52(false);
41819
- useEffect48(() => {
41756
+ useEffect47(() => {
41820
41757
  return deps.eventBus.subscribe((event) => {
41821
41758
  if (event.type === "chain-log-degraded") setDegraded(true);
41822
41759
  });
@@ -41829,9 +41766,9 @@ var ChainLogDegradedBanner = () => {
41829
41766
  };
41830
41767
 
41831
41768
  // src/application/ui/tui/components/progress-overlay.tsx
41832
- import { promises as fs36 } from "fs";
41769
+ import { promises as fs35 } from "fs";
41833
41770
  import { join as join71 } from "path";
41834
- import { useEffect as useEffect49, useState as useState53 } from "react";
41771
+ import { useEffect as useEffect48, useState as useState53 } from "react";
41835
41772
  import { Box as Box88, Text as Text83, useInput as useInput33 } from "ink";
41836
41773
  import { Fragment as Fragment13, jsx as jsx106, jsxs as jsxs84 } from "react/jsx-runtime";
41837
41774
  var CHROME_ROWS4 = 10;
@@ -41842,7 +41779,7 @@ var formatAgo = (modifiedAtMs, now) => {
41842
41779
  };
41843
41780
  var useProgressFile = (sprintId, dataRoot) => {
41844
41781
  const [state, setState] = useState53({ kind: "loading" });
41845
- useEffect49(() => {
41782
+ useEffect48(() => {
41846
41783
  let cancelled = false;
41847
41784
  if (sprintId === void 0) {
41848
41785
  setState({ kind: "missing" });
@@ -41857,7 +41794,7 @@ var useProgressFile = (sprintId, dataRoot) => {
41857
41794
  return;
41858
41795
  }
41859
41796
  const progressPath = join71(dir, "progress.md");
41860
- const [stat, content] = await Promise.all([fs36.stat(progressPath), fs36.readFile(progressPath, "utf8")]);
41797
+ const [stat, content] = await Promise.all([fs35.stat(progressPath), fs35.readFile(progressPath, "utf8")]);
41861
41798
  if (cancelled) return;
41862
41799
  const modifiedAtMs = stat.mtimeMs;
41863
41800
  if (content.trim().length === 0) {
@@ -41887,7 +41824,7 @@ var useProgressFile = (sprintId, dataRoot) => {
41887
41824
  var useProgressScroll = (state, bodyRows) => {
41888
41825
  const [offset, setOffset] = useState53(0);
41889
41826
  const lineCount = state.kind === "ok" ? state.lines.length : 0;
41890
- useEffect49(() => {
41827
+ useEffect48(() => {
41891
41828
  setOffset(0);
41892
41829
  }, [lineCount]);
41893
41830
  const maxOffset = Math.max(0, lineCount - bodyRows);
@@ -42084,7 +42021,7 @@ import { useState as useState55 } from "react";
42084
42021
  import { useApp as useApp2 } from "ink";
42085
42022
 
42086
42023
  // src/application/ui/tui/migration/migration-gate.tsx
42087
- import { useEffect as useEffect50, useRef as useRef19, useState as useState54 } from "react";
42024
+ import { useEffect as useEffect49, useRef as useRef19, useState as useState54 } from "react";
42088
42025
  import { Box as Box90, Text as Text84, useInput as useInput34 } from "ink";
42089
42026
 
42090
42027
  // src/application/ui/tui/migration/learnings-backfill-adapter.ts
@@ -42132,7 +42069,7 @@ var MigrationGate = (props) => {
42132
42069
  const { engine, dataRoot, stateRoot, appVersion, now, writeFile: writeFile3, onResolve, onQuit } = props;
42133
42070
  const [state, setState] = useState54({ kind: "scanning" });
42134
42071
  const scannedRef = useRef19(false);
42135
- useEffect50(() => {
42072
+ useEffect49(() => {
42136
42073
  if (scannedRef.current) return;
42137
42074
  scannedRef.current = true;
42138
42075
  let cancelled = false;
@@ -42378,7 +42315,7 @@ var MigrationRoute = ({ gate, app, onResolved }) => {
42378
42315
  };
42379
42316
 
42380
42317
  // src/integration/persistence/data-migration/version-marker.ts
42381
- import { promises as fs37 } from "fs";
42318
+ import { promises as fs36 } from "fs";
42382
42319
  import { join as join72 } from "path";
42383
42320
  import { z as z50 } from "zod";
42384
42321
  var CURRENT_DATA_VERSION = 2;
@@ -42391,7 +42328,7 @@ var versionMarkerPath = (dataRoot) => join72(String(dataRoot), DATA_VERSION_FILE
42391
42328
  var readDataVersion = async (dataRoot) => {
42392
42329
  let bytes;
42393
42330
  try {
42394
- bytes = await fs37.readFile(versionMarkerPath(dataRoot), "utf8");
42331
+ bytes = await fs36.readFile(versionMarkerPath(dataRoot), "utf8");
42395
42332
  } catch {
42396
42333
  return { dataVersion: 1, lastWrittenByAppVersion: "" };
42397
42334
  }
@@ -42413,8 +42350,8 @@ var writeDataVersion = async (dataRoot, marker) => {
42413
42350
  const body = `${JSON.stringify(marker, null, 2)}
42414
42351
  `;
42415
42352
  try {
42416
- await fs37.mkdir(String(dataRoot), { recursive: true });
42417
- await fs37.writeFile(path2, body, "utf8");
42353
+ await fs36.mkdir(String(dataRoot), { recursive: true });
42354
+ await fs36.writeFile(path2, body, "utf8");
42418
42355
  return Result.ok(void 0);
42419
42356
  } catch (cause) {
42420
42357
  return Result.error(new StorageError({ subCode: "io", message: `marker write failed: ${path2}`, path: path2, cause }));
@@ -42426,7 +42363,7 @@ var needsMigration = async (dataRoot) => {
42426
42363
  };
42427
42364
 
42428
42365
  // src/integration/persistence/data-migration/dry-run.ts
42429
- import { promises as fs38 } from "fs";
42366
+ import { promises as fs37 } from "fs";
42430
42367
  import { join as join73 } from "path";
42431
42368
  var PROJECTS_DIR = "projects";
42432
42369
  var SPRINTS_DIR = "sprints";
@@ -42445,7 +42382,7 @@ var dryRun = async (dataRoot) => {
42445
42382
  var checkBackupTargetWritable = async (dataRoot, acc) => {
42446
42383
  const parent = join73(String(dataRoot), "..");
42447
42384
  try {
42448
- await fs38.access(parent, fs38.constants.W_OK);
42385
+ await fs37.access(parent, fs37.constants.W_OK);
42449
42386
  } catch {
42450
42387
  acc.problems.push({
42451
42388
  name: parent,
@@ -42563,7 +42500,7 @@ var planRename = async (parent, fromName, toName, id, slug, kind, acc) => {
42563
42500
  };
42564
42501
  var pathPresent = async (path2) => {
42565
42502
  try {
42566
- await fs38.stat(path2);
42503
+ await fs37.stat(path2);
42567
42504
  return true;
42568
42505
  } catch {
42569
42506
  return false;
@@ -42571,7 +42508,7 @@ var pathPresent = async (path2) => {
42571
42508
  };
42572
42509
  var isDirectory = async (path2) => {
42573
42510
  try {
42574
- return (await fs38.stat(path2)).isDirectory();
42511
+ return (await fs37.stat(path2)).isDirectory();
42575
42512
  } catch {
42576
42513
  return false;
42577
42514
  }
@@ -42594,7 +42531,7 @@ var readProjectSlugForId = async (dataRoot, projectId) => {
42594
42531
  var readSlugField = async (filePath) => {
42595
42532
  let bytes;
42596
42533
  try {
42597
- bytes = await fs38.readFile(filePath, "utf8");
42534
+ bytes = await fs37.readFile(filePath, "utf8");
42598
42535
  } catch {
42599
42536
  return void 0;
42600
42537
  }
@@ -42611,11 +42548,11 @@ var readSlugField = async (filePath) => {
42611
42548
  };
42612
42549
 
42613
42550
  // src/integration/persistence/data-migration/apply.ts
42614
- import { promises as fs41 } from "fs";
42551
+ import { promises as fs40 } from "fs";
42615
42552
  import { join as join76 } from "path";
42616
42553
 
42617
42554
  // src/integration/persistence/data-migration/backup.ts
42618
- import { promises as fs39 } from "fs";
42555
+ import { promises as fs38 } from "fs";
42619
42556
  import { basename as basename7, dirname as dirname22, join as join74 } from "path";
42620
42557
  var BACKUP_DIR_PREFIX = "data.backup-";
42621
42558
  var backupDirName = (fromVersion, timestamp) => `${BACKUP_DIR_PREFIX}v${String(fromVersion)}-${sanitizeTimestamp(timestamp)}`;
@@ -42623,7 +42560,7 @@ var backupDataDir = async (dataRoot, fromVersion, timestamp) => {
42623
42560
  const src = String(dataRoot);
42624
42561
  const dest = join74(dirname22(src), backupDirName(fromVersion, timestamp));
42625
42562
  try {
42626
- await fs39.cp(src, dest, {
42563
+ await fs38.cp(src, dest, {
42627
42564
  recursive: true,
42628
42565
  // Follow symlinks so the backup captures their real CONTENT, not a dangling node. The default
42629
42566
  // (`dereference: false`) copies a symlink as a link, which would leave the backup incomplete for
@@ -42645,7 +42582,7 @@ var backupDataDir = async (dataRoot, fromVersion, timestamp) => {
42645
42582
  );
42646
42583
  }
42647
42584
  try {
42648
- const stat = await fs39.stat(dest);
42585
+ const stat = await fs38.stat(dest);
42649
42586
  if (!stat.isDirectory()) {
42650
42587
  return Result.error(
42651
42588
  new StorageError({ subCode: "io", message: `backup verify failed: ${dest} is not a directory`, path: dest })
@@ -42667,8 +42604,8 @@ var backupDataDir = async (dataRoot, fromVersion, timestamp) => {
42667
42604
  };
42668
42605
  var verifyEntryCount = async (src, dest) => {
42669
42606
  try {
42670
- const srcEntries = (await fs39.readdir(src)).filter((name) => !name.startsWith(BACKUP_DIR_PREFIX));
42671
- const destEntries = await fs39.readdir(dest);
42607
+ const srcEntries = (await fs38.readdir(src)).filter((name) => !name.startsWith(BACKUP_DIR_PREFIX));
42608
+ const destEntries = await fs38.readdir(dest);
42672
42609
  if (destEntries.length < srcEntries.length) {
42673
42610
  return Result.error(
42674
42611
  new StorageError({
@@ -42688,10 +42625,10 @@ var verifyEntryCount = async (src, dest) => {
42688
42625
  var sanitizeTimestamp = (timestamp) => timestamp.replace(/:/g, "-");
42689
42626
 
42690
42627
  // src/integration/persistence/data-migration/lock-guard.ts
42691
- import { promises as fs40 } from "fs";
42628
+ import { promises as fs39 } from "fs";
42692
42629
  import { join as join75 } from "path";
42693
42630
  var LOCKS_SUBDIR2 = "locks";
42694
- var HELD_WITHIN_MS = 3e4;
42631
+ var HELD_WITHIN_MS = DEFAULT_STALE_AFTER_MS;
42695
42632
  var anyLockHeld = async (stateRoot) => {
42696
42633
  const locksDir = join75(String(stateRoot), LOCKS_SUBDIR2);
42697
42634
  const entries = await listDir(locksDir);
@@ -42701,7 +42638,7 @@ var anyLockHeld = async (stateRoot) => {
42701
42638
  if (!name.endsWith(".lock")) continue;
42702
42639
  let mtimeMs;
42703
42640
  try {
42704
- ({ mtimeMs } = await fs40.lstat(join75(locksDir, name)));
42641
+ ({ mtimeMs } = await fs39.lstat(join75(locksDir, name)));
42705
42642
  } catch {
42706
42643
  continue;
42707
42644
  }
@@ -42794,7 +42731,7 @@ var mergeOne = async (merge, ctx) => {
42794
42731
  };
42795
42732
  var readTextOrEmpty = async (path2) => {
42796
42733
  try {
42797
- return await fs41.readFile(path2, "utf8");
42734
+ return await fs40.readFile(path2, "utf8");
42798
42735
  } catch {
42799
42736
  return "";
42800
42737
  }
@@ -42812,7 +42749,7 @@ var backfillLearningsMd = async (dataRoot, ctx) => {
42812
42749
  if (await pathPresent2(mdPath)) continue;
42813
42750
  let body;
42814
42751
  try {
42815
- body = await fs41.readFile(ledgerPath, "utf8");
42752
+ body = await fs40.readFile(ledgerPath, "utf8");
42816
42753
  } catch {
42817
42754
  continue;
42818
42755
  }
@@ -42825,7 +42762,7 @@ var backfillLearningsMd = async (dataRoot, ctx) => {
42825
42762
  };
42826
42763
  var ledgerExceedsCeiling = async (ledgerPath) => {
42827
42764
  try {
42828
- const { size } = await fs41.stat(ledgerPath);
42765
+ const { size } = await fs40.stat(ledgerPath);
42829
42766
  return size > MIRROR_BACKFILL_CEILING_BYTES;
42830
42767
  } catch {
42831
42768
  return false;
@@ -42833,7 +42770,7 @@ var ledgerExceedsCeiling = async (ledgerPath) => {
42833
42770
  };
42834
42771
  var pathPresent2 = async (path2) => {
42835
42772
  try {
42836
- await fs41.stat(path2);
42773
+ await fs40.stat(path2);
42837
42774
  return true;
42838
42775
  } catch {
42839
42776
  return false;
@@ -42879,7 +42816,7 @@ var resolveInitialState = ({
42879
42816
  };
42880
42817
 
42881
42818
  // src/integration/persistence/selection/last-selection-store.ts
42882
- import { promises as fs42 } from "fs";
42819
+ import { promises as fs41 } from "fs";
42883
42820
  import { join as join77 } from "path";
42884
42821
  var FILE_NAME = "last-selection.json";
42885
42822
  var createLastSelectionStore = (stateRoot) => {
@@ -42887,7 +42824,7 @@ var createLastSelectionStore = (stateRoot) => {
42887
42824
  return {
42888
42825
  async read() {
42889
42826
  try {
42890
- const raw = await fs42.readFile(path2, "utf8");
42827
+ const raw = await fs41.readFile(path2, "utf8");
42891
42828
  const parsed = JSON.parse(raw);
42892
42829
  if (typeof parsed !== "object" || parsed === null) return void 0;
42893
42830
  const rec = parsed;
@@ -42905,10 +42842,10 @@ var createLastSelectionStore = (stateRoot) => {
42905
42842
  async write(value) {
42906
42843
  try {
42907
42844
  if (value === void 0) {
42908
- await fs42.rm(path2, { force: true });
42845
+ await fs41.rm(path2, { force: true });
42909
42846
  return;
42910
42847
  }
42911
- await fs42.writeFile(path2, JSON.stringify(value, null, 2), "utf8");
42848
+ await fs41.writeFile(path2, JSON.stringify(value, null, 2), "utf8");
42912
42849
  } catch {
42913
42850
  }
42914
42851
  }
@@ -43007,13 +42944,13 @@ var startHeapWatchdog = (deps) => {
43007
42944
  };
43008
42945
 
43009
42946
  // src/integration/observability/heap-snapshot.ts
43010
- import * as fs43 from "fs";
42947
+ import * as fs42 from "fs";
43011
42948
  import * as path from "path";
43012
42949
  import * as v82 from "v8";
43013
42950
  var snapshotFilename = (timestamp) => `heap-${timestamp.replace(/[:.]/g, "-")}.heapsnapshot`;
43014
42951
  var writeHeapSnapshotToDir = (dir, clock) => {
43015
42952
  try {
43016
- fs43.mkdirSync(dir, { recursive: true });
42953
+ fs42.mkdirSync(dir, { recursive: true });
43017
42954
  const timestamp = clock?.() ?? (/* @__PURE__ */ new Date()).toISOString();
43018
42955
  const fullPath = path.join(dir, snapshotFilename(timestamp));
43019
42956
  v82.writeHeapSnapshot(fullPath);
@@ -44534,11 +44471,11 @@ var formatTaskLine = (t) => {
44534
44471
  };
44535
44472
 
44536
44473
  // src/application/ui/cli/commands/runs.ts
44537
- import { promises as fs45 } from "fs";
44474
+ import { promises as fs44 } from "fs";
44538
44475
  import { createInterface as createInterface2 } from "readline";
44539
44476
 
44540
44477
  // src/integration/ai/runs/_engine/run-enumeration.ts
44541
- import { promises as fs44 } from "fs";
44478
+ import { promises as fs43 } from "fs";
44542
44479
  import { join as join80 } from "path";
44543
44480
  var parseRunTimestamp = (runDirName) => {
44544
44481
  const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z-/.exec(runDirName);
@@ -44639,7 +44576,7 @@ var listRuns = async (runsRoot) => {
44639
44576
  const root = String(runsRoot);
44640
44577
  let flowDirs;
44641
44578
  try {
44642
- flowDirs = await fs44.readdir(root, { withFileTypes: true });
44579
+ flowDirs = await fs43.readdir(root, { withFileTypes: true });
44643
44580
  } catch (cause) {
44644
44581
  if (isErrnoException2(cause) && cause.code === "ENOENT") return Result.ok([]);
44645
44582
  return Result.error(
@@ -44656,7 +44593,7 @@ var listRuns = async (runsRoot) => {
44656
44593
  const flowPath = join80(root, flowDir.name);
44657
44594
  let runDirs;
44658
44595
  try {
44659
- runDirs = await fs44.readdir(flowPath, { withFileTypes: true });
44596
+ runDirs = await fs43.readdir(flowPath, { withFileTypes: true });
44660
44597
  } catch (cause) {
44661
44598
  if (isErrnoException2(cause) && cause.code === "ENOENT") continue;
44662
44599
  return Result.error(
@@ -44701,7 +44638,7 @@ var computeDirSize = async (dir) => {
44701
44638
  let total = 0;
44702
44639
  let entries;
44703
44640
  try {
44704
- entries = await fs44.readdir(dir, { withFileTypes: true });
44641
+ entries = await fs43.readdir(dir, { withFileTypes: true });
44705
44642
  } catch {
44706
44643
  return 0;
44707
44644
  }
@@ -44712,7 +44649,7 @@ var computeDirSize = async (dir) => {
44712
44649
  continue;
44713
44650
  }
44714
44651
  try {
44715
- const stat = await fs44.lstat(entryPath);
44652
+ const stat = await fs43.lstat(entryPath);
44716
44653
  if (stat.isFile()) total += stat.size;
44717
44654
  } catch {
44718
44655
  }
@@ -45033,7 +44970,7 @@ var performPrune = async (candidates) => {
45033
44970
  let freedCount = 0;
45034
44971
  for (const candidate of candidates) {
45035
44972
  try {
45036
- await fs45.rm(String(candidate.path), { recursive: true, force: false });
44973
+ await fs44.rm(String(candidate.path), { recursive: true, force: false });
45037
44974
  freedBytes += candidate.sizeBytes;
45038
44975
  freedCount += 1;
45039
44976
  } catch (cause) {