@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/README.md +93 -0
- package/dist/index.js +215 -136
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/templates/core-prompt.md +0 -16
- package/templates/skills/manage-tasks/SKILL.md +0 -3
- package/templates/workflow.md +4 -18
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
|
|
138
|
-
import { join as
|
|
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
|
-
//
|
|
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
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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 (!
|
|
380
|
+
if (!this.socketExists()) {
|
|
372
381
|
return false;
|
|
373
382
|
}
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
380
|
-
},
|
|
391
|
+
resolve2(false);
|
|
392
|
+
}, this.connectTimeout);
|
|
381
393
|
this.socket.on("connect", () => {
|
|
382
394
|
clearTimeout(timeout);
|
|
383
395
|
this.connected = true;
|
|
384
|
-
|
|
396
|
+
resolve2(true);
|
|
385
397
|
});
|
|
386
398
|
this.socket.on("error", () => {
|
|
387
399
|
clearTimeout(timeout);
|
|
388
400
|
this.socket = null;
|
|
389
|
-
|
|
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((
|
|
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
|
-
|
|
425
|
+
if (response.success && response.data !== void 0) {
|
|
426
|
+
resolve2(response.data);
|
|
412
427
|
} else {
|
|
413
|
-
|
|
428
|
+
resolve2(null);
|
|
414
429
|
}
|
|
415
430
|
} catch {
|
|
416
|
-
|
|
431
|
+
resolve2(null);
|
|
417
432
|
}
|
|
418
433
|
}
|
|
419
434
|
};
|
|
420
435
|
const onError = () => {
|
|
421
436
|
cleanup();
|
|
422
|
-
|
|
437
|
+
resolve2(null);
|
|
423
438
|
};
|
|
424
439
|
const timeout = setTimeout(() => {
|
|
425
440
|
cleanup();
|
|
426
|
-
|
|
427
|
-
},
|
|
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
|
|
464
|
-
|
|
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)
|
|
482
|
+
if (stopped)
|
|
483
|
+
return;
|
|
470
484
|
if (!client) {
|
|
471
|
-
client = new
|
|
485
|
+
client = new DaemonSocket({ cwd });
|
|
472
486
|
const connected = await client.connect();
|
|
473
487
|
if (!connected) {
|
|
474
|
-
|
|
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
|
-
|
|
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
|
|
592
|
+
const resolve2 = this.resolvers.shift();
|
|
565
593
|
log(`push() resolving pending next() call (${this.resolvers.length} resolvers remaining)`);
|
|
566
|
-
|
|
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
|
|
610
|
+
for (const resolve2 of this.resolvers) {
|
|
583
611
|
log(`close() resolving pending resolver with done=true`);
|
|
584
|
-
|
|
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((
|
|
624
|
-
this.resolvers.push(
|
|
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 {
|
|
717
|
-
import { join as
|
|
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 =
|
|
721
|
-
const
|
|
722
|
-
const
|
|
723
|
-
const
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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 =
|
|
1095
|
-
var todoFile5 =
|
|
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
|
|
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 =
|
|
1390
|
+
const todoExists = existsSync10(todoFile5);
|
|
1307
1391
|
setHasTodoFile(todoExists);
|
|
1308
|
-
const todoContent = todoExists ?
|
|
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
|
-
|
|
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
|
-
|
|
1367
|
-
|
|
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
|
-
|
|
1378
|
-
|
|
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
|
|
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 =
|
|
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
|
|
1749
|
-
import { join as
|
|
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 =
|
|
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
|
|
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 =
|
|
1915
|
-
const todoContent = todoExists ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1974
|
-
|
|
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
|
-
|
|
1984
|
-
|
|
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
|
-
|
|
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
|
|
2094
|
-
import { join as
|
|
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
|
|
2099
|
-
import { join as
|
|
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 =
|
|
2104
|
-
const destPath =
|
|
2105
|
-
const destDirPath =
|
|
2106
|
-
if (!
|
|
2107
|
-
|
|
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 (
|
|
2188
|
+
if (existsSync12(destPath)) {
|
|
2110
2189
|
result.skipped.push(dest);
|
|
2111
|
-
} else if (
|
|
2112
|
-
|
|
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 =
|
|
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 =
|
|
2130
|
-
const claudeDir =
|
|
2131
|
-
if (
|
|
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 =
|
|
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 =
|
|
2241
|
+
const gitignorePath = join14(process.cwd(), ".gitignore");
|
|
2163
2242
|
const eventsLogEntry = ".ralph/events-*.jsonl";
|
|
2164
|
-
if (
|
|
2165
|
-
const content =
|
|
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
|
|
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 =
|
|
2250
|
-
return
|
|
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
|
|
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((
|
|
2454
|
+
description = await new Promise((resolve2) => {
|
|
2376
2455
|
rl.question("Todo: ", (answer) => {
|
|
2377
2456
|
rl.close();
|
|
2378
|
-
|
|
2457
|
+
resolve2(answer.trim());
|
|
2379
2458
|
});
|
|
2380
2459
|
});
|
|
2381
2460
|
if (!description) {
|