ralphctl 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/application/ui/tui/launch.ts
7
- import React65 from "react";
7
+ import React66 from "react";
8
8
 
9
9
  // src/application/bootstrap/storage-paths.ts
10
10
  import { promises as fs } from "fs";
@@ -2667,24 +2667,16 @@ var buildCodexArgs = (session, opts) => {
2667
2667
  const perms = sandboxFor(session.permissions);
2668
2668
  if (!perms.ok) return Result.error(perms.error);
2669
2669
  const args = ["exec"];
2670
- if (session.resume !== void 0) {
2670
+ const isResume = session.resume !== void 0;
2671
+ if (isResume) {
2671
2672
  args.push("resume", String(session.resume));
2672
2673
  }
2673
- args.push(
2674
- "--ephemeral",
2675
- "--skip-git-repo-check",
2676
- "-o",
2677
- opts.outputFile,
2678
- "--json",
2679
- "-m",
2680
- session.model,
2681
- "-C",
2682
- String(session.cwd),
2683
- "-s",
2684
- perms.value.sandbox
2685
- );
2686
- for (const root of session.additionalRoots ?? []) {
2687
- args.push("--add-dir", String(root));
2674
+ args.push("--ephemeral", "--skip-git-repo-check", "-o", opts.outputFile, "--json", "-m", session.model);
2675
+ if (!isResume) {
2676
+ args.push("-C", String(session.cwd), "-s", perms.value.sandbox);
2677
+ for (const root of session.additionalRoots ?? []) {
2678
+ args.push("--add-dir", String(root));
2679
+ }
2688
2680
  }
2689
2681
  if (opts.reasoningEffort !== void 0) {
2690
2682
  args.push("-c", `model_reasoning_effort=${opts.reasoningEffort}`);
@@ -2862,6 +2854,18 @@ var spawnAttempt2 = async (input) => {
2862
2854
  const signals = parseHarnessSignals(body, IsoTimestamp.now());
2863
2855
  const wrote = await writeJsonAtomic(String(session.signalsFile), signals);
2864
2856
  if (!wrote.ok) return { kind: "error", error: wrote.error };
2857
+ if (session.bodyFile !== void 0) {
2858
+ const bodyWrote = await writeTextAtomic(String(session.bodyFile), body);
2859
+ if (!bodyWrote.ok) {
2860
+ deps.eventBus.publish({
2861
+ type: "log",
2862
+ level: "warn",
2863
+ message: `codex-provider: failed to write body file \u2014 diagnostic capture skipped`,
2864
+ meta: { bodyFile: String(session.bodyFile), error: bodyWrote.error.message },
2865
+ at: IsoTimestamp.now()
2866
+ });
2867
+ }
2868
+ }
2865
2869
  return {
2866
2870
  kind: "success",
2867
2871
  output: {
@@ -3958,11 +3962,7 @@ var defaultTemplatesDir = () => TEMPLATES_DIR;
3958
3962
  var isNodeErrnoCode2 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
3959
3963
 
3960
3964
  // src/integration/ai/readiness/claude/probe.ts
3961
- import { promises as fs8 } from "fs";
3962
- import { basename, join as join7 } from "path";
3963
-
3964
- // src/domain/value/kebab-case.ts
3965
- var toKebabCase = (input) => input.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
3965
+ import { join as join8 } from "path";
3966
3966
 
3967
3967
  // src/domain/value/error/probe-error.ts
3968
3968
  var ProbeError = class extends Error {
@@ -3983,66 +3983,22 @@ var ProbeError = class extends Error {
3983
3983
  }
3984
3984
  };
3985
3985
 
3986
- // src/integration/ai/readiness/_engine/state.ts
3987
- var unknownState = { kind: "unknown" };
3988
- var absentState = (evaluatedAt) => ({ kind: "absent", evaluatedAt });
3989
- var presentState = (evaluatedAt, artifacts) => ({
3990
- kind: "present",
3991
- evaluatedAt,
3992
- artifacts
3993
- });
3986
+ // src/integration/ai/readiness/_engine/probe-fs.ts
3987
+ import { promises as fs8 } from "fs";
3988
+ import { basename, join as join7 } from "path";
3994
3989
 
3995
- // src/integration/ai/readiness/_engine/predicates.ts
3996
- var isPresent = (state) => state.kind === "present";
3997
- var hasAnyClaudeArtifact = (a) => a.claudeMd !== void 0 || a.agentsMd !== void 0 || a.settings !== void 0 || a.settingsLocal !== void 0 || a.mcpConfig !== void 0 || a.skills.length > 0 || a.commands.length > 0 || a.agents.length > 0 || a.hooks.length > 0;
3998
- var hasAnyCopilotArtifact = (a) => a.copilotInstructions !== void 0;
3990
+ // src/domain/value/kebab-case.ts
3991
+ var toKebabCase = (input) => input.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
3999
3992
 
4000
- // src/integration/ai/readiness/claude/probe.ts
4001
- var claudeProbe = {
4002
- tool: "claude-code",
4003
- async evaluate(repository, now) {
4004
- const root = repository.path;
4005
- const claudeMd = await probeFile(join7(root, "CLAUDE.md"));
4006
- if (!claudeMd.ok) return Result.error(claudeMd.error);
4007
- const agentsMd = await probeFile(join7(root, "AGENTS.md"));
4008
- if (!agentsMd.ok) return Result.error(agentsMd.error);
4009
- const settings = await probeFile(join7(root, ".claude/settings.json"));
4010
- if (!settings.ok) return Result.error(settings.error);
4011
- const settingsLocal = await probeFile(join7(root, ".claude/settings.local.json"));
4012
- if (!settingsLocal.ok) return Result.error(settingsLocal.error);
4013
- const mcpConfig = await probeFile(join7(root, ".mcp.json"));
4014
- if (!mcpConfig.ok) return Result.error(mcpConfig.error);
4015
- const skills = await probeNamedDirCollection(join7(root, ".claude/skills"), "SKILL.md");
4016
- if (!skills.ok) return Result.error(skills.error);
4017
- const commands = await probeNamedFileCollection(join7(root, ".claude/commands"));
4018
- if (!commands.ok) return Result.error(commands.error);
4019
- const agents = await probeNamedFileCollection(join7(root, ".claude/agents"));
4020
- if (!agents.ok) return Result.error(agents.error);
4021
- const hooks = await readHooks([settings.value, settingsLocal.value]);
4022
- if (!hooks.ok) return Result.error(hooks.error);
4023
- const artifacts = {
4024
- tool: "claude-code",
4025
- ...claudeMd.value !== void 0 ? { claudeMd: claudeMd.value } : {},
4026
- ...agentsMd.value !== void 0 ? { agentsMd: agentsMd.value } : {},
4027
- ...settings.value !== void 0 ? { settings: settings.value } : {},
4028
- ...settingsLocal.value !== void 0 ? { settingsLocal: settingsLocal.value } : {},
4029
- ...mcpConfig.value !== void 0 ? { mcpConfig: mcpConfig.value } : {},
4030
- skills: skills.value,
4031
- commands: commands.value,
4032
- agents: agents.value,
4033
- hooks: hooks.value
4034
- };
4035
- return Result.ok(hasAnyClaudeArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
4036
- }
4037
- };
3993
+ // src/integration/ai/readiness/_engine/probe-fs.ts
4038
3994
  var probeFile = async (path) => {
4039
3995
  try {
4040
3996
  const stat = await fs8.stat(path);
4041
3997
  if (!stat.isFile()) return Result.ok(void 0);
4042
3998
  return Result.ok({ path });
4043
3999
  } catch (cause) {
4044
- if (isNodeErrnoCode3(cause, "ENOENT")) return Result.ok(void 0);
4045
- if (isNodeErrnoCode3(cause, "EACCES")) {
4000
+ if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(void 0);
4001
+ if (isNodeErrnoCode(cause, "EACCES")) {
4046
4002
  return Result.error(
4047
4003
  new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
4048
4004
  );
@@ -4050,23 +4006,6 @@ var probeFile = async (path) => {
4050
4006
  return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to stat ${path}`, path, cause }));
4051
4007
  }
4052
4008
  };
4053
- var probeNamedFileCollection = async (dir) => {
4054
- const entries = await listDir2(dir);
4055
- if (!entries.ok) return Result.error(entries.error);
4056
- const refs = [];
4057
- for (const entry of entries.value) {
4058
- if (!entry.endsWith(".md")) continue;
4059
- const full = join7(dir, entry);
4060
- const stat = await statSafely(full);
4061
- if (!stat.ok) return Result.error(stat.error);
4062
- if (stat.value === void 0 || !stat.value.isFile()) continue;
4063
- const baseName = entry.slice(0, -".md".length);
4064
- const slug = Slug.parse(toKebabCase(baseName));
4065
- if (!slug.ok) continue;
4066
- refs.push({ name: slug.value, path: full });
4067
- }
4068
- return Result.ok(refs);
4069
- };
4070
4009
  var probeNamedDirCollection = async (dir, childMarker) => {
4071
4010
  const entries = await listDir2(dir);
4072
4011
  if (!entries.ok) return Result.error(entries.error);
@@ -4086,12 +4025,29 @@ var probeNamedDirCollection = async (dir, childMarker) => {
4086
4025
  }
4087
4026
  return Result.ok(refs);
4088
4027
  };
4028
+ var probeNamedFileCollection = async (dir) => {
4029
+ const entries = await listDir2(dir);
4030
+ if (!entries.ok) return Result.error(entries.error);
4031
+ const refs = [];
4032
+ for (const entry of entries.value) {
4033
+ if (!entry.endsWith(".md")) continue;
4034
+ const full = join7(dir, entry);
4035
+ const stat = await statSafely(full);
4036
+ if (!stat.ok) return Result.error(stat.error);
4037
+ if (stat.value === void 0 || !stat.value.isFile()) continue;
4038
+ const baseName = entry.slice(0, -".md".length);
4039
+ const slug = Slug.parse(toKebabCase(baseName));
4040
+ if (!slug.ok) continue;
4041
+ refs.push({ name: slug.value, path: full });
4042
+ }
4043
+ return Result.ok(refs);
4044
+ };
4089
4045
  var listDir2 = async (dir) => {
4090
4046
  try {
4091
4047
  return Result.ok(await fs8.readdir(dir));
4092
4048
  } catch (cause) {
4093
- if (isNodeErrnoCode3(cause, "ENOENT") || isNodeErrnoCode3(cause, "ENOTDIR")) return Result.ok([]);
4094
- if (isNodeErrnoCode3(cause, "EACCES")) {
4049
+ if (isNodeErrnoCode(cause, "ENOENT") || isNodeErrnoCode(cause, "ENOTDIR")) return Result.ok([]);
4050
+ if (isNodeErrnoCode(cause, "EACCES")) {
4095
4051
  return Result.error(
4096
4052
  new ProbeError({ subCode: "fs-permission", message: `permission denied listing ${dir}`, path: dir, cause })
4097
4053
  );
@@ -4103,8 +4059,8 @@ var statSafely = async (path) => {
4103
4059
  try {
4104
4060
  return Result.ok(await fs8.stat(path));
4105
4061
  } catch (cause) {
4106
- if (isNodeErrnoCode3(cause, "ENOENT")) return Result.ok(void 0);
4107
- if (isNodeErrnoCode3(cause, "EACCES")) {
4062
+ if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(void 0);
4063
+ if (isNodeErrnoCode(cause, "EACCES")) {
4108
4064
  return Result.error(
4109
4065
  new ProbeError({ subCode: "fs-permission", message: `permission denied stat ${path}`, path, cause })
4110
4066
  );
@@ -4112,6 +4068,73 @@ var statSafely = async (path) => {
4112
4068
  return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to stat ${path}`, path, cause }));
4113
4069
  }
4114
4070
  };
4071
+ var readFileSafely = async (path) => {
4072
+ try {
4073
+ return Result.ok(await fs8.readFile(path, "utf8"));
4074
+ } catch (cause) {
4075
+ if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(void 0);
4076
+ if (isNodeErrnoCode(cause, "EACCES")) {
4077
+ return Result.error(
4078
+ new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
4079
+ );
4080
+ }
4081
+ return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to read ${path}`, path, cause }));
4082
+ }
4083
+ };
4084
+
4085
+ // src/integration/ai/readiness/_engine/state.ts
4086
+ var unknownState = { kind: "unknown" };
4087
+ var absentState = (evaluatedAt) => ({ kind: "absent", evaluatedAt });
4088
+ var presentState = (evaluatedAt, artifacts) => ({
4089
+ kind: "present",
4090
+ evaluatedAt,
4091
+ artifacts
4092
+ });
4093
+
4094
+ // src/integration/ai/readiness/_engine/predicates.ts
4095
+ var isPresent = (state) => state.kind === "present";
4096
+ var hasAnyClaudeArtifact = (a) => a.claudeMd !== void 0 || a.agentsMd !== void 0 || a.settings !== void 0 || a.settingsLocal !== void 0 || a.mcpConfig !== void 0 || a.skills.length > 0 || a.commands.length > 0 || a.agents.length > 0 || a.hooks.length > 0;
4097
+ var hasAnyCopilotArtifact = (a) => a.copilotInstructions !== void 0;
4098
+ var hasAnyCodexArtifact = (a) => a.agentsMd !== void 0 || a.skills.length > 0;
4099
+
4100
+ // src/integration/ai/readiness/claude/probe.ts
4101
+ var claudeProbe = {
4102
+ tool: "claude-code",
4103
+ async evaluate(repository, now) {
4104
+ const root = repository.path;
4105
+ const claudeMd = await probeFile(join8(root, "CLAUDE.md"));
4106
+ if (!claudeMd.ok) return Result.error(claudeMd.error);
4107
+ const agentsMd = await probeFile(join8(root, "AGENTS.md"));
4108
+ if (!agentsMd.ok) return Result.error(agentsMd.error);
4109
+ const settings = await probeFile(join8(root, ".claude/settings.json"));
4110
+ if (!settings.ok) return Result.error(settings.error);
4111
+ const settingsLocal = await probeFile(join8(root, ".claude/settings.local.json"));
4112
+ if (!settingsLocal.ok) return Result.error(settingsLocal.error);
4113
+ const mcpConfig = await probeFile(join8(root, ".mcp.json"));
4114
+ if (!mcpConfig.ok) return Result.error(mcpConfig.error);
4115
+ const skills = await probeNamedDirCollection(join8(root, ".claude/skills"), "SKILL.md");
4116
+ if (!skills.ok) return Result.error(skills.error);
4117
+ const commands = await probeNamedFileCollection(join8(root, ".claude/commands"));
4118
+ if (!commands.ok) return Result.error(commands.error);
4119
+ const agents = await probeNamedFileCollection(join8(root, ".claude/agents"));
4120
+ if (!agents.ok) return Result.error(agents.error);
4121
+ const hooks = await readHooks([settings.value, settingsLocal.value]);
4122
+ if (!hooks.ok) return Result.error(hooks.error);
4123
+ const artifacts = {
4124
+ tool: "claude-code",
4125
+ ...claudeMd.value !== void 0 ? { claudeMd: claudeMd.value } : {},
4126
+ ...agentsMd.value !== void 0 ? { agentsMd: agentsMd.value } : {},
4127
+ ...settings.value !== void 0 ? { settings: settings.value } : {},
4128
+ ...settingsLocal.value !== void 0 ? { settingsLocal: settingsLocal.value } : {},
4129
+ ...mcpConfig.value !== void 0 ? { mcpConfig: mcpConfig.value } : {},
4130
+ skills: skills.value,
4131
+ commands: commands.value,
4132
+ agents: agents.value,
4133
+ hooks: hooks.value
4134
+ };
4135
+ return Result.ok(hasAnyClaudeArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
4136
+ }
4137
+ };
4115
4138
  var readHooks = async (settingsRefs) => {
4116
4139
  const hooks = [];
4117
4140
  for (const ref2 of settingsRefs) {
@@ -4131,19 +4154,6 @@ var readHooks = async (settingsRefs) => {
4131
4154
  }
4132
4155
  return Result.ok(hooks);
4133
4156
  };
4134
- var readFileSafely = async (path) => {
4135
- try {
4136
- return Result.ok(await fs8.readFile(path, "utf8"));
4137
- } catch (cause) {
4138
- if (isNodeErrnoCode3(cause, "ENOENT")) return Result.ok(void 0);
4139
- if (isNodeErrnoCode3(cause, "EACCES")) {
4140
- return Result.error(
4141
- new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
4142
- );
4143
- }
4144
- return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to read ${path}`, path, cause }));
4145
- }
4146
- };
4147
4157
  var extractHooks = (settings, sink) => {
4148
4158
  if (typeof settings !== "object" || settings === null) return;
4149
4159
  const hooks = settings.hooks;
@@ -4164,33 +4174,41 @@ var extractHooks = (settings, sink) => {
4164
4174
  }
4165
4175
  }
4166
4176
  };
4167
- var isNodeErrnoCode3 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
4168
4177
 
4169
4178
  // src/integration/ai/readiness/codex/probe.ts
4179
+ import { join as join9 } from "path";
4170
4180
  var codexProbe = {
4171
4181
  tool: "codex",
4172
- evaluate(repository, now) {
4173
- void repository;
4174
- void now;
4175
- return Promise.resolve(Result.ok(unknownState));
4182
+ async evaluate(repository, now) {
4183
+ const root = repository.path;
4184
+ const agentsMd = await probeFile(join9(root, "AGENTS.md"));
4185
+ if (!agentsMd.ok) return Result.error(agentsMd.error);
4186
+ const skills = await probeNamedDirCollection(join9(root, ".agents/skills"), "SKILL.md");
4187
+ if (!skills.ok) return Result.error(skills.error);
4188
+ const artifacts = {
4189
+ tool: "codex",
4190
+ ...agentsMd.value !== void 0 ? { agentsMd: agentsMd.value } : {},
4191
+ skills: skills.value
4192
+ };
4193
+ return Result.ok(hasAnyCodexArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
4176
4194
  }
4177
4195
  };
4178
4196
 
4179
4197
  // src/integration/ai/readiness/copilot/probe.ts
4180
4198
  import { promises as fs9 } from "fs";
4181
- import { join as join8 } from "path";
4199
+ import { join as join10 } from "path";
4182
4200
  var copilotProbe = {
4183
4201
  tool: "copilot",
4184
4202
  async evaluate(repository, now) {
4185
- const path = join8(repository.path, ".github/copilot-instructions.md");
4203
+ const path = join10(repository.path, ".github/copilot-instructions.md");
4186
4204
  try {
4187
4205
  const stat = await fs9.stat(path);
4188
4206
  if (!stat.isFile()) return Result.ok(absentState(now));
4189
4207
  const artifacts = { tool: "copilot", copilotInstructions: { path } };
4190
4208
  return Result.ok(hasAnyCopilotArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
4191
4209
  } catch (cause) {
4192
- if (isNodeErrnoCode4(cause, "ENOENT")) return Result.ok(absentState(now));
4193
- if (isNodeErrnoCode4(cause, "EACCES")) {
4210
+ if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(absentState(now));
4211
+ if (isNodeErrnoCode(cause, "EACCES")) {
4194
4212
  return Result.error(
4195
4213
  new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
4196
4214
  );
@@ -4199,7 +4217,6 @@ var copilotProbe = {
4199
4217
  }
4200
4218
  }
4201
4219
  };
4202
- var isNodeErrnoCode4 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
4203
4220
 
4204
4221
  // src/integration/observability/in-memory-event-bus.ts
4205
4222
  var createInMemoryEventBus = () => {
@@ -4247,7 +4264,7 @@ var loggerForScope = (deps, scope) => {
4247
4264
  };
4248
4265
 
4249
4266
  // src/integration/version/npm-version-checker.ts
4250
- import { join as join9 } from "path";
4267
+ import { join as join11 } from "path";
4251
4268
  import { z as z15 } from "zod";
4252
4269
 
4253
4270
  // src/business/version/version-check.ts
@@ -4289,7 +4306,7 @@ var buildVersionCheck = (current, latest, now) => ({
4289
4306
  var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1e3;
4290
4307
  var DEFAULT_FETCH_TIMEOUT_MS = 3e3;
4291
4308
  var RegistryPayloadSchema = z15.object({ version: z15.string() });
4292
- var cachePath = (stateRoot) => join9(String(stateRoot), "version-check.json");
4309
+ var cachePath = (stateRoot) => join11(String(stateRoot), "version-check.json");
4293
4310
  var registryUrl = (packageName) => `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
4294
4311
  var shouldSkip = (env) => env["NO_NETWORK"] !== void 0 || env["VITEST"] !== void 0;
4295
4312
  var readCache = async (path) => {
@@ -4343,7 +4360,7 @@ var createNpmVersionChecker = (deps) => {
4343
4360
  // package.json
4344
4361
  var package_default = {
4345
4362
  name: "ralphctl",
4346
- version: "0.7.1",
4363
+ version: "0.7.3",
4347
4364
  description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
4348
4365
  homepage: "https://github.com/lukas-grigis/ralphctl",
4349
4366
  type: "module",
@@ -4445,11 +4462,11 @@ var CLI_METADATA = {
4445
4462
  // src/integration/ai/skills/_engine/filesystem-skills-adapter.ts
4446
4463
  import { existsSync } from "fs";
4447
4464
  import { mkdir, rm, rmdir, writeFile } from "fs/promises";
4448
- import { join as join11 } from "path";
4465
+ import { join as join13 } from "path";
4449
4466
 
4450
4467
  // src/integration/io/git-exclude.ts
4451
4468
  import { promises as fs10 } from "fs";
4452
- import { isAbsolute as isAbsolute2, join as join10 } from "path";
4469
+ import { isAbsolute as isAbsolute2, join as join12 } from "path";
4453
4470
  var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
4454
4471
  const resolved = await resolveExcludePath(String(repoRoot));
4455
4472
  if (resolved === void 0) return Result.ok(void 0);
@@ -4457,7 +4474,7 @@ var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
4457
4474
  try {
4458
4475
  existing = await fs10.readFile(resolved, "utf8");
4459
4476
  } catch (cause) {
4460
- if (isNodeErrnoCode5(cause, "ENOENT")) {
4477
+ if (isNodeErrnoCode3(cause, "ENOENT")) {
4461
4478
  } else {
4462
4479
  return Result.error(
4463
4480
  new StorageError({
@@ -4478,16 +4495,16 @@ var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
4478
4495
  return writeTextAtomic(resolved, next);
4479
4496
  };
4480
4497
  var resolveExcludePath = async (repoRoot) => {
4481
- const gitMarker = join10(repoRoot, ".git");
4498
+ const gitMarker = join12(repoRoot, ".git");
4482
4499
  let stat;
4483
4500
  try {
4484
4501
  stat = await fs10.stat(gitMarker);
4485
4502
  } catch (cause) {
4486
- if (isNodeErrnoCode5(cause, "ENOENT") || isNodeErrnoCode5(cause, "ENOTDIR")) return void 0;
4503
+ if (isNodeErrnoCode3(cause, "ENOENT") || isNodeErrnoCode3(cause, "ENOTDIR")) return void 0;
4487
4504
  throw cause;
4488
4505
  }
4489
4506
  if (stat.isDirectory()) {
4490
- return join10(gitMarker, "info", "exclude");
4507
+ return join12(gitMarker, "info", "exclude");
4491
4508
  }
4492
4509
  if (!stat.isFile()) return void 0;
4493
4510
  let pointer;
@@ -4499,10 +4516,10 @@ var resolveExcludePath = async (repoRoot) => {
4499
4516
  const match = /^gitdir:\s*(.+)\s*$/m.exec(pointer);
4500
4517
  if (match === null) return void 0;
4501
4518
  const gitdir = match[1].trim();
4502
- const absoluteGitdir = isAbsolute2(gitdir) ? gitdir : join10(repoRoot, gitdir);
4503
- return join10(absoluteGitdir, "info", "exclude");
4519
+ const absoluteGitdir = isAbsolute2(gitdir) ? gitdir : join12(repoRoot, gitdir);
4520
+ return join12(absoluteGitdir, "info", "exclude");
4504
4521
  };
4505
- var isNodeErrnoCode5 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
4522
+ var isNodeErrnoCode3 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
4506
4523
 
4507
4524
  // src/integration/ai/skills/_engine/filesystem-skills-adapter.ts
4508
4525
  var renderSkill = (skill) => {
@@ -4525,7 +4542,7 @@ var tryRmdirIfEmpty = async (path) => {
4525
4542
  var createFilesystemSkillsAdapter = (deps) => {
4526
4543
  const installed = /* @__PURE__ */ new Map();
4527
4544
  const excludeAttempted = /* @__PURE__ */ new Set();
4528
- const skillsSubdir = join11(deps.parentDir, "skills");
4545
+ const skillsSubdir = join13(deps.parentDir, "skills");
4529
4546
  const excludePattern = `${skillsSubdir}/ralphctl-*`;
4530
4547
  const pruneStale = () => {
4531
4548
  for (const key of [...installed.keys()]) {
@@ -4535,14 +4552,14 @@ var createFilesystemSkillsAdapter = (deps) => {
4535
4552
  return {
4536
4553
  async install(sessionDir, skills) {
4537
4554
  pruneStale();
4538
- const skillsDir = join11(String(sessionDir), skillsSubdir);
4555
+ const skillsDir = join13(String(sessionDir), skillsSubdir);
4539
4556
  const tracked = installed.get(String(sessionDir)) ?? /* @__PURE__ */ new Set();
4540
4557
  for (const skill of skills) {
4541
- const dst = join11(skillsDir, skill.name);
4558
+ const dst = join13(skillsDir, skill.name);
4542
4559
  if (existsSync(dst)) continue;
4543
4560
  try {
4544
4561
  await mkdir(dst, { recursive: true });
4545
- await writeFile(join11(dst, "SKILL.md"), renderSkill(skill), "utf-8");
4562
+ await writeFile(join13(dst, "SKILL.md"), renderSkill(skill), "utf-8");
4546
4563
  tracked.add(skill.name);
4547
4564
  } catch (cause) {
4548
4565
  if (tracked.size > 0) installed.set(String(sessionDir), tracked);
@@ -4573,10 +4590,10 @@ var createFilesystemSkillsAdapter = (deps) => {
4573
4590
  const key = String(sessionDir);
4574
4591
  const tracked = installed.get(key);
4575
4592
  if (tracked === void 0 || tracked.size === 0) return Result.ok(void 0);
4576
- const skillsDir = join11(key, skillsSubdir);
4593
+ const skillsDir = join13(key, skillsSubdir);
4577
4594
  try {
4578
4595
  for (const id of tracked) {
4579
- await rm(join11(skillsDir, id), { recursive: true, force: true });
4596
+ await rm(join13(skillsDir, id), { recursive: true, force: true });
4580
4597
  }
4581
4598
  installed.delete(key);
4582
4599
  } catch (cause) {
@@ -4590,7 +4607,7 @@ var createFilesystemSkillsAdapter = (deps) => {
4590
4607
  );
4591
4608
  }
4592
4609
  await tryRmdirIfEmpty(skillsDir);
4593
- await tryRmdirIfEmpty(join11(key, deps.parentDir));
4610
+ await tryRmdirIfEmpty(join13(key, deps.parentDir));
4594
4611
  return Result.ok(void 0);
4595
4612
  }
4596
4613
  };
@@ -4652,7 +4669,7 @@ var createSkillsAdapter = (deps) => {
4652
4669
  };
4653
4670
 
4654
4671
  // src/integration/ai/skills/bundled/source.ts
4655
- import { dirname as dirname7, join as join12 } from "path";
4672
+ import { dirname as dirname7, join as join14 } from "path";
4656
4673
  import { fileURLToPath as fileURLToPath2 } from "url";
4657
4674
  import { readFile } from "fs/promises";
4658
4675
 
@@ -4681,7 +4698,7 @@ var skillsForFlow = (flowId) => FLOW_SKILLS[flowId];
4681
4698
  var defaultBundledRoot = (() => {
4682
4699
  const here = dirname7(fileURLToPath2(import.meta.url));
4683
4700
  const isBundled = import.meta.url.endsWith("/cli.mjs") || import.meta.url.endsWith("\\cli.mjs");
4684
- return isBundled ? join12(here, "skills") : here;
4701
+ return isBundled ? join14(here, "skills") : here;
4685
4702
  })();
4686
4703
  var splitFrontmatter = (raw) => {
4687
4704
  const trimmed = raw.replace(/^\uFEFF/u, "");
@@ -4709,7 +4726,7 @@ var parseSimpleYaml = (input) => {
4709
4726
  return result;
4710
4727
  };
4711
4728
  var readSkill = async (root, name) => {
4712
- const path = join12(root, name, "SKILL.md");
4729
+ const path = join14(root, name, "SKILL.md");
4713
4730
  let raw;
4714
4731
  try {
4715
4732
  raw = await readFile(path, "utf-8");
@@ -5202,7 +5219,7 @@ var getRunInTerminal = () => (fn) => ref.current(fn);
5202
5219
 
5203
5220
  // src/application/ui/tui/App.tsx
5204
5221
  import "react";
5205
- import { Box as Box49 } from "ink";
5222
+ import { Box as Box50 } from "ink";
5206
5223
 
5207
5224
  // src/application/ui/tui/runtime/deps-context.tsx
5208
5225
  import { createContext, useContext } from "react";
@@ -5699,13 +5716,13 @@ var probeGitConfig = async (id, label, key, runCommand2, hint) => {
5699
5716
  }
5700
5717
  return { id, label, status: "warn", detail: `${key} not set`, hint, group: "vcs" };
5701
5718
  };
5702
- var probeCliAuth = async (id, label, binary, args, hint, runCommand2) => {
5719
+ var probeCliAuth = async (id, label, binary, args, hint, runCommand2, group = "vcs") => {
5703
5720
  const r = await runCommand2(binary, args);
5704
5721
  if (r.ok) {
5705
- return { id, label, status: "pass", detail: "authenticated", group: "vcs" };
5722
+ return { id, label, status: "pass", detail: "authenticated", group };
5706
5723
  }
5707
5724
  const detail = r.stderr.split("\n").find((line) => line.trim().length > 0)?.trim() ?? "not authenticated";
5708
- return { id, label, status: "warn", detail, hint, group: "vcs" };
5725
+ return { id, label, status: "warn", detail, hint, group };
5709
5726
  };
5710
5727
  var createDoctorFlow = (deps) => leaf("doctor", {
5711
5728
  useCase: {
@@ -5817,6 +5834,7 @@ var createDoctorFlow = (deps) => leaf("doctor", {
5817
5834
  }
5818
5835
  const settings = await deps.settingsRepo.load();
5819
5836
  const configuredProvider = settings.ok ? settings.value.ai.provider : void 0;
5837
+ let codexInstalled = false;
5820
5838
  for (const provider of Object.keys(PROVIDER_BINARY)) {
5821
5839
  const binary = PROVIDER_BINARY[provider];
5822
5840
  const isConfigured = provider === configuredProvider;
@@ -5828,12 +5846,26 @@ var createDoctorFlow = (deps) => leaf("doctor", {
5828
5846
  deps.commandExists,
5829
5847
  `install the '${binary}' CLI and ensure it is on your PATH`
5830
5848
  );
5849
+ if (provider === "openai-codex") codexInstalled = probe.status === "pass";
5831
5850
  if (probe.status === "fail") {
5832
5851
  probes.push({ ...probe, status: "warn" });
5833
5852
  } else {
5834
5853
  probes.push(probe);
5835
5854
  }
5836
5855
  }
5856
+ if (configuredProvider === "openai-codex" && codexInstalled) {
5857
+ probes.push(
5858
+ await probeCliAuth(
5859
+ "codex-auth",
5860
+ "OpenAI Codex CLI authenticated",
5861
+ "codex",
5862
+ ["login", "status"],
5863
+ "run `codex login` to sign in",
5864
+ deps.runCommand,
5865
+ "ai"
5866
+ )
5867
+ );
5868
+ }
5837
5869
  const projects = await deps.projectRepo.list();
5838
5870
  probes.push({
5839
5871
  id: "projects-list",
@@ -6465,10 +6497,17 @@ var spinnerGlyph = (frame) => glyphs.spinner[frame % glyphs.spinner.length] ?? "
6465
6497
 
6466
6498
  // src/application/ui/tui/components/spinner.tsx
6467
6499
  import { jsxs as jsxs6 } from "react/jsx-runtime";
6468
- var Spinner = ({ label, color = inkColors.info }) => {
6469
- const frame = useSpinnerFrame();
6500
+ var Spinner = ({ label, color = inkColors.info, active = true, dim }) => {
6501
+ const frame = useSpinnerFrame(active);
6502
+ const glyph = spinnerGlyph(frame);
6503
+ if (dim === true) {
6504
+ return /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
6505
+ glyph,
6506
+ label !== void 0 && label.length > 0 ? ` ${label}` : ""
6507
+ ] });
6508
+ }
6470
6509
  return /* @__PURE__ */ jsxs6(Text7, { color, children: [
6471
- spinnerGlyph(frame),
6510
+ glyph,
6472
6511
  label !== void 0 && label.length > 0 ? ` ${label}` : ""
6473
6512
  ] });
6474
6513
  };
@@ -9176,14 +9215,14 @@ var launchAddTickets = (ctx) => {
9176
9215
  };
9177
9216
 
9178
9217
  // src/application/ui/shared/launch/refine.ts
9179
- import { join as join15 } from "path";
9218
+ import { join as join17 } from "path";
9180
9219
 
9181
9220
  // src/application/flows/refine/flow.ts
9182
- import { join as join14 } from "path";
9221
+ import { join as join16 } from "path";
9183
9222
 
9184
9223
  // src/application/flows/_shared/build-unit.ts
9185
9224
  import { promises as fs11 } from "fs";
9186
- import { join as join13 } from "path";
9225
+ import { join as join15 } from "path";
9187
9226
  var buildUnitLeaf = (opts) => leaf(opts.name, {
9188
9227
  useCase: {
9189
9228
  execute: async (input) => {
@@ -9204,7 +9243,7 @@ var buildUnitLeaf = (opts) => leaf(opts.name, {
9204
9243
  return Result.ok(parsed.value);
9205
9244
  }
9206
9245
  },
9207
- input: (ctx) => ({ path: join13(String(opts.parent(ctx)), opts.slug(ctx)) }),
9246
+ input: (ctx) => ({ path: join15(String(opts.parent(ctx)), opts.slug(ctx)) }),
9208
9247
  output: (ctx, root) => opts.write(ctx, root)
9209
9248
  });
9210
9249
 
@@ -9666,8 +9705,8 @@ var createRefineFlow = (deps, opts) => {
9666
9705
  parent: () => opts.refinementRoot,
9667
9706
  slug: () => ticketSlug(ticket),
9668
9707
  write: (ctx, root) => {
9669
- const promptPath = AbsolutePath.parse(join14(String(root), "prompt.md"));
9670
- const outputPath = AbsolutePath.parse(join14(String(root), "requirements.md"));
9708
+ const promptPath = AbsolutePath.parse(join16(String(root), "prompt.md"));
9709
+ const outputPath = AbsolutePath.parse(join16(String(root), "requirements.md"));
9671
9710
  if (!promptPath.ok || !outputPath.ok) {
9672
9711
  throw promptPath.ok ? outputPath.ok ? new Error("unreachable") : outputPath.error : promptPath.error;
9673
9712
  }
@@ -9768,7 +9807,7 @@ var launchRefine = async (ctx) => {
9768
9807
  if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
9769
9808
  const pending = snapshot.sprint.tickets.filter((t) => t.status === "pending");
9770
9809
  const refinementRoot = AbsolutePath.parse(
9771
- join15(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "refinement")
9810
+ join17(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "refinement")
9772
9811
  );
9773
9812
  if (!refinementRoot.ok) return { ok: false, reason: refinementRoot.error.message };
9774
9813
  let defaultIssueOrigin = snapshot.project?.defaultIssueOrigin;
@@ -9841,10 +9880,10 @@ ${body.trim()}`;
9841
9880
  };
9842
9881
 
9843
9882
  // src/application/ui/shared/launch/plan.ts
9844
- import { join as join17 } from "path";
9883
+ import { join as join19 } from "path";
9845
9884
 
9846
9885
  // src/application/flows/plan/flow.ts
9847
- import { join as join16 } from "path";
9886
+ import { join as join18 } from "path";
9848
9887
 
9849
9888
  // src/application/flows/_shared/sprint/load-execution.ts
9850
9889
  var loadSprintExecutionLeaf = (deps, name = "load-sprint-execution") => leaf(name, {
@@ -10585,8 +10624,8 @@ var createPlanFlow = (deps, opts) => {
10585
10624
  parent: () => opts.planRoot,
10586
10625
  slug: () => slug,
10587
10626
  write: (ctx, root) => {
10588
- const promptPath = AbsolutePath.parse(join16(String(root), "prompt.md"));
10589
- const outputPath = AbsolutePath.parse(join16(String(root), "plan.json"));
10627
+ const promptPath = AbsolutePath.parse(join18(String(root), "prompt.md"));
10628
+ const outputPath = AbsolutePath.parse(join18(String(root), "plan.json"));
10590
10629
  if (!promptPath.ok) throw promptPath.error;
10591
10630
  if (!outputPath.ok) throw outputPath.error;
10592
10631
  return {
@@ -10676,7 +10715,7 @@ var launchPlan = (ctx) => {
10676
10715
  if (!snapshot.project) return { ok: false, reason: "No project loaded." };
10677
10716
  if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
10678
10717
  const planRoot = AbsolutePath.parse(
10679
- join17(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "plan")
10718
+ join19(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "plan")
10680
10719
  );
10681
10720
  if (!planRoot.ok) return { ok: false, reason: planRoot.error.message };
10682
10721
  const reviewBeforeApprove = async (proposedTasks) => {
@@ -10730,7 +10769,7 @@ ${summary}`;
10730
10769
  };
10731
10770
 
10732
10771
  // src/application/ui/shared/launch/implement.ts
10733
- import { join as join21 } from "path";
10772
+ import { join as join23 } from "path";
10734
10773
 
10735
10774
  // src/application/chain/build/guard.ts
10736
10775
  var guard = (name, predicate, body) => ({
@@ -11643,9 +11682,9 @@ var implementSession = (sandboxCwd, repoPath, prompt, model, signalsFile) => ({
11643
11682
  });
11644
11683
 
11645
11684
  // src/application/flows/implement/leaves/round-artifacts.ts
11646
- import { join as join18 } from "path";
11685
+ import { join as join20 } from "path";
11647
11686
  var nextRoundNum = async (workspaceRoot) => {
11648
- const entries = await listDir(join18(String(workspaceRoot), "rounds"));
11687
+ const entries = await listDir(join20(String(workspaceRoot), "rounds"));
11649
11688
  if (!entries.ok) return 1;
11650
11689
  let max = 0;
11651
11690
  for (const name of entries.value) {
@@ -11654,12 +11693,12 @@ var nextRoundNum = async (workspaceRoot) => {
11654
11693
  }
11655
11694
  return max + 1;
11656
11695
  };
11657
- var roundSignalsPath = (workspaceRoot, round, role) => join18(String(workspaceRoot), "rounds", String(round), role, "signals.json");
11658
- var roundEvaluationRelativePath = (round) => join18("rounds", String(round), "evaluator", "evaluation.md");
11659
- var roundDir = (workspaceRoot, round, role) => join18(String(workspaceRoot), "rounds", String(round), role);
11696
+ var roundSignalsPath = (workspaceRoot, round, role) => join20(String(workspaceRoot), "rounds", String(round), role, "signals.json");
11697
+ var roundEvaluationRelativePath = (round) => join20("rounds", String(round), "evaluator", "evaluation.md");
11698
+ var roundDir = (workspaceRoot, round, role) => join20(String(workspaceRoot), "rounds", String(round), role);
11660
11699
  var writeEvaluatorRoundArtifacts = async (workspaceRoot, round, signals, logger) => {
11661
11700
  const base = roundDir(workspaceRoot, round, "evaluator");
11662
- const evaluation = await writeTextAtomic(join18(base, "evaluation.md"), renderEvaluation(findEvaluation2(signals)));
11701
+ const evaluation = await writeTextAtomic(join20(base, "evaluation.md"), renderEvaluation(findEvaluation2(signals)));
11663
11702
  if (!evaluation.ok) {
11664
11703
  logger?.warn("failed to write evaluator round artifact", { round, base, error: evaluation.error.message });
11665
11704
  }
@@ -12783,7 +12822,7 @@ var startAttemptLeaf = (deps, taskId) => leaf(
12783
12822
 
12784
12823
  // src/application/flows/implement/leaves/build-task-workspace.ts
12785
12824
  import { promises as fs16 } from "fs";
12786
- import { join as join19 } from "path";
12825
+ import { join as join21 } from "path";
12787
12826
  var renderDoneCriteria = (task) => {
12788
12827
  const header = `# Done criteria \u2014 ${task.name}
12789
12828
 
@@ -12816,7 +12855,7 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
12816
12855
  useCase: {
12817
12856
  execute: async (input) => {
12818
12857
  const log = deps.logger.named("implement.workspace");
12819
- const workspaceRoot = join19(String(opts.sprintDir), "implement", String(input.task.id));
12858
+ const workspaceRoot = join21(String(opts.sprintDir), "implement", String(input.task.id));
12820
12859
  const prompt = await buildImplementPrompt(deps.templateLoader, {
12821
12860
  task: input.task,
12822
12861
  projectPath: String(opts.cwd),
@@ -12824,10 +12863,10 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
12824
12863
  ...opts.checkScript !== void 0 ? { checkScript: opts.checkScript } : {}
12825
12864
  });
12826
12865
  if (!prompt.ok) return Result.error(prompt.error);
12827
- const wrotePrompt = await writeOrError(join19(workspaceRoot, "prompt.md"), String(prompt.value));
12866
+ const wrotePrompt = await writeOrError(join21(workspaceRoot, "prompt.md"), String(prompt.value));
12828
12867
  if (!wrotePrompt.ok) return Result.error(wrotePrompt.error);
12829
12868
  const wroteCriteria = await writeOrError(
12830
- join19(workspaceRoot, "done-criteria.md"),
12869
+ join21(workspaceRoot, "done-criteria.md"),
12831
12870
  renderDoneCriteria(input.task)
12832
12871
  );
12833
12872
  if (!wroteCriteria.ok) return Result.error(wroteCriteria.error);
@@ -12898,15 +12937,20 @@ var transitionSprintToReviewLeaf = (deps) => leaf("transition-sprint-to-review",
12898
12937
 
12899
12938
  // src/integration/io/lock-paths.ts
12900
12939
  import { createHash } from "crypto";
12901
- import { join as join20 } from "path";
12940
+ import { join as join22 } from "path";
12902
12941
  var repoLockFile = (locksRoot, worktreePath) => {
12903
12942
  const hash = createHash("sha1").update(String(worktreePath)).digest("hex").slice(0, 16);
12904
- return AbsolutePath.parse(join20(String(locksRoot), `repo-${hash}.lock`));
12943
+ return AbsolutePath.parse(join22(String(locksRoot), `repo-${hash}.lock`));
12905
12944
  };
12906
12945
 
12907
12946
  // src/application/flows/implement/leaves/with-repo-lock.ts
12908
12947
  var withRepoLock = (opts, inner) => ({
12909
12948
  name: `with-repo-lock(${inner.name})`,
12949
+ // Expose the wrapped chain through the composite-pattern `children` slot so `flattenLeaves`
12950
+ // walks into it when the TUI builds its planned-leaf list. Without this the wrapper looked
12951
+ // like an opaque single leaf and the Flow-steps panel rendered only "with-repo-lock(…)" —
12952
+ // never the real setup / per-task / teardown sequence inside the lock.
12953
+ children: [inner],
12910
12954
  async execute(ctx, signal, onTrace) {
12911
12955
  const lockPath = repoLockFile(opts.locksRoot, opts.worktreePath);
12912
12956
  if (!lockPath.ok) {
@@ -13196,11 +13240,11 @@ var launchImplement = (ctx) => {
13196
13240
  return a.status === "in_progress" ? -1 : 1;
13197
13241
  });
13198
13242
  if (todoTasks.length === 0) return { ok: false, reason: "No tasks to implement or resume." };
13199
- const sprintDirPath = AbsolutePath.parse(join21(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id)));
13243
+ const sprintDirPath = AbsolutePath.parse(join23(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id)));
13200
13244
  if (!sprintDirPath.ok) return { ok: false, reason: sprintDirPath.error.message };
13201
- const progressPath = AbsolutePath.parse(join21(String(sprintDirPath.value), "progress.md"));
13245
+ const progressPath = AbsolutePath.parse(join23(String(sprintDirPath.value), "progress.md"));
13202
13246
  if (!progressPath.ok) return { ok: false, reason: progressPath.error.message };
13203
- const chainLogPath = AbsolutePath.parse(join21(String(sprintDirPath.value), "chain.log"));
13247
+ const chainLogPath = AbsolutePath.parse(join23(String(sprintDirPath.value), "chain.log"));
13204
13248
  if (!chainLogPath.ok) return { ok: false, reason: chainLogPath.error.message };
13205
13249
  const chainLog = startFileLogSink({ file: chainLogPath.value, bus: deps.app.eventBus });
13206
13250
  const repositories = /* @__PURE__ */ new Map();
@@ -13265,7 +13309,7 @@ var launchImplement = (ctx) => {
13265
13309
  };
13266
13310
 
13267
13311
  // src/application/ui/shared/launch/review.ts
13268
- import { join as join23 } from "path";
13312
+ import { join as join25 } from "path";
13269
13313
 
13270
13314
  // src/application/flows/review/leaves/ensure-feedback-file.ts
13271
13315
  import { promises as fs18 } from "fs";
@@ -13508,12 +13552,12 @@ var buildApplyFeedbackPrompt = async (deps, input) => buildPrompt(deps, applyFee
13508
13552
 
13509
13553
  // src/integration/ai/signals/_engine/temp-signals-file.ts
13510
13554
  import { tmpdir as tmpdir2 } from "os";
13511
- import { join as join22 } from "path";
13555
+ import { join as join24 } from "path";
13512
13556
  var counter = 0;
13513
13557
  var allocSignalsTempPath = (label) => {
13514
13558
  counter += 1;
13515
13559
  const filename = `ralphctl-signals-${label}-${String(process.pid)}-${String(Date.now())}-${String(counter)}.json`;
13516
- return AbsolutePath.parse(join22(tmpdir2(), filename));
13560
+ return AbsolutePath.parse(join24(tmpdir2(), filename));
13517
13561
  };
13518
13562
  var withSignalsTempPath = async (label, fn) => {
13519
13563
  const path = allocSignalsTempPath(label);
@@ -13772,11 +13816,11 @@ var launchReview = (ctx) => {
13772
13816
  if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
13773
13817
  if (!cwd) return { ok: false, reason: "No repository path resolvable from the project." };
13774
13818
  const feedbackPath = AbsolutePath.parse(
13775
- join23(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "feedback.md")
13819
+ join25(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "feedback.md")
13776
13820
  );
13777
13821
  if (!feedbackPath.ok) return { ok: false, reason: feedbackPath.error.message };
13778
13822
  const progressPath = AbsolutePath.parse(
13779
- join23(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "progress.md")
13823
+ join25(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "progress.md")
13780
13824
  );
13781
13825
  if (!progressPath.ok) return { ok: false, reason: progressPath.error.message };
13782
13826
  const checkScript = snapshot.project?.repositories.find((r) => r.checkScript !== void 0)?.checkScript;
@@ -14005,11 +14049,11 @@ var probeReadinessLeaf = (deps) => leaf("probe", {
14005
14049
  import { promises as fs21 } from "fs";
14006
14050
 
14007
14051
  // src/integration/ai/readiness/_engine/setup.ts
14008
- import { join as join25 } from "path";
14052
+ import { join as join27 } from "path";
14009
14053
 
14010
14054
  // src/integration/ai/runs/_engine/run-artifacts.ts
14011
14055
  import { promises as fs20 } from "fs";
14012
- import { join as join24 } from "path";
14056
+ import { join as join26 } from "path";
14013
14057
  var BODY_PREVIEW_LIMIT = 800;
14014
14058
  var buildRunDirName = () => {
14015
14059
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -14019,7 +14063,7 @@ var buildRunDirName = () => {
14019
14063
  var readRunBodyPreview = async (runDir, options) => {
14020
14064
  let raw;
14021
14065
  try {
14022
- raw = await fs20.readFile(join24(String(runDir), "body.txt"), "utf8");
14066
+ raw = await fs20.readFile(join26(String(runDir), "body.txt"), "utf8");
14023
14067
  } catch (cause) {
14024
14068
  if (isErrnoException(cause) && cause.code === "ENOENT") return void 0;
14025
14069
  const code = isErrnoException(cause) ? cause.code : "unknown";
@@ -14148,11 +14192,11 @@ var setupReadinessUseCase = async (deps, input) => {
14148
14192
  ...input.existingContextFile !== void 0 ? { existingContextFile: input.existingContextFile } : {}
14149
14193
  });
14150
14194
  if (!prompt.ok) return Result.error(prompt.error);
14151
- const runDir = AbsolutePath.parse(join25(String(deps.runsRoot), "readiness", buildRunDirName()));
14195
+ const runDir = AbsolutePath.parse(join27(String(deps.runsRoot), "readiness", buildRunDirName()));
14152
14196
  if (!runDir.ok) return Result.error(runDir.error);
14153
- const promptFile = AbsolutePath.parse(join25(String(runDir.value), "prompt.md"));
14197
+ const promptFile = AbsolutePath.parse(join27(String(runDir.value), "prompt.md"));
14154
14198
  if (!promptFile.ok) return Result.error(promptFile.error);
14155
- const bodyFile = AbsolutePath.parse(join25(String(runDir.value), "body.txt"));
14199
+ const bodyFile = AbsolutePath.parse(join27(String(runDir.value), "body.txt"));
14156
14200
  if (!bodyFile.ok) return Result.error(bodyFile.error);
14157
14201
  const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
14158
14202
  if (!promptWrote.ok) return Result.error(promptWrote.error);
@@ -14201,7 +14245,7 @@ var setupReadinessUseCase = async (deps, input) => {
14201
14245
  const verifyScript = signals.value.find(
14202
14246
  (s) => s.type === "verify-script"
14203
14247
  )?.command;
14204
- const targetPath = AbsolutePath.parse(join25(String(input.repository.path), targetPathFor(input.tool)));
14248
+ const targetPath = AbsolutePath.parse(join27(String(input.repository.path), targetPathFor(input.tool)));
14205
14249
  if (!targetPath.ok) return Result.error(targetPath.error);
14206
14250
  log.info(`proposal ready for repo ${input.repository.name}`, {
14207
14251
  repositoryId: String(input.repository.id),
@@ -14267,6 +14311,9 @@ var pickExistingContextPath = (tool, state) => {
14267
14311
  if (tool === "copilot" && a.tool === "copilot") {
14268
14312
  return a.copilotInstructions !== void 0 ? String(a.copilotInstructions.path) : void 0;
14269
14313
  }
14314
+ if (tool === "codex" && a.tool === "codex") {
14315
+ return a.agentsMd !== void 0 ? String(a.agentsMd.path) : void 0;
14316
+ }
14270
14317
  return void 0;
14271
14318
  };
14272
14319
  var proposeReadinessLeaf = (deps) => leaf("propose", {
@@ -14447,7 +14494,7 @@ var launchReadiness = (ctx) => {
14447
14494
  };
14448
14495
 
14449
14496
  // src/application/flows/detect-skills/leaves/propose.ts
14450
- import { join as join26 } from "path";
14497
+ import { join as join28 } from "path";
14451
14498
 
14452
14499
  // src/integration/ai/prompts/detect-skills/definition.ts
14453
14500
  var detectSkillsPromptDef = {
@@ -14511,11 +14558,11 @@ var proposeUseCase = async (deps, input) => {
14511
14558
  skillsConvention: deps.skillsAdapter.describeSkillsConvention()
14512
14559
  });
14513
14560
  if (!prompt.ok) return Result.error(prompt.error);
14514
- const runDir = AbsolutePath.parse(join26(String(deps.runsRoot), "detect-skills", buildRunDirName()));
14561
+ const runDir = AbsolutePath.parse(join28(String(deps.runsRoot), "detect-skills", buildRunDirName()));
14515
14562
  if (!runDir.ok) return Result.error(runDir.error);
14516
- const promptFile = AbsolutePath.parse(join26(String(runDir.value), "prompt.md"));
14563
+ const promptFile = AbsolutePath.parse(join28(String(runDir.value), "prompt.md"));
14517
14564
  if (!promptFile.ok) return Result.error(promptFile.error);
14518
- const bodyFile = AbsolutePath.parse(join26(String(runDir.value), "body.txt"));
14565
+ const bodyFile = AbsolutePath.parse(join28(String(runDir.value), "body.txt"));
14519
14566
  if (!bodyFile.ok) return Result.error(bodyFile.error);
14520
14567
  const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
14521
14568
  if (!promptWrote.ok) return Result.error(promptWrote.error);
@@ -15129,7 +15176,7 @@ var launchDetectSkills = (ctx) => {
15129
15176
  };
15130
15177
 
15131
15178
  // src/application/flows/detect-scripts/leaves/propose.ts
15132
- import { join as join27 } from "path";
15179
+ import { join as join29 } from "path";
15133
15180
 
15134
15181
  // src/integration/ai/prompts/detect-scripts/definition.ts
15135
15182
  var detectScriptsPromptDef = {
@@ -15174,11 +15221,11 @@ var proposeUseCase2 = async (deps, input) => {
15174
15221
  repositoryPath: String(input.repository.path)
15175
15222
  });
15176
15223
  if (!prompt.ok) return Result.error(prompt.error);
15177
- const runDir = AbsolutePath.parse(join27(String(deps.runsRoot), "detect-scripts", buildRunDirName()));
15224
+ const runDir = AbsolutePath.parse(join29(String(deps.runsRoot), "detect-scripts", buildRunDirName()));
15178
15225
  if (!runDir.ok) return Result.error(runDir.error);
15179
- const promptFile = AbsolutePath.parse(join27(String(runDir.value), "prompt.md"));
15226
+ const promptFile = AbsolutePath.parse(join29(String(runDir.value), "prompt.md"));
15180
15227
  if (!promptFile.ok) return Result.error(promptFile.error);
15181
- const bodyFile = AbsolutePath.parse(join27(String(runDir.value), "body.txt"));
15228
+ const bodyFile = AbsolutePath.parse(join29(String(runDir.value), "body.txt"));
15182
15229
  if (!bodyFile.ok) return Result.error(bodyFile.error);
15183
15230
  const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
15184
15231
  if (!promptWrote.ok) return Result.error(promptWrote.error);
@@ -15522,10 +15569,10 @@ var launchDetectScripts = (ctx) => {
15522
15569
  };
15523
15570
 
15524
15571
  // src/application/ui/shared/launch/ideate.ts
15525
- import { join as join29 } from "path";
15572
+ import { join as join31 } from "path";
15526
15573
 
15527
15574
  // src/application/flows/ideate/flow.ts
15528
- import { join as join28 } from "path";
15575
+ import { join as join30 } from "path";
15529
15576
 
15530
15577
  // src/integration/ai/prompts/ideate/definition.ts
15531
15578
  var nonEmpty2 = (field) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message: `${field} must not be empty` })) : Result.ok(v);
@@ -15762,8 +15809,8 @@ var createIdeateFlow = (deps, opts) => {
15762
15809
  parent: () => opts.ideateRoot,
15763
15810
  slug: () => slug,
15764
15811
  write: (ctx, root) => {
15765
- const promptPath = AbsolutePath.parse(join28(String(root), "prompt.md"));
15766
- const outputPath = AbsolutePath.parse(join28(String(root), "ideate.json"));
15812
+ const promptPath = AbsolutePath.parse(join30(String(root), "prompt.md"));
15813
+ const outputPath = AbsolutePath.parse(join30(String(root), "ideate.json"));
15767
15814
  if (!promptPath.ok) throw promptPath.error;
15768
15815
  if (!outputPath.ok) throw outputPath.error;
15769
15816
  return {
@@ -15827,7 +15874,7 @@ var launchIdeate = async (ctx) => {
15827
15874
  const bodyAns = await deps.interactive.askText("Idea description (paste or type)");
15828
15875
  if (!bodyAns.ok) return { ok: false, reason: bodyAns.error.message };
15829
15876
  const ideateRoot = AbsolutePath.parse(
15830
- join29(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "ideate")
15877
+ join31(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "ideate")
15831
15878
  );
15832
15879
  if (!ideateRoot.ok) return { ok: false, reason: ideateRoot.error.message };
15833
15880
  const element = createIdeateFlow(
@@ -17779,20 +17826,20 @@ import { Box as Box34, Text as Text36, useInput as useInput15 } from "ink";
17779
17826
  import { useMemo as useMemo10 } from "react";
17780
17827
  import { Box as Box30, Text as Text32 } from "ink";
17781
17828
  import { jsx as jsx42, jsxs as jsxs31 } from "react/jsx-runtime";
17782
- var glyphFor = (status, frame) => {
17829
+ var glyphFor = (status) => {
17783
17830
  switch (status) {
17784
17831
  case "completed":
17785
- return [glyphs.phaseDone, inkColors.success];
17832
+ return { kind: "static", glyph: glyphs.phaseDone, color: inkColors.success };
17786
17833
  case "failed":
17787
- return [glyphs.cross, inkColors.error];
17834
+ return { kind: "static", glyph: glyphs.cross, color: inkColors.error };
17788
17835
  case "aborted":
17789
- return [glyphs.warningGlyph, inkColors.warning];
17836
+ return { kind: "static", glyph: glyphs.warningGlyph, color: inkColors.warning };
17790
17837
  case "skipped":
17791
- return [glyphs.phaseDisabled, inkColors.muted];
17838
+ return { kind: "static", glyph: glyphs.phaseDisabled, color: inkColors.muted };
17792
17839
  case "running":
17793
- return [spinnerGlyph(frame), inkColors.info];
17840
+ return { kind: "spinner", color: inkColors.info };
17794
17841
  case "pending":
17795
- return [glyphs.phasePending, inkColors.muted];
17842
+ return { kind: "static", glyph: glyphs.phasePending, color: inkColors.muted };
17796
17843
  }
17797
17844
  };
17798
17845
  var trailingLabelFor = (status) => {
@@ -17842,7 +17889,6 @@ var StepTrace = ({
17842
17889
  inFlightLabel,
17843
17890
  plan
17844
17891
  }) => {
17845
- const frame = useSpinnerFrame(running);
17846
17892
  const traceLastEntry = trace[trace.length - 1];
17847
17893
  const merged = useMemo10(
17848
17894
  () => plan !== void 0 ? mergePlanWithTrace(plan, trace, running) : traceToRows(trace),
@@ -17856,11 +17902,11 @@ var StepTrace = ({
17856
17902
  const rows = runningIdx >= 0 ? filtered.slice(Math.max(0, runningIdx - Math.floor(maxRows / 2))).slice(0, maxRows) : filtered.slice(-maxRows);
17857
17903
  return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
17858
17904
  rows.map((row, i) => {
17859
- const [g, color] = glyphFor(row.status, frame);
17905
+ const instruction = glyphFor(row.status);
17860
17906
  const trailing = trailingLabelFor(row.status);
17861
17907
  const dimRow = row.status === "pending";
17862
17908
  return /* @__PURE__ */ jsxs31(Box30, { paddingX: spacing.indent, children: [
17863
- /* @__PURE__ */ jsx42(Text32, { color, bold: true, children: g }),
17909
+ instruction.kind === "spinner" ? /* @__PURE__ */ jsx42(Spinner, { active: running, color: instruction.color }) : /* @__PURE__ */ jsx42(Text32, { color: instruction.color, bold: true, children: instruction.glyph }),
17864
17910
  /* @__PURE__ */ jsxs31(Text32, { dimColor: dimRow, children: [
17865
17911
  " ",
17866
17912
  row.name
@@ -17871,7 +17917,7 @@ var StepTrace = ({
17871
17917
  " ",
17872
17918
  fmtDuration(row.durationMs)
17873
17919
  ] }),
17874
- trailing !== void 0 && /* @__PURE__ */ jsxs31(Text32, { color, children: [
17920
+ trailing !== void 0 && /* @__PURE__ */ jsxs31(Text32, { color: instruction.color, children: [
17875
17921
  " ",
17876
17922
  glyphs.emDash,
17877
17923
  " ",
@@ -17886,7 +17932,7 @@ var StepTrace = ({
17886
17932
  ] }, `${row.name}-${String(i)}`);
17887
17933
  }),
17888
17934
  plan === void 0 && running && inFlightLabel !== void 0 && /* @__PURE__ */ jsxs31(Box30, { paddingX: spacing.indent, children: [
17889
- /* @__PURE__ */ jsx42(Text32, { color: inkColors.info, bold: true, children: spinnerGlyph(frame) }),
17935
+ /* @__PURE__ */ jsx42(Spinner, { active: running, color: inkColors.info }),
17890
17936
  /* @__PURE__ */ jsxs31(Text32, { dimColor: true, children: [
17891
17937
  " ",
17892
17938
  inFlightLabel
@@ -17982,30 +18028,23 @@ var SignalLine = ({ signal }) => {
17982
18028
  /* @__PURE__ */ jsx43(Text33, { bold: row.bold ?? false, children: truncate2(row.text, 80) })
17983
18029
  ] });
17984
18030
  };
18031
+ var LEGEND_ENTRIES = [
18032
+ { label: "change", color: inkColors.info, description: "file/code edit" },
18033
+ { label: "learning", color: inkColors.highlight, description: "cross-task insight" },
18034
+ { label: "decision", color: inkColors.highlight, description: "design choice" },
18035
+ { label: "verified", color: inkColors.success, description: "task self-check passed" },
18036
+ { label: "blocked", color: inkColors.error, description: "task self-blocked" },
18037
+ { label: "commit", color: inkColors.info, description: "proposed commit message" }
18038
+ ];
17985
18039
  var SignalLegend = () => /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingX: spacing.indent, marginBottom: spacing.section, children: [
17986
- /* @__PURE__ */ jsxs32(Text33, { dimColor: true, children: [
17987
- /* @__PURE__ */ jsx43(Text33, { bold: true, children: "legend" }),
17988
- " ",
17989
- /* @__PURE__ */ jsx43(Text33, { color: inkColors.info, children: "change" }),
17990
- " = file/code edit",
17991
- " ",
17992
- /* @__PURE__ */ jsx43(Text33, { color: inkColors.highlight, children: "learning" }),
17993
- " = cross-task insight",
17994
- " ",
17995
- /* @__PURE__ */ jsx43(Text33, { color: inkColors.highlight, children: "decision" }),
17996
- " = design choice"
17997
- ] }),
17998
- /* @__PURE__ */ jsxs32(Text33, { dimColor: true, children: [
17999
- " ",
18000
- /* @__PURE__ */ jsx43(Text33, { color: inkColors.success, children: "verified" }),
18001
- " = task self-check passed",
18002
- " ",
18003
- /* @__PURE__ */ jsx43(Text33, { color: inkColors.error, children: "blocked" }),
18004
- " = task self-blocked",
18005
- " ",
18006
- /* @__PURE__ */ jsx43(Text33, { color: inkColors.info, children: "commit" }),
18007
- " = proposed commit message"
18008
- ] })
18040
+ /* @__PURE__ */ jsx43(Text33, { dimColor: true, bold: true, children: "legend" }),
18041
+ /* @__PURE__ */ jsx43(Box31, { flexDirection: "column", paddingLeft: 2, children: LEGEND_ENTRIES.map((entry) => /* @__PURE__ */ jsxs32(Box31, { children: [
18042
+ /* @__PURE__ */ jsx43(Text33, { color: entry.color, bold: true, children: padLabel2(entry.label) }),
18043
+ /* @__PURE__ */ jsxs32(Text33, { dimColor: true, children: [
18044
+ "= ",
18045
+ entry.description
18046
+ ] })
18047
+ ] }, entry.label)) })
18009
18048
  ] });
18010
18049
  var EvaluationLine = ({ evaluation }) => {
18011
18050
  const color = evaluation.status === "passed" ? inkColors.success : evaluation.status === "failed" ? inkColors.error : inkColors.warning;
@@ -18029,14 +18068,9 @@ var EvaluationLine = ({ evaluation }) => {
18029
18068
  evaluation.dimensions.length > 0 && /* @__PURE__ */ jsx43(Box31, { paddingLeft: 6, children: /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: evaluation.dimensions.map((d) => `${d.dimension}: ${String(d.score)}/5 ${d.passed ? glyphs.check : glyphs.cross}`).join(` ${glyphs.bullet} `) }) })
18030
18069
  ] });
18031
18070
  };
18032
- var SubStepLine = ({
18033
- sub,
18034
- running,
18035
- spinner
18036
- }) => {
18071
+ var SubStepLine = ({ sub, running }) => {
18037
18072
  const presentation = SUB_STEP_PRESENTATION[sub.status];
18038
18073
  const glyph = running && sub.status === "completed" ? presentation.glyph : presentation.glyph;
18039
- void spinner;
18040
18074
  return /* @__PURE__ */ jsxs32(Box31, { children: [
18041
18075
  /* @__PURE__ */ jsxs32(Text33, { color: presentation.color, bold: true, children: [
18042
18076
  glyphs.activityArrow,
@@ -18065,15 +18099,20 @@ var TaskBlock = ({
18065
18099
  task,
18066
18100
  running,
18067
18101
  display,
18068
- maxSignals
18102
+ maxSignals,
18103
+ maxSubSteps,
18104
+ maxEvaluations
18069
18105
  }) => {
18070
18106
  const presentation = STATUS_PRESENTATION[task.status];
18071
- const frame = useSpinnerFrame(running && task.status === "running");
18072
- const glyph = task.status === "running" ? spinnerGlyph(frame) : presentation.glyph;
18107
+ const isSpinning = task.status === "running";
18073
18108
  const signalRows = task.signals.slice(-maxSignals);
18109
+ const subStepRows = task.subSteps.slice(-maxSubSteps);
18110
+ const subStepElided = task.subSteps.length - subStepRows.length;
18111
+ const evalRows = task.evaluations.slice(-maxEvaluations);
18112
+ const evalElided = task.evaluations.length - evalRows.length;
18074
18113
  return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginBottom: spacing.section, children: [
18075
18114
  /* @__PURE__ */ jsxs32(Box31, { children: [
18076
- /* @__PURE__ */ jsx43(Text33, { color: presentation.color, bold: true, children: glyph }),
18115
+ isSpinning ? /* @__PURE__ */ jsx43(Spinner, { active: running, color: presentation.color }) : /* @__PURE__ */ jsx43(Text33, { color: presentation.color, bold: true, children: presentation.glyph }),
18077
18116
  /* @__PURE__ */ jsxs32(Text33, { bold: true, children: [
18078
18117
  " ",
18079
18118
  display
@@ -18099,8 +18138,14 @@ var TaskBlock = ({
18099
18138
  ] })
18100
18139
  ] }),
18101
18140
  task.errorMessage !== void 0 && /* @__PURE__ */ jsx43(Box31, { paddingLeft: 2, children: /* @__PURE__ */ jsx43(Text33, { color: inkColors.error, children: task.errorMessage }) }),
18102
- task.subSteps.length > 0 && /* @__PURE__ */ jsx43(Box31, { flexDirection: "column", paddingLeft: 2, children: task.subSteps.map((s, i) => /* @__PURE__ */ jsx43(SubStepLine, { sub: s, running, spinner: spinnerGlyph(frame) }, `${task.id}-sub-${String(i)}`)) }),
18103
- task.evaluations.length > 0 && /* @__PURE__ */ jsx43(Box31, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: task.evaluations.map((e, i) => /* @__PURE__ */ jsx43(EvaluationLine, { evaluation: e }, `${task.id}-eval-${String(i)}`)) }),
18141
+ subStepRows.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingLeft: 2, children: [
18142
+ subStepElided > 0 && /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: `\u2026 ${String(subStepElided)} earlier sub-steps` }),
18143
+ subStepRows.map((s, i) => /* @__PURE__ */ jsx43(SubStepLine, { sub: s, running }, `${task.id}-sub-${String(i)}`))
18144
+ ] }),
18145
+ evalRows.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: [
18146
+ evalElided > 0 && /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: `\u2026 ${String(evalElided)} earlier evaluations` }),
18147
+ evalRows.map((e, i) => /* @__PURE__ */ jsx43(EvaluationLine, { evaluation: e }, `${task.id}-eval-${String(i)}`))
18148
+ ] }),
18104
18149
  signalRows.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: [
18105
18150
  /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: "signals" }),
18106
18151
  /* @__PURE__ */ jsx43(Box31, { flexDirection: "column", paddingLeft: 2, children: signalRows.map((s, i) => /* @__PURE__ */ jsx43(SignalLine, { signal: s }, `${task.id}-sig-${String(i)}`)) })
@@ -18126,7 +18171,9 @@ var TasksPanel = ({
18126
18171
  running,
18127
18172
  nameById,
18128
18173
  maxSignalsPerTask = 8,
18129
- maxOrphanSignals = 6
18174
+ maxOrphanSignals = 6,
18175
+ maxSubStepsPerTask = 12,
18176
+ maxEvaluationsPerTask = 6
18130
18177
  }) => {
18131
18178
  if (bucketed.tasks.length === 0 && bucketed.orphanSignals.length === 0) {
18132
18179
  return /* @__PURE__ */ jsx43(Box31, { paddingX: spacing.indent, children: /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: "(no tasks yet)" }) });
@@ -18136,7 +18183,18 @@ var TasksPanel = ({
18136
18183
  /* @__PURE__ */ jsx43(OrphanSignals, { signals: bucketed.orphanSignals, max: maxOrphanSignals }),
18137
18184
  bucketed.tasks.map((task) => {
18138
18185
  const display = nameById?.get(task.id) ?? `${task.id.slice(0, 8)}\u2026`;
18139
- return /* @__PURE__ */ jsx43(TaskBlock, { task, running, display, maxSignals: maxSignalsPerTask }, task.id);
18186
+ return /* @__PURE__ */ jsx43(
18187
+ TaskBlock,
18188
+ {
18189
+ task,
18190
+ running,
18191
+ display,
18192
+ maxSignals: maxSignalsPerTask,
18193
+ maxSubSteps: maxSubStepsPerTask,
18194
+ maxEvaluations: maxEvaluationsPerTask
18195
+ },
18196
+ task.id
18197
+ );
18140
18198
  })
18141
18199
  ] });
18142
18200
  };
@@ -18447,7 +18505,6 @@ var ExecuteView = () => {
18447
18505
  filter: (e) => "chainId" in e && e.chainId === sessionId2,
18448
18506
  limit: 2e3
18449
18507
  });
18450
- const frame = useSpinnerFrame(session?.descriptor.status === "running");
18451
18508
  const term = useTerminalSize();
18452
18509
  const isRunning = session?.descriptor.status === "running";
18453
18510
  useViewHints(
@@ -18540,10 +18597,7 @@ var ExecuteView = () => {
18540
18597
  String(tasksTotal)
18541
18598
  ] })
18542
18599
  ] }),
18543
- isRunning && /* @__PURE__ */ jsx46(Box34, { marginLeft: 2, children: /* @__PURE__ */ jsxs35(Text36, { color: inkColors.info, children: [
18544
- spinnerGlyph(frame),
18545
- " live"
18546
- ] }) })
18600
+ isRunning && /* @__PURE__ */ jsx46(Box34, { marginLeft: 2, children: /* @__PURE__ */ jsx46(Spinner, { active: isRunning, color: inkColors.info, label: "live" }) })
18547
18601
  ] }),
18548
18602
  currentTask !== void 0 && currentTaskName !== void 0 && /* @__PURE__ */ jsxs35(Box34, { children: [
18549
18603
  /* @__PURE__ */ jsxs35(Text36, { dimColor: true, children: [
@@ -18582,7 +18636,7 @@ var ExecuteView = () => {
18582
18636
  ] })
18583
18637
  ] })
18584
18638
  ] }) });
18585
- const outerFlowFilter = (name) => !isPerTaskLeaf(name);
18639
+ const outerFlowFilter = (name) => !isPerTaskLeaf(name) && !name.startsWith("with-repo-lock(");
18586
18640
  const flowStepsPanel = /* @__PURE__ */ jsx46(
18587
18641
  StepTrace,
18588
18642
  {
@@ -19314,7 +19368,7 @@ var ProbeRow = ({ probe }) => /* @__PURE__ */ jsxs39(Box38, { flexDirection: "co
19314
19368
  // src/application/ui/tui/views/export-context-view.tsx
19315
19369
  import { useCallback as useCallback7, useEffect as useEffect25, useState as useState31 } from "react";
19316
19370
  import { Box as Box39, Text as Text41, useInput as useInput20 } from "ink";
19317
- import { join as join30 } from "path";
19371
+ import { join as join32 } from "path";
19318
19372
 
19319
19373
  // src/business/sprint/views/context-md.ts
19320
19374
  var renderSprintContextMarkdown = (input) => {
@@ -19446,7 +19500,7 @@ var ExportContextView = () => {
19446
19500
  return;
19447
19501
  }
19448
19502
  const outputPath = AbsolutePath.parse(
19449
- join30(String(storage2.dataRoot), "sprints", String(selection.sprintId), "context.md")
19503
+ join32(String(storage2.dataRoot), "sprints", String(selection.sprintId), "context.md")
19450
19504
  );
19451
19505
  if (!outputPath.ok) {
19452
19506
  setRun({ kind: "error", message: outputPath.error.message });
@@ -19504,7 +19558,7 @@ var ExportContextView = () => {
19504
19558
  // src/application/ui/tui/views/export-requirements-view.tsx
19505
19559
  import { useCallback as useCallback8, useEffect as useEffect26, useState as useState32 } from "react";
19506
19560
  import { Box as Box40, Text as Text42, useInput as useInput21 } from "ink";
19507
- import { join as join31 } from "path";
19561
+ import { join as join33 } from "path";
19508
19562
 
19509
19563
  // src/business/sprint/views/requirements-md.ts
19510
19564
  var renderSprintRequirementsMarkdown = (sprint) => {
@@ -19569,7 +19623,7 @@ var ExportRequirementsView = () => {
19569
19623
  return;
19570
19624
  }
19571
19625
  const outputPath = AbsolutePath.parse(
19572
- join31(String(storage2.dataRoot), "sprints", String(selection.sprintId), "requirements.md")
19626
+ join33(String(storage2.dataRoot), "sprints", String(selection.sprintId), "requirements.md")
19573
19627
  );
19574
19628
  if (!outputPath.ok) {
19575
19629
  setRun({ kind: "error", message: outputPath.error.message });
@@ -19904,12 +19958,12 @@ var WelcomeView = () => {
19904
19958
  import { useEffect as useEffect29, useState as useState36 } from "react";
19905
19959
  import { Box as Box44, Text as Text46 } from "ink";
19906
19960
  import { homedir as osHomedir2 } from "os";
19907
- import { basename as basename3, join as join33 } from "path";
19961
+ import { basename as basename3, join as join35 } from "path";
19908
19962
 
19909
19963
  // src/application/ui/tui/prompts/path-picker-prompt.tsx
19910
19964
  import { useEffect as useEffect28, useState as useState35 } from "react";
19911
19965
  import { promises as fs24 } from "fs";
19912
- import { dirname as dirname10, join as join32 } from "path";
19966
+ import { dirname as dirname10, join as join34 } from "path";
19913
19967
  import { homedir } from "os";
19914
19968
  import { Box as Box43, Text as Text45, useInput as useInput23 } from "ink";
19915
19969
  import { jsx as jsx55, jsxs as jsxs44 } from "react/jsx-runtime";
@@ -19917,7 +19971,7 @@ var VISIBLE_ROWS2 = 12;
19917
19971
  var clamp5 = (n, min, max) => Math.max(min, Math.min(max, n));
19918
19972
  var expandHome = (input) => {
19919
19973
  if (input === "~") return homedir();
19920
- if (input.startsWith("~/")) return join32(homedir(), input.slice(2));
19974
+ if (input.startsWith("~/")) return join34(homedir(), input.slice(2));
19921
19975
  return input;
19922
19976
  };
19923
19977
  var PathPickerPrompt = ({
@@ -20005,7 +20059,7 @@ var PathPickerPrompt = ({
20005
20059
  onSubmit(cwd);
20006
20060
  return;
20007
20061
  }
20008
- setCwd(join32(cwd, row.entry.name));
20062
+ setCwd(join34(cwd, row.entry.name));
20009
20063
  setCursor(1);
20010
20064
  }
20011
20065
  },
@@ -20087,7 +20141,7 @@ var labelFor = (row) => {
20087
20141
  import { jsx as jsx56, jsxs as jsxs45 } from "react/jsx-runtime";
20088
20142
  var expandHome2 = (input) => {
20089
20143
  if (input === "~") return osHomedir2();
20090
- if (input.startsWith("~/")) return join33(osHomedir2(), input.slice(2));
20144
+ if (input.startsWith("~/")) return join35(osHomedir2(), input.slice(2));
20091
20145
  return input;
20092
20146
  };
20093
20147
  var backStep = (step) => {
@@ -20303,11 +20357,11 @@ var StepView = ({ step, onChange, onCancel, onSubmit }) => {
20303
20357
  import { useEffect as useEffect30, useState as useState37 } from "react";
20304
20358
  import { Box as Box45, Text as Text47 } from "ink";
20305
20359
  import { homedir as osHomedir3 } from "os";
20306
- import { basename as basename4, join as join34 } from "path";
20360
+ import { basename as basename4, join as join36 } from "path";
20307
20361
  import { jsx as jsx57, jsxs as jsxs46 } from "react/jsx-runtime";
20308
20362
  var expandHome3 = (input) => {
20309
20363
  if (input === "~") return osHomedir3();
20310
- if (input.startsWith("~/")) return join34(osHomedir3(), input.slice(2));
20364
+ if (input.startsWith("~/")) return join36(osHomedir3(), input.slice(2));
20311
20365
  return input;
20312
20366
  };
20313
20367
  var backStep2 = (step) => {
@@ -21087,8 +21141,40 @@ var useGlobalKeys = (opts = {}) => {
21087
21141
  });
21088
21142
  };
21089
21143
 
21144
+ // src/application/ui/tui/components/memory-pressure-banner.tsx
21145
+ import { useEffect as useEffect34, useState as useState41 } from "react";
21146
+ import { Box as Box49, Text as Text51 } from "ink";
21147
+ import { jsxs as jsxs50 } from "react/jsx-runtime";
21148
+ var formatMb = (bytes) => `${(bytes / 1048576).toFixed(0)} MB`;
21149
+ var formatPercent = (ratio) => `${Math.round(ratio * 100)}%`;
21150
+ var MemoryPressureBanner = () => {
21151
+ const deps = useDeps();
21152
+ const [latest, setLatest] = useState41(void 0);
21153
+ useEffect34(() => {
21154
+ const unsub = deps.eventBus.subscribe((event) => {
21155
+ if (event.type === "memory-pressure") setLatest(event);
21156
+ });
21157
+ return unsub;
21158
+ }, [deps.eventBus]);
21159
+ if (latest === void 0 || latest.severity === "recovered") return null;
21160
+ const tone = latest.severity === "critical" ? inkColors.error : inkColors.warning;
21161
+ const headline = latest.severity === "critical" ? `memory critical \u2014 ${formatPercent(latest.ratio)} of heap; auto-cleared in-memory buffers` : `memory pressure \u2014 ${formatPercent(latest.ratio)} of heap used; consider aborting and restarting`;
21162
+ const detail = `(${formatMb(latest.heapUsed)} / ${formatMb(latest.heapLimit)})`;
21163
+ return /* @__PURE__ */ jsxs50(Box49, { paddingX: spacing.indent, flexDirection: "row", children: [
21164
+ /* @__PURE__ */ jsxs50(Text51, { bold: true, color: tone, children: [
21165
+ glyphs.warningGlyph,
21166
+ " ",
21167
+ headline
21168
+ ] }),
21169
+ /* @__PURE__ */ jsxs50(Text51, { dimColor: true, children: [
21170
+ " ",
21171
+ detail
21172
+ ] })
21173
+ ] });
21174
+ };
21175
+
21090
21176
  // src/application/ui/tui/App.tsx
21091
- import { jsx as jsx62 } from "react/jsx-runtime";
21177
+ import { jsx as jsx62, jsxs as jsxs51 } from "react/jsx-runtime";
21092
21178
  var App = ({
21093
21179
  deps,
21094
21180
  storage: storage2,
@@ -21111,7 +21197,10 @@ var Layout = ({ children }) => {
21111
21197
  const ui = useUiState();
21112
21198
  const { rows } = useTerminalSize();
21113
21199
  useGlobalKeys({ disabled: ui.promptActive });
21114
- return /* @__PURE__ */ jsx62(Box49, { flexDirection: "column", height: rows, children });
21200
+ return /* @__PURE__ */ jsxs51(Box50, { flexDirection: "column", height: rows, children: [
21201
+ /* @__PURE__ */ jsx62(MemoryPressureBanner, {}),
21202
+ children
21203
+ ] });
21115
21204
  };
21116
21205
 
21117
21206
  // src/application/ui/tui/launch-routing.ts
@@ -21138,10 +21227,10 @@ var resolveInitialState = ({
21138
21227
 
21139
21228
  // src/integration/persistence/selection/last-selection-store.ts
21140
21229
  import { promises as fs25 } from "fs";
21141
- import { join as join35 } from "path";
21230
+ import { join as join37 } from "path";
21142
21231
  var FILE_NAME = "last-selection.json";
21143
21232
  var createLastSelectionStore = (stateRoot) => {
21144
- const path = join35(String(stateRoot), FILE_NAME);
21233
+ const path = join37(String(stateRoot), FILE_NAME);
21145
21234
  return {
21146
21235
  async read() {
21147
21236
  try {
@@ -21195,6 +21284,69 @@ var createLogLevelGate = (initial) => {
21195
21284
  };
21196
21285
  };
21197
21286
 
21287
+ // src/integration/observability/heap-watchdog.ts
21288
+ import * as v8 from "v8";
21289
+ var MIN_INTERVAL_MS = 1e3;
21290
+ var DEFAULT_INTERVAL_MS = 1e4;
21291
+ var DEFAULT_WARNING_RATIO = 0.8;
21292
+ var DEFAULT_CRITICAL_RATIO = 0.95;
21293
+ var defaultReadHeap = () => {
21294
+ const stats = v8.getHeapStatistics();
21295
+ return { heapUsed: stats.used_heap_size, heapLimit: stats.heap_size_limit };
21296
+ };
21297
+ var classify = (ratio, warning, critical) => {
21298
+ if (ratio >= critical) return "critical";
21299
+ if (ratio >= warning) return "warning";
21300
+ return "ok";
21301
+ };
21302
+ var startHeapWatchdog = (deps) => {
21303
+ const intervalMs = Math.max(MIN_INTERVAL_MS, deps.intervalMs ?? DEFAULT_INTERVAL_MS);
21304
+ const warning = deps.warningRatio ?? DEFAULT_WARNING_RATIO;
21305
+ const critical = deps.criticalRatio ?? DEFAULT_CRITICAL_RATIO;
21306
+ const clock = deps.clock ?? IsoTimestamp.now;
21307
+ const readHeap = deps.readHeap ?? defaultReadHeap;
21308
+ let stopped = false;
21309
+ let previousBand = "ok";
21310
+ const emit = (severity, reading, ratio) => {
21311
+ deps.eventBus.publish({
21312
+ type: "memory-pressure",
21313
+ severity,
21314
+ ratio,
21315
+ heapUsed: reading.heapUsed,
21316
+ heapLimit: reading.heapLimit,
21317
+ at: clock()
21318
+ });
21319
+ };
21320
+ const sample = () => {
21321
+ if (stopped) return;
21322
+ const reading = readHeap();
21323
+ const ratio = reading.heapLimit > 0 ? reading.heapUsed / reading.heapLimit : 0;
21324
+ const band = classify(ratio, warning, critical);
21325
+ if (band === previousBand) return;
21326
+ if (band === "warning") emit("warning", reading, ratio);
21327
+ else if (band === "critical") {
21328
+ emit("critical", reading, ratio);
21329
+ try {
21330
+ deps.onCritical?.();
21331
+ } catch (err) {
21332
+ console.warn("[heap-watchdog] onCritical threw:", err);
21333
+ }
21334
+ } else {
21335
+ emit("recovered", reading, ratio);
21336
+ }
21337
+ previousBand = band;
21338
+ };
21339
+ const handle = setInterval(sample, intervalMs);
21340
+ handle.unref?.();
21341
+ return {
21342
+ stop() {
21343
+ if (stopped) return;
21344
+ stopped = true;
21345
+ clearInterval(handle);
21346
+ }
21347
+ };
21348
+ };
21349
+
21198
21350
  // src/application/ui/tui/launch.ts
21199
21351
  var bootstrap = async () => {
21200
21352
  const paths = resolveStoragePaths();
@@ -21218,6 +21370,13 @@ var bootstrap = async () => {
21218
21370
  const unsubLogForward = deps.eventBus.subscribe((event) => {
21219
21371
  if (event.type === "log" && passesLogLevel(event.level, logLevelGate.get())) logBus.emit(event);
21220
21372
  });
21373
+ const heapWatchdog = startHeapWatchdog({
21374
+ eventBus: deps.eventBus,
21375
+ onCritical: () => {
21376
+ harnessBus.clear();
21377
+ logBus.clear();
21378
+ }
21379
+ });
21221
21380
  const sessions = createSessionManager();
21222
21381
  const queue = createPromptQueue();
21223
21382
  void createInkInteractivePrompt(queue);
@@ -21254,6 +21413,7 @@ var bootstrap = async () => {
21254
21413
  drain: () => {
21255
21414
  queue.drain(new Error("TUI shutting down"));
21256
21415
  unsubLogForward();
21416
+ heapWatchdog.stop();
21257
21417
  }
21258
21418
  };
21259
21419
  };
@@ -21268,7 +21428,7 @@ var launchTui = async () => {
21268
21428
  process.exitCode = 1;
21269
21429
  return;
21270
21430
  }
21271
- const appElement = React65.createElement(App, booted.app);
21431
+ const appElement = React66.createElement(App, booted.app);
21272
21432
  const host = createInkHost({ appElement });
21273
21433
  setRunInTerminal(host.runInTerminal);
21274
21434
  try {