@neriros/ralphy 2.13.15 → 2.15.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 +740 -593
  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.15.0")
56422
+ return "2.15.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, 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,67 @@ 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 LabeledBox({
72318
+ label,
72319
+ labelNode,
72320
+ labelVisualWidth,
72321
+ borderColor = "gray",
72322
+ width,
72323
+ children,
72324
+ ...rest2
72325
+ }) {
72326
+ const innerWidth = Math.max(0, width - 2);
72327
+ const visualLen = labelVisualWidth ?? (label ? label.length + 2 : 0);
72328
+ const dashes = Math.max(0, innerWidth - visualLen);
72329
+ const left = Math.floor(dashes / 2);
72330
+ const right = dashes - left;
72331
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72332
+ flexDirection: "column",
72333
+ width,
72334
+ children: [
72335
+ labelNode ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72336
+ flexDirection: "row",
72337
+ children: [
72338
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72339
+ color: borderColor,
72340
+ children: `\u256D${"\u2500".repeat(left)}`
72341
+ }, undefined, false, undefined, this),
72342
+ labelNode,
72343
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72344
+ color: borderColor,
72345
+ children: `${"\u2500".repeat(right)}\u256E`
72346
+ }, undefined, false, undefined, this)
72347
+ ]
72348
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72349
+ color: borderColor,
72350
+ children: `\u256D${"\u2500".repeat(left)} ${label ?? ""} ${"\u2500".repeat(right)}\u256E`
72351
+ }, undefined, false, undefined, this),
72352
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72353
+ borderStyle: "round",
72354
+ borderTop: false,
72355
+ borderColor,
72356
+ width,
72357
+ ...rest2,
72358
+ children
72359
+ }, undefined, false, undefined, this)
72360
+ ]
72361
+ }, undefined, true, undefined, this);
72362
+ }
72363
+ function Link({ url, label, color }) {
72364
+ if (!HYPERLINKS_SUPPORTED)
72365
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72366
+ color,
72367
+ children: label
72368
+ }, undefined, false, undefined, this);
72369
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Transform, {
72370
+ transform: (output) => `\x1B]8;;${url}\x07${output}\x1B]8;;\x07`,
72371
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72372
+ color,
72373
+ underline: true,
72374
+ children: label
72375
+ }, undefined, false, undefined, this)
72376
+ }, undefined, false, undefined, this);
72183
72377
  }
72184
72378
  var ANSI_STRIP_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
72185
72379
  var BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
@@ -72216,7 +72410,7 @@ function modeBadge(mode) {
72216
72410
  case "fresh":
72217
72411
  return { text: "NEW", color: "cyan" };
72218
72412
  case "resume":
72219
- return { text: "RESUME", color: "yellow" };
72413
+ return { text: "RES", color: "yellow" };
72220
72414
  case "conflict-fix":
72221
72415
  return { text: "FIX", color: "magenta" };
72222
72416
  default:
@@ -72284,10 +72478,16 @@ function displayTailLines(activeCount) {
72284
72478
  }
72285
72479
  var AGENT_LOG_PATH = join16(homedir3(), ".ralph", "agent-mode.log");
72286
72480
  var SESSION_START = new Date().toISOString();
72481
+ mkdir4(dirname4(AGENT_LOG_PATH), { recursive: true }).catch(() => {
72482
+ return;
72483
+ });
72287
72484
  function writeAgentLog(text) {
72288
- const line = `[${new Date().toISOString()}] ${text}
72485
+ const clean = text.replace(ANSI_STRIP_RE, "").trim();
72486
+ if (!clean)
72487
+ return;
72488
+ const line = `[${new Date().toISOString()}] ${clean}
72289
72489
  `;
72290
- Bun.file(AGENT_LOG_PATH).text().catch(() => "").then((existing) => Bun.write(AGENT_LOG_PATH, existing + line)).catch(() => {
72490
+ appendFile(AGENT_LOG_PATH, line).catch(() => {
72291
72491
  return;
72292
72492
  });
72293
72493
  }
@@ -72491,43 +72691,40 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72491
72691
  setFocusedIdx(n - 1);
72492
72692
  }
72493
72693
  }, { isActive: isRawModeSupported && activeCount > 1 });
72494
- const FIXED_OVERHEAD = 22;
72495
72694
  const nonFocusedCount = Math.max(0, activeCount - 1);
72496
- const focusedTailLines = Math.max(5, termHeight - FIXED_OVERHEAD - nonFocusedCount);
72695
+ const tasksBoxLines = activeCount > 0 ? 5 : 0;
72696
+ const FIXED_OVERHEAD = 5 + 5 + tasksBoxLines + 8 + nonFocusedCount * 4;
72697
+ const focusedTailLines = Math.max(3, termHeight - FIXED_OVERHEAD);
72497
72698
  const compactTailLines = displayTailLines(activeCount);
72498
72699
  return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72499
72700
  flexDirection: "column",
72500
72701
  children: [
72501
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Static, {
72502
- items: logs,
72503
- children: (line) => line.color ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72702
+ logs.length > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
72703
+ label: "LOGS",
72704
+ borderColor: "gray",
72705
+ flexDirection: "column",
72706
+ paddingX: 1,
72707
+ width: termWidth,
72708
+ children: logs.slice(-5).map((line) => line.color ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72504
72709
  color: line.color,
72505
72710
  children: line.text
72506
72711
  }, line.id, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72507
72712
  children: line.text
72508
- }, line.id, false, undefined, this)
72713
+ }, line.id, false, undefined, this))
72509
72714
  }, undefined, false, undefined, this),
72510
72715
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72511
72716
  flexDirection: "column",
72512
- marginTop: 1,
72717
+ marginTop: 0,
72513
72718
  children: [
72514
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72515
- borderStyle: "round",
72719
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
72720
+ label: "\u25C8 RALPH AGENT",
72516
72721
  borderColor: "blue",
72517
- flexDirection: "column",
72518
- paddingX: 1,
72519
72722
  width: termWidth,
72723
+ paddingX: 1,
72724
+ flexDirection: "column",
72520
72725
  children: [
72521
72726
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72522
72727
  children: [
72523
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72524
- bold: true,
72525
- color: "cyan",
72526
- children: [
72527
- "\u25C8 RALPH AGENT",
72528
- " "
72529
- ]
72530
- }, undefined, true, undefined, this),
72531
72728
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72532
72729
  dimColor: true,
72533
72730
  children: [
@@ -72627,206 +72824,176 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72627
72824
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72628
72825
  flexDirection: "row",
72629
72826
  gap: 1,
72630
- marginTop: 1,
72827
+ marginTop: 0,
72631
72828
  width: termWidth,
72632
72829
  children: [
72633
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72634
- borderStyle: "round",
72830
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
72831
+ label: "POLL STATUS",
72635
72832
  borderColor: "gray",
72636
- flexDirection: "column",
72833
+ width: termWidth - 30,
72637
72834
  paddingX: 1,
72638
- flexGrow: 1,
72639
- children: [
72640
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72641
- dimColor: true,
72642
- bold: true,
72643
- children: "POLL STATUS"
72644
- }, undefined, false, undefined, this),
72645
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72646
- gap: 2,
72647
- marginTop: 0,
72648
- children: [
72649
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72650
- color: "gray",
72651
- children: spinnerFrame
72652
- }, undefined, false, undefined, this),
72653
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72654
- children: pollStatus.state === "polling" ? "Polling Linear\u2026" : pollStatus.lastAt !== null ? "Idle" : "Starting\u2026"
72655
- }, undefined, false, undefined, this),
72656
- pollStatus.lastAt !== null && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
72657
- children: [
72658
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72659
- dimColor: true,
72660
- children: "\u2502"
72661
- }, undefined, false, undefined, this),
72662
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72663
- dimColor: true,
72664
- children: "found"
72665
- }, undefined, false, undefined, this),
72666
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72667
- color: "white",
72668
- children: pollStatus.lastFound
72669
- }, undefined, false, undefined, this),
72670
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72671
- dimColor: true,
72672
- children: "\u2502"
72673
- }, undefined, false, undefined, this),
72674
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72675
- dimColor: true,
72676
- children: "new"
72677
- }, undefined, false, undefined, this),
72678
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72679
- color: pollStatus.lastAdded > 0 ? "green" : "white",
72680
- children: pollStatus.lastAdded
72681
- }, undefined, false, undefined, this),
72682
- secsToNextPoll !== null && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
72683
- children: [
72684
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72685
- dimColor: true,
72686
- children: "\u2502"
72687
- }, undefined, false, undefined, this),
72688
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72689
- dimColor: true,
72690
- children: "next in"
72691
- }, undefined, false, undefined, this),
72692
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72693
- color: "gray",
72694
- children: [
72695
- secsToNextPoll,
72696
- "s"
72697
- ]
72698
- }, undefined, true, undefined, this)
72699
- ]
72700
- }, undefined, true, undefined, this)
72701
- ]
72702
- }, undefined, true, undefined, this)
72703
- ]
72704
- }, undefined, true, undefined, this)
72705
- ]
72706
- }, undefined, true, undefined, this),
72707
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72708
- borderStyle: "round",
72709
- borderColor: "gray",
72710
72835
  flexDirection: "column",
72836
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72837
+ gap: 2,
72838
+ children: [
72839
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72840
+ color: "gray",
72841
+ children: spinnerFrame
72842
+ }, undefined, false, undefined, this),
72843
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72844
+ children: pollStatus.state === "polling" ? "Polling Linear\u2026" : pollStatus.lastAt !== null ? "Idle" : "Starting\u2026"
72845
+ }, undefined, false, undefined, this),
72846
+ pollStatus.lastAt !== null && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
72847
+ children: [
72848
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72849
+ dimColor: true,
72850
+ children: "\u2502"
72851
+ }, undefined, false, undefined, this),
72852
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72853
+ dimColor: true,
72854
+ children: "found"
72855
+ }, undefined, false, undefined, this),
72856
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72857
+ color: "white",
72858
+ children: pollStatus.lastFound
72859
+ }, undefined, false, undefined, this),
72860
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72861
+ dimColor: true,
72862
+ children: "\u2502"
72863
+ }, undefined, false, undefined, this),
72864
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72865
+ dimColor: true,
72866
+ children: "new"
72867
+ }, undefined, false, undefined, this),
72868
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72869
+ color: pollStatus.lastAdded > 0 ? "green" : "white",
72870
+ children: pollStatus.lastAdded
72871
+ }, undefined, false, undefined, this),
72872
+ secsToNextPoll !== null && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
72873
+ children: [
72874
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72875
+ dimColor: true,
72876
+ children: "\u2502"
72877
+ }, undefined, false, undefined, this),
72878
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72879
+ dimColor: true,
72880
+ children: "next in"
72881
+ }, undefined, false, undefined, this),
72882
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72883
+ color: "gray",
72884
+ children: [
72885
+ secsToNextPoll,
72886
+ "s"
72887
+ ]
72888
+ }, undefined, true, undefined, this)
72889
+ ]
72890
+ }, undefined, true, undefined, this)
72891
+ ]
72892
+ }, undefined, true, undefined, this)
72893
+ ]
72894
+ }, undefined, true, undefined, this)
72895
+ }, undefined, false, undefined, this),
72896
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
72897
+ label: "WORKERS",
72898
+ borderColor: "gray",
72899
+ width: 29,
72711
72900
  paddingX: 1,
72712
- minWidth: 28,
72713
- children: [
72714
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72715
- dimColor: true,
72716
- bold: true,
72717
- children: "WORKERS"
72718
- }, undefined, false, undefined, this),
72719
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72720
- gap: 3,
72721
- marginTop: 0,
72722
- children: [
72723
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72724
- gap: 1,
72725
- children: [
72726
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72727
- dimColor: true,
72728
- children: "active"
72729
- }, undefined, false, undefined, this),
72730
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72731
- color: activeCount > 0 ? "cyan" : "gray",
72732
- bold: true,
72733
- children: activeCount
72734
- }, undefined, false, undefined, this)
72735
- ]
72736
- }, undefined, true, undefined, this),
72737
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72738
- gap: 1,
72739
- children: [
72740
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72741
- dimColor: true,
72742
- children: "queued"
72743
- }, undefined, false, undefined, this),
72744
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72745
- color: coord?.queuedCount ?? 0 > 0 ? "yellow" : "gray",
72746
- bold: true,
72747
- children: coord?.queuedCount ?? 0
72748
- }, undefined, false, undefined, this)
72749
- ]
72750
- }, undefined, true, undefined, this)
72751
- ]
72752
- }, undefined, true, undefined, this)
72753
- ]
72754
- }, undefined, true, undefined, this)
72901
+ flexDirection: "column",
72902
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72903
+ gap: 3,
72904
+ children: [
72905
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72906
+ gap: 1,
72907
+ children: [
72908
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72909
+ dimColor: true,
72910
+ children: "active"
72911
+ }, undefined, false, undefined, this),
72912
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72913
+ color: activeCount > 0 ? "cyan" : "gray",
72914
+ bold: true,
72915
+ children: activeCount
72916
+ }, undefined, false, undefined, this)
72917
+ ]
72918
+ }, undefined, true, undefined, this),
72919
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72920
+ gap: 1,
72921
+ children: [
72922
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72923
+ dimColor: true,
72924
+ children: "queued"
72925
+ }, undefined, false, undefined, this),
72926
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72927
+ color: coord?.queuedCount ?? 0 > 0 ? "yellow" : "gray",
72928
+ bold: true,
72929
+ children: coord?.queuedCount ?? 0
72930
+ }, undefined, false, undefined, this)
72931
+ ]
72932
+ }, undefined, true, undefined, this)
72933
+ ]
72934
+ }, undefined, true, undefined, this)
72935
+ }, undefined, false, undefined, this)
72755
72936
  ]
72756
72937
  }, undefined, true, undefined, this),
72757
- activeCount > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72758
- borderStyle: "round",
72938
+ activeCount > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
72939
+ label: `TASKS${activeCount > 1 ? " Tab/\u2190 \u2192 \xB7 1-9" : ""}`,
72759
72940
  borderColor: "gray",
72760
- flexDirection: "column",
72761
- paddingX: 1,
72762
- marginTop: 1,
72763
72941
  width: termWidth,
72764
- children: [
72765
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72766
- gap: 1,
72767
- children: [
72768
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72769
- dimColor: true,
72770
- bold: true,
72771
- children: "TASKS"
72772
- }, undefined, false, undefined, this),
72773
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72774
- dimColor: true,
72775
- children: activeCount > 1 ? " Tab/\u2190 \u2192 to switch \xB7 1-9 jump" : ""
72776
- }, undefined, false, undefined, this)
72777
- ]
72778
- }, undefined, true, undefined, this),
72779
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72780
- gap: 3,
72781
- flexWrap: "wrap",
72782
- children: coord?.activeWorkers.map((w, idx) => {
72783
- const meta = workerMetaRef.current.get(w.changeName);
72784
- const phase = meta?.phase ?? "working";
72785
- const pBadge = priorityBadge(w.issue.priority);
72786
- const isFocused = idx === safeFocusedIdx;
72787
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72788
- gap: 1,
72789
- children: [
72790
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72791
- color: isFocused ? "white" : "gray",
72792
- bold: isFocused,
72793
- children: [
72794
- "[",
72795
- idx + 1,
72796
- "]"
72797
- ]
72798
- }, undefined, true, undefined, this),
72799
- pBadge.label && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72800
- color: pBadge.color,
72801
- children: pBadge.text
72802
- }, undefined, false, undefined, this),
72803
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72804
- color: isFocused ? "cyan" : "gray",
72805
- bold: isFocused,
72806
- children: w.issueIdentifier
72807
- }, undefined, false, undefined, this),
72808
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72809
- color: phaseColor(phase),
72810
- dimColor: !isFocused,
72811
- children: phase
72812
- }, undefined, false, undefined, this),
72813
- isFocused && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72814
- color: "white",
72815
- children: "\u25C0"
72816
- }, undefined, false, undefined, this)
72817
- ]
72818
- }, w.changeName, true, undefined, this);
72819
- })
72820
- }, undefined, false, undefined, this)
72821
- ]
72822
- }, undefined, true, undefined, this),
72942
+ paddingX: 1,
72943
+ flexDirection: "column",
72944
+ children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72945
+ gap: 3,
72946
+ flexWrap: "wrap",
72947
+ children: coord?.activeWorkers.map((w, idx) => {
72948
+ const meta = workerMetaRef.current.get(w.changeName);
72949
+ const phase = meta?.phase ?? "working";
72950
+ const pBadge = priorityBadge(w.issue.priority);
72951
+ const isFocused = idx === safeFocusedIdx;
72952
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72953
+ gap: 1,
72954
+ children: [
72955
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72956
+ color: isFocused ? "white" : "gray",
72957
+ bold: isFocused,
72958
+ children: [
72959
+ "[",
72960
+ idx + 1,
72961
+ "]"
72962
+ ]
72963
+ }, undefined, true, undefined, this),
72964
+ pBadge.label && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72965
+ color: pBadge.color,
72966
+ children: [
72967
+ pBadge.text,
72968
+ " ",
72969
+ pBadge.label
72970
+ ]
72971
+ }, undefined, true, undefined, this),
72972
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
72973
+ url: w.issue.url,
72974
+ label: w.issueIdentifier,
72975
+ color: isFocused ? "cyan" : "gray"
72976
+ }, undefined, false, undefined, this),
72977
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72978
+ color: phaseColor(phase),
72979
+ dimColor: !isFocused,
72980
+ children: phase
72981
+ }, undefined, false, undefined, this),
72982
+ isFocused && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72983
+ color: "white",
72984
+ children: "\u25C0"
72985
+ }, undefined, false, undefined, this)
72986
+ ]
72987
+ }, w.changeName, true, undefined, this);
72988
+ })
72989
+ }, undefined, false, undefined, this)
72990
+ }, undefined, false, undefined, this),
72823
72991
  coord?.activeWorkers.map((w, idx) => {
72824
72992
  const isFocused = idx === safeFocusedIdx;
72825
72993
  const meta = workerMetaRef.current.get(w.changeName);
72826
72994
  const elapsed = meta ? fmtElapsed(now2 - meta.startedAt) : "\u2013";
72827
72995
  const iter = meta?.iter ?? 0;
72828
72996
  const phase = meta?.phase ?? "working";
72829
- const phaseElapsed = meta ? fmtElapsed(now2 - meta.phaseStartedAt) : "\u2013";
72830
72997
  const phaseDetail = meta?.phaseDetail ?? "";
72831
72998
  const cmd = meta?.currentCmd;
72832
72999
  const cmdElapsed = cmd ? fmtElapsed(now2 - cmd.startedAt) : null;
@@ -72839,11 +73006,38 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72839
73006
  const bColor = isFocused ? workerBorderColor(phase) : "gray";
72840
73007
  const visibleTailLines = isFocused ? focusedTailLines : compactTailLines;
72841
73008
  if (!isFocused && activeCount > 1) {
72842
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72843
- borderStyle: "round",
73009
+ const cardLabelWidth2 = (prUrl ? prLabel(prUrl).length + 3 : 0) + w.issueIdentifier.length + 2;
73010
+ const cardLabelNode2 = /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
73011
+ children: [
73012
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73013
+ color: "gray",
73014
+ children: " "
73015
+ }, undefined, false, undefined, this),
73016
+ prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
73017
+ url: prUrl,
73018
+ label: prLabel(prUrl),
73019
+ color: "green"
73020
+ }, undefined, false, undefined, this),
73021
+ prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73022
+ color: "gray",
73023
+ children: " \xB7 "
73024
+ }, undefined, false, undefined, this),
73025
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
73026
+ url: w.issue.url,
73027
+ label: w.issueIdentifier,
73028
+ color: "cyan"
73029
+ }, undefined, false, undefined, this),
73030
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73031
+ color: "gray",
73032
+ children: " "
73033
+ }, undefined, false, undefined, this)
73034
+ ]
73035
+ }, undefined, true, undefined, this);
73036
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
73037
+ labelNode: cardLabelNode2,
73038
+ labelVisualWidth: cardLabelWidth2,
72844
73039
  borderColor: "gray",
72845
73040
  paddingX: 1,
72846
- marginTop: 1,
72847
73041
  gap: 2,
72848
73042
  width: termWidth,
72849
73043
  children: [
@@ -72914,12 +73108,39 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72914
73108
  ]
72915
73109
  }, w.changeName, true, undefined, this);
72916
73110
  }
72917
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72918
- borderStyle: "round",
73111
+ const cardLabelWidth = (prUrl ? prLabel(prUrl).length + 3 : 0) + w.issueIdentifier.length + 2;
73112
+ const cardLabelNode = /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
73113
+ children: [
73114
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73115
+ color: bColor,
73116
+ children: " "
73117
+ }, undefined, false, undefined, this),
73118
+ prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
73119
+ url: prUrl,
73120
+ label: prLabel(prUrl),
73121
+ color: "green"
73122
+ }, undefined, false, undefined, this),
73123
+ prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73124
+ color: bColor,
73125
+ children: " \xB7 "
73126
+ }, undefined, false, undefined, this),
73127
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
73128
+ url: w.issue.url,
73129
+ label: w.issueIdentifier,
73130
+ color: "cyan"
73131
+ }, undefined, false, undefined, this),
73132
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73133
+ color: bColor,
73134
+ children: " "
73135
+ }, undefined, false, undefined, this)
73136
+ ]
73137
+ }, undefined, true, undefined, this);
73138
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
73139
+ labelNode: cardLabelNode,
73140
+ labelVisualWidth: cardLabelWidth,
72919
73141
  borderColor: bColor,
72920
73142
  flexDirection: "column",
72921
73143
  paddingX: 1,
72922
- marginTop: 1,
72923
73144
  width: termWidth,
72924
73145
  children: [
72925
73146
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
@@ -72928,23 +73149,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72928
73149
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72929
73150
  children: spinnerFrame
72930
73151
  }, undefined, false, undefined, this),
72931
- pBadge.label && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72932
- color: pBadge.color,
72933
- children: [
72934
- pBadge.text,
72935
- " ",
72936
- pBadge.label
72937
- ]
72938
- }, undefined, true, undefined, this),
72939
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72940
- color: "cyan",
72941
- bold: true,
72942
- children: w.issueIdentifier
72943
- }, undefined, false, undefined, this),
72944
73152
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72945
73153
  color: "white",
72946
73154
  bold: true,
72947
- children: trunc(w.issue.title, Math.max(30, termWidth - 60))
73155
+ children: trunc(w.issue.title, Math.max(20, termWidth - 55))
72948
73156
  }, undefined, false, undefined, this),
72949
73157
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72950
73158
  color: mBadge.color,
@@ -72956,12 +73164,16 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72956
73164
  ]
72957
73165
  }, undefined, true, undefined, this),
72958
73166
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72959
- dimColor: true,
72960
- children: "\u2502"
72961
- }, undefined, false, undefined, this),
73167
+ color: pColor,
73168
+ bold: true,
73169
+ children: [
73170
+ phase,
73171
+ phaseDetail ? ` (${phaseDetail})` : ""
73172
+ ]
73173
+ }, undefined, true, undefined, this),
72962
73174
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72963
73175
  dimColor: true,
72964
- children: "elapsed"
73176
+ children: "\u2502"
72965
73177
  }, undefined, false, undefined, this),
72966
73178
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72967
73179
  color: "white",
@@ -72973,49 +73185,24 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72973
73185
  }, undefined, false, undefined, this),
72974
73186
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72975
73187
  dimColor: true,
72976
- children: "iter"
73188
+ children: "\u21BA"
72977
73189
  }, undefined, false, undefined, this),
72978
73190
  /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72979
73191
  color: "white",
72980
73192
  bold: true,
72981
73193
  children: iter
73194
+ }, undefined, false, undefined, this),
73195
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73196
+ dimColor: true,
73197
+ children: "\u2502"
73198
+ }, undefined, false, undefined, this),
73199
+ meta?.logFile && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
73200
+ url: `file://${meta.logFile}`,
73201
+ label: "LOG",
73202
+ color: "gray"
72982
73203
  }, undefined, false, undefined, this)
72983
73204
  ]
72984
73205
  }, undefined, true, undefined, this),
72985
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72986
- gap: 3,
72987
- marginTop: 0,
72988
- children: [
72989
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
72990
- gap: 1,
72991
- children: [
72992
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
72993
- dimColor: true,
72994
- children: "\u2197 LINEAR"
72995
- }, 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)
73000
- }, undefined, false, undefined, this)
73001
- ]
73002
- }, undefined, true, undefined, this),
73003
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
73004
- gap: 1,
73005
- children: [
73006
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73007
- dimColor: true,
73008
- children: "\u2197 PR"
73009
- }, undefined, false, undefined, this),
73010
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73011
- color: "green",
73012
- underline: HYPERLINKS_SUPPORTED,
73013
- children: osc8(prUrl, prLabel(prUrl))
73014
- }, undefined, false, undefined, this)
73015
- ]
73016
- }, undefined, true, undefined, this)
73017
- ]
73018
- }, undefined, true, undefined, this),
73019
73206
  currentTask && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
73020
73207
  gap: 1,
73021
73208
  marginTop: 0,
@@ -73031,62 +73218,22 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73031
73218
  }, undefined, false, undefined, this)
73032
73219
  ]
73033
73220
  }, undefined, true, undefined, this),
73034
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
73035
- gap: 3,
73221
+ cmd && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
73222
+ gap: 1,
73036
73223
  marginTop: 0,
73037
- flexWrap: "wrap",
73038
73224
  children: [
73039
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
73040
- gap: 1,
73041
- children: [
73042
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73043
- dimColor: true,
73044
- children: "PHASE"
73045
- }, undefined, false, undefined, this),
73046
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73047
- color: pColor,
73048
- bold: true,
73049
- children: [
73050
- phase,
73051
- phaseDetail ? ` (${phaseDetail})` : ""
73052
- ]
73053
- }, undefined, true, undefined, this),
73054
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73055
- dimColor: true,
73056
- children: phaseElapsed
73057
- }, undefined, false, undefined, this)
73058
- ]
73059
- }, undefined, true, undefined, this),
73060
- cmd && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
73061
- gap: 1,
73062
- children: [
73063
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73064
- color: "yellow",
73065
- children: "\u23F5 CMD"
73066
- }, undefined, false, undefined, this),
73067
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73068
- color: "yellow",
73069
- children: fmtCmd(cmd.argv)
73070
- }, undefined, false, undefined, this),
73071
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73072
- dimColor: true,
73073
- children: cmdElapsed
73074
- }, undefined, false, undefined, this)
73075
- ]
73076
- }, undefined, true, undefined, this),
73077
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
73078
- gap: 1,
73079
- children: [
73080
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73081
- dimColor: true,
73082
- children: "LOG"
73083
- }, undefined, false, undefined, this),
73084
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73085
- dimColor: true,
73086
- children: trunc(meta?.logFile ? relative(process.cwd(), meta.logFile) : "\u2013", 40)
73087
- }, undefined, false, undefined, this)
73088
- ]
73089
- }, undefined, true, undefined, this)
73225
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73226
+ color: "yellow",
73227
+ children: "\u23F5 CMD"
73228
+ }, undefined, false, undefined, this),
73229
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73230
+ color: "yellow",
73231
+ children: fmtCmd(cmd.argv)
73232
+ }, undefined, false, undefined, this),
73233
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
73234
+ dimColor: true,
73235
+ children: cmdElapsed
73236
+ }, undefined, false, undefined, this)
73090
73237
  ]
73091
73238
  }, undefined, true, undefined, this),
73092
73239
  tail2.length > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
@@ -73119,11 +73266,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73119
73266
  }
73120
73267
 
73121
73268
  // 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";
73269
+ import { join as join17, dirname as dirname5 } from "path";
73270
+ import { readdir, mkdir as mkdir5 } from "fs/promises";
73124
73271
  function resolveOpenspecBin() {
73125
73272
  const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
73126
- return join17(dirname4(pkgJsonPath), "bin", "openspec.js");
73273
+ return join17(dirname5(pkgJsonPath), "bin", "openspec.js");
73127
73274
  }
73128
73275
  function runOpenspec(args, options = {}) {
73129
73276
  const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
@@ -73181,7 +73328,7 @@ class OpenSpecChangeStore {
73181
73328
  }
73182
73329
  async writeTaskList(name, content) {
73183
73330
  const path = join17("openspec", "changes", name, "tasks.md");
73184
- await mkdir4(dirname4(path), { recursive: true });
73331
+ await mkdir5(dirname5(path), { recursive: true });
73185
73332
  await Bun.write(path, content);
73186
73333
  }
73187
73334
  async appendSteering(name, message) {
@@ -73192,7 +73339,7 @@ class OpenSpecChangeStore {
73192
73339
 
73193
73340
  ${existing.trimStart()}` : `${message}
73194
73341
  `;
73195
- await mkdir4(dirname4(path), { recursive: true });
73342
+ await mkdir5(dirname5(path), { recursive: true });
73196
73343
  await Bun.write(path, updated);
73197
73344
  }
73198
73345
  async readSection(name, artifact, heading) {
@@ -73403,8 +73550,8 @@ try {
73403
73550
  const statesDir = layout.statesDir;
73404
73551
  const tasksDir = layout.tasksDir;
73405
73552
  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");
73553
+ await mkdir6(statesDir, { recursive: true });
73554
+ const openspecBin = join19(dirname6(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
73408
73555
  Bun.spawnSync({
73409
73556
  cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
73410
73557
  stdio: ["inherit", "inherit", "inherit"],
@@ -73471,13 +73618,13 @@ try {
73471
73618
  process.exit(0);
73472
73619
  }
73473
73620
  if (args.mode === "task" && args.name) {
73474
- await mkdir5(join19(statesDir, args.name), { recursive: true });
73475
- await mkdir5(join19(tasksDir, args.name), { recursive: true });
73621
+ await mkdir6(join19(statesDir, args.name), { recursive: true });
73622
+ await mkdir6(join19(tasksDir, args.name), { recursive: true });
73476
73623
  }
73477
73624
  if (args.mode === "agent") {
73478
- await mkdir5(statesDir, { recursive: true });
73479
- await mkdir5(tasksDir, { recursive: true });
73480
- await mkdir5(join19(projectRoot, ".ralph"), { recursive: true });
73625
+ await mkdir6(statesDir, { recursive: true });
73626
+ await mkdir6(tasksDir, { recursive: true });
73627
+ await mkdir6(join19(projectRoot, ".ralph"), { recursive: true });
73481
73628
  }
73482
73629
  await runWithContext(createDefaultContext(), async () => {
73483
73630
  const { waitUntilExit } = render_default(import_react59.createElement(App2, { args, statesDir, tasksDir, projectRoot }));