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 +21 -9
- package/dist/{chunk-U62BX47C.mjs → chunk-CSICORGV.mjs} +275 -173
- package/dist/cli.mjs +22 -5
- package/dist/prompts/harness-context.md +5 -0
- package/dist/prompts/ideate-auto.md +34 -17
- package/dist/prompts/ideate.md +18 -2
- package/dist/prompts/plan-auto.md +7 -12
- package/dist/prompts/plan-common.md +18 -2
- package/dist/prompts/plan-interactive.md +8 -13
- package/dist/prompts/signals-evaluation.md +6 -0
- package/dist/prompts/signals-planning.md +5 -0
- package/dist/prompts/signals-task.md +7 -0
- package/dist/prompts/task-evaluation-resume.md +25 -13
- package/dist/prompts/task-evaluation.md +7 -1
- package/dist/prompts/task-execution.md +10 -19
- package/dist/prompts/validation-checklist.md +14 -0
- package/dist/{wizard-HWOH2HPV.mjs → wizard-XZ7OGBCJ.mjs} +1 -1
- package/package.json +1 -1
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
|
|
185
|
-
|
|
|
186
|
-
| `ralphctl sprint start`
|
|
187
|
-
| `ralphctl sprint health`
|
|
188
|
-
| `ralphctl
|
|
189
|
-
| `ralphctl
|
|
190
|
-
| `ralphctl task
|
|
191
|
-
| `ralphctl
|
|
192
|
-
| `ralphctl sprint
|
|
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
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
2335
|
-
import { join as
|
|
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 =
|
|
2347
|
-
if (
|
|
2582
|
+
const projectSettingsPath = join7(projectPath, ".claude", "settings.local.json");
|
|
2583
|
+
if (existsSync3(projectSettingsPath)) {
|
|
2348
2584
|
const projectResult = Result6.try(() => {
|
|
2349
|
-
const content =
|
|
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 =
|
|
2363
|
-
if (
|
|
2598
|
+
const userSettingsPath = join7(homedir(), ".claude", "settings.json");
|
|
2599
|
+
if (existsSync3(userSettingsPath)) {
|
|
2364
2600
|
const userResult = Result6.try(() => {
|
|
2365
|
-
const content =
|
|
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 =
|
|
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
|
|
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-
|
|
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(
|
|
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(
|
|
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-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
package/dist/prompts/ideate.md
CHANGED
|
@@ -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
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
|
|
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,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
|
-
|
|
4
|
-
|
|
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
|
-
##
|
|
22
|
+
## Fix Protocol
|
|
11
23
|
|
|
12
|
-
1. **
|
|
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. **
|
|
15
|
-
|
|
16
|
-
3. **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
{{
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
</
|
|
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. **
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ralphctl",
|
|
3
|
-
"version": "0.2.
|
|
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",
|