@felixthemangy/ralph-loop 0.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 ADDED
@@ -0,0 +1,70 @@
1
+ # ralph
2
+
3
+ > The runtime that replaces you as the person who prompts the agent.
4
+
5
+ `ralph` reads your `RALPH.md` task file and gives agents their next brief — so you don't have to copy-paste it yourself.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install -g ralph-loop
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```sh
16
+ ralph init # scaffold a RALPH.md in the current repo
17
+ ralph status # show ACTIVE / NEXT / DONE
18
+ ralph next # print the first unblocked task brief
19
+ ralph done <id> # move a task to DONE and print the next brief
20
+ ralph brief <id> # print a specific task's brief without marking it done
21
+ ```
22
+
23
+ ## RALPH.md format
24
+
25
+ ```markdown
26
+ ## ACTIVE
27
+
28
+ *(none)*
29
+
30
+ ---
31
+
32
+ ## NEXT
33
+
34
+ #### task-id — Task title
35
+ **Brief:** What the agent should do.
36
+ **Blocked by:** nothing
37
+
38
+ #### another-task — Another task
39
+ **Brief:** What the agent should do.
40
+ **Blocked by:** task-id
41
+
42
+ ---
43
+
44
+ ## DONE
45
+
46
+ | Task ID | Title | Completed |
47
+ |---------|-------|-----------|
48
+ ```
49
+
50
+ - Tasks under `## NEXT` are `####` headings with a `**Brief:**` and `**Blocked by:**` line.
51
+ - A task is blocked if `**Blocked by:**` is anything other than `nothing` (case-insensitive).
52
+ - `ralph next` returns the first unblocked task.
53
+ - `ralph done <id>` removes the task from NEXT and appends a row to the DONE table.
54
+ - `ralph` walks up from the current directory to find `RALPH.md`, so you can run it from any subdirectory of the repo.
55
+
56
+ ## Multi-agent loop
57
+
58
+ ```sh
59
+ # Agent calls this at the start of each session
60
+ ralph next
61
+
62
+ # Agent calls this when work is complete
63
+ ralph done <task-id>
64
+ ```
65
+
66
+ No human needed in the loop — `ralph done` automatically prints the next brief so the agent can immediately continue.
67
+
68
+ ## License
69
+
70
+ MIT
@@ -0,0 +1,2 @@
1
+ export declare function cmdBrief(taskId: string): void;
2
+ //# sourceMappingURL=brief.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../src/commands/brief.ts"],"names":[],"mappings":"AAIA,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiB7C"}
@@ -0,0 +1,19 @@
1
+ import chalk from 'chalk';
2
+ import { findRalphFile, readRalph, findTaskById } from '../parser.js';
3
+ import { printTaskBrief } from './next.js';
4
+ export function cmdBrief(taskId) {
5
+ const filePath = findRalphFile();
6
+ if (!filePath) {
7
+ console.error(chalk.red('No RALPH.md found. Run ralph init to create one.'));
8
+ process.exit(1);
9
+ }
10
+ const doc = readRalph(filePath);
11
+ const task = findTaskById(doc.tasks, taskId);
12
+ if (!task) {
13
+ console.error(chalk.red(`No task matching "${taskId}" found in NEXT.`));
14
+ console.error(chalk.dim(' Available: ' + doc.tasks.map(t => t.id).join(', ')));
15
+ process.exit(1);
16
+ }
17
+ printTaskBrief(task);
18
+ }
19
+ //# sourceMappingURL=brief.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.js","sourceRoot":"","sources":["../../src/commands/brief.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAE7C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,MAAM,kBAAkB,CAAC,CAAC,CAAC;QACxE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,cAAc,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdDone(taskId: string): void;
2
+ //# sourceMappingURL=done.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"done.d.ts","sourceRoot":"","sources":["../../src/commands/done.ts"],"names":[],"mappings":"AAKA,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAkC5C"}
@@ -0,0 +1,36 @@
1
+ import chalk from 'chalk';
2
+ import { findRalphFile, readRalph, findTaskById, getFirstUnblockedTask } from '../parser.js';
3
+ import { moveTaskToDone, writeRalph } from '../writer.js';
4
+ import { printTaskBrief } from './next.js';
5
+ export function cmdDone(taskId) {
6
+ const filePath = findRalphFile();
7
+ if (!filePath) {
8
+ console.error(chalk.red('No RALPH.md found. Run ralph init to create one.'));
9
+ process.exit(1);
10
+ }
11
+ const doc = readRalph(filePath);
12
+ const task = findTaskById(doc.tasks, taskId);
13
+ if (!task) {
14
+ console.error(chalk.red(`No task matching "${taskId}" found in NEXT.`));
15
+ console.error(chalk.dim(' Available: ' + doc.tasks.map(t => t.id).join(', ')));
16
+ process.exit(1);
17
+ }
18
+ // Move to DONE and write back
19
+ const updated = moveTaskToDone(doc, task);
20
+ writeRalph(filePath, updated);
21
+ console.log(chalk.green('✓') + ` Marked done: ${chalk.bold(task.title)}`);
22
+ // Print the next unblocked task automatically
23
+ const updatedDoc = readRalph(filePath);
24
+ const nextTask = getFirstUnblockedTask(updatedDoc.tasks);
25
+ if (nextTask) {
26
+ console.log(chalk.dim('\n Next up:'));
27
+ printTaskBrief(nextTask);
28
+ }
29
+ else if (updatedDoc.tasks.length === 0) {
30
+ console.log(chalk.green('\n 🎉 NEXT is empty — all tasks complete!\n'));
31
+ }
32
+ else {
33
+ console.log(chalk.yellow('\n All remaining tasks are blocked. Update dependencies in RALPH.md.\n'));
34
+ }
35
+ }
36
+ //# sourceMappingURL=done.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"done.js","sourceRoot":"","sources":["../../src/commands/done.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,UAAU,OAAO,CAAC,MAAc;IACpC,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAE7C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,MAAM,kBAAkB,CAAC,CAAC,CAAC;QACxE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,8BAA8B;IAC9B,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1C,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,iBAAiB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAE1E,8CAA8C;IAC9C,MAAM,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,QAAQ,GAAG,qBAAqB,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAEzD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC;QACvC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;SAAM,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC,CAAC;IAC3E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,yEAAyE,CAAC,CAAC,CAAC;IACvG,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdInit(): void;
2
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAOA,wBAAgB,OAAO,IAAI,IAAI,CAc9B"}
@@ -0,0 +1,18 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import chalk from 'chalk';
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ export function cmdInit() {
7
+ const dest = path.join(process.cwd(), 'RALPH.md');
8
+ if (fs.existsSync(dest)) {
9
+ console.log(chalk.yellow('RALPH.md already exists. Remove it first if you want to re-scaffold.'));
10
+ process.exit(1);
11
+ }
12
+ const templatePath = path.join(__dirname, '..', 'templates', 'ralph-template.md');
13
+ const template = fs.readFileSync(templatePath, 'utf-8');
14
+ fs.writeFileSync(dest, template, 'utf-8');
15
+ console.log(chalk.green('✓') + ' Created RALPH.md');
16
+ console.log(chalk.dim(' Edit it, then run: ralph next'));
17
+ }
18
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,UAAU,OAAO;IACrB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC;IAElD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sEAAsE,CAAC,CAAC,CAAC;QAClG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAClF,MAAM,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACxD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE1C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,mBAAmB,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare function printTaskBrief(task: {
2
+ id: string;
3
+ title: string;
4
+ brief: string;
5
+ blockedBy: string;
6
+ }): void;
7
+ export declare function cmdNext(): void;
8
+ //# sourceMappingURL=next.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/commands/next.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAU1G;AAED,wBAAgB,OAAO,IAAI,IAAI,CAwB9B"}
@@ -0,0 +1,37 @@
1
+ import chalk from 'chalk';
2
+ import { findRalphFile, readRalph, getFirstUnblockedTask } from '../parser.js';
3
+ export function printTaskBrief(task) {
4
+ console.log(chalk.bold.cyan('\n┌─ BRIEF ──────────────────────────────────────────'));
5
+ console.log(chalk.bold(`│ ${task.title}`));
6
+ console.log(chalk.cyan('├──────────────────────────────────────────────────'));
7
+ const briefLines = task.brief.split('\n');
8
+ for (const line of briefLines) {
9
+ console.log('│ ' + line);
10
+ }
11
+ console.log(chalk.cyan('└──────────────────────────────────────────────────'));
12
+ console.log(chalk.dim(`\n When done: ralph done ${task.id}\n`));
13
+ }
14
+ export function cmdNext() {
15
+ const filePath = findRalphFile();
16
+ if (!filePath) {
17
+ console.error(chalk.red('No RALPH.md found. Run ralph init to create one.'));
18
+ process.exit(1);
19
+ }
20
+ const doc = readRalph(filePath);
21
+ const task = getFirstUnblockedTask(doc.tasks);
22
+ if (!task) {
23
+ if (doc.tasks.length === 0) {
24
+ console.log(chalk.yellow('\nNo tasks in NEXT. Add some to RALPH.md and try again.\n'));
25
+ }
26
+ else {
27
+ console.log(chalk.yellow('\nAll tasks in NEXT are blocked. Resolve dependencies first.\n'));
28
+ for (const t of doc.tasks) {
29
+ console.log(chalk.dim(` ${t.id} → blocked by: ${t.blockedBy}`));
30
+ }
31
+ console.log();
32
+ }
33
+ process.exit(1);
34
+ }
35
+ printTaskBrief(task);
36
+ }
37
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.js","sourceRoot":"","sources":["../../src/commands/next.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE/E,MAAM,UAAU,cAAc,CAAC,IAAqE;IAClG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC,CAAC;IACtF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC,CAAC;IAC/E,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC,CAAC;IAC/E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,6BAA6B,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,qBAAqB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAE9C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,2DAA2D,CAAC,CAAC,CAAC;QACzF,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,gEAAgE,CAAC,CAAC,CAAC;YAC5F,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;gBAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;YACrE,CAAC;YACD,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,cAAc,CAAC,IAAI,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdStatus(): void;
2
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,IAAI,IAAI,CAyChC"}
@@ -0,0 +1,43 @@
1
+ import chalk from 'chalk';
2
+ import { findRalphFile, readRalph, isBlocked, isActiveEmpty } from '../parser.js';
3
+ export function cmdStatus() {
4
+ const filePath = findRalphFile();
5
+ if (!filePath) {
6
+ console.error(chalk.red('No RALPH.md found. Run ralph init to create one.'));
7
+ process.exit(1);
8
+ }
9
+ const doc = readRalph(filePath);
10
+ // ACTIVE
11
+ console.log(chalk.bold('\n── ACTIVE ──'));
12
+ if (isActiveEmpty(doc.activeContent)) {
13
+ console.log(chalk.dim(' (none)'));
14
+ }
15
+ else {
16
+ console.log(' ' + doc.activeContent.split('\n').join('\n '));
17
+ }
18
+ // NEXT
19
+ console.log(chalk.bold('\n── NEXT ──'));
20
+ if (doc.tasks.length === 0) {
21
+ console.log(chalk.dim(' (no tasks)'));
22
+ }
23
+ else {
24
+ for (const task of doc.tasks) {
25
+ const blocked = isBlocked(task);
26
+ const icon = blocked ? chalk.red('✗') : chalk.green('✓');
27
+ const label = blocked
28
+ ? chalk.dim(task.title) + chalk.red(` [blocked: ${task.blockedBy}]`)
29
+ : chalk.white(task.title);
30
+ console.log(` ${icon} ${label}`);
31
+ }
32
+ }
33
+ // DONE — count data rows (all pipe rows except the header and separator)
34
+ const doneLines = doc.doneContent
35
+ .split('\n')
36
+ .filter(l => /^\|/.test(l) && !/^\|[-: |]+$/.test(l));
37
+ // First pipe-row is the header; remainder are data rows
38
+ const doneCount = Math.max(0, doneLines.length - 1);
39
+ console.log(chalk.bold('\n── DONE ──'));
40
+ console.log(chalk.dim(` ${doneCount} task${doneCount !== 1 ? 's' : ''} completed`));
41
+ console.log();
42
+ }
43
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElF,MAAM,UAAU,SAAS;IACvB,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEhC,SAAS;IACT,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC1C,IAAI,aAAa,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IACrC,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,OAAO;IACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;IACxC,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC;IACzC,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACzD,MAAM,KAAK,GAAG,OAAO;gBACnB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,SAAS,GAAG,CAAC;gBACpE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW;SAC9B,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,wDAAwD;IACxD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,SAAS,QAAQ,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { cmdInit } from './commands/init.js';
4
+ import { cmdStatus } from './commands/status.js';
5
+ import { cmdNext } from './commands/next.js';
6
+ import { cmdDone } from './commands/done.js';
7
+ import { cmdBrief } from './commands/brief.js';
8
+ const program = new Command();
9
+ program
10
+ .name('ralph')
11
+ .description('The runtime that replaces you as the person who prompts the agent.')
12
+ .version('0.1.0');
13
+ program
14
+ .command('init')
15
+ .description('Scaffold a RALPH.md template in the current repo')
16
+ .action(cmdInit);
17
+ program
18
+ .command('status')
19
+ .description('Print ACTIVE / NEXT / DONE summary from RALPH.md')
20
+ .action(cmdStatus);
21
+ program
22
+ .command('next')
23
+ .description('Print the brief for the first unblocked task in NEXT')
24
+ .action(cmdNext);
25
+ program
26
+ .command('done <task-id>')
27
+ .description('Move a task from NEXT to DONE and print the next brief')
28
+ .action(cmdDone);
29
+ program
30
+ .command('brief <task-id>')
31
+ .description('Print the brief for a specific task without marking it done')
32
+ .action(cmdBrief);
33
+ program.parse(process.argv);
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,oEAAoE,CAAC;KACjF,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,SAAS,CAAC,CAAC;AAErB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,sDAAsD,CAAC;KACnE,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnB,OAAO;KACJ,OAAO,CAAC,gBAAgB,CAAC;KACzB,WAAW,CAAC,wDAAwD,CAAC;KACrE,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnB,OAAO;KACJ,OAAO,CAAC,iBAAiB,CAAC;KAC1B,WAAW,CAAC,6DAA6D,CAAC;KAC1E,MAAM,CAAC,QAAQ,CAAC,CAAC;AAEpB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ export interface Task {
2
+ id: string;
3
+ title: string;
4
+ brief: string;
5
+ blockedBy: string;
6
+ raw: string;
7
+ }
8
+ export interface ParsedRalph {
9
+ activeContent: string;
10
+ tasks: Task[];
11
+ doneContent: string;
12
+ raw: string;
13
+ }
14
+ /** Walk up from startDir looking for RALPH.md */
15
+ export declare function findRalphFile(startDir?: string): string | null;
16
+ export declare function readRalph(filePath: string): ParsedRalph;
17
+ export declare function parseRalph(content: string): ParsedRalph;
18
+ /** A task is blocked if Blocked by is anything other than "nothing" (case-insensitive) */
19
+ export declare function isBlocked(task: Task): boolean;
20
+ /** ACTIVE is empty when the section body is blank or contains only *(none)* / horizontal rules */
21
+ export declare function isActiveEmpty(activeContent: string): boolean;
22
+ /** Case-insensitive substring match on task ID or title */
23
+ export declare function findTaskById(tasks: Task[], query: string): Task | undefined;
24
+ export declare function getFirstUnblockedTask(tasks: Task[]): Task | undefined;
25
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,QAAQ,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAS7E;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAGvD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAkBvD;AAgCD,0FAA0F;AAC1F,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAG7C;AAED,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAO5D;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAK3E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,GAAG,SAAS,CAErE"}
package/dist/parser.js ADDED
@@ -0,0 +1,81 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ /** Walk up from startDir looking for RALPH.md */
4
+ export function findRalphFile(startDir = process.cwd()) {
5
+ let dir = startDir;
6
+ while (true) {
7
+ const candidate = path.join(dir, 'RALPH.md');
8
+ if (fs.existsSync(candidate))
9
+ return candidate;
10
+ const parent = path.dirname(dir);
11
+ if (parent === dir)
12
+ return null;
13
+ dir = parent;
14
+ }
15
+ }
16
+ export function readRalph(filePath) {
17
+ const content = fs.readFileSync(filePath, 'utf-8');
18
+ return parseRalph(content);
19
+ }
20
+ export function parseRalph(content) {
21
+ const activeMatch = content.match(/^## ACTIVE\s*\n([\s\S]*?)(?=^## )/im);
22
+ const nextMatch = content.match(/^## NEXT\s*\n([\s\S]*?)(?=^## )/im);
23
+ const doneMatch = content.match(/^## DONE\s*\n([\s\S]*)/im);
24
+ const activeRaw = activeMatch ? activeMatch[1].trim() : '';
25
+ const nextContent = nextMatch ? nextMatch[1] : '';
26
+ const doneContent = doneMatch ? doneMatch[1].trim() : '';
27
+ // Strip cosmetic --- separators from the active section before storing
28
+ const activeContent = activeRaw.replace(/^---\s*$/gm, '').trim();
29
+ return {
30
+ activeContent,
31
+ tasks: parseNextSection(nextContent),
32
+ doneContent,
33
+ raw: content,
34
+ };
35
+ }
36
+ function parseNextSection(content) {
37
+ const tasks = [];
38
+ // Split on #### headings; each block includes the heading line
39
+ const blocks = content.split(/(?=^####\s)/m).filter(b => b.trim().length > 0);
40
+ for (const block of blocks) {
41
+ const headingMatch = block.match(/^####\s+(.+)/m);
42
+ if (!headingMatch)
43
+ continue;
44
+ const heading = headingMatch[1].trim();
45
+ // ID is the first slug-like token before an em-dash, en-dash, or whitespace
46
+ const idMatch = heading.match(/^([\w-]+)/);
47
+ const id = idMatch ? idMatch[1] : heading.slice(0, 32).replace(/\s+/g, '-');
48
+ // Brief may be multiline. Use ^ with m flag so we anchor to line start,
49
+ // not to an inline "**Blocked by:**" that might appear inside the brief text.
50
+ const briefMatch = block.match(/^\*\*Brief:\*\*\s*([\s\S]+?)(?=\n\*\*[A-Z]|\n####|$)/m);
51
+ const brief = briefMatch ? briefMatch[1].trim() : '';
52
+ // Blocked by: must be at the start of a line so inline occurrences are ignored
53
+ const blockedByMatch = block.match(/^\*\*Blocked by:\*\*\s*(.+?)(?:\n|$)/im);
54
+ const blockedBy = blockedByMatch ? blockedByMatch[1].trim() : 'nothing';
55
+ tasks.push({ id, title: heading, brief, blockedBy, raw: block });
56
+ }
57
+ return tasks;
58
+ }
59
+ /** A task is blocked if Blocked by is anything other than "nothing" (case-insensitive) */
60
+ export function isBlocked(task) {
61
+ const val = task.blockedBy.toLowerCase().trim();
62
+ return val !== 'nothing' && val !== '';
63
+ }
64
+ /** ACTIVE is empty when the section body is blank or contains only *(none)* / horizontal rules */
65
+ export function isActiveEmpty(activeContent) {
66
+ const stripped = activeContent
67
+ .replace(/<!--[\s\S]*?-->/g, '') // strip HTML comments
68
+ .replace(/\*\(none\)\*/gi, '') // strip *(none)*
69
+ .replace(/^---\s*$/gm, '') // strip horizontal rules
70
+ .trim();
71
+ return stripped === '';
72
+ }
73
+ /** Case-insensitive substring match on task ID or title */
74
+ export function findTaskById(tasks, query) {
75
+ const q = query.toLowerCase();
76
+ return tasks.find(t => t.id.toLowerCase().includes(q) || t.title.toLowerCase().includes(q));
77
+ }
78
+ export function getFirstUnblockedTask(tasks) {
79
+ return tasks.find(t => !isBlocked(t));
80
+ }
81
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { ParsedRalph, Task } from './parser.js';
2
+ /** Remove a task block from NEXT and append a row to the DONE table. Returns new file content. */
3
+ export declare function moveTaskToDone(doc: ParsedRalph, task: Task): string;
4
+ export declare function writeRalph(filePath: string, content: string): void;
5
+ //# sourceMappingURL=writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEhD,kGAAkG;AAClG,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CA+BnE;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAElE"}
package/dist/writer.js ADDED
@@ -0,0 +1,27 @@
1
+ import * as fs from 'fs';
2
+ /** Remove a task block from NEXT and append a row to the DONE table. Returns new file content. */
3
+ export function moveTaskToDone(doc, task) {
4
+ let content = doc.raw;
5
+ // Remove the raw task block from the file
6
+ content = content.replace(task.raw, '');
7
+ // Collapse triple+ blank lines left by removal
8
+ content = content.replace(/\n{3,}/g, '\n\n');
9
+ // Build DONE table row
10
+ const date = new Date().toISOString().split('T')[0];
11
+ const row = `| ${task.id} | ${task.title} | ${date} |`;
12
+ // Does a DONE table already have a header row?
13
+ const hasTable = /\|\s*Task(?:\s+ID)?\s*\|/i.test(content);
14
+ if (hasTable) {
15
+ // Append after the last row of the table (find separator line, insert after last data row)
16
+ content = content.replace(/(## DONE[\s\S]*?(?:\|[-| :]+\|\n)((?:\|.+\|\n)*))/i, `$1${row}\n`);
17
+ }
18
+ else {
19
+ // No table yet — create one inside the DONE section
20
+ content = content.replace(/^(## DONE\s*\n)/im, `$1\n| Task ID | Title | Completed |\n|---------|-------|----------|\n${row}\n`);
21
+ }
22
+ return content;
23
+ }
24
+ export function writeRalph(filePath, content) {
25
+ fs.writeFileSync(filePath, content, 'utf-8');
26
+ }
27
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../src/writer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAGzB,kGAAkG;AAClG,MAAM,UAAU,cAAc,CAAC,GAAgB,EAAE,IAAU;IACzD,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC;IAEtB,0CAA0C;IAC1C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAExC,+CAA+C;IAC/C,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAE7C,uBAAuB;IACvB,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,KAAK,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC;IAEvD,+CAA+C;IAC/C,MAAM,QAAQ,GAAG,2BAA2B,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAE3D,IAAI,QAAQ,EAAE,CAAC;QACb,2FAA2F;QAC3F,OAAO,GAAG,OAAO,CAAC,OAAO,CACvB,oDAAoD,EACpD,KAAK,GAAG,IAAI,CACb,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,oDAAoD;QACpD,OAAO,GAAG,OAAO,CAAC,OAAO,CACvB,mBAAmB,EACnB,wEAAwE,GAAG,IAAI,CAChF,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,OAAe;IAC1D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@felixthemangy/ralph-loop",
3
+ "version": "0.1.0",
4
+ "description": "The runtime that replaces you as the person who prompts the agent.",
5
+ "type": "module",
6
+ "bin": {
7
+ "ralph": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "test": "vitest run",
14
+ "typecheck": "tsc --noEmit"
15
+ },
16
+ "keywords": ["cli", "agent", "loop-engineering", "ralph", "multi-agent"],
17
+ "author": "StartupHuman",
18
+ "license": "MIT",
19
+ "files": ["dist", "README.md"],
20
+ "engines": { "node": ">=20" },
21
+ "dependencies": {
22
+ "chalk": "^5.3.0",
23
+ "commander": "^12.1.0",
24
+ "gray-matter": "^4.0.3"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.14.0",
28
+ "tsx": "^4.15.0",
29
+ "typescript": "^5.4.0",
30
+ "vitest": "^1.6.0"
31
+ }
32
+ }