@neriros/ralphy 2.7.9 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +267 -60
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -50549,7 +50549,7 @@ var require_axios = __commonJS((exports, module) => {
50549
50549
 
50550
50550
  // apps/cli/src/index.ts
50551
50551
  import { resolve, join as join17, dirname as dirname4 } from "path";
50552
- import { exists, mkdir as mkdir3 } from "fs/promises";
50552
+ import { exists, mkdir as mkdir3, rm } from "fs/promises";
50553
50553
 
50554
50554
  // node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
50555
50555
  import { Stream } from "stream";
@@ -56117,7 +56117,7 @@ function log(msg) {
56117
56117
  }
56118
56118
 
56119
56119
  // apps/cli/src/cli.ts
56120
- var VALID_MODES = new Set(["task", "list", "status", "init", "agent"]);
56120
+ var VALID_MODES = new Set(["task", "list", "status", "init", "agent", "clean"]);
56121
56121
  var VALID_MODELS = new Set(["haiku", "sonnet", "opus"]);
56122
56122
  var HELP_TEXT = [
56123
56123
  "Usage: ralph <command> [options]",
@@ -56128,6 +56128,7 @@ var HELP_TEXT = [
56128
56128
  " status Show detailed change status",
56129
56129
  " init Initialize OpenSpec in current directory",
56130
56130
  " agent Poll Linear for new tasks and run loops concurrently",
56131
+ " clean Remove worktree, branch, openspec change, and task state for --name",
56131
56132
  "",
56132
56133
  "Options:",
56133
56134
  " --name <name> Change name (required for most commands)",
@@ -60611,54 +60612,70 @@ function countTaskItems(content) {
60611
60612
  const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
60612
60613
  return { checked, unchecked };
60613
60614
  }
60614
- function buildRows(statesDir) {
60615
+ function buildRows(statesDir, projectRoot) {
60615
60616
  const storage = getStorage();
60616
- const entries = storage.list(statesDir);
60617
60617
  const rows = [];
60618
- for (const entry of entries) {
60619
- const raw = storage.read(join3(statesDir, entry, ".ralph-state.json"));
60620
- if (raw === null)
60621
- continue;
60622
- let state;
60623
- try {
60624
- state = JSON.parse(raw);
60625
- } catch {
60626
- continue;
60618
+ const seenNames = new Set;
60619
+ const sources = [{ dir: statesDir, label: "main" }];
60620
+ if (projectRoot) {
60621
+ const worktreesRoot = join3(projectRoot, ".ralph", "worktrees");
60622
+ for (const wt of storage.list(worktreesRoot)) {
60623
+ sources.push({
60624
+ dir: join3(worktreesRoot, wt, ".ralph", "tasks"),
60625
+ label: `wt:${wt}`
60626
+ });
60627
60627
  }
60628
- if (String(state.status ?? "") === "completed")
60629
- continue;
60630
- const promptRaw = String(state.prompt ?? "");
60631
- const firstLine = promptRaw.split(`
60628
+ }
60629
+ for (const { dir, label } of sources) {
60630
+ for (const entry of storage.list(dir)) {
60631
+ if (seenNames.has(entry))
60632
+ continue;
60633
+ const raw = storage.read(join3(dir, entry, ".ralph-state.json"));
60634
+ if (raw === null)
60635
+ continue;
60636
+ let state;
60637
+ try {
60638
+ state = JSON.parse(raw);
60639
+ } catch {
60640
+ continue;
60641
+ }
60642
+ if (String(state.status ?? "") === "completed")
60643
+ continue;
60644
+ const promptRaw = String(state.prompt ?? "");
60645
+ const firstLine = promptRaw.split(`
60632
60646
  `).find((l) => l.trim() !== "") ?? "";
60633
- let progress = "\u2014";
60634
- let progressStyled = true;
60635
- const tasksContent = storage.read(join3(statesDir, entry, "tasks.md"));
60636
- if (tasksContent !== null) {
60637
- const { checked, unchecked } = countTaskItems(tasksContent);
60638
- const total = checked + unchecked;
60639
- if (total > 0) {
60640
- progress = `${checked}/${total}`;
60641
- progressStyled = false;
60642
- }
60643
- }
60644
- rows.push({
60645
- name: String(state.name ?? entry),
60646
- phase: String(state.status ?? "active"),
60647
- status: String(state.status ?? "unknown"),
60648
- iters: String(state.iteration ?? 0),
60649
- progress,
60650
- progressStyled,
60651
- prompt: firstLine.replace(/^#+\s*/, "").trim().slice(0, 60)
60652
- });
60647
+ let progress = "\u2014";
60648
+ let progressStyled = true;
60649
+ const tasksContent = storage.read(join3(dir, entry, "tasks.md"));
60650
+ if (tasksContent !== null) {
60651
+ const { checked, unchecked } = countTaskItems(tasksContent);
60652
+ const total = checked + unchecked;
60653
+ if (total > 0) {
60654
+ progress = `${checked}/${total}`;
60655
+ progressStyled = false;
60656
+ }
60657
+ }
60658
+ seenNames.add(entry);
60659
+ rows.push({
60660
+ name: String(state.name ?? entry),
60661
+ phase: String(state.status ?? "active"),
60662
+ status: String(state.status ?? "unknown"),
60663
+ iters: String(state.iteration ?? 0),
60664
+ progress,
60665
+ progressStyled,
60666
+ prompt: firstLine.replace(/^#+\s*/, "").trim().slice(0, 60),
60667
+ source: label
60668
+ });
60669
+ }
60653
60670
  }
60654
60671
  return rows;
60655
60672
  }
60656
- function TaskList({ statesDir }) {
60673
+ function TaskList({ statesDir, projectRoot }) {
60657
60674
  const { exit } = use_app_default();
60658
60675
  import_react22.useEffect(() => {
60659
60676
  exit();
60660
60677
  }, [exit]);
60661
- const rows = buildRows(statesDir);
60678
+ const rows = buildRows(statesDir, projectRoot);
60662
60679
  if (rows.length === 0) {
60663
60680
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
60664
60681
  flexDirection: "column",
@@ -60681,9 +60698,10 @@ function TaskList({ statesDir }) {
60681
60698
  phase: Math.max(5, ...rows.map((r) => r.phase.length)),
60682
60699
  status: Math.max(6, ...rows.map((r) => r.status.length)),
60683
60700
  iters: 5,
60684
- progress: 8
60701
+ progress: 8,
60702
+ source: Math.max(6, ...rows.map((r) => r.source.length))
60685
60703
  };
60686
- const ruleWidth = cols.name + cols.phase + cols.status + cols.iters + cols.progress + 60 + 10;
60704
+ const ruleWidth = cols.name + cols.phase + cols.status + cols.iters + cols.progress + cols.source + 60 + 12;
60687
60705
  return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
60688
60706
  flexDirection: "column",
60689
60707
  children: [
@@ -60717,6 +60735,11 @@ function TaskList({ statesDir }) {
60717
60735
  children: "Progress".padEnd(cols.progress)
60718
60736
  }, undefined, false, undefined, this),
60719
60737
  " ",
60738
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
60739
+ bold: true,
60740
+ children: "Source".padEnd(cols.source)
60741
+ }, undefined, false, undefined, this),
60742
+ " ",
60720
60743
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
60721
60744
  bold: true,
60722
60745
  children: "Description"
@@ -60745,6 +60768,11 @@ function TaskList({ statesDir }) {
60745
60768
  children: row.progress.padStart(cols.progress)
60746
60769
  }, undefined, false, undefined, this) : row.progress.padStart(cols.progress),
60747
60770
  " ",
60771
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
60772
+ dimColor: true,
60773
+ children: row.source.padEnd(cols.source)
60774
+ }, undefined, false, undefined, this),
60775
+ " ",
60748
60776
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
60749
60777
  dimColor: true,
60750
60778
  children: row.prompt
@@ -69758,7 +69786,12 @@ async function addLabelToIssue(apiKey, issueId, labelId) {
69758
69786
  import { join as join10 } from "path";
69759
69787
  var AgentStateSchema = exports_external.object({
69760
69788
  processedIssueIds: exports_external.array(exports_external.string()).default([]),
69761
- lastPollAt: exports_external.string().nullable().default(null)
69789
+ startedIssueIds: exports_external.array(exports_external.string()).default([]),
69790
+ lastPollAt: exports_external.string().nullable().default(null),
69791
+ changeMeta: exports_external.record(exports_external.string(), exports_external.object({
69792
+ issueId: exports_external.string(),
69793
+ identifier: exports_external.string()
69794
+ })).default({})
69762
69795
  });
69763
69796
  function statePath(projectRoot) {
69764
69797
  return join10(projectRoot, ".ralph", "agent-state.json");
@@ -70049,9 +70082,14 @@ class AgentCoordinator {
70049
70082
  const updater = this.deps.updater;
70050
70083
  if (!updater)
70051
70084
  return;
70052
- if (this.opts.postComments !== false) {
70085
+ const alreadyStarted = this.state?.startedIssueIds.includes(issue.id) ?? false;
70086
+ if (this.opts.postComments !== false && !alreadyStarted) {
70053
70087
  try {
70054
70088
  await updater.postComment(issue, `\uD83E\uDD16 Ralph started working on this issue. Tracking change: \`${changeName}\``);
70089
+ if (this.state && !this.state.startedIssueIds.includes(issue.id)) {
70090
+ this.state.startedIssueIds.push(issue.id);
70091
+ await this.deps.saveState(this.state);
70092
+ }
70055
70093
  } catch (err) {
70056
70094
  this.deps.onLog(`! Linear comment failed for ${issue.identifier}: ${err.message}`, "red");
70057
70095
  }
@@ -70299,7 +70337,10 @@ var bunCmdRunner = {
70299
70337
  const stderr = await new Response(proc.stderr).text();
70300
70338
  const code = await proc.exited;
70301
70339
  if (code !== 0) {
70302
- const err = new Error(`command \`${cmd[0]}\` failed`);
70340
+ const firstStderrLine = stderr.trim().split(`
70341
+ `)[0] ?? "";
70342
+ const summary = firstStderrLine ? `: ${firstStderrLine}` : "";
70343
+ const err = new Error(`\`${cmd.join(" ")}\` exited ${code}${summary}`);
70303
70344
  err.stderr = stderr;
70304
70345
  err.code = code;
70305
70346
  throw err;
@@ -70312,11 +70353,28 @@ function nextId() {
70312
70353
  lineCounter += 1;
70313
70354
  return `${Date.now()}-${lineCounter}`;
70314
70355
  }
70356
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
70357
+ function fmtElapsed(ms) {
70358
+ const s = Math.floor(ms / 1000);
70359
+ if (s < 60)
70360
+ return `${s}s`;
70361
+ const m = Math.floor(s / 60);
70362
+ const rem = s % 60;
70363
+ if (m < 60)
70364
+ return `${m}m${rem.toString().padStart(2, "0")}s`;
70365
+ const h = Math.floor(m / 60);
70366
+ return `${h}h${(m % 60).toString().padStart(2, "0")}m`;
70367
+ }
70315
70368
  function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70316
70369
  const { exit } = use_app_default();
70317
70370
  const [logs, setLogs] = import_react57.useState([]);
70318
70371
  const [, setTick] = import_react57.useState(0);
70372
+ const [clock, setClock] = import_react57.useState(0);
70319
70373
  const coordRef = import_react57.useRef(null);
70374
+ const workerMetaRef = import_react57.useRef(new Map);
70375
+ const nextPollAtRef = import_react57.useRef(0);
70376
+ const pollIntervalRef = import_react57.useRef(0);
70377
+ const [pollStatus, setPollStatus] = import_react57.useState({ state: "idle", lastFound: null, lastAdded: null, lastAt: null, filterDesc: "" });
70320
70378
  function appendLog(text, color) {
70321
70379
  setLogs((prev) => [...prev, { id: nextId(), text, color }]);
70322
70380
  }
@@ -70329,6 +70387,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70329
70387
  appendLog(`agent mode \u2014 config: ${cfgPath}`, "gray");
70330
70388
  const concurrency = args.concurrency || cfg.concurrency;
70331
70389
  const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
70390
+ pollIntervalRef.current = pollInterval;
70332
70391
  appendLog(`concurrency=${concurrency} pollInterval=${pollInterval}s`, "gray");
70333
70392
  const apiKey = process.env["LINEAR_API_KEY"];
70334
70393
  if (!apiKey) {
@@ -70402,6 +70461,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70402
70461
  issueByChange.set(changeName, issue);
70403
70462
  if (workerBranch)
70404
70463
  branchByChange.set(changeName, workerBranch);
70464
+ try {
70465
+ const s = await readAgentState(projectRoot);
70466
+ s.changeMeta[changeName] = { issueId: issue.id, identifier: issue.identifier };
70467
+ await writeAgentState(projectRoot, s);
70468
+ } catch (err) {
70469
+ appendLog(`! failed to record agent meta for ${changeName}: ${err.message}`, "yellow");
70470
+ }
70405
70471
  if (cfg.setupScript) {
70406
70472
  await runScript("setup", cfg.setupScript, workerCwd);
70407
70473
  }
@@ -70447,8 +70513,14 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70447
70513
  stderr: "ignore",
70448
70514
  stdin: "ignore"
70449
70515
  });
70516
+ workerMetaRef.current.set(changeName, {
70517
+ startedAt: Date.now(),
70518
+ statesDir: statesDirByChange.get(changeName) ?? statesDir,
70519
+ iter: 0
70520
+ });
70450
70521
  const wantPr = args.createPr || cfg.createPrOnSuccess;
70451
70522
  const CI_FAILED_EXIT = 70;
70523
+ const PR_FAILED_EXIT = 71;
70452
70524
  const wrapped = proc.exited.then(async (code) => {
70453
70525
  if (cfg.teardownScript) {
70454
70526
  try {
@@ -70462,6 +70534,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70462
70534
  const prIssue = issueByChange.get(changeName);
70463
70535
  if (!branch || !prIssue) {
70464
70536
  appendLog(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
70537
+ effectiveCode = PR_FAILED_EXIT;
70465
70538
  } else {
70466
70539
  try {
70467
70540
  const pr = await createPullRequest({ cwd: cwd2, branch, issue: prIssue, base: cfg.prBaseBranch }, bunCmdRunner);
@@ -70521,7 +70594,10 @@ ${stamped}
70521
70594
  }
70522
70595
  }
70523
70596
  } catch (err) {
70524
- appendLog(`! PR create failed for ${changeName}: ${err.message}`, "red");
70597
+ const e = err;
70598
+ const detail = e.stderr?.trim() || e.message;
70599
+ appendLog(`! PR create failed for ${changeName}: ${detail}`, "red");
70600
+ effectiveCode = PR_FAILED_EXIT;
70525
70601
  }
70526
70602
  }
70527
70603
  }
@@ -70539,6 +70615,7 @@ ${stamped}
70539
70615
  statesDirByChange.delete(changeName);
70540
70616
  branchByChange.delete(changeName);
70541
70617
  issueByChange.delete(changeName);
70618
+ workerMetaRef.current.delete(changeName);
70542
70619
  return effectiveCode;
70543
70620
  });
70544
70621
  return { exited: wrapped, kill: () => proc.kill() };
@@ -70591,15 +70668,25 @@ ${stamped}
70591
70668
  });
70592
70669
  coordRef.current = coord2;
70593
70670
  await coord2.init();
70671
+ const filterDesc = `team=${filter2.team ?? "*"}, assignee=${filter2.assignee ?? "*"}, statuses=${filter2.statuses?.length ? filter2.statuses.join(",") : "open"}${filter2.labels?.length ? `, labels=${filter2.labels.join(",")}` : ""}`;
70594
70672
  const tick = async () => {
70595
70673
  if (cancelled)
70596
70674
  return;
70597
- const filterDesc = `team=${filter2.team ?? "*"}, assignee=${filter2.assignee ?? "*"}, statuses=${filter2.statuses?.length ? filter2.statuses.join(",") : "open"}${filter2.labels?.length ? `, labels=${filter2.labels.join(",")}` : ""}`;
70598
- appendLog(`\u2026 polling Linear (${filterDesc})`);
70675
+ setPollStatus((p) => ({ ...p, state: "polling", filterDesc }));
70599
70676
  const { found, added } = await coord2.pollOnce();
70600
- appendLog(` found ${found} open, ${added} new (queue=${coord2.queuedCount})`);
70601
70677
  if (cancelled)
70602
70678
  return;
70679
+ if (added > 0) {
70680
+ appendLog(` ${added} new issue${added === 1 ? "" : "s"} queued (found ${found} open)`);
70681
+ }
70682
+ setPollStatus({
70683
+ state: "idle",
70684
+ lastFound: found,
70685
+ lastAdded: added,
70686
+ lastAt: Date.now(),
70687
+ filterDesc
70688
+ });
70689
+ nextPollAtRef.current = Date.now() + pollInterval * 1000;
70603
70690
  pollTimer = setTimeout(tick, pollInterval * 1000);
70604
70691
  };
70605
70692
  tick();
@@ -70624,7 +70711,34 @@ ${stamped}
70624
70711
  process.off("SIGTERM", onSig);
70625
70712
  };
70626
70713
  }, []);
70714
+ import_react57.useEffect(() => {
70715
+ let cancelled = false;
70716
+ const interval = setInterval(() => {
70717
+ if (cancelled)
70718
+ return;
70719
+ (async () => {
70720
+ for (const [changeName, meta] of workerMetaRef.current) {
70721
+ try {
70722
+ const file = Bun.file(join14(meta.statesDir, changeName, ".ralph-state.json"));
70723
+ if (await file.exists()) {
70724
+ const json = await file.json();
70725
+ meta.iter = json.iteration ?? meta.iter;
70726
+ }
70727
+ } catch {}
70728
+ }
70729
+ if (!cancelled)
70730
+ setClock((c) => c + 1);
70731
+ })();
70732
+ }, 1000);
70733
+ return () => {
70734
+ cancelled = true;
70735
+ clearInterval(interval);
70736
+ };
70737
+ }, []);
70627
70738
  const coord = coordRef.current;
70739
+ const spinnerFrame = SPINNER_FRAMES[clock % SPINNER_FRAMES.length];
70740
+ const now2 = Date.now();
70741
+ const secsToNextPoll = nextPollAtRef.current ? Math.max(0, Math.ceil((nextPollAtRef.current - now2) / 1000)) : null;
70628
70742
  return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
70629
70743
  flexDirection: "column",
70630
70744
  children: [
@@ -70644,23 +70758,41 @@ ${stamped}
70644
70758
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
70645
70759
  dimColor: true,
70646
70760
  children: [
70761
+ spinnerFrame,
70762
+ " ",
70763
+ pollStatus.state === "polling" ? `polling Linear (${pollStatus.filterDesc})` : pollStatus.lastAt !== null ? `last poll: ${pollStatus.lastFound} open, ${pollStatus.lastAdded} new${secsToNextPoll !== null ? ` \xB7 next in ${secsToNextPoll}s` : ""}` : "starting\u2026"
70764
+ ]
70765
+ }, undefined, true, undefined, this),
70766
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
70767
+ dimColor: true,
70768
+ children: [
70769
+ " ",
70647
70770
  "workers active: ",
70648
70771
  coord?.activeCount ?? 0,
70649
70772
  " \xB7 queued: ",
70650
70773
  coord?.queuedCount ?? 0
70651
70774
  ]
70652
70775
  }, undefined, true, undefined, this),
70653
- coord?.activeWorkers.map((w) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
70654
- color: "cyan",
70655
- children: [
70656
- " ",
70657
- "\u25CF ",
70658
- w.issueIdentifier,
70659
- " (",
70660
- w.changeName,
70661
- ")"
70662
- ]
70663
- }, w.changeName, true, undefined, this))
70776
+ coord?.activeWorkers.map((w) => {
70777
+ const meta = workerMetaRef.current.get(w.changeName);
70778
+ const elapsed = meta ? fmtElapsed(now2 - meta.startedAt) : "\u2013";
70779
+ const iter = meta?.iter ?? 0;
70780
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
70781
+ color: "cyan",
70782
+ children: [
70783
+ " ",
70784
+ spinnerFrame,
70785
+ " ",
70786
+ w.issueIdentifier,
70787
+ " (",
70788
+ w.changeName,
70789
+ ") \xB7 iter ",
70790
+ iter,
70791
+ " \xB7 ",
70792
+ elapsed
70793
+ ]
70794
+ }, w.changeName, true, undefined, this);
70795
+ })
70664
70796
  ]
70665
70797
  }, undefined, true, undefined, this)
70666
70798
  ]
@@ -70811,7 +70943,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
70811
70943
  switch (args.mode) {
70812
70944
  case "list":
70813
70945
  return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskList, {
70814
- statesDir
70946
+ statesDir,
70947
+ projectRoot
70815
70948
  }, undefined, false, undefined, this);
70816
70949
  case "agent":
70817
70950
  return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AgentMode, {
@@ -70847,6 +70980,10 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
70847
70980
  children: "Initialized openspec directory"
70848
70981
  }, undefined, false, undefined, this)
70849
70982
  }, undefined, false, undefined, this);
70983
+ case "clean":
70984
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
70985
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {}, undefined, false, undefined, this)
70986
+ }, undefined, false, undefined, this);
70850
70987
  case "task": {
70851
70988
  if (!args.name) {
70852
70989
  return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
@@ -70930,6 +71067,76 @@ try {
70930
71067
  cwd: process.cwd()
70931
71068
  });
70932
71069
  }
71070
+ if (args.mode === "clean") {
71071
+ if (!args.name) {
71072
+ process.stderr.write(`Error: --name is required for clean mode
71073
+ `);
71074
+ process.exit(1);
71075
+ }
71076
+ const worktreeDir = join17(projectRoot, ".ralph", "worktrees", args.name);
71077
+ const changeDir = join17(tasksDir, args.name);
71078
+ const stateDir = join17(statesDir, args.name);
71079
+ const branch = `ralph/${args.name}`;
71080
+ const removed = [];
71081
+ if (await exists(worktreeDir)) {
71082
+ const proc = Bun.spawn({
71083
+ cmd: ["git", "worktree", "remove", "--force", worktreeDir],
71084
+ cwd: projectRoot,
71085
+ stdout: "pipe",
71086
+ stderr: "pipe"
71087
+ });
71088
+ const code = await proc.exited;
71089
+ if (code !== 0) {
71090
+ await rm(worktreeDir, { recursive: true, force: true });
71091
+ await Bun.spawn({
71092
+ cmd: ["git", "worktree", "prune"],
71093
+ cwd: projectRoot,
71094
+ stdout: "ignore",
71095
+ stderr: "ignore"
71096
+ }).exited;
71097
+ }
71098
+ removed.push(`worktree ${worktreeDir}`);
71099
+ }
71100
+ const branchProc = Bun.spawn({
71101
+ cmd: ["git", "branch", "-D", branch],
71102
+ cwd: projectRoot,
71103
+ stdout: "ignore",
71104
+ stderr: "ignore"
71105
+ });
71106
+ if (await branchProc.exited === 0)
71107
+ removed.push(`branch ${branch}`);
71108
+ if (await exists(changeDir)) {
71109
+ await rm(changeDir, { recursive: true, force: true });
71110
+ removed.push(`openspec change ${changeDir}`);
71111
+ }
71112
+ if (await exists(stateDir)) {
71113
+ await rm(stateDir, { recursive: true, force: true });
71114
+ removed.push(`task state ${stateDir}`);
71115
+ }
71116
+ try {
71117
+ const agentState = await readAgentState(projectRoot);
71118
+ const meta = agentState.changeMeta[args.name];
71119
+ if (meta) {
71120
+ agentState.processedIssueIds = agentState.processedIssueIds.filter((id) => id !== meta.issueId);
71121
+ agentState.startedIssueIds = agentState.startedIssueIds.filter((id) => id !== meta.issueId);
71122
+ delete agentState.changeMeta[args.name];
71123
+ await writeAgentState(projectRoot, agentState);
71124
+ removed.push(`agent-state entry for ${meta.identifier} (${meta.issueId})`);
71125
+ }
71126
+ } catch {}
71127
+ if (removed.length === 0) {
71128
+ process.stdout.write(`Nothing to clean for '${args.name}'
71129
+ `);
71130
+ } else {
71131
+ process.stdout.write(`Cleaned '${args.name}':
71132
+ `);
71133
+ for (const r of removed)
71134
+ process.stdout.write(` \u2713 removed ${r}
71135
+ `);
71136
+ }
71137
+ await shutdown();
71138
+ process.exit(0);
71139
+ }
70933
71140
  if (args.mode === "task" && args.name) {
70934
71141
  await mkdir3(join17(statesDir, args.name), { recursive: true });
70935
71142
  await mkdir3(join17(tasksDir, args.name), { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.7.9",
3
+ "version": "2.8.0",
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",