@neriros/ralphy 3.10.6 → 3.10.8

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.
@@ -18928,8 +18928,8 @@ import { readFileSync } from "fs";
18928
18928
  import { resolve } from "path";
18929
18929
  function getVersion() {
18930
18930
  try {
18931
- if ("3.10.6")
18932
- return "3.10.6";
18931
+ if ("3.10.8")
18932
+ return "3.10.8";
18933
18933
  } catch {}
18934
18934
  const dirsToTry = [];
18935
18935
  try {
@@ -80519,7 +80519,7 @@ function foldLegacyAssignee(v) {
80519
80519
  }
80520
80520
  return rest2;
80521
80521
  }
80522
- var CURRENT_WORKFLOW_VERSION = 3, MarkerSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
80522
+ var CURRENT_WORKFLOW_VERSION = 4, MarkerSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
80523
80523
  var init_schema = __esm(() => {
80524
80524
  init_zod();
80525
80525
  MarkerSchema = exports_external.discriminatedUnion("type", [
@@ -80533,7 +80533,13 @@ var init_schema = __esm(() => {
80533
80533
  exports_external.object({ type: exports_external.literal("project"), value: exports_external.string().min(1) }).strict(),
80534
80534
  exports_external.object({ type: exports_external.literal("comment"), value: exports_external.string().min(1) }).strict()
80535
80535
  ]);
80536
- SET_INDICATOR_KEYS = ["setInProgress", "setDone", "setError", "clearApproved"];
80536
+ SET_INDICATOR_KEYS = [
80537
+ "setInProgress",
80538
+ "setDone",
80539
+ "setPrReady",
80540
+ "setError",
80541
+ "clearApproved"
80542
+ ];
80537
80543
  GetIndicatorSchema = exports_external.object({
80538
80544
  filter: exports_external.array(MarkerSchema).default([])
80539
80545
  });
@@ -80547,6 +80553,7 @@ var init_schema = __esm(() => {
80547
80553
  getAutoApprove: GetIndicatorSchema.optional(),
80548
80554
  setInProgress: SetIndicatorSchema.optional(),
80549
80555
  setDone: SetIndicatorSchema.optional(),
80556
+ setPrReady: SetIndicatorSchema.optional(),
80550
80557
  setError: SetIndicatorSchema.optional(),
80551
80558
  setAwaitingConfirmation: SetIndicatorSchema.optional(),
80552
80559
  clearApproved: SetIndicatorSchema.optional(),
@@ -80851,6 +80858,9 @@ linear:
80851
80858
  # setDone: # status/label to set when the PR is opened
80852
80859
  # type: status
80853
80860
  # value: In Review
80861
+ # setPrReady: # additive: marker set when the PR is ready for human review
80862
+ # type: status # (fires unless the PR is auto-merged immediately; does not replace setDone)
80863
+ # value: In Review
80854
80864
  # setError: # label applied when a task is quarantined
80855
80865
  # type: label
80856
80866
  # value: "ralph:error"
@@ -81072,7 +81082,7 @@ function modelOptionValues() {
81072
81082
  const field = findField("model");
81073
81083
  return field && field.spec.kind === "select" ? field.spec.options.map((o) => o.value) : [];
81074
81084
  }
81075
- var PROMPT_BODY_FIELD_ID = "promptBody", REPO_LINK_FIELD_ID = "repo.link", yes = () => ({ kind: "confirm", defaultChoice: "confirm" }), no = () => ({ kind: "confirm", defaultChoice: "cancel" }), PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_FILTER, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
81085
+ var PROMPT_BODY_FIELD_ID = "promptBody", REPO_LINK_FIELD_ID = "repo.link", AWAITING_STATUS_FIELD_ID = "linear.confirmationMode.awaitingStatus", yes = () => ({ kind: "confirm", defaultChoice: "confirm" }), no = () => ({ kind: "confirm", defaultChoice: "cancel" }), PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_FILTER, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
81076
81086
  var init_fields = __esm(() => {
81077
81087
  PROJECT_NAME = {
81078
81088
  id: "project.name",
@@ -81436,6 +81446,14 @@ var init_fields = __esm(() => {
81436
81446
  spec: { kind: "number", placeholder: "3" },
81437
81447
  when: isOn("linear.confirmationMode.enabled")
81438
81448
  },
81449
+ {
81450
+ id: AWAITING_STATUS_FIELD_ID,
81451
+ label: "Park awaiting-approval tickets in a status?",
81452
+ hint: "e.g. Planned \u2014 blank keeps them In Progress",
81453
+ description: "When the confirmation gate opens, move the ticket to this Linear status so the board shows it waiting on a human (it must be a real status in your team). Ralphy also adds it to the in-progress pickup filter so the parked ticket keeps being polled, and re-asserts In Progress on approval. Leave blank to keep parked tickets in In Progress. Pairs with status-based indicators.",
81454
+ spec: { kind: "text", placeholder: "Planned" },
81455
+ when: isOn("linear.confirmationMode.enabled")
81456
+ },
81439
81457
  {
81440
81458
  id: "linear.indicators",
81441
81459
  label: "Linear lifecycle indicators",
@@ -82908,6 +82926,20 @@ function buildFromAnswers(mode, answers, build = buildWorkflowMarkdown) {
82908
82926
  values2["linear.indicators"] = map3;
82909
82927
  }
82910
82928
  }
82929
+ const parkStatusRaw = values2[AWAITING_STATUS_FIELD_ID];
82930
+ const parkStatus = typeof parkStatusRaw === "string" ? parkStatusRaw.trim() : "";
82931
+ if (values2["linear.confirmationMode.enabled"] === true && parkStatus && values2["linear.indicators"] && typeof values2["linear.indicators"] === "object") {
82932
+ const map3 = { ...values2["linear.indicators"] };
82933
+ map3.setAwaitingConfirmation = { type: "status", value: parkStatus };
82934
+ const existing = map3.getInProgress;
82935
+ const filter2 = existing && !Array.isArray(existing) && "filter" in existing ? [...existing.filter] : [];
82936
+ if (!filter2.some((m) => m.type === "status" && m.value === parkStatus)) {
82937
+ filter2.push({ type: "status", value: parkStatus });
82938
+ }
82939
+ map3.getInProgress = { filter: filter2 };
82940
+ values2["linear.indicators"] = map3;
82941
+ }
82942
+ delete values2[AWAITING_STATUS_FIELD_ID];
82911
82943
  const linkRepo = values2[REPO_LINK_FIELD_ID] === true;
82912
82944
  delete values2[REPO_LINK_FIELD_ID];
82913
82945
  if (!linkRepo) {
@@ -83931,6 +83963,12 @@ var init_SetupWizard = __esm(async () => {
83931
83963
  description: "Set when the task finishes and its pull request is opened.",
83932
83964
  slots: ["setDone"]
83933
83965
  },
83966
+ {
83967
+ key: "prReady",
83968
+ label: "PR ready",
83969
+ description: "Optional, additive: set when the PR is marked ready for human review (non-draft), layered on top of Done. Skipped only on the immediate non-draft auto-merge path.",
83970
+ slots: ["setPrReady"]
83971
+ },
83934
83972
  {
83935
83973
  key: "error",
83936
83974
  label: "Error",
@@ -84019,6 +84057,11 @@ var init_migrations = __esm(() => {
84019
84057
  version: 3,
84020
84058
  description: "The per-workflow `linear.assignee` setting is replaced by a global " + "`linear.filter` expression (e.g. `assignee = me`) applied to every " + "ticket fetch. Existing `assignee` values are folded in automatically; " + "note that an empty filter now defaults to `assignee = me` (it previously " + "meant unassigned-only).",
84021
84059
  fields: ["linear.filter"]
84060
+ },
84061
+ {
84062
+ version: 4,
84063
+ description: "A new additive `setPrReady` Linear indicator marks a ticket the moment its " + "PR is human-mergeable (ready, non-draft), layered on top of `setDone`. " + "Re-run the indicator builder to add it, or keep your current indicators.",
84064
+ fields: ["linear.indicators"]
84022
84065
  }
84023
84066
  ];
84024
84067
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max2, migration) => Math.max(max2, migration.version), 0);
@@ -85044,6 +85087,89 @@ var init_schema2 = __esm(() => {
85044
85087
  ALL_OWNED_SLOTS = new Set(Object.values(OWNERSHIP).flatMap((slots) => [...slots]));
85045
85088
  });
85046
85089
 
85090
+ // packages/core/src/state/sidecar.ts
85091
+ import { dirname as dirname5, join as join9 } from "path";
85092
+ import { mkdir as mkdir3, rename, unlink } from "fs/promises";
85093
+ function slotSidecarPath(changeDir, slot) {
85094
+ return join9(changeDir, `${CORE_STATE_FILE.replace(/\.json$/, "")}.${slot}.json`);
85095
+ }
85096
+ function parseObject(text) {
85097
+ if (text === null)
85098
+ return null;
85099
+ try {
85100
+ const parsed = JSON.parse(text);
85101
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
85102
+ return parsed;
85103
+ }
85104
+ return null;
85105
+ } catch {
85106
+ return null;
85107
+ }
85108
+ }
85109
+ function deepSet(target, path, value) {
85110
+ if (path === "") {
85111
+ for (const k of Object.keys(target))
85112
+ delete target[k];
85113
+ if (value && typeof value === "object" && !Array.isArray(value)) {
85114
+ Object.assign(target, value);
85115
+ }
85116
+ return;
85117
+ }
85118
+ const segments = path.split(".");
85119
+ let cursor = target;
85120
+ for (let i = 0;i < segments.length - 1; i++) {
85121
+ const key = segments[i];
85122
+ const existing = cursor[key];
85123
+ if (existing === null || typeof existing !== "object" || Array.isArray(existing)) {
85124
+ const next = {};
85125
+ cursor[key] = next;
85126
+ cursor = next;
85127
+ } else {
85128
+ cursor = existing;
85129
+ }
85130
+ }
85131
+ cursor[segments[segments.length - 1]] = value;
85132
+ }
85133
+ async function atomicWrite(path, content) {
85134
+ await mkdir3(dirname5(path), { recursive: true });
85135
+ const tmp = `${path}.tmp-${process.pid}-${writeSeq++}`;
85136
+ try {
85137
+ await Bun.write(tmp, content);
85138
+ await rename(tmp, path);
85139
+ } catch (err) {
85140
+ await unlink(tmp).catch(() => {});
85141
+ throw err;
85142
+ }
85143
+ }
85144
+ async function readSlotSidecar(changeDir, slot) {
85145
+ const file2 = Bun.file(slotSidecarPath(changeDir, slot));
85146
+ if (!await file2.exists())
85147
+ return;
85148
+ const obj = parseObject(await file2.text().catch(() => null));
85149
+ return obj ?? undefined;
85150
+ }
85151
+ async function writeSlotField(changeDir, path, value, seedInline) {
85152
+ const [slot, ...rest2] = path.split(".");
85153
+ const sidecarPath = slotSidecarPath(changeDir, slot);
85154
+ const existing = parseObject(await Bun.file(sidecarPath).text().catch(() => null));
85155
+ const obj = existing ?? (seedInline ? structuredClone(seedInline) : {});
85156
+ deepSet(obj, rest2.join("."), value);
85157
+ await atomicWrite(sidecarPath, JSON.stringify(obj, null, 2) + `
85158
+ `);
85159
+ }
85160
+ function overlaySidecarsSync(changeDir, target, read) {
85161
+ for (const slot of ALL_OWNED_SLOTS) {
85162
+ const obj = parseObject(read(slotSidecarPath(changeDir, slot)));
85163
+ if (obj !== undefined && obj !== null)
85164
+ target[slot] = obj;
85165
+ }
85166
+ return target;
85167
+ }
85168
+ var CORE_STATE_FILE = ".ralph-state.json", writeSeq = 0;
85169
+ var init_sidecar = __esm(() => {
85170
+ init_schema2();
85171
+ });
85172
+
85047
85173
  // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
85048
85174
  var util, objectUtil, ZodParsedType, getParsedType2 = (data) => {
85049
85175
  const t = typeof data;
@@ -89147,16 +89273,24 @@ function formatTaskName(name) {
89147
89273
  }
89148
89274
 
89149
89275
  // packages/core/src/state.ts
89150
- import { join as join9 } from "path";
89276
+ import { join as join10 } from "path";
89277
+ function stripOwnedSlots(state) {
89278
+ const out = { ...state };
89279
+ for (const slot of ALL_OWNED_SLOTS)
89280
+ delete out[slot];
89281
+ return out;
89282
+ }
89151
89283
  function readState(changeDir) {
89152
- const filePath = join9(changeDir, STATE_FILE2);
89284
+ const filePath = join10(changeDir, STATE_FILE2);
89153
89285
  const raw = getStorage().read(filePath);
89154
89286
  if (raw === null)
89155
89287
  throw new Error(".ralph-state.json not found");
89156
- return StateSchema.parse(JSON.parse(raw));
89288
+ const base2 = JSON.parse(raw);
89289
+ overlaySidecarsSync(changeDir, base2, (p) => getStorage().read(p));
89290
+ return StateSchema.parse(base2);
89157
89291
  }
89158
89292
  function tryReadStateRaw(changeDir) {
89159
- const filePath = join9(changeDir, STATE_FILE2);
89293
+ const filePath = join10(changeDir, STATE_FILE2);
89160
89294
  const text = getStorage().read(filePath);
89161
89295
  if (text === null)
89162
89296
  return { state: null, raw: null };
@@ -89167,12 +89301,14 @@ function tryReadStateRaw(changeDir) {
89167
89301
  return { state: null, raw: null };
89168
89302
  }
89169
89303
  const raw = parsed && typeof parsed === "object" ? parsed : {};
89170
- const result2 = StateSchema.safeParse(parsed);
89304
+ overlaySidecarsSync(changeDir, raw, (p) => getStorage().read(p));
89305
+ const result2 = StateSchema.safeParse(raw);
89171
89306
  return { state: result2.success ? result2.data : null, raw };
89172
89307
  }
89173
89308
  function writeState(changeDir, state) {
89174
- const filePath = join9(changeDir, STATE_FILE2);
89175
- getStorage().write(filePath, JSON.stringify(state, null, 2) + `
89309
+ const filePath = join10(changeDir, STATE_FILE2);
89310
+ const core2 = stripOwnedSlots(state);
89311
+ getStorage().write(filePath, JSON.stringify(core2, null, 2) + `
89176
89312
  `);
89177
89313
  }
89178
89314
  function updateState(changeDir, updater) {
@@ -89211,7 +89347,7 @@ function buildInitialState(options) {
89211
89347
  });
89212
89348
  }
89213
89349
  function ensureState(changeDir) {
89214
- const filePath = join9(changeDir, STATE_FILE2);
89350
+ const filePath = join10(changeDir, STATE_FILE2);
89215
89351
  const storage = getStorage();
89216
89352
  if (storage.read(filePath) !== null) {
89217
89353
  return readState(changeDir);
@@ -89225,11 +89361,12 @@ var STATE_FILE2 = ".ralph-state.json";
89225
89361
  var init_state = __esm(() => {
89226
89362
  init_types2();
89227
89363
  init_context();
89364
+ init_schema2();
89365
+ init_sidecar();
89228
89366
  });
89229
89367
 
89230
89368
  // packages/core/src/state/store.ts
89231
- import { dirname as dirname5, join as join10 } from "path";
89232
- import { mkdir as mkdir3 } from "fs/promises";
89369
+ import { join as join11 } from "path";
89233
89370
  async function readJson(filePath) {
89234
89371
  const file2 = Bun.file(filePath);
89235
89372
  if (!await file2.exists())
@@ -89244,22 +89381,6 @@ async function readJson(filePath) {
89244
89381
  return {};
89245
89382
  }
89246
89383
  }
89247
- function deepSet(target, path, value) {
89248
- const segments = path.split(".");
89249
- let cursor = target;
89250
- for (let i = 0;i < segments.length - 1; i++) {
89251
- const key = segments[i];
89252
- const existing = cursor[key];
89253
- if (existing === undefined || existing === null || typeof existing !== "object" || Array.isArray(existing)) {
89254
- const next = {};
89255
- cursor[key] = next;
89256
- cursor = next;
89257
- } else {
89258
- cursor = existing;
89259
- }
89260
- }
89261
- cursor[segments[segments.length - 1]] = value;
89262
- }
89263
89384
  async function writeField(changeDir, featureName, path, value) {
89264
89385
  const allowed = OWNERSHIP[featureName];
89265
89386
  if (!allowed) {
@@ -89269,16 +89390,15 @@ async function writeField(changeDir, featureName, path, value) {
89269
89390
  if (!allowed.includes(topSlot)) {
89270
89391
  throw new OwnershipError(featureName, path, `feature '${featureName}' may not write '${path}' (owns ${allowed.join(", ")})`);
89271
89392
  }
89272
- const filePath = join10(changeDir, STATE_FILE3);
89273
- const existing = await readJson(filePath);
89274
- deepSet(existing, path, value);
89275
- await mkdir3(dirname5(filePath), { recursive: true });
89276
- await Bun.write(filePath, JSON.stringify(existing, null, 2) + `
89277
- `);
89393
+ const inline = (await readJson(join11(changeDir, STATE_FILE3)))[topSlot];
89394
+ const seed = inline && typeof inline === "object" && !Array.isArray(inline) ? inline : undefined;
89395
+ await writeSlotField(changeDir, path, value, seed);
89278
89396
  }
89279
89397
  var STATE_FILE3 = ".ralph-state.json", OwnershipError;
89280
89398
  var init_store = __esm(() => {
89281
89399
  init_schema2();
89400
+ init_sidecar();
89401
+ init_sidecar();
89282
89402
  init_state();
89283
89403
  OwnershipError = class OwnershipError extends Error {
89284
89404
  featureName;
@@ -89293,14 +89413,14 @@ var init_store = __esm(() => {
89293
89413
  });
89294
89414
 
89295
89415
  // apps/loop/src/components/TaskStatus.tsx
89296
- import { join as join11 } from "path";
89416
+ import { join as join12 } from "path";
89297
89417
  function TaskStatus({ state, stateDir }) {
89298
89418
  const storage = getStorage();
89299
89419
  const cost = Math.round(state.usage.total_cost_usd * 100) / 100;
89300
89420
  const time3 = Math.round(state.usage.total_duration_ms / 1000 * 10) / 10 + "s";
89301
89421
  const artifacts = OPENSPEC_ARTIFACTS.map((name) => ({
89302
89422
  name,
89303
- exists: storage.read(join11(stateDir, name)) !== null
89423
+ exists: storage.read(join12(stateDir, name)) !== null
89304
89424
  }));
89305
89425
  const recent = state.history.slice(-10);
89306
89426
  return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
@@ -98017,8 +98137,8 @@ var init_rate_limit_detection = __esm(() => {
98017
98137
  });
98018
98138
 
98019
98139
  // packages/engine/src/agents/claude.ts
98020
- import { mkdtemp, unlink } from "fs/promises";
98021
- import { join as join12 } from "path";
98140
+ import { mkdtemp, unlink as unlink2 } from "fs/promises";
98141
+ import { join as join13 } from "path";
98022
98142
  import { tmpdir } from "os";
98023
98143
  function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, reviewerModel) {
98024
98144
  const effectiveModel = reviewerModel ?? model;
@@ -98039,7 +98159,7 @@ function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, review
98039
98159
  }
98040
98160
  async function runInteractive(req) {
98041
98161
  const { model, prompt, taskDir } = req;
98042
- const promptFile = taskDir ? join12(taskDir, "_interactive_prompt.md") : join12(await mkdtemp(join12(tmpdir(), "ralph-")), "prompt.md");
98162
+ const promptFile = taskDir ? join13(taskDir, "_interactive_prompt.md") : join13(await mkdtemp(join13(tmpdir(), "ralph-")), "prompt.md");
98043
98163
  await Bun.write(promptFile, prompt);
98044
98164
  try {
98045
98165
  const cmd = [
@@ -98066,14 +98186,14 @@ async function runInteractive(req) {
98066
98186
  env: scrubClaudeEnv(process.env)
98067
98187
  });
98068
98188
  const exitCode = await proc.exited;
98069
- const doneFile = taskDir ? join12(taskDir, "_interactive_done") : null;
98189
+ const doneFile = taskDir ? join13(taskDir, "_interactive_done") : null;
98070
98190
  if (doneFile && await Bun.file(doneFile).exists()) {
98071
98191
  return { exitCode: 0, usage: null, sessionId: null, rateLimited: false };
98072
98192
  }
98073
98193
  return { exitCode, usage: null, sessionId: null, rateLimited: false };
98074
98194
  } finally {
98075
98195
  try {
98076
- await unlink(promptFile);
98196
+ await unlink2(promptFile);
98077
98197
  } catch {}
98078
98198
  }
98079
98199
  }
@@ -99105,6 +99225,12 @@ class FlowActorStore {
99105
99225
  return typeof s.value === "string" || typeof s.status === "string";
99106
99226
  }
99107
99227
  async loadSnapshot(changeDir) {
99228
+ const sidecar = await readSlotSidecar(changeDir, "flow");
99229
+ if (sidecar && typeof sidecar === "object") {
99230
+ const snap = sidecar.actorSnapshot;
99231
+ if (snap !== undefined && snap !== null)
99232
+ return snap;
99233
+ }
99108
99234
  const filePath = `${changeDir}/${STATE_FILE4}`;
99109
99235
  const file2 = Bun.file(filePath);
99110
99236
  if (!await file2.exists())
@@ -99563,11 +99689,11 @@ var init_meta_prompt = __esm(() => {
99563
99689
  });
99564
99690
 
99565
99691
  // packages/core/src/loop.ts
99566
- import { join as join13 } from "path";
99692
+ import { join as join14 } from "path";
99567
99693
  function buildTaskPrompt(state, taskDir, reviewPhase) {
99568
99694
  const storage = getStorage();
99569
99695
  let prompt = "";
99570
- const steeringContent = storage.read(join13(taskDir, "steering.md"));
99696
+ const steeringContent = storage.read(join14(taskDir, "steering.md"));
99571
99697
  if (steeringContent !== null) {
99572
99698
  const steeringLines = steeringContent.split(`
99573
99699
  `).filter((line) => !line.startsWith("#")).filter((line) => line.trim()).slice(0, STEERING_MAX_LINES);
@@ -99586,8 +99712,8 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
99586
99712
  `;
99587
99713
  }
99588
99714
  }
99589
- const agentTasksPath = join13(taskDir, AGENT_TASKS_FILENAME);
99590
- const missionTasksPath = join13(taskDir, MISSION_TASKS_FILENAME);
99715
+ const agentTasksPath = join14(taskDir, AGENT_TASKS_FILENAME);
99716
+ const missionTasksPath = join14(taskDir, MISSION_TASKS_FILENAME);
99591
99717
  const agentTasksContent = storage.read(agentTasksPath);
99592
99718
  const missionTasksContent = storage.read(missionTasksPath);
99593
99719
  let activePath = null;
@@ -99663,7 +99789,7 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
99663
99789
  }
99664
99790
  }
99665
99791
  if (reviewPhase?.enabled) {
99666
- const reviewFindingsPath = join13(taskDir, "review-findings.md");
99792
+ const reviewFindingsPath = join14(taskDir, "review-findings.md");
99667
99793
  const reviewFindingsContent = storage.read(reviewFindingsPath);
99668
99794
  const hasUncheckedMission = missionTasksContent !== null && /^- \[ \]/m.test(missionTasksContent);
99669
99795
  const hasUncheckedAgent = agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent);
@@ -99737,7 +99863,7 @@ When all tasks are complete and all files are committed, push your branch and op
99737
99863
  }
99738
99864
  function buildSteeringBlock(taskDir) {
99739
99865
  const storage = getStorage();
99740
- const steeringContent = storage.read(join13(taskDir, "steering.md"));
99866
+ const steeringContent = storage.read(join14(taskDir, "steering.md"));
99741
99867
  if (steeringContent === null)
99742
99868
  return "";
99743
99869
  const steeringLines = steeringContent.split(`
@@ -99835,7 +99961,7 @@ function buildPlanPrompt(state, taskDir) {
99835
99961
  return prompt;
99836
99962
  }
99837
99963
  function buildReviewPrompt(state, taskDir) {
99838
- const reviewFindingsPath = join13(taskDir, "review-findings.md");
99964
+ const reviewFindingsPath = join14(taskDir, "review-findings.md");
99839
99965
  let prompt = buildSteeringBlock(taskDir);
99840
99966
  prompt += `---
99841
99967
 
@@ -99900,7 +100026,7 @@ function buildPhasePrompt(phase, state, taskDir, reviewPhase, metaPromptOptions)
99900
100026
  }
99901
100027
  function checkStopSignal(taskDir, stateDir) {
99902
100028
  const storage = getStorage();
99903
- const stopFile = join13(taskDir, "STOP");
100029
+ const stopFile = join14(taskDir, "STOP");
99904
100030
  const reason = storage.read(stopFile);
99905
100031
  if (reason === null)
99906
100032
  return null;
@@ -99960,7 +100086,7 @@ function updateStateIteration(stateDir, result2, startedAt, engine, model, usage
99960
100086
  }
99961
100087
  function appendSteeringMessage(taskDir, message) {
99962
100088
  const storage = getStorage();
99963
- const steeringPath = join13(taskDir, "steering.md");
100089
+ const steeringPath = join14(taskDir, "steering.md");
99964
100090
  const existing = storage.read(steeringPath);
99965
100091
  const updated = existing ? `${message}
99966
100092
 
@@ -100010,7 +100136,7 @@ var init_loop2 = __esm(() => {
100010
100136
  });
100011
100137
 
100012
100138
  // apps/loop/src/hooks/useLoop.ts
100013
- import { join as join14 } from "path";
100139
+ import { join as join15 } from "path";
100014
100140
  function sleep(seconds) {
100015
100141
  return new Promise((resolve3) => setTimeout(resolve3, seconds * 1000));
100016
100142
  }
@@ -100072,7 +100198,7 @@ function useLoop(opts) {
100072
100198
  }
100073
100199
  } else {
100074
100200
  if (rawState !== null) {
100075
- addInfo(`.ralph-state.json was malformed \u2014 reinitialising. External fields (linearComments, specAttachments) preserved.`);
100201
+ addInfo(`.ralph-state.json was malformed \u2014 reinitialising. Feature-owned slots (linearComments, specAttachments, \u2026) live in their own sidecar files and are unaffected.`);
100076
100202
  }
100077
100203
  currentState = buildInitialState({
100078
100204
  name: opts.name,
@@ -100082,12 +100208,6 @@ function useLoop(opts) {
100082
100208
  manualTest: opts.manualTest,
100083
100209
  createPr: opts.createPr ?? false
100084
100210
  });
100085
- if (rawState !== null && rawState.linearComments) {
100086
- currentState.linearComments = rawState.linearComments;
100087
- }
100088
- if (rawState !== null && rawState.specAttachments) {
100089
- currentState.specAttachments = rawState.specAttachments;
100090
- }
100091
100211
  writeState(stateDir, currentState);
100092
100212
  }
100093
100213
  const isResume2 = currentState.iteration > 0;
@@ -100132,8 +100252,8 @@ function useLoop(opts) {
100132
100252
  setState(currentState);
100133
100253
  if (!actor.getSnapshot().matches("running"))
100134
100254
  break;
100135
- const tasksContent = storage.read(join14(tasksDir, MISSION_TASKS_FILENAME));
100136
- const agentTasksContent = storage.read(join14(tasksDir, AGENT_TASKS_FILENAME));
100255
+ const tasksContent = storage.read(join15(tasksDir, MISSION_TASKS_FILENAME));
100256
+ const agentTasksContent = storage.read(join15(tasksDir, AGENT_TASKS_FILENAME));
100137
100257
  if (tasksContent === null && currentState.iteration > 0 && typeof opts.changeStore.listChanges === "function") {
100138
100258
  let stillActive = true;
100139
100259
  try {
@@ -100170,7 +100290,7 @@ function useLoop(opts) {
100170
100290
  const agentDone = agentTasksContent === null || allCompleted(agentTasksContent);
100171
100291
  if (missionDone && agentDone && tasksContent !== null) {
100172
100292
  if (opts.reviewPhase?.enabled) {
100173
- const reviewFindingsPath = join14(tasksDir, "review-findings.md");
100293
+ const reviewFindingsPath = join15(tasksDir, "review-findings.md");
100174
100294
  const reviewFindingsFile = Bun.file(reviewFindingsPath);
100175
100295
  const findingsExists = await reviewFindingsFile.exists();
100176
100296
  const findingsContent = findingsExists ? await reviewFindingsFile.text() : null;
@@ -100199,7 +100319,7 @@ function useLoop(opts) {
100199
100319
  model: opts.reviewPhase.reviewerModel ?? opts.model,
100200
100320
  prompt: reviewPrompt,
100201
100321
  logFlag: opts.log,
100202
- logFile: join14(stateDir, `log-review-${roundNum}.json`),
100322
+ logFile: join15(stateDir, `log-review-${roundNum}.json`),
100203
100323
  taskDir: tasksDir,
100204
100324
  reviewerContextStrategy: opts.reviewPhase.reviewerContextStrategy ?? "fresh",
100205
100325
  onFeedEvent: addFeedEvent
@@ -100273,8 +100393,8 @@ function useLoop(opts) {
100273
100393
  const time3 = new Date().toLocaleTimeString("en-US", { hour12: false });
100274
100394
  addIterationHeader(localIter, time3);
100275
100395
  addInfo(`Iteration ${localIter} (total: ${currentState.iteration})`);
100276
- const proposalContent = storage.read(join14(tasksDir, "proposal.md"));
100277
- const designContent = storage.read(join14(tasksDir, "design.md"));
100396
+ const proposalContent = storage.read(join15(tasksDir, "proposal.md"));
100397
+ const designContent = storage.read(join15(tasksDir, "design.md"));
100278
100398
  const routedPhase = routeTaskPhase(opts.phase, {
100279
100399
  proposal: proposalContent,
100280
100400
  design: designContent,
@@ -100294,7 +100414,7 @@ function useLoop(opts) {
100294
100414
  model: opts.model,
100295
100415
  prompt,
100296
100416
  logFlag: opts.log,
100297
- logFile: join14(stateDir, "log.json"),
100417
+ logFile: join15(stateDir, "log.json"),
100298
100418
  taskDir: tasksDir,
100299
100419
  interactive: false,
100300
100420
  onFeedEvent: addFeedEvent,
@@ -100317,7 +100437,7 @@ function useLoop(opts) {
100317
100437
  model: opts.model,
100318
100438
  prompt: buildSteeringPrompt(steerMessage),
100319
100439
  logFlag: opts.log,
100320
- logFile: join14(stateDir, "log.json"),
100440
+ logFile: join15(stateDir, "log.json"),
100321
100441
  taskDir: tasksDir,
100322
100442
  onFeedEvent: addResumeFeedEvent,
100323
100443
  signal: resumeController.signal,
@@ -100624,7 +100744,7 @@ var init_TaskLoop = __esm(async () => {
100624
100744
  });
100625
100745
 
100626
100746
  // apps/loop/src/components/App.tsx
100627
- import { join as join15 } from "path";
100747
+ import { join as join16 } from "path";
100628
100748
  function ExitAfterRender({ children }) {
100629
100749
  const { exit } = use_app_default();
100630
100750
  import_react59.useEffect(() => {
@@ -100677,7 +100797,7 @@ function App2({ args, taskPhase }) {
100677
100797
  }
100678
100798
  const layout = getLayout();
100679
100799
  const stateDir = layout.taskStateDir(args.name);
100680
- if (getStorage().read(join15(stateDir, ".ralph-state.json")) === null) {
100800
+ if (getStorage().read(join16(stateDir, ".ralph-state.json")) === null) {
100681
100801
  return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
100682
100802
  message: `Error: change '${args.name}' not found`
100683
100803
  }, undefined, false, undefined, this);
@@ -100731,7 +100851,7 @@ var init_App2 = __esm(async () => {
100731
100851
 
100732
100852
  // packages/log/src/log.ts
100733
100853
  import { appendFile } from "fs/promises";
100734
- import { join as join16, dirname as dirname7 } from "path";
100854
+ import { join as join17, dirname as dirname7 } from "path";
100735
100855
  import { homedir as homedir4 } from "os";
100736
100856
  import { mkdir as mkdir5 } from "fs/promises";
100737
100857
  function fmt(type, text) {
@@ -100780,14 +100900,14 @@ var init_log = __esm(() => {
100780
100900
  init_version();
100781
100901
  jsonLogChains = new Map;
100782
100902
  ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
100783
- AGENT_LOG_PATH = join16(homedir4(), ".ralph", "agent-mode.log");
100903
+ AGENT_LOG_PATH = join17(homedir4(), ".ralph", "agent-mode.log");
100784
100904
  mkdir5(dirname7(AGENT_LOG_PATH), { recursive: true }).catch(() => {
100785
100905
  return;
100786
100906
  });
100787
100907
  });
100788
100908
 
100789
100909
  // apps/loop/src/debug.ts
100790
- import { join as join17 } from "path";
100910
+ import { join as join18 } from "path";
100791
100911
  function fmtTs(d) {
100792
100912
  return d.toISOString().replace("T", " ").slice(0, 23);
100793
100913
  }
@@ -100899,7 +101019,7 @@ function detectDebugStuck(lines) {
100899
101019
  };
100900
101020
  }
100901
101021
  async function inspectBinary(projectRoot) {
100902
- const binPath = join17(projectRoot, ".ralph", "bin", "cli.js");
101022
+ const binPath = join18(projectRoot, ".ralph", "bin", "cli.js");
100903
101023
  const file2 = Bun.file(binPath);
100904
101024
  if (!await file2.exists())
100905
101025
  return null;
@@ -100924,7 +101044,7 @@ async function inspectBinary(projectRoot) {
100924
101044
  async function resolveDebugTarget(projectRoot, opts) {
100925
101045
  const agentLogFile = Bun.file(AGENT_LOG_PATH);
100926
101046
  const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
100927
- const jsonlLogFile = Bun.file(join17(projectRoot, ".ralph", "agent.log"));
101047
+ const jsonlLogFile = Bun.file(join18(projectRoot, ".ralph", "agent.log"));
100928
101048
  const jsonlLines = await jsonlLogFile.exists() ? parseJsonlLog(await jsonlLogFile.text()) : [];
100929
101049
  const allLines = [...textLines, ...jsonlLines];
100930
101050
  if (opts.name && !opts.issue) {
@@ -101029,7 +101149,7 @@ async function runDebug(opts) {
101029
101149
  `);
101030
101150
  const agentLogFile = Bun.file(AGENT_LOG_PATH);
101031
101151
  const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
101032
- const jsonlLogPath = join17(projectRoot, ".ralph", "agent.log");
101152
+ const jsonlLogPath = join18(projectRoot, ".ralph", "agent.log");
101033
101153
  const jsonlLogFile = Bun.file(jsonlLogPath);
101034
101154
  const hasJsonlLog = await jsonlLogFile.exists();
101035
101155
  let { changeName, identifier: issueIdentifier } = await resolveDebugTarget(projectRoot, {
@@ -101043,7 +101163,7 @@ async function runDebug(opts) {
101043
101163
  }
101044
101164
  const jsonlLines = hasJsonlLog ? parseJsonlLog(await jsonlLogFile.text(), changeName) : [];
101045
101165
  const relevantText = textLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
101046
- const workerLogFile = Bun.file(join17(projectRoot, ".ralph", "logs", `${changeName}.log`));
101166
+ const workerLogFile = Bun.file(join18(projectRoot, ".ralph", "logs", `${changeName}.log`));
101047
101167
  const workerLines = await workerLogFile.exists() ? parseTextLog(await workerLogFile.text()) : [];
101048
101168
  const merged = [...relevantText, ...jsonlLines, ...workerLines].sort((a, b) => +a.ts - +b.ts);
101049
101169
  const seen = new Set;
@@ -101190,6 +101310,8 @@ async function runDebug(opts) {
101190
101310
  out(" \u26A0 setError applied \u2014 issue is quarantined in Linear");
101191
101311
  if (logHas("setDone applied"))
101192
101312
  out(" \u2713 setDone applied \u2014 issue marked done in Linear");
101313
+ if (logHas("setPrReady applied"))
101314
+ out(" \u2713 setPrReady applied \u2014 PR is ready for human review");
101193
101315
  if (logHas("clearConflicted applied"))
101194
101316
  out(" \u2713 clearConflicted applied");
101195
101317
  if (logHas("setConflicted applied"))
@@ -101200,8 +101322,8 @@ async function runDebug(opts) {
101200
101322
  out(" \u26A0 PR currently has merge conflicts");
101201
101323
  if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
101202
101324
  out(" \u26A0 PR has failing CI checks");
101203
- const worktreePath = join17(projectRoot, ".ralph", "worktrees", changeName);
101204
- if (await Bun.file(join17(worktreePath, ".git")).exists()) {
101325
+ const worktreePath = join18(projectRoot, ".ralph", "worktrees", changeName);
101326
+ if (await Bun.file(join18(worktreePath, ".git")).exists()) {
101205
101327
  out(` Worktree : ${worktreePath}`);
101206
101328
  }
101207
101329
  if (!timeline.length)
@@ -101221,12 +101343,12 @@ __export(exports_src2, {
101221
101343
  taskMain: () => taskMain,
101222
101344
  main: () => main2
101223
101345
  });
101224
- import { join as join18 } from "path";
101346
+ import { join as join19 } from "path";
101225
101347
  import { exists as exists2, mkdir as mkdir6, rm as rm2 } from "fs/promises";
101226
101348
  async function ensureRalphGitignore(projectRoot) {
101227
- const ralphDir = join18(projectRoot, ".ralph");
101349
+ const ralphDir = join19(projectRoot, ".ralph");
101228
101350
  await mkdir6(ralphDir, { recursive: true });
101229
- const gitignorePath = join18(ralphDir, ".gitignore");
101351
+ const gitignorePath = join19(ralphDir, ".gitignore");
101230
101352
  const file2 = Bun.file(gitignorePath);
101231
101353
  if (await file2.exists()) {
101232
101354
  const existing = await file2.text();
@@ -101293,9 +101415,9 @@ async function main2(argv) {
101293
101415
  `);
101294
101416
  return 1;
101295
101417
  }
101296
- const worktreeDir = join18(worktreesDir(projectRoot), args.name);
101297
- const changeDir = join18(tasksDir, args.name);
101298
- const stateDir = join18(statesDir, args.name);
101418
+ const worktreeDir = join19(worktreesDir(projectRoot), args.name);
101419
+ const changeDir = join19(tasksDir, args.name);
101420
+ const stateDir = join19(statesDir, args.name);
101299
101421
  const branch = `ralph/${args.name}`;
101300
101422
  const removed = [];
101301
101423
  if (await exists2(worktreeDir)) {
@@ -101346,8 +101468,8 @@ async function main2(argv) {
101346
101468
  return 0;
101347
101469
  }
101348
101470
  if (args.mode === "task" && args.name) {
101349
- await mkdir6(join18(statesDir, args.name), { recursive: true });
101350
- await mkdir6(join18(tasksDir, args.name), { recursive: true });
101471
+ await mkdir6(join19(statesDir, args.name), { recursive: true });
101472
+ await mkdir6(join19(tasksDir, args.name), { recursive: true });
101351
101473
  await ensureRalphGitignore(projectRoot);
101352
101474
  }
101353
101475
  await runWithContext(createDefaultContext({ layout, args }), async () => {
@@ -101375,8 +101497,8 @@ async function taskMain(argv) {
101375
101497
  const layout = projectLayout(projectRoot);
101376
101498
  const statesDir = layout.statesDir;
101377
101499
  const tasksDir = layout.tasksDir;
101378
- await mkdir6(join18(statesDir, args.name), { recursive: true });
101379
- await mkdir6(join18(tasksDir, args.name), { recursive: true });
101500
+ await mkdir6(join19(statesDir, args.name), { recursive: true });
101501
+ await mkdir6(join19(tasksDir, args.name), { recursive: true });
101380
101502
  await ensureRalphGitignore(projectRoot);
101381
101503
  await runWithContext(createDefaultContext({ layout, args }), async () => {
101382
101504
  const { waitUntilExit } = render_default(import_react60.createElement(App2, {
@@ -101639,6 +101761,7 @@ var init_cli2 = __esm(() => {
101639
101761
  "getAutoMerge",
101640
101762
  "setInProgress",
101641
101763
  "setDone",
101764
+ "setPrReady",
101642
101765
  "setError"
101643
101766
  ]);
101644
101767
  GET_KEYS = new Set(["getTodo", "getInProgress", "getAutoMerge"]);
@@ -101677,9 +101800,10 @@ var init_cli2 = __esm(() => {
101677
101800
  " --worktree Run each task in its own git worktree",
101678
101801
  " --indicator <k>:<t>:<v> Override an indicator (repeatable).",
101679
101802
  " Keys: getTodo, getInProgress, getAutoMerge,",
101680
- " setInProgress, setDone, setError",
101803
+ " setInProgress, setDone, setPrReady, setError",
101681
101804
  " Types: label, status, attachment, project, comment",
101682
101805
  " --indicator setInProgress:attachment:In Progress",
101806
+ " --indicator setPrReady:status:In Review (additive ready marker)",
101683
101807
  " (attachment upserts a single 'Ralphy' entry; value = subtitle)",
101684
101808
  " --create-pr Push the worker branch and open a GitHub PR on success (needs --worktree)",
101685
101809
  " --fix-ci After opening the PR, re-run on CI failures until green (needs --create-pr)",
@@ -101773,7 +101897,7 @@ function formatError2(err) {
101773
101897
  }
101774
101898
 
101775
101899
  // apps/agent/src/shared/capabilities/fs-change.ts
101776
- import { join as join19, dirname as dirname8 } from "path";
101900
+ import { join as join20, dirname as dirname8 } from "path";
101777
101901
  import { mkdir as mkdir7 } from "fs/promises";
101778
101902
  var scaffold, prependTask, appendSteering, fsChange;
101779
101903
  var init_fs_change = __esm(() => {
@@ -101786,11 +101910,11 @@ var init_fs_change = __esm(() => {
101786
101910
  errorFormatter: formatError2,
101787
101911
  run: async (args) => {
101788
101912
  await mkdir7(args.changeDir, { recursive: true });
101789
- await mkdir7(join19(args.changeDir, "specs"), { recursive: true });
101913
+ await mkdir7(join20(args.changeDir, "specs"), { recursive: true });
101790
101914
  await mkdir7(args.stateDir, { recursive: true });
101791
- await Bun.write(join19(args.changeDir, "proposal.md"), args.proposal);
101792
- await Bun.write(join19(args.changeDir, "tasks.md"), args.tasks);
101793
- await Bun.write(join19(args.changeDir, "design.md"), args.design);
101915
+ await Bun.write(join20(args.changeDir, "proposal.md"), args.proposal);
101916
+ await Bun.write(join20(args.changeDir, "tasks.md"), args.tasks);
101917
+ await Bun.write(join20(args.changeDir, "design.md"), args.design);
101794
101918
  }
101795
101919
  };
101796
101920
  prependTask = {
@@ -101808,7 +101932,7 @@ var init_fs_change = __esm(() => {
101808
101932
  retryPolicy: NO_RETRY,
101809
101933
  errorFormatter: formatError2,
101810
101934
  run: async (args) => {
101811
- const path = join19(args.changeDir, "steering.md");
101935
+ const path = join20(args.changeDir, "steering.md");
101812
101936
  const f2 = Bun.file(path);
101813
101937
  const existing = await f2.exists() ? await f2.text() : null;
101814
101938
  const updated = existing ? `${args.message}
@@ -101823,11 +101947,11 @@ ${existing.trimStart()}` : `${args.message}
101823
101947
  });
101824
101948
 
101825
101949
  // apps/agent/src/agent/worktree.ts
101826
- import { basename as basename2, join as join20 } from "path";
101950
+ import { basename as basename2, join as join21 } from "path";
101827
101951
  import { homedir as homedir5 } from "os";
101828
101952
  import { exists as exists3 } from "fs/promises";
101829
101953
  function worktreesDir2(projectRoot) {
101830
- return join20(homedir5(), ".ralph", basename2(projectRoot), "worktrees");
101954
+ return join21(homedir5(), ".ralph", basename2(projectRoot), "worktrees");
101831
101955
  }
101832
101956
  function branchForChange(changeName) {
101833
101957
  return `ralph/${changeName}`;
@@ -101846,7 +101970,7 @@ function createWorktree(projectRoot, changeName, baseBranch, runner) {
101846
101970
  }
101847
101971
  async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
101848
101972
  const dir = worktreesDir2(projectRoot);
101849
- const cwd2 = join20(dir, changeName);
101973
+ const cwd2 = join21(dir, changeName);
101850
101974
  const branch = branchForChange(changeName);
101851
101975
  const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
101852
101976
  if (list.stdout.includes(`worktree ${cwd2}
@@ -101871,7 +101995,7 @@ async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
101871
101995
  return { cwd: cwd2, branch };
101872
101996
  }
101873
101997
  async function installPrePushHook(cwd2, runner) {
101874
- const hookPath = join20(cwd2, ".ralph-hooks", "pre-push");
101998
+ const hookPath = join21(cwd2, ".ralph-hooks", "pre-push");
101875
101999
  await Bun.write(hookPath, PRE_PUSH_HOOK_SCRIPT);
101876
102000
  const chmod = Bun.spawn(["chmod", "+x", hookPath]);
101877
102001
  await chmod.exited;
@@ -101917,8 +102041,8 @@ async function isWorktreeSafeToRemove(cwd2, base2, runner) {
101917
102041
  return { safe: true, dirty, unpushedCommits };
101918
102042
  }
101919
102043
  async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
101920
- const dst = join20(worktreeCwd, ".mcp.json");
101921
- const src = join20(projectRoot, ".mcp.json");
102044
+ const dst = join21(worktreeCwd, ".mcp.json");
102045
+ const src = join21(projectRoot, ".mcp.json");
101922
102046
  const source = await exists3(dst) ? dst : await exists3(src) ? src : null;
101923
102047
  if (!source)
101924
102048
  return;
@@ -101932,7 +102056,7 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
101932
102056
  if (servers && typeof servers === "object") {
101933
102057
  for (const cfg of Object.values(servers)) {
101934
102058
  if (Array.isArray(cfg.args)) {
101935
- cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join20(projectRoot, a) : a);
102059
+ cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join21(projectRoot, a) : a);
101936
102060
  }
101937
102061
  }
101938
102062
  }
@@ -103782,7 +103906,7 @@ function emitFeatureSkipped(bus, id, reason) {
103782
103906
  var init_run_feature = () => {};
103783
103907
 
103784
103908
  // apps/agent/src/agent/post-task.ts
103785
- import { join as join21, dirname as dirname9 } from "path";
103909
+ import { join as join22, dirname as dirname9 } from "path";
103786
103910
  function summarizeUncommittedStatus(stdout) {
103787
103911
  const lines = stdout.split(`
103788
103912
  `).filter((line) => line.length > 0);
@@ -103854,7 +103978,7 @@ async function reactivateState(stateFilePath, log3, changeName) {
103854
103978
  async function runWorkerWithFixTask(ctx, heading, body) {
103855
103979
  try {
103856
103980
  await runCapability(fsChange.prependTask, {
103857
- tasksPath: join21(ctx.changeDir, AGENT_TASKS_FILENAME),
103981
+ tasksPath: join22(ctx.changeDir, AGENT_TASKS_FILENAME),
103858
103982
  heading,
103859
103983
  failureOutput: body
103860
103984
  });
@@ -104149,6 +104273,7 @@ async function runPrPhase(input, deps) {
104149
104273
  emit: emit3,
104150
104274
  respawnWorker,
104151
104275
  registerPr,
104276
+ onPrReady,
104152
104277
  checkPrConflict,
104153
104278
  resolveDependencyBaseBranch
104154
104279
  } = deps;
@@ -104343,6 +104468,9 @@ ${indented}${suffix}`, "yellow");
104343
104468
  log3(`! manual merge failed for ${prUrl}: ${e.stderr?.trim() || e.message}`, "yellow");
104344
104469
  }
104345
104470
  }
104471
+ if (!(wantAutoMerge && !prReadyNeeded)) {
104472
+ await onPrReady?.(prUrl);
104473
+ }
104346
104474
  return 0;
104347
104475
  }
104348
104476
  async function runWorktreeCleanupPhase(input, deps) {
@@ -104401,7 +104529,7 @@ async function runValidateOnlyPhase(input, deps) {
104401
104529
  emit3("validate-fix", command);
104402
104530
  log3(`! validation check failed: ${command}`, "yellow");
104403
104531
  try {
104404
- await prependFixTask(join21(changeDir, AGENT_TASKS_FILENAME), `Fix failing validation: ${command}`, output || `Command exited with code ${exitCode}`);
104532
+ await prependFixTask(join22(changeDir, AGENT_TASKS_FILENAME), `Fix failing validation: ${command}`, output || `Command exited with code ${exitCode}`);
104405
104533
  } catch (err) {
104406
104534
  log3(`! could not prepend fix task: ${err.message}`, "red");
104407
104535
  return 1;
@@ -104412,7 +104540,7 @@ async function runValidateOnlyPhase(input, deps) {
104412
104540
  }
104413
104541
  }
104414
104542
  try {
104415
- await prependFixTask(join21(changeDir, AGENT_TASKS_FILENAME), "Run openspec validation", [
104543
+ await prependFixTask(join22(changeDir, AGENT_TASKS_FILENAME), "Run openspec validation", [
104416
104544
  `Run \`bunx openspec validate ${changeName}\` to validate the change artifacts.`,
104417
104545
  `Commit any pending changes before running the validation command.`
104418
104546
  ].join(`
@@ -104425,7 +104553,7 @@ async function runValidateOnlyPhase(input, deps) {
104425
104553
  return respawnWorker();
104426
104554
  }
104427
104555
  async function recordGaveUp(stateFilePath, log3, changeName) {
104428
- const path = join21(dirname9(stateFilePath), GAVEUP_COUNT_FILE);
104556
+ const path = join22(dirname9(stateFilePath), GAVEUP_COUNT_FILE);
104429
104557
  try {
104430
104558
  const file2 = Bun.file(path);
104431
104559
  const current = await file2.exists() ? Number.parseInt(await file2.text(), 10) || 0 : 0;
@@ -104548,6 +104676,7 @@ async function runPostTask(input, deps) {
104548
104676
  emit: emit3,
104549
104677
  respawnWorker,
104550
104678
  ...deps.registerPr !== undefined ? { registerPr: deps.registerPr } : {},
104679
+ ...deps.onPrReady !== undefined ? { onPrReady: deps.onPrReady } : {},
104551
104680
  ...deps.checkPrConflict !== undefined ? { checkPrConflict: deps.checkPrConflict } : {},
104552
104681
  ...deps.resolveDependencyBaseBranch !== undefined ? { resolveDependencyBaseBranch: deps.resolveDependencyBaseBranch } : {}
104553
104682
  });
@@ -105831,15 +105960,15 @@ var init_coordinator2 = __esm(() => {
105831
105960
  });
105832
105961
 
105833
105962
  // apps/agent/src/agent/scaffold.ts
105834
- import { join as join22 } from "path";
105963
+ import { join as join23 } from "path";
105835
105964
  function changeNameForIssue(issue2) {
105836
105965
  const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40).replace(/^-+|-+$/g, "");
105837
105966
  return slug ? `${issue2.identifier.toLowerCase()}-${slug}` : issue2.identifier.toLowerCase();
105838
105967
  }
105839
105968
  async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = [], appendPrompt = "", attachments = []) {
105840
105969
  const name = changeNameForIssue(issue2);
105841
- const changeDir = join22(tasksDir, name);
105842
- const stateDir = join22(statesDir, name);
105970
+ const changeDir = join23(tasksDir, name);
105971
+ const stateDir = join23(statesDir, name);
105843
105972
  const commentsBlock = comments.length > 0 ? [
105844
105973
  "",
105845
105974
  "## Linear comments",
@@ -105892,8 +106021,8 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
105892
106021
  `- [ ] Refine proposal.md with the problem statement, approach, and acceptance criteria derived from the research`,
105893
106022
  `- [ ] Fill in \`## Why\` and \`## What Changes\` in proposal.md so \`openspec validate\` passes (these sections are required by the validator)`,
105894
106023
  `- [ ] Add at least one spec delta under \`specs/<capability>/spec.md\` describing the behavior added/modified/removed by this change`,
105895
- `- [ ] Fill in design.md with the technical design (files to touch, data flow, edge cases)`,
105896
- `- [ ] 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.`,
106024
+ `- [ ] Fill in design.md with the technical design (files to touch, data flow, edge cases). design.md holds prose and tables ONLY \u2014 never a task checklist; the implementation tasks belong in this tasks.md file (next item).`,
106025
+ `- [ ] Append an \`## Implementation\` section to **this tasks.md file** (below the \`## Planning\` section above \u2014 NOT in design.md) 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.`,
105897
106026
  `- [ ] Is there anything else to add? Review the complete change context and document any additional edge cases, constraints, or open questions not captured above.`,
105898
106027
  ""
105899
106028
  ].join(`
@@ -105975,19 +106104,22 @@ var init_detections = __esm(() => {
105975
106104
  });
105976
106105
 
105977
106106
  // apps/agent/src/features/confirmation/state.ts
105978
- import { dirname as dirname10, join as join23 } from "path";
105979
- import { mkdir as mkdir8 } from "fs/promises";
105980
- async function readConfirmationState(statePath) {
106107
+ import { dirname as dirname10, join as join24 } from "path";
106108
+ async function readInlineConfirmation(statePath) {
105981
106109
  const f2 = Bun.file(statePath);
105982
- let stateObj = {};
105983
- if (await f2.exists()) {
105984
- try {
105985
- stateObj = await f2.json();
105986
- } catch {
105987
- stateObj = {};
105988
- }
106110
+ if (!await f2.exists())
106111
+ return null;
106112
+ try {
106113
+ const obj = await f2.json();
106114
+ return obj.confirmation ?? null;
106115
+ } catch {
106116
+ return null;
105989
106117
  }
105990
- const existing = stateObj.confirmation ?? null;
106118
+ }
106119
+ async function readConfirmationState(statePath) {
106120
+ const changeDir = dirname10(statePath);
106121
+ const sidecar = await readSlotSidecar(changeDir, "confirmation");
106122
+ const existing = sidecar ?? await readInlineConfirmation(statePath) ?? null;
105991
106123
  const confirmation = {
105992
106124
  askedAt: existing?.askedAt ?? null,
105993
106125
  lastReminderAt: existing?.lastReminderAt ?? null,
@@ -105995,14 +106127,13 @@ async function readConfirmationState(statePath) {
105995
106127
  rounds: existing?.rounds ?? 0,
105996
106128
  stuckPostedAt: existing?.stuckPostedAt ?? null,
105997
106129
  lastReviseConsumedAt: existing?.lastReviseConsumedAt ?? null,
105998
- awaitingMarkerAppliedAt: existing?.awaitingMarkerAppliedAt ?? null
106130
+ awaitingMarkerAppliedAt: existing?.awaitingMarkerAppliedAt ?? null,
106131
+ earlyDraftPrAt: existing?.earlyDraftPrAt ?? null
105999
106132
  };
106000
- return { stateObj, confirmation };
106133
+ return { stateObj: {}, confirmation };
106001
106134
  }
106002
- async function writeConfirmationState(statePath, stateObj, confirmation) {
106003
- await mkdir8(dirname10(statePath), { recursive: true });
106004
- await Bun.write(statePath, JSON.stringify({ ...stateObj, confirmation }, null, 2) + `
106005
- `);
106135
+ async function writeConfirmationState(statePath, _stateObj, confirmation) {
106136
+ await writeSlotField(dirname10(statePath), "confirmation", confirmation);
106006
106137
  }
106007
106138
  async function restartFromDesign(changeDir, changeName) {
106008
106139
  const designStub = [
@@ -106012,8 +106143,8 @@ async function restartFromDesign(changeDir, changeName) {
106012
106143
  ""
106013
106144
  ].join(`
106014
106145
  `);
106015
- await Bun.write(join23(changeDir, "design.md"), designStub);
106016
- const tasksPath = join23(changeDir, "tasks.md");
106146
+ await Bun.write(join24(changeDir, "design.md"), designStub);
106147
+ const tasksPath = join24(changeDir, "tasks.md");
106017
106148
  if (await Bun.file(tasksPath).exists()) {
106018
106149
  await Bun.write(tasksPath, `# Tasks
106019
106150
 
@@ -106025,6 +106156,7 @@ async function appendSteeringNote(changeDir, message) {
106025
106156
  await runCapability(fsChange.appendSteering, { changeDir, message });
106026
106157
  }
106027
106158
  var init_state2 = __esm(() => {
106159
+ init_store();
106028
106160
  init_fs_change();
106029
106161
  });
106030
106162
 
@@ -106242,8 +106374,7 @@ var init_inspect = __esm(() => {
106242
106374
  });
106243
106375
 
106244
106376
  // apps/agent/src/features/confirmation/awaiting.ts
106245
- import { join as join24, dirname as dirname11 } from "path";
106246
- import { mkdir as mkdir9 } from "fs/promises";
106377
+ import { join as join25 } from "path";
106247
106378
  async function resolveChangeCwdForIssue(issue2, changeName, deps) {
106248
106379
  const tracked = deps.cwdOf(changeName);
106249
106380
  if (tracked)
@@ -106251,12 +106382,12 @@ async function resolveChangeCwdForIssue(issue2, changeName, deps) {
106251
106382
  if (!deps.useWorktree)
106252
106383
  return deps.projectRoot;
106253
106384
  const root = worktreesDir2(deps.projectRoot);
106254
- const canonical = join24(root, worktreeDirNameForIssue(issue2));
106255
- if (await Bun.file(join24(canonical, "openspec", "changes", changeName, "tasks.md")).exists()) {
106385
+ const canonical = join25(root, worktreeDirNameForIssue(issue2));
106386
+ if (await Bun.file(join25(canonical, "openspec", "changes", changeName, "tasks.md")).exists()) {
106256
106387
  return canonical;
106257
106388
  }
106258
- const legacy = join24(root, changeName);
106259
- if (await Bun.file(join24(legacy, "openspec", "changes", changeName, "tasks.md")).exists()) {
106389
+ const legacy = join25(root, changeName);
106390
+ if (await Bun.file(join25(legacy, "openspec", "changes", changeName, "tasks.md")).exists()) {
106260
106391
  return legacy;
106261
106392
  }
106262
106393
  return deps.projectRoot;
@@ -106276,17 +106407,8 @@ async function postPlanReadyCommentOnce(issue2, statePath, changeName, deps) {
106276
106407
  return;
106277
106408
  if (deps.cfg.linear.postComments === false)
106278
106409
  return;
106279
- let stateObj = {};
106280
- const f2 = Bun.file(statePath);
106281
- if (await f2.exists()) {
106282
- try {
106283
- stateObj = await f2.json();
106284
- } catch {
106285
- stateObj = {};
106286
- }
106287
- }
106288
- const confirmation = stateObj.confirmation ?? null;
106289
- if (confirmation?.askedAt)
106410
+ const { confirmation } = await readConfirmationState(statePath);
106411
+ if (confirmation.askedAt)
106290
106412
  return;
106291
106413
  const approvalSentence = describeApprovalMarker(deps.cfg.linear.indicators.getApproved);
106292
106414
  const handle = deps.cfg.linear.mentionHandle;
@@ -106297,16 +106419,8 @@ async function postPlanReadyCommentOnce(issue2, statePath, changeName, deps) {
106297
106419
  deps.onLog(`! Linear plan-ready comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
106298
106420
  return;
106299
106421
  }
106300
- const nextConfirmation = {
106301
- askedAt: new Date().toISOString(),
106302
- lastReminderAt: confirmation?.lastReminderAt ?? null,
106303
- confirmedAt: confirmation?.confirmedAt ?? null,
106304
- rounds: confirmation?.rounds ?? 0
106305
- };
106306
106422
  try {
106307
- await mkdir9(dirname11(statePath), { recursive: true });
106308
- await Bun.write(statePath, JSON.stringify({ ...stateObj, confirmation: nextConfirmation }, null, 2) + `
106309
- `);
106423
+ await writeConfirmationState(statePath, {}, { ...confirmation, askedAt: new Date().toISOString() });
106310
106424
  } catch (err) {
106311
106425
  deps.onLog(`! could not persist confirmation.askedAt for ${issue2.identifier}: ${err.message}`, "yellow");
106312
106426
  }
@@ -106330,10 +106444,42 @@ async function applyAwaitingMarkerOnce(issue2, statePath, state, deps) {
106330
106444
  deps.onLog(`! persist awaitingMarkerAppliedAt for ${issue2.identifier}: ${err.message}`, "yellow");
106331
106445
  }
106332
106446
  }
106447
+ async function openDraftPrOnce(issue2, statePath, changeName, cwd2, state, deps) {
106448
+ if (deps.cfg.prDraft !== true)
106449
+ return;
106450
+ if (!deps.openDraftPr)
106451
+ return;
106452
+ if (state.confirmation.earlyDraftPrAt)
106453
+ return;
106454
+ let url2 = null;
106455
+ try {
106456
+ url2 = await deps.openDraftPr(issue2, changeName, cwd2);
106457
+ } catch (err) {
106458
+ deps.onLog(`! early draft PR open failed for ${issue2.identifier}: ${err.message}`, "yellow");
106459
+ }
106460
+ state.confirmation.earlyDraftPrAt = new Date().toISOString();
106461
+ try {
106462
+ await writeConfirmationState(statePath, state.stateObj, state.confirmation);
106463
+ } catch (err) {
106464
+ deps.onLog(`! persist earlyDraftPrAt for ${issue2.identifier}: ${err.message}`, "yellow");
106465
+ }
106466
+ if (url2)
106467
+ deps.onLog(` ${issue2.identifier}: opened draft PR for design \u2014 ${url2}`, "gray");
106468
+ }
106469
+ function issueInAwaitingStatus(issue2, indicators) {
106470
+ const set3 = indicators.setAwaitingConfirmation;
106471
+ if (!set3)
106472
+ return false;
106473
+ const current = issue2.state?.name;
106474
+ if (!current)
106475
+ return false;
106476
+ return markersOf(set3).some((m) => m.type === "status" && m.value === current);
106477
+ }
106333
106478
  async function releaseAwaitingMarker(issue2, statePath, deps) {
106334
106479
  const { stateObj, confirmation } = await readConfirmationState(statePath);
106335
- if (!confirmation.awaitingMarkerAppliedAt)
106480
+ if (!confirmation.awaitingMarkerAppliedAt && !issueInAwaitingStatus(issue2, deps.indicators)) {
106336
106481
  return;
106482
+ }
106337
106483
  if (deps.indicators.clearAwaitingConfirmation) {
106338
106484
  try {
106339
106485
  await deps.applyIndicator(issue2, deps.indicators.clearAwaitingConfirmation);
@@ -106341,6 +106487,13 @@ async function releaseAwaitingMarker(issue2, statePath, deps) {
106341
106487
  deps.onLog(`! clearAwaitingConfirmation failed for ${issue2.identifier}: ${err.message}`, "yellow");
106342
106488
  }
106343
106489
  }
106490
+ if (deps.indicators.setInProgress) {
106491
+ try {
106492
+ await deps.applyIndicator(issue2, deps.indicators.setInProgress);
106493
+ } catch (err) {
106494
+ deps.onLog(`! restore setInProgress after awaiting release failed for ${issue2.identifier}: ${err.message}`, "yellow");
106495
+ }
106496
+ }
106344
106497
  confirmation.awaitingMarkerAppliedAt = null;
106345
106498
  try {
106346
106499
  await writeConfirmationState(statePath, stateObj, confirmation);
@@ -106369,9 +106522,9 @@ async function processAwaitingForIssue(issue2, deps) {
106369
106522
  const layout = projectLayout(cwd2);
106370
106523
  const changeDir = layout.changeDir(changeName);
106371
106524
  const statePath = layout.stateFile(changeName);
106372
- const tasks2 = await readTextOrNull(join24(changeDir, "tasks.md"));
106373
- const proposal = await readTextOrNull(join24(changeDir, "proposal.md"));
106374
- const design = await readTextOrNull(join24(changeDir, "design.md"));
106525
+ const tasks2 = await readTextOrNull(join25(changeDir, "tasks.md"));
106526
+ const proposal = await readTextOrNull(join25(changeDir, "proposal.md"));
106527
+ const design = await readTextOrNull(join25(changeDir, "design.md"));
106375
106528
  let commentsCache = null;
106376
106529
  const getComments = async () => {
106377
106530
  if (commentsCache)
@@ -106455,6 +106608,7 @@ async function processAwaitingForIssue(issue2, deps) {
106455
106608
  cfg,
106456
106609
  onLog: deps.onLog
106457
106610
  });
106611
+ await openDraftPrOnce(issue2, statePath, changeName, cwd2, { stateObj, confirmation }, { cfg, openDraftPr: deps.openDraftPr, onLog: deps.onLog });
106458
106612
  const { stateObj: state2, confirmation: confirmation2 } = await readConfirmationState(statePath);
106459
106613
  const { outcome, next } = await inspectAwaitingTicket(confirmation2, {
106460
106614
  mentionHandle: cfg.linear.mentionHandle,
@@ -106529,6 +106683,7 @@ var init_awaiting = __esm(() => {
106529
106683
  init_worktree();
106530
106684
  init_scaffold();
106531
106685
  init_linear();
106686
+ init_types2();
106532
106687
  init_workflow();
106533
106688
  init_state2();
106534
106689
  init_inspect();
@@ -106603,9 +106758,26 @@ async function resolveDependencyBaseBranchImpl(issue2, runner, runnerCwd, deps)
106603
106758
  }
106604
106759
  return null;
106605
106760
  }
106761
+ function createOpenDraftPr(deps) {
106762
+ const create3 = deps.createPr ?? createPullRequest;
106763
+ return async (issue2, changeName, cwd2) => {
106764
+ const branch = deps.branchByChange.get(changeName);
106765
+ if (!branch)
106766
+ return null;
106767
+ const base2 = baseBranchFromLabels(issue2.labels) ?? deps.prBaseBranch;
106768
+ const result2 = await create3({ cwd: cwd2, branch, issue: issue2, base: base2, draft: true }, deps.cmdRunner);
106769
+ const url2 = result2?.url ?? null;
106770
+ if (url2) {
106771
+ deps.prByChange.set(changeName, url2);
106772
+ deps.invalidatePrUrlForIssue(issue2.id);
106773
+ }
106774
+ return url2;
106775
+ };
106776
+ }
106606
106777
  var GITHUB_PR_URL_RE, PR_NUMBER_RE, TICKET_IN_TITLE_RE;
106607
106778
  var init_pr_helpers = __esm(() => {
106608
106779
  init_linear();
106780
+ init_pr();
106609
106781
  GITHUB_PR_URL_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/;
106610
106782
  PR_NUMBER_RE = /\/pull\/(\d+)/;
106611
106783
  TICKET_IN_TITLE_RE = /^([A-Za-z][A-Za-z0-9]*-\d+)\b/;
@@ -106895,8 +107067,8 @@ var init_linear_resolvers = __esm(() => {
106895
107067
  });
106896
107068
 
106897
107069
  // apps/agent/src/agent/wire/prepare.ts
106898
- import { mkdir as mkdir10 } from "fs/promises";
106899
- import { join as join25 } from "path";
107070
+ import { mkdir as mkdir8 } from "fs/promises";
107071
+ import { join as join26 } from "path";
106900
107072
  function createPrepareHelpers(input) {
106901
107073
  const {
106902
107074
  args,
@@ -106960,7 +107132,7 @@ function createPrepareHelpers(input) {
106960
107132
  let changeName;
106961
107133
  const wtLayoutPre = projectLayout(workerCwd);
106962
107134
  const derivedName = changeNameForIssue(issue2);
106963
- const tasksMdPath = join25(wtLayoutPre.changeDir(derivedName), "tasks.md");
107135
+ const tasksMdPath = join26(wtLayoutPre.changeDir(derivedName), "tasks.md");
106964
107136
  const tasksMdExists = await Bun.file(tasksMdPath).exists();
106965
107137
  const isFresh = !tasksMdExists;
106966
107138
  if (isFresh) {
@@ -106999,8 +107171,8 @@ function createPrepareHelpers(input) {
106999
107171
  changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue2, comments, appendPrompt, attachments);
107000
107172
  } else {
107001
107173
  changeName = derivedName;
107002
- await mkdir10(wtLayoutPre.changeDir(changeName), { recursive: true });
107003
- await mkdir10(wtLayoutPre.taskStateDir(changeName), { recursive: true });
107174
+ await mkdir8(wtLayoutPre.changeDir(changeName), { recursive: true });
107175
+ await mkdir8(wtLayoutPre.taskStateDir(changeName), { recursive: true });
107004
107176
  }
107005
107177
  maps.cwdByChange.set(changeName, workerCwd);
107006
107178
  maps.statesDirByChange.set(changeName, scaffoldStatesDir);
@@ -107038,7 +107210,7 @@ function createPrepareHelpers(input) {
107038
107210
  if (!workerCwd)
107039
107211
  return;
107040
107212
  const wtLayout = projectLayout(workerCwd);
107041
- const tasksFile = join25(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
107213
+ const tasksFile = join26(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
107042
107214
  if (trigger === "review") {
107043
107215
  let body2;
107044
107216
  let heading;
@@ -107331,21 +107503,24 @@ var init_pr_discovery = __esm(() => {
107331
107503
  });
107332
107504
 
107333
107505
  // apps/agent/src/features/review-followup/scan.ts
107334
- import { dirname as dirname12, join as join26 } from "path";
107506
+ import { dirname as dirname11, join as join27 } from "path";
107335
107507
  async function resolveReviewStateDir(changeName, deps) {
107336
107508
  const root = deps.cwdOf(changeName);
107337
107509
  if (root)
107338
- return dirname12(projectLayout(root).stateFile(changeName));
107510
+ return dirname11(projectLayout(root).stateFile(changeName));
107339
107511
  if (!deps.useWorktree)
107340
- return dirname12(projectLayout(deps.projectRoot).stateFile(changeName));
107341
- const wtPath = join26(worktreesDir2(deps.projectRoot), changeName);
107512
+ return dirname11(projectLayout(deps.projectRoot).stateFile(changeName));
107513
+ const wtPath = join27(worktreesDir2(deps.projectRoot), changeName);
107342
107514
  const statePath = projectLayout(wtPath).stateFile(changeName);
107343
107515
  if (await Bun.file(statePath).exists())
107344
- return dirname12(statePath);
107516
+ return dirname11(statePath);
107345
107517
  return null;
107346
107518
  }
107347
107519
  async function readReviewWatermark(stateDir) {
107348
- const file2 = Bun.file(join26(stateDir, ".ralph-state.json"));
107520
+ const sidecar = await readSlotSidecar(stateDir, "review");
107521
+ if (sidecar)
107522
+ return sidecar.lastConsumedCommentAt ?? null;
107523
+ const file2 = Bun.file(join27(stateDir, ".ralph-state.json"));
107349
107524
  if (!await file2.exists())
107350
107525
  return null;
107351
107526
  try {
@@ -107557,7 +107732,7 @@ var init_github = __esm(() => {
107557
107732
 
107558
107733
  // apps/agent/src/agent/wire/mention-scan.ts
107559
107734
  import { readdir as readdir2 } from "fs/promises";
107560
- import { join as join27 } from "path";
107735
+ import { join as join28 } from "path";
107561
107736
  function createMentionScanner(input) {
107562
107737
  const {
107563
107738
  apiKey,
@@ -107723,7 +107898,7 @@ function createMentionScanner(input) {
107723
107898
  async function isChangeArchivedForIssue(issue2, cwdByChange, projectRoot) {
107724
107899
  const changeName = changeNameForIssue(issue2);
107725
107900
  const root = cwdByChange.get(changeName) ?? projectRoot;
107726
- const archiveDir = join27(projectLayout(root).tasksDir, "archive");
107901
+ const archiveDir = join28(projectLayout(root).tasksDir, "archive");
107727
107902
  let entries;
107728
107903
  try {
107729
107904
  entries = await readdir2(archiveDir);
@@ -107747,9 +107922,9 @@ var init_mention_scan = __esm(() => {
107747
107922
  });
107748
107923
 
107749
107924
  // apps/agent/src/agent/wire/spawn/default.ts
107750
- import { join as join28 } from "path";
107925
+ import { join as join29 } from "path";
107751
107926
  function defaultSpawn(changeName, cmd, cwd2, logsDir, onWorkerOutput, note) {
107752
- const logFilePath = join28(logsDir, `${changeName}.log`);
107927
+ const logFilePath = join29(logsDir, `${changeName}.log`);
107753
107928
  const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
107754
107929
  const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
107755
107930
  const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
@@ -107810,16 +107985,16 @@ var init_default2 = __esm(() => {
107810
107985
  });
107811
107986
 
107812
107987
  // apps/agent/src/agent/state/agent-run-state.ts
107813
- import { basename as basename3, join as join29 } from "path";
107988
+ import { basename as basename3, join as join30 } from "path";
107814
107989
  import { homedir as homedir6 } from "os";
107815
- import { mkdir as mkdir11, writeFile } from "fs/promises";
107990
+ import { mkdir as mkdir9, writeFile } from "fs/promises";
107816
107991
  function agentRunStatePath(projectRoot) {
107817
- return join29(homedir6(), ".ralph", basename3(projectRoot), "agent-state.json");
107992
+ return join30(homedir6(), ".ralph", basename3(projectRoot), "agent-state.json");
107818
107993
  }
107819
107994
  async function writeAgentRunState(state) {
107820
107995
  const path = agentRunStatePath(state.projectRoot);
107821
107996
  try {
107822
- await mkdir11(join29(homedir6(), ".ralph", basename3(state.projectRoot)), { recursive: true });
107997
+ await mkdir9(join30(homedir6(), ".ralph", basename3(state.projectRoot)), { recursive: true });
107823
107998
  await writeFile(path, JSON.stringify(state, null, 2) + `
107824
107999
  `, "utf-8");
107825
108000
  } catch {}
@@ -107845,18 +108020,18 @@ var CI_FAILED_EXIT2 = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
107845
108020
 
107846
108021
  // packages/retro/src/paths.ts
107847
108022
  import { homedir as homedir7 } from "os";
107848
- import { mkdir as mkdir12 } from "fs/promises";
107849
- import { join as join30 } from "path";
108023
+ import { mkdir as mkdir10 } from "fs/promises";
108024
+ import { join as join31 } from "path";
107850
108025
  function retroDir() {
107851
- return join30(homedir7(), ".ralph", "retro");
108026
+ return join31(homedir7(), ".ralph", "retro");
107852
108027
  }
107853
108028
  async function resolveRetroOutputPath(identifier, date5, dir = retroDir()) {
107854
- await mkdir12(dir, { recursive: true });
107855
- const base2 = join30(dir, `${identifier}-${date5}.md`);
108029
+ await mkdir10(dir, { recursive: true });
108030
+ const base2 = join31(dir, `${identifier}-${date5}.md`);
107856
108031
  if (!await Bun.file(base2).exists())
107857
108032
  return base2;
107858
108033
  for (let n = 2;; n++) {
107859
- const candidate = join30(dir, `${identifier}-${date5}-${n}.md`);
108034
+ const candidate = join31(dir, `${identifier}-${date5}-${n}.md`);
107860
108035
  if (!await Bun.file(candidate).exists())
107861
108036
  return candidate;
107862
108037
  }
@@ -107970,7 +108145,7 @@ var init_retro = __esm(() => {
107970
108145
  });
107971
108146
 
107972
108147
  // apps/agent/src/agent/wire/spawn/worker.ts
107973
- import { join as join31 } from "path";
108148
+ import { join as join32 } from "path";
107974
108149
  function localDateStamp(d) {
107975
108150
  const y = d.getFullYear();
107976
108151
  const m = String(d.getMonth() + 1).padStart(2, "0");
@@ -108005,6 +108180,8 @@ function createSpawnWorker(input) {
108005
108180
  indicators,
108006
108181
  cmdRunner,
108007
108182
  gitRunner,
108183
+ applyIndicator,
108184
+ bus,
108008
108185
  onLog,
108009
108186
  diag,
108010
108187
  runners,
@@ -108099,7 +108276,7 @@ function createSpawnWorker(input) {
108099
108276
  paths: {
108100
108277
  changeDir: info.changeDir,
108101
108278
  stateFilePath: info.stateFilePath,
108102
- logFile: join31(logsDir, `${info.changeName}.log`),
108279
+ logFile: join32(logsDir, `${info.changeName}.log`),
108103
108280
  jsonLogFile: args.jsonLogFile ?? null,
108104
108281
  agentStateFile: agentRunStatePath(projectRoot)
108105
108282
  }
@@ -108116,7 +108293,7 @@ function createSpawnWorker(input) {
108116
108293
  return function spawnWorker(changeName, _issue, trigger) {
108117
108294
  const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
108118
108295
  const injected = runners?.spawnWorker;
108119
- const missionTasksPath = join31(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
108296
+ const missionTasksPath = join32(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
108120
108297
  const prevTasksPromise = (async () => {
108121
108298
  const f2 = Bun.file(missionTasksPath);
108122
108299
  return await f2.exists() ? await f2.text() : "";
@@ -108124,7 +108301,7 @@ function createSpawnWorker(input) {
108124
108301
  let logFilePath;
108125
108302
  let handle;
108126
108303
  if (injected) {
108127
- logFilePath = join31(logsDir, `${changeName}.log`);
108304
+ logFilePath = join32(logsDir, `${changeName}.log`);
108128
108305
  handle = injected(buildTaskCmdFor(changeName), cwd2);
108129
108306
  } else {
108130
108307
  const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, logsDir, onWorkerOutput, `spawn at ${new Date().toISOString()}`);
@@ -108146,7 +108323,7 @@ function createSpawnWorker(input) {
108146
108323
  const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
108147
108324
  const wrapped = handle.exited.then(async (code) => {
108148
108325
  const workerLayout = projectLayout(cwd2);
108149
- const validateSpecPath = join31(workerLayout.changeDir(changeName), "specs", "validate.md");
108326
+ const validateSpecPath = join32(workerLayout.changeDir(changeName), "specs", "validate.md");
108150
108327
  const hasValidateSpec = await Bun.file(validateSpecPath).exists();
108151
108328
  const wantValidateOnly = hasValidateSpec && !wantPrBase;
108152
108329
  if (hasValidateSpec) {
@@ -108233,6 +108410,23 @@ function createSpawnWorker(input) {
108233
108410
  runScript,
108234
108411
  ...retroDepEntry(args.agentDebug, runRetrospectiveHook),
108235
108412
  registerPr: (cn, url2) => onPrRegistered(cn, url2),
108413
+ ...issueForChange && indicators.setPrReady ? {
108414
+ onPrReady: async () => {
108415
+ const issue2 = issueForChange;
108416
+ const marker = indicators.setPrReady;
108417
+ try {
108418
+ await applyIndicator(issue2, marker);
108419
+ onLog(` ${issue2.identifier}: setPrReady applied`, "gray");
108420
+ } catch (err) {
108421
+ onLog(`! Linear setPrReady failed for ${issue2.identifier}: ${err.message}`, "yellow");
108422
+ emitCapture(bus, "agent_indicator_failed", {
108423
+ indicator: "setPrReady",
108424
+ issue_identifier: issue2.identifier,
108425
+ error: err.message
108426
+ });
108427
+ }
108428
+ }
108429
+ } : {},
108236
108430
  ...onWorkerPhase && {
108237
108431
  onPhase: (phase2, detail) => onWorkerPhase(changeName, phase2, detail)
108238
108432
  },
@@ -108270,6 +108464,7 @@ var init_worker = __esm(() => {
108270
108464
  init_agent_run_state();
108271
108465
  init_retro();
108272
108466
  init_engine();
108467
+ init_coordinator();
108273
108468
  });
108274
108469
 
108275
108470
  // apps/agent/src/agent/baseline/runner.ts
@@ -108612,44 +108807,33 @@ var init_linear_sync = __esm(() => {
108612
108807
  });
108613
108808
 
108614
108809
  // apps/agent/src/agent/linear-sync/comment-sync.ts
108615
- import { dirname as dirname13, join as join32 } from "path";
108616
- import { mkdir as mkdir13, rename, unlink as unlink2 } from "fs/promises";
108617
- async function readStateJson(statePath) {
108810
+ import { dirname as dirname12, join as join33 } from "path";
108811
+ async function readInlineLinearComments(statePath) {
108618
108812
  const file2 = Bun.file(statePath);
108619
108813
  if (!await file2.exists())
108620
- return null;
108814
+ return;
108621
108815
  try {
108622
- return await file2.json();
108816
+ const obj = await file2.json();
108817
+ return obj.linearComments ?? undefined;
108623
108818
  } catch {
108624
- return null;
108625
- }
108626
- }
108627
- async function writeStateJson(statePath, state) {
108628
- await mkdir13(dirname13(statePath), { recursive: true });
108629
- const tmp = `${statePath}.tmp-${process.pid}-${writeStateSeq++}`;
108630
- try {
108631
- await Bun.write(tmp, JSON.stringify(state, null, 2) + `
108632
- `);
108633
- await rename(tmp, statePath);
108634
- } catch (err) {
108635
- await unlink2(tmp).catch(() => {});
108636
- throw err;
108819
+ return;
108637
108820
  }
108638
108821
  }
108639
- function readComments(state) {
108640
- const raw = state?.linearComments ?? {};
108822
+ async function readComments(statePath) {
108823
+ const changeDir = dirname12(statePath);
108824
+ const raw = await readSlotSidecar(changeDir, "linearComments") ?? await readInlineLinearComments(statePath) ?? {};
108825
+ const r = raw;
108641
108826
  return {
108642
- planCommentId: raw?.planCommentId ?? null,
108643
- tasksCommentId: raw?.tasksCommentId ?? null,
108644
- planPostedAt: raw?.planPostedAt ?? null,
108645
- tasksCommentSha256: raw?.tasksCommentSha256 ?? null
108827
+ planCommentId: r.planCommentId ?? null,
108828
+ tasksCommentId: r.tasksCommentId ?? null,
108829
+ planPostedAt: r.planPostedAt ?? null,
108830
+ tasksCommentSha256: r.tasksCommentSha256 ?? null
108646
108831
  };
108647
108832
  }
108648
108833
  async function patchComments(statePath, patch) {
108649
- const existing = await readStateJson(statePath) ?? {};
108650
- const current = readComments(existing);
108834
+ const current = await readComments(statePath);
108651
108835
  const next = { ...current, ...patch };
108652
- await writeStateJson(statePath, { ...existing, linearComments: next });
108836
+ await writeSlotField(dirname12(statePath), "linearComments", next);
108653
108837
  }
108654
108838
  function isCommentNotFoundError(err) {
108655
108839
  if (!err)
@@ -108664,7 +108848,7 @@ function isCommentNotFoundError(err) {
108664
108848
  return text.includes("not found") || text.includes("could not find") || text.includes("entity not found");
108665
108849
  }
108666
108850
  async function readTasksMd(changeDir, log3) {
108667
- const file2 = Bun.file(join32(changeDir, "tasks.md"));
108851
+ const file2 = Bun.file(join33(changeDir, "tasks.md"));
108668
108852
  if (!await file2.exists()) {
108669
108853
  log3(` comment-sync: tasks.md missing in ${changeDir}, skipping`, "gray");
108670
108854
  return null;
@@ -108685,8 +108869,7 @@ async function postOrUpdateTasksComment(deps) {
108685
108869
  return null;
108686
108870
  const body = renderTasksCommentBody(tasksMd, deps.changeName, deps.iteration);
108687
108871
  const hash2 = sha256Hex(tasksMd);
108688
- const state = await readStateJson(deps.statePath);
108689
- const comments = readComments(state);
108872
+ const comments = await readComments(deps.statePath);
108690
108873
  if (comments.tasksCommentId) {
108691
108874
  if (comments.tasksCommentSha256 === hash2) {
108692
108875
  deps.log(` comment-sync: tasks.md unchanged for ${deps.changeName}, skipping`, "gray");
@@ -108762,8 +108945,7 @@ async function readSection(path, heading) {
108762
108945
  return body.trim() || null;
108763
108946
  }
108764
108947
  async function postPlanCommentOnce(deps) {
108765
- const state = await readStateJson(deps.statePath);
108766
- const comments = readComments(state);
108948
+ const comments = await readComments(deps.statePath);
108767
108949
  if (comments.planCommentId)
108768
108950
  return null;
108769
108951
  const tasksMd = await readTasksMd(deps.changeDir, deps.log);
@@ -108772,14 +108954,14 @@ async function postPlanCommentOnce(deps) {
108772
108954
  const check2 = parsePlanningSection(tasksMd);
108773
108955
  if (!check2.allChecked)
108774
108956
  return null;
108775
- const proposalPath = join32(deps.changeDir, "proposal.md");
108957
+ const proposalPath = join33(deps.changeDir, "proposal.md");
108776
108958
  const why = await readSection(proposalPath, "Why");
108777
108959
  const whatChanges = await readSection(proposalPath, "What Changes");
108778
108960
  if (!why && !whatChanges) {
108779
108961
  deps.log(` comment-sync: proposal.md has no Why/What Changes, skipping plan comment`, "gray");
108780
108962
  return null;
108781
108963
  }
108782
- const designSummary = await readFirstParagraph(join32(deps.changeDir, "design.md"));
108964
+ const designSummary = await readFirstParagraph(join33(deps.changeDir, "design.md"));
108783
108965
  const parts = [`### ${PLAN_COMMENT_TITLE} \u2014 \`${deps.changeName}\``];
108784
108966
  if (why) {
108785
108967
  parts.push("", "**Why**", "", why);
@@ -108817,8 +108999,7 @@ ${deps.message.trim()}`;
108817
108999
  } catch (err) {
108818
109000
  deps.log(`! comment-sync: steering comment create failed: ${err.message}`, "yellow");
108819
109001
  }
108820
- const state = await readStateJson(deps.statePath);
108821
- const comments = readComments(state);
109002
+ const comments = await readComments(deps.statePath);
108822
109003
  if (comments.tasksCommentId) {
108823
109004
  try {
108824
109005
  await deps.mutations.deleteIssueComment(deps.apiKey, comments.tasksCommentId);
@@ -108841,8 +109022,9 @@ ${deps.message.trim()}`;
108841
109022
  iteration: deps.iteration
108842
109023
  });
108843
109024
  }
108844
- var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering", writeStateSeq = 0;
109025
+ var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering";
108845
109026
  var init_comment_sync = __esm(() => {
109027
+ init_store();
108846
109028
  init_linear_sync();
108847
109029
  });
108848
109030
 
@@ -261055,7 +261237,7 @@ var init_render_pdf = __esm(() => {
261055
261237
  });
261056
261238
 
261057
261239
  // apps/agent/src/agent/linear-sync/spec-attachments.ts
261058
- import { dirname as dirname14, join as join33 } from "path";
261240
+ import { dirname as dirname13, join as join34 } from "path";
261059
261241
  function describeLinearError(err) {
261060
261242
  const e = err;
261061
261243
  const parts = [e.message ?? String(err)];
@@ -261070,25 +261252,29 @@ function describeLinearError(err) {
261070
261252
  return parts.join(" ");
261071
261253
  }
261072
261254
  function stateDirOf(statePath) {
261073
- return dirname14(statePath);
261255
+ return dirname13(statePath);
261074
261256
  }
261075
- async function readRawState(statePath) {
261257
+ async function readInlineSpecAttachments(statePath) {
261076
261258
  const file2 = Bun.file(statePath);
261077
261259
  if (!await file2.exists())
261078
261260
  return {};
261079
261261
  try {
261080
261262
  const parsed = await file2.json();
261081
261263
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
261082
- return parsed;
261264
+ const sa = parsed.specAttachments;
261265
+ return sa && typeof sa === "object" && !Array.isArray(sa) ? sa : {};
261083
261266
  }
261084
261267
  return {};
261085
261268
  } catch {
261086
261269
  return {};
261087
261270
  }
261088
261271
  }
261272
+ async function readSpecAttachmentsSubtree(statePath) {
261273
+ const sidecar = await readSlotSidecar(dirname13(statePath), "specAttachments");
261274
+ return sidecar ?? await readInlineSpecAttachments(statePath);
261275
+ }
261089
261276
  async function readSpecAttachments(statePath) {
261090
- const raw = await readRawState(statePath);
261091
- const sa = raw.specAttachments ?? {};
261277
+ const sa = await readSpecAttachmentsSubtree(statePath);
261092
261278
  return {
261093
261279
  proposal: {
261094
261280
  attachmentId: sa.proposal?.attachmentId ?? null,
@@ -261142,12 +261328,25 @@ function hasMeaningfulContent(bytes) {
261142
261328
  }
261143
261329
  return false;
261144
261330
  }
261331
+ function extractImplementationSection(tasksMarkdown) {
261332
+ const captured = [];
261333
+ let capturing = false;
261334
+ for (const line of tasksMarkdown.split(/\r?\n/)) {
261335
+ const heading = /^##\s+(.+?)\s*$/.exec(line)?.[1];
261336
+ if (heading !== undefined)
261337
+ capturing = heading.trim().toLowerCase() === "implementation";
261338
+ if (capturing)
261339
+ captured.push(line);
261340
+ }
261341
+ return captured.join(`
261342
+ `).trim();
261343
+ }
261145
261344
  async function syncSlot(deps, slot) {
261146
261345
  const spec = SLOT_SPECS[slot];
261147
261346
  const [primaryName, ...trailingNames] = spec.sourceFiles;
261148
261347
  if (!primaryName)
261149
261348
  return;
261150
- const primary = Bun.file(join33(deps.changeDir, primaryName));
261349
+ const primary = Bun.file(join34(deps.changeDir, primaryName));
261151
261350
  if (!await primary.exists()) {
261152
261351
  deps.log(` spec-attachments: ${primaryName} missing, skipping`, "gray");
261153
261352
  return;
@@ -261166,24 +261365,28 @@ async function syncSlot(deps, slot) {
261166
261365
  const parts = [primaryBytes];
261167
261366
  const enc = new TextEncoder;
261168
261367
  for (const name of trailingNames) {
261169
- const f2 = Bun.file(join33(deps.changeDir, name));
261368
+ const f2 = Bun.file(join34(deps.changeDir, name));
261170
261369
  if (!await f2.exists())
261171
261370
  continue;
261371
+ let raw;
261172
261372
  try {
261173
- const bytes = await f2.bytes();
261174
- if (bytes.length === 0)
261175
- continue;
261176
- parts.push(enc.encode(`
261373
+ raw = await f2.bytes();
261374
+ } catch (err) {
261375
+ deps.log(`! spec-attachments: read ${name} failed (continuing without it): ${err.message}`, "yellow");
261376
+ continue;
261377
+ }
261378
+ if (raw.length === 0)
261379
+ continue;
261380
+ const decoded = new TextDecoder().decode(raw);
261381
+ const body = name === "tasks.md" ? extractImplementationSection(decoded) : decoded.trim();
261382
+ if (!body)
261383
+ continue;
261384
+ parts.push(enc.encode(`
261177
261385
 
261178
261386
  ---
261179
261387
 
261180
- # ${name}
261181
-
261388
+ ${body}
261182
261389
  `));
261183
- parts.push(bytes);
261184
- } catch (err) {
261185
- deps.log(`! spec-attachments: read ${name} failed (continuing without it): ${err.message}`, "yellow");
261186
- }
261187
261390
  }
261188
261391
  const totalLen = parts.reduce((n, p) => n + p.length, 0);
261189
261392
  const sourceBytes = new Uint8Array(totalLen);
@@ -261252,8 +261455,7 @@ async function syncSlot(deps, slot) {
261252
261455
  deps.log(` spec-attachments: created ${spec.uploadFilename} attachment`, "gray");
261253
261456
  }
261254
261457
  async function purgeLegacyProposalSlots(deps) {
261255
- const raw = await readRawState(deps.statePath);
261256
- const sa = raw.specAttachments ?? {};
261458
+ const sa = await readSpecAttachmentsSubtree(deps.statePath);
261257
261459
  if (sa.legacyProposalPurged === true)
261258
261460
  return;
261259
261461
  const state = await readSpecAttachments(deps.statePath);
@@ -261431,9 +261633,9 @@ var init_comment_sync2 = __esm(() => {
261431
261633
  });
261432
261634
 
261433
261635
  // apps/agent/src/features/pr-tracker/state.ts
261434
- import { join as join34 } from "path";
261636
+ import { join as join35 } from "path";
261435
261637
  async function readState2(projectRoot) {
261436
- const path = join34(projectRoot, PR_TRACKER_STATE_RELPATH);
261638
+ const path = join35(projectRoot, PR_TRACKER_STATE_RELPATH);
261437
261639
  const file2 = Bun.file(path);
261438
261640
  if (!await file2.exists())
261439
261641
  return {};
@@ -261449,7 +261651,7 @@ async function readState2(projectRoot) {
261449
261651
  }
261450
261652
  }
261451
261653
  async function writeState2(projectRoot, state) {
261452
- const path = join34(projectRoot, PR_TRACKER_STATE_RELPATH);
261654
+ const path = join35(projectRoot, PR_TRACKER_STATE_RELPATH);
261453
261655
  await Bun.write(path, JSON.stringify(state, null, 2));
261454
261656
  }
261455
261657
  var PR_TRACKER_STATE_RELPATH = ".ralph/pr-tracker-state.json";
@@ -261528,7 +261730,7 @@ var init_pr_tracker = __esm(() => {
261528
261730
  });
261529
261731
 
261530
261732
  // apps/agent/src/agent/wire.ts
261531
- import { join as join35 } from "path";
261733
+ import { join as join36 } from "path";
261532
261734
  function buildAgentCoordinator(input) {
261533
261735
  const {
261534
261736
  args,
@@ -261547,7 +261749,7 @@ function buildAgentCoordinator(input) {
261547
261749
  onWorkerCmd,
261548
261750
  onAwaitingTicket
261549
261751
  } = input;
261550
- const logsDir = join35(projectRoot, ".ralph", "logs");
261752
+ const logsDir = join36(projectRoot, ".ralph", "logs");
261551
261753
  const bus = createBus();
261552
261754
  subscribeAgentDiag(bus, onLog);
261553
261755
  const diag = (area, message, color) => {
@@ -261657,6 +261859,8 @@ function buildAgentCoordinator(input) {
261657
261859
  indicators,
261658
261860
  cmdRunner,
261659
261861
  gitRunner,
261862
+ applyIndicator: resolvers.applyIndicator,
261863
+ bus,
261660
261864
  onLog,
261661
261865
  diag,
261662
261866
  runners: input.runners,
@@ -261682,6 +261886,13 @@ function buildAgentCoordinator(input) {
261682
261886
  ...onWorkerOutput ? { onWorkerOutput } : {},
261683
261887
  ...onWorkerCmd ? { onWorkerCmd } : {}
261684
261888
  });
261889
+ const openDraftPr = createOpenDraftPr({
261890
+ branchByChange,
261891
+ prByChange,
261892
+ cmdRunner,
261893
+ prBaseBranch: cfg.prBaseBranch,
261894
+ invalidatePrUrlForIssue: (issueId) => prDiscovery.invalidatePrUrlForIssue(issueId)
261895
+ });
261685
261896
  const confirmationCaps = {
261686
261897
  detect: (issue2) => processAwaitingForIssue(issue2, {
261687
261898
  cfg,
@@ -261694,6 +261905,7 @@ function buildAgentCoordinator(input) {
261694
261905
  reapForAwaiting: (cn) => coordRef.current?.reapForAwaiting(cn),
261695
261906
  applyIndicator: resolvers.applyIndicator,
261696
261907
  applyMarker: resolvers.applyMarker,
261908
+ openDraftPr,
261697
261909
  ...onAwaitingTicket ? { onAwaitingTicket } : {},
261698
261910
  onLog
261699
261911
  }),
@@ -261768,7 +261980,7 @@ function buildAgentCoordinator(input) {
261768
261980
  const changeDir = projectLayout(root).changeDir(changeName);
261769
261981
  const parts = [];
261770
261982
  for (const name of ["tasks.md", "proposal.md", "design.md"]) {
261771
- const file2 = Bun.file(join35(changeDir, name));
261983
+ const file2 = Bun.file(join36(changeDir, name));
261772
261984
  if (!await file2.exists())
261773
261985
  continue;
261774
261986
  parts.push(`${name}:${file2.lastModified}:${file2.size}`);
@@ -261813,7 +262025,7 @@ function buildAgentCoordinator(input) {
261813
262025
  getGaveUpTotal: async () => {
261814
262026
  let total = 0;
261815
262027
  for (const [changeName, root] of cwdByChange) {
261816
- const file2 = Bun.file(join35(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
262028
+ const file2 = Bun.file(join36(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
261817
262029
  if (!await file2.exists())
261818
262030
  continue;
261819
262031
  try {
@@ -261848,14 +262060,14 @@ var init_wire = __esm(() => {
261848
262060
  });
261849
262061
 
261850
262062
  // apps/agent/src/agent/json-log/json-log-file.ts
261851
- import { mkdir as mkdir14, appendFile as appendFile2 } from "fs/promises";
261852
- import { dirname as dirname15 } from "path";
262063
+ import { mkdir as mkdir11, appendFile as appendFile2 } from "fs/promises";
262064
+ import { dirname as dirname14 } from "path";
261853
262065
  function createJsonLogFileSink(path) {
261854
262066
  if (!path)
261855
262067
  return { emit: () => {} };
261856
262068
  let chain = (async () => {
261857
262069
  try {
261858
- await mkdir14(dirname15(path), { recursive: true });
262070
+ await mkdir11(dirname14(path), { recursive: true });
261859
262071
  await Bun.write(path, "");
261860
262072
  } catch {}
261861
262073
  })();
@@ -262101,7 +262313,7 @@ var init_output_utils = __esm(() => {
262101
262313
  });
262102
262314
 
262103
262315
  // apps/agent/src/agent/state/worker-state-poll.ts
262104
- import { join as join36 } from "path";
262316
+ import { join as join37 } from "path";
262105
262317
  function parseSubtasks(tasksMd) {
262106
262318
  const out = [];
262107
262319
  let skipSection = false;
@@ -262134,7 +262346,7 @@ function initialWorkerSnapshot() {
262134
262346
  async function readWorkerSnapshot(input) {
262135
262347
  const next = { ...input.prev };
262136
262348
  try {
262137
- const file2 = Bun.file(join36(input.statesDir, input.changeName, ".ralph-state.json"));
262349
+ const file2 = Bun.file(join37(input.statesDir, input.changeName, ".ralph-state.json"));
262138
262350
  if (await file2.exists()) {
262139
262351
  const json2 = await file2.json();
262140
262352
  next.iter = json2.iteration ?? next.iter;
@@ -262143,10 +262355,10 @@ async function readWorkerSnapshot(input) {
262143
262355
  } catch {}
262144
262356
  if (input.changeDir) {
262145
262357
  try {
262146
- const tasksFile = Bun.file(join36(input.changeDir, "tasks.md"));
262147
- const proposalFile = Bun.file(join36(input.changeDir, "proposal.md"));
262148
- const designFile = Bun.file(join36(input.changeDir, "design.md"));
262149
- const reviewFindingsFile = Bun.file(join36(input.changeDir, "review-findings.md"));
262358
+ const tasksFile = Bun.file(join37(input.changeDir, "tasks.md"));
262359
+ const proposalFile = Bun.file(join37(input.changeDir, "proposal.md"));
262360
+ const designFile = Bun.file(join37(input.changeDir, "design.md"));
262361
+ const reviewFindingsFile = Bun.file(join37(input.changeDir, "review-findings.md"));
262150
262362
  const [tasksText, proposalText, designText, reviewFindingsText] = await Promise.all([
262151
262363
  tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
262152
262364
  proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
@@ -262209,7 +262421,7 @@ var init_worker_state_poll = __esm(() => {
262209
262421
  });
262210
262422
 
262211
262423
  // apps/agent/src/components/AgentMode.tsx
262212
- import { join as join37 } from "path";
262424
+ import { join as join38 } from "path";
262213
262425
  async function appendSteeringImpl(changeDir, message) {
262214
262426
  await runWithContext(createDefaultContext(), async () => {
262215
262427
  appendSteeringMessage(changeDir, message);
@@ -263782,7 +263994,7 @@ function AgentMode({
263782
263994
  },
263783
263995
  onSubmit: async (message) => {
263784
263996
  try {
263785
- await appendSteering2(join37(tasksDir, w2.changeName), message);
263997
+ await appendSteering2(join38(tasksDir, w2.changeName), message);
263786
263998
  fileEmit({ type: "steering_submitted", changeName: w2.changeName, message });
263787
263999
  } catch (err) {
263788
264000
  const text = err.message;
@@ -264086,7 +264298,7 @@ __export(exports_list, {
264086
264298
  buildBuckets: () => buildBuckets,
264087
264299
  backlogRankByIssueId: () => backlogRankByIssueId
264088
264300
  });
264089
- import { join as join38 } from "path";
264301
+ import { join as join39 } from "path";
264090
264302
  function countTaskItems(content) {
264091
264303
  const checked = (content.match(/^- \[x\]/gm) ?? []).length;
264092
264304
  const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
@@ -264102,13 +264314,13 @@ function buildLocalRows() {
264102
264314
  const sources = [{ dir: statesDir, label: "main" }];
264103
264315
  const worktreesRoot = worktreesDir2(projectRoot);
264104
264316
  for (const wt of storage.list(worktreesRoot)) {
264105
- sources.push({ dir: join38(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
264317
+ sources.push({ dir: join39(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
264106
264318
  }
264107
264319
  for (const { dir, label } of sources) {
264108
264320
  for (const entry of storage.list(dir)) {
264109
264321
  if (seen.has(entry))
264110
264322
  continue;
264111
- const raw = storage.read(join38(dir, entry, ".ralph-state.json"));
264323
+ const raw = storage.read(join39(dir, entry, ".ralph-state.json"));
264112
264324
  if (raw === null)
264113
264325
  continue;
264114
264326
  let state;
@@ -264123,7 +264335,7 @@ function buildLocalRows() {
264123
264335
  const firstLine = promptRaw.split(`
264124
264336
  `).find((l3) => l3.trim() !== "") ?? "";
264125
264337
  let progress = "\u2014";
264126
- const tasksContent = storage.read(join38(dir, entry, "tasks.md"));
264338
+ const tasksContent = storage.read(join39(dir, entry, "tasks.md"));
264127
264339
  if (tasksContent !== null) {
264128
264340
  const { checked, unchecked } = countTaskItems(tasksContent);
264129
264341
  const total = checked + unchecked;
@@ -264623,8 +264835,8 @@ var exports_json_runner = {};
264623
264835
  __export(exports_json_runner, {
264624
264836
  runAgentJson: () => runAgentJson
264625
264837
  });
264626
- import { join as join39 } from "path";
264627
- import { mkdir as mkdir15 } from "fs/promises";
264838
+ import { join as join40 } from "path";
264839
+ import { mkdir as mkdir12 } from "fs/promises";
264628
264840
  import { homedir as homedir8 } from "os";
264629
264841
  function makeEmit(fileSink) {
264630
264842
  return (event) => {
@@ -264645,7 +264857,7 @@ async function runAgentJson({
264645
264857
  tasksDir,
264646
264858
  runPreflight: runPreflight2 = runPreflight
264647
264859
  }) {
264648
- await mkdir15(join39(homedir8(), ".ralph"), { recursive: true }).catch(() => {
264860
+ await mkdir12(join40(homedir8(), ".ralph"), { recursive: true }).catch(() => {
264649
264861
  return;
264650
264862
  });
264651
264863
  const fileSink = createJsonLogFileSink(args.jsonLogFile);
@@ -264861,8 +265073,8 @@ var exports_src3 = {};
264861
265073
  __export(exports_src3, {
264862
265074
  main: () => main3
264863
265075
  });
264864
- import { mkdir as mkdir16 } from "fs/promises";
264865
- import { join as join40 } from "path";
265076
+ import { mkdir as mkdir13 } from "fs/promises";
265077
+ import { join as join41 } from "path";
264866
265078
  async function main3(argv) {
264867
265079
  if (argv.includes("--help") || argv.includes("-h")) {
264868
265080
  printAgentHelp();
@@ -264926,9 +265138,9 @@ async function main3(argv) {
264926
265138
  return 1;
264927
265139
  }
264928
265140
  }
264929
- await mkdir16(statesDir, { recursive: true });
264930
- await mkdir16(tasksDir, { recursive: true });
264931
- await mkdir16(join40(projectRoot, ".ralph"), { recursive: true });
265141
+ await mkdir13(statesDir, { recursive: true });
265142
+ await mkdir13(tasksDir, { recursive: true });
265143
+ await mkdir13(join41(projectRoot, ".ralph"), { recursive: true });
264932
265144
  if (shouldFallbackToJsonOutput(args, process.stdin.isTTY)) {
264933
265145
  process.stderr.write(`agent: stdin is not a TTY \u2014 falling back to --json-output mode.
264934
265146
  `);