@neriros/ralphy 3.0.1 → 3.2.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 +35 -1
  2. package/dist/shell/index.js +2064 -480
  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.2.0")
18932
+ return "3.2.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, 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,49 @@ ${failureOutput.trim()}
70159
70223
  ${fence}`;
70160
70224
  await Bun.write(tasksPath, prependSection(existing, stamped, body));
70161
70225
  }
70226
+ function normalizeNewlyAppendedSectionWithReport(previous, current) {
70227
+ const prevHeadings = new Set;
70228
+ for (const line of previous.split(`
70229
+ `)) {
70230
+ if (line.startsWith("## "))
70231
+ prevHeadings.add(line);
70232
+ }
70233
+ const sections = current.split(/(?=^## )/m);
70234
+ const headings = [];
70235
+ let count = 0;
70236
+ const out = sections.map((section) => {
70237
+ const nlIdx = section.indexOf(`
70238
+ `);
70239
+ const headingLine = nlIdx === -1 ? section.replace(/\n$/, "") : section.slice(0, nlIdx);
70240
+ if (!headingLine.startsWith("## "))
70241
+ return section;
70242
+ if (prevHeadings.has(headingLine))
70243
+ return section;
70244
+ let localCount = 0;
70245
+ const rewritten = section.replace(/^(\s*)- \[[xX]\] (.+)$/gm, (_m, indent, rest2) => {
70246
+ localCount += 1;
70247
+ return `${indent}- [ ] ${rest2}`;
70248
+ });
70249
+ if (localCount > 0) {
70250
+ headings.push(headingLine.slice(3));
70251
+ count += localCount;
70252
+ }
70253
+ return rewritten;
70254
+ });
70255
+ return { text: count > 0 ? out.join("") : current, headings, count };
70256
+ }
70257
+ var MISSION_TASKS_FILENAME = "tasks.md", AGENT_TASKS_FILENAME = "agent-tasks.md", FLOW_TASK_HEADING_PREFIXES;
70258
+ var init_tasks_md = __esm(() => {
70259
+ FLOW_TASK_HEADING_PREFIXES = [
70260
+ "Fix failing CI checks",
70261
+ "Fix push rejection",
70262
+ "Resolve PR merge conflicts",
70263
+ "Resolve merge conflict with origin/",
70264
+ "Address reviewer comments",
70265
+ "Address GitHub @ralphy mention",
70266
+ "Address Linear @ralphy mention"
70267
+ ];
70268
+ });
70162
70269
 
70163
70270
  // packages/core/src/loop.ts
70164
70271
  import { join as join10 } from "path";
@@ -70184,9 +70291,21 @@ function buildTaskPrompt(state, taskDir) {
70184
70291
  `;
70185
70292
  }
70186
70293
  }
70187
- const tasksContent = storage.read(join10(taskDir, "tasks.md"));
70188
- if (tasksContent !== null) {
70189
- const section = firstUnchecked(tasksContent);
70294
+ const agentTasksPath = join10(taskDir, AGENT_TASKS_FILENAME);
70295
+ const missionTasksPath = join10(taskDir, MISSION_TASKS_FILENAME);
70296
+ const agentTasksContent = storage.read(agentTasksPath);
70297
+ const missionTasksContent = storage.read(missionTasksPath);
70298
+ let activePath = null;
70299
+ let activeContent = null;
70300
+ if (agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent)) {
70301
+ activePath = agentTasksPath;
70302
+ activeContent = agentTasksContent;
70303
+ } else if (missionTasksContent !== null) {
70304
+ activePath = missionTasksPath;
70305
+ activeContent = missionTasksContent;
70306
+ }
70307
+ if (activeContent !== null && activePath !== null) {
70308
+ const section = firstUnchecked(activeContent);
70190
70309
  if (section) {
70191
70310
  prompt += `---
70192
70311
 
@@ -70199,7 +70318,7 @@ function buildTaskPrompt(state, taskDir) {
70199
70318
  prompt += `---
70200
70319
 
70201
70320
  `;
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.
70321
+ 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
70322
 
70204
70323
  `;
70205
70324
  }
@@ -70220,10 +70339,11 @@ function buildTaskPrompt(state, taskDir) {
70220
70339
  `;
70221
70340
  }
70222
70341
  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);
70342
+ const tasksContent = missionTasksContent;
70343
+ const hasUncheckedMission = tasksContent !== null && /^- \[ \]/m.test(tasksContent);
70344
+ const hasUncheckedAgent = agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent);
70345
+ if (!hasUncheckedMission && !hasUncheckedAgent) {
70346
+ const hasManualTestSection = tasksContent !== null && /^## Manual Testing/m.test(tasksContent);
70227
70347
  if (!hasManualTestSection) {
70228
70348
  prompt += `---
70229
70349
 
@@ -70379,11 +70499,14 @@ var STEERING_MAX_LINES = 20;
70379
70499
  var init_loop = __esm(() => {
70380
70500
  init_state();
70381
70501
  init_context();
70502
+ init_tasks_md();
70503
+ init_tasks_md();
70382
70504
  });
70383
70505
 
70384
70506
  // apps/loop/src/loop.ts
70385
70507
  var init_loop2 = __esm(() => {
70386
70508
  init_loop();
70509
+ init_tasks_md();
70387
70510
  });
70388
70511
 
70389
70512
  // apps/loop/src/hooks/useLoop.ts
@@ -70392,22 +70515,22 @@ function sleep(seconds) {
70392
70515
  return new Promise((resolve3) => setTimeout(resolve3, seconds * 1000));
70393
70516
  }
70394
70517
  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);
70518
+ const [state, setState] = import_react55.useState(null);
70519
+ const [iteration, setIteration] = import_react55.useState(0);
70520
+ const [consecutiveFailures, setConsecutiveFailures] = import_react55.useState(0);
70521
+ const [logLines, setLogLines] = import_react55.useState([]);
70522
+ const [stopReason, setStopReason] = import_react55.useState(null);
70523
+ const [isRunning, setIsRunning] = import_react55.useState(true);
70524
+ const [isResume, setIsResume] = import_react55.useState(false);
70525
+ const [startedAt] = import_react55.useState(() => Date.now());
70526
+ const lineIdRef = import_react55.useRef(0);
70527
+ const steerControllerRef = import_react55.useRef(null);
70528
+ const pendingSteerRef = import_react55.useRef(null);
70406
70529
  const steer = (message) => {
70407
70530
  pendingSteerRef.current = message;
70408
70531
  steerControllerRef.current?.abort();
70409
70532
  };
70410
- import_react54.useEffect(() => {
70533
+ import_react55.useEffect(() => {
70411
70534
  let cancelled = false;
70412
70535
  const nextId = () => String(lineIdRef.current++);
70413
70536
  const addInfo = (text) => {
@@ -70471,15 +70594,24 @@ function useLoop(opts) {
70471
70594
  const stop = checkStopCondition(currentState, iter, opts, loopStartTime, consFailures);
70472
70595
  if (stop !== null) {
70473
70596
  finalStopReason = stop;
70474
- setStopReason(stop);
70475
70597
  break;
70476
70598
  }
70477
- const tasksContent = storage.read(join11(tasksDir, "tasks.md"));
70599
+ const tasksContent = storage.read(join11(tasksDir, MISSION_TASKS_FILENAME));
70600
+ const agentTasksContent = storage.read(join11(tasksDir, AGENT_TASKS_FILENAME));
70478
70601
  if (tasksContent !== null) {
70479
70602
  const remaining = countUnchecked(tasksContent);
70480
- addInfo(`tasks.md: ${remaining} unchecked item${remaining === 1 ? "" : "s"} remaining`);
70481
- }
70482
- if (tasksContent !== null && allCompleted(tasksContent)) {
70603
+ const agentRemaining = agentTasksContent !== null ? countUnchecked(agentTasksContent) : 0;
70604
+ const parts = [
70605
+ `tasks.md: ${remaining} unchecked item${remaining === 1 ? "" : "s"} remaining`
70606
+ ];
70607
+ if (agentTasksContent !== null) {
70608
+ parts.push(`agent-tasks.md: ${agentRemaining} unchecked item${agentRemaining === 1 ? "" : "s"} remaining`);
70609
+ }
70610
+ addInfo(parts.join(" \xB7 "));
70611
+ }
70612
+ const missionDone = tasksContent !== null && allCompleted(tasksContent);
70613
+ const agentDone = agentTasksContent === null || allCompleted(agentTasksContent);
70614
+ if (missionDone && agentDone && tasksContent !== null) {
70483
70615
  addInfo("All tasks completed \u2014 archiving change.");
70484
70616
  currentState = {
70485
70617
  ...currentState,
@@ -70495,7 +70627,6 @@ function useLoop(opts) {
70495
70627
  addInfo(`Archive warning: ${err}`);
70496
70628
  }
70497
70629
  finalStopReason = "completed";
70498
- setStopReason("completed");
70499
70630
  break;
70500
70631
  }
70501
70632
  iter++;
@@ -70555,7 +70686,6 @@ function useLoop(opts) {
70555
70686
  if (failure.shouldStop || engineResult.rateLimited) {
70556
70687
  capture("engine_rate_limited", { exit_code: engineResult.exitCode, iteration: iter });
70557
70688
  finalStopReason = "rateLimited";
70558
- setStopReason("rateLimited");
70559
70689
  break;
70560
70690
  }
70561
70691
  capture("iteration_failed", {
@@ -70614,6 +70744,9 @@ function useLoop(opts) {
70614
70744
  gitPush();
70615
70745
  } catch {}
70616
70746
  }
70747
+ if (finalStopReason !== null) {
70748
+ setStopReason(finalStopReason);
70749
+ }
70617
70750
  setIsRunning(false);
70618
70751
  });
70619
70752
  return () => {
@@ -70632,7 +70765,7 @@ function useLoop(opts) {
70632
70765
  steer
70633
70766
  };
70634
70767
  }
70635
- var import_react54;
70768
+ var import_react55;
70636
70769
  var init_useLoop = __esm(() => {
70637
70770
  init_state();
70638
70771
  init_engine();
@@ -70640,7 +70773,7 @@ var init_useLoop = __esm(() => {
70640
70773
  init_context();
70641
70774
  init_src();
70642
70775
  init_loop2();
70643
- import_react54 = __toESM(require_react(), 1);
70776
+ import_react55 = __toESM(require_react(), 1);
70644
70777
  });
70645
70778
 
70646
70779
  // apps/loop/src/components/TaskLoop.tsx
@@ -70695,10 +70828,10 @@ function handleSteerKeyInput(key, history, currentIndex) {
70695
70828
  return navigateHistory(history, currentIndex, dir);
70696
70829
  }
70697
70830
  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);
70831
+ const [inputKey, setInputKey] = import_react56.useState(0);
70832
+ const [defaultValue, setDefaultValue] = import_react56.useState("");
70833
+ const historyRef = import_react56.useRef([]);
70834
+ const historyIndexRef = import_react56.useRef(-1);
70702
70835
  use_input_default((_input, key) => {
70703
70836
  const result2 = handleSteerKeyInput(key, historyRef.current, historyIndexRef.current);
70704
70837
  if (result2) {
@@ -70730,12 +70863,19 @@ function TaskLoop({ opts }) {
70730
70863
  const { exit } = use_app_default();
70731
70864
  const loop = useLoop(opts);
70732
70865
  const { isRawModeSupported } = use_stdin_default();
70733
- const bannerItem = import_react55.useRef({ id: "__banner__", kind: "banner" });
70734
- const feedItems = import_react55.useMemo(() => [
70866
+ const { stdout } = use_stdout_default();
70867
+ const { resizeKey } = useTerminalSize();
70868
+ const bannerItem = import_react56.useRef({ id: "__banner__", kind: "banner" });
70869
+ import_react56.useEffect(() => {
70870
+ if (resizeKey === 0)
70871
+ return;
70872
+ stdout.write("\x1B[2J\x1B[3J\x1B[H");
70873
+ }, [resizeKey, stdout]);
70874
+ const feedItems = import_react56.useMemo(() => [
70735
70875
  bannerItem.current,
70736
70876
  ...loop.logLines.map((e) => ({ id: e.id, kind: "entry", entry: e }))
70737
70877
  ], [loop.logLines]);
70738
- import_react55.useEffect(() => {
70878
+ import_react56.useEffect(() => {
70739
70879
  if (!loop.isRunning) {
70740
70880
  exit();
70741
70881
  }
@@ -70783,7 +70923,7 @@ function TaskLoop({ opts }) {
70783
70923
  }, undefined, false, undefined, this)
70784
70924
  ]
70785
70925
  }, undefined, true, undefined, this),
70786
- loop.stopReason && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(jsx_dev_runtime7.Fragment, {
70926
+ !loop.isRunning && loop.stopReason && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(jsx_dev_runtime7.Fragment, {
70787
70927
  children: [
70788
70928
  /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(StatusBar, {
70789
70929
  iteration: loop.iteration,
@@ -70805,11 +70945,12 @@ function TaskLoop({ opts }) {
70805
70945
  ]
70806
70946
  }, undefined, true, undefined, this)
70807
70947
  ]
70808
- }, undefined, true, undefined, this);
70948
+ }, resizeKey, true, undefined, this);
70809
70949
  }
70810
- var import_react55, jsx_dev_runtime7;
70950
+ var import_react56, jsx_dev_runtime7;
70811
70951
  var init_TaskLoop = __esm(async () => {
70812
70952
  init_useLoop();
70953
+ init_useTerminalSize();
70813
70954
  await __promiseAll([
70814
70955
  init_build2(),
70815
70956
  init_build3(),
@@ -70819,7 +70960,7 @@ var init_TaskLoop = __esm(async () => {
70819
70960
  init_StatusBar(),
70820
70961
  init_StopMessage()
70821
70962
  ]);
70822
- import_react55 = __toESM(require_react(), 1);
70963
+ import_react56 = __toESM(require_react(), 1);
70823
70964
  jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
70824
70965
  });
70825
70966
 
@@ -70827,7 +70968,7 @@ var init_TaskLoop = __esm(async () => {
70827
70968
  import { join as join13 } from "path";
70828
70969
  function ExitAfterRender({ children }) {
70829
70970
  const { exit } = use_app_default();
70830
- import_react56.useEffect(() => {
70971
+ import_react57.useEffect(() => {
70831
70972
  exit();
70832
70973
  }, [exit]);
70833
70974
  return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(jsx_dev_runtime8.Fragment, {
@@ -70836,7 +70977,7 @@ function ExitAfterRender({ children }) {
70836
70977
  }
70837
70978
  function ErrorMessage({ message }) {
70838
70979
  const { exit } = use_app_default();
70839
- import_react56.useEffect(() => {
70980
+ import_react57.useEffect(() => {
70840
70981
  process.exitCode = 1;
70841
70982
  exit();
70842
70983
  }, [exit]);
@@ -70915,7 +71056,7 @@ function App2({ args, statesDir, tasksDir }) {
70915
71056
  }
70916
71057
  }
70917
71058
  }
70918
- var import_react56, jsx_dev_runtime8;
71059
+ var import_react57, jsx_dev_runtime8;
70919
71060
  var init_App2 = __esm(async () => {
70920
71061
  init_state();
70921
71062
  init_context();
@@ -70925,7 +71066,7 @@ var init_App2 = __esm(async () => {
70925
71066
  init_TaskStatus(),
70926
71067
  init_TaskLoop()
70927
71068
  ]);
70928
- import_react56 = __toESM(require_react(), 1);
71069
+ import_react57 = __toESM(require_react(), 1);
70929
71070
  jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
70930
71071
  });
70931
71072
 
@@ -79420,7 +79561,7 @@ __export(exports_regexes, {
79420
79561
  integer: () => integer,
79421
79562
  idnEmail: () => idnEmail,
79422
79563
  html5Email: () => html5Email,
79423
- hostname: () => hostname,
79564
+ hostname: () => hostname2,
79424
79565
  hex: () => hex,
79425
79566
  guid: () => guid,
79426
79567
  extendedDuration: () => extendedDuration,
@@ -79475,7 +79616,7 @@ var cuid, cuid2, ulid, xid, ksuid, nanoid, duration, extendedDuration, guid, uui
79475
79616
  }, uuid4, uuid6, uuid7, email, html5Email, rfc5322Email, unicodeEmail, idnEmail, browserEmail, _emoji = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`, ipv4, ipv6, mac = (delimiter) => {
79476
79617
  const escapedDelim = escapeRegex(delimiter ?? ":");
79477
79618
  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) => {
79619
+ }, 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
79620
  const regex2 = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
79480
79621
  return new RegExp(`^${regex2}$`);
79481
79622
  }, 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 +79646,7 @@ var init_regexes = __esm(() => {
79505
79646
  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
79647
  base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
79507
79648
  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])?)*\.?$/;
79649
+ 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
79650
  domain = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
79510
79651
  e164 = /^\+[1-9]\d{6,14}$/;
79511
79652
  date = /* @__PURE__ */ new RegExp(`^${dateSource}$`);
@@ -90290,7 +90431,7 @@ __export(exports_schemas2, {
90290
90431
  int: () => int,
90291
90432
  instanceof: () => _instanceof,
90292
90433
  httpUrl: () => httpUrl,
90293
- hostname: () => hostname2,
90434
+ hostname: () => hostname3,
90294
90435
  hex: () => hex2,
90295
90436
  hash: () => hash,
90296
90437
  guid: () => guid2,
@@ -90474,7 +90615,7 @@ function jwt(params) {
90474
90615
  function stringFormat(format2, fnOrRegex, _params = {}) {
90475
90616
  return _stringFormat(ZodCustomStringFormat, format2, fnOrRegex, _params);
90476
90617
  }
90477
- function hostname2(_params) {
90618
+ function hostname3(_params) {
90478
90619
  return _stringFormat(ZodCustomStringFormat, "hostname", exports_regexes.hostname, _params);
90479
90620
  }
90480
90621
  function hex2(_params) {
@@ -92112,7 +92253,7 @@ __export(exports_external2, {
92112
92253
  instanceof: () => _instanceof,
92113
92254
  includes: () => _includes,
92114
92255
  httpUrl: () => httpUrl,
92115
- hostname: () => hostname2,
92256
+ hostname: () => hostname3,
92116
92257
  hex: () => hex2,
92117
92258
  hash: () => hash,
92118
92259
  guid: () => guid2,
@@ -92364,6 +92505,7 @@ var init_schema = __esm(() => {
92364
92505
  mentionHandle: exports_external2.string().default("@ralphy"),
92365
92506
  codeReviewTrigger: exports_external2.boolean().default(false),
92366
92507
  codeReviewStaleHours: exports_external2.number().nonnegative().default(24),
92508
+ syncTasksToDescription: exports_external2.boolean().default(false),
92367
92509
  indicators: IndicatorsSchema.default({})
92368
92510
  }).strict().default({
92369
92511
  postComments: true,
@@ -92372,6 +92514,7 @@ var init_schema = __esm(() => {
92372
92514
  mentionHandle: "@ralphy",
92373
92515
  codeReviewTrigger: false,
92374
92516
  codeReviewStaleHours: 24,
92517
+ syncTasksToDescription: false,
92375
92518
  indicators: {}
92376
92519
  }),
92377
92520
  github: exports_external2.object({
@@ -92394,7 +92537,20 @@ var init_schema = __esm(() => {
92394
92537
  fix_on_failure: exports_external2.boolean().optional(),
92395
92538
  max_attempts: exports_external2.number().int().positive().optional(),
92396
92539
  poll_interval_seconds: exports_external2.number().int().positive().optional()
92397
- }).strict().optional()
92540
+ }).strict().optional(),
92541
+ preExistingErrorCheck: exports_external2.object({
92542
+ enabled: exports_external2.boolean().default(false),
92543
+ commands: exports_external2.array(exports_external2.string()).default([]),
92544
+ baseBranch: exports_external2.string().default("main"),
92545
+ label: exports_external2.string().default("ralph:pre-existing-error"),
92546
+ outputCharLimit: exports_external2.number().int().positive().default(4000)
92547
+ }).strict().default({
92548
+ enabled: false,
92549
+ commands: [],
92550
+ baseBranch: "main",
92551
+ label: "ralph:pre-existing-error",
92552
+ outputCharLimit: 4000
92553
+ })
92398
92554
  });
92399
92555
  });
92400
92556
 
@@ -92478,6 +92634,17 @@ engine: claude
92478
92634
  # Model tier: "haiku", "sonnet", or "opus".
92479
92635
  model: opus
92480
92636
 
92637
+ # Pre-existing error check: gate the agent when the base branch is already broken.
92638
+ # When enabled, the agent runs these commands against the base branch HEAD before
92639
+ # scheduling new work; failures open a Linear ticket and pause new pickups.
92640
+ preExistingErrorCheck:
92641
+ enabled: false
92642
+ # Commands to run against the base branch. When empty, falls back to commands.lint / commands.test.
92643
+ commands: []
92644
+ baseBranch: main
92645
+ label: "ralph:pre-existing-error"
92646
+ outputCharLimit: 4000
92647
+
92481
92648
  linear:
92482
92649
  # Linear team key (e.g. "ENG"). Omit to match all teams.
92483
92650
  # team: ENG
@@ -92496,6 +92663,11 @@ linear:
92496
92663
  codeReviewTrigger: false
92497
92664
  codeReviewStaleHours: 24
92498
92665
 
92666
+ # Mirror the loop's tasks.md into the Linear issue description as a
92667
+ # checklist between sentinel markers. Updates on worker launch, on the
92668
+ # same cadence as updateEveryIterations, and on done-transition.
92669
+ syncTasksToDescription: false
92670
+
92499
92671
  # Indicators map Ralph lifecycle events to Linear labels/statuses.
92500
92672
  # Grouped by lifecycle: each get* is followed by the set*/clear* that
92501
92673
  # mutates the same state, so the lifecycle reads top-to-bottom.
@@ -92556,6 +92728,10 @@ Previous attempt failed with: {{ last_error }}
92556
92728
 
92557
92729
  {{ issue.description }}
92558
92730
 
92731
+ {% if issue.labels %}
92732
+ Labels: {{ issue.labels | join(", ") }}
92733
+ {% endif %}
92734
+
92559
92735
  {% if rules %}
92560
92736
  Project rules:
92561
92737
  {% for rule in rules %}- {{ rule }}
@@ -92743,6 +92919,7 @@ function renderTemplate(src, ctx) {
92743
92919
  var exports_workflow = {};
92744
92920
  __export(exports_workflow, {
92745
92921
  workflowPath: () => workflowPath,
92922
+ resolveBaselineCommands: () => resolveBaselineCommands,
92746
92923
  renderWorkflowPrompt: () => renderWorkflowPrompt,
92747
92924
  renderTemplate: () => renderTemplate,
92748
92925
  parseWorkflow: () => parseWorkflow,
@@ -92875,6 +93052,17 @@ function extractDefaultBody() {
92875
93052
  const m = FRONTMATTER_RE.exec(DEFAULT_WORKFLOW_MD);
92876
93053
  return m ? m[2] ?? "" : "";
92877
93054
  }
93055
+ function resolveBaselineCommands(config2) {
93056
+ const configured = config2.preExistingErrorCheck?.commands ?? [];
93057
+ if (configured.length > 0)
93058
+ return [...configured];
93059
+ const fallback = [];
93060
+ if (config2.commands.lint)
93061
+ fallback.push(config2.commands.lint);
93062
+ if (config2.commands.test)
93063
+ fallback.push(config2.commands.test);
93064
+ return fallback;
93065
+ }
92878
93066
  function renderWorkflowPrompt(workflow, ctx) {
92879
93067
  const fullCtx = {
92880
93068
  project: workflow.config.project,
@@ -93027,12 +93215,12 @@ async function main(argv) {
93027
93215
  await ensureRalphGitignore(projectRoot);
93028
93216
  }
93029
93217
  await runWithContext(createDefaultContext(), async () => {
93030
- const { waitUntilExit } = render_default(import_react57.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
93218
+ const { waitUntilExit } = render_default(import_react58.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
93031
93219
  await waitUntilExit();
93032
93220
  });
93033
93221
  return typeof process.exitCode === "number" ? process.exitCode : 0;
93034
93222
  }
93035
- var import_react57;
93223
+ var import_react58;
93036
93224
  var init_src5 = __esm(async () => {
93037
93225
  init_context();
93038
93226
  init_layout();
@@ -93044,7 +93232,7 @@ var init_src5 = __esm(async () => {
93044
93232
  init_build2(),
93045
93233
  init_App2()
93046
93234
  ]);
93047
- import_react57 = __toESM(require_react(), 1);
93235
+ import_react58 = __toESM(require_react(), 1);
93048
93236
  });
93049
93237
 
93050
93238
  // apps/agent/src/cli.ts
@@ -93225,6 +93413,9 @@ async function parseArgs2(argv) {
93225
93413
  case "--debug":
93226
93414
  result2.debug = true;
93227
93415
  break;
93416
+ case "--pre-existing-error-check":
93417
+ result2.preExistingErrorCheck = true;
93418
+ break;
93228
93419
  default:
93229
93420
  if (VALID_MODES2.has(arg)) {
93230
93421
  result2.mode = arg;
@@ -93305,6 +93496,7 @@ var init_cli2 = __esm(() => {
93305
93496
  " --code-review Watch open tracked PRs for unresolved review comments",
93306
93497
  " --max-tickets <n> Stop picking up new issues after N have been started (0 = unlimited)",
93307
93498
  " --json-output Emit JSONL to stdout instead of the Ink dashboard (for scripting/CI)",
93499
+ " --pre-existing-error-check Run baseline commands against the base branch; pause new pickups + open a Linear ticket when red",
93308
93500
  " --debug List mode: explain why a Linear ticket was not picked up (use with --name)",
93309
93501
  " --help, -h Show this help message",
93310
93502
  "",
@@ -93420,12 +93612,57 @@ function buildIssueFilter(spec) {
93420
93612
  }
93421
93613
  return where;
93422
93614
  }
93615
+ async function fetchMentionScanIssues(apiKey, spec) {
93616
+ const where = {
93617
+ state: { type: { in: ["unstarted", "started", "backlog", "triage", "completed"] } }
93618
+ };
93619
+ if (spec.team)
93620
+ where.team = { key: { eq: spec.team } };
93621
+ if (spec.assignee) {
93622
+ if (spec.assignee === "me")
93623
+ where.assignee = { isMe: { eq: true } };
93624
+ else if (spec.assignee.includes("@"))
93625
+ where.assignee = { email: { eq: spec.assignee } };
93626
+ else
93627
+ where.assignee = { id: { eq: spec.assignee } };
93628
+ }
93629
+ const query = `query MentionScanIssues($filter: IssueFilter) {
93630
+ issues(filter: $filter, first: 50) {
93631
+ nodes {
93632
+ id identifier title description url priority createdAt
93633
+ state { name type }
93634
+ assignee { id email name }
93635
+ labels { nodes { name } }
93636
+ relations(first: 50) {
93637
+ nodes { type relatedIssue { id state { type } } }
93638
+ }
93639
+ }
93640
+ }
93641
+ }`;
93642
+ const data = await linearRequest(apiKey, query, {
93643
+ filter: where
93644
+ });
93645
+ const DONE_STATE_TYPES = new Set(["completed", "cancelled"]);
93646
+ return data.issues.nodes.map((n) => ({
93647
+ id: n.id,
93648
+ identifier: n.identifier,
93649
+ title: n.title,
93650
+ description: n.description,
93651
+ url: n.url,
93652
+ state: n.state,
93653
+ assignee: n.assignee,
93654
+ labels: n.labels.nodes.map((l) => l.name),
93655
+ priority: n.priority,
93656
+ createdAt: n.createdAt ?? "",
93657
+ blockedByIds: (n.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => r.relatedIssue.id)
93658
+ }));
93659
+ }
93423
93660
  async function fetchOpenIssues(apiKey, spec) {
93424
93661
  const where = buildIssueFilter(spec);
93425
93662
  const query = `query Issues($filter: IssueFilter) {
93426
93663
  issues(filter: $filter, first: 50) {
93427
93664
  nodes {
93428
- id identifier title description url priority
93665
+ id identifier title description url priority createdAt
93429
93666
  state { name type }
93430
93667
  assignee { id email name }
93431
93668
  labels { nodes { name } }
@@ -93452,6 +93689,7 @@ async function fetchOpenIssues(apiKey, spec) {
93452
93689
  assignee: n.assignee,
93453
93690
  labels: n.labels.nodes.map((l) => l.name),
93454
93691
  priority: n.priority,
93692
+ createdAt: n.createdAt ?? "",
93455
93693
  blockedByIds: (n.relations?.nodes ?? []).filter((r) => r.type === "blocked_by" && !DONE_STATE_TYPES.has(r.relatedIssue.state.type)).map((r) => r.relatedIssue.id)
93456
93694
  }));
93457
93695
  }
@@ -93478,6 +93716,15 @@ async function linearRequest(apiKey, query, variables) {
93478
93716
  }
93479
93717
  return json2.data;
93480
93718
  }
93719
+ async function addReactionToComment(apiKey, commentId, emoji3) {
93720
+ const mutation = `mutation Reaction($commentId: String!, $emoji: String!) {
93721
+ reactionCreate(input: { commentId: $commentId, emoji: $emoji }) { success }
93722
+ }`;
93723
+ await linearRequest(apiKey, mutation, {
93724
+ commentId,
93725
+ emoji: emoji3
93726
+ });
93727
+ }
93481
93728
  async function addIssueComment(apiKey, issueId, body) {
93482
93729
  const mutation = `mutation Comment($issueId: String!, $body: String!) {
93483
93730
  commentCreate(input: { issueId: $issueId, body: $body }) { success }
@@ -93653,6 +93900,53 @@ function issueMatchesGetIndicator(issue2, indicator) {
93653
93900
  return false;
93654
93901
  });
93655
93902
  }
93903
+ async function createIssue(apiKey, input) {
93904
+ const mutation = `mutation CreateIssue($input: IssueCreateInput!) {
93905
+ issueCreate(input: $input) {
93906
+ success
93907
+ issue { id identifier }
93908
+ }
93909
+ }`;
93910
+ const variables = {
93911
+ input: {
93912
+ teamId: input.teamId,
93913
+ title: input.title,
93914
+ description: input.description,
93915
+ ...input.labelIds && input.labelIds.length > 0 ? { labelIds: input.labelIds } : {}
93916
+ }
93917
+ };
93918
+ const data = await linearRequest(apiKey, mutation, variables);
93919
+ const issue2 = data.issueCreate.issue;
93920
+ if (!issue2)
93921
+ throw new Error("issueCreate returned no issue");
93922
+ return issue2;
93923
+ }
93924
+ async function updateIssueDescription(apiKey, issueId, description) {
93925
+ const mutation = `mutation UpdateDesc($id: String!, $description: String!) {
93926
+ issueUpdate(id: $id, input: { description: $description }) { success }
93927
+ }`;
93928
+ await linearRequest(apiKey, mutation, {
93929
+ id: issueId,
93930
+ description
93931
+ });
93932
+ }
93933
+ async function findOpenIssueByLabel(apiKey, teamKey, labelName) {
93934
+ const query = `query OpenByLabel($team: String!, $label: String!) {
93935
+ issues(
93936
+ filter: {
93937
+ team: { key: { eq: $team } },
93938
+ labels: { some: { name: { eq: $label } } },
93939
+ state: { type: { in: ["unstarted", "started", "backlog", "triage"] } }
93940
+ },
93941
+ first: 1,
93942
+ orderBy: createdAt
93943
+ ) {
93944
+ nodes { id identifier description }
93945
+ }
93946
+ }`;
93947
+ const data = await linearRequest(apiKey, query, { team: teamKey, label: labelName });
93948
+ return data.issues.nodes[0] ?? null;
93949
+ }
93656
93950
  async function removeLabelFromIssue(apiKey, issueId, labelId) {
93657
93951
  const mutation = `mutation RemoveLabel($id: String!, $labelId: String!) {
93658
93952
  issueRemoveLabel(id: $id, labelId: $labelId) { success }
@@ -93664,6 +93958,43 @@ async function removeLabelFromIssue(apiKey, issueId, labelId) {
93664
93958
  }
93665
93959
  var LINEAR_API = "https://api.linear.app/graphql", RALPHY_ATTACHMENT_TITLE_FILTER = "Ralphy", RALPHY_ATTACHMENT_TITLE = "Ralphy", BRANCH_LABEL_PREFIX = "ralph:branch:";
93666
93960
 
93961
+ // apps/agent/src/sort/compare.ts
93962
+ function chain(...comparators) {
93963
+ return (a, b) => {
93964
+ for (const c of comparators) {
93965
+ const r = c(a, b);
93966
+ if (r !== 0)
93967
+ return r;
93968
+ }
93969
+ return 0;
93970
+ };
93971
+ }
93972
+
93973
+ // apps/agent/src/queue/queue-order.ts
93974
+ function compareQueueEntries(getAutoMerge) {
93975
+ const isAutoMergeBoost = (e) => e.mode === "conflict-fix" && issueMatchesGetIndicator(e.issue, getAutoMerge);
93976
+ return chain((a, b) => Number(!isAutoMergeBoost(a)) - Number(!isAutoMergeBoost(b)), (a, b) => {
93977
+ const pa = a.issue.priority === 0 ? Infinity : a.issue.priority;
93978
+ const pb = b.issue.priority === 0 ? Infinity : b.issue.priority;
93979
+ return pa - pb;
93980
+ }, (a, b) => MODE_RANK[a.mode] - MODE_RANK[b.mode], (a, b) => {
93981
+ const ca = a.issue.createdAt;
93982
+ const cb = b.issue.createdAt;
93983
+ if (ca === cb)
93984
+ return 0;
93985
+ return ca < cb ? -1 : 1;
93986
+ });
93987
+ }
93988
+ var MODE_RANK;
93989
+ var init_queue_order = __esm(() => {
93990
+ MODE_RANK = {
93991
+ resume: 0,
93992
+ "conflict-fix": 1,
93993
+ review: 2,
93994
+ fresh: 3
93995
+ };
93996
+ });
93997
+
93667
93998
  // apps/agent/src/agent/coordinator.ts
93668
93999
  class AgentCoordinator {
93669
94000
  deps;
@@ -93672,6 +94003,7 @@ class AgentCoordinator {
93672
94003
  pendingIds = new Set;
93673
94004
  queue = [];
93674
94005
  stopped = false;
94006
+ paused = null;
93675
94007
  conflictNotified = new Set;
93676
94008
  ticketsStarted = 0;
93677
94009
  constructor(deps, opts) {
@@ -93690,6 +94022,18 @@ class AgentCoordinator {
93690
94022
  get ticketsStartedCount() {
93691
94023
  return this.ticketsStarted;
93692
94024
  }
94025
+ isPaused() {
94026
+ return this.paused !== null;
94027
+ }
94028
+ getPause() {
94029
+ return this.paused;
94030
+ }
94031
+ setPaused(state) {
94032
+ this.paused = state;
94033
+ }
94034
+ clearPaused() {
94035
+ this.paused = null;
94036
+ }
93693
94037
  async init() {}
93694
94038
  async pollOnce() {
93695
94039
  if (this.stopped)
@@ -93713,11 +94057,23 @@ class AgentCoordinator {
93713
94057
  return emptyPollResult();
93714
94058
  }
93715
94059
  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");
94060
+ this.deps.onFileLog?.(` poll: ${todo.length} todo, ${inProgress.length} in-progress, ${conflicted.length} conflicted, ${review.length} review, ${mentions.length} mention`);
93717
94061
  }
93718
94062
  const queuedIds = new Set(this.queue.map((q) => q.issue.id));
93719
94063
  const activeIds = new Set(this.workers.map((w) => w.issueId));
93720
94064
  const eligible = (id) => !queuedIds.has(id) && !activeIds.has(id) && !this.pendingIds.has(id);
94065
+ if (this.paused) {
94066
+ this.deps.onLog(` paused \u2014 baseline broken (${this.paused.issueIdentifier}); skipping new pickups`, "yellow");
94067
+ const buckets2 = {
94068
+ todo: todo.length,
94069
+ inProgress: inProgress.length,
94070
+ conflicted: conflicted.length,
94071
+ review: review.length,
94072
+ mentions: mentions.length
94073
+ };
94074
+ const found2 = buckets2.todo + buckets2.inProgress + buckets2.conflicted + buckets2.review + buckets2.mentions;
94075
+ return { found: found2, added: 0, buckets: buckets2, prStatus: emptyPrStatus() };
94076
+ }
93721
94077
  const maxT = this.opts.maxTickets ?? 0;
93722
94078
  const atTicketLimit = () => {
93723
94079
  if (maxT === 0)
@@ -93746,7 +94102,11 @@ class AgentCoordinator {
93746
94102
  this.queue.push({ issue: issue2, mode: "conflict-fix" });
93747
94103
  queuedIds.add(issue2.id);
93748
94104
  added += 1;
93749
- this.deps.onLog(` \u21B3 ${issue2.identifier} queued (conflict-fix)`, "gray");
94105
+ if (this.isAutoMergeUnblock(issue2)) {
94106
+ this.deps.onLog(` \u21B3 ${issue2.identifier} queued (auto-merge unblock, prioritized)`, "cyan");
94107
+ } else {
94108
+ this.deps.onLog(` \u21B3 ${issue2.identifier} queued (conflict-fix)`, "gray");
94109
+ }
93750
94110
  }
93751
94111
  for (const issue2 of review) {
93752
94112
  if (atTicketLimit())
@@ -93781,19 +94141,7 @@ class AgentCoordinator {
93781
94141
  this.deps.onLog(` \u21B3 ${issue2.identifier} queued (fresh)`, "gray");
93782
94142
  }
93783
94143
  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
- });
94144
+ this.queue.sort(compareQueueEntries(this.opts.getAutoMerge));
93797
94145
  }
93798
94146
  this.spawnNext();
93799
94147
  const prStatus = await this.scanDoneForConflicts();
@@ -93808,6 +94156,9 @@ class AgentCoordinator {
93808
94156
  const found = buckets.todo + buckets.inProgress + buckets.conflicted + buckets.review + buckets.mentions;
93809
94157
  return { found, added, buckets, prStatus };
93810
94158
  }
94159
+ isAutoMergeUnblock(issue2) {
94160
+ return issueMatchesGetIndicator(issue2, this.opts.getAutoMerge);
94161
+ }
93811
94162
  dependenciesResolved(issue2) {
93812
94163
  if (issue2.blockedByIds.length === 0)
93813
94164
  return true;
@@ -93849,6 +94200,13 @@ class AgentCoordinator {
93849
94200
  } catch (err) {
93850
94201
  this.deps.onLog(`! Linear progress comment failed for ${w.issueIdentifier}: ${err.message}`, "red");
93851
94202
  }
94203
+ if (this.deps.syncTasks) {
94204
+ try {
94205
+ await this.deps.syncTasks(w, count);
94206
+ } catch (err) {
94207
+ this.deps.onLog(`! sync-tasks (progress) failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
94208
+ }
94209
+ }
93852
94210
  }
93853
94211
  }
93854
94212
  async scanDoneForConflicts() {
@@ -94006,7 +94364,8 @@ class AgentCoordinator {
94006
94364
  issue: issue2,
94007
94365
  mode,
94008
94366
  kill: handle.kill,
94009
- lastReportedIteration: 0
94367
+ lastReportedIteration: 0,
94368
+ restarting: false
94010
94369
  };
94011
94370
  this.workers.push(worker);
94012
94371
  this.pendingIds.delete(issue2.id);
@@ -94020,10 +94379,24 @@ class AgentCoordinator {
94020
94379
  issue_identifier: issue2.identifier
94021
94380
  });
94022
94381
  this.deps.onWorkersChanged();
94382
+ if (this.deps.syncTasks) {
94383
+ try {
94384
+ await this.deps.syncTasks(worker, 0);
94385
+ } catch (err) {
94386
+ this.deps.onLog(`! sync-tasks (launch) failed for ${issue2.identifier}: ${err.message}`, "yellow");
94387
+ }
94388
+ }
94023
94389
  handle.exited.then(async (code) => {
94024
94390
  const idx = this.workers.indexOf(worker);
94025
94391
  if (idx >= 0)
94026
94392
  this.workers.splice(idx, 1);
94393
+ if (worker.restarting) {
94394
+ this.ticketsStarted = Math.max(0, this.ticketsStarted - 1);
94395
+ this.queue.unshift({ issue: issue2, mode: "resume" });
94396
+ this.deps.onWorkersChanged();
94397
+ this.spawnNext();
94398
+ return;
94399
+ }
94027
94400
  const ok = code === 0;
94028
94401
  this.deps.onLog(`${ok ? "\u2713" : "\u2717"} ${issue2.identifier} \u2192 ${prep.changeName} exited (code ${code})`, ok ? "green" : "red");
94029
94402
  capture("agent_worker_exited", {
@@ -94037,8 +94410,51 @@ class AgentCoordinator {
94037
94410
  this.spawnNext();
94038
94411
  });
94039
94412
  }
94413
+ async restartWorker(changeName) {
94414
+ if (this.stopped)
94415
+ return false;
94416
+ const worker = this.workers.find((w) => w.changeName === changeName);
94417
+ if (!worker)
94418
+ return false;
94419
+ if (worker.restarting)
94420
+ return true;
94421
+ worker.restarting = true;
94422
+ capture("agent_worker_restarted", {
94423
+ change_name: changeName,
94424
+ reason: "steering"
94425
+ });
94426
+ try {
94427
+ worker.kill();
94428
+ } catch {}
94429
+ return true;
94430
+ }
94040
94431
  async notifyExited(issue2, changeName, code, mode) {
94041
94432
  const ok = code === 0;
94433
+ if (this.deps.syncTasks && ok) {
94434
+ const synthetic = {
94435
+ changeName,
94436
+ issueId: issue2.id,
94437
+ issueIdentifier: issue2.identifier,
94438
+ issue: issue2,
94439
+ mode,
94440
+ kill: () => {},
94441
+ lastReportedIteration: 0,
94442
+ restarting: false
94443
+ };
94444
+ try {
94445
+ let iteration = 0;
94446
+ if (this.deps.getIterationCount) {
94447
+ try {
94448
+ iteration = await this.deps.getIterationCount(changeName);
94449
+ } catch {
94450
+ iteration = 0;
94451
+ }
94452
+ }
94453
+ await this.deps.syncTasks(synthetic, iteration);
94454
+ } catch (err) {
94455
+ this.deps.onLog(`! sync-tasks (done) failed for ${issue2.identifier}: ${err.message}`, "yellow");
94456
+ }
94457
+ }
94042
94458
  if (this.opts.postComments !== false) {
94043
94459
  const body = ok ? mode === "conflict-fix" ? `\u2705 Ralph resolved merge conflicts on this issue. Change: \`${changeName}\`` : `\u2705 Ralph completed work on this issue. Change: \`${changeName}\`` : `\u2717 Ralph exited with code ${code} on this issue. Change: \`${changeName}\`
94044
94460
 
@@ -94121,6 +94537,7 @@ var emptyPrStatus = () => ({ mergeable: 0, conflicted: 0, ciFailed: 0 }), emptyP
94121
94537
  prStatus: emptyPrStatus()
94122
94538
  });
94123
94539
  var init_coordinator = __esm(() => {
94540
+ init_queue_order();
94124
94541
  init_src();
94125
94542
  });
94126
94543
 
@@ -94149,6 +94566,7 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
94149
94566
  ""
94150
94567
  ])
94151
94568
  ] : [];
94569
+ const descriptionBody = issue2.description?.trim() || "_No description provided in Linear._";
94152
94570
  const proposal = [
94153
94571
  `# ${issue2.identifier}: ${issue2.title}`,
94154
94572
  "",
@@ -94157,9 +94575,17 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
94157
94575
  issue2.assignee ? `Assignee: ${issue2.assignee.name}` : "",
94158
94576
  issue2.labels.length ? `Labels: ${issue2.labels.join(", ")}` : "",
94159
94577
  "",
94578
+ "## Why",
94579
+ "",
94580
+ descriptionBody,
94581
+ "",
94582
+ "## What Changes",
94583
+ "",
94584
+ "_Describe the concrete changes this proposal introduces (one bullet per change)._",
94585
+ "",
94160
94586
  "## Description",
94161
94587
  "",
94162
- issue2.description?.trim() || "_No description provided in Linear._",
94588
+ descriptionBody,
94163
94589
  ...commentsBlock,
94164
94590
  ...appendPrompt.trim() ? ["", "## Additional instructions", "", appendPrompt.trim()] : [],
94165
94591
  "",
@@ -94176,8 +94602,10 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
94176
94602
  "",
94177
94603
  `- [ ] Read the Linear issue at ${issue2.url} and research the codebase to understand the mission and its scope`,
94178
94604
  `- [ ] Refine proposal.md with the problem statement, approach, and acceptance criteria derived from the research`,
94605
+ `- [ ] Fill in \`## Why\` and \`## What Changes\` in proposal.md so \`openspec validate\` passes (these sections are required by the validator)`,
94606
+ `- [ ] Add at least one spec delta under \`specs/<capability>/spec.md\` describing the behavior added/modified/removed by this change`,
94179
94607
  `- [ ] Fill in design.md with the technical design (files to touch, data flow, edge cases)`,
94180
- `- [ ] 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\`)`,
94608
+ `- [ ] Append an \`## Implementation\` section below with concrete mission-specific tasks derived from the plan, including tests and \`bun run lint\` / \`bun run test\`. Every item in the new section MUST start as \`- [ ]\` (unchecked) \u2014 do not pre-check items even if you already did the work during planning. The loop ticks them off in later iterations after each one is verified.`,
94181
94609
  ""
94182
94610
  ].join(`
94183
94611
  `);
@@ -94509,6 +94937,33 @@ async function createPullRequest(input, runner) {
94509
94937
 
94510
94938
  // apps/agent/src/agent/post-task.ts
94511
94939
  import { join as join20 } from "path";
94940
+ function summarizeUncommittedStatus(stdout) {
94941
+ const lines = stdout.split(`
94942
+ `).filter((line) => line.length > 0);
94943
+ const preview = lines.slice(0, 10);
94944
+ return { count: lines.length, preview, truncated: Math.max(0, lines.length - preview.length) };
94945
+ }
94946
+ async function findExistingOpenPrUrl(cmd, cwd2, branch) {
94947
+ try {
94948
+ const result2 = await cmd.run([
94949
+ "gh",
94950
+ "pr",
94951
+ "list",
94952
+ "--head",
94953
+ branch,
94954
+ "--state",
94955
+ "open",
94956
+ "--json",
94957
+ "url",
94958
+ "--jq",
94959
+ ".[0].url // empty"
94960
+ ], cwd2);
94961
+ const url2 = result2.stdout.trim();
94962
+ return url2 || null;
94963
+ } catch {
94964
+ return null;
94965
+ }
94966
+ }
94512
94967
  async function detectRepoAutoMergeAllowed(prUrl, cmd, cwd2, log2) {
94513
94968
  const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/\d+/.exec(prUrl);
94514
94969
  if (!m)
@@ -94552,7 +95007,7 @@ async function reactivateState(stateFilePath, log2, changeName) {
94552
95007
  }
94553
95008
  async function runWorkerWithFixTask(ctx, heading, body) {
94554
95009
  try {
94555
- await prependFixTask(join20(ctx.changeDir, "tasks.md"), heading, body);
95010
+ await prependFixTask(join20(ctx.changeDir, AGENT_TASKS_FILENAME), heading, body);
94556
95011
  } catch (err) {
94557
95012
  ctx.log(`! could not prepend fix task: ${err.message}`, "red");
94558
95013
  return 1;
@@ -94831,8 +95286,20 @@ async function runPrPhase(input, deps) {
94831
95286
  };
94832
95287
  try {
94833
95288
  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");
95289
+ const summary = summarizeUncommittedStatus(status.stdout);
95290
+ if (summary.count > 0) {
95291
+ const existingPrUrl = branch ? await findExistingOpenPrUrl(cmd, cwd2, branch) : null;
95292
+ const indented = summary.preview.map((line) => ` ${line}`).join(`
95293
+ `);
95294
+ const suffix = summary.truncated ? `
95295
+ ... and ${summary.truncated} more` : "";
95296
+ if (existingPrUrl) {
95297
+ log2(` ${changeName}: ${summary.count} uncommitted file(s) after worker \u2014 will retry next iteration:
95298
+ ${indented}${suffix}`, "gray");
95299
+ } else {
95300
+ 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:
95301
+ ${indented}${suffix}`, "yellow");
95302
+ }
94836
95303
  }
94837
95304
  } catch (err) {
94838
95305
  log2(`! git status check failed for ${changeName}: ${err.message}`, "yellow");
@@ -94987,14 +95454,370 @@ async function runPostTask(input, deps) {
94987
95454
  }
94988
95455
  var CI_FAILED_EXIT = 70, PR_FAILED_EXIT = 71, repoAutoMergeCache;
94989
95456
  var init_post_task = __esm(() => {
95457
+ init_tasks_md();
94990
95458
  init_ci();
94991
95459
  init_worktree();
94992
95460
  repoAutoMergeCache = new Map;
94993
95461
  });
94994
95462
 
95463
+ // apps/agent/src/agent/baseline/runner.ts
95464
+ import { createHash } from "crypto";
95465
+ async function runBaseline(input) {
95466
+ const { cmdRunner, gitRunner, cwd: cwd2, commands, baseBranch, outputCharLimit } = input;
95467
+ if (commands.length === 0) {
95468
+ return { ok: true, failures: [], fingerprint: "" };
95469
+ }
95470
+ try {
95471
+ await gitRunner.run(["fetch", "origin", baseBranch], cwd2);
95472
+ await gitRunner.run(["reset", "--hard", `origin/${baseBranch}`], cwd2);
95473
+ } catch (err) {
95474
+ const e = err;
95475
+ const failure = {
95476
+ command: `git checkout ${baseBranch}`,
95477
+ exitCode: -1,
95478
+ stdout: "",
95479
+ stderr: truncate3(e.stderr ?? e.message, outputCharLimit),
95480
+ fingerprint: fingerprintFor(`git checkout ${baseBranch}`, e.stderr ?? e.message)
95481
+ };
95482
+ return {
95483
+ ok: false,
95484
+ failures: [failure],
95485
+ fingerprint: failure.fingerprint
95486
+ };
95487
+ }
95488
+ const failures = [];
95489
+ for (const command of commands) {
95490
+ const parts = parseCommand(command);
95491
+ let stdout = "";
95492
+ let stderr = "";
95493
+ let exitCode = 0;
95494
+ try {
95495
+ const r = await cmdRunner.run(parts, cwd2);
95496
+ stdout = r.stdout;
95497
+ stderr = r.stderr;
95498
+ } catch (err) {
95499
+ const e = err;
95500
+ stdout = e.stdout ?? "";
95501
+ stderr = e.stderr ?? e.message;
95502
+ exitCode = typeof e.code === "number" ? e.code : 1;
95503
+ }
95504
+ if (exitCode !== 0) {
95505
+ failures.push({
95506
+ command,
95507
+ exitCode,
95508
+ stdout: truncate3(stdout, outputCharLimit),
95509
+ stderr: truncate3(stderr, outputCharLimit),
95510
+ fingerprint: fingerprintFor(command, stderr || stdout)
95511
+ });
95512
+ }
95513
+ }
95514
+ return {
95515
+ ok: failures.length === 0,
95516
+ failures,
95517
+ fingerprint: failures[0]?.fingerprint ?? ""
95518
+ };
95519
+ }
95520
+ function parseCommand(cmd) {
95521
+ return cmd.trim().split(/\s+/);
95522
+ }
95523
+ function truncate3(text, limit) {
95524
+ if (text.length <= limit)
95525
+ return text;
95526
+ return text.slice(0, limit) + `
95527
+ \u2026(truncated ${text.length - limit} chars)`;
95528
+ }
95529
+ function fingerprintFor(command, output) {
95530
+ const firstLine = (output || "").split(`
95531
+ `).find((l) => l.trim().length > 0) ?? "";
95532
+ return createHash("sha1").update(`${command}
95533
+ ${firstLine.trim()}`).digest("hex").slice(0, 12);
95534
+ }
95535
+ var init_runner = () => {};
95536
+
95537
+ // apps/agent/src/agent/baseline/gate.ts
95538
+ async function runBaselineGate(deps) {
95539
+ if (!deps.enabled)
95540
+ return;
95541
+ if (deps.commands.length === 0) {
95542
+ deps.onLog(" baseline check skipped \u2014 no commands configured", "gray");
95543
+ return;
95544
+ }
95545
+ const result2 = await runBaseline({
95546
+ cmdRunner: deps.cmdRunner,
95547
+ gitRunner: deps.gitRunner,
95548
+ cwd: deps.cwd,
95549
+ commands: deps.commands,
95550
+ baseBranch: deps.baseBranch,
95551
+ outputCharLimit: deps.outputCharLimit
95552
+ });
95553
+ if (result2.ok) {
95554
+ if (deps.coordinator.isPaused()) {
95555
+ deps.coordinator.clearPaused();
95556
+ deps.onLog("\u2713 baseline recovered \u2014 resuming new pickups", "green");
95557
+ }
95558
+ return;
95559
+ }
95560
+ const firstFailure = result2.failures[0];
95561
+ const description = renderIssueBody(result2, deps.outputCharLimit);
95562
+ const title = `Pre-existing baseline error on ${deps.baseBranch}: \`${firstFailure.command}\``;
95563
+ let issueIdentifier = "BASELINE";
95564
+ let issueId;
95565
+ if (deps.linear) {
95566
+ try {
95567
+ const existing = await deps.linear.findOpen();
95568
+ if (existing) {
95569
+ const existingFp = extractFingerprint(existing.description ?? "");
95570
+ if (existingFp !== result2.fingerprint) {
95571
+ await deps.linear.updateDescription(existing.id, description);
95572
+ deps.onLog(` baseline ticket ${existing.identifier} updated (fingerprint changed)`, "yellow");
95573
+ }
95574
+ issueIdentifier = existing.identifier;
95575
+ issueId = existing.id;
95576
+ } else {
95577
+ const created = await deps.linear.create(title, description);
95578
+ issueIdentifier = created.identifier;
95579
+ issueId = created.id;
95580
+ deps.onLog(` baseline ticket ${created.identifier} created`, "yellow");
95581
+ }
95582
+ } catch (err) {
95583
+ deps.onLog(`! Linear baseline ticket sync failed: ${err.message}`, "red");
95584
+ }
95585
+ } else {
95586
+ deps.onLog("! baseline failed but no Linear client configured \u2014 pausing without ticket", "yellow");
95587
+ }
95588
+ const since = deps.coordinator.isPaused() ? deps.coordinator.getPause()?.since ?? deps.now?.() ?? Date.now() : deps.now?.() ?? Date.now();
95589
+ deps.coordinator.setPaused({
95590
+ issueIdentifier,
95591
+ ...issueId !== undefined ? { issueId } : {},
95592
+ command: firstFailure.command,
95593
+ fingerprint: result2.fingerprint,
95594
+ since
95595
+ });
95596
+ }
95597
+ function extractFingerprint(body) {
95598
+ const m = FINGERPRINT_MARKER_RE.exec(body);
95599
+ return m?.[1] ?? null;
95600
+ }
95601
+ function renderIssueBody(result2, outputCharLimit) {
95602
+ const lines = [];
95603
+ lines.push(`<!-- ralphy:baseline:${result2.fingerprint} -->`);
95604
+ lines.push("");
95605
+ lines.push("Ralph detected a failing command on the base branch.");
95606
+ lines.push("");
95607
+ lines.push("New issues will not be picked up by Ralph until this is resolved.");
95608
+ lines.push("Mark this Linear issue as Done to lift the pause.");
95609
+ lines.push("");
95610
+ for (const f2 of result2.failures) {
95611
+ lines.push(`### \`${f2.command}\` \u2014 exit ${f2.exitCode}`);
95612
+ lines.push("");
95613
+ if (f2.stdout.trim()) {
95614
+ lines.push("**stdout:**");
95615
+ lines.push("```");
95616
+ lines.push(truncateForBody(f2.stdout, outputCharLimit));
95617
+ lines.push("```");
95618
+ }
95619
+ if (f2.stderr.trim()) {
95620
+ lines.push("**stderr:**");
95621
+ lines.push("```");
95622
+ lines.push(truncateForBody(f2.stderr, outputCharLimit));
95623
+ lines.push("```");
95624
+ }
95625
+ lines.push("");
95626
+ }
95627
+ return lines.join(`
95628
+ `);
95629
+ }
95630
+ function truncateForBody(text, limit) {
95631
+ if (text.length <= limit)
95632
+ return text;
95633
+ return text.slice(0, limit) + `
95634
+ \u2026(truncated)`;
95635
+ }
95636
+ var FINGERPRINT_MARKER_RE;
95637
+ var init_gate = __esm(() => {
95638
+ init_runner();
95639
+ FINGERPRINT_MARKER_RE = /<!--\s*ralphy:baseline:([a-f0-9]+)\s*-->/i;
95640
+ });
95641
+
95642
+ // apps/agent/src/agent/linear-sync/index.ts
95643
+ function parseTasksMd(md) {
95644
+ const lines = md.split(/\r?\n/);
95645
+ const sections = [];
95646
+ let current = null;
95647
+ let i = 0;
95648
+ while (i < lines.length) {
95649
+ const line = lines[i];
95650
+ const headingMatch = /^##\s+(.+?)\s*$/.exec(line);
95651
+ if (headingMatch) {
95652
+ current = { heading: headingMatch[1], items: [] };
95653
+ sections.push(current);
95654
+ i += 1;
95655
+ continue;
95656
+ }
95657
+ const bulletMatch = /^(\s*)-\s+\[( |x|X)\]\s+(.+?)\s*$/.exec(line);
95658
+ if (bulletMatch && current) {
95659
+ const indent = bulletMatch[1] ?? "";
95660
+ const checked = bulletMatch[2]?.toLowerCase() === "x";
95661
+ const text = bulletMatch[3] ?? "";
95662
+ const bullet = `${indent}- [${checked ? "x" : " "}] ${text}`;
95663
+ i += 1;
95664
+ let j = i;
95665
+ while (j < lines.length && lines[j].trim() === "")
95666
+ j += 1;
95667
+ let code;
95668
+ if (j < lines.length && /^\s*```/.test(lines[j])) {
95669
+ const fenceOpen = lines[j];
95670
+ const fenceMatch = /^(\s*)```/.exec(fenceOpen);
95671
+ const fenceIndent = fenceMatch?.[1] ?? "";
95672
+ const buf = [];
95673
+ j += 1;
95674
+ while (j < lines.length) {
95675
+ if (new RegExp(`^${fenceIndent}\`\`\`\\s*$`).test(lines[j])) {
95676
+ j += 1;
95677
+ break;
95678
+ }
95679
+ buf.push(lines[j]);
95680
+ j += 1;
95681
+ }
95682
+ code = buf.join(`
95683
+ `);
95684
+ i = j;
95685
+ }
95686
+ current.items.push(code !== undefined ? { bullet, code } : { bullet });
95687
+ continue;
95688
+ }
95689
+ i += 1;
95690
+ }
95691
+ return sections;
95692
+ }
95693
+ function truncate4(s, max2) {
95694
+ if (s.length <= max2)
95695
+ return s;
95696
+ return `${s.slice(0, max2)}
95697
+ \u2026(truncated)`;
95698
+ }
95699
+ function renderTasksBlock(tasksMd, meta3) {
95700
+ const sections = parseTasksMd(tasksMd);
95701
+ const out = [];
95702
+ out.push(RALPHY_TASKS_START);
95703
+ out.push("### Ralph progress");
95704
+ out.push("");
95705
+ for (const section of sections) {
95706
+ if (section.items.length === 0)
95707
+ continue;
95708
+ out.push(`**${section.heading}**`);
95709
+ out.push("");
95710
+ for (const item of section.items) {
95711
+ out.push(item.bullet);
95712
+ if (item.code !== undefined) {
95713
+ const inner = truncate4(item.code, MAX_CODE_BLOCK_BYTES);
95714
+ out.push(` <details><summary>output</summary><pre>${inner}</pre></details>`);
95715
+ }
95716
+ }
95717
+ out.push("");
95718
+ }
95719
+ out.push(`<sub>\`${meta3.changeName}\` \xB7 iteration ${meta3.iteration}</sub>`);
95720
+ out.push(RALPHY_TASKS_END);
95721
+ return out.join(`
95722
+ `);
95723
+ }
95724
+ function applyTasksBlock(existingDescription, block) {
95725
+ const existing = existingDescription ?? "";
95726
+ const startIdx = existing.indexOf(RALPHY_TASKS_START);
95727
+ const endIdx = startIdx >= 0 ? existing.indexOf(RALPHY_TASKS_END, startIdx + RALPHY_TASKS_START.length) : -1;
95728
+ if (startIdx >= 0 && endIdx >= 0) {
95729
+ const before2 = existing.slice(0, startIdx);
95730
+ const after2 = existing.slice(endIdx + RALPHY_TASKS_END.length);
95731
+ return `${before2}${block}${after2}`;
95732
+ }
95733
+ if (existing.length === 0)
95734
+ return block;
95735
+ const trimmed = existing.replace(/\s+$/, "");
95736
+ return `${trimmed}
95737
+
95738
+ ${block}`;
95739
+ }
95740
+ async function syncTasksToLinearDescription(deps) {
95741
+ const file2 = Bun.file(deps.tasksPath);
95742
+ if (!await file2.exists()) {
95743
+ deps.log(` sync-tasks: tasks.md missing at ${deps.tasksPath}, skipping`, "gray");
95744
+ return null;
95745
+ }
95746
+ let tasksMd;
95747
+ try {
95748
+ tasksMd = await file2.text();
95749
+ } catch (err) {
95750
+ deps.log(`! sync-tasks: read failed for ${deps.tasksPath}: ${err.message}`, "yellow");
95751
+ return null;
95752
+ }
95753
+ const block = renderTasksBlock(tasksMd, {
95754
+ changeName: deps.changeName,
95755
+ iteration: deps.iteration
95756
+ });
95757
+ if (block.length > MAX_BLOCK_BYTES) {
95758
+ deps.log(`! sync-tasks: rendered block exceeds ${MAX_BLOCK_BYTES} bytes (${block.length}), skipping update`, "yellow");
95759
+ return null;
95760
+ }
95761
+ const next = applyTasksBlock(deps.currentDescription, block);
95762
+ if (next === (deps.currentDescription ?? ""))
95763
+ return null;
95764
+ try {
95765
+ await deps.updateIssueDescription(deps.apiKey, deps.issueId, next);
95766
+ deps.log(` sync-tasks: updated Linear description for ${deps.changeName}`, "gray");
95767
+ return next;
95768
+ } catch (err) {
95769
+ deps.log(`! sync-tasks: updateIssueDescription failed: ${err.message}`, "yellow");
95770
+ return null;
95771
+ }
95772
+ }
95773
+ var RALPHY_TASKS_START = "<!-- ralphy:tasks:start -->", RALPHY_TASKS_END = "<!-- ralphy:tasks:end -->", MAX_BLOCK_BYTES, MAX_CODE_BLOCK_BYTES;
95774
+ var init_linear_sync = __esm(() => {
95775
+ MAX_BLOCK_BYTES = 60 * 1024;
95776
+ MAX_CODE_BLOCK_BYTES = 2 * 1024;
95777
+ });
95778
+
94995
95779
  // apps/agent/src/agent/wire.ts
94996
95780
  import { join as join21 } from "path";
94997
95781
  import { mkdir as mkdir6 } from "fs/promises";
95782
+ async function pickOpenPrUrlFromAttachments(urls, issueIdent, cmd, cwd2, onLog) {
95783
+ const candidates = urls.filter((url2) => GITHUB_PR_URL_RE.test(url2));
95784
+ let sawNonOpenPr = false;
95785
+ for (const url2 of candidates) {
95786
+ try {
95787
+ const res = await cmd.run(["gh", "pr", "view", url2, "--json", "state"], cwd2);
95788
+ const parsed = JSON.parse(res.stdout.trim());
95789
+ if (parsed.state === "OPEN")
95790
+ return { url: url2, sawNonOpenPr };
95791
+ if (parsed.state === "MERGED" || parsed.state === "CLOSED")
95792
+ sawNonOpenPr = true;
95793
+ } catch (err) {
95794
+ onLog(`! gh pr view ${url2} failed for ${issueIdent}: ${err.message}`, "yellow");
95795
+ }
95796
+ }
95797
+ return { url: null, sawNonOpenPr };
95798
+ }
95799
+ function githubReactionSlug(emoji3) {
95800
+ switch (emoji3) {
95801
+ case "\uD83D\uDC40":
95802
+ return "eyes";
95803
+ case "\uD83D\uDC4D":
95804
+ return "+1";
95805
+ case "\uD83D\uDC4E":
95806
+ return "-1";
95807
+ case "\u2764\uFE0F":
95808
+ return "heart";
95809
+ case "\uD83C\uDF89":
95810
+ return "hooray";
95811
+ case "\uD83D\uDE80":
95812
+ return "rocket";
95813
+ case "\uD83D\uDE04":
95814
+ return "laugh";
95815
+ case "\uD83D\uDE15":
95816
+ return "confused";
95817
+ default:
95818
+ return emoji3;
95819
+ }
95820
+ }
94998
95821
  function traceCmdRunner(base2, onStart, onEnd) {
94999
95822
  return {
95000
95823
  run: async (cmd, cwd2) => {
@@ -95111,6 +95934,7 @@ function buildAgentCoordinator(input) {
95111
95934
  tasksDir,
95112
95935
  apiKey,
95113
95936
  onLog,
95937
+ onFileLog,
95114
95938
  onWorkersChanged,
95115
95939
  onWorkerStarted,
95116
95940
  onWorkerExited,
@@ -95275,21 +96099,23 @@ function buildAgentCoordinator(input) {
95275
96099
  return { workerCwd, scaffoldTasksDir, scaffoldStatesDir, branch };
95276
96100
  const probeName = issue2.identifier.toLowerCase();
95277
96101
  const baseBranch = baseBranchFromLabels(issue2.labels) ?? cfg.prBaseBranch;
96102
+ let wt;
95278
96103
  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
- }
96104
+ wt = await createWorktree(projectRoot, probeName, baseBranch, gitRunner);
95291
96105
  } catch (err) {
95292
- onLog(`! worktree create failed for ${issue2.identifier}: ${err.message} \u2014 falling back to project root`, "yellow");
96106
+ onLog(`! worktree create failed for ${issue2.identifier}: ${err.message} \u2014 skipping (useWorktree is required)`, "red");
96107
+ throw err;
96108
+ }
96109
+ workerCwd = wt.cwd;
96110
+ branch = wt.branch;
96111
+ const wtLayout = projectLayout(wt.cwd);
96112
+ scaffoldTasksDir = wtLayout.tasksDir;
96113
+ scaffoldStatesDir = wtLayout.statesDir;
96114
+ onLog(` ${issue2.identifier} worktree: ${wt.cwd} (${wt.branch})`, "gray");
96115
+ try {
96116
+ await seedWorktreeMcpConfig(projectRoot, wt.cwd);
96117
+ } catch (err) {
96118
+ onLog(`! seeding .mcp.json failed for ${issue2.identifier}: ${err.message}`, "yellow");
95293
96119
  }
95294
96120
  return { workerCwd, scaffoldTasksDir, scaffoldStatesDir, branch };
95295
96121
  }
@@ -95312,7 +96138,8 @@ function buildAgentCoordinator(input) {
95312
96138
  identifier: issue2.identifier,
95313
96139
  title: issue2.title,
95314
96140
  description: issue2.description ?? "",
95315
- url: issue2.url
96141
+ url: issue2.url,
96142
+ labels: issue2.labels
95316
96143
  },
95317
96144
  attempt: 1,
95318
96145
  last_error: ""
@@ -95337,7 +96164,7 @@ function buildAgentCoordinator(input) {
95337
96164
  branchByChange.set(changeName, branch);
95338
96165
  if (mode === "review") {
95339
96166
  const wtLayout = projectLayout(workerCwd);
95340
- const tasksFile = join21(wtLayout.changeDir(changeName), "tasks.md");
96167
+ const tasksFile = join21(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
95341
96168
  let body;
95342
96169
  let heading;
95343
96170
  if (trigger) {
@@ -95362,7 +96189,7 @@ function buildAgentCoordinator(input) {
95362
96189
  await reactivateState2(wtLayout.stateFile(changeName), changeName);
95363
96190
  } else if (mode === "conflict-fix") {
95364
96191
  const wtLayout = projectLayout(workerCwd);
95365
- const tasksFile = join21(wtLayout.changeDir(changeName), "tasks.md");
96192
+ const tasksFile = join21(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
95366
96193
  const prUrl = prByChange.get(changeName);
95367
96194
  const body = [
95368
96195
  `The PR for this change has merge conflicts with \`${cfg.prBaseBranch}\`.`,
@@ -95501,6 +96328,11 @@ PR: ${prUrl}` : ""
95501
96328
  function spawnWorker(changeName) {
95502
96329
  const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
95503
96330
  const injected = input.runners?.spawnWorker;
96331
+ const missionTasksPath = join21(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
96332
+ const prevTasksPromise = (async () => {
96333
+ const f2 = Bun.file(missionTasksPath);
96334
+ return await f2.exists() ? await f2.text() : "";
96335
+ })();
95504
96336
  let logFilePath;
95505
96337
  let handle;
95506
96338
  if (injected) {
@@ -95526,6 +96358,21 @@ PR: ${prUrl}` : ""
95526
96358
  const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
95527
96359
  const wrapped = handle.exited.then(async (code) => {
95528
96360
  const workerLayout = projectLayout(cwd2);
96361
+ try {
96362
+ const prevTasks = await prevTasksPromise;
96363
+ const nextFile = Bun.file(missionTasksPath);
96364
+ if (await nextFile.exists()) {
96365
+ const nextTasks = await nextFile.text();
96366
+ const report = normalizeNewlyAppendedSectionWithReport(prevTasks, nextTasks);
96367
+ if (report.text !== nextTasks) {
96368
+ await Bun.write(missionTasksPath, report.text);
96369
+ const sections = report.headings.map((h) => `## ${h}`).join(", ");
96370
+ onLog(`! normalized ${report.count} pre-checked item(s) in newly added section(s) ${sections}`, "yellow");
96371
+ }
96372
+ }
96373
+ } catch (err) {
96374
+ onLog(`! tasks.md normalization failed: ${err.message}`, "yellow");
96375
+ }
95529
96376
  const effectiveCode = await runPostTask({
95530
96377
  changeName,
95531
96378
  cwd: cwd2,
@@ -95681,9 +96528,13 @@ PR: ${prUrl}` : ""
95681
96528
  if (byBranch)
95682
96529
  return byBranch;
95683
96530
  const fromLinear = await discoverPrUrlFromLinear(issue2);
95684
- if (fromLinear) {
95685
- onLog(` ${issue2.identifier}: PR discovered via Linear attachment (${fromLinear})`, "gray");
95686
- return fromLinear;
96531
+ if (fromLinear.url) {
96532
+ onLog(` ${issue2.identifier}: PR discovered via Linear attachment (${fromLinear.url})`, "gray");
96533
+ return fromLinear.url;
96534
+ }
96535
+ if (fromLinear.sawNonOpenPr) {
96536
+ markPrUnavailable(changeName);
96537
+ return null;
95687
96538
  }
95688
96539
  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
96540
  markPrUnavailable(changeName);
@@ -95729,14 +96580,14 @@ PR: ${prUrl}` : ""
95729
96580
  return null;
95730
96581
  }
95731
96582
  async function discoverPrUrlFromLinear(issue2) {
96583
+ let attachments;
95732
96584
  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;
96585
+ attachments = await fetchIssueAttachments(apiKey, issue2.id);
95736
96586
  } catch (err) {
95737
96587
  onLog(`! Linear attachments fetch failed for ${issue2.identifier}: ${err.message}`, "yellow");
95738
- return null;
96588
+ return { url: null, sawNonOpenPr: false };
95739
96589
  }
96590
+ return pickOpenPrUrlFromAttachments(attachments.map((a) => a.url), issue2.identifier, cmdRunner, projectRoot, onLog);
95740
96591
  }
95741
96592
  async function fetchDoneCandidates() {
95742
96593
  if (!indicators.setDone)
@@ -95755,9 +96606,9 @@ PR: ${prUrl}` : ""
95755
96606
  const handle = cfg.linear.mentionHandle;
95756
96607
  let candidates = [];
95757
96608
  try {
95758
- candidates = await fetchDoneCandidates();
96609
+ candidates = await fetchMentionScanIssues(apiKey, { team, assignee });
95759
96610
  } catch (err) {
95760
- onLog(`! mention scan: fetchDoneCandidates failed: ${err.message}`, "yellow");
96611
+ onLog(`! mention scan: fetchMentionScanIssues failed: ${err.message}`, "yellow");
95761
96612
  return [];
95762
96613
  }
95763
96614
  const out = [];
@@ -95789,6 +96640,11 @@ PR: ${prUrl}` : ""
95789
96640
  url: issue2.url
95790
96641
  }
95791
96642
  });
96643
+ try {
96644
+ await addReactionToComment(apiKey, c.id, "\uD83D\uDC40");
96645
+ } catch (err) {
96646
+ onLog(`! mention scan: Linear reaction failed for ${issue2.identifier}: ${err.message}`, "yellow");
96647
+ }
95792
96648
  queued.add(issue2.id);
95793
96649
  break;
95794
96650
  }
@@ -95800,6 +96656,7 @@ PR: ${prUrl}` : ""
95800
96656
  continue;
95801
96657
  if (wantMention) {
95802
96658
  const ghComments = await fetchPrIssueComments(prUrl);
96659
+ const prMatch = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/\d+/.exec(prUrl);
95803
96660
  for (const c of ghComments) {
95804
96661
  if (!containsHandle(c.body, handle))
95805
96662
  continue;
@@ -95815,6 +96672,14 @@ PR: ${prUrl}` : ""
95815
96672
  url: c.url
95816
96673
  }
95817
96674
  });
96675
+ if (prMatch) {
96676
+ const [, owner, repo] = prMatch;
96677
+ try {
96678
+ await addGithubReactionToComment({ owner, repo, kind: "issue" }, c.id, "\uD83D\uDC40");
96679
+ } catch (err) {
96680
+ onLog(`! mention scan: GitHub reaction failed for ${prUrl}: ${err.message}`, "yellow");
96681
+ }
96682
+ }
95818
96683
  queued.add(issue2.id);
95819
96684
  break;
95820
96685
  }
@@ -95982,6 +96847,11 @@ PR: ${prUrl}` : ""
95982
96847
  prByChange.set(changeName, found);
95983
96848
  return found;
95984
96849
  }
96850
+ async function addGithubReactionToComment(source, commentId, emoji3) {
96851
+ const content = githubReactionSlug(emoji3);
96852
+ const path = source.kind === "issue" ? `repos/${source.owner}/${source.repo}/issues/comments/${commentId}/reactions` : `repos/${source.owner}/${source.repo}/pulls/comments/${commentId}/reactions`;
96853
+ await cmdRunner.run(["gh", "api", "-X", "POST", path, "-f", `content=${content}`], projectRoot);
96854
+ }
95985
96855
  async function fetchPrIssueComments(prUrl) {
95986
96856
  const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/.exec(prUrl);
95987
96857
  if (!m)
@@ -95993,7 +96863,7 @@ PR: ${prUrl}` : ""
95993
96863
  "api",
95994
96864
  `repos/${owner}/${repo}/issues/${num}/comments`,
95995
96865
  "--jq",
95996
- "[.[] | {body: .body, createdAt: .created_at, author: .user.login, url: .html_url}]"
96866
+ "[.[] | {id: .id, body: .body, createdAt: .created_at, author: .user.login, url: .html_url}]"
95997
96867
  ], projectRoot);
95998
96868
  const parsed = JSON.parse(res.stdout || "[]");
95999
96869
  return parsed;
@@ -96020,6 +96890,7 @@ PR: ${prUrl}` : ""
96020
96890
  },
96021
96891
  checkPrStatus,
96022
96892
  onLog,
96893
+ ...onFileLog ? { onFileLog } : {},
96023
96894
  onWorkersChanged,
96024
96895
  getIterationCount: async (changeName) => {
96025
96896
  const root = cwdByChange.get(changeName) ?? projectRoot;
@@ -96028,7 +96899,29 @@ PR: ${prUrl}` : ""
96028
96899
  return 0;
96029
96900
  const json2 = await file2.json();
96030
96901
  return json2.iteration ?? 0;
96031
- }
96902
+ },
96903
+ ...cfg.linear.syncTasksToDescription && apiKey ? {
96904
+ syncTasks: async (worker, iteration) => {
96905
+ const root = cwdByChange.get(worker.changeName) ?? projectRoot;
96906
+ const tasksPath = join21(projectLayout(root).changeDir(worker.changeName), "tasks.md");
96907
+ const cachedIssue = issueByChange.get(worker.changeName) ?? worker.issue;
96908
+ const next = await syncTasksToLinearDescription({
96909
+ apiKey,
96910
+ issueId: worker.issueId,
96911
+ currentDescription: cachedIssue.description,
96912
+ tasksPath,
96913
+ changeName: worker.changeName,
96914
+ iteration,
96915
+ log: onLog,
96916
+ updateIssueDescription
96917
+ });
96918
+ if (next !== null) {
96919
+ const updated = { ...cachedIssue, description: next };
96920
+ issueByChange.set(worker.changeName, updated);
96921
+ worker.issue = updated;
96922
+ }
96923
+ }
96924
+ } : {}
96032
96925
  }, {
96033
96926
  concurrency,
96034
96927
  ...indicators.setInProgress !== undefined ? { setInProgress: indicators.setInProgress } : {},
@@ -96037,17 +96930,66 @@ PR: ${prUrl}` : ""
96037
96930
  ...indicators.setConflicted !== undefined ? { setConflicted: indicators.setConflicted } : {},
96038
96931
  ...indicators.clearConflicted !== undefined ? { clearConflicted: indicators.clearConflicted } : {},
96039
96932
  ...indicators.clearReview !== undefined ? { clearReview: indicators.clearReview } : {},
96933
+ ...indicators.getAutoMerge !== undefined ? { getAutoMerge: indicators.getAutoMerge } : {},
96040
96934
  postComments: cfg.linear.postComments,
96041
96935
  commentEveryIterations: cfg.linear.updateEveryIterations,
96042
96936
  ...args.maxTickets > 0 ? { maxTickets: args.maxTickets } : {}
96043
96937
  });
96044
96938
  const filterDesc = describeIndicators(indicators, team, assignee);
96939
+ const baselineCfg = cfg.preExistingErrorCheck;
96940
+ const baselineCommands = resolveBaselineCommands(cfg);
96941
+ const baselineEnabled = (args.preExistingErrorCheck ?? baselineCfg.enabled) === true;
96942
+ const baselineTeam = team;
96943
+ const runBaselineGateOnce = async () => {
96944
+ if (!baselineEnabled)
96945
+ return;
96946
+ await runBaselineGate({
96947
+ enabled: true,
96948
+ commands: baselineCommands,
96949
+ baseBranch: baselineCfg.baseBranch,
96950
+ outputCharLimit: baselineCfg.outputCharLimit,
96951
+ cwd: projectRoot,
96952
+ cmdRunner,
96953
+ gitRunner,
96954
+ coordinator: coord,
96955
+ ...baselineTeam && apiKey ? {
96956
+ linear: {
96957
+ findOpen: () => findOpenIssueByLabel(apiKey, baselineTeam, baselineCfg.label),
96958
+ create: async (title, description) => {
96959
+ const teamId = await fetchTeamIdByKey(apiKey, baselineTeam);
96960
+ if (!teamId)
96961
+ throw new Error("Linear team not found");
96962
+ let labelIds;
96963
+ try {
96964
+ const labelId = await resolveLabelIdForTeam(baselineTeam, baselineCfg.label);
96965
+ if (labelId)
96966
+ labelIds = [labelId];
96967
+ } catch {}
96968
+ return createIssue(apiKey, {
96969
+ teamId,
96970
+ title,
96971
+ description,
96972
+ ...labelIds ? { labelIds } : {}
96973
+ });
96974
+ },
96975
+ updateDescription: (id, description) => updateIssueDescription(apiKey, id, description)
96976
+ }
96977
+ } : {},
96978
+ onLog
96979
+ });
96980
+ };
96981
+ async function resolveLabelIdForTeam(teamKey, labelName) {
96982
+ const fakeIssue = { identifier: `${teamKey}-0` };
96983
+ return resolveLabelId(fakeIssue, labelName);
96984
+ }
96045
96985
  return {
96046
96986
  coord,
96047
96987
  filterDesc,
96048
96988
  concurrency,
96049
96989
  pollInterval,
96050
- getWorkerCwd: (changeName) => cwdByChange.get(changeName)
96990
+ getWorkerCwd: (changeName) => cwdByChange.get(changeName),
96991
+ syncTasksEnabled: Boolean(cfg.linear.syncTasksToDescription && apiKey),
96992
+ runBaselineGate: runBaselineGateOnce
96051
96993
  };
96052
96994
  }
96053
96995
  function describeIndicators(indicators, team, assignee) {
@@ -96068,10 +97010,11 @@ function describeIndicators(indicators, team, assignee) {
96068
97010
  }
96069
97011
  return parts.join(", ");
96070
97012
  }
96071
- var bunGitRunner, bunCmdRunner;
97013
+ var GITHUB_PR_URL_RE, bunGitRunner, bunCmdRunner;
96072
97014
  var init_wire = __esm(() => {
96073
97015
  init_log();
96074
97016
  init_layout();
97017
+ init_tasks_md();
96075
97018
  init_workflow();
96076
97019
  init_types2();
96077
97020
  init_coordinator();
@@ -96079,6 +97022,10 @@ var init_wire = __esm(() => {
96079
97022
  init_worktree();
96080
97023
  init_ci();
96081
97024
  init_post_task();
97025
+ init_gate();
97026
+ init_workflow();
97027
+ init_linear_sync();
97028
+ GITHUB_PR_URL_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/;
96082
97029
  bunGitRunner = {
96083
97030
  run: async (args, cwd2) => {
96084
97031
  const proc = Bun.spawn({ cmd: ["git", ...args], cwd: cwd2, stdout: "pipe", stderr: "pipe" });
@@ -96155,15 +97102,286 @@ function deriveOpenSpecPhase(inputs) {
96155
97102
  return "implement";
96156
97103
  return "tasks";
96157
97104
  }
97105
+ function shouldShowPhasePipeline(phase) {
97106
+ return phase === "proposal" || phase === "design" || phase === "tasks";
97107
+ }
97108
+ function shouldShowSubtasksPanel(phase, showPendingTasks, hasSubtasks) {
97109
+ if (!showPendingTasks || !hasSubtasks)
97110
+ return false;
97111
+ return phase == null || phase === "implement" || phase === "done";
97112
+ }
97113
+ function shouldShowProgressBar(phase, showPendingTasks, hasProgress) {
97114
+ if (showPendingTasks || !hasProgress)
97115
+ return false;
97116
+ return phase == null || phase === "implement" || phase === "done";
97117
+ }
97118
+ function phasePipeline(phase) {
97119
+ if (phase === "done") {
97120
+ return PIPELINE_PHASES.map((p) => ({ phase: p, label: p, status: "done" }));
97121
+ }
97122
+ const idx = PIPELINE_PHASES.indexOf(phase);
97123
+ return PIPELINE_PHASES.map((p, i) => ({
97124
+ phase: p,
97125
+ label: p,
97126
+ status: i < idx ? "done" : i === idx ? "current" : "pending"
97127
+ }));
97128
+ }
97129
+ var PIPELINE_PHASES;
97130
+ var init_phase = __esm(() => {
97131
+ PIPELINE_PHASES = [
97132
+ "proposal",
97133
+ "design",
97134
+ "tasks",
97135
+ "implement"
97136
+ ];
97137
+ });
97138
+
97139
+ // apps/agent/src/hooks/useTerminalSize.ts
97140
+ function readSize2() {
97141
+ return {
97142
+ columns: process.stdout.columns ?? 80,
97143
+ rows: process.stdout.rows ?? 24
97144
+ };
97145
+ }
97146
+ function useTerminalSize2() {
97147
+ const [size2, setSize] = import_react59.useState(() => ({
97148
+ ...readSize2(),
97149
+ resizeKey: 0
97150
+ }));
97151
+ import_react59.useEffect(() => {
97152
+ if (!process.stdout.isTTY)
97153
+ return;
97154
+ const onResize = () => {
97155
+ const { columns, rows } = readSize2();
97156
+ setSize((prev) => {
97157
+ if (prev.columns === columns && prev.rows === rows)
97158
+ return prev;
97159
+ return { columns, rows, resizeKey: prev.resizeKey + 1 };
97160
+ });
97161
+ };
97162
+ process.stdout.on("resize", onResize);
97163
+ return () => {
97164
+ process.stdout.off("resize", onResize);
97165
+ };
97166
+ }, []);
97167
+ return size2;
97168
+ }
97169
+ var import_react59;
97170
+ var init_useTerminalSize2 = __esm(() => {
97171
+ import_react59 = __toESM(require_react(), 1);
97172
+ });
97173
+
97174
+ // apps/agent/src/components/SteeringField.tsx
97175
+ function reducer2(state, action) {
97176
+ switch (action.type) {
97177
+ case "toggleFocus":
97178
+ return { ...state, focused: !state.focused };
97179
+ case "clearAndBlur":
97180
+ return { ...state, buffer: "", cursor: 0, focused: false };
97181
+ case "insert": {
97182
+ const before2 = state.buffer.slice(0, state.cursor);
97183
+ const after2 = state.buffer.slice(state.cursor);
97184
+ return {
97185
+ ...state,
97186
+ buffer: before2 + action.chars + after2,
97187
+ cursor: state.cursor + action.chars.length
97188
+ };
97189
+ }
97190
+ case "backspace": {
97191
+ if (state.cursor === 0)
97192
+ return state;
97193
+ return {
97194
+ ...state,
97195
+ buffer: state.buffer.slice(0, state.cursor - 1) + state.buffer.slice(state.cursor),
97196
+ cursor: state.cursor - 1
97197
+ };
97198
+ }
97199
+ case "moveLeft":
97200
+ return { ...state, cursor: Math.max(0, state.cursor - 1) };
97201
+ case "moveRight":
97202
+ return { ...state, cursor: Math.min(state.buffer.length, state.cursor + 1) };
97203
+ case "status":
97204
+ return { ...state, status: action.value };
97205
+ }
97206
+ }
97207
+ function SteeringField({
97208
+ active,
97209
+ width,
97210
+ onSubmit,
97211
+ onFocusChange,
97212
+ initialBuffer = "",
97213
+ initialCursor,
97214
+ initialFocused = false,
97215
+ onStateChange
97216
+ }) {
97217
+ const [state, dispatch] = import_react60.useReducer(reducer2, { initialBuffer, initialCursor, initialFocused }, (init2) => ({
97218
+ buffer: init2.initialBuffer,
97219
+ cursor: init2.initialCursor ?? init2.initialBuffer.length,
97220
+ focused: init2.initialFocused,
97221
+ status: "idle"
97222
+ }));
97223
+ const { buffer, cursor: cursor4, focused, status } = state;
97224
+ const stateRef = import_react60.useRef(state);
97225
+ stateRef.current = state;
97226
+ const hintTimerRef = import_react60.useRef(null);
97227
+ import_react60.useEffect(() => {
97228
+ onFocusChange?.(focused);
97229
+ }, [focused, onFocusChange]);
97230
+ import_react60.useEffect(() => {
97231
+ onStateChange?.({ buffer, cursor: cursor4, focused });
97232
+ }, [buffer, cursor4, focused, onStateChange]);
97233
+ import_react60.useEffect(() => {
97234
+ return () => {
97235
+ if (hintTimerRef.current)
97236
+ clearTimeout(hintTimerRef.current);
97237
+ };
97238
+ }, []);
97239
+ function flashStatus(next) {
97240
+ dispatch({ type: "status", value: next });
97241
+ if (hintTimerRef.current)
97242
+ clearTimeout(hintTimerRef.current);
97243
+ hintTimerRef.current = setTimeout(() => dispatch({ type: "status", value: "idle" }), STATUS_HINT_MS);
97244
+ }
97245
+ use_input_default((input, key) => {
97246
+ if (key.ctrl && (input === "s" || input === "S")) {
97247
+ dispatch({ type: "toggleFocus" });
97248
+ return;
97249
+ }
97250
+ if (!stateRef.current.focused)
97251
+ return;
97252
+ if (key.escape) {
97253
+ dispatch({ type: "clearAndBlur" });
97254
+ return;
97255
+ }
97256
+ if (key.return) {
97257
+ const trimmed = stateRef.current.buffer.trim();
97258
+ if (trimmed.length === 0)
97259
+ return;
97260
+ Promise.resolve().then(() => onSubmit(trimmed)).then(() => {
97261
+ flashStatus("sent");
97262
+ }).catch(() => {
97263
+ flashStatus("failed");
97264
+ });
97265
+ dispatch({ type: "clearAndBlur" });
97266
+ return;
97267
+ }
97268
+ if (key.backspace || key.delete) {
97269
+ dispatch({ type: "backspace" });
97270
+ return;
97271
+ }
97272
+ if (key.leftArrow) {
97273
+ dispatch({ type: "moveLeft" });
97274
+ return;
97275
+ }
97276
+ if (key.rightArrow) {
97277
+ dispatch({ type: "moveRight" });
97278
+ return;
97279
+ }
97280
+ if (key.tab || key.upArrow || key.downArrow || key.ctrl || key.meta)
97281
+ return;
97282
+ if (!input)
97283
+ return;
97284
+ const printable = input.replace(/[\x00-\x1f\x7f]/g, "");
97285
+ if (!printable)
97286
+ return;
97287
+ dispatch({ type: "insert", chars: printable });
97288
+ }, { isActive: active });
97289
+ if (!active)
97290
+ return null;
97291
+ const placeholder = status === "sent" ? PLACEHOLDER_SENT : status === "failed" ? PLACEHOLDER_FAILED : PLACEHOLDER_IDLE;
97292
+ const borderColor = focused ? "yellow" : "gray";
97293
+ const placeholderColor = status === "sent" ? "green" : status === "failed" ? "red" : "gray";
97294
+ const innerWidth = Math.max(0, width - 4);
97295
+ const labelText = " STEER (CTRL+S) ";
97296
+ const dashes = Math.max(0, innerWidth - labelText.length);
97297
+ const left = Math.floor(dashes / 2);
97298
+ const right = dashes - left;
97299
+ const before2 = buffer.slice(0, cursor4);
97300
+ const at2 = buffer.slice(cursor4, cursor4 + 1) || " ";
97301
+ const after2 = buffer.slice(cursor4 + 1);
97302
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97303
+ flexDirection: "column",
97304
+ width,
97305
+ children: [
97306
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97307
+ color: borderColor,
97308
+ children: `\u256D${"\u2500".repeat(left)}${labelText}${"\u2500".repeat(right)}\u256E`
97309
+ }, undefined, false, undefined, this),
97310
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97311
+ borderStyle: "round",
97312
+ borderTop: false,
97313
+ borderColor,
97314
+ width,
97315
+ paddingX: 1,
97316
+ children: focused ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97317
+ children: [
97318
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97319
+ color: "white",
97320
+ children: "> "
97321
+ }, undefined, false, undefined, this),
97322
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97323
+ children: before2
97324
+ }, undefined, false, undefined, this),
97325
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97326
+ inverse: true,
97327
+ children: at2
97328
+ }, undefined, false, undefined, this),
97329
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97330
+ children: after2
97331
+ }, undefined, false, undefined, this)
97332
+ ]
97333
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97334
+ color: placeholderColor,
97335
+ dimColor: status === "idle",
97336
+ children: placeholder
97337
+ }, undefined, false, undefined, this)
97338
+ }, undefined, false, undefined, this)
97339
+ ]
97340
+ }, undefined, true, undefined, this);
97341
+ }
97342
+ 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";
97343
+ var init_SteeringField = __esm(async () => {
97344
+ await init_build2();
97345
+ import_react60 = __toESM(require_react(), 1);
97346
+ jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
97347
+ });
96158
97348
 
96159
97349
  // apps/agent/src/components/AgentMode.tsx
96160
97350
  import { join as join22 } from "path";
97351
+ async function appendSteeringImpl(changeDir, message) {
97352
+ await runWithContext(createDefaultContext(), async () => {
97353
+ appendSteeringMessage(changeDir, message);
97354
+ });
97355
+ }
96161
97356
  function nextId() {
96162
97357
  lineCounter += 1;
96163
97358
  return `${Date.now()}-${lineCounter}`;
96164
97359
  }
96165
- function visibleLogWindow(lines, cap = MAX_LOG_VIEWPORT_LINES) {
96166
- return lines.slice(-cap);
97360
+ function parseSubtasks(tasksMd) {
97361
+ const out = [];
97362
+ let skipSection = false;
97363
+ for (const line of tasksMd.split(`
97364
+ `)) {
97365
+ const heading = line.match(/^##\s+(.+?)\s*$/);
97366
+ if (heading) {
97367
+ const title = heading[1].trim();
97368
+ skipSection = title.toLowerCase() === "planning" || isFlowTaskHeading(title);
97369
+ continue;
97370
+ }
97371
+ if (skipSection)
97372
+ continue;
97373
+ const m = line.match(/^- \[([ xX])\] (.+)$/);
97374
+ if (m)
97375
+ out.push({ done: m[1] !== " ", text: m[2].trim() });
97376
+ }
97377
+ return out;
97378
+ }
97379
+ function orderSubtasksForCappedDisplay(subtasks) {
97380
+ const pending = [];
97381
+ const done = [];
97382
+ for (const s of subtasks)
97383
+ (s.done ? done : pending).push(s);
97384
+ return [...pending, ...done];
96167
97385
  }
96168
97386
  function fmtCmd(argv) {
96169
97387
  const joined = argv.join(" ");
@@ -96213,28 +97431,28 @@ function LabeledBox({
96213
97431
  const dashes = Math.max(0, innerWidth - visualLen);
96214
97432
  const left = Math.floor(dashes / 2);
96215
97433
  const right = dashes - left;
96216
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97434
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96217
97435
  flexDirection: "column",
96218
97436
  width,
96219
97437
  children: [
96220
- labelNode ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97438
+ labelNode ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96221
97439
  flexDirection: "row",
96222
97440
  children: [
96223
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97441
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96224
97442
  color: borderColor,
96225
97443
  children: `\u256D${"\u2500".repeat(left)}`
96226
97444
  }, undefined, false, undefined, this),
96227
97445
  labelNode,
96228
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97446
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96229
97447
  color: borderColor,
96230
97448
  children: `${"\u2500".repeat(right)}\u256E`
96231
97449
  }, undefined, false, undefined, this)
96232
97450
  ]
96233
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97451
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96234
97452
  color: borderColor,
96235
97453
  children: `\u256D${"\u2500".repeat(left)} ${label ?? ""} ${"\u2500".repeat(right)}\u256E`
96236
97454
  }, undefined, false, undefined, this),
96237
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97455
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96238
97456
  borderStyle: "round",
96239
97457
  borderTop: false,
96240
97458
  borderColor,
@@ -96247,13 +97465,13 @@ function LabeledBox({
96247
97465
  }
96248
97466
  function Link({ url: url2, label, color }) {
96249
97467
  if (!HYPERLINKS_SUPPORTED)
96250
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97468
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96251
97469
  color,
96252
97470
  children: label
96253
97471
  }, undefined, false, undefined, this);
96254
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Transform, {
97472
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Transform, {
96255
97473
  transform: (output) => `\x1B]8;;${url2}\x07${output}\x1B]8;;\x07`,
96256
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97474
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96257
97475
  color,
96258
97476
  underline: true,
96259
97477
  children: label
@@ -96369,19 +97587,37 @@ function displayTailLines(activeCount) {
96369
97587
  return 8;
96370
97588
  return 5;
96371
97589
  }
96372
- function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97590
+ function AgentMode({
97591
+ args,
97592
+ projectRoot,
97593
+ statesDir,
97594
+ tasksDir,
97595
+ appendSteering = appendSteeringImpl,
97596
+ buildCoordinator = buildAgentCoordinator,
97597
+ ensureConfig = ensureRalphyConfig,
97598
+ loadConfig = loadRalphyConfig
97599
+ }) {
96373
97600
  const { exit } = use_app_default();
96374
97601
  const { stdout } = use_stdout_default();
96375
97602
  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({
97603
+ const { columns, rows, resizeKey } = useTerminalSize2();
97604
+ import_react61.useEffect(() => {
97605
+ if (resizeKey === 0)
97606
+ return;
97607
+ stdout.write("\x1B[2J\x1B[3J\x1B[H");
97608
+ }, [resizeKey, stdout]);
97609
+ const [logs, setLogs] = import_react61.useState([]);
97610
+ const [, setTick] = import_react61.useState(0);
97611
+ const [clock, setClock] = import_react61.useState(0);
97612
+ const [focusedIdx, setFocusedIdx] = import_react61.useState(0);
97613
+ const [showPendingTasks, setShowPendingTasks] = import_react61.useState(true);
97614
+ const [showAllSubtasks, setShowAllSubtasks] = import_react61.useState(false);
97615
+ const coordRef = import_react61.useRef(null);
97616
+ const workerMetaRef = import_react61.useRef(new Map);
97617
+ const nextPollAtRef = import_react61.useRef(0);
97618
+ const cfgRef = import_react61.useRef(null);
97619
+ const [effective, setEffective] = import_react61.useState(null);
97620
+ const [pollStatus, setPollStatus] = import_react61.useState({
96385
97621
  state: "idle",
96386
97622
  lastFound: null,
96387
97623
  lastAdded: null,
@@ -96394,20 +97630,20 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96394
97630
  setLogs((prev) => [...prev, { id: nextId(), text, color }]);
96395
97631
  logCoord(text, workerLogFile);
96396
97632
  }
96397
- import_react58.useEffect(() => {
97633
+ import_react61.useEffect(() => {
96398
97634
  let pollTimer = null;
96399
97635
  let cancelled = false;
96400
97636
  async function init2() {
96401
97637
  logSession(`=== session start ${SESSION_START} ===`);
96402
- const cfgPath = await ensureRalphyConfig(projectRoot);
96403
- const cfg2 = await loadRalphyConfig(projectRoot);
97638
+ const cfgPath = await ensureConfig(projectRoot);
97639
+ const cfg2 = await loadConfig(projectRoot);
96404
97640
  cfgRef.current = cfg2;
96405
97641
  appendLog(`agent mode v${VERSION} \u2014 config: ${cfgPath}`, "gray");
96406
97642
  const apiKey = process.env["LINEAR_API_KEY"];
96407
97643
  if (!apiKey) {
96408
97644
  throw new Error("LINEAR_API_KEY not set \u2014 cannot poll Linear");
96409
97645
  }
96410
- const { coord: coord2, filterDesc, concurrency, pollInterval } = buildAgentCoordinator({
97646
+ const { coord: coord2, filterDesc, concurrency, pollInterval, runBaselineGate: runBaselineGate2 } = buildCoordinator({
96411
97647
  args,
96412
97648
  cfg: cfg2,
96413
97649
  projectRoot,
@@ -96415,6 +97651,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96415
97651
  tasksDir,
96416
97652
  apiKey,
96417
97653
  onLog: appendLog,
97654
+ onFileLog: (text) => logCoord(text),
96418
97655
  onWorkersChanged: () => setTick((t) => t + 1),
96419
97656
  onWorkerStarted: (changeName, dir, logFile, changeDir) => {
96420
97657
  logSession(`worker-started ${changeName} log=${logFile}`, logFile);
@@ -96428,6 +97665,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96428
97665
  phaseDetail: "",
96429
97666
  phaseStartedAt: Date.now(),
96430
97667
  currentTask: null,
97668
+ subtasks: [],
96431
97669
  taskProgress: null,
96432
97670
  openspecPhase: null,
96433
97671
  prUrl: null,
@@ -96477,12 +97715,20 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96477
97715
  m.prUrl = prUrl;
96478
97716
  }
96479
97717
  });
97718
+ setEffective({ concurrency, pollInterval });
96480
97719
  coordRef.current = coord2;
96481
97720
  await coord2.init();
96482
97721
  const tick = async () => {
96483
97722
  if (cancelled)
96484
97723
  return;
96485
97724
  setPollStatus((p) => ({ ...p, state: "polling", filterDesc }));
97725
+ try {
97726
+ await runBaselineGate2();
97727
+ } catch (err) {
97728
+ appendLog(`! baseline gate failed: ${err.message}`, "yellow");
97729
+ }
97730
+ if (cancelled)
97731
+ return;
96486
97732
  const { found, added, buckets, prStatus } = await coord2.pollOnce();
96487
97733
  if (cancelled)
96488
97734
  return;
@@ -96554,7 +97800,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96554
97800
  process.off("SIGTERM", onSig);
96555
97801
  };
96556
97802
  }, []);
96557
- import_react58.useEffect(() => {
97803
+ import_react61.useEffect(() => {
96558
97804
  let cancelled = false;
96559
97805
  const interval = setInterval(() => {
96560
97806
  if (cancelled)
@@ -96581,8 +97827,9 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96581
97827
  designFile.exists().then((ok) => ok ? designFile.text() : null)
96582
97828
  ]);
96583
97829
  if (tasksText !== null) {
96584
- const match = tasksText.match(/^- \[ \] (.+)$/m);
96585
- meta3.currentTask = match?.[1]?.trim() ?? null;
97830
+ const subtasks = parseSubtasks(tasksText);
97831
+ meta3.subtasks = subtasks;
97832
+ meta3.currentTask = subtasks.find((s) => !s.done)?.text ?? null;
96586
97833
  const { checked, total } = countProgress(tasksText);
96587
97834
  meta3.taskProgress = total > 0 ? { checked, total } : null;
96588
97835
  }
@@ -96611,10 +97858,26 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96611
97858
  const now2 = Date.now();
96612
97859
  const secsToNextPoll = nextPollAtRef.current ? Math.max(0, Math.ceil((nextPollAtRef.current - now2) / 1000)) : null;
96613
97860
  const activeCount = coord?.activeCount ?? 0;
96614
- const termWidth = (stdout?.columns ?? 100) - 2;
96615
- const termHeight = stdout?.rows ?? 40;
97861
+ const termWidth = columns - 2;
97862
+ const termHeight = rows;
96616
97863
  const safeFocusedIdx = activeCount > 0 ? Math.min(focusedIdx, activeCount - 1) : 0;
97864
+ const steeringFocusedRef = import_react61.useRef(false);
97865
+ const steeringBufferRef = import_react61.useRef("");
97866
+ const steeringCursorRef = import_react61.useRef(0);
97867
+ const steeringFocusedInitRef = import_react61.useRef(false);
96617
97868
  use_input_default((input, key) => {
97869
+ if (steeringFocusedRef.current)
97870
+ return;
97871
+ if (key.ctrl && key.meta && (input === "t" || input === "T")) {
97872
+ if (activeCount > 0)
97873
+ setShowAllSubtasks((v) => !v);
97874
+ return;
97875
+ }
97876
+ if (key.ctrl && (input === "t" || input === "T")) {
97877
+ if (activeCount > 0)
97878
+ setShowPendingTasks((v) => !v);
97879
+ return;
97880
+ }
96618
97881
  if (activeCount === 0)
96619
97882
  return;
96620
97883
  if (key.tab || key.rightArrow) {
@@ -96626,57 +97889,80 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96626
97889
  if (!isNaN(n) && n >= 1 && n <= activeCount)
96627
97890
  setFocusedIdx(n - 1);
96628
97891
  }
96629
- }, { isActive: isRawModeSupported && activeCount > 1 });
97892
+ }, { isActive: isRawModeSupported && activeCount > 0 });
97893
+ const focusedWorker = coordRef.current?.activeWorkers[safeFocusedIdx];
97894
+ const steeringActive = isRawModeSupported && activeCount > 0 && focusedWorker !== undefined;
96630
97895
  const nonFocusedCount = Math.max(0, activeCount - 1);
96631
97896
  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;
97897
+ const steeringBoxLines = steeringActive ? 3 : 0;
97898
+ const FIXED_OVERHEAD = 5 + 7 + tasksBoxLines + 8 + steeringBoxLines + nonFocusedCount * 4;
96635
97899
  const focusedTailLines = Math.max(3, termHeight - FIXED_OVERHEAD);
96636
97900
  const compactTailLines = displayTailLines(activeCount);
96637
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97901
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96638
97902
  flexDirection: "column",
96639
97903
  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, {
97904
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Static, {
97905
+ items: logs,
97906
+ children: (line) => line.color ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96647
97907
  color: line.color,
96648
97908
  children: line.text
96649
- }, line.id, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97909
+ }, line.id, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96650
97910
  children: line.text
96651
- }, line.id, false, undefined, this))
97911
+ }, line.id, false, undefined, this)
96652
97912
  }, undefined, false, undefined, this),
96653
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
97913
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96654
97914
  flexDirection: "column",
96655
97915
  marginTop: 0,
96656
97916
  children: [
96657
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
97917
+ (() => {
97918
+ const pause = coordRef.current?.getPause?.() ?? null;
97919
+ if (!pause)
97920
+ return null;
97921
+ const seconds = Math.floor((Date.now() - pause.since) / 1000);
97922
+ const duration3 = seconds < 60 ? `${seconds}s` : seconds < 3600 ? `${Math.floor(seconds / 60)}m` : `${Math.floor(seconds / 3600)}h${Math.floor(seconds % 3600 / 60)}m`;
97923
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97924
+ borderStyle: "round",
97925
+ borderColor: "red",
97926
+ paddingX: 1,
97927
+ width: termWidth,
97928
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97929
+ color: "red",
97930
+ bold: true,
97931
+ children: [
97932
+ "\u26D4 BASELINE BROKEN ",
97933
+ pause.issueIdentifier,
97934
+ " \xB7 ",
97935
+ duration3,
97936
+ " \xB7 `",
97937
+ pause.command,
97938
+ "`"
97939
+ ]
97940
+ }, undefined, true, undefined, this)
97941
+ }, undefined, false, undefined, this);
97942
+ })(),
97943
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
96658
97944
  label: "\u25C8 RALPH AGENT",
96659
97945
  borderColor: "blue",
96660
97946
  width: termWidth,
96661
97947
  paddingX: 1,
96662
97948
  flexDirection: "column",
96663
97949
  children: [
96664
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97950
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96665
97951
  children: [
96666
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97952
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96667
97953
  dimColor: true,
96668
97954
  children: [
96669
97955
  "v",
96670
97956
  VERSION
96671
97957
  ]
96672
97958
  }, undefined, true, undefined, this),
96673
- cfg && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97959
+ cfg && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96674
97960
  children: [
96675
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97961
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96676
97962
  dimColor: true,
96677
97963
  children: " \u2502 "
96678
97964
  }, undefined, false, undefined, this),
96679
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97965
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96680
97966
  color: "cyan",
96681
97967
  bold: true,
96682
97968
  children: [
@@ -96685,51 +97971,51 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96685
97971
  cfg.model
96686
97972
  ]
96687
97973
  }, undefined, true, undefined, this),
96688
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97974
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96689
97975
  dimColor: true,
96690
97976
  children: [
96691
97977
  " \u2502 \xD7",
96692
- cfg.concurrency
97978
+ effective?.concurrency ?? cfg.concurrency
96693
97979
  ]
96694
97980
  }, undefined, true, undefined, this),
96695
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97981
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96696
97982
  dimColor: true,
96697
97983
  children: [
96698
97984
  " \u2502 poll ",
96699
- cfg.pollIntervalSeconds,
97985
+ effective?.pollInterval ?? cfg.pollIntervalSeconds,
96700
97986
  "s"
96701
97987
  ]
96702
97988
  }, undefined, true, undefined, this),
96703
- cfg.maxIterationsPerTask > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97989
+ cfg.maxIterationsPerTask > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96704
97990
  color: "yellow",
96705
97991
  children: [
96706
97992
  " \u2502 iter \u2264",
96707
97993
  cfg.maxIterationsPerTask
96708
97994
  ]
96709
97995
  }, undefined, true, undefined, this),
96710
- cfg.maxCostUsdPerTask > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
97996
+ cfg.maxCostUsdPerTask > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96711
97997
  color: "yellow",
96712
97998
  children: [
96713
97999
  " \u2502 cost \u2264$",
96714
98000
  cfg.maxCostUsdPerTask
96715
98001
  ]
96716
98002
  }, undefined, true, undefined, this),
96717
- args.maxTickets > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98003
+ args.maxTickets > 0 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96718
98004
  color: "yellow",
96719
98005
  children: [
96720
98006
  " \u2502 tickets \u2264",
96721
98007
  args.maxTickets
96722
98008
  ]
96723
98009
  }, undefined, true, undefined, this),
96724
- cfg.createPrOnSuccess && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98010
+ cfg.createPrOnSuccess && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96725
98011
  color: "green",
96726
98012
  children: " \u25CF PR"
96727
98013
  }, undefined, false, undefined, this),
96728
- cfg.fixCiOnFailure && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98014
+ cfg.fixCiOnFailure && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96729
98015
  color: "green",
96730
98016
  children: " \u25CF fixCI"
96731
98017
  }, undefined, false, undefined, this),
96732
- cfg.useWorktree && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98018
+ cfg.useWorktree && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96733
98019
  color: "green",
96734
98020
  children: " \u25CF worktree"
96735
98021
  }, undefined, false, undefined, this)
@@ -96749,7 +98035,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96749
98035
  lines.push(remaining.slice(0, budget));
96750
98036
  remaining = remaining.slice(budget);
96751
98037
  }
96752
- return lines.map((segment, i) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98038
+ return lines.map((segment, i) => /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96753
98039
  dimColor: true,
96754
98040
  children: [
96755
98041
  i === 0 ? prefix : indent,
@@ -96759,157 +98045,140 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96759
98045
  })()
96760
98046
  ]
96761
98047
  }, undefined, true, undefined, this),
96762
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98048
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96763
98049
  flexDirection: "row",
96764
98050
  gap: 1,
96765
98051
  marginTop: 0,
96766
98052
  width: termWidth,
96767
98053
  children: [
96768
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
98054
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
96769
98055
  label: "POLL STATUS",
96770
98056
  borderColor: "gray",
96771
- width: termWidth - 15,
98057
+ width: termWidth - 17,
96772
98058
  paddingX: 1,
96773
98059
  flexDirection: "column",
96774
98060
  children: [
96775
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98061
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96776
98062
  gap: 2,
96777
98063
  children: [
96778
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98064
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96779
98065
  color: "gray",
96780
98066
  children: spinnerFrame
96781
98067
  }, undefined, false, undefined, this),
96782
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98068
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96783
98069
  children: pollStatus.state === "polling" ? "Polling Linear\u2026" : pollStatus.lastAt !== null ? "Idle" : "Starting\u2026"
96784
98070
  }, undefined, false, undefined, this),
96785
- pollStatus.lastAt !== null && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
98071
+ pollStatus.lastAt !== null && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
98072
+ children: pollStatus.lastBuckets && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
98073
+ children: [
98074
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98075
+ dimColor: true,
98076
+ children: "\u2502"
98077
+ }, undefined, false, undefined, this),
98078
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98079
+ dimColor: true,
98080
+ children: "todo"
98081
+ }, undefined, false, undefined, this),
98082
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98083
+ color: "white",
98084
+ children: pollStatus.lastBuckets.todo
98085
+ }, undefined, false, undefined, this),
98086
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98087
+ dimColor: true,
98088
+ children: "\xB7"
98089
+ }, undefined, false, undefined, this),
98090
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98091
+ dimColor: true,
98092
+ children: "resume"
98093
+ }, undefined, false, undefined, this),
98094
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98095
+ color: pollStatus.lastBuckets.inProgress > 0 ? "cyan" : "white",
98096
+ children: pollStatus.lastBuckets.inProgress
98097
+ }, undefined, false, undefined, this),
98098
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98099
+ dimColor: true,
98100
+ children: "\xB7"
98101
+ }, undefined, false, undefined, this),
98102
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98103
+ dimColor: true,
98104
+ children: "review"
98105
+ }, undefined, false, undefined, this),
98106
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98107
+ color: pollStatus.lastBuckets.review > 0 ? "yellow" : "white",
98108
+ children: pollStatus.lastBuckets.review
98109
+ }, undefined, false, undefined, this),
98110
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98111
+ dimColor: true,
98112
+ children: "\xB7"
98113
+ }, undefined, false, undefined, this),
98114
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98115
+ dimColor: true,
98116
+ children: "mentions"
98117
+ }, undefined, false, undefined, this),
98118
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98119
+ color: pollStatus.lastBuckets.mentions > 0 ? "magenta" : "white",
98120
+ children: pollStatus.lastBuckets.mentions
98121
+ }, undefined, false, undefined, this)
98122
+ ]
98123
+ }, undefined, true, undefined, this)
98124
+ }, undefined, false, undefined, this)
98125
+ ]
98126
+ }, undefined, true, undefined, this),
98127
+ pollStatus.lastAt !== null && pollStatus.lastPrStatus && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98128
+ gap: 2,
98129
+ children: [
98130
+ secsToNextPoll !== null ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98131
+ gap: 1,
98132
+ width: 7,
96786
98133
  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, {
98134
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98135
+ dimColor: true,
98136
+ children: "\u21BA"
98137
+ }, undefined, false, undefined, this),
98138
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98139
+ color: "gray",
96852
98140
  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)
98141
+ secsToNextPoll,
98142
+ "s"
96868
98143
  ]
96869
98144
  }, undefined, true, undefined, this)
96870
98145
  ]
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, {
98146
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96878
98147
  children: " ".repeat(7)
96879
98148
  }, undefined, false, undefined, this),
96880
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98149
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96881
98150
  dimColor: true,
96882
98151
  children: "\u2502"
96883
98152
  }, undefined, false, undefined, this),
96884
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98153
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96885
98154
  dimColor: true,
96886
98155
  children: "mergeable"
96887
98156
  }, undefined, false, undefined, this),
96888
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98157
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96889
98158
  color: pollStatus.lastPrStatus.mergeable > 0 ? "green" : "white",
96890
98159
  children: pollStatus.lastPrStatus.mergeable
96891
98160
  }, undefined, false, undefined, this),
96892
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98161
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96893
98162
  dimColor: true,
96894
98163
  children: "\xB7"
96895
98164
  }, undefined, false, undefined, this),
96896
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98165
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96897
98166
  dimColor: true,
96898
98167
  children: "conflicted"
96899
98168
  }, undefined, false, undefined, this),
96900
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98169
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96901
98170
  color: pollStatus.lastPrStatus.conflicted > 0 ? "red" : "white",
96902
98171
  children: pollStatus.lastPrStatus.conflicted
96903
98172
  }, undefined, false, undefined, this),
96904
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98173
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96905
98174
  dimColor: true,
96906
98175
  children: "\xB7"
96907
98176
  }, undefined, false, undefined, this),
96908
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98177
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96909
98178
  dimColor: true,
96910
98179
  children: "ci-failed"
96911
98180
  }, undefined, false, undefined, this),
96912
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98181
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96913
98182
  color: pollStatus.lastPrStatus.ciFailed > 0 ? "red" : "white",
96914
98183
  children: pollStatus.lastPrStatus.ciFailed
96915
98184
  }, undefined, false, undefined, this)
@@ -96917,35 +98186,35 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96917
98186
  }, undefined, true, undefined, this)
96918
98187
  ]
96919
98188
  }, undefined, true, undefined, this),
96920
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
98189
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
96921
98190
  label: "WORKERS",
96922
98191
  borderColor: "gray",
96923
- width: 14,
98192
+ width: 16,
96924
98193
  paddingX: 1,
96925
98194
  flexDirection: "column",
96926
98195
  children: [
96927
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98196
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96928
98197
  gap: 1,
96929
98198
  children: [
96930
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98199
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96931
98200
  dimColor: true,
96932
- children: "act"
98201
+ children: "active"
96933
98202
  }, undefined, false, undefined, this),
96934
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98203
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96935
98204
  color: activeCount > 0 ? "cyan" : "gray",
96936
98205
  bold: true,
96937
98206
  children: activeCount
96938
98207
  }, undefined, false, undefined, this)
96939
98208
  ]
96940
98209
  }, undefined, true, undefined, this),
96941
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98210
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96942
98211
  gap: 1,
96943
98212
  children: [
96944
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98213
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96945
98214
  dimColor: true,
96946
98215
  children: "queue"
96947
98216
  }, undefined, false, undefined, this),
96948
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98217
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96949
98218
  color: (coord?.queuedCount ?? 0) > 0 ? "yellow" : "gray",
96950
98219
  bold: true,
96951
98220
  children: coord?.queuedCount ?? 0
@@ -96956,13 +98225,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96956
98225
  }, undefined, true, undefined, this)
96957
98226
  ]
96958
98227
  }, undefined, true, undefined, this),
96959
- activeCount > 1 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
98228
+ activeCount > 1 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
96960
98229
  label: `TASKS${activeCount > 1 ? " Tab/\u2190 \u2192 \xB7 1-9" : ""}`,
96961
98230
  borderColor: "gray",
96962
98231
  width: termWidth,
96963
98232
  paddingX: 1,
96964
98233
  flexDirection: "column",
96965
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98234
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96966
98235
  gap: 3,
96967
98236
  flexWrap: "wrap",
96968
98237
  children: coord?.activeWorkers.map((w, idx) => {
@@ -96970,10 +98239,10 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96970
98239
  const phase = meta3?.phase ?? "working";
96971
98240
  const pBadge = priorityBadge(w.issue.priority);
96972
98241
  const isFocused = idx === safeFocusedIdx;
96973
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98242
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
96974
98243
  gap: 1,
96975
98244
  children: [
96976
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98245
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96977
98246
  color: isFocused ? "white" : "gray",
96978
98247
  bold: isFocused,
96979
98248
  children: [
@@ -96982,7 +98251,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96982
98251
  "]"
96983
98252
  ]
96984
98253
  }, undefined, true, undefined, this),
96985
- pBadge.label && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98254
+ pBadge.label && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96986
98255
  color: pBadge.color,
96987
98256
  children: [
96988
98257
  pBadge.text,
@@ -96990,17 +98259,17 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
96990
98259
  pBadge.label
96991
98260
  ]
96992
98261
  }, undefined, true, undefined, this),
96993
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98262
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
96994
98263
  url: w.issue.url,
96995
98264
  label: w.issueIdentifier,
96996
98265
  color: isFocused ? "cyan" : "gray"
96997
98266
  }, undefined, false, undefined, this),
96998
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98267
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
96999
98268
  color: phaseColor(phase),
97000
98269
  dimColor: !isFocused,
97001
98270
  children: phase
97002
98271
  }, undefined, false, undefined, this),
97003
- isFocused && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98272
+ isFocused && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97004
98273
  color: "white",
97005
98274
  children: "\u25C0"
97006
98275
  }, undefined, false, undefined, this)
@@ -97023,6 +98292,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97023
98292
  const currentTask = meta3?.currentTask ?? null;
97024
98293
  const taskProgress = meta3?.taskProgress ?? null;
97025
98294
  const openspecPhase = meta3?.openspecPhase ?? null;
98295
+ const subtasks = meta3?.subtasks ?? [];
97026
98296
  const pBadge = priorityBadge(w.issue.priority);
97027
98297
  const mBadge = modeBadge(w.mode);
97028
98298
  const pColor = phaseColor(phase);
@@ -97030,33 +98300,33 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97030
98300
  const visibleTailLines = isFocused ? focusedTailLines : compactTailLines;
97031
98301
  if (!isFocused && activeCount > 1) {
97032
98302
  const cardLabelWidth2 = (prUrl ? prLabel(prUrl).length + 3 : 0) + w.issueIdentifier.length + 2;
97033
- const cardLabelNode2 = /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
98303
+ const cardLabelNode2 = /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
97034
98304
  children: [
97035
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98305
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97036
98306
  color: "gray",
97037
98307
  children: " "
97038
98308
  }, undefined, false, undefined, this),
97039
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98309
+ prUrl && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
97040
98310
  url: prUrl,
97041
98311
  label: prLabel(prUrl),
97042
98312
  color: "green"
97043
98313
  }, undefined, false, undefined, this),
97044
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98314
+ prUrl && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97045
98315
  color: "gray",
97046
98316
  children: " \xB7 "
97047
98317
  }, undefined, false, undefined, this),
97048
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98318
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
97049
98319
  url: w.issue.url,
97050
98320
  label: w.issueIdentifier,
97051
98321
  color: "cyan"
97052
98322
  }, undefined, false, undefined, this),
97053
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98323
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97054
98324
  color: "gray",
97055
98325
  children: " "
97056
98326
  }, undefined, false, undefined, this)
97057
98327
  ]
97058
98328
  }, undefined, true, undefined, this);
97059
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
98329
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
97060
98330
  labelNode: cardLabelNode2,
97061
98331
  labelVisualWidth: cardLabelWidth2,
97062
98332
  borderColor: "gray",
@@ -97064,7 +98334,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97064
98334
  gap: 2,
97065
98335
  width: termWidth,
97066
98336
  children: [
97067
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98337
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97068
98338
  dimColor: true,
97069
98339
  children: [
97070
98340
  "[",
@@ -97072,54 +98342,54 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97072
98342
  "]"
97073
98343
  ]
97074
98344
  }, undefined, true, undefined, this),
97075
- pBadge.label && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98345
+ pBadge.label && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97076
98346
  color: pBadge.color,
97077
98347
  children: pBadge.text
97078
98348
  }, undefined, false, undefined, this),
97079
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98349
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97080
98350
  color: "gray",
97081
98351
  bold: true,
97082
98352
  children: w.issueIdentifier
97083
98353
  }, undefined, false, undefined, this),
97084
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98354
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97085
98355
  dimColor: true,
97086
98356
  children: trunc(w.issue.title, 40)
97087
98357
  }, undefined, false, undefined, this),
97088
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98358
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97089
98359
  dimColor: true,
97090
98360
  children: "\u2502"
97091
98361
  }, undefined, false, undefined, this),
97092
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98362
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97093
98363
  color: pColor,
97094
98364
  dimColor: true,
97095
98365
  children: phase
97096
98366
  }, undefined, false, undefined, this),
97097
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98367
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97098
98368
  dimColor: true,
97099
98369
  children: "\u2502"
97100
98370
  }, undefined, false, undefined, this),
97101
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98371
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97102
98372
  dimColor: true,
97103
98373
  children: elapsed
97104
98374
  }, undefined, false, undefined, this),
97105
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98375
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97106
98376
  dimColor: true,
97107
98377
  children: "\xB7"
97108
98378
  }, undefined, false, undefined, this),
97109
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98379
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97110
98380
  dimColor: true,
97111
98381
  children: [
97112
98382
  "iter ",
97113
98383
  iter
97114
98384
  ]
97115
98385
  }, undefined, true, undefined, this),
97116
- currentTask && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
98386
+ currentTask && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
97117
98387
  children: [
97118
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98388
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97119
98389
  dimColor: true,
97120
98390
  children: "\u2502"
97121
98391
  }, undefined, false, undefined, this),
97122
- openspecPhase && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98392
+ openspecPhase && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97123
98393
  color: openspecPhaseColor(openspecPhase),
97124
98394
  children: [
97125
98395
  "[",
@@ -97127,7 +98397,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97127
98397
  "]"
97128
98398
  ]
97129
98399
  }, undefined, true, undefined, this),
97130
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98400
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97131
98401
  dimColor: true,
97132
98402
  children: [
97133
98403
  "\u25B6 ",
@@ -97140,33 +98410,33 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97140
98410
  }, w.changeName, true, undefined, this);
97141
98411
  }
97142
98412
  const cardLabelWidth = (prUrl ? prLabel(prUrl).length + 3 : 0) + w.issueIdentifier.length + 2;
97143
- const cardLabelNode = /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
98413
+ const cardLabelNode = /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
97144
98414
  children: [
97145
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98415
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97146
98416
  color: bColor,
97147
98417
  children: " "
97148
98418
  }, undefined, false, undefined, this),
97149
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98419
+ prUrl && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
97150
98420
  url: prUrl,
97151
98421
  label: prLabel(prUrl),
97152
98422
  color: "green"
97153
98423
  }, undefined, false, undefined, this),
97154
- prUrl && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98424
+ prUrl && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97155
98425
  color: bColor,
97156
98426
  children: " \xB7 "
97157
98427
  }, undefined, false, undefined, this),
97158
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Link, {
98428
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Link, {
97159
98429
  url: w.issue.url,
97160
98430
  label: w.issueIdentifier,
97161
98431
  color: "cyan"
97162
98432
  }, undefined, false, undefined, this),
97163
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98433
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97164
98434
  color: bColor,
97165
98435
  children: " "
97166
98436
  }, undefined, false, undefined, this)
97167
98437
  ]
97168
98438
  }, undefined, true, undefined, this);
97169
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(LabeledBox, {
98439
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(LabeledBox, {
97170
98440
  labelNode: cardLabelNode,
97171
98441
  labelVisualWidth: cardLabelWidth,
97172
98442
  borderColor: bColor,
@@ -97174,18 +98444,18 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97174
98444
  paddingX: 1,
97175
98445
  width: termWidth,
97176
98446
  children: [
97177
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98447
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97178
98448
  gap: 2,
97179
98449
  children: [
97180
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98450
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97181
98451
  children: spinnerFrame
97182
98452
  }, undefined, false, undefined, this),
97183
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98453
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97184
98454
  color: "white",
97185
98455
  bold: true,
97186
98456
  children: trunc(w.issue.title, Math.max(20, termWidth - 55))
97187
98457
  }, undefined, false, undefined, this),
97188
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98458
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97189
98459
  color: mBadge.color,
97190
98460
  bold: true,
97191
98461
  children: [
@@ -97194,7 +98464,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97194
98464
  "]"
97195
98465
  ]
97196
98466
  }, undefined, true, undefined, this),
97197
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98467
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97198
98468
  color: pColor,
97199
98469
  bold: true,
97200
98470
  children: [
@@ -97202,79 +98472,39 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97202
98472
  phaseDetail ? ` (${phaseDetail})` : ""
97203
98473
  ]
97204
98474
  }, undefined, true, undefined, this),
97205
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98475
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97206
98476
  dimColor: true,
97207
98477
  children: "\u2502"
97208
98478
  }, undefined, false, undefined, this),
97209
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98479
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97210
98480
  color: "white",
97211
98481
  children: elapsed
97212
98482
  }, undefined, false, undefined, this),
97213
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98483
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97214
98484
  dimColor: true,
97215
98485
  children: "\u2502"
97216
98486
  }, undefined, false, undefined, this),
97217
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98487
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97218
98488
  dimColor: true,
97219
98489
  children: "\u21BA"
97220
98490
  }, undefined, false, undefined, this),
97221
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98491
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97222
98492
  color: "white",
97223
98493
  bold: true,
97224
98494
  children: iter
97225
98495
  }, undefined, false, undefined, this)
97226
98496
  ]
97227
98497
  }, 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, {
98498
+ currentTask && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97269
98499
  gap: 1,
97270
98500
  marginTop: 0,
97271
98501
  children: [
97272
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98502
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97273
98503
  color: "yellow",
97274
98504
  bold: true,
97275
98505
  children: "\u25B6 TASK"
97276
98506
  }, undefined, false, undefined, this),
97277
- openspecPhase && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98507
+ openspecPhase && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97278
98508
  color: openspecPhaseColor(openspecPhase),
97279
98509
  bold: true,
97280
98510
  children: [
@@ -97283,42 +98513,42 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97283
98513
  "]"
97284
98514
  ]
97285
98515
  }, undefined, true, undefined, this),
97286
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98516
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97287
98517
  color: "white",
97288
98518
  children: trunc(currentTask, termWidth - 14 - (openspecPhase ? openspecPhase.length + 11 : 0))
97289
98519
  }, undefined, false, undefined, this)
97290
98520
  ]
97291
98521
  }, undefined, true, undefined, this),
97292
- cmd && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98522
+ cmd && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97293
98523
  gap: 1,
97294
98524
  marginTop: 0,
97295
98525
  children: [
97296
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98526
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97297
98527
  color: "yellow",
97298
98528
  children: "\u23F5 CMD"
97299
98529
  }, undefined, false, undefined, this),
97300
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98530
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97301
98531
  color: "yellow",
97302
98532
  children: fmtCmd(cmd.argv)
97303
98533
  }, undefined, false, undefined, this),
97304
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98534
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97305
98535
  dimColor: true,
97306
98536
  children: cmdElapsed
97307
98537
  }, undefined, false, undefined, this)
97308
98538
  ]
97309
98539
  }, undefined, true, undefined, this),
97310
- tail2.length > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
98540
+ tail2.length > 0 && !(showPendingTasks && showAllSubtasks) && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
97311
98541
  flexDirection: "column",
97312
98542
  marginTop: 0,
97313
98543
  children: [
97314
- /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98544
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97315
98545
  dimColor: true,
97316
98546
  children: [
97317
98547
  "\u2500 OUTPUT ",
97318
98548
  "\u2500".repeat(Math.max(4, termWidth - 14))
97319
98549
  ]
97320
98550
  }, undefined, true, undefined, this),
97321
- tail2.slice(-visibleTailLines).map((line, i) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
98551
+ tail2.slice(-visibleTailLines).map((line, i) => /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
97322
98552
  dimColor: true,
97323
98553
  children: [
97324
98554
  "\u2502 ",
@@ -97326,24 +98556,185 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
97326
98556
  ]
97327
98557
  }, `${w.changeName}-tail-${i}`, true, undefined, this))
97328
98558
  ]
97329
- }, undefined, true, undefined, this)
98559
+ }, undefined, true, undefined, this),
98560
+ shouldShowPhasePipeline(openspecPhase) && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98561
+ marginTop: 0,
98562
+ children: phasePipeline(openspecPhase).map((seg, i, arr) => {
98563
+ const glyph = seg.status === "done" ? "\u2713" : seg.status === "current" ? "\u25CF" : "\u25CB";
98564
+ const node2 = seg.status === "done" ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98565
+ color: "green",
98566
+ children: [
98567
+ glyph,
98568
+ " ",
98569
+ seg.label
98570
+ ]
98571
+ }, undefined, true, undefined, this) : seg.status === "current" ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98572
+ color: openspecPhaseColor(seg.phase),
98573
+ bold: true,
98574
+ children: [
98575
+ glyph,
98576
+ " ",
98577
+ seg.label
98578
+ ]
98579
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98580
+ dimColor: true,
98581
+ children: [
98582
+ glyph,
98583
+ " ",
98584
+ seg.label
98585
+ ]
98586
+ }, undefined, true, undefined, this);
98587
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98588
+ children: [
98589
+ node2,
98590
+ i < arr.length - 1 && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98591
+ dimColor: true,
98592
+ children: " \u2500 "
98593
+ }, undefined, false, undefined, this)
98594
+ ]
98595
+ }, seg.phase, true, undefined, this);
98596
+ })
98597
+ }, undefined, false, undefined, this),
98598
+ shouldShowSubtasksPanel(openspecPhase, showPendingTasks, subtasks.length > 0) && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98599
+ flexDirection: "column",
98600
+ marginTop: 0,
98601
+ children: [
98602
+ (() => {
98603
+ const header = `\u2500 SUBTASKS (${subtasks.length}) CTRL+T to close `;
98604
+ const pad2 = "\u2500".repeat(Math.max(4, termWidth - header.length - 4));
98605
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98606
+ dimColor: true,
98607
+ children: `${header}${pad2}`
98608
+ }, undefined, false, undefined, this);
98609
+ })(),
98610
+ (showAllSubtasks ? subtasks : orderSubtasksForCappedDisplay(subtasks).slice(0, MAX_PENDING_DISPLAY)).map((s, i, arr) => {
98611
+ const ord = `${i + 1}.`.padStart(`${arr.length}.`.length, " ");
98612
+ const reserved = ord.length + 5;
98613
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98614
+ children: [
98615
+ s.done ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98616
+ dimColor: true,
98617
+ children: `${ord} [x] `
98618
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98619
+ children: `${ord} [ ] `
98620
+ }, undefined, false, undefined, this),
98621
+ s.done ? /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98622
+ dimColor: true,
98623
+ children: trunc(s.text, termWidth - reserved)
98624
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98625
+ children: trunc(s.text, termWidth - reserved)
98626
+ }, undefined, false, undefined, this)
98627
+ ]
98628
+ }, `${w.changeName}-subtask-${i}`, true, undefined, this);
98629
+ }),
98630
+ !showAllSubtasks && subtasks.length > MAX_PENDING_DISPLAY && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98631
+ dimColor: true,
98632
+ children: ` \u2026 +${subtasks.length - MAX_PENDING_DISPLAY} more (CTRL+ALT+T to expand)`
98633
+ }, undefined, false, undefined, this)
98634
+ ]
98635
+ }, undefined, true, undefined, this),
98636
+ steeringActive && idx === safeFocusedIdx && /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98637
+ marginTop: 0,
98638
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(SteeringField, {
98639
+ active: steeringActive,
98640
+ width: termWidth - 2,
98641
+ initialBuffer: steeringBufferRef.current,
98642
+ initialCursor: steeringCursorRef.current,
98643
+ initialFocused: steeringFocusedInitRef.current,
98644
+ onFocusChange: (f2) => {
98645
+ steeringFocusedRef.current = f2;
98646
+ steeringFocusedInitRef.current = f2;
98647
+ },
98648
+ onStateChange: (s) => {
98649
+ steeringBufferRef.current = s.buffer;
98650
+ steeringCursorRef.current = s.cursor;
98651
+ },
98652
+ onSubmit: async (message) => {
98653
+ try {
98654
+ await appendSteering(join22(tasksDir, w.changeName), message);
98655
+ } catch (err) {
98656
+ appendLog(`! steering append failed for ${w.changeName}: ${err.message}`, "red");
98657
+ throw err;
98658
+ }
98659
+ const restarted = await coordRef.current?.restartWorker(w.changeName);
98660
+ if (restarted) {
98661
+ appendLog(` ${w.changeName}: steering applied, restarting worker`, "cyan");
98662
+ } else {
98663
+ appendLog(` ${w.changeName}: steering queued \u2014 will apply on next iteration`, "gray");
98664
+ }
98665
+ }
98666
+ }, undefined, false, undefined, this)
98667
+ }, undefined, false, undefined, this),
98668
+ shouldShowProgressBar(openspecPhase, showPendingTasks, taskProgress !== null) && taskProgress && (() => {
98669
+ const hint = " CTRL+T to open";
98670
+ const bar = calcProgressBar(taskProgress.checked, taskProgress.total, termWidth - 4 - hint.length);
98671
+ if (!bar)
98672
+ return null;
98673
+ const { countStr, filledLeft, leftSlot, filledRight, rightSlot } = bar;
98674
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Box_default, {
98675
+ marginTop: 0,
98676
+ children: [
98677
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98678
+ dimColor: true,
98679
+ children: "["
98680
+ }, undefined, false, undefined, this),
98681
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98682
+ color: "green",
98683
+ children: "\u2588".repeat(filledLeft)
98684
+ }, undefined, false, undefined, this),
98685
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98686
+ dimColor: true,
98687
+ children: "\u2591".repeat(leftSlot - filledLeft)
98688
+ }, undefined, false, undefined, this),
98689
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98690
+ color: "white",
98691
+ bold: true,
98692
+ children: countStr
98693
+ }, undefined, false, undefined, this),
98694
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98695
+ color: "green",
98696
+ children: "\u2588".repeat(filledRight)
98697
+ }, undefined, false, undefined, this),
98698
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98699
+ dimColor: true,
98700
+ children: "\u2591".repeat(rightSlot - filledRight)
98701
+ }, undefined, false, undefined, this),
98702
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98703
+ dimColor: true,
98704
+ children: "]"
98705
+ }, undefined, false, undefined, this),
98706
+ /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
98707
+ dimColor: true,
98708
+ children: hint
98709
+ }, undefined, false, undefined, this)
98710
+ ]
98711
+ }, undefined, true, undefined, this);
98712
+ })()
97330
98713
  ]
97331
98714
  }, w.changeName, true, undefined, this);
97332
98715
  })
97333
98716
  ]
97334
98717
  }, undefined, true, undefined, this)
97335
98718
  ]
97336
- }, undefined, true, undefined, this);
98719
+ }, resizeKey, true, undefined, this);
97337
98720
  }
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;
98721
+ 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
98722
  var init_AgentMode = __esm(async () => {
97340
98723
  init_cli2();
97341
98724
  init_config();
97342
98725
  init_wire();
98726
+ init_tasks_md();
98727
+ init_phase();
97343
98728
  init_log();
97344
- await init_build2();
97345
- import_react58 = __toESM(require_react(), 1);
97346
- jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
98729
+ init_useTerminalSize2();
98730
+ init_loop();
98731
+ init_context();
98732
+ await __promiseAll([
98733
+ init_build2(),
98734
+ init_SteeringField()
98735
+ ]);
98736
+ import_react61 = __toESM(require_react(), 1);
98737
+ jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
97347
98738
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
97348
98739
  HYPERLINKS_SUPPORTED = !process.env["TMUX"];
97349
98740
  ANSI_STRIP_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
@@ -97353,6 +98744,112 @@ var init_AgentMode = __esm(async () => {
97353
98744
  SESSION_START = new Date().toISOString();
97354
98745
  });
97355
98746
 
98747
+ // apps/agent/src/pr-status.ts
98748
+ function bucketChecks(rollup, prState) {
98749
+ if (rollup === null || rollup === undefined || rollup.length === 0) {
98750
+ return prState === "MERGED" ? "pass" : "pending";
98751
+ }
98752
+ let anyPending = false;
98753
+ let anyFail = false;
98754
+ for (const c of rollup) {
98755
+ const status = (c.status ?? "").toUpperCase();
98756
+ const conclusion = (c.conclusion ?? "").toUpperCase();
98757
+ const state = (c.state ?? "").toUpperCase();
98758
+ if (status && status !== "COMPLETED") {
98759
+ anyPending = true;
98760
+ continue;
98761
+ }
98762
+ if (state === "PENDING" || state === "EXPECTED") {
98763
+ anyPending = true;
98764
+ continue;
98765
+ }
98766
+ const settled = conclusion || state;
98767
+ if (settled === "FAILURE" || settled === "TIMED_OUT" || settled === "CANCELLED" || settled === "ERROR") {
98768
+ anyFail = true;
98769
+ }
98770
+ }
98771
+ if (anyPending)
98772
+ return "pending";
98773
+ if (anyFail)
98774
+ return "fail";
98775
+ return "pass";
98776
+ }
98777
+ async function fetchPrStatus(url2, runner, cwd2) {
98778
+ let stdout;
98779
+ try {
98780
+ const out = await runner.run(["gh", "pr", "view", url2, "--json", PR_VIEW_FIELDS], cwd2);
98781
+ stdout = out.stdout;
98782
+ } catch (err) {
98783
+ const e = err;
98784
+ const msg = (e.stderr?.trim().split(`
98785
+ `)[0] ?? e.message ?? "gh failed").slice(0, 200);
98786
+ return { kind: "error", message: msg };
98787
+ }
98788
+ let raw;
98789
+ try {
98790
+ raw = JSON.parse(stdout || "{}");
98791
+ } catch (err) {
98792
+ return { kind: "error", message: `parse error: ${err.message}` };
98793
+ }
98794
+ const stateUpper = (raw.state ?? "").toUpperCase();
98795
+ const state = stateUpper === "OPEN" || stateUpper === "CLOSED" || stateUpper === "MERGED" ? stateUpper : "OPEN";
98796
+ const mergeableUpper = (raw.mergeable ?? "UNKNOWN").toUpperCase();
98797
+ const mergeable = mergeableUpper === "MERGEABLE" || mergeableUpper === "CONFLICTING" ? mergeableUpper : "UNKNOWN";
98798
+ return {
98799
+ kind: "ok",
98800
+ state,
98801
+ isDraft: Boolean(raw.isDraft),
98802
+ mergeable,
98803
+ ciBucket: bucketChecks(raw.statusCheckRollup, state),
98804
+ autoMergeEnabled: raw.autoMergeRequest !== null && raw.autoMergeRequest !== undefined,
98805
+ createdAt: raw.createdAt ?? ""
98806
+ };
98807
+ }
98808
+ var PR_VIEW_FIELDS = "state,isDraft,mergeable,statusCheckRollup,autoMergeRequest,createdAt";
98809
+
98810
+ // apps/agent/src/list-sort.ts
98811
+ function assignTier(status) {
98812
+ if (status === null || status.kind === "error")
98813
+ return 5;
98814
+ const conflict = status.mergeable === "CONFLICTING";
98815
+ const failing = status.ciBucket === "fail";
98816
+ if (conflict && status.autoMergeEnabled)
98817
+ return 1;
98818
+ if (failing && status.autoMergeEnabled)
98819
+ return 2;
98820
+ if (conflict)
98821
+ return 3;
98822
+ if (failing)
98823
+ return 4;
98824
+ return 5;
98825
+ }
98826
+ function createdAtOf(status) {
98827
+ if (status && status.kind === "ok")
98828
+ return status.createdAt;
98829
+ return "";
98830
+ }
98831
+ function sortRows(rows) {
98832
+ const cmp = chain((a, b) => assignTier(a.status) - assignTier(b.status), (a, b) => {
98833
+ const ia = a.issueCreatedAt;
98834
+ const ib = b.issueCreatedAt;
98835
+ if (ia === ib)
98836
+ return 0;
98837
+ if (ia === "")
98838
+ return 1;
98839
+ if (ib === "")
98840
+ return -1;
98841
+ return ia < ib ? -1 : 1;
98842
+ }, (a, b) => {
98843
+ const ca = createdAtOf(a.status);
98844
+ const cb = createdAtOf(b.status);
98845
+ if (ca === cb)
98846
+ return 0;
98847
+ return ca < cb ? -1 : 1;
98848
+ }, (a, b) => a.bucketOrder - b.bucketOrder, (a, b) => a.identifier.localeCompare(b.identifier));
98849
+ return [...rows].sort(cmp);
98850
+ }
98851
+ var init_list_sort = () => {};
98852
+
97356
98853
  // apps/agent/src/list.ts
97357
98854
  var exports_list = {};
97358
98855
  __export(exports_list, {
@@ -97484,40 +98981,100 @@ async function fetchBucketIssues(apiKey, bucket, team, assignee) {
97484
98981
  };
97485
98982
  return fetchOpenIssues(apiKey, spec);
97486
98983
  }
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)}
98984
+ function formatPrStatusMarker(status) {
98985
+ if (status === null)
98986
+ return "(no PR)";
98987
+ if (status.kind === "error")
98988
+ return "?";
98989
+ if (status.state === "MERGED")
98990
+ return "merged";
98991
+ if (status.state === "CLOSED")
98992
+ return "closed";
98993
+ const parts = [];
98994
+ if (status.mergeable === "CONFLICTING")
98995
+ parts.push("\u2717conflict");
98996
+ if (status.ciBucket === "fail")
98997
+ parts.push("\u2717ci");
98998
+ if (status.ciBucket === "pending")
98999
+ parts.push("\u23F3ci");
99000
+ if (status.isDraft)
99001
+ parts.push("draft");
99002
+ if (status.autoMergeEnabled)
99003
+ parts.push("auto-merge");
99004
+ if (parts.length === 0)
99005
+ return "ok";
99006
+ return parts.join(" ");
99007
+ }
99008
+ async function fetchAndPrintLinear(apiKey, buckets, team, assignee, cwd2, runner) {
99009
+ const bucketResults = await Promise.all(buckets.map(async (bucket) => {
99010
+ if (!bucket.indicator || bucket.indicator.filter.length === 0) {
99011
+ return { bucket, issues: [], error: null };
99012
+ }
99013
+ try {
99014
+ const issues = await fetchBucketIssues(apiKey, bucket, team, assignee);
99015
+ return { bucket, issues, error: null };
99016
+ } catch (err) {
99017
+ return {
99018
+ bucket,
99019
+ issues: [],
99020
+ error: err instanceof Error ? err.message : String(err)
99021
+ };
99022
+ }
99023
+ }));
99024
+ for (const { bucket, error: error48 } of bucketResults) {
99025
+ if (error48) {
99026
+ process.stdout.write(`
99027
+ ${bucket.label}: error fetching from Linear \u2014 ${error48}
97497
99028
  `);
97498
- return;
99029
+ }
97499
99030
  }
97500
- const filterStr = bucket.indicator.filter.map((m) => `${m.type}:${m.value}`).join(", ");
99031
+ const seen = new Map;
99032
+ let order = 0;
99033
+ for (const { bucket, issues } of bucketResults) {
99034
+ for (const issue2 of issues) {
99035
+ if (seen.has(issue2.id))
99036
+ continue;
99037
+ seen.set(issue2.id, {
99038
+ issueId: issue2.id,
99039
+ identifier: issue2.identifier,
99040
+ status: null,
99041
+ bucketOrder: order++,
99042
+ issueCreatedAt: issue2.createdAt,
99043
+ bucketLabel: bucket.label,
99044
+ stateName: issue2.state.name,
99045
+ title: issue2.title.slice(0, 60),
99046
+ prUrl: null
99047
+ });
99048
+ }
99049
+ }
99050
+ const rows = [...seen.values()];
99051
+ await Promise.all(rows.map(async (row) => {
99052
+ try {
99053
+ const attachments = await fetchIssueAttachments(apiKey, row.issueId);
99054
+ row.prUrl = findPullRequestUrl(attachments);
99055
+ } catch {}
99056
+ }));
99057
+ await Promise.all(rows.map(async (row) => {
99058
+ if (!row.prUrl)
99059
+ return;
99060
+ row.status = await fetchPrStatus(row.prUrl, runner, cwd2);
99061
+ }));
99062
+ const sorted = sortRows(rows);
97501
99063
  process.stdout.write(`
97502
- ${bucket.label} [${filterStr}] \u2014 ${issues.length} issue(s)
99064
+ Linear tickets: ${sorted.length} issue(s)
97503
99065
  `);
97504
- if (issues.length === 0)
99066
+ if (sorted.length === 0)
97505
99067
  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)"}
99068
+ const idWidth = Math.max(10, ...sorted.map((r) => r.identifier.length));
99069
+ const bucketWidth = Math.max(6, ...sorted.map((r) => r.bucketLabel.length));
99070
+ const stateWidth = Math.max(5, ...sorted.map((r) => r.stateName.length));
99071
+ const markers = sorted.map((r) => formatPrStatusMarker(r.status));
99072
+ const markerWidth = Math.max(9, ...markers.map((m) => m.length));
99073
+ process.stdout.write(` ${pad2("Identifier", idWidth)} ${pad2("Bucket", bucketWidth)} ${pad2("State", stateWidth)} ${pad2("Title", 60)} ${pad2("PR Status", markerWidth)} PR URL
99074
+ `);
99075
+ for (let i = 0;i < sorted.length; i += 1) {
99076
+ const r = sorted[i];
99077
+ 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
99078
  `);
97522
99079
  }
97523
99080
  }
@@ -97566,18 +99123,14 @@ Linear: LINEAR_API_KEY not set \u2014 cannot fetch tickets. Configured buckets:
97566
99123
  }
97567
99124
  return;
97568
99125
  }
97569
- process.stdout.write(`
97570
- Linear tickets:
97571
- `);
97572
99126
  if (team)
97573
- process.stdout.write(` team: ${team}
99127
+ process.stdout.write(`
99128
+ team: ${team}
97574
99129
  `);
97575
99130
  if (assignee)
97576
- process.stdout.write(` assignee: ${assignee}
99131
+ process.stdout.write(`assignee: ${assignee}
97577
99132
  `);
97578
- for (const bucket of buckets) {
97579
- await printBucket(apiKey, bucket, team, assignee);
97580
- }
99133
+ await fetchAndPrintLinear(apiKey, buckets, team, assignee, projectRoot, localCmdRunner);
97581
99134
  }
97582
99135
  function normalizeIdentifier(input) {
97583
99136
  const match = input.match(/^([A-Za-z]+)-(\d+)(?:-.*)?$/);
@@ -97724,12 +99277,27 @@ Per-bucket diagnostics:
97724
99277
  }
97725
99278
  }
97726
99279
  }
97727
- var RALPHY_ATTACHMENT_TITLE2 = "Ralphy";
99280
+ var localCmdRunner, RALPHY_ATTACHMENT_TITLE2 = "Ralphy";
97728
99281
  var init_list = __esm(() => {
97729
99282
  init_context();
97730
99283
  init_types2();
97731
99284
  init_worktree();
97732
99285
  init_config();
99286
+ init_list_sort();
99287
+ localCmdRunner = {
99288
+ run: async (cmd, cwd2) => {
99289
+ const proc = Bun.spawn({ cmd, cwd: cwd2, stdout: "pipe", stderr: "pipe" });
99290
+ const stdout = await new Response(proc.stdout).text();
99291
+ const stderr = await new Response(proc.stderr).text();
99292
+ const code = await proc.exited;
99293
+ if (code !== 0) {
99294
+ const err = new Error(`\`${cmd.join(" ")}\` exited ${code}`);
99295
+ err.stderr = stderr;
99296
+ throw err;
99297
+ }
99298
+ return { stdout, stderr };
99299
+ }
99300
+ };
97733
99301
  });
97734
99302
 
97735
99303
  // apps/agent/src/agent/json-runner.ts
@@ -97773,7 +99341,7 @@ async function runAgentJson({
97773
99341
  process.exitCode = 1;
97774
99342
  return;
97775
99343
  }
97776
- const { coord, filterDesc, concurrency, pollInterval } = buildAgentCoordinator({
99344
+ const { coord, filterDesc, concurrency, pollInterval, runBaselineGate: runBaselineGate2 } = buildAgentCoordinator({
97777
99345
  args,
97778
99346
  cfg,
97779
99347
  projectRoot,
@@ -97836,6 +99404,17 @@ async function runAgentJson({
97836
99404
  if (cancelled)
97837
99405
  return;
97838
99406
  emit({ type: "poll_start" });
99407
+ try {
99408
+ await runBaselineGate2();
99409
+ } catch (err) {
99410
+ emit({
99411
+ type: "log",
99412
+ text: `baseline gate failed: ${err.message}`,
99413
+ color: "yellow"
99414
+ });
99415
+ }
99416
+ if (cancelled)
99417
+ return;
97839
99418
  const { found, added, buckets, prStatus } = await coord.pollOnce();
97840
99419
  if (cancelled)
97841
99420
  return;
@@ -97949,12 +99528,12 @@ async function main2(argv) {
97949
99528
  return typeof process.exitCode === "number" ? process.exitCode : 0;
97950
99529
  }
97951
99530
  await runWithContext(createDefaultContext(), async () => {
97952
- const { waitUntilExit } = render_default(import_react59.createElement(AgentMode, { args, projectRoot, statesDir, tasksDir }));
99531
+ const { waitUntilExit } = render_default(import_react62.createElement(AgentMode, { args, projectRoot, statesDir, tasksDir }));
97953
99532
  await waitUntilExit();
97954
99533
  });
97955
99534
  return typeof process.exitCode === "number" ? process.exitCode : 0;
97956
99535
  }
97957
- var import_react59;
99536
+ var import_react62;
97958
99537
  var init_src6 = __esm(async () => {
97959
99538
  init_context();
97960
99539
  init_layout();
@@ -97964,7 +99543,7 @@ var init_src6 = __esm(async () => {
97964
99543
  init_build2(),
97965
99544
  init_AgentMode()
97966
99545
  ]);
97967
- import_react59 = __toESM(require_react(), 1);
99546
+ import_react62 = __toESM(require_react(), 1);
97968
99547
  });
97969
99548
 
97970
99549
  // apps/shell/src/index.ts
@@ -98027,7 +99606,12 @@ ${HELP}
98027
99606
  setDefaultProperties({ subcommand });
98028
99607
  capture("command_run", { subcommand });
98029
99608
  try {
98030
- return await dispatch(subcommand, argv.slice(1));
99609
+ const code = await dispatch(subcommand, argv.slice(1));
99610
+ capture("command_exit", { subcommand, exit_code: code });
99611
+ return code;
99612
+ } catch (err) {
99613
+ captureError("command_error", err, { subcommand });
99614
+ throw err;
98031
99615
  } finally {
98032
99616
  await shutdown();
98033
99617
  }