cralph 1.0.0-alpha.4 → 1.0.0-alpha.6
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 +73 -82
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/paths.ts +61 -8
- package/src/prompt.ts +17 -11
- package/src/runner.ts +60 -46
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
<img src="assets/ralph.png" alt="Ralph cooking" width="500">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
Claude in a loop.
|
|
7
|
+
Claude in a loop. Give it a rule, let it cook.
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
.ralph/
|
|
11
|
+
├── rule.md ──loop──> ./
|
|
12
|
+
├── refs/ (output)
|
|
13
|
+
└── TODO.md
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## What is Ralph?
|
|
@@ -21,7 +21,7 @@ refs/ ──loop──> ./
|
|
|
21
21
|
while :; do cat PROMPT.md | claude -p ; done
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
cralph wraps this into a CLI with
|
|
24
|
+
cralph wraps this into a CLI with config, logging, and TODO tracking.
|
|
25
25
|
|
|
26
26
|
## Install
|
|
27
27
|
|
|
@@ -35,121 +35,112 @@ Or with npm:
|
|
|
35
35
|
npm install -g cralph
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Quick Start
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
#
|
|
41
|
+
# In an empty directory - creates starter structure
|
|
42
|
+
cralph
|
|
43
|
+
|
|
44
|
+
# Edit rule.md with your instructions, then run again
|
|
42
45
|
cralph
|
|
46
|
+
```
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Auto-detects .ralph/paths.json in cwd
|
|
45
52
|
cralph
|
|
46
53
|
|
|
47
54
|
# Override with flags
|
|
48
55
|
cralph --refs ./source --rule ./rule.md --output .
|
|
49
|
-
```
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
# Auto-confirm prompts (CI/automation)
|
|
58
|
+
cralph --yes
|
|
59
|
+
```
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
## How It Works
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
1. Checks Claude CLI auth (cached for 6 hours)
|
|
64
|
+
2. Loads config from `.ralph/paths.json`
|
|
65
|
+
3. Runs `claude -p --dangerously-skip-permissions` in a loop
|
|
66
|
+
4. Claude updates `.ralph/TODO.md` after each iteration
|
|
67
|
+
5. Stops when Claude outputs `<promise>COMPLETE</promise>`
|
|
60
68
|
|
|
61
|
-
## Config
|
|
69
|
+
## Config
|
|
62
70
|
|
|
63
71
|
```json
|
|
64
72
|
{
|
|
65
|
-
"refs": ["
|
|
66
|
-
"rule": "./.
|
|
73
|
+
"refs": ["./.ralph/refs"],
|
|
74
|
+
"rule": "./.ralph/rule.md",
|
|
67
75
|
"output": "."
|
|
68
76
|
}
|
|
69
77
|
```
|
|
70
78
|
|
|
71
|
-
Save as `.ralph/paths.json
|
|
79
|
+
Save as `.ralph/paths.json`. Refs are optional reference material (read-only).
|
|
72
80
|
|
|
73
|
-
##
|
|
81
|
+
## Files
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
| File | Description |
|
|
84
|
+
|------|-------------|
|
|
85
|
+
| `.ralph/paths.json` | Configuration |
|
|
86
|
+
| `.ralph/rule.md` | Your instructions for Claude |
|
|
87
|
+
| `.ralph/refs/` | Optional reference material (read-only) |
|
|
88
|
+
| `.ralph/TODO.md` | Task tracking (updated by Claude) |
|
|
89
|
+
| `.ralph/ralph.log` | Session log |
|
|
90
|
+
| `~/.cralph/auth-cache.json` | Auth cache (6h TTL) |
|
|
80
91
|
|
|
81
|
-
|
|
92
|
+
### TODO Format
|
|
82
93
|
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
◐ Checking Claude authentication...
|
|
86
|
-
✔ Claude authenticated
|
|
87
|
-
```
|
|
94
|
+
Claude maintains this structure:
|
|
88
95
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
◐ Checking Claude authentication...
|
|
92
|
-
✖ Claude CLI is not authenticated
|
|
96
|
+
```markdown
|
|
97
|
+
# Tasks
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
│ │
|
|
97
|
-
│ Then type: /login │
|
|
98
|
-
╰─────────────────────╯
|
|
99
|
+
- [ ] Pending task
|
|
100
|
+
- [x] Completed task
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
+
# Notes
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
❯ Found .ralph/paths.json. What would you like to do?
|
|
106
|
-
● 🚀 Run with this config
|
|
107
|
-
○ ✏️ Edit configuration
|
|
104
|
+
Any relevant context
|
|
108
105
|
```
|
|
109
106
|
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
ℹ Interactive configuration mode
|
|
107
|
+
## First Run (Empty Directory)
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
◻ 📁 src
|
|
117
|
-
◻ 📁 src/components
|
|
118
|
-
◼ 📁 docs
|
|
109
|
+
```
|
|
110
|
+
? No directories found. Create starter structure in /path/to/dir? (Y/n)
|
|
119
111
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
○ 📄 README.md
|
|
112
|
+
ℹ Created .ralph/refs/ directory
|
|
113
|
+
ℹ Created .ralph/rule.md with starter template
|
|
114
|
+
ℹ Created .ralph/paths.json
|
|
124
115
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
╭──────────────────────────────────────────────╮
|
|
117
|
+
│ 1. Add source files to .ralph/refs/ │
|
|
118
|
+
│ 2. Edit .ralph/rule.md with your instructions│
|
|
119
|
+
│ 3. Run cralph again │
|
|
120
|
+
╰──────────────────────────────────────────────╯
|
|
129
121
|
```
|
|
130
122
|
|
|
131
|
-
|
|
123
|
+
Use `--yes` to skip confirmation (for CI/automation).
|
|
124
|
+
|
|
125
|
+
## Prompts
|
|
126
|
+
|
|
127
|
+
**Config detected:**
|
|
132
128
|
```
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
❯ Found .ralph/paths.json. What would you like to do?
|
|
130
|
+
● 🚀 Run with this config
|
|
131
|
+
○ ✏️ Edit configuration
|
|
135
132
|
```
|
|
136
133
|
|
|
137
|
-
**TODO
|
|
134
|
+
**TODO has progress:**
|
|
138
135
|
```
|
|
139
|
-
? Found existing TODO with progress. Reset to start fresh? (
|
|
136
|
+
? Found existing TODO with progress. Reset to start fresh? (Y/n)
|
|
140
137
|
```
|
|
141
|
-
- If the `.ralph/TODO.md` file has been modified from previous runs, you'll be asked whether to reset it
|
|
142
|
-
- Default is **No** (continue with existing progress)
|
|
143
|
-
- Choose **Yes** to start fresh with a clean TODO
|
|
144
138
|
|
|
145
|
-
|
|
146
|
-
- Press `Ctrl+C` at any time to exit
|
|
147
|
-
- Running Claude processes are terminated cleanly
|
|
139
|
+
## Path Selection
|
|
148
140
|
|
|
149
|
-
**
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
- `.ralph/TODO.md` - Agent status tracker
|
|
141
|
+
- **Space** - Toggle selection
|
|
142
|
+
- **Enter** - Confirm
|
|
143
|
+
- **Ctrl+C** - Exit
|
|
153
144
|
|
|
154
145
|
## Testing
|
|
155
146
|
|
|
@@ -157,8 +148,8 @@ If not authenticated:
|
|
|
157
148
|
bun test
|
|
158
149
|
```
|
|
159
150
|
|
|
160
|
-
- **Unit tests**
|
|
161
|
-
- **E2E tests**
|
|
151
|
+
- **Unit tests** - Config, prompt building, CLI
|
|
152
|
+
- **E2E tests** - Full loop with Claude (requires auth)
|
|
162
153
|
|
|
163
154
|
## Requirements
|
|
164
155
|
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -136,7 +136,7 @@ const main = defineCommand({
|
|
|
136
136
|
// Interactive selection
|
|
137
137
|
const refs = args.refs
|
|
138
138
|
? args.refs.split(",").map((r) => resolve(cwd, r.trim()))
|
|
139
|
-
: await selectRefs(cwd, existingConfig?.refs);
|
|
139
|
+
: await selectRefs(cwd, existingConfig?.refs, args.yes);
|
|
140
140
|
|
|
141
141
|
const rule = args.rule
|
|
142
142
|
? resolve(cwd, args.rule)
|
package/src/paths.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { consola } from "consola";
|
|
2
2
|
import { resolve, join } from "path";
|
|
3
|
-
import { readdir, stat } from "fs/promises";
|
|
3
|
+
import { readdir, stat, mkdir } from "fs/promises";
|
|
4
4
|
import type { PathsFileConfig, RalphConfig } from "./types";
|
|
5
5
|
|
|
6
6
|
// Dim text helper
|
|
@@ -105,16 +105,62 @@ export async function loadPathsFile(filePath: string): Promise<PathsFileConfig>
|
|
|
105
105
|
return content as PathsFileConfig;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Create starter structure for empty directories
|
|
110
|
+
*/
|
|
111
|
+
export async function createStarterStructure(cwd: string): Promise<void> {
|
|
112
|
+
// Create .ralph/
|
|
113
|
+
const ralphDir = join(cwd, ".ralph");
|
|
114
|
+
await mkdir(ralphDir, { recursive: true });
|
|
115
|
+
|
|
116
|
+
// Create .ralph/refs/
|
|
117
|
+
const refsDir = join(ralphDir, "refs");
|
|
118
|
+
await mkdir(refsDir, { recursive: true });
|
|
119
|
+
consola.info("Created .ralph/refs/ directory");
|
|
120
|
+
|
|
121
|
+
// Create .ralph/rule.md
|
|
122
|
+
const rulePath = join(ralphDir, "rule.md");
|
|
123
|
+
await Bun.write(rulePath, STARTER_RULE);
|
|
124
|
+
consola.info("Created .ralph/rule.md with starter template");
|
|
125
|
+
|
|
126
|
+
// Create .ralph/paths.json with default config
|
|
127
|
+
const pathsConfig = {
|
|
128
|
+
refs: ["./.ralph/refs"],
|
|
129
|
+
rule: "./.ralph/rule.md",
|
|
130
|
+
output: ".",
|
|
131
|
+
};
|
|
132
|
+
await Bun.write(join(ralphDir, "paths.json"), JSON.stringify(pathsConfig, null, 2));
|
|
133
|
+
consola.info("Created .ralph/paths.json");
|
|
134
|
+
|
|
135
|
+
consola.box("1. Add source files to .ralph/refs/\n2. Edit .ralph/rule.md with your instructions\n3. Run cralph again");
|
|
136
|
+
}
|
|
137
|
+
|
|
108
138
|
/**
|
|
109
139
|
* Prompt user to select refs directories (simple multiselect)
|
|
140
|
+
* @param autoConfirm - If true, skip confirmation prompts
|
|
110
141
|
*/
|
|
111
|
-
export async function selectRefs(cwd: string, defaults?: string[]): Promise<string[]> {
|
|
142
|
+
export async function selectRefs(cwd: string, defaults?: string[], autoConfirm?: boolean): Promise<string[]> {
|
|
112
143
|
// Get all directories up to 3 levels deep
|
|
113
|
-
|
|
144
|
+
let allDirs = await listDirectoriesRecursive(cwd, 3);
|
|
114
145
|
|
|
115
146
|
if (allDirs.length === 0) {
|
|
116
|
-
|
|
117
|
-
|
|
147
|
+
// Ask before creating starter structure (skip if autoConfirm)
|
|
148
|
+
if (!autoConfirm) {
|
|
149
|
+
const confirm = await consola.prompt(
|
|
150
|
+
`No directories found. Create starter structure in ${cwd}?`,
|
|
151
|
+
{
|
|
152
|
+
type: "confirm",
|
|
153
|
+
initial: true,
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (confirm !== true) {
|
|
158
|
+
throw new Error("Setup cancelled");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await createStarterStructure(cwd);
|
|
163
|
+
process.exit(0);
|
|
118
164
|
}
|
|
119
165
|
|
|
120
166
|
// Convert to relative paths for display
|
|
@@ -146,14 +192,21 @@ export async function selectRefs(cwd: string, defaults?: string[]): Promise<stri
|
|
|
146
192
|
return selected as string[];
|
|
147
193
|
}
|
|
148
194
|
|
|
195
|
+
const STARTER_RULE = `I want a file named hello.txt
|
|
196
|
+
`;
|
|
197
|
+
|
|
149
198
|
/**
|
|
150
199
|
* Prompt user to select a rule file
|
|
151
200
|
*/
|
|
152
201
|
export async function selectRule(cwd: string, defaultRule?: string): Promise<string> {
|
|
153
|
-
|
|
202
|
+
let files = await listFilesRecursive(cwd, [".mdc", ".md"]);
|
|
154
203
|
if (files.length === 0) {
|
|
155
|
-
|
|
156
|
-
|
|
204
|
+
// This shouldn't happen if selectRefs ran first, but handle it just in case
|
|
205
|
+
const rulePath = join(cwd, "rule.md");
|
|
206
|
+
await Bun.write(rulePath, STARTER_RULE);
|
|
207
|
+
consola.info("Created rule.md with starter template");
|
|
208
|
+
consola.box("Edit rule.md with your instructions then run cralph again");
|
|
209
|
+
process.exit(0);
|
|
157
210
|
}
|
|
158
211
|
|
|
159
212
|
// Show relative paths for readability
|
package/src/prompt.ts
CHANGED
|
@@ -8,13 +8,16 @@ const BASE_PROMPT = `You are an autonomous agent running in a loop.
|
|
|
8
8
|
|
|
9
9
|
FIRST: Read and internalize the rules provided below.
|
|
10
10
|
|
|
11
|
-
Your job is to
|
|
11
|
+
Your job is to follow the rules and complete the task. Write output to the output directory.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
If refs paths are provided, they are READ-ONLY reference material. Never delete, move, or modify files in refs.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**IMPORTANT: At the end of EVERY iteration, update the TODO file with your progress.**
|
|
16
|
+
Keep the TODO structure with these sections:
|
|
17
|
+
- **# Tasks** - Checklist with [ ] for pending and [x] for done
|
|
18
|
+
- **# Notes** - Any relevant notes or context
|
|
16
19
|
|
|
17
|
-
STOPPING CONDITION: When
|
|
20
|
+
STOPPING CONDITION: When done, update the TODO file, then output exactly:
|
|
18
21
|
|
|
19
22
|
<promise>COMPLETE</promise>
|
|
20
23
|
|
|
@@ -23,8 +26,10 @@ This signals the automation to stop. Only output this tag when truly done.`;
|
|
|
23
26
|
/**
|
|
24
27
|
* Build the complete prompt with config and rules injected
|
|
25
28
|
*/
|
|
26
|
-
export function buildPrompt(config: RalphConfig, rulesContent: string): string {
|
|
27
|
-
const refsList = config.refs.
|
|
29
|
+
export function buildPrompt(config: RalphConfig, rulesContent: string, todoFile: string): string {
|
|
30
|
+
const refsList = config.refs.length > 0
|
|
31
|
+
? config.refs.map((r) => `- ${r}`).join("\n")
|
|
32
|
+
: "_None_";
|
|
28
33
|
|
|
29
34
|
return `${BASE_PROMPT}
|
|
30
35
|
|
|
@@ -32,7 +37,10 @@ export function buildPrompt(config: RalphConfig, rulesContent: string): string {
|
|
|
32
37
|
|
|
33
38
|
## Configuration
|
|
34
39
|
|
|
35
|
-
**
|
|
40
|
+
**TODO file (update after each iteration):**
|
|
41
|
+
${todoFile}
|
|
42
|
+
|
|
43
|
+
**Refs paths (optional, read-only reference material):**
|
|
36
44
|
${refsList}
|
|
37
45
|
|
|
38
46
|
**Output directory:**
|
|
@@ -42,8 +50,6 @@ ${config.output}
|
|
|
42
50
|
|
|
43
51
|
## Rules
|
|
44
52
|
|
|
45
|
-
The following rules define how to classify, refine, and write documentation:
|
|
46
|
-
|
|
47
53
|
${rulesContent}
|
|
48
54
|
`;
|
|
49
55
|
}
|
|
@@ -51,9 +57,9 @@ ${rulesContent}
|
|
|
51
57
|
/**
|
|
52
58
|
* Read rule file and build complete prompt
|
|
53
59
|
*/
|
|
54
|
-
export async function createPrompt(config: RalphConfig): Promise<string> {
|
|
60
|
+
export async function createPrompt(config: RalphConfig, todoFile: string): Promise<string> {
|
|
55
61
|
const ruleFile = Bun.file(config.rule);
|
|
56
62
|
const ruleContent = await ruleFile.text();
|
|
57
63
|
|
|
58
|
-
return buildPrompt(config, ruleContent);
|
|
64
|
+
return buildPrompt(config, ruleContent, todoFile);
|
|
59
65
|
}
|
package/src/runner.ts
CHANGED
|
@@ -1,30 +1,73 @@
|
|
|
1
1
|
import { consola } from "consola";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { mkdir } from "fs/promises";
|
|
4
|
+
import { homedir } from "os";
|
|
4
5
|
import type { RalphConfig, RunnerState, IterationResult } from "./types";
|
|
5
6
|
import { createPrompt } from "./prompt";
|
|
6
7
|
|
|
7
8
|
const COMPLETION_SIGNAL = "<promise>COMPLETE</promise>";
|
|
9
|
+
const AUTH_CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
8
10
|
|
|
9
|
-
const INITIAL_TODO_CONTENT = `#
|
|
11
|
+
const INITIAL_TODO_CONTENT = `# Tasks
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
- [ ] Task 1
|
|
14
|
+
- [ ] Task 2
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Processed Files
|
|
16
|
+
# Notes
|
|
16
17
|
|
|
17
18
|
_None yet_
|
|
19
|
+
`;
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Get the auth cache file path
|
|
23
|
+
*/
|
|
24
|
+
function getAuthCachePath(): string {
|
|
25
|
+
return join(homedir(), ".cralph", "auth-cache.json");
|
|
26
|
+
}
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Check if cached auth is still valid
|
|
30
|
+
*/
|
|
31
|
+
async function isAuthCacheValid(): Promise<boolean> {
|
|
32
|
+
try {
|
|
33
|
+
const cachePath = getAuthCachePath();
|
|
34
|
+
const file = Bun.file(cachePath);
|
|
35
|
+
if (!(await file.exists())) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const cache = await file.json();
|
|
39
|
+
const cachedAt = cache.timestamp;
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
return now - cachedAt < AUTH_CACHE_TTL_MS;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Save successful auth to cache
|
|
49
|
+
*/
|
|
50
|
+
async function saveAuthCache(): Promise<void> {
|
|
51
|
+
try {
|
|
52
|
+
const cachePath = getAuthCachePath();
|
|
53
|
+
const cacheDir = join(homedir(), ".cralph");
|
|
54
|
+
await mkdir(cacheDir, { recursive: true });
|
|
55
|
+
await Bun.write(cachePath, JSON.stringify({ timestamp: Date.now() }));
|
|
56
|
+
} catch {
|
|
57
|
+
// Ignore cache write errors
|
|
58
|
+
}
|
|
59
|
+
}
|
|
23
60
|
|
|
24
61
|
/**
|
|
25
62
|
* Check if Claude CLI is authenticated by sending a minimal test prompt
|
|
63
|
+
* Uses cache to avoid checking too frequently (6 hour TTL)
|
|
26
64
|
*/
|
|
27
65
|
export async function checkClaudeAuth(): Promise<boolean> {
|
|
66
|
+
// Check cache first
|
|
67
|
+
if (await isAuthCacheValid()) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
28
71
|
try {
|
|
29
72
|
// Send a minimal prompt to test auth
|
|
30
73
|
const proc = Bun.spawn(["claude", "-p"], {
|
|
@@ -47,8 +90,13 @@ export async function checkClaudeAuth(): Promise<boolean> {
|
|
|
47
90
|
return false;
|
|
48
91
|
}
|
|
49
92
|
|
|
50
|
-
// If exit code is 0, auth is working
|
|
51
|
-
|
|
93
|
+
// If exit code is 0, auth is working - save to cache
|
|
94
|
+
if (exitCode === 0) {
|
|
95
|
+
await saveAuthCache();
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
52
100
|
} catch {
|
|
53
101
|
return false;
|
|
54
102
|
}
|
|
@@ -101,7 +149,7 @@ Ralph Session: ${state.startTime.toISOString()}
|
|
|
101
149
|
"Found existing TODO with progress. Reset to start fresh?",
|
|
102
150
|
{
|
|
103
151
|
type: "confirm",
|
|
104
|
-
initial:
|
|
152
|
+
initial: true,
|
|
105
153
|
}
|
|
106
154
|
);
|
|
107
155
|
|
|
@@ -127,28 +175,6 @@ async function log(state: RunnerState, message: string): Promise<void> {
|
|
|
127
175
|
await Bun.write(state.logFile, existing + logLine);
|
|
128
176
|
}
|
|
129
177
|
|
|
130
|
-
/**
|
|
131
|
-
* Count files in refs directories (excluding .gitkeep and hidden files)
|
|
132
|
-
*/
|
|
133
|
-
async function countRefs(refs: string[]): Promise<number> {
|
|
134
|
-
let count = 0;
|
|
135
|
-
|
|
136
|
-
for (const refPath of refs) {
|
|
137
|
-
try {
|
|
138
|
-
const entries = await Array.fromAsync(
|
|
139
|
-
new Bun.Glob("**/*").scan({ cwd: refPath, onlyFiles: true })
|
|
140
|
-
);
|
|
141
|
-
count += entries.filter(
|
|
142
|
-
(e) => !e.startsWith(".") && !e.includes("/.") && e !== ".gitkeep"
|
|
143
|
-
).length;
|
|
144
|
-
} catch {
|
|
145
|
-
// Directory might not exist or be empty
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return count;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
178
|
// Track current subprocess for cleanup
|
|
153
179
|
let currentProc: ReturnType<typeof Bun.spawn> | null = null;
|
|
154
180
|
|
|
@@ -234,17 +260,8 @@ export async function run(config: RalphConfig): Promise<void> {
|
|
|
234
260
|
consola.info(`Log: ${state.logFile}`);
|
|
235
261
|
consola.info(`TODO: ${state.todoFile}`);
|
|
236
262
|
|
|
237
|
-
// Count initial refs
|
|
238
|
-
const initialCount = await countRefs(config.refs);
|
|
239
|
-
consola.info(`Found ${initialCount} files to process`);
|
|
240
|
-
|
|
241
|
-
if (initialCount === 0) {
|
|
242
|
-
consola.warn("No files found in refs directories");
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
263
|
// Build prompt once
|
|
247
|
-
const prompt = await createPrompt(config);
|
|
264
|
+
const prompt = await createPrompt(config, state.todoFile);
|
|
248
265
|
|
|
249
266
|
// Ensure output directory exists
|
|
250
267
|
await mkdir(config.output, { recursive: true });
|
|
@@ -255,9 +272,6 @@ export async function run(config: RalphConfig): Promise<void> {
|
|
|
255
272
|
while (true) {
|
|
256
273
|
console.log("━".repeat(40));
|
|
257
274
|
|
|
258
|
-
const refCount = await countRefs(config.refs);
|
|
259
|
-
consola.info(`${refCount} ref files remaining`);
|
|
260
|
-
|
|
261
275
|
const result = await runIteration(prompt, state, cwd);
|
|
262
276
|
|
|
263
277
|
if (result.isComplete) {
|