ralphctl 0.7.1 → 0.7.2

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
@@ -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.2",
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");
@@ -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",
@@ -9176,14 +9208,14 @@ var launchAddTickets = (ctx) => {
9176
9208
  };
9177
9209
 
9178
9210
  // src/application/ui/shared/launch/refine.ts
9179
- import { join as join15 } from "path";
9211
+ import { join as join17 } from "path";
9180
9212
 
9181
9213
  // src/application/flows/refine/flow.ts
9182
- import { join as join14 } from "path";
9214
+ import { join as join16 } from "path";
9183
9215
 
9184
9216
  // src/application/flows/_shared/build-unit.ts
9185
9217
  import { promises as fs11 } from "fs";
9186
- import { join as join13 } from "path";
9218
+ import { join as join15 } from "path";
9187
9219
  var buildUnitLeaf = (opts) => leaf(opts.name, {
9188
9220
  useCase: {
9189
9221
  execute: async (input) => {
@@ -9204,7 +9236,7 @@ var buildUnitLeaf = (opts) => leaf(opts.name, {
9204
9236
  return Result.ok(parsed.value);
9205
9237
  }
9206
9238
  },
9207
- input: (ctx) => ({ path: join13(String(opts.parent(ctx)), opts.slug(ctx)) }),
9239
+ input: (ctx) => ({ path: join15(String(opts.parent(ctx)), opts.slug(ctx)) }),
9208
9240
  output: (ctx, root) => opts.write(ctx, root)
9209
9241
  });
9210
9242
 
@@ -9666,8 +9698,8 @@ var createRefineFlow = (deps, opts) => {
9666
9698
  parent: () => opts.refinementRoot,
9667
9699
  slug: () => ticketSlug(ticket),
9668
9700
  write: (ctx, root) => {
9669
- const promptPath = AbsolutePath.parse(join14(String(root), "prompt.md"));
9670
- const outputPath = AbsolutePath.parse(join14(String(root), "requirements.md"));
9701
+ const promptPath = AbsolutePath.parse(join16(String(root), "prompt.md"));
9702
+ const outputPath = AbsolutePath.parse(join16(String(root), "requirements.md"));
9671
9703
  if (!promptPath.ok || !outputPath.ok) {
9672
9704
  throw promptPath.ok ? outputPath.ok ? new Error("unreachable") : outputPath.error : promptPath.error;
9673
9705
  }
@@ -9768,7 +9800,7 @@ var launchRefine = async (ctx) => {
9768
9800
  if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
9769
9801
  const pending = snapshot.sprint.tickets.filter((t) => t.status === "pending");
9770
9802
  const refinementRoot = AbsolutePath.parse(
9771
- join15(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "refinement")
9803
+ join17(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "refinement")
9772
9804
  );
9773
9805
  if (!refinementRoot.ok) return { ok: false, reason: refinementRoot.error.message };
9774
9806
  let defaultIssueOrigin = snapshot.project?.defaultIssueOrigin;
@@ -9841,10 +9873,10 @@ ${body.trim()}`;
9841
9873
  };
9842
9874
 
9843
9875
  // src/application/ui/shared/launch/plan.ts
9844
- import { join as join17 } from "path";
9876
+ import { join as join19 } from "path";
9845
9877
 
9846
9878
  // src/application/flows/plan/flow.ts
9847
- import { join as join16 } from "path";
9879
+ import { join as join18 } from "path";
9848
9880
 
9849
9881
  // src/application/flows/_shared/sprint/load-execution.ts
9850
9882
  var loadSprintExecutionLeaf = (deps, name = "load-sprint-execution") => leaf(name, {
@@ -10585,8 +10617,8 @@ var createPlanFlow = (deps, opts) => {
10585
10617
  parent: () => opts.planRoot,
10586
10618
  slug: () => slug,
10587
10619
  write: (ctx, root) => {
10588
- const promptPath = AbsolutePath.parse(join16(String(root), "prompt.md"));
10589
- const outputPath = AbsolutePath.parse(join16(String(root), "plan.json"));
10620
+ const promptPath = AbsolutePath.parse(join18(String(root), "prompt.md"));
10621
+ const outputPath = AbsolutePath.parse(join18(String(root), "plan.json"));
10590
10622
  if (!promptPath.ok) throw promptPath.error;
10591
10623
  if (!outputPath.ok) throw outputPath.error;
10592
10624
  return {
@@ -10676,7 +10708,7 @@ var launchPlan = (ctx) => {
10676
10708
  if (!snapshot.project) return { ok: false, reason: "No project loaded." };
10677
10709
  if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
10678
10710
  const planRoot = AbsolutePath.parse(
10679
- join17(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "plan")
10711
+ join19(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "plan")
10680
10712
  );
10681
10713
  if (!planRoot.ok) return { ok: false, reason: planRoot.error.message };
10682
10714
  const reviewBeforeApprove = async (proposedTasks) => {
@@ -10730,7 +10762,7 @@ ${summary}`;
10730
10762
  };
10731
10763
 
10732
10764
  // src/application/ui/shared/launch/implement.ts
10733
- import { join as join21 } from "path";
10765
+ import { join as join23 } from "path";
10734
10766
 
10735
10767
  // src/application/chain/build/guard.ts
10736
10768
  var guard = (name, predicate, body) => ({
@@ -11643,9 +11675,9 @@ var implementSession = (sandboxCwd, repoPath, prompt, model, signalsFile) => ({
11643
11675
  });
11644
11676
 
11645
11677
  // src/application/flows/implement/leaves/round-artifacts.ts
11646
- import { join as join18 } from "path";
11678
+ import { join as join20 } from "path";
11647
11679
  var nextRoundNum = async (workspaceRoot) => {
11648
- const entries = await listDir(join18(String(workspaceRoot), "rounds"));
11680
+ const entries = await listDir(join20(String(workspaceRoot), "rounds"));
11649
11681
  if (!entries.ok) return 1;
11650
11682
  let max = 0;
11651
11683
  for (const name of entries.value) {
@@ -11654,12 +11686,12 @@ var nextRoundNum = async (workspaceRoot) => {
11654
11686
  }
11655
11687
  return max + 1;
11656
11688
  };
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);
11689
+ var roundSignalsPath = (workspaceRoot, round, role) => join20(String(workspaceRoot), "rounds", String(round), role, "signals.json");
11690
+ var roundEvaluationRelativePath = (round) => join20("rounds", String(round), "evaluator", "evaluation.md");
11691
+ var roundDir = (workspaceRoot, round, role) => join20(String(workspaceRoot), "rounds", String(round), role);
11660
11692
  var writeEvaluatorRoundArtifacts = async (workspaceRoot, round, signals, logger) => {
11661
11693
  const base = roundDir(workspaceRoot, round, "evaluator");
11662
- const evaluation = await writeTextAtomic(join18(base, "evaluation.md"), renderEvaluation(findEvaluation2(signals)));
11694
+ const evaluation = await writeTextAtomic(join20(base, "evaluation.md"), renderEvaluation(findEvaluation2(signals)));
11663
11695
  if (!evaluation.ok) {
11664
11696
  logger?.warn("failed to write evaluator round artifact", { round, base, error: evaluation.error.message });
11665
11697
  }
@@ -12783,7 +12815,7 @@ var startAttemptLeaf = (deps, taskId) => leaf(
12783
12815
 
12784
12816
  // src/application/flows/implement/leaves/build-task-workspace.ts
12785
12817
  import { promises as fs16 } from "fs";
12786
- import { join as join19 } from "path";
12818
+ import { join as join21 } from "path";
12787
12819
  var renderDoneCriteria = (task) => {
12788
12820
  const header = `# Done criteria \u2014 ${task.name}
12789
12821
 
@@ -12816,7 +12848,7 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
12816
12848
  useCase: {
12817
12849
  execute: async (input) => {
12818
12850
  const log = deps.logger.named("implement.workspace");
12819
- const workspaceRoot = join19(String(opts.sprintDir), "implement", String(input.task.id));
12851
+ const workspaceRoot = join21(String(opts.sprintDir), "implement", String(input.task.id));
12820
12852
  const prompt = await buildImplementPrompt(deps.templateLoader, {
12821
12853
  task: input.task,
12822
12854
  projectPath: String(opts.cwd),
@@ -12824,10 +12856,10 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
12824
12856
  ...opts.checkScript !== void 0 ? { checkScript: opts.checkScript } : {}
12825
12857
  });
12826
12858
  if (!prompt.ok) return Result.error(prompt.error);
12827
- const wrotePrompt = await writeOrError(join19(workspaceRoot, "prompt.md"), String(prompt.value));
12859
+ const wrotePrompt = await writeOrError(join21(workspaceRoot, "prompt.md"), String(prompt.value));
12828
12860
  if (!wrotePrompt.ok) return Result.error(wrotePrompt.error);
12829
12861
  const wroteCriteria = await writeOrError(
12830
- join19(workspaceRoot, "done-criteria.md"),
12862
+ join21(workspaceRoot, "done-criteria.md"),
12831
12863
  renderDoneCriteria(input.task)
12832
12864
  );
12833
12865
  if (!wroteCriteria.ok) return Result.error(wroteCriteria.error);
@@ -12898,15 +12930,20 @@ var transitionSprintToReviewLeaf = (deps) => leaf("transition-sprint-to-review",
12898
12930
 
12899
12931
  // src/integration/io/lock-paths.ts
12900
12932
  import { createHash } from "crypto";
12901
- import { join as join20 } from "path";
12933
+ import { join as join22 } from "path";
12902
12934
  var repoLockFile = (locksRoot, worktreePath) => {
12903
12935
  const hash = createHash("sha1").update(String(worktreePath)).digest("hex").slice(0, 16);
12904
- return AbsolutePath.parse(join20(String(locksRoot), `repo-${hash}.lock`));
12936
+ return AbsolutePath.parse(join22(String(locksRoot), `repo-${hash}.lock`));
12905
12937
  };
12906
12938
 
12907
12939
  // src/application/flows/implement/leaves/with-repo-lock.ts
12908
12940
  var withRepoLock = (opts, inner) => ({
12909
12941
  name: `with-repo-lock(${inner.name})`,
12942
+ // Expose the wrapped chain through the composite-pattern `children` slot so `flattenLeaves`
12943
+ // walks into it when the TUI builds its planned-leaf list. Without this the wrapper looked
12944
+ // like an opaque single leaf and the Flow-steps panel rendered only "with-repo-lock(…)" —
12945
+ // never the real setup / per-task / teardown sequence inside the lock.
12946
+ children: [inner],
12910
12947
  async execute(ctx, signal, onTrace) {
12911
12948
  const lockPath = repoLockFile(opts.locksRoot, opts.worktreePath);
12912
12949
  if (!lockPath.ok) {
@@ -13196,11 +13233,11 @@ var launchImplement = (ctx) => {
13196
13233
  return a.status === "in_progress" ? -1 : 1;
13197
13234
  });
13198
13235
  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)));
13236
+ const sprintDirPath = AbsolutePath.parse(join23(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id)));
13200
13237
  if (!sprintDirPath.ok) return { ok: false, reason: sprintDirPath.error.message };
13201
- const progressPath = AbsolutePath.parse(join21(String(sprintDirPath.value), "progress.md"));
13238
+ const progressPath = AbsolutePath.parse(join23(String(sprintDirPath.value), "progress.md"));
13202
13239
  if (!progressPath.ok) return { ok: false, reason: progressPath.error.message };
13203
- const chainLogPath = AbsolutePath.parse(join21(String(sprintDirPath.value), "chain.log"));
13240
+ const chainLogPath = AbsolutePath.parse(join23(String(sprintDirPath.value), "chain.log"));
13204
13241
  if (!chainLogPath.ok) return { ok: false, reason: chainLogPath.error.message };
13205
13242
  const chainLog = startFileLogSink({ file: chainLogPath.value, bus: deps.app.eventBus });
13206
13243
  const repositories = /* @__PURE__ */ new Map();
@@ -13265,7 +13302,7 @@ var launchImplement = (ctx) => {
13265
13302
  };
13266
13303
 
13267
13304
  // src/application/ui/shared/launch/review.ts
13268
- import { join as join23 } from "path";
13305
+ import { join as join25 } from "path";
13269
13306
 
13270
13307
  // src/application/flows/review/leaves/ensure-feedback-file.ts
13271
13308
  import { promises as fs18 } from "fs";
@@ -13508,12 +13545,12 @@ var buildApplyFeedbackPrompt = async (deps, input) => buildPrompt(deps, applyFee
13508
13545
 
13509
13546
  // src/integration/ai/signals/_engine/temp-signals-file.ts
13510
13547
  import { tmpdir as tmpdir2 } from "os";
13511
- import { join as join22 } from "path";
13548
+ import { join as join24 } from "path";
13512
13549
  var counter = 0;
13513
13550
  var allocSignalsTempPath = (label) => {
13514
13551
  counter += 1;
13515
13552
  const filename = `ralphctl-signals-${label}-${String(process.pid)}-${String(Date.now())}-${String(counter)}.json`;
13516
- return AbsolutePath.parse(join22(tmpdir2(), filename));
13553
+ return AbsolutePath.parse(join24(tmpdir2(), filename));
13517
13554
  };
13518
13555
  var withSignalsTempPath = async (label, fn) => {
13519
13556
  const path = allocSignalsTempPath(label);
@@ -13772,11 +13809,11 @@ var launchReview = (ctx) => {
13772
13809
  if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
13773
13810
  if (!cwd) return { ok: false, reason: "No repository path resolvable from the project." };
13774
13811
  const feedbackPath = AbsolutePath.parse(
13775
- join23(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "feedback.md")
13812
+ join25(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "feedback.md")
13776
13813
  );
13777
13814
  if (!feedbackPath.ok) return { ok: false, reason: feedbackPath.error.message };
13778
13815
  const progressPath = AbsolutePath.parse(
13779
- join23(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "progress.md")
13816
+ join25(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "progress.md")
13780
13817
  );
13781
13818
  if (!progressPath.ok) return { ok: false, reason: progressPath.error.message };
13782
13819
  const checkScript = snapshot.project?.repositories.find((r) => r.checkScript !== void 0)?.checkScript;
@@ -14005,11 +14042,11 @@ var probeReadinessLeaf = (deps) => leaf("probe", {
14005
14042
  import { promises as fs21 } from "fs";
14006
14043
 
14007
14044
  // src/integration/ai/readiness/_engine/setup.ts
14008
- import { join as join25 } from "path";
14045
+ import { join as join27 } from "path";
14009
14046
 
14010
14047
  // src/integration/ai/runs/_engine/run-artifacts.ts
14011
14048
  import { promises as fs20 } from "fs";
14012
- import { join as join24 } from "path";
14049
+ import { join as join26 } from "path";
14013
14050
  var BODY_PREVIEW_LIMIT = 800;
14014
14051
  var buildRunDirName = () => {
14015
14052
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -14019,7 +14056,7 @@ var buildRunDirName = () => {
14019
14056
  var readRunBodyPreview = async (runDir, options) => {
14020
14057
  let raw;
14021
14058
  try {
14022
- raw = await fs20.readFile(join24(String(runDir), "body.txt"), "utf8");
14059
+ raw = await fs20.readFile(join26(String(runDir), "body.txt"), "utf8");
14023
14060
  } catch (cause) {
14024
14061
  if (isErrnoException(cause) && cause.code === "ENOENT") return void 0;
14025
14062
  const code = isErrnoException(cause) ? cause.code : "unknown";
@@ -14148,11 +14185,11 @@ var setupReadinessUseCase = async (deps, input) => {
14148
14185
  ...input.existingContextFile !== void 0 ? { existingContextFile: input.existingContextFile } : {}
14149
14186
  });
14150
14187
  if (!prompt.ok) return Result.error(prompt.error);
14151
- const runDir = AbsolutePath.parse(join25(String(deps.runsRoot), "readiness", buildRunDirName()));
14188
+ const runDir = AbsolutePath.parse(join27(String(deps.runsRoot), "readiness", buildRunDirName()));
14152
14189
  if (!runDir.ok) return Result.error(runDir.error);
14153
- const promptFile = AbsolutePath.parse(join25(String(runDir.value), "prompt.md"));
14190
+ const promptFile = AbsolutePath.parse(join27(String(runDir.value), "prompt.md"));
14154
14191
  if (!promptFile.ok) return Result.error(promptFile.error);
14155
- const bodyFile = AbsolutePath.parse(join25(String(runDir.value), "body.txt"));
14192
+ const bodyFile = AbsolutePath.parse(join27(String(runDir.value), "body.txt"));
14156
14193
  if (!bodyFile.ok) return Result.error(bodyFile.error);
14157
14194
  const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
14158
14195
  if (!promptWrote.ok) return Result.error(promptWrote.error);
@@ -14201,7 +14238,7 @@ var setupReadinessUseCase = async (deps, input) => {
14201
14238
  const verifyScript = signals.value.find(
14202
14239
  (s) => s.type === "verify-script"
14203
14240
  )?.command;
14204
- const targetPath = AbsolutePath.parse(join25(String(input.repository.path), targetPathFor(input.tool)));
14241
+ const targetPath = AbsolutePath.parse(join27(String(input.repository.path), targetPathFor(input.tool)));
14205
14242
  if (!targetPath.ok) return Result.error(targetPath.error);
14206
14243
  log.info(`proposal ready for repo ${input.repository.name}`, {
14207
14244
  repositoryId: String(input.repository.id),
@@ -14267,6 +14304,9 @@ var pickExistingContextPath = (tool, state) => {
14267
14304
  if (tool === "copilot" && a.tool === "copilot") {
14268
14305
  return a.copilotInstructions !== void 0 ? String(a.copilotInstructions.path) : void 0;
14269
14306
  }
14307
+ if (tool === "codex" && a.tool === "codex") {
14308
+ return a.agentsMd !== void 0 ? String(a.agentsMd.path) : void 0;
14309
+ }
14270
14310
  return void 0;
14271
14311
  };
14272
14312
  var proposeReadinessLeaf = (deps) => leaf("propose", {
@@ -14447,7 +14487,7 @@ var launchReadiness = (ctx) => {
14447
14487
  };
14448
14488
 
14449
14489
  // src/application/flows/detect-skills/leaves/propose.ts
14450
- import { join as join26 } from "path";
14490
+ import { join as join28 } from "path";
14451
14491
 
14452
14492
  // src/integration/ai/prompts/detect-skills/definition.ts
14453
14493
  var detectSkillsPromptDef = {
@@ -14511,11 +14551,11 @@ var proposeUseCase = async (deps, input) => {
14511
14551
  skillsConvention: deps.skillsAdapter.describeSkillsConvention()
14512
14552
  });
14513
14553
  if (!prompt.ok) return Result.error(prompt.error);
14514
- const runDir = AbsolutePath.parse(join26(String(deps.runsRoot), "detect-skills", buildRunDirName()));
14554
+ const runDir = AbsolutePath.parse(join28(String(deps.runsRoot), "detect-skills", buildRunDirName()));
14515
14555
  if (!runDir.ok) return Result.error(runDir.error);
14516
- const promptFile = AbsolutePath.parse(join26(String(runDir.value), "prompt.md"));
14556
+ const promptFile = AbsolutePath.parse(join28(String(runDir.value), "prompt.md"));
14517
14557
  if (!promptFile.ok) return Result.error(promptFile.error);
14518
- const bodyFile = AbsolutePath.parse(join26(String(runDir.value), "body.txt"));
14558
+ const bodyFile = AbsolutePath.parse(join28(String(runDir.value), "body.txt"));
14519
14559
  if (!bodyFile.ok) return Result.error(bodyFile.error);
14520
14560
  const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
14521
14561
  if (!promptWrote.ok) return Result.error(promptWrote.error);
@@ -15129,7 +15169,7 @@ var launchDetectSkills = (ctx) => {
15129
15169
  };
15130
15170
 
15131
15171
  // src/application/flows/detect-scripts/leaves/propose.ts
15132
- import { join as join27 } from "path";
15172
+ import { join as join29 } from "path";
15133
15173
 
15134
15174
  // src/integration/ai/prompts/detect-scripts/definition.ts
15135
15175
  var detectScriptsPromptDef = {
@@ -15174,11 +15214,11 @@ var proposeUseCase2 = async (deps, input) => {
15174
15214
  repositoryPath: String(input.repository.path)
15175
15215
  });
15176
15216
  if (!prompt.ok) return Result.error(prompt.error);
15177
- const runDir = AbsolutePath.parse(join27(String(deps.runsRoot), "detect-scripts", buildRunDirName()));
15217
+ const runDir = AbsolutePath.parse(join29(String(deps.runsRoot), "detect-scripts", buildRunDirName()));
15178
15218
  if (!runDir.ok) return Result.error(runDir.error);
15179
- const promptFile = AbsolutePath.parse(join27(String(runDir.value), "prompt.md"));
15219
+ const promptFile = AbsolutePath.parse(join29(String(runDir.value), "prompt.md"));
15180
15220
  if (!promptFile.ok) return Result.error(promptFile.error);
15181
- const bodyFile = AbsolutePath.parse(join27(String(runDir.value), "body.txt"));
15221
+ const bodyFile = AbsolutePath.parse(join29(String(runDir.value), "body.txt"));
15182
15222
  if (!bodyFile.ok) return Result.error(bodyFile.error);
15183
15223
  const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
15184
15224
  if (!promptWrote.ok) return Result.error(promptWrote.error);
@@ -15522,10 +15562,10 @@ var launchDetectScripts = (ctx) => {
15522
15562
  };
15523
15563
 
15524
15564
  // src/application/ui/shared/launch/ideate.ts
15525
- import { join as join29 } from "path";
15565
+ import { join as join31 } from "path";
15526
15566
 
15527
15567
  // src/application/flows/ideate/flow.ts
15528
- import { join as join28 } from "path";
15568
+ import { join as join30 } from "path";
15529
15569
 
15530
15570
  // src/integration/ai/prompts/ideate/definition.ts
15531
15571
  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 +15802,8 @@ var createIdeateFlow = (deps, opts) => {
15762
15802
  parent: () => opts.ideateRoot,
15763
15803
  slug: () => slug,
15764
15804
  write: (ctx, root) => {
15765
- const promptPath = AbsolutePath.parse(join28(String(root), "prompt.md"));
15766
- const outputPath = AbsolutePath.parse(join28(String(root), "ideate.json"));
15805
+ const promptPath = AbsolutePath.parse(join30(String(root), "prompt.md"));
15806
+ const outputPath = AbsolutePath.parse(join30(String(root), "ideate.json"));
15767
15807
  if (!promptPath.ok) throw promptPath.error;
15768
15808
  if (!outputPath.ok) throw outputPath.error;
15769
15809
  return {
@@ -15827,7 +15867,7 @@ var launchIdeate = async (ctx) => {
15827
15867
  const bodyAns = await deps.interactive.askText("Idea description (paste or type)");
15828
15868
  if (!bodyAns.ok) return { ok: false, reason: bodyAns.error.message };
15829
15869
  const ideateRoot = AbsolutePath.parse(
15830
- join29(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "ideate")
15870
+ join31(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "ideate")
15831
15871
  );
15832
15872
  if (!ideateRoot.ok) return { ok: false, reason: ideateRoot.error.message };
15833
15873
  const element = createIdeateFlow(
@@ -17982,30 +18022,23 @@ var SignalLine = ({ signal }) => {
17982
18022
  /* @__PURE__ */ jsx43(Text33, { bold: row.bold ?? false, children: truncate2(row.text, 80) })
17983
18023
  ] });
17984
18024
  };
18025
+ var LEGEND_ENTRIES = [
18026
+ { label: "change", color: inkColors.info, description: "file/code edit" },
18027
+ { label: "learning", color: inkColors.highlight, description: "cross-task insight" },
18028
+ { label: "decision", color: inkColors.highlight, description: "design choice" },
18029
+ { label: "verified", color: inkColors.success, description: "task self-check passed" },
18030
+ { label: "blocked", color: inkColors.error, description: "task self-blocked" },
18031
+ { label: "commit", color: inkColors.info, description: "proposed commit message" }
18032
+ ];
17985
18033
  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
- ] })
18034
+ /* @__PURE__ */ jsx43(Text33, { dimColor: true, bold: true, children: "legend" }),
18035
+ /* @__PURE__ */ jsx43(Box31, { flexDirection: "column", paddingLeft: 2, children: LEGEND_ENTRIES.map((entry) => /* @__PURE__ */ jsxs32(Box31, { children: [
18036
+ /* @__PURE__ */ jsx43(Text33, { color: entry.color, bold: true, children: padLabel2(entry.label) }),
18037
+ /* @__PURE__ */ jsxs32(Text33, { dimColor: true, children: [
18038
+ "= ",
18039
+ entry.description
18040
+ ] })
18041
+ ] }, entry.label)) })
18009
18042
  ] });
18010
18043
  var EvaluationLine = ({ evaluation }) => {
18011
18044
  const color = evaluation.status === "passed" ? inkColors.success : evaluation.status === "failed" ? inkColors.error : inkColors.warning;
@@ -18582,7 +18615,7 @@ var ExecuteView = () => {
18582
18615
  ] })
18583
18616
  ] })
18584
18617
  ] }) });
18585
- const outerFlowFilter = (name) => !isPerTaskLeaf(name);
18618
+ const outerFlowFilter = (name) => !isPerTaskLeaf(name) && !name.startsWith("with-repo-lock(");
18586
18619
  const flowStepsPanel = /* @__PURE__ */ jsx46(
18587
18620
  StepTrace,
18588
18621
  {
@@ -19314,7 +19347,7 @@ var ProbeRow = ({ probe }) => /* @__PURE__ */ jsxs39(Box38, { flexDirection: "co
19314
19347
  // src/application/ui/tui/views/export-context-view.tsx
19315
19348
  import { useCallback as useCallback7, useEffect as useEffect25, useState as useState31 } from "react";
19316
19349
  import { Box as Box39, Text as Text41, useInput as useInput20 } from "ink";
19317
- import { join as join30 } from "path";
19350
+ import { join as join32 } from "path";
19318
19351
 
19319
19352
  // src/business/sprint/views/context-md.ts
19320
19353
  var renderSprintContextMarkdown = (input) => {
@@ -19446,7 +19479,7 @@ var ExportContextView = () => {
19446
19479
  return;
19447
19480
  }
19448
19481
  const outputPath = AbsolutePath.parse(
19449
- join30(String(storage2.dataRoot), "sprints", String(selection.sprintId), "context.md")
19482
+ join32(String(storage2.dataRoot), "sprints", String(selection.sprintId), "context.md")
19450
19483
  );
19451
19484
  if (!outputPath.ok) {
19452
19485
  setRun({ kind: "error", message: outputPath.error.message });
@@ -19504,7 +19537,7 @@ var ExportContextView = () => {
19504
19537
  // src/application/ui/tui/views/export-requirements-view.tsx
19505
19538
  import { useCallback as useCallback8, useEffect as useEffect26, useState as useState32 } from "react";
19506
19539
  import { Box as Box40, Text as Text42, useInput as useInput21 } from "ink";
19507
- import { join as join31 } from "path";
19540
+ import { join as join33 } from "path";
19508
19541
 
19509
19542
  // src/business/sprint/views/requirements-md.ts
19510
19543
  var renderSprintRequirementsMarkdown = (sprint) => {
@@ -19569,7 +19602,7 @@ var ExportRequirementsView = () => {
19569
19602
  return;
19570
19603
  }
19571
19604
  const outputPath = AbsolutePath.parse(
19572
- join31(String(storage2.dataRoot), "sprints", String(selection.sprintId), "requirements.md")
19605
+ join33(String(storage2.dataRoot), "sprints", String(selection.sprintId), "requirements.md")
19573
19606
  );
19574
19607
  if (!outputPath.ok) {
19575
19608
  setRun({ kind: "error", message: outputPath.error.message });
@@ -19904,12 +19937,12 @@ var WelcomeView = () => {
19904
19937
  import { useEffect as useEffect29, useState as useState36 } from "react";
19905
19938
  import { Box as Box44, Text as Text46 } from "ink";
19906
19939
  import { homedir as osHomedir2 } from "os";
19907
- import { basename as basename3, join as join33 } from "path";
19940
+ import { basename as basename3, join as join35 } from "path";
19908
19941
 
19909
19942
  // src/application/ui/tui/prompts/path-picker-prompt.tsx
19910
19943
  import { useEffect as useEffect28, useState as useState35 } from "react";
19911
19944
  import { promises as fs24 } from "fs";
19912
- import { dirname as dirname10, join as join32 } from "path";
19945
+ import { dirname as dirname10, join as join34 } from "path";
19913
19946
  import { homedir } from "os";
19914
19947
  import { Box as Box43, Text as Text45, useInput as useInput23 } from "ink";
19915
19948
  import { jsx as jsx55, jsxs as jsxs44 } from "react/jsx-runtime";
@@ -19917,7 +19950,7 @@ var VISIBLE_ROWS2 = 12;
19917
19950
  var clamp5 = (n, min, max) => Math.max(min, Math.min(max, n));
19918
19951
  var expandHome = (input) => {
19919
19952
  if (input === "~") return homedir();
19920
- if (input.startsWith("~/")) return join32(homedir(), input.slice(2));
19953
+ if (input.startsWith("~/")) return join34(homedir(), input.slice(2));
19921
19954
  return input;
19922
19955
  };
19923
19956
  var PathPickerPrompt = ({
@@ -20005,7 +20038,7 @@ var PathPickerPrompt = ({
20005
20038
  onSubmit(cwd);
20006
20039
  return;
20007
20040
  }
20008
- setCwd(join32(cwd, row.entry.name));
20041
+ setCwd(join34(cwd, row.entry.name));
20009
20042
  setCursor(1);
20010
20043
  }
20011
20044
  },
@@ -20087,7 +20120,7 @@ var labelFor = (row) => {
20087
20120
  import { jsx as jsx56, jsxs as jsxs45 } from "react/jsx-runtime";
20088
20121
  var expandHome2 = (input) => {
20089
20122
  if (input === "~") return osHomedir2();
20090
- if (input.startsWith("~/")) return join33(osHomedir2(), input.slice(2));
20123
+ if (input.startsWith("~/")) return join35(osHomedir2(), input.slice(2));
20091
20124
  return input;
20092
20125
  };
20093
20126
  var backStep = (step) => {
@@ -20303,11 +20336,11 @@ var StepView = ({ step, onChange, onCancel, onSubmit }) => {
20303
20336
  import { useEffect as useEffect30, useState as useState37 } from "react";
20304
20337
  import { Box as Box45, Text as Text47 } from "ink";
20305
20338
  import { homedir as osHomedir3 } from "os";
20306
- import { basename as basename4, join as join34 } from "path";
20339
+ import { basename as basename4, join as join36 } from "path";
20307
20340
  import { jsx as jsx57, jsxs as jsxs46 } from "react/jsx-runtime";
20308
20341
  var expandHome3 = (input) => {
20309
20342
  if (input === "~") return osHomedir3();
20310
- if (input.startsWith("~/")) return join34(osHomedir3(), input.slice(2));
20343
+ if (input.startsWith("~/")) return join36(osHomedir3(), input.slice(2));
20311
20344
  return input;
20312
20345
  };
20313
20346
  var backStep2 = (step) => {
@@ -21138,10 +21171,10 @@ var resolveInitialState = ({
21138
21171
 
21139
21172
  // src/integration/persistence/selection/last-selection-store.ts
21140
21173
  import { promises as fs25 } from "fs";
21141
- import { join as join35 } from "path";
21174
+ import { join as join37 } from "path";
21142
21175
  var FILE_NAME = "last-selection.json";
21143
21176
  var createLastSelectionStore = (stateRoot) => {
21144
- const path = join35(String(stateRoot), FILE_NAME);
21177
+ const path = join37(String(stateRoot), FILE_NAME);
21145
21178
  return {
21146
21179
  async read() {
21147
21180
  try {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "generatedAt": "2026-05-19T05:25:59.916Z",
3
+ "generatedAt": "2026-05-19T19:29:09.887Z",
4
4
  "assets": [
5
5
  "prompts/_partials/harness-context.md",
6
6
  "prompts/_partials/signals-evaluation.md",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralphctl",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Agent harness for long-running AI coding tasks — orchestrates Claude Code & GitHub Copilot across repositories",
5
5
  "homepage": "https://github.com/lukas-grigis/ralphctl",
6
6
  "type": "module",