ralphctl 0.1.4 → 0.2.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 +23 -14
- package/dist/{add-7LBVENXM.mjs → add-SEDQ3VK7.mjs} +4 -4
- package/dist/{add-DVEYDCTR.mjs → add-TGJTRHIF.mjs} +3 -3
- package/dist/{chunk-M7JV6MKD.mjs → chunk-HLGOQNJ4.mjs} +384 -96
- package/dist/{chunk-LFDW6MWF.mjs → chunk-KPTPKLXY.mjs} +16 -3
- package/dist/{chunk-PDI6HBZ7.mjs → chunk-LG6B7QVO.mjs} +1 -1
- package/dist/{chunk-YIB7QYU4.mjs → chunk-Q3VWJARJ.mjs} +2 -2
- package/dist/{chunk-F2MMCTB5.mjs → chunk-XPDI4SYI.mjs} +5 -4
- package/dist/{chunk-DZ6HHTM5.mjs → chunk-XQHEKKDN.mjs} +1 -1
- package/dist/{chunk-W3TY22IS.mjs → chunk-ZDEVRTGY.mjs} +10 -3
- package/dist/cli.mjs +174 -65
- package/dist/{create-MQ4OHZAX.mjs → create-DJHCP7LN.mjs} +3 -3
- package/dist/{handle-K2AZLTKU.mjs → handle-CCTBNAJZ.mjs} +1 -1
- package/dist/{project-Q4LKML42.mjs → project-ZYGNPVGL.mjs} +2 -2
- package/dist/prompts/ideate-auto.md +3 -2
- package/dist/prompts/ideate.md +2 -2
- package/dist/prompts/plan-auto.md +11 -8
- package/dist/prompts/plan-common.md +13 -8
- package/dist/prompts/plan-interactive.md +11 -10
- package/dist/prompts/task-evaluation.md +54 -0
- package/dist/prompts/task-execution.md +7 -5
- package/dist/{resolver-NH34HTB6.mjs → resolver-L52KR4GY.mjs} +2 -2
- package/dist/{sprint-UHYXSEBJ.mjs → sprint-LUXAV3Q3.mjs} +2 -2
- package/dist/{wizard-MCDDXLGE.mjs → wizard-2OKIQLZJ.mjs} +6 -6
- package/package.json +17 -14
- package/schemas/config.schema.json +10 -0
- package/schemas/projects.schema.json +5 -0
- package/schemas/tasks.schema.json +9 -0
package/dist/cli.mjs
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
import {
|
|
3
3
|
addCheckScriptToRepository,
|
|
4
4
|
projectAddCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Q3VWJARJ.mjs";
|
|
6
6
|
import {
|
|
7
7
|
addTask,
|
|
8
8
|
areAllTasksDone,
|
|
9
9
|
branchExists,
|
|
10
|
+
buildHeadlessAiRequest,
|
|
10
11
|
buildIdeateAutoPrompt,
|
|
11
12
|
buildIdeatePrompt,
|
|
12
13
|
buildTicketRefinePrompt,
|
|
13
14
|
exportRequirementsToMarkdown,
|
|
15
|
+
extractJsonArray,
|
|
14
16
|
extractJsonObject,
|
|
15
17
|
formatTicketForPrompt,
|
|
16
18
|
getActiveProvider,
|
|
@@ -31,6 +33,7 @@ import {
|
|
|
31
33
|
providerDisplayName,
|
|
32
34
|
removeTask,
|
|
33
35
|
renderParsedTasksTable,
|
|
36
|
+
reorderByDependencies,
|
|
34
37
|
reorderTask,
|
|
35
38
|
resolveProvider,
|
|
36
39
|
runAiSession,
|
|
@@ -49,13 +52,13 @@ import {
|
|
|
49
52
|
sprintStartCommand,
|
|
50
53
|
updateTaskStatus,
|
|
51
54
|
validateImportTasks
|
|
52
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-HLGOQNJ4.mjs";
|
|
53
56
|
import {
|
|
54
57
|
escapableSelect
|
|
55
58
|
} from "./chunk-7LZ6GOGN.mjs";
|
|
56
59
|
import {
|
|
57
60
|
sprintCreateCommand
|
|
58
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-XQHEKKDN.mjs";
|
|
59
62
|
import {
|
|
60
63
|
addTicket,
|
|
61
64
|
allRequirementsApproved,
|
|
@@ -70,7 +73,7 @@ import {
|
|
|
70
73
|
removeTicket,
|
|
71
74
|
ticketAddCommand,
|
|
72
75
|
updateTicket
|
|
73
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-XPDI4SYI.mjs";
|
|
74
77
|
import {
|
|
75
78
|
EXIT_ERROR,
|
|
76
79
|
exitWithCode
|
|
@@ -81,8 +84,9 @@ import {
|
|
|
81
84
|
listProjects,
|
|
82
85
|
removeProject,
|
|
83
86
|
removeProjectRepo
|
|
84
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-LG6B7QVO.mjs";
|
|
85
88
|
import {
|
|
89
|
+
DEFAULT_EVALUATION_ITERATIONS,
|
|
86
90
|
assertSprintStatus,
|
|
87
91
|
closeSprint,
|
|
88
92
|
deleteSprint,
|
|
@@ -91,6 +95,7 @@ import {
|
|
|
91
95
|
getCurrentSprint,
|
|
92
96
|
getCurrentSprintOrThrow,
|
|
93
97
|
getEditor,
|
|
98
|
+
getEvaluationIterations,
|
|
94
99
|
getProgress,
|
|
95
100
|
getSprint,
|
|
96
101
|
listSprints,
|
|
@@ -100,8 +105,9 @@ import {
|
|
|
100
105
|
setAiProvider,
|
|
101
106
|
setCurrentSprint,
|
|
102
107
|
setEditor,
|
|
108
|
+
setEvaluationIterations,
|
|
103
109
|
withFileLock
|
|
104
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-KPTPKLXY.mjs";
|
|
105
111
|
import {
|
|
106
112
|
ensureError,
|
|
107
113
|
wrapAsync
|
|
@@ -127,7 +133,7 @@ import {
|
|
|
127
133
|
getTasksFilePath,
|
|
128
134
|
readValidatedJson,
|
|
129
135
|
validateProjectPath
|
|
130
|
-
} from "./chunk-
|
|
136
|
+
} from "./chunk-ZDEVRTGY.mjs";
|
|
131
137
|
import {
|
|
132
138
|
DomainError,
|
|
133
139
|
NoCurrentSprintError,
|
|
@@ -414,6 +420,12 @@ function buildConfigSubMenu() {
|
|
|
414
420
|
const items = [];
|
|
415
421
|
items.push({ name: "Show Settings", value: "show", description: "View current configuration" });
|
|
416
422
|
items.push({ name: "Set AI Provider", value: "set provider", description: "Choose Claude Code or GitHub Copilot" });
|
|
423
|
+
items.push({ name: "Set Editor", value: "set editor", description: "Editor for refinement sessions" });
|
|
424
|
+
items.push({
|
|
425
|
+
name: "Set Evaluation Iterations",
|
|
426
|
+
value: "set evaluationIterations",
|
|
427
|
+
description: "Generator-evaluator loop count (0 = disabled)"
|
|
428
|
+
});
|
|
417
429
|
items.push(line());
|
|
418
430
|
items.push({ name: "Back", value: "back", description: "Return to main menu" });
|
|
419
431
|
return { title: "Configuration", items };
|
|
@@ -610,7 +622,7 @@ async function showDashboard() {
|
|
|
610
622
|
}
|
|
611
623
|
|
|
612
624
|
// src/interactive/index.ts
|
|
613
|
-
import { select as select3 } from "@inquirer/prompts";
|
|
625
|
+
import { input as input5, select as select3 } from "@inquirer/prompts";
|
|
614
626
|
|
|
615
627
|
// src/commands/project/list.ts
|
|
616
628
|
async function projectListCommand() {
|
|
@@ -1146,7 +1158,7 @@ async function invokeAiInteractive(prompt, repoPaths, ideateDir) {
|
|
|
1146
1158
|
await writeFile(contextFile, prompt, "utf-8");
|
|
1147
1159
|
const provider = await getActiveProvider();
|
|
1148
1160
|
const startPrompt = `I have a quick idea I want to implement. The full context is in ideate-context.md. Please read that file and help me refine the idea into requirements and then plan implementation tasks.`;
|
|
1149
|
-
const args = ["--add-dir",
|
|
1161
|
+
const args = repoPaths.flatMap((path) => ["--add-dir", path]);
|
|
1150
1162
|
const result = spawnInteractive(
|
|
1151
1163
|
startPrompt,
|
|
1152
1164
|
{
|
|
@@ -1162,36 +1174,55 @@ async function invokeAiInteractive(prompt, repoPaths, ideateDir) {
|
|
|
1162
1174
|
}
|
|
1163
1175
|
async function invokeAiAuto(prompt, repoPaths, ideateDir) {
|
|
1164
1176
|
const provider = await getActiveProvider();
|
|
1165
|
-
const
|
|
1166
|
-
for (const path of repoPaths) {
|
|
1167
|
-
args.push("--add-dir", path);
|
|
1168
|
-
}
|
|
1169
|
-
args.push("-p", prompt);
|
|
1177
|
+
const request = buildHeadlessAiRequest(repoPaths, prompt);
|
|
1170
1178
|
return spawnHeadless(
|
|
1171
1179
|
{
|
|
1172
1180
|
cwd: ideateDir,
|
|
1173
|
-
args,
|
|
1181
|
+
args: request.args,
|
|
1182
|
+
prompt: request.prompt,
|
|
1174
1183
|
env: provider.getSpawnEnv()
|
|
1175
1184
|
},
|
|
1176
1185
|
provider
|
|
1177
1186
|
);
|
|
1178
1187
|
}
|
|
1179
1188
|
function parseIdeateOutput(output) {
|
|
1180
|
-
const
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1183
|
-
|
|
1189
|
+
const firstBrace = output.indexOf("{");
|
|
1190
|
+
const firstBracket = output.indexOf("[");
|
|
1191
|
+
const objectFirst = firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket);
|
|
1192
|
+
if (objectFirst) {
|
|
1193
|
+
return parseIdeateObject(output);
|
|
1194
|
+
}
|
|
1195
|
+
if (firstBracket !== -1) {
|
|
1196
|
+
const arrayR = Result.try(() => extractJsonArray(output));
|
|
1197
|
+
if (arrayR.ok) {
|
|
1198
|
+
const parseR = Result.try(() => JSON.parse(arrayR.value));
|
|
1199
|
+
if (parseR.ok && Array.isArray(parseR.value)) {
|
|
1200
|
+
return { requirements: "", tasks: parseR.value };
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1184
1203
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1204
|
+
throw new Error("No valid ideate output found \u2014 expected { requirements, tasks } object or a tasks array");
|
|
1205
|
+
}
|
|
1206
|
+
function parseIdeateObject(output) {
|
|
1207
|
+
const jsonStr = extractJsonObject(output);
|
|
1208
|
+
const parsed = JSON.parse(jsonStr);
|
|
1209
|
+
const result = IdeateOutputSchema.safeParse(parsed);
|
|
1210
|
+
if (result.success) {
|
|
1211
|
+
return result.data;
|
|
1212
|
+
}
|
|
1213
|
+
if (typeof parsed === "object" && parsed !== null && "tasks" in parsed) {
|
|
1214
|
+
const obj = parsed;
|
|
1215
|
+
if (Array.isArray(obj["tasks"])) {
|
|
1216
|
+
const requirements = typeof obj["requirements"] === "string" ? obj["requirements"] : "";
|
|
1217
|
+
return { requirements, tasks: obj["tasks"] };
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const issues = result.error.issues.map((issue) => {
|
|
1221
|
+
const path = issue.path.length > 0 ? `[${issue.path.join(".")}]` : "";
|
|
1222
|
+
return ` ${path}: ${issue.message}`;
|
|
1223
|
+
}).join("\n");
|
|
1224
|
+
throw new Error(`Invalid ideate output format:
|
|
1192
1225
|
${issues}`);
|
|
1193
|
-
}
|
|
1194
|
-
return result.data;
|
|
1195
1226
|
}
|
|
1196
1227
|
async function sprintIdeateCommand(args) {
|
|
1197
1228
|
const { sprintId, options } = parseArgs(args);
|
|
@@ -1291,8 +1322,12 @@ async function sprintIdeateCommand(args) {
|
|
|
1291
1322
|
reposByProject.set(projectName, project.repositories);
|
|
1292
1323
|
selectedPaths = await selectProjectPaths(reposByProject, "Select paths to explore:");
|
|
1293
1324
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1325
|
+
const updatedSprint = await getSprint(id);
|
|
1326
|
+
const savedTicket = updatedSprint.tickets.find((t) => t.id === ticket.id);
|
|
1327
|
+
if (savedTicket) {
|
|
1328
|
+
savedTicket.affectedRepositories = selectedPaths;
|
|
1329
|
+
}
|
|
1330
|
+
await saveSprint(updatedSprint);
|
|
1296
1331
|
if (selectedPaths.length > 1) {
|
|
1297
1332
|
console.log(muted(`Paths: ${selectedPaths.join(", ")}`));
|
|
1298
1333
|
} else {
|
|
@@ -1332,13 +1367,13 @@ async function sprintIdeateCommand(args) {
|
|
|
1332
1367
|
return;
|
|
1333
1368
|
}
|
|
1334
1369
|
const ideateOutput = ideateR.value;
|
|
1335
|
-
const
|
|
1336
|
-
const
|
|
1337
|
-
if (
|
|
1338
|
-
|
|
1339
|
-
|
|
1370
|
+
const autoSprint = await getSprint(id);
|
|
1371
|
+
const autoTicket = autoSprint.tickets.find((t) => t.id === ticket.id);
|
|
1372
|
+
if (autoTicket) {
|
|
1373
|
+
autoTicket.requirements = ideateOutput.requirements;
|
|
1374
|
+
autoTicket.requirementStatus = "approved";
|
|
1340
1375
|
}
|
|
1341
|
-
await saveSprint(
|
|
1376
|
+
await saveSprint(autoSprint);
|
|
1342
1377
|
showSuccess("Requirements approved and saved!");
|
|
1343
1378
|
log.newline();
|
|
1344
1379
|
const parsedTasksR = Result.try(() => parseTasksJson(JSON.stringify(ideateOutput.tasks)));
|
|
@@ -1348,6 +1383,9 @@ async function sprintIdeateCommand(args) {
|
|
|
1348
1383
|
return;
|
|
1349
1384
|
}
|
|
1350
1385
|
const parsedTasks = parsedTasksR.value;
|
|
1386
|
+
for (const task of parsedTasks) {
|
|
1387
|
+
task.ticketId ??= ticket.id;
|
|
1388
|
+
}
|
|
1351
1389
|
if (parsedTasks.length === 0) {
|
|
1352
1390
|
showWarning("No tasks generated.");
|
|
1353
1391
|
log.newline();
|
|
@@ -1358,7 +1396,7 @@ async function sprintIdeateCommand(args) {
|
|
|
1358
1396
|
console.log(renderParsedTasksTable(parsedTasks));
|
|
1359
1397
|
console.log("");
|
|
1360
1398
|
const existingTasks = await getTasks(id);
|
|
1361
|
-
const ticketIds = new Set(
|
|
1399
|
+
const ticketIds = new Set(autoSprint.tickets.map((t) => t.id));
|
|
1362
1400
|
const validationErrors = validateImportTasks(parsedTasks, existingTasks, ticketIds);
|
|
1363
1401
|
if (validationErrors.length > 0) {
|
|
1364
1402
|
showError("Validation failed");
|
|
@@ -1368,8 +1406,13 @@ async function sprintIdeateCommand(args) {
|
|
|
1368
1406
|
log.newline();
|
|
1369
1407
|
return;
|
|
1370
1408
|
}
|
|
1409
|
+
if (ideateOutput.requirements === "") {
|
|
1410
|
+
showWarning("AI output was a bare tasks array \u2014 requirements not captured.");
|
|
1411
|
+
}
|
|
1371
1412
|
showInfo("Importing tasks...");
|
|
1372
1413
|
const imported = await importTasks(parsedTasks, id);
|
|
1414
|
+
await reorderByDependencies(id);
|
|
1415
|
+
log.dim("Tasks reordered by dependencies.");
|
|
1373
1416
|
terminalBell();
|
|
1374
1417
|
showSuccess(`Imported ${String(imported)}/${String(parsedTasks.length)} tasks.`);
|
|
1375
1418
|
log.newline();
|
|
@@ -1405,13 +1448,13 @@ async function sprintIdeateCommand(args) {
|
|
|
1405
1448
|
return;
|
|
1406
1449
|
}
|
|
1407
1450
|
const ideateOutput = ideateR.value;
|
|
1408
|
-
const
|
|
1409
|
-
const
|
|
1410
|
-
if (
|
|
1411
|
-
|
|
1412
|
-
|
|
1451
|
+
const interactiveSprint = await getSprint(id);
|
|
1452
|
+
const interactiveTicket = interactiveSprint.tickets.find((t) => t.id === ticket.id);
|
|
1453
|
+
if (interactiveTicket) {
|
|
1454
|
+
interactiveTicket.requirements = ideateOutput.requirements;
|
|
1455
|
+
interactiveTicket.requirementStatus = "approved";
|
|
1413
1456
|
}
|
|
1414
|
-
await saveSprint(
|
|
1457
|
+
await saveSprint(interactiveSprint);
|
|
1415
1458
|
showSuccess("Requirements approved and saved!");
|
|
1416
1459
|
log.newline();
|
|
1417
1460
|
const parsedTasksR = Result.try(() => parseTasksJson(JSON.stringify(ideateOutput.tasks)));
|
|
@@ -1421,6 +1464,9 @@ async function sprintIdeateCommand(args) {
|
|
|
1421
1464
|
return;
|
|
1422
1465
|
}
|
|
1423
1466
|
const parsedTasks = parsedTasksR.value;
|
|
1467
|
+
for (const task of parsedTasks) {
|
|
1468
|
+
task.ticketId ??= ticket.id;
|
|
1469
|
+
}
|
|
1424
1470
|
if (parsedTasks.length === 0) {
|
|
1425
1471
|
showWarning("No tasks in file.");
|
|
1426
1472
|
log.newline();
|
|
@@ -1431,7 +1477,7 @@ async function sprintIdeateCommand(args) {
|
|
|
1431
1477
|
console.log(renderParsedTasksTable(parsedTasks));
|
|
1432
1478
|
console.log("");
|
|
1433
1479
|
const existingTasks = await getTasks(id);
|
|
1434
|
-
const ticketIds = new Set(
|
|
1480
|
+
const ticketIds = new Set(interactiveSprint.tickets.map((t) => t.id));
|
|
1435
1481
|
const validationErrors = validateImportTasks(parsedTasks, existingTasks, ticketIds);
|
|
1436
1482
|
if (validationErrors.length > 0) {
|
|
1437
1483
|
showError("Validation failed");
|
|
@@ -1441,8 +1487,13 @@ async function sprintIdeateCommand(args) {
|
|
|
1441
1487
|
log.newline();
|
|
1442
1488
|
return;
|
|
1443
1489
|
}
|
|
1490
|
+
if (ideateOutput.requirements === "") {
|
|
1491
|
+
showWarning("AI output was a bare tasks array \u2014 requirements not captured.");
|
|
1492
|
+
}
|
|
1444
1493
|
showInfo("Importing tasks...");
|
|
1445
1494
|
const imported = await importTasks(parsedTasks, id);
|
|
1495
|
+
await reorderByDependencies(id);
|
|
1496
|
+
log.dim("Tasks reordered by dependencies.");
|
|
1446
1497
|
terminalBell();
|
|
1447
1498
|
showSuccess(`Imported ${String(imported)}/${String(parsedTasks.length)} tasks.`);
|
|
1448
1499
|
log.newline();
|
|
@@ -3216,7 +3267,7 @@ async function progressShowCommand() {
|
|
|
3216
3267
|
async function configSetCommand(args) {
|
|
3217
3268
|
if (args.length < 2) {
|
|
3218
3269
|
showError("Usage: ralphctl config set <key> <value>");
|
|
3219
|
-
log.dim("Available keys: provider, editor");
|
|
3270
|
+
log.dim("Available keys: provider, editor, evaluationIterations");
|
|
3220
3271
|
log.newline();
|
|
3221
3272
|
return;
|
|
3222
3273
|
}
|
|
@@ -3247,16 +3298,32 @@ async function configSetCommand(args) {
|
|
|
3247
3298
|
log.newline();
|
|
3248
3299
|
return;
|
|
3249
3300
|
}
|
|
3301
|
+
if (key === "evaluationIterations") {
|
|
3302
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
3303
|
+
if (Number.isNaN(parsed) || parsed < 0 || !Number.isInteger(parsed)) {
|
|
3304
|
+
showError(`Invalid evaluation iterations: ${value ?? "(empty)"}`);
|
|
3305
|
+
log.dim("Must be an integer >= 0 (0 = disabled)");
|
|
3306
|
+
log.newline();
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
await setEvaluationIterations(parsed);
|
|
3310
|
+
showSuccess(`Evaluation iterations set to: ${String(parsed)}`);
|
|
3311
|
+
log.newline();
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3250
3314
|
showError(`Unknown config key: ${key ?? "(empty)"}`);
|
|
3251
|
-
log.dim("Available keys: provider, editor");
|
|
3315
|
+
log.dim("Available keys: provider, editor, evaluationIterations");
|
|
3252
3316
|
log.newline();
|
|
3253
3317
|
}
|
|
3254
3318
|
async function configShowCommand() {
|
|
3255
3319
|
const provider = await getAiProvider();
|
|
3256
3320
|
const editorCmd = await getEditor();
|
|
3321
|
+
const evalIterations = await getEvaluationIterations();
|
|
3257
3322
|
printHeader("Configuration", icons.info);
|
|
3258
3323
|
console.log(field("AI Provider", provider ?? "(not set \u2014 will prompt on first use)"));
|
|
3259
3324
|
console.log(field("Editor", editorCmd ?? "(not set \u2014 will prompt on first use)"));
|
|
3325
|
+
const evalDisplay = evalIterations === DEFAULT_EVALUATION_ITERATIONS ? `${String(evalIterations)} (default)` : evalIterations === 0 ? "0 (disabled)" : String(evalIterations);
|
|
3326
|
+
console.log(field("Evaluation Iterations", evalDisplay));
|
|
3260
3327
|
log.newline();
|
|
3261
3328
|
}
|
|
3262
3329
|
|
|
@@ -3320,7 +3387,8 @@ async function checkAiProvider() {
|
|
|
3320
3387
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3321
3388
|
});
|
|
3322
3389
|
if (result.status === 0) {
|
|
3323
|
-
|
|
3390
|
+
const detail = provider === "copilot" ? `${binary} found (public preview)` : `${binary} found`;
|
|
3391
|
+
return { name: "AI provider binary", status: "pass", detail };
|
|
3324
3392
|
}
|
|
3325
3393
|
return {
|
|
3326
3394
|
name: "AI provider binary",
|
|
@@ -3375,6 +3443,21 @@ async function checkProjectPaths() {
|
|
|
3375
3443
|
}
|
|
3376
3444
|
return { name: "Project paths", status: "fail", detail: issues.join("; ") };
|
|
3377
3445
|
}
|
|
3446
|
+
async function checkEvaluationConfig() {
|
|
3447
|
+
const config = await getConfig();
|
|
3448
|
+
if (config.evaluationIterations == null) {
|
|
3449
|
+
return {
|
|
3450
|
+
name: "Evaluation config",
|
|
3451
|
+
status: "warn",
|
|
3452
|
+
detail: "evaluationIterations not set \u2014 defaulting to 1 (set via: ralphctl config set evaluationIterations <n>)"
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
return {
|
|
3456
|
+
name: "Evaluation config",
|
|
3457
|
+
status: "pass",
|
|
3458
|
+
detail: `evaluationIterations: ${String(config.evaluationIterations)}`
|
|
3459
|
+
};
|
|
3460
|
+
}
|
|
3378
3461
|
async function checkCurrentSprint() {
|
|
3379
3462
|
const config = await getConfig();
|
|
3380
3463
|
const sprintId = config.currentSprint;
|
|
@@ -3402,7 +3485,8 @@ async function doctorCommand() {
|
|
|
3402
3485
|
checkAiProvider(),
|
|
3403
3486
|
checkDataDirectory(),
|
|
3404
3487
|
checkProjectPaths(),
|
|
3405
|
-
checkCurrentSprint()
|
|
3488
|
+
checkCurrentSprint(),
|
|
3489
|
+
checkEvaluationConfig()
|
|
3406
3490
|
]);
|
|
3407
3491
|
results.push(...asyncResults);
|
|
3408
3492
|
for (const result of results) {
|
|
@@ -3520,6 +3604,26 @@ var commandMap = {
|
|
|
3520
3604
|
theme: selectTheme
|
|
3521
3605
|
});
|
|
3522
3606
|
await configSetCommand(["provider", choice]);
|
|
3607
|
+
},
|
|
3608
|
+
"set editor": async () => {
|
|
3609
|
+
const current = await getEditor();
|
|
3610
|
+
const value = await input5({
|
|
3611
|
+
message: `${emoji.donut} Which editor should open for refinement?`,
|
|
3612
|
+
default: current ?? void 0,
|
|
3613
|
+
theme: selectTheme
|
|
3614
|
+
});
|
|
3615
|
+
if (value.trim()) {
|
|
3616
|
+
await configSetCommand(["editor", value.trim()]);
|
|
3617
|
+
}
|
|
3618
|
+
},
|
|
3619
|
+
"set evaluationIterations": async () => {
|
|
3620
|
+
const current = await getEvaluationIterations();
|
|
3621
|
+
const value = await input5({
|
|
3622
|
+
message: `${emoji.donut} How many evaluation loops? (0 = disabled)`,
|
|
3623
|
+
default: String(current),
|
|
3624
|
+
theme: selectTheme
|
|
3625
|
+
});
|
|
3626
|
+
await configSetCommand(["evaluationIterations", value.trim()]);
|
|
3523
3627
|
}
|
|
3524
3628
|
}
|
|
3525
3629
|
};
|
|
@@ -3659,7 +3763,7 @@ async function interactiveMode() {
|
|
|
3659
3763
|
continue;
|
|
3660
3764
|
}
|
|
3661
3765
|
if (command === "wizard") {
|
|
3662
|
-
const { runWizard } = await import("./wizard-
|
|
3766
|
+
const { runWizard } = await import("./wizard-2OKIQLZJ.mjs");
|
|
3663
3767
|
await runWizard();
|
|
3664
3768
|
continue;
|
|
3665
3769
|
}
|
|
@@ -3870,7 +3974,7 @@ Examples:
|
|
|
3870
3974
|
sprint.command("health").description("Check sprint health").action(async () => {
|
|
3871
3975
|
await sprintHealthCommand();
|
|
3872
3976
|
});
|
|
3873
|
-
sprint.command("start [id]").description("Run automated implementation loop").option("-s, --session", "Interactive AI session (collaborate with your AI provider)").option("-t, --step", "Step through tasks with approval between each").option("-c, --count <n>", "Limit to N tasks").option("--no-commit", "Skip automatic git commit after each task completes").option("--concurrency <n>", "Max parallel tasks (default: auto based on unique repos)").option("--max-retries <n>", "Max rate-limit retries per task (default: 5)").option("--fail-fast", "Stop launching new tasks on first failure").option("-f, --force", "Skip precondition checks (e.g., unplanned tickets)").option("--refresh-check", "Force re-run check scripts even if they already ran this sprint").option("-b, --branch", "Create sprint branch (ralphctl/<sprint-id>) in all repos").option("--branch-name <name>", "Use a custom branch name for sprint execution").addHelpText(
|
|
3977
|
+
sprint.command("start [id]").description("Run automated implementation loop").option("-s, --session", "Interactive AI session (collaborate with your AI provider)").option("-t, --step", "Step through tasks with approval between each").option("-c, --count <n>", "Limit to N tasks").option("--no-commit", "Skip automatic git commit after each task completes").option("--concurrency <n>", "Max parallel tasks (default: auto based on unique repos)").option("--max-retries <n>", "Max rate-limit retries per task (default: 5)").option("--fail-fast", "Stop launching new tasks on first failure").option("-f, --force", "Skip precondition checks (e.g., unplanned tickets)").option("--refresh-check", "Force re-run check scripts even if they already ran this sprint").option("-b, --branch", "Create sprint branch (ralphctl/<sprint-id>) in all repos").option("--branch-name <name>", "Use a custom branch name for sprint execution").option("--max-budget-usd <amount>", "Max USD budget per AI task (Claude only)").option("--fallback-model <model>", "Fallback model when primary is overloaded (Claude only)").addHelpText(
|
|
3874
3978
|
"after",
|
|
3875
3979
|
`
|
|
3876
3980
|
Exit Codes:
|
|
@@ -3906,6 +4010,8 @@ Branch Management:
|
|
|
3906
4010
|
if (opts?.refreshCheck) args.push("--refresh-check");
|
|
3907
4011
|
if (opts?.branch) args.push("--branch");
|
|
3908
4012
|
if (opts?.branchName) args.push("--branch-name", opts.branchName);
|
|
4013
|
+
if (opts?.maxBudgetUsd) args.push("--max-budget-usd", opts.maxBudgetUsd);
|
|
4014
|
+
if (opts?.fallbackModel) args.push("--fallback-model", opts.fallbackModel);
|
|
3909
4015
|
await sprintStartCommand(args);
|
|
3910
4016
|
}
|
|
3911
4017
|
);
|
|
@@ -4128,8 +4234,8 @@ Checks performed:
|
|
|
4128
4234
|
// package.json
|
|
4129
4235
|
var package_default = {
|
|
4130
4236
|
name: "ralphctl",
|
|
4131
|
-
version: "0.
|
|
4132
|
-
description: "
|
|
4237
|
+
version: "0.2.0",
|
|
4238
|
+
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
4133
4239
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
4134
4240
|
type: "module",
|
|
4135
4241
|
license: "MIT",
|
|
@@ -4143,13 +4249,15 @@ var package_default = {
|
|
|
4143
4249
|
},
|
|
4144
4250
|
keywords: [
|
|
4145
4251
|
"cli",
|
|
4146
|
-
"
|
|
4147
|
-
"
|
|
4148
|
-
"
|
|
4149
|
-
"
|
|
4150
|
-
"
|
|
4252
|
+
"agent-harness",
|
|
4253
|
+
"claude-code",
|
|
4254
|
+
"github-copilot",
|
|
4255
|
+
"ai-coding",
|
|
4256
|
+
"task-orchestration",
|
|
4151
4257
|
"anthropic",
|
|
4152
|
-
"developer-tools"
|
|
4258
|
+
"developer-tools",
|
|
4259
|
+
"long-running-agents",
|
|
4260
|
+
"generator-evaluator"
|
|
4153
4261
|
],
|
|
4154
4262
|
bin: {
|
|
4155
4263
|
ralphctl: "./dist/cli.mjs"
|
|
@@ -4180,7 +4288,7 @@ var package_default = {
|
|
|
4180
4288
|
node: ">=24.0.0"
|
|
4181
4289
|
},
|
|
4182
4290
|
dependencies: {
|
|
4183
|
-
"@inquirer/prompts": "^8.3.
|
|
4291
|
+
"@inquirer/prompts": "^8.3.2",
|
|
4184
4292
|
colorette: "^2.0.20",
|
|
4185
4293
|
commander: "^14.0.3",
|
|
4186
4294
|
"gradient-string": "^3.0.0",
|
|
@@ -4191,19 +4299,20 @@ var package_default = {
|
|
|
4191
4299
|
},
|
|
4192
4300
|
devDependencies: {
|
|
4193
4301
|
"@eslint/js": "^10.0.1",
|
|
4194
|
-
"@types/node": "^25.
|
|
4302
|
+
"@types/node": "^25.5.0",
|
|
4195
4303
|
"@types/tabtab": "^3.0.4",
|
|
4196
|
-
|
|
4304
|
+
"@vitest/coverage-v8": "^4.1.1",
|
|
4305
|
+
eslint: "^10.1.0",
|
|
4197
4306
|
"eslint-config-prettier": "^10.1.8",
|
|
4198
4307
|
globals: "^17.4.0",
|
|
4199
4308
|
husky: "^9.1.7",
|
|
4200
|
-
"lint-staged": "^16.
|
|
4309
|
+
"lint-staged": "^16.4.0",
|
|
4201
4310
|
prettier: "^3.8.1",
|
|
4202
4311
|
tsup: "^8.5.1",
|
|
4203
4312
|
tsx: "^4.21.0",
|
|
4204
4313
|
typescript: "^5.9.3",
|
|
4205
|
-
"typescript-eslint": "^8.
|
|
4206
|
-
vitest: "^4.
|
|
4314
|
+
"typescript-eslint": "^8.57.2",
|
|
4315
|
+
vitest: "^4.1.1"
|
|
4207
4316
|
},
|
|
4208
4317
|
"lint-staged": {
|
|
4209
4318
|
"*.ts": [
|
|
@@ -4247,7 +4356,7 @@ registerCompletionCommands(program);
|
|
|
4247
4356
|
registerDoctorCommands(program);
|
|
4248
4357
|
async function main() {
|
|
4249
4358
|
if (process.env["COMP_CWORD"] && process.env["COMP_POINT"] && process.env["COMP_LINE"]) {
|
|
4250
|
-
const { handleCompletionRequest } = await import("./handle-
|
|
4359
|
+
const { handleCompletionRequest } = await import("./handle-CCTBNAJZ.mjs");
|
|
4251
4360
|
if (await handleCompletionRequest(program)) return;
|
|
4252
4361
|
}
|
|
4253
4362
|
if (process.argv.length <= 2 || process.argv[2] === "interactive") {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
sprintCreateCommand
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-XQHEKKDN.mjs";
|
|
5
|
+
import "./chunk-KPTPKLXY.mjs";
|
|
6
6
|
import "./chunk-OEUJDSHY.mjs";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-ZDEVRTGY.mjs";
|
|
8
8
|
import "./chunk-EDJX7TT6.mjs";
|
|
9
9
|
import "./chunk-QBXHAXHI.mjs";
|
|
10
10
|
export {
|
|
@@ -7,7 +7,7 @@ async function handleCompletionRequest(program) {
|
|
|
7
7
|
return false;
|
|
8
8
|
}
|
|
9
9
|
const tabtab = (await import("tabtab")).default;
|
|
10
|
-
const { resolveCompletions } = await import("./resolver-
|
|
10
|
+
const { resolveCompletions } = await import("./resolver-L52KR4GY.mjs");
|
|
11
11
|
const tabEnv = tabtab.parseEnv(env);
|
|
12
12
|
const completions = await resolveCompletions(program, {
|
|
13
13
|
line: tabEnv.line,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
removeProject,
|
|
10
10
|
removeProjectRepo,
|
|
11
11
|
updateProject
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-LG6B7QVO.mjs";
|
|
13
|
+
import "./chunk-ZDEVRTGY.mjs";
|
|
14
14
|
import {
|
|
15
15
|
ProjectExistsError,
|
|
16
16
|
ProjectNotFoundError
|
|
@@ -52,7 +52,8 @@ Analyze the idea and produce complete, implementation-agnostic requirements:
|
|
|
52
52
|
|
|
53
53
|
Explore the selected repositories and produce implementation tasks:
|
|
54
54
|
|
|
55
|
-
1. **Explore codebase** — Read CLAUDE.md
|
|
55
|
+
1. **Explore codebase** — Read the repository instruction files (`CLAUDE.md`, `.github/copilot-instructions.md`, etc.)
|
|
56
|
+
when present, understand project structure, find patterns
|
|
56
57
|
2. **Map requirements to implementation** — Determine which parts map to which repository
|
|
57
58
|
3. **Create tasks** — Following the Planning Common Context guidelines below
|
|
58
59
|
4. **Validate** — Ensure tasks are non-overlapping, properly ordered, and completable
|
|
@@ -111,7 +112,7 @@ If you cannot produce a valid plan, output `<planning-blocked>reason</planning-b
|
|
|
111
112
|
- Each task has `id`, `name`, `projectPath`, `steps`, and optional `blockedBy`
|
|
112
113
|
- `projectPath` must be one of the Selected Repositories paths
|
|
113
114
|
- Steps reference actual files discovered during exploration
|
|
114
|
-
- Verification steps use commands from
|
|
115
|
+
- Verification steps use commands from the repository instruction files if available
|
|
115
116
|
- Tasks properly ordered by dependencies
|
|
116
117
|
|
|
117
118
|
**Example:**
|
package/dist/prompts/ideate.md
CHANGED
|
@@ -79,8 +79,8 @@ Focus: Determine HOW to implement the approved requirements
|
|
|
79
79
|
|
|
80
80
|
**Steps:**
|
|
81
81
|
|
|
82
|
-
1. **Explore the codebase** — Read
|
|
83
|
-
verification commands
|
|
82
|
+
1. **Explore the codebase** — Read the repository instruction files (`CLAUDE.md`, `.github/copilot-instructions.md`,
|
|
83
|
+
etc.) when present, check project structure, find similar implementations, extract verification commands
|
|
84
84
|
2. **Review approved requirements** — Understand WHAT was approved in Phase 1
|
|
85
85
|
3. **Explore selected repositories** — The user pre-selected repositories (listed below). Deep-dive to understand
|
|
86
86
|
patterns, conventions, and existing code
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Headless Task Planning Protocol
|
|
2
2
|
|
|
3
|
-
You are a task planning specialist. Your goal is to produce a dependency-ordered set of implementation tasks — each one
|
|
4
|
-
|
|
3
|
+
You are a task planning specialist. Your goal is to produce a dependency-ordered set of implementation tasks — each one
|
|
4
|
+
a
|
|
5
|
+
self-contained mini-spec that can be picked up cold and completed in a single AI session. Make all decisions
|
|
5
6
|
autonomously based on codebase analysis — there is no user to interact with.
|
|
6
7
|
|
|
7
8
|
## Protocol
|
|
@@ -10,9 +11,10 @@ autonomously based on codebase analysis — there is no user to interact with.
|
|
|
10
11
|
|
|
11
12
|
Explore efficiently — read what matters, skip what does not:
|
|
12
13
|
|
|
13
|
-
1. **Read
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
1. **Read project instructions first** — Start with `CLAUDE.md` if it exists, and also check provider-specific files
|
|
15
|
+
such
|
|
16
|
+
as `.github/copilot-instructions.md` when present. Follow any links to other documentation. Check `.claude/`
|
|
17
|
+
directory for agents, rules, and memory (see "Project Resources" section below).
|
|
16
18
|
2. **Read manifest files** — package.json, pyproject.toml, Cargo.toml, go.mod, pom.xml, etc. for dependencies and
|
|
17
19
|
scripts
|
|
18
20
|
3. **Read README** — Project overview, setup, and architecture
|
|
@@ -21,8 +23,8 @@ Explore efficiently — read what matters, skip what does not:
|
|
|
21
23
|
exactly.
|
|
22
24
|
6. **Extract verification commands** — Find the exact build, test, lint, and typecheck commands
|
|
23
25
|
|
|
24
|
-
**Do NOT read every file.** Read
|
|
25
|
-
tasks.
|
|
26
|
+
**Do NOT read every file.** Read the project instruction files/README first, then only the specific files needed to
|
|
27
|
+
understand patterns and plan tasks.
|
|
26
28
|
|
|
27
29
|
### Step 2: Review Ticket Requirements
|
|
28
30
|
|
|
@@ -73,7 +75,8 @@ Before outputting JSON, verify EVERY item on this checklist:
|
|
|
73
75
|
3. **Valid dependencies** — All `blockedBy` references point to earlier tasks with real code dependencies
|
|
74
76
|
4. **Maximized parallelism** — Independent tasks do NOT block each other unnecessarily
|
|
75
77
|
5. **Precise steps** — Every task has 3+ specific, actionable steps with file references
|
|
76
|
-
6. **Verification steps** — Every task ends with project-appropriate verification commands from
|
|
78
|
+
6. **Verification steps** — Every task ends with project-appropriate verification commands from the repository
|
|
79
|
+
instructions
|
|
77
80
|
7. **projectPath assigned** — Every task has a `projectPath` from the project's repository paths
|
|
78
81
|
8. **Clear done state** — For each task, the question "how do I know this is done?" has an obvious answer
|
|
79
82
|
9. **Valid JSON** — The output parses as a JSON array of task objects matching the schema
|