ralph-cli-claude 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 +149 -0
- package/dist/commands/docker.d.ts +1 -0
- package/dist/commands/docker.js +406 -0
- package/dist/commands/help.d.ts +1 -0
- package/dist/commands/help.js +44 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +98 -0
- package/dist/commands/once.d.ts +1 -0
- package/dist/commands/once.js +28 -0
- package/dist/commands/prd.d.ts +1 -0
- package/dist/commands/prd.js +149 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +64 -0
- package/dist/commands/scripts.d.ts +1 -0
- package/dist/commands/scripts.js +115 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +44 -0
- package/dist/templates/prompts.d.ts +10 -0
- package/dist/templates/prompts.js +72 -0
- package/dist/utils/config.d.ts +17 -0
- package/dist/utils/config.js +47 -0
- package/dist/utils/prompt.d.ts +7 -0
- package/dist/utils/prompt.js +53 -0
- package/package.json +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function once(_args: string[]): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { checkFilesExist, loadPrompt, getPaths } from "../utils/config.js";
|
|
3
|
+
export async function once(_args) {
|
|
4
|
+
checkFilesExist();
|
|
5
|
+
const prompt = loadPrompt();
|
|
6
|
+
const paths = getPaths();
|
|
7
|
+
console.log("Starting single ralph iteration...\n");
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const proc = spawn("claude", [
|
|
10
|
+
"--permission-mode", "acceptEdits",
|
|
11
|
+
"--dangerously-skip-permissions",
|
|
12
|
+
"-p",
|
|
13
|
+
`@${paths.prd} @${paths.progress} ${prompt}`,
|
|
14
|
+
], {
|
|
15
|
+
stdio: "inherit",
|
|
16
|
+
shell: true,
|
|
17
|
+
});
|
|
18
|
+
proc.on("close", (code) => {
|
|
19
|
+
if (code !== 0) {
|
|
20
|
+
console.error(`\nClaude exited with code ${code}`);
|
|
21
|
+
}
|
|
22
|
+
resolve();
|
|
23
|
+
});
|
|
24
|
+
proc.on("error", (err) => {
|
|
25
|
+
reject(new Error(`Failed to start claude: ${err.message}`));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function prd(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { promptInput, promptSelect } from "../utils/prompt.js";
|
|
4
|
+
import { getRalphDir } from "../utils/config.js";
|
|
5
|
+
const PRD_FILE = "prd.json";
|
|
6
|
+
const CATEGORIES = ["ui", "feature", "bugfix", "setup", "development", "testing", "docs"];
|
|
7
|
+
function getPrdPath() {
|
|
8
|
+
return join(getRalphDir(), PRD_FILE);
|
|
9
|
+
}
|
|
10
|
+
function loadPrd() {
|
|
11
|
+
const path = getPrdPath();
|
|
12
|
+
if (!existsSync(path)) {
|
|
13
|
+
throw new Error(".ralph/prd.json not found. Run 'ralph init' first.");
|
|
14
|
+
}
|
|
15
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
16
|
+
}
|
|
17
|
+
function savePrd(entries) {
|
|
18
|
+
writeFileSync(getPrdPath(), JSON.stringify(entries, null, 2) + "\n");
|
|
19
|
+
}
|
|
20
|
+
async function add() {
|
|
21
|
+
console.log("Add new PRD entry\n");
|
|
22
|
+
const category = await promptSelect("Select category:", CATEGORIES);
|
|
23
|
+
const description = await promptInput("\nDescription: ");
|
|
24
|
+
if (!description) {
|
|
25
|
+
console.error("Description is required.");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
console.log("\nEnter verification steps (empty line to finish):");
|
|
29
|
+
const steps = [];
|
|
30
|
+
let stepNum = 1;
|
|
31
|
+
while (true) {
|
|
32
|
+
const step = await promptInput(` Step ${stepNum}: `);
|
|
33
|
+
if (!step)
|
|
34
|
+
break;
|
|
35
|
+
steps.push(step);
|
|
36
|
+
stepNum++;
|
|
37
|
+
}
|
|
38
|
+
if (steps.length === 0) {
|
|
39
|
+
steps.push("Verify the feature works as expected");
|
|
40
|
+
}
|
|
41
|
+
const entry = {
|
|
42
|
+
category,
|
|
43
|
+
description,
|
|
44
|
+
steps,
|
|
45
|
+
passes: false,
|
|
46
|
+
};
|
|
47
|
+
const prd = loadPrd();
|
|
48
|
+
prd.push(entry);
|
|
49
|
+
savePrd(prd);
|
|
50
|
+
console.log(`\nAdded entry #${prd.length}: "${description}"`);
|
|
51
|
+
}
|
|
52
|
+
function list() {
|
|
53
|
+
const prd = loadPrd();
|
|
54
|
+
if (prd.length === 0) {
|
|
55
|
+
console.log("No PRD entries found.");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.log("\nPRD Entries:\n");
|
|
59
|
+
prd.forEach((entry, i) => {
|
|
60
|
+
const status = entry.passes ? "\x1b[32m[PASS]\x1b[0m" : "\x1b[33m[ ]\x1b[0m";
|
|
61
|
+
console.log(` ${i + 1}. ${status} [${entry.category}] ${entry.description}`);
|
|
62
|
+
entry.steps.forEach((step, j) => {
|
|
63
|
+
console.log(` ${j + 1}. ${step}`);
|
|
64
|
+
});
|
|
65
|
+
console.log();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function status() {
|
|
69
|
+
const prd = loadPrd();
|
|
70
|
+
if (prd.length === 0) {
|
|
71
|
+
console.log("No PRD entries found.");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const passing = prd.filter((e) => e.passes).length;
|
|
75
|
+
const total = prd.length;
|
|
76
|
+
const percentage = Math.round((passing / total) * 100);
|
|
77
|
+
console.log(`\nPRD Status: ${passing}/${total} passing (${percentage}%)\n`);
|
|
78
|
+
// Progress bar
|
|
79
|
+
const barWidth = 30;
|
|
80
|
+
const filled = Math.round((passing / total) * barWidth);
|
|
81
|
+
const bar = "\x1b[32m" + "\u2588".repeat(filled) + "\x1b[0m" + "\u2591".repeat(barWidth - filled);
|
|
82
|
+
console.log(` [${bar}]\n`);
|
|
83
|
+
// By category
|
|
84
|
+
const byCategory = {};
|
|
85
|
+
prd.forEach((entry) => {
|
|
86
|
+
if (!byCategory[entry.category]) {
|
|
87
|
+
byCategory[entry.category] = { pass: 0, total: 0 };
|
|
88
|
+
}
|
|
89
|
+
byCategory[entry.category].total++;
|
|
90
|
+
if (entry.passes)
|
|
91
|
+
byCategory[entry.category].pass++;
|
|
92
|
+
});
|
|
93
|
+
console.log(" By category:");
|
|
94
|
+
Object.entries(byCategory).forEach(([cat, stats]) => {
|
|
95
|
+
console.log(` ${cat}: ${stats.pass}/${stats.total}`);
|
|
96
|
+
});
|
|
97
|
+
if (passing === total) {
|
|
98
|
+
console.log("\n \x1b[32m\u2713 All requirements complete!\x1b[0m");
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const remaining = prd.filter((e) => !e.passes);
|
|
102
|
+
console.log(`\n Remaining (${remaining.length}):`);
|
|
103
|
+
remaining.forEach((entry) => {
|
|
104
|
+
console.log(` - [${entry.category}] ${entry.description}`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function toggle(args) {
|
|
109
|
+
const index = parseInt(args[0]);
|
|
110
|
+
if (!index || isNaN(index)) {
|
|
111
|
+
console.error("Usage: ralph prd toggle <number>");
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
const prd = loadPrd();
|
|
115
|
+
if (index < 1 || index > prd.length) {
|
|
116
|
+
console.error(`Invalid entry number. Must be 1-${prd.length}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
const entry = prd[index - 1];
|
|
120
|
+
entry.passes = !entry.passes;
|
|
121
|
+
savePrd(prd);
|
|
122
|
+
const statusText = entry.passes ? "PASSING" : "NOT PASSING";
|
|
123
|
+
console.log(`Entry #${index} "${entry.description}" is now ${statusText}`);
|
|
124
|
+
}
|
|
125
|
+
export async function prd(args) {
|
|
126
|
+
const subcommand = args[0];
|
|
127
|
+
switch (subcommand) {
|
|
128
|
+
case "add":
|
|
129
|
+
await add();
|
|
130
|
+
break;
|
|
131
|
+
case "list":
|
|
132
|
+
list();
|
|
133
|
+
break;
|
|
134
|
+
case "status":
|
|
135
|
+
status();
|
|
136
|
+
break;
|
|
137
|
+
case "toggle":
|
|
138
|
+
toggle(args.slice(1));
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
console.error("Usage: ralph prd <add|list|status|toggle>");
|
|
142
|
+
console.error("\nSubcommands:");
|
|
143
|
+
console.error(" add Add a new PRD entry");
|
|
144
|
+
console.error(" list List all PRD entries");
|
|
145
|
+
console.error(" status Show completion status");
|
|
146
|
+
console.error(" toggle <n> Toggle passes status for entry n");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { checkFilesExist, loadPrompt, getPaths } from "../utils/config.js";
|
|
3
|
+
async function runIteration(prompt, paths) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
let output = "";
|
|
6
|
+
const proc = spawn("claude", [
|
|
7
|
+
"--permission-mode", "acceptEdits",
|
|
8
|
+
"--dangerously-skip-permissions",
|
|
9
|
+
"-p",
|
|
10
|
+
`@${paths.prd} @${paths.progress} ${prompt}`,
|
|
11
|
+
], {
|
|
12
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
13
|
+
shell: true,
|
|
14
|
+
});
|
|
15
|
+
proc.stdout.on("data", (data) => {
|
|
16
|
+
const chunk = data.toString();
|
|
17
|
+
output += chunk;
|
|
18
|
+
process.stdout.write(chunk);
|
|
19
|
+
});
|
|
20
|
+
proc.on("close", (code) => {
|
|
21
|
+
resolve({ exitCode: code ?? 0, output });
|
|
22
|
+
});
|
|
23
|
+
proc.on("error", (err) => {
|
|
24
|
+
reject(new Error(`Failed to start claude: ${err.message}`));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function run(args) {
|
|
29
|
+
const iterations = parseInt(args[0]);
|
|
30
|
+
if (!iterations || iterations < 1 || isNaN(iterations)) {
|
|
31
|
+
console.error("Usage: ralph run <iterations>");
|
|
32
|
+
console.error(" <iterations> must be a positive integer");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
checkFilesExist();
|
|
36
|
+
const prompt = loadPrompt();
|
|
37
|
+
const paths = getPaths();
|
|
38
|
+
console.log(`Starting ${iterations} ralph iteration(s)...\n`);
|
|
39
|
+
for (let i = 1; i <= iterations; i++) {
|
|
40
|
+
console.log(`\n${"=".repeat(50)}`);
|
|
41
|
+
console.log(`Iteration ${i} of ${iterations}`);
|
|
42
|
+
console.log(`${"=".repeat(50)}\n`);
|
|
43
|
+
const { exitCode, output } = await runIteration(prompt, paths);
|
|
44
|
+
if (exitCode !== 0) {
|
|
45
|
+
console.error(`\nClaude exited with code ${exitCode}`);
|
|
46
|
+
console.log("Continuing to next iteration...");
|
|
47
|
+
}
|
|
48
|
+
// Check for completion signal
|
|
49
|
+
if (output.includes("<promise>COMPLETE</promise>")) {
|
|
50
|
+
console.log("\n" + "=".repeat(50));
|
|
51
|
+
console.log("PRD COMPLETE - All features implemented!");
|
|
52
|
+
console.log("=".repeat(50));
|
|
53
|
+
// Try to send notification (optional)
|
|
54
|
+
try {
|
|
55
|
+
spawn("tt", ["notify", "Ralph: PRD Complete!"], { stdio: "ignore" });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// tt notify not available, ignore
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
console.log("\nRalph run finished.");
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function scripts(_args: string[]): Promise<void>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, writeFileSync, chmodSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { promptConfirm } from "../utils/prompt.js";
|
|
4
|
+
import { getRalphDir } from "../utils/config.js";
|
|
5
|
+
const RALPH_ONCE_SH = `#!/usr/bin/env bash
|
|
6
|
+
# Generated by ralph-cli
|
|
7
|
+
# Run a single automation iteration
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
RALPH_DIR="$SCRIPT_DIR/.ralph"
|
|
13
|
+
|
|
14
|
+
if [ ! -f "$RALPH_DIR/prompt.md" ]; then
|
|
15
|
+
echo "Error: .ralph/prompt.md not found. Run 'ralph init' first."
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
PROMPT=$(cat "$RALPH_DIR/prompt.md")
|
|
20
|
+
|
|
21
|
+
claude --permission-mode acceptEdits --dangerously-skip-permissions \\
|
|
22
|
+
-p "@$RALPH_DIR/prd.json @$RALPH_DIR/progress.txt $PROMPT"
|
|
23
|
+
`;
|
|
24
|
+
const RALPH_SH = `#!/usr/bin/env bash
|
|
25
|
+
# Generated by ralph-cli
|
|
26
|
+
# Run multiple automation iterations
|
|
27
|
+
|
|
28
|
+
set -e
|
|
29
|
+
|
|
30
|
+
SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
31
|
+
RALPH_DIR="$SCRIPT_DIR/.ralph"
|
|
32
|
+
|
|
33
|
+
if [ -z "$1" ]; then
|
|
34
|
+
echo "Usage: ./ralph.sh <iterations>"
|
|
35
|
+
echo " <iterations> Number of automation cycles to run"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if ! [[ "$1" =~ ^[0-9]+$ ]] || [ "$1" -lt 1 ]; then
|
|
40
|
+
echo "Error: iterations must be a positive integer"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
ITERATIONS=$1
|
|
45
|
+
|
|
46
|
+
if [ ! -f "$RALPH_DIR/prompt.md" ]; then
|
|
47
|
+
echo "Error: .ralph/prompt.md not found. Run 'ralph init' first."
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
PROMPT=$(cat "$RALPH_DIR/prompt.md")
|
|
52
|
+
|
|
53
|
+
TEMP_FILE=$(mktemp)
|
|
54
|
+
trap "rm -f $TEMP_FILE" EXIT
|
|
55
|
+
|
|
56
|
+
for ((i=1; i<=ITERATIONS; i++)); do
|
|
57
|
+
echo ""
|
|
58
|
+
echo "=================================================="
|
|
59
|
+
echo "Iteration $i of $ITERATIONS"
|
|
60
|
+
echo "=================================================="
|
|
61
|
+
echo ""
|
|
62
|
+
|
|
63
|
+
set +e
|
|
64
|
+
claude --permission-mode acceptEdits --dangerously-skip-permissions \\
|
|
65
|
+
-p "@$RALPH_DIR/prd.json @$RALPH_DIR/progress.txt $PROMPT" | tee "$TEMP_FILE"
|
|
66
|
+
set -e
|
|
67
|
+
|
|
68
|
+
if grep -q "<promise>COMPLETE</promise>" "$TEMP_FILE"; then
|
|
69
|
+
echo ""
|
|
70
|
+
echo "=================================================="
|
|
71
|
+
echo "PRD COMPLETE - All features implemented!"
|
|
72
|
+
echo "=================================================="
|
|
73
|
+
|
|
74
|
+
# Try to send notification (optional)
|
|
75
|
+
if command -v tt &> /dev/null; then
|
|
76
|
+
tt notify "Ralph: PRD Complete!"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
break
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
echo ""
|
|
84
|
+
echo "Ralph run finished."
|
|
85
|
+
`;
|
|
86
|
+
export async function scripts(_args) {
|
|
87
|
+
const cwd = process.cwd();
|
|
88
|
+
const ralphDir = getRalphDir();
|
|
89
|
+
// Check if .ralph directory exists (means init was run)
|
|
90
|
+
if (!existsSync(ralphDir)) {
|
|
91
|
+
console.error("Error: .ralph/ directory not found. Run 'ralph init' first.");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
console.log("Generate shell scripts for sandboxed environments\n");
|
|
95
|
+
const files = [
|
|
96
|
+
{ name: "ralph.sh", content: RALPH_SH },
|
|
97
|
+
{ name: "ralph-once.sh", content: RALPH_ONCE_SH },
|
|
98
|
+
];
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
const filePath = join(cwd, file.name);
|
|
101
|
+
if (existsSync(filePath)) {
|
|
102
|
+
const overwrite = await promptConfirm(`${file.name} already exists. Overwrite?`);
|
|
103
|
+
if (!overwrite) {
|
|
104
|
+
console.log(`Skipped ${file.name}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
writeFileSync(filePath, file.content);
|
|
109
|
+
chmodSync(filePath, 0o755);
|
|
110
|
+
console.log(`Created ${file.name}`);
|
|
111
|
+
}
|
|
112
|
+
console.log("\nShell scripts created! Usage:");
|
|
113
|
+
console.log(" ./ralph-once.sh # Single iteration");
|
|
114
|
+
console.log(" ./ralph.sh 5 # Run 5 iterations");
|
|
115
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { help } from "./commands/help.js";
|
|
3
|
+
import { init } from "./commands/init.js";
|
|
4
|
+
import { once } from "./commands/once.js";
|
|
5
|
+
import { run } from "./commands/run.js";
|
|
6
|
+
import { prd } from "./commands/prd.js";
|
|
7
|
+
import { scripts } from "./commands/scripts.js";
|
|
8
|
+
import { docker } from "./commands/docker.js";
|
|
9
|
+
const commands = {
|
|
10
|
+
help,
|
|
11
|
+
init,
|
|
12
|
+
once,
|
|
13
|
+
run,
|
|
14
|
+
prd,
|
|
15
|
+
scripts,
|
|
16
|
+
docker,
|
|
17
|
+
};
|
|
18
|
+
async function main() {
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const command = args[0];
|
|
21
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
22
|
+
help([]);
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
const handler = commands[command];
|
|
26
|
+
if (!handler) {
|
|
27
|
+
console.error(`Unknown command: ${command}`);
|
|
28
|
+
console.error(`Run 'ralph help' for usage information.`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
await handler(args.slice(1));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error instanceof Error) {
|
|
36
|
+
console.error(`Error: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.error("An unexpected error occurred");
|
|
40
|
+
}
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
main();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface LanguageConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
checkCommand: string;
|
|
4
|
+
testCommand: string;
|
|
5
|
+
description: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const LANGUAGES: Record<string, LanguageConfig>;
|
|
8
|
+
export declare function generatePrompt(config: LanguageConfig): string;
|
|
9
|
+
export declare const DEFAULT_PRD = "[\n {\n \"category\": \"setup\",\n \"description\": \"Example: Project builds successfully\",\n \"steps\": [\n \"Run the build command\",\n \"Verify no errors occur\"\n ],\n \"passes\": false\n }\n]";
|
|
10
|
+
export declare const DEFAULT_PROGRESS = "# Progress Log\n";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const LANGUAGES = {
|
|
2
|
+
bun: {
|
|
3
|
+
name: "Bun (TypeScript)",
|
|
4
|
+
checkCommand: "bun check",
|
|
5
|
+
testCommand: "bun test",
|
|
6
|
+
description: "Bun runtime with TypeScript",
|
|
7
|
+
},
|
|
8
|
+
node: {
|
|
9
|
+
name: "Node.js (TypeScript)",
|
|
10
|
+
checkCommand: "npm run typecheck",
|
|
11
|
+
testCommand: "npm test",
|
|
12
|
+
description: "Node.js with TypeScript",
|
|
13
|
+
},
|
|
14
|
+
python: {
|
|
15
|
+
name: "Python",
|
|
16
|
+
checkCommand: "mypy .",
|
|
17
|
+
testCommand: "pytest",
|
|
18
|
+
description: "Python with mypy type checking",
|
|
19
|
+
},
|
|
20
|
+
go: {
|
|
21
|
+
name: "Go",
|
|
22
|
+
checkCommand: "go build ./...",
|
|
23
|
+
testCommand: "go test ./...",
|
|
24
|
+
description: "Go language",
|
|
25
|
+
},
|
|
26
|
+
rust: {
|
|
27
|
+
name: "Rust",
|
|
28
|
+
checkCommand: "cargo check",
|
|
29
|
+
testCommand: "cargo test",
|
|
30
|
+
description: "Rust with Cargo",
|
|
31
|
+
},
|
|
32
|
+
none: {
|
|
33
|
+
name: "None (custom)",
|
|
34
|
+
checkCommand: "echo 'no check configured'",
|
|
35
|
+
testCommand: "echo 'no tests configured'",
|
|
36
|
+
description: "Custom configuration",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export function generatePrompt(config) {
|
|
40
|
+
return `You are an AI developer working on this project. Your task is to implement features from the PRD.
|
|
41
|
+
|
|
42
|
+
INSTRUCTIONS:
|
|
43
|
+
1. Read the @prd.json file to find the highest priority feature that has "passes": false
|
|
44
|
+
2. Implement that feature completely
|
|
45
|
+
3. Verify your changes work by running:
|
|
46
|
+
- Type/build check: ${config.checkCommand}
|
|
47
|
+
- Tests: ${config.testCommand}
|
|
48
|
+
4. Update the PRD entry to set "passes": true once verified
|
|
49
|
+
5. Append a brief note about what you did to @progress.txt
|
|
50
|
+
6. Create a git commit with a descriptive message for this feature
|
|
51
|
+
7. Only work on ONE feature per execution
|
|
52
|
+
|
|
53
|
+
IMPORTANT:
|
|
54
|
+
- Focus on a single feature at a time
|
|
55
|
+
- Ensure all checks pass before marking complete
|
|
56
|
+
- Write clear commit messages
|
|
57
|
+
- If the PRD is fully complete (all items pass), output: <promise>COMPLETE</promise>
|
|
58
|
+
|
|
59
|
+
Now, read the PRD and begin working on the highest priority incomplete feature.`;
|
|
60
|
+
}
|
|
61
|
+
export const DEFAULT_PRD = `[
|
|
62
|
+
{
|
|
63
|
+
"category": "setup",
|
|
64
|
+
"description": "Example: Project builds successfully",
|
|
65
|
+
"steps": [
|
|
66
|
+
"Run the build command",
|
|
67
|
+
"Verify no errors occur"
|
|
68
|
+
],
|
|
69
|
+
"passes": false
|
|
70
|
+
}
|
|
71
|
+
]`;
|
|
72
|
+
export const DEFAULT_PROGRESS = `# Progress Log\n`;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface RalphConfig {
|
|
2
|
+
language: string;
|
|
3
|
+
checkCommand: string;
|
|
4
|
+
testCommand: string;
|
|
5
|
+
imageName?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function getRalphDir(): string;
|
|
8
|
+
export declare function loadConfig(): RalphConfig;
|
|
9
|
+
export declare function loadPrompt(): string;
|
|
10
|
+
export declare function checkFilesExist(): void;
|
|
11
|
+
export declare function getPaths(): {
|
|
12
|
+
dir: string;
|
|
13
|
+
config: string;
|
|
14
|
+
prompt: string;
|
|
15
|
+
prd: string;
|
|
16
|
+
progress: string;
|
|
17
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const RALPH_DIR = ".ralph";
|
|
4
|
+
const CONFIG_FILE = "config.json";
|
|
5
|
+
const PROMPT_FILE = "prompt.md";
|
|
6
|
+
const PRD_FILE = "prd.json";
|
|
7
|
+
const PROGRESS_FILE = "progress.txt";
|
|
8
|
+
export function getRalphDir() {
|
|
9
|
+
return join(process.cwd(), RALPH_DIR);
|
|
10
|
+
}
|
|
11
|
+
export function loadConfig() {
|
|
12
|
+
const configPath = join(getRalphDir(), CONFIG_FILE);
|
|
13
|
+
if (!existsSync(configPath)) {
|
|
14
|
+
throw new Error(".ralph/config.json not found. Run 'ralph init' first.");
|
|
15
|
+
}
|
|
16
|
+
const content = readFileSync(configPath, "utf-8");
|
|
17
|
+
return JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
export function loadPrompt() {
|
|
20
|
+
const promptPath = join(getRalphDir(), PROMPT_FILE);
|
|
21
|
+
if (!existsSync(promptPath)) {
|
|
22
|
+
throw new Error(".ralph/prompt.md not found. Run 'ralph init' first.");
|
|
23
|
+
}
|
|
24
|
+
return readFileSync(promptPath, "utf-8");
|
|
25
|
+
}
|
|
26
|
+
export function checkFilesExist() {
|
|
27
|
+
const ralphDir = getRalphDir();
|
|
28
|
+
if (!existsSync(ralphDir)) {
|
|
29
|
+
throw new Error(".ralph/ directory not found. Run 'ralph init' first.");
|
|
30
|
+
}
|
|
31
|
+
const requiredFiles = [CONFIG_FILE, PROMPT_FILE, PRD_FILE, PROGRESS_FILE];
|
|
32
|
+
for (const file of requiredFiles) {
|
|
33
|
+
if (!existsSync(join(ralphDir, file))) {
|
|
34
|
+
throw new Error(`.ralph/${file} not found. Run 'ralph init' first.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function getPaths() {
|
|
39
|
+
const ralphDir = getRalphDir();
|
|
40
|
+
return {
|
|
41
|
+
dir: ralphDir,
|
|
42
|
+
config: join(ralphDir, CONFIG_FILE),
|
|
43
|
+
prompt: join(ralphDir, PROMPT_FILE),
|
|
44
|
+
prd: join(ralphDir, PRD_FILE),
|
|
45
|
+
progress: join(ralphDir, PROGRESS_FILE),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function createPrompt(): {
|
|
2
|
+
question: (query: string) => Promise<string>;
|
|
3
|
+
close: () => void;
|
|
4
|
+
};
|
|
5
|
+
export declare function promptInput(message: string): Promise<string>;
|
|
6
|
+
export declare function promptSelect(message: string, options: string[]): Promise<string>;
|
|
7
|
+
export declare function promptConfirm(message: string): Promise<boolean>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as readline from "readline";
|
|
2
|
+
export function createPrompt() {
|
|
3
|
+
const rl = readline.createInterface({
|
|
4
|
+
input: process.stdin,
|
|
5
|
+
output: process.stdout,
|
|
6
|
+
});
|
|
7
|
+
return {
|
|
8
|
+
question: (query) => new Promise((resolve) => {
|
|
9
|
+
rl.question(query, (answer) => {
|
|
10
|
+
resolve(answer);
|
|
11
|
+
});
|
|
12
|
+
}),
|
|
13
|
+
close: () => rl.close(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function promptInput(message) {
|
|
17
|
+
const prompt = createPrompt();
|
|
18
|
+
const answer = await prompt.question(message);
|
|
19
|
+
prompt.close();
|
|
20
|
+
return answer.trim();
|
|
21
|
+
}
|
|
22
|
+
export async function promptSelect(message, options) {
|
|
23
|
+
console.log(`\n${message}`);
|
|
24
|
+
options.forEach((opt, i) => {
|
|
25
|
+
console.log(` ${i + 1}. ${opt}`);
|
|
26
|
+
});
|
|
27
|
+
const prompt = createPrompt();
|
|
28
|
+
while (true) {
|
|
29
|
+
const answer = await prompt.question("\nEnter number: ");
|
|
30
|
+
const num = parseInt(answer.trim());
|
|
31
|
+
if (num >= 1 && num <= options.length) {
|
|
32
|
+
prompt.close();
|
|
33
|
+
return options[num - 1];
|
|
34
|
+
}
|
|
35
|
+
console.log("Invalid selection.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function promptConfirm(message) {
|
|
39
|
+
const prompt = createPrompt();
|
|
40
|
+
while (true) {
|
|
41
|
+
const answer = await prompt.question(`${message} (y/n): `);
|
|
42
|
+
const normalized = answer.trim().toLowerCase();
|
|
43
|
+
if (normalized === "y" || normalized === "yes") {
|
|
44
|
+
prompt.close();
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (normalized === "n" || normalized === "no") {
|
|
48
|
+
prompt.close();
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
console.log("Please enter y or n.");
|
|
52
|
+
}
|
|
53
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ralph-cli-claude",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-driven development automation CLI for Claude Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ralph": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "npx tsx src/index.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"readline": "^1.3.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.0.0",
|
|
25
|
+
"tsx": "^4.0.0",
|
|
26
|
+
"typescript": "^5.0.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"cli",
|
|
30
|
+
"claude",
|
|
31
|
+
"claude-code",
|
|
32
|
+
"ai",
|
|
33
|
+
"automation",
|
|
34
|
+
"prd",
|
|
35
|
+
"anthropic",
|
|
36
|
+
"llm",
|
|
37
|
+
"developer-tools",
|
|
38
|
+
"devtools"
|
|
39
|
+
],
|
|
40
|
+
"author": "Lars Gregori",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/choas/ralph-cli-claude.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/choas/ralph-cli-claude#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/choas/ralph-cli-claude/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|