@neriros/ralphy 2.7.3 → 2.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -105,11 +105,21 @@ Defaults are written to `ralphy.config.json` on first run; CLI flags override co
105
105
  "doneLabel": "ralphy-done",
106
106
  "postComments": true,
107
107
  },
108
+ "useWorktree": true,
109
+ "cleanupWorktreeOnSuccess": false,
110
+ "setupScript": "bun install",
111
+ "teardownScript": "git status",
108
112
  }
109
113
  ```
110
114
 
111
115
  `doneStatus` and `doneLabel` are independent — set either, both, or neither. Use `doneLabel` if your team marks completion via a label rather than a workflow state.
112
116
 
117
+ #### Per-task git worktrees
118
+
119
+ With `--worktree` (or `useWorktree: true` in config) each task runs in an isolated worktree at `.ralph/worktrees/<change-name>` checked out onto a fresh `ralph/<change-name>` branch. The change is scaffolded _inside_ the worktree, and the loop's cwd is the worktree, so concurrent workers can't stomp on each other.
120
+
121
+ Use `setupScript` (run inside the worktree right after scaffolding) to install dependencies, copy `.env`, etc. Use `teardownScript` (run after the loop exits, before any worktree cleanup) to gather artifacts or roll back local mutations. Both run via `sh -c`; failures are logged but never block the loop. With `cleanupWorktreeOnSuccess: true` the worktree is removed when the worker exits 0 — failed workers always keep their worktree (and branch) for human inspection.
122
+
113
123
  Failed workers (non-zero exit) are not marked processed, so they'll be retried on the next poll. SIGINT/SIGTERM cleanly stops polling and kills active workers. All Linear side effects are best-effort — failures log a warning but never block the task loop.
114
124
 
115
125
  ## CLI Options
@@ -133,14 +143,18 @@ Failed workers (non-zero exit) are not marked processed, so they'll be retried o
133
143
 
134
144
  ### Agent mode flags
135
145
 
136
- | Option | Description |
137
- | ------------------------ | -------------------------------------------- |
138
- | `--linear-team <key>` | Linear team key (e.g. `ENG`) |
139
- | `--linear-assignee <id>` | Filter by assignee (user id, email, or `me`) |
140
- | `--linear-status <name>` | Filter by status name (repeatable) |
141
- | `--linear-label <name>` | Filter by label name (repeatable, any-of) |
142
- | `--poll-interval <s>` | Seconds between Linear polls (default: 60) |
143
- | `--concurrency <n>` | Max concurrent task loops (default: 1) |
146
+ | Option | Description |
147
+ | ----------------------------- | --------------------------------------------- |
148
+ | `--linear-team <key>` | Linear team key (e.g. `ENG`) |
149
+ | `--linear-assignee <id>` | Filter by assignee (user id, email, or `me`) |
150
+ | `--linear-status <name>` | Filter by status name (repeatable) |
151
+ | `--linear-label <name>` | Filter by label name (repeatable, any-of) |
152
+ | `--poll-interval <s>` | Seconds between Linear polls (default: 60) |
153
+ | `--concurrency <n>` | Max concurrent task loops (default: 1) |
154
+ | `--worktree` | Run each task in its own git worktree |
155
+ | `--in-progress-status <name>` | Linear status to set when work starts |
156
+ | `--done-status <name>` | Linear status to set on successful completion |
157
+ | `--done-label <name>` | Linear label to add on successful completion |
144
158
 
145
159
  ## OpenSpec Flow
146
160
 
package/dist/cli/index.js CHANGED
@@ -49811,10 +49811,10 @@ var require_axios = __commonJS((exports, module) => {
49811
49811
  } = utils$1;
49812
49812
  var globalFetchAPI = (({
49813
49813
  Request,
49814
- Response
49814
+ Response: Response2
49815
49815
  }) => ({
49816
49816
  Request,
49817
- Response
49817
+ Response: Response2
49818
49818
  }))(utils$1.global);
49819
49819
  var {
49820
49820
  ReadableStream: ReadableStream$1,
@@ -49834,11 +49834,11 @@ var require_axios = __commonJS((exports, module) => {
49834
49834
  const {
49835
49835
  fetch: envFetch,
49836
49836
  Request,
49837
- Response
49837
+ Response: Response2
49838
49838
  } = env3;
49839
49839
  const isFetchSupported = envFetch ? isFunction2(envFetch) : typeof fetch === "function";
49840
49840
  const isRequestSupported = isFunction2(Request);
49841
- const isResponseSupported = isFunction2(Response);
49841
+ const isResponseSupported = isFunction2(Response2);
49842
49842
  if (!isFetchSupported) {
49843
49843
  return false;
49844
49844
  }
@@ -49860,7 +49860,7 @@ var require_axios = __commonJS((exports, module) => {
49860
49860
  }
49861
49861
  return duplexAccessed && !hasContentType;
49862
49862
  });
49863
- const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils$1.isReadableStream(new Response("").body));
49863
+ const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils$1.isReadableStream(new Response2("").body));
49864
49864
  const resolvers = {
49865
49865
  stream: supportsResponseStream && ((res) => res.body)
49866
49866
  };
@@ -49971,7 +49971,7 @@ var require_axios = __commonJS((exports, module) => {
49971
49971
  });
49972
49972
  const responseContentLength = utils$1.toFiniteNumber(response.headers.get("content-length"));
49973
49973
  const [onProgress, flush] = onDownloadProgress && progressEventDecorator(responseContentLength, progressEventReducer(asyncDecorator(onDownloadProgress), true)) || [];
49974
- response = new Response(trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => {
49974
+ response = new Response2(trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => {
49975
49975
  flush && flush();
49976
49976
  unsubscribe && unsubscribe();
49977
49977
  }), options);
@@ -50006,9 +50006,9 @@ var require_axios = __commonJS((exports, module) => {
50006
50006
  const {
50007
50007
  fetch: fetch2,
50008
50008
  Request,
50009
- Response
50009
+ Response: Response2
50010
50010
  } = env3;
50011
- const seeds = [Request, Response, fetch2];
50011
+ const seeds = [Request, Response2, fetch2];
50012
50012
  let len = seeds.length, i = len, seed, target, map2 = seedCache;
50013
50013
  while (i--) {
50014
50014
  seed = seeds[i];
@@ -50548,7 +50548,7 @@ var require_axios = __commonJS((exports, module) => {
50548
50548
  });
50549
50549
 
50550
50550
  // apps/cli/src/index.ts
50551
- import { resolve, join as join15, dirname as dirname4 } from "path";
50551
+ import { resolve, join as join17, dirname as dirname4 } from "path";
50552
50552
  import { exists, mkdir as mkdir3 } from "fs/promises";
50553
50553
 
50554
50554
  // node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
@@ -56152,6 +56152,10 @@ var HELP_TEXT = [
56152
56152
  " --linear-label <name> Filter by label name (repeatable, any-of)",
56153
56153
  " --poll-interval <s> Seconds between Linear polls (default: 60)",
56154
56154
  " --concurrency <n> Max concurrent task loops (default: 1)",
56155
+ " --worktree Run each task in its own git worktree (.ralph/worktrees/<name>)",
56156
+ " --in-progress-status <name> Linear status to set when work starts on an issue",
56157
+ " --done-status <name> Linear status to set when work completes successfully",
56158
+ " --done-label <name> Linear label to add when work completes successfully",
56155
56159
  "",
56156
56160
  " --help, -h Show this help message",
56157
56161
  "",
@@ -56187,7 +56191,11 @@ async function parseArgs(argv) {
56187
56191
  linearStatus: [],
56188
56192
  linearLabel: [],
56189
56193
  pollInterval: 60,
56190
- concurrency: 1
56194
+ concurrency: 1,
56195
+ worktree: false,
56196
+ inProgressStatus: "",
56197
+ doneStatus: "",
56198
+ doneLabel: ""
56191
56199
  };
56192
56200
  let expectModel = false;
56193
56201
  let expectModelFlag = false;
@@ -56207,6 +56215,9 @@ async function parseArgs(argv) {
56207
56215
  let expectLinearLabel = false;
56208
56216
  let expectPollInterval = false;
56209
56217
  let expectConcurrency = false;
56218
+ let expectInProgressStatus = false;
56219
+ let expectDoneStatus = false;
56220
+ let expectDoneLabel = false;
56210
56221
  for (const arg of argv) {
56211
56222
  if (expectModel) {
56212
56223
  if (VALID_MODELS.has(arg)) {
@@ -56302,6 +56313,21 @@ async function parseArgs(argv) {
56302
56313
  expectConcurrency = false;
56303
56314
  continue;
56304
56315
  }
56316
+ if (expectInProgressStatus) {
56317
+ result2.inProgressStatus = arg;
56318
+ expectInProgressStatus = false;
56319
+ continue;
56320
+ }
56321
+ if (expectDoneStatus) {
56322
+ result2.doneStatus = arg;
56323
+ expectDoneStatus = false;
56324
+ continue;
56325
+ }
56326
+ if (expectDoneLabel) {
56327
+ result2.doneLabel = arg;
56328
+ expectDoneLabel = false;
56329
+ continue;
56330
+ }
56305
56331
  switch (arg) {
56306
56332
  case "--claude":
56307
56333
  if (result2.engineSet && result2.engine !== "claude") {
@@ -56378,6 +56404,18 @@ async function parseArgs(argv) {
56378
56404
  case "--concurrency":
56379
56405
  expectConcurrency = true;
56380
56406
  break;
56407
+ case "--worktree":
56408
+ result2.worktree = true;
56409
+ break;
56410
+ case "--in-progress-status":
56411
+ expectInProgressStatus = true;
56412
+ break;
56413
+ case "--done-status":
56414
+ expectDoneStatus = true;
56415
+ break;
56416
+ case "--done-label":
56417
+ expectDoneLabel = true;
56418
+ break;
56381
56419
  default:
56382
56420
  if (VALID_MODES.has(arg)) {
56383
56421
  result2.mode = arg;
@@ -56445,7 +56483,7 @@ function createDefaultContext() {
56445
56483
 
56446
56484
  // apps/cli/src/components/App.tsx
56447
56485
  var import_react58 = __toESM(require_react(), 1);
56448
- import { join as join14 } from "path";
56486
+ import { join as join16 } from "path";
56449
56487
 
56450
56488
  // packages/core/src/state.ts
56451
56489
  import { join as join2 } from "path";
@@ -69801,6 +69839,10 @@ var RalphyConfigSchema = exports_external.object({
69801
69839
  pollIntervalSeconds: exports_external.number().int().positive().default(60),
69802
69840
  maxIterationsPerTask: exports_external.number().int().nonnegative().default(0),
69803
69841
  maxCostUsdPerTask: exports_external.number().nonnegative().default(0),
69842
+ useWorktree: exports_external.boolean().default(false),
69843
+ cleanupWorktreeOnSuccess: exports_external.boolean().default(false),
69844
+ setupScript: exports_external.string().optional(),
69845
+ teardownScript: exports_external.string().optional(),
69804
69846
  engine: exports_external.enum(["claude", "codex"]).default("claude"),
69805
69847
  model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
69806
69848
  linear: exports_external.object({
@@ -70026,8 +70068,55 @@ class AgentCoordinator {
70026
70068
  }
70027
70069
  }
70028
70070
 
70071
+ // apps/cli/src/agent/worktree.ts
70072
+ import { join as join13 } from "path";
70073
+ function worktreesDir(projectRoot) {
70074
+ return join13(projectRoot, ".ralph", "worktrees");
70075
+ }
70076
+ function branchForChange(changeName) {
70077
+ return `ralph/${changeName}`;
70078
+ }
70079
+ async function createWorktree(projectRoot, changeName, runner) {
70080
+ const dir = worktreesDir(projectRoot);
70081
+ const cwd2 = join13(dir, changeName);
70082
+ const branch = branchForChange(changeName);
70083
+ const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
70084
+ if (list.stdout.includes(`worktree ${cwd2}
70085
+ `)) {
70086
+ return { cwd: cwd2, branch };
70087
+ }
70088
+ let branchExists = true;
70089
+ try {
70090
+ await runner.run(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], projectRoot);
70091
+ } catch {
70092
+ branchExists = false;
70093
+ }
70094
+ const cmd = branchExists ? ["worktree", "add", cwd2, branch] : ["worktree", "add", "-b", branch, cwd2];
70095
+ await runner.run(cmd, projectRoot);
70096
+ return { cwd: cwd2, branch };
70097
+ }
70098
+ async function removeWorktree(projectRoot, cwd2, runner) {
70099
+ await runner.run(["worktree", "remove", "--force", cwd2], projectRoot);
70100
+ }
70101
+
70029
70102
  // apps/cli/src/components/AgentMode.tsx
70030
70103
  var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
70104
+ import { join as join14 } from "path";
70105
+ var bunGitRunner = {
70106
+ run: async (args, cwd2) => {
70107
+ const proc = Bun.spawn({ cmd: ["git", ...args], cwd: cwd2, stdout: "pipe", stderr: "pipe" });
70108
+ const stdout = await new Response(proc.stdout).text();
70109
+ const stderr = await new Response(proc.stderr).text();
70110
+ const code = await proc.exited;
70111
+ if (code !== 0) {
70112
+ const err = new Error("git command failed");
70113
+ err.stderr = stderr;
70114
+ err.code = code;
70115
+ throw err;
70116
+ }
70117
+ return { stdout, stderr };
70118
+ }
70119
+ };
70031
70120
  var lineCounter = 0;
70032
70121
  function nextId() {
70033
70122
  lineCounter += 1;
@@ -70066,6 +70155,24 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70066
70155
  const stateCache = new Map;
70067
70156
  const labelCache = new Map;
70068
70157
  const teamKeyOf = (issue) => issue.identifier.split("-")[0];
70158
+ const useWorktree = args.worktree || cfg.useWorktree;
70159
+ const cwdByChange = new Map;
70160
+ async function runScript(label, cmd, cwd2) {
70161
+ appendLog(` ${label}: ${cmd}`, "gray");
70162
+ const proc = Bun.spawn({
70163
+ cmd: ["sh", "-c", cmd],
70164
+ cwd: cwd2,
70165
+ stdout: "ignore",
70166
+ stderr: "pipe",
70167
+ stdin: "ignore"
70168
+ });
70169
+ const code = await proc.exited;
70170
+ if (code !== 0) {
70171
+ const stderr = await new Response(proc.stderr).text();
70172
+ appendLog(`! ${label} exited code ${code}${stderr ? `: ${stderr.trim().split(`
70173
+ `)[0]}` : ""}`, "yellow");
70174
+ }
70175
+ }
70069
70176
  const coord2 = new AgentCoordinator({
70070
70177
  fetchIssues: (f2) => fetchOpenIssues(apiKey, f2),
70071
70178
  scaffold: async (issue) => {
@@ -70075,7 +70182,27 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70075
70182
  } catch (err) {
70076
70183
  appendLog(`! Linear comment fetch failed for ${issue.identifier}: ${err.message}`, "yellow");
70077
70184
  }
70078
- return scaffoldChangeForIssue(tasksDir, statesDir, issue, comments);
70185
+ let workerCwd = projectRoot;
70186
+ let scaffoldTasksDir = tasksDir;
70187
+ let scaffoldStatesDir = statesDir;
70188
+ const probeName = issue.identifier.toLowerCase();
70189
+ if (useWorktree) {
70190
+ try {
70191
+ const wt = await createWorktree(projectRoot, probeName, bunGitRunner);
70192
+ workerCwd = wt.cwd;
70193
+ scaffoldTasksDir = join14(wt.cwd, "openspec", "changes");
70194
+ scaffoldStatesDir = join14(wt.cwd, ".ralph", "tasks");
70195
+ appendLog(` ${issue.identifier} worktree: ${wt.cwd} (${wt.branch})`, "gray");
70196
+ } catch (err) {
70197
+ appendLog(`! worktree create failed for ${issue.identifier}: ${err.message} \u2014 falling back to project root`, "yellow");
70198
+ }
70199
+ }
70200
+ const changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue, comments);
70201
+ cwdByChange.set(changeName, workerCwd);
70202
+ if (cfg.setupScript) {
70203
+ await runScript("setup", cfg.setupScript, workerCwd);
70204
+ }
70205
+ return changeName;
70079
70206
  },
70080
70207
  spawnWorker: (changeName) => {
70081
70208
  const cmd = [
@@ -70093,14 +70220,35 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70093
70220
  const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
70094
70221
  if (maxCost > 0)
70095
70222
  cmd.push("--max-cost", String(maxCost));
70223
+ const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
70096
70224
  const proc = Bun.spawn({
70097
70225
  cmd,
70098
- cwd: projectRoot,
70226
+ cwd: cwd2,
70099
70227
  stdout: "ignore",
70100
70228
  stderr: "ignore",
70101
70229
  stdin: "ignore"
70102
70230
  });
70103
- return { exited: proc.exited, kill: () => proc.kill() };
70231
+ const wrapped = proc.exited.then(async (code) => {
70232
+ if (cfg.teardownScript) {
70233
+ try {
70234
+ await runScript("teardown", cfg.teardownScript, cwd2);
70235
+ } catch {}
70236
+ }
70237
+ if (useWorktree && cwd2 !== projectRoot) {
70238
+ const ok = code === 0;
70239
+ if (ok && cfg.cleanupWorktreeOnSuccess) {
70240
+ try {
70241
+ await removeWorktree(projectRoot, cwd2, bunGitRunner);
70242
+ appendLog(` removed worktree ${cwd2}`, "gray");
70243
+ } catch (err) {
70244
+ appendLog(`! worktree remove failed for ${changeName}: ${err.message}`, "yellow");
70245
+ }
70246
+ }
70247
+ }
70248
+ cwdByChange.delete(changeName);
70249
+ return code;
70250
+ });
70251
+ return { exited: wrapped, kill: () => proc.kill() };
70104
70252
  },
70105
70253
  loadState: () => readAgentState(projectRoot),
70106
70254
  saveState: (s) => writeAgentState(projectRoot, s),
@@ -70134,9 +70282,9 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70134
70282
  }, {
70135
70283
  concurrency,
70136
70284
  filter: filter2,
70137
- inProgressStatus: cfg.linear.inProgressStatus,
70138
- doneStatus: cfg.linear.doneStatus,
70139
- doneLabel: cfg.linear.doneLabel,
70285
+ inProgressStatus: args.inProgressStatus || cfg.linear.inProgressStatus,
70286
+ doneStatus: args.doneStatus || cfg.linear.doneStatus,
70287
+ doneLabel: args.doneLabel || cfg.linear.doneLabel,
70140
70288
  postComments: cfg.linear.postComments
70141
70289
  });
70142
70290
  coordRef.current = coord2;
@@ -70218,11 +70366,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70218
70366
  }
70219
70367
 
70220
70368
  // packages/openspec/src/openspec-change-store.ts
70221
- import { join as join13, dirname as dirname3 } from "path";
70369
+ import { join as join15, dirname as dirname3 } from "path";
70222
70370
  import { readdir, mkdir as mkdir2 } from "fs/promises";
70223
70371
  function resolveOpenspecBin() {
70224
70372
  const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
70225
- return join13(dirname3(pkgJsonPath), "bin", "openspec.js");
70373
+ return join15(dirname3(pkgJsonPath), "bin", "openspec.js");
70226
70374
  }
70227
70375
  function runOpenspec(args, options = {}) {
70228
70376
  const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
@@ -70248,7 +70396,7 @@ class OpenSpecChangeStore {
70248
70396
  }
70249
70397
  }
70250
70398
  getChangeDirectory(name) {
70251
- return join13("openspec", "changes", name);
70399
+ return join15("openspec", "changes", name);
70252
70400
  }
70253
70401
  async listChanges() {
70254
70402
  const result2 = runOpenspec(["list", "--json"]);
@@ -70262,7 +70410,7 @@ class OpenSpecChangeStore {
70262
70410
  }
70263
70411
  } catch {}
70264
70412
  }
70265
- const changesDir = join13("openspec", "changes");
70413
+ const changesDir = join15("openspec", "changes");
70266
70414
  if (!await Bun.file(changesDir).exists())
70267
70415
  return [];
70268
70416
  try {
@@ -70273,18 +70421,18 @@ class OpenSpecChangeStore {
70273
70421
  }
70274
70422
  }
70275
70423
  async readTaskList(name) {
70276
- const file = Bun.file(join13("openspec", "changes", name, "tasks.md"));
70424
+ const file = Bun.file(join15("openspec", "changes", name, "tasks.md"));
70277
70425
  if (!await file.exists())
70278
70426
  return "";
70279
70427
  return await file.text();
70280
70428
  }
70281
70429
  async writeTaskList(name, content) {
70282
- const path = join13("openspec", "changes", name, "tasks.md");
70430
+ const path = join15("openspec", "changes", name, "tasks.md");
70283
70431
  await mkdir2(dirname3(path), { recursive: true });
70284
70432
  await Bun.write(path, content);
70285
70433
  }
70286
70434
  async appendSteering(name, message) {
70287
- const path = join13("openspec", "changes", name, "steering.md");
70435
+ const path = join15("openspec", "changes", name, "steering.md");
70288
70436
  const file = Bun.file(path);
70289
70437
  const existing = await file.exists() ? await file.text() : null;
70290
70438
  const updated = existing ? `${message}
@@ -70295,7 +70443,7 @@ ${existing.trimStart()}` : `${message}
70295
70443
  await Bun.write(path, updated);
70296
70444
  }
70297
70445
  async readSection(name, artifact, heading) {
70298
- const file = Bun.file(join13("openspec", "changes", name, artifact));
70446
+ const file = Bun.file(join15("openspec", "changes", name, artifact));
70299
70447
  if (!await file.exists())
70300
70448
  return "";
70301
70449
  const content = await file.text();
@@ -70376,8 +70524,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
70376
70524
  message: "Error: --name is required for status mode"
70377
70525
  }, undefined, false, undefined, this);
70378
70526
  }
70379
- const stateDir = join14(statesDir, args.name);
70380
- if (getStorage().read(join14(stateDir, ".ralph-state.json")) === null) {
70527
+ const stateDir = join16(statesDir, args.name);
70528
+ if (getStorage().read(join16(stateDir, ".ralph-state.json")) === null) {
70381
70529
  return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
70382
70530
  message: `Error: change '${args.name}' not found`
70383
70531
  }, undefined, false, undefined, this);
@@ -70434,7 +70582,7 @@ if (typeof globalThis.Bun === "undefined") {
70434
70582
  async function findProjectRoot() {
70435
70583
  let dir = process.cwd();
70436
70584
  while (dir !== "/") {
70437
- if (await exists(join15(dir, "openspec")))
70585
+ if (await exists(join17(dir, "openspec")))
70438
70586
  return dir;
70439
70587
  dir = resolve(dir, "..");
70440
70588
  }
@@ -70469,11 +70617,11 @@ try {
70469
70617
  capture("command_run", { mode: args.mode, engine: args.engine, model: args.model });
70470
70618
  try {
70471
70619
  const projectRoot = await findProjectRoot();
70472
- const statesDir = join15(projectRoot, ".ralph", "tasks");
70473
- const tasksDir = join15(projectRoot, "openspec", "changes");
70620
+ const statesDir = join17(projectRoot, ".ralph", "tasks");
70621
+ const tasksDir = join17(projectRoot, "openspec", "changes");
70474
70622
  if (args.mode === "init") {
70475
70623
  await mkdir3(statesDir, { recursive: true });
70476
- const openspecBin = join15(dirname4(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
70624
+ const openspecBin = join17(dirname4(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
70477
70625
  Bun.spawnSync({
70478
70626
  cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
70479
70627
  stdio: ["inherit", "inherit", "inherit"],
@@ -70481,13 +70629,13 @@ try {
70481
70629
  });
70482
70630
  }
70483
70631
  if (args.mode === "task" && args.name) {
70484
- await mkdir3(join15(statesDir, args.name), { recursive: true });
70485
- await mkdir3(join15(tasksDir, args.name), { recursive: true });
70632
+ await mkdir3(join17(statesDir, args.name), { recursive: true });
70633
+ await mkdir3(join17(tasksDir, args.name), { recursive: true });
70486
70634
  }
70487
70635
  if (args.mode === "agent") {
70488
70636
  await mkdir3(statesDir, { recursive: true });
70489
70637
  await mkdir3(tasksDir, { recursive: true });
70490
- await mkdir3(join15(projectRoot, ".ralph"), { recursive: true });
70638
+ await mkdir3(join17(projectRoot, ".ralph"), { recursive: true });
70491
70639
  }
70492
70640
  await runWithContext(createDefaultContext(), async () => {
70493
70641
  const { waitUntilExit } = render_default(import_react59.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.7.3",
3
+ "version": "2.7.5",
4
4
  "description": "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
5
5
  "keywords": [
6
6
  "agent",