@neriros/ralphy 2.13.15 → 2.14.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 +441 -286
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -50837,8 +50837,8 @@ var require_axios = __commonJS((exports, module) => {
50837
50837
  });
50838
50838
 
50839
50839
  // apps/cli/src/index.ts
50840
- import { resolve as resolve2, join as join19, dirname as dirname5 } from "path";
50841
- import { exists as exists2, mkdir as mkdir5, rm } from "fs/promises";
50840
+ import { resolve as resolve2, join as join19, dirname as dirname6 } from "path";
50841
+ import { exists as exists2, mkdir as mkdir6, rm } from "fs/promises";
50842
50842
 
50843
50843
  // node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
50844
50844
  import { Stream } from "stream";
@@ -56116,6 +56116,12 @@ function Static(props) {
56116
56116
  }
56117
56117
  // node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/components/Transform.js
56118
56118
  var import_react12 = __toESM(require_react(), 1);
56119
+ function Transform({ children, transform: transform2 }) {
56120
+ if (children === undefined || children === null) {
56121
+ return null;
56122
+ }
56123
+ return import_react12.default.createElement("ink-text", { style: { flexGrow: 0, flexShrink: 1, flexDirection: "row" }, internal_transform: transform2 }, children);
56124
+ }
56119
56125
  // node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/components/Newline.js
56120
56126
  var import_react13 = __toESM(require_react(), 1);
56121
56127
  // node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/components/Spacer.js
@@ -56411,10 +56417,13 @@ function log(msg) {
56411
56417
  import { readFileSync as readFileSync2 } from "fs";
56412
56418
  import { resolve } from "path";
56413
56419
  function getVersion() {
56420
+ try {
56421
+ if ("2.14.0")
56422
+ return "2.14.0";
56423
+ } catch {}
56414
56424
  const dirsToTry = [];
56415
56425
  try {
56416
- const cliDir = import.meta.dir;
56417
- dirsToTry.push(cliDir);
56426
+ dirsToTry.push(import.meta.dir);
56418
56427
  } catch {}
56419
56428
  dirsToTry.push(process.cwd());
56420
56429
  for (const startDir of dirsToTry) {
@@ -70108,8 +70117,9 @@ function TaskLoop({ opts }) {
70108
70117
 
70109
70118
  // apps/cli/src/components/AgentMode.tsx
70110
70119
  var import_react57 = __toESM(require_react(), 1);
70111
- import { join as join16, relative } from "path";
70120
+ import { join as join16, relative, dirname as dirname4 } from "path";
70112
70121
  import { homedir as homedir3 } from "os";
70122
+ import { appendFile, mkdir as mkdir4 } from "fs/promises";
70113
70123
 
70114
70124
  // apps/cli/src/agent/config.ts
70115
70125
  import { join as join10 } from "path";
@@ -70280,45 +70290,34 @@ var DEFAULT_CONFIG_TEMPLATE = `{
70280
70290
  // Post a progress update every N iterations. 0 disables. Requires postComments.
70281
70291
  "updateEveryIterations": 10,
70282
70292
 
70283
- // ---------------------------------------------------------------------------
70284
- // Linear indicators \u2014 COMMENTED OUT BY DEFAULT
70285
- //
70286
70293
  // Indicators map Ralph lifecycle events to Linear labels/statuses.
70287
- // WARNING: Activating indicators will query AND mutate your Linear workspace.
70288
- // Labels or statuses that do not already exist may be created automatically.
70289
- // Review every value against your actual Linear workspace before enabling,
70290
- // then replace the empty object below with the full indicators block.
70291
- //
70292
- // To activate, replace "indicators": {} with:
70293
- //
70294
- // "indicators": {
70295
- // // Issues to pick up (any-of filter \u2014 Ralph will start working on these).
70296
- // "getTodo": { "filter": [{ "type": "status", "value": "Todo" }] },
70297
- //
70298
- // // Issues already in flight (resume after restart).
70299
- // "getInProgress": { "filter": [{ "type": "label", "value": "ralph:in-progress" }] },
70300
- //
70301
- // // Issues whose PR has a merge conflict (Ralph will attempt a re-fix run).
70302
- // "getConflicted": { "filter": [{ "type": "label", "value": "ralph:conflict" }] },
70303
- //
70304
- // // Applied when Ralph picks up an issue.
70305
- // "setInProgress": { "type": "label", "value": "ralph:in-progress" },
70306
- //
70307
- // // Applied on clean success.
70308
- // "setDone": { "type": "status", "value": "In Review" },
70309
- //
70310
- // // Applied when the task exits with an error (quarantine signal).
70311
- // "setError": { "type": "label", "value": "ralph:error" },
70312
- //
70313
- // // Applied when a PR merge conflict is detected.
70314
- // "setConflicted": { "type": "label", "value": "ralph:conflict" },
70315
- //
70316
- // // Label-only marker(s) removed once the conflict is fixed.
70317
- // // Note: only label-typed markers are valid here \u2014 status removal is not supported.
70318
- // "clearConflicted": { "type": "label", "value": "ralph:conflict" }
70319
- // }
70320
- // ---------------------------------------------------------------------------
70321
- "indicators": {}
70294
+ // WARNING: activating indicators will query AND mutate your Linear workspace.
70295
+ // Uncomment each entry after confirming the label/status names match your workspace.
70296
+ "indicators": {
70297
+ // Issues to pick up (any-of filter \u2014 Ralph will start working on these).
70298
+ // "getTodo": { "filter": [{ "type": "status", "value": "Todo" }] },
70299
+
70300
+ // Issues already in flight (resume after restart).
70301
+ // "getInProgress": { "filter": [{ "type": "label", "value": "ralph:in-progress" }] },
70302
+
70303
+ // Issues whose PR has a merge conflict (Ralph will attempt a re-fix run).
70304
+ // "getConflicted": { "filter": [{ "type": "label", "value": "ralph:conflict" }] },
70305
+
70306
+ // Applied when Ralph picks up an issue.
70307
+ // "setInProgress": { "type": "label", "value": "ralph:in-progress" },
70308
+
70309
+ // Applied on clean success.
70310
+ // "setDone": { "type": "status", "value": "In Review" },
70311
+
70312
+ // Applied when the task exits with an error (quarantine signal).
70313
+ // "setError": { "type": "label", "value": "ralph:error" },
70314
+
70315
+ // Applied when a PR merge conflict is detected.
70316
+ // "setConflicted": { "type": "label", "value": "ralph:conflict" },
70317
+
70318
+ // Label removed once the conflict is fixed (status removal is not supported here).
70319
+ // "clearConflicted": { "type": "label", "value": "ralph:conflict" }
70320
+ }
70322
70321
  }
70323
70322
  }
70324
70323
  `;
@@ -70530,14 +70529,19 @@ async function fetchTeamIdByKey(apiKey, teamKey) {
70530
70529
  });
70531
70530
  return data.teams.nodes[0]?.id ?? null;
70532
70531
  }
70533
- async function createIssueLabel(apiKey, teamId, name) {
70534
- const mutation = `mutation CreateLabel($teamId: String!, $name: String!) {
70532
+ async function createIssueLabel(apiKey, teamId, name, parentId) {
70533
+ const mutation = parentId ? `mutation CreateLabel($teamId: String!, $name: String!, $parentId: String!) {
70534
+ issueLabelCreate(input: { teamId: $teamId, name: $name, parentId: $parentId }) {
70535
+ success
70536
+ issueLabel { id }
70537
+ }
70538
+ }` : `mutation CreateLabel($teamId: String!, $name: String!) {
70535
70539
  issueLabelCreate(input: { teamId: $teamId, name: $name }) {
70536
70540
  success
70537
70541
  issueLabel { id }
70538
70542
  }
70539
70543
  }`;
70540
- const data = await linearRequest(apiKey, mutation, { teamId, name });
70544
+ const data = await linearRequest(apiKey, mutation, parentId ? { teamId, name, parentId } : { teamId, name });
70541
70545
  return data.issueLabelCreate.issueLabel?.id ?? null;
70542
70546
  }
70543
70547
  async function addLabelToIssue(apiKey, issueId, labelId) {
@@ -70790,7 +70794,7 @@ class AgentCoordinator {
70790
70794
  this.pendingIds.delete(issue.id);
70791
70795
  return;
70792
70796
  }
70793
- if (mode === "fresh" && this.opts.setInProgress) {
70797
+ if (mode !== "resume" && this.opts.setInProgress) {
70794
70798
  try {
70795
70799
  await this.deps.applyIndicator(issue, this.opts.setInProgress);
70796
70800
  } catch (err) {
@@ -70896,6 +70900,11 @@ class AgentCoordinator {
70896
70900
  error: err.message
70897
70901
  });
70898
70902
  }
70903
+ if (this.opts.setInProgress) {
70904
+ try {
70905
+ await this.deps.removeIndicator(issue, this.opts.setInProgress);
70906
+ } catch {}
70907
+ }
70899
70908
  }
70900
70909
  } else if (this.opts.setError) {
70901
70910
  try {
@@ -70908,6 +70917,11 @@ class AgentCoordinator {
70908
70917
  error: err.message
70909
70918
  });
70910
70919
  }
70920
+ if (this.opts.setInProgress) {
70921
+ try {
70922
+ await this.deps.removeIndicator(issue, this.opts.setInProgress);
70923
+ } catch {}
70924
+ }
70911
70925
  }
70912
70926
  }
70913
70927
  stop() {
@@ -71135,6 +71149,7 @@ async function createPullRequest(input, runner) {
71135
71149
  // apps/cli/src/agent/ci.ts
71136
71150
  var PR_CHECKS_FIELDS = "name,bucket,link,workflow,event";
71137
71151
  var TRANSIENT_GH_RE = /HTTP 5\d\d|Gateway Timeout|Bad Gateway|Service Unavailable|connection reset|ECONNRESET|ETIMEDOUT|getaddrinfo|EAI_AGAIN|could not resolve host/i;
71152
+ var NO_CHECKS_RE = /no checks reported/i;
71138
71153
  var GH_RETRY_DELAYS = [5000, 15000, 45000];
71139
71154
  async function runGhWithRetry(cmd, runner, cwd2, onRetry, sleep2 = (ms) => new Promise((r) => setTimeout(r, ms))) {
71140
71155
  let lastErr;
@@ -71159,7 +71174,18 @@ ${e.stdout ?? ""}`;
71159
71174
  throw lastErr;
71160
71175
  }
71161
71176
  async function getPrChecksStatus(prRef, runner, cwd2, onTransientRetry) {
71162
- const out = await runGhWithRetry(["gh", "pr", "checks", prRef, "--json", PR_CHECKS_FIELDS], runner, cwd2, onTransientRetry);
71177
+ let out;
71178
+ try {
71179
+ out = await runGhWithRetry(["gh", "pr", "checks", prRef, "--json", PR_CHECKS_FIELDS], runner, cwd2, onTransientRetry);
71180
+ } catch (err) {
71181
+ const e = err;
71182
+ const blob = `${e.message}
71183
+ ${e.stderr ?? ""}
71184
+ ${e.stdout ?? ""}`;
71185
+ if (NO_CHECKS_RE.test(blob))
71186
+ return { bucket: "pass", failedRunIds: [] };
71187
+ throw err;
71188
+ }
71163
71189
  const checks = JSON.parse(out.stdout || "[]").filter((c) => c.bucket !== "skipping");
71164
71190
  if (checks.some((c) => c.bucket === "pending")) {
71165
71191
  return { bucket: "pending", failedRunIds: [] };
@@ -71262,6 +71288,261 @@ async function reactivateState(stateFilePath, log2, changeName) {
71262
71288
  log2(`! could not reactivate state for ${changeName}: ${err.message}`, "yellow");
71263
71289
  }
71264
71290
  }
71291
+ async function runWorkerWithFixTask(ctx, heading, body) {
71292
+ try {
71293
+ await prependFixTask(join14(ctx.changeDir, "tasks.md"), heading, body);
71294
+ } catch (err) {
71295
+ ctx.log(`! could not prepend fix task: ${err.message}`, "red");
71296
+ return 1;
71297
+ }
71298
+ await reactivateState(ctx.stateFilePath, ctx.log, ctx.changeName);
71299
+ return ctx.respawnWorker();
71300
+ }
71301
+ async function pushWithLeases(ctx) {
71302
+ try {
71303
+ ctx.emit("pushing", "after conflict resolution");
71304
+ await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
71305
+ return true;
71306
+ } catch (pushErr) {
71307
+ const pe = pushErr;
71308
+ const blob = `${pe.message}
71309
+ ${pe.stderr ?? ""}`;
71310
+ if (!/non-fast-forward|Updates were rejected/i.test(blob)) {
71311
+ ctx.log(`! push after conflict fix failed: ${pe.message}`, "red");
71312
+ return false;
71313
+ }
71314
+ try {
71315
+ await ctx.cmd.run(["git", "push", "--force-with-lease", "origin", ctx.branch], ctx.cwd);
71316
+ return true;
71317
+ } catch (forceErr) {
71318
+ ctx.log(`! force-push after conflict fix failed: ${forceErr.message}`, "red");
71319
+ return false;
71320
+ }
71321
+ }
71322
+ }
71323
+ async function commitResidualChanges(ctx, maxAttempts) {
71324
+ let hookFixAttempt = 0;
71325
+ while (true) {
71326
+ ctx.emit("committing", "git status");
71327
+ let dirty = "";
71328
+ try {
71329
+ const status = await ctx.cmd.run(["git", "status", "--porcelain"], ctx.cwd);
71330
+ dirty = status.stdout.trim();
71331
+ } catch (err) {
71332
+ ctx.log(`! git status failed for ${ctx.changeName}: ${err.message}`, "yellow");
71333
+ break;
71334
+ }
71335
+ if (!dirty)
71336
+ break;
71337
+ try {
71338
+ ctx.emit("committing", "git add -A");
71339
+ await ctx.cmd.run(["git", "add", "-A"], ctx.cwd);
71340
+ ctx.emit("committing", "git commit");
71341
+ await ctx.cmd.run(["git", "commit", "-m", `chore(ralph): residual changes for ${ctx.changeName}`], ctx.cwd);
71342
+ ctx.log(` committed residual changes for ${ctx.changeName}`, "gray");
71343
+ break;
71344
+ } catch (err) {
71345
+ const e = err;
71346
+ const detail = e.stderr?.trim() || e.message;
71347
+ const combined = `${e.stdout ?? ""}
71348
+ ${e.stderr ?? ""}`;
71349
+ if (/nothing to commit/i.test(combined) || /empty git commit/i.test(combined))
71350
+ break;
71351
+ if (hookFixAttempt >= maxAttempts) {
71352
+ 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");
71353
+ ctx.log(` detail: ${detail}`, "red");
71354
+ return { gaveUp: true, hookFixAttempt };
71355
+ }
71356
+ hookFixAttempt += 1;
71357
+ ctx.emit("commit-retry", `${hookFixAttempt}/${maxAttempts}`);
71358
+ ctx.log(`! commit rejected for ${ctx.changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxAttempts})`, "yellow");
71359
+ ctx.log(` detail: ${detail}`, "yellow");
71360
+ 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.
71361
+
71362
+ ` + combined.trim());
71363
+ if (retryCode !== 0) {
71364
+ ctx.log(`! worker re-run after commit rejection exited code ${retryCode} \u2014 giving up`, "red");
71365
+ return { gaveUp: true, hookFixAttempt };
71366
+ }
71367
+ }
71368
+ }
71369
+ return { gaveUp: false, hookFixAttempt };
71370
+ }
71371
+ async function createPrWithRetry(ctx, issue, initialHookFixAttempt) {
71372
+ const maxAttempts = ctx.cfg.maxCiFixAttempts;
71373
+ let hookFixAttempt = initialHookFixAttempt;
71374
+ let nonFfRebaseAttempted = false;
71375
+ let pr = null;
71376
+ while (true) {
71377
+ try {
71378
+ ctx.emit("pr-create", "git push + gh pr create");
71379
+ pr = await createPullRequest({ cwd: ctx.cwd, branch: ctx.branch, issue, base: ctx.cfg.prBaseBranch }, ctx.cmd);
71380
+ return { pr, gaveUp: false };
71381
+ } catch (err) {
71382
+ const e = err;
71383
+ const detail = e.stderr?.trim() || e.message;
71384
+ const combined = `${e.stdout ?? ""}
71385
+ ${e.stderr ?? ""}`;
71386
+ const isNonFastForward = /non-fast-forward|Updates were rejected because the (tip of your current branch is behind|remote contains work)/i.test(combined) && !/pre-push hook|hook declined/i.test(combined);
71387
+ const isHookReject = /pre-push hook|hook declined/i.test(combined);
71388
+ const pushRejected = isHookReject || /failed to push some refs/i.test(combined);
71389
+ if (isNonFastForward && !nonFfRebaseAttempted) {
71390
+ nonFfRebaseAttempted = true;
71391
+ ctx.emit("rebasing", `git pull --rebase origin ${ctx.branch}`);
71392
+ ctx.log(` non-fast-forward push for ${ctx.changeName} \u2014 rebasing onto origin/${ctx.branch}`, "yellow");
71393
+ try {
71394
+ await ctx.cmd.run(["git", "fetch", "origin", ctx.branch], ctx.cwd);
71395
+ await ctx.cmd.run(["git", "pull", "--rebase", "origin", ctx.branch], ctx.cwd);
71396
+ continue;
71397
+ } catch (rebaseErr) {
71398
+ const re = rebaseErr;
71399
+ const reBlob = `${re.stdout ?? ""}
71400
+ ${re.stderr ?? ""}`;
71401
+ const isConflict = /CONFLICT|Merge conflict|could not apply|both modified/i.test(reBlob);
71402
+ if (!isConflict) {
71403
+ ctx.log(`! rebase failed for ${ctx.changeName}: ${rebaseErr.message} \u2014 giving up`, "red");
71404
+ return { pr: null, gaveUp: true };
71405
+ }
71406
+ ctx.emit("rebasing", "conflicts detected \u2014 aborting + queueing fix task");
71407
+ try {
71408
+ await ctx.cmd.run(["git", "rebase", "--abort"], ctx.cwd);
71409
+ } catch {}
71410
+ let conflictedFiles = "";
71411
+ try {
71412
+ const r = await ctx.cmd.run(["git", "diff", "--name-only", `HEAD..origin/${ctx.branch}`], ctx.cwd);
71413
+ conflictedFiles = r.stdout.trim();
71414
+ } catch {}
71415
+ if (hookFixAttempt >= maxAttempts) {
71416
+ ctx.log(`! merge conflict on rebase of ${ctx.branch} after ${hookFixAttempt} attempts \u2014 worktree preserved at ${ctx.cwd}`, "red");
71417
+ ctx.log(` detail: ${reBlob.trim().split(`
71418
+ `).slice(0, 8).join(`
71419
+ `)}`, "red");
71420
+ return { pr: null, gaveUp: true };
71421
+ }
71422
+ hookFixAttempt += 1;
71423
+ ctx.emit("rebasing", `conflict-fix ${hookFixAttempt}/${maxAttempts}`);
71424
+ ctx.log(`! merge conflict rebasing ${ctx.branch} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxAttempts})`, "yellow");
71425
+ const retryCode2 = await runWorkerWithFixTask(ctx, "Resolve merge conflict with origin/" + ctx.branch, `Push to origin/${ctx.branch} was rejected as non-fast-forward, and rebasing ` + `onto origin/${ctx.branch} produced merge conflicts.
71426
+
71427
+ ` + `Run \`git fetch origin ${ctx.branch}\` and \`git rebase origin/${ctx.branch}\`, ` + `resolve every conflict, \`git add\` the resolved files, and finish with ` + `\`git rebase --continue\`. The push will be retried after this loop ` + `iteration finishes.
71428
+
71429
+ ` + (conflictedFiles ? `Files that differ between your branch and origin/${ctx.branch}:
71430
+ ${conflictedFiles}
71431
+
71432
+ ` : "") + `Rebase output:
71433
+ ${reBlob.trim()}`);
71434
+ if (retryCode2 !== 0) {
71435
+ ctx.log(`! worker re-run after merge conflict exited code ${retryCode2} \u2014 giving up`, "red");
71436
+ return { pr: null, gaveUp: true };
71437
+ }
71438
+ nonFfRebaseAttempted = false;
71439
+ continue;
71440
+ }
71441
+ }
71442
+ if (!pushRejected || hookFixAttempt >= maxAttempts) {
71443
+ if (pushRejected) {
71444
+ ctx.log(`! push rejected for ${ctx.changeName} after ${hookFixAttempt} fix attempts (push still failing) \u2014 worktree preserved at ${ctx.cwd}`, "red");
71445
+ ctx.log(` detail: ${detail}`, "red");
71446
+ } else {
71447
+ ctx.log(`! PR create failed for ${ctx.changeName}: ${detail}`, "red");
71448
+ }
71449
+ return { pr: null, gaveUp: true };
71450
+ }
71451
+ hookFixAttempt += 1;
71452
+ ctx.emit("push-retry", `${hookFixAttempt}/${maxAttempts}`);
71453
+ ctx.log(`! push rejected for ${ctx.changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxAttempts})`, "yellow");
71454
+ ctx.log(` detail: ${detail}`, "yellow");
71455
+ const retryCode = await runWorkerWithFixTask(ctx, "Fix push rejection", `Push to origin/${ctx.branch} was rejected. Fix the underlying problem ` + `(e.g. failing pre-push hook checks), then the push will be retried.
71456
+
71457
+ ` + combined.trim());
71458
+ if (retryCode !== 0) {
71459
+ ctx.log(`! worker re-run after push rejection exited code ${retryCode} \u2014 giving up`, "red");
71460
+ return { pr: null, gaveUp: true };
71461
+ }
71462
+ }
71463
+ }
71464
+ }
71465
+ async function fixConflictsAndCiLoop(ctx, prUrl, wantFixCi, checkPrConflict) {
71466
+ const wantConflictLoop = !!checkPrConflict;
71467
+ const maxOuterAttempts = ctx.cfg.maxCiFixAttempts;
71468
+ let outerAttempt = 0;
71469
+ let ciConfirmedGreen = false;
71470
+ while (outerAttempt < maxOuterAttempts) {
71471
+ if (wantConflictLoop) {
71472
+ ctx.emit("conflict-check");
71473
+ let conflicting = false;
71474
+ try {
71475
+ conflicting = await checkPrConflict(prUrl);
71476
+ } catch (err) {
71477
+ ctx.log(`! conflict check failed: ${err.message}`, "yellow");
71478
+ }
71479
+ if (conflicting) {
71480
+ outerAttempt++;
71481
+ ciConfirmedGreen = false;
71482
+ ctx.emit("conflict-fix-inner", `attempt ${outerAttempt}/${maxOuterAttempts}`);
71483
+ ctx.log(` merge conflicts on PR (attempt ${outerAttempt}/${maxOuterAttempts}) \u2014 spawning resolution task`, "yellow");
71484
+ const conflictCode = await runWorkerWithFixTask(ctx, "Resolve PR merge conflicts", [
71485
+ `The PR ${prUrl} has merge conflicts with \`${ctx.cfg.prBaseBranch}\`.`,
71486
+ "",
71487
+ "Steps:",
71488
+ `1. \`git fetch origin ${ctx.cfg.prBaseBranch}\` then rebase or merge \`${ctx.cfg.prBaseBranch}\` into the current branch.`,
71489
+ "2. Resolve conflicts in the files git lists.",
71490
+ "3. Stage and commit the resolution."
71491
+ ].join(`
71492
+ `));
71493
+ if (conflictCode !== 0) {
71494
+ ctx.log(`! conflict resolution worker exited code ${conflictCode} \u2014 giving up`, "red");
71495
+ return PR_FAILED_EXIT;
71496
+ }
71497
+ const pushed = await pushWithLeases(ctx);
71498
+ if (!pushed)
71499
+ return PR_FAILED_EXIT;
71500
+ continue;
71501
+ }
71502
+ }
71503
+ if (!wantFixCi)
71504
+ break;
71505
+ if (!ciConfirmedGreen) {
71506
+ ctx.log(` watching CI for ${prUrl} (max ${ctx.cfg.maxCiFixAttempts} fix attempts)`, "gray");
71507
+ ctx.emit("ci-poll", "starting");
71508
+ const result2 = await fixCiUntilGreen({
71509
+ onPhase: (p, d) => ctx.emit(p, d),
71510
+ 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")),
71511
+ getFailedLogs: (ids) => fetchFailedRunLogs(ids, ctx.cmd, ctx.cwd),
71512
+ runTaskWithSteering: async (steering) => {
71513
+ try {
71514
+ await prependFixTask(join14(ctx.changeDir, "tasks.md"), "Fix failing CI checks", steering);
71515
+ } catch (err) {
71516
+ ctx.log(`! could not prepend fix task: ${err.message}`, "red");
71517
+ }
71518
+ return ctx.respawnWorker();
71519
+ },
71520
+ pushBranch: async () => {
71521
+ await ctx.cmd.run(["git", "push", "origin", ctx.branch], ctx.cwd);
71522
+ },
71523
+ log: ctx.log,
71524
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms))
71525
+ }, {
71526
+ maxAttempts: ctx.cfg.maxCiFixAttempts,
71527
+ pollIntervalSeconds: ctx.cfg.ciPollIntervalSeconds
71528
+ });
71529
+ if (!result2.success) {
71530
+ ctx.log(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"}) \u2014 withholding done-status until CI passes`, "red");
71531
+ return CI_FAILED_EXIT;
71532
+ }
71533
+ ciConfirmedGreen = true;
71534
+ }
71535
+ if (wantConflictLoop) {
71536
+ continue;
71537
+ }
71538
+ return 0;
71539
+ }
71540
+ if (outerAttempt >= maxOuterAttempts) {
71541
+ ctx.log(`! outer fix loop exhausted ${maxOuterAttempts} attempts \u2014 giving up`, "red");
71542
+ return CI_FAILED_EXIT;
71543
+ }
71544
+ return 0;
71545
+ }
71265
71546
  async function runPostTask(input, deps) {
71266
71547
  const { log: log2, cmd, git, runScript } = deps;
71267
71548
  const emit = (phase, detail) => deps.onPhase?.(phase, detail);
@@ -71293,201 +71574,33 @@ async function runPostTask(input, deps) {
71293
71574
  log2(`! createPr requested but no worktree branch is tracked for ${changeName} (use --worktree)`, "yellow");
71294
71575
  effectiveCode = PR_FAILED_EXIT;
71295
71576
  } else {
71296
- const maxHookFixAttempts = cfg.maxCiFixAttempts;
71297
- const runWorkerWithFixTask = async (heading, failureOutput) => {
71298
- try {
71299
- await prependFixTask(join14(changeDir, "tasks.md"), heading, failureOutput);
71300
- } catch (err) {
71301
- log2(`! could not prepend fix task: ${err.message}`, "red");
71302
- return 1;
71303
- }
71304
- await reactivateState(stateFilePath, log2, changeName);
71305
- return respawnWorker();
71577
+ const ctx = {
71578
+ changeName,
71579
+ cwd: cwd2,
71580
+ branch,
71581
+ changeDir,
71582
+ stateFilePath,
71583
+ cfg,
71584
+ cmd,
71585
+ log: log2,
71586
+ emit,
71587
+ respawnWorker
71306
71588
  };
71307
- let hookFixAttempt = 0;
71308
- let commitGaveUp = false;
71309
- while (true) {
71310
- emit("committing", "git status");
71311
- let dirty = "";
71312
- try {
71313
- const status = await cmd.run(["git", "status", "--porcelain"], cwd2);
71314
- dirty = status.stdout.trim();
71315
- } catch (err) {
71316
- log2(`! git status failed for ${changeName}: ${err.message}`, "yellow");
71317
- break;
71318
- }
71319
- if (!dirty)
71320
- break;
71321
- try {
71322
- emit("committing", "git add -A");
71323
- await cmd.run(["git", "add", "-A"], cwd2);
71324
- emit("committing", "git commit");
71325
- await cmd.run(["git", "commit", "-m", `chore(ralph): residual changes for ${changeName}`], cwd2);
71326
- log2(` committed residual changes for ${changeName}`, "gray");
71327
- break;
71328
- } catch (err) {
71329
- const e = err;
71330
- const detail = e.stderr?.trim() || e.message;
71331
- const combined = `${e.stdout ?? ""}
71332
- ${e.stderr ?? ""}`;
71333
- if (/nothing to commit/i.test(combined))
71334
- break;
71335
- if (hookFixAttempt >= maxHookFixAttempts) {
71336
- log2(`! commit rejected for ${changeName} after ${hookFixAttempt} hook-fix attempts (host pre-commit hook still failing) \u2014 worktree preserved at ${cwd2}`, "red");
71337
- log2(` detail: ${detail}`, "red");
71338
- effectiveCode = PR_FAILED_EXIT;
71339
- commitGaveUp = true;
71340
- break;
71341
- }
71342
- hookFixAttempt += 1;
71343
- emit("commit-retry", `${hookFixAttempt}/${maxHookFixAttempts}`);
71344
- log2(`! commit rejected for ${changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
71345
- log2(` detail: ${detail}`, "yellow");
71346
- const retryCode = await runWorkerWithFixTask("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.
71347
-
71348
- ` + combined.trim());
71349
- if (retryCode !== 0) {
71350
- log2(`! worker re-run after commit rejection exited code ${retryCode} \u2014 giving up`, "red");
71351
- effectiveCode = PR_FAILED_EXIT;
71352
- commitGaveUp = true;
71353
- break;
71354
- }
71355
- }
71356
- }
71357
- let pr = null;
71358
- let prGaveUp = commitGaveUp;
71359
- let nonFfRebaseAttempted = false;
71360
- while (!prGaveUp) {
71361
- try {
71362
- emit("pr-create", "git push + gh pr create");
71363
- pr = await createPullRequest({ cwd: cwd2, branch, issue, base: cfg.prBaseBranch }, cmd);
71364
- break;
71365
- } catch (err) {
71366
- const e = err;
71367
- const detail = e.stderr?.trim() || e.message;
71368
- const combined = `${e.stdout ?? ""}
71369
- ${e.stderr ?? ""}`;
71370
- const isNonFastForward = /non-fast-forward|Updates were rejected because the (tip of your current branch is behind|remote contains work)/i.test(combined) && !/pre-push hook|hook declined/i.test(combined);
71371
- const isHookReject = /pre-push hook|hook declined/i.test(combined);
71372
- const pushRejected = isHookReject || /failed to push some refs/i.test(combined);
71373
- if (isNonFastForward && !nonFfRebaseAttempted) {
71374
- nonFfRebaseAttempted = true;
71375
- emit("rebasing", `git pull --rebase origin ${branch}`);
71376
- log2(` non-fast-forward push for ${changeName} \u2014 rebasing onto origin/${branch}`, "yellow");
71377
- try {
71378
- await cmd.run(["git", "fetch", "origin", branch], cwd2);
71379
- await cmd.run(["git", "pull", "--rebase", "origin", branch], cwd2);
71380
- continue;
71381
- } catch (rebaseErr) {
71382
- const re = rebaseErr;
71383
- const reBlob = `${re.stdout ?? ""}
71384
- ${re.stderr ?? ""}`;
71385
- const isConflict = /CONFLICT|Merge conflict|could not apply|both modified/i.test(reBlob);
71386
- if (!isConflict) {
71387
- log2(`! rebase failed for ${changeName}: ${rebaseErr.message} \u2014 giving up`, "red");
71388
- effectiveCode = PR_FAILED_EXIT;
71389
- prGaveUp = true;
71390
- break;
71391
- }
71392
- emit("rebasing", "conflicts detected \u2014 aborting + queueing fix task");
71393
- try {
71394
- await cmd.run(["git", "rebase", "--abort"], cwd2);
71395
- } catch {}
71396
- let conflictedFiles = "";
71397
- try {
71398
- const r = await cmd.run(["git", "diff", "--name-only", `HEAD..origin/${branch}`], cwd2);
71399
- conflictedFiles = r.stdout.trim();
71400
- } catch {}
71401
- if (hookFixAttempt >= maxHookFixAttempts) {
71402
- log2(`! merge conflict on rebase of ${branch} after ${hookFixAttempt} attempts \u2014 worktree preserved at ${cwd2}`, "red");
71403
- log2(` detail: ${reBlob.trim().split(`
71404
- `).slice(0, 8).join(`
71405
- `)}`, "red");
71406
- effectiveCode = PR_FAILED_EXIT;
71407
- prGaveUp = true;
71408
- break;
71409
- }
71410
- hookFixAttempt += 1;
71411
- emit("rebasing", `conflict-fix ${hookFixAttempt}/${maxHookFixAttempts}`);
71412
- log2(`! merge conflict rebasing ${branch} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
71413
- const retryCode2 = await runWorkerWithFixTask("Resolve merge conflict with origin/" + branch, `Push to origin/${branch} was rejected as non-fast-forward, and rebasing ` + `onto origin/${branch} produced merge conflicts.
71414
-
71415
- ` + `Run \`git fetch origin ${branch}\` and \`git rebase origin/${branch}\`, ` + `resolve every conflict, \`git add\` the resolved files, and finish with ` + `\`git rebase --continue\`. The push will be retried after this loop ` + `iteration finishes.
71416
-
71417
- ` + (conflictedFiles ? `Files that differ between your branch and origin/${branch}:
71418
- ${conflictedFiles}
71419
-
71420
- ` : "") + `Rebase output:
71421
- ${reBlob.trim()}`);
71422
- if (retryCode2 !== 0) {
71423
- log2(`! worker re-run after merge conflict exited code ${retryCode2} \u2014 giving up`, "red");
71424
- effectiveCode = PR_FAILED_EXIT;
71425
- prGaveUp = true;
71426
- break;
71427
- }
71428
- nonFfRebaseAttempted = false;
71429
- continue;
71430
- }
71431
- }
71432
- if (!pushRejected || hookFixAttempt >= maxHookFixAttempts) {
71433
- if (pushRejected) {
71434
- log2(`! push rejected for ${changeName} after ${hookFixAttempt} fix attempts (push still failing) \u2014 worktree preserved at ${cwd2}`, "red");
71435
- log2(` detail: ${detail}`, "red");
71436
- } else {
71437
- log2(`! PR create failed for ${changeName}: ${detail}`, "red");
71438
- }
71439
- effectiveCode = PR_FAILED_EXIT;
71440
- prGaveUp = true;
71441
- break;
71442
- }
71443
- hookFixAttempt += 1;
71444
- emit("push-retry", `${hookFixAttempt}/${maxHookFixAttempts}`);
71445
- log2(`! push rejected for ${changeName} \u2014 prepending fix task and re-running loop (attempt ${hookFixAttempt}/${maxHookFixAttempts})`, "yellow");
71446
- log2(` detail: ${detail}`, "yellow");
71447
- const retryCode = await runWorkerWithFixTask("Fix push rejection", `Push to origin/${branch} was rejected. Fix the underlying problem ` + `(e.g. failing pre-push hook checks), then the push will be retried.
71448
-
71449
- ` + combined.trim());
71450
- if (retryCode !== 0) {
71451
- log2(`! worker re-run after push rejection exited code ${retryCode} \u2014 giving up`, "red");
71452
- effectiveCode = PR_FAILED_EXIT;
71453
- prGaveUp = true;
71454
- break;
71455
- }
71456
- }
71457
- }
71458
- if (prGaveUp) {} else if (!pr) {
71459
- log2(` no commits ahead of ${cfg.prBaseBranch} \u2014 skipping PR`, "gray");
71589
+ const { gaveUp: commitGaveUp, hookFixAttempt } = await commitResidualChanges(ctx, cfg.maxCiFixAttempts);
71590
+ if (commitGaveUp) {
71591
+ effectiveCode = PR_FAILED_EXIT;
71460
71592
  } else {
71461
- log2(` ${pr.created ? "opened" : "found existing"} PR: ${pr.url}`, "green");
71462
- deps.registerPr?.(changeName, pr.url);
71463
- if (wantFixCi) {
71464
- log2(` watching CI for ${pr.url} (max ${cfg.maxCiFixAttempts} fix attempts)`, "gray");
71465
- emit("ci-poll", "starting");
71466
- const result2 = await fixCiUntilGreen({
71467
- onPhase: (p, d) => emit(p, d),
71468
- getStatus: () => getPrChecksStatus(pr.url, cmd, cwd2, (n, ms, why) => log2(` gh transient (try ${n}) \u2014 retry in ${Math.round(ms / 1000)}s \xB7 ${why}`, "yellow")),
71469
- getFailedLogs: (ids) => fetchFailedRunLogs(ids, cmd, cwd2),
71470
- runTaskWithSteering: async (steering) => {
71471
- try {
71472
- await prependFixTask(join14(changeDir, "tasks.md"), "Fix failing CI checks", steering);
71473
- } catch (err) {
71474
- log2(`! could not prepend fix task: ${err.message}`, "red");
71475
- }
71476
- return respawnWorker();
71477
- },
71478
- pushBranch: async () => {
71479
- await cmd.run(["git", "push", "origin", branch], cwd2);
71480
- },
71481
- log: log2,
71482
- sleep: (ms) => new Promise((r) => setTimeout(r, ms))
71483
- }, {
71484
- maxAttempts: cfg.maxCiFixAttempts,
71485
- pollIntervalSeconds: cfg.ciPollIntervalSeconds
71486
- });
71487
- if (!result2.success) {
71488
- log2(`! CI fix loop gave up after ${result2.attempts} attempts (${result2.reason ?? "unknown"}) \u2014 withholding done-status until CI passes`, "red");
71489
- effectiveCode = CI_FAILED_EXIT;
71490
- }
71593
+ const { pr, gaveUp: prGaveUp } = await createPrWithRetry(ctx, issue, hookFixAttempt);
71594
+ if (prGaveUp) {
71595
+ effectiveCode = PR_FAILED_EXIT;
71596
+ } else if (!pr) {
71597
+ log2(` no commits ahead of ${cfg.prBaseBranch} \u2014 skipping PR`, "gray");
71598
+ } else {
71599
+ log2(` ${pr.created ? "opened" : "found existing"} PR: ${pr.url}`, "green");
71600
+ deps.registerPr?.(changeName, pr.url);
71601
+ const loopCode = await fixConflictsAndCiLoop(ctx, pr.url, wantFixCi, deps.checkPrConflict);
71602
+ if (loopCode !== 0)
71603
+ effectiveCode = loopCode;
71491
71604
  }
71492
71605
  }
71493
71606
  }
@@ -71664,13 +71777,31 @@ function buildAgentCoordinator(input) {
71664
71777
  teamId = fetched;
71665
71778
  teamIdCache.set(t, teamId);
71666
71779
  }
71667
- const newId = await createIssueLabel(apiKey, teamId, name);
71780
+ const colonIdx = name.indexOf(":");
71781
+ let parentId;
71782
+ let childName = name;
71783
+ if (colonIdx > 0) {
71784
+ const groupName = name.slice(0, colonIdx);
71785
+ childName = name.slice(colonIdx + 1);
71786
+ const existingGroup = map2.get(groupName.toLowerCase());
71787
+ if (existingGroup) {
71788
+ parentId = existingGroup;
71789
+ } else {
71790
+ const groupId = await createIssueLabel(apiKey, teamId, groupName);
71791
+ if (groupId) {
71792
+ map2.set(groupName.toLowerCase(), groupId);
71793
+ parentId = groupId;
71794
+ }
71795
+ }
71796
+ }
71797
+ const newId = await createIssueLabel(apiKey, teamId, childName, parentId);
71668
71798
  if (!newId)
71669
71799
  return null;
71670
71800
  map2.set(name.toLowerCase(), newId);
71671
71801
  onLog(` created Linear label '${name}' for team ${t}`, "gray");
71672
71802
  return newId;
71673
- } catch {
71803
+ } catch (err) {
71804
+ onLog(`! Linear label '${name}' creation threw: ${err.message}`, "yellow");
71674
71805
  return null;
71675
71806
  }
71676
71807
  }
@@ -71897,6 +72028,13 @@ PR: ${prUrl}` : ""
71897
72028
  return null;
71898
72029
  }
71899
72030
  };
72031
+ const ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
72032
+ const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
72033
+ const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
72034
+ const ITER_HEADER_LINE_RE = /^\u2500\u2500/;
72035
+ function isLogWorthy(clean) {
72036
+ return !BOX_ONLY_RE.test(clean) && !STATUS_BAR_LINE_RE.test(clean) && !ITER_HEADER_LINE_RE.test(clean);
72037
+ }
71900
72038
  async function pump(stream, label) {
71901
72039
  if (!stream)
71902
72040
  return;
@@ -71916,16 +72054,18 @@ PR: ${prUrl}` : ""
71916
72054
  `)) >= 0) {
71917
72055
  const line = buf.slice(0, nl);
71918
72056
  buf = buf.slice(nl + 1);
71919
- if (writer)
71920
- writer.write(line + `
72057
+ const clean = line.replace(ANSI_RE, "").trim();
72058
+ if (writer && clean && isLogWorthy(clean))
72059
+ writer.write(clean + `
71921
72060
  `);
71922
72061
  if (line)
71923
72062
  onWorkerOutput?.(changeName, label === "err" ? `! ${line}` : line);
71924
72063
  }
71925
72064
  }
71926
72065
  if (buf) {
71927
- if (writer)
71928
- writer.write(buf + `
72066
+ const clean = buf.replace(ANSI_RE, "").trim();
72067
+ if (writer && clean && isLogWorthy(clean))
72068
+ writer.write(clean + `
71929
72069
  `);
71930
72070
  onWorkerOutput?.(changeName, label === "err" ? `! ${buf}` : buf);
71931
72071
  }
@@ -72011,6 +72151,14 @@ PR: ${prUrl}` : ""
72011
72151
  },
72012
72152
  ...onWorkerPhase && {
72013
72153
  onPhase: (phase, detail) => onWorkerPhase(changeName, phase, detail)
72154
+ },
72155
+ checkPrConflict: async (prUrl) => {
72156
+ try {
72157
+ const res = await tracedCmd.run(["gh", "pr", "view", prUrl, "--json", "mergeable", "--jq", ".mergeable"], cwd2);
72158
+ return res.stdout.trim() === "CONFLICTING";
72159
+ } catch {
72160
+ return false;
72161
+ }
72014
72162
  }
72015
72163
  });
72016
72164
  cwdByChange.delete(changeName);
@@ -72165,21 +72313,21 @@ function prLabel(prUrl) {
72165
72313
  const m = prUrl.match(/\/pull\/(\d+)/);
72166
72314
  return m ? `#${m[1]}` : "PR";
72167
72315
  }
72168
- var HYPERLINKS_SUPPORTED = (() => {
72169
- if (process.env["TMUX"])
72170
- return false;
72171
- const tp = process.env["TERM_PROGRAM"];
72172
- if (tp === "iTerm.app" || tp === "WezTerm" || tp === "Hyper")
72173
- return true;
72174
- const term = process.env["TERM"];
72175
- if (term === "xterm-kitty" || term === "foot" || term === "xterm-ghostty")
72176
- return true;
72177
- if (process.env["VTE_VERSION"])
72178
- return true;
72179
- return false;
72180
- })();
72181
- function osc8(url, label) {
72182
- return HYPERLINKS_SUPPORTED ? `\x1B]8;;${url}\x07${label}\x1B]8;;\x07` : label;
72316
+ var HYPERLINKS_SUPPORTED = !process.env["TMUX"];
72317
+ function Link({ url, label, color }) {
72318
+ if (!HYPERLINKS_SUPPORTED)
72319
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72320
+ color,
72321
+ children: label
72322
+ }, undefined, false, undefined, this);
72323
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Transform, {
72324
+ transform: (output) => `\x1B]8;;${url}\x07${output}\x1B]8;;\x07`,
72325
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72326
+ color,
72327
+ underline: true,
72328
+ children: label
72329
+ }, undefined, false, undefined, this)
72330
+ }, undefined, false, undefined, this);
72183
72331
  }
72184
72332
  var ANSI_STRIP_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
72185
72333
  var BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
@@ -72284,10 +72432,16 @@ function displayTailLines(activeCount) {
72284
72432
  }
72285
72433
  var AGENT_LOG_PATH = join16(homedir3(), ".ralph", "agent-mode.log");
72286
72434
  var SESSION_START = new Date().toISOString();
72435
+ mkdir4(dirname4(AGENT_LOG_PATH), { recursive: true }).catch(() => {
72436
+ return;
72437
+ });
72287
72438
  function writeAgentLog(text) {
72288
- const line = `[${new Date().toISOString()}] ${text}
72439
+ const clean = text.replace(ANSI_STRIP_RE, "").trim();
72440
+ if (!clean)
72441
+ return;
72442
+ const line = `[${new Date().toISOString()}] ${clean}
72289
72443
  `;
72290
- Bun.file(AGENT_LOG_PATH).text().catch(() => "").then((existing) => Bun.write(AGENT_LOG_PATH, existing + line)).catch(() => {
72444
+ appendFile(AGENT_LOG_PATH, line).catch(() => {
72291
72445
  return;
72292
72446
  });
72293
72447
  }
@@ -72491,9 +72645,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72491
72645
  setFocusedIdx(n - 1);
72492
72646
  }
72493
72647
  }, { isActive: isRawModeSupported && activeCount > 1 });
72494
- const FIXED_OVERHEAD = 22;
72495
72648
  const nonFocusedCount = Math.max(0, activeCount - 1);
72496
- const focusedTailLines = Math.max(5, termHeight - FIXED_OVERHEAD - nonFocusedCount);
72649
+ const tasksBoxLines = activeCount > 0 ? 5 : 0;
72650
+ const FIXED_OVERHEAD = 5 + 5 + tasksBoxLines + 8 + nonFocusedCount * 4;
72651
+ const focusedTailLines = Math.max(3, termHeight - FIXED_OVERHEAD);
72497
72652
  const compactTailLines = displayTailLines(activeCount);
72498
72653
  return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72499
72654
  flexDirection: "column",
@@ -72993,10 +73148,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72993
73148
  dimColor: true,
72994
73149
  children: "\u2197 LINEAR"
72995
73150
  }, undefined, false, undefined, this),
72996
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72997
- color: "blue",
72998
- underline: HYPERLINKS_SUPPORTED,
72999
- children: osc8(w.issue.url, w.issueIdentifier)
73151
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
73152
+ url: w.issue.url,
73153
+ label: w.issueIdentifier,
73154
+ color: "blue"
73000
73155
  }, undefined, false, undefined, this)
73001
73156
  ]
73002
73157
  }, undefined, true, undefined, this),
@@ -73007,10 +73162,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73007
73162
  dimColor: true,
73008
73163
  children: "\u2197 PR"
73009
73164
  }, undefined, false, undefined, this),
73010
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73011
- color: "green",
73012
- underline: HYPERLINKS_SUPPORTED,
73013
- children: osc8(prUrl, prLabel(prUrl))
73165
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
73166
+ url: prUrl,
73167
+ label: prLabel(prUrl),
73168
+ color: "green"
73014
73169
  }, undefined, false, undefined, this)
73015
73170
  ]
73016
73171
  }, undefined, true, undefined, this)
@@ -73119,11 +73274,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73119
73274
  }
73120
73275
 
73121
73276
  // packages/openspec/src/openspec-change-store.ts
73122
- import { join as join17, dirname as dirname4 } from "path";
73123
- import { readdir, mkdir as mkdir4 } from "fs/promises";
73277
+ import { join as join17, dirname as dirname5 } from "path";
73278
+ import { readdir, mkdir as mkdir5 } from "fs/promises";
73124
73279
  function resolveOpenspecBin() {
73125
73280
  const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
73126
- return join17(dirname4(pkgJsonPath), "bin", "openspec.js");
73281
+ return join17(dirname5(pkgJsonPath), "bin", "openspec.js");
73127
73282
  }
73128
73283
  function runOpenspec(args, options = {}) {
73129
73284
  const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
@@ -73181,7 +73336,7 @@ class OpenSpecChangeStore {
73181
73336
  }
73182
73337
  async writeTaskList(name, content) {
73183
73338
  const path = join17("openspec", "changes", name, "tasks.md");
73184
- await mkdir4(dirname4(path), { recursive: true });
73339
+ await mkdir5(dirname5(path), { recursive: true });
73185
73340
  await Bun.write(path, content);
73186
73341
  }
73187
73342
  async appendSteering(name, message) {
@@ -73192,7 +73347,7 @@ class OpenSpecChangeStore {
73192
73347
 
73193
73348
  ${existing.trimStart()}` : `${message}
73194
73349
  `;
73195
- await mkdir4(dirname4(path), { recursive: true });
73350
+ await mkdir5(dirname5(path), { recursive: true });
73196
73351
  await Bun.write(path, updated);
73197
73352
  }
73198
73353
  async readSection(name, artifact, heading) {
@@ -73403,8 +73558,8 @@ try {
73403
73558
  const statesDir = layout.statesDir;
73404
73559
  const tasksDir = layout.tasksDir;
73405
73560
  if (args.mode === "init") {
73406
- await mkdir5(statesDir, { recursive: true });
73407
- const openspecBin = join19(dirname5(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
73561
+ await mkdir6(statesDir, { recursive: true });
73562
+ const openspecBin = join19(dirname6(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
73408
73563
  Bun.spawnSync({
73409
73564
  cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
73410
73565
  stdio: ["inherit", "inherit", "inherit"],
@@ -73471,13 +73626,13 @@ try {
73471
73626
  process.exit(0);
73472
73627
  }
73473
73628
  if (args.mode === "task" && args.name) {
73474
- await mkdir5(join19(statesDir, args.name), { recursive: true });
73475
- await mkdir5(join19(tasksDir, args.name), { recursive: true });
73629
+ await mkdir6(join19(statesDir, args.name), { recursive: true });
73630
+ await mkdir6(join19(tasksDir, args.name), { recursive: true });
73476
73631
  }
73477
73632
  if (args.mode === "agent") {
73478
- await mkdir5(statesDir, { recursive: true });
73479
- await mkdir5(tasksDir, { recursive: true });
73480
- await mkdir5(join19(projectRoot, ".ralph"), { recursive: true });
73633
+ await mkdir6(statesDir, { recursive: true });
73634
+ await mkdir6(tasksDir, { recursive: true });
73635
+ await mkdir6(join19(projectRoot, ".ralph"), { recursive: true });
73481
73636
  }
73482
73637
  await runWithContext(createDefaultContext(), async () => {
73483
73638
  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.13.15",
3
+ "version": "2.14.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",