cralph 1.0.0-alpha.3 → 1.0.0-alpha.5
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 +71 -75
- package/package.json +1 -1
- package/src/paths.ts +45 -7
- package/src/prompt.ts +17 -11
- package/src/runner.ts +99 -55
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,113 +35,108 @@ 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
|
-
|
|
107
|
+
## First Run (Empty Directory)
|
|
108
|
+
|
|
111
109
|
```
|
|
112
|
-
ℹ
|
|
110
|
+
ℹ Created .ralph/refs/ directory
|
|
111
|
+
ℹ Created .ralph/rule.md with starter template
|
|
112
|
+
ℹ Created .ralph/paths.json
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
╭──────────────────────────────────────────────╮
|
|
115
|
+
│ 1. Add source files to .ralph/refs/ │
|
|
116
|
+
│ 2. Edit .ralph/rule.md with your instructions│
|
|
117
|
+
│ 3. Run cralph again │
|
|
118
|
+
╰──────────────────────────────────────────────╯
|
|
119
|
+
```
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
❯ Select rule file:
|
|
122
|
-
● 📄 .cursor/rules/my-rules.mdc (cursor rule)
|
|
123
|
-
○ 📄 README.md
|
|
121
|
+
## Prompts
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
**Config detected:**
|
|
124
|
+
```
|
|
125
|
+
❯ Found .ralph/paths.json. What would you like to do?
|
|
126
|
+
● 🚀 Run with this config
|
|
127
|
+
○ ✏️ Edit configuration
|
|
129
128
|
```
|
|
130
129
|
|
|
131
|
-
**
|
|
130
|
+
**TODO has progress:**
|
|
132
131
|
```
|
|
133
|
-
?
|
|
134
|
-
✔ Saved .ralph/paths.json
|
|
132
|
+
? Found existing TODO with progress. Reset to start fresh? (Y/n)
|
|
135
133
|
```
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
- Press `Ctrl+C` at any time to exit
|
|
139
|
-
- Running Claude processes are terminated cleanly
|
|
135
|
+
## Path Selection
|
|
140
136
|
|
|
141
|
-
**
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
- `.ralph/TODO.md` - Agent status tracker
|
|
137
|
+
- **Space** - Toggle selection
|
|
138
|
+
- **Enter** - Confirm
|
|
139
|
+
- **Ctrl+C** - Exit
|
|
145
140
|
|
|
146
141
|
## Testing
|
|
147
142
|
|
|
@@ -149,7 +144,8 @@ If not authenticated:
|
|
|
149
144
|
bun test
|
|
150
145
|
```
|
|
151
146
|
|
|
152
|
-
|
|
147
|
+
- **Unit tests** - Config, prompt building, CLI
|
|
148
|
+
- **E2E tests** - Full loop with Claude (requires auth)
|
|
153
149
|
|
|
154
150
|
## Requirements
|
|
155
151
|
|
package/package.json
CHANGED
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,47 @@ 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)
|
|
110
140
|
*/
|
|
111
141
|
export async function selectRefs(cwd: string, defaults?: string[]): Promise<string[]> {
|
|
112
142
|
// Get all directories up to 3 levels deep
|
|
113
|
-
|
|
143
|
+
let allDirs = await listDirectoriesRecursive(cwd, 3);
|
|
114
144
|
|
|
115
145
|
if (allDirs.length === 0) {
|
|
116
|
-
|
|
117
|
-
|
|
146
|
+
// Create starter structure and exit gracefully
|
|
147
|
+
await createStarterStructure(cwd);
|
|
148
|
+
process.exit(0);
|
|
118
149
|
}
|
|
119
150
|
|
|
120
151
|
// Convert to relative paths for display
|
|
@@ -146,14 +177,21 @@ export async function selectRefs(cwd: string, defaults?: string[]): Promise<stri
|
|
|
146
177
|
return selected as string[];
|
|
147
178
|
}
|
|
148
179
|
|
|
180
|
+
const STARTER_RULE = `I want a file named hello.txt
|
|
181
|
+
`;
|
|
182
|
+
|
|
149
183
|
/**
|
|
150
184
|
* Prompt user to select a rule file
|
|
151
185
|
*/
|
|
152
186
|
export async function selectRule(cwd: string, defaultRule?: string): Promise<string> {
|
|
153
|
-
|
|
187
|
+
let files = await listFilesRecursive(cwd, [".mdc", ".md"]);
|
|
154
188
|
if (files.length === 0) {
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
// This shouldn't happen if selectRefs ran first, but handle it just in case
|
|
190
|
+
const rulePath = join(cwd, "rule.md");
|
|
191
|
+
await Bun.write(rulePath, STARTER_RULE);
|
|
192
|
+
consola.info("Created rule.md with starter template");
|
|
193
|
+
consola.box("Edit rule.md with your instructions then run cralph again");
|
|
194
|
+
process.exit(0);
|
|
157
195
|
}
|
|
158
196
|
|
|
159
197
|
// 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,15 +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
|
|
10
|
+
|
|
11
|
+
const INITIAL_TODO_CONTENT = `# Tasks
|
|
12
|
+
|
|
13
|
+
- [ ] Task 1
|
|
14
|
+
- [ ] Task 2
|
|
15
|
+
|
|
16
|
+
# Notes
|
|
17
|
+
|
|
18
|
+
_None yet_
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the auth cache file path
|
|
23
|
+
*/
|
|
24
|
+
function getAuthCachePath(): string {
|
|
25
|
+
return join(homedir(), ".cralph", "auth-cache.json");
|
|
26
|
+
}
|
|
27
|
+
|
|
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
|
+
}
|
|
8
60
|
|
|
9
61
|
/**
|
|
10
62
|
* Check if Claude CLI is authenticated by sending a minimal test prompt
|
|
63
|
+
* Uses cache to avoid checking too frequently (6 hour TTL)
|
|
11
64
|
*/
|
|
12
65
|
export async function checkClaudeAuth(): Promise<boolean> {
|
|
66
|
+
// Check cache first
|
|
67
|
+
if (await isAuthCacheValid()) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
13
71
|
try {
|
|
14
72
|
// Send a minimal prompt to test auth
|
|
15
73
|
const proc = Bun.spawn(["claude", "-p"], {
|
|
@@ -32,14 +90,31 @@ export async function checkClaudeAuth(): Promise<boolean> {
|
|
|
32
90
|
return false;
|
|
33
91
|
}
|
|
34
92
|
|
|
35
|
-
// If exit code is 0, auth is working
|
|
36
|
-
|
|
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;
|
|
37
100
|
} catch {
|
|
38
101
|
return false;
|
|
39
102
|
}
|
|
40
103
|
}
|
|
41
104
|
|
|
42
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Check if the TODO file is in a clean/initial state
|
|
108
|
+
*/
|
|
109
|
+
async function isTodoClean(todoPath: string): Promise<boolean> {
|
|
110
|
+
const file = Bun.file(todoPath);
|
|
111
|
+
if (!(await file.exists())) {
|
|
112
|
+
return true; // Non-existent is considered clean
|
|
113
|
+
}
|
|
114
|
+
const content = await file.text();
|
|
115
|
+
return content.trim() === INITIAL_TODO_CONTENT.trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
43
118
|
/**
|
|
44
119
|
* Initialize the runner state and log file
|
|
45
120
|
*/
|
|
@@ -61,26 +136,29 @@ Ralph Session: ${state.startTime.toISOString()}
|
|
|
61
136
|
|
|
62
137
|
await Bun.write(state.logFile, logHeader);
|
|
63
138
|
|
|
64
|
-
//
|
|
139
|
+
// Check TODO file state
|
|
65
140
|
const todoFile = Bun.file(state.todoFile);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
## Pending
|
|
80
|
-
|
|
81
|
-
_Check refs/ for new documents_
|
|
82
|
-
`
|
|
141
|
+
const todoExists = await todoFile.exists();
|
|
142
|
+
|
|
143
|
+
if (!todoExists) {
|
|
144
|
+
// Create fresh TODO file
|
|
145
|
+
await Bun.write(state.todoFile, INITIAL_TODO_CONTENT);
|
|
146
|
+
} else if (!(await isTodoClean(state.todoFile))) {
|
|
147
|
+
// TODO exists and has been modified - ask about reset
|
|
148
|
+
const response = await consola.prompt(
|
|
149
|
+
"Found existing TODO with progress. Reset to start fresh?",
|
|
150
|
+
{
|
|
151
|
+
type: "confirm",
|
|
152
|
+
initial: true,
|
|
153
|
+
}
|
|
83
154
|
);
|
|
155
|
+
|
|
156
|
+
if (response === true) {
|
|
157
|
+
await Bun.write(state.todoFile, INITIAL_TODO_CONTENT);
|
|
158
|
+
consola.info("TODO reset to clean state");
|
|
159
|
+
} else {
|
|
160
|
+
consola.info("Continuing with existing TODO state");
|
|
161
|
+
}
|
|
84
162
|
}
|
|
85
163
|
|
|
86
164
|
return state;
|
|
@@ -97,28 +175,6 @@ async function log(state: RunnerState, message: string): Promise<void> {
|
|
|
97
175
|
await Bun.write(state.logFile, existing + logLine);
|
|
98
176
|
}
|
|
99
177
|
|
|
100
|
-
/**
|
|
101
|
-
* Count files in refs directories (excluding .gitkeep and hidden files)
|
|
102
|
-
*/
|
|
103
|
-
async function countRefs(refs: string[]): Promise<number> {
|
|
104
|
-
let count = 0;
|
|
105
|
-
|
|
106
|
-
for (const refPath of refs) {
|
|
107
|
-
try {
|
|
108
|
-
const entries = await Array.fromAsync(
|
|
109
|
-
new Bun.Glob("**/*").scan({ cwd: refPath, onlyFiles: true })
|
|
110
|
-
);
|
|
111
|
-
count += entries.filter(
|
|
112
|
-
(e) => !e.startsWith(".") && !e.includes("/.") && e !== ".gitkeep"
|
|
113
|
-
).length;
|
|
114
|
-
} catch {
|
|
115
|
-
// Directory might not exist or be empty
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return count;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
178
|
// Track current subprocess for cleanup
|
|
123
179
|
let currentProc: ReturnType<typeof Bun.spawn> | null = null;
|
|
124
180
|
|
|
@@ -204,17 +260,8 @@ export async function run(config: RalphConfig): Promise<void> {
|
|
|
204
260
|
consola.info(`Log: ${state.logFile}`);
|
|
205
261
|
consola.info(`TODO: ${state.todoFile}`);
|
|
206
262
|
|
|
207
|
-
// Count initial refs
|
|
208
|
-
const initialCount = await countRefs(config.refs);
|
|
209
|
-
consola.info(`Found ${initialCount} files to process`);
|
|
210
|
-
|
|
211
|
-
if (initialCount === 0) {
|
|
212
|
-
consola.warn("No files found in refs directories");
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
263
|
// Build prompt once
|
|
217
|
-
const prompt = await createPrompt(config);
|
|
264
|
+
const prompt = await createPrompt(config, state.todoFile);
|
|
218
265
|
|
|
219
266
|
// Ensure output directory exists
|
|
220
267
|
await mkdir(config.output, { recursive: true });
|
|
@@ -225,9 +272,6 @@ export async function run(config: RalphConfig): Promise<void> {
|
|
|
225
272
|
while (true) {
|
|
226
273
|
console.log("━".repeat(40));
|
|
227
274
|
|
|
228
|
-
const refCount = await countRefs(config.refs);
|
|
229
|
-
consola.info(`${refCount} ref files remaining`);
|
|
230
|
-
|
|
231
275
|
const result = await runIteration(prompt, state, cwd);
|
|
232
276
|
|
|
233
277
|
if (result.isComplete) {
|