ralphctl 0.1.4 → 0.2.1

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.
Files changed (28) hide show
  1. package/README.md +23 -14
  2. package/dist/{add-7LBVENXM.mjs → add-SEDQ3VK7.mjs} +4 -4
  3. package/dist/{add-DVEYDCTR.mjs → add-TGJTRHIF.mjs} +3 -3
  4. package/dist/{chunk-M7JV6MKD.mjs → chunk-AXNZMHFQ.mjs} +384 -96
  5. package/dist/{chunk-LFDW6MWF.mjs → chunk-KPTPKLXY.mjs} +16 -3
  6. package/dist/{chunk-PDI6HBZ7.mjs → chunk-LG6B7QVO.mjs} +1 -1
  7. package/dist/{chunk-YIB7QYU4.mjs → chunk-Q3VWJARJ.mjs} +2 -2
  8. package/dist/{chunk-F2MMCTB5.mjs → chunk-XPDI4SYI.mjs} +5 -4
  9. package/dist/{chunk-DZ6HHTM5.mjs → chunk-XQHEKKDN.mjs} +1 -1
  10. package/dist/{chunk-W3TY22IS.mjs → chunk-ZDEVRTGY.mjs} +10 -3
  11. package/dist/cli.mjs +174 -65
  12. package/dist/{create-MQ4OHZAX.mjs → create-DJHCP7LN.mjs} +3 -3
  13. package/dist/{handle-K2AZLTKU.mjs → handle-CCTBNAJZ.mjs} +1 -1
  14. package/dist/{project-Q4LKML42.mjs → project-ZYGNPVGL.mjs} +2 -2
  15. package/dist/prompts/ideate-auto.md +3 -2
  16. package/dist/prompts/ideate.md +2 -2
  17. package/dist/prompts/plan-auto.md +11 -8
  18. package/dist/prompts/plan-common.md +13 -8
  19. package/dist/prompts/plan-interactive.md +11 -10
  20. package/dist/prompts/task-evaluation.md +54 -0
  21. package/dist/prompts/task-execution.md +7 -5
  22. package/dist/{resolver-NH34HTB6.mjs → resolver-L52KR4GY.mjs} +2 -2
  23. package/dist/{sprint-UHYXSEBJ.mjs → sprint-LUXAV3Q3.mjs} +2 -2
  24. package/dist/{wizard-MCDDXLGE.mjs → wizard-TFJXEYD2.mjs} +6 -6
  25. package/package.json +17 -14
  26. package/schemas/config.schema.json +10 -0
  27. package/schemas/projects.schema.json +5 -0
  28. 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-YIB7QYU4.mjs";
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-M7JV6MKD.mjs";
55
+ } from "./chunk-AXNZMHFQ.mjs";
53
56
  import {
54
57
  escapableSelect
55
58
  } from "./chunk-7LZ6GOGN.mjs";
56
59
  import {
57
60
  sprintCreateCommand
58
- } from "./chunk-DZ6HHTM5.mjs";
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-F2MMCTB5.mjs";
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-PDI6HBZ7.mjs";
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-LFDW6MWF.mjs";
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-W3TY22IS.mjs";
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", ...repoPaths];
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 args = ["--permission-mode", "plan", "--print"];
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 jsonStr = extractJsonObject(output);
1181
- const parseR = Result.try(() => JSON.parse(jsonStr));
1182
- if (!parseR.ok) {
1183
- throw new Error(`Invalid JSON: ${parseR.error.message}`, { cause: parseR.error });
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
- const result = IdeateOutputSchema.safeParse(parseR.value);
1186
- if (!result.success) {
1187
- const issues = result.error.issues.map((issue) => {
1188
- const path = issue.path.length > 0 ? `[${issue.path.join(".")}]` : "";
1189
- return ` ${path}: ${issue.message}`;
1190
- }).join("\n");
1191
- throw new Error(`Invalid ideate output format:
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
- ticket.affectedRepositories = selectedPaths;
1295
- await saveSprint(sprint);
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 ticketIdx = sprint.tickets.findIndex((t) => t.id === ticket.id);
1336
- const ticketToUpdate = sprint.tickets[ticketIdx];
1337
- if (ticketIdx !== -1 && ticketToUpdate) {
1338
- ticketToUpdate.requirements = ideateOutput.requirements;
1339
- ticketToUpdate.requirementStatus = "approved";
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(sprint);
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(sprint.tickets.map((t) => t.id));
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 ticketIdx = sprint.tickets.findIndex((t) => t.id === ticket.id);
1409
- const ticketToUpdate = sprint.tickets[ticketIdx];
1410
- if (ticketIdx !== -1 && ticketToUpdate) {
1411
- ticketToUpdate.requirements = ideateOutput.requirements;
1412
- ticketToUpdate.requirementStatus = "approved";
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(sprint);
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(sprint.tickets.map((t) => t.id));
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
- return { name: "AI provider binary", status: "pass", detail: `${binary} found` };
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-MCDDXLGE.mjs");
3766
+ const { runWizard } = await import("./wizard-TFJXEYD2.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.1.4",
4132
- description: "Sprint and task management CLI for AI-assisted coding",
4237
+ version: "0.2.1",
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
- "claude",
4147
- "ai",
4148
- "sprint",
4149
- "task-management",
4150
- "planning",
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.0",
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.3.3",
4302
+ "@types/node": "^25.5.0",
4195
4303
  "@types/tabtab": "^3.0.4",
4196
- eslint: "^10.0.2",
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.3.1",
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.56.1",
4206
- vitest: "^4.0.18"
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-K2AZLTKU.mjs");
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-DZ6HHTM5.mjs";
5
- import "./chunk-LFDW6MWF.mjs";
4
+ } from "./chunk-XQHEKKDN.mjs";
5
+ import "./chunk-KPTPKLXY.mjs";
6
6
  import "./chunk-OEUJDSHY.mjs";
7
- import "./chunk-W3TY22IS.mjs";
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-NH34HTB6.mjs");
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-PDI6HBZ7.mjs";
13
- import "./chunk-W3TY22IS.mjs";
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 (if exists), understand project structure, find patterns
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 CLAUDE.md if available
115
+ - Verification steps use commands from the repository instruction files if available
115
116
  - Tasks properly ordered by dependencies
116
117
 
117
118
  **Example:**
@@ -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 CLAUDE.md (if exists), check project structure, find similar implementations, extract
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 a
4
- self-contained mini-spec that can be picked up cold and completed in a single Claude session. Make all decisions
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 CLAUDE.md first** (if it exists) Primary source for project conventions, patterns, verification commands, and
14
- architecture. Follow any links to other documentation. Check `.claude/` directory for agents, rules, and memory (see
15
- "Project Resources" section below).
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 CLAUDE.md/README, then only the specific files needed to understand patterns and plan
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 CLAUDE.md
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