@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 +70 -0
- package/dist/commands/brief.d.ts +2 -0
- package/dist/commands/brief.d.ts.map +1 -0
- package/dist/commands/brief.js +19 -0
- package/dist/commands/brief.js.map +1 -0
- package/dist/commands/done.d.ts +2 -0
- package/dist/commands/done.d.ts.map +1 -0
- package/dist/commands/done.js +36 -0
- package/dist/commands/done.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +18 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/next.d.ts +8 -0
- package/dist/commands/next.d.ts.map +1 -0
- package/dist/commands/next.js +37 -0
- package/dist/commands/next.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +43 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +25 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +81 -0
- package/dist/parser.js.map +1 -0
- package/dist/writer.d.ts +5 -0
- package/dist/writer.d.ts.map +1 -0
- package/dist/writer.js +27 -0
- package/dist/writer.js.map +1 -0
- package/package.json +32 -0
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/parser.d.ts
ADDED
|
@@ -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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"}
|
package/dist/writer.d.ts
ADDED
|
@@ -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
|
+
}
|