@neriros/ralphy 2.16.4 → 2.16.6
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/README.md +16 -1
- package/dist/cli/index.js +202 -140
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,9 +231,24 @@ Each worker card shows:
|
|
|
231
231
|
- `▶ TASK` — first unchecked task from `tasks.md`, updated every second
|
|
232
232
|
- `PHASE` with color (cyan = working, yellow = git ops, blue = CI, green = done, red = gave-up) + time in phase
|
|
233
233
|
- `⏵ CMD` when a shell command is in flight (shows the command and how long it's been running)
|
|
234
|
-
- `LOG` — path to the worker's log file for `tail -f`
|
|
234
|
+
- `LOG` — path to the worker's log file for `tail -f` (format: `[ISO] [type] message`)
|
|
235
235
|
- `─ OUTPUT ─` section with live stdout/stderr (scales to fill remaining terminal height for the focused worker)
|
|
236
236
|
|
|
237
|
+
### Log files
|
|
238
|
+
|
|
239
|
+
All log entries use the format `[2024-01-15T12:00:00.000Z] [type] message`. Four log types:
|
|
240
|
+
|
|
241
|
+
| Type | Meaning | Destination |
|
|
242
|
+
| --------- | -------------------------------------------------- | -------------------------------------------------- |
|
|
243
|
+
| `session` | Worker start / stop boundaries | `agent-mode.log` + worker log |
|
|
244
|
+
| `phase` | Phase transitions (working → pushing → ci-poll …) | `agent-mode.log` + worker log |
|
|
245
|
+
| `coord` | Coordinator events (Linear poll, worktree, labels) | `agent-mode.log` + worker log (when task-specific) |
|
|
246
|
+
| `output` | Raw subprocess stdout/stderr lines | Worker log only |
|
|
247
|
+
|
|
248
|
+
- **`~/.ralph/agent-mode.log`** — global session log, appended each agent run
|
|
249
|
+
- **`<projectRoot>/.ralph/logs/<change>.log`** — per-worker unified log; includes output, phases, and coordinator events for that task. `tail -f` this for live progress.
|
|
250
|
+
- **`<taskDir>/LOG.jsonl`** — structured JSON event log for the web UI (each line has a `ts` field)
|
|
251
|
+
|
|
237
252
|
## OpenSpec Flow
|
|
238
253
|
|
|
239
254
|
There are no phases. One loop, one prompt, one `tasks.md` checklist.
|
package/dist/cli/index.js
CHANGED
|
@@ -35029,8 +35029,8 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
35029
35029
|
import { resolve } from "path";
|
|
35030
35030
|
function getVersion() {
|
|
35031
35031
|
try {
|
|
35032
|
-
if ("2.16.
|
|
35033
|
-
return "2.16.
|
|
35032
|
+
if ("2.16.6")
|
|
35033
|
+
return "2.16.6";
|
|
35034
35034
|
} catch {}
|
|
35035
35035
|
const dirsToTry = [];
|
|
35036
35036
|
try {
|
|
@@ -59616,19 +59616,73 @@ var init_config = __esm(() => {
|
|
|
59616
59616
|
});
|
|
59617
59617
|
});
|
|
59618
59618
|
|
|
59619
|
+
// packages/log/src/log.ts
|
|
59620
|
+
import { appendFile } from "fs/promises";
|
|
59621
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
59622
|
+
import { homedir as homedir2 } from "os";
|
|
59623
|
+
import { mkdir as mkdir2 } from "fs/promises";
|
|
59624
|
+
function fmt(type, text) {
|
|
59625
|
+
return `[${new Date().toISOString()}] [${type}] ${text}
|
|
59626
|
+
`;
|
|
59627
|
+
}
|
|
59628
|
+
function write(path, line) {
|
|
59629
|
+
appendFile(path, line).catch(() => {
|
|
59630
|
+
return;
|
|
59631
|
+
});
|
|
59632
|
+
}
|
|
59633
|
+
function logSession(text, workerLogFile) {
|
|
59634
|
+
const clean = text.replace(ANSI_RE, "").trim();
|
|
59635
|
+
if (!clean)
|
|
59636
|
+
return;
|
|
59637
|
+
const line = fmt("session", clean);
|
|
59638
|
+
write(AGENT_LOG_PATH, line);
|
|
59639
|
+
if (workerLogFile)
|
|
59640
|
+
write(workerLogFile, line);
|
|
59641
|
+
}
|
|
59642
|
+
function logCoord(text, workerLogFile) {
|
|
59643
|
+
const clean = text.replace(ANSI_RE, "").trim();
|
|
59644
|
+
if (!clean)
|
|
59645
|
+
return;
|
|
59646
|
+
const line = fmt("coord", clean);
|
|
59647
|
+
write(AGENT_LOG_PATH, line);
|
|
59648
|
+
if (workerLogFile)
|
|
59649
|
+
write(workerLogFile, line);
|
|
59650
|
+
}
|
|
59651
|
+
function logPhase(changeName, workerLogFile, phase, detail) {
|
|
59652
|
+
const msg = `${changeName}: ${phase}${detail ? ` (${detail})` : ""}`;
|
|
59653
|
+
const line = fmt("phase", msg);
|
|
59654
|
+
write(AGENT_LOG_PATH, line);
|
|
59655
|
+
write(workerLogFile, line);
|
|
59656
|
+
}
|
|
59657
|
+
function logOutput(workerLogFile, text) {
|
|
59658
|
+
write(workerLogFile, fmt("output", text));
|
|
59659
|
+
}
|
|
59660
|
+
async function initWorkerLog(logFile) {
|
|
59661
|
+
await mkdir2(dirname4(logFile), { recursive: true });
|
|
59662
|
+
await Bun.write(logFile, "");
|
|
59663
|
+
}
|
|
59664
|
+
var ANSI_RE, AGENT_LOG_PATH;
|
|
59665
|
+
var init_log = __esm(() => {
|
|
59666
|
+
ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
59667
|
+
AGENT_LOG_PATH = join11(homedir2(), ".ralph", "agent-mode.log");
|
|
59668
|
+
mkdir2(dirname4(AGENT_LOG_PATH), { recursive: true }).catch(() => {
|
|
59669
|
+
return;
|
|
59670
|
+
});
|
|
59671
|
+
});
|
|
59672
|
+
|
|
59619
59673
|
// packages/core/src/layout.ts
|
|
59620
|
-
import { join as
|
|
59674
|
+
import { join as join12 } from "path";
|
|
59621
59675
|
function projectLayout(root) {
|
|
59622
|
-
const statesDir =
|
|
59623
|
-
const tasksDir =
|
|
59676
|
+
const statesDir = join12(root, ".ralph", "tasks");
|
|
59677
|
+
const tasksDir = join12(root, "openspec", "changes");
|
|
59624
59678
|
return {
|
|
59625
59679
|
root,
|
|
59626
59680
|
statesDir,
|
|
59627
59681
|
tasksDir,
|
|
59628
|
-
agentStateFile:
|
|
59629
|
-
changeDir: (name) =>
|
|
59630
|
-
taskStateDir: (name) =>
|
|
59631
|
-
stateFile: (name) =>
|
|
59682
|
+
agentStateFile: join12(root, ".ralph", "agent-state.json"),
|
|
59683
|
+
changeDir: (name) => join12(tasksDir, name),
|
|
59684
|
+
taskStateDir: (name) => join12(statesDir, name),
|
|
59685
|
+
stateFile: (name) => join12(statesDir, name, STATE_FILE2)
|
|
59632
59686
|
};
|
|
59633
59687
|
}
|
|
59634
59688
|
var STATE_FILE2 = ".ralph-state.json";
|
|
@@ -60221,19 +60275,19 @@ var init_coordinator = __esm(() => {
|
|
|
60221
60275
|
});
|
|
60222
60276
|
|
|
60223
60277
|
// apps/cli/src/agent/scaffold.ts
|
|
60224
|
-
import { join as
|
|
60225
|
-
import { mkdir as
|
|
60278
|
+
import { join as join13 } from "path";
|
|
60279
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
60226
60280
|
function changeNameForIssue(issue) {
|
|
60227
60281
|
const slug = issue.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
60228
60282
|
return slug ? `${issue.identifier.toLowerCase()}-${slug}` : issue.identifier.toLowerCase();
|
|
60229
60283
|
}
|
|
60230
60284
|
async function scaffoldChangeForIssue(tasksDir, statesDir, issue, comments = [], appendPrompt = "") {
|
|
60231
60285
|
const name = changeNameForIssue(issue);
|
|
60232
|
-
const changeDir =
|
|
60233
|
-
const stateDir =
|
|
60234
|
-
await
|
|
60235
|
-
await
|
|
60236
|
-
await
|
|
60286
|
+
const changeDir = join13(tasksDir, name);
|
|
60287
|
+
const stateDir = join13(statesDir, name);
|
|
60288
|
+
await mkdir3(changeDir, { recursive: true });
|
|
60289
|
+
await mkdir3(join13(changeDir, "specs"), { recursive: true });
|
|
60290
|
+
await mkdir3(stateDir, { recursive: true });
|
|
60237
60291
|
const commentsBlock = comments.length > 0 ? [
|
|
60238
60292
|
"",
|
|
60239
60293
|
"## Linear comments",
|
|
@@ -60284,26 +60338,26 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue, comments = [],
|
|
|
60284
60338
|
""
|
|
60285
60339
|
].join(`
|
|
60286
60340
|
`);
|
|
60287
|
-
await Bun.write(
|
|
60288
|
-
await Bun.write(
|
|
60289
|
-
await Bun.write(
|
|
60341
|
+
await Bun.write(join13(changeDir, "proposal.md"), proposal);
|
|
60342
|
+
await Bun.write(join13(changeDir, "tasks.md"), tasks);
|
|
60343
|
+
await Bun.write(join13(changeDir, "design.md"), design);
|
|
60290
60344
|
return name;
|
|
60291
60345
|
}
|
|
60292
60346
|
var init_scaffold = () => {};
|
|
60293
60347
|
|
|
60294
60348
|
// apps/cli/src/agent/worktree.ts
|
|
60295
|
-
import { basename, join as
|
|
60296
|
-
import { homedir as
|
|
60349
|
+
import { basename, join as join14 } from "path";
|
|
60350
|
+
import { homedir as homedir3 } from "os";
|
|
60297
60351
|
import { exists } from "fs/promises";
|
|
60298
60352
|
function worktreesDir(projectRoot) {
|
|
60299
|
-
return
|
|
60353
|
+
return join14(homedir3(), ".ralph", basename(projectRoot), "worktrees");
|
|
60300
60354
|
}
|
|
60301
60355
|
function branchForChange(changeName) {
|
|
60302
60356
|
return `ralph/${changeName}`;
|
|
60303
60357
|
}
|
|
60304
60358
|
async function createWorktree(projectRoot, changeName, runner) {
|
|
60305
60359
|
const dir = worktreesDir(projectRoot);
|
|
60306
|
-
const cwd2 =
|
|
60360
|
+
const cwd2 = join14(dir, changeName);
|
|
60307
60361
|
const branch = branchForChange(changeName);
|
|
60308
60362
|
const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
|
|
60309
60363
|
if (list.stdout.includes(`worktree ${cwd2}
|
|
@@ -60360,8 +60414,8 @@ async function isWorktreeSafeToRemove(cwd2, base2, runner) {
|
|
|
60360
60414
|
return { safe: true, dirty, unpushedCommits };
|
|
60361
60415
|
}
|
|
60362
60416
|
async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
60363
|
-
const dst =
|
|
60364
|
-
const src =
|
|
60417
|
+
const dst = join14(worktreeCwd, ".mcp.json");
|
|
60418
|
+
const src = join14(projectRoot, ".mcp.json");
|
|
60365
60419
|
const source = await exists(dst) ? dst : await exists(src) ? src : null;
|
|
60366
60420
|
if (!source)
|
|
60367
60421
|
return;
|
|
@@ -60375,7 +60429,7 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
|
60375
60429
|
if (servers && typeof servers === "object") {
|
|
60376
60430
|
for (const cfg of Object.values(servers)) {
|
|
60377
60431
|
if (Array.isArray(cfg.args)) {
|
|
60378
|
-
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ?
|
|
60432
|
+
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join14(projectRoot, a) : a);
|
|
60379
60433
|
}
|
|
60380
60434
|
}
|
|
60381
60435
|
}
|
|
@@ -60558,7 +60612,7 @@ var init_ci = __esm(() => {
|
|
|
60558
60612
|
});
|
|
60559
60613
|
|
|
60560
60614
|
// apps/cli/src/agent/post-task.ts
|
|
60561
|
-
import { join as
|
|
60615
|
+
import { join as join15 } from "path";
|
|
60562
60616
|
async function reactivateState(stateFilePath, log2, changeName) {
|
|
60563
60617
|
const file = Bun.file(stateFilePath);
|
|
60564
60618
|
if (!await file.exists())
|
|
@@ -60577,7 +60631,7 @@ async function reactivateState(stateFilePath, log2, changeName) {
|
|
|
60577
60631
|
}
|
|
60578
60632
|
async function runWorkerWithFixTask(ctx, heading, body) {
|
|
60579
60633
|
try {
|
|
60580
|
-
await prependFixTask(
|
|
60634
|
+
await prependFixTask(join15(ctx.changeDir, "tasks.md"), heading, body);
|
|
60581
60635
|
} catch (err) {
|
|
60582
60636
|
ctx.log(`! could not prepend fix task: ${err.message}`, "red");
|
|
60583
60637
|
return 1;
|
|
@@ -60889,8 +60943,8 @@ var init_post_task = __esm(() => {
|
|
|
60889
60943
|
});
|
|
60890
60944
|
|
|
60891
60945
|
// apps/cli/src/agent/wire.ts
|
|
60892
|
-
import { join as
|
|
60893
|
-
import { mkdir as
|
|
60946
|
+
import { join as join16 } from "path";
|
|
60947
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
60894
60948
|
function traceCmdRunner(base2, onStart, onEnd) {
|
|
60895
60949
|
return {
|
|
60896
60950
|
run: async (cmd, cwd2) => {
|
|
@@ -60951,7 +61005,7 @@ function buildAgentCoordinator(input) {
|
|
|
60951
61005
|
onWorkerOutput,
|
|
60952
61006
|
onWorkerCmd
|
|
60953
61007
|
} = input;
|
|
60954
|
-
const logsDir =
|
|
61008
|
+
const logsDir = join16(projectRoot, ".ralph", "logs");
|
|
60955
61009
|
const concurrency = args.concurrency || cfg.concurrency;
|
|
60956
61010
|
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
60957
61011
|
const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
|
|
@@ -61143,8 +61197,8 @@ function buildAgentCoordinator(input) {
|
|
|
61143
61197
|
} else {
|
|
61144
61198
|
changeName = changeNameForIssue(issue);
|
|
61145
61199
|
const wtLayout = projectLayout(workerCwd);
|
|
61146
|
-
await
|
|
61147
|
-
await
|
|
61200
|
+
await mkdir4(wtLayout.changeDir(changeName), { recursive: true });
|
|
61201
|
+
await mkdir4(wtLayout.taskStateDir(changeName), { recursive: true });
|
|
61148
61202
|
}
|
|
61149
61203
|
cwdByChange.set(changeName, workerCwd);
|
|
61150
61204
|
statesDirByChange.set(changeName, scaffoldStatesDir);
|
|
@@ -61153,7 +61207,7 @@ function buildAgentCoordinator(input) {
|
|
|
61153
61207
|
branchByChange.set(changeName, branch);
|
|
61154
61208
|
if (mode === "conflict-fix") {
|
|
61155
61209
|
const wtLayout = projectLayout(workerCwd);
|
|
61156
|
-
const tasksFile =
|
|
61210
|
+
const tasksFile = join16(wtLayout.changeDir(changeName), "tasks.md");
|
|
61157
61211
|
const prUrl = prByChange.get(changeName);
|
|
61158
61212
|
const body = [
|
|
61159
61213
|
`The PR for this change has merge conflicts with \`${cfg.prBaseBranch}\`.`,
|
|
@@ -61231,21 +61285,8 @@ PR: ${prUrl}` : ""
|
|
|
61231
61285
|
return c;
|
|
61232
61286
|
}
|
|
61233
61287
|
function defaultSpawn(changeName, cmd, cwd2, note) {
|
|
61234
|
-
const logFilePath =
|
|
61235
|
-
|
|
61236
|
-
const ensureLogWriter = async () => {
|
|
61237
|
-
if (logWriter)
|
|
61238
|
-
return logWriter;
|
|
61239
|
-
try {
|
|
61240
|
-
await Bun.write(logFilePath, "");
|
|
61241
|
-
logWriter = Bun.file(logFilePath).writer();
|
|
61242
|
-
return logWriter;
|
|
61243
|
-
} catch (err) {
|
|
61244
|
-
onLog(`! could not open worker log ${logFilePath}: ${err.message}`, "yellow");
|
|
61245
|
-
return null;
|
|
61246
|
-
}
|
|
61247
|
-
};
|
|
61248
|
-
const ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
61288
|
+
const logFilePath = join16(logsDir, `${changeName}.log`);
|
|
61289
|
+
const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
61249
61290
|
const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
61250
61291
|
const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
61251
61292
|
const ITER_HEADER_LINE_RE = /^\u2500\u2500/;
|
|
@@ -61258,7 +61299,6 @@ PR: ${prUrl}` : ""
|
|
|
61258
61299
|
const reader = stream.getReader();
|
|
61259
61300
|
const decoder = new TextDecoder;
|
|
61260
61301
|
let buf = "";
|
|
61261
|
-
const writer = await ensureLogWriter();
|
|
61262
61302
|
try {
|
|
61263
61303
|
while (true) {
|
|
61264
61304
|
const { value, done } = await reader.read();
|
|
@@ -61271,26 +61311,20 @@ PR: ${prUrl}` : ""
|
|
|
61271
61311
|
`)) >= 0) {
|
|
61272
61312
|
const line = buf.slice(0, nl);
|
|
61273
61313
|
buf = buf.slice(nl + 1);
|
|
61274
|
-
const clean = line.replace(
|
|
61275
|
-
if (
|
|
61276
|
-
|
|
61277
|
-
`);
|
|
61314
|
+
const clean = line.replace(ANSI_RE2, "").trim();
|
|
61315
|
+
if (clean && isLogWorthy(clean))
|
|
61316
|
+
logOutput(logFilePath, clean);
|
|
61278
61317
|
if (line)
|
|
61279
61318
|
onWorkerOutput?.(changeName, label === "err" ? `! ${line}` : line);
|
|
61280
61319
|
}
|
|
61281
61320
|
}
|
|
61282
61321
|
if (buf) {
|
|
61283
|
-
const clean = buf.replace(
|
|
61284
|
-
if (
|
|
61285
|
-
|
|
61286
|
-
`);
|
|
61322
|
+
const clean = buf.replace(ANSI_RE2, "").trim();
|
|
61323
|
+
if (clean && isLogWorthy(clean))
|
|
61324
|
+
logOutput(logFilePath, clean);
|
|
61287
61325
|
onWorkerOutput?.(changeName, label === "err" ? `! ${buf}` : buf);
|
|
61288
61326
|
}
|
|
61289
|
-
} catch {}
|
|
61290
|
-
try {
|
|
61291
|
-
writer?.flush();
|
|
61292
|
-
} catch {}
|
|
61293
|
-
}
|
|
61327
|
+
} catch {}
|
|
61294
61328
|
}
|
|
61295
61329
|
const p = Bun.spawn({
|
|
61296
61330
|
cmd,
|
|
@@ -61299,13 +61333,10 @@ PR: ${prUrl}` : ""
|
|
|
61299
61333
|
stderr: "pipe",
|
|
61300
61334
|
stdin: "ignore"
|
|
61301
61335
|
});
|
|
61302
|
-
(
|
|
61303
|
-
|
|
61304
|
-
|
|
61305
|
-
|
|
61306
|
-
--- ${note} ---
|
|
61307
|
-
`);
|
|
61308
|
-
})();
|
|
61336
|
+
initWorkerLog(logFilePath).then(() => {
|
|
61337
|
+
if (note)
|
|
61338
|
+
logSession(note, logFilePath);
|
|
61339
|
+
});
|
|
61309
61340
|
pump(p.stdout, "out");
|
|
61310
61341
|
pump(p.stderr, "err");
|
|
61311
61342
|
return { exited: p.exited, kill: () => p.kill(), logFilePath };
|
|
@@ -61316,7 +61347,7 @@ PR: ${prUrl}` : ""
|
|
|
61316
61347
|
let logFilePath;
|
|
61317
61348
|
let handle;
|
|
61318
61349
|
if (injected) {
|
|
61319
|
-
logFilePath =
|
|
61350
|
+
logFilePath = join16(logsDir, `${changeName}.log`);
|
|
61320
61351
|
handle = injected(buildTaskCmdFor(changeName), cwd2);
|
|
61321
61352
|
} else {
|
|
61322
61353
|
const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, `spawn at ${new Date().toISOString()}`);
|
|
@@ -61500,6 +61531,7 @@ function describeIndicators(indicators, team, assignee) {
|
|
|
61500
61531
|
}
|
|
61501
61532
|
var bunGitRunner, bunCmdRunner;
|
|
61502
61533
|
var init_wire = __esm(() => {
|
|
61534
|
+
init_log();
|
|
61503
61535
|
init_layout();
|
|
61504
61536
|
init_types2();
|
|
61505
61537
|
init_coordinator();
|
|
@@ -61546,7 +61578,7 @@ var exports_json_runner = {};
|
|
|
61546
61578
|
__export(exports_json_runner, {
|
|
61547
61579
|
runAgentJson: () => runAgentJson
|
|
61548
61580
|
});
|
|
61549
|
-
import { join as
|
|
61581
|
+
import { join as join20 } from "path";
|
|
61550
61582
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
61551
61583
|
import { homedir as homedir4 } from "os";
|
|
61552
61584
|
function cleanOutputLine2(raw) {
|
|
@@ -61571,7 +61603,7 @@ async function runAgentJson({
|
|
|
61571
61603
|
statesDir,
|
|
61572
61604
|
tasksDir
|
|
61573
61605
|
}) {
|
|
61574
|
-
await mkdir6(
|
|
61606
|
+
await mkdir6(join20(homedir4(), ".ralph"), { recursive: true }).catch(() => {
|
|
61575
61607
|
return;
|
|
61576
61608
|
});
|
|
61577
61609
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
@@ -61677,7 +61709,7 @@ var init_json_runner = __esm(() => {
|
|
|
61677
61709
|
});
|
|
61678
61710
|
|
|
61679
61711
|
// apps/cli/src/index.ts
|
|
61680
|
-
import { resolve as resolve2, join as
|
|
61712
|
+
import { resolve as resolve2, join as join21, dirname as dirname6 } from "path";
|
|
61681
61713
|
import { exists as exists2, mkdir as mkdir7, rm } from "fs/promises";
|
|
61682
61714
|
|
|
61683
61715
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
@@ -66800,7 +66832,7 @@ function createDefaultContext() {
|
|
|
66800
66832
|
|
|
66801
66833
|
// apps/cli/src/components/App.tsx
|
|
66802
66834
|
var import_react58 = __toESM(require_react(), 1);
|
|
66803
|
-
import { join as
|
|
66835
|
+
import { join as join19 } from "path";
|
|
66804
66836
|
|
|
66805
66837
|
// packages/core/src/state.ts
|
|
66806
66838
|
init_types2();
|
|
@@ -72377,11 +72409,19 @@ var import_react57 = __toESM(require_react(), 1);
|
|
|
72377
72409
|
init_cli();
|
|
72378
72410
|
init_config();
|
|
72379
72411
|
init_wire();
|
|
72380
|
-
|
|
72381
|
-
import { join as join16, dirname as dirname4 } from "path";
|
|
72412
|
+
import { join as join17 } from "path";
|
|
72382
72413
|
import { pathToFileURL } from "url";
|
|
72383
|
-
|
|
72384
|
-
|
|
72414
|
+
|
|
72415
|
+
// packages/core/src/progress.ts
|
|
72416
|
+
function countProgress(content) {
|
|
72417
|
+
const checked = (content.match(/^- \[x\]/gm) ?? []).length;
|
|
72418
|
+
const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
72419
|
+
return { checked, unchecked, total: checked + unchecked };
|
|
72420
|
+
}
|
|
72421
|
+
|
|
72422
|
+
// apps/cli/src/components/AgentMode.tsx
|
|
72423
|
+
init_log();
|
|
72424
|
+
var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
|
|
72385
72425
|
var lineCounter = 0;
|
|
72386
72426
|
function nextId() {
|
|
72387
72427
|
lineCounter += 1;
|
|
@@ -72408,6 +72448,18 @@ function fmtElapsed(ms) {
|
|
|
72408
72448
|
function trunc(s, max2) {
|
|
72409
72449
|
return s.length > max2 ? s.slice(0, max2 - 1) + "\u2026" : s;
|
|
72410
72450
|
}
|
|
72451
|
+
function calcProgressBar(checked, total, width) {
|
|
72452
|
+
const countStr = `${checked}/${total}`;
|
|
72453
|
+
const inner = width - 2;
|
|
72454
|
+
if (inner < countStr.length + 2)
|
|
72455
|
+
return null;
|
|
72456
|
+
const leftSlot = Math.floor((inner - countStr.length) / 2);
|
|
72457
|
+
const rightSlot = Math.max(0, inner - countStr.length - leftSlot);
|
|
72458
|
+
const filled = total > 0 ? Math.round(checked / total * inner) : 0;
|
|
72459
|
+
const filledLeft = Math.min(filled, leftSlot);
|
|
72460
|
+
const filledRight = Math.max(0, Math.min(filled - leftSlot - countStr.length, rightSlot));
|
|
72461
|
+
return { countStr, filledLeft, leftSlot, filledRight, rightSlot };
|
|
72462
|
+
}
|
|
72411
72463
|
function prLabel(prUrl) {
|
|
72412
72464
|
const m = prUrl.match(/\/pull\/(\d+)/);
|
|
72413
72465
|
return m ? `#${m[1]}` : "PR";
|
|
@@ -72571,28 +72623,7 @@ function displayTailLines(activeCount) {
|
|
|
72571
72623
|
return 8;
|
|
72572
72624
|
return 5;
|
|
72573
72625
|
}
|
|
72574
|
-
var AGENT_LOG_PATH = join16(homedir3(), ".ralph", "agent-mode.log");
|
|
72575
72626
|
var SESSION_START = new Date().toISOString();
|
|
72576
|
-
mkdir4(dirname4(AGENT_LOG_PATH), { recursive: true }).catch(() => {
|
|
72577
|
-
return;
|
|
72578
|
-
});
|
|
72579
|
-
function writeAgentLog(text) {
|
|
72580
|
-
const clean = text.replace(ANSI_STRIP_RE, "").trim();
|
|
72581
|
-
if (!clean)
|
|
72582
|
-
return;
|
|
72583
|
-
const line = `[${new Date().toISOString()}] ${clean}
|
|
72584
|
-
`;
|
|
72585
|
-
appendFile(AGENT_LOG_PATH, line).catch(() => {
|
|
72586
|
-
return;
|
|
72587
|
-
});
|
|
72588
|
-
}
|
|
72589
|
-
function writePhaseLog(phaseLogFile, text) {
|
|
72590
|
-
const line = `[${new Date().toISOString()}] ${text}
|
|
72591
|
-
`;
|
|
72592
|
-
appendFile(phaseLogFile, line).catch(() => {
|
|
72593
|
-
return;
|
|
72594
|
-
});
|
|
72595
|
-
}
|
|
72596
72627
|
function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
72597
72628
|
const { exit } = use_app_default();
|
|
72598
72629
|
const { stdout } = use_stdout_default();
|
|
@@ -72606,15 +72637,15 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72606
72637
|
const nextPollAtRef = import_react57.useRef(0);
|
|
72607
72638
|
const cfgRef = import_react57.useRef(null);
|
|
72608
72639
|
const [pollStatus, setPollStatus] = import_react57.useState({ state: "idle", lastFound: null, lastAdded: null, lastAt: null, filterDesc: "" });
|
|
72609
|
-
function appendLog(text, color) {
|
|
72640
|
+
function appendLog(text, color, workerLogFile) {
|
|
72610
72641
|
setLogs((prev) => [...prev, { id: nextId(), text, color }]);
|
|
72611
|
-
|
|
72642
|
+
logCoord(text, workerLogFile);
|
|
72612
72643
|
}
|
|
72613
72644
|
import_react57.useEffect(() => {
|
|
72614
72645
|
let pollTimer = null;
|
|
72615
72646
|
let cancelled = false;
|
|
72616
72647
|
async function init2() {
|
|
72617
|
-
|
|
72648
|
+
logSession(`=== session start ${SESSION_START} ===`);
|
|
72618
72649
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
72619
72650
|
const cfg2 = await loadRalphyConfig(projectRoot);
|
|
72620
72651
|
cfgRef.current = cfg2;
|
|
@@ -72635,49 +72666,37 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72635
72666
|
onLog: appendLog,
|
|
72636
72667
|
onWorkersChanged: () => setTick((t) => t + 1),
|
|
72637
72668
|
onWorkerStarted: (changeName, dir, logFile, changeDir) => {
|
|
72638
|
-
|
|
72639
|
-
const phaseLogFile = logFile.replace(/\.log$/, "-phases.log");
|
|
72640
|
-
mkdir4(dirname4(phaseLogFile), { recursive: true }).then(() => appendFile(phaseLogFile, `=== session ${SESSION_START} | worker-started ${new Date().toISOString()} ===
|
|
72641
|
-
`)).catch(() => {
|
|
72642
|
-
return;
|
|
72643
|
-
});
|
|
72669
|
+
logSession(`worker-started ${changeName} log=${logFile}`, logFile);
|
|
72644
72670
|
workerMetaRef.current.set(changeName, {
|
|
72645
72671
|
startedAt: Date.now(),
|
|
72646
72672
|
statesDir: dir,
|
|
72647
72673
|
logFile,
|
|
72648
|
-
phaseLogFile,
|
|
72649
72674
|
changeDir,
|
|
72650
72675
|
iter: 0,
|
|
72651
72676
|
phase: "working",
|
|
72652
72677
|
phaseDetail: "",
|
|
72653
72678
|
phaseStartedAt: Date.now(),
|
|
72654
72679
|
currentTask: null,
|
|
72680
|
+
taskProgress: null,
|
|
72655
72681
|
prUrl: null,
|
|
72656
72682
|
currentCmd: null,
|
|
72657
72683
|
tail: []
|
|
72658
72684
|
});
|
|
72659
72685
|
},
|
|
72660
72686
|
onWorkerExited: (changeName) => {
|
|
72661
|
-
writeAgentLog(`worker-exited ${changeName}`);
|
|
72662
72687
|
const m = workerMetaRef.current.get(changeName);
|
|
72663
|
-
|
|
72664
|
-
writePhaseLog(m.phaseLogFile, `=== worker-exited ===`);
|
|
72665
|
-
}
|
|
72688
|
+
logSession(`worker-exited ${changeName}`, m?.logFile);
|
|
72666
72689
|
workerMetaRef.current.delete(changeName);
|
|
72667
72690
|
},
|
|
72668
72691
|
onWorkerPhase: (changeName, phase, detail) => {
|
|
72669
72692
|
const m = workerMetaRef.current.get(changeName);
|
|
72670
72693
|
if (!m)
|
|
72671
72694
|
return;
|
|
72672
|
-
if (m.phase !== phase)
|
|
72673
|
-
writeAgentLog(`phase ${changeName}: ${phase}${detail ? ` (${detail})` : ""}`);
|
|
72695
|
+
if (m.phase !== phase)
|
|
72674
72696
|
m.phaseStartedAt = Date.now();
|
|
72675
|
-
}
|
|
72676
72697
|
m.phase = phase;
|
|
72677
72698
|
m.phaseDetail = detail ?? "";
|
|
72678
|
-
|
|
72679
|
-
writePhaseLog(m.phaseLogFile, `${phase}${detail ? ` (${detail})` : ""}`);
|
|
72680
|
-
}
|
|
72699
|
+
logPhase(changeName, m.logFile, phase, detail);
|
|
72681
72700
|
},
|
|
72682
72701
|
onWorkerOutput: (changeName, line) => {
|
|
72683
72702
|
const m = workerMetaRef.current.get(changeName);
|
|
@@ -72758,7 +72777,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72758
72777
|
(async () => {
|
|
72759
72778
|
for (const [changeName, meta] of workerMetaRef.current) {
|
|
72760
72779
|
try {
|
|
72761
|
-
const file = Bun.file(
|
|
72780
|
+
const file = Bun.file(join17(meta.statesDir, changeName, ".ralph-state.json"));
|
|
72762
72781
|
if (await file.exists()) {
|
|
72763
72782
|
const json = await file.json();
|
|
72764
72783
|
meta.iter = json.iteration ?? meta.iter;
|
|
@@ -72768,11 +72787,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72768
72787
|
}
|
|
72769
72788
|
if (meta.changeDir) {
|
|
72770
72789
|
try {
|
|
72771
|
-
const tasksFile = Bun.file(
|
|
72790
|
+
const tasksFile = Bun.file(join17(meta.changeDir, "tasks.md"));
|
|
72772
72791
|
if (await tasksFile.exists()) {
|
|
72773
72792
|
const text = await tasksFile.text();
|
|
72774
72793
|
const match = text.match(/^- \[ \] (.+)$/m);
|
|
72775
72794
|
meta.currentTask = match?.[1]?.trim() ?? null;
|
|
72795
|
+
const { checked, total } = countProgress(text);
|
|
72796
|
+
meta.taskProgress = total > 0 ? { checked, total } : null;
|
|
72776
72797
|
}
|
|
72777
72798
|
} catch (err) {
|
|
72778
72799
|
console.error(`Failed to read tasks.md for worker '${changeName}' (may not exist yet):`, err);
|
|
@@ -73119,6 +73140,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
73119
73140
|
const tail2 = meta?.tail ?? [];
|
|
73120
73141
|
const prUrl = meta?.prUrl ?? null;
|
|
73121
73142
|
const currentTask = meta?.currentTask ?? null;
|
|
73143
|
+
const taskProgress = meta?.taskProgress ?? null;
|
|
73122
73144
|
const pBadge = priorityBadge(w.issue.priority);
|
|
73123
73145
|
const mBadge = modeBadge(w.mode);
|
|
73124
73146
|
const pColor = phaseColor(phase);
|
|
@@ -73322,6 +73344,46 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
73322
73344
|
}, undefined, false, undefined, this)
|
|
73323
73345
|
]
|
|
73324
73346
|
}, undefined, true, undefined, this),
|
|
73347
|
+
taskProgress && (() => {
|
|
73348
|
+
const bar = calcProgressBar(taskProgress.checked, taskProgress.total, termWidth - 4);
|
|
73349
|
+
if (!bar)
|
|
73350
|
+
return null;
|
|
73351
|
+
const { countStr, filledLeft, leftSlot, filledRight, rightSlot } = bar;
|
|
73352
|
+
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
|
|
73353
|
+
marginTop: 0,
|
|
73354
|
+
children: [
|
|
73355
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
73356
|
+
dimColor: true,
|
|
73357
|
+
children: "["
|
|
73358
|
+
}, undefined, false, undefined, this),
|
|
73359
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
73360
|
+
color: "green",
|
|
73361
|
+
children: "\u2588".repeat(filledLeft)
|
|
73362
|
+
}, undefined, false, undefined, this),
|
|
73363
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
73364
|
+
dimColor: true,
|
|
73365
|
+
children: "\u2591".repeat(leftSlot - filledLeft)
|
|
73366
|
+
}, undefined, false, undefined, this),
|
|
73367
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
73368
|
+
color: "white",
|
|
73369
|
+
bold: true,
|
|
73370
|
+
children: countStr
|
|
73371
|
+
}, undefined, false, undefined, this),
|
|
73372
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
73373
|
+
color: "green",
|
|
73374
|
+
children: "\u2588".repeat(filledRight)
|
|
73375
|
+
}, undefined, false, undefined, this),
|
|
73376
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
73377
|
+
dimColor: true,
|
|
73378
|
+
children: "\u2591".repeat(rightSlot - filledRight)
|
|
73379
|
+
}, undefined, false, undefined, this),
|
|
73380
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
73381
|
+
dimColor: true,
|
|
73382
|
+
children: "]"
|
|
73383
|
+
}, undefined, false, undefined, this)
|
|
73384
|
+
]
|
|
73385
|
+
}, undefined, true, undefined, this);
|
|
73386
|
+
})(),
|
|
73325
73387
|
currentTask && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
|
|
73326
73388
|
gap: 1,
|
|
73327
73389
|
marginTop: 0,
|
|
@@ -73385,11 +73447,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
73385
73447
|
}
|
|
73386
73448
|
|
|
73387
73449
|
// packages/openspec/src/openspec-change-store.ts
|
|
73388
|
-
import { join as
|
|
73450
|
+
import { join as join18, dirname as dirname5 } from "path";
|
|
73389
73451
|
import { readdir, mkdir as mkdir5 } from "fs/promises";
|
|
73390
73452
|
function resolveOpenspecBin() {
|
|
73391
73453
|
const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
|
|
73392
|
-
return
|
|
73454
|
+
return join18(dirname5(pkgJsonPath), "bin", "openspec.js");
|
|
73393
73455
|
}
|
|
73394
73456
|
function runOpenspec(args, options = {}) {
|
|
73395
73457
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
@@ -73415,7 +73477,7 @@ class OpenSpecChangeStore {
|
|
|
73415
73477
|
}
|
|
73416
73478
|
}
|
|
73417
73479
|
getChangeDirectory(name) {
|
|
73418
|
-
return
|
|
73480
|
+
return join18("openspec", "changes", name);
|
|
73419
73481
|
}
|
|
73420
73482
|
async listChanges() {
|
|
73421
73483
|
const result2 = runOpenspec(["list", "--json"]);
|
|
@@ -73429,7 +73491,7 @@ class OpenSpecChangeStore {
|
|
|
73429
73491
|
}
|
|
73430
73492
|
} catch {}
|
|
73431
73493
|
}
|
|
73432
|
-
const changesDir =
|
|
73494
|
+
const changesDir = join18("openspec", "changes");
|
|
73433
73495
|
if (!await Bun.file(changesDir).exists())
|
|
73434
73496
|
return [];
|
|
73435
73497
|
try {
|
|
@@ -73440,18 +73502,18 @@ class OpenSpecChangeStore {
|
|
|
73440
73502
|
}
|
|
73441
73503
|
}
|
|
73442
73504
|
async readTaskList(name) {
|
|
73443
|
-
const file = Bun.file(
|
|
73505
|
+
const file = Bun.file(join18("openspec", "changes", name, "tasks.md"));
|
|
73444
73506
|
if (!await file.exists())
|
|
73445
73507
|
return "";
|
|
73446
73508
|
return await file.text();
|
|
73447
73509
|
}
|
|
73448
73510
|
async writeTaskList(name, content) {
|
|
73449
|
-
const path =
|
|
73511
|
+
const path = join18("openspec", "changes", name, "tasks.md");
|
|
73450
73512
|
await mkdir5(dirname5(path), { recursive: true });
|
|
73451
73513
|
await Bun.write(path, content);
|
|
73452
73514
|
}
|
|
73453
73515
|
async appendSteering(name, message) {
|
|
73454
|
-
const path =
|
|
73516
|
+
const path = join18("openspec", "changes", name, "steering.md");
|
|
73455
73517
|
const file = Bun.file(path);
|
|
73456
73518
|
const existing = await file.exists() ? await file.text() : null;
|
|
73457
73519
|
const updated = existing ? `${message}
|
|
@@ -73462,7 +73524,7 @@ ${existing.trimStart()}` : `${message}
|
|
|
73462
73524
|
await Bun.write(path, updated);
|
|
73463
73525
|
}
|
|
73464
73526
|
async readSection(name, artifact, heading) {
|
|
73465
|
-
const file = Bun.file(
|
|
73527
|
+
const file = Bun.file(join18("openspec", "changes", name, artifact));
|
|
73466
73528
|
if (!await file.exists())
|
|
73467
73529
|
return "";
|
|
73468
73530
|
const content = await file.text();
|
|
@@ -73580,8 +73642,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
73580
73642
|
message: "Error: --name is required for status mode"
|
|
73581
73643
|
}, undefined, false, undefined, this);
|
|
73582
73644
|
}
|
|
73583
|
-
const stateDir =
|
|
73584
|
-
if (getStorage().read(
|
|
73645
|
+
const stateDir = join19(statesDir, args.name);
|
|
73646
|
+
if (getStorage().read(join19(stateDir, ".ralph-state.json")) === null) {
|
|
73585
73647
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
73586
73648
|
message: `Error: change '${args.name}' not found`
|
|
73587
73649
|
}, undefined, false, undefined, this);
|
|
@@ -73633,7 +73695,7 @@ if (typeof globalThis.Bun === "undefined") {
|
|
|
73633
73695
|
async function findProjectRoot() {
|
|
73634
73696
|
let dir = process.cwd();
|
|
73635
73697
|
while (dir !== "/") {
|
|
73636
|
-
if (await exists2(
|
|
73698
|
+
if (await exists2(join21(dir, "openspec")))
|
|
73637
73699
|
return dir;
|
|
73638
73700
|
dir = resolve2(dir, "..");
|
|
73639
73701
|
}
|
|
@@ -73674,7 +73736,7 @@ try {
|
|
|
73674
73736
|
const tasksDir = layout.tasksDir;
|
|
73675
73737
|
if (args.mode === "init") {
|
|
73676
73738
|
await mkdir7(statesDir, { recursive: true });
|
|
73677
|
-
const openspecBin =
|
|
73739
|
+
const openspecBin = join21(dirname6(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
|
|
73678
73740
|
Bun.spawnSync({
|
|
73679
73741
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
73680
73742
|
stdio: ["inherit", "inherit", "inherit"],
|
|
@@ -73687,9 +73749,9 @@ try {
|
|
|
73687
73749
|
`);
|
|
73688
73750
|
process.exit(1);
|
|
73689
73751
|
}
|
|
73690
|
-
const worktreeDir =
|
|
73691
|
-
const changeDir =
|
|
73692
|
-
const stateDir =
|
|
73752
|
+
const worktreeDir = join21(worktreesDir(projectRoot), args.name);
|
|
73753
|
+
const changeDir = join21(tasksDir, args.name);
|
|
73754
|
+
const stateDir = join21(statesDir, args.name);
|
|
73693
73755
|
const branch = `ralph/${args.name}`;
|
|
73694
73756
|
const removed = [];
|
|
73695
73757
|
if (await exists2(worktreeDir)) {
|
|
@@ -73741,13 +73803,13 @@ try {
|
|
|
73741
73803
|
process.exit(0);
|
|
73742
73804
|
}
|
|
73743
73805
|
if (args.mode === "task" && args.name) {
|
|
73744
|
-
await mkdir7(
|
|
73745
|
-
await mkdir7(
|
|
73806
|
+
await mkdir7(join21(statesDir, args.name), { recursive: true });
|
|
73807
|
+
await mkdir7(join21(tasksDir, args.name), { recursive: true });
|
|
73746
73808
|
}
|
|
73747
73809
|
if (args.mode === "agent") {
|
|
73748
73810
|
await mkdir7(statesDir, { recursive: true });
|
|
73749
73811
|
await mkdir7(tasksDir, { recursive: true });
|
|
73750
|
-
await mkdir7(
|
|
73812
|
+
await mkdir7(join21(projectRoot, ".ralph"), { recursive: true });
|
|
73751
73813
|
}
|
|
73752
73814
|
if (args.mode === "agent" && args.jsonOutput) {
|
|
73753
73815
|
const { runAgentJson: runAgentJson2 } = await Promise.resolve().then(() => (init_json_runner(), exports_json_runner));
|