@neriros/ralphy 2.16.2 → 2.16.4

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 +41 -70
  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.16.2")
35033
- return "2.16.2";
35032
+ if ("2.16.4")
35033
+ return "2.16.4";
35034
35034
  } catch {}
35035
35035
  const dirsToTry = [];
35036
35036
  try {
@@ -59481,6 +59481,9 @@ var MarkerSchema, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, Ralp
59481
59481
  // Seconds between CI status polls.
59482
59482
  "ciPollIntervalSeconds": 30,
59483
59483
 
59484
+ // CI check names to ignore when polling PR status (case-insensitive).
59485
+ // "ignoreCiChecks": ["Vercel", "codeql"],
59486
+
59484
59487
  // Underlying engine: "claude" or "codex".
59485
59488
  "engine": "claude",
59486
59489
 
@@ -59591,6 +59594,7 @@ var init_config = __esm(() => {
59591
59594
  fixCiOnFailure: exports_external.boolean().default(false),
59592
59595
  maxCiFixAttempts: exports_external.number().int().positive().default(5),
59593
59596
  ciPollIntervalSeconds: exports_external.number().int().positive().default(30),
59597
+ ignoreCiChecks: exports_external.array(exports_external.string()).default([]),
59594
59598
  engine: exports_external.enum(["claude", "codex"]).default("claude"),
59595
59599
  model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
59596
59600
  linear: exports_external.object({
@@ -60450,7 +60454,7 @@ ${e.stdout ?? ""}`;
60450
60454
  }
60451
60455
  throw lastErr;
60452
60456
  }
60453
- async function getPrChecksStatus(prRef, runner, cwd2, onTransientRetry) {
60457
+ async function getPrChecksStatus(prRef, runner, cwd2, onTransientRetry, ignoreCiChecks = []) {
60454
60458
  let out;
60455
60459
  try {
60456
60460
  out = await runGhWithRetry(["gh", "pr", "checks", prRef, "--json", PR_CHECKS_FIELDS], runner, cwd2, onTransientRetry);
@@ -60463,7 +60467,8 @@ ${e.stdout ?? ""}`;
60463
60467
  return { bucket: "pass", failedRunIds: [] };
60464
60468
  throw err;
60465
60469
  }
60466
- const checks = JSON.parse(out.stdout || "[]").filter((c) => c.bucket !== "skipping");
60470
+ const ignoredLower = ignoreCiChecks.map((n) => n.toLowerCase());
60471
+ const checks = JSON.parse(out.stdout || "[]").filter((c) => !ignoredLower.includes(c.name.toLowerCase())).filter((c) => c.bucket !== "skipping");
60467
60472
  if (checks.some((c) => c.bucket === "pending")) {
60468
60473
  return { bucket: "pending", failedRunIds: [] };
60469
60474
  }
@@ -60602,57 +60607,9 @@ ${pe.stderr ?? ""}`;
60602
60607
  }
60603
60608
  }
60604
60609
  }
60605
- async function commitResidualChanges(ctx, maxAttempts) {
60606
- let hookFixAttempt = 0;
60607
- while (true) {
60608
- ctx.emit("committing", "git status");
60609
- let dirty = "";
60610
- try {
60611
- const status = await ctx.cmd.run(["git", "status", "--porcelain"], ctx.cwd);
60612
- dirty = status.stdout.trim();
60613
- } catch (err) {
60614
- ctx.log(`! git status failed for ${ctx.changeName}: ${err.message}`, "yellow");
60615
- break;
60616
- }
60617
- if (!dirty)
60618
- break;
60619
- try {
60620
- ctx.emit("committing", "git add -A");
60621
- await ctx.cmd.run(["git", "add", "-A"], ctx.cwd);
60622
- ctx.emit("committing", "git commit");
60623
- await ctx.cmd.run(["git", "commit", "-m", `chore(ralph): residual changes for ${ctx.changeName}`], ctx.cwd);
60624
- ctx.log(` committed residual changes for ${ctx.changeName}`, "gray");
60625
- break;
60626
- } catch (err) {
60627
- const e = err;
60628
- const detail = e.stderr?.trim() || e.message;
60629
- const combined = `${e.stdout ?? ""}
60630
- ${e.stderr ?? ""}`;
60631
- if (/nothing to commit/i.test(combined) || /empty git commit/i.test(combined))
60632
- break;
60633
- if (hookFixAttempt >= maxAttempts) {
60634
- ctx.log(`! commit rejected for ${ctx.changeName} after ${hookFixAttempt} hook-fix attempts (host pre-commit hook still failing) \u2014 worktree preserved at ${ctx.cwd}`, "red");
60635
- ctx.log(` detail: ${detail}`, "red");
60636
- return { gaveUp: true, hookFixAttempt };
60637
- }
60638
- hookFixAttempt += 1;
60639
- ctx.emit("commit-retry", `${hookFixAttempt}/${maxAttempts}`);
60640
- ctx.log(`! commit rejected for ${ctx.changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxAttempts})`, "yellow");
60641
- ctx.log(` detail: ${detail}`, "yellow");
60642
- const retryCode = await runWorkerWithFixTask(ctx, "Fix host pre-commit hook rejection", `Committing residual changes was rejected by the host repo's pre-commit hook. ` + `Fix the underlying problem, then the commit will be retried.
60643
-
60644
- ` + combined.trim());
60645
- if (retryCode !== 0) {
60646
- ctx.log(`! worker re-run after commit rejection exited code ${retryCode} \u2014 giving up`, "red");
60647
- return { gaveUp: true, hookFixAttempt };
60648
- }
60649
- }
60650
- }
60651
- return { gaveUp: false, hookFixAttempt };
60652
- }
60653
- async function createPrWithRetry(ctx, issue, initialHookFixAttempt) {
60610
+ async function createPrWithRetry(ctx, issue) {
60654
60611
  const maxAttempts = ctx.cfg.maxCiFixAttempts;
60655
- let hookFixAttempt = initialHookFixAttempt;
60612
+ let hookFixAttempt = 0;
60656
60613
  let nonFfRebaseAttempted = false;
60657
60614
  let pr = null;
60658
60615
  while (true) {
@@ -60688,12 +60645,16 @@ ${re.stderr ?? ""}`;
60688
60645
  ctx.emit("rebasing", "conflicts detected \u2014 aborting + queueing fix task");
60689
60646
  try {
60690
60647
  await ctx.cmd.run(["git", "rebase", "--abort"], ctx.cwd);
60691
- } catch {}
60648
+ } catch (err2) {
60649
+ ctx.log(`! git rebase --abort failed (worktree may already be clean): ${err2.message}`, "yellow");
60650
+ }
60692
60651
  let conflictedFiles = "";
60693
60652
  try {
60694
60653
  const r = await ctx.cmd.run(["git", "diff", "--name-only", `HEAD..origin/${ctx.branch}`], ctx.cwd);
60695
60654
  conflictedFiles = r.stdout.trim();
60696
- } catch {}
60655
+ } catch (err2) {
60656
+ ctx.log(`! could not list conflicted files: ${err2.message}`, "yellow");
60657
+ }
60697
60658
  if (hookFixAttempt >= maxAttempts) {
60698
60659
  ctx.log(`! merge conflict on rebase of ${ctx.branch} after ${hookFixAttempt} attempts \u2014 worktree preserved at ${ctx.cwd}`, "red");
60699
60660
  ctx.log(` detail: ${reBlob.trim().split(`
@@ -60789,7 +60750,7 @@ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
60789
60750
  ctx.emit("ci-poll", "starting");
60790
60751
  const result2 = await fixCiUntilGreen({
60791
60752
  onPhase: (p, d) => ctx.emit(p, d),
60792
- getStatus: () => getPrChecksStatus(prUrl, ctx.cmd, ctx.cwd, (n, ms, why) => ctx.log(` gh transient (try ${n}) \u2014 retry in ${Math.round(ms / 1000)}s \xB7 ${why}`, "yellow")),
60753
+ getStatus: () => getPrChecksStatus(prUrl, ctx.cmd, ctx.cwd, (n, ms, why) => ctx.log(` gh transient (try ${n}) \u2014 retry in ${Math.round(ms / 1000)}s \xB7 ${why}`, "yellow"), ctx.cfg.ignoreCiChecks),
60793
60754
  getFailedLogs: (ids) => fetchFailedRunLogs(ids, ctx.cmd, ctx.cwd),
60794
60755
  runTaskWithSteering: (steering) => runWorkerWithFixTask(ctx, "Fix failing CI checks", steering),
60795
60756
  pushBranch: async () => {
@@ -60855,11 +60816,16 @@ async function runPostTask(input, deps) {
60855
60816
  emit,
60856
60817
  respawnWorker
60857
60818
  };
60858
- const { gaveUp: commitGaveUp, hookFixAttempt } = await commitResidualChanges(ctx, cfg.maxCiFixAttempts);
60859
- if (commitGaveUp) {
60860
- effectiveCode = PR_FAILED_EXIT;
60861
- } else {
60862
- const { pr, gaveUp: prGaveUp } = await createPrWithRetry(ctx, issue, hookFixAttempt);
60819
+ try {
60820
+ const status = await cmd.run(["git", "status", "--porcelain"], cwd2);
60821
+ if (status.stdout.trim()) {
60822
+ log2(`! ${changeName} has uncommitted changes after worker exit \u2014 the agent should commit everything before finishing. These changes will not be included in the PR.`, "yellow");
60823
+ }
60824
+ } catch (err) {
60825
+ log2(`! git status check failed for ${changeName}: ${err.message}`, "yellow");
60826
+ }
60827
+ {
60828
+ const { pr, gaveUp: prGaveUp } = await createPrWithRetry(ctx, issue);
60863
60829
  if (prGaveUp) {
60864
60830
  effectiveCode = PR_FAILED_EXIT;
60865
60831
  } else if (!pr) {
@@ -60882,7 +60848,9 @@ async function runPostTask(input, deps) {
60882
60848
  emit("teardown", cfg.teardownScript);
60883
60849
  try {
60884
60850
  await runScript("teardown", cfg.teardownScript, cwd2);
60885
- } catch {}
60851
+ } catch (err) {
60852
+ log2(`! teardown script threw: ${err.message}`, "yellow");
60853
+ }
60886
60854
  }
60887
60855
  if (useWorktree && cwd2 !== projectRoot) {
60888
60856
  emit("cleanup", "checking worktree safety");
@@ -61385,7 +61353,8 @@ PR: ${prUrl}` : ""
61385
61353
  prBaseBranch: cfg.prBaseBranch,
61386
61354
  maxCiFixAttempts: cfg.maxCiFixAttempts,
61387
61355
  ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
61388
- cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess
61356
+ cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
61357
+ ignoreCiChecks: cfg.ignoreCiChecks
61389
61358
  },
61390
61359
  respawnWorker: respawn
61391
61360
  }, {
@@ -71880,6 +71849,8 @@ function buildTaskPrompt(state, taskDir) {
71880
71849
 
71881
71850
  `;
71882
71851
  prompt += `Run \`bunx openspec validate ${state.name}\` before committing.
71852
+ `;
71853
+ prompt += `Commit all changed files yourself before finishing \u2014 stage files individually (e.g. \`git add path/to/file\`), never \`git add -A\` or \`git commit -am\`. Nothing is committed automatically after you exit.
71883
71854
  `;
71884
71855
  return prompt;
71885
71856
  }
@@ -72551,8 +72522,6 @@ function phaseColor(phase) {
72551
72522
  return "cyan";
72552
72523
  case "scaffolding":
72553
72524
  return "magenta";
72554
- case "committing":
72555
- case "commit-retry":
72556
72525
  case "pushing":
72557
72526
  case "push-retry":
72558
72527
  case "rebasing":
@@ -72577,8 +72546,6 @@ function workerBorderColor(phase) {
72577
72546
  case "working":
72578
72547
  case "scaffolding":
72579
72548
  return "cyan";
72580
- case "committing":
72581
- case "commit-retry":
72582
72549
  case "pushing":
72583
72550
  case "push-retry":
72584
72551
  case "rebasing":
@@ -72796,7 +72763,9 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72796
72763
  const json = await file.json();
72797
72764
  meta.iter = json.iteration ?? meta.iter;
72798
72765
  }
72799
- } catch {}
72766
+ } catch (err) {
72767
+ console.error(`Failed to read state file for worker '${changeName}' (may not exist yet):`, err);
72768
+ }
72800
72769
  if (meta.changeDir) {
72801
72770
  try {
72802
72771
  const tasksFile = Bun.file(join16(meta.changeDir, "tasks.md"));
@@ -72805,7 +72774,9 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72805
72774
  const match = text.match(/^- \[ \] (.+)$/m);
72806
72775
  meta.currentTask = match?.[1]?.trim() ?? null;
72807
72776
  }
72808
- } catch {}
72777
+ } catch (err) {
72778
+ console.error(`Failed to read tasks.md for worker '${changeName}' (may not exist yet):`, err);
72779
+ }
72809
72780
  }
72810
72781
  }
72811
72782
  if (!cancelled)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.16.2",
3
+ "version": "2.16.4",
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",