@herbcaudill/ralph 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -134,8 +134,9 @@ var EnhancedTextInput = ({
134
134
  };
135
135
 
136
136
  // src/components/SessionRunner.tsx
137
- import { appendFileSync, writeFileSync as writeFileSync2, readFileSync as readFileSync5, existsSync as existsSync8 } from "fs";
138
- import { join as join10, basename } from "path";
137
+ import { appendFileSync, writeFileSync as writeFileSync2, readFileSync as readFileSync6, existsSync as existsSync10 } from "fs";
138
+ import { join as join11, basename } from "path";
139
+ import { randomUUID } from "crypto";
139
140
  import { query } from "@anthropic-ai/claude-agent-sdk";
140
141
 
141
142
  // src/lib/addTodo.ts
@@ -350,54 +351,68 @@ var ProgressBar = ({ completed, total, width = 12, repoName: repoName3 }) => {
350
351
  return /* @__PURE__ */ React2.createElement(Text2, null, repoName3 && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, repoName3), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " \u2502 ")), /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, filled), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, empty), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", completed, "/", total, " "));
351
352
  };
352
353
 
353
- // src/lib/beadsClient.ts
354
+ // ../../../beads-sdk/dist/exec.js
355
+ import { spawn } from "child_process";
356
+
357
+ // ../../../beads-sdk/dist/socket.js
354
358
  import { createConnection } from "net";
355
359
  import { join as join6 } from "path";
356
360
  import { existsSync as existsSync4 } from "fs";
357
- var SOCKET_PATH = join6(process.cwd(), ".beads", "bd.sock");
358
- var BeadsClient = class _BeadsClient {
359
- socket = null;
360
- connected = false;
361
- /**
362
- * Check if the beads daemon socket exists.
363
- */
364
- static socketExists() {
365
- return existsSync4(SOCKET_PATH);
366
- }
367
- /**
368
- * Connect to the beads daemon.
369
- */
361
+ var DaemonSocket = class {
362
+ constructor(options = {}) {
363
+ this.socket = null;
364
+ this.connected = false;
365
+ const cwd = options.cwd ?? process.cwd();
366
+ this.socketPath = join6(cwd, ".beads", "bd.sock");
367
+ this.connectTimeout = options.connectTimeout ?? 2e3;
368
+ this.requestTimeout = options.requestTimeout ?? 5e3;
369
+ }
370
+ /** Check if the beads daemon socket file exists. */
371
+ socketExists() {
372
+ return existsSync4(this.socketPath);
373
+ }
374
+ /** Check if connected to the daemon. */
375
+ get isConnected() {
376
+ return this.connected && this.socket !== null;
377
+ }
378
+ /** Connect to the beads daemon. Returns true if connected, false otherwise. */
370
379
  async connect() {
371
- if (!_BeadsClient.socketExists()) {
380
+ if (!this.socketExists()) {
372
381
  return false;
373
382
  }
374
- return new Promise((resolve) => {
375
- this.socket = createConnection(SOCKET_PATH);
383
+ if (this.connected && this.socket) {
384
+ return true;
385
+ }
386
+ return new Promise((resolve2) => {
387
+ this.socket = createConnection(this.socketPath);
376
388
  const timeout = setTimeout(() => {
377
389
  this.socket?.destroy();
378
390
  this.socket = null;
379
- resolve(false);
380
- }, 2e3);
391
+ resolve2(false);
392
+ }, this.connectTimeout);
381
393
  this.socket.on("connect", () => {
382
394
  clearTimeout(timeout);
383
395
  this.connected = true;
384
- resolve(true);
396
+ resolve2(true);
385
397
  });
386
398
  this.socket.on("error", () => {
387
399
  clearTimeout(timeout);
388
400
  this.socket = null;
389
- resolve(false);
401
+ this.connected = false;
402
+ resolve2(false);
403
+ });
404
+ this.socket.on("close", () => {
405
+ this.socket = null;
406
+ this.connected = false;
390
407
  });
391
408
  });
392
409
  }
393
- /**
394
- * Send an RPC request and wait for response.
395
- */
410
+ /** Send an RPC request and wait for response. */
396
411
  async execute(operation, args = {}) {
397
412
  if (!this.socket || !this.connected) {
398
413
  return null;
399
414
  }
400
- return new Promise((resolve) => {
415
+ return new Promise((resolve2) => {
401
416
  const request = { operation, args };
402
417
  const requestLine = JSON.stringify(request) + "\n";
403
418
  let responseData = "";
@@ -407,24 +422,24 @@ var BeadsClient = class _BeadsClient {
407
422
  cleanup();
408
423
  try {
409
424
  const response = JSON.parse(responseData.trim());
410
- if (response.success && response.data) {
411
- resolve(response.data);
425
+ if (response.success && response.data !== void 0) {
426
+ resolve2(response.data);
412
427
  } else {
413
- resolve(null);
428
+ resolve2(null);
414
429
  }
415
430
  } catch {
416
- resolve(null);
431
+ resolve2(null);
417
432
  }
418
433
  }
419
434
  };
420
435
  const onError = () => {
421
436
  cleanup();
422
- resolve(null);
437
+ resolve2(null);
423
438
  };
424
439
  const timeout = setTimeout(() => {
425
440
  cleanup();
426
- resolve(null);
427
- }, 5e3);
441
+ resolve2(null);
442
+ }, this.requestTimeout);
428
443
  const cleanup = () => {
429
444
  clearTimeout(timeout);
430
445
  this.socket?.off("data", onData);
@@ -437,21 +452,18 @@ var BeadsClient = class _BeadsClient {
437
452
  }
438
453
  /**
439
454
  * Get mutations since a given timestamp.
455
+ * Returns all mutation events that occurred after the specified timestamp.
440
456
  */
441
457
  async getMutations(since = 0) {
442
458
  const result = await this.execute("get_mutations", { since });
443
459
  return result ?? [];
444
460
  }
445
- /**
446
- * Get ready issues (no blockers).
447
- */
461
+ /** Get ready issues (open and unblocked). */
448
462
  async getReady() {
449
463
  const result = await this.execute("ready", {});
450
464
  return result ?? [];
451
465
  }
452
- /**
453
- * Close the connection.
454
- */
466
+ /** Close the connection to the daemon. */
455
467
  close() {
456
468
  if (this.socket) {
457
469
  this.socket.destroy();
@@ -460,27 +472,29 @@ var BeadsClient = class _BeadsClient {
460
472
  }
461
473
  }
462
474
  };
463
- function watchForNewIssues(onNewIssue, interval = 5e3) {
464
- let lastTimestamp = Date.now();
475
+ function watchMutations(onMutation, options = {}) {
476
+ const { cwd, interval = 1e3, since } = options;
477
+ let lastTimestamp = since ?? Date.now();
465
478
  let client = null;
466
479
  let timeoutId = null;
467
480
  let stopped = false;
468
481
  const poll = async () => {
469
- if (stopped) return;
482
+ if (stopped)
483
+ return;
470
484
  if (!client) {
471
- client = new BeadsClient();
485
+ client = new DaemonSocket({ cwd });
472
486
  const connected = await client.connect();
473
487
  if (!connected) {
474
- timeoutId = setTimeout(poll, interval);
488
+ if (!stopped) {
489
+ timeoutId = setTimeout(poll, interval);
490
+ }
475
491
  return;
476
492
  }
477
493
  }
478
494
  try {
479
495
  const mutations = await client.getMutations(lastTimestamp);
480
496
  for (const mutation of mutations) {
481
- if (mutation.Type === "create") {
482
- onNewIssue(mutation);
483
- }
497
+ onMutation(mutation);
484
498
  const mutationTime = new Date(mutation.Timestamp).getTime();
485
499
  if (mutationTime > lastTimestamp) {
486
500
  lastTimestamp = mutationTime;
@@ -499,11 +513,25 @@ function watchForNewIssues(onNewIssue, interval = 5e3) {
499
513
  stopped = true;
500
514
  if (timeoutId) {
501
515
  clearTimeout(timeoutId);
516
+ timeoutId = null;
502
517
  }
503
518
  client?.close();
519
+ client = null;
504
520
  };
505
521
  }
506
522
 
523
+ // src/lib/beadsClient.ts
524
+ function watchForNewIssues(onNewIssue, interval = 5e3) {
525
+ return watchMutations(
526
+ (event) => {
527
+ if (event.Type === "create") {
528
+ onNewIssue(event);
529
+ }
530
+ },
531
+ { interval }
532
+ );
533
+ }
534
+
507
535
  // src/lib/debug.ts
508
536
  var isDebugEnabled = (namespace) => {
509
537
  const debugEnv = process.env.RALPH_DEBUG;
@@ -561,9 +589,9 @@ var MessageQueue = class {
561
589
  return;
562
590
  }
563
591
  if (this.resolvers.length > 0) {
564
- const resolve = this.resolvers.shift();
592
+ const resolve2 = this.resolvers.shift();
565
593
  log(`push() resolving pending next() call (${this.resolvers.length} resolvers remaining)`);
566
- resolve({ value: message, done: false });
594
+ resolve2({ value: message, done: false });
567
595
  } else {
568
596
  this.queue.push(message);
569
597
  log(`push() added to queue (queue length: ${this.queue.length})`);
@@ -579,9 +607,9 @@ var MessageQueue = class {
579
607
  }
580
608
  log(`close() called - resolving ${this.resolvers.length} pending resolvers`);
581
609
  this.closed = true;
582
- for (const resolve of this.resolvers) {
610
+ for (const resolve2 of this.resolvers) {
583
611
  log(`close() resolving pending resolver with done=true`);
584
- resolve({ value: void 0, done: true });
612
+ resolve2({ value: void 0, done: true });
585
613
  }
586
614
  this.resolvers = [];
587
615
  log(`close() complete`);
@@ -620,8 +648,8 @@ var MessageQueue = class {
620
648
  log(
621
649
  `next() #${callId}: queue empty, creating pending resolver (${this.resolvers.length + 1} total)`
622
650
  );
623
- return new Promise((resolve) => {
624
- this.resolvers.push(resolve);
651
+ return new Promise((resolve2) => {
652
+ this.resolvers.push(resolve2);
625
653
  });
626
654
  }
627
655
  };
@@ -713,26 +741,82 @@ function parseTaskLifecycleEvent(text) {
713
741
  }
714
742
 
715
743
  // src/lib/getPromptContent.ts
716
- import { readFileSync as readFileSync4, existsSync as existsSync7 } from "fs";
717
- import { join as join9, dirname } from "path";
744
+ import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
745
+ import { join as join10, dirname as dirname3 } from "path";
718
746
  import { fileURLToPath } from "url";
747
+
748
+ // ../shared/dist/prompts/loadPrompt.js
749
+ import { existsSync as existsSync8, readFileSync as readFileSync4, copyFileSync, mkdirSync as mkdirSync2 } from "fs";
750
+ import { join as join9, dirname as dirname2 } from "path";
751
+
752
+ // ../shared/dist/prompts/getWorkspaceRoot.js
753
+ import { existsSync as existsSync7 } from "fs";
754
+ import { dirname, resolve } from "path";
755
+ function getWorkspaceRoot(cwd = process.cwd()) {
756
+ const start = resolve(cwd);
757
+ let current = start;
758
+ while (true) {
759
+ const gitPath = resolve(current, ".git");
760
+ if (existsSync7(gitPath)) {
761
+ return current;
762
+ }
763
+ const parent = dirname(current);
764
+ if (parent === current) {
765
+ return start;
766
+ }
767
+ current = parent;
768
+ }
769
+ }
770
+
771
+ // ../shared/dist/prompts/loadPrompt.js
772
+ var WORKFLOW_PLACEHOLDER = "{WORKFLOW}";
773
+ function loadSessionPrompt(options) {
774
+ const { templatesDir, cwd = process.cwd() } = options;
775
+ const workspaceRoot = getWorkspaceRoot(cwd);
776
+ const corePromptPath = join9(templatesDir, "core-prompt.md");
777
+ if (!existsSync8(corePromptPath)) {
778
+ throw new Error(`Core prompt not found at ${corePromptPath}`);
779
+ }
780
+ const corePrompt = readFileSync4(corePromptPath, "utf-8");
781
+ const customWorkflowPath = join9(workspaceRoot, ".ralph", "workflow.md");
782
+ const defaultWorkflowPath = join9(templatesDir, "workflow.md");
783
+ let workflowContent;
784
+ let hasCustomWorkflow2;
785
+ let workflowPath;
786
+ if (existsSync8(customWorkflowPath)) {
787
+ workflowContent = readFileSync4(customWorkflowPath, "utf-8");
788
+ hasCustomWorkflow2 = true;
789
+ workflowPath = customWorkflowPath;
790
+ } else if (existsSync8(defaultWorkflowPath)) {
791
+ workflowContent = readFileSync4(defaultWorkflowPath, "utf-8");
792
+ hasCustomWorkflow2 = false;
793
+ workflowPath = defaultWorkflowPath;
794
+ } else {
795
+ throw new Error(`Workflow file not found at ${customWorkflowPath} or ${defaultWorkflowPath}`);
796
+ }
797
+ const content = corePrompt.replace(WORKFLOW_PLACEHOLDER, workflowContent);
798
+ return {
799
+ content,
800
+ hasCustomWorkflow: hasCustomWorkflow2,
801
+ workflowPath
802
+ };
803
+ }
804
+
805
+ // src/lib/getPromptContent.ts
719
806
  var getPromptContent = () => {
720
- const __dirname = dirname(fileURLToPath(import.meta.url));
721
- const ralphDir6 = join9(process.cwd(), ".ralph");
722
- const promptFile = join9(ralphDir6, "prompt.md");
723
- const todoFile7 = join9(ralphDir6, "todo.md");
724
- const beadsDir3 = join9(process.cwd(), ".beads");
725
- const templatesDir = join9(__dirname, "..", "..", "templates");
726
- if (existsSync7(promptFile)) {
727
- return readFileSync4(promptFile, "utf-8");
728
- }
729
- const useBeadsTemplate = existsSync7(beadsDir3) || !existsSync7(todoFile7);
730
- const templateFile = useBeadsTemplate ? "prompt-beads.md" : "prompt-todos.md";
731
- const templatePath = join9(templatesDir, templateFile);
732
- if (existsSync7(templatePath)) {
733
- return readFileSync4(templatePath, "utf-8");
734
- }
735
- return "Work on the highest-priority task.";
807
+ const __dirname = dirname3(fileURLToPath(import.meta.url));
808
+ const workspaceRoot = getWorkspaceRoot(process.cwd());
809
+ const ralphDir6 = join10(workspaceRoot, ".ralph");
810
+ const promptFile = join10(ralphDir6, "prompt.md");
811
+ const templatesDir = join10(__dirname, "..", "..", "templates");
812
+ if (existsSync9(promptFile)) {
813
+ return readFileSync5(promptFile, "utf-8");
814
+ }
815
+ const { content } = loadSessionPrompt({
816
+ templatesDir,
817
+ cwd: workspaceRoot
818
+ });
819
+ return content;
736
820
  };
737
821
 
738
822
  // src/lib/sdkMessageToEvent.ts
@@ -1091,8 +1175,8 @@ var renderStaticItem = (item) => {
1091
1175
 
1092
1176
  // src/components/SessionRunner.tsx
1093
1177
  var log2 = createDebugLogger("session");
1094
- var ralphDir5 = join10(process.cwd(), ".ralph");
1095
- var todoFile5 = join10(ralphDir5, "todo.md");
1178
+ var ralphDir5 = join11(process.cwd(), ".ralph");
1179
+ var todoFile5 = join11(ralphDir5, "todo.md");
1096
1180
  var repoName = basename(process.cwd());
1097
1181
  var SessionRunner = ({
1098
1182
  totalSessions,
@@ -1129,7 +1213,7 @@ var SessionRunner = ({
1129
1213
  const [isPaused, setIsPaused] = useState3(false);
1130
1214
  const isPausedRef = useRef(false);
1131
1215
  const [currentTaskId, setCurrentTaskId] = useState3(null);
1132
- const [currentTaskTitle, setCurrentTaskTitle] = useState3(null);
1216
+ const sessionIdRef = useRef(null);
1133
1217
  const [staticItems, setStaticItems] = useState3([
1134
1218
  { type: "header", claudeVersion, ralphVersion, key: "header" }
1135
1219
  ]);
@@ -1303,9 +1387,9 @@ var SessionRunner = ({
1303
1387
  const logFile = logFileRef.current;
1304
1388
  setEvents([]);
1305
1389
  const promptContent = getPromptContent();
1306
- const todoExists = existsSync8(todoFile5);
1390
+ const todoExists = existsSync10(todoFile5);
1307
1391
  setHasTodoFile(todoExists);
1308
- const todoContent = todoExists ? readFileSync5(todoFile5, "utf-8") : "";
1392
+ const todoContent = todoExists ? readFileSync6(todoFile5, "utf-8") : "";
1309
1393
  const roundHeader = `# Ralph, round ${currentSession}
1310
1394
 
1311
1395
  `;
@@ -1316,6 +1400,8 @@ var SessionRunner = ({
1316
1400
  ${todoContent}` : `${roundHeader}${promptContent}`;
1317
1401
  const abortController = new AbortController();
1318
1402
  setIsRunning(true);
1403
+ const sessionId = randomUUID();
1404
+ sessionIdRef.current = sessionId;
1319
1405
  const messageQueue = new MessageQueue();
1320
1406
  messageQueueRef.current = messageQueue;
1321
1407
  messageQueue.push(createUserMessage(fullPrompt));
@@ -1356,26 +1442,21 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
1356
1442
  if (taskInfo) {
1357
1443
  if (taskInfo.action === "starting") {
1358
1444
  setCurrentTaskId(taskInfo.taskId ?? null);
1359
- setCurrentTaskTitle(taskInfo.taskTitle ?? null);
1360
- log2(
1361
- `Task started: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
1362
- );
1445
+ log2(`Task started: ${taskInfo.taskId}`);
1363
1446
  const taskStartedEvent = {
1364
1447
  type: "ralph_task_started",
1365
1448
  taskId: taskInfo.taskId,
1366
- taskTitle: taskInfo.taskTitle,
1367
- session: currentSession
1449
+ session: currentSession,
1450
+ sessionId: sessionIdRef.current
1368
1451
  };
1369
1452
  appendFileSync(logFile, JSON.stringify(taskStartedEvent) + "\n");
1370
1453
  } else if (taskInfo.action === "completed") {
1371
- log2(
1372
- `Task completed: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
1373
- );
1454
+ log2(`Task completed: ${taskInfo.taskId}`);
1374
1455
  const taskCompletedEvent = {
1375
1456
  type: "ralph_task_completed",
1376
1457
  taskId: taskInfo.taskId,
1377
- taskTitle: taskInfo.taskTitle,
1378
- session: currentSession
1458
+ session: currentSession,
1459
+ sessionId: sessionIdRef.current
1379
1460
  };
1380
1461
  appendFileSync(logFile, JSON.stringify(taskCompletedEvent) + "\n");
1381
1462
  }
@@ -1472,7 +1553,7 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
1472
1553
  // src/components/ReplayLog.tsx
1473
1554
  import React8, { useState as useState5, useEffect as useEffect5 } from "react";
1474
1555
  import { Box as Box6, Text as Text8, useApp as useApp2 } from "ink";
1475
- import { readFileSync as readFileSync6 } from "fs";
1556
+ import { readFileSync as readFileSync7 } from "fs";
1476
1557
 
1477
1558
  // src/components/EventDisplay.tsx
1478
1559
  import React6, { useMemo, useState as useState4, useEffect as useEffect4, useRef as useRef2 } from "react";
@@ -1711,7 +1792,7 @@ var ReplayLog = ({
1711
1792
  const [error, setError] = useState5();
1712
1793
  useEffect5(() => {
1713
1794
  try {
1714
- const content = readFileSync6(filePath, "utf-8");
1795
+ const content = readFileSync7(filePath, "utf-8");
1715
1796
  const eventStrings = content.split(/\n\n+/).filter((s) => s.trim());
1716
1797
  const parsedEvents = [];
1717
1798
  for (const eventStr of eventStrings) {
@@ -1745,8 +1826,9 @@ var ReplayLog = ({
1745
1826
  // src/components/JsonOutput.tsx
1746
1827
  import React9, { useState as useState6, useEffect as useEffect6, useRef as useRef3 } from "react";
1747
1828
  import { useApp as useApp3, Text as Text9 } from "ink";
1748
- import { writeFileSync as writeFileSync3, readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
1749
- import { join as join11, basename as basename2 } from "path";
1829
+ import { writeFileSync as writeFileSync3, readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
1830
+ import { join as join12, basename as basename2 } from "path";
1831
+ import { randomUUID as randomUUID2 } from "crypto";
1750
1832
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
1751
1833
 
1752
1834
  // src/lib/parseStdinCommand.ts
@@ -1838,7 +1920,7 @@ var outputEvent = (event) => {
1838
1920
 
1839
1921
  // src/components/JsonOutput.tsx
1840
1922
  var log5 = createDebugLogger("session");
1841
- var todoFile6 = join11(process.cwd(), ".ralph", "todo.md");
1923
+ var todoFile6 = join12(process.cwd(), ".ralph", "todo.md");
1842
1924
  var repoName2 = basename2(process.cwd());
1843
1925
  var JsonOutput = ({ totalSessions, agent }) => {
1844
1926
  const { exit } = useApp3();
@@ -1854,7 +1936,7 @@ var JsonOutput = ({ totalSessions, agent }) => {
1854
1936
  const isPausedRef = useRef3(false);
1855
1937
  const stdinCleanupRef = useRef3(null);
1856
1938
  const currentTaskIdRef = useRef3(null);
1857
- const currentTaskTitleRef = useRef3(null);
1939
+ const sessionIdRef = useRef3(null);
1858
1940
  useEffect6(() => {
1859
1941
  stopAfterCurrentRef.current = stopAfterCurrent;
1860
1942
  }, [stopAfterCurrent]);
@@ -1911,8 +1993,8 @@ var JsonOutput = ({ totalSessions, agent }) => {
1911
1993
  writeFileSync3(logFileRef.current, "");
1912
1994
  }
1913
1995
  const promptContent = getPromptContent();
1914
- const todoExists = existsSync9(todoFile6);
1915
- const todoContent = todoExists ? readFileSync7(todoFile6, "utf-8") : "";
1996
+ const todoExists = existsSync11(todoFile6);
1997
+ const todoContent = todoExists ? readFileSync8(todoFile6, "utf-8") : "";
1916
1998
  const fullPrompt = todoContent ? `${promptContent}
1917
1999
 
1918
2000
  ## Current Todo List
@@ -1920,13 +2002,15 @@ var JsonOutput = ({ totalSessions, agent }) => {
1920
2002
  ${todoContent}` : promptContent;
1921
2003
  const abortController = new AbortController();
1922
2004
  setIsRunning(true);
2005
+ const sessionId = randomUUID2();
2006
+ sessionIdRef.current = sessionId;
1923
2007
  outputEvent({
1924
2008
  type: "ralph_session_start",
1925
2009
  session: currentSession,
1926
2010
  totalSessions,
1927
2011
  repo: repoName2,
1928
2012
  taskId: currentTaskIdRef.current,
1929
- taskTitle: currentTaskTitleRef.current
2013
+ sessionId
1930
2014
  });
1931
2015
  const messageQueue = new MessageQueue();
1932
2016
  messageQueueRef.current = messageQueue;
@@ -1963,25 +2047,20 @@ ${todoContent}` : promptContent;
1963
2047
  if (taskInfo) {
1964
2048
  if (taskInfo.action === "starting") {
1965
2049
  currentTaskIdRef.current = taskInfo.taskId ?? null;
1966
- currentTaskTitleRef.current = taskInfo.taskTitle ?? null;
1967
- log5(
1968
- `Task started: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
1969
- );
2050
+ log5(`Task started: ${taskInfo.taskId}`);
1970
2051
  outputEvent({
1971
2052
  type: "ralph_task_started",
1972
2053
  taskId: taskInfo.taskId,
1973
- taskTitle: taskInfo.taskTitle,
1974
- session: currentSession
2054
+ session: currentSession,
2055
+ sessionId: sessionIdRef.current
1975
2056
  });
1976
2057
  } else if (taskInfo.action === "completed") {
1977
- log5(
1978
- `Task completed: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
1979
- );
2058
+ log5(`Task completed: ${taskInfo.taskId}`);
1980
2059
  outputEvent({
1981
2060
  type: "ralph_task_completed",
1982
2061
  taskId: taskInfo.taskId,
1983
- taskTitle: taskInfo.taskTitle,
1984
- session: currentSession
2062
+ session: currentSession,
2063
+ sessionId: sessionIdRef.current
1985
2064
  });
1986
2065
  }
1987
2066
  }
@@ -2005,7 +2084,7 @@ ${todoContent}` : promptContent;
2005
2084
  type: "ralph_session_end",
2006
2085
  session: currentSession,
2007
2086
  taskId: currentTaskIdRef.current,
2008
- taskTitle: currentTaskTitleRef.current
2087
+ sessionId: sessionIdRef.current
2009
2088
  });
2010
2089
  if (stopAfterCurrentRef.current) {
2011
2090
  log5(`Stop after current requested - exiting gracefully`);
@@ -2090,26 +2169,26 @@ var App = ({
2090
2169
  // src/components/InitRalph.tsx
2091
2170
  import { Text as Text10, Box as Box7 } from "ink";
2092
2171
  import React11, { useEffect as useEffect7, useState as useState7 } from "react";
2093
- import { existsSync as existsSync11, readFileSync as readFileSync8, appendFileSync as appendFileSync2, writeFileSync as writeFileSync4 } from "fs";
2094
- import { join as join13, dirname as dirname3 } from "path";
2172
+ import { existsSync as existsSync13, readFileSync as readFileSync9, appendFileSync as appendFileSync2, writeFileSync as writeFileSync4 } from "fs";
2173
+ import { join as join14, dirname as dirname5 } from "path";
2095
2174
  import { fileURLToPath as fileURLToPath2 } from "url";
2096
2175
 
2097
2176
  // src/lib/copyTemplates.ts
2098
- import { existsSync as existsSync10, mkdirSync as mkdirSync2, copyFileSync } from "fs";
2099
- import { join as join12, dirname as dirname2 } from "path";
2177
+ import { existsSync as existsSync12, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2 } from "fs";
2178
+ import { join as join13, dirname as dirname4 } from "path";
2100
2179
  function copyTemplates(templatesDir, destDir, files) {
2101
2180
  const result = { created: [], skipped: [], errors: [] };
2102
2181
  for (const { src, dest } of files) {
2103
- const srcPath = join12(templatesDir, src);
2104
- const destPath = join12(destDir, dest);
2105
- const destDirPath = dirname2(destPath);
2106
- if (!existsSync10(destDirPath)) {
2107
- mkdirSync2(destDirPath, { recursive: true });
2182
+ const srcPath = join13(templatesDir, src);
2183
+ const destPath = join13(destDir, dest);
2184
+ const destDirPath = dirname4(destPath);
2185
+ if (!existsSync12(destDirPath)) {
2186
+ mkdirSync3(destDirPath, { recursive: true });
2108
2187
  }
2109
- if (existsSync10(destPath)) {
2188
+ if (existsSync12(destPath)) {
2110
2189
  result.skipped.push(dest);
2111
- } else if (existsSync10(srcPath)) {
2112
- copyFileSync(srcPath, destPath);
2190
+ } else if (existsSync12(srcPath)) {
2191
+ copyFileSync2(srcPath, destPath);
2113
2192
  result.created.push(dest);
2114
2193
  } else {
2115
2194
  result.errors.push(`Template not found: ${src}`);
@@ -2120,20 +2199,20 @@ function copyTemplates(templatesDir, destDir, files) {
2120
2199
 
2121
2200
  // src/components/InitRalph.tsx
2122
2201
  function InitRalph() {
2123
- const __dirname = dirname3(fileURLToPath2(import.meta.url));
2202
+ const __dirname = dirname5(fileURLToPath2(import.meta.url));
2124
2203
  const [status, setStatus] = useState7("checking");
2125
2204
  const [createdFiles, setCreatedFiles] = useState7([]);
2126
2205
  const [skippedFiles, setSkippedFiles] = useState7([]);
2127
2206
  const [errors, setErrors] = useState7([]);
2128
2207
  useEffect7(() => {
2129
- const ralphDir6 = join13(process.cwd(), ".ralph");
2130
- const claudeDir = join13(process.cwd(), ".claude");
2131
- if (existsSync11(join13(ralphDir6, "workflow.md"))) {
2208
+ const ralphDir6 = join14(process.cwd(), ".ralph");
2209
+ const claudeDir = join14(process.cwd(), ".claude");
2210
+ if (existsSync13(join14(ralphDir6, "workflow.md"))) {
2132
2211
  setStatus("exists");
2133
2212
  return;
2134
2213
  }
2135
2214
  const initialize = async () => {
2136
- const templatesDir = join13(__dirname, "..", "..", "templates");
2215
+ const templatesDir = join14(__dirname, "..", "..", "templates");
2137
2216
  setStatus("creating");
2138
2217
  try {
2139
2218
  const allCreated = [];
@@ -2159,10 +2238,10 @@ function InitRalph() {
2159
2238
  allCreated.push(...agentsResult.created.map((f) => `.claude/${f}`));
2160
2239
  allSkipped.push(...agentsResult.skipped.map((f) => `.claude/${f}`));
2161
2240
  allErrors.push(...agentsResult.errors);
2162
- const gitignorePath = join13(process.cwd(), ".gitignore");
2241
+ const gitignorePath = join14(process.cwd(), ".gitignore");
2163
2242
  const eventsLogEntry = ".ralph/events-*.jsonl";
2164
- if (existsSync11(gitignorePath)) {
2165
- const content = readFileSync8(gitignorePath, "utf-8");
2243
+ if (existsSync13(gitignorePath)) {
2244
+ const content = readFileSync9(gitignorePath, "utf-8");
2166
2245
  if (!content.includes(eventsLogEntry)) {
2167
2246
  const newline = content.endsWith("\n") ? "" : "\n";
2168
2247
  appendFileSync2(gitignorePath, `${newline}${eventsLogEntry}
@@ -2240,20 +2319,20 @@ var getDefaultSessions = () => {
2240
2319
  };
2241
2320
 
2242
2321
  // src/lib/getLatestLogFile.ts
2243
- import { join as join14 } from "path";
2322
+ import { join as join15 } from "path";
2244
2323
  var getLatestLogFile = () => {
2245
2324
  const maxNumber = findMaxLogNumber();
2246
2325
  if (maxNumber === 0) {
2247
2326
  return void 0;
2248
2327
  }
2249
- const ralphDir6 = join14(process.cwd(), ".ralph");
2250
- return join14(ralphDir6, `events-${maxNumber}.jsonl`);
2328
+ const ralphDir6 = join15(process.cwd(), ".ralph");
2329
+ return join15(ralphDir6, `events-${maxNumber}.jsonl`);
2251
2330
  };
2252
2331
 
2253
2332
  // package.json
2254
2333
  var package_default = {
2255
2334
  name: "@herbcaudill/ralph",
2256
- version: "1.0.2",
2335
+ version: "1.1.0",
2257
2336
  description: "Autonomous AI session engine for Claude CLI",
2258
2337
  type: "module",
2259
2338
  main: "./dist/index.js",
@@ -2271,7 +2350,6 @@ var package_default = {
2271
2350
  dev: "tsc --watch",
2272
2351
  typecheck: "tsc --noEmit",
2273
2352
  ralph: "tsx src/index.ts",
2274
- "test:all": "pnpm typecheck && vitest run",
2275
2353
  test: "vitest run",
2276
2354
  "test:e2e": "vitest --config vitest.e2e.config.ts",
2277
2355
  "test:watch": "vitest --watch",
@@ -2306,6 +2384,7 @@ var package_default = {
2306
2384
  react: "^19.2.3"
2307
2385
  },
2308
2386
  devDependencies: {
2387
+ "@herbcaudill/beads-sdk": "link:../../../beads-sdk",
2309
2388
  "@herbcaudill/ralph-shared": "workspace:*",
2310
2389
  "@types/node": "^24.10.1",
2311
2390
  "@types/react": "^19.2.8",
@@ -2372,10 +2451,10 @@ program.command("todo [description...]").description("add a todo item and commit
2372
2451
  input: process.stdin,
2373
2452
  output: process.stdout
2374
2453
  });
2375
- description = await new Promise((resolve) => {
2454
+ description = await new Promise((resolve2) => {
2376
2455
  rl.question("Todo: ", (answer) => {
2377
2456
  rl.close();
2378
- resolve(answer.trim());
2457
+ resolve2(answer.trim());
2379
2458
  });
2380
2459
  });
2381
2460
  if (!description) {