@neriros/ralphy 3.0.1 → 3.1.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 (3) hide show
  1. package/README.md +29 -0
  2. package/dist/shell/index.js +1764 -474
  3. package/package.json +1 -1
@@ -18923,8 +18923,44 @@ var init_node = __esm(() => {
18923
18923
  };
18924
18924
  });
18925
18925
 
18926
+ // packages/version/src/version.ts
18927
+ import { readFileSync } from "fs";
18928
+ import { resolve } from "path";
18929
+ function getVersion() {
18930
+ try {
18931
+ if ("3.1.0")
18932
+ return "3.1.0";
18933
+ } catch {}
18934
+ const dirsToTry = [];
18935
+ try {
18936
+ dirsToTry.push(import.meta.dir);
18937
+ } catch {}
18938
+ dirsToTry.push(process.cwd());
18939
+ for (const startDir of dirsToTry) {
18940
+ let current = startDir;
18941
+ for (let i = 0;i < 10; i++) {
18942
+ const pkgPath = resolve(current, "package.json");
18943
+ try {
18944
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
18945
+ if (pkg.workspaces && pkg.version && pkg.version !== "0.0.0") {
18946
+ return pkg.version;
18947
+ }
18948
+ } catch {}
18949
+ const parent = resolve(current, "..");
18950
+ if (parent === current)
18951
+ break;
18952
+ current = parent;
18953
+ }
18954
+ }
18955
+ return "unknown";
18956
+ }
18957
+ var VERSION;
18958
+ var init_version = __esm(() => {
18959
+ VERSION = getVersion();
18960
+ });
18961
+
18926
18962
  // packages/telemetry/src/index.ts
18927
- import { homedir } from "os";
18963
+ import { homedir, hostname, platform, arch, release } from "os";
18928
18964
  import { join } from "path";
18929
18965
  import { randomUUID } from "crypto";
18930
18966
  function setDefaultProperties(props) {
@@ -18946,6 +18982,23 @@ async function init() {
18946
18982
  flushAt: 20,
18947
18983
  flushInterval: 0
18948
18984
  });
18985
+ defaultProps = {
18986
+ version: VERSION,
18987
+ machine_name: hostname(),
18988
+ platform: platform(),
18989
+ arch: arch(),
18990
+ os_release: release(),
18991
+ ...defaultProps
18992
+ };
18993
+ }
18994
+ function captureError(event, error, properties) {
18995
+ const err = error instanceof Error ? error : new Error(String(error));
18996
+ capture(event, {
18997
+ ...properties,
18998
+ error_message: err.message,
18999
+ error_name: err.name,
19000
+ error_stack: err.stack
19001
+ });
18949
19002
  }
18950
19003
  function capture(event, properties) {
18951
19004
  const merged = { ...defaultProps, ...properties };
@@ -18958,47 +19011,12 @@ async function shutdown() {
18958
19011
  var POSTHOG_KEY, HOST = "https://eu.i.posthog.com", enabled, client = null, distinctId = "anonymous", defaultProps;
18959
19012
  var init_src = __esm(() => {
18960
19013
  init_node();
19014
+ init_version();
18961
19015
  POSTHOG_KEY = process.env["RALPH_POSTHOG_KEY"] ?? "phc_Bua8TpmaxSSM8h43htLrm6VUoaB2L9GZgA4kiEcMrpaY";
18962
19016
  enabled = process.env["RALPH_TELEMETRY"] !== "0";
18963
19017
  defaultProps = {};
18964
19018
  });
18965
19019
 
18966
- // packages/version/src/version.ts
18967
- import { readFileSync } from "fs";
18968
- import { resolve } from "path";
18969
- function getVersion() {
18970
- try {
18971
- if ("3.0.1")
18972
- return "3.0.1";
18973
- } catch {}
18974
- const dirsToTry = [];
18975
- try {
18976
- dirsToTry.push(import.meta.dir);
18977
- } catch {}
18978
- dirsToTry.push(process.cwd());
18979
- for (const startDir of dirsToTry) {
18980
- let current = startDir;
18981
- for (let i = 0;i < 10; i++) {
18982
- const pkgPath = resolve(current, "package.json");
18983
- try {
18984
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
18985
- if (pkg.workspaces && pkg.version && pkg.version !== "0.0.0") {
18986
- return pkg.version;
18987
- }
18988
- } catch {}
18989
- const parent = resolve(current, "..");
18990
- if (parent === current)
18991
- break;
18992
- current = parent;
18993
- }
18994
- }
18995
- return "unknown";
18996
- }
18997
- var VERSION;
18998
- var init_version = __esm(() => {
18999
- VERSION = getVersion();
19000
- });
19001
-
19002
19020
  // node_modules/.bun/react@18.3.1/node_modules/react/cjs/react.development.js
19003
19021
  var require_react_development = __commonJS((exports, module) => {
19004
19022
  if (true) {
@@ -20939,7 +20957,7 @@ var init_compat = __esm(() => {
20939
20957
  });
20940
20958
 
20941
20959
  // node_modules/.bun/environment@1.1.0/node_modules/environment/index.js
20942
- var isBrowser, isNode, isBun, isDeno, isElectron, isJsDom, isWebWorker, isDedicatedWorker, isSharedWorker, isServiceWorker, platform, isMacOs, isWindows, isLinux, isIos, isAndroid;
20960
+ var isBrowser, isNode, isBun, isDeno, isElectron, isJsDom, isWebWorker, isDedicatedWorker, isSharedWorker, isServiceWorker, platform2, isMacOs, isWindows, isLinux, isIos, isAndroid;
20943
20961
  var init_environment = __esm(() => {
20944
20962
  isBrowser = globalThis.window?.document !== undefined;
20945
20963
  isNode = globalThis.process?.versions?.node !== undefined;
@@ -20951,12 +20969,12 @@ var init_environment = __esm(() => {
20951
20969
  isDedicatedWorker = typeof DedicatedWorkerGlobalScope !== "undefined" && globalThis instanceof DedicatedWorkerGlobalScope;
20952
20970
  isSharedWorker = typeof SharedWorkerGlobalScope !== "undefined" && globalThis instanceof SharedWorkerGlobalScope;
20953
20971
  isServiceWorker = typeof ServiceWorkerGlobalScope !== "undefined" && globalThis instanceof ServiceWorkerGlobalScope;
20954
- platform = globalThis.navigator?.userAgentData?.platform;
20955
- isMacOs = platform === "macOS" || globalThis.navigator?.platform === "MacIntel" || globalThis.navigator?.userAgent?.includes(" Mac ") === true || globalThis.process?.platform === "darwin";
20956
- isWindows = platform === "Windows" || globalThis.navigator?.platform === "Win32" || globalThis.process?.platform === "win32";
20957
- isLinux = platform === "Linux" || globalThis.navigator?.platform?.startsWith("Linux") === true || globalThis.navigator?.userAgent?.includes(" Linux ") === true || globalThis.process?.platform === "linux";
20958
- isIos = platform === "iOS" || globalThis.navigator?.platform === "MacIntel" && globalThis.navigator?.maxTouchPoints > 1 || /iPad|iPhone|iPod/.test(globalThis.navigator?.platform);
20959
- isAndroid = platform === "Android" || globalThis.navigator?.platform === "Android" || globalThis.navigator?.userAgent?.includes(" Android ") === true || globalThis.process?.platform === "android";
20972
+ platform2 = globalThis.navigator?.userAgentData?.platform;
20973
+ isMacOs = platform2 === "macOS" || globalThis.navigator?.platform === "MacIntel" || globalThis.navigator?.userAgent?.includes(" Mac ") === true || globalThis.process?.platform === "darwin";
20974
+ isWindows = platform2 === "Windows" || globalThis.navigator?.platform === "Win32" || globalThis.process?.platform === "win32";
20975
+ isLinux = platform2 === "Linux" || globalThis.navigator?.platform?.startsWith("Linux") === true || globalThis.navigator?.userAgent?.includes(" Linux ") === true || globalThis.process?.platform === "linux";
20976
+ isIos = platform2 === "iOS" || globalThis.navigator?.platform === "MacIntel" && globalThis.navigator?.maxTouchPoints > 1 || /iPad|iPhone|iPod/.test(globalThis.navigator?.platform);
20977
+ isAndroid = platform2 === "Android" || globalThis.navigator?.platform === "Android" || globalThis.navigator?.userAgent?.includes(" Android ") === true || globalThis.process?.platform === "android";
20960
20978
  });
20961
20979
 
20962
20980
  // node_modules/.bun/ansi-escapes@7.3.0/node_modules/ansi-escapes/base.js
@@ -68777,6 +68795,41 @@ var init_FeedLine = __esm(async () => {
68777
68795
  };
68778
68796
  });
68779
68797
 
68798
+ // apps/loop/src/hooks/useTerminalSize.ts
68799
+ function readSize() {
68800
+ return {
68801
+ columns: process.stdout.columns ?? 80,
68802
+ rows: process.stdout.rows ?? 24
68803
+ };
68804
+ }
68805
+ function useTerminalSize() {
68806
+ const [size2, setSize] = import_react53.useState(() => ({
68807
+ ...readSize(),
68808
+ resizeKey: 0
68809
+ }));
68810
+ import_react53.useEffect(() => {
68811
+ if (!process.stdout.isTTY)
68812
+ return;
68813
+ const onResize = () => {
68814
+ const { columns, rows } = readSize();
68815
+ setSize((prev) => {
68816
+ if (prev.columns === columns && prev.rows === rows)
68817
+ return prev;
68818
+ return { columns, rows, resizeKey: prev.resizeKey + 1 };
68819
+ });
68820
+ };
68821
+ process.stdout.on("resize", onResize);
68822
+ return () => {
68823
+ process.stdout.off("resize", onResize);
68824
+ };
68825
+ }, []);
68826
+ return size2;
68827
+ }
68828
+ var import_react53;
68829
+ var init_useTerminalSize = __esm(() => {
68830
+ import_react53 = __toESM(require_react(), 1);
68831
+ });
68832
+
68780
68833
  // apps/loop/src/components/StatusBar.tsx
68781
68834
  function formatElapsed(ms) {
68782
68835
  const totalSec = Math.floor(ms / 1000);
@@ -68803,14 +68856,16 @@ function StatusBar({
68803
68856
  model,
68804
68857
  isRunning
68805
68858
  }) {
68806
- const [elapsed, setElapsed] = import_react53.useState(0);
68807
- import_react53.useEffect(() => {
68859
+ const [elapsed, setElapsed] = import_react54.useState(0);
68860
+ import_react54.useEffect(() => {
68808
68861
  if (!isRunning)
68809
68862
  return;
68810
68863
  const id = setInterval(() => setElapsed(Date.now() - startedAt), 1000);
68811
68864
  return () => clearInterval(id);
68812
68865
  }, [isRunning, startedAt]);
68813
- const bar = "\u2500".repeat(52);
68866
+ const { columns } = useTerminalSize();
68867
+ const barWidth = Math.max(8, Math.min(52, columns));
68868
+ const bar = "\u2500".repeat(barWidth);
68814
68869
  return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
68815
68870
  flexDirection: "column",
68816
68871
  children: [
@@ -68874,13 +68929,14 @@ function StatusBar({
68874
68929
  ]
68875
68930
  }, undefined, true, undefined, this);
68876
68931
  }
68877
- var import_react53, jsx_dev_runtime5;
68932
+ var import_react54, jsx_dev_runtime5;
68878
68933
  var init_StatusBar = __esm(async () => {
68934
+ init_useTerminalSize();
68879
68935
  await __promiseAll([
68880
68936
  init_build2(),
68881
68937
  init_build3()
68882
68938
  ]);
68883
- import_react53 = __toESM(require_react(), 1);
68939
+ import_react54 = __toESM(require_react(), 1);
68884
68940
  jsx_dev_runtime5 = __toESM(require_jsx_dev_runtime(), 1);
68885
68941
  });
68886
68942
 
@@ -69137,7 +69193,7 @@ var init_spawn = __esm(() => {
69137
69193
  });
69138
69194
 
69139
69195
  // packages/engine/src/formatters/claude-stream.ts
69140
- function extractToolInputSummary(input) {
69196
+ function extractToolInputSummary(input, maxWidth) {
69141
69197
  if (typeof input.file_path === "string") {
69142
69198
  return { kind: "file", name: input.file_path.split("/").pop() ?? input.file_path };
69143
69199
  }
@@ -69169,14 +69225,16 @@ function extractToolInputSummary(input) {
69169
69225
  const keys2 = Object.keys(input);
69170
69226
  if (keys2.length === 0)
69171
69227
  return;
69228
+ const budget = maxWidth && maxWidth > 0 ? Math.max(80, maxWidth - PREFIX_PADDING) : LEGACY_BUDGET;
69229
+ const valueCap = maxWidth && maxWidth > 0 ? Math.max(LEGACY_VALUE_CAP, Math.floor(budget / 3)) : LEGACY_VALUE_CAP;
69172
69230
  const parts = [];
69173
69231
  let len = 0;
69174
69232
  for (const k of keys2) {
69175
69233
  const v = input[k];
69176
69234
  const val = typeof v === "string" ? v : JSON.stringify(v);
69177
- const short = val.length > 40 ? val.slice(0, 40) + "\u2026" : val;
69235
+ const short = val.length > valueCap ? val.slice(0, valueCap) + "\u2026" : val;
69178
69236
  const part = `${k}=${short}`;
69179
- if (len + part.length > 120)
69237
+ if (len + part.length > budget)
69180
69238
  break;
69181
69239
  parts.push(part);
69182
69240
  len += part.length + 2;
@@ -69195,7 +69253,7 @@ function extractUsage(event) {
69195
69253
  cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0
69196
69254
  };
69197
69255
  }
69198
- function parseClaudeLine(line, state) {
69256
+ function parseClaudeLine(line, state, options) {
69199
69257
  if (!line.trim())
69200
69258
  return [];
69201
69259
  let event;
@@ -69248,7 +69306,7 @@ function parseClaudeLine(line, state) {
69248
69306
  } else if (btype === "tool_use") {
69249
69307
  state.toolCount++;
69250
69308
  const name = block.name ?? "?";
69251
- const summary = extractToolInputSummary(block.input ?? {});
69309
+ const summary = extractToolInputSummary(block.input ?? {}, options?.maxWidth);
69252
69310
  const ev = { type: "tool-start", name };
69253
69311
  if (summary)
69254
69312
  ev.summary = summary;
@@ -69325,6 +69383,7 @@ function parseClaudeLine(line, state) {
69325
69383
  }
69326
69384
  return events;
69327
69385
  }
69386
+ var LEGACY_BUDGET = 120, LEGACY_VALUE_CAP = 40, PREFIX_PADDING = 20;
69328
69387
 
69329
69388
  // packages/engine/src/agents/stream.ts
69330
69389
  async function* streamLines(stream) {
@@ -69452,6 +69511,7 @@ var init_claude = __esm(() => {
69452
69511
  let sessionId = null;
69453
69512
  let detectedRateLimit = false;
69454
69513
  const stdout = proc.stdout;
69514
+ const parseOptions = process.stdout.isTTY && process.stdout.columns ? { maxWidth: process.stdout.columns } : undefined;
69455
69515
  for await (const line of streamLines(stdout)) {
69456
69516
  req.onRawLine?.(line);
69457
69517
  if (sessionId === null) {
@@ -69462,7 +69522,7 @@ var init_claude = __esm(() => {
69462
69522
  }
69463
69523
  } catch {}
69464
69524
  }
69465
- for (const event of parseClaudeLine(line, claudeState)) {
69525
+ for (const event of parseClaudeLine(line, claudeState, parseOptions)) {
69466
69526
  if (event.type === "text" && isRateLimitText(event.text)) {
69467
69527
  detectedRateLimit = true;
69468
69528
  }
@@ -70117,6 +70177,10 @@ function commitTaskDir(taskDir, message) {
70117
70177
  var init_git = () => {};
70118
70178
 
70119
70179
  // packages/core/src/tasks-md.ts
70180
+ function isFlowTaskHeading(heading) {
70181
+ const stripped = heading.replace(/\s*\([^()]*\)\s*$/, "").trim();
70182
+ return FLOW_TASK_HEADING_PREFIXES.some((p) => stripped.startsWith(p));
70183
+ }
70120
70184
  function firstUnchecked(tasksContent) {
70121
70185
  const sections = tasksContent.split(/(?=^## )/m);
70122
70186
  for (const section of sections) {
@@ -70159,6 +70223,18 @@ ${failureOutput.trim()}
70159
70223
  ${fence}`;
70160
70224
  await Bun.write(tasksPath, prependSection(existing, stamped, body));
70161
70225
  }
70226
+ var MISSION_TASKS_FILENAME = "tasks.md", AGENT_TASKS_FILENAME = "agent-tasks.md", FLOW_TASK_HEADING_PREFIXES;
70227
+ var init_tasks_md = __esm(() => {
70228
+ FLOW_TASK_HEADING_PREFIXES = [
70229
+ "Fix failing CI checks",
70230
+ "Fix push rejection",
70231
+ "Resolve PR merge conflicts",
70232
+ "Resolve merge conflict with origin/",
70233
+ "Address reviewer comments",
70234
+ "Address GitHub @ralphy mention",
70235
+ "Address Linear @ralphy mention"
70236
+ ];
70237
+ });
70162
70238
 
70163
70239
  // packages/core/src/loop.ts
70164
70240
  import { join as join10 } from "path";
@@ -70184,9 +70260,21 @@ function buildTaskPrompt(state, taskDir) {
70184
70260
  `;
70185
70261
  }
70186
70262
  }
70187
- const tasksContent = storage.read(join10(taskDir, "tasks.md"));
70188
- if (tasksContent !== null) {
70189
- const section = firstUnchecked(tasksContent);
70263
+ const agentTasksPath = join10(taskDir, AGENT_TASKS_FILENAME);
70264
+ const missionTasksPath = join10(taskDir, MISSION_TASKS_FILENAME);
70265
+ const agentTasksContent = storage.read(agentTasksPath);
70266
+ const missionTasksContent = storage.read(missionTasksPath);
70267
+ let activePath = null;
70268
+ let activeContent = null;
70269
+ if (agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent)) {
70270
+ activePath = agentTasksPath;
70271
+ activeContent = agentTasksContent;
70272
+ } else if (missionTasksContent !== null) {
70273
+ activePath = missionTasksPath;
70274
+ activeContent = missionTasksContent;
70275
+ }
70276
+ if (activeContent !== null && activePath !== null) {
70277
+ const section = firstUnchecked(activeContent);
70190
70278
  if (section) {
70191
70279
  prompt += `---
70192
70280
 
@@ -70199,7 +70287,7 @@ function buildTaskPrompt(state, taskDir) {
70199
70287
  prompt += `---
70200
70288
 
70201
70289
  `;
70202
- prompt += `**Tracking progress**: as you finish each item above, edit ` + `\`${join10(taskDir, "tasks.md")}\` and change its \`- [ ]\` to ` + `\`- [x]\` in the same commit. The loop reads this file between ` + `iterations and stops when no \`- [ ]\` items remain \u2014 if you do ` + `not tick the box, the next iteration will repeat this task.
70290
+ prompt += `**Tracking progress**: as you finish each item above, edit ` + `\`${activePath}\` and change its \`- [ ]\` to ` + `\`- [x]\` in the same commit. The loop reads this file between ` + `iterations and stops when no \`- [ ]\` items remain \u2014 if you do ` + `not tick the box, the next iteration will repeat this task.
70203
70291
 
70204
70292
  `;
70205
70293
  }
@@ -70220,10 +70308,11 @@ function buildTaskPrompt(state, taskDir) {
70220
70308
  `;
70221
70309
  }
70222
70310
  if (state.manualTest) {
70223
- const tasksContent2 = storage.read(join10(taskDir, "tasks.md"));
70224
- const hasUncheckedTasks = tasksContent2 !== null && /^- \[ \]/m.test(tasksContent2);
70225
- if (!hasUncheckedTasks) {
70226
- const hasManualTestSection = tasksContent2 !== null && /^## Manual Testing/m.test(tasksContent2);
70311
+ const tasksContent = missionTasksContent;
70312
+ const hasUncheckedMission = tasksContent !== null && /^- \[ \]/m.test(tasksContent);
70313
+ const hasUncheckedAgent = agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent);
70314
+ if (!hasUncheckedMission && !hasUncheckedAgent) {
70315
+ const hasManualTestSection = tasksContent !== null && /^## Manual Testing/m.test(tasksContent);
70227
70316
  if (!hasManualTestSection) {
70228
70317
  prompt += `---
70229
70318
 
@@ -70379,11 +70468,14 @@ var STEERING_MAX_LINES = 20;
70379
70468
  var init_loop = __esm(() => {
70380
70469
  init_state();
70381
70470
  init_context();
70471
+ init_tasks_md();
70472
+ init_tasks_md();
70382
70473
  });
70383
70474
 
70384
70475
  // apps/loop/src/loop.ts
70385
70476
  var init_loop2 = __esm(() => {
70386
70477
  init_loop();
70478
+ init_tasks_md();
70387
70479
  });
70388
70480
 
70389
70481
  // apps/loop/src/hooks/useLoop.ts
@@ -70392,22 +70484,22 @@ function sleep(seconds) {
70392
70484
  return new Promise((resolve3) => setTimeout(resolve3, seconds * 1000));
70393
70485
  }
70394
70486
  function useLoop(opts) {
70395
- const [state, setState] = import_react54.useState(null);
70396
- const [iteration, setIteration] = import_react54.useState(0);
70397
- const [consecutiveFailures, setConsecutiveFailures] = import_react54.useState(0);
70398
- const [logLines, setLogLines] = import_react54.useState([]);
70399
- const [stopReason, setStopReason] = import_react54.useState(null);
70400
- const [isRunning, setIsRunning] = import_react54.useState(true);
70401
- const [isResume, setIsResume] = import_react54.useState(false);
70402
- const [startedAt] = import_react54.useState(() => Date.now());
70403
- const lineIdRef = import_react54.useRef(0);
70404
- const steerControllerRef = import_react54.useRef(null);
70405
- const pendingSteerRef = import_react54.useRef(null);
70487
+ const [state, setState] = import_react55.useState(null);
70488
+ const [iteration, setIteration] = import_react55.useState(0);
70489
+ const [consecutiveFailures, setConsecutiveFailures] = import_react55.useState(0);
70490
+ const [logLines, setLogLines] = import_react55.useState([]);
70491
+ const [stopReason, setStopReason] = import_react55.useState(null);
70492
+ const [isRunning, setIsRunning] = import_react55.useState(true);
70493
+ const [isResume, setIsResume] = import_react55.useState(false);
70494
+ const [startedAt] = import_react55.useState(() => Date.now());
70495
+ const lineIdRef = import_react55.useRef(0);
70496
+ const steerControllerRef = import_react55.useRef(null);
70497
+ const pendingSteerRef = import_react55.useRef(null);
70406
70498
  const steer = (message) => {
70407
70499
  pendingSteerRef.current = message;
70408
70500
  steerControllerRef.current?.abort();
70409
70501
  };
70410
- import_react54.useEffect(() => {
70502
+ import_react55.useEffect(() => {
70411
70503
  let cancelled = false;
70412
70504
  const nextId = () => String(lineIdRef.current++);
70413
70505
  const addInfo = (text) => {
@@ -70471,15 +70563,24 @@ function useLoop(opts) {
70471
70563
  const stop = checkStopCondition(currentState, iter, opts, loopStartTime, consFailures);
70472
70564
  if (stop !== null) {
70473
70565
  finalStopReason = stop;
70474
- setStopReason(stop);
70475
70566
  break;
70476
70567
  }
70477
- const tasksContent = storage.read(join11(tasksDir, "tasks.md"));
70568
+ const tasksContent = storage.read(join11(tasksDir, MISSION_TASKS_FILENAME));
70569
+ const agentTasksContent = storage.read(join11(tasksDir, AGENT_TASKS_FILENAME));
70478
70570
  if (tasksContent !== null) {
70479
70571
  const remaining = countUnchecked(tasksContent);
70480
- addInfo(`tasks.md: ${remaining} unchecked item${remaining === 1 ? "" : "s"} remaining`);
70481
- }
70482
- if (tasksContent !== null && allCompleted(tasksContent)) {
70572
+ const agentRemaining = agentTasksContent !== null ? countUnchecked(agentTasksContent) : 0;
70573
+ const parts = [
70574
+ `tasks.md: ${remaining} unchecked item${remaining === 1 ? "" : "s"} remaining`
70575
+ ];
70576
+ if (agentTasksContent !== null) {
70577
+ parts.push(`agent-tasks.md: ${agentRemaining} unchecked item${agentRemaining === 1 ? "" : "s"} remaining`);
70578
+ }
70579
+ addInfo(parts.join(" \xB7 "));
70580
+ }
70581
+ const missionDone = tasksContent !== null && allCompleted(tasksContent);
70582
+ const agentDone = agentTasksContent === null || allCompleted(agentTasksContent);
70583
+ if (missionDone && agentDone && tasksContent !== null) {
70483
70584
  addInfo("All tasks completed \u2014 archiving change.");
70484
70585
  currentState = {
70485
70586
  ...currentState,
@@ -70495,7 +70596,6 @@ function useLoop(opts) {
70495
70596
  addInfo(`Archive warning: ${err}`);
70496
70597
  }
70497
70598
  finalStopReason = "completed";
70498
- setStopReason("completed");
70499
70599
  break;
70500
70600
  }
70501
70601
  iter++;
@@ -70555,7 +70655,6 @@ function useLoop(opts) {
70555
70655
  if (failure.shouldStop || engineResult.rateLimited) {
70556
70656
  capture("engine_rate_limited", { exit_code: engineResult.exitCode, iteration: iter });
70557
70657
  finalStopReason = "rateLimited";
70558
- setStopReason("rateLimited");
70559
70658
  break;
70560
70659
  }
70561
70660
  capture("iteration_failed", {
@@ -70614,6 +70713,9 @@ function useLoop(opts) {
70614
70713
  gitPush();
70615
70714
  } catch {}
70616
70715
  }
70716
+ if (finalStopReason !== null) {
70717
+ setStopReason(finalStopReason);
70718
+ }
70617
70719
  setIsRunning(false);
70618
70720
  });
70619
70721
  return () => {
@@ -70632,7 +70734,7 @@ function useLoop(opts) {
70632
70734
  steer
70633
70735
  };
70634
70736
  }
70635
- var import_react54;
70737
+ var import_react55;
70636
70738
  var init_useLoop = __esm(() => {
70637
70739
  init_state();
70638
70740
  init_engine();
@@ -70640,7 +70742,7 @@ var init_useLoop = __esm(() => {
70640
70742
  init_context();
70641
70743
  init_src();
70642
70744
  init_loop2();
70643
- import_react54 = __toESM(require_react(), 1);
70745
+ import_react55 = __toESM(require_react(), 1);
70644
70746
  });
70645
70747
 
70646
70748
  // apps/loop/src/components/TaskLoop.tsx
@@ -70695,10 +70797,10 @@ function handleSteerKeyInput(key, history, currentIndex) {
70695
70797
  return navigateHistory(history, currentIndex, dir);
70696
70798
  }
70697
70799
  function SteerInput({ onSubmit }) {
70698
- const [inputKey, setInputKey] = import_react55.useState(0);
70699
- const [defaultValue, setDefaultValue] = import_react55.useState("");
70700
- const historyRef = import_react55.useRef([]);
70701
- const historyIndexRef = import_react55.useRef(-1);
70800
+ const [inputKey, setInputKey] = import_react56.useState(0);
70801
+ const [defaultValue, setDefaultValue] = import_react56.useState("");
70802
+ const historyRef = import_react56.useRef([]);
70803
+ const historyIndexRef = import_react56.useRef(-1);
70702
70804
  use_input_default((_input, key) => {
70703
70805
  const result2 = handleSteerKeyInput(key, historyRef.current, historyIndexRef.current);
70704
70806
  if (result2) {
@@ -70730,12 +70832,19 @@ function TaskLoop({ opts }) {
70730
70832
  const { exit } = use_app_default();
70731
70833
  const loop = useLoop(opts);
70732
70834
  const { isRawModeSupported } = use_stdin_default();
70733
- const bannerItem = import_react55.useRef({ id: "__banner__", kind: "banner" });
70734
- const feedItems = import_react55.useMemo(() => [
70835
+ const { stdout } = use_stdout_default();
70836
+ const { resizeKey } = useTerminalSize();
70837
+ const bannerItem = import_react56.useRef({ id: "__banner__", kind: "banner" });
70838
+ import_react56.useEffect(() => {
70839
+ if (resizeKey === 0)
70840
+ return;
70841
+ stdout.write("\x1B[2J\x1B[3J\x1B[H");
70842
+ }, [resizeKey, stdout]);
70843
+ const feedItems = import_react56.useMemo(() => [
70735
70844
  bannerItem.current,
70736
70845
  ...loop.logLines.map((e) => ({ id: e.id, kind: "entry", entry: e }))
70737
70846
  ], [loop.logLines]);
70738
- import_react55.useEffect(() => {
70847
+ import_react56.useEffect(() => {
70739
70848
  if (!loop.isRunning) {
70740
70849
  exit();
70741
70850
  }
@@ -70783,7 +70892,7 @@ function TaskLoop({ opts }) {
70783
70892
  }, undefined, false, undefined, this)
70784
70893
  ]
70785
70894
  }, undefined, true, undefined, this),
70786
- loop.stopReason && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(jsx_dev_runtime7.Fragment, {
70895
+ !loop.isRunning && loop.stopReason && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(jsx_dev_runtime7.Fragment, {
70787
70896
  children: [
70788
70897
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(StatusBar, {
70789
70898
  iteration: loop.iteration,
@@ -70805,11 +70914,12 @@ function TaskLoop({ opts }) {
70805
70914
  ]
70806
70915
  }, undefined, true, undefined, this)
70807
70916
  ]
70808
- }, undefined, true, undefined, this);
70917
+ }, resizeKey, true, undefined, this);
70809
70918
  }
70810
- var import_react55, jsx_dev_runtime7;
70919
+ var import_react56, jsx_dev_runtime7;
70811
70920
  var init_TaskLoop = __esm(async () => {
70812
70921
  init_useLoop();
70922
+ init_useTerminalSize();
70813
70923
  await __promiseAll([
70814
70924
  init_build2(),
70815
70925
  init_build3(),
@@ -70819,7 +70929,7 @@ var init_TaskLoop = __esm(async () => {
70819
70929
  init_StatusBar(),
70820
70930
  init_StopMessage()
70821
70931
  ]);
70822
- import_react55 = __toESM(require_react(), 1);
70932
+ import_react56 = __toESM(require_react(), 1);
70823
70933
  jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
70824
70934
  });
70825
70935
 
@@ -70827,7 +70937,7 @@ var init_TaskLoop = __esm(async () => {
70827
70937
  import { join as join13 } from "path";
70828
70938
  function ExitAfterRender({ children }) {
70829
70939
  const { exit } = use_app_default();
70830
- import_react56.useEffect(() => {
70940
+ import_react57.useEffect(() => {
70831
70941
  exit();
70832
70942
  }, [exit]);
70833
70943
  return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(jsx_dev_runtime8.Fragment, {
@@ -70836,7 +70946,7 @@ function ExitAfterRender({ children }) {
70836
70946
  }
70837
70947
  function ErrorMessage({ message }) {
70838
70948
  const { exit } = use_app_default();
70839
- import_react56.useEffect(() => {
70949
+ import_react57.useEffect(() => {
70840
70950
  process.exitCode = 1;
70841
70951
  exit();
70842
70952
  }, [exit]);
@@ -70915,7 +71025,7 @@ function App2({ args, statesDir, tasksDir }) {
70915
71025
  }
70916
71026
  }
70917
71027
  }
70918
- var import_react56, jsx_dev_runtime8;
71028
+ var import_react57, jsx_dev_runtime8;
70919
71029
  var init_App2 = __esm(async () => {
70920
71030
  init_state();
70921
71031
  init_context();
@@ -70925,7 +71035,7 @@ var init_App2 = __esm(async () => {
70925
71035
  init_TaskStatus(),
70926
71036
  init_TaskLoop()
70927
71037
  ]);
70928
- import_react56 = __toESM(require_react(), 1);
71038
+ import_react57 = __toESM(require_react(), 1);
70929
71039
  jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
70930
71040
  });
70931
71041
 
@@ -79420,7 +79530,7 @@ __export(exports_regexes, {
79420
79530
  integer: () => integer,
79421
79531
  idnEmail: () => idnEmail,
79422
79532
  html5Email: () => html5Email,
79423
- hostname: () => hostname,
79533
+ hostname: () => hostname2,
79424
79534
  hex: () => hex,
79425
79535
  guid: () => guid,
79426
79536
  extendedDuration: () => extendedDuration,
@@ -79475,7 +79585,7 @@ var cuid, cuid2, ulid, xid, ksuid, nanoid, duration, extendedDuration, guid, uui
79475
79585
  }, uuid4, uuid6, uuid7, email, html5Email, rfc5322Email, unicodeEmail, idnEmail, browserEmail, _emoji = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`, ipv4, ipv6, mac = (delimiter) => {
79476
79586
  const escapedDelim = escapeRegex(delimiter ?? ":");
79477
79587
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
79478
- }, cidrv4, cidrv6, base64, base64url, hostname, domain, e164, dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`, date, string = (params) => {
79588
+ }, cidrv4, cidrv6, base64, base64url, hostname2, domain, e164, dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`, date, string = (params) => {
79479
79589
  const regex2 = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
79480
79590
  return new RegExp(`^${regex2}$`);
79481
79591
  }, bigint, integer, number, boolean, _null, _undefined, lowercase, uppercase, hex, md5_hex, md5_base64, md5_base64url, sha1_hex, sha1_base64, sha1_base64url, sha256_hex, sha256_base64, sha256_base64url, sha384_hex, sha384_base64, sha384_base64url, sha512_hex, sha512_base64, sha512_base64url;
@@ -79505,7 +79615,7 @@ var init_regexes = __esm(() => {
79505
79615
  cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
79506
79616
  base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
79507
79617
  base64url = /^[A-Za-z0-9_-]*$/;
79508
- hostname = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
79618
+ hostname2 = /^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
79509
79619
  domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
79510
79620
  e164 = /^\+[1-9]\d{6,14}$/;
79511
79621
  date = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
@@ -90290,7 +90400,7 @@ __export(exports_schemas2, {
90290
90400
  int: () => int,
90291
90401
  instanceof: () => _instanceof,
90292
90402
  httpUrl: () => httpUrl,
90293
- hostname: () => hostname2,
90403
+ hostname: () => hostname3,
90294
90404
  hex: () => hex2,
90295
90405
  hash: () => hash,
90296
90406
  guid: () => guid2,
@@ -90474,7 +90584,7 @@ function jwt(params) {
90474
90584
  function stringFormat(format2, fnOrRegex, _params = {}) {
90475
90585
  return _stringFormat(ZodCustomStringFormat, format2, fnOrRegex, _params);
90476
90586
  }
90477
- function hostname2(_params) {
90587
+ function hostname3(_params) {
90478
90588
  return _stringFormat(ZodCustomStringFormat, "hostname", exports_regexes.hostname, _params);
90479
90589
  }
90480
90590
  function hex2(_params) {
@@ -92112,7 +92222,7 @@ __export(exports_external2, {
92112
92222
  instanceof: () => _instanceof,
92113
92223
  includes: () => _includes,
92114
92224
  httpUrl: () => httpUrl,
92115
- hostname: () => hostname2,
92225
+ hostname: () => hostname3,
92116
92226
  hex: () => hex2,
92117
92227
  hash: () => hash,
92118
92228
  guid: () => guid2,
@@ -92394,7 +92504,20 @@ var init_schema = __esm(() => {
92394
92504
  fix_on_failure: exports_external2.boolean().optional(),
92395
92505
  max_attempts: exports_external2.number().int().positive().optional(),
92396
92506
  poll_interval_seconds: exports_external2.number().int().positive().optional()
92397
- }).strict().optional()
92507
+ }).strict().optional(),
92508
+ preExistingErrorCheck: exports_external2.object({
92509
+ enabled: exports_external2.boolean().default(false),
92510
+ commands: exports_external2.array(exports_external2.string()).default([]),
92511
+ baseBranch: exports_external2.string().default("main"),
92512
+ label: exports_external2.string().default("ralph:pre-existing-error"),
92513
+ outputCharLimit: exports_external2.number().int().positive().default(4000)
92514
+ }).strict().default({
92515
+ enabled: false,
92516
+ commands: [],
92517
+ baseBranch: "main",
92518
+ label: "ralph:pre-existing-error",
92519
+ outputCharLimit: 4000
92520
+ })
92398
92521
  });
92399
92522
  });
92400
92523
 
@@ -92478,6 +92601,17 @@ engine: claude
92478
92601
  # Model tier: "haiku", "sonnet", or "opus".
92479
92602
  model: opus
92480
92603
 
92604
+ # Pre-existing error check: gate the agent when the base branch is already broken.
92605
+ # When enabled, the agent runs these commands against the base branch HEAD before
92606
+ # scheduling new work; failures open a Linear ticket and pause new pickups.
92607
+ preExistingErrorCheck:
92608
+ enabled: false
92609
+ # Commands to run against the base branch. When empty, falls back to commands.lint / commands.test.
92610
+ commands: []
92611
+ baseBranch: main
92612
+ label: "ralph:pre-existing-error"
92613
+ outputCharLimit: 4000
92614
+
92481
92615
  linear:
92482
92616
  # Linear team key (e.g. "ENG"). Omit to match all teams.
92483
92617
  # team: ENG
@@ -92556,6 +92690,10 @@ Previous attempt failed with: {{ last_error }}
92556
92690
 
92557
92691
  {{ issue.description }}
92558
92692
 
92693
+ {% if issue.labels %}
92694
+ Labels: {{ issue.labels | join(", ") }}
92695
+ {% endif %}
92696
+
92559
92697
  {% if rules %}
92560
92698
  Project rules:
92561
92699
  {% for rule in rules %}- {{ rule }}
@@ -92743,6 +92881,7 @@ function renderTemplate(src, ctx) {
92743
92881
  var exports_workflow = {};
92744
92882
  __export(exports_workflow, {
92745
92883
  workflowPath: () => workflowPath,
92884
+ resolveBaselineCommands: () => resolveBaselineCommands,
92746
92885
  renderWorkflowPrompt: () => renderWorkflowPrompt,
92747
92886
  renderTemplate: () => renderTemplate,
92748
92887
  parseWorkflow: () => parseWorkflow,
@@ -92875,6 +93014,17 @@ function extractDefaultBody() {
92875
93014
  const m = FRONTMATTER_RE.exec(DEFAULT_WORKFLOW_MD);
92876
93015
  return m ? m[2] ?? "" : "";
92877
93016
  }
93017
+ function resolveBaselineCommands(config2) {
93018
+ const configured = config2.preExistingErrorCheck?.commands ?? [];
93019
+ if (configured.length > 0)
93020
+ return [...configured];
93021
+ const fallback = [];
93022
+ if (config2.commands.lint)
93023
+ fallback.push(config2.commands.lint);
93024
+ if (config2.commands.test)
93025
+ fallback.push(config2.commands.test);
93026
+ return fallback;
93027
+ }
92878
93028
  function renderWorkflowPrompt(workflow, ctx) {
92879
93029
  const fullCtx = {
92880
93030
  project: workflow.config.project,
@@ -93027,12 +93177,12 @@ async function main(argv) {
93027
93177
  await ensureRalphGitignore(projectRoot);
93028
93178
  }
93029
93179
  await runWithContext(createDefaultContext(), async () => {
93030
- const { waitUntilExit } = render_default(import_react57.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
93180
+ const { waitUntilExit } = render_default(import_react58.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
93031
93181
  await waitUntilExit();
93032
93182
  });
93033
93183
  return typeof process.exitCode === "number" ? process.exitCode : 0;
93034
93184
  }
93035
- var import_react57;
93185
+ var import_react58;
93036
93186
  var init_src5 = __esm(async () => {
93037
93187
  init_context();
93038
93188
  init_layout();
@@ -93044,7 +93194,7 @@ var init_src5 = __esm(async () => {
93044
93194
  init_build2(),
93045
93195
  init_App2()
93046
93196
  ]);
93047
- import_react57 = __toESM(require_react(), 1);
93197
+ import_react58 = __toESM(require_react(), 1);
93048
93198
  });
93049
93199
 
93050
93200
  // apps/agent/src/cli.ts
@@ -93225,6 +93375,9 @@ async function parseArgs2(argv) {
93225
93375
  case "--debug":
93226
93376
  result2.debug = true;
93227
93377
  break;
93378
+ case "--pre-existing-error-check":
93379
+ result2.preExistingErrorCheck = true;
93380
+ break;
93228
93381
  default:
93229
93382
  if (VALID_MODES2.has(arg)) {
93230
93383
  result2.mode = arg;
@@ -93305,6 +93458,7 @@ var init_cli2 = __esm(() => {
93305
93458
  " --code-review Watch open tracked PRs for unresolved review comments",
93306
93459
  " --max-tickets <n> Stop picking up new issues after N have been started (0 = unlimited)",
93307
93460
  " --json-output Emit JSONL to stdout instead of the Ink dashboard (for scripting/CI)",
93461
+ " --pre-existing-error-check Run baseline commands against the base branch; pause new pickups + open a Linear ticket when red",
93308
93462
  " --debug List mode: explain why a Linear ticket was not picked up (use with --name)",
93309
93463
  " --help, -h Show this help message",
93310
93464
  "",
@@ -93425,7 +93579,7 @@ async function fetchOpenIssues(apiKey, spec) {
93425
93579
  const query = `query Issues($filter: IssueFilter) {
93426
93580
  issues(filter: $filter, first: 50) {
93427
93581
  nodes {
93428
- id identifier title description url priority
93582
+ id identifier title description url priority createdAt
93429
93583
  state { name type }
93430
93584
  assignee { id email name }
93431
93585
  labels { nodes { name } }
@@ -93452,6 +93606,7 @@ async function fetchOpenIssues(apiKey, spec) {
93452
93606
  assignee: n.assignee,
93453
93607
  labels: n.labels.nodes.map((l) => l.name),
93454
93608
  priority: n.priority,
93609
+ createdAt: n.createdAt ?? "",
93455
93610
  blockedByIds: (n.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => r.relatedIssue.id)
93456
93611
  }));
93457
93612
  }
@@ -93478,6 +93633,15 @@ async function linearRequest(apiKey, query, variables) {
93478
93633
  }
93479
93634
  return json2.data;
93480
93635
  }
93636
+ async function addReactionToComment(apiKey, commentId, emoji3) {
93637
+ const mutation = `mutation Reaction($commentId: String!, $emoji: String!) {
93638
+ reactionCreate(input: { commentId: $commentId, emoji: $emoji }) { success }
93639
+ }`;
93640
+ await linearRequest(apiKey, mutation, {
93641
+ commentId,
93642
+ emoji: emoji3
93643
+ });
93644
+ }
93481
93645
  async function addIssueComment(apiKey, issueId, body) {
93482
93646
  const mutation = `mutation Comment($issueId: String!, $body: String!) {
93483
93647
  commentCreate(input: { issueId: $issueId, body: $body }) { success }
@@ -93653,6 +93817,53 @@ function issueMatchesGetIndicator(issue2, indicator) {
93653
93817
  return false;
93654
93818
  });
93655
93819
  }
93820
+ async function createIssue(apiKey, input) {
93821
+ const mutation = `mutation CreateIssue($input: IssueCreateInput!) {
93822
+ issueCreate(input: $input) {
93823
+ success
93824
+ issue { id identifier }
93825
+ }
93826
+ }`;
93827
+ const variables = {
93828
+ input: {
93829
+ teamId: input.teamId,
93830
+ title: input.title,
93831
+ description: input.description,
93832
+ ...input.labelIds && input.labelIds.length > 0 ? { labelIds: input.labelIds } : {}
93833
+ }
93834
+ };
93835
+ const data = await linearRequest(apiKey, mutation, variables);
93836
+ const issue2 = data.issueCreate.issue;
93837
+ if (!issue2)
93838
+ throw new Error("issueCreate returned no issue");
93839
+ return issue2;
93840
+ }
93841
+ async function updateIssueDescription(apiKey, issueId, description) {
93842
+ const mutation = `mutation UpdateDesc($id: String!, $description: String!) {
93843
+ issueUpdate(id: $id, input: { description: $description }) { success }
93844
+ }`;
93845
+ await linearRequest(apiKey, mutation, {
93846
+ id: issueId,
93847
+ description
93848
+ });
93849
+ }
93850
+ async function findOpenIssueByLabel(apiKey, teamKey, labelName) {
93851
+ const query = `query OpenByLabel($team: String!, $label: String!) {
93852
+ issues(
93853
+ filter: {
93854
+ team: { key: { eq: $team } },
93855
+ labels: { some: { name: { eq: $label } } },
93856
+ state: { type: { in: ["unstarted", "started", "backlog", "triage"] } }
93857
+ },
93858
+ first: 1,
93859
+ orderBy: createdAt
93860
+ ) {
93861
+ nodes { id identifier description }
93862
+ }
93863
+ }`;
93864
+ const data = await linearRequest(apiKey, query, { team: teamKey, label: labelName });
93865
+ return data.issues.nodes[0] ?? null;
93866
+ }
93656
93867
  async function removeLabelFromIssue(apiKey, issueId, labelId) {
93657
93868
  const mutation = `mutation RemoveLabel($id: String!, $labelId: String!) {
93658
93869
  issueRemoveLabel(id: $id, labelId: $labelId) { success }
@@ -93664,6 +93875,43 @@ async function removeLabelFromIssue(apiKey, issueId, labelId) {
93664
93875
  }
93665
93876
  var LINEAR_API = "https://api.linear.app/graphql", RALPHY_ATTACHMENT_TITLE_FILTER = "Ralphy", RALPHY_ATTACHMENT_TITLE = "Ralphy", BRANCH_LABEL_PREFIX = "ralph:branch:";
93666
93877
 
93878
+ // apps/agent/src/sort/compare.ts
93879
+ function chain(...comparators) {
93880
+ return (a, b) => {
93881
+ for (const c of comparators) {
93882
+ const r = c(a, b);
93883
+ if (r !== 0)
93884
+ return r;
93885
+ }
93886
+ return 0;
93887
+ };
93888
+ }
93889
+
93890
+ // apps/agent/src/queue/queue-order.ts
93891
+ function compareQueueEntries(getAutoMerge) {
93892
+ const isAutoMergeBoost = (e) => e.mode === "conflict-fix" && issueMatchesGetIndicator(e.issue, getAutoMerge);
93893
+ return chain((a, b) => Number(!isAutoMergeBoost(a)) - Number(!isAutoMergeBoost(b)), (a, b) => {
93894
+ const pa = a.issue.priority === 0 ? Infinity : a.issue.priority;
93895
+ const pb = b.issue.priority === 0 ? Infinity : b.issue.priority;
93896
+ return pa - pb;
93897
+ }, (a, b) => MODE_RANK[a.mode] - MODE_RANK[b.mode], (a, b) => {
93898
+ const ca = a.issue.createdAt;
93899
+ const cb = b.issue.createdAt;
93900
+ if (ca === cb)
93901
+ return 0;
93902
+ return ca < cb ? -1 : 1;
93903
+ });
93904
+ }
93905
+ var MODE_RANK;
93906
+ var init_queue_order = __esm(() => {
93907
+ MODE_RANK = {
93908
+ resume: 0,
93909
+ "conflict-fix": 1,
93910
+ review: 2,
93911
+ fresh: 3
93912
+ };
93913
+ });
93914
+
93667
93915
  // apps/agent/src/agent/coordinator.ts
93668
93916
  class AgentCoordinator {
93669
93917
  deps;
@@ -93672,6 +93920,7 @@ class AgentCoordinator {
93672
93920
  pendingIds = new Set;
93673
93921
  queue = [];
93674
93922
  stopped = false;
93923
+ paused = null;
93675
93924
  conflictNotified = new Set;
93676
93925
  ticketsStarted = 0;
93677
93926
  constructor(deps, opts) {
@@ -93690,6 +93939,18 @@ class AgentCoordinator {
93690
93939
  get ticketsStartedCount() {
93691
93940
  return this.ticketsStarted;
93692
93941
  }
93942
+ isPaused() {
93943
+ return this.paused !== null;
93944
+ }
93945
+ getPause() {
93946
+ return this.paused;
93947
+ }
93948
+ setPaused(state) {
93949
+ this.paused = state;
93950
+ }
93951
+ clearPaused() {
93952
+ this.paused = null;
93953
+ }
93693
93954
  async init() {}
93694
93955
  async pollOnce() {
93695
93956
  if (this.stopped)
@@ -93713,11 +93974,23 @@ class AgentCoordinator {
93713
93974
  return emptyPollResult();
93714
93975
  }
93715
93976
  if (todo.length + inProgress.length + conflicted.length + review.length + mentions.length > 0) {
93716
- this.deps.onLog(` poll: ${todo.length} todo, ${inProgress.length} in-progress, ${conflicted.length} conflicted, ${review.length} review, ${mentions.length} mention`, "gray");
93977
+ this.deps.onFileLog?.(` poll: ${todo.length} todo, ${inProgress.length} in-progress, ${conflicted.length} conflicted, ${review.length} review, ${mentions.length} mention`);
93717
93978
  }
93718
93979
  const queuedIds = new Set(this.queue.map((q) => q.issue.id));
93719
93980
  const activeIds = new Set(this.workers.map((w) => w.issueId));
93720
93981
  const eligible = (id) => !queuedIds.has(id) && !activeIds.has(id) && !this.pendingIds.has(id);
93982
+ if (this.paused) {
93983
+ this.deps.onLog(` paused \u2014 baseline broken (${this.paused.issueIdentifier}); skipping new pickups`, "yellow");
93984
+ const buckets2 = {
93985
+ todo: todo.length,
93986
+ inProgress: inProgress.length,
93987
+ conflicted: conflicted.length,
93988
+ review: review.length,
93989
+ mentions: mentions.length
93990
+ };
93991
+ const found2 = buckets2.todo + buckets2.inProgress + buckets2.conflicted + buckets2.review + buckets2.mentions;
93992
+ return { found: found2, added: 0, buckets: buckets2, prStatus: emptyPrStatus() };
93993
+ }
93721
93994
  const maxT = this.opts.maxTickets ?? 0;
93722
93995
  const atTicketLimit = () => {
93723
93996
  if (maxT === 0)
@@ -93746,7 +94019,11 @@ class AgentCoordinator {
93746
94019
  this.queue.push({ issue: issue2, mode: "conflict-fix" });
93747
94020
  queuedIds.add(issue2.id);
93748
94021
  added += 1;
93749
- this.deps.onLog(` \u21B3 ${issue2.identifier} queued (conflict-fix)`, "gray");
94022
+ if (this.isAutoMergeUnblock(issue2)) {
94023
+ this.deps.onLog(` \u21B3 ${issue2.identifier} queued (auto-merge unblock, prioritized)`, "cyan");
94024
+ } else {
94025
+ this.deps.onLog(` \u21B3 ${issue2.identifier} queued (conflict-fix)`, "gray");
94026
+ }
93750
94027
  }
93751
94028
  for (const issue2 of review) {
93752
94029
  if (atTicketLimit())
@@ -93781,19 +94058,7 @@ class AgentCoordinator {
93781
94058
  this.deps.onLog(` \u21B3 ${issue2.identifier} queued (fresh)`, "gray");
93782
94059
  }
93783
94060
  if (added > 0) {
93784
- const modeRank = {
93785
- resume: 0,
93786
- "conflict-fix": 1,
93787
- review: 2,
93788
- fresh: 3
93789
- };
93790
- this.queue.sort((a, b) => {
93791
- const pa = a.issue.priority === 0 ? Infinity : a.issue.priority;
93792
- const pb = b.issue.priority === 0 ? Infinity : b.issue.priority;
93793
- if (pa !== pb)
93794
- return pa - pb;
93795
- return modeRank[a.mode] - modeRank[b.mode];
93796
- });
94061
+ this.queue.sort(compareQueueEntries(this.opts.getAutoMerge));
93797
94062
  }
93798
94063
  this.spawnNext();
93799
94064
  const prStatus = await this.scanDoneForConflicts();
@@ -93808,6 +94073,9 @@ class AgentCoordinator {
93808
94073
  const found = buckets.todo + buckets.inProgress + buckets.conflicted + buckets.review + buckets.mentions;
93809
94074
  return { found, added, buckets, prStatus };
93810
94075
  }
94076
+ isAutoMergeUnblock(issue2) {
94077
+ return issueMatchesGetIndicator(issue2, this.opts.getAutoMerge);
94078
+ }
93811
94079
  dependenciesResolved(issue2) {
93812
94080
  if (issue2.blockedByIds.length === 0)
93813
94081
  return true;
@@ -94006,7 +94274,8 @@ class AgentCoordinator {
94006
94274
  issue: issue2,
94007
94275
  mode,
94008
94276
  kill: handle.kill,
94009
- lastReportedIteration: 0
94277
+ lastReportedIteration: 0,
94278
+ restarting: false
94010
94279
  };
94011
94280
  this.workers.push(worker);
94012
94281
  this.pendingIds.delete(issue2.id);
@@ -94024,6 +94293,13 @@ class AgentCoordinator {
94024
94293
  const idx = this.workers.indexOf(worker);
94025
94294
  if (idx >= 0)
94026
94295
  this.workers.splice(idx, 1);
94296
+ if (worker.restarting) {
94297
+ this.ticketsStarted = Math.max(0, this.ticketsStarted - 1);
94298
+ this.queue.unshift({ issue: issue2, mode: "resume" });
94299
+ this.deps.onWorkersChanged();
94300
+ this.spawnNext();
94301
+ return;
94302
+ }
94027
94303
  const ok = code === 0;
94028
94304
  this.deps.onLog(`${ok ? "\u2713" : "\u2717"} ${issue2.identifier} \u2192 ${prep.changeName} exited (code ${code})`, ok ? "green" : "red");
94029
94305
  capture("agent_worker_exited", {
@@ -94037,6 +94313,24 @@ class AgentCoordinator {
94037
94313
  this.spawnNext();
94038
94314
  });
94039
94315
  }
94316
+ async restartWorker(changeName) {
94317
+ if (this.stopped)
94318
+ return false;
94319
+ const worker = this.workers.find((w) => w.changeName === changeName);
94320
+ if (!worker)
94321
+ return false;
94322
+ if (worker.restarting)
94323
+ return true;
94324
+ worker.restarting = true;
94325
+ capture("agent_worker_restarted", {
94326
+ change_name: changeName,
94327
+ reason: "steering"
94328
+ });
94329
+ try {
94330
+ worker.kill();
94331
+ } catch {}
94332
+ return true;
94333
+ }
94040
94334
  async notifyExited(issue2, changeName, code, mode) {
94041
94335
  const ok = code === 0;
94042
94336
  if (this.opts.postComments !== false) {
@@ -94121,6 +94415,7 @@ var emptyPrStatus = () => ({ mergeable: 0, conflicted: 0, ciFailed: 0 }), emptyP
94121
94415
  prStatus: emptyPrStatus()
94122
94416
  });
94123
94417
  var init_coordinator = __esm(() => {
94418
+ init_queue_order();
94124
94419
  init_src();
94125
94420
  });
94126
94421
 
@@ -94149,6 +94444,7 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
94149
94444
  ""
94150
94445
  ])
94151
94446
  ] : [];
94447
+ const descriptionBody = issue2.description?.trim() || "_No description provided in Linear._";
94152
94448
  const proposal = [
94153
94449
  `# ${issue2.identifier}: ${issue2.title}`,
94154
94450
  "",
@@ -94157,9 +94453,17 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
94157
94453
  issue2.assignee ? `Assignee: ${issue2.assignee.name}` : "",
94158
94454
  issue2.labels.length ? `Labels: ${issue2.labels.join(", ")}` : "",
94159
94455
  "",
94456
+ "## Why",
94457
+ "",
94458
+ descriptionBody,
94459
+ "",
94460
+ "## What Changes",
94461
+ "",
94462
+ "_Describe the concrete changes this proposal introduces (one bullet per change)._",
94463
+ "",
94160
94464
  "## Description",
94161
94465
  "",
94162
- issue2.description?.trim() || "_No description provided in Linear._",
94466
+ descriptionBody,
94163
94467
  ...commentsBlock,
94164
94468
  ...appendPrompt.trim() ? ["", "## Additional instructions", "", appendPrompt.trim()] : [],
94165
94469
  "",
@@ -94176,6 +94480,8 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
94176
94480
  "",
94177
94481
  `- [ ] Read the Linear issue at ${issue2.url} and research the codebase to understand the mission and its scope`,
94178
94482
  `- [ ] Refine proposal.md with the problem statement, approach, and acceptance criteria derived from the research`,
94483
+ `- [ ] Fill in \`## Why\` and \`## What Changes\` in proposal.md so \`openspec validate\` passes (these sections are required by the validator)`,
94484
+ `- [ ] Add at least one spec delta under \`specs/<capability>/spec.md\` describing the behavior added/modified/removed by this change`,
94179
94485
  `- [ ] Fill in design.md with the technical design (files to touch, data flow, edge cases)`,
94180
94486
  `- [ ] Append an \`## Implementation\` section below with concrete mission-specific tasks derived from the plan (one \`- [ ] task\` per discrete unit of work, including tests and \`bun run lint\` / \`bun run test\`)`,
94181
94487
  ""
@@ -94509,6 +94815,33 @@ async function createPullRequest(input, runner) {
94509
94815
 
94510
94816
  // apps/agent/src/agent/post-task.ts
94511
94817
  import { join as join20 } from "path";
94818
+ function summarizeUncommittedStatus(stdout) {
94819
+ const lines = stdout.split(`
94820
+ `).filter((line) => line.length > 0);
94821
+ const preview = lines.slice(0, 10);
94822
+ return { count: lines.length, preview, truncated: Math.max(0, lines.length - preview.length) };
94823
+ }
94824
+ async function findExistingOpenPrUrl(cmd, cwd2, branch) {
94825
+ try {
94826
+ const result2 = await cmd.run([
94827
+ "gh",
94828
+ "pr",
94829
+ "list",
94830
+ "--head",
94831
+ branch,
94832
+ "--state",
94833
+ "open",
94834
+ "--json",
94835
+ "url",
94836
+ "--jq",
94837
+ ".[0].url // empty"
94838
+ ], cwd2);
94839
+ const url2 = result2.stdout.trim();
94840
+ return url2 || null;
94841
+ } catch {
94842
+ return null;
94843
+ }
94844
+ }
94512
94845
  async function detectRepoAutoMergeAllowed(prUrl, cmd, cwd2, log2) {
94513
94846
  const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/\d+/.exec(prUrl);
94514
94847
  if (!m)
@@ -94552,7 +94885,7 @@ async function reactivateState(stateFilePath, log2, changeName) {
94552
94885
  }
94553
94886
  async function runWorkerWithFixTask(ctx, heading, body) {
94554
94887
  try {
94555
- await prependFixTask(join20(ctx.changeDir, "tasks.md"), heading, body);
94888
+ await prependFixTask(join20(ctx.changeDir, AGENT_TASKS_FILENAME), heading, body);
94556
94889
  } catch (err) {
94557
94890
  ctx.log(`! could not prepend fix task: ${err.message}`, "red");
94558
94891
  return 1;
@@ -94831,8 +95164,20 @@ async function runPrPhase(input, deps) {
94831
95164
  };
94832
95165
  try {
94833
95166
  const status = await cmd.run(["git", "status", "--porcelain"], cwd2);
94834
- if (status.stdout.trim()) {
94835
- log2(`! ${changeName} has uncommitted changes after worker exit \u2014 the agent should commit everything before finishing. These changes will not be included in the PR.`, "yellow");
95167
+ const summary = summarizeUncommittedStatus(status.stdout);
95168
+ if (summary.count > 0) {
95169
+ const existingPrUrl = branch ? await findExistingOpenPrUrl(cmd, cwd2, branch) : null;
95170
+ const indented = summary.preview.map((line) => ` ${line}`).join(`
95171
+ `);
95172
+ const suffix = summary.truncated ? `
95173
+ ... and ${summary.truncated} more` : "";
95174
+ if (existingPrUrl) {
95175
+ log2(` ${changeName}: ${summary.count} uncommitted file(s) after worker \u2014 will retry next iteration:
95176
+ ${indented}${suffix}`, "gray");
95177
+ } else {
95178
+ log2(`! ${changeName} has uncommitted changes after worker exit \u2014 the agent should commit everything before finishing. These changes will not be included in the PR:
95179
+ ${indented}${suffix}`, "yellow");
95180
+ }
94836
95181
  }
94837
95182
  } catch (err) {
94838
95183
  log2(`! git status check failed for ${changeName}: ${err.message}`, "yellow");
@@ -94987,14 +95332,233 @@ async function runPostTask(input, deps) {
94987
95332
  }
94988
95333
  var CI_FAILED_EXIT = 70, PR_FAILED_EXIT = 71, repoAutoMergeCache;
94989
95334
  var init_post_task = __esm(() => {
95335
+ init_tasks_md();
94990
95336
  init_ci();
94991
95337
  init_worktree();
94992
95338
  repoAutoMergeCache = new Map;
94993
95339
  });
94994
95340
 
95341
+ // apps/agent/src/agent/baseline/runner.ts
95342
+ import { createHash } from "crypto";
95343
+ async function runBaseline(input) {
95344
+ const { cmdRunner, gitRunner, cwd: cwd2, commands, baseBranch, outputCharLimit } = input;
95345
+ if (commands.length === 0) {
95346
+ return { ok: true, failures: [], fingerprint: "" };
95347
+ }
95348
+ try {
95349
+ await gitRunner.run(["fetch", "origin", baseBranch], cwd2);
95350
+ await gitRunner.run(["reset", "--hard", `origin/${baseBranch}`], cwd2);
95351
+ } catch (err) {
95352
+ const e = err;
95353
+ const failure = {
95354
+ command: `git checkout ${baseBranch}`,
95355
+ exitCode: -1,
95356
+ stdout: "",
95357
+ stderr: truncate3(e.stderr ?? e.message, outputCharLimit),
95358
+ fingerprint: fingerprintFor(`git checkout ${baseBranch}`, e.stderr ?? e.message)
95359
+ };
95360
+ return {
95361
+ ok: false,
95362
+ failures: [failure],
95363
+ fingerprint: failure.fingerprint
95364
+ };
95365
+ }
95366
+ const failures = [];
95367
+ for (const command of commands) {
95368
+ const parts = parseCommand(command);
95369
+ let stdout = "";
95370
+ let stderr = "";
95371
+ let exitCode = 0;
95372
+ try {
95373
+ const r = await cmdRunner.run(parts, cwd2);
95374
+ stdout = r.stdout;
95375
+ stderr = r.stderr;
95376
+ } catch (err) {
95377
+ const e = err;
95378
+ stdout = e.stdout ?? "";
95379
+ stderr = e.stderr ?? e.message;
95380
+ exitCode = typeof e.code === "number" ? e.code : 1;
95381
+ }
95382
+ if (exitCode !== 0) {
95383
+ failures.push({
95384
+ command,
95385
+ exitCode,
95386
+ stdout: truncate3(stdout, outputCharLimit),
95387
+ stderr: truncate3(stderr, outputCharLimit),
95388
+ fingerprint: fingerprintFor(command, stderr || stdout)
95389
+ });
95390
+ }
95391
+ }
95392
+ return {
95393
+ ok: failures.length === 0,
95394
+ failures,
95395
+ fingerprint: failures[0]?.fingerprint ?? ""
95396
+ };
95397
+ }
95398
+ function parseCommand(cmd) {
95399
+ return cmd.trim().split(/\s+/);
95400
+ }
95401
+ function truncate3(text, limit) {
95402
+ if (text.length <= limit)
95403
+ return text;
95404
+ return text.slice(0, limit) + `
95405
+ \u2026(truncated ${text.length - limit} chars)`;
95406
+ }
95407
+ function fingerprintFor(command, output) {
95408
+ const firstLine = (output || "").split(`
95409
+ `).find((l) => l.trim().length > 0) ?? "";
95410
+ return createHash("sha1").update(`${command}
95411
+ ${firstLine.trim()}`).digest("hex").slice(0, 12);
95412
+ }
95413
+ var init_runner = () => {};
95414
+
95415
+ // apps/agent/src/agent/baseline/gate.ts
95416
+ async function runBaselineGate(deps) {
95417
+ if (!deps.enabled)
95418
+ return;
95419
+ if (deps.commands.length === 0) {
95420
+ deps.onLog(" baseline check skipped \u2014 no commands configured", "gray");
95421
+ return;
95422
+ }
95423
+ const result2 = await runBaseline({
95424
+ cmdRunner: deps.cmdRunner,
95425
+ gitRunner: deps.gitRunner,
95426
+ cwd: deps.cwd,
95427
+ commands: deps.commands,
95428
+ baseBranch: deps.baseBranch,
95429
+ outputCharLimit: deps.outputCharLimit
95430
+ });
95431
+ if (result2.ok) {
95432
+ if (deps.coordinator.isPaused()) {
95433
+ deps.coordinator.clearPaused();
95434
+ deps.onLog("\u2713 baseline recovered \u2014 resuming new pickups", "green");
95435
+ }
95436
+ return;
95437
+ }
95438
+ const firstFailure = result2.failures[0];
95439
+ const description = renderIssueBody(result2, deps.outputCharLimit);
95440
+ const title = `Pre-existing baseline error on ${deps.baseBranch}: \`${firstFailure.command}\``;
95441
+ let issueIdentifier = "BASELINE";
95442
+ let issueId;
95443
+ if (deps.linear) {
95444
+ try {
95445
+ const existing = await deps.linear.findOpen();
95446
+ if (existing) {
95447
+ const existingFp = extractFingerprint(existing.description ?? "");
95448
+ if (existingFp !== result2.fingerprint) {
95449
+ await deps.linear.updateDescription(existing.id, description);
95450
+ deps.onLog(` baseline ticket ${existing.identifier} updated (fingerprint changed)`, "yellow");
95451
+ }
95452
+ issueIdentifier = existing.identifier;
95453
+ issueId = existing.id;
95454
+ } else {
95455
+ const created = await deps.linear.create(title, description);
95456
+ issueIdentifier = created.identifier;
95457
+ issueId = created.id;
95458
+ deps.onLog(` baseline ticket ${created.identifier} created`, "yellow");
95459
+ }
95460
+ } catch (err) {
95461
+ deps.onLog(`! Linear baseline ticket sync failed: ${err.message}`, "red");
95462
+ }
95463
+ } else {
95464
+ deps.onLog("! baseline failed but no Linear client configured \u2014 pausing without ticket", "yellow");
95465
+ }
95466
+ const since = deps.coordinator.isPaused() ? deps.coordinator.getPause()?.since ?? deps.now?.() ?? Date.now() : deps.now?.() ?? Date.now();
95467
+ deps.coordinator.setPaused({
95468
+ issueIdentifier,
95469
+ ...issueId !== undefined ? { issueId } : {},
95470
+ command: firstFailure.command,
95471
+ fingerprint: result2.fingerprint,
95472
+ since
95473
+ });
95474
+ }
95475
+ function extractFingerprint(body) {
95476
+ const m = FINGERPRINT_MARKER_RE.exec(body);
95477
+ return m?.[1] ?? null;
95478
+ }
95479
+ function renderIssueBody(result2, outputCharLimit) {
95480
+ const lines = [];
95481
+ lines.push(`<!-- ralphy:baseline:${result2.fingerprint} -->`);
95482
+ lines.push("");
95483
+ lines.push("Ralph detected a failing command on the base branch.");
95484
+ lines.push("");
95485
+ lines.push("New issues will not be picked up by Ralph until this is resolved.");
95486
+ lines.push("Mark this Linear issue as Done to lift the pause.");
95487
+ lines.push("");
95488
+ for (const f2 of result2.failures) {
95489
+ lines.push(`### \`${f2.command}\` \u2014 exit ${f2.exitCode}`);
95490
+ lines.push("");
95491
+ if (f2.stdout.trim()) {
95492
+ lines.push("**stdout:**");
95493
+ lines.push("```");
95494
+ lines.push(truncateForBody(f2.stdout, outputCharLimit));
95495
+ lines.push("```");
95496
+ }
95497
+ if (f2.stderr.trim()) {
95498
+ lines.push("**stderr:**");
95499
+ lines.push("```");
95500
+ lines.push(truncateForBody(f2.stderr, outputCharLimit));
95501
+ lines.push("```");
95502
+ }
95503
+ lines.push("");
95504
+ }
95505
+ return lines.join(`
95506
+ `);
95507
+ }
95508
+ function truncateForBody(text, limit) {
95509
+ if (text.length <= limit)
95510
+ return text;
95511
+ return text.slice(0, limit) + `
95512
+ \u2026(truncated)`;
95513
+ }
95514
+ var FINGERPRINT_MARKER_RE;
95515
+ var init_gate = __esm(() => {
95516
+ init_runner();
95517
+ FINGERPRINT_MARKER_RE = /<!--\s*ralphy:baseline:([a-f0-9]+)\s*-->/i;
95518
+ });
95519
+
94995
95520
  // apps/agent/src/agent/wire.ts
94996
95521
  import { join as join21 } from "path";
94997
95522
  import { mkdir as mkdir6 } from "fs/promises";
95523
+ async function pickOpenPrUrlFromAttachments(urls, issueIdent, cmd, cwd2, onLog) {
95524
+ const candidates = urls.filter((url2) => GITHUB_PR_URL_RE.test(url2));
95525
+ let sawNonOpenPr = false;
95526
+ for (const url2 of candidates) {
95527
+ try {
95528
+ const res = await cmd.run(["gh", "pr", "view", url2, "--json", "state"], cwd2);
95529
+ const parsed = JSON.parse(res.stdout.trim());
95530
+ if (parsed.state === "OPEN")
95531
+ return { url: url2, sawNonOpenPr };
95532
+ if (parsed.state === "MERGED" || parsed.state === "CLOSED")
95533
+ sawNonOpenPr = true;
95534
+ } catch (err) {
95535
+ onLog(`! gh pr view ${url2} failed for ${issueIdent}: ${err.message}`, "yellow");
95536
+ }
95537
+ }
95538
+ return { url: null, sawNonOpenPr };
95539
+ }
95540
+ function githubReactionSlug(emoji3) {
95541
+ switch (emoji3) {
95542
+ case "\uD83D\uDC40":
95543
+ return "eyes";
95544
+ case "\uD83D\uDC4D":
95545
+ return "+1";
95546
+ case "\uD83D\uDC4E":
95547
+ return "-1";
95548
+ case "\u2764\uFE0F":
95549
+ return "heart";
95550
+ case "\uD83C\uDF89":
95551
+ return "hooray";
95552
+ case "\uD83D\uDE80":
95553
+ return "rocket";
95554
+ case "\uD83D\uDE04":
95555
+ return "laugh";
95556
+ case "\uD83D\uDE15":
95557
+ return "confused";
95558
+ default:
95559
+ return emoji3;
95560
+ }
95561
+ }
94998
95562
  function traceCmdRunner(base2, onStart, onEnd) {
94999
95563
  return {
95000
95564
  run: async (cmd, cwd2) => {
@@ -95111,6 +95675,7 @@ function buildAgentCoordinator(input) {
95111
95675
  tasksDir,
95112
95676
  apiKey,
95113
95677
  onLog,
95678
+ onFileLog,
95114
95679
  onWorkersChanged,
95115
95680
  onWorkerStarted,
95116
95681
  onWorkerExited,
@@ -95275,21 +95840,23 @@ function buildAgentCoordinator(input) {
95275
95840
  return { workerCwd, scaffoldTasksDir, scaffoldStatesDir, branch };
95276
95841
  const probeName = issue2.identifier.toLowerCase();
95277
95842
  const baseBranch = baseBranchFromLabels(issue2.labels) ?? cfg.prBaseBranch;
95843
+ let wt;
95278
95844
  try {
95279
- const wt = await createWorktree(projectRoot, probeName, baseBranch, gitRunner);
95280
- workerCwd = wt.cwd;
95281
- branch = wt.branch;
95282
- const wtLayout = projectLayout(wt.cwd);
95283
- scaffoldTasksDir = wtLayout.tasksDir;
95284
- scaffoldStatesDir = wtLayout.statesDir;
95285
- onLog(` ${issue2.identifier} worktree: ${wt.cwd} (${wt.branch})`, "gray");
95286
- try {
95287
- await seedWorktreeMcpConfig(projectRoot, wt.cwd);
95288
- } catch (err) {
95289
- onLog(`! seeding .mcp.json failed for ${issue2.identifier}: ${err.message}`, "yellow");
95290
- }
95845
+ wt = await createWorktree(projectRoot, probeName, baseBranch, gitRunner);
95846
+ } catch (err) {
95847
+ onLog(`! worktree create failed for ${issue2.identifier}: ${err.message} \u2014 skipping (useWorktree is required)`, "red");
95848
+ throw err;
95849
+ }
95850
+ workerCwd = wt.cwd;
95851
+ branch = wt.branch;
95852
+ const wtLayout = projectLayout(wt.cwd);
95853
+ scaffoldTasksDir = wtLayout.tasksDir;
95854
+ scaffoldStatesDir = wtLayout.statesDir;
95855
+ onLog(` ${issue2.identifier} worktree: ${wt.cwd} (${wt.branch})`, "gray");
95856
+ try {
95857
+ await seedWorktreeMcpConfig(projectRoot, wt.cwd);
95291
95858
  } catch (err) {
95292
- onLog(`! worktree create failed for ${issue2.identifier}: ${err.message} \u2014 falling back to project root`, "yellow");
95859
+ onLog(`! seeding .mcp.json failed for ${issue2.identifier}: ${err.message}`, "yellow");
95293
95860
  }
95294
95861
  return { workerCwd, scaffoldTasksDir, scaffoldStatesDir, branch };
95295
95862
  }
@@ -95312,7 +95879,8 @@ function buildAgentCoordinator(input) {
95312
95879
  identifier: issue2.identifier,
95313
95880
  title: issue2.title,
95314
95881
  description: issue2.description ?? "",
95315
- url: issue2.url
95882
+ url: issue2.url,
95883
+ labels: issue2.labels
95316
95884
  },
95317
95885
  attempt: 1,
95318
95886
  last_error: ""
@@ -95337,7 +95905,7 @@ function buildAgentCoordinator(input) {
95337
95905
  branchByChange.set(changeName, branch);
95338
95906
  if (mode === "review") {
95339
95907
  const wtLayout = projectLayout(workerCwd);
95340
- const tasksFile = join21(wtLayout.changeDir(changeName), "tasks.md");
95908
+ const tasksFile = join21(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
95341
95909
  let body;
95342
95910
  let heading;
95343
95911
  if (trigger) {
@@ -95362,7 +95930,7 @@ function buildAgentCoordinator(input) {
95362
95930
  await reactivateState2(wtLayout.stateFile(changeName), changeName);
95363
95931
  } else if (mode === "conflict-fix") {
95364
95932
  const wtLayout = projectLayout(workerCwd);
95365
- const tasksFile = join21(wtLayout.changeDir(changeName), "tasks.md");
95933
+ const tasksFile = join21(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
95366
95934
  const prUrl = prByChange.get(changeName);
95367
95935
  const body = [
95368
95936
  `The PR for this change has merge conflicts with \`${cfg.prBaseBranch}\`.`,
@@ -95681,9 +96249,13 @@ PR: ${prUrl}` : ""
95681
96249
  if (byBranch)
95682
96250
  return byBranch;
95683
96251
  const fromLinear = await discoverPrUrlFromLinear(issue2);
95684
- if (fromLinear) {
95685
- onLog(` ${issue2.identifier}: PR discovered via Linear attachment (${fromLinear})`, "gray");
95686
- return fromLinear;
96252
+ if (fromLinear.url) {
96253
+ onLog(` ${issue2.identifier}: PR discovered via Linear attachment (${fromLinear.url})`, "gray");
96254
+ return fromLinear.url;
96255
+ }
96256
+ if (fromLinear.sawNonOpenPr) {
96257
+ markPrUnavailable(changeName);
96258
+ return null;
95687
96259
  }
95688
96260
  onLog(` ${issue2.identifier}: no open PR found on head=${branch} or Linear attachments; conflict scan skipped for ${PR_UNAVAILABLE_TTL_MS / 60000}m`, "gray");
95689
96261
  markPrUnavailable(changeName);
@@ -95729,14 +96301,14 @@ PR: ${prUrl}` : ""
95729
96301
  return null;
95730
96302
  }
95731
96303
  async function discoverPrUrlFromLinear(issue2) {
96304
+ let attachments;
95732
96305
  try {
95733
- const attachments = await fetchIssueAttachments(apiKey, issue2.id);
95734
- const match = attachments.find((a) => /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/.test(a.url));
95735
- return match?.url ?? null;
96306
+ attachments = await fetchIssueAttachments(apiKey, issue2.id);
95736
96307
  } catch (err) {
95737
96308
  onLog(`! Linear attachments fetch failed for ${issue2.identifier}: ${err.message}`, "yellow");
95738
- return null;
96309
+ return { url: null, sawNonOpenPr: false };
95739
96310
  }
96311
+ return pickOpenPrUrlFromAttachments(attachments.map((a) => a.url), issue2.identifier, cmdRunner, projectRoot, onLog);
95740
96312
  }
95741
96313
  async function fetchDoneCandidates() {
95742
96314
  if (!indicators.setDone)
@@ -95789,6 +96361,11 @@ PR: ${prUrl}` : ""
95789
96361
  url: issue2.url
95790
96362
  }
95791
96363
  });
96364
+ try {
96365
+ await addReactionToComment(apiKey, c.id, "\uD83D\uDC40");
96366
+ } catch (err) {
96367
+ onLog(`! mention scan: Linear reaction failed for ${issue2.identifier}: ${err.message}`, "yellow");
96368
+ }
95792
96369
  queued.add(issue2.id);
95793
96370
  break;
95794
96371
  }
@@ -95800,6 +96377,7 @@ PR: ${prUrl}` : ""
95800
96377
  continue;
95801
96378
  if (wantMention) {
95802
96379
  const ghComments = await fetchPrIssueComments(prUrl);
96380
+ const prMatch = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/\d+/.exec(prUrl);
95803
96381
  for (const c of ghComments) {
95804
96382
  if (!containsHandle(c.body, handle))
95805
96383
  continue;
@@ -95815,6 +96393,14 @@ PR: ${prUrl}` : ""
95815
96393
  url: c.url
95816
96394
  }
95817
96395
  });
96396
+ if (prMatch) {
96397
+ const [, owner, repo] = prMatch;
96398
+ try {
96399
+ await addGithubReactionToComment({ owner, repo, kind: "issue" }, c.id, "\uD83D\uDC40");
96400
+ } catch (err) {
96401
+ onLog(`! mention scan: GitHub reaction failed for ${prUrl}: ${err.message}`, "yellow");
96402
+ }
96403
+ }
95818
96404
  queued.add(issue2.id);
95819
96405
  break;
95820
96406
  }
@@ -95982,6 +96568,11 @@ PR: ${prUrl}` : ""
95982
96568
  prByChange.set(changeName, found);
95983
96569
  return found;
95984
96570
  }
96571
+ async function addGithubReactionToComment(source, commentId, emoji3) {
96572
+ const content = githubReactionSlug(emoji3);
96573
+ const path = source.kind === "issue" ? `repos/${source.owner}/${source.repo}/issues/comments/${commentId}/reactions` : `repos/${source.owner}/${source.repo}/pulls/comments/${commentId}/reactions`;
96574
+ await cmdRunner.run(["gh", "api", "-X", "POST", path, "-f", `content=${content}`], projectRoot);
96575
+ }
95985
96576
  async function fetchPrIssueComments(prUrl) {
95986
96577
  const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/.exec(prUrl);
95987
96578
  if (!m)
@@ -95993,7 +96584,7 @@ PR: ${prUrl}` : ""
95993
96584
  "api",
95994
96585
  `repos/${owner}/${repo}/issues/${num}/comments`,
95995
96586
  "--jq",
95996
- "[.[] | {body: .body, createdAt: .created_at, author: .user.login, url: .html_url}]"
96587
+ "[.[] | {id: .id, body: .body, createdAt: .created_at, author: .user.login, url: .html_url}]"
95997
96588
  ], projectRoot);
95998
96589
  const parsed = JSON.parse(res.stdout || "[]");
95999
96590
  return parsed;
@@ -96020,6 +96611,7 @@ PR: ${prUrl}` : ""
96020
96611
  },
96021
96612
  checkPrStatus,
96022
96613
  onLog,
96614
+ ...onFileLog ? { onFileLog } : {},
96023
96615
  onWorkersChanged,
96024
96616
  getIterationCount: async (changeName) => {
96025
96617
  const root = cwdByChange.get(changeName) ?? projectRoot;
@@ -96037,17 +96629,65 @@ PR: ${prUrl}` : ""
96037
96629
  ...indicators.setConflicted !== undefined ? { setConflicted: indicators.setConflicted } : {},
96038
96630
  ...indicators.clearConflicted !== undefined ? { clearConflicted: indicators.clearConflicted } : {},
96039
96631
  ...indicators.clearReview !== undefined ? { clearReview: indicators.clearReview } : {},
96632
+ ...indicators.getAutoMerge !== undefined ? { getAutoMerge: indicators.getAutoMerge } : {},
96040
96633
  postComments: cfg.linear.postComments,
96041
96634
  commentEveryIterations: cfg.linear.updateEveryIterations,
96042
96635
  ...args.maxTickets > 0 ? { maxTickets: args.maxTickets } : {}
96043
96636
  });
96044
96637
  const filterDesc = describeIndicators(indicators, team, assignee);
96638
+ const baselineCfg = cfg.preExistingErrorCheck;
96639
+ const baselineCommands = resolveBaselineCommands(cfg);
96640
+ const baselineEnabled = (args.preExistingErrorCheck ?? baselineCfg.enabled) === true;
96641
+ const baselineTeam = team;
96642
+ const runBaselineGateOnce = async () => {
96643
+ if (!baselineEnabled)
96644
+ return;
96645
+ await runBaselineGate({
96646
+ enabled: true,
96647
+ commands: baselineCommands,
96648
+ baseBranch: baselineCfg.baseBranch,
96649
+ outputCharLimit: baselineCfg.outputCharLimit,
96650
+ cwd: projectRoot,
96651
+ cmdRunner,
96652
+ gitRunner,
96653
+ coordinator: coord,
96654
+ ...baselineTeam && apiKey ? {
96655
+ linear: {
96656
+ findOpen: () => findOpenIssueByLabel(apiKey, baselineTeam, baselineCfg.label),
96657
+ create: async (title, description) => {
96658
+ const teamId = await fetchTeamIdByKey(apiKey, baselineTeam);
96659
+ if (!teamId)
96660
+ throw new Error("Linear team not found");
96661
+ let labelIds;
96662
+ try {
96663
+ const labelId = await resolveLabelIdForTeam(baselineTeam, baselineCfg.label);
96664
+ if (labelId)
96665
+ labelIds = [labelId];
96666
+ } catch {}
96667
+ return createIssue(apiKey, {
96668
+ teamId,
96669
+ title,
96670
+ description,
96671
+ ...labelIds ? { labelIds } : {}
96672
+ });
96673
+ },
96674
+ updateDescription: (id, description) => updateIssueDescription(apiKey, id, description)
96675
+ }
96676
+ } : {},
96677
+ onLog
96678
+ });
96679
+ };
96680
+ async function resolveLabelIdForTeam(teamKey, labelName) {
96681
+ const fakeIssue = { identifier: `${teamKey}-0` };
96682
+ return resolveLabelId(fakeIssue, labelName);
96683
+ }
96045
96684
  return {
96046
96685
  coord,
96047
96686
  filterDesc,
96048
96687
  concurrency,
96049
96688
  pollInterval,
96050
- getWorkerCwd: (changeName) => cwdByChange.get(changeName)
96689
+ getWorkerCwd: (changeName) => cwdByChange.get(changeName),
96690
+ runBaselineGate: runBaselineGateOnce
96051
96691
  };
96052
96692
  }
96053
96693
  function describeIndicators(indicators, team, assignee) {
@@ -96068,10 +96708,11 @@ function describeIndicators(indicators, team, assignee) {
96068
96708
  }
96069
96709
  return parts.join(", ");
96070
96710
  }
96071
- var bunGitRunner, bunCmdRunner;
96711
+ var GITHUB_PR_URL_RE, bunGitRunner, bunCmdRunner;
96072
96712
  var init_wire = __esm(() => {
96073
96713
  init_log();
96074
96714
  init_layout();
96715
+ init_tasks_md();
96075
96716
  init_workflow();
96076
96717
  init_types2();
96077
96718
  init_coordinator();
@@ -96079,6 +96720,9 @@ var init_wire = __esm(() => {
96079
96720
  init_worktree();
96080
96721
  init_ci();
96081
96722
  init_post_task();
96723
+ init_gate();
96724
+ init_workflow();
96725
+ GITHUB_PR_URL_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/;
96082
96726
  bunGitRunner = {
96083
96727
  run: async (args, cwd2) => {
96084
96728
  const proc = Bun.spawn({ cmd: ["git", ...args], cwd: cwd2, stdout: "pipe", stderr: "pipe" });
@@ -96155,15 +96799,286 @@ function deriveOpenSpecPhase(inputs) {
96155
96799
  return "implement";
96156
96800
  return "tasks";
96157
96801
  }
96802
+ function shouldShowPhasePipeline(phase) {
96803
+ return phase === "proposal" || phase === "design" || phase === "tasks";
96804
+ }
96805
+ function shouldShowSubtasksPanel(phase, showPendingTasks, hasSubtasks) {
96806
+ if (!showPendingTasks || !hasSubtasks)
96807
+ return false;
96808
+ return phase == null || phase === "implement" || phase === "done";
96809
+ }
96810
+ function shouldShowProgressBar(phase, showPendingTasks, hasProgress) {
96811
+ if (showPendingTasks || !hasProgress)
96812
+ return false;
96813
+ return phase == null || phase === "implement" || phase === "done";
96814
+ }
96815
+ function phasePipeline(phase) {
96816
+ if (phase === "done") {
96817
+ return PIPELINE_PHASES.map((p) => ({ phase: p, label: p, status: "done" }));
96818
+ }
96819
+ const idx = PIPELINE_PHASES.indexOf(phase);
96820
+ return PIPELINE_PHASES.map((p, i) => ({
96821
+ phase: p,
96822
+ label: p,
96823
+ status: i < idx ? "done" : i === idx ? "current" : "pending"
96824
+ }));
96825
+ }
96826
+ var PIPELINE_PHASES;
96827
+ var init_phase = __esm(() => {
96828
+ PIPELINE_PHASES = [
96829
+ "proposal",
96830
+ "design",
96831
+ "tasks",
96832
+ "implement"
96833
+ ];
96834
+ });
96835
+
96836
+ // apps/agent/src/hooks/useTerminalSize.ts
96837
+ function readSize2() {
96838
+ return {
96839
+ columns: process.stdout.columns ?? 80,
96840
+ rows: process.stdout.rows ?? 24
96841
+ };
96842
+ }
96843
+ function useTerminalSize2() {
96844
+ const [size2, setSize] = import_react59.useState(() => ({
96845
+ ...readSize2(),
96846
+ resizeKey: 0
96847
+ }));
96848
+ import_react59.useEffect(() => {
96849
+ if (!process.stdout.isTTY)
96850
+ return;
96851
+ const onResize = () => {
96852
+ const { columns, rows } = readSize2();
96853
+ setSize((prev) => {
96854
+ if (prev.columns === columns && prev.rows === rows)
96855
+ return prev;
96856
+ return { columns, rows, resizeKey: prev.resizeKey + 1 };
96857
+ });
96858
+ };
96859
+ process.stdout.on("resize", onResize);
96860
+ return () => {
96861
+ process.stdout.off("resize", onResize);
96862
+ };
96863
+ }, []);
96864
+ return size2;
96865
+ }
96866
+ var import_react59;
96867
+ var init_useTerminalSize2 = __esm(() => {
96868
+ import_react59 = __toESM(require_react(), 1);
96869
+ });
96870
+
96871
+ // apps/agent/src/components/SteeringField.tsx
96872
+ function reducer2(state, action) {
96873
+ switch (action.type) {
96874
+ case "toggleFocus":
96875
+ return { ...state, focused: !state.focused };
96876
+ case "clearAndBlur":
96877
+ return { ...state, buffer: "", cursor: 0, focused: false };
96878
+ case "insert": {
96879
+ const before2 = state.buffer.slice(0, state.cursor);
96880
+ const after2 = state.buffer.slice(state.cursor);
96881
+ return {
96882
+ ...state,
96883
+ buffer: before2 + action.chars + after2,
96884
+ cursor: state.cursor + action.chars.length
96885
+ };
96886
+ }
96887
+ case "backspace": {
96888
+ if (state.cursor === 0)
96889
+ return state;
96890
+ return {
96891
+ ...state,
96892
+ buffer: state.buffer.slice(0, state.cursor - 1) + state.buffer.slice(state.cursor),
96893
+ cursor: state.cursor - 1
96894
+ };
96895
+ }
96896
+ case "moveLeft":
96897
+ return { ...state, cursor: Math.max(0, state.cursor - 1) };
96898
+ case "moveRight":
96899
+ return { ...state, cursor: Math.min(state.buffer.length, state.cursor + 1) };
96900
+ case "status":
96901
+ return { ...state, status: action.value };
96902
+ }
96903
+ }
96904
+ function SteeringField({
96905
+ active,
96906
+ width,
96907
+ onSubmit,
96908
+ onFocusChange,
96909
+ initialBuffer = "",
96910
+ initialCursor,
96911
+ initialFocused = false,
96912
+ onStateChange
96913
+ }) {
96914
+ const [state, dispatch] = import_react60.useReducer(reducer2, { initialBuffer, initialCursor, initialFocused }, (init2) => ({
96915
+ buffer: init2.initialBuffer,
96916
+ cursor: init2.initialCursor ?? init2.initialBuffer.length,
96917
+ focused: init2.initialFocused,
96918
+ status: "idle"
96919
+ }));
96920
+ const { buffer, cursor: cursor4, focused, status } = state;
96921
+ const stateRef = import_react60.useRef(state);
96922
+ stateRef.current = state;
96923
+ const hintTimerRef = import_react60.useRef(null);
96924
+ import_react60.useEffect(() => {
96925
+ onFocusChange?.(focused);
96926
+ }, [focused, onFocusChange]);
96927
+ import_react60.useEffect(() => {
96928
+ onStateChange?.({ buffer, cursor: cursor4, focused });
96929
+ }, [buffer, cursor4, focused, onStateChange]);
96930
+ import_react60.useEffect(() => {
96931
+ return () => {
96932
+ if (hintTimerRef.current)
96933
+ clearTimeout(hintTimerRef.current);
96934
+ };
96935
+ }, []);
96936
+ function flashStatus(next) {
96937
+ dispatch({ type: "status", value: next });
96938
+ if (hintTimerRef.current)
96939
+ clearTimeout(hintTimerRef.current);
96940
+ hintTimerRef.current = setTimeout(() => dispatch({ type: "status", value: "idle" }), STATUS_HINT_MS);
96941
+ }
96942
+ use_input_default((input, key) => {
96943
+ if (key.ctrl && (input === "s" || input === "S")) {
96944
+ dispatch({ type: "toggleFocus" });
96945
+ return;
96946
+ }
96947
+ if (!stateRef.current.focused)
96948
+ return;
96949
+ if (key.escape) {
96950
+ dispatch({ type: "clearAndBlur" });
96951
+ return;
96952
+ }
96953
+ if (key.return) {
96954
+ const trimmed = stateRef.current.buffer.trim();
96955
+ if (trimmed.length === 0)
96956
+ return;
96957
+ Promise.resolve().then(() => onSubmit(trimmed)).then(() => {
96958
+ flashStatus("sent");
96959
+ }).catch(() => {
96960
+ flashStatus("failed");
96961
+ });
96962
+ dispatch({ type: "clearAndBlur" });
96963
+ return;
96964
+ }
96965
+ if (key.backspace || key.delete) {
96966
+ dispatch({ type: "backspace" });
96967
+ return;
96968
+ }
96969
+ if (key.leftArrow) {
96970
+ dispatch({ type: "moveLeft" });
96971
+ return;
96972
+ }
96973
+ if (key.rightArrow) {
96974
+ dispatch({ type: "moveRight" });
96975
+ return;
96976
+ }
96977
+ if (key.tab || key.upArrow || key.downArrow || key.ctrl || key.meta)
96978
+ return;
96979
+ if (!input)
96980
+ return;
96981
+ const printable = input.replace(/[\x00-\x1f\x7f]/g, "");
96982
+ if (!printable)
96983
+ return;
96984
+ dispatch({ type: "insert", chars: printable });
96985
+ }, { isActive: active });
96986
+ if (!active)
96987
+ return null;
96988
+ const placeholder = status === "sent" ? PLACEHOLDER_SENT : status === "failed" ? PLACEHOLDER_FAILED : PLACEHOLDER_IDLE;
96989
+ const borderColor = focused ? "yellow" : "gray";
96990
+ const placeholderColor = status === "sent" ? "green" : status === "failed" ? "red" : "gray";
96991
+ const innerWidth = Math.max(0, width - 4);
96992
+ const labelText = " STEER (CTRL+S) ";
96993
+ const dashes = Math.max(0, innerWidth - labelText.length);
96994
+ const left = Math.floor(dashes / 2);
96995
+ const right = dashes - left;
96996
+ const before2 = buffer.slice(0, cursor4);
96997
+ const at2 = buffer.slice(cursor4, cursor4 + 1) || " ";
96998
+ const after2 = buffer.slice(cursor4 + 1);
96999
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97000
+ flexDirection: "column",
97001
+ width,
97002
+ children: [
97003
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97004
+ color: borderColor,
97005
+ children: `\u256D${"\u2500".repeat(left)}${labelText}${"\u2500".repeat(right)}\u256E`
97006
+ }, undefined, false, undefined, this),
97007
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97008
+ borderStyle: "round",
97009
+ borderTop: false,
97010
+ borderColor,
97011
+ width,
97012
+ paddingX: 1,
97013
+ children: focused ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97014
+ children: [
97015
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97016
+ color: "white",
97017
+ children: "> "
97018
+ }, undefined, false, undefined, this),
97019
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97020
+ children: before2
97021
+ }, undefined, false, undefined, this),
97022
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97023
+ inverse: true,
97024
+ children: at2
97025
+ }, undefined, false, undefined, this),
97026
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97027
+ children: after2
97028
+ }, undefined, false, undefined, this)
97029
+ ]
97030
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97031
+ color: placeholderColor,
97032
+ dimColor: status === "idle",
97033
+ children: placeholder
97034
+ }, undefined, false, undefined, this)
97035
+ }, undefined, false, undefined, this)
97036
+ ]
97037
+ }, undefined, true, undefined, this);
97038
+ }
97039
+ var import_react60, jsx_dev_runtime9, STATUS_HINT_MS = 2000, PLACEHOLDER_IDLE = "CTRL+S to steer", PLACEHOLDER_SENT = "steered \u2192 next iteration", PLACEHOLDER_FAILED = "send failed";
97040
+ var init_SteeringField = __esm(async () => {
97041
+ await init_build2();
97042
+ import_react60 = __toESM(require_react(), 1);
97043
+ jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
97044
+ });
96158
97045
 
96159
97046
  // apps/agent/src/components/AgentMode.tsx
96160
97047
  import { join as join22 } from "path";
97048
+ async function appendSteeringImpl(changeDir, message) {
97049
+ await runWithContext(createDefaultContext(), async () => {
97050
+ appendSteeringMessage(changeDir, message);
97051
+ });
97052
+ }
96161
97053
  function nextId() {
96162
97054
  lineCounter += 1;
96163
97055
  return `${Date.now()}-${lineCounter}`;
96164
97056
  }
96165
- function visibleLogWindow(lines, cap = MAX_LOG_VIEWPORT_LINES) {
96166
- return lines.slice(-cap);
97057
+ function parseSubtasks(tasksMd) {
97058
+ const out = [];
97059
+ let skipSection = false;
97060
+ for (const line of tasksMd.split(`
97061
+ `)) {
97062
+ const heading = line.match(/^##\s+(.+?)\s*$/);
97063
+ if (heading) {
97064
+ const title = heading[1].trim();
97065
+ skipSection = title.toLowerCase() === "planning" || isFlowTaskHeading(title);
97066
+ continue;
97067
+ }
97068
+ if (skipSection)
97069
+ continue;
97070
+ const m = line.match(/^- \[([ xX])\] (.+)$/);
97071
+ if (m)
97072
+ out.push({ done: m[1] !== " ", text: m[2].trim() });
97073
+ }
97074
+ return out;
97075
+ }
97076
+ function orderSubtasksForCappedDisplay(subtasks) {
97077
+ const pending = [];
97078
+ const done = [];
97079
+ for (const s of subtasks)
97080
+ (s.done ? done : pending).push(s);
97081
+ return [...pending, ...done];
96167
97082
  }
96168
97083
  function fmtCmd(argv) {
96169
97084
  const joined = argv.join(" ");
@@ -96213,28 +97128,28 @@ function LabeledBox({
96213
97128
  const dashes = Math.max(0, innerWidth - visualLen);
96214
97129
  const left = Math.floor(dashes / 2);
96215
97130
  const right = dashes - left;
96216
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97131
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96217
97132
  flexDirection: "column",
96218
97133
  width,
96219
97134
  children: [
96220
- labelNode ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97135
+ labelNode ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96221
97136
  flexDirection: "row",
96222
97137
  children: [
96223
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97138
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96224
97139
  color: borderColor,
96225
97140
  children: `\u256D${"\u2500".repeat(left)}`
96226
97141
  }, undefined, false, undefined, this),
96227
97142
  labelNode,
96228
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97143
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96229
97144
  color: borderColor,
96230
97145
  children: `${"\u2500".repeat(right)}\u256E`
96231
97146
  }, undefined, false, undefined, this)
96232
97147
  ]
96233
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97148
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96234
97149
  color: borderColor,
96235
97150
  children: `\u256D${"\u2500".repeat(left)} ${label ?? ""} ${"\u2500".repeat(right)}\u256E`
96236
97151
  }, undefined, false, undefined, this),
96237
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97152
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96238
97153
  borderStyle: "round",
96239
97154
  borderTop: false,
96240
97155
  borderColor,
@@ -96247,13 +97162,13 @@ function LabeledBox({
96247
97162
  }
96248
97163
  function Link({ url: url2, label, color }) {
96249
97164
  if (!HYPERLINKS_SUPPORTED)
96250
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97165
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96251
97166
  color,
96252
97167
  children: label
96253
97168
  }, undefined, false, undefined, this);
96254
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Transform, {
97169
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Transform, {
96255
97170
  transform: (output) => `\x1B]8;;${url2}\x07${output}\x1B]8;;\x07`,
96256
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97171
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96257
97172
  color,
96258
97173
  underline: true,
96259
97174
  children: label
@@ -96369,19 +97284,36 @@ function displayTailLines(activeCount) {
96369
97284
  return 8;
96370
97285
  return 5;
96371
97286
  }
96372
- function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97287
+ function AgentMode({
97288
+ args,
97289
+ projectRoot,
97290
+ statesDir,
97291
+ tasksDir,
97292
+ appendSteering = appendSteeringImpl,
97293
+ buildCoordinator = buildAgentCoordinator,
97294
+ ensureConfig = ensureRalphyConfig,
97295
+ loadConfig = loadRalphyConfig
97296
+ }) {
96373
97297
  const { exit } = use_app_default();
96374
97298
  const { stdout } = use_stdout_default();
96375
97299
  const { isRawModeSupported } = use_stdin_default();
96376
- const [logs, setLogs] = import_react58.useState([]);
96377
- const [, setTick] = import_react58.useState(0);
96378
- const [clock, setClock] = import_react58.useState(0);
96379
- const [focusedIdx, setFocusedIdx] = import_react58.useState(0);
96380
- const coordRef = import_react58.useRef(null);
96381
- const workerMetaRef = import_react58.useRef(new Map);
96382
- const nextPollAtRef = import_react58.useRef(0);
96383
- const cfgRef = import_react58.useRef(null);
96384
- const [pollStatus, setPollStatus] = import_react58.useState({
97300
+ const { columns, rows, resizeKey } = useTerminalSize2();
97301
+ import_react61.useEffect(() => {
97302
+ if (resizeKey === 0)
97303
+ return;
97304
+ stdout.write("\x1B[2J\x1B[3J\x1B[H");
97305
+ }, [resizeKey, stdout]);
97306
+ const [logs, setLogs] = import_react61.useState([]);
97307
+ const [, setTick] = import_react61.useState(0);
97308
+ const [clock, setClock] = import_react61.useState(0);
97309
+ const [focusedIdx, setFocusedIdx] = import_react61.useState(0);
97310
+ const [showPendingTasks, setShowPendingTasks] = import_react61.useState(true);
97311
+ const [showAllSubtasks, setShowAllSubtasks] = import_react61.useState(false);
97312
+ const coordRef = import_react61.useRef(null);
97313
+ const workerMetaRef = import_react61.useRef(new Map);
97314
+ const nextPollAtRef = import_react61.useRef(0);
97315
+ const cfgRef = import_react61.useRef(null);
97316
+ const [pollStatus, setPollStatus] = import_react61.useState({
96385
97317
  state: "idle",
96386
97318
  lastFound: null,
96387
97319
  lastAdded: null,
@@ -96394,20 +97326,20 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96394
97326
  setLogs((prev) => [...prev, { id: nextId(), text, color }]);
96395
97327
  logCoord(text, workerLogFile);
96396
97328
  }
96397
- import_react58.useEffect(() => {
97329
+ import_react61.useEffect(() => {
96398
97330
  let pollTimer = null;
96399
97331
  let cancelled = false;
96400
97332
  async function init2() {
96401
97333
  logSession(`=== session start ${SESSION_START} ===`);
96402
- const cfgPath = await ensureRalphyConfig(projectRoot);
96403
- const cfg2 = await loadRalphyConfig(projectRoot);
97334
+ const cfgPath = await ensureConfig(projectRoot);
97335
+ const cfg2 = await loadConfig(projectRoot);
96404
97336
  cfgRef.current = cfg2;
96405
97337
  appendLog(`agent mode v${VERSION} \u2014 config: ${cfgPath}`, "gray");
96406
97338
  const apiKey = process.env["LINEAR_API_KEY"];
96407
97339
  if (!apiKey) {
96408
97340
  throw new Error("LINEAR_API_KEY not set \u2014 cannot poll Linear");
96409
97341
  }
96410
- const { coord: coord2, filterDesc, concurrency, pollInterval } = buildAgentCoordinator({
97342
+ const { coord: coord2, filterDesc, concurrency, pollInterval, runBaselineGate: runBaselineGate2 } = buildCoordinator({
96411
97343
  args,
96412
97344
  cfg: cfg2,
96413
97345
  projectRoot,
@@ -96415,6 +97347,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96415
97347
  tasksDir,
96416
97348
  apiKey,
96417
97349
  onLog: appendLog,
97350
+ onFileLog: (text) => logCoord(text),
96418
97351
  onWorkersChanged: () => setTick((t) => t + 1),
96419
97352
  onWorkerStarted: (changeName, dir, logFile, changeDir) => {
96420
97353
  logSession(`worker-started ${changeName} log=${logFile}`, logFile);
@@ -96428,6 +97361,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96428
97361
  phaseDetail: "",
96429
97362
  phaseStartedAt: Date.now(),
96430
97363
  currentTask: null,
97364
+ subtasks: [],
96431
97365
  taskProgress: null,
96432
97366
  openspecPhase: null,
96433
97367
  prUrl: null,
@@ -96483,6 +97417,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96483
97417
  if (cancelled)
96484
97418
  return;
96485
97419
  setPollStatus((p) => ({ ...p, state: "polling", filterDesc }));
97420
+ try {
97421
+ await runBaselineGate2();
97422
+ } catch (err) {
97423
+ appendLog(`! baseline gate failed: ${err.message}`, "yellow");
97424
+ }
97425
+ if (cancelled)
97426
+ return;
96486
97427
  const { found, added, buckets, prStatus } = await coord2.pollOnce();
96487
97428
  if (cancelled)
96488
97429
  return;
@@ -96554,7 +97495,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96554
97495
  process.off("SIGTERM", onSig);
96555
97496
  };
96556
97497
  }, []);
96557
- import_react58.useEffect(() => {
97498
+ import_react61.useEffect(() => {
96558
97499
  let cancelled = false;
96559
97500
  const interval = setInterval(() => {
96560
97501
  if (cancelled)
@@ -96581,8 +97522,9 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96581
97522
  designFile.exists().then((ok) => ok ? designFile.text() : null)
96582
97523
  ]);
96583
97524
  if (tasksText !== null) {
96584
- const match = tasksText.match(/^- \[ \] (.+)$/m);
96585
- meta3.currentTask = match?.[1]?.trim() ?? null;
97525
+ const subtasks = parseSubtasks(tasksText);
97526
+ meta3.subtasks = subtasks;
97527
+ meta3.currentTask = subtasks.find((s) => !s.done)?.text ?? null;
96586
97528
  const { checked, total } = countProgress(tasksText);
96587
97529
  meta3.taskProgress = total > 0 ? { checked, total } : null;
96588
97530
  }
@@ -96611,10 +97553,26 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96611
97553
  const now2 = Date.now();
96612
97554
  const secsToNextPoll = nextPollAtRef.current ? Math.max(0, Math.ceil((nextPollAtRef.current - now2) / 1000)) : null;
96613
97555
  const activeCount = coord?.activeCount ?? 0;
96614
- const termWidth = (stdout?.columns ?? 100) - 2;
96615
- const termHeight = stdout?.rows ?? 40;
97556
+ const termWidth = columns - 2;
97557
+ const termHeight = rows;
96616
97558
  const safeFocusedIdx = activeCount > 0 ? Math.min(focusedIdx, activeCount - 1) : 0;
97559
+ const steeringFocusedRef = import_react61.useRef(false);
97560
+ const steeringBufferRef = import_react61.useRef("");
97561
+ const steeringCursorRef = import_react61.useRef(0);
97562
+ const steeringFocusedInitRef = import_react61.useRef(false);
96617
97563
  use_input_default((input, key) => {
97564
+ if (steeringFocusedRef.current)
97565
+ return;
97566
+ if (key.ctrl && key.meta && (input === "t" || input === "T")) {
97567
+ if (activeCount > 0)
97568
+ setShowAllSubtasks((v) => !v);
97569
+ return;
97570
+ }
97571
+ if (key.ctrl && (input === "t" || input === "T")) {
97572
+ if (activeCount > 0)
97573
+ setShowPendingTasks((v) => !v);
97574
+ return;
97575
+ }
96618
97576
  if (activeCount === 0)
96619
97577
  return;
96620
97578
  if (key.tab || key.rightArrow) {
@@ -96626,57 +97584,80 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96626
97584
  if (!isNaN(n) && n >= 1 && n <= activeCount)
96627
97585
  setFocusedIdx(n - 1);
96628
97586
  }
96629
- }, { isActive: isRawModeSupported && activeCount > 1 });
97587
+ }, { isActive: isRawModeSupported && activeCount > 0 });
97588
+ const focusedWorker = coordRef.current?.activeWorkers[safeFocusedIdx];
97589
+ const steeringActive = isRawModeSupported && activeCount > 0 && focusedWorker !== undefined;
96630
97590
  const nonFocusedCount = Math.max(0, activeCount - 1);
96631
97591
  const tasksBoxLines = activeCount > 1 ? 5 : 0;
96632
- const visibleLogLines = Math.min(logs.length, MAX_LOG_VIEWPORT_LINES);
96633
- const logsBoxLines = logs.length > 0 ? visibleLogLines + 2 : 0;
96634
- const FIXED_OVERHEAD = logsBoxLines + 5 + 6 + tasksBoxLines + 8 + nonFocusedCount * 4;
97592
+ const steeringBoxLines = steeringActive ? 3 : 0;
97593
+ const FIXED_OVERHEAD = 5 + 7 + tasksBoxLines + 8 + steeringBoxLines + nonFocusedCount * 4;
96635
97594
  const focusedTailLines = Math.max(3, termHeight - FIXED_OVERHEAD);
96636
97595
  const compactTailLines = displayTailLines(activeCount);
96637
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97596
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96638
97597
  flexDirection: "column",
96639
97598
  children: [
96640
- logs.length > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
96641
- label: "LOGS",
96642
- borderColor: "gray",
96643
- flexDirection: "column",
96644
- paddingX: 1,
96645
- width: termWidth,
96646
- children: visibleLogWindow(logs).map((line) => line.color ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97599
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Static, {
97600
+ items: logs,
97601
+ children: (line) => line.color ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96647
97602
  color: line.color,
96648
97603
  children: line.text
96649
- }, line.id, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97604
+ }, line.id, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96650
97605
  children: line.text
96651
- }, line.id, false, undefined, this))
97606
+ }, line.id, false, undefined, this)
96652
97607
  }, undefined, false, undefined, this),
96653
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97608
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96654
97609
  flexDirection: "column",
96655
97610
  marginTop: 0,
96656
97611
  children: [
96657
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
97612
+ (() => {
97613
+ const pause = coordRef.current?.getPause?.() ?? null;
97614
+ if (!pause)
97615
+ return null;
97616
+ const seconds = Math.floor((Date.now() - pause.since) / 1000);
97617
+ const duration3 = seconds < 60 ? `${seconds}s` : seconds < 3600 ? `${Math.floor(seconds / 60)}m` : `${Math.floor(seconds / 3600)}h${Math.floor(seconds % 3600 / 60)}m`;
97618
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97619
+ borderStyle: "round",
97620
+ borderColor: "red",
97621
+ paddingX: 1,
97622
+ width: termWidth,
97623
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97624
+ color: "red",
97625
+ bold: true,
97626
+ children: [
97627
+ "\u26D4 BASELINE BROKEN ",
97628
+ pause.issueIdentifier,
97629
+ " \xB7 ",
97630
+ duration3,
97631
+ " \xB7 `",
97632
+ pause.command,
97633
+ "`"
97634
+ ]
97635
+ }, undefined, true, undefined, this)
97636
+ }, undefined, false, undefined, this);
97637
+ })(),
97638
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
96658
97639
  label: "\u25C8 RALPH AGENT",
96659
97640
  borderColor: "blue",
96660
97641
  width: termWidth,
96661
97642
  paddingX: 1,
96662
97643
  flexDirection: "column",
96663
97644
  children: [
96664
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97645
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96665
97646
  children: [
96666
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97647
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96667
97648
  dimColor: true,
96668
97649
  children: [
96669
97650
  "v",
96670
97651
  VERSION
96671
97652
  ]
96672
97653
  }, undefined, true, undefined, this),
96673
- cfg && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97654
+ cfg && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96674
97655
  children: [
96675
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97656
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96676
97657
  dimColor: true,
96677
97658
  children: " \u2502 "
96678
97659
  }, undefined, false, undefined, this),
96679
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97660
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96680
97661
  color: "cyan",
96681
97662
  bold: true,
96682
97663
  children: [
@@ -96685,14 +97666,14 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96685
97666
  cfg.model
96686
97667
  ]
96687
97668
  }, undefined, true, undefined, this),
96688
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97669
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96689
97670
  dimColor: true,
96690
97671
  children: [
96691
97672
  " \u2502 \xD7",
96692
97673
  cfg.concurrency
96693
97674
  ]
96694
97675
  }, undefined, true, undefined, this),
96695
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97676
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96696
97677
  dimColor: true,
96697
97678
  children: [
96698
97679
  " \u2502 poll ",
@@ -96700,36 +97681,36 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96700
97681
  "s"
96701
97682
  ]
96702
97683
  }, undefined, true, undefined, this),
96703
- cfg.maxIterationsPerTask > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97684
+ cfg.maxIterationsPerTask > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96704
97685
  color: "yellow",
96705
97686
  children: [
96706
97687
  " \u2502 iter \u2264",
96707
97688
  cfg.maxIterationsPerTask
96708
97689
  ]
96709
97690
  }, undefined, true, undefined, this),
96710
- cfg.maxCostUsdPerTask > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97691
+ cfg.maxCostUsdPerTask > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96711
97692
  color: "yellow",
96712
97693
  children: [
96713
97694
  " \u2502 cost \u2264$",
96714
97695
  cfg.maxCostUsdPerTask
96715
97696
  ]
96716
97697
  }, undefined, true, undefined, this),
96717
- args.maxTickets > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97698
+ args.maxTickets > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96718
97699
  color: "yellow",
96719
97700
  children: [
96720
97701
  " \u2502 tickets \u2264",
96721
97702
  args.maxTickets
96722
97703
  ]
96723
97704
  }, undefined, true, undefined, this),
96724
- cfg.createPrOnSuccess && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97705
+ cfg.createPrOnSuccess && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96725
97706
  color: "green",
96726
97707
  children: " \u25CF PR"
96727
97708
  }, undefined, false, undefined, this),
96728
- cfg.fixCiOnFailure && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97709
+ cfg.fixCiOnFailure && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96729
97710
  color: "green",
96730
97711
  children: " \u25CF fixCI"
96731
97712
  }, undefined, false, undefined, this),
96732
- cfg.useWorktree && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97713
+ cfg.useWorktree && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96733
97714
  color: "green",
96734
97715
  children: " \u25CF worktree"
96735
97716
  }, undefined, false, undefined, this)
@@ -96749,7 +97730,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96749
97730
  lines.push(remaining.slice(0, budget));
96750
97731
  remaining = remaining.slice(budget);
96751
97732
  }
96752
- return lines.map((segment, i) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97733
+ return lines.map((segment, i) => /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96753
97734
  dimColor: true,
96754
97735
  children: [
96755
97736
  i === 0 ? prefix : indent,
@@ -96759,157 +97740,151 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96759
97740
  })()
96760
97741
  ]
96761
97742
  }, undefined, true, undefined, this),
96762
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97743
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96763
97744
  flexDirection: "row",
96764
97745
  gap: 1,
96765
97746
  marginTop: 0,
96766
97747
  width: termWidth,
96767
97748
  children: [
96768
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
97749
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
96769
97750
  label: "POLL STATUS",
96770
97751
  borderColor: "gray",
96771
- width: termWidth - 15,
97752
+ width: termWidth - 17,
96772
97753
  paddingX: 1,
96773
97754
  flexDirection: "column",
96774
97755
  children: [
96775
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97756
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96776
97757
  gap: 2,
96777
97758
  children: [
96778
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97759
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96779
97760
  color: "gray",
96780
97761
  children: spinnerFrame
96781
97762
  }, undefined, false, undefined, this),
96782
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97763
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96783
97764
  children: pollStatus.state === "polling" ? "Polling Linear\u2026" : pollStatus.lastAt !== null ? "Idle" : "Starting\u2026"
96784
97765
  }, undefined, false, undefined, this),
96785
- pollStatus.lastAt !== null && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
97766
+ pollStatus.lastAt !== null && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
97767
+ children: pollStatus.lastBuckets && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
97768
+ children: [
97769
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97770
+ dimColor: true,
97771
+ children: "\u2502"
97772
+ }, undefined, false, undefined, this),
97773
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97774
+ dimColor: true,
97775
+ children: "todo"
97776
+ }, undefined, false, undefined, this),
97777
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97778
+ color: "white",
97779
+ children: pollStatus.lastBuckets.todo
97780
+ }, undefined, false, undefined, this),
97781
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97782
+ dimColor: true,
97783
+ children: "\xB7"
97784
+ }, undefined, false, undefined, this),
97785
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97786
+ dimColor: true,
97787
+ children: "resume"
97788
+ }, undefined, false, undefined, this),
97789
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97790
+ color: pollStatus.lastBuckets.inProgress > 0 ? "cyan" : "white",
97791
+ children: pollStatus.lastBuckets.inProgress
97792
+ }, undefined, false, undefined, this),
97793
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97794
+ dimColor: true,
97795
+ children: "\xB7"
97796
+ }, undefined, false, undefined, this),
97797
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97798
+ dimColor: true,
97799
+ children: "conflict"
97800
+ }, undefined, false, undefined, this),
97801
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97802
+ color: pollStatus.lastBuckets.conflicted > 0 ? "red" : "white",
97803
+ children: pollStatus.lastBuckets.conflicted
97804
+ }, undefined, false, undefined, this),
97805
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97806
+ dimColor: true,
97807
+ children: "\xB7"
97808
+ }, undefined, false, undefined, this),
97809
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97810
+ dimColor: true,
97811
+ children: "review"
97812
+ }, undefined, false, undefined, this),
97813
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97814
+ color: pollStatus.lastBuckets.review > 0 ? "yellow" : "white",
97815
+ children: pollStatus.lastBuckets.review
97816
+ }, undefined, false, undefined, this),
97817
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97818
+ dimColor: true,
97819
+ children: "\xB7"
97820
+ }, undefined, false, undefined, this),
97821
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97822
+ dimColor: true,
97823
+ children: "mentions"
97824
+ }, undefined, false, undefined, this),
97825
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97826
+ color: pollStatus.lastBuckets.mentions > 0 ? "magenta" : "white",
97827
+ children: pollStatus.lastBuckets.mentions
97828
+ }, undefined, false, undefined, this)
97829
+ ]
97830
+ }, undefined, true, undefined, this)
97831
+ }, undefined, false, undefined, this)
97832
+ ]
97833
+ }, undefined, true, undefined, this),
97834
+ pollStatus.lastAt !== null && pollStatus.lastPrStatus && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97835
+ gap: 2,
97836
+ children: [
97837
+ secsToNextPoll !== null ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97838
+ gap: 1,
96786
97839
  children: [
96787
- pollStatus.lastBuckets && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
96788
- children: [
96789
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96790
- dimColor: true,
96791
- children: "\u2502"
96792
- }, undefined, false, undefined, this),
96793
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96794
- dimColor: true,
96795
- children: "todo"
96796
- }, undefined, false, undefined, this),
96797
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96798
- color: "white",
96799
- children: pollStatus.lastBuckets.todo
96800
- }, undefined, false, undefined, this),
96801
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96802
- dimColor: true,
96803
- children: "\xB7"
96804
- }, undefined, false, undefined, this),
96805
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96806
- dimColor: true,
96807
- children: "res"
96808
- }, undefined, false, undefined, this),
96809
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96810
- color: pollStatus.lastBuckets.inProgress > 0 ? "cyan" : "white",
96811
- children: pollStatus.lastBuckets.inProgress
96812
- }, undefined, false, undefined, this),
96813
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96814
- dimColor: true,
96815
- children: "\xB7"
96816
- }, undefined, false, undefined, this),
96817
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96818
- dimColor: true,
96819
- children: "conf"
96820
- }, undefined, false, undefined, this),
96821
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96822
- color: pollStatus.lastBuckets.conflicted > 0 ? "red" : "white",
96823
- children: pollStatus.lastBuckets.conflicted
96824
- }, undefined, false, undefined, this),
96825
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96826
- dimColor: true,
96827
- children: "\xB7"
96828
- }, undefined, false, undefined, this),
96829
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96830
- dimColor: true,
96831
- children: "rev"
96832
- }, undefined, false, undefined, this),
96833
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96834
- color: pollStatus.lastBuckets.review > 0 ? "yellow" : "white",
96835
- children: pollStatus.lastBuckets.review
96836
- }, undefined, false, undefined, this),
96837
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96838
- dimColor: true,
96839
- children: "\xB7"
96840
- }, undefined, false, undefined, this),
96841
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96842
- dimColor: true,
96843
- children: "@"
96844
- }, undefined, false, undefined, this),
96845
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96846
- color: pollStatus.lastBuckets.mentions > 0 ? "magenta" : "white",
96847
- children: pollStatus.lastBuckets.mentions
96848
- }, undefined, false, undefined, this)
96849
- ]
96850
- }, undefined, true, undefined, this),
96851
- secsToNextPoll !== null && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
97840
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97841
+ dimColor: true,
97842
+ children: "\u21BA"
97843
+ }, undefined, false, undefined, this),
97844
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97845
+ color: "gray",
96852
97846
  children: [
96853
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96854
- dimColor: true,
96855
- children: "\u2502"
96856
- }, undefined, false, undefined, this),
96857
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96858
- dimColor: true,
96859
- children: "\u21BA"
96860
- }, undefined, false, undefined, this),
96861
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
96862
- color: "gray",
96863
- children: [
96864
- secsToNextPoll,
96865
- "s"
96866
- ]
96867
- }, undefined, true, undefined, this)
97847
+ secsToNextPoll,
97848
+ "s"
96868
97849
  ]
96869
97850
  }, undefined, true, undefined, this)
96870
97851
  ]
96871
- }, undefined, true, undefined, this)
96872
- ]
96873
- }, undefined, true, undefined, this),
96874
- pollStatus.lastAt !== null && pollStatus.lastPrStatus && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
96875
- gap: 2,
96876
- children: [
96877
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97852
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96878
97853
  children: " ".repeat(7)
96879
97854
  }, undefined, false, undefined, this),
96880
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97855
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96881
97856
  dimColor: true,
96882
97857
  children: "\u2502"
96883
97858
  }, undefined, false, undefined, this),
96884
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97859
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96885
97860
  dimColor: true,
96886
97861
  children: "mergeable"
96887
97862
  }, undefined, false, undefined, this),
96888
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97863
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96889
97864
  color: pollStatus.lastPrStatus.mergeable > 0 ? "green" : "white",
96890
97865
  children: pollStatus.lastPrStatus.mergeable
96891
97866
  }, undefined, false, undefined, this),
96892
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97867
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96893
97868
  dimColor: true,
96894
97869
  children: "\xB7"
96895
97870
  }, undefined, false, undefined, this),
96896
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97871
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96897
97872
  dimColor: true,
96898
97873
  children: "conflicted"
96899
97874
  }, undefined, false, undefined, this),
96900
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97875
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96901
97876
  color: pollStatus.lastPrStatus.conflicted > 0 ? "red" : "white",
96902
97877
  children: pollStatus.lastPrStatus.conflicted
96903
97878
  }, undefined, false, undefined, this),
96904
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97879
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96905
97880
  dimColor: true,
96906
97881
  children: "\xB7"
96907
97882
  }, undefined, false, undefined, this),
96908
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97883
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96909
97884
  dimColor: true,
96910
97885
  children: "ci-failed"
96911
97886
  }, undefined, false, undefined, this),
96912
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97887
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96913
97888
  color: pollStatus.lastPrStatus.ciFailed > 0 ? "red" : "white",
96914
97889
  children: pollStatus.lastPrStatus.ciFailed
96915
97890
  }, undefined, false, undefined, this)
@@ -96917,35 +97892,35 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96917
97892
  }, undefined, true, undefined, this)
96918
97893
  ]
96919
97894
  }, undefined, true, undefined, this),
96920
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
97895
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
96921
97896
  label: "WORKERS",
96922
97897
  borderColor: "gray",
96923
- width: 14,
97898
+ width: 16,
96924
97899
  paddingX: 1,
96925
97900
  flexDirection: "column",
96926
97901
  children: [
96927
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97902
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96928
97903
  gap: 1,
96929
97904
  children: [
96930
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97905
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96931
97906
  dimColor: true,
96932
- children: "act"
97907
+ children: "active"
96933
97908
  }, undefined, false, undefined, this),
96934
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97909
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96935
97910
  color: activeCount > 0 ? "cyan" : "gray",
96936
97911
  bold: true,
96937
97912
  children: activeCount
96938
97913
  }, undefined, false, undefined, this)
96939
97914
  ]
96940
97915
  }, undefined, true, undefined, this),
96941
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97916
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96942
97917
  gap: 1,
96943
97918
  children: [
96944
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97919
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96945
97920
  dimColor: true,
96946
97921
  children: "queue"
96947
97922
  }, undefined, false, undefined, this),
96948
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97923
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96949
97924
  color: (coord?.queuedCount ?? 0) > 0 ? "yellow" : "gray",
96950
97925
  bold: true,
96951
97926
  children: coord?.queuedCount ?? 0
@@ -96956,13 +97931,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96956
97931
  }, undefined, true, undefined, this)
96957
97932
  ]
96958
97933
  }, undefined, true, undefined, this),
96959
- activeCount > 1 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
97934
+ activeCount > 1 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
96960
97935
  label: `TASKS${activeCount > 1 ? " Tab/\u2190 \u2192 \xB7 1-9" : ""}`,
96961
97936
  borderColor: "gray",
96962
97937
  width: termWidth,
96963
97938
  paddingX: 1,
96964
97939
  flexDirection: "column",
96965
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97940
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96966
97941
  gap: 3,
96967
97942
  flexWrap: "wrap",
96968
97943
  children: coord?.activeWorkers.map((w, idx) => {
@@ -96970,10 +97945,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96970
97945
  const phase = meta3?.phase ?? "working";
96971
97946
  const pBadge = priorityBadge(w.issue.priority);
96972
97947
  const isFocused = idx === safeFocusedIdx;
96973
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97948
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96974
97949
  gap: 1,
96975
97950
  children: [
96976
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97951
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96977
97952
  color: isFocused ? "white" : "gray",
96978
97953
  bold: isFocused,
96979
97954
  children: [
@@ -96982,7 +97957,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96982
97957
  "]"
96983
97958
  ]
96984
97959
  }, undefined, true, undefined, this),
96985
- pBadge.label && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97960
+ pBadge.label && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96986
97961
  color: pBadge.color,
96987
97962
  children: [
96988
97963
  pBadge.text,
@@ -96990,17 +97965,17 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96990
97965
  pBadge.label
96991
97966
  ]
96992
97967
  }, undefined, true, undefined, this),
96993
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
97968
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
96994
97969
  url: w.issue.url,
96995
97970
  label: w.issueIdentifier,
96996
97971
  color: isFocused ? "cyan" : "gray"
96997
97972
  }, undefined, false, undefined, this),
96998
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97973
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96999
97974
  color: phaseColor(phase),
97000
97975
  dimColor: !isFocused,
97001
97976
  children: phase
97002
97977
  }, undefined, false, undefined, this),
97003
- isFocused && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97978
+ isFocused && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97004
97979
  color: "white",
97005
97980
  children: "\u25C0"
97006
97981
  }, undefined, false, undefined, this)
@@ -97023,6 +97998,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97023
97998
  const currentTask = meta3?.currentTask ?? null;
97024
97999
  const taskProgress = meta3?.taskProgress ?? null;
97025
98000
  const openspecPhase = meta3?.openspecPhase ?? null;
98001
+ const subtasks = meta3?.subtasks ?? [];
97026
98002
  const pBadge = priorityBadge(w.issue.priority);
97027
98003
  const mBadge = modeBadge(w.mode);
97028
98004
  const pColor = phaseColor(phase);
@@ -97030,33 +98006,33 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97030
98006
  const visibleTailLines = isFocused ? focusedTailLines : compactTailLines;
97031
98007
  if (!isFocused && activeCount > 1) {
97032
98008
  const cardLabelWidth2 = (prUrl ? prLabel(prUrl).length + 3 : 0) + w.issueIdentifier.length + 2;
97033
- const cardLabelNode2 = /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
98009
+ const cardLabelNode2 = /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
97034
98010
  children: [
97035
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98011
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97036
98012
  color: "gray",
97037
98013
  children: " "
97038
98014
  }, undefined, false, undefined, this),
97039
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98015
+ prUrl && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
97040
98016
  url: prUrl,
97041
98017
  label: prLabel(prUrl),
97042
98018
  color: "green"
97043
98019
  }, undefined, false, undefined, this),
97044
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98020
+ prUrl && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97045
98021
  color: "gray",
97046
98022
  children: " \xB7 "
97047
98023
  }, undefined, false, undefined, this),
97048
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98024
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
97049
98025
  url: w.issue.url,
97050
98026
  label: w.issueIdentifier,
97051
98027
  color: "cyan"
97052
98028
  }, undefined, false, undefined, this),
97053
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98029
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97054
98030
  color: "gray",
97055
98031
  children: " "
97056
98032
  }, undefined, false, undefined, this)
97057
98033
  ]
97058
98034
  }, undefined, true, undefined, this);
97059
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
98035
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
97060
98036
  labelNode: cardLabelNode2,
97061
98037
  labelVisualWidth: cardLabelWidth2,
97062
98038
  borderColor: "gray",
@@ -97064,7 +98040,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97064
98040
  gap: 2,
97065
98041
  width: termWidth,
97066
98042
  children: [
97067
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98043
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97068
98044
  dimColor: true,
97069
98045
  children: [
97070
98046
  "[",
@@ -97072,54 +98048,54 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97072
98048
  "]"
97073
98049
  ]
97074
98050
  }, undefined, true, undefined, this),
97075
- pBadge.label && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98051
+ pBadge.label && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97076
98052
  color: pBadge.color,
97077
98053
  children: pBadge.text
97078
98054
  }, undefined, false, undefined, this),
97079
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98055
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97080
98056
  color: "gray",
97081
98057
  bold: true,
97082
98058
  children: w.issueIdentifier
97083
98059
  }, undefined, false, undefined, this),
97084
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98060
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97085
98061
  dimColor: true,
97086
98062
  children: trunc(w.issue.title, 40)
97087
98063
  }, undefined, false, undefined, this),
97088
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98064
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97089
98065
  dimColor: true,
97090
98066
  children: "\u2502"
97091
98067
  }, undefined, false, undefined, this),
97092
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98068
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97093
98069
  color: pColor,
97094
98070
  dimColor: true,
97095
98071
  children: phase
97096
98072
  }, undefined, false, undefined, this),
97097
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98073
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97098
98074
  dimColor: true,
97099
98075
  children: "\u2502"
97100
98076
  }, undefined, false, undefined, this),
97101
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98077
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97102
98078
  dimColor: true,
97103
98079
  children: elapsed
97104
98080
  }, undefined, false, undefined, this),
97105
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98081
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97106
98082
  dimColor: true,
97107
98083
  children: "\xB7"
97108
98084
  }, undefined, false, undefined, this),
97109
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98085
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97110
98086
  dimColor: true,
97111
98087
  children: [
97112
98088
  "iter ",
97113
98089
  iter
97114
98090
  ]
97115
98091
  }, undefined, true, undefined, this),
97116
- currentTask && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
98092
+ currentTask && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
97117
98093
  children: [
97118
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98094
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97119
98095
  dimColor: true,
97120
98096
  children: "\u2502"
97121
98097
  }, undefined, false, undefined, this),
97122
- openspecPhase && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98098
+ openspecPhase && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97123
98099
  color: openspecPhaseColor(openspecPhase),
97124
98100
  children: [
97125
98101
  "[",
@@ -97127,7 +98103,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97127
98103
  "]"
97128
98104
  ]
97129
98105
  }, undefined, true, undefined, this),
97130
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98106
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97131
98107
  dimColor: true,
97132
98108
  children: [
97133
98109
  "\u25B6 ",
@@ -97140,33 +98116,33 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97140
98116
  }, w.changeName, true, undefined, this);
97141
98117
  }
97142
98118
  const cardLabelWidth = (prUrl ? prLabel(prUrl).length + 3 : 0) + w.issueIdentifier.length + 2;
97143
- const cardLabelNode = /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
98119
+ const cardLabelNode = /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
97144
98120
  children: [
97145
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98121
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97146
98122
  color: bColor,
97147
98123
  children: " "
97148
98124
  }, undefined, false, undefined, this),
97149
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98125
+ prUrl && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
97150
98126
  url: prUrl,
97151
98127
  label: prLabel(prUrl),
97152
98128
  color: "green"
97153
98129
  }, undefined, false, undefined, this),
97154
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98130
+ prUrl && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97155
98131
  color: bColor,
97156
98132
  children: " \xB7 "
97157
98133
  }, undefined, false, undefined, this),
97158
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98134
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
97159
98135
  url: w.issue.url,
97160
98136
  label: w.issueIdentifier,
97161
98137
  color: "cyan"
97162
98138
  }, undefined, false, undefined, this),
97163
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98139
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97164
98140
  color: bColor,
97165
98141
  children: " "
97166
98142
  }, undefined, false, undefined, this)
97167
98143
  ]
97168
98144
  }, undefined, true, undefined, this);
97169
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
98145
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
97170
98146
  labelNode: cardLabelNode,
97171
98147
  labelVisualWidth: cardLabelWidth,
97172
98148
  borderColor: bColor,
@@ -97174,18 +98150,18 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97174
98150
  paddingX: 1,
97175
98151
  width: termWidth,
97176
98152
  children: [
97177
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98153
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97178
98154
  gap: 2,
97179
98155
  children: [
97180
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98156
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97181
98157
  children: spinnerFrame
97182
98158
  }, undefined, false, undefined, this),
97183
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98159
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97184
98160
  color: "white",
97185
98161
  bold: true,
97186
98162
  children: trunc(w.issue.title, Math.max(20, termWidth - 55))
97187
98163
  }, undefined, false, undefined, this),
97188
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98164
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97189
98165
  color: mBadge.color,
97190
98166
  bold: true,
97191
98167
  children: [
@@ -97194,7 +98170,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97194
98170
  "]"
97195
98171
  ]
97196
98172
  }, undefined, true, undefined, this),
97197
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98173
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97198
98174
  color: pColor,
97199
98175
  bold: true,
97200
98176
  children: [
@@ -97202,79 +98178,39 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97202
98178
  phaseDetail ? ` (${phaseDetail})` : ""
97203
98179
  ]
97204
98180
  }, undefined, true, undefined, this),
97205
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98181
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97206
98182
  dimColor: true,
97207
98183
  children: "\u2502"
97208
98184
  }, undefined, false, undefined, this),
97209
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98185
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97210
98186
  color: "white",
97211
98187
  children: elapsed
97212
98188
  }, undefined, false, undefined, this),
97213
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98189
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97214
98190
  dimColor: true,
97215
98191
  children: "\u2502"
97216
98192
  }, undefined, false, undefined, this),
97217
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98193
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97218
98194
  dimColor: true,
97219
98195
  children: "\u21BA"
97220
98196
  }, undefined, false, undefined, this),
97221
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98197
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97222
98198
  color: "white",
97223
98199
  bold: true,
97224
98200
  children: iter
97225
98201
  }, undefined, false, undefined, this)
97226
98202
  ]
97227
98203
  }, undefined, true, undefined, this),
97228
- taskProgress && (() => {
97229
- const bar = calcProgressBar(taskProgress.checked, taskProgress.total, termWidth - 4);
97230
- if (!bar)
97231
- return null;
97232
- const { countStr, filledLeft, leftSlot, filledRight, rightSlot } = bar;
97233
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97234
- marginTop: 0,
97235
- children: [
97236
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97237
- dimColor: true,
97238
- children: "["
97239
- }, undefined, false, undefined, this),
97240
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97241
- color: "green",
97242
- children: "\u2588".repeat(filledLeft)
97243
- }, undefined, false, undefined, this),
97244
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97245
- dimColor: true,
97246
- children: "\u2591".repeat(leftSlot - filledLeft)
97247
- }, undefined, false, undefined, this),
97248
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97249
- color: "white",
97250
- bold: true,
97251
- children: countStr
97252
- }, undefined, false, undefined, this),
97253
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97254
- color: "green",
97255
- children: "\u2588".repeat(filledRight)
97256
- }, undefined, false, undefined, this),
97257
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97258
- dimColor: true,
97259
- children: "\u2591".repeat(rightSlot - filledRight)
97260
- }, undefined, false, undefined, this),
97261
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97262
- dimColor: true,
97263
- children: "]"
97264
- }, undefined, false, undefined, this)
97265
- ]
97266
- }, undefined, true, undefined, this);
97267
- })(),
97268
- currentTask && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98204
+ currentTask && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97269
98205
  gap: 1,
97270
98206
  marginTop: 0,
97271
98207
  children: [
97272
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98208
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97273
98209
  color: "yellow",
97274
98210
  bold: true,
97275
98211
  children: "\u25B6 TASK"
97276
98212
  }, undefined, false, undefined, this),
97277
- openspecPhase && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98213
+ openspecPhase && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97278
98214
  color: openspecPhaseColor(openspecPhase),
97279
98215
  bold: true,
97280
98216
  children: [
@@ -97283,42 +98219,42 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97283
98219
  "]"
97284
98220
  ]
97285
98221
  }, undefined, true, undefined, this),
97286
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98222
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97287
98223
  color: "white",
97288
98224
  children: trunc(currentTask, termWidth - 14 - (openspecPhase ? openspecPhase.length + 11 : 0))
97289
98225
  }, undefined, false, undefined, this)
97290
98226
  ]
97291
98227
  }, undefined, true, undefined, this),
97292
- cmd && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98228
+ cmd && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97293
98229
  gap: 1,
97294
98230
  marginTop: 0,
97295
98231
  children: [
97296
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98232
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97297
98233
  color: "yellow",
97298
98234
  children: "\u23F5 CMD"
97299
98235
  }, undefined, false, undefined, this),
97300
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98236
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97301
98237
  color: "yellow",
97302
98238
  children: fmtCmd(cmd.argv)
97303
98239
  }, undefined, false, undefined, this),
97304
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98240
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97305
98241
  dimColor: true,
97306
98242
  children: cmdElapsed
97307
98243
  }, undefined, false, undefined, this)
97308
98244
  ]
97309
98245
  }, undefined, true, undefined, this),
97310
- tail2.length > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98246
+ tail2.length > 0 && !(showPendingTasks && showAllSubtasks) && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97311
98247
  flexDirection: "column",
97312
98248
  marginTop: 0,
97313
98249
  children: [
97314
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98250
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97315
98251
  dimColor: true,
97316
98252
  children: [
97317
98253
  "\u2500 OUTPUT ",
97318
98254
  "\u2500".repeat(Math.max(4, termWidth - 14))
97319
98255
  ]
97320
98256
  }, undefined, true, undefined, this),
97321
- tail2.slice(-visibleTailLines).map((line, i) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98257
+ tail2.slice(-visibleTailLines).map((line, i) => /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97322
98258
  dimColor: true,
97323
98259
  children: [
97324
98260
  "\u2502 ",
@@ -97326,24 +98262,185 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97326
98262
  ]
97327
98263
  }, `${w.changeName}-tail-${i}`, true, undefined, this))
97328
98264
  ]
97329
- }, undefined, true, undefined, this)
98265
+ }, undefined, true, undefined, this),
98266
+ shouldShowPhasePipeline(openspecPhase) && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98267
+ marginTop: 0,
98268
+ children: phasePipeline(openspecPhase).map((seg, i, arr) => {
98269
+ const glyph = seg.status === "done" ? "\u2713" : seg.status === "current" ? "\u25CF" : "\u25CB";
98270
+ const node2 = seg.status === "done" ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98271
+ color: "green",
98272
+ children: [
98273
+ glyph,
98274
+ " ",
98275
+ seg.label
98276
+ ]
98277
+ }, undefined, true, undefined, this) : seg.status === "current" ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98278
+ color: openspecPhaseColor(seg.phase),
98279
+ bold: true,
98280
+ children: [
98281
+ glyph,
98282
+ " ",
98283
+ seg.label
98284
+ ]
98285
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98286
+ dimColor: true,
98287
+ children: [
98288
+ glyph,
98289
+ " ",
98290
+ seg.label
98291
+ ]
98292
+ }, undefined, true, undefined, this);
98293
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98294
+ children: [
98295
+ node2,
98296
+ i < arr.length - 1 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98297
+ dimColor: true,
98298
+ children: " \u2500 "
98299
+ }, undefined, false, undefined, this)
98300
+ ]
98301
+ }, seg.phase, true, undefined, this);
98302
+ })
98303
+ }, undefined, false, undefined, this),
98304
+ shouldShowSubtasksPanel(openspecPhase, showPendingTasks, subtasks.length > 0) && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98305
+ flexDirection: "column",
98306
+ marginTop: 0,
98307
+ children: [
98308
+ (() => {
98309
+ const header = `\u2500 SUBTASKS (${subtasks.length}) CTRL+T to close `;
98310
+ const pad2 = "\u2500".repeat(Math.max(4, termWidth - header.length - 4));
98311
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98312
+ dimColor: true,
98313
+ children: `${header}${pad2}`
98314
+ }, undefined, false, undefined, this);
98315
+ })(),
98316
+ (showAllSubtasks ? subtasks : orderSubtasksForCappedDisplay(subtasks).slice(0, MAX_PENDING_DISPLAY)).map((s, i, arr) => {
98317
+ const ord = `${i + 1}.`.padStart(`${arr.length}.`.length, " ");
98318
+ const reserved = ord.length + 5;
98319
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98320
+ children: [
98321
+ s.done ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98322
+ dimColor: true,
98323
+ children: `${ord} [x] `
98324
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98325
+ children: `${ord} [ ] `
98326
+ }, undefined, false, undefined, this),
98327
+ s.done ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98328
+ dimColor: true,
98329
+ children: trunc(s.text, termWidth - reserved)
98330
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98331
+ children: trunc(s.text, termWidth - reserved)
98332
+ }, undefined, false, undefined, this)
98333
+ ]
98334
+ }, `${w.changeName}-subtask-${i}`, true, undefined, this);
98335
+ }),
98336
+ !showAllSubtasks && subtasks.length > MAX_PENDING_DISPLAY && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98337
+ dimColor: true,
98338
+ children: ` \u2026 +${subtasks.length - MAX_PENDING_DISPLAY} more (CTRL+ALT+T to expand)`
98339
+ }, undefined, false, undefined, this)
98340
+ ]
98341
+ }, undefined, true, undefined, this),
98342
+ steeringActive && idx === safeFocusedIdx && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98343
+ marginTop: 0,
98344
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(SteeringField, {
98345
+ active: steeringActive,
98346
+ width: termWidth - 2,
98347
+ initialBuffer: steeringBufferRef.current,
98348
+ initialCursor: steeringCursorRef.current,
98349
+ initialFocused: steeringFocusedInitRef.current,
98350
+ onFocusChange: (f2) => {
98351
+ steeringFocusedRef.current = f2;
98352
+ steeringFocusedInitRef.current = f2;
98353
+ },
98354
+ onStateChange: (s) => {
98355
+ steeringBufferRef.current = s.buffer;
98356
+ steeringCursorRef.current = s.cursor;
98357
+ },
98358
+ onSubmit: async (message) => {
98359
+ try {
98360
+ await appendSteering(join22(tasksDir, w.changeName), message);
98361
+ } catch (err) {
98362
+ appendLog(`! steering append failed for ${w.changeName}: ${err.message}`, "red");
98363
+ throw err;
98364
+ }
98365
+ const restarted = await coordRef.current?.restartWorker(w.changeName);
98366
+ if (restarted) {
98367
+ appendLog(` ${w.changeName}: steering applied, restarting worker`, "cyan");
98368
+ } else {
98369
+ appendLog(` ${w.changeName}: steering queued \u2014 will apply on next iteration`, "gray");
98370
+ }
98371
+ }
98372
+ }, undefined, false, undefined, this)
98373
+ }, undefined, false, undefined, this),
98374
+ shouldShowProgressBar(openspecPhase, showPendingTasks, taskProgress !== null) && taskProgress && (() => {
98375
+ const hint = " CTRL+T to open";
98376
+ const bar = calcProgressBar(taskProgress.checked, taskProgress.total, termWidth - 4 - hint.length);
98377
+ if (!bar)
98378
+ return null;
98379
+ const { countStr, filledLeft, leftSlot, filledRight, rightSlot } = bar;
98380
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98381
+ marginTop: 0,
98382
+ children: [
98383
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98384
+ dimColor: true,
98385
+ children: "["
98386
+ }, undefined, false, undefined, this),
98387
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98388
+ color: "green",
98389
+ children: "\u2588".repeat(filledLeft)
98390
+ }, undefined, false, undefined, this),
98391
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98392
+ dimColor: true,
98393
+ children: "\u2591".repeat(leftSlot - filledLeft)
98394
+ }, undefined, false, undefined, this),
98395
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98396
+ color: "white",
98397
+ bold: true,
98398
+ children: countStr
98399
+ }, undefined, false, undefined, this),
98400
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98401
+ color: "green",
98402
+ children: "\u2588".repeat(filledRight)
98403
+ }, undefined, false, undefined, this),
98404
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98405
+ dimColor: true,
98406
+ children: "\u2591".repeat(rightSlot - filledRight)
98407
+ }, undefined, false, undefined, this),
98408
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98409
+ dimColor: true,
98410
+ children: "]"
98411
+ }, undefined, false, undefined, this),
98412
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98413
+ dimColor: true,
98414
+ children: hint
98415
+ }, undefined, false, undefined, this)
98416
+ ]
98417
+ }, undefined, true, undefined, this);
98418
+ })()
97330
98419
  ]
97331
98420
  }, w.changeName, true, undefined, this);
97332
98421
  })
97333
98422
  ]
97334
98423
  }, undefined, true, undefined, this)
97335
98424
  ]
97336
- }, undefined, true, undefined, this);
98425
+ }, resizeKey, true, undefined, this);
97337
98426
  }
97338
- var import_react58, jsx_dev_runtime9, lineCounter = 0, TAIL_BUFFER_SIZE = 30, CMD_DISPLAY_MAX = 80, MAX_LOG_VIEWPORT_LINES = 15, SPINNER_FRAMES, HYPERLINKS_SUPPORTED, ANSI_STRIP_RE, BOX_ONLY_RE, STATUS_BAR_LINE_RE, ITER_HEADER_LINE_RE, SESSION_START;
98427
+ var import_react61, jsx_dev_runtime10, lineCounter = 0, TAIL_BUFFER_SIZE = 30, CMD_DISPLAY_MAX = 80, MAX_PENDING_DISPLAY = 15, SPINNER_FRAMES, HYPERLINKS_SUPPORTED, ANSI_STRIP_RE, BOX_ONLY_RE, STATUS_BAR_LINE_RE, ITER_HEADER_LINE_RE, SESSION_START;
97339
98428
  var init_AgentMode = __esm(async () => {
97340
98429
  init_cli2();
97341
98430
  init_config();
97342
98431
  init_wire();
98432
+ init_tasks_md();
98433
+ init_phase();
97343
98434
  init_log();
97344
- await init_build2();
97345
- import_react58 = __toESM(require_react(), 1);
97346
- jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
98435
+ init_useTerminalSize2();
98436
+ init_loop();
98437
+ init_context();
98438
+ await __promiseAll([
98439
+ init_build2(),
98440
+ init_SteeringField()
98441
+ ]);
98442
+ import_react61 = __toESM(require_react(), 1);
98443
+ jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
97347
98444
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
97348
98445
  HYPERLINKS_SUPPORTED = !process.env["TMUX"];
97349
98446
  ANSI_STRIP_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
@@ -97353,6 +98450,112 @@ var init_AgentMode = __esm(async () => {
97353
98450
  SESSION_START = new Date().toISOString();
97354
98451
  });
97355
98452
 
98453
+ // apps/agent/src/pr-status.ts
98454
+ function bucketChecks(rollup, prState) {
98455
+ if (rollup === null || rollup === undefined || rollup.length === 0) {
98456
+ return prState === "MERGED" ? "pass" : "pending";
98457
+ }
98458
+ let anyPending = false;
98459
+ let anyFail = false;
98460
+ for (const c of rollup) {
98461
+ const status = (c.status ?? "").toUpperCase();
98462
+ const conclusion = (c.conclusion ?? "").toUpperCase();
98463
+ const state = (c.state ?? "").toUpperCase();
98464
+ if (status && status !== "COMPLETED") {
98465
+ anyPending = true;
98466
+ continue;
98467
+ }
98468
+ if (state === "PENDING" || state === "EXPECTED") {
98469
+ anyPending = true;
98470
+ continue;
98471
+ }
98472
+ const settled = conclusion || state;
98473
+ if (settled === "FAILURE" || settled === "TIMED_OUT" || settled === "CANCELLED" || settled === "ERROR") {
98474
+ anyFail = true;
98475
+ }
98476
+ }
98477
+ if (anyPending)
98478
+ return "pending";
98479
+ if (anyFail)
98480
+ return "fail";
98481
+ return "pass";
98482
+ }
98483
+ async function fetchPrStatus(url2, runner, cwd2) {
98484
+ let stdout;
98485
+ try {
98486
+ const out = await runner.run(["gh", "pr", "view", url2, "--json", PR_VIEW_FIELDS], cwd2);
98487
+ stdout = out.stdout;
98488
+ } catch (err) {
98489
+ const e = err;
98490
+ const msg = (e.stderr?.trim().split(`
98491
+ `)[0] ?? e.message ?? "gh failed").slice(0, 200);
98492
+ return { kind: "error", message: msg };
98493
+ }
98494
+ let raw;
98495
+ try {
98496
+ raw = JSON.parse(stdout || "{}");
98497
+ } catch (err) {
98498
+ return { kind: "error", message: `parse error: ${err.message}` };
98499
+ }
98500
+ const stateUpper = (raw.state ?? "").toUpperCase();
98501
+ const state = stateUpper === "OPEN" || stateUpper === "CLOSED" || stateUpper === "MERGED" ? stateUpper : "OPEN";
98502
+ const mergeableUpper = (raw.mergeable ?? "UNKNOWN").toUpperCase();
98503
+ const mergeable = mergeableUpper === "MERGEABLE" || mergeableUpper === "CONFLICTING" ? mergeableUpper : "UNKNOWN";
98504
+ return {
98505
+ kind: "ok",
98506
+ state,
98507
+ isDraft: Boolean(raw.isDraft),
98508
+ mergeable,
98509
+ ciBucket: bucketChecks(raw.statusCheckRollup, state),
98510
+ autoMergeEnabled: raw.autoMergeRequest !== null && raw.autoMergeRequest !== undefined,
98511
+ createdAt: raw.createdAt ?? ""
98512
+ };
98513
+ }
98514
+ var PR_VIEW_FIELDS = "state,isDraft,mergeable,statusCheckRollup,autoMergeRequest,createdAt";
98515
+
98516
+ // apps/agent/src/list-sort.ts
98517
+ function assignTier(status) {
98518
+ if (status === null || status.kind === "error")
98519
+ return 5;
98520
+ const conflict = status.mergeable === "CONFLICTING";
98521
+ const failing = status.ciBucket === "fail";
98522
+ if (conflict && status.autoMergeEnabled)
98523
+ return 1;
98524
+ if (failing && status.autoMergeEnabled)
98525
+ return 2;
98526
+ if (conflict)
98527
+ return 3;
98528
+ if (failing)
98529
+ return 4;
98530
+ return 5;
98531
+ }
98532
+ function createdAtOf(status) {
98533
+ if (status && status.kind === "ok")
98534
+ return status.createdAt;
98535
+ return "";
98536
+ }
98537
+ function sortRows(rows) {
98538
+ const cmp = chain((a, b) => assignTier(a.status) - assignTier(b.status), (a, b) => {
98539
+ const ia = a.issueCreatedAt;
98540
+ const ib = b.issueCreatedAt;
98541
+ if (ia === ib)
98542
+ return 0;
98543
+ if (ia === "")
98544
+ return 1;
98545
+ if (ib === "")
98546
+ return -1;
98547
+ return ia < ib ? -1 : 1;
98548
+ }, (a, b) => {
98549
+ const ca = createdAtOf(a.status);
98550
+ const cb = createdAtOf(b.status);
98551
+ if (ca === cb)
98552
+ return 0;
98553
+ return ca < cb ? -1 : 1;
98554
+ }, (a, b) => a.bucketOrder - b.bucketOrder, (a, b) => a.identifier.localeCompare(b.identifier));
98555
+ return [...rows].sort(cmp);
98556
+ }
98557
+ var init_list_sort = () => {};
98558
+
97356
98559
  // apps/agent/src/list.ts
97357
98560
  var exports_list = {};
97358
98561
  __export(exports_list, {
@@ -97484,40 +98687,100 @@ async function fetchBucketIssues(apiKey, bucket, team, assignee) {
97484
98687
  };
97485
98688
  return fetchOpenIssues(apiKey, spec);
97486
98689
  }
97487
- async function printBucket(apiKey, bucket, team, assignee) {
97488
- if (!bucket.indicator || bucket.indicator.filter.length === 0) {
97489
- return;
97490
- }
97491
- let issues = [];
97492
- try {
97493
- issues = await fetchBucketIssues(apiKey, bucket, team, assignee);
97494
- } catch (err) {
97495
- process.stdout.write(`
97496
- ${bucket.label}: error fetching from Linear \u2014 ${err instanceof Error ? err.message : String(err)}
98690
+ function formatPrStatusMarker(status) {
98691
+ if (status === null)
98692
+ return "(no PR)";
98693
+ if (status.kind === "error")
98694
+ return "?";
98695
+ if (status.state === "MERGED")
98696
+ return "merged";
98697
+ if (status.state === "CLOSED")
98698
+ return "closed";
98699
+ const parts = [];
98700
+ if (status.mergeable === "CONFLICTING")
98701
+ parts.push("\u2717conflict");
98702
+ if (status.ciBucket === "fail")
98703
+ parts.push("\u2717ci");
98704
+ if (status.ciBucket === "pending")
98705
+ parts.push("\u23F3ci");
98706
+ if (status.isDraft)
98707
+ parts.push("draft");
98708
+ if (status.autoMergeEnabled)
98709
+ parts.push("auto-merge");
98710
+ if (parts.length === 0)
98711
+ return "ok";
98712
+ return parts.join(" ");
98713
+ }
98714
+ async function fetchAndPrintLinear(apiKey, buckets, team, assignee, cwd2, runner) {
98715
+ const bucketResults = await Promise.all(buckets.map(async (bucket) => {
98716
+ if (!bucket.indicator || bucket.indicator.filter.length === 0) {
98717
+ return { bucket, issues: [], error: null };
98718
+ }
98719
+ try {
98720
+ const issues = await fetchBucketIssues(apiKey, bucket, team, assignee);
98721
+ return { bucket, issues, error: null };
98722
+ } catch (err) {
98723
+ return {
98724
+ bucket,
98725
+ issues: [],
98726
+ error: err instanceof Error ? err.message : String(err)
98727
+ };
98728
+ }
98729
+ }));
98730
+ for (const { bucket, error: error48 } of bucketResults) {
98731
+ if (error48) {
98732
+ process.stdout.write(`
98733
+ ${bucket.label}: error fetching from Linear \u2014 ${error48}
97497
98734
  `);
97498
- return;
98735
+ }
98736
+ }
98737
+ const seen = new Map;
98738
+ let order = 0;
98739
+ for (const { bucket, issues } of bucketResults) {
98740
+ for (const issue2 of issues) {
98741
+ if (seen.has(issue2.id))
98742
+ continue;
98743
+ seen.set(issue2.id, {
98744
+ issueId: issue2.id,
98745
+ identifier: issue2.identifier,
98746
+ status: null,
98747
+ bucketOrder: order++,
98748
+ issueCreatedAt: issue2.createdAt,
98749
+ bucketLabel: bucket.label,
98750
+ stateName: issue2.state.name,
98751
+ title: issue2.title.slice(0, 60),
98752
+ prUrl: null
98753
+ });
98754
+ }
97499
98755
  }
97500
- const filterStr = bucket.indicator.filter.map((m) => `${m.type}:${m.value}`).join(", ");
98756
+ const rows = [...seen.values()];
98757
+ await Promise.all(rows.map(async (row) => {
98758
+ try {
98759
+ const attachments = await fetchIssueAttachments(apiKey, row.issueId);
98760
+ row.prUrl = findPullRequestUrl(attachments);
98761
+ } catch {}
98762
+ }));
98763
+ await Promise.all(rows.map(async (row) => {
98764
+ if (!row.prUrl)
98765
+ return;
98766
+ row.status = await fetchPrStatus(row.prUrl, runner, cwd2);
98767
+ }));
98768
+ const sorted = sortRows(rows);
97501
98769
  process.stdout.write(`
97502
- ${bucket.label} [${filterStr}] \u2014 ${issues.length} issue(s)
98770
+ Linear tickets: ${sorted.length} issue(s)
97503
98771
  `);
97504
- if (issues.length === 0)
98772
+ if (sorted.length === 0)
97505
98773
  return;
97506
- const prUrls = await Promise.all(issues.map(async (issue2) => {
97507
- try {
97508
- const attachments = await fetchIssueAttachments(apiKey, issue2.id);
97509
- return findPullRequestUrl(attachments);
97510
- } catch {
97511
- return null;
97512
- }
97513
- }));
97514
- const idWidth = Math.max(3, ...issues.map((i) => i.identifier.length));
97515
- const stateWidth = Math.max(5, ...issues.map((i) => i.state.name.length));
97516
- for (let index = 0;index < issues.length; index += 1) {
97517
- const issue2 = issues[index];
97518
- const pr = prUrls[index];
97519
- const title = issue2.title.slice(0, 60);
97520
- process.stdout.write(` ${pad2(issue2.identifier, idWidth)} ${pad2(issue2.state.name, stateWidth)} ${pad2(title, 60)} ${pr ?? "(no PR)"}
98774
+ const idWidth = Math.max(10, ...sorted.map((r) => r.identifier.length));
98775
+ const bucketWidth = Math.max(6, ...sorted.map((r) => r.bucketLabel.length));
98776
+ const stateWidth = Math.max(5, ...sorted.map((r) => r.stateName.length));
98777
+ const markers = sorted.map((r) => formatPrStatusMarker(r.status));
98778
+ const markerWidth = Math.max(9, ...markers.map((m) => m.length));
98779
+ process.stdout.write(` ${pad2("Identifier", idWidth)} ${pad2("Bucket", bucketWidth)} ${pad2("State", stateWidth)} ${pad2("Title", 60)} ${pad2("PR Status", markerWidth)} PR URL
98780
+ `);
98781
+ for (let i = 0;i < sorted.length; i += 1) {
98782
+ const r = sorted[i];
98783
+ process.stdout.write(` ${pad2(r.identifier, idWidth)} ${pad2(r.bucketLabel, bucketWidth)} ${pad2(r.stateName, stateWidth)} ${pad2(r.title, 60)} ${pad2(markers[i], markerWidth)} ${r.prUrl ?? "(no PR)"}
97521
98784
  `);
97522
98785
  }
97523
98786
  }
@@ -97566,18 +98829,14 @@ Linear: LINEAR_API_KEY not set \u2014 cannot fetch tickets. Configured buckets:
97566
98829
  }
97567
98830
  return;
97568
98831
  }
97569
- process.stdout.write(`
97570
- Linear tickets:
97571
- `);
97572
98832
  if (team)
97573
- process.stdout.write(` team: ${team}
98833
+ process.stdout.write(`
98834
+ team: ${team}
97574
98835
  `);
97575
98836
  if (assignee)
97576
- process.stdout.write(` assignee: ${assignee}
98837
+ process.stdout.write(`assignee: ${assignee}
97577
98838
  `);
97578
- for (const bucket of buckets) {
97579
- await printBucket(apiKey, bucket, team, assignee);
97580
- }
98839
+ await fetchAndPrintLinear(apiKey, buckets, team, assignee, projectRoot, localCmdRunner);
97581
98840
  }
97582
98841
  function normalizeIdentifier(input) {
97583
98842
  const match = input.match(/^([A-Za-z]+)-(\d+)(?:-.*)?$/);
@@ -97724,12 +98983,27 @@ Per-bucket diagnostics:
97724
98983
  }
97725
98984
  }
97726
98985
  }
97727
- var RALPHY_ATTACHMENT_TITLE2 = "Ralphy";
98986
+ var localCmdRunner, RALPHY_ATTACHMENT_TITLE2 = "Ralphy";
97728
98987
  var init_list = __esm(() => {
97729
98988
  init_context();
97730
98989
  init_types2();
97731
98990
  init_worktree();
97732
98991
  init_config();
98992
+ init_list_sort();
98993
+ localCmdRunner = {
98994
+ run: async (cmd, cwd2) => {
98995
+ const proc = Bun.spawn({ cmd, cwd: cwd2, stdout: "pipe", stderr: "pipe" });
98996
+ const stdout = await new Response(proc.stdout).text();
98997
+ const stderr = await new Response(proc.stderr).text();
98998
+ const code = await proc.exited;
98999
+ if (code !== 0) {
99000
+ const err = new Error(`\`${cmd.join(" ")}\` exited ${code}`);
99001
+ err.stderr = stderr;
99002
+ throw err;
99003
+ }
99004
+ return { stdout, stderr };
99005
+ }
99006
+ };
97733
99007
  });
97734
99008
 
97735
99009
  // apps/agent/src/agent/json-runner.ts
@@ -97773,7 +99047,7 @@ async function runAgentJson({
97773
99047
  process.exitCode = 1;
97774
99048
  return;
97775
99049
  }
97776
- const { coord, filterDesc, concurrency, pollInterval } = buildAgentCoordinator({
99050
+ const { coord, filterDesc, concurrency, pollInterval, runBaselineGate: runBaselineGate2 } = buildAgentCoordinator({
97777
99051
  args,
97778
99052
  cfg,
97779
99053
  projectRoot,
@@ -97836,6 +99110,17 @@ async function runAgentJson({
97836
99110
  if (cancelled)
97837
99111
  return;
97838
99112
  emit({ type: "poll_start" });
99113
+ try {
99114
+ await runBaselineGate2();
99115
+ } catch (err) {
99116
+ emit({
99117
+ type: "log",
99118
+ text: `baseline gate failed: ${err.message}`,
99119
+ color: "yellow"
99120
+ });
99121
+ }
99122
+ if (cancelled)
99123
+ return;
97839
99124
  const { found, added, buckets, prStatus } = await coord.pollOnce();
97840
99125
  if (cancelled)
97841
99126
  return;
@@ -97949,12 +99234,12 @@ async function main2(argv) {
97949
99234
  return typeof process.exitCode === "number" ? process.exitCode : 0;
97950
99235
  }
97951
99236
  await runWithContext(createDefaultContext(), async () => {
97952
- const { waitUntilExit } = render_default(import_react59.createElement(AgentMode, { args, projectRoot, statesDir, tasksDir }));
99237
+ const { waitUntilExit } = render_default(import_react62.createElement(AgentMode, { args, projectRoot, statesDir, tasksDir }));
97953
99238
  await waitUntilExit();
97954
99239
  });
97955
99240
  return typeof process.exitCode === "number" ? process.exitCode : 0;
97956
99241
  }
97957
- var import_react59;
99242
+ var import_react62;
97958
99243
  var init_src6 = __esm(async () => {
97959
99244
  init_context();
97960
99245
  init_layout();
@@ -97964,7 +99249,7 @@ var init_src6 = __esm(async () => {
97964
99249
  init_build2(),
97965
99250
  init_AgentMode()
97966
99251
  ]);
97967
- import_react59 = __toESM(require_react(), 1);
99252
+ import_react62 = __toESM(require_react(), 1);
97968
99253
  });
97969
99254
 
97970
99255
  // apps/shell/src/index.ts
@@ -98027,7 +99312,12 @@ ${HELP}
98027
99312
  setDefaultProperties({ subcommand });
98028
99313
  capture("command_run", { subcommand });
98029
99314
  try {
98030
- return await dispatch(subcommand, argv.slice(1));
99315
+ const code = await dispatch(subcommand, argv.slice(1));
99316
+ capture("command_exit", { subcommand, exit_code: code });
99317
+ return code;
99318
+ } catch (err) {
99319
+ captureError("command_error", err, { subcommand });
99320
+ throw err;
98031
99321
  } finally {
98032
99322
  await shutdown();
98033
99323
  }