cralph 1.0.0-beta.7 → 1.0.0-beta.8
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 +69 -39
- package/index.ts +1 -1
- package/package.json +1 -1
- package/src/cli.ts +34 -33
- package/src/paths.ts +81 -59
- package/src/prompt.ts +5 -16
- package/src/runner.ts +1 -1
- package/src/types.ts +0 -3
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
<img src="https://raw.githubusercontent.com/mguleryuz/cralph/main/assets/ralph.png" alt="Ralph cooking" width="500">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
Claude in a loop. Give it a
|
|
7
|
+
Claude in a loop. Give it a TODO, let it cook.
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
.ralph/
|
|
11
|
-
├──
|
|
12
|
-
├──
|
|
13
|
-
└──
|
|
11
|
+
├── refs/ (read-only reference material)
|
|
12
|
+
├── TODO.md ──loop──> ./
|
|
13
|
+
└── paths.json (output)
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## What is Ralph?
|
|
@@ -39,7 +39,7 @@ bun add -g cralph
|
|
|
39
39
|
# In any directory without .ralph/ - creates starter structure
|
|
40
40
|
cralph
|
|
41
41
|
|
|
42
|
-
#
|
|
42
|
+
# Run again - prepare your TODO, then run
|
|
43
43
|
cralph
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -50,7 +50,7 @@ cralph
|
|
|
50
50
|
cralph
|
|
51
51
|
|
|
52
52
|
# Override with flags
|
|
53
|
-
cralph --refs ./source --
|
|
53
|
+
cralph --refs ./source --output .
|
|
54
54
|
|
|
55
55
|
# Auto-confirm prompts (CI/automation)
|
|
56
56
|
cralph --yes
|
|
@@ -59,19 +59,63 @@ cralph --yes
|
|
|
59
59
|
## How It Works
|
|
60
60
|
|
|
61
61
|
1. Checks Claude CLI auth (cached for 6 hours)
|
|
62
|
-
2. Looks for `.ralph/` in current directory
|
|
63
|
-
3.
|
|
62
|
+
2. Looks for `.ralph/` in current directory
|
|
63
|
+
3. Shows main menu: **Run** / **Prepare TODO** / **Edit config**
|
|
64
64
|
4. Runs `claude -p --dangerously-skip-permissions` in a loop
|
|
65
65
|
5. Claude completes **ONE task per iteration**, marks it done, then stops
|
|
66
66
|
6. Auto-commits progress after each iteration (fails gracefully if no git)
|
|
67
67
|
7. Stops when Claude outputs `<promise>COMPLETE</promise>`
|
|
68
68
|
|
|
69
|
+
## Main Menu
|
|
70
|
+
|
|
71
|
+
When `.ralph/paths.json` exists, you get:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
❯ Found .ralph/paths.json. What would you like to do?
|
|
75
|
+
● 🚀 Run with this config
|
|
76
|
+
○ 📝 Prepare TODO
|
|
77
|
+
○ ✏️ Edit configuration
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- **Run** — validates config and starts the loop
|
|
81
|
+
- **Prepare TODO** — describe your tasks, Claude generates TODO.md, returns to menu
|
|
82
|
+
- **Edit** — re-select refs/output, save config, returns to menu
|
|
83
|
+
|
|
84
|
+
## Prepare TODO
|
|
85
|
+
|
|
86
|
+
Selecting **Prepare TODO** prompts you to describe what Claude should work on:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
? Describe your tasks (what should Claude work on?):
|
|
90
|
+
> Build a REST API with user auth, add unit tests, setup error handling
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Claude generates a structured TODO.md with ordered, actionable tasks:
|
|
94
|
+
|
|
95
|
+
```markdown
|
|
96
|
+
# Tasks
|
|
97
|
+
|
|
98
|
+
- [ ] Set up Express server with basic routing
|
|
99
|
+
- [ ] Add user authentication with JWT
|
|
100
|
+
- [ ] Create user CRUD endpoints
|
|
101
|
+
- [ ] Add error handling middleware
|
|
102
|
+
- [ ] Write unit tests for auth module
|
|
103
|
+
- [ ] Write unit tests for user endpoints
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
# Notes
|
|
108
|
+
|
|
109
|
+
_Append progress and learnings here after each iteration_
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
You can prepare TODO multiple times — each run overwrites the previous.
|
|
113
|
+
|
|
69
114
|
## Config
|
|
70
115
|
|
|
71
116
|
```json
|
|
72
117
|
{
|
|
73
118
|
"refs": ["./.ralph/refs"],
|
|
74
|
-
"rule": "./.ralph/rule.md",
|
|
75
119
|
"output": "."
|
|
76
120
|
}
|
|
77
121
|
```
|
|
@@ -82,10 +126,9 @@ Save as `.ralph/paths.json`. Refs are optional reference material (read-only).
|
|
|
82
126
|
|
|
83
127
|
| File | Description |
|
|
84
128
|
|------|-------------|
|
|
85
|
-
| `.ralph/paths.json` | Configuration |
|
|
86
|
-
| `.ralph/rule.md` | Your instructions for Claude |
|
|
129
|
+
| `.ralph/paths.json` | Configuration (refs, output) |
|
|
87
130
|
| `.ralph/refs/` | Optional reference material (read-only) |
|
|
88
|
-
| `.ralph/TODO.md` | Task tracking (updated by Claude) |
|
|
131
|
+
| `.ralph/TODO.md` | Task tracking (generated or manual, updated by Claude) |
|
|
89
132
|
| `.ralph/ralph.log` | Session log |
|
|
90
133
|
| `~/.cralph/auth-cache.json` | Auth cache (6h TTL) |
|
|
91
134
|
|
|
@@ -107,9 +150,6 @@ Claude maintains this structure (one task per iteration):
|
|
|
107
150
|
- What was implemented
|
|
108
151
|
- Files changed
|
|
109
152
|
- Learnings: patterns discovered, gotchas encountered
|
|
110
|
-
|
|
111
|
-
## Task 2 - Done
|
|
112
|
-
- ...
|
|
113
153
|
```
|
|
114
154
|
|
|
115
155
|
## First Run (No .ralph/ in cwd)
|
|
@@ -124,39 +164,29 @@ Select **Create starter structure** to generate the default config:
|
|
|
124
164
|
|
|
125
165
|
```
|
|
126
166
|
ℹ Created .ralph/refs/ directory
|
|
127
|
-
ℹ Created .ralph/rule.md with starter template
|
|
128
167
|
ℹ Created .ralph/paths.json
|
|
129
168
|
|
|
130
|
-
|
|
131
|
-
│ 1. Add source files to .ralph/refs/
|
|
132
|
-
│ 2.
|
|
133
|
-
|
|
134
|
-
╰─────────────────────────────────────────────────╯
|
|
169
|
+
╭──────────────────────────────────────────────╮
|
|
170
|
+
│ 1. Add source files to .ralph/refs/ │
|
|
171
|
+
│ 2. Run cralph again to prepare your TODO │
|
|
172
|
+
╰──────────────────────────────────────────────╯
|
|
135
173
|
```
|
|
136
174
|
|
|
137
|
-
|
|
175
|
+
## TODO Reset
|
|
138
176
|
|
|
139
|
-
|
|
177
|
+
When running, if TODO.md has existing progress:
|
|
140
178
|
|
|
141
|
-
## Prompts
|
|
142
|
-
|
|
143
|
-
**Config detected:**
|
|
144
179
|
```
|
|
145
|
-
|
|
146
|
-
● 🚀 Run with this config
|
|
147
|
-
○ ✏️ Edit configuration
|
|
180
|
+
? Found existing TODO with progress. Reset to start fresh? (y/N)
|
|
148
181
|
```
|
|
149
182
|
|
|
150
|
-
**
|
|
151
|
-
```
|
|
152
|
-
? Found existing TODO with progress. Reset to start fresh? (Y/n)
|
|
153
|
-
```
|
|
183
|
+
Default is **No** — continues with existing progress.
|
|
154
184
|
|
|
155
185
|
## Path Selection
|
|
156
186
|
|
|
157
|
-
- **Space**
|
|
158
|
-
- **Enter**
|
|
159
|
-
- **Ctrl+C**
|
|
187
|
+
- **Space** — Toggle selection
|
|
188
|
+
- **Enter** — Confirm
|
|
189
|
+
- **Ctrl+C** — Exit
|
|
160
190
|
|
|
161
191
|
## Platform Support
|
|
162
192
|
|
|
@@ -168,7 +198,7 @@ cralph works on **macOS**, **Linux**, and **Windows** with platform-specific han
|
|
|
168
198
|
| Linux | lost+found, proc, sys |
|
|
169
199
|
| Windows | System Volume Information, $Recycle.Bin, Windows |
|
|
170
200
|
|
|
171
|
-
Permission errors (`EPERM`, `EACCES`) are handled gracefully on all platforms
|
|
201
|
+
Permission errors (`EPERM`, `EACCES`) are handled gracefully on all platforms.
|
|
172
202
|
|
|
173
203
|
## Testing
|
|
174
204
|
|
|
@@ -176,8 +206,8 @@ Permission errors (`EPERM`, `EACCES`) are handled gracefully on all platforms, a
|
|
|
176
206
|
bun test
|
|
177
207
|
```
|
|
178
208
|
|
|
179
|
-
- **Unit tests**
|
|
180
|
-
- **E2E tests**
|
|
209
|
+
- **Unit tests** — Config, prompt building, CLI, access error handling, platform detection, shutdown state
|
|
210
|
+
- **E2E tests** — Full loop with Claude (requires auth)
|
|
181
211
|
|
|
182
212
|
## Requirements
|
|
183
213
|
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Re-export for programmatic usage
|
|
2
2
|
export * from "./src/types";
|
|
3
|
-
export { loadPathsFile, validateConfig, resolvePathsConfig, toRelativePath } from "./src/paths";
|
|
3
|
+
export { loadPathsFile, validateConfig, resolvePathsConfig, toRelativePath, prepareTodo } from "./src/paths";
|
|
4
4
|
export { createPrompt, buildPrompt } from "./src/prompt";
|
|
5
5
|
export { run, checkClaudeAuth } from "./src/runner";
|
|
6
6
|
export { cleanupSubprocess, setShuttingDown, isShuttingDown } from "./src/state";
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
loadPathsFile,
|
|
9
9
|
validateConfig,
|
|
10
10
|
selectRefs,
|
|
11
|
-
selectRule,
|
|
12
11
|
selectOutput,
|
|
13
12
|
checkForPathsFile,
|
|
14
13
|
resolvePathsConfig,
|
|
15
14
|
toRelativePath,
|
|
15
|
+
prepareTodo,
|
|
16
16
|
} from "./paths";
|
|
17
17
|
import { run, checkClaudeAuth } from "./runner";
|
|
18
18
|
import type { RalphConfig } from "./types";
|
|
@@ -47,7 +47,7 @@ const main = defineCommand({
|
|
|
47
47
|
meta: {
|
|
48
48
|
name: "cralph",
|
|
49
49
|
version: "1.0.0",
|
|
50
|
-
description: "Claude in a loop.
|
|
50
|
+
description: "Claude in a loop. Give it a TODO, let it cook.",
|
|
51
51
|
},
|
|
52
52
|
args: {
|
|
53
53
|
refs: {
|
|
@@ -57,13 +57,6 @@ const main = defineCommand({
|
|
|
57
57
|
alias: "r",
|
|
58
58
|
required: false,
|
|
59
59
|
},
|
|
60
|
-
rule: {
|
|
61
|
-
type: "string",
|
|
62
|
-
description: "Path to rule file (.mdc or .md)",
|
|
63
|
-
valueHint: "rule.md",
|
|
64
|
-
alias: "u",
|
|
65
|
-
required: false,
|
|
66
|
-
},
|
|
67
60
|
output: {
|
|
68
61
|
type: "string",
|
|
69
62
|
description: "Output directory where results will be written",
|
|
@@ -113,18 +106,28 @@ const main = defineCommand({
|
|
|
113
106
|
|
|
114
107
|
consola.success("Claude authenticated");
|
|
115
108
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
109
|
+
// Main selection loop - prepare/edit return here, run breaks out
|
|
110
|
+
while (true) {
|
|
111
|
+
const pathsFileResult = args.yes
|
|
112
|
+
? await checkForPathsFile(cwd, true)
|
|
113
|
+
: await checkForPathsFile(cwd);
|
|
114
|
+
|
|
115
|
+
if (pathsFileResult?.action === "prepare") {
|
|
116
|
+
const loaded = await loadPathsFile(pathsFileResult.path);
|
|
117
|
+
const resolved = resolvePathsConfig(loaded, cwd);
|
|
118
|
+
const outputBase = resolved.output === cwd ? cwd : resolved.output;
|
|
119
|
+
await prepareTodo(outputBase);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (pathsFileResult?.action === "run") {
|
|
124
|
+
consola.info(`Loading config from ${pathsFileResult.path}`);
|
|
125
|
+
const loaded = await loadPathsFile(pathsFileResult.path);
|
|
126
|
+
config = resolvePathsConfig(loaded, cwd);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Edit mode or no config found
|
|
128
131
|
let existingConfig: RalphConfig | null = null;
|
|
129
132
|
if (pathsFileResult?.action === "edit") {
|
|
130
133
|
consola.info("Edit configuration");
|
|
@@ -139,19 +142,15 @@ const main = defineCommand({
|
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
// Interactive selection
|
|
142
|
-
const refs = args.refs
|
|
145
|
+
const refs = args.refs
|
|
143
146
|
? args.refs.split(",").map((r) => resolve(cwd, r.trim()))
|
|
144
147
|
: await selectRefs(cwd, existingConfig?.refs, args.yes);
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
? resolve(cwd, args.rule)
|
|
148
|
-
: await selectRule(cwd, existingConfig?.rule);
|
|
149
|
-
|
|
150
|
-
const output = args.output
|
|
148
|
+
|
|
149
|
+
const output = args.output
|
|
151
150
|
? resolve(cwd, args.output)
|
|
152
151
|
: await selectOutput(cwd, existingConfig?.output);
|
|
153
152
|
|
|
154
|
-
config = { refs,
|
|
153
|
+
config = { refs, output };
|
|
155
154
|
|
|
156
155
|
// Offer to save config
|
|
157
156
|
const saveConfig = await consola.prompt("Save configuration to .ralph/paths.json?", {
|
|
@@ -159,16 +158,15 @@ const main = defineCommand({
|
|
|
159
158
|
cancel: "symbol",
|
|
160
159
|
initial: true,
|
|
161
160
|
});
|
|
162
|
-
|
|
161
|
+
|
|
163
162
|
throwIfCancelled(saveConfig);
|
|
164
163
|
|
|
165
164
|
if (saveConfig === true) {
|
|
166
165
|
const ralphDir = join(cwd, ".ralph");
|
|
167
166
|
await mkdir(ralphDir, { recursive: true });
|
|
168
|
-
|
|
167
|
+
|
|
169
168
|
const pathsConfig = {
|
|
170
169
|
refs: config.refs.map((r) => toRelativePath(r, cwd)),
|
|
171
|
-
rule: toRelativePath(config.rule, cwd),
|
|
172
170
|
output: toRelativePath(config.output, cwd),
|
|
173
171
|
};
|
|
174
172
|
await Bun.write(
|
|
@@ -177,8 +175,12 @@ const main = defineCommand({
|
|
|
177
175
|
);
|
|
178
176
|
consola.success("Saved .ralph/paths.json");
|
|
179
177
|
}
|
|
178
|
+
|
|
179
|
+
// After edit, loop back to main selection
|
|
180
|
+
continue;
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
|
|
182
184
|
// Validate configuration
|
|
183
185
|
consola.info("Validating configuration...");
|
|
184
186
|
await validateConfig(config);
|
|
@@ -186,7 +188,6 @@ const main = defineCommand({
|
|
|
186
188
|
// Show config summary
|
|
187
189
|
consola.info("Configuration:");
|
|
188
190
|
consola.info(` Refs: ${config.refs.join(", ")}`);
|
|
189
|
-
consola.info(` Rule: ${config.rule}`);
|
|
190
191
|
consola.info(` Output: ${config.output}`);
|
|
191
192
|
console.log();
|
|
192
193
|
|
package/src/paths.ts
CHANGED
|
@@ -6,10 +6,6 @@ import type { PathsFileConfig, RalphConfig } from "./types";
|
|
|
6
6
|
import { isAccessError, shouldExcludeDir } from "./platform";
|
|
7
7
|
import { throwIfCancelled } from "./state";
|
|
8
8
|
|
|
9
|
-
// Starter rule template for new projects
|
|
10
|
-
const STARTER_RULE = `I want a file named hello.txt
|
|
11
|
-
`;
|
|
12
|
-
|
|
13
9
|
// Dim text helper
|
|
14
10
|
const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
|
|
15
11
|
const CONTROLS = dim("↑↓ Navigate • Space Toggle • Enter • Ctrl+C Exit");
|
|
@@ -20,7 +16,6 @@ const CONTROLS = dim("↑↓ Navigate • Space Toggle • Enter • Ctrl+C Exit
|
|
|
20
16
|
export function resolvePathsConfig(loaded: PathsFileConfig, cwd: string): RalphConfig {
|
|
21
17
|
return {
|
|
22
18
|
refs: loaded.refs.map((r) => resolve(cwd, r)),
|
|
23
|
-
rule: resolve(cwd, loaded.rule),
|
|
24
19
|
output: resolve(cwd, loaded.output),
|
|
25
20
|
};
|
|
26
21
|
}
|
|
@@ -38,6 +33,7 @@ export function toRelativePath(absolutePath: string, cwd: string): string {
|
|
|
38
33
|
*/
|
|
39
34
|
function shouldSkipDirectory(entry: Dirent): boolean {
|
|
40
35
|
if (!entry.isDirectory()) return true;
|
|
36
|
+
if (entry.name === ".ralph") return false; // Always show .ralph
|
|
41
37
|
if (entry.name.startsWith(".")) return true;
|
|
42
38
|
if (shouldExcludeDir(entry.name)) return true;
|
|
43
39
|
return false;
|
|
@@ -50,7 +46,7 @@ async function listDirectories(basePath: string): Promise<string[]> {
|
|
|
50
46
|
try {
|
|
51
47
|
const entries = await readdir(basePath, { withFileTypes: true });
|
|
52
48
|
return entries
|
|
53
|
-
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
|
|
49
|
+
.filter((e) => e.isDirectory() && (e.name === ".ralph" || !e.name.startsWith(".")))
|
|
54
50
|
.map((e) => e.name);
|
|
55
51
|
} catch (error) {
|
|
56
52
|
// Silently skip directories we can't access
|
|
@@ -154,27 +150,21 @@ export async function createStarterStructure(cwd: string): Promise<void> {
|
|
|
154
150
|
// Create .ralph/
|
|
155
151
|
const ralphDir = join(cwd, ".ralph");
|
|
156
152
|
await mkdir(ralphDir, { recursive: true });
|
|
157
|
-
|
|
153
|
+
|
|
158
154
|
// Create .ralph/refs/
|
|
159
155
|
const refsDir = join(ralphDir, "refs");
|
|
160
156
|
await mkdir(refsDir, { recursive: true });
|
|
161
157
|
consola.info("Created .ralph/refs/ directory");
|
|
162
|
-
|
|
163
|
-
// Create .ralph/rule.md
|
|
164
|
-
const rulePath = join(ralphDir, "rule.md");
|
|
165
|
-
await Bun.write(rulePath, STARTER_RULE);
|
|
166
|
-
consola.info("Created .ralph/rule.md with starter template");
|
|
167
|
-
|
|
158
|
+
|
|
168
159
|
// Create .ralph/paths.json with default config
|
|
169
160
|
const pathsConfig = {
|
|
170
161
|
refs: ["./.ralph/refs"],
|
|
171
|
-
rule: "./.ralph/rule.md",
|
|
172
162
|
output: ".",
|
|
173
163
|
};
|
|
174
164
|
await Bun.write(join(ralphDir, "paths.json"), JSON.stringify(pathsConfig, null, 2));
|
|
175
165
|
consola.info("Created .ralph/paths.json");
|
|
176
|
-
|
|
177
|
-
consola.box("1. Add source files to .ralph/refs/\n2.
|
|
166
|
+
|
|
167
|
+
consola.box("1. Add source files to .ralph/refs/\n2. Run cralph again to prepare your TODO");
|
|
178
168
|
}
|
|
179
169
|
|
|
180
170
|
/**
|
|
@@ -263,39 +253,80 @@ export async function selectRefs(cwd: string, defaults?: string[], autoConfirm?:
|
|
|
263
253
|
}
|
|
264
254
|
|
|
265
255
|
/**
|
|
266
|
-
* Prompt user to
|
|
256
|
+
* Prompt user to prepare TODO.md by describing their tasks
|
|
257
|
+
* Uses Claude to generate a properly formatted TODO.md
|
|
267
258
|
*/
|
|
268
|
-
export async function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
259
|
+
export async function prepareTodo(cwd: string): Promise<void> {
|
|
260
|
+
const ralphDir = join(cwd, ".ralph");
|
|
261
|
+
const todoPath = join(ralphDir, "TODO.md");
|
|
262
|
+
|
|
263
|
+
const description = await consola.prompt(
|
|
264
|
+
"Describe your tasks (what should Claude work on?):",
|
|
265
|
+
{
|
|
266
|
+
type: "text",
|
|
267
|
+
cancel: "symbol",
|
|
268
|
+
placeholder: "e.g. Build a REST API with user auth, add tests, setup CI...",
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
throwIfCancelled(description);
|
|
273
|
+
|
|
274
|
+
if (!description || (description as string).trim() === "") {
|
|
275
|
+
consola.warn("No description provided, skipping TODO preparation");
|
|
276
|
+
return;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
|
|
280
|
-
const options = files.map((f) => ({
|
|
281
|
-
label: `📄 ${f.replace(cwd + "/", "")}`,
|
|
282
|
-
value: f,
|
|
283
|
-
hint: f === defaultRule ? "current" : (f.endsWith(".mdc") ? "cursor rule" : "markdown"),
|
|
284
|
-
}));
|
|
279
|
+
consola.start("Generating TODO.md from your description...");
|
|
285
280
|
|
|
286
|
-
|
|
287
|
-
const initialValue = defaultRule && files.includes(defaultRule) ? defaultRule : files[0];
|
|
281
|
+
const todoPrompt = `You are generating a TODO.md file for an autonomous coding agent. Based on the user's description, create a well-structured TODO.md with clear, actionable tasks.
|
|
288
282
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
});
|
|
283
|
+
Rules:
|
|
284
|
+
- Each task should be a single, focused unit of work
|
|
285
|
+
- Tasks should be ordered logically (dependencies first)
|
|
286
|
+
- Use the checkbox format: "- [ ] Task description"
|
|
287
|
+
- Keep task descriptions concise but specific
|
|
288
|
+
- Include a Notes section placeholder at the bottom
|
|
296
289
|
|
|
297
|
-
|
|
298
|
-
|
|
290
|
+
User's description:
|
|
291
|
+
${(description as string).trim()}
|
|
292
|
+
|
|
293
|
+
Output ONLY the TODO.md content, nothing else. Use this exact format:
|
|
294
|
+
|
|
295
|
+
# Tasks
|
|
296
|
+
|
|
297
|
+
- [ ] First task
|
|
298
|
+
- [ ] Second task
|
|
299
|
+
...
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
# Notes
|
|
304
|
+
|
|
305
|
+
_Append progress and learnings here after each iteration_`;
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const proc = Bun.spawn(["claude", "-p"], {
|
|
309
|
+
stdin: new Blob([todoPrompt]),
|
|
310
|
+
stdout: "pipe",
|
|
311
|
+
stderr: "pipe",
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const stdout = await new Response(proc.stdout).text();
|
|
315
|
+
const exitCode = await proc.exited;
|
|
316
|
+
|
|
317
|
+
if (exitCode !== 0) {
|
|
318
|
+
consola.error("Failed to generate TODO.md");
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Write the generated TODO
|
|
323
|
+
await mkdir(ralphDir, { recursive: true });
|
|
324
|
+
await Bun.write(todoPath, stdout.trim() + "\n");
|
|
325
|
+
consola.success("Generated .ralph/TODO.md");
|
|
326
|
+
} catch (error) {
|
|
327
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
328
|
+
consola.error(`Failed to generate TODO: ${msg}`);
|
|
329
|
+
}
|
|
299
330
|
}
|
|
300
331
|
|
|
301
332
|
/**
|
|
@@ -340,19 +371,18 @@ export async function selectOutput(cwd: string, defaultOutput?: string): Promise
|
|
|
340
371
|
|
|
341
372
|
/**
|
|
342
373
|
* Check if a paths file exists and offer to use it
|
|
343
|
-
* Returns: { action: "run", path: string } | { action: "edit" } | null
|
|
344
374
|
* @param autoRun - If true, skip prompt and auto-select "run" when config exists
|
|
345
375
|
*/
|
|
346
|
-
export async function checkForPathsFile(cwd: string, autoRun?: boolean): Promise<{ action: "run"
|
|
376
|
+
export async function checkForPathsFile(cwd: string, autoRun?: boolean): Promise<{ action: "run" | "edit" | "prepare"; path: string } | null> {
|
|
347
377
|
const filePath = join(cwd, ".ralph", "paths.json");
|
|
348
378
|
const file = Bun.file(filePath);
|
|
349
|
-
|
|
379
|
+
|
|
350
380
|
if (await file.exists()) {
|
|
351
381
|
// Auto-run if flag is set
|
|
352
382
|
if (autoRun) {
|
|
353
383
|
return { action: "run", path: filePath };
|
|
354
384
|
}
|
|
355
|
-
|
|
385
|
+
|
|
356
386
|
console.log(CONTROLS);
|
|
357
387
|
const action = await consola.prompt(
|
|
358
388
|
`Found .ralph/paths.json. What would you like to do?`,
|
|
@@ -361,17 +391,15 @@ export async function checkForPathsFile(cwd: string, autoRun?: boolean): Promise
|
|
|
361
391
|
cancel: "symbol",
|
|
362
392
|
options: [
|
|
363
393
|
{ label: "🚀 Run with this config", value: "run" },
|
|
394
|
+
{ label: "📝 Prepare TODO", value: "prepare" },
|
|
364
395
|
{ label: "✏️ Edit configuration", value: "edit" },
|
|
365
396
|
],
|
|
366
397
|
}
|
|
367
398
|
);
|
|
368
|
-
|
|
399
|
+
|
|
369
400
|
throwIfCancelled(action);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return { action: "run", path: filePath };
|
|
373
|
-
}
|
|
374
|
-
return { action: "edit" };
|
|
401
|
+
|
|
402
|
+
return { action: action as "run" | "edit" | "prepare", path: filePath };
|
|
375
403
|
}
|
|
376
404
|
|
|
377
405
|
return null;
|
|
@@ -390,11 +418,5 @@ export async function validateConfig(config: RalphConfig): Promise<void> {
|
|
|
390
418
|
}
|
|
391
419
|
}
|
|
392
420
|
|
|
393
|
-
// Check rule file
|
|
394
|
-
const ruleFile = Bun.file(config.rule);
|
|
395
|
-
if (!(await ruleFile.exists())) {
|
|
396
|
-
throw new Error(`Rule file does not exist: ${config.rule}`);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
421
|
// Output directory will be created if needed
|
|
400
422
|
}
|
package/src/prompt.ts
CHANGED
|
@@ -6,8 +6,6 @@ import type { RalphConfig } from "./types";
|
|
|
6
6
|
*/
|
|
7
7
|
const BASE_PROMPT = `You are an autonomous coding agent running in a loop.
|
|
8
8
|
|
|
9
|
-
FIRST: Read and internalize the rules provided below.
|
|
10
|
-
|
|
11
9
|
## Your Task This Iteration
|
|
12
10
|
|
|
13
11
|
1. Read the TODO file
|
|
@@ -55,10 +53,10 @@ After completing ONE task, check the TODO file:
|
|
|
55
53
|
**IMPORTANT:** Do NOT continue to the next task. Complete ONE task, then STOP.`;
|
|
56
54
|
|
|
57
55
|
/**
|
|
58
|
-
* Build the complete prompt with config
|
|
56
|
+
* Build the complete prompt with config injected
|
|
59
57
|
*/
|
|
60
|
-
export function buildPrompt(config: RalphConfig,
|
|
61
|
-
const refsList = config.refs.length > 0
|
|
58
|
+
export function buildPrompt(config: RalphConfig, todoFile: string): string {
|
|
59
|
+
const refsList = config.refs.length > 0
|
|
62
60
|
? config.refs.map((r) => `- ${r}`).join("\n")
|
|
63
61
|
: "_None_";
|
|
64
62
|
|
|
@@ -76,21 +74,12 @@ ${refsList}
|
|
|
76
74
|
|
|
77
75
|
**Output directory (write your work here):**
|
|
78
76
|
${config.output}
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Rules (Your Instructions)
|
|
83
|
-
|
|
84
|
-
${rulesContent}
|
|
85
77
|
`;
|
|
86
78
|
}
|
|
87
79
|
|
|
88
80
|
/**
|
|
89
|
-
*
|
|
81
|
+
* Build the complete prompt from config
|
|
90
82
|
*/
|
|
91
83
|
export async function createPrompt(config: RalphConfig, todoFile: string): Promise<string> {
|
|
92
|
-
|
|
93
|
-
const ruleContent = await ruleFile.text();
|
|
94
|
-
|
|
95
|
-
return buildPrompt(config, ruleContent, todoFile);
|
|
84
|
+
return buildPrompt(config, todoFile);
|
|
96
85
|
}
|
package/src/runner.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export interface PathsFileConfig {
|
|
5
5
|
refs: string[];
|
|
6
|
-
rule: string;
|
|
7
6
|
output: string;
|
|
8
7
|
}
|
|
9
8
|
|
|
@@ -13,8 +12,6 @@ export interface PathsFileConfig {
|
|
|
13
12
|
export interface RalphConfig {
|
|
14
13
|
/** Paths to reference material directories/files */
|
|
15
14
|
refs: string[];
|
|
16
|
-
/** Path to the rule file (.mdc or .md) */
|
|
17
|
-
rule: string;
|
|
18
15
|
/** Output directory for generated docs */
|
|
19
16
|
output: string;
|
|
20
17
|
}
|