ralphctl 0.2.4 → 0.2.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 CHANGED
@@ -99,6 +99,8 @@ Or run `ralphctl` with no arguments for an interactive menu that walks you throu
99
99
  - **Catch mistakes before they compound** — independent AI review after each task, iterating until quality passes or
100
100
  budget is exhausted
101
101
  - **Coordinate across repositories** — one sprint can span multiple repos with automatic dependency tracking
102
+ - **Branch per sprint** — optional shared branch across every affected repo, with `sprint close --create-pr` to open
103
+ pull requests when you're done
102
104
  - **Run tasks in parallel** — one per repo, with rate-limit backoff and automatic session resume
103
105
  - **Separate the what from the how** — AI clarifies requirements first, then generates implementation tasks, with human
104
106
  approval gates
@@ -119,6 +121,15 @@ ralphctl config set provider copilot # Use GitHub Copilot
119
121
 
120
122
  Auto-prompts on first AI command if not set. Both CLIs must be in your PATH and authenticated.
121
123
 
124
+ Tune the generator-evaluator loop:
125
+
126
+ ```bash
127
+ ralphctl config set evaluationIterations 2 # Up to 2 fix attempts per task (default: 1)
128
+ ralphctl config set evaluationIterations 0 # Disable evaluation entirely
129
+ ```
130
+
131
+ `sprint start --no-evaluate` skips evaluation for a single run without touching the global setting.
132
+
122
133
  <details>
123
134
  <summary>Provider differences</summary>
124
135
 
@@ -181,15 +192,16 @@ export RALPHCTL_ROOT="/path/to/custom/data-dir"
181
192
 
182
193
  ### Execution & Monitoring
183
194
 
184
- | Command | Description |
185
- | ------------------------ | --------------------------------- |
186
- | `ralphctl sprint start` | Execute tasks with AI |
187
- | `ralphctl sprint health` | Diagnose blockers and stale tasks |
188
- | `ralphctl status` | Sprint overview with progress bar |
189
- | `ralphctl task list` | List tasks in the current sprint |
190
- | `ralphctl task next` | Show the next unblocked task |
191
- | `ralphctl sprint close` | Close an active sprint |
192
- | `ralphctl sprint delete` | Delete a sprint permanently |
195
+ | Command | Description |
196
+ | -------------------------- | ------------------------------------------------------ |
197
+ | `ralphctl sprint start` | Execute tasks with AI (`--branch` for a sprint branch) |
198
+ | `ralphctl sprint health` | Diagnose blockers and stale tasks |
199
+ | `ralphctl sprint insights` | Analyze evaluator results across tasks |
200
+ | `ralphctl status` | Sprint overview with progress bar |
201
+ | `ralphctl task list` | List tasks in the current sprint |
202
+ | `ralphctl task next` | Show the next unblocked task |
203
+ | `ralphctl sprint close` | Close an active sprint (`--create-pr` for PRs) |
204
+ | `ralphctl sprint delete` | Delete a sprint permanently |
193
205
 
194
206
  Run `ralphctl <command> --help` for details on any command.
195
207
 
@@ -125,37 +125,91 @@ var promptDir = getPromptDir();
125
125
  function loadTemplate(name) {
126
126
  return readFileSync(join(promptDir, `${name}.md`), "utf-8");
127
127
  }
128
- function buildPlanPrompt(template, context, schema) {
129
- const common = loadTemplate("plan-common");
130
- return template.replace("{{COMMON}}", common).replace("{{CONTEXT}}", context).replace("{{SCHEMA}}", schema);
128
+ function loadPartial(name) {
129
+ return loadTemplate(name).replace(/\s+$/, "");
131
130
  }
132
- function buildInteractivePrompt(context, outputFile, schema) {
133
- const template = loadTemplate("plan-interactive");
134
- return buildPlanPrompt(template, context, schema).replace("{{OUTPUT_FILE}}", outputFile);
131
+ var UNREPLACED_TOKEN_RE = /\{\{[A-Z_]+\}\}/g;
132
+ function composePrompt(template, substitutions) {
133
+ let result = template;
134
+ for (const [key, value] of Object.entries(substitutions)) {
135
+ result = result.replaceAll(`{{${key}}}`, value);
136
+ }
137
+ const remaining = result.match(UNREPLACED_TOKEN_RE);
138
+ if (remaining) {
139
+ throw new Error(`composePrompt: unreplaced placeholders: ${[...new Set(remaining)].join(", ")}`);
140
+ }
141
+ return result;
142
+ }
143
+ function buildPlanCommon(projectToolingSection) {
144
+ return composePrompt(loadPartial("plan-common"), {
145
+ PROJECT_TOOLING: projectToolingSection
146
+ });
147
+ }
148
+ function buildPlannerBase(projectToolingSection) {
149
+ return {
150
+ HARNESS_CONTEXT: loadPartial("harness-context"),
151
+ COMMON: buildPlanCommon(projectToolingSection),
152
+ VALIDATION: loadPartial("validation-checklist"),
153
+ SIGNALS: loadPartial("signals-planning")
154
+ };
155
+ }
156
+ function buildInteractivePrompt(context, outputFile, schema, projectToolingSection) {
157
+ return composePrompt(loadTemplate("plan-interactive"), {
158
+ ...buildPlannerBase(projectToolingSection),
159
+ CONTEXT: context,
160
+ OUTPUT_FILE: outputFile,
161
+ SCHEMA: schema
162
+ });
135
163
  }
136
- function buildAutoPrompt(context, schema) {
137
- const template = loadTemplate("plan-auto");
138
- return buildPlanPrompt(template, context, schema);
164
+ function buildAutoPrompt(context, schema, projectToolingSection) {
165
+ return composePrompt(loadTemplate("plan-auto"), {
166
+ ...buildPlannerBase(projectToolingSection),
167
+ CONTEXT: context,
168
+ SCHEMA: schema
169
+ });
139
170
  }
140
171
  function buildTaskExecutionPrompt(progressFilePath, noCommit, contextFileName) {
141
172
  const template = loadTemplate("task-execution");
142
- const commitStep = noCommit ? "" : "\n> **Before continuing:** Create a git commit with a descriptive message for the changes made.\n";
173
+ const commitStep = noCommit ? "" : "\n - **Before continuing:** Create a git commit with a descriptive message for the changes made.";
143
174
  const commitConstraint = noCommit ? "" : "- **Must commit** \u2014 Create a git commit before signaling completion.\n";
144
- return template.replaceAll("{{PROGRESS_FILE}}", progressFilePath).replaceAll("{{COMMIT_STEP}}", commitStep).replaceAll("{{COMMIT_CONSTRAINT}}", commitConstraint).replaceAll("{{CONTEXT_FILE}}", contextFileName);
175
+ return composePrompt(template, {
176
+ HARNESS_CONTEXT: loadPartial("harness-context"),
177
+ SIGNALS: loadPartial("signals-task"),
178
+ PROGRESS_FILE: progressFilePath,
179
+ COMMIT_STEP: commitStep,
180
+ COMMIT_CONSTRAINT: commitConstraint,
181
+ CONTEXT_FILE: contextFileName
182
+ });
145
183
  }
146
184
  function buildTicketRefinePrompt(ticketContent, outputFile, schema, issueContext = "") {
147
185
  const template = loadTemplate("ticket-refine");
148
- return template.replace("{{TICKET}}", ticketContent).replace("{{OUTPUT_FILE}}", outputFile).replace("{{SCHEMA}}", schema).replace("{{ISSUE_CONTEXT}}", issueContext);
186
+ return composePrompt(template, {
187
+ TICKET: ticketContent,
188
+ OUTPUT_FILE: outputFile,
189
+ SCHEMA: schema,
190
+ ISSUE_CONTEXT: issueContext
191
+ });
149
192
  }
150
- function buildIdeatePrompt(ideaTitle, ideaDescription, projectName, repositories, outputFile, schema) {
151
- const template = loadTemplate("ideate");
152
- const common = loadTemplate("plan-common");
153
- return template.replace("{{IDEA_TITLE}}", ideaTitle).replace("{{IDEA_DESCRIPTION}}", ideaDescription).replace("{{PROJECT_NAME}}", projectName).replace("{{REPOSITORIES}}", repositories).replace("{{OUTPUT_FILE}}", outputFile).replace("{{SCHEMA}}", schema).replace("{{COMMON}}", common);
193
+ function buildIdeatePrompt(ideaTitle, ideaDescription, projectName, repositories, outputFile, schema, projectToolingSection) {
194
+ return composePrompt(loadTemplate("ideate"), {
195
+ ...buildPlannerBase(projectToolingSection),
196
+ IDEA_TITLE: ideaTitle,
197
+ IDEA_DESCRIPTION: ideaDescription,
198
+ PROJECT_NAME: projectName,
199
+ REPOSITORIES: repositories,
200
+ OUTPUT_FILE: outputFile,
201
+ SCHEMA: schema
202
+ });
154
203
  }
155
- function buildIdeateAutoPrompt(ideaTitle, ideaDescription, projectName, repositories, schema) {
156
- const template = loadTemplate("ideate-auto");
157
- const common = loadTemplate("plan-common");
158
- return template.replace("{{IDEA_TITLE}}", ideaTitle).replace("{{IDEA_DESCRIPTION}}", ideaDescription).replace("{{PROJECT_NAME}}", projectName).replace("{{REPOSITORIES}}", repositories).replace("{{SCHEMA}}", schema).replace("{{COMMON}}", common);
204
+ function buildIdeateAutoPrompt(ideaTitle, ideaDescription, projectName, repositories, schema, projectToolingSection) {
205
+ return composePrompt(loadTemplate("ideate-auto"), {
206
+ ...buildPlannerBase(projectToolingSection),
207
+ IDEA_TITLE: ideaTitle,
208
+ IDEA_DESCRIPTION: ideaDescription,
209
+ PROJECT_NAME: projectName,
210
+ REPOSITORIES: repositories,
211
+ SCHEMA: schema
212
+ });
159
213
  }
160
214
  function buildEvaluatorPrompt(ctx) {
161
215
  const template = loadTemplate("task-evaluation");
@@ -170,12 +224,27 @@ ${ctx.verificationCriteria.map((c) => `- ${c}`).join("\n")}` : "";
170
224
  const checkSection = ctx.checkScriptSection ? `
171
225
 
172
226
  ${ctx.checkScriptSection}` : "";
173
- return template.replaceAll("{{TASK_NAME}}", ctx.taskName).replace("{{TASK_DESCRIPTION_SECTION}}", descriptionSection).replace("{{TASK_STEPS_SECTION}}", stepsSection).replace("{{VERIFICATION_CRITERIA_SECTION}}", criteriaSection).replace("{{PROJECT_PATH}}", ctx.projectPath).replace("{{CHECK_SCRIPT_SECTION}}", checkSection).replace("{{PROJECT_TOOLING_SECTION}}", ctx.projectToolingSection);
227
+ return composePrompt(template, {
228
+ HARNESS_CONTEXT: loadPartial("harness-context"),
229
+ SIGNALS: loadPartial("signals-evaluation"),
230
+ TASK_NAME: ctx.taskName,
231
+ TASK_DESCRIPTION_SECTION: descriptionSection,
232
+ TASK_STEPS_SECTION: stepsSection,
233
+ VERIFICATION_CRITERIA_SECTION: criteriaSection,
234
+ PROJECT_PATH: ctx.projectPath,
235
+ CHECK_SCRIPT_SECTION: checkSection,
236
+ PROJECT_TOOLING: ctx.projectToolingSection
237
+ });
174
238
  }
175
239
  function buildEvaluationResumePrompt(ctx) {
176
240
  const template = loadTemplate("task-evaluation-resume");
177
241
  const commitInstruction = ctx.needsCommit ? "\n - **Then commit the fix** with a descriptive message before signaling completion." : "";
178
- return template.replace("{{CRITIQUE}}", ctx.critique).replace("{{COMMIT_INSTRUCTION}}", commitInstruction);
242
+ return composePrompt(template, {
243
+ HARNESS_CONTEXT: loadPartial("harness-context"),
244
+ SIGNALS: loadPartial("signals-task"),
245
+ CRITIQUE: ctx.critique,
246
+ COMMIT_INSTRUCTION: commitInstruction
247
+ });
179
248
  }
180
249
 
181
250
  // src/utils/requirements-export.ts
@@ -1060,7 +1129,7 @@ ${text}`;
1060
1129
 
1061
1130
  // src/commands/sprint/plan.ts
1062
1131
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1063
- import { join as join5 } from "path";
1132
+ import { join as join6 } from "path";
1064
1133
  import { confirm as confirm3 } from "@inquirer/prompts";
1065
1134
  import { Result as Result5 } from "typescript-result";
1066
1135
 
@@ -1362,6 +1431,172 @@ function validateImportTasks(importTasks2, existingTasks, ticketIds) {
1362
1431
  return errors;
1363
1432
  }
1364
1433
 
1434
+ // src/ai/project-tooling.ts
1435
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
1436
+ import { join as join5 } from "path";
1437
+ var EMPTY_TOOLING = {
1438
+ agents: [],
1439
+ skills: [],
1440
+ mcpServers: [],
1441
+ hasClaudeMd: false,
1442
+ hasAgentsMd: false,
1443
+ hasCopilotInstructions: false
1444
+ };
1445
+ function safeListDir(path, predicate) {
1446
+ try {
1447
+ if (!existsSync2(path)) return [];
1448
+ return readdirSync(path).filter(predicate).sort();
1449
+ } catch {
1450
+ return [];
1451
+ }
1452
+ }
1453
+ var EVALUATOR_DENYLISTED_AGENTS = /* @__PURE__ */ new Set(["implementer", "planner"]);
1454
+ function detectAgents(projectPath) {
1455
+ const agentsDir = join5(projectPath, ".claude", "agents");
1456
+ return safeListDir(agentsDir, (name) => name.endsWith(".md")).map((name) => name.replace(/\.md$/, "")).filter((name) => !EVALUATOR_DENYLISTED_AGENTS.has(name));
1457
+ }
1458
+ function detectSkills(projectPath) {
1459
+ const skillsDir = join5(projectPath, ".claude", "skills");
1460
+ try {
1461
+ if (!existsSync2(skillsDir)) return [];
1462
+ return readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
1463
+ } catch {
1464
+ return [];
1465
+ }
1466
+ }
1467
+ function detectMcpServers(projectPath) {
1468
+ const mcpFile = join5(projectPath, ".mcp.json");
1469
+ if (!existsSync2(mcpFile)) return [];
1470
+ try {
1471
+ const raw = readFileSync2(mcpFile, "utf-8");
1472
+ const parsed = JSON.parse(raw);
1473
+ const servers = parsed.mcpServers;
1474
+ if (!servers || typeof servers !== "object") return [];
1475
+ return Object.keys(servers).sort();
1476
+ } catch {
1477
+ return [];
1478
+ }
1479
+ }
1480
+ function detectProjectTooling(projectPath) {
1481
+ if (!projectPath || !existsSync2(projectPath)) {
1482
+ return EMPTY_TOOLING;
1483
+ }
1484
+ return {
1485
+ agents: detectAgents(projectPath),
1486
+ skills: detectSkills(projectPath),
1487
+ mcpServers: detectMcpServers(projectPath),
1488
+ hasClaudeMd: existsSync2(join5(projectPath, "CLAUDE.md")),
1489
+ hasAgentsMd: existsSync2(join5(projectPath, "AGENTS.md")),
1490
+ hasCopilotInstructions: existsSync2(join5(projectPath, ".github", "copilot-instructions.md"))
1491
+ };
1492
+ }
1493
+ function detectProjectToolingAcrossPaths(projectPaths) {
1494
+ if (projectPaths.length === 0) {
1495
+ return EMPTY_TOOLING;
1496
+ }
1497
+ const agents = /* @__PURE__ */ new Set();
1498
+ const skills = /* @__PURE__ */ new Set();
1499
+ const mcpServers = /* @__PURE__ */ new Set();
1500
+ let hasClaudeMd = false;
1501
+ let hasAgentsMd = false;
1502
+ let hasCopilotInstructions = false;
1503
+ for (const path of projectPaths) {
1504
+ const tooling = detectProjectTooling(path);
1505
+ for (const agent of tooling.agents) agents.add(agent);
1506
+ for (const skill of tooling.skills) skills.add(skill);
1507
+ for (const server of tooling.mcpServers) mcpServers.add(server);
1508
+ hasClaudeMd = hasClaudeMd || tooling.hasClaudeMd;
1509
+ hasAgentsMd = hasAgentsMd || tooling.hasAgentsMd;
1510
+ hasCopilotInstructions = hasCopilotInstructions || tooling.hasCopilotInstructions;
1511
+ }
1512
+ return {
1513
+ agents: [...agents].sort(),
1514
+ skills: [...skills].sort(),
1515
+ mcpServers: [...mcpServers].sort(),
1516
+ hasClaudeMd,
1517
+ hasAgentsMd,
1518
+ hasCopilotInstructions
1519
+ };
1520
+ }
1521
+ function buildProjectToolingSection(paths) {
1522
+ const tooling = typeof paths === "string" ? detectProjectTooling(paths) : detectProjectToolingAcrossPaths([...paths]);
1523
+ return renderProjectToolingSection(tooling);
1524
+ }
1525
+ function renderProjectToolingSection(tooling) {
1526
+ const hasAny = tooling.agents.length > 0 || tooling.skills.length > 0 || tooling.mcpServers.length > 0 || tooling.hasClaudeMd || tooling.hasAgentsMd || tooling.hasCopilotInstructions;
1527
+ if (!hasAny) return "";
1528
+ const lines = [];
1529
+ lines.push("## Project Tooling (use these \u2014 they exist for a reason)");
1530
+ lines.push("");
1531
+ lines.push(
1532
+ "This project ships with tooling that you should prefer over generic approaches. Verification and evaluation must adapt to the project\u2019s actual stack and the agents, skills, and MCP servers it has installed."
1533
+ );
1534
+ lines.push("");
1535
+ if (tooling.agents.length > 0) {
1536
+ lines.push("### Subagents available");
1537
+ lines.push("");
1538
+ lines.push("Delegate via the Task tool with `subagent_type=<name>` when the diff matches a specialty:");
1539
+ for (const agent of tooling.agents) {
1540
+ const hint = describeAgentHint(agent);
1541
+ lines.push(`- \`${agent}\`${hint ? ` \u2014 ${hint}` : ""}`);
1542
+ }
1543
+ lines.push("");
1544
+ }
1545
+ if (tooling.skills.length > 0) {
1546
+ lines.push("### Skills available");
1547
+ lines.push("");
1548
+ lines.push("Invoke via the Skill tool when the skill name matches the work in front of you:");
1549
+ for (const skill of tooling.skills) {
1550
+ lines.push(`- \`${skill}\``);
1551
+ }
1552
+ lines.push("");
1553
+ }
1554
+ if (tooling.mcpServers.length > 0) {
1555
+ lines.push("### MCP servers available");
1556
+ lines.push("");
1557
+ lines.push(
1558
+ "These give you tools beyond the filesystem. Use them to **interact with the running system**, not just read its source."
1559
+ );
1560
+ for (const server of tooling.mcpServers) {
1561
+ const hint = describeMcpHint(server);
1562
+ lines.push(`- \`${server}\`${hint ? ` \u2014 ${hint}` : ""}`);
1563
+ }
1564
+ lines.push("");
1565
+ }
1566
+ const instructionFiles = [];
1567
+ if (tooling.hasClaudeMd) instructionFiles.push("`CLAUDE.md`");
1568
+ if (tooling.hasAgentsMd) instructionFiles.push("`AGENTS.md`");
1569
+ if (tooling.hasCopilotInstructions) instructionFiles.push("`.github/copilot-instructions.md`");
1570
+ if (instructionFiles.length > 0) {
1571
+ lines.push("### Project instructions");
1572
+ lines.push("");
1573
+ lines.push(
1574
+ `Read ${instructionFiles.join(" / ")} for project-specific verification commands, conventions, and constraints. If no check script is configured, derive verification commands from these files (e.g. \`package.json\` scripts referenced there).`
1575
+ );
1576
+ lines.push("");
1577
+ }
1578
+ return lines.join("\n");
1579
+ }
1580
+ function describeAgentHint(name) {
1581
+ const hints = {
1582
+ auditor: "use for security-sensitive diffs (auth, input handling, file IO, secrets)",
1583
+ reviewer: "use for general code-quality review of the diff",
1584
+ tester: "use to assess test coverage and quality of new tests",
1585
+ designer: "use for UI/UX/theming changes"
1586
+ };
1587
+ return hints[name] ?? null;
1588
+ }
1589
+ function describeMcpHint(name) {
1590
+ const lower = name.toLowerCase();
1591
+ if (lower.includes("playwright")) return "use for any UI/frontend task \u2014 click through the changed flow";
1592
+ if (lower.includes("puppeteer")) return "use for browser automation on UI changes";
1593
+ if (lower.includes("github")) return "use to inspect related PRs/issues for context";
1594
+ if (lower.includes("postgres") || lower.includes("mysql") || lower.includes("sqlite")) {
1595
+ return "use to verify database schema/migration changes against a real DB";
1596
+ }
1597
+ return null;
1598
+ }
1599
+
1365
1600
  // src/interactive/selectors.ts
1366
1601
  import { checkbox, confirm as confirm2, input } from "@inquirer/prompts";
1367
1602
  async function selectProject(message = "Select project:") {
@@ -1775,7 +2010,7 @@ async function getSprintContext(sprintName, ticketsByProject, existingTasks) {
1775
2010
  return lines.join("\n");
1776
2011
  }
1777
2012
  async function invokeAiInteractive(prompt, repoPaths, planDir) {
1778
- const contextFile = join5(planDir, "planning-context.md");
2013
+ const contextFile = join6(planDir, "planning-context.md");
1779
2014
  await writeFile3(contextFile, prompt, "utf-8");
1780
2015
  const provider = await getActiveProvider();
1781
2016
  const ticketCount = (prompt.match(/^####/gm) ?? []).length;
@@ -1943,8 +2178,9 @@ async function sprintPlanCommand(args) {
1943
2178
  const planDir = getPlanningDir(id);
1944
2179
  await mkdir2(planDir, { recursive: true });
1945
2180
  const ticketIds = new Set(sprint.tickets.map((t) => t.id));
2181
+ const projectToolingSection = buildProjectToolingSection(selectedPaths);
1946
2182
  if (options.auto) {
1947
- const prompt = buildAutoPrompt(context, schema);
2183
+ const prompt = buildAutoPrompt(context, schema, projectToolingSection);
1948
2184
  const spinner = createSpinner(`${providerName} is planning tasks...`);
1949
2185
  spinner.start();
1950
2186
  const outputR = await wrapAsync(() => invokeAiAuto(prompt, selectedPaths, planDir), ensureError);
@@ -2000,8 +2236,8 @@ async function sprintPlanCommand(args) {
2000
2236
  showSuccess(`Imported ${String(imported)}/${String(parsedTasks.length)} tasks.`);
2001
2237
  log.newline();
2002
2238
  } else {
2003
- const outputFile = join5(planDir, "tasks.json");
2004
- const prompt = buildInteractivePrompt(context, outputFile, schema);
2239
+ const outputFile = join6(planDir, "tasks.json");
2240
+ const prompt = buildInteractivePrompt(context, outputFile, schema, projectToolingSection);
2005
2241
  showInfo(`Starting interactive ${providerName} session...`);
2006
2242
  console.log(
2007
2243
  muted(
@@ -2327,12 +2563,12 @@ var RateLimitCoordinator = class {
2327
2563
  // src/ai/task-context.ts
2328
2564
  import { execSync } from "child_process";
2329
2565
  import { writeFile as writeFile4 } from "fs/promises";
2330
- import { join as join7 } from "path";
2566
+ import { join as join8 } from "path";
2331
2567
  import { Result as Result7 } from "typescript-result";
2332
2568
 
2333
2569
  // src/ai/permissions.ts
2334
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2335
- import { join as join6 } from "path";
2570
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
2571
+ import { join as join7 } from "path";
2336
2572
  import { homedir } from "os";
2337
2573
  import { Result as Result6 } from "typescript-result";
2338
2574
  function getProviderPermissions(projectPath, provider) {
@@ -2343,10 +2579,10 @@ function getProviderPermissions(projectPath, provider) {
2343
2579
  if (provider === "copilot") {
2344
2580
  return permissions;
2345
2581
  }
2346
- const projectSettingsPath = join6(projectPath, ".claude", "settings.local.json");
2347
- if (existsSync2(projectSettingsPath)) {
2582
+ const projectSettingsPath = join7(projectPath, ".claude", "settings.local.json");
2583
+ if (existsSync3(projectSettingsPath)) {
2348
2584
  const projectResult = Result6.try(() => {
2349
- const content = readFileSync2(projectSettingsPath, "utf-8");
2585
+ const content = readFileSync3(projectSettingsPath, "utf-8");
2350
2586
  return JSON.parse(content);
2351
2587
  });
2352
2588
  if (projectResult.ok) {
@@ -2359,10 +2595,10 @@ function getProviderPermissions(projectPath, provider) {
2359
2595
  }
2360
2596
  }
2361
2597
  }
2362
- const userSettingsPath = join6(homedir(), ".claude", "settings.json");
2363
- if (existsSync2(userSettingsPath)) {
2598
+ const userSettingsPath = join7(homedir(), ".claude", "settings.json");
2599
+ if (existsSync3(userSettingsPath)) {
2364
2600
  const userResult = Result6.try(() => {
2365
- const content = readFileSync2(userSettingsPath, "utf-8");
2601
+ const content = readFileSync3(userSettingsPath, "utf-8");
2366
2602
  return JSON.parse(content);
2367
2603
  });
2368
2604
  if (userResult.ok) {
@@ -2576,7 +2812,7 @@ function getContextFileName(sprintId, taskId) {
2576
2812
  return `.ralphctl-sprint-${sprintId}-task-${taskId}-context.md`;
2577
2813
  }
2578
2814
  async function writeTaskContextFile(projectPath, taskContent, instructions, sprintId, taskId) {
2579
- const contextFile = join7(projectPath, getContextFileName(sprintId, taskId));
2815
+ const contextFile = join8(projectPath, getContextFileName(sprintId, taskId));
2580
2816
  const warning2 = `<!-- TEMPORARY FILE - DO NOT COMMIT -->
2581
2817
  <!-- This file is auto-generated by ralphctl for task execution context -->
2582
2818
  <!-- It will be automatically cleaned up after task completion -->
@@ -2643,140 +2879,6 @@ function runLifecycleHook(projectPath, script, event, timeoutOverrideMs) {
2643
2879
  return { passed: result.status === 0, output };
2644
2880
  }
2645
2881
 
2646
- // src/ai/project-tooling.ts
2647
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
2648
- import { join as join8 } from "path";
2649
- var EMPTY_TOOLING = {
2650
- agents: [],
2651
- skills: [],
2652
- mcpServers: [],
2653
- hasClaudeMd: false,
2654
- hasAgentsMd: false,
2655
- hasCopilotInstructions: false
2656
- };
2657
- function safeListDir(path, predicate) {
2658
- try {
2659
- if (!existsSync3(path)) return [];
2660
- return readdirSync(path).filter(predicate).sort();
2661
- } catch {
2662
- return [];
2663
- }
2664
- }
2665
- var EVALUATOR_DENYLISTED_AGENTS = /* @__PURE__ */ new Set(["implementer", "planner"]);
2666
- function detectAgents(projectPath) {
2667
- const agentsDir = join8(projectPath, ".claude", "agents");
2668
- return safeListDir(agentsDir, (name) => name.endsWith(".md")).map((name) => name.replace(/\.md$/, "")).filter((name) => !EVALUATOR_DENYLISTED_AGENTS.has(name));
2669
- }
2670
- function detectSkills(projectPath) {
2671
- const skillsDir = join8(projectPath, ".claude", "skills");
2672
- try {
2673
- if (!existsSync3(skillsDir)) return [];
2674
- return readdirSync(skillsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
2675
- } catch {
2676
- return [];
2677
- }
2678
- }
2679
- function detectMcpServers(projectPath) {
2680
- const mcpFile = join8(projectPath, ".mcp.json");
2681
- if (!existsSync3(mcpFile)) return [];
2682
- try {
2683
- const raw = readFileSync3(mcpFile, "utf-8");
2684
- const parsed = JSON.parse(raw);
2685
- const servers = parsed.mcpServers;
2686
- if (!servers || typeof servers !== "object") return [];
2687
- return Object.keys(servers).sort();
2688
- } catch {
2689
- return [];
2690
- }
2691
- }
2692
- function detectProjectTooling(projectPath) {
2693
- if (!projectPath || !existsSync3(projectPath)) {
2694
- return EMPTY_TOOLING;
2695
- }
2696
- return {
2697
- agents: detectAgents(projectPath),
2698
- skills: detectSkills(projectPath),
2699
- mcpServers: detectMcpServers(projectPath),
2700
- hasClaudeMd: existsSync3(join8(projectPath, "CLAUDE.md")),
2701
- hasAgentsMd: existsSync3(join8(projectPath, "AGENTS.md")),
2702
- hasCopilotInstructions: existsSync3(join8(projectPath, ".github", "copilot-instructions.md"))
2703
- };
2704
- }
2705
- function renderProjectToolingSection(tooling) {
2706
- const hasAny = tooling.agents.length > 0 || tooling.skills.length > 0 || tooling.mcpServers.length > 0 || tooling.hasClaudeMd || tooling.hasAgentsMd || tooling.hasCopilotInstructions;
2707
- if (!hasAny) return "";
2708
- const lines = [];
2709
- lines.push("## Project Tooling (use these \u2014 they exist for a reason)");
2710
- lines.push("");
2711
- lines.push(
2712
- "This project ships with tooling that you should prefer over generic approaches. Verification and evaluation must adapt to the project\u2019s actual stack and the agents, skills, and MCP servers it has installed."
2713
- );
2714
- lines.push("");
2715
- if (tooling.agents.length > 0) {
2716
- lines.push("### Subagents available");
2717
- lines.push("");
2718
- lines.push("Delegate via the Task tool with `subagent_type=<name>` when the diff matches a specialty:");
2719
- for (const agent of tooling.agents) {
2720
- const hint = describeAgentHint(agent);
2721
- lines.push(`- \`${agent}\`${hint ? ` \u2014 ${hint}` : ""}`);
2722
- }
2723
- lines.push("");
2724
- }
2725
- if (tooling.skills.length > 0) {
2726
- lines.push("### Skills available");
2727
- lines.push("");
2728
- lines.push("Invoke via the Skill tool when the skill name matches the work in front of you:");
2729
- for (const skill of tooling.skills) {
2730
- lines.push(`- \`${skill}\``);
2731
- }
2732
- lines.push("");
2733
- }
2734
- if (tooling.mcpServers.length > 0) {
2735
- lines.push("### MCP servers available");
2736
- lines.push("");
2737
- lines.push(
2738
- "These give you tools beyond the filesystem. Use them to **interact with the running system**, not just read its source."
2739
- );
2740
- for (const server of tooling.mcpServers) {
2741
- const hint = describeMcpHint(server);
2742
- lines.push(`- \`${server}\`${hint ? ` \u2014 ${hint}` : ""}`);
2743
- }
2744
- lines.push("");
2745
- }
2746
- const instructionFiles = [];
2747
- if (tooling.hasClaudeMd) instructionFiles.push("`CLAUDE.md`");
2748
- if (tooling.hasAgentsMd) instructionFiles.push("`AGENTS.md`");
2749
- if (tooling.hasCopilotInstructions) instructionFiles.push("`.github/copilot-instructions.md`");
2750
- if (instructionFiles.length > 0) {
2751
- lines.push("### Project instructions");
2752
- lines.push("");
2753
- lines.push(
2754
- `Read ${instructionFiles.join(" / ")} for project-specific verification commands, conventions, and constraints. If no check script is configured, derive verification commands from these files (e.g. \`package.json\` scripts referenced there).`
2755
- );
2756
- lines.push("");
2757
- }
2758
- return lines.join("\n");
2759
- }
2760
- function describeAgentHint(name) {
2761
- const hints = {
2762
- auditor: "use for security-sensitive diffs (auth, input handling, file IO, secrets)",
2763
- reviewer: "use for general code-quality review of the diff",
2764
- tester: "use to assess test coverage and quality of new tests",
2765
- designer: "use for UI/UX/theming changes"
2766
- };
2767
- return hints[name] ?? null;
2768
- }
2769
- function describeMcpHint(name) {
2770
- const lower = name.toLowerCase();
2771
- if (lower.includes("playwright")) return "use for any UI/frontend task \u2014 click through the changed flow";
2772
- if (lower.includes("puppeteer")) return "use for browser automation on UI changes";
2773
- if (lower.includes("github")) return "use to inspect related PRs/issues for context";
2774
- if (lower.includes("postgres") || lower.includes("mysql") || lower.includes("sqlite")) {
2775
- return "use to verify database schema/migration changes against a real DB";
2776
- }
2777
- return null;
2778
- }
2779
-
2780
2882
  // src/ai/evaluator.ts
2781
2883
  var EVALUATOR_MAX_TURNS = 100;
2782
2884
  function getEvaluatorModel(generatorModel, provider) {
@@ -2831,8 +2933,7 @@ ${checkScript}
2831
2933
  \`\`\`
2832
2934
 
2833
2935
  If this script fails, the implementation fails regardless of code quality. Record the full output.` : null;
2834
- const tooling = detectProjectTooling(task.projectPath);
2835
- const projectToolingSection = renderProjectToolingSection(tooling);
2936
+ const projectToolingSection = buildProjectToolingSection(task.projectPath);
2836
2937
  return {
2837
2938
  taskName: task.name,
2838
2939
  taskDescription: task.description ?? "",
@@ -4215,6 +4316,7 @@ export {
4215
4316
  parseRequirementsFile,
4216
4317
  runAiSession,
4217
4318
  sprintRefineCommand,
4319
+ buildProjectToolingSection,
4218
4320
  getTaskImportSchema,
4219
4321
  parsePlanningBlocked,
4220
4322
  buildHeadlessAiRequest,
package/dist/cli.mjs CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  buildHeadlessAiRequest,
11
11
  buildIdeateAutoPrompt,
12
12
  buildIdeatePrompt,
13
+ buildProjectToolingSection,
13
14
  buildTicketRefinePrompt,
14
15
  exportRequirementsToMarkdown,
15
16
  extractJsonArray,
@@ -52,7 +53,7 @@ import {
52
53
  sprintStartCommand,
53
54
  updateTaskStatus,
54
55
  validateImportTasks
55
- } from "./chunk-U62BX47C.mjs";
56
+ } from "./chunk-CSICORGV.mjs";
56
57
  import {
57
58
  escapableSelect
58
59
  } from "./chunk-7LZ6GOGN.mjs";
@@ -1338,8 +1339,16 @@ async function sprintIdeateCommand(args) {
1338
1339
  const schema = await getTaskImportSchema();
1339
1340
  const ideateDir = getIdeateDir(id, ticket.id);
1340
1341
  await mkdir(ideateDir, { recursive: true });
1342
+ const projectToolingSection = buildProjectToolingSection(selectedPaths);
1341
1343
  if (options.auto) {
1342
- const prompt = buildIdeateAutoPrompt(ideaTitle, ideaDescription, projectName, repositoriesText, schema);
1344
+ const prompt = buildIdeateAutoPrompt(
1345
+ ideaTitle,
1346
+ ideaDescription,
1347
+ projectName,
1348
+ repositoriesText,
1349
+ schema,
1350
+ projectToolingSection
1351
+ );
1343
1352
  const spinner = createSpinner(`${providerName} is refining idea and planning tasks...`);
1344
1353
  spinner.start();
1345
1354
  const outputR = await wrapAsync(() => invokeAiAuto(prompt, selectedPaths, ideateDir), ensureError);
@@ -1419,7 +1428,15 @@ async function sprintIdeateCommand(args) {
1419
1428
  log.newline();
1420
1429
  } else {
1421
1430
  const outputFile = join(ideateDir, "output.json");
1422
- const prompt = buildIdeatePrompt(ideaTitle, ideaDescription, projectName, repositoriesText, outputFile, schema);
1431
+ const prompt = buildIdeatePrompt(
1432
+ ideaTitle,
1433
+ ideaDescription,
1434
+ projectName,
1435
+ repositoriesText,
1436
+ outputFile,
1437
+ schema,
1438
+ projectToolingSection
1439
+ );
1423
1440
  showInfo(`Starting interactive ${providerName} session...`);
1424
1441
  console.log(muted(` Exploring: ${selectedPaths.join(", ")}`));
1425
1442
  console.log(muted(`
@@ -3764,7 +3781,7 @@ async function interactiveMode() {
3764
3781
  continue;
3765
3782
  }
3766
3783
  if (command === "wizard") {
3767
- const { runWizard } = await import("./wizard-HWOH2HPV.mjs");
3784
+ const { runWizard } = await import("./wizard-XZ7OGBCJ.mjs");
3768
3785
  await runWizard();
3769
3786
  continue;
3770
3787
  }
@@ -4323,7 +4340,7 @@ Checks performed:
4323
4340
  // package.json
4324
4341
  var package_default = {
4325
4342
  name: "ralphctl",
4326
- version: "0.2.4",
4343
+ version: "0.2.5",
4327
4344
  description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
4328
4345
  homepage: "https://github.com/lukas-grigis/ralphctl",
4329
4346
  type: "module",
@@ -0,0 +1,5 @@
1
+ <harness-context>
2
+ Your context window will be automatically compacted as it approaches its limit, allowing you to continue working
3
+ indefinitely. Do not stop early or rush completion due to token budget concerns — the harness manages session
4
+ lifecycle. Focus on doing the work correctly within your designated role.
5
+ </harness-context>
@@ -4,6 +4,10 @@ You are a combined requirements analyst and task planner working autonomously. T
4
4
  requirements and a dependency-ordered set of implementation tasks. Make all decisions based on the idea description and
5
5
  codebase analysis — there is no user to interact with.
6
6
 
7
+ {{HARNESS_CONTEXT}}
8
+
9
+ When finished, emit a signal from the `<signals>` block below.
10
+
7
11
  ## Two-Phase Protocol
8
12
 
9
13
  ### Phase 1: Refine Requirements (WHAT)
@@ -50,13 +54,34 @@ Analyze the idea and produce complete, implementation-agnostic requirements:
50
54
 
51
55
  ### Phase 2: Plan Implementation (HOW)
52
56
 
53
- Explore the selected repositories and produce implementation tasks:
57
+ Phase 2 begins with reconnaissance — orient yourself in the codebase before generating tasks. Skip exploration and your
58
+ plan will be guesswork.
59
+
60
+ #### Step 0: Explore the Project
61
+
62
+ Explore efficiently — read what matters, skip what does not:
63
+
64
+ 1. **Read project instructions first** — start with `CLAUDE.md` if it exists, and also check provider-specific files
65
+ such as `.github/copilot-instructions.md` and `AGENTS.md` when present. Follow any links to other documentation.
66
+ Check the `.claude/` directory for agents, rules, and memory (see "Project Resources" in the Planning Common
67
+ Context below).
68
+ 2. **Read manifest files** — `package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `pom.xml`, etc. for dependencies
69
+ and scripts
70
+ 3. **Read README** — project overview, setup, and architecture
71
+ 4. **Scan directory structure** — understand the layout before diving into files
72
+ 5. **Find similar implementations** — look for existing features similar to what the requirements call for; follow
73
+ their patterns
74
+ 6. **Extract verification commands** — find the exact build, test, lint, and typecheck commands from the repository
75
+ instruction files or project config
54
76
 
55
- 1. **Explore codebase** Read the repository instruction files (`CLAUDE.md`, `.github/copilot-instructions.md`, etc.)
56
- when present, understand project structure, find patterns
57
- 2. **Map requirements to implementation** — Determine which parts map to which repository
58
- 3. **Create tasks** Following the Planning Common Context guidelines below
59
- 4. **Validate** — Ensure tasks are non-overlapping, properly ordered, and completable
77
+ Read project instruction files and README first, then only the specific files needed to understand patterns and plan
78
+ tasks broad exploration wastes context budget without improving task quality.
79
+
80
+ #### Step 1: Generate the Plan
81
+
82
+ 1. **Map requirements to implementation** — Determine which parts of the approved requirements map to which repository
83
+ 2. **Create tasks** — Following the Planning Common Context guidelines below
84
+ 3. **Validate** — Ensure tasks are non-overlapping, properly ordered, and completable
60
85
 
61
86
  ### Blocker Handling
62
87
 
@@ -84,17 +109,7 @@ You have access to these repositories:
84
109
 
85
110
  {{COMMON}}
86
111
 
87
- ## Pre-Output Validation
88
-
89
- Before outputting JSON, verify:
90
-
91
- 1. **Requirements complete** — Problem statement, acceptance criteria, and scope boundaries are all present
92
- 2. **No file overlap** — No two tasks modify the same files (or overlap is delineated in steps)
93
- 3. **Correct order** — Foundations before dependents, all `blockedBy` references point to earlier tasks
94
- 4. **Maximized parallelism** — Independent tasks do NOT block each other unnecessarily
95
- 5. **Precise steps** — Every task has 3+ specific, actionable steps with file references
96
- 6. **Verification steps** — Every task ends with project-appropriate verification commands
97
- 7. **projectPath assigned** — Every task uses a path from the Selected Repositories
112
+ {{VALIDATION}}
98
113
 
99
114
  ## Output Format
100
115
 
@@ -149,6 +164,8 @@ If you cannot produce a valid plan, output `<planning-blocked>reason</planning-b
149
164
  }
150
165
  ```
151
166
 
167
+ {{SIGNALS}}
168
+
152
169
  ---
153
170
 
154
171
  Proceed autonomously: refine the idea into clear requirements, explore the codebase, then generate tasks. Output only
@@ -3,6 +3,10 @@
3
3
  You are a combined requirements analyst and task planner. Your goal is to quickly turn a rough idea into refined
4
4
  requirements and a dependency-ordered set of implementation tasks in a single session.
5
5
 
6
+ {{HARNESS_CONTEXT}}
7
+
8
+ When finished, emit a signal from the `<signals>` block below.
9
+
6
10
  ## Two-Phase Protocol
7
11
 
8
12
  ### Phase 1: Refine Requirements (WHAT)
@@ -27,8 +31,16 @@ Focus: Clarify WHAT needs to be built (implementation-agnostic)
27
31
  - What are the acceptance criteria? (Given/When/Then format)
28
32
  - What edge cases and error states need handling?
29
33
  - What are the business constraints? (performance, compatibility, etc.)
30
- 3. **Stop when ready** — Stop asking questions when the problem statement is clear, requirements have acceptance
31
- criteria, scope boundaries are explicit, and major edge cases are addressed
34
+ 3. **Stop when ready** — Stop asking questions when ALL of these are true:
35
+ - The problem statement is clear and agreed upon
36
+ - Every functional requirement has at least one acceptance criterion
37
+ - Scope boundaries (in/out) are explicitly defined
38
+ - Major edge cases and error states are addressed
39
+ - No remaining ambiguity about what the feature should do — two developers reading these requirements would build
40
+ the same observable behavior
41
+
42
+ If the idea description already answers all of these, skip directly to Step 4.
43
+
32
44
  4. **Present requirements** — Show the complete refined requirements in readable markdown, then ask for approval using
33
45
  AskUserQuestion:
34
46
  ```
@@ -103,6 +115,8 @@ Focus: Determine HOW to implement the approved requirements
103
115
  - Ask: "Does this task breakdown look correct? Any changes needed?"
104
116
  7. **Wait for confirmation** — write the JSON to the output file after the user confirms
105
117
 
118
+ {{VALIDATION}}
119
+
106
120
  ## Idea to Refine and Plan
107
121
 
108
122
  **Title:** {{IDEA_TITLE}}
@@ -176,6 +190,8 @@ Use this exact JSON Schema:
176
190
  - Tasks can reference each other via `id` and `blockedBy`
177
191
  - Only write after BOTH requirements AND task breakdown are approved
178
192
 
193
+ {{SIGNALS}}
194
+
179
195
  ---
180
196
 
181
197
  Start with Phase 1: Read the idea above, identify what's clear vs ambiguous, then ask your first clarifying question.
@@ -4,6 +4,10 @@ You are a task planning specialist. Your goal is to produce a dependency-ordered
4
4
  self-contained mini-spec that an AI agent can pick up cold and complete in a single session. Make all decisions
5
5
  autonomously based on codebase analysis — there is no user to interact with.
6
6
 
7
+ {{HARNESS_CONTEXT}}
8
+
9
+ When finished, emit a signal from the `<signals>` block below.
10
+
7
11
  ## Protocol
8
12
 
9
13
  ### Step 1: Explore the Project
@@ -65,18 +69,7 @@ If you cannot produce a valid task breakdown, signal the issue instead of output
65
69
 
66
70
  ### Step 6: Pre-Output Validation
67
71
 
68
- Before outputting JSON, verify EVERY item on this checklist:
69
-
70
- 1. **No file overlap** — No two tasks modify the same files (or overlap is explicitly delineated in steps)
71
- 2. **Correct order** — Foundations before dependents
72
- 3. **Valid dependencies** — All `blockedBy` references point to earlier tasks with real code dependencies
73
- 4. **Maximized parallelism** — Independent tasks do NOT block each other unnecessarily
74
- 5. **Precise steps** — Every task has 3+ specific, actionable steps with file references
75
- 6. **Verification steps** — Every task ends with project-appropriate verification commands from the repository
76
- instructions
77
- 7. **projectPath assigned** — Every task has a `projectPath` from the project's repository paths
78
- 8. **Verification criteria** — Every task has 2-4 verificationCriteria that are testable and unambiguous
79
- 9. **Valid JSON** — The output parses as valid JSON matching the schema
72
+ {{VALIDATION}}
80
73
 
81
74
  ## Output
82
75
 
@@ -142,3 +135,5 @@ JSON Schema:
142
135
  }
143
136
  ]
144
137
  ```
138
+
139
+ {{SIGNALS}}
@@ -78,7 +78,7 @@ Aim for 2-4 criteria per task. Include at least one criterion that is computatio
78
78
  lint clean). For **UI/frontend tasks**, if the project has Playwright configured, add a browser-verifiable criterion —
79
79
  the evaluator will attempt visual verification using Playwright or browser tools when the project supports it.
80
80
 
81
- ### Rules
81
+ ### Guidelines
82
82
 
83
83
  1. **Outcome-oriented** — Each task delivers a testable result
84
84
  2. **Merge create+use** — Never separate "create X" from "use X" — that is one task
@@ -108,7 +108,7 @@ the evaluator will attempt visual verification using Playwright or browser tools
108
108
 
109
109
  Tasks execute in dependency order — foundations before dependents.
110
110
 
111
- ### Rules
111
+ ### Guidelines
112
112
 
113
113
  1. **Foundation first** — Shared utilities, types, schemas before anything that uses them
114
114
  2. **Declare all dependencies** — Use `blockedBy` to enforce order. Do not rely on array position alone.
@@ -205,3 +205,19 @@ commands.
205
205
 
206
206
  Start with an action verb (Add, Create, Update, Fix, Refactor, Remove, Migrate). Include the feature/concept, not files.
207
207
  Keep under 60 characters. Avoid vague verbs (Improve, Enhance, Handle).
208
+
209
+ ## Delegation to Available Tooling
210
+
211
+ The "Project Tooling" section below (when present) lists subagents, skills, and MCP servers detected in the target
212
+ repositories. Use these in your task planning:
213
+
214
+ - **Surface tool delegation in task steps.** When a step's nature matches an available tool's specialization, write
215
+ the step so the executor knows to delegate. For example, if the tooling section lists a subagent specialized in
216
+ security review, security-sensitive task steps should explicitly recommend invoking it via the Task tool. Generic
217
+ pseudo-step: _"Delegate the final review of authentication changes to the `<name>` subagent via the Task tool."_
218
+ - **Pull verification criteria from available tools.** UI tasks should add browser-verifiable criteria when a
219
+ Playwright or similar MCP is listed. Database tasks should reference DB-inspection MCPs when present.
220
+ - **Do not invent tools.** Only reference tools that actually appear in the Project Tooling section. If the section is
221
+ empty or absent, omit delegation recommendations entirely — do not fabricate subagent names.
222
+
223
+ {{PROJECT_TOOLING}}
@@ -4,6 +4,10 @@ You are a task planning specialist collaborating with the user. Your goal is to
4
4
  implementation tasks — each one a self-contained mini-spec that an AI agent can pick up cold and complete in a single
5
5
  session.
6
6
 
7
+ {{HARNESS_CONTEXT}}
8
+
9
+ When finished, emit a signal from the `<signals>` block below.
10
+
7
11
  ## Protocol
8
12
 
9
13
  ### Step 1: Explore the Project
@@ -47,7 +51,7 @@ selection.
47
51
 
48
52
  Using the confirmed repositories and your codebase exploration, create tasks. Use the tools available to you:
49
53
 
50
- Use available tools to search, explore, and read the codebase. When you need implementation decisions from the user, use AskUserQuestion:
54
+ Use available tools to search, explore, and read the codebase. When you need implementation decisions from the user, use AskUserQuestion with:
51
55
 
52
56
  - **Recommended option first** with "(Recommended)" in the label
53
57
  - **2-4 options** with descriptions explaining trade-offs
@@ -109,18 +113,7 @@ If you encounter issues that prevent planning, communicate clearly:
109
113
 
110
114
  ### Step 7: Pre-Output Checklist
111
115
 
112
- Before writing the final JSON, verify every item:
113
-
114
- - [ ] Each task modifies 1-3 primary files (up to 5-7 total including tests)
115
- - [ ] No two tasks modify the same files without clear delineation in their steps
116
- - [ ] Tasks are ordered so foundations come before dependents
117
- - [ ] Every `blockedBy` reference points to an earlier task that produces code this task needs
118
- - [ ] Independent tasks do NOT block each other (parallelism maximized)
119
- - [ ] Every task has 3+ specific, actionable steps with file references
120
- - [ ] Steps reference concrete files and functions from the actual codebase
121
- - [ ] Each task includes verification using commands from the repository instruction files (if available)
122
- - [ ] Every task has 2-4 verificationCriteria that are testable and unambiguous
123
- - [ ] Every task has a `projectPath` from the project's repository paths
116
+ {{VALIDATION}}
124
117
 
125
118
  ## Sprint Context
126
119
 
@@ -185,6 +178,8 @@ Use this exact JSON Schema:
185
178
  }
186
179
  ```
187
180
 
181
+ {{SIGNALS}}
182
+
188
183
  ---
189
184
 
190
185
  Start by reading the repository instruction files and exploring the codebase, then discuss the approach with the user.
@@ -0,0 +1,6 @@
1
+ <signals>
2
+
3
+ - `<evaluation-passed>` — All four dimensions pass; implementation accepted
4
+ - `<evaluation-failed>critique</evaluation-failed>` — One or more dimensions fail; critique describes specific issues to fix
5
+
6
+ </signals>
@@ -0,0 +1,5 @@
1
+ <signals>
2
+
3
+ - `<planning-blocked>reason</planning-blocked>` — Cannot produce a valid plan; describe the blocker
4
+
5
+ </signals>
@@ -0,0 +1,7 @@
1
+ <signals>
2
+
3
+ - `<task-verified>output</task-verified>` — Records verification results (required before completion)
4
+ - `<task-complete>` — Marks task as done (ONLY after verified)
5
+ - `<task-blocked>reason</task-blocked>` — Marks task as blocked (cannot proceed)
6
+
7
+ </signals>
@@ -1,22 +1,34 @@
1
1
  # Evaluator Feedback — Fix and Re-verify
2
2
 
3
- The independent code reviewer found issues with your implementation. Treat this as ground truth — do not argue with
4
- it. Read the critique carefully, fix each identified issue, then re-verify and signal completion.
3
+ You are a task implementer responding to a code review. The independent reviewer's findings are
4
+ authoritative fix each issue precisely, re-verify, and signal completion.
5
+
6
+ {{HARNESS_CONTEXT}}
7
+
8
+ When finished, emit a signal from the `<signals>` block below.
9
+
10
+ <constraints>
11
+
12
+ - **Stay within scope** — fix only what the critique flags; do not expand the task or refactor neighboring code
13
+ - **Fix, don't rewrite** — make minimal targeted changes; preserve the existing implementation structure where possible
14
+ - **Don't argue with the critique** — treat reviewer findings as authoritative; if a finding is genuinely wrong, signal `<task-blocked>` instead of ignoring it
15
+
16
+ </constraints>
5
17
 
6
18
  ## Critique
7
19
 
8
20
  {{CRITIQUE}}
9
21
 
10
- ## What to do now
22
+ ## Fix Protocol
11
23
 
12
- 1. **Fix each issue in the critique above.** Reference the file:line locations the reviewer cited. If a citation is
24
+ 1. **Address each issue** Reference the file:line locations the reviewer cited. If a citation is
13
25
  wrong, find the actually-affected location and fix that.
14
- 2. **Stay in scope.** If the critique calls out something outside your task scope, fix only what is within scope and
15
- note the rest. Do not expand the task.
16
- 3. **Re-run verification commands.** Run the project's check script (or the equivalent verification commands) and
17
- confirm they pass.{{COMMIT_INSTRUCTION}}
18
- 4. **Re-output verification results** wrapped in `<task-verified>...</task-verified>`.
19
- 5. **Signal completion** with `<task-complete>` ONLY after all of the above pass.
20
-
21
- If the critique is unfixable (e.g. it asks for something that contradicts the spec, or requires changes you cannot
22
- make), signal `<task-blocked>reason</task-blocked>` instead of completing.
26
+ 2. **Re-run verification** Run the project's check script (or the equivalent verification
27
+ commands) and confirm they pass.{{COMMIT_INSTRUCTION}}
28
+ 3. **Output verification results** Wrap output in `<task-verified>...</task-verified>`.
29
+ 4. **Signal completion** — Output `<task-complete>` ONLY after all steps above pass.
30
+
31
+ If an issue is unfixable (contradicts the spec, or requires changes outside your scope), signal
32
+ `<task-blocked>reason</task-blocked>` instead of completing.
33
+
34
+ {{SIGNALS}}
@@ -3,6 +3,10 @@
3
3
  You are an independent code reviewer evaluating whether an implementation satisfies its specification. Assume problems
4
4
  exist until you prove otherwise through investigation.
5
5
 
6
+ {{HARNESS_CONTEXT}}
7
+
8
+ When finished, emit a signal from the `<signals>` block below.
9
+
6
10
  <task-specification>
7
11
 
8
12
  These verification criteria are the pre-agreed definition of "done" — your primary grading rubric.
@@ -22,7 +26,7 @@ You are working in this project directory:
22
26
  {{PROJECT_PATH}}
23
27
  ```
24
28
 
25
- {{PROJECT_TOOLING_SECTION}}
29
+ {{PROJECT_TOOLING}}
26
30
 
27
31
  ### Phase 1: Computational Verification (run before reasoning)
28
32
 
@@ -180,3 +184,5 @@ Each issue must reference which dimension it violates.]
180
184
  > query: `WHERE name LIKE $1` with `%${query}%` as parameter.
181
185
 
182
186
  Be direct and specific — point to files, lines, and concrete problems.
187
+
188
+ {{SIGNALS}}
@@ -6,13 +6,11 @@ completion. Do not expand scope beyond what the declared steps specify.
6
6
  Implement the task described in {{CONTEXT_FILE}}. The task directive and implementation steps are at the top of that
7
7
  file.
8
8
 
9
- <harness-context>
10
- Your context window will be automatically compacted as it approaches its limit, allowing you to continue working
11
- indefinitely. Do not stop tasks early or rush completion due to token budget concerns. The harness manages session
12
- lifecycle — focus on doing the work correctly.
13
- </harness-context>
9
+ {{HARNESS_CONTEXT}}
14
10
 
15
- <rules>
11
+ When finished, emit a signal from the `<signals>` block below.
12
+
13
+ <constraints>
16
14
 
17
15
  - **One task only** — complete this task, then stop. The harness manages task sequencing; continuing to the next task
18
16
  would conflict with parallel execution.
@@ -29,7 +27,7 @@ lifecycle — focus on doing the work correctly.
29
27
  - **Leave task definitions unchanged** — the task name, description, steps, and other task files are immutable.
30
28
  {{COMMIT_CONSTRAINT}}
31
29
 
32
- </rules>
30
+ </constraints>
33
31
 
34
32
  ## Phase 1: Reconnaissance (feedforward — understand before acting)
35
33
 
@@ -77,9 +75,9 @@ Proceed to Phase 2 once all reconnaissance steps pass.
77
75
  - If a step is unclear, attempt reasonable interpretation before marking blocked
78
76
  - If steps seem incomplete relative to ticket requirements, signal `<task-blocked>` rather than improvising —
79
77
  the planner may have intentionally scoped them this way to avoid conflicts
80
- 3. **Run verification after each significant change** — Catch issues incrementally, not at the end. Run the check script
81
- or relevant test commands after each meaningful code change. This is cheaper than debugging a pile of errors at the
82
- end.
78
+ 3. **Smoke-test as you go** — Run relevant test or typecheck commands after each meaningful code change to catch issues
79
+ early. This is incremental sanity-checking, not the final gate. **The authoritative gate is Phase 3 step 2 below:
80
+ the full check script runs there and must pass.**
83
81
 
84
82
  ## Phase 3: Completion
85
83
 
@@ -88,8 +86,7 @@ Complete these steps IN ORDER:
88
86
  1. **Confirm all steps done** — Every task step has been completed
89
87
  2. **Run ALL verification commands** — Execute every verification command (see Check Script section in the context file
90
88
  or project instructions). Fix any failures before proceeding. The harness runs the check script as a post-task
91
- gate — your task is not marked done unless it passes.
92
- {{COMMIT_STEP}}
89
+ gate — your task is not marked done unless it passes.{{COMMIT_STEP}}
93
90
  3. **Update progress file** — Append to {{PROGRESS_FILE}} using this format:
94
91
 
95
92
  ```markdown
@@ -175,10 +172,4 @@ Signal `<task-blocked>Missing dependency: [what and which task]</task-blocked>`.
175
172
  Follow project patterns over steps if they conflict. If steps seem incomplete relative to requirements:
176
173
  `<task-blocked>Steps incomplete: [what appears missing]</task-blocked>`.
177
174
 
178
- <signals>
179
-
180
- - `<task-verified>output</task-verified>` — Records verification results (required before completion)
181
- - `<task-complete>` — Marks task as done (ONLY after verified)
182
- - `<task-blocked>reason</task-blocked>` — Marks task as blocked (cannot proceed)
183
-
184
- </signals>
175
+ {{SIGNALS}}
@@ -0,0 +1,14 @@
1
+ ## Pre-Output Validation
2
+
3
+ Before writing the JSON output, verify EVERY item:
4
+
5
+ 1. **Requirements complete** — Problem statement, acceptance criteria, and scope boundaries are all present (when applicable)
6
+ 2. **No file overlap** — No two tasks modify the same files (or overlap is explicitly delineated in steps)
7
+ 3. **Foundations before dependents** — Tasks are ordered so prerequisites come first
8
+ 4. **Valid dependencies** — All `blockedBy` references point to earlier tasks with real code dependencies
9
+ 5. **Maximized parallelism** — Independent tasks do NOT block each other unnecessarily
10
+ 6. **Precise steps** — Every task has 3+ specific, actionable steps with file references
11
+ 7. **Verification steps** — Every task ends with project-appropriate verification commands
12
+ 8. **`projectPath` assigned** — Every task uses a path from the available repositories
13
+ 9. **Verification criteria** — Every task has 2-4 `verificationCriteria` that are testable and unambiguous
14
+ 10. **Output format compliance** — Output matches the schema exactly: no markdown fences around JSON, no commentary, no surrounding text. The harness parses raw output as JSON.
@@ -3,7 +3,7 @@ import {
3
3
  sprintPlanCommand,
4
4
  sprintRefineCommand,
5
5
  sprintStartCommand
6
- } from "./chunk-U62BX47C.mjs";
6
+ } from "./chunk-CSICORGV.mjs";
7
7
  import "./chunk-7LZ6GOGN.mjs";
8
8
  import {
9
9
  sprintCreateCommand
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralphctl",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Agent harness for long-running AI coding tasks — orchestrates Claude Code & GitHub Copilot across repositories",
5
5
  "homepage": "https://github.com/lukas-grigis/ralphctl",
6
6
  "type": "module",