@neriros/ralphy 2.17.1 → 2.17.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.
Files changed (2) hide show
  1. package/dist/cli/index.js +251 -42
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -35029,8 +35029,8 @@ import { readFileSync as readFileSync2 } from "fs";
35029
35029
  import { resolve } from "path";
35030
35030
  function getVersion() {
35031
35031
  try {
35032
- if ("2.17.1")
35033
- return "2.17.1";
35032
+ if ("2.17.2")
35033
+ return "2.17.2";
35034
35034
  } catch {}
35035
35035
  const dirsToTry = [];
35036
35036
  try {
@@ -35131,6 +35131,7 @@ async function parseArgs(argv) {
35131
35131
  createPr: false,
35132
35132
  fixCi: false,
35133
35133
  maxTickets: 0,
35134
+ projectRoot: undefined,
35134
35135
  jsonOutput: false
35135
35136
  };
35136
35137
  let expectModel = false;
@@ -35149,6 +35150,7 @@ async function parseArgs(argv) {
35149
35150
  let expectConcurrency = false;
35150
35151
  let expectMaxTickets = false;
35151
35152
  let expectIndicator = false;
35153
+ let expectProjectRoot = false;
35152
35154
  for (const arg of argv) {
35153
35155
  if (expectModel) {
35154
35156
  if (VALID_MODELS.has(arg)) {
@@ -35237,6 +35239,11 @@ async function parseArgs(argv) {
35237
35239
  expectIndicator = false;
35238
35240
  continue;
35239
35241
  }
35242
+ if (expectProjectRoot) {
35243
+ result2.projectRoot = arg;
35244
+ expectProjectRoot = false;
35245
+ continue;
35246
+ }
35240
35247
  switch (arg) {
35241
35248
  case "--claude":
35242
35249
  if (result2.engineSet && result2.engine !== "claude") {
@@ -35325,6 +35332,9 @@ async function parseArgs(argv) {
35325
35332
  case "--from-agent":
35326
35333
  result2.fromAgent = true;
35327
35334
  break;
35335
+ case "--project-root":
35336
+ expectProjectRoot = true;
35337
+ break;
35328
35338
  default:
35329
35339
  if (VALID_MODES.has(arg)) {
35330
35340
  result2.mode = arg;
@@ -73753,8 +73763,11 @@ init_worktree();
73753
73763
  // apps/cli/src/debug.ts
73754
73764
  init_log();
73755
73765
  import { join as join20 } from "path";
73766
+ function fmtTs(d) {
73767
+ return d.toISOString().replace("T", " ").slice(0, 23);
73768
+ }
73756
73769
  var LOG_LINE_RE = /^\[(.+?)\] \[(.+?)\] (.+)$/;
73757
- function parseLog(content) {
73770
+ function parseTextLog(content) {
73758
73771
  return content.split(`
73759
73772
  `).filter(Boolean).flatMap((line) => {
73760
73773
  const m = LOG_LINE_RE.exec(line);
@@ -73766,23 +73779,154 @@ function parseLog(content) {
73766
73779
  return [{ ts, type: m[2], text: m[3] }];
73767
73780
  });
73768
73781
  }
73769
- function fmtTs(d) {
73770
- return d.toISOString().replace("T", " ").slice(0, 23);
73782
+ function parseJsonlLog(content, filterChangeName) {
73783
+ return content.split(`
73784
+ `).filter(Boolean).flatMap((line) => {
73785
+ let entry;
73786
+ try {
73787
+ entry = JSON.parse(line);
73788
+ } catch {
73789
+ return [];
73790
+ }
73791
+ const ts = new Date(entry.ts);
73792
+ if (isNaN(ts.getTime()))
73793
+ return [];
73794
+ if (filterChangeName && entry.changeName && entry.changeName !== filterChangeName) {
73795
+ return [];
73796
+ }
73797
+ const cn = entry.changeName ?? "";
73798
+ switch (entry.type) {
73799
+ case "started":
73800
+ return [{ ts, type: "agent", text: `agent started v${entry.version ?? "?"}` }];
73801
+ case "stopped":
73802
+ return [{ ts, type: "agent", text: "agent stopped" }];
73803
+ case "worker_started":
73804
+ return [{ ts, type: "spawn", text: `${cn}: worker spawned` }];
73805
+ case "worker_phase": {
73806
+ const detail = entry.detail ? ` (${entry.detail})` : "";
73807
+ return [{ ts, type: "phase", text: `${cn}: ${entry.phase}${detail}` }];
73808
+ }
73809
+ case "worker_cmd_start":
73810
+ return [
73811
+ {
73812
+ ts,
73813
+ type: "cmd",
73814
+ text: `${cn}: \u2192 ${(entry.cmd ?? []).slice(0, 4).join(" ")}`
73815
+ }
73816
+ ];
73817
+ case "worker_cmd_end":
73818
+ return [
73819
+ {
73820
+ ts,
73821
+ type: "cmd",
73822
+ text: `${cn}: \u2190 ${(entry.cmd ?? []).slice(0, 2).join(" ")} (${entry.durationMs}ms, ${entry.ok ? "ok" : "err"})`
73823
+ }
73824
+ ];
73825
+ case "worker_pr":
73826
+ return [{ ts, type: "pr", text: `${cn}: PR \u2192 ${entry.prUrl}` }];
73827
+ case "worker_exited":
73828
+ return [
73829
+ {
73830
+ ts,
73831
+ type: "exit",
73832
+ text: `${cn}: exited (code ${entry.exitCode ?? "?"})`
73833
+ }
73834
+ ];
73835
+ case "log":
73836
+ return [{ ts, type: "coord", text: entry.text ?? "" }];
73837
+ case "poll_done":
73838
+ return [
73839
+ {
73840
+ ts,
73841
+ type: "poll",
73842
+ text: `poll: found=${entry.found} added=${entry.added}`
73843
+ }
73844
+ ];
73845
+ default:
73846
+ return [];
73847
+ }
73848
+ });
73849
+ }
73850
+ function detectStuck(lines) {
73851
+ if (lines.length < 10)
73852
+ return null;
73853
+ const recent = lines.slice(-20).filter((l) => l.type === "phase");
73854
+ if (recent.length < 5)
73855
+ return null;
73856
+ const phaseNames = recent.map((l) => l.text.split(": ").slice(1).join(": "));
73857
+ const unique = new Set(phaseNames);
73858
+ if (unique.size !== 1)
73859
+ return null;
73860
+ const phase = phaseNames[0];
73861
+ const all = lines.filter((l) => l.type === "phase" && l.text.includes(phase));
73862
+ const first = all[0];
73863
+ const last2 = all[all.length - 1];
73864
+ const minutesStuck = (last2.ts.getTime() - first.ts.getTime()) / 60000;
73865
+ const prEntry = lines.find((l) => l.type === "pr");
73866
+ const cmdEntry = lines.find((l) => l.type === "cmd" && l.text.includes("gh") && l.text.includes("mergeable"));
73867
+ const watchingPrUrl = prEntry?.text.split("PR \u2192 ")[1] ?? cmdEntry?.text.match(/https:\/\/github\.com\/[^\s)]+/)?.[0];
73868
+ return {
73869
+ phase,
73870
+ count: all.length,
73871
+ firstSeen: first.ts,
73872
+ lastSeen: last2.ts,
73873
+ minutesStuck,
73874
+ watchingPrUrl
73875
+ };
73876
+ }
73877
+ async function inspectBinary(projectRoot) {
73878
+ const binPath = join20(projectRoot, ".ralph", "bin", "cli.js");
73879
+ const file = Bun.file(binPath);
73880
+ if (!await file.exists())
73881
+ return null;
73882
+ let embeddedVersion;
73883
+ try {
73884
+ const slice2 = await file.slice(0, 50000).text();
73885
+ const m = /"(\d+\.\d+\.\d+)"/.exec(slice2);
73886
+ if (m)
73887
+ embeddedVersion = m[1];
73888
+ } catch {}
73889
+ let builtAt;
73890
+ try {
73891
+ const r = Bun.spawnSync(["stat", "-f", "%Sm", "-t", "%Y-%m-%dT%H:%M:%S", binPath], {
73892
+ stderr: "ignore"
73893
+ });
73894
+ const s = r.stdout.toString().trim();
73895
+ if (s)
73896
+ builtAt = new Date(s);
73897
+ } catch {}
73898
+ return { path: binPath, embeddedVersion, builtAt };
73771
73899
  }
73772
73900
  var SPAWN_RE = /\u25B6 (\S+) \u2192 (\S+)/;
73773
- async function resolveDebugTarget(opts) {
73901
+ async function resolveDebugTarget(projectRoot, opts) {
73774
73902
  const agentLogFile = Bun.file(AGENT_LOG_PATH);
73775
- const agentLines = await agentLogFile.exists() ? parseLog(await agentLogFile.text()) : [];
73903
+ const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
73904
+ const jsonlLogFile = Bun.file(join20(projectRoot, ".ralph", "agent.log"));
73905
+ const jsonlLines = await jsonlLogFile.exists() ? parseJsonlLog(await jsonlLogFile.text()) : [];
73906
+ const allLines = [...textLines, ...jsonlLines];
73776
73907
  if (opts.name && !opts.issue) {
73777
- for (const line of agentLines) {
73908
+ for (const line of allLines) {
73778
73909
  const m = SPAWN_RE.exec(line.text);
73779
73910
  if (m && m[2] === opts.name)
73780
73911
  return { changeName: opts.name, identifier: m[1] };
73781
73912
  }
73913
+ for (const line of allLines) {
73914
+ if (line.text.includes(opts.name) && line.text.includes("COD-")) {
73915
+ const id = /COD-\d+/.exec(line.text)?.[0];
73916
+ if (id)
73917
+ return { changeName: opts.name, identifier: id };
73918
+ }
73919
+ }
73782
73920
  return { changeName: opts.name, identifier: undefined };
73783
73921
  }
73784
73922
  if (opts.issue && !opts.name) {
73785
- for (const line of agentLines) {
73923
+ const pattern = new RegExp(`(cod-${opts.issue.toLowerCase().replace("cod-", "")}[\\w-]+)`);
73924
+ for (const line of allLines) {
73925
+ const m = pattern.exec(line.text);
73926
+ if (m)
73927
+ return { changeName: m[1], identifier: opts.issue };
73928
+ }
73929
+ for (const line of allLines) {
73786
73930
  const m = SPAWN_RE.exec(line.text);
73787
73931
  if (m && m[1] === opts.issue)
73788
73932
  return { changeName: m[2], identifier: opts.issue };
@@ -73852,34 +73996,33 @@ async function fetchGithubPr(changeName) {
73852
73996
  ]) ?? [];
73853
73997
  return { ...pr, checks };
73854
73998
  }
73999
+ function fetchMergeableNow(prUrl) {
74000
+ const result2 = Bun.spawnSync(["gh", "pr", "view", prUrl, "--json", "mergeable", "--jq", ".mergeable"], { stderr: "ignore" });
74001
+ return result2.exitCode === 0 ? result2.stdout.toString().trim() : null;
74002
+ }
73855
74003
  async function runDebug(opts) {
73856
74004
  const { projectRoot } = opts;
74005
+ const out = (s) => process.stdout.write(s + `
74006
+ `);
73857
74007
  const agentLogFile = Bun.file(AGENT_LOG_PATH);
73858
- const agentLogContent = await agentLogFile.exists() ? await agentLogFile.text() : "";
73859
- const agentLines = parseLog(agentLogContent);
73860
- let { changeName, identifier: issueIdentifier } = await resolveDebugTarget({
74008
+ const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
74009
+ const jsonlLogPath = join20(projectRoot, ".ralph", "agent.log");
74010
+ const jsonlLogFile = Bun.file(jsonlLogPath);
74011
+ const hasJsonlLog = await jsonlLogFile.exists();
74012
+ let { changeName, identifier: issueIdentifier } = await resolveDebugTarget(projectRoot, {
73861
74013
  ...opts.name !== undefined ? { name: opts.name } : {},
73862
74014
  ...opts.issue !== undefined ? { issue: opts.issue } : {}
73863
74015
  });
73864
74016
  if (!changeName) {
73865
- process.stderr.write(`! Could not resolve a change name for ${opts.issue ?? opts.name}. Has this issue been started?
74017
+ process.stderr.write(`! Could not resolve change name for ${opts.issue ?? opts.name}.
73866
74018
  `);
73867
74019
  process.exit(1);
73868
74020
  }
73869
- const relevant = agentLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
73870
- if (!issueIdentifier) {
73871
- for (const line of relevant) {
73872
- const m = SPAWN_RE.exec(line.text);
73873
- if (m && m[2] === changeName) {
73874
- issueIdentifier = m[1];
73875
- break;
73876
- }
73877
- }
73878
- }
73879
- const workerLogPath = join20(projectRoot, ".ralph", "logs", `${changeName}.log`);
73880
- const workerLogFile = Bun.file(workerLogPath);
73881
- const workerLines = await workerLogFile.exists() ? parseLog(await workerLogFile.text()) : [];
73882
- const merged = [...relevant, ...workerLines].sort((a, b) => +a.ts - +b.ts);
74021
+ const jsonlLines = hasJsonlLog ? parseJsonlLog(await jsonlLogFile.text(), changeName) : [];
74022
+ const relevantText = textLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
74023
+ const workerLogFile = Bun.file(join20(projectRoot, ".ralph", "logs", `${changeName}.log`));
74024
+ const workerLines = await workerLogFile.exists() ? parseTextLog(await workerLogFile.text()) : [];
74025
+ const merged = [...relevantText, ...jsonlLines, ...workerLines].sort((a, b) => +a.ts - +b.ts);
73883
74026
  const seen = new Set;
73884
74027
  const timeline = merged.filter((l) => {
73885
74028
  const key = `${l.ts.getTime()}:${l.type}:${l.text}`;
@@ -73888,8 +74031,24 @@ async function runDebug(opts) {
73888
74031
  seen.add(key);
73889
74032
  return true;
73890
74033
  });
73891
- const out = (s) => process.stdout.write(s + `
73892
- `);
74034
+ if (!issueIdentifier) {
74035
+ for (const line of timeline) {
74036
+ const m = SPAWN_RE.exec(line.text);
74037
+ if (m && m[2] === changeName) {
74038
+ issueIdentifier = m[1];
74039
+ break;
74040
+ }
74041
+ if (line.text.includes(changeName)) {
74042
+ const id = /(COD|ENG|DEV)-\d+/.exec(line.text)?.[0];
74043
+ if (id) {
74044
+ issueIdentifier = id;
74045
+ break;
74046
+ }
74047
+ }
74048
+ }
74049
+ }
74050
+ const stuck = detectStuck(timeline);
74051
+ const binary = await inspectBinary(projectRoot);
73893
74052
  out(`
73894
74053
  === Ralph Debug: ${changeName}${issueIdentifier ? ` (${issueIdentifier})` : ""} ===
73895
74054
  `);
@@ -73897,12 +74056,30 @@ async function runDebug(opts) {
73897
74056
  if (!timeline.length) {
73898
74057
  out(" (no log entries found)");
73899
74058
  } else {
73900
- for (const line of timeline) {
73901
- const prefix = line.type === "output" ? " \u2502" : " \xB7";
73902
- out(`${prefix} ${fmtTs(line.ts)} [${line.type.padEnd(7)}] ${line.text}`);
74059
+ if (stuck && timeline.length > 20) {
74060
+ const phaseLines = timeline.filter((l) => l.type === "phase" && l.text.includes(stuck.phase));
74061
+ const nonPhase = timeline.filter((l) => !(l.type === "phase" && l.text.includes(stuck.phase)));
74062
+ for (const line of nonPhase) {
74063
+ const prefix = line.type === "output" ? " \u2502" : " \xB7";
74064
+ out(`${prefix} ${fmtTs(line.ts)} [${line.type.padEnd(7)}] ${line.text}`);
74065
+ }
74066
+ out(` \u21BA ... ${phaseLines.length}\xD7 ${stuck.phase} (${stuck.minutesStuck.toFixed(1)} min) ...`);
74067
+ } else {
74068
+ for (const line of timeline) {
74069
+ const prefix = line.type === "output" ? " \u2502" : " \xB7";
74070
+ out(`${prefix} ${fmtTs(line.ts)} [${line.type.padEnd(7)}] ${line.text}`);
74071
+ }
73903
74072
  }
73904
74073
  }
73905
74074
  out("");
74075
+ if (binary) {
74076
+ out("\u2500\u2500 Installed binary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
74077
+ out(` Path : ${binary.path}`);
74078
+ out(` Embedded version : ${binary.embeddedVersion ?? "(unknown)"}`);
74079
+ if (binary.builtAt)
74080
+ out(` Built at : ${fmtTs(binary.builtAt)}`);
74081
+ out("");
74082
+ }
73906
74083
  out("\u2500\u2500 Current Linear state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
73907
74084
  if (!issueIdentifier) {
73908
74085
  out(" (unknown identifier \u2014 pass --issue to query Linear directly)");
@@ -73924,7 +74101,13 @@ async function runDebug(opts) {
73924
74101
  out("\u2500\u2500 Current GitHub PR \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
73925
74102
  const pr = await fetchGithubPr(changeName);
73926
74103
  if (!pr) {
73927
- out(` (no PR found for branch ralph/${changeName})`);
74104
+ if (stuck?.watchingPrUrl) {
74105
+ const m = fetchMergeableNow(stuck.watchingPrUrl);
74106
+ out(` Watching : ${stuck.watchingPrUrl}`);
74107
+ out(` Mergeable: ${m ?? "(error fetching)"}`);
74108
+ } else {
74109
+ out(` (no PR found for branch ralph/${changeName})`);
74110
+ }
73928
74111
  } else {
73929
74112
  const failing = pr.checks.filter((c) => c.conclusion === "FAILURE" || c.conclusion === "failure");
73930
74113
  const pending = pr.checks.filter((c) => c.state === "PENDING" || c.state === "IN_PROGRESS");
@@ -73946,32 +74129,58 @@ async function runDebug(opts) {
73946
74129
  const lastEvent = timeline.at(-1);
73947
74130
  if (lastEvent)
73948
74131
  out(` Last event : ${fmtTs(lastEvent.ts)} ${lastEvent.text}`);
73949
- const exitLine = relevant.find((l) => /exited \(code \d+\)/.test(l.text));
74132
+ const exitLine = timeline.find((l) => /exited \(code \d+\)/.test(l.text));
73950
74133
  if (exitLine) {
73951
74134
  const code = Number(/code (\d+)/.exec(exitLine.text)?.[1]);
73952
- const meaning = code === 0 ? "success" : code === 70 ? "CI fix loop exhausted its attempt budget" : code === 71 ? "push or PR creation failed (pre-push hook or remote rejection)" : "worker subprocess failed";
74135
+ const meaning = code === 0 ? "success" : code === 70 ? "CI fix loop exhausted its attempt budget" : code === 71 ? "push or PR creation failed" : "worker subprocess failed";
73953
74136
  out(` Exit code : ${code} \u2014 ${meaning}`);
73954
74137
  }
73955
- const logHas = (s) => relevant.some((l) => l.text.includes(s));
74138
+ if (stuck) {
74139
+ out(` \u26A0 STUCK in ${stuck.phase} \u2014 ${stuck.count} iterations over ${stuck.minutesStuck.toFixed(1)} min`);
74140
+ if (stuck.watchingPrUrl) {
74141
+ const mergeable = fetchMergeableNow(stuck.watchingPrUrl);
74142
+ out(` Watching : ${stuck.watchingPrUrl}`);
74143
+ out(` Mergeable : ${mergeable ?? "(error)"} (live fetch)`);
74144
+ if (mergeable === "MERGEABLE") {
74145
+ out(` \u2192 PR is MERGEABLE \u2014 loop should have exited. Likely cause:`);
74146
+ if (binary?.embeddedVersion && binary.embeddedVersion < "2.17.1") {
74147
+ out(` Local binary is v${binary.embeddedVersion} (fix shipped in v2.17.1). Update with:`);
74148
+ out(` cd ${projectRoot} && bunx @neriros/ralphy@latest make-install`);
74149
+ } else {
74150
+ out(` This is the conflict-check infinite loop bug (fixed in v2.17.1).`);
74151
+ out(` Restart the agent after updating to v2.17.1.`);
74152
+ }
74153
+ }
74154
+ }
74155
+ }
74156
+ if (binary) {
74157
+ const embV = binary.embeddedVersion ?? "?";
74158
+ const logV = timeline.find((l) => l.text.includes("agent started"))?.text.match(/v([\d.]+)/)?.[1];
74159
+ if (logV && embV !== logV) {
74160
+ out(` \u26A0 Version mismatch: binary says v${embV}, agent reported v${logV}`);
74161
+ out(` The binary reads version from a package.json at runtime \u2014 the actual`);
74162
+ out(` running code is v${embV}, not v${logV}. Update the local install.`);
74163
+ }
74164
+ }
74165
+ const logHas = (s) => timeline.some((l) => l.text.includes(s));
73956
74166
  if (logHas("setError applied"))
73957
74167
  out(" \u26A0 setError applied \u2014 issue is quarantined in Linear");
73958
74168
  if (logHas("setDone applied"))
73959
74169
  out(" \u2713 setDone applied \u2014 issue marked done in Linear");
73960
74170
  if (logHas("clearConflicted applied"))
73961
- out(" \u2713 clearConflicted applied \u2014 conflicts resolved");
74171
+ out(" \u2713 clearConflicted applied");
73962
74172
  if (logHas("setConflicted applied"))
73963
74173
  out(" \u26A0 setConflicted applied \u2014 merge conflicts detected");
73964
74174
  if (logHas("skipping PR phase"))
73965
74175
  out(" \u21A9 PR phase skipped \u2014 worker exited non-zero");
73966
74176
  if (pr?.mergeable === "CONFLICTING")
73967
74177
  out(" \u26A0 PR currently has merge conflicts");
73968
- if (pr?.checks.some((c) => c.conclusion === "FAILURE" || c.conclusion === "failure")) {
74178
+ if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
73969
74179
  out(" \u26A0 PR has failing CI checks");
73970
- }
73971
74180
  const worktreePath = join20(projectRoot, ".ralph", "worktrees", changeName);
73972
- const worktreeExists = await Bun.file(join20(worktreePath, ".git")).exists();
73973
- if (worktreeExists)
74181
+ if (await Bun.file(join20(worktreePath, ".git")).exists()) {
73974
74182
  out(` Worktree : ${worktreePath}`);
74183
+ }
73975
74184
  if (!timeline.length)
73976
74185
  out(" (no log entries \u2014 has this change been started yet?)");
73977
74186
  out("");
@@ -74041,7 +74250,7 @@ try {
74041
74250
  `);
74042
74251
  process.exit(1);
74043
74252
  }
74044
- await runDebug({ name: args.name, projectRoot });
74253
+ await runDebug({ name: args.name, projectRoot: args.projectRoot ?? projectRoot });
74045
74254
  await shutdown();
74046
74255
  process.exit(0);
74047
74256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.17.1",
3
+ "version": "2.17.2",
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",