@neriros/ralphy 2.16.5 → 2.16.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/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.5")
35033
- return "2.16.5";
35032
+ if ("2.16.7")
35033
+ return "2.16.7";
35034
35034
  } catch {}
35035
35035
  const dirsToTry = [];
35036
35036
  try {
@@ -35121,6 +35121,7 @@ async function parseArgs(argv) {
35121
35121
  log: false,
35122
35122
  verbose: false,
35123
35123
  manualTest: false,
35124
+ fromAgent: false,
35124
35125
  linearTeam: "",
35125
35126
  linearAssignee: "",
35126
35127
  pollInterval: 60,
@@ -35321,6 +35322,9 @@ async function parseArgs(argv) {
35321
35322
  case "--manual-test":
35322
35323
  result2.manualTest = true;
35323
35324
  break;
35325
+ case "--from-agent":
35326
+ result2.fromAgent = true;
35327
+ break;
35324
35328
  default:
35325
35329
  if (VALID_MODES.has(arg)) {
35326
35330
  result2.mode = arg;
@@ -39426,6 +39430,7 @@ var init_types2 = __esm(() => {
39426
39430
  engine: exports_external.enum(["claude", "codex"]).default("claude"),
39427
39431
  model: exports_external.string().default("opus"),
39428
39432
  manualTest: exports_external.boolean().default(false),
39433
+ createPr: exports_external.boolean().default(false),
39429
39434
  usage: UsageSchema.default({}),
39430
39435
  history: exports_external.array(HistoryEntrySchema).default([]),
39431
39436
  metadata: exports_external.object({ branch: exports_external.string().optional() }).default({})
@@ -59616,19 +59621,73 @@ var init_config = __esm(() => {
59616
59621
  });
59617
59622
  });
59618
59623
 
59624
+ // packages/log/src/log.ts
59625
+ import { appendFile } from "fs/promises";
59626
+ import { join as join11, dirname as dirname4 } from "path";
59627
+ import { homedir as homedir2 } from "os";
59628
+ import { mkdir as mkdir2 } from "fs/promises";
59629
+ function fmt(type, text) {
59630
+ return `[${new Date().toISOString()}] [${type}] ${text}
59631
+ `;
59632
+ }
59633
+ function write(path, line) {
59634
+ appendFile(path, line).catch(() => {
59635
+ return;
59636
+ });
59637
+ }
59638
+ function logSession(text, workerLogFile) {
59639
+ const clean = text.replace(ANSI_RE, "").trim();
59640
+ if (!clean)
59641
+ return;
59642
+ const line = fmt("session", clean);
59643
+ write(AGENT_LOG_PATH, line);
59644
+ if (workerLogFile)
59645
+ write(workerLogFile, line);
59646
+ }
59647
+ function logCoord(text, workerLogFile) {
59648
+ const clean = text.replace(ANSI_RE, "").trim();
59649
+ if (!clean)
59650
+ return;
59651
+ const line = fmt("coord", clean);
59652
+ write(AGENT_LOG_PATH, line);
59653
+ if (workerLogFile)
59654
+ write(workerLogFile, line);
59655
+ }
59656
+ function logPhase(changeName, workerLogFile, phase, detail) {
59657
+ const msg = `${changeName}: ${phase}${detail ? ` (${detail})` : ""}`;
59658
+ const line = fmt("phase", msg);
59659
+ write(AGENT_LOG_PATH, line);
59660
+ write(workerLogFile, line);
59661
+ }
59662
+ function logOutput(workerLogFile, text) {
59663
+ write(workerLogFile, fmt("output", text));
59664
+ }
59665
+ async function initWorkerLog(logFile) {
59666
+ await mkdir2(dirname4(logFile), { recursive: true });
59667
+ await Bun.write(logFile, "");
59668
+ }
59669
+ var ANSI_RE, AGENT_LOG_PATH;
59670
+ var init_log = __esm(() => {
59671
+ ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
59672
+ AGENT_LOG_PATH = join11(homedir2(), ".ralph", "agent-mode.log");
59673
+ mkdir2(dirname4(AGENT_LOG_PATH), { recursive: true }).catch(() => {
59674
+ return;
59675
+ });
59676
+ });
59677
+
59619
59678
  // packages/core/src/layout.ts
59620
- import { join as join11 } from "path";
59679
+ import { join as join12 } from "path";
59621
59680
  function projectLayout(root) {
59622
- const statesDir = join11(root, ".ralph", "tasks");
59623
- const tasksDir = join11(root, "openspec", "changes");
59681
+ const statesDir = join12(root, ".ralph", "tasks");
59682
+ const tasksDir = join12(root, "openspec", "changes");
59624
59683
  return {
59625
59684
  root,
59626
59685
  statesDir,
59627
59686
  tasksDir,
59628
- agentStateFile: join11(root, ".ralph", "agent-state.json"),
59629
- changeDir: (name) => join11(tasksDir, name),
59630
- taskStateDir: (name) => join11(statesDir, name),
59631
- stateFile: (name) => join11(statesDir, name, STATE_FILE2)
59687
+ agentStateFile: join12(root, ".ralph", "agent-state.json"),
59688
+ changeDir: (name) => join12(tasksDir, name),
59689
+ taskStateDir: (name) => join12(statesDir, name),
59690
+ stateFile: (name) => join12(statesDir, name, STATE_FILE2)
59632
59691
  };
59633
59692
  }
59634
59693
  var STATE_FILE2 = ".ralph-state.json";
@@ -60221,19 +60280,19 @@ var init_coordinator = __esm(() => {
60221
60280
  });
60222
60281
 
60223
60282
  // apps/cli/src/agent/scaffold.ts
60224
- import { join as join12 } from "path";
60225
- import { mkdir as mkdir2 } from "fs/promises";
60283
+ import { join as join13 } from "path";
60284
+ import { mkdir as mkdir3 } from "fs/promises";
60226
60285
  function changeNameForIssue(issue) {
60227
60286
  const slug = issue.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
60228
60287
  return slug ? `${issue.identifier.toLowerCase()}-${slug}` : issue.identifier.toLowerCase();
60229
60288
  }
60230
60289
  async function scaffoldChangeForIssue(tasksDir, statesDir, issue, comments = [], appendPrompt = "") {
60231
60290
  const name = changeNameForIssue(issue);
60232
- const changeDir = join12(tasksDir, name);
60233
- const stateDir = join12(statesDir, name);
60234
- await mkdir2(changeDir, { recursive: true });
60235
- await mkdir2(join12(changeDir, "specs"), { recursive: true });
60236
- await mkdir2(stateDir, { recursive: true });
60291
+ const changeDir = join13(tasksDir, name);
60292
+ const stateDir = join13(statesDir, name);
60293
+ await mkdir3(changeDir, { recursive: true });
60294
+ await mkdir3(join13(changeDir, "specs"), { recursive: true });
60295
+ await mkdir3(stateDir, { recursive: true });
60237
60296
  const commentsBlock = comments.length > 0 ? [
60238
60297
  "",
60239
60298
  "## Linear comments",
@@ -60284,26 +60343,26 @@ async function scaffoldChangeForIssue(tasksDir, statesDir, issue, comments = [],
60284
60343
  ""
60285
60344
  ].join(`
60286
60345
  `);
60287
- await Bun.write(join12(changeDir, "proposal.md"), proposal);
60288
- await Bun.write(join12(changeDir, "tasks.md"), tasks);
60289
- await Bun.write(join12(changeDir, "design.md"), design);
60346
+ await Bun.write(join13(changeDir, "proposal.md"), proposal);
60347
+ await Bun.write(join13(changeDir, "tasks.md"), tasks);
60348
+ await Bun.write(join13(changeDir, "design.md"), design);
60290
60349
  return name;
60291
60350
  }
60292
60351
  var init_scaffold = () => {};
60293
60352
 
60294
60353
  // apps/cli/src/agent/worktree.ts
60295
- import { basename, join as join13 } from "path";
60296
- import { homedir as homedir2 } from "os";
60354
+ import { basename, join as join14 } from "path";
60355
+ import { homedir as homedir3 } from "os";
60297
60356
  import { exists } from "fs/promises";
60298
60357
  function worktreesDir(projectRoot) {
60299
- return join13(homedir2(), ".ralph", basename(projectRoot), "worktrees");
60358
+ return join14(homedir3(), ".ralph", basename(projectRoot), "worktrees");
60300
60359
  }
60301
60360
  function branchForChange(changeName) {
60302
60361
  return `ralph/${changeName}`;
60303
60362
  }
60304
60363
  async function createWorktree(projectRoot, changeName, runner) {
60305
60364
  const dir = worktreesDir(projectRoot);
60306
- const cwd2 = join13(dir, changeName);
60365
+ const cwd2 = join14(dir, changeName);
60307
60366
  const branch = branchForChange(changeName);
60308
60367
  const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
60309
60368
  if (list.stdout.includes(`worktree ${cwd2}
@@ -60360,8 +60419,8 @@ async function isWorktreeSafeToRemove(cwd2, base2, runner) {
60360
60419
  return { safe: true, dirty, unpushedCommits };
60361
60420
  }
60362
60421
  async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
60363
- const dst = join13(worktreeCwd, ".mcp.json");
60364
- const src = join13(projectRoot, ".mcp.json");
60422
+ const dst = join14(worktreeCwd, ".mcp.json");
60423
+ const src = join14(projectRoot, ".mcp.json");
60365
60424
  const source = await exists(dst) ? dst : await exists(src) ? src : null;
60366
60425
  if (!source)
60367
60426
  return;
@@ -60375,7 +60434,7 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
60375
60434
  if (servers && typeof servers === "object") {
60376
60435
  for (const cfg of Object.values(servers)) {
60377
60436
  if (Array.isArray(cfg.args)) {
60378
- cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join13(projectRoot, a) : a);
60437
+ cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join14(projectRoot, a) : a);
60379
60438
  }
60380
60439
  }
60381
60440
  }
@@ -60558,7 +60617,7 @@ var init_ci = __esm(() => {
60558
60617
  });
60559
60618
 
60560
60619
  // apps/cli/src/agent/post-task.ts
60561
- import { join as join14 } from "path";
60620
+ import { join as join15 } from "path";
60562
60621
  async function reactivateState(stateFilePath, log2, changeName) {
60563
60622
  const file = Bun.file(stateFilePath);
60564
60623
  if (!await file.exists())
@@ -60577,7 +60636,7 @@ async function reactivateState(stateFilePath, log2, changeName) {
60577
60636
  }
60578
60637
  async function runWorkerWithFixTask(ctx, heading, body) {
60579
60638
  try {
60580
- await prependFixTask(join14(ctx.changeDir, "tasks.md"), heading, body);
60639
+ await prependFixTask(join15(ctx.changeDir, "tasks.md"), heading, body);
60581
60640
  } catch (err) {
60582
60641
  ctx.log(`! could not prepend fix task: ${err.message}`, "red");
60583
60642
  return 1;
@@ -60889,8 +60948,8 @@ var init_post_task = __esm(() => {
60889
60948
  });
60890
60949
 
60891
60950
  // apps/cli/src/agent/wire.ts
60892
- import { join as join15 } from "path";
60893
- import { mkdir as mkdir3 } from "fs/promises";
60951
+ import { join as join16 } from "path";
60952
+ import { mkdir as mkdir4 } from "fs/promises";
60894
60953
  function traceCmdRunner(base2, onStart, onEnd) {
60895
60954
  return {
60896
60955
  run: async (cmd, cwd2) => {
@@ -60951,7 +61010,7 @@ function buildAgentCoordinator(input) {
60951
61010
  onWorkerOutput,
60952
61011
  onWorkerCmd
60953
61012
  } = input;
60954
- const logsDir = join15(projectRoot, ".ralph", "logs");
61013
+ const logsDir = join16(projectRoot, ".ralph", "logs");
60955
61014
  const concurrency = args.concurrency || cfg.concurrency;
60956
61015
  const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
60957
61016
  const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
@@ -61143,8 +61202,8 @@ function buildAgentCoordinator(input) {
61143
61202
  } else {
61144
61203
  changeName = changeNameForIssue(issue);
61145
61204
  const wtLayout = projectLayout(workerCwd);
61146
- await mkdir3(wtLayout.changeDir(changeName), { recursive: true });
61147
- await mkdir3(wtLayout.taskStateDir(changeName), { recursive: true });
61205
+ await mkdir4(wtLayout.changeDir(changeName), { recursive: true });
61206
+ await mkdir4(wtLayout.taskStateDir(changeName), { recursive: true });
61148
61207
  }
61149
61208
  cwdByChange.set(changeName, workerCwd);
61150
61209
  statesDirByChange.set(changeName, scaffoldStatesDir);
@@ -61153,7 +61212,7 @@ function buildAgentCoordinator(input) {
61153
61212
  branchByChange.set(changeName, branch);
61154
61213
  if (mode === "conflict-fix") {
61155
61214
  const wtLayout = projectLayout(workerCwd);
61156
- const tasksFile = join15(wtLayout.changeDir(changeName), "tasks.md");
61215
+ const tasksFile = join16(wtLayout.changeDir(changeName), "tasks.md");
61157
61216
  const prUrl = prByChange.get(changeName);
61158
61217
  const body = [
61159
61218
  `The PR for this change has merge conflicts with \`${cfg.prBaseBranch}\`.`,
@@ -61228,24 +61287,12 @@ PR: ${prUrl}` : ""
61228
61287
  c.push("--verbose");
61229
61288
  if (args.manualTest || cfg.enableManualTest)
61230
61289
  c.push("--manual-test");
61290
+ c.push("--from-agent");
61231
61291
  return c;
61232
61292
  }
61233
61293
  function defaultSpawn(changeName, cmd, cwd2, note) {
61234
- const logFilePath = join15(logsDir, `${changeName}.log`);
61235
- let logWriter = null;
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;
61294
+ const logFilePath = join16(logsDir, `${changeName}.log`);
61295
+ const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
61249
61296
  const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
61250
61297
  const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
61251
61298
  const ITER_HEADER_LINE_RE = /^\u2500\u2500/;
@@ -61258,7 +61305,6 @@ PR: ${prUrl}` : ""
61258
61305
  const reader = stream.getReader();
61259
61306
  const decoder = new TextDecoder;
61260
61307
  let buf = "";
61261
- const writer = await ensureLogWriter();
61262
61308
  try {
61263
61309
  while (true) {
61264
61310
  const { value, done } = await reader.read();
@@ -61271,26 +61317,20 @@ PR: ${prUrl}` : ""
61271
61317
  `)) >= 0) {
61272
61318
  const line = buf.slice(0, nl);
61273
61319
  buf = buf.slice(nl + 1);
61274
- const clean = line.replace(ANSI_RE, "").trim();
61275
- if (writer && clean && isLogWorthy(clean))
61276
- writer.write(clean + `
61277
- `);
61320
+ const clean = line.replace(ANSI_RE2, "").trim();
61321
+ if (clean && isLogWorthy(clean))
61322
+ logOutput(logFilePath, clean);
61278
61323
  if (line)
61279
61324
  onWorkerOutput?.(changeName, label === "err" ? `! ${line}` : line);
61280
61325
  }
61281
61326
  }
61282
61327
  if (buf) {
61283
- const clean = buf.replace(ANSI_RE, "").trim();
61284
- if (writer && clean && isLogWorthy(clean))
61285
- writer.write(clean + `
61286
- `);
61328
+ const clean = buf.replace(ANSI_RE2, "").trim();
61329
+ if (clean && isLogWorthy(clean))
61330
+ logOutput(logFilePath, clean);
61287
61331
  onWorkerOutput?.(changeName, label === "err" ? `! ${buf}` : buf);
61288
61332
  }
61289
- } catch {} finally {
61290
- try {
61291
- writer?.flush();
61292
- } catch {}
61293
- }
61333
+ } catch {}
61294
61334
  }
61295
61335
  const p = Bun.spawn({
61296
61336
  cmd,
@@ -61299,13 +61339,10 @@ PR: ${prUrl}` : ""
61299
61339
  stderr: "pipe",
61300
61340
  stdin: "ignore"
61301
61341
  });
61302
- (async () => {
61303
- const writer = await ensureLogWriter();
61304
- if (note && writer)
61305
- writer.write(`
61306
- --- ${note} ---
61307
- `);
61308
- })();
61342
+ initWorkerLog(logFilePath).then(() => {
61343
+ if (note)
61344
+ logSession(note, logFilePath);
61345
+ });
61309
61346
  pump(p.stdout, "out");
61310
61347
  pump(p.stderr, "err");
61311
61348
  return { exited: p.exited, kill: () => p.kill(), logFilePath };
@@ -61316,7 +61353,7 @@ PR: ${prUrl}` : ""
61316
61353
  let logFilePath;
61317
61354
  let handle;
61318
61355
  if (injected) {
61319
- logFilePath = join15(logsDir, `${changeName}.log`);
61356
+ logFilePath = join16(logsDir, `${changeName}.log`);
61320
61357
  handle = injected(buildTaskCmdFor(changeName), cwd2);
61321
61358
  } else {
61322
61359
  const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, `spawn at ${new Date().toISOString()}`);
@@ -61500,6 +61537,7 @@ function describeIndicators(indicators, team, assignee) {
61500
61537
  }
61501
61538
  var bunGitRunner, bunCmdRunner;
61502
61539
  var init_wire = __esm(() => {
61540
+ init_log();
61503
61541
  init_layout();
61504
61542
  init_types2();
61505
61543
  init_coordinator();
@@ -61546,7 +61584,7 @@ var exports_json_runner = {};
61546
61584
  __export(exports_json_runner, {
61547
61585
  runAgentJson: () => runAgentJson
61548
61586
  });
61549
- import { join as join19 } from "path";
61587
+ import { join as join20 } from "path";
61550
61588
  import { mkdir as mkdir6 } from "fs/promises";
61551
61589
  import { homedir as homedir4 } from "os";
61552
61590
  function cleanOutputLine2(raw) {
@@ -61571,7 +61609,7 @@ async function runAgentJson({
61571
61609
  statesDir,
61572
61610
  tasksDir
61573
61611
  }) {
61574
- await mkdir6(join19(homedir4(), ".ralph"), { recursive: true }).catch(() => {
61612
+ await mkdir6(join20(homedir4(), ".ralph"), { recursive: true }).catch(() => {
61575
61613
  return;
61576
61614
  });
61577
61615
  const cfgPath = await ensureRalphyConfig(projectRoot);
@@ -61677,7 +61715,7 @@ var init_json_runner = __esm(() => {
61677
61715
  });
61678
61716
 
61679
61717
  // apps/cli/src/index.ts
61680
- import { resolve as resolve2, join as join20, dirname as dirname6 } from "path";
61718
+ import { resolve as resolve2, join as join21, dirname as dirname6 } from "path";
61681
61719
  import { exists as exists2, mkdir as mkdir7, rm } from "fs/promises";
61682
61720
 
61683
61721
  // node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
@@ -66800,7 +66838,7 @@ function createDefaultContext() {
66800
66838
 
66801
66839
  // apps/cli/src/components/App.tsx
66802
66840
  var import_react58 = __toESM(require_react(), 1);
66803
- import { join as join18 } from "path";
66841
+ import { join as join19 } from "path";
66804
66842
 
66805
66843
  // packages/core/src/state.ts
66806
66844
  init_types2();
@@ -66853,6 +66891,7 @@ function buildInitialState(options) {
66853
66891
  engine: options.engine ?? "claude",
66854
66892
  model: options.model ?? "opus",
66855
66893
  manualTest: options.manualTest ?? false,
66894
+ createPr: options.createPr ?? false,
66856
66895
  createdAt: now2,
66857
66896
  lastModified: now2,
66858
66897
  metadata: { branch }
@@ -71852,6 +71891,17 @@ function buildTaskPrompt(state, taskDir) {
71852
71891
  `;
71853
71892
  prompt += `Commit all changed files yourself before finishing \u2014 stage files individually (e.g. \`git add path/to/file\`), never \`git add -A\` or \`git commit -am\`. Nothing is committed automatically after you exit.
71854
71893
  `;
71894
+ if (state.createPr) {
71895
+ prompt += `
71896
+ When all tasks are complete and all files are committed, push your branch and open a pull request:
71897
+ `;
71898
+ prompt += ` git push -u origin HEAD
71899
+ `;
71900
+ prompt += ` gh pr create --title "${state.name}" --body "Summary of changes for ${state.name}"
71901
+ `;
71902
+ prompt += `Use the change name as the PR title and write a concise summary of the implementation in the body.
71903
+ `;
71904
+ }
71855
71905
  return prompt;
71856
71906
  }
71857
71907
  function checkStopSignal(taskDir, stateDir) {
@@ -72019,7 +72069,8 @@ function useLoop(opts) {
72019
72069
  prompt: opts.prompt,
72020
72070
  engine: opts.engine,
72021
72071
  model: opts.model,
72022
- manualTest: opts.manualTest
72072
+ manualTest: opts.manualTest,
72073
+ createPr: opts.createPr ?? false
72023
72074
  });
72024
72075
  writeState(stateDir, currentState);
72025
72076
  }
@@ -72377,10 +72428,8 @@ var import_react57 = __toESM(require_react(), 1);
72377
72428
  init_cli();
72378
72429
  init_config();
72379
72430
  init_wire();
72380
- import { join as join16, dirname as dirname4 } from "path";
72431
+ import { join as join17 } from "path";
72381
72432
  import { pathToFileURL } from "url";
72382
- import { homedir as homedir3 } from "os";
72383
- import { appendFile, mkdir as mkdir4 } from "fs/promises";
72384
72433
 
72385
72434
  // packages/core/src/progress.ts
72386
72435
  function countProgress(content) {
@@ -72390,6 +72439,7 @@ function countProgress(content) {
72390
72439
  }
72391
72440
 
72392
72441
  // apps/cli/src/components/AgentMode.tsx
72442
+ init_log();
72393
72443
  var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
72394
72444
  var lineCounter = 0;
72395
72445
  function nextId() {
@@ -72592,28 +72642,7 @@ function displayTailLines(activeCount) {
72592
72642
  return 8;
72593
72643
  return 5;
72594
72644
  }
72595
- var AGENT_LOG_PATH = join16(homedir3(), ".ralph", "agent-mode.log");
72596
72645
  var SESSION_START = new Date().toISOString();
72597
- mkdir4(dirname4(AGENT_LOG_PATH), { recursive: true }).catch(() => {
72598
- return;
72599
- });
72600
- function writeAgentLog(text) {
72601
- const clean = text.replace(ANSI_STRIP_RE, "").trim();
72602
- if (!clean)
72603
- return;
72604
- const line = `[${new Date().toISOString()}] ${clean}
72605
- `;
72606
- appendFile(AGENT_LOG_PATH, line).catch(() => {
72607
- return;
72608
- });
72609
- }
72610
- function writePhaseLog(phaseLogFile, text) {
72611
- const line = `[${new Date().toISOString()}] ${text}
72612
- `;
72613
- appendFile(phaseLogFile, line).catch(() => {
72614
- return;
72615
- });
72616
- }
72617
72646
  function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72618
72647
  const { exit } = use_app_default();
72619
72648
  const { stdout } = use_stdout_default();
@@ -72627,15 +72656,15 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72627
72656
  const nextPollAtRef = import_react57.useRef(0);
72628
72657
  const cfgRef = import_react57.useRef(null);
72629
72658
  const [pollStatus, setPollStatus] = import_react57.useState({ state: "idle", lastFound: null, lastAdded: null, lastAt: null, filterDesc: "" });
72630
- function appendLog(text, color) {
72659
+ function appendLog(text, color, workerLogFile) {
72631
72660
  setLogs((prev) => [...prev, { id: nextId(), text, color }]);
72632
- writeAgentLog(text);
72661
+ logCoord(text, workerLogFile);
72633
72662
  }
72634
72663
  import_react57.useEffect(() => {
72635
72664
  let pollTimer = null;
72636
72665
  let cancelled = false;
72637
72666
  async function init2() {
72638
- writeAgentLog(`=== session start ${SESSION_START} ===`);
72667
+ logSession(`=== session start ${SESSION_START} ===`);
72639
72668
  const cfgPath = await ensureRalphyConfig(projectRoot);
72640
72669
  const cfg2 = await loadRalphyConfig(projectRoot);
72641
72670
  cfgRef.current = cfg2;
@@ -72656,17 +72685,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72656
72685
  onLog: appendLog,
72657
72686
  onWorkersChanged: () => setTick((t) => t + 1),
72658
72687
  onWorkerStarted: (changeName, dir, logFile, changeDir) => {
72659
- writeAgentLog(`worker-started ${changeName} log=${logFile}`);
72660
- const phaseLogFile = logFile.replace(/\.log$/, "-phases.log");
72661
- mkdir4(dirname4(phaseLogFile), { recursive: true }).then(() => appendFile(phaseLogFile, `=== session ${SESSION_START} | worker-started ${new Date().toISOString()} ===
72662
- `)).catch(() => {
72663
- return;
72664
- });
72688
+ logSession(`worker-started ${changeName} log=${logFile}`, logFile);
72665
72689
  workerMetaRef.current.set(changeName, {
72666
72690
  startedAt: Date.now(),
72667
72691
  statesDir: dir,
72668
72692
  logFile,
72669
- phaseLogFile,
72670
72693
  changeDir,
72671
72694
  iter: 0,
72672
72695
  phase: "working",
@@ -72680,26 +72703,19 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72680
72703
  });
72681
72704
  },
72682
72705
  onWorkerExited: (changeName) => {
72683
- writeAgentLog(`worker-exited ${changeName}`);
72684
72706
  const m = workerMetaRef.current.get(changeName);
72685
- if (m?.phaseLogFile) {
72686
- writePhaseLog(m.phaseLogFile, `=== worker-exited ===`);
72687
- }
72707
+ logSession(`worker-exited ${changeName}`, m?.logFile);
72688
72708
  workerMetaRef.current.delete(changeName);
72689
72709
  },
72690
72710
  onWorkerPhase: (changeName, phase, detail) => {
72691
72711
  const m = workerMetaRef.current.get(changeName);
72692
72712
  if (!m)
72693
72713
  return;
72694
- if (m.phase !== phase) {
72695
- writeAgentLog(`phase ${changeName}: ${phase}${detail ? ` (${detail})` : ""}`);
72714
+ if (m.phase !== phase)
72696
72715
  m.phaseStartedAt = Date.now();
72697
- }
72698
72716
  m.phase = phase;
72699
72717
  m.phaseDetail = detail ?? "";
72700
- if (m.phaseLogFile) {
72701
- writePhaseLog(m.phaseLogFile, `${phase}${detail ? ` (${detail})` : ""}`);
72702
- }
72718
+ logPhase(changeName, m.logFile, phase, detail);
72703
72719
  },
72704
72720
  onWorkerOutput: (changeName, line) => {
72705
72721
  const m = workerMetaRef.current.get(changeName);
@@ -72780,7 +72796,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72780
72796
  (async () => {
72781
72797
  for (const [changeName, meta] of workerMetaRef.current) {
72782
72798
  try {
72783
- const file = Bun.file(join16(meta.statesDir, changeName, ".ralph-state.json"));
72799
+ const file = Bun.file(join17(meta.statesDir, changeName, ".ralph-state.json"));
72784
72800
  if (await file.exists()) {
72785
72801
  const json = await file.json();
72786
72802
  meta.iter = json.iteration ?? meta.iter;
@@ -72790,7 +72806,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
72790
72806
  }
72791
72807
  if (meta.changeDir) {
72792
72808
  try {
72793
- const tasksFile = Bun.file(join16(meta.changeDir, "tasks.md"));
72809
+ const tasksFile = Bun.file(join17(meta.changeDir, "tasks.md"));
72794
72810
  if (await tasksFile.exists()) {
72795
72811
  const text = await tasksFile.text();
72796
72812
  const match = text.match(/^- \[ \] (.+)$/m);
@@ -73450,11 +73466,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
73450
73466
  }
73451
73467
 
73452
73468
  // packages/openspec/src/openspec-change-store.ts
73453
- import { join as join17, dirname as dirname5 } from "path";
73469
+ import { join as join18, dirname as dirname5 } from "path";
73454
73470
  import { readdir, mkdir as mkdir5 } from "fs/promises";
73455
73471
  function resolveOpenspecBin() {
73456
73472
  const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
73457
- return join17(dirname5(pkgJsonPath), "bin", "openspec.js");
73473
+ return join18(dirname5(pkgJsonPath), "bin", "openspec.js");
73458
73474
  }
73459
73475
  function runOpenspec(args, options = {}) {
73460
73476
  const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
@@ -73480,7 +73496,7 @@ class OpenSpecChangeStore {
73480
73496
  }
73481
73497
  }
73482
73498
  getChangeDirectory(name) {
73483
- return join17("openspec", "changes", name);
73499
+ return join18("openspec", "changes", name);
73484
73500
  }
73485
73501
  async listChanges() {
73486
73502
  const result2 = runOpenspec(["list", "--json"]);
@@ -73494,7 +73510,7 @@ class OpenSpecChangeStore {
73494
73510
  }
73495
73511
  } catch {}
73496
73512
  }
73497
- const changesDir = join17("openspec", "changes");
73513
+ const changesDir = join18("openspec", "changes");
73498
73514
  if (!await Bun.file(changesDir).exists())
73499
73515
  return [];
73500
73516
  try {
@@ -73505,18 +73521,18 @@ class OpenSpecChangeStore {
73505
73521
  }
73506
73522
  }
73507
73523
  async readTaskList(name) {
73508
- const file = Bun.file(join17("openspec", "changes", name, "tasks.md"));
73524
+ const file = Bun.file(join18("openspec", "changes", name, "tasks.md"));
73509
73525
  if (!await file.exists())
73510
73526
  return "";
73511
73527
  return await file.text();
73512
73528
  }
73513
73529
  async writeTaskList(name, content) {
73514
- const path = join17("openspec", "changes", name, "tasks.md");
73530
+ const path = join18("openspec", "changes", name, "tasks.md");
73515
73531
  await mkdir5(dirname5(path), { recursive: true });
73516
73532
  await Bun.write(path, content);
73517
73533
  }
73518
73534
  async appendSteering(name, message) {
73519
- const path = join17("openspec", "changes", name, "steering.md");
73535
+ const path = join18("openspec", "changes", name, "steering.md");
73520
73536
  const file = Bun.file(path);
73521
73537
  const existing = await file.exists() ? await file.text() : null;
73522
73538
  const updated = existing ? `${message}
@@ -73527,7 +73543,7 @@ ${existing.trimStart()}` : `${message}
73527
73543
  await Bun.write(path, updated);
73528
73544
  }
73529
73545
  async readSection(name, artifact, heading) {
73530
- const file = Bun.file(join17("openspec", "changes", name, artifact));
73546
+ const file = Bun.file(join18("openspec", "changes", name, artifact));
73531
73547
  if (!await file.exists())
73532
73548
  return "";
73533
73549
  const content = await file.text();
@@ -73619,6 +73635,7 @@ function TaskModeWrapper({ args, statesDir, tasksDir, projectRoot }) {
73619
73635
  log: args.log,
73620
73636
  verbose: args.verbose,
73621
73637
  manualTest,
73638
+ createPr: args.fromAgent,
73622
73639
  statesDir,
73623
73640
  tasksDir,
73624
73641
  changeStore: new OpenSpecChangeStore
@@ -73645,8 +73662,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
73645
73662
  message: "Error: --name is required for status mode"
73646
73663
  }, undefined, false, undefined, this);
73647
73664
  }
73648
- const stateDir = join18(statesDir, args.name);
73649
- if (getStorage().read(join18(stateDir, ".ralph-state.json")) === null) {
73665
+ const stateDir = join19(statesDir, args.name);
73666
+ if (getStorage().read(join19(stateDir, ".ralph-state.json")) === null) {
73650
73667
  return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
73651
73668
  message: `Error: change '${args.name}' not found`
73652
73669
  }, undefined, false, undefined, this);
@@ -73698,7 +73715,7 @@ if (typeof globalThis.Bun === "undefined") {
73698
73715
  async function findProjectRoot() {
73699
73716
  let dir = process.cwd();
73700
73717
  while (dir !== "/") {
73701
- if (await exists2(join20(dir, "openspec")))
73718
+ if (await exists2(join21(dir, "openspec")))
73702
73719
  return dir;
73703
73720
  dir = resolve2(dir, "..");
73704
73721
  }
@@ -73739,7 +73756,7 @@ try {
73739
73756
  const tasksDir = layout.tasksDir;
73740
73757
  if (args.mode === "init") {
73741
73758
  await mkdir7(statesDir, { recursive: true });
73742
- const openspecBin = join20(dirname6(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
73759
+ const openspecBin = join21(dirname6(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
73743
73760
  Bun.spawnSync({
73744
73761
  cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
73745
73762
  stdio: ["inherit", "inherit", "inherit"],
@@ -73752,9 +73769,9 @@ try {
73752
73769
  `);
73753
73770
  process.exit(1);
73754
73771
  }
73755
- const worktreeDir = join20(worktreesDir(projectRoot), args.name);
73756
- const changeDir = join20(tasksDir, args.name);
73757
- const stateDir = join20(statesDir, args.name);
73772
+ const worktreeDir = join21(worktreesDir(projectRoot), args.name);
73773
+ const changeDir = join21(tasksDir, args.name);
73774
+ const stateDir = join21(statesDir, args.name);
73758
73775
  const branch = `ralph/${args.name}`;
73759
73776
  const removed = [];
73760
73777
  if (await exists2(worktreeDir)) {
@@ -73806,13 +73823,13 @@ try {
73806
73823
  process.exit(0);
73807
73824
  }
73808
73825
  if (args.mode === "task" && args.name) {
73809
- await mkdir7(join20(statesDir, args.name), { recursive: true });
73810
- await mkdir7(join20(tasksDir, args.name), { recursive: true });
73826
+ await mkdir7(join21(statesDir, args.name), { recursive: true });
73827
+ await mkdir7(join21(tasksDir, args.name), { recursive: true });
73811
73828
  }
73812
73829
  if (args.mode === "agent") {
73813
73830
  await mkdir7(statesDir, { recursive: true });
73814
73831
  await mkdir7(tasksDir, { recursive: true });
73815
- await mkdir7(join20(projectRoot, ".ralph"), { recursive: true });
73832
+ await mkdir7(join21(projectRoot, ".ralph"), { recursive: true });
73816
73833
  }
73817
73834
  if (args.mode === "agent" && args.jsonOutput) {
73818
73835
  const { runAgentJson: runAgentJson2 } = await Promise.resolve().then(() => (init_json_runner(), exports_json_runner));
package/dist/mcp/index.js CHANGED
@@ -23967,6 +23967,7 @@ var StateSchema = exports_external.object({
23967
23967
  engine: exports_external.enum(["claude", "codex"]).default("claude"),
23968
23968
  model: exports_external.string().default("opus"),
23969
23969
  manualTest: exports_external.boolean().default(false),
23970
+ createPr: exports_external.boolean().default(false),
23970
23971
  usage: UsageSchema.default({}),
23971
23972
  history: exports_external.array(HistoryEntrySchema).default([]),
23972
23973
  metadata: exports_external.object({ branch: exports_external.string().optional() }).default({})
@@ -24033,6 +24034,7 @@ function buildInitialState(options) {
24033
24034
  engine: options.engine ?? "claude",
24034
24035
  model: options.model ?? "opus",
24035
24036
  manualTest: options.manualTest ?? false,
24037
+ createPr: options.createPr ?? false,
24036
24038
  createdAt: now,
24037
24039
  lastModified: now,
24038
24040
  metadata: { branch }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.16.5",
3
+ "version": "2.16.7",
4
4
  "description": "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
5
5
  "keywords": [
6
6
  "agent",