ralphctl 0.2.3 → 0.2.4
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/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-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-ORVGM6EV.mjs → chunk-U62BX47C.mjs} +446 -169
- package/dist/{chunk-LAERLCL5.mjs → chunk-UBPZHHCD.mjs} +2 -2
- package/dist/cli.mjs +10 -10
- 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/task-evaluation-resume.md +22 -0
- package/dist/prompts/task-evaluation.md +2 -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-HWOH2HPV.mjs} +6 -6
- package/package.json +1 -1
- package/schemas/tasks.schema.json +10 -1
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
addCheckScriptToRepository,
|
|
4
4
|
projectAddCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UBPZHHCD.mjs";
|
|
6
6
|
import "./chunk-7LZ6GOGN.mjs";
|
|
7
7
|
import "./chunk-7TG3EAQ2.mjs";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-EUNAUHC3.mjs";
|
|
9
9
|
import "./chunk-OEUJDSHY.mjs";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-IB6OCKZW.mjs";
|
|
11
11
|
import "./chunk-EDJX7TT6.mjs";
|
|
12
12
|
import "./chunk-QBXHAXHI.mjs";
|
|
13
13
|
export {
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
addSingleTicketInteractive,
|
|
4
4
|
ticketAddCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-742XQ7FL.mjs";
|
|
6
6
|
import "./chunk-7TG3EAQ2.mjs";
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-EUNAUHC3.mjs";
|
|
8
|
+
import "./chunk-JRFOUFD3.mjs";
|
|
9
9
|
import "./chunk-OEUJDSHY.mjs";
|
|
10
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-IB6OCKZW.mjs";
|
|
11
11
|
import "./chunk-EDJX7TT6.mjs";
|
|
12
12
|
import "./chunk-QBXHAXHI.mjs";
|
|
13
13
|
export {
|
|
@@ -6,14 +6,14 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
listProjects,
|
|
8
8
|
projectExists
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EUNAUHC3.mjs";
|
|
10
10
|
import {
|
|
11
11
|
assertSprintStatus,
|
|
12
12
|
generateUuid8,
|
|
13
13
|
getEditor,
|
|
14
14
|
resolveSprintId,
|
|
15
15
|
setEditor
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-JRFOUFD3.mjs";
|
|
17
17
|
import {
|
|
18
18
|
ensureError,
|
|
19
19
|
unwrapOrThrow,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
getSprintFilePath,
|
|
25
25
|
readValidatedJson,
|
|
26
26
|
writeValidatedJson
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-IB6OCKZW.mjs";
|
|
28
28
|
import {
|
|
29
29
|
IOError,
|
|
30
30
|
IssueFetchError,
|
|
@@ -53,6 +53,13 @@ function getTasksFilePath(sprintId) {
|
|
|
53
53
|
function getProgressFilePath(sprintId) {
|
|
54
54
|
return join(getSprintDir(sprintId), "progress.md");
|
|
55
55
|
}
|
|
56
|
+
function getEvaluationsDir(sprintId) {
|
|
57
|
+
return join(getSprintDir(sprintId), "evaluations");
|
|
58
|
+
}
|
|
59
|
+
function getEvaluationFilePath(sprintId, taskId) {
|
|
60
|
+
assertSafeSegment(taskId, "task ID");
|
|
61
|
+
return join(getEvaluationsDir(sprintId), `${taskId}.md`);
|
|
62
|
+
}
|
|
56
63
|
function assertSafeSegment(segment, label) {
|
|
57
64
|
if (!segment || segment.includes("/") || segment.includes("\\") || segment.includes("..") || segment.includes("\0")) {
|
|
58
65
|
throw new Error(`Path traversal detected in ${label}: ${segment}`);
|
|
@@ -203,6 +210,7 @@ import { z } from "zod";
|
|
|
203
210
|
var SprintStatusSchema = z.enum(["draft", "active", "closed"]);
|
|
204
211
|
var TaskStatusSchema = z.enum(["todo", "in_progress", "done"]);
|
|
205
212
|
var RequirementStatusSchema = z.enum(["pending", "approved"]);
|
|
213
|
+
var EvaluationStatusSchema = z.enum(["passed", "failed", "malformed"]);
|
|
206
214
|
var RepositorySchema = z.object({
|
|
207
215
|
name: z.string().min(1),
|
|
208
216
|
// Auto-derived from basename(path)
|
|
@@ -254,8 +262,12 @@ var TaskSchema = z.object({
|
|
|
254
262
|
// Output from verification run
|
|
255
263
|
evaluated: z.boolean().default(false),
|
|
256
264
|
// Whether evaluation passed
|
|
257
|
-
evaluationOutput: z.string().optional()
|
|
258
|
-
//
|
|
265
|
+
evaluationOutput: z.string().optional(),
|
|
266
|
+
// Truncated output from evaluation run (full critique lives in evaluationFile)
|
|
267
|
+
evaluationStatus: EvaluationStatusSchema.optional(),
|
|
268
|
+
// Discriminator: 'passed' | 'failed' | 'malformed'
|
|
269
|
+
evaluationFile: z.string().optional()
|
|
270
|
+
// Sidecar file path containing the full untruncated critique
|
|
259
271
|
});
|
|
260
272
|
var TasksSchema = z.array(TaskSchema);
|
|
261
273
|
var ImportTaskSchema = z.object({
|
|
@@ -309,6 +321,7 @@ export {
|
|
|
309
321
|
getSprintFilePath,
|
|
310
322
|
getTasksFilePath,
|
|
311
323
|
getProgressFilePath,
|
|
324
|
+
getEvaluationFilePath,
|
|
312
325
|
getRefinementDir,
|
|
313
326
|
getPlanningDir,
|
|
314
327
|
getIdeateDir,
|
|
@@ -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,
|
|
@@ -168,7 +170,12 @@ ${ctx.verificationCriteria.map((c) => `- ${c}`).join("\n")}` : "";
|
|
|
168
170
|
const checkSection = ctx.checkScriptSection ? `
|
|
169
171
|
|
|
170
172
|
${ctx.checkScriptSection}` : "";
|
|
171
|
-
return template.replaceAll("{{TASK_NAME}}", ctx.taskName).replace("{{TASK_DESCRIPTION_SECTION}}", descriptionSection).replace("{{TASK_STEPS_SECTION}}", stepsSection).replace("{{VERIFICATION_CRITERIA_SECTION}}", criteriaSection).replace("{{PROJECT_PATH}}", ctx.projectPath).replace("{{CHECK_SCRIPT_SECTION}}", checkSection);
|
|
173
|
+
return template.replaceAll("{{TASK_NAME}}", ctx.taskName).replace("{{TASK_DESCRIPTION_SECTION}}", descriptionSection).replace("{{TASK_STEPS_SECTION}}", stepsSection).replace("{{VERIFICATION_CRITERIA_SECTION}}", criteriaSection).replace("{{PROJECT_PATH}}", ctx.projectPath).replace("{{CHECK_SCRIPT_SECTION}}", checkSection).replace("{{PROJECT_TOOLING_SECTION}}", ctx.projectToolingSection);
|
|
174
|
+
}
|
|
175
|
+
function buildEvaluationResumePrompt(ctx) {
|
|
176
|
+
const template = loadTemplate("task-evaluation-resume");
|
|
177
|
+
const commitInstruction = ctx.needsCommit ? "\n - **Then commit the fix** with a descriptive message before signaling completion." : "";
|
|
178
|
+
return template.replace("{{CRITIQUE}}", ctx.critique).replace("{{COMMIT_INSTRUCTION}}", commitInstruction);
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
// src/utils/requirements-export.ts
|
|
@@ -1163,6 +1170,12 @@ async function updateTask(taskId, updates, sprintId) {
|
|
|
1163
1170
|
if (updates.evaluationOutput !== void 0) {
|
|
1164
1171
|
task.evaluationOutput = updates.evaluationOutput;
|
|
1165
1172
|
}
|
|
1173
|
+
if (updates.evaluationStatus !== void 0) {
|
|
1174
|
+
task.evaluationStatus = updates.evaluationStatus;
|
|
1175
|
+
}
|
|
1176
|
+
if (updates.evaluationFile !== void 0) {
|
|
1177
|
+
task.evaluationFile = updates.evaluationFile;
|
|
1178
|
+
}
|
|
1166
1179
|
await saveTasks(tasks, id);
|
|
1167
1180
|
return task;
|
|
1168
1181
|
});
|
|
@@ -1360,7 +1373,7 @@ async function selectProject(message = "Select project:") {
|
|
|
1360
1373
|
default: true
|
|
1361
1374
|
});
|
|
1362
1375
|
if (create) {
|
|
1363
|
-
const { projectAddCommand } = await import("./add-
|
|
1376
|
+
const { projectAddCommand } = await import("./add-3T225IX5.mjs");
|
|
1364
1377
|
await projectAddCommand({ interactive: true });
|
|
1365
1378
|
const updated = await listProjects();
|
|
1366
1379
|
if (updated.length === 0) return null;
|
|
@@ -1433,7 +1446,7 @@ async function selectSprint(message = "Select sprint:", filter) {
|
|
|
1433
1446
|
default: true
|
|
1434
1447
|
});
|
|
1435
1448
|
if (create) {
|
|
1436
|
-
const { sprintCreateCommand } = await import("./create-
|
|
1449
|
+
const { sprintCreateCommand } = await import("./create-MYGOWO2F.mjs");
|
|
1437
1450
|
await sprintCreateCommand({ interactive: true });
|
|
1438
1451
|
const updated = await listSprints();
|
|
1439
1452
|
const refiltered = filter ? updated.filter((s) => filter.includes(s.status)) : updated;
|
|
@@ -1468,7 +1481,7 @@ async function selectTicket(message = "Select ticket:", filter) {
|
|
|
1468
1481
|
default: true
|
|
1469
1482
|
});
|
|
1470
1483
|
if (create) {
|
|
1471
|
-
const { ticketAddCommand } = await import("./add-
|
|
1484
|
+
const { ticketAddCommand } = await import("./add-6A5432U2.mjs");
|
|
1472
1485
|
await ticketAddCommand({ interactive: true });
|
|
1473
1486
|
const updated = await listTickets();
|
|
1474
1487
|
const refiltered = filter ? updated.filter(filter) : updated;
|
|
@@ -2059,15 +2072,165 @@ async function sprintPlanCommand(args) {
|
|
|
2059
2072
|
}
|
|
2060
2073
|
|
|
2061
2074
|
// src/commands/sprint/start.ts
|
|
2062
|
-
import { Result as
|
|
2075
|
+
import { Result as Result10 } from "typescript-result";
|
|
2063
2076
|
|
|
2064
2077
|
// src/ai/runner.ts
|
|
2065
2078
|
import { confirm as confirm5, input as input2, select as select2 } from "@inquirer/prompts";
|
|
2066
|
-
import { Result as
|
|
2079
|
+
import { Result as Result9 } from "typescript-result";
|
|
2067
2080
|
|
|
2068
2081
|
// src/ai/executor.ts
|
|
2069
2082
|
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
2070
2083
|
import { readFile as readFile4, unlink as unlink2 } from "fs/promises";
|
|
2084
|
+
import { Result as Result8 } from "typescript-result";
|
|
2085
|
+
|
|
2086
|
+
// src/utils/git.ts
|
|
2087
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
2088
|
+
var BRANCH_NAME_RE = /^[a-zA-Z0-9/_.-]+$/;
|
|
2089
|
+
var BRANCH_NAME_INVALID_PATTERNS = [/\.\./, /\.$/, /\/$/, /\.lock$/, /^-/, /\/\//];
|
|
2090
|
+
function isValidBranchName(name) {
|
|
2091
|
+
if (!name || name.length > 250) return false;
|
|
2092
|
+
if (!BRANCH_NAME_RE.test(name)) return false;
|
|
2093
|
+
for (const pattern of BRANCH_NAME_INVALID_PATTERNS) {
|
|
2094
|
+
if (pattern.test(name)) return false;
|
|
2095
|
+
}
|
|
2096
|
+
return true;
|
|
2097
|
+
}
|
|
2098
|
+
function getCurrentBranch(cwd) {
|
|
2099
|
+
assertSafeCwd(cwd);
|
|
2100
|
+
const result = spawnSync2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
2101
|
+
cwd,
|
|
2102
|
+
encoding: "utf-8",
|
|
2103
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2104
|
+
});
|
|
2105
|
+
if (result.status !== 0) {
|
|
2106
|
+
throw new Error(`Failed to get current branch in ${cwd}: ${result.stderr.trim()}`);
|
|
2107
|
+
}
|
|
2108
|
+
return result.stdout.trim();
|
|
2109
|
+
}
|
|
2110
|
+
function branchExists(cwd, name) {
|
|
2111
|
+
assertSafeCwd(cwd);
|
|
2112
|
+
if (!isValidBranchName(name)) {
|
|
2113
|
+
throw new Error(`Invalid branch name: ${name}`);
|
|
2114
|
+
}
|
|
2115
|
+
const result = spawnSync2("git", ["show-ref", "--verify", `refs/heads/${name}`], {
|
|
2116
|
+
cwd,
|
|
2117
|
+
encoding: "utf-8",
|
|
2118
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2119
|
+
});
|
|
2120
|
+
return result.status === 0;
|
|
2121
|
+
}
|
|
2122
|
+
function createAndCheckoutBranch(cwd, name) {
|
|
2123
|
+
assertSafeCwd(cwd);
|
|
2124
|
+
if (!isValidBranchName(name)) {
|
|
2125
|
+
throw new Error(`Invalid branch name: ${name}`);
|
|
2126
|
+
}
|
|
2127
|
+
const current = getCurrentBranch(cwd);
|
|
2128
|
+
if (current === name) {
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
if (branchExists(cwd, name)) {
|
|
2132
|
+
const result = spawnSync2("git", ["checkout", name], {
|
|
2133
|
+
cwd,
|
|
2134
|
+
encoding: "utf-8",
|
|
2135
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2136
|
+
});
|
|
2137
|
+
if (result.status !== 0) {
|
|
2138
|
+
throw new Error(`Failed to checkout branch '${name}' in ${cwd}: ${result.stderr.trim()}`);
|
|
2139
|
+
}
|
|
2140
|
+
} else {
|
|
2141
|
+
const result = spawnSync2("git", ["checkout", "-b", name], {
|
|
2142
|
+
cwd,
|
|
2143
|
+
encoding: "utf-8",
|
|
2144
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2145
|
+
});
|
|
2146
|
+
if (result.status !== 0) {
|
|
2147
|
+
throw new Error(`Failed to create branch '${name}' in ${cwd}: ${result.stderr.trim()}`);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
function verifyCurrentBranch(cwd, expected) {
|
|
2152
|
+
const current = getCurrentBranch(cwd);
|
|
2153
|
+
return current === expected;
|
|
2154
|
+
}
|
|
2155
|
+
function getDefaultBranch(cwd) {
|
|
2156
|
+
assertSafeCwd(cwd);
|
|
2157
|
+
const result = spawnSync2("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
|
|
2158
|
+
cwd,
|
|
2159
|
+
encoding: "utf-8",
|
|
2160
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2161
|
+
});
|
|
2162
|
+
if (result.status === 0) {
|
|
2163
|
+
const ref = result.stdout.trim();
|
|
2164
|
+
const parts = ref.split("/");
|
|
2165
|
+
return parts[parts.length - 1] ?? "main";
|
|
2166
|
+
}
|
|
2167
|
+
const stderr = result.stderr.trim();
|
|
2168
|
+
if (stderr.includes("is not a symbolic ref") || stderr.includes("No such ref")) {
|
|
2169
|
+
if (branchExists(cwd, "main")) return "main";
|
|
2170
|
+
if (branchExists(cwd, "master")) return "master";
|
|
2171
|
+
return "main";
|
|
2172
|
+
}
|
|
2173
|
+
throw new Error(`Failed to detect default branch in ${cwd}: ${stderr}`);
|
|
2174
|
+
}
|
|
2175
|
+
function getHeadSha(cwd) {
|
|
2176
|
+
try {
|
|
2177
|
+
assertSafeCwd(cwd);
|
|
2178
|
+
const result = spawnSync2("git", ["rev-parse", "HEAD"], {
|
|
2179
|
+
cwd,
|
|
2180
|
+
encoding: "utf-8",
|
|
2181
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2182
|
+
});
|
|
2183
|
+
if (result.status !== 0) return null;
|
|
2184
|
+
return result.stdout.trim() || null;
|
|
2185
|
+
} catch {
|
|
2186
|
+
return null;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
function hasUncommittedChanges(cwd) {
|
|
2190
|
+
assertSafeCwd(cwd);
|
|
2191
|
+
const result = spawnSync2("git", ["status", "--porcelain"], {
|
|
2192
|
+
cwd,
|
|
2193
|
+
encoding: "utf-8",
|
|
2194
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2195
|
+
});
|
|
2196
|
+
if (result.status !== 0) {
|
|
2197
|
+
throw new Error(`Failed to check git status in ${cwd}: ${result.stderr.trim()}`);
|
|
2198
|
+
}
|
|
2199
|
+
return result.stdout.trim().length > 0;
|
|
2200
|
+
}
|
|
2201
|
+
function generateBranchName(sprintId) {
|
|
2202
|
+
return `ralphctl/${sprintId}`;
|
|
2203
|
+
}
|
|
2204
|
+
function isGhAvailable() {
|
|
2205
|
+
const result = spawnSync2("gh", ["--version"], {
|
|
2206
|
+
encoding: "utf-8",
|
|
2207
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2208
|
+
});
|
|
2209
|
+
return result.status === 0;
|
|
2210
|
+
}
|
|
2211
|
+
function isGlabAvailable() {
|
|
2212
|
+
const result = spawnSync2("glab", ["--version"], {
|
|
2213
|
+
encoding: "utf-8",
|
|
2214
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2215
|
+
});
|
|
2216
|
+
return result.status === 0;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// src/store/evaluation.ts
|
|
2220
|
+
async function writeEvaluation(sprintId, taskId, iteration, status, body) {
|
|
2221
|
+
const filePath = getEvaluationFilePath(sprintId, taskId);
|
|
2222
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2223
|
+
const header = `## ${timestamp} \u2014 Iteration ${String(iteration)} \u2014 ${status.toUpperCase()}
|
|
2224
|
+
|
|
2225
|
+
`;
|
|
2226
|
+
const entry = `${header}${body.trimEnd()}
|
|
2227
|
+
|
|
2228
|
+
---
|
|
2229
|
+
|
|
2230
|
+
`;
|
|
2231
|
+
unwrapOrThrow(await appendToFile(filePath, entry));
|
|
2232
|
+
return filePath;
|
|
2233
|
+
}
|
|
2071
2234
|
|
|
2072
2235
|
// src/ai/parser.ts
|
|
2073
2236
|
function parseExecutionResult(output) {
|
|
@@ -2455,7 +2618,7 @@ function runPermissionCheck(ctx, noCommit, provider) {
|
|
|
2455
2618
|
}
|
|
2456
2619
|
|
|
2457
2620
|
// src/ai/lifecycle.ts
|
|
2458
|
-
import { spawnSync as
|
|
2621
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
2459
2622
|
var DEFAULT_HOOK_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2460
2623
|
function getHookTimeoutMs() {
|
|
2461
2624
|
const envVal = process.env["RALPHCTL_SETUP_TIMEOUT_MS"];
|
|
@@ -2468,7 +2631,7 @@ function getHookTimeoutMs() {
|
|
|
2468
2631
|
function runLifecycleHook(projectPath, script, event, timeoutOverrideMs) {
|
|
2469
2632
|
assertSafeCwd(projectPath);
|
|
2470
2633
|
const timeoutMs = timeoutOverrideMs ?? getHookTimeoutMs();
|
|
2471
|
-
const result =
|
|
2634
|
+
const result = spawnSync3(script, {
|
|
2472
2635
|
cwd: projectPath,
|
|
2473
2636
|
shell: true,
|
|
2474
2637
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2480,7 +2643,142 @@ function runLifecycleHook(projectPath, script, event, timeoutOverrideMs) {
|
|
|
2480
2643
|
return { passed: result.status === 0, output };
|
|
2481
2644
|
}
|
|
2482
2645
|
|
|
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
|
+
|
|
2483
2780
|
// src/ai/evaluator.ts
|
|
2781
|
+
var EVALUATOR_MAX_TURNS = 100;
|
|
2484
2782
|
function getEvaluatorModel(generatorModel, provider) {
|
|
2485
2783
|
if (provider.name !== "claude" || !generatorModel) return null;
|
|
2486
2784
|
const modelLower = generatorModel.toLowerCase();
|
|
@@ -2512,13 +2810,16 @@ function parseDimensionScores(output) {
|
|
|
2512
2810
|
function parseEvaluationResult(output) {
|
|
2513
2811
|
const dimensions = parseDimensionScores(output);
|
|
2514
2812
|
if (output.includes("<evaluation-passed>")) {
|
|
2515
|
-
return { passed: true, output, dimensions };
|
|
2813
|
+
return { passed: true, status: "passed", output, dimensions };
|
|
2516
2814
|
}
|
|
2517
2815
|
const failedMatch = /<evaluation-failed>([\s\S]*?)<\/evaluation-failed>/.exec(output);
|
|
2518
2816
|
if (failedMatch) {
|
|
2519
|
-
return { passed: false, output: failedMatch[1]?.trim() ?? output, dimensions };
|
|
2817
|
+
return { passed: false, status: "failed", output: failedMatch[1]?.trim() ?? output, dimensions };
|
|
2520
2818
|
}
|
|
2521
|
-
|
|
2819
|
+
if (dimensions.length > 0) {
|
|
2820
|
+
return { passed: false, status: "failed", output, dimensions };
|
|
2821
|
+
}
|
|
2822
|
+
return { passed: false, status: "malformed", output, dimensions };
|
|
2522
2823
|
}
|
|
2523
2824
|
function buildEvaluatorContext(task, checkScript) {
|
|
2524
2825
|
const checkScriptSection = checkScript ? `## Check Script (Computational Gate)
|
|
@@ -2530,31 +2831,42 @@ ${checkScript}
|
|
|
2530
2831
|
\`\`\`
|
|
2531
2832
|
|
|
2532
2833
|
If this script fails, the implementation fails regardless of code quality. Record the full output.` : null;
|
|
2834
|
+
const tooling = detectProjectTooling(task.projectPath);
|
|
2835
|
+
const projectToolingSection = renderProjectToolingSection(tooling);
|
|
2533
2836
|
return {
|
|
2534
2837
|
taskName: task.name,
|
|
2535
2838
|
taskDescription: task.description ?? "",
|
|
2536
2839
|
taskSteps: task.steps,
|
|
2537
2840
|
verificationCriteria: task.verificationCriteria,
|
|
2538
2841
|
projectPath: task.projectPath,
|
|
2539
|
-
checkScriptSection
|
|
2842
|
+
checkScriptSection,
|
|
2843
|
+
projectToolingSection
|
|
2540
2844
|
};
|
|
2541
2845
|
}
|
|
2542
|
-
async function runEvaluation(task, generatorModel, checkScript, sprintId, provider) {
|
|
2846
|
+
async function runEvaluation(task, generatorModel, checkScript, sprintId, provider, options) {
|
|
2543
2847
|
const p = provider ?? await getActiveProvider();
|
|
2544
2848
|
const evaluatorModel = getEvaluatorModel(generatorModel, p);
|
|
2545
2849
|
const sprintDir = getSprintDir(sprintId);
|
|
2546
2850
|
const ctx = buildEvaluatorContext(task, checkScript);
|
|
2547
2851
|
const prompt = buildEvaluatorPrompt(ctx);
|
|
2548
2852
|
const providerArgs = ["--add-dir", sprintDir];
|
|
2549
|
-
if (
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2853
|
+
if (p.name === "claude") {
|
|
2854
|
+
if (evaluatorModel) {
|
|
2855
|
+
providerArgs.push("--model", evaluatorModel);
|
|
2856
|
+
}
|
|
2857
|
+
providerArgs.push("--max-turns", String(EVALUATOR_MAX_TURNS));
|
|
2858
|
+
}
|
|
2859
|
+
await options?.coordinator?.waitIfPaused();
|
|
2860
|
+
const result = await spawnWithRetry(
|
|
2861
|
+
{
|
|
2862
|
+
cwd: task.projectPath,
|
|
2863
|
+
args: providerArgs,
|
|
2864
|
+
prompt,
|
|
2865
|
+
env: p.getSpawnEnv()
|
|
2866
|
+
},
|
|
2867
|
+
{ maxRetries: options?.maxRetries },
|
|
2868
|
+
p
|
|
2869
|
+
);
|
|
2558
2870
|
return parseEvaluationResult(result.stdout);
|
|
2559
2871
|
}
|
|
2560
2872
|
|
|
@@ -2701,6 +3013,31 @@ async function executeTask(ctx, options, sprintId, resumeSessionId, provider, ch
|
|
|
2701
3013
|
return { ...parsed, sessionId: spawnResult.sessionId, model: spawnResult.model };
|
|
2702
3014
|
}
|
|
2703
3015
|
var MAX_EVAL_OUTPUT = 2e3;
|
|
3016
|
+
var EVAL_SPAWN_FAILURE_PREFIX = "Evaluator spawn failed:";
|
|
3017
|
+
function isEvalSpawnFailure(output) {
|
|
3018
|
+
return output.startsWith(EVAL_SPAWN_FAILURE_PREFIX);
|
|
3019
|
+
}
|
|
3020
|
+
async function runEvaluationSafely(task, generatorModel, checkScript, sprintId, provider, options, coordinator) {
|
|
3021
|
+
const evalR = await wrapAsync(
|
|
3022
|
+
() => runEvaluation(task, generatorModel, checkScript, sprintId, provider, {
|
|
3023
|
+
coordinator,
|
|
3024
|
+
maxRetries: options.maxRetries
|
|
3025
|
+
}),
|
|
3026
|
+
ensureError
|
|
3027
|
+
);
|
|
3028
|
+
if (evalR.ok) return evalR.value;
|
|
3029
|
+
const err = evalR.error;
|
|
3030
|
+
if (err instanceof SpawnError && err.rateLimited && coordinator) {
|
|
3031
|
+
coordinator.pause(err.retryAfterMs ?? 6e4);
|
|
3032
|
+
}
|
|
3033
|
+
console.log(warning(`Evaluator spawn failed for ${task.name}: ${err.message} \u2014 marking malformed`));
|
|
3034
|
+
return {
|
|
3035
|
+
passed: false,
|
|
3036
|
+
status: "malformed",
|
|
3037
|
+
output: `${EVAL_SPAWN_FAILURE_PREFIX} ${err.message}`,
|
|
3038
|
+
dimensions: []
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
2704
3041
|
async function runEvaluationLoop(params) {
|
|
2705
3042
|
const {
|
|
2706
3043
|
task,
|
|
@@ -2711,30 +3048,37 @@ async function runEvaluationLoop(params) {
|
|
|
2711
3048
|
options,
|
|
2712
3049
|
evalIterations,
|
|
2713
3050
|
checkTimeout,
|
|
2714
|
-
useSpinner = false
|
|
3051
|
+
useSpinner = false,
|
|
3052
|
+
coordinator
|
|
2715
3053
|
} = params;
|
|
2716
3054
|
const evalCheckScript = getEffectiveCheckScript(project, task.projectPath);
|
|
2717
3055
|
const sprintDir = getSprintDir(sprintId);
|
|
2718
|
-
let evalResult = await
|
|
3056
|
+
let evalResult = await runEvaluationSafely(
|
|
3057
|
+
task,
|
|
3058
|
+
result.model,
|
|
3059
|
+
evalCheckScript,
|
|
3060
|
+
sprintId,
|
|
3061
|
+
provider,
|
|
3062
|
+
options,
|
|
3063
|
+
coordinator
|
|
3064
|
+
);
|
|
3065
|
+
let evaluationFile = await tryWriteEvaluationEntry(sprintId, task, 1, evalResult);
|
|
2719
3066
|
let currentSessionId = result.sessionId;
|
|
2720
3067
|
let currentModel = result.model;
|
|
2721
|
-
for (let i = 0; i < evalIterations && !evalResult.passed; i++) {
|
|
2722
|
-
console.log(warning(`Evaluation failed for ${task.name}
|
|
3068
|
+
for (let i = 0; i < evalIterations && !evalResult.passed && evalResult.status !== "malformed"; i++) {
|
|
3069
|
+
console.log(warning(`Evaluation failed for ${task.name} \u2014 fix attempt ${String(i + 1)}/${String(evalIterations)}`));
|
|
2723
3070
|
console.log(muted(evalResult.output.slice(0, 500)));
|
|
3071
|
+
const headBefore = getHeadSha(task.projectPath);
|
|
3072
|
+
const resumePrompt = buildEvaluationResumePrompt({
|
|
3073
|
+
critique: evalResult.output,
|
|
3074
|
+
needsCommit: !options.noCommit
|
|
3075
|
+
});
|
|
2724
3076
|
const resumeSpinner = useSpinner ? createSpinner(`Fixing evaluation issues: ${task.name}`).start() : null;
|
|
2725
3077
|
const resumeResult = await spawnWithRetry(
|
|
2726
3078
|
{
|
|
2727
3079
|
cwd: task.projectPath,
|
|
2728
3080
|
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.`,
|
|
3081
|
+
prompt: resumePrompt,
|
|
2738
3082
|
resumeSessionId: currentSessionId ?? void 0,
|
|
2739
3083
|
env: provider.getSpawnEnv()
|
|
2740
3084
|
},
|
|
@@ -2753,35 +3097,84 @@ If the critique is about something outside your task scope, fix only what is wit
|
|
|
2753
3097
|
if (resumeResult.model) currentModel = resumeResult.model;
|
|
2754
3098
|
const fixResult = parseExecutionResult(resumeResult.stdout);
|
|
2755
3099
|
if (!fixResult.success) {
|
|
2756
|
-
|
|
3100
|
+
const reason = `Generator could not fix issues after feedback (no <task-complete> signal)`;
|
|
3101
|
+
console.log(warning(`${reason}: ${task.name}`));
|
|
3102
|
+
const stubPath = await tryWriteEvaluationStub(sprintId, task, i + 2, reason);
|
|
3103
|
+
if (stubPath) evaluationFile = stubPath;
|
|
3104
|
+
break;
|
|
3105
|
+
}
|
|
3106
|
+
const headAfter = getHeadSha(task.projectPath);
|
|
3107
|
+
const dirtyR = Result8.try(() => hasUncommittedChanges(task.projectPath));
|
|
3108
|
+
const dirty = dirtyR.ok ? dirtyR.value : false;
|
|
3109
|
+
if (headBefore !== null && headAfter === headBefore && !dirty) {
|
|
3110
|
+
const reason = "Generator no-op (HEAD unchanged, no uncommitted changes)";
|
|
3111
|
+
console.log(warning(`${reason}: ${task.name}`));
|
|
3112
|
+
const stubPath = await tryWriteEvaluationStub(sprintId, task, i + 2, reason);
|
|
3113
|
+
if (stubPath) evaluationFile = stubPath;
|
|
2757
3114
|
break;
|
|
2758
3115
|
}
|
|
2759
3116
|
const recheckScript = getEffectiveCheckScript(project, task.projectPath);
|
|
2760
3117
|
if (recheckScript) {
|
|
2761
3118
|
const recheckResult = runLifecycleHook(task.projectPath, recheckScript, "taskComplete", checkTimeout);
|
|
2762
3119
|
if (!recheckResult.passed) {
|
|
3120
|
+
const reason = `Post-task check failed after generator fix: ${recheckResult.output.slice(0, 200)}`;
|
|
2763
3121
|
console.log(warning(`Post-task check failed after generator fix: ${task.name}`));
|
|
3122
|
+
const stubPath = await tryWriteEvaluationStub(sprintId, task, i + 2, reason);
|
|
3123
|
+
if (stubPath) evaluationFile = stubPath;
|
|
2764
3124
|
break;
|
|
2765
3125
|
}
|
|
2766
3126
|
}
|
|
2767
|
-
evalResult = await
|
|
3127
|
+
evalResult = await runEvaluationSafely(
|
|
3128
|
+
task,
|
|
3129
|
+
currentModel,
|
|
3130
|
+
evalCheckScript,
|
|
3131
|
+
sprintId,
|
|
3132
|
+
provider,
|
|
3133
|
+
options,
|
|
3134
|
+
coordinator
|
|
3135
|
+
);
|
|
3136
|
+
const entryPath = await tryWriteEvaluationEntry(sprintId, task, i + 2, evalResult);
|
|
3137
|
+
if (entryPath) evaluationFile = entryPath;
|
|
2768
3138
|
}
|
|
2769
3139
|
await updateTask(
|
|
2770
3140
|
task.id,
|
|
2771
3141
|
{
|
|
2772
3142
|
evaluated: true,
|
|
2773
|
-
|
|
3143
|
+
evaluationStatus: evalResult.status,
|
|
3144
|
+
evaluationOutput: evalResult.output.slice(0, MAX_EVAL_OUTPUT),
|
|
3145
|
+
...evaluationFile ? { evaluationFile } : {}
|
|
2774
3146
|
},
|
|
2775
3147
|
sprintId
|
|
2776
3148
|
);
|
|
2777
|
-
if (
|
|
3149
|
+
if (evalResult.status === "malformed") {
|
|
3150
|
+
const cause = isEvalSpawnFailure(evalResult.output) ? evalResult.output : "no signal, no dimensions";
|
|
3151
|
+
console.log(warning(`Evaluator output was malformed for ${task.name} (${cause}) \u2014 marking done`));
|
|
3152
|
+
} else if (!evalResult.passed) {
|
|
2778
3153
|
console.log(
|
|
2779
|
-
warning(`Evaluation did not pass after ${String(evalIterations)}
|
|
3154
|
+
warning(`Evaluation did not pass after ${String(evalIterations)} fix attempt(s) \u2014 marking done: ${task.name}`)
|
|
2780
3155
|
);
|
|
2781
3156
|
} else {
|
|
2782
3157
|
console.log(success(`Evaluation passed: ${task.name}`));
|
|
2783
3158
|
}
|
|
2784
3159
|
}
|
|
3160
|
+
async function tryWriteEvaluationEntry(sprintId, task, iteration, evalResult) {
|
|
3161
|
+
let body;
|
|
3162
|
+
if (evalResult.status === "malformed") {
|
|
3163
|
+
body = isEvalSpawnFailure(evalResult.output) ? evalResult.output : "_(evaluator output had no parseable signal \u2014 see executor stdout)_";
|
|
3164
|
+
} else {
|
|
3165
|
+
body = evalResult.output;
|
|
3166
|
+
}
|
|
3167
|
+
return tryWriteEvaluationRaw(sprintId, task, iteration, evalResult.status, body);
|
|
3168
|
+
}
|
|
3169
|
+
async function tryWriteEvaluationStub(sprintId, task, iteration, reason) {
|
|
3170
|
+
return tryWriteEvaluationRaw(sprintId, task, iteration, "failed", `_(no re-evaluation: ${reason})_`);
|
|
3171
|
+
}
|
|
3172
|
+
async function tryWriteEvaluationRaw(sprintId, task, iteration, status, body) {
|
|
3173
|
+
const writeR = await wrapAsync(() => writeEvaluation(sprintId, task.id, iteration, status, body), ensureError);
|
|
3174
|
+
if (writeR.ok) return writeR.value;
|
|
3175
|
+
console.log(warning(`Could not persist evaluation sidecar for ${task.name}: ${writeR.error.message}`));
|
|
3176
|
+
return null;
|
|
3177
|
+
}
|
|
2785
3178
|
async function areAllRemainingBlocked(sprintId) {
|
|
2786
3179
|
const remaining = await getRemainingTasks(sprintId);
|
|
2787
3180
|
if (remaining.length === 0) return false;
|
|
@@ -2926,9 +3319,10 @@ Starting ${label} in ${task.projectPath} (session)...
|
|
|
2926
3319
|
console.log(success("Verification: passed"));
|
|
2927
3320
|
}
|
|
2928
3321
|
const checkScript = getEffectiveCheckScript(project, task.projectPath);
|
|
3322
|
+
const sequentialRepo = project?.repositories.find((r) => r.path === task.projectPath);
|
|
2929
3323
|
if (checkScript) {
|
|
2930
3324
|
console.log(muted(`Running post-task check: ${checkScript}`));
|
|
2931
|
-
const hookResult = runLifecycleHook(task.projectPath, checkScript, "taskComplete");
|
|
3325
|
+
const hookResult = runLifecycleHook(task.projectPath, checkScript, "taskComplete", sequentialRepo?.checkTimeout);
|
|
2932
3326
|
if (!hookResult.passed) {
|
|
2933
3327
|
console.log(warning(`
|
|
2934
3328
|
Post-task check failed for: ${task.name}`));
|
|
@@ -2956,6 +3350,7 @@ Post-task check failed for: ${task.name}`));
|
|
|
2956
3350
|
provider,
|
|
2957
3351
|
options,
|
|
2958
3352
|
evalIterations,
|
|
3353
|
+
checkTimeout: sequentialRepo?.checkTimeout,
|
|
2959
3354
|
useSpinner: true
|
|
2960
3355
|
});
|
|
2961
3356
|
}
|
|
@@ -3289,7 +3684,8 @@ Post-task check failed for: ${settled.task.name}`));
|
|
|
3289
3684
|
provider,
|
|
3290
3685
|
options,
|
|
3291
3686
|
evalIterations,
|
|
3292
|
-
checkTimeout: taskRepo?.checkTimeout
|
|
3687
|
+
checkTimeout: taskRepo?.checkTimeout,
|
|
3688
|
+
coordinator
|
|
3293
3689
|
});
|
|
3294
3690
|
}
|
|
3295
3691
|
await updateTaskStatus(settled.task.id, "done", sprintId);
|
|
@@ -3368,125 +3764,6 @@ Waiting for ${String(running.size)} remaining task(s)...`));
|
|
|
3368
3764
|
};
|
|
3369
3765
|
}
|
|
3370
3766
|
|
|
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
3767
|
// src/ai/runner.ts
|
|
3491
3768
|
async function promptBranchStrategy(sprintId) {
|
|
3492
3769
|
const autoBranch = generateBranchName(sprintId);
|
|
@@ -3536,7 +3813,7 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
|
|
|
3536
3813
|
const uniquePaths = [...new Set(remainingTasks.map((t) => t.projectPath))];
|
|
3537
3814
|
if (uniquePaths.length === 0) return;
|
|
3538
3815
|
for (const projectPath of uniquePaths) {
|
|
3539
|
-
const uncommittedR =
|
|
3816
|
+
const uncommittedR = Result9.try(() => hasUncommittedChanges(projectPath));
|
|
3540
3817
|
if (!uncommittedR.ok) {
|
|
3541
3818
|
log.dim(` Skipping ${projectPath} \u2014 not a git repository`);
|
|
3542
3819
|
continue;
|
|
@@ -3548,7 +3825,7 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
|
|
|
3548
3825
|
}
|
|
3549
3826
|
}
|
|
3550
3827
|
for (const projectPath of uniquePaths) {
|
|
3551
|
-
const branchR =
|
|
3828
|
+
const branchR = Result9.try(() => {
|
|
3552
3829
|
const currentBranch = getCurrentBranch(projectPath);
|
|
3553
3830
|
if (currentBranch === branchName) {
|
|
3554
3831
|
log.dim(` Already on branch '${branchName}' in ${projectPath}`);
|
|
@@ -3569,7 +3846,7 @@ async function ensureSprintBranches(sprintId, sprint, branchName) {
|
|
|
3569
3846
|
}
|
|
3570
3847
|
}
|
|
3571
3848
|
function verifySprintBranch(projectPath, expectedBranch) {
|
|
3572
|
-
const r =
|
|
3849
|
+
const r = Result9.try(() => {
|
|
3573
3850
|
if (verifyCurrentBranch(projectPath, expectedBranch)) return true;
|
|
3574
3851
|
log.dim(` Branch mismatch in ${projectPath} \u2014 checking out '${expectedBranch}'`);
|
|
3575
3852
|
createAndCheckoutBranch(projectPath, expectedBranch);
|
|
@@ -3868,7 +4145,7 @@ function parseArgs3(args) {
|
|
|
3868
4145
|
return { sprintId, options };
|
|
3869
4146
|
}
|
|
3870
4147
|
async function sprintStartCommand(args) {
|
|
3871
|
-
const parseR =
|
|
4148
|
+
const parseR = Result10.try(() => parseArgs3(args));
|
|
3872
4149
|
if (!parseR.ok) {
|
|
3873
4150
|
showError(parseR.error.message);
|
|
3874
4151
|
log.newline();
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-7TG3EAQ2.mjs";
|
|
9
9
|
import {
|
|
10
10
|
createProject
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-EUNAUHC3.mjs";
|
|
12
12
|
import {
|
|
13
13
|
ensureError,
|
|
14
14
|
wrapAsync
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
expandTilde,
|
|
18
18
|
validateProjectPath
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-IB6OCKZW.mjs";
|
|
20
20
|
import {
|
|
21
21
|
IOError,
|
|
22
22
|
ProjectExistsError
|
package/dist/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
addCheckScriptToRepository,
|
|
4
4
|
projectAddCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UBPZHHCD.mjs";
|
|
6
6
|
import {
|
|
7
7
|
addTask,
|
|
8
8
|
areAllTasksDone,
|
|
@@ -52,13 +52,13 @@ import {
|
|
|
52
52
|
sprintStartCommand,
|
|
53
53
|
updateTaskStatus,
|
|
54
54
|
validateImportTasks
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-U62BX47C.mjs";
|
|
56
56
|
import {
|
|
57
57
|
escapableSelect
|
|
58
58
|
} from "./chunk-7LZ6GOGN.mjs";
|
|
59
59
|
import {
|
|
60
60
|
sprintCreateCommand
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-DUU5346E.mjs";
|
|
62
62
|
import {
|
|
63
63
|
addTicket,
|
|
64
64
|
allRequirementsApproved,
|
|
@@ -73,7 +73,7 @@ import {
|
|
|
73
73
|
removeTicket,
|
|
74
74
|
ticketAddCommand,
|
|
75
75
|
updateTicket
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-742XQ7FL.mjs";
|
|
77
77
|
import {
|
|
78
78
|
EXIT_ERROR,
|
|
79
79
|
exitWithCode
|
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
listProjects,
|
|
85
85
|
removeProject,
|
|
86
86
|
removeProjectRepo
|
|
87
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-EUNAUHC3.mjs";
|
|
88
88
|
import {
|
|
89
89
|
DEFAULT_EVALUATION_ITERATIONS,
|
|
90
90
|
assertSprintStatus,
|
|
@@ -107,7 +107,7 @@ import {
|
|
|
107
107
|
setEditor,
|
|
108
108
|
setEvaluationIterations,
|
|
109
109
|
withFileLock
|
|
110
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-JRFOUFD3.mjs";
|
|
111
111
|
import {
|
|
112
112
|
ensureError,
|
|
113
113
|
wrapAsync
|
|
@@ -134,7 +134,7 @@ import {
|
|
|
134
134
|
getTasksFilePath,
|
|
135
135
|
readValidatedJson,
|
|
136
136
|
validateProjectPath
|
|
137
|
-
} from "./chunk-
|
|
137
|
+
} from "./chunk-IB6OCKZW.mjs";
|
|
138
138
|
import {
|
|
139
139
|
DomainError,
|
|
140
140
|
NoCurrentSprintError,
|
|
@@ -3764,7 +3764,7 @@ async function interactiveMode() {
|
|
|
3764
3764
|
continue;
|
|
3765
3765
|
}
|
|
3766
3766
|
if (command === "wizard") {
|
|
3767
|
-
const { runWizard } = await import("./wizard-
|
|
3767
|
+
const { runWizard } = await import("./wizard-HWOH2HPV.mjs");
|
|
3768
3768
|
await runWizard();
|
|
3769
3769
|
continue;
|
|
3770
3770
|
}
|
|
@@ -4323,7 +4323,7 @@ Checks performed:
|
|
|
4323
4323
|
// package.json
|
|
4324
4324
|
var package_default = {
|
|
4325
4325
|
name: "ralphctl",
|
|
4326
|
-
version: "0.2.
|
|
4326
|
+
version: "0.2.4",
|
|
4327
4327
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
4328
4328
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
4329
4329
|
type: "module",
|
|
@@ -4445,7 +4445,7 @@ registerCompletionCommands(program);
|
|
|
4445
4445
|
registerDoctorCommands(program);
|
|
4446
4446
|
async function main() {
|
|
4447
4447
|
if (process.env["COMP_CWORD"] && process.env["COMP_POINT"] && process.env["COMP_LINE"]) {
|
|
4448
|
-
const { handleCompletionRequest } = await import("./handle-
|
|
4448
|
+
const { handleCompletionRequest } = await import("./handle-TA4MYNQJ.mjs");
|
|
4449
4449
|
if (await handleCompletionRequest(program)) return;
|
|
4450
4450
|
}
|
|
4451
4451
|
if (process.argv.length <= 2 || process.argv[2] === "interactive") {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
sprintCreateCommand
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-DUU5346E.mjs";
|
|
5
|
+
import "./chunk-JRFOUFD3.mjs";
|
|
6
6
|
import "./chunk-OEUJDSHY.mjs";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-IB6OCKZW.mjs";
|
|
8
8
|
import "./chunk-EDJX7TT6.mjs";
|
|
9
9
|
import "./chunk-QBXHAXHI.mjs";
|
|
10
10
|
export {
|
|
@@ -7,7 +7,7 @@ async function handleCompletionRequest(program) {
|
|
|
7
7
|
return false;
|
|
8
8
|
}
|
|
9
9
|
const tabtab = (await import("tabtab")).default;
|
|
10
|
-
const { resolveCompletions } = await import("./resolver-
|
|
10
|
+
const { resolveCompletions } = await import("./resolver-RXEY6EJE.mjs");
|
|
11
11
|
const tabEnv = tabtab.parseEnv(env);
|
|
12
12
|
const completions = await resolveCompletions(program, {
|
|
13
13
|
line: tabEnv.line,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
removeProject,
|
|
10
10
|
removeProjectRepo,
|
|
11
11
|
updateProject
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-EUNAUHC3.mjs";
|
|
13
|
+
import "./chunk-IB6OCKZW.mjs";
|
|
14
14
|
import {
|
|
15
15
|
ProjectExistsError,
|
|
16
16
|
ProjectNotFoundError
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Evaluator Feedback — Fix and Re-verify
|
|
2
|
+
|
|
3
|
+
The independent code reviewer found issues with your implementation. Treat this as ground truth — do not argue with
|
|
4
|
+
it. Read the critique carefully, fix each identified issue, then re-verify and signal completion.
|
|
5
|
+
|
|
6
|
+
## Critique
|
|
7
|
+
|
|
8
|
+
{{CRITIQUE}}
|
|
9
|
+
|
|
10
|
+
## What to do now
|
|
11
|
+
|
|
12
|
+
1. **Fix each issue in the critique above.** Reference the file:line locations the reviewer cited. If a citation is
|
|
13
|
+
wrong, find the actually-affected location and fix that.
|
|
14
|
+
2. **Stay in scope.** If the critique calls out something outside your task scope, fix only what is within scope and
|
|
15
|
+
note the rest. Do not expand the task.
|
|
16
|
+
3. **Re-run verification commands.** Run the project's check script (or the equivalent verification commands) and
|
|
17
|
+
confirm they pass.{{COMMIT_INSTRUCTION}}
|
|
18
|
+
4. **Re-output verification results** wrapped in `<task-verified>...</task-verified>`.
|
|
19
|
+
5. **Signal completion** with `<task-complete>` ONLY after all of the above pass.
|
|
20
|
+
|
|
21
|
+
If the critique is unfixable (e.g. it asks for something that contradicts the spec, or requires changes you cannot
|
|
22
|
+
make), signal `<task-blocked>reason</task-blocked>` instead of completing.
|
|
@@ -11,7 +11,7 @@ var dynamicResolvers = {
|
|
|
11
11
|
"--project": async () => {
|
|
12
12
|
const result = await wrapAsync(
|
|
13
13
|
async () => {
|
|
14
|
-
const { listProjects } = await import("./project-
|
|
14
|
+
const { listProjects } = await import("./project-YONEJICR.mjs");
|
|
15
15
|
return listProjects();
|
|
16
16
|
},
|
|
17
17
|
(err) => new IOError("Failed to load projects for completion", err instanceof Error ? err : void 0)
|
|
@@ -45,7 +45,7 @@ var configValueCompletions = {
|
|
|
45
45
|
async function getSprintCompletions() {
|
|
46
46
|
const result = await wrapAsync(
|
|
47
47
|
async () => {
|
|
48
|
-
const { listSprints } = await import("./sprint-
|
|
48
|
+
const { listSprints } = await import("./sprint-FGLWYWKX.mjs");
|
|
49
49
|
return listSprints();
|
|
50
50
|
},
|
|
51
51
|
(err) => new IOError("Failed to load sprints for completion", err instanceof Error ? err : void 0)
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
listSprints,
|
|
13
13
|
resolveSprintId,
|
|
14
14
|
saveSprint
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-JRFOUFD3.mjs";
|
|
16
16
|
import "./chunk-OEUJDSHY.mjs";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-IB6OCKZW.mjs";
|
|
18
18
|
import {
|
|
19
19
|
NoCurrentSprintError,
|
|
20
20
|
SprintNotFoundError,
|
|
@@ -3,25 +3,25 @@ import {
|
|
|
3
3
|
sprintPlanCommand,
|
|
4
4
|
sprintRefineCommand,
|
|
5
5
|
sprintStartCommand
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-U62BX47C.mjs";
|
|
7
7
|
import "./chunk-7LZ6GOGN.mjs";
|
|
8
8
|
import {
|
|
9
9
|
sprintCreateCommand
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DUU5346E.mjs";
|
|
11
11
|
import {
|
|
12
12
|
addSingleTicketInteractive
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-742XQ7FL.mjs";
|
|
14
14
|
import "./chunk-7TG3EAQ2.mjs";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-EUNAUHC3.mjs";
|
|
16
16
|
import {
|
|
17
17
|
getCurrentSprint,
|
|
18
18
|
getSprint
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-JRFOUFD3.mjs";
|
|
20
20
|
import {
|
|
21
21
|
ensureError,
|
|
22
22
|
wrapAsync
|
|
23
23
|
} from "./chunk-OEUJDSHY.mjs";
|
|
24
|
-
import "./chunk-
|
|
24
|
+
import "./chunk-IB6OCKZW.mjs";
|
|
25
25
|
import "./chunk-EDJX7TT6.mjs";
|
|
26
26
|
import {
|
|
27
27
|
colors,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ralphctl",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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",
|
|
@@ -82,7 +82,16 @@
|
|
|
82
82
|
},
|
|
83
83
|
"evaluationOutput": {
|
|
84
84
|
"type": "string",
|
|
85
|
-
"description": "Output from the evaluation run (truncated to 2000 chars)"
|
|
85
|
+
"description": "Output from the evaluation run (truncated to 2000 chars; full critique in evaluationFile)"
|
|
86
|
+
},
|
|
87
|
+
"evaluationStatus": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"enum": ["passed", "failed", "malformed"],
|
|
90
|
+
"description": "Evaluation outcome discriminator. 'malformed' = evaluator output had no parseable signal."
|
|
91
|
+
},
|
|
92
|
+
"evaluationFile": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Sidecar file path containing the full untruncated critique"
|
|
86
95
|
}
|
|
87
96
|
}
|
|
88
97
|
}
|