ralphctl 0.2.3 → 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/{add-K7LNOYQ4.mjs → add-3T225IX5.mjs} +3 -3
- package/dist/{add-DWNLZQ7Q.mjs → add-6A5432U2.mjs} +4 -4
- package/dist/{chunk-QYF7QIZJ.mjs → chunk-742XQ7FL.mjs} +3 -3
- package/dist/{chunk-ORVGM6EV.mjs → chunk-CSICORGV.mjs} +583 -204
- package/dist/{chunk-V4ZUDZCG.mjs → chunk-DUU5346E.mjs} +1 -1
- package/dist/{chunk-7TBO6GOT.mjs → chunk-EUNAUHC3.mjs} +1 -1
- package/dist/{chunk-GLDPHKEW.mjs → chunk-IB6OCKZW.mjs} +15 -2
- package/dist/{chunk-ITRZMBLJ.mjs → chunk-JRFOUFD3.mjs} +1 -1
- package/dist/{chunk-LAERLCL5.mjs → chunk-UBPZHHCD.mjs} +2 -2
- package/dist/cli.mjs +29 -12
- package/dist/{create-5MILNF7E.mjs → create-MYGOWO2F.mjs} +3 -3
- package/dist/{handle-2BACSJLR.mjs → handle-TA4MYNQJ.mjs} +1 -1
- package/dist/{project-XC7AXA4B.mjs → project-YONEJICR.mjs} +2 -2
- 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 +34 -0
- package/dist/prompts/task-evaluation.md +8 -0
- package/dist/prompts/task-execution.md +10 -19
- package/dist/prompts/validation-checklist.md +14 -0
- package/dist/{resolver-CFY6DIOP.mjs → resolver-RXEY6EJE.mjs} +2 -2
- package/dist/{sprint-F4VRAEWZ.mjs → sprint-FGLWYWKX.mjs} +2 -2
- package/dist/{wizard-RCQ4QQOL.mjs → wizard-XZ7OGBCJ.mjs} +6 -6
- package/package.json +1 -1
- package/schemas/tasks.schema.json +10 -1
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
getPendingRequirements,
|
|
12
12
|
groupTicketsByProject,
|
|
13
13
|
listTickets
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-742XQ7FL.mjs";
|
|
15
15
|
import {
|
|
16
16
|
EXIT_ALL_BLOCKED,
|
|
17
17
|
EXIT_ERROR,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
getProject,
|
|
25
25
|
listProjects
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-EUNAUHC3.mjs";
|
|
27
27
|
import {
|
|
28
28
|
activateSprint,
|
|
29
29
|
assertSprintStatus,
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
setAiProvider,
|
|
41
41
|
summarizeProgressForContext,
|
|
42
42
|
withFileLock
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-JRFOUFD3.mjs";
|
|
44
44
|
import {
|
|
45
45
|
ensureError,
|
|
46
46
|
unwrapOrThrow,
|
|
@@ -50,9 +50,11 @@ import {
|
|
|
50
50
|
ImportTasksSchema,
|
|
51
51
|
RefinedRequirementsSchema,
|
|
52
52
|
TasksSchema,
|
|
53
|
+
appendToFile,
|
|
53
54
|
assertSafeCwd,
|
|
54
55
|
ensureDir,
|
|
55
56
|
fileExists,
|
|
57
|
+
getEvaluationFilePath,
|
|
56
58
|
getPlanningDir,
|
|
57
59
|
getProgressFilePath,
|
|
58
60
|
getRefinementDir,
|
|
@@ -61,7 +63,7 @@ import {
|
|
|
61
63
|
getTasksFilePath,
|
|
62
64
|
readValidatedJson,
|
|
63
65
|
writeValidatedJson
|
|
64
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-IB6OCKZW.mjs";
|
|
65
67
|
import {
|
|
66
68
|
DependencyCycleError,
|
|
67
69
|
IOError,
|
|
@@ -123,37 +125,91 @@ var promptDir = getPromptDir();
|
|
|
123
125
|
function loadTemplate(name) {
|
|
124
126
|
return readFileSync(join(promptDir, `${name}.md`), "utf-8");
|
|
125
127
|
}
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
return template.replace("{{COMMON}}", common).replace("{{CONTEXT}}", context).replace("{{SCHEMA}}", schema);
|
|
128
|
+
function loadPartial(name) {
|
|
129
|
+
return loadTemplate(name).replace(/\s+$/, "");
|
|
129
130
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
+
});
|
|
133
163
|
}
|
|
134
|
-
function buildAutoPrompt(context, schema) {
|
|
135
|
-
|
|
136
|
-
|
|
164
|
+
function buildAutoPrompt(context, schema, projectToolingSection) {
|
|
165
|
+
return composePrompt(loadTemplate("plan-auto"), {
|
|
166
|
+
...buildPlannerBase(projectToolingSection),
|
|
167
|
+
CONTEXT: context,
|
|
168
|
+
SCHEMA: schema
|
|
169
|
+
});
|
|
137
170
|
}
|
|
138
171
|
function buildTaskExecutionPrompt(progressFilePath, noCommit, contextFileName) {
|
|
139
172
|
const template = loadTemplate("task-execution");
|
|
140
|
-
const commitStep = noCommit ? "" : "\n
|
|
173
|
+
const commitStep = noCommit ? "" : "\n - **Before continuing:** Create a git commit with a descriptive message for the changes made.";
|
|
141
174
|
const commitConstraint = noCommit ? "" : "- **Must commit** \u2014 Create a git commit before signaling completion.\n";
|
|
142
|
-
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
|
+
});
|
|
143
183
|
}
|
|
144
184
|
function buildTicketRefinePrompt(ticketContent, outputFile, schema, issueContext = "") {
|
|
145
185
|
const template = loadTemplate("ticket-refine");
|
|
146
|
-
return template
|
|
186
|
+
return composePrompt(template, {
|
|
187
|
+
TICKET: ticketContent,
|
|
188
|
+
OUTPUT_FILE: outputFile,
|
|
189
|
+
SCHEMA: schema,
|
|
190
|
+
ISSUE_CONTEXT: issueContext
|
|
191
|
+
});
|
|
147
192
|
}
|
|
148
|
-
function buildIdeatePrompt(ideaTitle, ideaDescription, projectName, repositories, outputFile, schema) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
});
|
|
152
203
|
}
|
|
153
|
-
function buildIdeateAutoPrompt(ideaTitle, ideaDescription, projectName, repositories, schema) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
});
|
|
157
213
|
}
|
|
158
214
|
function buildEvaluatorPrompt(ctx) {
|
|
159
215
|
const template = loadTemplate("task-evaluation");
|
|
@@ -168,7 +224,27 @@ ${ctx.verificationCriteria.map((c) => `- ${c}`).join("\n")}` : "";
|
|
|
168
224
|
const checkSection = ctx.checkScriptSection ? `
|
|
169
225
|
|
|
170
226
|
${ctx.checkScriptSection}` : "";
|
|
171
|
-
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
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function buildEvaluationResumePrompt(ctx) {
|
|
240
|
+
const template = loadTemplate("task-evaluation-resume");
|
|
241
|
+
const commitInstruction = ctx.needsCommit ? "\n - **Then commit the fix** with a descriptive message before signaling completion." : "";
|
|
242
|
+
return composePrompt(template, {
|
|
243
|
+
HARNESS_CONTEXT: loadPartial("harness-context"),
|
|
244
|
+
SIGNALS: loadPartial("signals-task"),
|
|
245
|
+
CRITIQUE: ctx.critique,
|
|
246
|
+
COMMIT_INSTRUCTION: commitInstruction
|
|
247
|
+
});
|
|
172
248
|
}
|
|
173
249
|
|
|
174
250
|
// src/utils/requirements-export.ts
|
|
@@ -1053,7 +1129,7 @@ ${text}`;
|
|
|
1053
1129
|
|
|
1054
1130
|
// src/commands/sprint/plan.ts
|
|
1055
1131
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
1056
|
-
import { join as
|
|
1132
|
+
import { join as join6 } from "path";
|
|
1057
1133
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
1058
1134
|
import { Result as Result5 } from "typescript-result";
|
|
1059
1135
|
|
|
@@ -1163,6 +1239,12 @@ async function updateTask(taskId, updates, sprintId) {
|
|
|
1163
1239
|
if (updates.evaluationOutput !== void 0) {
|
|
1164
1240
|
task.evaluationOutput = updates.evaluationOutput;
|
|
1165
1241
|
}
|
|
1242
|
+
if (updates.evaluationStatus !== void 0) {
|
|
1243
|
+
task.evaluationStatus = updates.evaluationStatus;
|
|
1244
|
+
}
|
|
1245
|
+
if (updates.evaluationFile !== void 0) {
|
|
1246
|
+
task.evaluationFile = updates.evaluationFile;
|
|
1247
|
+
}
|
|
1166
1248
|
await saveTasks(tasks, id);
|
|
1167
1249
|
return task;
|
|
1168
1250
|
});
|
|
@@ -1349,6 +1431,172 @@ function validateImportTasks(importTasks2, existingTasks, ticketIds) {
|
|
|
1349
1431
|
return errors;
|
|
1350
1432
|
}
|
|
1351
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
|
+
|
|
1352
1600
|
// src/interactive/selectors.ts
|
|
1353
1601
|
import { checkbox, confirm as confirm2, input } from "@inquirer/prompts";
|
|
1354
1602
|
async function selectProject(message = "Select project:") {
|
|
@@ -1360,7 +1608,7 @@ async function selectProject(message = "Select project:") {
|
|
|
1360
1608
|
default: true
|
|
1361
1609
|
});
|
|
1362
1610
|
if (create) {
|
|
1363
|
-
const { projectAddCommand } = await import("./add-
|
|
1611
|
+
const { projectAddCommand } = await import("./add-3T225IX5.mjs");
|
|
1364
1612
|
await projectAddCommand({ interactive: true });
|
|
1365
1613
|
const updated = await listProjects();
|
|
1366
1614
|
if (updated.length === 0) return null;
|
|
@@ -1433,7 +1681,7 @@ async function selectSprint(message = "Select sprint:", filter) {
|
|
|
1433
1681
|
default: true
|
|
1434
1682
|
});
|
|
1435
1683
|
if (create) {
|
|
1436
|
-
const { sprintCreateCommand } = await import("./create-
|
|
1684
|
+
const { sprintCreateCommand } = await import("./create-MYGOWO2F.mjs");
|
|
1437
1685
|
await sprintCreateCommand({ interactive: true });
|
|
1438
1686
|
const updated = await listSprints();
|
|
1439
1687
|
const refiltered = filter ? updated.filter((s) => filter.includes(s.status)) : updated;
|
|
@@ -1468,7 +1716,7 @@ async function selectTicket(message = "Select ticket:", filter) {
|
|
|
1468
1716
|
default: true
|
|
1469
1717
|
});
|
|
1470
1718
|
if (create) {
|
|
1471
|
-
const { ticketAddCommand } = await import("./add-
|
|
1719
|
+
const { ticketAddCommand } = await import("./add-6A5432U2.mjs");
|
|
1472
1720
|
await ticketAddCommand({ interactive: true });
|
|
1473
1721
|
const updated = await listTickets();
|
|
1474
1722
|
const refiltered = filter ? updated.filter(filter) : updated;
|
|
@@ -1762,7 +2010,7 @@ async function getSprintContext(sprintName, ticketsByProject, existingTasks) {
|
|
|
1762
2010
|
return lines.join("\n");
|
|
1763
2011
|
}
|
|
1764
2012
|
async function invokeAiInteractive(prompt, repoPaths, planDir) {
|
|
1765
|
-
const contextFile =
|
|
2013
|
+
const contextFile = join6(planDir, "planning-context.md");
|
|
1766
2014
|
await writeFile3(contextFile, prompt, "utf-8");
|
|
1767
2015
|
const provider = await getActiveProvider();
|
|
1768
2016
|
const ticketCount = (prompt.match(/^####/gm) ?? []).length;
|
|
@@ -1930,8 +2178,9 @@ async function sprintPlanCommand(args) {
|
|
|
1930
2178
|
const planDir = getPlanningDir(id);
|
|
1931
2179
|
await mkdir2(planDir, { recursive: true });
|
|
1932
2180
|
const ticketIds = new Set(sprint.tickets.map((t) => t.id));
|
|
2181
|
+
const projectToolingSection = buildProjectToolingSection(selectedPaths);
|
|
1933
2182
|
if (options.auto) {
|
|
1934
|
-
const prompt = buildAutoPrompt(context, schema);
|
|
2183
|
+
const prompt = buildAutoPrompt(context, schema, projectToolingSection);
|
|
1935
2184
|
const spinner = createSpinner(`${providerName} is planning tasks...`);
|
|
1936
2185
|
spinner.start();
|
|
1937
2186
|
const outputR = await wrapAsync(() => invokeAiAuto(prompt, selectedPaths, planDir), ensureError);
|
|
@@ -1987,8 +2236,8 @@ async function sprintPlanCommand(args) {
|
|
|
1987
2236
|
showSuccess(`Imported ${String(imported)}/${String(parsedTasks.length)} tasks.`);
|
|
1988
2237
|
log.newline();
|
|
1989
2238
|
} else {
|
|
1990
|
-
const outputFile =
|
|
1991
|
-
const prompt = buildInteractivePrompt(context, outputFile, schema);
|
|
2239
|
+
const outputFile = join6(planDir, "tasks.json");
|
|
2240
|
+
const prompt = buildInteractivePrompt(context, outputFile, schema, projectToolingSection);
|
|
1992
2241
|
showInfo(`Starting interactive ${providerName} session...`);
|
|
1993
2242
|
console.log(
|
|
1994
2243
|
muted(
|
|
@@ -2059,15 +2308,165 @@ async function sprintPlanCommand(args) {
|
|
|
2059
2308
|
}
|
|
2060
2309
|
|
|
2061
2310
|
// src/commands/sprint/start.ts
|
|
2062
|
-
import { Result as
|
|
2311
|
+
import { Result as Result10 } from "typescript-result";
|
|
2063
2312
|
|
|
2064
2313
|
// src/ai/runner.ts
|
|
2065
2314
|
import { confirm as confirm5, input as input2, select as select2 } from "@inquirer/prompts";
|
|
2066
|
-
import { Result as
|
|
2315
|
+
import { Result as Result9 } from "typescript-result";
|
|
2067
2316
|
|
|
2068
2317
|
// src/ai/executor.ts
|
|
2069
2318
|
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
2070
2319
|
import { readFile as readFile4, unlink as unlink2 } from "fs/promises";
|
|
2320
|
+
import { Result as Result8 } from "typescript-result";
|
|
2321
|
+
|
|
2322
|
+
// src/utils/git.ts
|
|
2323
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
2324
|
+
var BRANCH_NAME_RE = /^[a-zA-Z0-9/_.-]+$/;
|
|
2325
|
+
var BRANCH_NAME_INVALID_PATTERNS = [/\.\./, /\.$/, /\/$/, /\.lock$/, /^-/, /\/\//];
|
|
2326
|
+
function isValidBranchName(name) {
|
|
2327
|
+
if (!name || name.length > 250) return false;
|
|
2328
|
+
if (!BRANCH_NAME_RE.test(name)) return false;
|
|
2329
|
+
for (const pattern of BRANCH_NAME_INVALID_PATTERNS) {
|
|
2330
|
+
if (pattern.test(name)) return false;
|
|
2331
|
+
}
|
|
2332
|
+
return true;
|
|
2333
|
+
}
|
|
2334
|
+
function getCurrentBranch(cwd) {
|
|
2335
|
+
assertSafeCwd(cwd);
|
|
2336
|
+
const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
2337
|
+
cwd,
|
|
2338
|
+
encoding: "utf-8",
|
|
2339
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2340
|
+
});
|
|
2341
|
+
if (result.status !== 0) {
|
|
2342
|
+
throw new Error(`Failed to get current branch in ${cwd}: ${result.stderr.trim()}`);
|
|
2343
|
+
}
|
|
2344
|
+
return result.stdout.trim();
|
|
2345
|
+
}
|
|
2346
|
+
function branchExists(cwd, name) {
|
|
2347
|
+
assertSafeCwd(cwd);
|
|
2348
|
+
if (!isValidBranchName(name)) {
|
|
2349
|
+
throw new Error(`Invalid branch name: ${name}`);
|
|
2350
|
+
}
|
|
2351
|
+
const result = spawnSync2("git", ["show-ref", "--verify", `refs/heads/${name}`], {
|
|
2352
|
+
cwd,
|
|
2353
|
+
encoding: "utf-8",
|
|
2354
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2355
|
+
});
|
|
2356
|
+
return result.status === 0;
|
|
2357
|
+
}
|
|
2358
|
+
function createAndCheckoutBranch(cwd, name) {
|
|
2359
|
+
assertSafeCwd(cwd);
|
|
2360
|
+
if (!isValidBranchName(name)) {
|
|
2361
|
+
throw new Error(`Invalid branch name: ${name}`);
|
|
2362
|
+
}
|
|
2363
|
+
const current = getCurrentBranch(cwd);
|
|
2364
|
+
if (current === name) {
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
if (branchExists(cwd, name)) {
|
|
2368
|
+
const result = spawnSync2("git", ["checkout", name], {
|
|
2369
|
+
cwd,
|
|
2370
|
+
encoding: "utf-8",
|
|
2371
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2372
|
+
});
|
|
2373
|
+
if (result.status !== 0) {
|
|
2374
|
+
throw new Error(`Failed to checkout branch '${name}' in ${cwd}: ${result.stderr.trim()}`);
|
|
2375
|
+
}
|
|
2376
|
+
} else {
|
|
2377
|
+
const result = spawnSync2("git", ["checkout", "-b", name], {
|
|
2378
|
+
cwd,
|
|
2379
|
+
encoding: "utf-8",
|
|
2380
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2381
|
+
});
|
|
2382
|
+
if (result.status !== 0) {
|
|
2383
|
+
throw new Error(`Failed to create branch '${name}' in ${cwd}: ${result.stderr.trim()}`);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
function verifyCurrentBranch(cwd, expected) {
|
|
2388
|
+
const current = getCurrentBranch(cwd);
|
|
2389
|
+
return current === expected;
|
|
2390
|
+
}
|
|
2391
|
+
function getDefaultBranch(cwd) {
|
|
2392
|
+
assertSafeCwd(cwd);
|
|
2393
|
+
const result = spawnSync2("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
2394
|
+
cwd,
|
|
2395
|
+
encoding: "utf-8",
|
|
2396
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2397
|
+
});
|
|
2398
|
+
if (result.status === 0) {
|
|
2399
|
+
const ref = result.stdout.trim();
|
|
2400
|
+
const parts = ref.split("/");
|
|
2401
|
+
return parts[parts.length - 1] ?? "main";
|
|
2402
|
+
}
|
|
2403
|
+
const stderr = result.stderr.trim();
|
|
2404
|
+
if (stderr.includes("is not a symbolic ref") || stderr.includes("No such ref")) {
|
|
2405
|
+
if (branchExists(cwd, "main")) return "main";
|
|
2406
|
+
if (branchExists(cwd, "master")) return "master";
|
|
2407
|
+
return "main";
|
|
2408
|
+
}
|
|
2409
|
+
throw new Error(`Failed to detect default branch in ${cwd}: ${stderr}`);
|
|
2410
|
+
}
|
|
2411
|
+
function getHeadSha(cwd) {
|
|
2412
|
+
try {
|
|
2413
|
+
assertSafeCwd(cwd);
|
|
2414
|
+
const result = spawnSync2("git", ["rev-parse", "HEAD"], {
|
|
2415
|
+
cwd,
|
|
2416
|
+
encoding: "utf-8",
|
|
2417
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2418
|
+
});
|
|
2419
|
+
if (result.status !== 0) return null;
|
|
2420
|
+
return result.stdout.trim() || null;
|
|
2421
|
+
} catch {
|
|
2422
|
+
return null;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
function hasUncommittedChanges(cwd) {
|
|
2426
|
+
assertSafeCwd(cwd);
|
|
2427
|
+
const result = spawnSync2("git", ["status", "--porcelain"], {
|
|
2428
|
+
cwd,
|
|
2429
|
+
encoding: "utf-8",
|
|
2430
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2431
|
+
});
|
|
2432
|
+
if (result.status !== 0) {
|
|
2433
|
+
throw new Error(`Failed to check git status in ${cwd}: ${result.stderr.trim()}`);
|
|
2434
|
+
}
|
|
2435
|
+
return result.stdout.trim().length > 0;
|
|
2436
|
+
}
|
|
2437
|
+
function generateBranchName(sprintId) {
|
|
2438
|
+
return `ralphctl/${sprintId}`;
|
|
2439
|
+
}
|
|
2440
|
+
function isGhAvailable() {
|
|
2441
|
+
const result = spawnSync2("gh", ["--version"], {
|
|
2442
|
+
encoding: "utf-8",
|
|
2443
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2444
|
+
});
|
|
2445
|
+
return result.status === 0;
|
|
2446
|
+
}
|
|
2447
|
+
function isGlabAvailable() {
|
|
2448
|
+
const result = spawnSync2("glab", ["--version"], {
|
|
2449
|
+
encoding: "utf-8",
|
|
2450
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2451
|
+
});
|
|
2452
|
+
return result.status === 0;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
// src/store/evaluation.ts
|
|
2456
|
+
async function writeEvaluation(sprintId, taskId, iteration, status, body) {
|
|
2457
|
+
const filePath = getEvaluationFilePath(sprintId, taskId);
|
|
2458
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2459
|
+
const header = `## ${timestamp} \u2014 Iteration ${String(iteration)} \u2014 ${status.toUpperCase()}
|
|
2460
|
+
|
|
2461
|
+
`;
|
|
2462
|
+
const entry = `${header}${body.trimEnd()}
|
|
2463
|
+
|
|
2464
|
+
---
|
|
2465
|
+
|
|
2466
|
+
`;
|
|
2467
|
+
unwrapOrThrow(await appendToFile(filePath, entry));
|
|
2468
|
+
return filePath;
|
|
2469
|
+
}
|
|
2071
2470
|
|
|
2072
2471
|
// src/ai/parser.ts
|
|
2073
2472
|
function parseExecutionResult(output) {
|
|
@@ -2164,12 +2563,12 @@ var RateLimitCoordinator = class {
|
|
|
2164
2563
|
// src/ai/task-context.ts
|
|
2165
2564
|
import { execSync } from "child_process";
|
|
2166
2565
|
import { writeFile as writeFile4 } from "fs/promises";
|
|
2167
|
-
import { join as
|
|
2566
|
+
import { join as join8 } from "path";
|
|
2168
2567
|
import { Result as Result7 } from "typescript-result";
|
|
2169
2568
|
|
|
2170
2569
|
// src/ai/permissions.ts
|
|
2171
|
-
import { existsSync as
|
|
2172
|
-
import { join as
|
|
2570
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
2571
|
+
import { join as join7 } from "path";
|
|
2173
2572
|
import { homedir } from "os";
|
|
2174
2573
|
import { Result as Result6 } from "typescript-result";
|
|
2175
2574
|
function getProviderPermissions(projectPath, provider) {
|
|
@@ -2180,10 +2579,10 @@ function getProviderPermissions(projectPath, provider) {
|
|
|
2180
2579
|
if (provider === "copilot") {
|
|
2181
2580
|
return permissions;
|
|
2182
2581
|
}
|
|
2183
|
-
const projectSettingsPath =
|
|
2184
|
-
if (
|
|
2582
|
+
const projectSettingsPath = join7(projectPath, ".claude", "settings.local.json");
|
|
2583
|
+
if (existsSync3(projectSettingsPath)) {
|
|
2185
2584
|
const projectResult = Result6.try(() => {
|
|
2186
|
-
const content =
|
|
2585
|
+
const content = readFileSync3(projectSettingsPath, "utf-8");
|
|
2187
2586
|
return JSON.parse(content);
|
|
2188
2587
|
});
|
|
2189
2588
|
if (projectResult.ok) {
|
|
@@ -2196,10 +2595,10 @@ function getProviderPermissions(projectPath, provider) {
|
|
|
2196
2595
|
}
|
|
2197
2596
|
}
|
|
2198
2597
|
}
|
|
2199
|
-
const userSettingsPath =
|
|
2200
|
-
if (
|
|
2598
|
+
const userSettingsPath = join7(homedir(), ".claude", "settings.json");
|
|
2599
|
+
if (existsSync3(userSettingsPath)) {
|
|
2201
2600
|
const userResult = Result6.try(() => {
|
|
2202
|
-
const content =
|
|
2601
|
+
const content = readFileSync3(userSettingsPath, "utf-8");
|
|
2203
2602
|
return JSON.parse(content);
|
|
2204
2603
|
});
|
|
2205
2604
|
if (userResult.ok) {
|
|
@@ -2413,7 +2812,7 @@ function getContextFileName(sprintId, taskId) {
|
|
|
2413
2812
|
return `.ralphctl-sprint-${sprintId}-task-${taskId}-context.md`;
|
|
2414
2813
|
}
|
|
2415
2814
|
async function writeTaskContextFile(projectPath, taskContent, instructions, sprintId, taskId) {
|
|
2416
|
-
const contextFile =
|
|
2815
|
+
const contextFile = join8(projectPath, getContextFileName(sprintId, taskId));
|
|
2417
2816
|
const warning2 = `<!-- TEMPORARY FILE - DO NOT COMMIT -->
|
|
2418
2817
|
<!-- This file is auto-generated by ralphctl for task execution context -->
|
|
2419
2818
|
<!-- It will be automatically cleaned up after task completion -->
|
|
@@ -2455,7 +2854,7 @@ function runPermissionCheck(ctx, noCommit, provider) {
|
|
|
2455
2854
|
}
|
|
2456
2855
|
|
|
2457
2856
|
// src/ai/lifecycle.ts
|
|
2458
|
-
import { spawnSync as
|
|
2857
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
2459
2858
|
var DEFAULT_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2460
2859
|
function getHookTimeoutMs() {
|
|
2461
2860
|
const envVal = process.env["RALPHCTL_SETUP_TIMEOUT_MS"];
|
|
@@ -2468,7 +2867,7 @@ function getHookTimeoutMs() {
|
|
|
2468
2867
|
function runLifecycleHook(projectPath, script, event, timeoutOverrideMs) {
|
|
2469
2868
|
assertSafeCwd(projectPath);
|
|
2470
2869
|
const timeoutMs = timeoutOverrideMs ?? getHookTimeoutMs();
|
|
2471
|
-
const result =
|
|
2870
|
+
const result = spawnSync3(script, {
|
|
2472
2871
|
cwd: projectPath,
|
|
2473
2872
|
shell: true,
|
|
2474
2873
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2481,6 +2880,7 @@ function runLifecycleHook(projectPath, script, event, timeoutOverrideMs) {
|
|
|
2481
2880
|
}
|
|
2482
2881
|
|
|
2483
2882
|
// src/ai/evaluator.ts
|
|
2883
|
+
var EVALUATOR_MAX_TURNS = 100;
|
|
2484
2884
|
function getEvaluatorModel(generatorModel, provider) {
|
|
2485
2885
|
if (provider.name !== "claude" || !generatorModel) return null;
|
|
2486
2886
|
const modelLower = generatorModel.toLowerCase();
|
|
@@ -2512,13 +2912,16 @@ function parseDimensionScores(output) {
|
|
|
2512
2912
|
function parseEvaluationResult(output) {
|
|
2513
2913
|
const dimensions = parseDimensionScores(output);
|
|
2514
2914
|
if (output.includes("<evaluation-passed>")) {
|
|
2515
|
-
return { passed: true, output, dimensions };
|
|
2915
|
+
return { passed: true, status: "passed", output, dimensions };
|
|
2516
2916
|
}
|
|
2517
2917
|
const failedMatch = /<evaluation-failed>([\s\S]*?)<\/evaluation-failed>/.exec(output);
|
|
2518
2918
|
if (failedMatch) {
|
|
2519
|
-
return { passed: false, output: failedMatch[1]?.trim() ?? output, dimensions };
|
|
2919
|
+
return { passed: false, status: "failed", output: failedMatch[1]?.trim() ?? output, dimensions };
|
|
2920
|
+
}
|
|
2921
|
+
if (dimensions.length > 0) {
|
|
2922
|
+
return { passed: false, status: "failed", output, dimensions };
|
|
2520
2923
|
}
|
|
2521
|
-
return { passed: false, output, dimensions };
|
|
2924
|
+
return { passed: false, status: "malformed", output, dimensions };
|
|
2522
2925
|
}
|
|
2523
2926
|
function buildEvaluatorContext(task, checkScript) {
|
|
2524
2927
|
const checkScriptSection = checkScript ? `## Check Script (Computational Gate)
|
|
@@ -2530,31 +2933,41 @@ ${checkScript}
|
|
|
2530
2933
|
\`\`\`
|
|
2531
2934
|
|
|
2532
2935
|
If this script fails, the implementation fails regardless of code quality. Record the full output.` : null;
|
|
2936
|
+
const projectToolingSection = buildProjectToolingSection(task.projectPath);
|
|
2533
2937
|
return {
|
|
2534
2938
|
taskName: task.name,
|
|
2535
2939
|
taskDescription: task.description ?? "",
|
|
2536
2940
|
taskSteps: task.steps,
|
|
2537
2941
|
verificationCriteria: task.verificationCriteria,
|
|
2538
2942
|
projectPath: task.projectPath,
|
|
2539
|
-
checkScriptSection
|
|
2943
|
+
checkScriptSection,
|
|
2944
|
+
projectToolingSection
|
|
2540
2945
|
};
|
|
2541
2946
|
}
|
|
2542
|
-
async function runEvaluation(task, generatorModel, checkScript, sprintId, provider) {
|
|
2947
|
+
async function runEvaluation(task, generatorModel, checkScript, sprintId, provider, options) {
|
|
2543
2948
|
const p = provider ?? await getActiveProvider();
|
|
2544
2949
|
const evaluatorModel = getEvaluatorModel(generatorModel, p);
|
|
2545
2950
|
const sprintDir = getSprintDir(sprintId);
|
|
2546
2951
|
const ctx = buildEvaluatorContext(task, checkScript);
|
|
2547
2952
|
const prompt = buildEvaluatorPrompt(ctx);
|
|
2548
2953
|
const providerArgs = ["--add-dir", sprintDir];
|
|
2549
|
-
if (
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2954
|
+
if (p.name === "claude") {
|
|
2955
|
+
if (evaluatorModel) {
|
|
2956
|
+
providerArgs.push("--model", evaluatorModel);
|
|
2957
|
+
}
|
|
2958
|
+
providerArgs.push("--max-turns", String(EVALUATOR_MAX_TURNS));
|
|
2959
|
+
}
|
|
2960
|
+
await options?.coordinator?.waitIfPaused();
|
|
2961
|
+
const result = await spawnWithRetry(
|
|
2962
|
+
{
|
|
2963
|
+
cwd: task.projectPath,
|
|
2964
|
+
args: providerArgs,
|
|
2965
|
+
prompt,
|
|
2966
|
+
env: p.getSpawnEnv()
|
|
2967
|
+
},
|
|
2968
|
+
{ maxRetries: options?.maxRetries },
|
|
2969
|
+
p
|
|
2970
|
+
);
|
|
2558
2971
|
return parseEvaluationResult(result.stdout);
|
|
2559
2972
|
}
|
|
2560
2973
|
|
|
@@ -2701,6 +3114,31 @@ async function executeTask(ctx, options, sprintId, resumeSessionId, provider, ch
|
|
|
2701
3114
|
return { ...parsed, sessionId: spawnResult.sessionId, model: spawnResult.model };
|
|
2702
3115
|
}
|
|
2703
3116
|
var MAX_EVAL_OUTPUT = 2e3;
|
|
3117
|
+
var EVAL_SPAWN_FAILURE_PREFIX = "Evaluator spawn failed:";
|
|
3118
|
+
function isEvalSpawnFailure(output) {
|
|
3119
|
+
return output.startsWith(EVAL_SPAWN_FAILURE_PREFIX);
|
|
3120
|
+
}
|
|
3121
|
+
async function runEvaluationSafely(task, generatorModel, checkScript, sprintId, provider, options, coordinator) {
|
|
3122
|
+
const evalR = await wrapAsync(
|
|
3123
|
+
() => runEvaluation(task, generatorModel, checkScript, sprintId, provider, {
|
|
3124
|
+
coordinator,
|
|
3125
|
+
maxRetries: options.maxRetries
|
|
3126
|
+
}),
|
|
3127
|
+
ensureError
|
|
3128
|
+
);
|
|
3129
|
+
if (evalR.ok) return evalR.value;
|
|
3130
|
+
const err = evalR.error;
|
|
3131
|
+
if (err instanceof SpawnError && err.rateLimited && coordinator) {
|
|
3132
|
+
coordinator.pause(err.retryAfterMs ?? 6e4);
|
|
3133
|
+
}
|
|
3134
|
+
console.log(warning(`Evaluator spawn failed for ${task.name}: ${err.message} \u2014 marking malformed`));
|
|
3135
|
+
return {
|
|
3136
|
+
passed: false,
|
|
3137
|
+
status: "malformed",
|
|
3138
|
+
output: `${EVAL_SPAWN_FAILURE_PREFIX} ${err.message}`,
|
|
3139
|
+
dimensions: []
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
2704
3142
|
async function runEvaluationLoop(params) {
|
|
2705
3143
|
const {
|
|
2706
3144
|
task,
|
|
@@ -2711,30 +3149,37 @@ async function runEvaluationLoop(params) {
|
|
|
2711
3149
|
options,
|
|
2712
3150
|
evalIterations,
|
|
2713
3151
|
checkTimeout,
|
|
2714
|
-
useSpinner = false
|
|
3152
|
+
useSpinner = false,
|
|
3153
|
+
coordinator
|
|
2715
3154
|
} = params;
|
|
2716
3155
|
const evalCheckScript = getEffectiveCheckScript(project, task.projectPath);
|
|
2717
3156
|
const sprintDir = getSprintDir(sprintId);
|
|
2718
|
-
let evalResult = await
|
|
3157
|
+
let evalResult = await runEvaluationSafely(
|
|
3158
|
+
task,
|
|
3159
|
+
result.model,
|
|
3160
|
+
evalCheckScript,
|
|
3161
|
+
sprintId,
|
|
3162
|
+
provider,
|
|
3163
|
+
options,
|
|
3164
|
+
coordinator
|
|
3165
|
+
);
|
|
3166
|
+
let evaluationFile = await tryWriteEvaluationEntry(sprintId, task, 1, evalResult);
|
|
2719
3167
|
let currentSessionId = result.sessionId;
|
|
2720
3168
|
let currentModel = result.model;
|
|
2721
|
-
for (let i = 0; i < evalIterations && !evalResult.passed; i++) {
|
|
2722
|
-
console.log(warning(`Evaluation failed for ${task.name}
|
|
3169
|
+
for (let i = 0; i < evalIterations && !evalResult.passed && evalResult.status !== "malformed"; i++) {
|
|
3170
|
+
console.log(warning(`Evaluation failed for ${task.name} \u2014 fix attempt ${String(i + 1)}/${String(evalIterations)}`));
|
|
2723
3171
|
console.log(muted(evalResult.output.slice(0, 500)));
|
|
3172
|
+
const headBefore = getHeadSha(task.projectPath);
|
|
3173
|
+
const resumePrompt = buildEvaluationResumePrompt({
|
|
3174
|
+
critique: evalResult.output,
|
|
3175
|
+
needsCommit: !options.noCommit
|
|
3176
|
+
});
|
|
2724
3177
|
const resumeSpinner = useSpinner ? createSpinner(`Fixing evaluation issues: ${task.name}`).start() : null;
|
|
2725
3178
|
const resumeResult = await spawnWithRetry(
|
|
2726
3179
|
{
|
|
2727
3180
|
cwd: task.projectPath,
|
|
2728
3181
|
args: ["--add-dir", sprintDir, ...buildProviderArgs(options, provider)],
|
|
2729
|
-
prompt:
|
|
2730
|
-
|
|
2731
|
-
${evalResult.output}
|
|
2732
|
-
|
|
2733
|
-
Review the critique carefully. Fix each identified issue in the code, then:
|
|
2734
|
-
1. Re-run verification commands to confirm the fix
|
|
2735
|
-
${options.noCommit ? "" : "2. Commit the fix with a descriptive message\n"}${options.noCommit ? "2" : "3"}. Signal completion with <task-verified> and <task-complete>
|
|
2736
|
-
|
|
2737
|
-
If the critique is about something outside your task scope, fix only what is within scope and signal completion.`,
|
|
3182
|
+
prompt: resumePrompt,
|
|
2738
3183
|
resumeSessionId: currentSessionId ?? void 0,
|
|
2739
3184
|
env: provider.getSpawnEnv()
|
|
2740
3185
|
},
|
|
@@ -2753,35 +3198,84 @@ If the critique is about something outside your task scope, fix only what is wit
|
|
|
2753
3198
|
if (resumeResult.model) currentModel = resumeResult.model;
|
|
2754
3199
|
const fixResult = parseExecutionResult(resumeResult.stdout);
|
|
2755
3200
|
if (!fixResult.success) {
|
|
2756
|
-
|
|
3201
|
+
const reason = `Generator could not fix issues after feedback (no <task-complete> signal)`;
|
|
3202
|
+
console.log(warning(`${reason}: ${task.name}`));
|
|
3203
|
+
const stubPath = await tryWriteEvaluationStub(sprintId, task, i + 2, reason);
|
|
3204
|
+
if (stubPath) evaluationFile = stubPath;
|
|
3205
|
+
break;
|
|
3206
|
+
}
|
|
3207
|
+
const headAfter = getHeadSha(task.projectPath);
|
|
3208
|
+
const dirtyR = Result8.try(() => hasUncommittedChanges(task.projectPath));
|
|
3209
|
+
const dirty = dirtyR.ok ? dirtyR.value : false;
|
|
3210
|
+
if (headBefore !== null && headAfter === headBefore && !dirty) {
|
|
3211
|
+
const reason = "Generator no-op (HEAD unchanged, no uncommitted changes)";
|
|
3212
|
+
console.log(warning(`${reason}: ${task.name}`));
|
|
3213
|
+
const stubPath = await tryWriteEvaluationStub(sprintId, task, i + 2, reason);
|
|
3214
|
+
if (stubPath) evaluationFile = stubPath;
|
|
2757
3215
|
break;
|
|
2758
3216
|
}
|
|
2759
3217
|
const recheckScript = getEffectiveCheckScript(project, task.projectPath);
|
|
2760
3218
|
if (recheckScript) {
|
|
2761
3219
|
const recheckResult = runLifecycleHook(task.projectPath, recheckScript, "taskComplete", checkTimeout);
|
|
2762
3220
|
if (!recheckResult.passed) {
|
|
3221
|
+
const reason = `Post-task check failed after generator fix: ${recheckResult.output.slice(0, 200)}`;
|
|
2763
3222
|
console.log(warning(`Post-task check failed after generator fix: ${task.name}`));
|
|
3223
|
+
const stubPath = await tryWriteEvaluationStub(sprintId, task, i + 2, reason);
|
|
3224
|
+
if (stubPath) evaluationFile = stubPath;
|
|
2764
3225
|
break;
|
|
2765
3226
|
}
|
|
2766
3227
|
}
|
|
2767
|
-
evalResult = await
|
|
3228
|
+
evalResult = await runEvaluationSafely(
|
|
3229
|
+
task,
|
|
3230
|
+
currentModel,
|
|
3231
|
+
evalCheckScript,
|
|
3232
|
+
sprintId,
|
|
3233
|
+
provider,
|
|
3234
|
+
options,
|
|
3235
|
+
coordinator
|
|
3236
|
+
);
|
|
3237
|
+
const entryPath = await tryWriteEvaluationEntry(sprintId, task, i + 2, evalResult);
|
|
3238
|
+
if (entryPath) evaluationFile = entryPath;
|
|
2768
3239
|
}
|
|
2769
3240
|
await updateTask(
|
|
2770
3241
|
task.id,
|
|
2771
3242
|
{
|
|
2772
3243
|
evaluated: true,
|
|
2773
|
-
|
|
3244
|
+
evaluationStatus: evalResult.status,
|
|
3245
|
+
evaluationOutput: evalResult.output.slice(0, MAX_EVAL_OUTPUT),
|
|
3246
|
+
...evaluationFile ? { evaluationFile } : {}
|
|
2774
3247
|
},
|
|
2775
3248
|
sprintId
|
|
2776
3249
|
);
|
|
2777
|
-
if (
|
|
3250
|
+
if (evalResult.status === "malformed") {
|
|
3251
|
+
const cause = isEvalSpawnFailure(evalResult.output) ? evalResult.output : "no signal, no dimensions";
|
|
3252
|
+
console.log(warning(`Evaluator output was malformed for ${task.name} (${cause}) \u2014 marking done`));
|
|
3253
|
+
} else if (!evalResult.passed) {
|
|
2778
3254
|
console.log(
|
|
2779
|
-
warning(`Evaluation did not pass after ${String(evalIterations)}
|
|
3255
|
+
warning(`Evaluation did not pass after ${String(evalIterations)} fix attempt(s) \u2014 marking done: ${task.name}`)
|
|
2780
3256
|
);
|
|
2781
3257
|
} else {
|
|
2782
3258
|
console.log(success(`Evaluation passed: ${task.name}`));
|
|
2783
3259
|
}
|
|
2784
3260
|
}
|
|
3261
|
+
async function tryWriteEvaluationEntry(sprintId, task, iteration, evalResult) {
|
|
3262
|
+
let body;
|
|
3263
|
+
if (evalResult.status === "malformed") {
|
|
3264
|
+
body = isEvalSpawnFailure(evalResult.output) ? evalResult.output : "_(evaluator output had no parseable signal \u2014 see executor stdout)_";
|
|
3265
|
+
} else {
|
|
3266
|
+
body = evalResult.output;
|
|
3267
|
+
}
|
|
3268
|
+
return tryWriteEvaluationRaw(sprintId, task, iteration, evalResult.status, body);
|
|
3269
|
+
}
|
|
3270
|
+
async function tryWriteEvaluationStub(sprintId, task, iteration, reason) {
|
|
3271
|
+
return tryWriteEvaluationRaw(sprintId, task, iteration, "failed", `_(no re-evaluation: ${reason})_`);
|
|
3272
|
+
}
|
|
3273
|
+
async function tryWriteEvaluationRaw(sprintId, task, iteration, status, body) {
|
|
3274
|
+
const writeR = await wrapAsync(() => writeEvaluation(sprintId, task.id, iteration, status, body), ensureError);
|
|
3275
|
+
if (writeR.ok) return writeR.value;
|
|
3276
|
+
console.log(warning(`Could not persist evaluation sidecar for ${task.name}: ${writeR.error.message}`));
|
|
3277
|
+
return null;
|
|
3278
|
+
}
|
|
2785
3279
|
async function areAllRemainingBlocked(sprintId) {
|
|
2786
3280
|
const remaining = await getRemainingTasks(sprintId);
|
|
2787
3281
|
if (remaining.length === 0) return false;
|
|
@@ -2926,9 +3420,10 @@ Starting ${label} in ${task.projectPath} (session)...
|
|
|
2926
3420
|
console.log(success("Verification: passed"));
|
|
2927
3421
|
}
|
|
2928
3422
|
const checkScript = getEffectiveCheckScript(project, task.projectPath);
|
|
3423
|
+
const sequentialRepo = project?.repositories.find((r) => r.path === task.projectPath);
|
|
2929
3424
|
if (checkScript) {
|
|
2930
3425
|
console.log(muted(`Running post-task check: ${checkScript}`));
|
|
2931
|
-
const hookResult = runLifecycleHook(task.projectPath, checkScript, "taskComplete");
|
|
3426
|
+
const hookResult = runLifecycleHook(task.projectPath, checkScript, "taskComplete", sequentialRepo?.checkTimeout);
|
|
2932
3427
|
if (!hookResult.passed) {
|
|
2933
3428
|
console.log(warning(`
|
|
2934
3429
|
Post-task check failed for: ${task.name}`));
|
|
@@ -2956,6 +3451,7 @@ Post-task check failed for: ${task.name}`));
|
|
|
2956
3451
|
provider,
|
|
2957
3452
|
options,
|
|
2958
3453
|
evalIterations,
|
|
3454
|
+
checkTimeout: sequentialRepo?.checkTimeout,
|
|
2959
3455
|
useSpinner: true
|
|
2960
3456
|
});
|
|
2961
3457
|
}
|
|
@@ -3289,7 +3785,8 @@ Post-task check failed for: ${settled.task.name}`));
|
|
|
3289
3785
|
provider,
|
|
3290
3786
|
options,
|
|
3291
3787
|
evalIterations,
|
|
3292
|
-
checkTimeout: taskRepo?.checkTimeout
|
|
3788
|
+
checkTimeout: taskRepo?.checkTimeout,
|
|
3789
|
+
coordinator
|
|
3293
3790
|
});
|
|
3294
3791
|
}
|
|
3295
3792
|
await updateTaskStatus(settled.task.id, "done", sprintId);
|
|
@@ -3368,125 +3865,6 @@ Waiting for ${String(running.size)} remaining task(s)...`));
|
|
|
3368
3865
|
};
|
|
3369
3866
|
}
|
|
3370
3867
|
|
|
3371
|
-
// src/utils/git.ts
|
|
3372
|
-
import { spawnSync as spawnSync3 } from "child_process";
|
|
3373
|
-
var BRANCH_NAME_RE = /^[a-zA-Z0-9/_.-]+$/;
|
|
3374
|
-
var BRANCH_NAME_INVALID_PATTERNS = [/\.\./, /\.$/, /\/$/, /\.lock$/, /^-/, /\/\//];
|
|
3375
|
-
function isValidBranchName(name) {
|
|
3376
|
-
if (!name || name.length > 250) return false;
|
|
3377
|
-
if (!BRANCH_NAME_RE.test(name)) return false;
|
|
3378
|
-
for (const pattern of BRANCH_NAME_INVALID_PATTERNS) {
|
|
3379
|
-
if (pattern.test(name)) return false;
|
|
3380
|
-
}
|
|
3381
|
-
return true;
|
|
3382
|
-
}
|
|
3383
|
-
function getCurrentBranch(cwd) {
|
|
3384
|
-
assertSafeCwd(cwd);
|
|
3385
|
-
const result = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
3386
|
-
cwd,
|
|
3387
|
-
encoding: "utf-8",
|
|
3388
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3389
|
-
});
|
|
3390
|
-
if (result.status !== 0) {
|
|
3391
|
-
throw new Error(`Failed to get current branch in ${cwd}: ${result.stderr.trim()}`);
|
|
3392
|
-
}
|
|
3393
|
-
return result.stdout.trim();
|
|
3394
|
-
}
|
|
3395
|
-
function branchExists(cwd, name) {
|
|
3396
|
-
assertSafeCwd(cwd);
|
|
3397
|
-
if (!isValidBranchName(name)) {
|
|
3398
|
-
throw new Error(`Invalid branch name: ${name}`);
|
|
3399
|
-
}
|
|
3400
|
-
const result = spawnSync3("git", ["show-ref", "--verify", `refs/heads/${name}`], {
|
|
3401
|
-
cwd,
|
|
3402
|
-
encoding: "utf-8",
|
|
3403
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3404
|
-
});
|
|
3405
|
-
return result.status === 0;
|
|
3406
|
-
}
|
|
3407
|
-
function createAndCheckoutBranch(cwd, name) {
|
|
3408
|
-
assertSafeCwd(cwd);
|
|
3409
|
-
if (!isValidBranchName(name)) {
|
|
3410
|
-
throw new Error(`Invalid branch name: ${name}`);
|
|
3411
|
-
}
|
|
3412
|
-
const current = getCurrentBranch(cwd);
|
|
3413
|
-
if (current === name) {
|
|
3414
|
-
return;
|
|
3415
|
-
}
|
|
3416
|
-
if (branchExists(cwd, name)) {
|
|
3417
|
-
const result = spawnSync3("git", ["checkout", name], {
|
|
3418
|
-
cwd,
|
|
3419
|
-
encoding: "utf-8",
|
|
3420
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3421
|
-
});
|
|
3422
|
-
if (result.status !== 0) {
|
|
3423
|
-
throw new Error(`Failed to checkout branch '${name}' in ${cwd}: ${result.stderr.trim()}`);
|
|
3424
|
-
}
|
|
3425
|
-
} else {
|
|
3426
|
-
const result = spawnSync3("git", ["checkout", "-b", name], {
|
|
3427
|
-
cwd,
|
|
3428
|
-
encoding: "utf-8",
|
|
3429
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3430
|
-
});
|
|
3431
|
-
if (result.status !== 0) {
|
|
3432
|
-
throw new Error(`Failed to create branch '${name}' in ${cwd}: ${result.stderr.trim()}`);
|
|
3433
|
-
}
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
|
-
function verifyCurrentBranch(cwd, expected) {
|
|
3437
|
-
const current = getCurrentBranch(cwd);
|
|
3438
|
-
return current === expected;
|
|
3439
|
-
}
|
|
3440
|
-
function getDefaultBranch(cwd) {
|
|
3441
|
-
assertSafeCwd(cwd);
|
|
3442
|
-
const result = spawnSync3("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
3443
|
-
cwd,
|
|
3444
|
-
encoding: "utf-8",
|
|
3445
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3446
|
-
});
|
|
3447
|
-
if (result.status === 0) {
|
|
3448
|
-
const ref = result.stdout.trim();
|
|
3449
|
-
const parts = ref.split("/");
|
|
3450
|
-
return parts[parts.length - 1] ?? "main";
|
|
3451
|
-
}
|
|
3452
|
-
const stderr = result.stderr.trim();
|
|
3453
|
-
if (stderr.includes("is not a symbolic ref") || stderr.includes("No such ref")) {
|
|
3454
|
-
if (branchExists(cwd, "main")) return "main";
|
|
3455
|
-
if (branchExists(cwd, "master")) return "master";
|
|
3456
|
-
return "main";
|
|
3457
|
-
}
|
|
3458
|
-
throw new Error(`Failed to detect default branch in ${cwd}: ${stderr}`);
|
|
3459
|
-
}
|
|
3460
|
-
function hasUncommittedChanges(cwd) {
|
|
3461
|
-
assertSafeCwd(cwd);
|
|
3462
|
-
const result = spawnSync3("git", ["status", "--porcelain"], {
|
|
3463
|
-
cwd,
|
|
3464
|
-
encoding: "utf-8",
|
|
3465
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3466
|
-
});
|
|
3467
|
-
if (result.status !== 0) {
|
|
3468
|
-
throw new Error(`Failed to check git status in ${cwd}: ${result.stderr.trim()}`);
|
|
3469
|
-
}
|
|
3470
|
-
return result.stdout.trim().length > 0;
|
|
3471
|
-
}
|
|
3472
|
-
function generateBranchName(sprintId) {
|
|
3473
|
-
return `ralphctl/${sprintId}`;
|
|
3474
|
-
}
|
|
3475
|
-
function isGhAvailable() {
|
|
3476
|
-
const result = spawnSync3("gh", ["--version"], {
|
|
3477
|
-
encoding: "utf-8",
|
|
3478
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3479
|
-
});
|
|
3480
|
-
return result.status === 0;
|
|
3481
|
-
}
|
|
3482
|
-
function isGlabAvailable() {
|
|
3483
|
-
const result = spawnSync3("glab", ["--version"], {
|
|
3484
|
-
encoding: "utf-8",
|
|
3485
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3486
|
-
});
|
|
3487
|
-
return result.status === 0;
|
|
3488
|
-
}
|
|
3489
|
-
|
|
3490
3868
|
// src/ai/runner.ts
|
|
3491
3869
|
async function promptBranchStrategy(sprintId) {
|
|
3492
3870
|
const autoBranch = generateBranchName(sprintId);
|
|
@@ -3536,7 +3914,7 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
|
|
|
3536
3914
|
const uniquePaths = [...new Set(remainingTasks.map((t) => t.projectPath))];
|
|
3537
3915
|
if (uniquePaths.length === 0) return;
|
|
3538
3916
|
for (const projectPath of uniquePaths) {
|
|
3539
|
-
const uncommittedR =
|
|
3917
|
+
const uncommittedR = Result9.try(() => hasUncommittedChanges(projectPath));
|
|
3540
3918
|
if (!uncommittedR.ok) {
|
|
3541
3919
|
log.dim(` Skipping ${projectPath} \u2014 not a git repository`);
|
|
3542
3920
|
continue;
|
|
@@ -3548,7 +3926,7 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
|
|
|
3548
3926
|
}
|
|
3549
3927
|
}
|
|
3550
3928
|
for (const projectPath of uniquePaths) {
|
|
3551
|
-
const branchR =
|
|
3929
|
+
const branchR = Result9.try(() => {
|
|
3552
3930
|
const currentBranch = getCurrentBranch(projectPath);
|
|
3553
3931
|
if (currentBranch === branchName) {
|
|
3554
3932
|
log.dim(` Already on branch '${branchName}' in ${projectPath}`);
|
|
@@ -3569,7 +3947,7 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
|
|
|
3569
3947
|
}
|
|
3570
3948
|
}
|
|
3571
3949
|
function verifySprintBranch(projectPath, expectedBranch) {
|
|
3572
|
-
const r =
|
|
3950
|
+
const r = Result9.try(() => {
|
|
3573
3951
|
if (verifyCurrentBranch(projectPath, expectedBranch)) return true;
|
|
3574
3952
|
log.dim(` Branch mismatch in ${projectPath} \u2014 checking out '${expectedBranch}'`);
|
|
3575
3953
|
createAndCheckoutBranch(projectPath, expectedBranch);
|
|
@@ -3868,7 +4246,7 @@ function parseArgs3(args) {
|
|
|
3868
4246
|
return { sprintId, options };
|
|
3869
4247
|
}
|
|
3870
4248
|
async function sprintStartCommand(args) {
|
|
3871
|
-
const parseR =
|
|
4249
|
+
const parseR = Result10.try(() => parseArgs3(args));
|
|
3872
4250
|
if (!parseR.ok) {
|
|
3873
4251
|
showError(parseR.error.message);
|
|
3874
4252
|
log.newline();
|
|
@@ -3938,6 +4316,7 @@ export {
|
|
|
3938
4316
|
parseRequirementsFile,
|
|
3939
4317
|
runAiSession,
|
|
3940
4318
|
sprintRefineCommand,
|
|
4319
|
+
buildProjectToolingSection,
|
|
3941
4320
|
getTaskImportSchema,
|
|
3942
4321
|
parsePlanningBlocked,
|
|
3943
4322
|
buildHeadlessAiRequest,
|