@neriros/ralphy 2.17.0 → 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 +264 -47
  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.0")
35033
- return "2.17.0";
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;
@@ -60796,6 +60806,8 @@ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
60796
60806
  } catch (err) {
60797
60807
  ctx.log(`! conflict check failed: ${err.message}`, "yellow");
60798
60808
  }
60809
+ if (!conflicting && ciConfirmedGreen)
60810
+ return 0;
60799
60811
  if (conflicting) {
60800
60812
  outerAttempt++;
60801
60813
  ciConfirmedGreen = false;
@@ -61442,12 +61454,18 @@ PR: ${prUrl}` : ""
61442
61454
  onPhase: (phase, detail) => onWorkerPhase(changeName, phase, detail)
61443
61455
  },
61444
61456
  checkPrConflict: async (prUrl) => {
61445
- try {
61446
- const res = await tracedCmd.run(["gh", "pr", "view", prUrl, "--json", "mergeable", "--jq", ".mergeable"], cwd2);
61447
- return res.stdout.trim() === "CONFLICTING";
61448
- } catch {
61449
- return false;
61457
+ for (let attempt2 = 0;attempt2 < 5; attempt2++) {
61458
+ try {
61459
+ const res = await tracedCmd.run(["gh", "pr", "view", prUrl, "--json", "mergeable", "--jq", ".mergeable"], cwd2);
61460
+ const mergeable = res.stdout.trim();
61461
+ if (mergeable !== "UNKNOWN")
61462
+ return mergeable === "CONFLICTING";
61463
+ } catch {
61464
+ return false;
61465
+ }
61466
+ await new Promise((r) => setTimeout(r, 2000));
61450
61467
  }
61468
+ return false;
61451
61469
  }
61452
61470
  });
61453
61471
  cwdByChange.delete(changeName);
@@ -73745,8 +73763,11 @@ init_worktree();
73745
73763
  // apps/cli/src/debug.ts
73746
73764
  init_log();
73747
73765
  import { join as join20 } from "path";
73766
+ function fmtTs(d) {
73767
+ return d.toISOString().replace("T", " ").slice(0, 23);
73768
+ }
73748
73769
  var LOG_LINE_RE = /^\[(.+?)\] \[(.+?)\] (.+)$/;
73749
- function parseLog(content) {
73770
+ function parseTextLog(content) {
73750
73771
  return content.split(`
73751
73772
  `).filter(Boolean).flatMap((line) => {
73752
73773
  const m = LOG_LINE_RE.exec(line);
@@ -73758,23 +73779,154 @@ function parseLog(content) {
73758
73779
  return [{ ts, type: m[2], text: m[3] }];
73759
73780
  });
73760
73781
  }
73761
- function fmtTs(d) {
73762
- 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 };
73763
73899
  }
73764
73900
  var SPAWN_RE = /\u25B6 (\S+) \u2192 (\S+)/;
73765
- async function resolveDebugTarget(opts) {
73901
+ async function resolveDebugTarget(projectRoot, opts) {
73766
73902
  const agentLogFile = Bun.file(AGENT_LOG_PATH);
73767
- 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];
73768
73907
  if (opts.name && !opts.issue) {
73769
- for (const line of agentLines) {
73908
+ for (const line of allLines) {
73770
73909
  const m = SPAWN_RE.exec(line.text);
73771
73910
  if (m && m[2] === opts.name)
73772
73911
  return { changeName: opts.name, identifier: m[1] };
73773
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
+ }
73774
73920
  return { changeName: opts.name, identifier: undefined };
73775
73921
  }
73776
73922
  if (opts.issue && !opts.name) {
73777
- 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) {
73778
73930
  const m = SPAWN_RE.exec(line.text);
73779
73931
  if (m && m[1] === opts.issue)
73780
73932
  return { changeName: m[2], identifier: opts.issue };
@@ -73844,34 +73996,33 @@ async function fetchGithubPr(changeName) {
73844
73996
  ]) ?? [];
73845
73997
  return { ...pr, checks };
73846
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
+ }
73847
74003
  async function runDebug(opts) {
73848
74004
  const { projectRoot } = opts;
74005
+ const out = (s) => process.stdout.write(s + `
74006
+ `);
73849
74007
  const agentLogFile = Bun.file(AGENT_LOG_PATH);
73850
- const agentLogContent = await agentLogFile.exists() ? await agentLogFile.text() : "";
73851
- const agentLines = parseLog(agentLogContent);
73852
- 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, {
73853
74013
  ...opts.name !== undefined ? { name: opts.name } : {},
73854
74014
  ...opts.issue !== undefined ? { issue: opts.issue } : {}
73855
74015
  });
73856
74016
  if (!changeName) {
73857
- 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}.
73858
74018
  `);
73859
74019
  process.exit(1);
73860
74020
  }
73861
- const relevant = agentLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
73862
- if (!issueIdentifier) {
73863
- for (const line of relevant) {
73864
- const m = SPAWN_RE.exec(line.text);
73865
- if (m && m[2] === changeName) {
73866
- issueIdentifier = m[1];
73867
- break;
73868
- }
73869
- }
73870
- }
73871
- const workerLogPath = join20(projectRoot, ".ralph", "logs", `${changeName}.log`);
73872
- const workerLogFile = Bun.file(workerLogPath);
73873
- const workerLines = await workerLogFile.exists() ? parseLog(await workerLogFile.text()) : [];
73874
- 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);
73875
74026
  const seen = new Set;
73876
74027
  const timeline = merged.filter((l) => {
73877
74028
  const key = `${l.ts.getTime()}:${l.type}:${l.text}`;
@@ -73880,8 +74031,24 @@ async function runDebug(opts) {
73880
74031
  seen.add(key);
73881
74032
  return true;
73882
74033
  });
73883
- const out = (s) => process.stdout.write(s + `
73884
- `);
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);
73885
74052
  out(`
73886
74053
  === Ralph Debug: ${changeName}${issueIdentifier ? ` (${issueIdentifier})` : ""} ===
73887
74054
  `);
@@ -73889,12 +74056,30 @@ async function runDebug(opts) {
73889
74056
  if (!timeline.length) {
73890
74057
  out(" (no log entries found)");
73891
74058
  } else {
73892
- for (const line of timeline) {
73893
- const prefix = line.type === "output" ? " \u2502" : " \xB7";
73894
- 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
+ }
73895
74072
  }
73896
74073
  }
73897
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
+ }
73898
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");
73899
74084
  if (!issueIdentifier) {
73900
74085
  out(" (unknown identifier \u2014 pass --issue to query Linear directly)");
@@ -73916,7 +74101,13 @@ async function runDebug(opts) {
73916
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");
73917
74102
  const pr = await fetchGithubPr(changeName);
73918
74103
  if (!pr) {
73919
- 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
+ }
73920
74111
  } else {
73921
74112
  const failing = pr.checks.filter((c) => c.conclusion === "FAILURE" || c.conclusion === "failure");
73922
74113
  const pending = pr.checks.filter((c) => c.state === "PENDING" || c.state === "IN_PROGRESS");
@@ -73938,32 +74129,58 @@ async function runDebug(opts) {
73938
74129
  const lastEvent = timeline.at(-1);
73939
74130
  if (lastEvent)
73940
74131
  out(` Last event : ${fmtTs(lastEvent.ts)} ${lastEvent.text}`);
73941
- const exitLine = relevant.find((l) => /exited \(code \d+\)/.test(l.text));
74132
+ const exitLine = timeline.find((l) => /exited \(code \d+\)/.test(l.text));
73942
74133
  if (exitLine) {
73943
74134
  const code = Number(/code (\d+)/.exec(exitLine.text)?.[1]);
73944
- 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";
73945
74136
  out(` Exit code : ${code} \u2014 ${meaning}`);
73946
74137
  }
73947
- 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));
73948
74166
  if (logHas("setError applied"))
73949
74167
  out(" \u26A0 setError applied \u2014 issue is quarantined in Linear");
73950
74168
  if (logHas("setDone applied"))
73951
74169
  out(" \u2713 setDone applied \u2014 issue marked done in Linear");
73952
74170
  if (logHas("clearConflicted applied"))
73953
- out(" \u2713 clearConflicted applied \u2014 conflicts resolved");
74171
+ out(" \u2713 clearConflicted applied");
73954
74172
  if (logHas("setConflicted applied"))
73955
74173
  out(" \u26A0 setConflicted applied \u2014 merge conflicts detected");
73956
74174
  if (logHas("skipping PR phase"))
73957
74175
  out(" \u21A9 PR phase skipped \u2014 worker exited non-zero");
73958
74176
  if (pr?.mergeable === "CONFLICTING")
73959
74177
  out(" \u26A0 PR currently has merge conflicts");
73960
- if (pr?.checks.some((c) => c.conclusion === "FAILURE" || c.conclusion === "failure")) {
74178
+ if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
73961
74179
  out(" \u26A0 PR has failing CI checks");
73962
- }
73963
74180
  const worktreePath = join20(projectRoot, ".ralph", "worktrees", changeName);
73964
- const worktreeExists = await Bun.file(join20(worktreePath, ".git")).exists();
73965
- if (worktreeExists)
74181
+ if (await Bun.file(join20(worktreePath, ".git")).exists()) {
73966
74182
  out(` Worktree : ${worktreePath}`);
74183
+ }
73967
74184
  if (!timeline.length)
73968
74185
  out(" (no log entries \u2014 has this change been started yet?)");
73969
74186
  out("");
@@ -74033,7 +74250,7 @@ try {
74033
74250
  `);
74034
74251
  process.exit(1);
74035
74252
  }
74036
- await runDebug({ name: args.name, projectRoot });
74253
+ await runDebug({ name: args.name, projectRoot: args.projectRoot ?? projectRoot });
74037
74254
  await shutdown();
74038
74255
  process.exit(0);
74039
74256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.17.0",
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",