@neriros/ralphy 3.10.5 → 3.10.7
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.
- package/dist/mcp/index.js +75 -39
- package/dist/shell/index.js +711 -286
- package/package.json +1 -1
package/dist/shell/index.js
CHANGED
|
@@ -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.
|
|
18932
|
-
return "3.10.
|
|
18931
|
+
if ("3.10.7")
|
|
18932
|
+
return "3.10.7";
|
|
18933
18933
|
} catch {}
|
|
18934
18934
|
const dirsToTry = [];
|
|
18935
18935
|
try {
|
|
@@ -81072,7 +81072,7 @@ function modelOptionValues() {
|
|
|
81072
81072
|
const field = findField("model");
|
|
81073
81073
|
return field && field.spec.kind === "select" ? field.spec.options.map((o) => o.value) : [];
|
|
81074
81074
|
}
|
|
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;
|
|
81075
|
+
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
81076
|
var init_fields = __esm(() => {
|
|
81077
81077
|
PROJECT_NAME = {
|
|
81078
81078
|
id: "project.name",
|
|
@@ -81436,6 +81436,14 @@ var init_fields = __esm(() => {
|
|
|
81436
81436
|
spec: { kind: "number", placeholder: "3" },
|
|
81437
81437
|
when: isOn("linear.confirmationMode.enabled")
|
|
81438
81438
|
},
|
|
81439
|
+
{
|
|
81440
|
+
id: AWAITING_STATUS_FIELD_ID,
|
|
81441
|
+
label: "Park awaiting-approval tickets in a status?",
|
|
81442
|
+
hint: "e.g. Planned \u2014 blank keeps them In Progress",
|
|
81443
|
+
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.",
|
|
81444
|
+
spec: { kind: "text", placeholder: "Planned" },
|
|
81445
|
+
when: isOn("linear.confirmationMode.enabled")
|
|
81446
|
+
},
|
|
81439
81447
|
{
|
|
81440
81448
|
id: "linear.indicators",
|
|
81441
81449
|
label: "Linear lifecycle indicators",
|
|
@@ -82908,6 +82916,20 @@ function buildFromAnswers(mode, answers, build = buildWorkflowMarkdown) {
|
|
|
82908
82916
|
values2["linear.indicators"] = map3;
|
|
82909
82917
|
}
|
|
82910
82918
|
}
|
|
82919
|
+
const parkStatusRaw = values2[AWAITING_STATUS_FIELD_ID];
|
|
82920
|
+
const parkStatus = typeof parkStatusRaw === "string" ? parkStatusRaw.trim() : "";
|
|
82921
|
+
if (values2["linear.confirmationMode.enabled"] === true && parkStatus && values2["linear.indicators"] && typeof values2["linear.indicators"] === "object") {
|
|
82922
|
+
const map3 = { ...values2["linear.indicators"] };
|
|
82923
|
+
map3.setAwaitingConfirmation = { type: "status", value: parkStatus };
|
|
82924
|
+
const existing = map3.getInProgress;
|
|
82925
|
+
const filter2 = existing && !Array.isArray(existing) && "filter" in existing ? [...existing.filter] : [];
|
|
82926
|
+
if (!filter2.some((m) => m.type === "status" && m.value === parkStatus)) {
|
|
82927
|
+
filter2.push({ type: "status", value: parkStatus });
|
|
82928
|
+
}
|
|
82929
|
+
map3.getInProgress = { filter: filter2 };
|
|
82930
|
+
values2["linear.indicators"] = map3;
|
|
82931
|
+
}
|
|
82932
|
+
delete values2[AWAITING_STATUS_FIELD_ID];
|
|
82911
82933
|
const linkRepo = values2[REPO_LINK_FIELD_ID] === true;
|
|
82912
82934
|
delete values2[REPO_LINK_FIELD_ID];
|
|
82913
82935
|
if (!linkRepo) {
|
|
@@ -85044,6 +85066,89 @@ var init_schema2 = __esm(() => {
|
|
|
85044
85066
|
ALL_OWNED_SLOTS = new Set(Object.values(OWNERSHIP).flatMap((slots) => [...slots]));
|
|
85045
85067
|
});
|
|
85046
85068
|
|
|
85069
|
+
// packages/core/src/state/sidecar.ts
|
|
85070
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
85071
|
+
import { mkdir as mkdir3, rename, unlink } from "fs/promises";
|
|
85072
|
+
function slotSidecarPath(changeDir, slot) {
|
|
85073
|
+
return join9(changeDir, `${CORE_STATE_FILE.replace(/\.json$/, "")}.${slot}.json`);
|
|
85074
|
+
}
|
|
85075
|
+
function parseObject(text) {
|
|
85076
|
+
if (text === null)
|
|
85077
|
+
return null;
|
|
85078
|
+
try {
|
|
85079
|
+
const parsed = JSON.parse(text);
|
|
85080
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
85081
|
+
return parsed;
|
|
85082
|
+
}
|
|
85083
|
+
return null;
|
|
85084
|
+
} catch {
|
|
85085
|
+
return null;
|
|
85086
|
+
}
|
|
85087
|
+
}
|
|
85088
|
+
function deepSet(target, path, value) {
|
|
85089
|
+
if (path === "") {
|
|
85090
|
+
for (const k of Object.keys(target))
|
|
85091
|
+
delete target[k];
|
|
85092
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
85093
|
+
Object.assign(target, value);
|
|
85094
|
+
}
|
|
85095
|
+
return;
|
|
85096
|
+
}
|
|
85097
|
+
const segments = path.split(".");
|
|
85098
|
+
let cursor = target;
|
|
85099
|
+
for (let i = 0;i < segments.length - 1; i++) {
|
|
85100
|
+
const key = segments[i];
|
|
85101
|
+
const existing = cursor[key];
|
|
85102
|
+
if (existing === null || typeof existing !== "object" || Array.isArray(existing)) {
|
|
85103
|
+
const next = {};
|
|
85104
|
+
cursor[key] = next;
|
|
85105
|
+
cursor = next;
|
|
85106
|
+
} else {
|
|
85107
|
+
cursor = existing;
|
|
85108
|
+
}
|
|
85109
|
+
}
|
|
85110
|
+
cursor[segments[segments.length - 1]] = value;
|
|
85111
|
+
}
|
|
85112
|
+
async function atomicWrite(path, content) {
|
|
85113
|
+
await mkdir3(dirname5(path), { recursive: true });
|
|
85114
|
+
const tmp = `${path}.tmp-${process.pid}-${writeSeq++}`;
|
|
85115
|
+
try {
|
|
85116
|
+
await Bun.write(tmp, content);
|
|
85117
|
+
await rename(tmp, path);
|
|
85118
|
+
} catch (err) {
|
|
85119
|
+
await unlink(tmp).catch(() => {});
|
|
85120
|
+
throw err;
|
|
85121
|
+
}
|
|
85122
|
+
}
|
|
85123
|
+
async function readSlotSidecar(changeDir, slot) {
|
|
85124
|
+
const file2 = Bun.file(slotSidecarPath(changeDir, slot));
|
|
85125
|
+
if (!await file2.exists())
|
|
85126
|
+
return;
|
|
85127
|
+
const obj = parseObject(await file2.text().catch(() => null));
|
|
85128
|
+
return obj ?? undefined;
|
|
85129
|
+
}
|
|
85130
|
+
async function writeSlotField(changeDir, path, value, seedInline) {
|
|
85131
|
+
const [slot, ...rest2] = path.split(".");
|
|
85132
|
+
const sidecarPath = slotSidecarPath(changeDir, slot);
|
|
85133
|
+
const existing = parseObject(await Bun.file(sidecarPath).text().catch(() => null));
|
|
85134
|
+
const obj = existing ?? (seedInline ? structuredClone(seedInline) : {});
|
|
85135
|
+
deepSet(obj, rest2.join("."), value);
|
|
85136
|
+
await atomicWrite(sidecarPath, JSON.stringify(obj, null, 2) + `
|
|
85137
|
+
`);
|
|
85138
|
+
}
|
|
85139
|
+
function overlaySidecarsSync(changeDir, target, read) {
|
|
85140
|
+
for (const slot of ALL_OWNED_SLOTS) {
|
|
85141
|
+
const obj = parseObject(read(slotSidecarPath(changeDir, slot)));
|
|
85142
|
+
if (obj !== undefined && obj !== null)
|
|
85143
|
+
target[slot] = obj;
|
|
85144
|
+
}
|
|
85145
|
+
return target;
|
|
85146
|
+
}
|
|
85147
|
+
var CORE_STATE_FILE = ".ralph-state.json", writeSeq = 0;
|
|
85148
|
+
var init_sidecar = __esm(() => {
|
|
85149
|
+
init_schema2();
|
|
85150
|
+
});
|
|
85151
|
+
|
|
85047
85152
|
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
85048
85153
|
var util, objectUtil, ZodParsedType, getParsedType2 = (data) => {
|
|
85049
85154
|
const t = typeof data;
|
|
@@ -89147,16 +89252,24 @@ function formatTaskName(name) {
|
|
|
89147
89252
|
}
|
|
89148
89253
|
|
|
89149
89254
|
// packages/core/src/state.ts
|
|
89150
|
-
import { join as
|
|
89255
|
+
import { join as join10 } from "path";
|
|
89256
|
+
function stripOwnedSlots(state) {
|
|
89257
|
+
const out = { ...state };
|
|
89258
|
+
for (const slot of ALL_OWNED_SLOTS)
|
|
89259
|
+
delete out[slot];
|
|
89260
|
+
return out;
|
|
89261
|
+
}
|
|
89151
89262
|
function readState(changeDir) {
|
|
89152
|
-
const filePath =
|
|
89263
|
+
const filePath = join10(changeDir, STATE_FILE2);
|
|
89153
89264
|
const raw = getStorage().read(filePath);
|
|
89154
89265
|
if (raw === null)
|
|
89155
89266
|
throw new Error(".ralph-state.json not found");
|
|
89156
|
-
|
|
89267
|
+
const base2 = JSON.parse(raw);
|
|
89268
|
+
overlaySidecarsSync(changeDir, base2, (p) => getStorage().read(p));
|
|
89269
|
+
return StateSchema.parse(base2);
|
|
89157
89270
|
}
|
|
89158
89271
|
function tryReadStateRaw(changeDir) {
|
|
89159
|
-
const filePath =
|
|
89272
|
+
const filePath = join10(changeDir, STATE_FILE2);
|
|
89160
89273
|
const text = getStorage().read(filePath);
|
|
89161
89274
|
if (text === null)
|
|
89162
89275
|
return { state: null, raw: null };
|
|
@@ -89167,12 +89280,14 @@ function tryReadStateRaw(changeDir) {
|
|
|
89167
89280
|
return { state: null, raw: null };
|
|
89168
89281
|
}
|
|
89169
89282
|
const raw = parsed && typeof parsed === "object" ? parsed : {};
|
|
89170
|
-
|
|
89283
|
+
overlaySidecarsSync(changeDir, raw, (p) => getStorage().read(p));
|
|
89284
|
+
const result2 = StateSchema.safeParse(raw);
|
|
89171
89285
|
return { state: result2.success ? result2.data : null, raw };
|
|
89172
89286
|
}
|
|
89173
89287
|
function writeState(changeDir, state) {
|
|
89174
|
-
const filePath =
|
|
89175
|
-
|
|
89288
|
+
const filePath = join10(changeDir, STATE_FILE2);
|
|
89289
|
+
const core2 = stripOwnedSlots(state);
|
|
89290
|
+
getStorage().write(filePath, JSON.stringify(core2, null, 2) + `
|
|
89176
89291
|
`);
|
|
89177
89292
|
}
|
|
89178
89293
|
function updateState(changeDir, updater) {
|
|
@@ -89211,7 +89326,7 @@ function buildInitialState(options) {
|
|
|
89211
89326
|
});
|
|
89212
89327
|
}
|
|
89213
89328
|
function ensureState(changeDir) {
|
|
89214
|
-
const filePath =
|
|
89329
|
+
const filePath = join10(changeDir, STATE_FILE2);
|
|
89215
89330
|
const storage = getStorage();
|
|
89216
89331
|
if (storage.read(filePath) !== null) {
|
|
89217
89332
|
return readState(changeDir);
|
|
@@ -89225,11 +89340,12 @@ var STATE_FILE2 = ".ralph-state.json";
|
|
|
89225
89340
|
var init_state = __esm(() => {
|
|
89226
89341
|
init_types2();
|
|
89227
89342
|
init_context();
|
|
89343
|
+
init_schema2();
|
|
89344
|
+
init_sidecar();
|
|
89228
89345
|
});
|
|
89229
89346
|
|
|
89230
89347
|
// packages/core/src/state/store.ts
|
|
89231
|
-
import {
|
|
89232
|
-
import { mkdir as mkdir3 } from "fs/promises";
|
|
89348
|
+
import { join as join11 } from "path";
|
|
89233
89349
|
async function readJson(filePath) {
|
|
89234
89350
|
const file2 = Bun.file(filePath);
|
|
89235
89351
|
if (!await file2.exists())
|
|
@@ -89244,22 +89360,6 @@ async function readJson(filePath) {
|
|
|
89244
89360
|
return {};
|
|
89245
89361
|
}
|
|
89246
89362
|
}
|
|
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
89363
|
async function writeField(changeDir, featureName, path, value) {
|
|
89264
89364
|
const allowed = OWNERSHIP[featureName];
|
|
89265
89365
|
if (!allowed) {
|
|
@@ -89269,16 +89369,15 @@ async function writeField(changeDir, featureName, path, value) {
|
|
|
89269
89369
|
if (!allowed.includes(topSlot)) {
|
|
89270
89370
|
throw new OwnershipError(featureName, path, `feature '${featureName}' may not write '${path}' (owns ${allowed.join(", ")})`);
|
|
89271
89371
|
}
|
|
89272
|
-
const
|
|
89273
|
-
const
|
|
89274
|
-
|
|
89275
|
-
await mkdir3(dirname5(filePath), { recursive: true });
|
|
89276
|
-
await Bun.write(filePath, JSON.stringify(existing, null, 2) + `
|
|
89277
|
-
`);
|
|
89372
|
+
const inline = (await readJson(join11(changeDir, STATE_FILE3)))[topSlot];
|
|
89373
|
+
const seed = inline && typeof inline === "object" && !Array.isArray(inline) ? inline : undefined;
|
|
89374
|
+
await writeSlotField(changeDir, path, value, seed);
|
|
89278
89375
|
}
|
|
89279
89376
|
var STATE_FILE3 = ".ralph-state.json", OwnershipError;
|
|
89280
89377
|
var init_store = __esm(() => {
|
|
89281
89378
|
init_schema2();
|
|
89379
|
+
init_sidecar();
|
|
89380
|
+
init_sidecar();
|
|
89282
89381
|
init_state();
|
|
89283
89382
|
OwnershipError = class OwnershipError extends Error {
|
|
89284
89383
|
featureName;
|
|
@@ -89293,14 +89392,14 @@ var init_store = __esm(() => {
|
|
|
89293
89392
|
});
|
|
89294
89393
|
|
|
89295
89394
|
// apps/loop/src/components/TaskStatus.tsx
|
|
89296
|
-
import { join as
|
|
89395
|
+
import { join as join12 } from "path";
|
|
89297
89396
|
function TaskStatus({ state, stateDir }) {
|
|
89298
89397
|
const storage = getStorage();
|
|
89299
89398
|
const cost = Math.round(state.usage.total_cost_usd * 100) / 100;
|
|
89300
89399
|
const time3 = Math.round(state.usage.total_duration_ms / 1000 * 10) / 10 + "s";
|
|
89301
89400
|
const artifacts = OPENSPEC_ARTIFACTS.map((name) => ({
|
|
89302
89401
|
name,
|
|
89303
|
-
exists: storage.read(
|
|
89402
|
+
exists: storage.read(join12(stateDir, name)) !== null
|
|
89304
89403
|
}));
|
|
89305
89404
|
const recent = state.history.slice(-10);
|
|
89306
89405
|
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
@@ -98017,8 +98116,8 @@ var init_rate_limit_detection = __esm(() => {
|
|
|
98017
98116
|
});
|
|
98018
98117
|
|
|
98019
98118
|
// packages/engine/src/agents/claude.ts
|
|
98020
|
-
import { mkdtemp, unlink } from "fs/promises";
|
|
98021
|
-
import { join as
|
|
98119
|
+
import { mkdtemp, unlink as unlink2 } from "fs/promises";
|
|
98120
|
+
import { join as join13 } from "path";
|
|
98022
98121
|
import { tmpdir } from "os";
|
|
98023
98122
|
function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, reviewerModel) {
|
|
98024
98123
|
const effectiveModel = reviewerModel ?? model;
|
|
@@ -98039,7 +98138,7 @@ function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, review
|
|
|
98039
98138
|
}
|
|
98040
98139
|
async function runInteractive(req) {
|
|
98041
98140
|
const { model, prompt, taskDir } = req;
|
|
98042
|
-
const promptFile = taskDir ?
|
|
98141
|
+
const promptFile = taskDir ? join13(taskDir, "_interactive_prompt.md") : join13(await mkdtemp(join13(tmpdir(), "ralph-")), "prompt.md");
|
|
98043
98142
|
await Bun.write(promptFile, prompt);
|
|
98044
98143
|
try {
|
|
98045
98144
|
const cmd = [
|
|
@@ -98066,14 +98165,14 @@ async function runInteractive(req) {
|
|
|
98066
98165
|
env: scrubClaudeEnv(process.env)
|
|
98067
98166
|
});
|
|
98068
98167
|
const exitCode = await proc.exited;
|
|
98069
|
-
const doneFile = taskDir ?
|
|
98168
|
+
const doneFile = taskDir ? join13(taskDir, "_interactive_done") : null;
|
|
98070
98169
|
if (doneFile && await Bun.file(doneFile).exists()) {
|
|
98071
98170
|
return { exitCode: 0, usage: null, sessionId: null, rateLimited: false };
|
|
98072
98171
|
}
|
|
98073
98172
|
return { exitCode, usage: null, sessionId: null, rateLimited: false };
|
|
98074
98173
|
} finally {
|
|
98075
98174
|
try {
|
|
98076
|
-
await
|
|
98175
|
+
await unlink2(promptFile);
|
|
98077
98176
|
} catch {}
|
|
98078
98177
|
}
|
|
98079
98178
|
}
|
|
@@ -99105,6 +99204,12 @@ class FlowActorStore {
|
|
|
99105
99204
|
return typeof s.value === "string" || typeof s.status === "string";
|
|
99106
99205
|
}
|
|
99107
99206
|
async loadSnapshot(changeDir) {
|
|
99207
|
+
const sidecar = await readSlotSidecar(changeDir, "flow");
|
|
99208
|
+
if (sidecar && typeof sidecar === "object") {
|
|
99209
|
+
const snap = sidecar.actorSnapshot;
|
|
99210
|
+
if (snap !== undefined && snap !== null)
|
|
99211
|
+
return snap;
|
|
99212
|
+
}
|
|
99108
99213
|
const filePath = `${changeDir}/${STATE_FILE4}`;
|
|
99109
99214
|
const file2 = Bun.file(filePath);
|
|
99110
99215
|
if (!await file2.exists())
|
|
@@ -99563,11 +99668,11 @@ var init_meta_prompt = __esm(() => {
|
|
|
99563
99668
|
});
|
|
99564
99669
|
|
|
99565
99670
|
// packages/core/src/loop.ts
|
|
99566
|
-
import { join as
|
|
99671
|
+
import { join as join14 } from "path";
|
|
99567
99672
|
function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
99568
99673
|
const storage = getStorage();
|
|
99569
99674
|
let prompt = "";
|
|
99570
|
-
const steeringContent = storage.read(
|
|
99675
|
+
const steeringContent = storage.read(join14(taskDir, "steering.md"));
|
|
99571
99676
|
if (steeringContent !== null) {
|
|
99572
99677
|
const steeringLines = steeringContent.split(`
|
|
99573
99678
|
`).filter((line) => !line.startsWith("#")).filter((line) => line.trim()).slice(0, STEERING_MAX_LINES);
|
|
@@ -99586,8 +99691,8 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
|
99586
99691
|
`;
|
|
99587
99692
|
}
|
|
99588
99693
|
}
|
|
99589
|
-
const agentTasksPath =
|
|
99590
|
-
const missionTasksPath =
|
|
99694
|
+
const agentTasksPath = join14(taskDir, AGENT_TASKS_FILENAME);
|
|
99695
|
+
const missionTasksPath = join14(taskDir, MISSION_TASKS_FILENAME);
|
|
99591
99696
|
const agentTasksContent = storage.read(agentTasksPath);
|
|
99592
99697
|
const missionTasksContent = storage.read(missionTasksPath);
|
|
99593
99698
|
let activePath = null;
|
|
@@ -99663,7 +99768,7 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
|
99663
99768
|
}
|
|
99664
99769
|
}
|
|
99665
99770
|
if (reviewPhase?.enabled) {
|
|
99666
|
-
const reviewFindingsPath =
|
|
99771
|
+
const reviewFindingsPath = join14(taskDir, "review-findings.md");
|
|
99667
99772
|
const reviewFindingsContent = storage.read(reviewFindingsPath);
|
|
99668
99773
|
const hasUncheckedMission = missionTasksContent !== null && /^- \[ \]/m.test(missionTasksContent);
|
|
99669
99774
|
const hasUncheckedAgent = agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent);
|
|
@@ -99737,7 +99842,7 @@ When all tasks are complete and all files are committed, push your branch and op
|
|
|
99737
99842
|
}
|
|
99738
99843
|
function buildSteeringBlock(taskDir) {
|
|
99739
99844
|
const storage = getStorage();
|
|
99740
|
-
const steeringContent = storage.read(
|
|
99845
|
+
const steeringContent = storage.read(join14(taskDir, "steering.md"));
|
|
99741
99846
|
if (steeringContent === null)
|
|
99742
99847
|
return "";
|
|
99743
99848
|
const steeringLines = steeringContent.split(`
|
|
@@ -99835,7 +99940,7 @@ function buildPlanPrompt(state, taskDir) {
|
|
|
99835
99940
|
return prompt;
|
|
99836
99941
|
}
|
|
99837
99942
|
function buildReviewPrompt(state, taskDir) {
|
|
99838
|
-
const reviewFindingsPath =
|
|
99943
|
+
const reviewFindingsPath = join14(taskDir, "review-findings.md");
|
|
99839
99944
|
let prompt = buildSteeringBlock(taskDir);
|
|
99840
99945
|
prompt += `---
|
|
99841
99946
|
|
|
@@ -99900,7 +100005,7 @@ function buildPhasePrompt(phase, state, taskDir, reviewPhase, metaPromptOptions)
|
|
|
99900
100005
|
}
|
|
99901
100006
|
function checkStopSignal(taskDir, stateDir) {
|
|
99902
100007
|
const storage = getStorage();
|
|
99903
|
-
const stopFile =
|
|
100008
|
+
const stopFile = join14(taskDir, "STOP");
|
|
99904
100009
|
const reason = storage.read(stopFile);
|
|
99905
100010
|
if (reason === null)
|
|
99906
100011
|
return null;
|
|
@@ -99960,7 +100065,7 @@ function updateStateIteration(stateDir, result2, startedAt, engine, model, usage
|
|
|
99960
100065
|
}
|
|
99961
100066
|
function appendSteeringMessage(taskDir, message) {
|
|
99962
100067
|
const storage = getStorage();
|
|
99963
|
-
const steeringPath =
|
|
100068
|
+
const steeringPath = join14(taskDir, "steering.md");
|
|
99964
100069
|
const existing = storage.read(steeringPath);
|
|
99965
100070
|
const updated = existing ? `${message}
|
|
99966
100071
|
|
|
@@ -100010,7 +100115,7 @@ var init_loop2 = __esm(() => {
|
|
|
100010
100115
|
});
|
|
100011
100116
|
|
|
100012
100117
|
// apps/loop/src/hooks/useLoop.ts
|
|
100013
|
-
import { join as
|
|
100118
|
+
import { join as join15 } from "path";
|
|
100014
100119
|
function sleep(seconds) {
|
|
100015
100120
|
return new Promise((resolve3) => setTimeout(resolve3, seconds * 1000));
|
|
100016
100121
|
}
|
|
@@ -100072,7 +100177,7 @@ function useLoop(opts) {
|
|
|
100072
100177
|
}
|
|
100073
100178
|
} else {
|
|
100074
100179
|
if (rawState !== null) {
|
|
100075
|
-
addInfo(`.ralph-state.json was malformed \u2014 reinitialising.
|
|
100180
|
+
addInfo(`.ralph-state.json was malformed \u2014 reinitialising. Feature-owned slots (linearComments, specAttachments, \u2026) live in their own sidecar files and are unaffected.`);
|
|
100076
100181
|
}
|
|
100077
100182
|
currentState = buildInitialState({
|
|
100078
100183
|
name: opts.name,
|
|
@@ -100082,12 +100187,6 @@ function useLoop(opts) {
|
|
|
100082
100187
|
manualTest: opts.manualTest,
|
|
100083
100188
|
createPr: opts.createPr ?? false
|
|
100084
100189
|
});
|
|
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
100190
|
writeState(stateDir, currentState);
|
|
100092
100191
|
}
|
|
100093
100192
|
const isResume2 = currentState.iteration > 0;
|
|
@@ -100132,8 +100231,8 @@ function useLoop(opts) {
|
|
|
100132
100231
|
setState(currentState);
|
|
100133
100232
|
if (!actor.getSnapshot().matches("running"))
|
|
100134
100233
|
break;
|
|
100135
|
-
const tasksContent = storage.read(
|
|
100136
|
-
const agentTasksContent = storage.read(
|
|
100234
|
+
const tasksContent = storage.read(join15(tasksDir, MISSION_TASKS_FILENAME));
|
|
100235
|
+
const agentTasksContent = storage.read(join15(tasksDir, AGENT_TASKS_FILENAME));
|
|
100137
100236
|
if (tasksContent === null && currentState.iteration > 0 && typeof opts.changeStore.listChanges === "function") {
|
|
100138
100237
|
let stillActive = true;
|
|
100139
100238
|
try {
|
|
@@ -100170,7 +100269,7 @@ function useLoop(opts) {
|
|
|
100170
100269
|
const agentDone = agentTasksContent === null || allCompleted(agentTasksContent);
|
|
100171
100270
|
if (missionDone && agentDone && tasksContent !== null) {
|
|
100172
100271
|
if (opts.reviewPhase?.enabled) {
|
|
100173
|
-
const reviewFindingsPath =
|
|
100272
|
+
const reviewFindingsPath = join15(tasksDir, "review-findings.md");
|
|
100174
100273
|
const reviewFindingsFile = Bun.file(reviewFindingsPath);
|
|
100175
100274
|
const findingsExists = await reviewFindingsFile.exists();
|
|
100176
100275
|
const findingsContent = findingsExists ? await reviewFindingsFile.text() : null;
|
|
@@ -100199,7 +100298,7 @@ function useLoop(opts) {
|
|
|
100199
100298
|
model: opts.reviewPhase.reviewerModel ?? opts.model,
|
|
100200
100299
|
prompt: reviewPrompt,
|
|
100201
100300
|
logFlag: opts.log,
|
|
100202
|
-
logFile:
|
|
100301
|
+
logFile: join15(stateDir, `log-review-${roundNum}.json`),
|
|
100203
100302
|
taskDir: tasksDir,
|
|
100204
100303
|
reviewerContextStrategy: opts.reviewPhase.reviewerContextStrategy ?? "fresh",
|
|
100205
100304
|
onFeedEvent: addFeedEvent
|
|
@@ -100273,8 +100372,8 @@ function useLoop(opts) {
|
|
|
100273
100372
|
const time3 = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
100274
100373
|
addIterationHeader(localIter, time3);
|
|
100275
100374
|
addInfo(`Iteration ${localIter} (total: ${currentState.iteration})`);
|
|
100276
|
-
const proposalContent = storage.read(
|
|
100277
|
-
const designContent = storage.read(
|
|
100375
|
+
const proposalContent = storage.read(join15(tasksDir, "proposal.md"));
|
|
100376
|
+
const designContent = storage.read(join15(tasksDir, "design.md"));
|
|
100278
100377
|
const routedPhase = routeTaskPhase(opts.phase, {
|
|
100279
100378
|
proposal: proposalContent,
|
|
100280
100379
|
design: designContent,
|
|
@@ -100294,7 +100393,7 @@ function useLoop(opts) {
|
|
|
100294
100393
|
model: opts.model,
|
|
100295
100394
|
prompt,
|
|
100296
100395
|
logFlag: opts.log,
|
|
100297
|
-
logFile:
|
|
100396
|
+
logFile: join15(stateDir, "log.json"),
|
|
100298
100397
|
taskDir: tasksDir,
|
|
100299
100398
|
interactive: false,
|
|
100300
100399
|
onFeedEvent: addFeedEvent,
|
|
@@ -100317,7 +100416,7 @@ function useLoop(opts) {
|
|
|
100317
100416
|
model: opts.model,
|
|
100318
100417
|
prompt: buildSteeringPrompt(steerMessage),
|
|
100319
100418
|
logFlag: opts.log,
|
|
100320
|
-
logFile:
|
|
100419
|
+
logFile: join15(stateDir, "log.json"),
|
|
100321
100420
|
taskDir: tasksDir,
|
|
100322
100421
|
onFeedEvent: addResumeFeedEvent,
|
|
100323
100422
|
signal: resumeController.signal,
|
|
@@ -100624,7 +100723,7 @@ var init_TaskLoop = __esm(async () => {
|
|
|
100624
100723
|
});
|
|
100625
100724
|
|
|
100626
100725
|
// apps/loop/src/components/App.tsx
|
|
100627
|
-
import { join as
|
|
100726
|
+
import { join as join16 } from "path";
|
|
100628
100727
|
function ExitAfterRender({ children }) {
|
|
100629
100728
|
const { exit } = use_app_default();
|
|
100630
100729
|
import_react59.useEffect(() => {
|
|
@@ -100677,7 +100776,7 @@ function App2({ args, taskPhase }) {
|
|
|
100677
100776
|
}
|
|
100678
100777
|
const layout = getLayout();
|
|
100679
100778
|
const stateDir = layout.taskStateDir(args.name);
|
|
100680
|
-
if (getStorage().read(
|
|
100779
|
+
if (getStorage().read(join16(stateDir, ".ralph-state.json")) === null) {
|
|
100681
100780
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
|
|
100682
100781
|
message: `Error: change '${args.name}' not found`
|
|
100683
100782
|
}, undefined, false, undefined, this);
|
|
@@ -100731,7 +100830,7 @@ var init_App2 = __esm(async () => {
|
|
|
100731
100830
|
|
|
100732
100831
|
// packages/log/src/log.ts
|
|
100733
100832
|
import { appendFile } from "fs/promises";
|
|
100734
|
-
import { join as
|
|
100833
|
+
import { join as join17, dirname as dirname7 } from "path";
|
|
100735
100834
|
import { homedir as homedir4 } from "os";
|
|
100736
100835
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
100737
100836
|
function fmt(type, text) {
|
|
@@ -100780,14 +100879,14 @@ var init_log = __esm(() => {
|
|
|
100780
100879
|
init_version();
|
|
100781
100880
|
jsonLogChains = new Map;
|
|
100782
100881
|
ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
100783
|
-
AGENT_LOG_PATH =
|
|
100882
|
+
AGENT_LOG_PATH = join17(homedir4(), ".ralph", "agent-mode.log");
|
|
100784
100883
|
mkdir5(dirname7(AGENT_LOG_PATH), { recursive: true }).catch(() => {
|
|
100785
100884
|
return;
|
|
100786
100885
|
});
|
|
100787
100886
|
});
|
|
100788
100887
|
|
|
100789
100888
|
// apps/loop/src/debug.ts
|
|
100790
|
-
import { join as
|
|
100889
|
+
import { join as join18 } from "path";
|
|
100791
100890
|
function fmtTs(d) {
|
|
100792
100891
|
return d.toISOString().replace("T", " ").slice(0, 23);
|
|
100793
100892
|
}
|
|
@@ -100899,7 +100998,7 @@ function detectDebugStuck(lines) {
|
|
|
100899
100998
|
};
|
|
100900
100999
|
}
|
|
100901
101000
|
async function inspectBinary(projectRoot) {
|
|
100902
|
-
const binPath =
|
|
101001
|
+
const binPath = join18(projectRoot, ".ralph", "bin", "cli.js");
|
|
100903
101002
|
const file2 = Bun.file(binPath);
|
|
100904
101003
|
if (!await file2.exists())
|
|
100905
101004
|
return null;
|
|
@@ -100924,7 +101023,7 @@ async function inspectBinary(projectRoot) {
|
|
|
100924
101023
|
async function resolveDebugTarget(projectRoot, opts) {
|
|
100925
101024
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
100926
101025
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
100927
|
-
const jsonlLogFile = Bun.file(
|
|
101026
|
+
const jsonlLogFile = Bun.file(join18(projectRoot, ".ralph", "agent.log"));
|
|
100928
101027
|
const jsonlLines = await jsonlLogFile.exists() ? parseJsonlLog(await jsonlLogFile.text()) : [];
|
|
100929
101028
|
const allLines = [...textLines, ...jsonlLines];
|
|
100930
101029
|
if (opts.name && !opts.issue) {
|
|
@@ -101029,7 +101128,7 @@ async function runDebug(opts) {
|
|
|
101029
101128
|
`);
|
|
101030
101129
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
101031
101130
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
101032
|
-
const jsonlLogPath =
|
|
101131
|
+
const jsonlLogPath = join18(projectRoot, ".ralph", "agent.log");
|
|
101033
101132
|
const jsonlLogFile = Bun.file(jsonlLogPath);
|
|
101034
101133
|
const hasJsonlLog = await jsonlLogFile.exists();
|
|
101035
101134
|
let { changeName, identifier: issueIdentifier } = await resolveDebugTarget(projectRoot, {
|
|
@@ -101043,7 +101142,7 @@ async function runDebug(opts) {
|
|
|
101043
101142
|
}
|
|
101044
101143
|
const jsonlLines = hasJsonlLog ? parseJsonlLog(await jsonlLogFile.text(), changeName) : [];
|
|
101045
101144
|
const relevantText = textLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
|
|
101046
|
-
const workerLogFile = Bun.file(
|
|
101145
|
+
const workerLogFile = Bun.file(join18(projectRoot, ".ralph", "logs", `${changeName}.log`));
|
|
101047
101146
|
const workerLines = await workerLogFile.exists() ? parseTextLog(await workerLogFile.text()) : [];
|
|
101048
101147
|
const merged = [...relevantText, ...jsonlLines, ...workerLines].sort((a, b) => +a.ts - +b.ts);
|
|
101049
101148
|
const seen = new Set;
|
|
@@ -101200,8 +101299,8 @@ async function runDebug(opts) {
|
|
|
101200
101299
|
out(" \u26A0 PR currently has merge conflicts");
|
|
101201
101300
|
if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
|
|
101202
101301
|
out(" \u26A0 PR has failing CI checks");
|
|
101203
|
-
const worktreePath =
|
|
101204
|
-
if (await Bun.file(
|
|
101302
|
+
const worktreePath = join18(projectRoot, ".ralph", "worktrees", changeName);
|
|
101303
|
+
if (await Bun.file(join18(worktreePath, ".git")).exists()) {
|
|
101205
101304
|
out(` Worktree : ${worktreePath}`);
|
|
101206
101305
|
}
|
|
101207
101306
|
if (!timeline.length)
|
|
@@ -101221,12 +101320,12 @@ __export(exports_src2, {
|
|
|
101221
101320
|
taskMain: () => taskMain,
|
|
101222
101321
|
main: () => main2
|
|
101223
101322
|
});
|
|
101224
|
-
import { join as
|
|
101323
|
+
import { join as join19 } from "path";
|
|
101225
101324
|
import { exists as exists2, mkdir as mkdir6, rm as rm2 } from "fs/promises";
|
|
101226
101325
|
async function ensureRalphGitignore(projectRoot) {
|
|
101227
|
-
const ralphDir =
|
|
101326
|
+
const ralphDir = join19(projectRoot, ".ralph");
|
|
101228
101327
|
await mkdir6(ralphDir, { recursive: true });
|
|
101229
|
-
const gitignorePath =
|
|
101328
|
+
const gitignorePath = join19(ralphDir, ".gitignore");
|
|
101230
101329
|
const file2 = Bun.file(gitignorePath);
|
|
101231
101330
|
if (await file2.exists()) {
|
|
101232
101331
|
const existing = await file2.text();
|
|
@@ -101293,9 +101392,9 @@ async function main2(argv) {
|
|
|
101293
101392
|
`);
|
|
101294
101393
|
return 1;
|
|
101295
101394
|
}
|
|
101296
|
-
const worktreeDir =
|
|
101297
|
-
const changeDir =
|
|
101298
|
-
const stateDir =
|
|
101395
|
+
const worktreeDir = join19(worktreesDir(projectRoot), args.name);
|
|
101396
|
+
const changeDir = join19(tasksDir, args.name);
|
|
101397
|
+
const stateDir = join19(statesDir, args.name);
|
|
101299
101398
|
const branch = `ralph/${args.name}`;
|
|
101300
101399
|
const removed = [];
|
|
101301
101400
|
if (await exists2(worktreeDir)) {
|
|
@@ -101346,8 +101445,8 @@ async function main2(argv) {
|
|
|
101346
101445
|
return 0;
|
|
101347
101446
|
}
|
|
101348
101447
|
if (args.mode === "task" && args.name) {
|
|
101349
|
-
await mkdir6(
|
|
101350
|
-
await mkdir6(
|
|
101448
|
+
await mkdir6(join19(statesDir, args.name), { recursive: true });
|
|
101449
|
+
await mkdir6(join19(tasksDir, args.name), { recursive: true });
|
|
101351
101450
|
await ensureRalphGitignore(projectRoot);
|
|
101352
101451
|
}
|
|
101353
101452
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
@@ -101375,8 +101474,8 @@ async function taskMain(argv) {
|
|
|
101375
101474
|
const layout = projectLayout(projectRoot);
|
|
101376
101475
|
const statesDir = layout.statesDir;
|
|
101377
101476
|
const tasksDir = layout.tasksDir;
|
|
101378
|
-
await mkdir6(
|
|
101379
|
-
await mkdir6(
|
|
101477
|
+
await mkdir6(join19(statesDir, args.name), { recursive: true });
|
|
101478
|
+
await mkdir6(join19(tasksDir, args.name), { recursive: true });
|
|
101380
101479
|
await ensureRalphGitignore(projectRoot);
|
|
101381
101480
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
101382
101481
|
const { waitUntilExit } = render_default(import_react60.createElement(App2, {
|
|
@@ -101471,6 +101570,7 @@ async function parseAgentArgs(argv) {
|
|
|
101471
101570
|
noTmux: false,
|
|
101472
101571
|
checks: false,
|
|
101473
101572
|
review: false,
|
|
101573
|
+
agentDebug: false,
|
|
101474
101574
|
ticketTokens: []
|
|
101475
101575
|
};
|
|
101476
101576
|
const state = emptyParseState();
|
|
@@ -101593,6 +101693,9 @@ async function parseAgentArgs(argv) {
|
|
|
101593
101693
|
case "--debug":
|
|
101594
101694
|
result2.debug = true;
|
|
101595
101695
|
break;
|
|
101696
|
+
case "--agent-debug":
|
|
101697
|
+
result2.agentDebug = true;
|
|
101698
|
+
break;
|
|
101596
101699
|
case "--pre-existing-error-check":
|
|
101597
101700
|
result2.preExistingErrorCheck = true;
|
|
101598
101701
|
break;
|
|
@@ -101692,6 +101795,7 @@ var init_cli2 = __esm(() => {
|
|
|
101692
101795
|
" --checks List mode: show failing CI check names per PR",
|
|
101693
101796
|
" --review List mode: show unresolved review comment count per PR",
|
|
101694
101797
|
" --debug List mode: explain why a Linear ticket was not picked up (use with --name)",
|
|
101798
|
+
" --agent-debug After each ticket finishes, run a one-shot self-review and write a report to ~/.ralph/retro/",
|
|
101695
101799
|
" --help, -h Show this help message",
|
|
101696
101800
|
"",
|
|
101697
101801
|
"Examples:",
|
|
@@ -101768,7 +101872,7 @@ function formatError2(err) {
|
|
|
101768
101872
|
}
|
|
101769
101873
|
|
|
101770
101874
|
// apps/agent/src/shared/capabilities/fs-change.ts
|
|
101771
|
-
import { join as
|
|
101875
|
+
import { join as join20, dirname as dirname8 } from "path";
|
|
101772
101876
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
101773
101877
|
var scaffold, prependTask, appendSteering, fsChange;
|
|
101774
101878
|
var init_fs_change = __esm(() => {
|
|
@@ -101781,11 +101885,11 @@ var init_fs_change = __esm(() => {
|
|
|
101781
101885
|
errorFormatter: formatError2,
|
|
101782
101886
|
run: async (args) => {
|
|
101783
101887
|
await mkdir7(args.changeDir, { recursive: true });
|
|
101784
|
-
await mkdir7(
|
|
101888
|
+
await mkdir7(join20(args.changeDir, "specs"), { recursive: true });
|
|
101785
101889
|
await mkdir7(args.stateDir, { recursive: true });
|
|
101786
|
-
await Bun.write(
|
|
101787
|
-
await Bun.write(
|
|
101788
|
-
await Bun.write(
|
|
101890
|
+
await Bun.write(join20(args.changeDir, "proposal.md"), args.proposal);
|
|
101891
|
+
await Bun.write(join20(args.changeDir, "tasks.md"), args.tasks);
|
|
101892
|
+
await Bun.write(join20(args.changeDir, "design.md"), args.design);
|
|
101789
101893
|
}
|
|
101790
101894
|
};
|
|
101791
101895
|
prependTask = {
|
|
@@ -101803,7 +101907,7 @@ var init_fs_change = __esm(() => {
|
|
|
101803
101907
|
retryPolicy: NO_RETRY,
|
|
101804
101908
|
errorFormatter: formatError2,
|
|
101805
101909
|
run: async (args) => {
|
|
101806
|
-
const path =
|
|
101910
|
+
const path = join20(args.changeDir, "steering.md");
|
|
101807
101911
|
const f2 = Bun.file(path);
|
|
101808
101912
|
const existing = await f2.exists() ? await f2.text() : null;
|
|
101809
101913
|
const updated = existing ? `${args.message}
|
|
@@ -101818,11 +101922,11 @@ ${existing.trimStart()}` : `${args.message}
|
|
|
101818
101922
|
});
|
|
101819
101923
|
|
|
101820
101924
|
// apps/agent/src/agent/worktree.ts
|
|
101821
|
-
import { basename as basename2, join as
|
|
101925
|
+
import { basename as basename2, join as join21 } from "path";
|
|
101822
101926
|
import { homedir as homedir5 } from "os";
|
|
101823
101927
|
import { exists as exists3 } from "fs/promises";
|
|
101824
101928
|
function worktreesDir2(projectRoot) {
|
|
101825
|
-
return
|
|
101929
|
+
return join21(homedir5(), ".ralph", basename2(projectRoot), "worktrees");
|
|
101826
101930
|
}
|
|
101827
101931
|
function branchForChange(changeName) {
|
|
101828
101932
|
return `ralph/${changeName}`;
|
|
@@ -101841,7 +101945,7 @@ function createWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
|
101841
101945
|
}
|
|
101842
101946
|
async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
101843
101947
|
const dir = worktreesDir2(projectRoot);
|
|
101844
|
-
const cwd2 =
|
|
101948
|
+
const cwd2 = join21(dir, changeName);
|
|
101845
101949
|
const branch = branchForChange(changeName);
|
|
101846
101950
|
const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
|
|
101847
101951
|
if (list.stdout.includes(`worktree ${cwd2}
|
|
@@ -101866,7 +101970,7 @@ async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
|
101866
101970
|
return { cwd: cwd2, branch };
|
|
101867
101971
|
}
|
|
101868
101972
|
async function installPrePushHook(cwd2, runner) {
|
|
101869
|
-
const hookPath =
|
|
101973
|
+
const hookPath = join21(cwd2, ".ralph-hooks", "pre-push");
|
|
101870
101974
|
await Bun.write(hookPath, PRE_PUSH_HOOK_SCRIPT);
|
|
101871
101975
|
const chmod = Bun.spawn(["chmod", "+x", hookPath]);
|
|
101872
101976
|
await chmod.exited;
|
|
@@ -101912,8 +102016,8 @@ async function isWorktreeSafeToRemove(cwd2, base2, runner) {
|
|
|
101912
102016
|
return { safe: true, dirty, unpushedCommits };
|
|
101913
102017
|
}
|
|
101914
102018
|
async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
101915
|
-
const dst =
|
|
101916
|
-
const src =
|
|
102019
|
+
const dst = join21(worktreeCwd, ".mcp.json");
|
|
102020
|
+
const src = join21(projectRoot, ".mcp.json");
|
|
101917
102021
|
const source = await exists3(dst) ? dst : await exists3(src) ? src : null;
|
|
101918
102022
|
if (!source)
|
|
101919
102023
|
return;
|
|
@@ -101927,7 +102031,7 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
|
101927
102031
|
if (servers && typeof servers === "object") {
|
|
101928
102032
|
for (const cfg of Object.values(servers)) {
|
|
101929
102033
|
if (Array.isArray(cfg.args)) {
|
|
101930
|
-
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ?
|
|
102034
|
+
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join21(projectRoot, a) : a);
|
|
101931
102035
|
}
|
|
101932
102036
|
}
|
|
101933
102037
|
}
|
|
@@ -103777,7 +103881,7 @@ function emitFeatureSkipped(bus, id, reason) {
|
|
|
103777
103881
|
var init_run_feature = () => {};
|
|
103778
103882
|
|
|
103779
103883
|
// apps/agent/src/agent/post-task.ts
|
|
103780
|
-
import { join as
|
|
103884
|
+
import { join as join22, dirname as dirname9 } from "path";
|
|
103781
103885
|
function summarizeUncommittedStatus(stdout) {
|
|
103782
103886
|
const lines = stdout.split(`
|
|
103783
103887
|
`).filter((line) => line.length > 0);
|
|
@@ -103849,7 +103953,7 @@ async function reactivateState(stateFilePath, log3, changeName) {
|
|
|
103849
103953
|
async function runWorkerWithFixTask(ctx, heading, body) {
|
|
103850
103954
|
try {
|
|
103851
103955
|
await runCapability(fsChange.prependTask, {
|
|
103852
|
-
tasksPath:
|
|
103956
|
+
tasksPath: join22(ctx.changeDir, AGENT_TASKS_FILENAME),
|
|
103853
103957
|
heading,
|
|
103854
103958
|
failureOutput: body
|
|
103855
103959
|
});
|
|
@@ -104396,7 +104500,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104396
104500
|
emit3("validate-fix", command);
|
|
104397
104501
|
log3(`! validation check failed: ${command}`, "yellow");
|
|
104398
104502
|
try {
|
|
104399
|
-
await prependFixTask(
|
|
104503
|
+
await prependFixTask(join22(changeDir, AGENT_TASKS_FILENAME), `Fix failing validation: ${command}`, output || `Command exited with code ${exitCode}`);
|
|
104400
104504
|
} catch (err) {
|
|
104401
104505
|
log3(`! could not prepend fix task: ${err.message}`, "red");
|
|
104402
104506
|
return 1;
|
|
@@ -104407,7 +104511,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104407
104511
|
}
|
|
104408
104512
|
}
|
|
104409
104513
|
try {
|
|
104410
|
-
await prependFixTask(
|
|
104514
|
+
await prependFixTask(join22(changeDir, AGENT_TASKS_FILENAME), "Run openspec validation", [
|
|
104411
104515
|
`Run \`bunx openspec validate ${changeName}\` to validate the change artifacts.`,
|
|
104412
104516
|
`Commit any pending changes before running the validation command.`
|
|
104413
104517
|
].join(`
|
|
@@ -104420,7 +104524,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104420
104524
|
return respawnWorker();
|
|
104421
104525
|
}
|
|
104422
104526
|
async function recordGaveUp(stateFilePath, log3, changeName) {
|
|
104423
|
-
const path =
|
|
104527
|
+
const path = join22(dirname9(stateFilePath), GAVEUP_COUNT_FILE);
|
|
104424
104528
|
try {
|
|
104425
104529
|
const file2 = Bun.file(path);
|
|
104426
104530
|
const current = await file2.exists() ? Number.parseInt(await file2.text(), 10) || 0 : 0;
|
|
@@ -104551,6 +104655,15 @@ async function runPostTask(input, deps) {
|
|
|
104551
104655
|
emit3(succeeded ? "done" : "gave-up", succeeded ? undefined : `exit ${effectiveCode}`);
|
|
104552
104656
|
if (!succeeded)
|
|
104553
104657
|
await recordGaveUp(stateFilePath, log3, changeName);
|
|
104658
|
+
await deps.runRetrospective?.({
|
|
104659
|
+
changeName,
|
|
104660
|
+
cwd: cwd2,
|
|
104661
|
+
changeDir,
|
|
104662
|
+
stateFilePath,
|
|
104663
|
+
branch,
|
|
104664
|
+
issue: issue2,
|
|
104665
|
+
effectiveCode
|
|
104666
|
+
});
|
|
104554
104667
|
await runWorktreeCleanupPhase({ changeName, cwd: cwd2, projectRoot, useWorktree, effectiveCode, cfg }, { git: git2, log: log3, emit: emit3 });
|
|
104555
104668
|
await runTeardownPhase({ cwd: cwd2, teardownScript: cfg.teardownScript }, { runScript, log: log3, emit: emit3 });
|
|
104556
104669
|
return effectiveCode;
|
|
@@ -105218,6 +105331,32 @@ class AgentCoordinator {
|
|
|
105218
105331
|
if (!this.deps.syncTasks || !this.deps.getIterationCount)
|
|
105219
105332
|
return;
|
|
105220
105333
|
for (const w of this.workers) {
|
|
105334
|
+
if (this.deps.getTasksFingerprint) {
|
|
105335
|
+
let fingerprint;
|
|
105336
|
+
try {
|
|
105337
|
+
fingerprint = await this.deps.getTasksFingerprint(w.changeName);
|
|
105338
|
+
} catch (err) {
|
|
105339
|
+
this.deps.onLog(`! tasks fingerprint read failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
|
|
105340
|
+
continue;
|
|
105341
|
+
}
|
|
105342
|
+
if (fingerprint === null || fingerprint === w.lastSyncedTasksFingerprint) {
|
|
105343
|
+
continue;
|
|
105344
|
+
}
|
|
105345
|
+
let iteration;
|
|
105346
|
+
try {
|
|
105347
|
+
iteration = await this.deps.getIterationCount(w.changeName);
|
|
105348
|
+
} catch (err) {
|
|
105349
|
+
this.deps.onLog(`! iteration count read failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
|
|
105350
|
+
continue;
|
|
105351
|
+
}
|
|
105352
|
+
try {
|
|
105353
|
+
await this.deps.syncTasks(w, iteration);
|
|
105354
|
+
w.lastSyncedTasksFingerprint = fingerprint;
|
|
105355
|
+
} catch (err) {
|
|
105356
|
+
this.deps.onLog(`! sync-tasks (poll) failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
|
|
105357
|
+
}
|
|
105358
|
+
continue;
|
|
105359
|
+
}
|
|
105221
105360
|
let count;
|
|
105222
105361
|
try {
|
|
105223
105362
|
count = await this.deps.getIterationCount(w.changeName);
|
|
@@ -105497,6 +105636,7 @@ class AgentCoordinator {
|
|
|
105497
105636
|
kill: handle.kill,
|
|
105498
105637
|
lastReportedIteration: 0,
|
|
105499
105638
|
lastSyncedIteration: 0,
|
|
105639
|
+
lastSyncedTasksFingerprint: null,
|
|
105500
105640
|
restarting: false,
|
|
105501
105641
|
reapedForAwaiting: false
|
|
105502
105642
|
};
|
|
@@ -105660,6 +105800,7 @@ class AgentCoordinator {
|
|
|
105660
105800
|
kill: () => {},
|
|
105661
105801
|
lastReportedIteration: 0,
|
|
105662
105802
|
lastSyncedIteration: 0,
|
|
105803
|
+
lastSyncedTasksFingerprint: null,
|
|
105663
105804
|
restarting: false,
|
|
105664
105805
|
reapedForAwaiting: false
|
|
105665
105806
|
};
|
|
@@ -105789,15 +105930,15 @@ var init_coordinator2 = __esm(() => {
|
|
|
105789
105930
|
});
|
|
105790
105931
|
|
|
105791
105932
|
// apps/agent/src/agent/scaffold.ts
|
|
105792
|
-
import { join as
|
|
105933
|
+
import { join as join23 } from "path";
|
|
105793
105934
|
function changeNameForIssue(issue2) {
|
|
105794
105935
|
const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40).replace(/^-+|-+$/g, "");
|
|
105795
105936
|
return slug ? `${issue2.identifier.toLowerCase()}-${slug}` : issue2.identifier.toLowerCase();
|
|
105796
105937
|
}
|
|
105797
105938
|
async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = [], appendPrompt = "", attachments = []) {
|
|
105798
105939
|
const name = changeNameForIssue(issue2);
|
|
105799
|
-
const changeDir =
|
|
105800
|
-
const stateDir =
|
|
105940
|
+
const changeDir = join23(tasksDir, name);
|
|
105941
|
+
const stateDir = join23(statesDir, name);
|
|
105801
105942
|
const commentsBlock = comments.length > 0 ? [
|
|
105802
105943
|
"",
|
|
105803
105944
|
"## Linear comments",
|
|
@@ -105850,8 +105991,8 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = []
|
|
|
105850
105991
|
`- [ ] Refine proposal.md with the problem statement, approach, and acceptance criteria derived from the research`,
|
|
105851
105992
|
`- [ ] Fill in \`## Why\` and \`## What Changes\` in proposal.md so \`openspec validate\` passes (these sections are required by the validator)`,
|
|
105852
105993
|
`- [ ] Add at least one spec delta under \`specs/<capability>/spec.md\` describing the behavior added/modified/removed by this change`,
|
|
105853
|
-
`- [ ] Fill in design.md with the technical design (files to touch, data flow, edge cases)
|
|
105854
|
-
`- [ ] 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.`,
|
|
105994
|
+
`- [ ] 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).`,
|
|
105995
|
+
`- [ ] 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.`,
|
|
105855
105996
|
`- [ ] Is there anything else to add? Review the complete change context and document any additional edge cases, constraints, or open questions not captured above.`,
|
|
105856
105997
|
""
|
|
105857
105998
|
].join(`
|
|
@@ -105933,19 +106074,22 @@ var init_detections = __esm(() => {
|
|
|
105933
106074
|
});
|
|
105934
106075
|
|
|
105935
106076
|
// apps/agent/src/features/confirmation/state.ts
|
|
105936
|
-
import { dirname as dirname10, join as
|
|
105937
|
-
|
|
105938
|
-
async function readConfirmationState(statePath) {
|
|
106077
|
+
import { dirname as dirname10, join as join24 } from "path";
|
|
106078
|
+
async function readInlineConfirmation(statePath) {
|
|
105939
106079
|
const f2 = Bun.file(statePath);
|
|
105940
|
-
|
|
105941
|
-
|
|
105942
|
-
|
|
105943
|
-
|
|
105944
|
-
|
|
105945
|
-
|
|
105946
|
-
|
|
106080
|
+
if (!await f2.exists())
|
|
106081
|
+
return null;
|
|
106082
|
+
try {
|
|
106083
|
+
const obj = await f2.json();
|
|
106084
|
+
return obj.confirmation ?? null;
|
|
106085
|
+
} catch {
|
|
106086
|
+
return null;
|
|
105947
106087
|
}
|
|
105948
|
-
|
|
106088
|
+
}
|
|
106089
|
+
async function readConfirmationState(statePath) {
|
|
106090
|
+
const changeDir = dirname10(statePath);
|
|
106091
|
+
const sidecar = await readSlotSidecar(changeDir, "confirmation");
|
|
106092
|
+
const existing = sidecar ?? await readInlineConfirmation(statePath) ?? null;
|
|
105949
106093
|
const confirmation = {
|
|
105950
106094
|
askedAt: existing?.askedAt ?? null,
|
|
105951
106095
|
lastReminderAt: existing?.lastReminderAt ?? null,
|
|
@@ -105953,14 +106097,13 @@ async function readConfirmationState(statePath) {
|
|
|
105953
106097
|
rounds: existing?.rounds ?? 0,
|
|
105954
106098
|
stuckPostedAt: existing?.stuckPostedAt ?? null,
|
|
105955
106099
|
lastReviseConsumedAt: existing?.lastReviseConsumedAt ?? null,
|
|
105956
|
-
awaitingMarkerAppliedAt: existing?.awaitingMarkerAppliedAt ?? null
|
|
106100
|
+
awaitingMarkerAppliedAt: existing?.awaitingMarkerAppliedAt ?? null,
|
|
106101
|
+
earlyDraftPrAt: existing?.earlyDraftPrAt ?? null
|
|
105957
106102
|
};
|
|
105958
|
-
return { stateObj, confirmation };
|
|
106103
|
+
return { stateObj: {}, confirmation };
|
|
105959
106104
|
}
|
|
105960
|
-
async function writeConfirmationState(statePath,
|
|
105961
|
-
await
|
|
105962
|
-
await Bun.write(statePath, JSON.stringify({ ...stateObj, confirmation }, null, 2) + `
|
|
105963
|
-
`);
|
|
106105
|
+
async function writeConfirmationState(statePath, _stateObj, confirmation) {
|
|
106106
|
+
await writeSlotField(dirname10(statePath), "confirmation", confirmation);
|
|
105964
106107
|
}
|
|
105965
106108
|
async function restartFromDesign(changeDir, changeName) {
|
|
105966
106109
|
const designStub = [
|
|
@@ -105970,8 +106113,8 @@ async function restartFromDesign(changeDir, changeName) {
|
|
|
105970
106113
|
""
|
|
105971
106114
|
].join(`
|
|
105972
106115
|
`);
|
|
105973
|
-
await Bun.write(
|
|
105974
|
-
const tasksPath =
|
|
106116
|
+
await Bun.write(join24(changeDir, "design.md"), designStub);
|
|
106117
|
+
const tasksPath = join24(changeDir, "tasks.md");
|
|
105975
106118
|
if (await Bun.file(tasksPath).exists()) {
|
|
105976
106119
|
await Bun.write(tasksPath, `# Tasks
|
|
105977
106120
|
|
|
@@ -105983,6 +106126,7 @@ async function appendSteeringNote(changeDir, message) {
|
|
|
105983
106126
|
await runCapability(fsChange.appendSteering, { changeDir, message });
|
|
105984
106127
|
}
|
|
105985
106128
|
var init_state2 = __esm(() => {
|
|
106129
|
+
init_store();
|
|
105986
106130
|
init_fs_change();
|
|
105987
106131
|
});
|
|
105988
106132
|
|
|
@@ -106200,8 +106344,7 @@ var init_inspect = __esm(() => {
|
|
|
106200
106344
|
});
|
|
106201
106345
|
|
|
106202
106346
|
// apps/agent/src/features/confirmation/awaiting.ts
|
|
106203
|
-
import { join as
|
|
106204
|
-
import { mkdir as mkdir9 } from "fs/promises";
|
|
106347
|
+
import { join as join25 } from "path";
|
|
106205
106348
|
async function resolveChangeCwdForIssue(issue2, changeName, deps) {
|
|
106206
106349
|
const tracked = deps.cwdOf(changeName);
|
|
106207
106350
|
if (tracked)
|
|
@@ -106209,12 +106352,12 @@ async function resolveChangeCwdForIssue(issue2, changeName, deps) {
|
|
|
106209
106352
|
if (!deps.useWorktree)
|
|
106210
106353
|
return deps.projectRoot;
|
|
106211
106354
|
const root = worktreesDir2(deps.projectRoot);
|
|
106212
|
-
const canonical =
|
|
106213
|
-
if (await Bun.file(
|
|
106355
|
+
const canonical = join25(root, worktreeDirNameForIssue(issue2));
|
|
106356
|
+
if (await Bun.file(join25(canonical, "openspec", "changes", changeName, "tasks.md")).exists()) {
|
|
106214
106357
|
return canonical;
|
|
106215
106358
|
}
|
|
106216
|
-
const legacy =
|
|
106217
|
-
if (await Bun.file(
|
|
106359
|
+
const legacy = join25(root, changeName);
|
|
106360
|
+
if (await Bun.file(join25(legacy, "openspec", "changes", changeName, "tasks.md")).exists()) {
|
|
106218
106361
|
return legacy;
|
|
106219
106362
|
}
|
|
106220
106363
|
return deps.projectRoot;
|
|
@@ -106234,17 +106377,8 @@ async function postPlanReadyCommentOnce(issue2, statePath, changeName, deps) {
|
|
|
106234
106377
|
return;
|
|
106235
106378
|
if (deps.cfg.linear.postComments === false)
|
|
106236
106379
|
return;
|
|
106237
|
-
|
|
106238
|
-
|
|
106239
|
-
if (await f2.exists()) {
|
|
106240
|
-
try {
|
|
106241
|
-
stateObj = await f2.json();
|
|
106242
|
-
} catch {
|
|
106243
|
-
stateObj = {};
|
|
106244
|
-
}
|
|
106245
|
-
}
|
|
106246
|
-
const confirmation = stateObj.confirmation ?? null;
|
|
106247
|
-
if (confirmation?.askedAt)
|
|
106380
|
+
const { confirmation } = await readConfirmationState(statePath);
|
|
106381
|
+
if (confirmation.askedAt)
|
|
106248
106382
|
return;
|
|
106249
106383
|
const approvalSentence = describeApprovalMarker(deps.cfg.linear.indicators.getApproved);
|
|
106250
106384
|
const handle = deps.cfg.linear.mentionHandle;
|
|
@@ -106255,16 +106389,8 @@ async function postPlanReadyCommentOnce(issue2, statePath, changeName, deps) {
|
|
|
106255
106389
|
deps.onLog(`! Linear plan-ready comment failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
106256
106390
|
return;
|
|
106257
106391
|
}
|
|
106258
|
-
const nextConfirmation = {
|
|
106259
|
-
askedAt: new Date().toISOString(),
|
|
106260
|
-
lastReminderAt: confirmation?.lastReminderAt ?? null,
|
|
106261
|
-
confirmedAt: confirmation?.confirmedAt ?? null,
|
|
106262
|
-
rounds: confirmation?.rounds ?? 0
|
|
106263
|
-
};
|
|
106264
106392
|
try {
|
|
106265
|
-
await
|
|
106266
|
-
await Bun.write(statePath, JSON.stringify({ ...stateObj, confirmation: nextConfirmation }, null, 2) + `
|
|
106267
|
-
`);
|
|
106393
|
+
await writeConfirmationState(statePath, {}, { ...confirmation, askedAt: new Date().toISOString() });
|
|
106268
106394
|
} catch (err) {
|
|
106269
106395
|
deps.onLog(`! could not persist confirmation.askedAt for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
106270
106396
|
}
|
|
@@ -106288,10 +106414,42 @@ async function applyAwaitingMarkerOnce(issue2, statePath, state, deps) {
|
|
|
106288
106414
|
deps.onLog(`! persist awaitingMarkerAppliedAt for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
106289
106415
|
}
|
|
106290
106416
|
}
|
|
106417
|
+
async function openDraftPrOnce(issue2, statePath, changeName, cwd2, state, deps) {
|
|
106418
|
+
if (deps.cfg.prDraft !== true)
|
|
106419
|
+
return;
|
|
106420
|
+
if (!deps.openDraftPr)
|
|
106421
|
+
return;
|
|
106422
|
+
if (state.confirmation.earlyDraftPrAt)
|
|
106423
|
+
return;
|
|
106424
|
+
let url2 = null;
|
|
106425
|
+
try {
|
|
106426
|
+
url2 = await deps.openDraftPr(issue2, changeName, cwd2);
|
|
106427
|
+
} catch (err) {
|
|
106428
|
+
deps.onLog(`! early draft PR open failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
106429
|
+
}
|
|
106430
|
+
state.confirmation.earlyDraftPrAt = new Date().toISOString();
|
|
106431
|
+
try {
|
|
106432
|
+
await writeConfirmationState(statePath, state.stateObj, state.confirmation);
|
|
106433
|
+
} catch (err) {
|
|
106434
|
+
deps.onLog(`! persist earlyDraftPrAt for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
106435
|
+
}
|
|
106436
|
+
if (url2)
|
|
106437
|
+
deps.onLog(` ${issue2.identifier}: opened draft PR for design \u2014 ${url2}`, "gray");
|
|
106438
|
+
}
|
|
106439
|
+
function issueInAwaitingStatus(issue2, indicators) {
|
|
106440
|
+
const set3 = indicators.setAwaitingConfirmation;
|
|
106441
|
+
if (!set3)
|
|
106442
|
+
return false;
|
|
106443
|
+
const current = issue2.state?.name;
|
|
106444
|
+
if (!current)
|
|
106445
|
+
return false;
|
|
106446
|
+
return markersOf(set3).some((m) => m.type === "status" && m.value === current);
|
|
106447
|
+
}
|
|
106291
106448
|
async function releaseAwaitingMarker(issue2, statePath, deps) {
|
|
106292
106449
|
const { stateObj, confirmation } = await readConfirmationState(statePath);
|
|
106293
|
-
if (!confirmation.awaitingMarkerAppliedAt)
|
|
106450
|
+
if (!confirmation.awaitingMarkerAppliedAt && !issueInAwaitingStatus(issue2, deps.indicators)) {
|
|
106294
106451
|
return;
|
|
106452
|
+
}
|
|
106295
106453
|
if (deps.indicators.clearAwaitingConfirmation) {
|
|
106296
106454
|
try {
|
|
106297
106455
|
await deps.applyIndicator(issue2, deps.indicators.clearAwaitingConfirmation);
|
|
@@ -106299,6 +106457,13 @@ async function releaseAwaitingMarker(issue2, statePath, deps) {
|
|
|
106299
106457
|
deps.onLog(`! clearAwaitingConfirmation failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
106300
106458
|
}
|
|
106301
106459
|
}
|
|
106460
|
+
if (deps.indicators.setInProgress) {
|
|
106461
|
+
try {
|
|
106462
|
+
await deps.applyIndicator(issue2, deps.indicators.setInProgress);
|
|
106463
|
+
} catch (err) {
|
|
106464
|
+
deps.onLog(`! restore setInProgress after awaiting release failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
106465
|
+
}
|
|
106466
|
+
}
|
|
106302
106467
|
confirmation.awaitingMarkerAppliedAt = null;
|
|
106303
106468
|
try {
|
|
106304
106469
|
await writeConfirmationState(statePath, stateObj, confirmation);
|
|
@@ -106327,9 +106492,9 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
106327
106492
|
const layout = projectLayout(cwd2);
|
|
106328
106493
|
const changeDir = layout.changeDir(changeName);
|
|
106329
106494
|
const statePath = layout.stateFile(changeName);
|
|
106330
|
-
const tasks2 = await readTextOrNull(
|
|
106331
|
-
const proposal = await readTextOrNull(
|
|
106332
|
-
const design = await readTextOrNull(
|
|
106495
|
+
const tasks2 = await readTextOrNull(join25(changeDir, "tasks.md"));
|
|
106496
|
+
const proposal = await readTextOrNull(join25(changeDir, "proposal.md"));
|
|
106497
|
+
const design = await readTextOrNull(join25(changeDir, "design.md"));
|
|
106333
106498
|
let commentsCache = null;
|
|
106334
106499
|
const getComments = async () => {
|
|
106335
106500
|
if (commentsCache)
|
|
@@ -106413,6 +106578,7 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
106413
106578
|
cfg,
|
|
106414
106579
|
onLog: deps.onLog
|
|
106415
106580
|
});
|
|
106581
|
+
await openDraftPrOnce(issue2, statePath, changeName, cwd2, { stateObj, confirmation }, { cfg, openDraftPr: deps.openDraftPr, onLog: deps.onLog });
|
|
106416
106582
|
const { stateObj: state2, confirmation: confirmation2 } = await readConfirmationState(statePath);
|
|
106417
106583
|
const { outcome, next } = await inspectAwaitingTicket(confirmation2, {
|
|
106418
106584
|
mentionHandle: cfg.linear.mentionHandle,
|
|
@@ -106487,6 +106653,7 @@ var init_awaiting = __esm(() => {
|
|
|
106487
106653
|
init_worktree();
|
|
106488
106654
|
init_scaffold();
|
|
106489
106655
|
init_linear();
|
|
106656
|
+
init_types2();
|
|
106490
106657
|
init_workflow();
|
|
106491
106658
|
init_state2();
|
|
106492
106659
|
init_inspect();
|
|
@@ -106561,9 +106728,26 @@ async function resolveDependencyBaseBranchImpl(issue2, runner, runnerCwd, deps)
|
|
|
106561
106728
|
}
|
|
106562
106729
|
return null;
|
|
106563
106730
|
}
|
|
106731
|
+
function createOpenDraftPr(deps) {
|
|
106732
|
+
const create3 = deps.createPr ?? createPullRequest;
|
|
106733
|
+
return async (issue2, changeName, cwd2) => {
|
|
106734
|
+
const branch = deps.branchByChange.get(changeName);
|
|
106735
|
+
if (!branch)
|
|
106736
|
+
return null;
|
|
106737
|
+
const base2 = baseBranchFromLabels(issue2.labels) ?? deps.prBaseBranch;
|
|
106738
|
+
const result2 = await create3({ cwd: cwd2, branch, issue: issue2, base: base2, draft: true }, deps.cmdRunner);
|
|
106739
|
+
const url2 = result2?.url ?? null;
|
|
106740
|
+
if (url2) {
|
|
106741
|
+
deps.prByChange.set(changeName, url2);
|
|
106742
|
+
deps.invalidatePrUrlForIssue(issue2.id);
|
|
106743
|
+
}
|
|
106744
|
+
return url2;
|
|
106745
|
+
};
|
|
106746
|
+
}
|
|
106564
106747
|
var GITHUB_PR_URL_RE, PR_NUMBER_RE, TICKET_IN_TITLE_RE;
|
|
106565
106748
|
var init_pr_helpers = __esm(() => {
|
|
106566
106749
|
init_linear();
|
|
106750
|
+
init_pr();
|
|
106567
106751
|
GITHUB_PR_URL_RE = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+/;
|
|
106568
106752
|
PR_NUMBER_RE = /\/pull\/(\d+)/;
|
|
106569
106753
|
TICKET_IN_TITLE_RE = /^([A-Za-z][A-Za-z0-9]*-\d+)\b/;
|
|
@@ -106853,8 +107037,8 @@ var init_linear_resolvers = __esm(() => {
|
|
|
106853
107037
|
});
|
|
106854
107038
|
|
|
106855
107039
|
// apps/agent/src/agent/wire/prepare.ts
|
|
106856
|
-
import { mkdir as
|
|
106857
|
-
import { join as
|
|
107040
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
107041
|
+
import { join as join26 } from "path";
|
|
106858
107042
|
function createPrepareHelpers(input) {
|
|
106859
107043
|
const {
|
|
106860
107044
|
args,
|
|
@@ -106918,7 +107102,7 @@ function createPrepareHelpers(input) {
|
|
|
106918
107102
|
let changeName;
|
|
106919
107103
|
const wtLayoutPre = projectLayout(workerCwd);
|
|
106920
107104
|
const derivedName = changeNameForIssue(issue2);
|
|
106921
|
-
const tasksMdPath =
|
|
107105
|
+
const tasksMdPath = join26(wtLayoutPre.changeDir(derivedName), "tasks.md");
|
|
106922
107106
|
const tasksMdExists = await Bun.file(tasksMdPath).exists();
|
|
106923
107107
|
const isFresh = !tasksMdExists;
|
|
106924
107108
|
if (isFresh) {
|
|
@@ -106957,8 +107141,8 @@ function createPrepareHelpers(input) {
|
|
|
106957
107141
|
changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue2, comments, appendPrompt, attachments);
|
|
106958
107142
|
} else {
|
|
106959
107143
|
changeName = derivedName;
|
|
106960
|
-
await
|
|
106961
|
-
await
|
|
107144
|
+
await mkdir8(wtLayoutPre.changeDir(changeName), { recursive: true });
|
|
107145
|
+
await mkdir8(wtLayoutPre.taskStateDir(changeName), { recursive: true });
|
|
106962
107146
|
}
|
|
106963
107147
|
maps.cwdByChange.set(changeName, workerCwd);
|
|
106964
107148
|
maps.statesDirByChange.set(changeName, scaffoldStatesDir);
|
|
@@ -106996,7 +107180,7 @@ function createPrepareHelpers(input) {
|
|
|
106996
107180
|
if (!workerCwd)
|
|
106997
107181
|
return;
|
|
106998
107182
|
const wtLayout = projectLayout(workerCwd);
|
|
106999
|
-
const tasksFile =
|
|
107183
|
+
const tasksFile = join26(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
|
|
107000
107184
|
if (trigger === "review") {
|
|
107001
107185
|
let body2;
|
|
107002
107186
|
let heading;
|
|
@@ -107289,21 +107473,24 @@ var init_pr_discovery = __esm(() => {
|
|
|
107289
107473
|
});
|
|
107290
107474
|
|
|
107291
107475
|
// apps/agent/src/features/review-followup/scan.ts
|
|
107292
|
-
import { dirname as
|
|
107476
|
+
import { dirname as dirname11, join as join27 } from "path";
|
|
107293
107477
|
async function resolveReviewStateDir(changeName, deps) {
|
|
107294
107478
|
const root = deps.cwdOf(changeName);
|
|
107295
107479
|
if (root)
|
|
107296
|
-
return
|
|
107480
|
+
return dirname11(projectLayout(root).stateFile(changeName));
|
|
107297
107481
|
if (!deps.useWorktree)
|
|
107298
|
-
return
|
|
107299
|
-
const wtPath =
|
|
107482
|
+
return dirname11(projectLayout(deps.projectRoot).stateFile(changeName));
|
|
107483
|
+
const wtPath = join27(worktreesDir2(deps.projectRoot), changeName);
|
|
107300
107484
|
const statePath = projectLayout(wtPath).stateFile(changeName);
|
|
107301
107485
|
if (await Bun.file(statePath).exists())
|
|
107302
|
-
return
|
|
107486
|
+
return dirname11(statePath);
|
|
107303
107487
|
return null;
|
|
107304
107488
|
}
|
|
107305
107489
|
async function readReviewWatermark(stateDir) {
|
|
107306
|
-
const
|
|
107490
|
+
const sidecar = await readSlotSidecar(stateDir, "review");
|
|
107491
|
+
if (sidecar)
|
|
107492
|
+
return sidecar.lastConsumedCommentAt ?? null;
|
|
107493
|
+
const file2 = Bun.file(join27(stateDir, ".ralph-state.json"));
|
|
107307
107494
|
if (!await file2.exists())
|
|
107308
107495
|
return null;
|
|
107309
107496
|
try {
|
|
@@ -107515,7 +107702,7 @@ var init_github = __esm(() => {
|
|
|
107515
107702
|
|
|
107516
107703
|
// apps/agent/src/agent/wire/mention-scan.ts
|
|
107517
107704
|
import { readdir as readdir2 } from "fs/promises";
|
|
107518
|
-
import { join as
|
|
107705
|
+
import { join as join28 } from "path";
|
|
107519
107706
|
function createMentionScanner(input) {
|
|
107520
107707
|
const {
|
|
107521
107708
|
apiKey,
|
|
@@ -107681,7 +107868,7 @@ function createMentionScanner(input) {
|
|
|
107681
107868
|
async function isChangeArchivedForIssue(issue2, cwdByChange, projectRoot) {
|
|
107682
107869
|
const changeName = changeNameForIssue(issue2);
|
|
107683
107870
|
const root = cwdByChange.get(changeName) ?? projectRoot;
|
|
107684
|
-
const archiveDir =
|
|
107871
|
+
const archiveDir = join28(projectLayout(root).tasksDir, "archive");
|
|
107685
107872
|
let entries;
|
|
107686
107873
|
try {
|
|
107687
107874
|
entries = await readdir2(archiveDir);
|
|
@@ -107705,9 +107892,9 @@ var init_mention_scan = __esm(() => {
|
|
|
107705
107892
|
});
|
|
107706
107893
|
|
|
107707
107894
|
// apps/agent/src/agent/wire/spawn/default.ts
|
|
107708
|
-
import { join as
|
|
107895
|
+
import { join as join29 } from "path";
|
|
107709
107896
|
function defaultSpawn(changeName, cmd, cwd2, logsDir, onWorkerOutput, note) {
|
|
107710
|
-
const logFilePath =
|
|
107897
|
+
const logFilePath = join29(logsDir, `${changeName}.log`);
|
|
107711
107898
|
const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
107712
107899
|
const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
107713
107900
|
const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
@@ -107767,8 +107954,190 @@ var init_default2 = __esm(() => {
|
|
|
107767
107954
|
init_log();
|
|
107768
107955
|
});
|
|
107769
107956
|
|
|
107957
|
+
// apps/agent/src/agent/state/agent-run-state.ts
|
|
107958
|
+
import { basename as basename3, join as join30 } from "path";
|
|
107959
|
+
import { homedir as homedir6 } from "os";
|
|
107960
|
+
import { mkdir as mkdir9, writeFile } from "fs/promises";
|
|
107961
|
+
function agentRunStatePath(projectRoot) {
|
|
107962
|
+
return join30(homedir6(), ".ralph", basename3(projectRoot), "agent-state.json");
|
|
107963
|
+
}
|
|
107964
|
+
async function writeAgentRunState(state) {
|
|
107965
|
+
const path = agentRunStatePath(state.projectRoot);
|
|
107966
|
+
try {
|
|
107967
|
+
await mkdir9(join30(homedir6(), ".ralph", basename3(state.projectRoot)), { recursive: true });
|
|
107968
|
+
await writeFile(path, JSON.stringify(state, null, 2) + `
|
|
107969
|
+
`, "utf-8");
|
|
107970
|
+
} catch {}
|
|
107971
|
+
}
|
|
107972
|
+
var init_agent_run_state = () => {};
|
|
107973
|
+
|
|
107974
|
+
// packages/retro/src/disposition.ts
|
|
107975
|
+
function dispositionFromExitCode(code) {
|
|
107976
|
+
switch (code) {
|
|
107977
|
+
case 0:
|
|
107978
|
+
return "done";
|
|
107979
|
+
case NO_CHANGES_EXIT2:
|
|
107980
|
+
return "no-changes";
|
|
107981
|
+
case CI_FAILED_EXIT2:
|
|
107982
|
+
return "ci-failed";
|
|
107983
|
+
case PR_FAILED_EXIT2:
|
|
107984
|
+
return "pr-failed";
|
|
107985
|
+
default:
|
|
107986
|
+
return "error";
|
|
107987
|
+
}
|
|
107988
|
+
}
|
|
107989
|
+
var CI_FAILED_EXIT2 = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
|
|
107990
|
+
|
|
107991
|
+
// packages/retro/src/paths.ts
|
|
107992
|
+
import { homedir as homedir7 } from "os";
|
|
107993
|
+
import { mkdir as mkdir10 } from "fs/promises";
|
|
107994
|
+
import { join as join31 } from "path";
|
|
107995
|
+
function retroDir() {
|
|
107996
|
+
return join31(homedir7(), ".ralph", "retro");
|
|
107997
|
+
}
|
|
107998
|
+
async function resolveRetroOutputPath(identifier, date5, dir = retroDir()) {
|
|
107999
|
+
await mkdir10(dir, { recursive: true });
|
|
108000
|
+
const base2 = join31(dir, `${identifier}-${date5}.md`);
|
|
108001
|
+
if (!await Bun.file(base2).exists())
|
|
108002
|
+
return base2;
|
|
108003
|
+
for (let n = 2;; n++) {
|
|
108004
|
+
const candidate = join31(dir, `${identifier}-${date5}-${n}.md`);
|
|
108005
|
+
if (!await Bun.file(candidate).exists())
|
|
108006
|
+
return candidate;
|
|
108007
|
+
}
|
|
108008
|
+
}
|
|
108009
|
+
var init_paths2 = () => {};
|
|
108010
|
+
|
|
108011
|
+
// packages/retro/src/prompt.ts
|
|
108012
|
+
function buildRetroPrompt(ctx, outputPath) {
|
|
108013
|
+
const disposition = dispositionFromExitCode(ctx.exitCode);
|
|
108014
|
+
const { paths } = ctx;
|
|
108015
|
+
const line = (label, value) => value ? `- ${label}: ${value}` : `- ${label}: (unavailable \u2014 note this in the report)`;
|
|
108016
|
+
return [
|
|
108017
|
+
`You are a retrospective analysis agent reviewing a finished automated ticket run.`,
|
|
108018
|
+
`Your job is to read the run's artifacts and write a thorough, honest self-review`,
|
|
108019
|
+
`to a markdown file. You are NOT fixing anything \u2014 this is analysis only.`,
|
|
108020
|
+
``,
|
|
108021
|
+
`## Ticket`,
|
|
108022
|
+
``,
|
|
108023
|
+
`- Identifier: ${ctx.identifier}`,
|
|
108024
|
+
`- Change name: ${ctx.changeName}`,
|
|
108025
|
+
`- Terminal disposition: ${disposition} (worker exit code ${ctx.exitCode})`,
|
|
108026
|
+
ctx.prUrl ? `- Pull request: ${ctx.prUrl}` : `- Pull request: none was opened`,
|
|
108027
|
+
`- Date: ${ctx.date}`,
|
|
108028
|
+
``,
|
|
108029
|
+
`### Ticket details`,
|
|
108030
|
+
``,
|
|
108031
|
+
ctx.ticketDigest,
|
|
108032
|
+
``,
|
|
108033
|
+
`## Data sources`,
|
|
108034
|
+
``,
|
|
108035
|
+
`Read whatever of the following exist. If a path is missing or empty, say so`,
|
|
108036
|
+
`explicitly in the report rather than guessing.`,
|
|
108037
|
+
``,
|
|
108038
|
+
line("Change directory (proposal/design/tasks/specs)", paths.changeDir),
|
|
108039
|
+
line("Loop state file", paths.stateFilePath),
|
|
108040
|
+
line("Worker log", paths.logFile),
|
|
108041
|
+
line("JSON event log", paths.jsonLogFile),
|
|
108042
|
+
line("Agent run state", paths.agentStateFile),
|
|
108043
|
+
ctx.prUrl ? `- You may inspect the PR read-only with \`gh pr view ${ctx.prUrl}\` and \`gh pr diff ${ctx.prUrl}\`.` : `- No PR exists; skip the PR section and note "no PR".`,
|
|
108044
|
+
``,
|
|
108045
|
+
`## Required report structure`,
|
|
108046
|
+
``,
|
|
108047
|
+
`Write GitHub-flavored markdown with these sections:`,
|
|
108048
|
+
`1. **Summary** \u2014 what the ticket asked for and how the run ended.`,
|
|
108049
|
+
`2. **What went well** \u2014 concrete things the run did right.`,
|
|
108050
|
+
`3. **What went wrong / friction** \u2014 failures, retries, wasted iterations,`,
|
|
108051
|
+
` wrong turns, anything that cost time or quality.`,
|
|
108052
|
+
`4. **Root-cause analysis** \u2014 for each problem, why it happened.`,
|
|
108053
|
+
`5. **Recommendations** \u2014 specific, actionable improvements (to the prompt,`,
|
|
108054
|
+
` the tasks, the codebase, or the workflow).`,
|
|
108055
|
+
`6. **Data gaps** \u2014 which data sources were unavailable or unread.`,
|
|
108056
|
+
``,
|
|
108057
|
+
`## Output`,
|
|
108058
|
+
``,
|
|
108059
|
+
`Write the complete report to this exact path using your file-write tool:`,
|
|
108060
|
+
``,
|
|
108061
|
+
` ${outputPath}`,
|
|
108062
|
+
``,
|
|
108063
|
+
`## Hard rules`,
|
|
108064
|
+
``,
|
|
108065
|
+
`- Do NOT run any git mutation: no commit, add, push, rebase, reset, checkout,`,
|
|
108066
|
+
` branch, merge, tag, or stash.`,
|
|
108067
|
+
`- Do NOT create, edit, comment on, close, or merge any pull request or issue.`,
|
|
108068
|
+
`- Do NOT modify any source file. The ONLY file you may write is the report at`,
|
|
108069
|
+
` the path above.`,
|
|
108070
|
+
`- Read-only inspection commands (\`git log\`, \`git diff\`, \`gh pr view\`,`,
|
|
108071
|
+
` \`gh pr diff\`, reading files) are allowed.`
|
|
108072
|
+
].join(`
|
|
108073
|
+
`);
|
|
108074
|
+
}
|
|
108075
|
+
var init_prompt = () => {};
|
|
108076
|
+
|
|
108077
|
+
// packages/retro/src/retro.ts
|
|
108078
|
+
async function runRetrospective(ctx, deps) {
|
|
108079
|
+
const { log: log3, runEngine: runEngine2, seen } = deps;
|
|
108080
|
+
const disposition = dispositionFromExitCode(ctx.exitCode);
|
|
108081
|
+
const key = `${ctx.identifier}:${disposition}:${ctx.date}`;
|
|
108082
|
+
if (seen.has(key)) {
|
|
108083
|
+
log3(` retrospective skipped for ${ctx.identifier} (already generated this run)`, "gray");
|
|
108084
|
+
return { written: false, skipped: "duplicate", disposition };
|
|
108085
|
+
}
|
|
108086
|
+
seen.add(key);
|
|
108087
|
+
try {
|
|
108088
|
+
const outputPath = await resolveRetroOutputPath(ctx.identifier, ctx.date);
|
|
108089
|
+
const prompt = buildRetroPrompt(ctx, outputPath);
|
|
108090
|
+
log3(` running retrospective for ${ctx.identifier} (${disposition}) \u2192 ${outputPath}`, "cyan");
|
|
108091
|
+
await runEngine2({
|
|
108092
|
+
engine: ctx.engine,
|
|
108093
|
+
model: ctx.model,
|
|
108094
|
+
prompt,
|
|
108095
|
+
cwd: ctx.cwd,
|
|
108096
|
+
onOutput: (l) => log3(l, "gray")
|
|
108097
|
+
});
|
|
108098
|
+
const written = await Bun.file(outputPath).exists();
|
|
108099
|
+
if (written) {
|
|
108100
|
+
log3(` retrospective written: ${outputPath}`, "green");
|
|
108101
|
+
} else {
|
|
108102
|
+
log3(`! retrospective engine finished but no report was written at ${outputPath}`, "yellow");
|
|
108103
|
+
}
|
|
108104
|
+
return { written, outputPath, disposition };
|
|
108105
|
+
} catch (err) {
|
|
108106
|
+
log3(`! retrospective failed for ${ctx.identifier}: ${err.message}`, "yellow");
|
|
108107
|
+
return { written: false, disposition };
|
|
108108
|
+
}
|
|
108109
|
+
}
|
|
108110
|
+
var init_retro = __esm(() => {
|
|
108111
|
+
init_paths2();
|
|
108112
|
+
init_prompt();
|
|
108113
|
+
init_paths2();
|
|
108114
|
+
init_prompt();
|
|
108115
|
+
});
|
|
108116
|
+
|
|
107770
108117
|
// apps/agent/src/agent/wire/spawn/worker.ts
|
|
107771
|
-
import { join as
|
|
108118
|
+
import { join as join32 } from "path";
|
|
108119
|
+
function localDateStamp(d) {
|
|
108120
|
+
const y = d.getFullYear();
|
|
108121
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
108122
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
108123
|
+
return `${y}-${m}-${day}`;
|
|
108124
|
+
}
|
|
108125
|
+
function buildTicketDigest(issue2, comments) {
|
|
108126
|
+
if (!issue2)
|
|
108127
|
+
return "(ticket details unavailable)";
|
|
108128
|
+
const lines = [`Title: ${issue2.title}`, "", issue2.description?.trim() || "(no description)"];
|
|
108129
|
+
if (comments.length > 0) {
|
|
108130
|
+
lines.push("", "Comments:");
|
|
108131
|
+
for (const c of comments) {
|
|
108132
|
+
lines.push(`- ${c.user?.name ?? "unknown"}: ${c.body}`);
|
|
108133
|
+
}
|
|
108134
|
+
}
|
|
108135
|
+
return lines.join(`
|
|
108136
|
+
`);
|
|
108137
|
+
}
|
|
108138
|
+
function retroDepEntry(agentDebug, hook) {
|
|
108139
|
+
return agentDebug ? { runRetrospective: hook } : {};
|
|
108140
|
+
}
|
|
107772
108141
|
function createSpawnWorker(input) {
|
|
107773
108142
|
const {
|
|
107774
108143
|
args,
|
|
@@ -107847,10 +108216,52 @@ function createSpawnWorker(input) {
|
|
|
107847
108216
|
c.push("--from-agent");
|
|
107848
108217
|
return c;
|
|
107849
108218
|
}
|
|
108219
|
+
const retroSeen = new Set;
|
|
108220
|
+
const runRetrospectiveHook = async (info) => {
|
|
108221
|
+
try {
|
|
108222
|
+
const identifier = info.issue?.identifier ?? info.changeName;
|
|
108223
|
+
const prUrl = prByChange?.get(info.changeName) ?? null;
|
|
108224
|
+
let digest = "(ticket details unavailable)";
|
|
108225
|
+
if (info.issue) {
|
|
108226
|
+
let comments = [];
|
|
108227
|
+
try {
|
|
108228
|
+
comments = await fetchIssueComments(apiKey, info.issue.id);
|
|
108229
|
+
} catch {}
|
|
108230
|
+
digest = buildTicketDigest(info.issue, comments);
|
|
108231
|
+
}
|
|
108232
|
+
const engine = args.engineSet ? args.engine : cfg.engine;
|
|
108233
|
+
const model = args.engineSet ? args.model : cfg.model;
|
|
108234
|
+
const ctx = {
|
|
108235
|
+
identifier,
|
|
108236
|
+
changeName: info.changeName,
|
|
108237
|
+
cwd: info.cwd,
|
|
108238
|
+
engine,
|
|
108239
|
+
model,
|
|
108240
|
+
exitCode: info.effectiveCode,
|
|
108241
|
+
prUrl,
|
|
108242
|
+
date: localDateStamp(new Date),
|
|
108243
|
+
ticketDigest: digest,
|
|
108244
|
+
paths: {
|
|
108245
|
+
changeDir: info.changeDir,
|
|
108246
|
+
stateFilePath: info.stateFilePath,
|
|
108247
|
+
logFile: join32(logsDir, `${info.changeName}.log`),
|
|
108248
|
+
jsonLogFile: args.jsonLogFile ?? null,
|
|
108249
|
+
agentStateFile: agentRunStatePath(projectRoot)
|
|
108250
|
+
}
|
|
108251
|
+
};
|
|
108252
|
+
await runRetrospective(ctx, {
|
|
108253
|
+
runEngine: (opts) => runEngine(opts),
|
|
108254
|
+
log: onLog,
|
|
108255
|
+
seen: retroSeen
|
|
108256
|
+
});
|
|
108257
|
+
} catch (err) {
|
|
108258
|
+
onLog(`! retrospective failed: ${err.message}`, "yellow");
|
|
108259
|
+
}
|
|
108260
|
+
};
|
|
107850
108261
|
return function spawnWorker(changeName, _issue, trigger) {
|
|
107851
108262
|
const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
|
|
107852
108263
|
const injected = runners?.spawnWorker;
|
|
107853
|
-
const missionTasksPath =
|
|
108264
|
+
const missionTasksPath = join32(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
|
|
107854
108265
|
const prevTasksPromise = (async () => {
|
|
107855
108266
|
const f2 = Bun.file(missionTasksPath);
|
|
107856
108267
|
return await f2.exists() ? await f2.text() : "";
|
|
@@ -107858,7 +108269,7 @@ function createSpawnWorker(input) {
|
|
|
107858
108269
|
let logFilePath;
|
|
107859
108270
|
let handle;
|
|
107860
108271
|
if (injected) {
|
|
107861
|
-
logFilePath =
|
|
108272
|
+
logFilePath = join32(logsDir, `${changeName}.log`);
|
|
107862
108273
|
handle = injected(buildTaskCmdFor(changeName), cwd2);
|
|
107863
108274
|
} else {
|
|
107864
108275
|
const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, logsDir, onWorkerOutput, `spawn at ${new Date().toISOString()}`);
|
|
@@ -107880,7 +108291,7 @@ function createSpawnWorker(input) {
|
|
|
107880
108291
|
const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
|
|
107881
108292
|
const wrapped = handle.exited.then(async (code) => {
|
|
107882
108293
|
const workerLayout = projectLayout(cwd2);
|
|
107883
|
-
const validateSpecPath =
|
|
108294
|
+
const validateSpecPath = join32(workerLayout.changeDir(changeName), "specs", "validate.md");
|
|
107884
108295
|
const hasValidateSpec = await Bun.file(validateSpecPath).exists();
|
|
107885
108296
|
const wantValidateOnly = hasValidateSpec && !wantPrBase;
|
|
107886
108297
|
if (hasValidateSpec) {
|
|
@@ -107965,6 +108376,7 @@ function createSpawnWorker(input) {
|
|
|
107965
108376
|
git: gitRunner,
|
|
107966
108377
|
log: onLog,
|
|
107967
108378
|
runScript,
|
|
108379
|
+
...retroDepEntry(args.agentDebug, runRetrospectiveHook),
|
|
107968
108380
|
registerPr: (cn, url2) => onPrRegistered(cn, url2),
|
|
107969
108381
|
...onWorkerPhase && {
|
|
107970
108382
|
onPhase: (phase2, detail) => onWorkerPhase(changeName, phase2, detail)
|
|
@@ -108000,6 +108412,9 @@ var init_worker = __esm(() => {
|
|
|
108000
108412
|
init_runners();
|
|
108001
108413
|
init_pr_helpers();
|
|
108002
108414
|
init_wait_for_mergeability();
|
|
108415
|
+
init_agent_run_state();
|
|
108416
|
+
init_retro();
|
|
108417
|
+
init_engine();
|
|
108003
108418
|
});
|
|
108004
108419
|
|
|
108005
108420
|
// apps/agent/src/agent/baseline/runner.ts
|
|
@@ -108342,44 +108757,33 @@ var init_linear_sync = __esm(() => {
|
|
|
108342
108757
|
});
|
|
108343
108758
|
|
|
108344
108759
|
// apps/agent/src/agent/linear-sync/comment-sync.ts
|
|
108345
|
-
import { dirname as
|
|
108346
|
-
|
|
108347
|
-
async function readStateJson(statePath) {
|
|
108760
|
+
import { dirname as dirname12, join as join33 } from "path";
|
|
108761
|
+
async function readInlineLinearComments(statePath) {
|
|
108348
108762
|
const file2 = Bun.file(statePath);
|
|
108349
108763
|
if (!await file2.exists())
|
|
108350
|
-
return
|
|
108764
|
+
return;
|
|
108351
108765
|
try {
|
|
108352
|
-
|
|
108766
|
+
const obj = await file2.json();
|
|
108767
|
+
return obj.linearComments ?? undefined;
|
|
108353
108768
|
} catch {
|
|
108354
|
-
return
|
|
108355
|
-
}
|
|
108356
|
-
}
|
|
108357
|
-
async function writeStateJson(statePath, state) {
|
|
108358
|
-
await mkdir11(dirname13(statePath), { recursive: true });
|
|
108359
|
-
const tmp = `${statePath}.tmp-${process.pid}-${writeStateSeq++}`;
|
|
108360
|
-
try {
|
|
108361
|
-
await Bun.write(tmp, JSON.stringify(state, null, 2) + `
|
|
108362
|
-
`);
|
|
108363
|
-
await rename(tmp, statePath);
|
|
108364
|
-
} catch (err) {
|
|
108365
|
-
await unlink2(tmp).catch(() => {});
|
|
108366
|
-
throw err;
|
|
108769
|
+
return;
|
|
108367
108770
|
}
|
|
108368
108771
|
}
|
|
108369
|
-
function readComments(
|
|
108370
|
-
const
|
|
108772
|
+
async function readComments(statePath) {
|
|
108773
|
+
const changeDir = dirname12(statePath);
|
|
108774
|
+
const raw = await readSlotSidecar(changeDir, "linearComments") ?? await readInlineLinearComments(statePath) ?? {};
|
|
108775
|
+
const r = raw;
|
|
108371
108776
|
return {
|
|
108372
|
-
planCommentId:
|
|
108373
|
-
tasksCommentId:
|
|
108374
|
-
planPostedAt:
|
|
108375
|
-
tasksCommentSha256:
|
|
108777
|
+
planCommentId: r.planCommentId ?? null,
|
|
108778
|
+
tasksCommentId: r.tasksCommentId ?? null,
|
|
108779
|
+
planPostedAt: r.planPostedAt ?? null,
|
|
108780
|
+
tasksCommentSha256: r.tasksCommentSha256 ?? null
|
|
108376
108781
|
};
|
|
108377
108782
|
}
|
|
108378
108783
|
async function patchComments(statePath, patch) {
|
|
108379
|
-
const
|
|
108380
|
-
const current = readComments(existing);
|
|
108784
|
+
const current = await readComments(statePath);
|
|
108381
108785
|
const next = { ...current, ...patch };
|
|
108382
|
-
await
|
|
108786
|
+
await writeSlotField(dirname12(statePath), "linearComments", next);
|
|
108383
108787
|
}
|
|
108384
108788
|
function isCommentNotFoundError(err) {
|
|
108385
108789
|
if (!err)
|
|
@@ -108394,7 +108798,7 @@ function isCommentNotFoundError(err) {
|
|
|
108394
108798
|
return text.includes("not found") || text.includes("could not find") || text.includes("entity not found");
|
|
108395
108799
|
}
|
|
108396
108800
|
async function readTasksMd(changeDir, log3) {
|
|
108397
|
-
const file2 = Bun.file(
|
|
108801
|
+
const file2 = Bun.file(join33(changeDir, "tasks.md"));
|
|
108398
108802
|
if (!await file2.exists()) {
|
|
108399
108803
|
log3(` comment-sync: tasks.md missing in ${changeDir}, skipping`, "gray");
|
|
108400
108804
|
return null;
|
|
@@ -108415,8 +108819,7 @@ async function postOrUpdateTasksComment(deps) {
|
|
|
108415
108819
|
return null;
|
|
108416
108820
|
const body = renderTasksCommentBody(tasksMd, deps.changeName, deps.iteration);
|
|
108417
108821
|
const hash2 = sha256Hex(tasksMd);
|
|
108418
|
-
const
|
|
108419
|
-
const comments = readComments(state);
|
|
108822
|
+
const comments = await readComments(deps.statePath);
|
|
108420
108823
|
if (comments.tasksCommentId) {
|
|
108421
108824
|
if (comments.tasksCommentSha256 === hash2) {
|
|
108422
108825
|
deps.log(` comment-sync: tasks.md unchanged for ${deps.changeName}, skipping`, "gray");
|
|
@@ -108492,8 +108895,7 @@ async function readSection(path, heading) {
|
|
|
108492
108895
|
return body.trim() || null;
|
|
108493
108896
|
}
|
|
108494
108897
|
async function postPlanCommentOnce(deps) {
|
|
108495
|
-
const
|
|
108496
|
-
const comments = readComments(state);
|
|
108898
|
+
const comments = await readComments(deps.statePath);
|
|
108497
108899
|
if (comments.planCommentId)
|
|
108498
108900
|
return null;
|
|
108499
108901
|
const tasksMd = await readTasksMd(deps.changeDir, deps.log);
|
|
@@ -108502,14 +108904,14 @@ async function postPlanCommentOnce(deps) {
|
|
|
108502
108904
|
const check2 = parsePlanningSection(tasksMd);
|
|
108503
108905
|
if (!check2.allChecked)
|
|
108504
108906
|
return null;
|
|
108505
|
-
const proposalPath =
|
|
108907
|
+
const proposalPath = join33(deps.changeDir, "proposal.md");
|
|
108506
108908
|
const why = await readSection(proposalPath, "Why");
|
|
108507
108909
|
const whatChanges = await readSection(proposalPath, "What Changes");
|
|
108508
108910
|
if (!why && !whatChanges) {
|
|
108509
108911
|
deps.log(` comment-sync: proposal.md has no Why/What Changes, skipping plan comment`, "gray");
|
|
108510
108912
|
return null;
|
|
108511
108913
|
}
|
|
108512
|
-
const designSummary = await readFirstParagraph(
|
|
108914
|
+
const designSummary = await readFirstParagraph(join33(deps.changeDir, "design.md"));
|
|
108513
108915
|
const parts = [`### ${PLAN_COMMENT_TITLE} \u2014 \`${deps.changeName}\``];
|
|
108514
108916
|
if (why) {
|
|
108515
108917
|
parts.push("", "**Why**", "", why);
|
|
@@ -108547,8 +108949,7 @@ ${deps.message.trim()}`;
|
|
|
108547
108949
|
} catch (err) {
|
|
108548
108950
|
deps.log(`! comment-sync: steering comment create failed: ${err.message}`, "yellow");
|
|
108549
108951
|
}
|
|
108550
|
-
const
|
|
108551
|
-
const comments = readComments(state);
|
|
108952
|
+
const comments = await readComments(deps.statePath);
|
|
108552
108953
|
if (comments.tasksCommentId) {
|
|
108553
108954
|
try {
|
|
108554
108955
|
await deps.mutations.deleteIssueComment(deps.apiKey, comments.tasksCommentId);
|
|
@@ -108571,8 +108972,9 @@ ${deps.message.trim()}`;
|
|
|
108571
108972
|
iteration: deps.iteration
|
|
108572
108973
|
});
|
|
108573
108974
|
}
|
|
108574
|
-
var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering"
|
|
108975
|
+
var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering";
|
|
108575
108976
|
var init_comment_sync = __esm(() => {
|
|
108977
|
+
init_store();
|
|
108576
108978
|
init_linear_sync();
|
|
108577
108979
|
});
|
|
108578
108980
|
|
|
@@ -260785,7 +261187,7 @@ var init_render_pdf = __esm(() => {
|
|
|
260785
261187
|
});
|
|
260786
261188
|
|
|
260787
261189
|
// apps/agent/src/agent/linear-sync/spec-attachments.ts
|
|
260788
|
-
import { dirname as
|
|
261190
|
+
import { dirname as dirname13, join as join34 } from "path";
|
|
260789
261191
|
function describeLinearError(err) {
|
|
260790
261192
|
const e = err;
|
|
260791
261193
|
const parts = [e.message ?? String(err)];
|
|
@@ -260800,25 +261202,29 @@ function describeLinearError(err) {
|
|
|
260800
261202
|
return parts.join(" ");
|
|
260801
261203
|
}
|
|
260802
261204
|
function stateDirOf(statePath) {
|
|
260803
|
-
return
|
|
261205
|
+
return dirname13(statePath);
|
|
260804
261206
|
}
|
|
260805
|
-
async function
|
|
261207
|
+
async function readInlineSpecAttachments(statePath) {
|
|
260806
261208
|
const file2 = Bun.file(statePath);
|
|
260807
261209
|
if (!await file2.exists())
|
|
260808
261210
|
return {};
|
|
260809
261211
|
try {
|
|
260810
261212
|
const parsed = await file2.json();
|
|
260811
261213
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
260812
|
-
|
|
261214
|
+
const sa = parsed.specAttachments;
|
|
261215
|
+
return sa && typeof sa === "object" && !Array.isArray(sa) ? sa : {};
|
|
260813
261216
|
}
|
|
260814
261217
|
return {};
|
|
260815
261218
|
} catch {
|
|
260816
261219
|
return {};
|
|
260817
261220
|
}
|
|
260818
261221
|
}
|
|
261222
|
+
async function readSpecAttachmentsSubtree(statePath) {
|
|
261223
|
+
const sidecar = await readSlotSidecar(dirname13(statePath), "specAttachments");
|
|
261224
|
+
return sidecar ?? await readInlineSpecAttachments(statePath);
|
|
261225
|
+
}
|
|
260819
261226
|
async function readSpecAttachments(statePath) {
|
|
260820
|
-
const
|
|
260821
|
-
const sa = raw.specAttachments ?? {};
|
|
261227
|
+
const sa = await readSpecAttachmentsSubtree(statePath);
|
|
260822
261228
|
return {
|
|
260823
261229
|
proposal: {
|
|
260824
261230
|
attachmentId: sa.proposal?.attachmentId ?? null,
|
|
@@ -260872,12 +261278,25 @@ function hasMeaningfulContent(bytes) {
|
|
|
260872
261278
|
}
|
|
260873
261279
|
return false;
|
|
260874
261280
|
}
|
|
261281
|
+
function extractImplementationSection(tasksMarkdown) {
|
|
261282
|
+
const captured = [];
|
|
261283
|
+
let capturing = false;
|
|
261284
|
+
for (const line of tasksMarkdown.split(/\r?\n/)) {
|
|
261285
|
+
const heading = /^##\s+(.+?)\s*$/.exec(line)?.[1];
|
|
261286
|
+
if (heading !== undefined)
|
|
261287
|
+
capturing = heading.trim().toLowerCase() === "implementation";
|
|
261288
|
+
if (capturing)
|
|
261289
|
+
captured.push(line);
|
|
261290
|
+
}
|
|
261291
|
+
return captured.join(`
|
|
261292
|
+
`).trim();
|
|
261293
|
+
}
|
|
260875
261294
|
async function syncSlot(deps, slot) {
|
|
260876
261295
|
const spec = SLOT_SPECS[slot];
|
|
260877
261296
|
const [primaryName, ...trailingNames] = spec.sourceFiles;
|
|
260878
261297
|
if (!primaryName)
|
|
260879
261298
|
return;
|
|
260880
|
-
const primary = Bun.file(
|
|
261299
|
+
const primary = Bun.file(join34(deps.changeDir, primaryName));
|
|
260881
261300
|
if (!await primary.exists()) {
|
|
260882
261301
|
deps.log(` spec-attachments: ${primaryName} missing, skipping`, "gray");
|
|
260883
261302
|
return;
|
|
@@ -260896,24 +261315,28 @@ async function syncSlot(deps, slot) {
|
|
|
260896
261315
|
const parts = [primaryBytes];
|
|
260897
261316
|
const enc = new TextEncoder;
|
|
260898
261317
|
for (const name of trailingNames) {
|
|
260899
|
-
const f2 = Bun.file(
|
|
261318
|
+
const f2 = Bun.file(join34(deps.changeDir, name));
|
|
260900
261319
|
if (!await f2.exists())
|
|
260901
261320
|
continue;
|
|
261321
|
+
let raw;
|
|
260902
261322
|
try {
|
|
260903
|
-
|
|
260904
|
-
|
|
260905
|
-
|
|
260906
|
-
|
|
261323
|
+
raw = await f2.bytes();
|
|
261324
|
+
} catch (err) {
|
|
261325
|
+
deps.log(`! spec-attachments: read ${name} failed (continuing without it): ${err.message}`, "yellow");
|
|
261326
|
+
continue;
|
|
261327
|
+
}
|
|
261328
|
+
if (raw.length === 0)
|
|
261329
|
+
continue;
|
|
261330
|
+
const decoded = new TextDecoder().decode(raw);
|
|
261331
|
+
const body = name === "tasks.md" ? extractImplementationSection(decoded) : decoded.trim();
|
|
261332
|
+
if (!body)
|
|
261333
|
+
continue;
|
|
261334
|
+
parts.push(enc.encode(`
|
|
260907
261335
|
|
|
260908
261336
|
---
|
|
260909
261337
|
|
|
260910
|
-
|
|
260911
|
-
|
|
261338
|
+
${body}
|
|
260912
261339
|
`));
|
|
260913
|
-
parts.push(bytes);
|
|
260914
|
-
} catch (err) {
|
|
260915
|
-
deps.log(`! spec-attachments: read ${name} failed (continuing without it): ${err.message}`, "yellow");
|
|
260916
|
-
}
|
|
260917
261340
|
}
|
|
260918
261341
|
const totalLen = parts.reduce((n, p) => n + p.length, 0);
|
|
260919
261342
|
const sourceBytes = new Uint8Array(totalLen);
|
|
@@ -260982,8 +261405,7 @@ async function syncSlot(deps, slot) {
|
|
|
260982
261405
|
deps.log(` spec-attachments: created ${spec.uploadFilename} attachment`, "gray");
|
|
260983
261406
|
}
|
|
260984
261407
|
async function purgeLegacyProposalSlots(deps) {
|
|
260985
|
-
const
|
|
260986
|
-
const sa = raw.specAttachments ?? {};
|
|
261408
|
+
const sa = await readSpecAttachmentsSubtree(deps.statePath);
|
|
260987
261409
|
if (sa.legacyProposalPurged === true)
|
|
260988
261410
|
return;
|
|
260989
261411
|
const state = await readSpecAttachments(deps.statePath);
|
|
@@ -261161,9 +261583,9 @@ var init_comment_sync2 = __esm(() => {
|
|
|
261161
261583
|
});
|
|
261162
261584
|
|
|
261163
261585
|
// apps/agent/src/features/pr-tracker/state.ts
|
|
261164
|
-
import { join as
|
|
261586
|
+
import { join as join35 } from "path";
|
|
261165
261587
|
async function readState2(projectRoot) {
|
|
261166
|
-
const path =
|
|
261588
|
+
const path = join35(projectRoot, PR_TRACKER_STATE_RELPATH);
|
|
261167
261589
|
const file2 = Bun.file(path);
|
|
261168
261590
|
if (!await file2.exists())
|
|
261169
261591
|
return {};
|
|
@@ -261179,7 +261601,7 @@ async function readState2(projectRoot) {
|
|
|
261179
261601
|
}
|
|
261180
261602
|
}
|
|
261181
261603
|
async function writeState2(projectRoot, state) {
|
|
261182
|
-
const path =
|
|
261604
|
+
const path = join35(projectRoot, PR_TRACKER_STATE_RELPATH);
|
|
261183
261605
|
await Bun.write(path, JSON.stringify(state, null, 2));
|
|
261184
261606
|
}
|
|
261185
261607
|
var PR_TRACKER_STATE_RELPATH = ".ralph/pr-tracker-state.json";
|
|
@@ -261258,7 +261680,7 @@ var init_pr_tracker = __esm(() => {
|
|
|
261258
261680
|
});
|
|
261259
261681
|
|
|
261260
261682
|
// apps/agent/src/agent/wire.ts
|
|
261261
|
-
import { join as
|
|
261683
|
+
import { join as join36 } from "path";
|
|
261262
261684
|
function buildAgentCoordinator(input) {
|
|
261263
261685
|
const {
|
|
261264
261686
|
args,
|
|
@@ -261277,7 +261699,7 @@ function buildAgentCoordinator(input) {
|
|
|
261277
261699
|
onWorkerCmd,
|
|
261278
261700
|
onAwaitingTicket
|
|
261279
261701
|
} = input;
|
|
261280
|
-
const logsDir =
|
|
261702
|
+
const logsDir = join36(projectRoot, ".ralph", "logs");
|
|
261281
261703
|
const bus = createBus();
|
|
261282
261704
|
subscribeAgentDiag(bus, onLog);
|
|
261283
261705
|
const diag = (area, message, color) => {
|
|
@@ -261412,6 +261834,13 @@ function buildAgentCoordinator(input) {
|
|
|
261412
261834
|
...onWorkerOutput ? { onWorkerOutput } : {},
|
|
261413
261835
|
...onWorkerCmd ? { onWorkerCmd } : {}
|
|
261414
261836
|
});
|
|
261837
|
+
const openDraftPr = createOpenDraftPr({
|
|
261838
|
+
branchByChange,
|
|
261839
|
+
prByChange,
|
|
261840
|
+
cmdRunner,
|
|
261841
|
+
prBaseBranch: cfg.prBaseBranch,
|
|
261842
|
+
invalidatePrUrlForIssue: (issueId) => prDiscovery.invalidatePrUrlForIssue(issueId)
|
|
261843
|
+
});
|
|
261415
261844
|
const confirmationCaps = {
|
|
261416
261845
|
detect: (issue2) => processAwaitingForIssue(issue2, {
|
|
261417
261846
|
cfg,
|
|
@@ -261424,6 +261853,7 @@ function buildAgentCoordinator(input) {
|
|
|
261424
261853
|
reapForAwaiting: (cn) => coordRef.current?.reapForAwaiting(cn),
|
|
261425
261854
|
applyIndicator: resolvers.applyIndicator,
|
|
261426
261855
|
applyMarker: resolvers.applyMarker,
|
|
261856
|
+
openDraftPr,
|
|
261427
261857
|
...onAwaitingTicket ? { onAwaitingTicket } : {},
|
|
261428
261858
|
onLog
|
|
261429
261859
|
}),
|
|
@@ -261493,6 +261923,18 @@ function buildAgentCoordinator(input) {
|
|
|
261493
261923
|
const json2 = await file2.json();
|
|
261494
261924
|
return json2.iteration ?? 0;
|
|
261495
261925
|
},
|
|
261926
|
+
getTasksFingerprint: async (changeName) => {
|
|
261927
|
+
const root = cwdByChange.get(changeName) ?? projectRoot;
|
|
261928
|
+
const changeDir = projectLayout(root).changeDir(changeName);
|
|
261929
|
+
const parts = [];
|
|
261930
|
+
for (const name of ["tasks.md", "proposal.md", "design.md"]) {
|
|
261931
|
+
const file2 = Bun.file(join36(changeDir, name));
|
|
261932
|
+
if (!await file2.exists())
|
|
261933
|
+
continue;
|
|
261934
|
+
parts.push(`${name}:${file2.lastModified}:${file2.size}`);
|
|
261935
|
+
}
|
|
261936
|
+
return parts.length > 0 ? parts.join("|") : null;
|
|
261937
|
+
},
|
|
261496
261938
|
...commentSync.enabled && commentSync.syncTasks ? { syncTasks: commentSync.syncTasks } : {},
|
|
261497
261939
|
...commentSync.enabled && commentSync.onSteeringAppended ? { onSteeringAppended: commentSync.onSteeringAppended } : {}
|
|
261498
261940
|
}, {
|
|
@@ -261531,7 +261973,7 @@ function buildAgentCoordinator(input) {
|
|
|
261531
261973
|
getGaveUpTotal: async () => {
|
|
261532
261974
|
let total = 0;
|
|
261533
261975
|
for (const [changeName, root] of cwdByChange) {
|
|
261534
|
-
const file2 = Bun.file(
|
|
261976
|
+
const file2 = Bun.file(join36(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
|
|
261535
261977
|
if (!await file2.exists())
|
|
261536
261978
|
continue;
|
|
261537
261979
|
try {
|
|
@@ -261566,14 +262008,14 @@ var init_wire = __esm(() => {
|
|
|
261566
262008
|
});
|
|
261567
262009
|
|
|
261568
262010
|
// apps/agent/src/agent/json-log/json-log-file.ts
|
|
261569
|
-
import { mkdir as
|
|
261570
|
-
import { dirname as
|
|
262011
|
+
import { mkdir as mkdir11, appendFile as appendFile2 } from "fs/promises";
|
|
262012
|
+
import { dirname as dirname14 } from "path";
|
|
261571
262013
|
function createJsonLogFileSink(path) {
|
|
261572
262014
|
if (!path)
|
|
261573
262015
|
return { emit: () => {} };
|
|
261574
262016
|
let chain = (async () => {
|
|
261575
262017
|
try {
|
|
261576
|
-
await
|
|
262018
|
+
await mkdir11(dirname14(path), { recursive: true });
|
|
261577
262019
|
await Bun.write(path, "");
|
|
261578
262020
|
} catch {}
|
|
261579
262021
|
})();
|
|
@@ -261819,7 +262261,7 @@ var init_output_utils = __esm(() => {
|
|
|
261819
262261
|
});
|
|
261820
262262
|
|
|
261821
262263
|
// apps/agent/src/agent/state/worker-state-poll.ts
|
|
261822
|
-
import { join as
|
|
262264
|
+
import { join as join37 } from "path";
|
|
261823
262265
|
function parseSubtasks(tasksMd) {
|
|
261824
262266
|
const out = [];
|
|
261825
262267
|
let skipSection = false;
|
|
@@ -261852,7 +262294,7 @@ function initialWorkerSnapshot() {
|
|
|
261852
262294
|
async function readWorkerSnapshot(input) {
|
|
261853
262295
|
const next = { ...input.prev };
|
|
261854
262296
|
try {
|
|
261855
|
-
const file2 = Bun.file(
|
|
262297
|
+
const file2 = Bun.file(join37(input.statesDir, input.changeName, ".ralph-state.json"));
|
|
261856
262298
|
if (await file2.exists()) {
|
|
261857
262299
|
const json2 = await file2.json();
|
|
261858
262300
|
next.iter = json2.iteration ?? next.iter;
|
|
@@ -261861,10 +262303,10 @@ async function readWorkerSnapshot(input) {
|
|
|
261861
262303
|
} catch {}
|
|
261862
262304
|
if (input.changeDir) {
|
|
261863
262305
|
try {
|
|
261864
|
-
const tasksFile = Bun.file(
|
|
261865
|
-
const proposalFile = Bun.file(
|
|
261866
|
-
const designFile = Bun.file(
|
|
261867
|
-
const reviewFindingsFile = Bun.file(
|
|
262306
|
+
const tasksFile = Bun.file(join37(input.changeDir, "tasks.md"));
|
|
262307
|
+
const proposalFile = Bun.file(join37(input.changeDir, "proposal.md"));
|
|
262308
|
+
const designFile = Bun.file(join37(input.changeDir, "design.md"));
|
|
262309
|
+
const reviewFindingsFile = Bun.file(join37(input.changeDir, "review-findings.md"));
|
|
261868
262310
|
const [tasksText, proposalText, designText, reviewFindingsText] = await Promise.all([
|
|
261869
262311
|
tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
|
|
261870
262312
|
proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
|
|
@@ -261927,7 +262369,7 @@ var init_worker_state_poll = __esm(() => {
|
|
|
261927
262369
|
});
|
|
261928
262370
|
|
|
261929
262371
|
// apps/agent/src/components/AgentMode.tsx
|
|
261930
|
-
import { join as
|
|
262372
|
+
import { join as join38 } from "path";
|
|
261931
262373
|
async function appendSteeringImpl(changeDir, message) {
|
|
261932
262374
|
await runWithContext(createDefaultContext(), async () => {
|
|
261933
262375
|
appendSteeringMessage(changeDir, message);
|
|
@@ -263500,7 +263942,7 @@ function AgentMode({
|
|
|
263500
263942
|
},
|
|
263501
263943
|
onSubmit: async (message) => {
|
|
263502
263944
|
try {
|
|
263503
|
-
await appendSteering2(
|
|
263945
|
+
await appendSteering2(join38(tasksDir, w2.changeName), message);
|
|
263504
263946
|
fileEmit({ type: "steering_submitted", changeName: w2.changeName, message });
|
|
263505
263947
|
} catch (err) {
|
|
263506
263948
|
const text = err.message;
|
|
@@ -263614,7 +264056,7 @@ function shouldFallbackToJsonOutput(args, stdinIsTty) {
|
|
|
263614
264056
|
}
|
|
263615
264057
|
|
|
263616
264058
|
// apps/agent/src/runtime/tmux.ts
|
|
263617
|
-
import { basename as
|
|
264059
|
+
import { basename as basename4 } from "path";
|
|
263618
264060
|
function tmuxAvailable() {
|
|
263619
264061
|
const result2 = Bun.spawnSync({ cmd: ["tmux", "-V"], stderr: "pipe" });
|
|
263620
264062
|
return result2.exitCode === 0;
|
|
@@ -263623,7 +264065,7 @@ function sessionName(projectRoot) {
|
|
|
263623
264065
|
const override = process.env["RALPH_SESSION_NAME"];
|
|
263624
264066
|
if (override)
|
|
263625
264067
|
return override;
|
|
263626
|
-
return `ralphy-agent-${
|
|
264068
|
+
return `ralphy-agent-${basename4(projectRoot)}`;
|
|
263627
264069
|
}
|
|
263628
264070
|
function sessionExists(name) {
|
|
263629
264071
|
const result2 = Bun.spawnSync({ cmd: ["tmux", "has-session", "-t", name], stderr: "pipe" });
|
|
@@ -263804,7 +264246,7 @@ __export(exports_list, {
|
|
|
263804
264246
|
buildBuckets: () => buildBuckets,
|
|
263805
264247
|
backlogRankByIssueId: () => backlogRankByIssueId
|
|
263806
264248
|
});
|
|
263807
|
-
import { join as
|
|
264249
|
+
import { join as join39 } from "path";
|
|
263808
264250
|
function countTaskItems(content) {
|
|
263809
264251
|
const checked = (content.match(/^- \[x\]/gm) ?? []).length;
|
|
263810
264252
|
const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
@@ -263820,13 +264262,13 @@ function buildLocalRows() {
|
|
|
263820
264262
|
const sources = [{ dir: statesDir, label: "main" }];
|
|
263821
264263
|
const worktreesRoot = worktreesDir2(projectRoot);
|
|
263822
264264
|
for (const wt of storage.list(worktreesRoot)) {
|
|
263823
|
-
sources.push({ dir:
|
|
264265
|
+
sources.push({ dir: join39(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
|
|
263824
264266
|
}
|
|
263825
264267
|
for (const { dir, label } of sources) {
|
|
263826
264268
|
for (const entry of storage.list(dir)) {
|
|
263827
264269
|
if (seen.has(entry))
|
|
263828
264270
|
continue;
|
|
263829
|
-
const raw = storage.read(
|
|
264271
|
+
const raw = storage.read(join39(dir, entry, ".ralph-state.json"));
|
|
263830
264272
|
if (raw === null)
|
|
263831
264273
|
continue;
|
|
263832
264274
|
let state;
|
|
@@ -263841,7 +264283,7 @@ function buildLocalRows() {
|
|
|
263841
264283
|
const firstLine = promptRaw.split(`
|
|
263842
264284
|
`).find((l3) => l3.trim() !== "") ?? "";
|
|
263843
264285
|
let progress = "\u2014";
|
|
263844
|
-
const tasksContent = storage.read(
|
|
264286
|
+
const tasksContent = storage.read(join39(dir, entry, "tasks.md"));
|
|
263845
264287
|
if (tasksContent !== null) {
|
|
263846
264288
|
const { checked, unchecked } = countTaskItems(tasksContent);
|
|
263847
264289
|
const total = checked + unchecked;
|
|
@@ -264336,31 +264778,14 @@ var init_list = __esm(() => {
|
|
|
264336
264778
|
};
|
|
264337
264779
|
});
|
|
264338
264780
|
|
|
264339
|
-
// apps/agent/src/agent/state/agent-run-state.ts
|
|
264340
|
-
import { basename as basename4, join as join37 } from "path";
|
|
264341
|
-
import { homedir as homedir6 } from "os";
|
|
264342
|
-
import { mkdir as mkdir13, writeFile } from "fs/promises";
|
|
264343
|
-
function agentRunStatePath(projectRoot) {
|
|
264344
|
-
return join37(homedir6(), ".ralph", basename4(projectRoot), "agent-state.json");
|
|
264345
|
-
}
|
|
264346
|
-
async function writeAgentRunState(state) {
|
|
264347
|
-
const path = agentRunStatePath(state.projectRoot);
|
|
264348
|
-
try {
|
|
264349
|
-
await mkdir13(join37(homedir6(), ".ralph", basename4(state.projectRoot)), { recursive: true });
|
|
264350
|
-
await writeFile(path, JSON.stringify(state, null, 2) + `
|
|
264351
|
-
`, "utf-8");
|
|
264352
|
-
} catch {}
|
|
264353
|
-
}
|
|
264354
|
-
var init_agent_run_state = () => {};
|
|
264355
|
-
|
|
264356
264781
|
// apps/agent/src/agent/json-runner.ts
|
|
264357
264782
|
var exports_json_runner = {};
|
|
264358
264783
|
__export(exports_json_runner, {
|
|
264359
264784
|
runAgentJson: () => runAgentJson
|
|
264360
264785
|
});
|
|
264361
|
-
import { join as
|
|
264362
|
-
import { mkdir as
|
|
264363
|
-
import { homedir as
|
|
264786
|
+
import { join as join40 } from "path";
|
|
264787
|
+
import { mkdir as mkdir12 } from "fs/promises";
|
|
264788
|
+
import { homedir as homedir8 } from "os";
|
|
264364
264789
|
function makeEmit(fileSink) {
|
|
264365
264790
|
return (event) => {
|
|
264366
264791
|
const payload = { ts: Date.now(), ...event };
|
|
@@ -264380,7 +264805,7 @@ async function runAgentJson({
|
|
|
264380
264805
|
tasksDir,
|
|
264381
264806
|
runPreflight: runPreflight2 = runPreflight
|
|
264382
264807
|
}) {
|
|
264383
|
-
await
|
|
264808
|
+
await mkdir12(join40(homedir8(), ".ralph"), { recursive: true }).catch(() => {
|
|
264384
264809
|
return;
|
|
264385
264810
|
});
|
|
264386
264811
|
const fileSink = createJsonLogFileSink(args.jsonLogFile);
|
|
@@ -264596,8 +265021,8 @@ var exports_src3 = {};
|
|
|
264596
265021
|
__export(exports_src3, {
|
|
264597
265022
|
main: () => main3
|
|
264598
265023
|
});
|
|
264599
|
-
import { mkdir as
|
|
264600
|
-
import { join as
|
|
265024
|
+
import { mkdir as mkdir13 } from "fs/promises";
|
|
265025
|
+
import { join as join41 } from "path";
|
|
264601
265026
|
async function main3(argv) {
|
|
264602
265027
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
264603
265028
|
printAgentHelp();
|
|
@@ -264661,9 +265086,9 @@ async function main3(argv) {
|
|
|
264661
265086
|
return 1;
|
|
264662
265087
|
}
|
|
264663
265088
|
}
|
|
264664
|
-
await
|
|
264665
|
-
await
|
|
264666
|
-
await
|
|
265089
|
+
await mkdir13(statesDir, { recursive: true });
|
|
265090
|
+
await mkdir13(tasksDir, { recursive: true });
|
|
265091
|
+
await mkdir13(join41(projectRoot, ".ralph"), { recursive: true });
|
|
264667
265092
|
if (shouldFallbackToJsonOutput(args, process.stdin.isTTY)) {
|
|
264668
265093
|
process.stderr.write(`agent: stdin is not a TTY \u2014 falling back to --json-output mode.
|
|
264669
265094
|
`);
|