@kody-ade/kody-engine-lite 0.1.127 → 0.1.129
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/bin/cli.js +524 -631
- package/package.json +1 -2
- package/skills/playwright-cli.md +0 -63
package/dist/bin/cli.js
CHANGED
|
@@ -180,8 +180,8 @@ var init_validators = __esm({
|
|
|
180
180
|
});
|
|
181
181
|
|
|
182
182
|
// src/config.ts
|
|
183
|
-
import * as
|
|
184
|
-
import * as
|
|
183
|
+
import * as fs7 from "fs";
|
|
184
|
+
import * as path6 from "path";
|
|
185
185
|
function resolveStageConfig(config, stageName, modelTier) {
|
|
186
186
|
const stageOverride = config.agent.stages?.[stageName];
|
|
187
187
|
if (stageOverride) return stageOverride;
|
|
@@ -225,10 +225,10 @@ function setConfigDir(dir) {
|
|
|
225
225
|
}
|
|
226
226
|
function getProjectConfig() {
|
|
227
227
|
if (_config) return _config;
|
|
228
|
-
const configPath =
|
|
229
|
-
if (
|
|
228
|
+
const configPath = path6.join(_configDir ?? process.cwd(), "kody.config.json");
|
|
229
|
+
if (fs7.existsSync(configPath)) {
|
|
230
230
|
try {
|
|
231
|
-
const result2 = parseJsonSafe(
|
|
231
|
+
const result2 = parseJsonSafe(fs7.readFileSync(configPath, "utf-8"));
|
|
232
232
|
if (!result2.ok) {
|
|
233
233
|
logger.warn(`kody.config.json: ${result2.error} \u2014 using defaults`);
|
|
234
234
|
_config = { ...DEFAULT_CONFIG };
|
|
@@ -302,24 +302,24 @@ var init_config = __esm({
|
|
|
302
302
|
});
|
|
303
303
|
|
|
304
304
|
// src/agent-runner.ts
|
|
305
|
-
import { spawn, execFileSync as
|
|
305
|
+
import { spawn, execFileSync as execFileSync5 } from "child_process";
|
|
306
306
|
function writeStdin(child, prompt) {
|
|
307
|
-
return new Promise((
|
|
307
|
+
return new Promise((resolve5, reject) => {
|
|
308
308
|
if (!child.stdin) {
|
|
309
|
-
|
|
309
|
+
resolve5();
|
|
310
310
|
return;
|
|
311
311
|
}
|
|
312
312
|
child.stdin.write(prompt, (err) => {
|
|
313
313
|
if (err) reject(err);
|
|
314
314
|
else {
|
|
315
315
|
child.stdin.end();
|
|
316
|
-
|
|
316
|
+
resolve5();
|
|
317
317
|
}
|
|
318
318
|
});
|
|
319
319
|
});
|
|
320
320
|
}
|
|
321
321
|
function waitForProcess(child, timeout) {
|
|
322
|
-
return new Promise((
|
|
322
|
+
return new Promise((resolve5) => {
|
|
323
323
|
const stdoutChunks = [];
|
|
324
324
|
const stderrChunks = [];
|
|
325
325
|
child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
@@ -332,7 +332,7 @@ function waitForProcess(child, timeout) {
|
|
|
332
332
|
}, timeout);
|
|
333
333
|
child.on("exit", (code) => {
|
|
334
334
|
clearTimeout(timer);
|
|
335
|
-
|
|
335
|
+
resolve5({
|
|
336
336
|
code,
|
|
337
337
|
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
338
338
|
stderr: Buffer.concat(stderrChunks).toString()
|
|
@@ -340,7 +340,7 @@ function waitForProcess(child, timeout) {
|
|
|
340
340
|
});
|
|
341
341
|
child.on("error", (err) => {
|
|
342
342
|
clearTimeout(timer);
|
|
343
|
-
|
|
343
|
+
resolve5({ code: -1, stdout: "", stderr: err.message });
|
|
344
344
|
});
|
|
345
345
|
});
|
|
346
346
|
}
|
|
@@ -376,7 +376,7 @@ ${errDetail}`
|
|
|
376
376
|
}
|
|
377
377
|
function checkCommand2(command2, args2) {
|
|
378
378
|
try {
|
|
379
|
-
|
|
379
|
+
execFileSync5(command2, args2, { timeout: 1e4, stdio: "pipe" });
|
|
380
380
|
return true;
|
|
381
381
|
} catch {
|
|
382
382
|
return false;
|
|
@@ -521,7 +521,7 @@ var init_mcp_config = __esm({
|
|
|
521
521
|
});
|
|
522
522
|
|
|
523
523
|
// src/github-api.ts
|
|
524
|
-
import { execFileSync as
|
|
524
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
525
525
|
function isGhExecError(err) {
|
|
526
526
|
return typeof err === "object" && err !== null;
|
|
527
527
|
}
|
|
@@ -545,7 +545,7 @@ function ghToken() {
|
|
|
545
545
|
function gh(args2, options) {
|
|
546
546
|
const token = ghToken();
|
|
547
547
|
const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
|
|
548
|
-
return
|
|
548
|
+
return execFileSync6("gh", args2, {
|
|
549
549
|
encoding: "utf-8",
|
|
550
550
|
timeout: API_TIMEOUT_MS,
|
|
551
551
|
cwd: _ghCwd,
|
|
@@ -949,18 +949,18 @@ var init_github_api = __esm({
|
|
|
949
949
|
});
|
|
950
950
|
|
|
951
951
|
// src/cli/task-resolution.ts
|
|
952
|
-
import * as
|
|
953
|
-
import * as
|
|
954
|
-
import { execFileSync as
|
|
952
|
+
import * as fs9 from "fs";
|
|
953
|
+
import * as path8 from "path";
|
|
954
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
955
955
|
function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
956
|
-
const tasksDir =
|
|
957
|
-
if (!
|
|
958
|
-
const allDirs =
|
|
956
|
+
const tasksDir = path8.join(projectDir, ".kody", "tasks");
|
|
957
|
+
if (!fs9.existsSync(tasksDir)) return null;
|
|
958
|
+
const allDirs = fs9.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
959
959
|
const prefix = `${issueNumber}-`;
|
|
960
960
|
const direct = allDirs.find((d) => d.startsWith(prefix));
|
|
961
961
|
if (direct) return direct;
|
|
962
962
|
try {
|
|
963
|
-
const branch =
|
|
963
|
+
const branch = execFileSync7("git", ["branch", "--show-current"], {
|
|
964
964
|
encoding: "utf-8",
|
|
965
965
|
cwd: projectDir,
|
|
966
966
|
timeout: 5e3,
|
|
@@ -999,14 +999,14 @@ function resolveTaskIdFromComments(issueNumber) {
|
|
|
999
999
|
}
|
|
1000
1000
|
}
|
|
1001
1001
|
function findPausedTaskifyForIssue(issueNumber, projectDir) {
|
|
1002
|
-
const tasksDir =
|
|
1003
|
-
if (!
|
|
1004
|
-
const allDirs =
|
|
1002
|
+
const tasksDir = path8.join(projectDir, ".kody", "tasks");
|
|
1003
|
+
if (!fs9.existsSync(tasksDir)) return null;
|
|
1004
|
+
const allDirs = fs9.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
|
|
1005
1005
|
for (const dir of allDirs) {
|
|
1006
|
-
const markerPath =
|
|
1007
|
-
if (!
|
|
1006
|
+
const markerPath = path8.join(tasksDir, dir, "taskify.marker");
|
|
1007
|
+
if (!fs9.existsSync(markerPath)) continue;
|
|
1008
1008
|
try {
|
|
1009
|
-
const marker = JSON.parse(
|
|
1009
|
+
const marker = JSON.parse(fs9.readFileSync(markerPath, "utf-8"));
|
|
1010
1010
|
if (marker.issueNumber === issueNumber) return dir;
|
|
1011
1011
|
} catch {
|
|
1012
1012
|
}
|
|
@@ -1030,10 +1030,10 @@ var init_task_resolution = __esm({
|
|
|
1030
1030
|
});
|
|
1031
1031
|
|
|
1032
1032
|
// src/cli/litellm.ts
|
|
1033
|
-
import * as
|
|
1033
|
+
import * as fs10 from "fs";
|
|
1034
1034
|
import * as os from "os";
|
|
1035
|
-
import * as
|
|
1036
|
-
import { execFileSync as
|
|
1035
|
+
import * as path9 from "path";
|
|
1036
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
1037
1037
|
async function checkLitellmHealth(url) {
|
|
1038
1038
|
try {
|
|
1039
1039
|
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -1132,17 +1132,17 @@ async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
|
1132
1132
|
logger.warn("No provider configured in kody.config.json \u2014 cannot start LiteLLM proxy");
|
|
1133
1133
|
return null;
|
|
1134
1134
|
}
|
|
1135
|
-
const configPath =
|
|
1136
|
-
|
|
1135
|
+
const configPath = path9.join(os.tmpdir(), "kody-litellm-config.yaml");
|
|
1136
|
+
fs10.writeFileSync(configPath, generatedConfig);
|
|
1137
1137
|
const portMatch = url.match(/:(\d+)/);
|
|
1138
1138
|
const port = portMatch ? portMatch[1] : "4000";
|
|
1139
1139
|
let litellmFound = false;
|
|
1140
1140
|
try {
|
|
1141
|
-
|
|
1141
|
+
execFileSync8("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
1142
1142
|
litellmFound = true;
|
|
1143
1143
|
} catch {
|
|
1144
1144
|
try {
|
|
1145
|
-
|
|
1145
|
+
execFileSync8("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
|
|
1146
1146
|
litellmFound = true;
|
|
1147
1147
|
} catch {
|
|
1148
1148
|
}
|
|
@@ -1155,17 +1155,17 @@ async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
|
1155
1155
|
let cmd;
|
|
1156
1156
|
let args2;
|
|
1157
1157
|
try {
|
|
1158
|
-
|
|
1158
|
+
execFileSync8("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
1159
1159
|
cmd = "litellm";
|
|
1160
1160
|
args2 = ["--config", configPath, "--port", port];
|
|
1161
1161
|
} catch {
|
|
1162
1162
|
cmd = "python3";
|
|
1163
1163
|
args2 = ["-m", "litellm", "--config", configPath, "--port", port];
|
|
1164
1164
|
}
|
|
1165
|
-
const dotenvPath =
|
|
1165
|
+
const dotenvPath = path9.join(projectDir, ".env");
|
|
1166
1166
|
const dotenvVars = {};
|
|
1167
|
-
if (
|
|
1168
|
-
for (const rawLine of
|
|
1167
|
+
if (fs10.existsSync(dotenvPath)) {
|
|
1168
|
+
for (const rawLine of fs10.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
1169
1169
|
const line = rawLine.trim();
|
|
1170
1170
|
if (!line || line.startsWith("#")) continue;
|
|
1171
1171
|
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
@@ -1225,8 +1225,8 @@ __export(taskify_command_exports, {
|
|
|
1225
1225
|
taskifyCommand: () => taskifyCommand,
|
|
1226
1226
|
topoSort: () => topoSort
|
|
1227
1227
|
});
|
|
1228
|
-
import * as
|
|
1229
|
-
import * as
|
|
1228
|
+
import * as fs11 from "fs";
|
|
1229
|
+
import * as path10 from "path";
|
|
1230
1230
|
import { fileURLToPath } from "url";
|
|
1231
1231
|
import { execSync } from "child_process";
|
|
1232
1232
|
function topoSort(tasks) {
|
|
@@ -1270,10 +1270,10 @@ function hasFlag(args2, flag) {
|
|
|
1270
1270
|
async function runTaskifyCommand() {
|
|
1271
1271
|
const args2 = process.argv.slice(3);
|
|
1272
1272
|
const cwdArg = getArg(args2, "--cwd") ?? process.cwd();
|
|
1273
|
-
const projectDir =
|
|
1273
|
+
const projectDir = path10.resolve(cwdArg);
|
|
1274
1274
|
const ticketId = getArg(args2, "--ticket") ?? process.env.TICKET_ID;
|
|
1275
1275
|
const prdFileArg = getArg(args2, "--file") ?? process.env.PRD_FILE;
|
|
1276
|
-
const prdFile = prdFileArg ?
|
|
1276
|
+
const prdFile = prdFileArg ? path10.resolve(projectDir, prdFileArg) : void 0;
|
|
1277
1277
|
const issueNumberStr = getArg(args2, "--issue-number") ?? process.env.ISSUE_NUMBER ?? "";
|
|
1278
1278
|
const issueNumber = issueNumberStr ? parseInt(issueNumberStr, 10) : void 0;
|
|
1279
1279
|
const feedback = getArg(args2, "--feedback") ?? process.env.FEEDBACK;
|
|
@@ -1284,7 +1284,7 @@ async function runTaskifyCommand() {
|
|
|
1284
1284
|
logger.error("Usage: kody taskify --ticket <ticket-id> OR kody taskify --file <prd.md> OR kody taskify --issue-number <n>");
|
|
1285
1285
|
process.exit(1);
|
|
1286
1286
|
}
|
|
1287
|
-
if (prdFile && !
|
|
1287
|
+
if (prdFile && !fs11.existsSync(prdFile)) {
|
|
1288
1288
|
logger.error(`File not found: ${prdFile}`);
|
|
1289
1289
|
process.exit(1);
|
|
1290
1290
|
}
|
|
@@ -1325,8 +1325,8 @@ async function runTaskifyCommand() {
|
|
|
1325
1325
|
async function taskifyCommand(opts) {
|
|
1326
1326
|
const { ticketId, prdFile, issueNumber, feedback, local, projectDir, taskId } = opts;
|
|
1327
1327
|
const config = getProjectConfig();
|
|
1328
|
-
const taskDir =
|
|
1329
|
-
|
|
1328
|
+
const taskDir = path10.join(projectDir, ".kody", "tasks", taskId);
|
|
1329
|
+
fs11.mkdirSync(taskDir, { recursive: true });
|
|
1330
1330
|
const mode = prdFile ? "file" : ticketId ? "ticket" : "issue";
|
|
1331
1331
|
logger.info(`[taskify] mode=${mode} source=${ticketId ?? prdFile ?? `issue#${issueNumber}`} issue=${issueNumber ?? "none"} task=${taskId}`);
|
|
1332
1332
|
let mcpConfigJson;
|
|
@@ -1350,7 +1350,7 @@ Add the required MCP server config to \`kody.config.json\` and try again.`
|
|
|
1350
1350
|
}
|
|
1351
1351
|
const sc = resolveStageConfig(config, "taskify", "strong");
|
|
1352
1352
|
const model = sc.model;
|
|
1353
|
-
const fileContent = prdFile ?
|
|
1353
|
+
const fileContent = prdFile ? fs11.readFileSync(prdFile, "utf-8") : void 0;
|
|
1354
1354
|
let issueBody;
|
|
1355
1355
|
if (mode === "issue" && issueNumber) {
|
|
1356
1356
|
const issue = getIssue(issueNumber);
|
|
@@ -1366,10 +1366,10 @@ ${issue.body}`;
|
|
|
1366
1366
|
let projectContext;
|
|
1367
1367
|
{
|
|
1368
1368
|
const parts = [];
|
|
1369
|
-
const memoryPath =
|
|
1370
|
-
if (
|
|
1369
|
+
const memoryPath = path10.join(projectDir, ".kody", "memory.md");
|
|
1370
|
+
if (fs11.existsSync(memoryPath)) {
|
|
1371
1371
|
try {
|
|
1372
|
-
const content =
|
|
1372
|
+
const content = fs11.readFileSync(memoryPath, "utf-8").slice(0, 2e3);
|
|
1373
1373
|
if (content.trim()) parts.push(`### Project Memory
|
|
1374
1374
|
${content}`);
|
|
1375
1375
|
} catch {
|
|
@@ -1388,14 +1388,14 @@ ${lines.join("\n")}
|
|
|
1388
1388
|
}
|
|
1389
1389
|
const prompt = buildPrompt({ ticketId, fileContent, issueBody, taskDir, feedback, projectContext });
|
|
1390
1390
|
if (issueNumber && !local) {
|
|
1391
|
-
const src = mode === "file" ? `file \`${
|
|
1391
|
+
const src = mode === "file" ? `file \`${path10.basename(prdFile)}\`` : mode === "ticket" ? `ticket **${ticketId}**` : `issue #${issueNumber} description`;
|
|
1392
1392
|
const runUrl = process.env.RUN_URL ? ` ([logs](${process.env.RUN_URL}))` : "";
|
|
1393
1393
|
postComment(issueNumber, `\u{1F680} Kody pipeline started: \`${taskId}\`${runUrl}
|
|
1394
1394
|
|
|
1395
1395
|
Kody is decomposing ${src} into tasks...`);
|
|
1396
1396
|
setLifecycleLabel(issueNumber, "planning");
|
|
1397
1397
|
}
|
|
1398
|
-
|
|
1398
|
+
fs11.writeFileSync(path10.join(taskDir, MARKER_FILE), JSON.stringify({ ticketId, prdFile, issueNumber }));
|
|
1399
1399
|
const runner = opts.runner ?? createClaudeCodeRunner();
|
|
1400
1400
|
logger.info(` model=${model} timeout=${TASKIFY_TIMEOUT_MS / 1e3}s`);
|
|
1401
1401
|
const result2 = await runner.run("taskify", prompt, model, TASKIFY_TIMEOUT_MS, taskDir, {
|
|
@@ -1413,8 +1413,8 @@ Kody is decomposing ${src} into tasks...`);
|
|
|
1413
1413
|
}
|
|
1414
1414
|
throw new TaskifyError(errMsg);
|
|
1415
1415
|
}
|
|
1416
|
-
const resultPath =
|
|
1417
|
-
if (!
|
|
1416
|
+
const resultPath = path10.join(taskDir, RESULT_FILE);
|
|
1417
|
+
if (!fs11.existsSync(resultPath)) {
|
|
1418
1418
|
const errMsg = `Claude did not write ${RESULT_FILE}. Output:
|
|
1419
1419
|
|
|
1420
1420
|
${result2.output?.slice(0, 500) ?? "(none)"}`;
|
|
@@ -1428,7 +1428,7 @@ ${errMsg}`);
|
|
|
1428
1428
|
}
|
|
1429
1429
|
let parsed;
|
|
1430
1430
|
try {
|
|
1431
|
-
parsed = JSON.parse(
|
|
1431
|
+
parsed = JSON.parse(fs11.readFileSync(resultPath, "utf-8"));
|
|
1432
1432
|
} catch {
|
|
1433
1433
|
const errMsg = `Could not parse ${RESULT_FILE} as JSON.`;
|
|
1434
1434
|
if (issueNumber && !local) {
|
|
@@ -1437,7 +1437,7 @@ ${errMsg}`);
|
|
|
1437
1437
|
}
|
|
1438
1438
|
throw new TaskifyError(errMsg);
|
|
1439
1439
|
}
|
|
1440
|
-
const sourceLabel = ticketId ?? (prdFile ?
|
|
1440
|
+
const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : issueNumber ? `issue #${issueNumber}` : "spec");
|
|
1441
1441
|
if (parsed.status === "questions") {
|
|
1442
1442
|
handleQuestions(parsed, sourceLabel, issueNumber, local ?? false);
|
|
1443
1443
|
} else if (parsed.status === "ready") {
|
|
@@ -1537,15 +1537,15 @@ function buildPrompt(opts) {
|
|
|
1537
1537
|
const { ticketId, fileContent, issueBody, taskDir, feedback, projectContext } = opts;
|
|
1538
1538
|
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
1539
1539
|
const candidates = [
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1540
|
+
path10.resolve(scriptDir, "..", "prompts", "taskify-ticket.md"),
|
|
1541
|
+
path10.resolve(scriptDir, "..", "..", "prompts", "taskify-ticket.md"),
|
|
1542
|
+
path10.resolve(__dirname, "..", "..", "prompts", "taskify-ticket.md"),
|
|
1543
|
+
path10.resolve(__dirname, "..", "prompts", "taskify-ticket.md")
|
|
1544
1544
|
];
|
|
1545
1545
|
let template = "";
|
|
1546
1546
|
for (const candidate of candidates) {
|
|
1547
|
-
if (
|
|
1548
|
-
template =
|
|
1547
|
+
if (fs11.existsSync(candidate)) {
|
|
1548
|
+
template = fs11.readFileSync(candidate, "utf-8");
|
|
1549
1549
|
break;
|
|
1550
1550
|
}
|
|
1551
1551
|
}
|
|
@@ -1569,13 +1569,13 @@ function buildPrompt(opts) {
|
|
|
1569
1569
|
return template;
|
|
1570
1570
|
}
|
|
1571
1571
|
function isTaskifyRun(taskDir) {
|
|
1572
|
-
return
|
|
1572
|
+
return fs11.existsSync(path10.join(taskDir, MARKER_FILE));
|
|
1573
1573
|
}
|
|
1574
1574
|
function readTaskifyMarker(taskDir) {
|
|
1575
|
-
const markerPath =
|
|
1576
|
-
if (!
|
|
1575
|
+
const markerPath = path10.join(taskDir, MARKER_FILE);
|
|
1576
|
+
if (!fs11.existsSync(markerPath)) return null;
|
|
1577
1577
|
try {
|
|
1578
|
-
return JSON.parse(
|
|
1578
|
+
return JSON.parse(fs11.readFileSync(markerPath, "utf-8"));
|
|
1579
1579
|
} catch {
|
|
1580
1580
|
return null;
|
|
1581
1581
|
}
|
|
@@ -1591,7 +1591,7 @@ var init_taskify_command = __esm({
|
|
|
1591
1591
|
init_logger();
|
|
1592
1592
|
init_task_resolution();
|
|
1593
1593
|
init_litellm();
|
|
1594
|
-
__dirname =
|
|
1594
|
+
__dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
1595
1595
|
TaskifyError = class extends Error {
|
|
1596
1596
|
constructor(message) {
|
|
1597
1597
|
super(message);
|
|
@@ -1607,9 +1607,9 @@ var init_taskify_command = __esm({
|
|
|
1607
1607
|
});
|
|
1608
1608
|
|
|
1609
1609
|
// src/cli/test-model-tests.ts
|
|
1610
|
-
import * as
|
|
1610
|
+
import * as fs12 from "fs";
|
|
1611
1611
|
import * as os2 from "os";
|
|
1612
|
-
import * as
|
|
1612
|
+
import * as path11 from "path";
|
|
1613
1613
|
import * as zlib from "zlib";
|
|
1614
1614
|
import { spawnSync, execSync as execSync2 } from "child_process";
|
|
1615
1615
|
function canRunApiTests(ctx) {
|
|
@@ -1937,8 +1937,8 @@ async function testExtendedThinking(ctx) {
|
|
|
1937
1937
|
async function testToolRead(ctx) {
|
|
1938
1938
|
if (!canRunApiTests(ctx)) {
|
|
1939
1939
|
const t2 = Date.now();
|
|
1940
|
-
const testFile2 =
|
|
1941
|
-
|
|
1940
|
+
const testFile2 = path11.join(os2.tmpdir(), "kody-test-model-read.txt");
|
|
1941
|
+
fs12.writeFileSync(testFile2, "KODY_SECRET_CONTENT_42");
|
|
1942
1942
|
try {
|
|
1943
1943
|
const r = runClaudeTest(ctx, `Read the file ${testFile2} and tell me its exact contents. Reply with ONLY the file contents.`);
|
|
1944
1944
|
const ok = r.stdout.includes("KODY_SECRET_CONTENT_42");
|
|
@@ -1952,12 +1952,12 @@ async function testToolRead(ctx) {
|
|
|
1952
1952
|
{ toolSelection: ok ? 100 : 0 }
|
|
1953
1953
|
);
|
|
1954
1954
|
} finally {
|
|
1955
|
-
|
|
1955
|
+
fs12.rmSync(testFile2, { force: true });
|
|
1956
1956
|
}
|
|
1957
1957
|
}
|
|
1958
1958
|
const t = Date.now();
|
|
1959
|
-
const testFile =
|
|
1960
|
-
|
|
1959
|
+
const testFile = path11.join(os2.tmpdir(), "kody-test-model-read.txt");
|
|
1960
|
+
fs12.writeFileSync(testFile, "KODY_SECRET_CONTENT_42");
|
|
1961
1961
|
try {
|
|
1962
1962
|
const conv = await runToolConversation(
|
|
1963
1963
|
ctx,
|
|
@@ -1986,17 +1986,17 @@ async function testToolRead(ctx) {
|
|
|
1986
1986
|
{ toolSelection: calledRead ? 100 : 0 }
|
|
1987
1987
|
);
|
|
1988
1988
|
} finally {
|
|
1989
|
-
|
|
1989
|
+
fs12.rmSync(testFile, { force: true });
|
|
1990
1990
|
}
|
|
1991
1991
|
}
|
|
1992
1992
|
async function testToolEdit(ctx) {
|
|
1993
1993
|
if (!canRunApiTests(ctx)) {
|
|
1994
1994
|
const t2 = Date.now();
|
|
1995
|
-
const testFile =
|
|
1996
|
-
|
|
1995
|
+
const testFile = path11.join(os2.tmpdir(), "kody-test-model-edit.txt");
|
|
1996
|
+
fs12.writeFileSync(testFile, "hello world");
|
|
1997
1997
|
try {
|
|
1998
1998
|
const r = runClaudeTest(ctx, `Use the Edit tool to replace "hello" with "goodbye" in ${testFile}. Do nothing else.`);
|
|
1999
|
-
const content =
|
|
1999
|
+
const content = fs12.existsSync(testFile) ? fs12.readFileSync(testFile, "utf-8") : "";
|
|
2000
2000
|
const ok = content.includes("goodbye");
|
|
2001
2001
|
return result(
|
|
2002
2002
|
"tool_edit",
|
|
@@ -2008,7 +2008,7 @@ async function testToolEdit(ctx) {
|
|
|
2008
2008
|
{ toolSelection: ok ? 100 : 0 }
|
|
2009
2009
|
);
|
|
2010
2010
|
} finally {
|
|
2011
|
-
|
|
2011
|
+
fs12.rmSync(testFile, { force: true });
|
|
2012
2012
|
}
|
|
2013
2013
|
}
|
|
2014
2014
|
const t = Date.now();
|
|
@@ -2082,8 +2082,8 @@ async function testToolBash(ctx) {
|
|
|
2082
2082
|
async function testImageAttachment(ctx) {
|
|
2083
2083
|
if (!canRunApiTests(ctx)) {
|
|
2084
2084
|
const t2 = Date.now();
|
|
2085
|
-
const tmpPng =
|
|
2086
|
-
|
|
2085
|
+
const tmpPng = path11.join(os2.tmpdir(), "kody-test-image.png");
|
|
2086
|
+
fs12.writeFileSync(tmpPng, createRedPng());
|
|
2087
2087
|
try {
|
|
2088
2088
|
const r = runClaudeTest(ctx, `Read the image file at ${tmpPng} and tell me what color it is. Reply with just the color name.`);
|
|
2089
2089
|
const text2 = r.stdout.toLowerCase();
|
|
@@ -2097,7 +2097,7 @@ async function testImageAttachment(ctx) {
|
|
|
2097
2097
|
ok ? "Image processed correctly via CLI" : `Got: ${text2.slice(0, 80)}`
|
|
2098
2098
|
);
|
|
2099
2099
|
} finally {
|
|
2100
|
-
|
|
2100
|
+
fs12.rmSync(tmpPng, { force: true });
|
|
2101
2101
|
}
|
|
2102
2102
|
}
|
|
2103
2103
|
const t = Date.now();
|
|
@@ -2312,10 +2312,10 @@ async function testReviewStage(ctx) {
|
|
|
2312
2312
|
}
|
|
2313
2313
|
async function testMcpTools(ctx) {
|
|
2314
2314
|
const t = Date.now();
|
|
2315
|
-
const mcpConfig =
|
|
2316
|
-
const testFile =
|
|
2315
|
+
const mcpConfig = path11.join(os2.tmpdir(), `kody-test-mcp-${Date.now()}.json`);
|
|
2316
|
+
const testFile = path11.join(ctx.projectDir, "kody-mcp-compat-test.txt");
|
|
2317
2317
|
try {
|
|
2318
|
-
|
|
2318
|
+
fs12.writeFileSync(mcpConfig, JSON.stringify({
|
|
2319
2319
|
mcpServers: {
|
|
2320
2320
|
filesystem: { command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", ctx.projectDir] }
|
|
2321
2321
|
}
|
|
@@ -2326,8 +2326,8 @@ async function testMcpTools(ctx) {
|
|
|
2326
2326
|
["--mcp-config", mcpConfig],
|
|
2327
2327
|
12e4
|
|
2328
2328
|
);
|
|
2329
|
-
const created =
|
|
2330
|
-
const content = created ?
|
|
2329
|
+
const created = fs12.existsSync(testFile);
|
|
2330
|
+
const content = created ? fs12.readFileSync(testFile, "utf-8").trim() : "";
|
|
2331
2331
|
const correct = content.includes("mcp-ok");
|
|
2332
2332
|
return result(
|
|
2333
2333
|
"mcp_tools",
|
|
@@ -2340,8 +2340,8 @@ async function testMcpTools(ctx) {
|
|
|
2340
2340
|
} catch (err) {
|
|
2341
2341
|
return result("mcp_tools", "advanced", "warn", 0, Date.now() - t, `MCP test error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2342
2342
|
} finally {
|
|
2343
|
-
|
|
2344
|
-
|
|
2343
|
+
fs12.rmSync(mcpConfig, { force: true });
|
|
2344
|
+
fs12.rmSync(testFile, { force: true });
|
|
2345
2345
|
revertChanges(ctx.projectDir);
|
|
2346
2346
|
}
|
|
2347
2347
|
}
|
|
@@ -2522,10 +2522,10 @@ var test_model_command_exports = {};
|
|
|
2522
2522
|
__export(test_model_command_exports, {
|
|
2523
2523
|
runTestModelCommand: () => runTestModelCommand
|
|
2524
2524
|
});
|
|
2525
|
-
import * as
|
|
2525
|
+
import * as fs13 from "fs";
|
|
2526
2526
|
import * as os3 from "os";
|
|
2527
|
-
import * as
|
|
2528
|
-
import { execFileSync as
|
|
2527
|
+
import * as path12 from "path";
|
|
2528
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
2529
2529
|
function parseTestModelArgs() {
|
|
2530
2530
|
const args2 = process.argv.slice(3);
|
|
2531
2531
|
function getArg3(flag) {
|
|
@@ -2598,16 +2598,16 @@ function generateConfig(provider, model, dropParams) {
|
|
|
2598
2598
|
}
|
|
2599
2599
|
async function startProxy(config, url) {
|
|
2600
2600
|
try {
|
|
2601
|
-
|
|
2601
|
+
execFileSync9("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
2602
2602
|
} catch {
|
|
2603
2603
|
try {
|
|
2604
|
-
|
|
2604
|
+
execFileSync9("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
|
|
2605
2605
|
} catch {
|
|
2606
2606
|
logger.error("litellm not installed. Install: pip install 'litellm[proxy]'");
|
|
2607
2607
|
return null;
|
|
2608
2608
|
}
|
|
2609
2609
|
}
|
|
2610
|
-
|
|
2610
|
+
fs13.writeFileSync(CONFIG_PATH, config);
|
|
2611
2611
|
const portMatch = url.match(/:(\d+)/);
|
|
2612
2612
|
const port = portMatch ? portMatch[1] : "4099";
|
|
2613
2613
|
const { spawn: spawn2 } = await import("child_process");
|
|
@@ -2649,7 +2649,7 @@ async function quickApiTest(url, model, apiKey) {
|
|
|
2649
2649
|
}
|
|
2650
2650
|
}
|
|
2651
2651
|
function delay(ms) {
|
|
2652
|
-
return new Promise((
|
|
2652
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2653
2653
|
}
|
|
2654
2654
|
async function runTestModelCommand() {
|
|
2655
2655
|
const opts = parseTestModelArgs();
|
|
@@ -2663,7 +2663,7 @@ async function runTestModelCommand() {
|
|
|
2663
2663
|
proxyProcess.kill();
|
|
2664
2664
|
proxyProcess = null;
|
|
2665
2665
|
}
|
|
2666
|
-
|
|
2666
|
+
fs13.rmSync(CONFIG_PATH, { force: true });
|
|
2667
2667
|
};
|
|
2668
2668
|
process.on("SIGINT", () => {
|
|
2669
2669
|
cleanup();
|
|
@@ -2757,7 +2757,7 @@ var init_test_model_command = __esm({
|
|
|
2757
2757
|
init_test_model_report();
|
|
2758
2758
|
TEST_PORT = 4099;
|
|
2759
2759
|
TEST_URL = `http://localhost:${TEST_PORT}`;
|
|
2760
|
-
CONFIG_PATH =
|
|
2760
|
+
CONFIG_PATH = path12.join(os3.tmpdir(), "kody-test-model-config.yaml");
|
|
2761
2761
|
}
|
|
2762
2762
|
});
|
|
2763
2763
|
|
|
@@ -2768,7 +2768,7 @@ __export(parse_inputs_exports, {
|
|
|
2768
2768
|
runCiParse: () => runCiParse,
|
|
2769
2769
|
writeOutputs: () => writeOutputs
|
|
2770
2770
|
});
|
|
2771
|
-
import * as
|
|
2771
|
+
import * as fs14 from "fs";
|
|
2772
2772
|
function generateTimestamp() {
|
|
2773
2773
|
const now = /* @__PURE__ */ new Date();
|
|
2774
2774
|
const pad2 = (n) => String(n).padStart(2, "0");
|
|
@@ -2917,12 +2917,12 @@ function writeOutputs(result2) {
|
|
|
2917
2917
|
function output(key, value) {
|
|
2918
2918
|
if (outputFile) {
|
|
2919
2919
|
if (value.includes("\n")) {
|
|
2920
|
-
|
|
2920
|
+
fs14.appendFileSync(outputFile, `${key}<<KODY_EOF
|
|
2921
2921
|
${value}
|
|
2922
2922
|
KODY_EOF
|
|
2923
2923
|
`);
|
|
2924
2924
|
} else {
|
|
2925
|
-
|
|
2925
|
+
fs14.appendFileSync(outputFile, `${key}=${value}
|
|
2926
2926
|
`);
|
|
2927
2927
|
}
|
|
2928
2928
|
}
|
|
@@ -3047,7 +3047,7 @@ var init_definitions = __esm({
|
|
|
3047
3047
|
});
|
|
3048
3048
|
|
|
3049
3049
|
// src/git-utils.ts
|
|
3050
|
-
import { execFileSync as
|
|
3050
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3051
3051
|
function getHookSafeEnv() {
|
|
3052
3052
|
if (!_hookSafeEnv) {
|
|
3053
3053
|
_hookSafeEnv = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
@@ -3055,7 +3055,7 @@ function getHookSafeEnv() {
|
|
|
3055
3055
|
return _hookSafeEnv;
|
|
3056
3056
|
}
|
|
3057
3057
|
function git(args2, options) {
|
|
3058
|
-
return
|
|
3058
|
+
return execFileSync10("git", args2, {
|
|
3059
3059
|
encoding: "utf-8",
|
|
3060
3060
|
timeout: options?.timeout ?? 3e4,
|
|
3061
3061
|
cwd: options?.cwd,
|
|
@@ -3241,14 +3241,14 @@ var init_git_utils = __esm({
|
|
|
3241
3241
|
});
|
|
3242
3242
|
|
|
3243
3243
|
// src/pipeline/state.ts
|
|
3244
|
-
import * as
|
|
3245
|
-
import * as
|
|
3244
|
+
import * as fs15 from "fs";
|
|
3245
|
+
import * as path13 from "path";
|
|
3246
3246
|
function loadState(taskId, taskDir) {
|
|
3247
|
-
const p =
|
|
3248
|
-
if (!
|
|
3247
|
+
const p = path13.join(taskDir, "status.json");
|
|
3248
|
+
if (!fs15.existsSync(p)) return null;
|
|
3249
3249
|
try {
|
|
3250
3250
|
const result2 = parseJsonSafe(
|
|
3251
|
-
|
|
3251
|
+
fs15.readFileSync(p, "utf-8"),
|
|
3252
3252
|
["taskId", "state", "stages", "createdAt", "updatedAt"]
|
|
3253
3253
|
);
|
|
3254
3254
|
if (!result2.ok) {
|
|
@@ -3266,10 +3266,10 @@ function writeState(state, taskDir) {
|
|
|
3266
3266
|
...state,
|
|
3267
3267
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3268
3268
|
};
|
|
3269
|
-
const target =
|
|
3269
|
+
const target = path13.join(taskDir, "status.json");
|
|
3270
3270
|
const tmp = target + ".tmp";
|
|
3271
|
-
|
|
3272
|
-
|
|
3271
|
+
fs15.writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
3272
|
+
fs15.renameSync(tmp, target);
|
|
3273
3273
|
return updated;
|
|
3274
3274
|
}
|
|
3275
3275
|
function initState(taskId) {
|
|
@@ -3310,16 +3310,16 @@ var init_complexity = __esm({
|
|
|
3310
3310
|
});
|
|
3311
3311
|
|
|
3312
3312
|
// src/memory.ts
|
|
3313
|
-
import * as
|
|
3314
|
-
import * as
|
|
3313
|
+
import * as fs16 from "fs";
|
|
3314
|
+
import * as path14 from "path";
|
|
3315
3315
|
function readProjectMemory(projectDir) {
|
|
3316
|
-
const memoryDir =
|
|
3317
|
-
if (!
|
|
3318
|
-
const files =
|
|
3316
|
+
const memoryDir = path14.join(projectDir, ".kody", "memory");
|
|
3317
|
+
if (!fs16.existsSync(memoryDir)) return "";
|
|
3318
|
+
const files = fs16.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
3319
3319
|
if (files.length === 0) return "";
|
|
3320
3320
|
const sections = [];
|
|
3321
3321
|
for (const file of files) {
|
|
3322
|
-
const content =
|
|
3322
|
+
const content = fs16.readFileSync(path14.join(memoryDir, file), "utf-8").trim();
|
|
3323
3323
|
if (content) {
|
|
3324
3324
|
sections.push(`## ${file.replace(".md", "")}
|
|
3325
3325
|
${content}`);
|
|
@@ -3338,8 +3338,8 @@ var init_memory = __esm({
|
|
|
3338
3338
|
});
|
|
3339
3339
|
|
|
3340
3340
|
// src/context-tiers.ts
|
|
3341
|
-
import * as
|
|
3342
|
-
import * as
|
|
3341
|
+
import * as fs17 from "fs";
|
|
3342
|
+
import * as path15 from "path";
|
|
3343
3343
|
function estimateTokens(text) {
|
|
3344
3344
|
return Math.ceil(text.length / 4);
|
|
3345
3345
|
}
|
|
@@ -3430,7 +3430,7 @@ function generateL1Json(content) {
|
|
|
3430
3430
|
}
|
|
3431
3431
|
}
|
|
3432
3432
|
function getTieredContent(filePath, content) {
|
|
3433
|
-
const key =
|
|
3433
|
+
const key = path15.basename(filePath);
|
|
3434
3434
|
return {
|
|
3435
3435
|
source: filePath,
|
|
3436
3436
|
L0: generateL0(content, key),
|
|
@@ -3442,15 +3442,15 @@ function selectTier(tiered, tier) {
|
|
|
3442
3442
|
return tiered[tier];
|
|
3443
3443
|
}
|
|
3444
3444
|
function readProjectMemoryTiered(projectDir, tier) {
|
|
3445
|
-
const memoryDir =
|
|
3446
|
-
if (!
|
|
3447
|
-
const files =
|
|
3445
|
+
const memoryDir = path15.join(projectDir, ".kody", "memory");
|
|
3446
|
+
if (!fs17.existsSync(memoryDir)) return "";
|
|
3447
|
+
const files = fs17.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
3448
3448
|
if (files.length === 0) return "";
|
|
3449
3449
|
const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
|
|
3450
3450
|
const sections = [];
|
|
3451
3451
|
for (const file of files) {
|
|
3452
|
-
const filePath =
|
|
3453
|
-
const content =
|
|
3452
|
+
const filePath = path15.join(memoryDir, file);
|
|
3453
|
+
const content = fs17.readFileSync(filePath, "utf-8").trim();
|
|
3454
3454
|
if (!content) continue;
|
|
3455
3455
|
const tiered = getTieredContent(filePath, content);
|
|
3456
3456
|
const selected = selectTier(tiered, tier);
|
|
@@ -3473,9 +3473,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
3473
3473
|
`;
|
|
3474
3474
|
context += `Task Directory: ${taskDir}
|
|
3475
3475
|
`;
|
|
3476
|
-
const taskMdPath =
|
|
3477
|
-
if (
|
|
3478
|
-
const content =
|
|
3476
|
+
const taskMdPath = path15.join(taskDir, "task.md");
|
|
3477
|
+
if (fs17.existsSync(taskMdPath)) {
|
|
3478
|
+
const content = fs17.readFileSync(taskMdPath, "utf-8");
|
|
3479
3479
|
const selected = selectContent(taskMdPath, content, policy.taskDescription);
|
|
3480
3480
|
const label = tierLabel("Task Description", policy.taskDescription);
|
|
3481
3481
|
context += `
|
|
@@ -3483,9 +3483,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
3483
3483
|
${selected}
|
|
3484
3484
|
`;
|
|
3485
3485
|
}
|
|
3486
|
-
const taskJsonPath =
|
|
3487
|
-
if (
|
|
3488
|
-
const content =
|
|
3486
|
+
const taskJsonPath = path15.join(taskDir, "task.json");
|
|
3487
|
+
if (fs17.existsSync(taskJsonPath)) {
|
|
3488
|
+
const content = fs17.readFileSync(taskJsonPath, "utf-8");
|
|
3489
3489
|
if (policy.taskClassification === "L2") {
|
|
3490
3490
|
try {
|
|
3491
3491
|
const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
|
|
@@ -3511,9 +3511,9 @@ ${selected}
|
|
|
3511
3511
|
}
|
|
3512
3512
|
}
|
|
3513
3513
|
}
|
|
3514
|
-
const specPath =
|
|
3515
|
-
if (
|
|
3516
|
-
const content =
|
|
3514
|
+
const specPath = path15.join(taskDir, "spec.md");
|
|
3515
|
+
if (fs17.existsSync(specPath)) {
|
|
3516
|
+
const content = fs17.readFileSync(specPath, "utf-8");
|
|
3517
3517
|
const selected = selectContent(specPath, content, policy.spec);
|
|
3518
3518
|
const label = tierLabel("Spec", policy.spec);
|
|
3519
3519
|
context += `
|
|
@@ -3521,9 +3521,9 @@ ${selected}
|
|
|
3521
3521
|
${selected}
|
|
3522
3522
|
`;
|
|
3523
3523
|
}
|
|
3524
|
-
const planPath =
|
|
3525
|
-
if (
|
|
3526
|
-
const content =
|
|
3524
|
+
const planPath = path15.join(taskDir, "plan.md");
|
|
3525
|
+
if (fs17.existsSync(planPath)) {
|
|
3526
|
+
const content = fs17.readFileSync(planPath, "utf-8");
|
|
3527
3527
|
const selected = selectContent(planPath, content, policy.plan);
|
|
3528
3528
|
const label = tierLabel("Plan", policy.plan);
|
|
3529
3529
|
context += `
|
|
@@ -3531,9 +3531,9 @@ ${selected}
|
|
|
3531
3531
|
${selected}
|
|
3532
3532
|
`;
|
|
3533
3533
|
}
|
|
3534
|
-
const contextMdPath =
|
|
3535
|
-
if (
|
|
3536
|
-
const content =
|
|
3534
|
+
const contextMdPath = path15.join(taskDir, "context.md");
|
|
3535
|
+
if (fs17.existsSync(contextMdPath)) {
|
|
3536
|
+
const content = fs17.readFileSync(contextMdPath, "utf-8");
|
|
3537
3537
|
const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
|
|
3538
3538
|
const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
|
|
3539
3539
|
context += `
|
|
@@ -3618,117 +3618,25 @@ var init_context_tiers = __esm({
|
|
|
3618
3618
|
}
|
|
3619
3619
|
});
|
|
3620
3620
|
|
|
3621
|
-
// src/tools.ts
|
|
3622
|
-
import * as fs19 from "fs";
|
|
3623
|
-
import * as path17 from "path";
|
|
3624
|
-
import { execSync as execSync3 } from "child_process";
|
|
3625
|
-
import { parse as parseYaml } from "yaml";
|
|
3626
|
-
function loadToolDeclarations(projectDir) {
|
|
3627
|
-
const toolsPath = path17.join(projectDir, ".kody", "tools.yml");
|
|
3628
|
-
if (!fs19.existsSync(toolsPath)) return [];
|
|
3629
|
-
try {
|
|
3630
|
-
const raw = fs19.readFileSync(toolsPath, "utf-8");
|
|
3631
|
-
const parsed = parseYaml(raw);
|
|
3632
|
-
if (!parsed || typeof parsed !== "object") return [];
|
|
3633
|
-
return Object.entries(parsed).map(([name, value]) => {
|
|
3634
|
-
const v = value;
|
|
3635
|
-
return {
|
|
3636
|
-
name,
|
|
3637
|
-
detect: Array.isArray(v.detect) ? v.detect : [],
|
|
3638
|
-
stages: Array.isArray(v.stages) ? v.stages : [],
|
|
3639
|
-
setup: typeof v.setup === "string" ? v.setup : "",
|
|
3640
|
-
skill: typeof v.skill === "string" ? v.skill : ""
|
|
3641
|
-
};
|
|
3642
|
-
});
|
|
3643
|
-
} catch (err) {
|
|
3644
|
-
logger.warn(`Failed to parse .kody/tools.yml: ${err instanceof Error ? err.message : String(err)}`);
|
|
3645
|
-
return [];
|
|
3646
|
-
}
|
|
3647
|
-
}
|
|
3648
|
-
function resolveSkillContent(skillFilename, projectDir) {
|
|
3649
|
-
if (!skillFilename) return "";
|
|
3650
|
-
const projectSkill = path17.join(projectDir, ".kody", "skills", skillFilename);
|
|
3651
|
-
if (fs19.existsSync(projectSkill)) {
|
|
3652
|
-
return fs19.readFileSync(projectSkill, "utf-8");
|
|
3653
|
-
}
|
|
3654
|
-
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
3655
|
-
const candidates = [
|
|
3656
|
-
path17.resolve(scriptDir, "..", "skills", skillFilename),
|
|
3657
|
-
path17.resolve(scriptDir, "..", "..", "skills", skillFilename)
|
|
3658
|
-
];
|
|
3659
|
-
for (const candidate of candidates) {
|
|
3660
|
-
if (fs19.existsSync(candidate)) {
|
|
3661
|
-
return fs19.readFileSync(candidate, "utf-8");
|
|
3662
|
-
}
|
|
3663
|
-
}
|
|
3664
|
-
logger.warn(`Skill file not found: ${skillFilename}`);
|
|
3665
|
-
return "";
|
|
3666
|
-
}
|
|
3667
|
-
function detectTools(declarations, projectDir) {
|
|
3668
|
-
const resolved = [];
|
|
3669
|
-
for (const decl of declarations) {
|
|
3670
|
-
const detected = decl.detect.some((pattern) => fs19.existsSync(path17.join(projectDir, pattern)));
|
|
3671
|
-
if (!detected) continue;
|
|
3672
|
-
const skillContent = resolveSkillContent(decl.skill, projectDir);
|
|
3673
|
-
resolved.push({
|
|
3674
|
-
name: decl.name,
|
|
3675
|
-
stages: decl.stages,
|
|
3676
|
-
setup: decl.setup,
|
|
3677
|
-
skillContent
|
|
3678
|
-
});
|
|
3679
|
-
}
|
|
3680
|
-
return resolved;
|
|
3681
|
-
}
|
|
3682
|
-
function runToolSetup(tools, projectDir) {
|
|
3683
|
-
for (const tool of tools) {
|
|
3684
|
-
if (!tool.setup) continue;
|
|
3685
|
-
try {
|
|
3686
|
-
logger.info(` Setting up ${tool.name}: ${tool.setup}`);
|
|
3687
|
-
execSync3(tool.setup, { cwd: projectDir, timeout: 12e4, stdio: "pipe" });
|
|
3688
|
-
logger.info(` \u2713 ${tool.name} setup complete`);
|
|
3689
|
-
} catch (err) {
|
|
3690
|
-
logger.warn(` \u26A0 ${tool.name} setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3691
|
-
}
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
function getToolSkillsForStage(tools, stageName) {
|
|
3695
|
-
const matched = tools.filter((t) => t.stages.includes(stageName) && t.skillContent);
|
|
3696
|
-
if (matched.length === 0) return "";
|
|
3697
|
-
const sections = matched.map((t) => `### ${t.name}
|
|
3698
|
-
|
|
3699
|
-
${t.skillContent}`);
|
|
3700
|
-
return `## Available Tools
|
|
3701
|
-
|
|
3702
|
-
The following tools are installed and ready to use in this environment.
|
|
3703
|
-
|
|
3704
|
-
${sections.join("\n\n")}`;
|
|
3705
|
-
}
|
|
3706
|
-
var init_tools = __esm({
|
|
3707
|
-
"src/tools.ts"() {
|
|
3708
|
-
"use strict";
|
|
3709
|
-
init_logger();
|
|
3710
|
-
}
|
|
3711
|
-
});
|
|
3712
|
-
|
|
3713
3621
|
// src/context.ts
|
|
3714
|
-
import * as
|
|
3715
|
-
import * as
|
|
3622
|
+
import * as fs18 from "fs";
|
|
3623
|
+
import * as path16 from "path";
|
|
3716
3624
|
function readPromptFile(stageName, projectDir) {
|
|
3717
3625
|
if (projectDir) {
|
|
3718
|
-
const stepFile =
|
|
3719
|
-
if (
|
|
3720
|
-
return
|
|
3626
|
+
const stepFile = path16.join(projectDir, ".kody", "steps", `${stageName}.md`);
|
|
3627
|
+
if (fs18.existsSync(stepFile)) {
|
|
3628
|
+
return fs18.readFileSync(stepFile, "utf-8");
|
|
3721
3629
|
}
|
|
3722
3630
|
console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
|
|
3723
3631
|
}
|
|
3724
3632
|
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
3725
3633
|
const candidates = [
|
|
3726
|
-
|
|
3727
|
-
|
|
3634
|
+
path16.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
|
|
3635
|
+
path16.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
|
|
3728
3636
|
];
|
|
3729
3637
|
for (const candidate of candidates) {
|
|
3730
|
-
if (
|
|
3731
|
-
return
|
|
3638
|
+
if (fs18.existsSync(candidate)) {
|
|
3639
|
+
return fs18.readFileSync(candidate, "utf-8");
|
|
3732
3640
|
}
|
|
3733
3641
|
}
|
|
3734
3642
|
throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
|
|
@@ -3740,18 +3648,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
|
|
|
3740
3648
|
`;
|
|
3741
3649
|
context += `Task Directory: ${taskDir}
|
|
3742
3650
|
`;
|
|
3743
|
-
const taskMdPath =
|
|
3744
|
-
if (
|
|
3745
|
-
const taskMd =
|
|
3651
|
+
const taskMdPath = path16.join(taskDir, "task.md");
|
|
3652
|
+
if (fs18.existsSync(taskMdPath)) {
|
|
3653
|
+
const taskMd = fs18.readFileSync(taskMdPath, "utf-8");
|
|
3746
3654
|
context += `
|
|
3747
3655
|
## Task Description
|
|
3748
3656
|
${taskMd}
|
|
3749
3657
|
`;
|
|
3750
3658
|
}
|
|
3751
|
-
const taskJsonPath =
|
|
3752
|
-
if (
|
|
3659
|
+
const taskJsonPath = path16.join(taskDir, "task.json");
|
|
3660
|
+
if (fs18.existsSync(taskJsonPath)) {
|
|
3753
3661
|
try {
|
|
3754
|
-
const taskDef = JSON.parse(
|
|
3662
|
+
const taskDef = JSON.parse(fs18.readFileSync(taskJsonPath, "utf-8"));
|
|
3755
3663
|
context += `
|
|
3756
3664
|
## Task Classification
|
|
3757
3665
|
`;
|
|
@@ -3764,27 +3672,27 @@ ${taskMd}
|
|
|
3764
3672
|
} catch {
|
|
3765
3673
|
}
|
|
3766
3674
|
}
|
|
3767
|
-
const specPath =
|
|
3768
|
-
if (
|
|
3769
|
-
const spec =
|
|
3675
|
+
const specPath = path16.join(taskDir, "spec.md");
|
|
3676
|
+
if (fs18.existsSync(specPath)) {
|
|
3677
|
+
const spec = fs18.readFileSync(specPath, "utf-8");
|
|
3770
3678
|
const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
|
|
3771
3679
|
context += `
|
|
3772
3680
|
## Spec Summary
|
|
3773
3681
|
${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
|
|
3774
3682
|
`;
|
|
3775
3683
|
}
|
|
3776
|
-
const planPath =
|
|
3777
|
-
if (
|
|
3778
|
-
const plan =
|
|
3684
|
+
const planPath = path16.join(taskDir, "plan.md");
|
|
3685
|
+
if (fs18.existsSync(planPath)) {
|
|
3686
|
+
const plan = fs18.readFileSync(planPath, "utf-8");
|
|
3779
3687
|
const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
|
|
3780
3688
|
context += `
|
|
3781
3689
|
## Plan Summary
|
|
3782
3690
|
${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
|
|
3783
3691
|
`;
|
|
3784
3692
|
}
|
|
3785
|
-
const contextMdPath =
|
|
3786
|
-
if (
|
|
3787
|
-
const accumulated =
|
|
3693
|
+
const contextMdPath = path16.join(taskDir, "context.md");
|
|
3694
|
+
if (fs18.existsSync(contextMdPath)) {
|
|
3695
|
+
const accumulated = fs18.readFileSync(contextMdPath, "utf-8");
|
|
3788
3696
|
const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
|
|
3789
3697
|
const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
|
|
3790
3698
|
context += `
|
|
@@ -3802,17 +3710,17 @@ ${feedback}
|
|
|
3802
3710
|
}
|
|
3803
3711
|
function inferHasUIFromScope(scope) {
|
|
3804
3712
|
return scope.some((filePath) => {
|
|
3805
|
-
const ext =
|
|
3713
|
+
const ext = path16.extname(filePath).toLowerCase();
|
|
3806
3714
|
if (UI_EXTENSIONS.has(ext)) return true;
|
|
3807
3715
|
const normalized = filePath.replace(/\\/g, "/");
|
|
3808
3716
|
return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
|
|
3809
3717
|
});
|
|
3810
3718
|
}
|
|
3811
3719
|
function taskHasUI(taskDir) {
|
|
3812
|
-
const taskJsonPath =
|
|
3813
|
-
if (!
|
|
3720
|
+
const taskJsonPath = path16.join(taskDir, "task.json");
|
|
3721
|
+
if (!fs18.existsSync(taskJsonPath)) return true;
|
|
3814
3722
|
try {
|
|
3815
|
-
const taskDef = JSON.parse(
|
|
3723
|
+
const taskDef = JSON.parse(fs18.readFileSync(taskJsonPath, "utf-8"));
|
|
3816
3724
|
const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
|
|
3817
3725
|
if (scope.length === 0) return true;
|
|
3818
3726
|
return inferHasUIFromScope(scope);
|
|
@@ -3837,23 +3745,51 @@ function getBrowserToolGuidance(stageName, taskDir) {
|
|
|
3837
3745
|
### Dev Server Setup (REQUIRED before browsing)
|
|
3838
3746
|
You MUST start the dev server before using any browser navigation tools:
|
|
3839
3747
|
\`\`\`bash
|
|
3840
|
-
# Start the dev server in the background
|
|
3841
|
-
${devServer.command} &
|
|
3842
|
-
|
|
3843
|
-
|
|
3748
|
+
# Start the dev server in the background with output redirected to a log file
|
|
3749
|
+
nohup ${devServer.command} > /tmp/dev-server.log 2>&1 &
|
|
3750
|
+
DEV_PID=$!
|
|
3751
|
+
|
|
3752
|
+
# Wait up to ${devServer.readyTimeout}s for the server to be ready
|
|
3753
|
+
for i in $(seq 1 ${devServer.readyTimeout}); do
|
|
3754
|
+
if curl -s -o /dev/null -w "%{http_code}" ${devServer.url} 2>/dev/null | grep -qE "^[23]"; then
|
|
3755
|
+
echo "Dev server is ready"
|
|
3756
|
+
break
|
|
3757
|
+
fi
|
|
3758
|
+
if ! kill -0 $DEV_PID 2>/dev/null; then
|
|
3759
|
+
echo "Dev server process died. Last 20 lines:"
|
|
3760
|
+
tail -20 /tmp/dev-server.log
|
|
3761
|
+
break
|
|
3762
|
+
fi
|
|
3763
|
+
sleep 1
|
|
3764
|
+
done
|
|
3844
3765
|
\`\`\`
|
|
3845
3766
|
The dev server URL is: ${devServer.url}
|
|
3846
|
-
|
|
3767
|
+
If the dev server fails to start (e.g. DB connection issues), skip browser verification and proceed with code-only changes. Do NOT hang waiting for it.
|
|
3768
|
+
After you are done browsing, kill the dev server: \`kill $DEV_PID 2>/dev/null || true\`` : `
|
|
3847
3769
|
### Dev Server Setup (REQUIRED before browsing)
|
|
3848
3770
|
You MUST start the project's dev server before using any browser navigation tools.
|
|
3849
3771
|
Check package.json for the dev command (usually \`pnpm dev\` or \`npm run dev\`).
|
|
3850
3772
|
\`\`\`bash
|
|
3851
|
-
# Start the dev server in the background
|
|
3852
|
-
pnpm dev &
|
|
3853
|
-
|
|
3854
|
-
|
|
3773
|
+
# Start the dev server in the background with output redirected
|
|
3774
|
+
nohup pnpm dev > /tmp/dev-server.log 2>&1 &
|
|
3775
|
+
DEV_PID=$!
|
|
3776
|
+
|
|
3777
|
+
# Wait up to 30s for the server to be ready
|
|
3778
|
+
for i in $(seq 1 30); do
|
|
3779
|
+
if curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 2>/dev/null | grep -qE "^[23]"; then
|
|
3780
|
+
echo "Dev server is ready"
|
|
3781
|
+
break
|
|
3782
|
+
fi
|
|
3783
|
+
if ! kill -0 $DEV_PID 2>/dev/null; then
|
|
3784
|
+
echo "Dev server process died. Last 20 lines:"
|
|
3785
|
+
tail -20 /tmp/dev-server.log
|
|
3786
|
+
break
|
|
3787
|
+
fi
|
|
3788
|
+
sleep 1
|
|
3789
|
+
done
|
|
3855
3790
|
\`\`\`
|
|
3856
|
-
|
|
3791
|
+
If the dev server fails to start (e.g. DB connection issues), skip browser verification and proceed with code-only changes. Do NOT hang waiting for it.
|
|
3792
|
+
After you are done browsing, kill the dev server: \`kill $DEV_PID 2>/dev/null || true\``;
|
|
3857
3793
|
if (stageName === "build" || stageName === "review-fix") {
|
|
3858
3794
|
return `## Browser Visual Verification (MANDATORY for UI tasks)
|
|
3859
3795
|
|
|
@@ -3918,7 +3854,7 @@ ${devServerBlock}
|
|
|
3918
3854
|
|
|
3919
3855
|
Use browser tools to navigate to pages and take snapshots to verify UI output.`;
|
|
3920
3856
|
}
|
|
3921
|
-
function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback
|
|
3857
|
+
function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
|
|
3922
3858
|
const config = getProjectConfig();
|
|
3923
3859
|
let assembled;
|
|
3924
3860
|
if (config.contextTiers?.enabled) {
|
|
@@ -3934,18 +3870,12 @@ ${prompt}` : prompt;
|
|
|
3934
3870
|
}
|
|
3935
3871
|
if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
|
|
3936
3872
|
assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
|
|
3937
|
-
const qaGuidePath =
|
|
3938
|
-
if (
|
|
3939
|
-
const qaGuide =
|
|
3873
|
+
const qaGuidePath = path16.join(projectDir, ".kody", "qa-guide.md");
|
|
3874
|
+
if (fs18.existsSync(qaGuidePath)) {
|
|
3875
|
+
const qaGuide = fs18.readFileSync(qaGuidePath, "utf-8").trim();
|
|
3940
3876
|
assembled = assembled + "\n\n" + qaGuide;
|
|
3941
3877
|
}
|
|
3942
3878
|
}
|
|
3943
|
-
if (tools?.length) {
|
|
3944
|
-
const toolSkills = getToolSkillsForStage(tools, stageName);
|
|
3945
|
-
if (toolSkills) {
|
|
3946
|
-
assembled = assembled + "\n\n" + toolSkills;
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
3879
|
return assembled;
|
|
3950
3880
|
}
|
|
3951
3881
|
function buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback) {
|
|
@@ -3985,7 +3915,6 @@ var init_context = __esm({
|
|
|
3985
3915
|
init_config();
|
|
3986
3916
|
init_context_tiers();
|
|
3987
3917
|
init_mcp_config();
|
|
3988
|
-
init_tools();
|
|
3989
3918
|
MAX_TASK_CONTEXT_PLAN = 1500;
|
|
3990
3919
|
MAX_TASK_CONTEXT_SPEC = 2e3;
|
|
3991
3920
|
MAX_ACCUMULATED_CONTEXT = 4e3;
|
|
@@ -4035,8 +3964,8 @@ var init_runner_selection = __esm({
|
|
|
4035
3964
|
});
|
|
4036
3965
|
|
|
4037
3966
|
// src/stages/agent.ts
|
|
4038
|
-
import * as
|
|
4039
|
-
import * as
|
|
3967
|
+
import * as fs19 from "fs";
|
|
3968
|
+
import * as path17 from "path";
|
|
4040
3969
|
function getSessionInfo(stageName, sessions) {
|
|
4041
3970
|
const group = SESSION_GROUP[stageName];
|
|
4042
3971
|
if (!group) return void 0;
|
|
@@ -4065,7 +3994,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4065
3994
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
4066
3995
|
return { outcome: "completed", retries: 0 };
|
|
4067
3996
|
}
|
|
4068
|
-
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback
|
|
3997
|
+
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
|
|
4069
3998
|
let currentModelTier = def.modelTier;
|
|
4070
3999
|
if (ctx.input.feedback && def.name === "build") {
|
|
4071
4000
|
logger.info(` feedback: ${ctx.input.feedback.slice(0, 200)}${ctx.input.feedback.length > 200 ? "..." : ""}`);
|
|
@@ -4123,27 +4052,27 @@ async function executeAgentStage(ctx, def) {
|
|
|
4123
4052
|
}
|
|
4124
4053
|
const result2 = lastResult;
|
|
4125
4054
|
if (def.outputFile && result2.output) {
|
|
4126
|
-
|
|
4055
|
+
fs19.writeFileSync(path17.join(ctx.taskDir, def.outputFile), result2.output);
|
|
4127
4056
|
}
|
|
4128
4057
|
if (def.outputFile) {
|
|
4129
|
-
const outputPath =
|
|
4130
|
-
if (!
|
|
4131
|
-
const ext =
|
|
4132
|
-
const base =
|
|
4133
|
-
const files =
|
|
4058
|
+
const outputPath = path17.join(ctx.taskDir, def.outputFile);
|
|
4059
|
+
if (!fs19.existsSync(outputPath)) {
|
|
4060
|
+
const ext = path17.extname(def.outputFile);
|
|
4061
|
+
const base = path17.basename(def.outputFile, ext);
|
|
4062
|
+
const files = fs19.readdirSync(ctx.taskDir);
|
|
4134
4063
|
const variant = files.find(
|
|
4135
4064
|
(f) => f.startsWith(base + "-") && f.endsWith(ext)
|
|
4136
4065
|
);
|
|
4137
4066
|
if (variant) {
|
|
4138
|
-
|
|
4067
|
+
fs19.renameSync(path17.join(ctx.taskDir, variant), outputPath);
|
|
4139
4068
|
logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
|
|
4140
4069
|
}
|
|
4141
4070
|
}
|
|
4142
4071
|
}
|
|
4143
4072
|
if (def.outputFile) {
|
|
4144
|
-
const outputPath =
|
|
4145
|
-
if (
|
|
4146
|
-
const content =
|
|
4073
|
+
const outputPath = path17.join(ctx.taskDir, def.outputFile);
|
|
4074
|
+
if (fs19.existsSync(outputPath)) {
|
|
4075
|
+
const content = fs19.readFileSync(outputPath, "utf-8");
|
|
4147
4076
|
const validation = validateStageOutput(def.name, content);
|
|
4148
4077
|
if (!validation.valid) {
|
|
4149
4078
|
if (def.name === "taskify") {
|
|
@@ -4157,7 +4086,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4157
4086
|
const stripped = stripFences(retryResult.output);
|
|
4158
4087
|
const retryValidation = validateTaskJson(stripped);
|
|
4159
4088
|
if (retryValidation.valid) {
|
|
4160
|
-
|
|
4089
|
+
fs19.writeFileSync(outputPath, retryResult.output);
|
|
4161
4090
|
logger.info(` taskify retry produced valid JSON`);
|
|
4162
4091
|
} else {
|
|
4163
4092
|
logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
|
|
@@ -4170,7 +4099,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4170
4099
|
risk_level: "low",
|
|
4171
4100
|
questions: []
|
|
4172
4101
|
}, null, 2);
|
|
4173
|
-
|
|
4102
|
+
fs19.writeFileSync(outputPath, fallback);
|
|
4174
4103
|
logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
|
|
4175
4104
|
}
|
|
4176
4105
|
}
|
|
@@ -4184,7 +4113,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4184
4113
|
return { outcome: "completed", outputFile: def.outputFile, retries };
|
|
4185
4114
|
}
|
|
4186
4115
|
function appendStageContext(taskDir, stageName, output) {
|
|
4187
|
-
const contextPath =
|
|
4116
|
+
const contextPath = path17.join(taskDir, "context.md");
|
|
4188
4117
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
|
|
4189
4118
|
let summary;
|
|
4190
4119
|
if (output && output.trim()) {
|
|
@@ -4197,7 +4126,7 @@ function appendStageContext(taskDir, stageName, output) {
|
|
|
4197
4126
|
### ${stageName} (${timestamp2})
|
|
4198
4127
|
${summary}
|
|
4199
4128
|
`;
|
|
4200
|
-
|
|
4129
|
+
fs19.appendFileSync(contextPath, entry);
|
|
4201
4130
|
}
|
|
4202
4131
|
var SESSION_GROUP;
|
|
4203
4132
|
var init_agent = __esm({
|
|
@@ -4220,7 +4149,7 @@ var init_agent = __esm({
|
|
|
4220
4149
|
});
|
|
4221
4150
|
|
|
4222
4151
|
// src/verify-runner.ts
|
|
4223
|
-
import { execFileSync as
|
|
4152
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
4224
4153
|
function isExecError(err) {
|
|
4225
4154
|
return typeof err === "object" && err !== null;
|
|
4226
4155
|
}
|
|
@@ -4256,7 +4185,7 @@ function runCommand(cmd, cwd, timeout) {
|
|
|
4256
4185
|
return { success: true, output: "", timedOut: false };
|
|
4257
4186
|
}
|
|
4258
4187
|
try {
|
|
4259
|
-
const output =
|
|
4188
|
+
const output = execFileSync11(parts[0], parts.slice(1), {
|
|
4260
4189
|
cwd,
|
|
4261
4190
|
timeout,
|
|
4262
4191
|
encoding: "utf-8",
|
|
@@ -4345,7 +4274,7 @@ var init_verify_runner = __esm({
|
|
|
4345
4274
|
});
|
|
4346
4275
|
|
|
4347
4276
|
// src/observer.ts
|
|
4348
|
-
import { execFileSync as
|
|
4277
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
4349
4278
|
async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, model, options) {
|
|
4350
4279
|
const context = [
|
|
4351
4280
|
`Stage: ${stageName}`,
|
|
@@ -4428,13 +4357,13 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
|
|
|
4428
4357
|
}
|
|
4429
4358
|
function getModifiedFiles(projectDir) {
|
|
4430
4359
|
try {
|
|
4431
|
-
const staged =
|
|
4360
|
+
const staged = execFileSync12("git", ["diff", "--name-only", "--cached"], {
|
|
4432
4361
|
encoding: "utf-8",
|
|
4433
4362
|
cwd: projectDir,
|
|
4434
4363
|
timeout: 5e3,
|
|
4435
4364
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4436
4365
|
}).trim();
|
|
4437
|
-
const unstaged =
|
|
4366
|
+
const unstaged = execFileSync12("git", ["diff", "--name-only"], {
|
|
4438
4367
|
encoding: "utf-8",
|
|
4439
4368
|
cwd: projectDir,
|
|
4440
4369
|
timeout: 5e3,
|
|
@@ -4477,8 +4406,8 @@ Error context:
|
|
|
4477
4406
|
});
|
|
4478
4407
|
|
|
4479
4408
|
// src/stages/gate.ts
|
|
4480
|
-
import * as
|
|
4481
|
-
import * as
|
|
4409
|
+
import * as fs20 from "fs";
|
|
4410
|
+
import * as path18 from "path";
|
|
4482
4411
|
function executeGateStage(ctx, def) {
|
|
4483
4412
|
if (ctx.input.dryRun) {
|
|
4484
4413
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
@@ -4521,7 +4450,7 @@ ${output}
|
|
|
4521
4450
|
`);
|
|
4522
4451
|
}
|
|
4523
4452
|
}
|
|
4524
|
-
|
|
4453
|
+
fs20.writeFileSync(path18.join(ctx.taskDir, "verify.md"), lines.join(""));
|
|
4525
4454
|
return {
|
|
4526
4455
|
outcome: verifyResult.pass ? "completed" : "failed",
|
|
4527
4456
|
retries: 0
|
|
@@ -4536,9 +4465,9 @@ var init_gate = __esm({
|
|
|
4536
4465
|
});
|
|
4537
4466
|
|
|
4538
4467
|
// src/stages/verify.ts
|
|
4539
|
-
import * as
|
|
4540
|
-
import * as
|
|
4541
|
-
import { execFileSync as
|
|
4468
|
+
import * as fs21 from "fs";
|
|
4469
|
+
import * as path19 from "path";
|
|
4470
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
4542
4471
|
async function executeVerifyWithAutofix(ctx, def) {
|
|
4543
4472
|
const maxAttempts = def.maxRetries ?? 2;
|
|
4544
4473
|
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
@@ -4548,8 +4477,8 @@ async function executeVerifyWithAutofix(ctx, def) {
|
|
|
4548
4477
|
return { ...gateResult, retries: attempt };
|
|
4549
4478
|
}
|
|
4550
4479
|
if (attempt < maxAttempts) {
|
|
4551
|
-
const verifyPath =
|
|
4552
|
-
const errorOutput =
|
|
4480
|
+
const verifyPath = path19.join(ctx.taskDir, "verify.md");
|
|
4481
|
+
const errorOutput = fs21.existsSync(verifyPath) ? fs21.readFileSync(verifyPath, "utf-8") : "Unknown error";
|
|
4553
4482
|
const modifiedFiles = getModifiedFiles(ctx.projectDir);
|
|
4554
4483
|
const defaultRunner = getRunnerForStage(ctx, "taskify");
|
|
4555
4484
|
const diagConfig = getProjectConfig();
|
|
@@ -4592,7 +4521,7 @@ ${diagnosis.resolution}`);
|
|
|
4592
4521
|
const parts = parseCommand(cmd);
|
|
4593
4522
|
if (parts.length === 0) return;
|
|
4594
4523
|
try {
|
|
4595
|
-
|
|
4524
|
+
execFileSync13(parts[0], parts.slice(1), {
|
|
4596
4525
|
stdio: "pipe",
|
|
4597
4526
|
timeout: FIX_COMMAND_TIMEOUT_MS
|
|
4598
4527
|
});
|
|
@@ -4645,8 +4574,8 @@ var init_verify = __esm({
|
|
|
4645
4574
|
});
|
|
4646
4575
|
|
|
4647
4576
|
// src/review-standalone.ts
|
|
4648
|
-
import * as
|
|
4649
|
-
import * as
|
|
4577
|
+
import * as fs22 from "fs";
|
|
4578
|
+
import * as path20 from "path";
|
|
4650
4579
|
function resolveReviewTarget(input) {
|
|
4651
4580
|
if (input.prs.length === 0) {
|
|
4652
4581
|
return {
|
|
@@ -4670,8 +4599,8 @@ Or comment on the specific PR: \`@kody review\``
|
|
|
4670
4599
|
}
|
|
4671
4600
|
async function runStandaloneReview(input) {
|
|
4672
4601
|
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
4673
|
-
const taskDir =
|
|
4674
|
-
|
|
4602
|
+
const taskDir = path20.join(input.projectDir, ".kody", "tasks", taskId);
|
|
4603
|
+
fs22.mkdirSync(taskDir, { recursive: true });
|
|
4675
4604
|
let diffInstruction = "";
|
|
4676
4605
|
let filesChangedSection = "";
|
|
4677
4606
|
if (input.baseBranch) {
|
|
@@ -4698,7 +4627,7 @@ ${fileList}`;
|
|
|
4698
4627
|
const taskContent = `# ${input.prTitle}
|
|
4699
4628
|
|
|
4700
4629
|
${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
4701
|
-
|
|
4630
|
+
fs22.writeFileSync(path20.join(taskDir, "task.md"), taskContent);
|
|
4702
4631
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
4703
4632
|
const ctx = {
|
|
4704
4633
|
taskId,
|
|
@@ -4720,10 +4649,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
|
4720
4649
|
error: result2.error ?? "Review stage failed"
|
|
4721
4650
|
};
|
|
4722
4651
|
}
|
|
4723
|
-
const reviewPath =
|
|
4652
|
+
const reviewPath = path20.join(taskDir, "review.md");
|
|
4724
4653
|
let reviewContent;
|
|
4725
|
-
if (
|
|
4726
|
-
reviewContent =
|
|
4654
|
+
if (fs22.existsSync(reviewPath)) {
|
|
4655
|
+
reviewContent = fs22.readFileSync(reviewPath, "utf-8");
|
|
4727
4656
|
}
|
|
4728
4657
|
return {
|
|
4729
4658
|
outcome: "completed",
|
|
@@ -4763,8 +4692,8 @@ var init_review_standalone = __esm({
|
|
|
4763
4692
|
});
|
|
4764
4693
|
|
|
4765
4694
|
// src/stages/review.ts
|
|
4766
|
-
import * as
|
|
4767
|
-
import * as
|
|
4695
|
+
import * as fs23 from "fs";
|
|
4696
|
+
import * as path21 from "path";
|
|
4768
4697
|
async function executeReviewWithFix(ctx, def) {
|
|
4769
4698
|
if (ctx.input.dryRun) {
|
|
4770
4699
|
return { outcome: "completed", retries: 0 };
|
|
@@ -4778,11 +4707,11 @@ async function executeReviewWithFix(ctx, def) {
|
|
|
4778
4707
|
if (reviewResult.outcome !== "completed") {
|
|
4779
4708
|
return reviewResult;
|
|
4780
4709
|
}
|
|
4781
|
-
const reviewFile =
|
|
4782
|
-
if (!
|
|
4710
|
+
const reviewFile = path21.join(ctx.taskDir, "review.md");
|
|
4711
|
+
if (!fs23.existsSync(reviewFile)) {
|
|
4783
4712
|
return { outcome: "failed", retries: iteration, error: "review.md not found" };
|
|
4784
4713
|
}
|
|
4785
|
-
const content =
|
|
4714
|
+
const content = fs23.readFileSync(reviewFile, "utf-8");
|
|
4786
4715
|
if (detectReviewVerdict(content) !== "fail") {
|
|
4787
4716
|
return { ...reviewResult, retries: iteration };
|
|
4788
4717
|
}
|
|
@@ -4811,15 +4740,15 @@ var init_review = __esm({
|
|
|
4811
4740
|
});
|
|
4812
4741
|
|
|
4813
4742
|
// src/stages/ship.ts
|
|
4814
|
-
import * as
|
|
4815
|
-
import * as
|
|
4816
|
-
import { execFileSync as
|
|
4743
|
+
import * as fs24 from "fs";
|
|
4744
|
+
import * as path22 from "path";
|
|
4745
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
4817
4746
|
function buildPrBody(ctx) {
|
|
4818
4747
|
const sections = [];
|
|
4819
|
-
const taskJsonPath =
|
|
4820
|
-
if (
|
|
4748
|
+
const taskJsonPath = path22.join(ctx.taskDir, "task.json");
|
|
4749
|
+
if (fs24.existsSync(taskJsonPath)) {
|
|
4821
4750
|
try {
|
|
4822
|
-
const raw =
|
|
4751
|
+
const raw = fs24.readFileSync(taskJsonPath, "utf-8");
|
|
4823
4752
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4824
4753
|
const task = JSON.parse(cleaned);
|
|
4825
4754
|
if (task.description) {
|
|
@@ -4838,9 +4767,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
4838
4767
|
} catch {
|
|
4839
4768
|
}
|
|
4840
4769
|
}
|
|
4841
|
-
const reviewPath =
|
|
4842
|
-
if (
|
|
4843
|
-
const review =
|
|
4770
|
+
const reviewPath = path22.join(ctx.taskDir, "review.md");
|
|
4771
|
+
if (fs24.existsSync(reviewPath)) {
|
|
4772
|
+
const review = fs24.readFileSync(reviewPath, "utf-8");
|
|
4844
4773
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
4845
4774
|
if (summaryMatch) {
|
|
4846
4775
|
const summary = summaryMatch[1].trim();
|
|
@@ -4857,14 +4786,14 @@ ${summary}`);
|
|
|
4857
4786
|
**Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
|
|
4858
4787
|
}
|
|
4859
4788
|
}
|
|
4860
|
-
const verifyPath =
|
|
4861
|
-
if (
|
|
4862
|
-
const verify =
|
|
4789
|
+
const verifyPath = path22.join(ctx.taskDir, "verify.md");
|
|
4790
|
+
if (fs24.existsSync(verifyPath)) {
|
|
4791
|
+
const verify = fs24.readFileSync(verifyPath, "utf-8");
|
|
4863
4792
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
4864
4793
|
}
|
|
4865
|
-
const planPath =
|
|
4866
|
-
if (
|
|
4867
|
-
const plan =
|
|
4794
|
+
const planPath = path22.join(ctx.taskDir, "plan.md");
|
|
4795
|
+
if (fs24.existsSync(planPath)) {
|
|
4796
|
+
const plan = fs24.readFileSync(planPath, "utf-8").trim();
|
|
4868
4797
|
if (plan) {
|
|
4869
4798
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
4870
4799
|
sections.push(`
|
|
@@ -4884,25 +4813,25 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
4884
4813
|
return sections.join("\n");
|
|
4885
4814
|
}
|
|
4886
4815
|
function executeShipStage(ctx, _def) {
|
|
4887
|
-
const shipPath =
|
|
4816
|
+
const shipPath = path22.join(ctx.taskDir, "ship.md");
|
|
4888
4817
|
if (ctx.input.dryRun) {
|
|
4889
|
-
|
|
4818
|
+
fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
4890
4819
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
4891
4820
|
}
|
|
4892
4821
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
4893
|
-
|
|
4822
|
+
fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
4894
4823
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
4895
4824
|
}
|
|
4896
4825
|
try {
|
|
4897
4826
|
const head = getCurrentBranch(ctx.projectDir);
|
|
4898
4827
|
const base = getDefaultBranch(ctx.projectDir);
|
|
4899
4828
|
try {
|
|
4900
|
-
|
|
4829
|
+
execFileSync14("git", ["add", ctx.taskDir], {
|
|
4901
4830
|
cwd: ctx.projectDir,
|
|
4902
4831
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
4903
4832
|
stdio: "pipe"
|
|
4904
4833
|
});
|
|
4905
|
-
|
|
4834
|
+
execFileSync14("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
|
|
4906
4835
|
cwd: ctx.projectDir,
|
|
4907
4836
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
4908
4837
|
stdio: "pipe"
|
|
@@ -4916,7 +4845,7 @@ function executeShipStage(ctx, _def) {
|
|
|
4916
4845
|
let repo = config.github?.repo;
|
|
4917
4846
|
if (!owner || !repo) {
|
|
4918
4847
|
try {
|
|
4919
|
-
const remoteUrl =
|
|
4848
|
+
const remoteUrl = execFileSync14("git", ["remote", "get-url", "origin"], {
|
|
4920
4849
|
encoding: "utf-8",
|
|
4921
4850
|
cwd: ctx.projectDir
|
|
4922
4851
|
}).trim();
|
|
@@ -4937,28 +4866,28 @@ function executeShipStage(ctx, _def) {
|
|
|
4937
4866
|
chore: "chore"
|
|
4938
4867
|
};
|
|
4939
4868
|
let prefix = "chore";
|
|
4940
|
-
const taskJsonPath =
|
|
4941
|
-
if (
|
|
4869
|
+
const taskJsonPath = path22.join(ctx.taskDir, "task.json");
|
|
4870
|
+
if (fs24.existsSync(taskJsonPath)) {
|
|
4942
4871
|
try {
|
|
4943
|
-
const raw =
|
|
4872
|
+
const raw = fs24.readFileSync(taskJsonPath, "utf-8");
|
|
4944
4873
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4945
4874
|
const task = JSON.parse(cleaned);
|
|
4946
4875
|
prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
4947
4876
|
} catch {
|
|
4948
4877
|
}
|
|
4949
4878
|
}
|
|
4950
|
-
const taskMdPath =
|
|
4951
|
-
if (
|
|
4952
|
-
const content =
|
|
4879
|
+
const taskMdPath = path22.join(ctx.taskDir, "task.md");
|
|
4880
|
+
if (fs24.existsSync(taskMdPath)) {
|
|
4881
|
+
const content = fs24.readFileSync(taskMdPath, "utf-8");
|
|
4953
4882
|
const heading = content.split("\n").find((l) => l.startsWith("# "));
|
|
4954
4883
|
if (heading) {
|
|
4955
4884
|
title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
|
|
4956
4885
|
}
|
|
4957
4886
|
}
|
|
4958
4887
|
if (title === "Update") {
|
|
4959
|
-
if (
|
|
4888
|
+
if (fs24.existsSync(taskJsonPath)) {
|
|
4960
4889
|
try {
|
|
4961
|
-
const raw =
|
|
4890
|
+
const raw = fs24.readFileSync(taskJsonPath, "utf-8");
|
|
4962
4891
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4963
4892
|
const task = JSON.parse(cleaned);
|
|
4964
4893
|
if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
|
|
@@ -4981,7 +4910,7 @@ function executeShipStage(ctx, _def) {
|
|
|
4981
4910
|
} catch {
|
|
4982
4911
|
}
|
|
4983
4912
|
}
|
|
4984
|
-
|
|
4913
|
+
fs24.writeFileSync(shipPath, `# Ship
|
|
4985
4914
|
|
|
4986
4915
|
Updated existing PR: ${existingPr.url}
|
|
4987
4916
|
PR #${existingPr.number}
|
|
@@ -5002,20 +4931,20 @@ PR #${existingPr.number}
|
|
|
5002
4931
|
} catch {
|
|
5003
4932
|
}
|
|
5004
4933
|
}
|
|
5005
|
-
|
|
4934
|
+
fs24.writeFileSync(shipPath, `# Ship
|
|
5006
4935
|
|
|
5007
4936
|
PR created: ${pr.url}
|
|
5008
4937
|
PR #${pr.number}
|
|
5009
4938
|
`);
|
|
5010
4939
|
} else {
|
|
5011
|
-
|
|
4940
|
+
fs24.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
5012
4941
|
}
|
|
5013
4942
|
}
|
|
5014
4943
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
5015
4944
|
} catch (err) {
|
|
5016
4945
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5017
4946
|
try {
|
|
5018
|
-
|
|
4947
|
+
fs24.writeFileSync(shipPath, `# Ship
|
|
5019
4948
|
|
|
5020
4949
|
Failed: ${msg}
|
|
5021
4950
|
`);
|
|
@@ -5064,15 +4993,15 @@ var init_executor_registry = __esm({
|
|
|
5064
4993
|
});
|
|
5065
4994
|
|
|
5066
4995
|
// src/pipeline/questions.ts
|
|
5067
|
-
import * as
|
|
5068
|
-
import * as
|
|
4996
|
+
import * as fs25 from "fs";
|
|
4997
|
+
import * as path23 from "path";
|
|
5069
4998
|
function checkForQuestions(ctx, stageName) {
|
|
5070
4999
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
5071
5000
|
try {
|
|
5072
5001
|
if (stageName === "taskify") {
|
|
5073
|
-
const taskJsonPath =
|
|
5074
|
-
if (!
|
|
5075
|
-
const raw =
|
|
5002
|
+
const taskJsonPath = path23.join(ctx.taskDir, "task.json");
|
|
5003
|
+
if (!fs25.existsSync(taskJsonPath)) return false;
|
|
5004
|
+
const raw = fs25.readFileSync(taskJsonPath, "utf-8");
|
|
5076
5005
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
5077
5006
|
const taskJson = JSON.parse(cleaned);
|
|
5078
5007
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -5087,9 +5016,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
5087
5016
|
}
|
|
5088
5017
|
}
|
|
5089
5018
|
if (stageName === "plan") {
|
|
5090
|
-
const planPath =
|
|
5091
|
-
if (!
|
|
5092
|
-
const plan =
|
|
5019
|
+
const planPath = path23.join(ctx.taskDir, "plan.md");
|
|
5020
|
+
if (!fs25.existsSync(planPath)) return false;
|
|
5021
|
+
const plan = fs25.readFileSync(planPath, "utf-8");
|
|
5093
5022
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
5094
5023
|
if (questionsMatch) {
|
|
5095
5024
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -5118,8 +5047,8 @@ var init_questions = __esm({
|
|
|
5118
5047
|
});
|
|
5119
5048
|
|
|
5120
5049
|
// src/pipeline/hooks.ts
|
|
5121
|
-
import * as
|
|
5122
|
-
import * as
|
|
5050
|
+
import * as fs26 from "fs";
|
|
5051
|
+
import * as path24 from "path";
|
|
5123
5052
|
function applyPreStageLabel(ctx, def) {
|
|
5124
5053
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
5125
5054
|
if (def.name === "plan") setLifecycleLabel(ctx.input.issueNumber, "planning");
|
|
@@ -5160,9 +5089,9 @@ function autoDetectComplexity(ctx, def) {
|
|
|
5160
5089
|
return { complexity, activeStages };
|
|
5161
5090
|
}
|
|
5162
5091
|
try {
|
|
5163
|
-
const taskJsonPath =
|
|
5164
|
-
if (!
|
|
5165
|
-
const raw =
|
|
5092
|
+
const taskJsonPath = path24.join(ctx.taskDir, "task.json");
|
|
5093
|
+
if (!fs26.existsSync(taskJsonPath)) return null;
|
|
5094
|
+
const raw = fs26.readFileSync(taskJsonPath, "utf-8");
|
|
5166
5095
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
5167
5096
|
const taskJson = JSON.parse(cleaned);
|
|
5168
5097
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -5192,8 +5121,8 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
5192
5121
|
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
5193
5122
|
if (ctx.input.mode === "rerun") return null;
|
|
5194
5123
|
if (!ctx.input.issueNumber) return null;
|
|
5195
|
-
const planPath =
|
|
5196
|
-
const plan =
|
|
5124
|
+
const planPath = path24.join(ctx.taskDir, "plan.md");
|
|
5125
|
+
const plan = fs26.existsSync(planPath) ? fs26.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
|
|
5197
5126
|
try {
|
|
5198
5127
|
postComment(
|
|
5199
5128
|
ctx.input.issueNumber,
|
|
@@ -5260,22 +5189,22 @@ var init_hooks = __esm({
|
|
|
5260
5189
|
});
|
|
5261
5190
|
|
|
5262
5191
|
// src/learning/auto-learn.ts
|
|
5263
|
-
import * as
|
|
5264
|
-
import * as
|
|
5192
|
+
import * as fs27 from "fs";
|
|
5193
|
+
import * as path25 from "path";
|
|
5265
5194
|
function stripAnsi(str) {
|
|
5266
5195
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
5267
5196
|
}
|
|
5268
5197
|
function autoLearn(ctx) {
|
|
5269
5198
|
try {
|
|
5270
|
-
const memoryDir =
|
|
5271
|
-
if (!
|
|
5272
|
-
|
|
5199
|
+
const memoryDir = path25.join(ctx.projectDir, ".kody", "memory");
|
|
5200
|
+
if (!fs27.existsSync(memoryDir)) {
|
|
5201
|
+
fs27.mkdirSync(memoryDir, { recursive: true });
|
|
5273
5202
|
}
|
|
5274
5203
|
const learnings = [];
|
|
5275
5204
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5276
|
-
const verifyPath =
|
|
5277
|
-
if (
|
|
5278
|
-
const verify = stripAnsi(
|
|
5205
|
+
const verifyPath = path25.join(ctx.taskDir, "verify.md");
|
|
5206
|
+
if (fs27.existsSync(verifyPath)) {
|
|
5207
|
+
const verify = stripAnsi(fs27.readFileSync(verifyPath, "utf-8"));
|
|
5279
5208
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
5280
5209
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
5281
5210
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -5284,18 +5213,18 @@ function autoLearn(ctx) {
|
|
|
5284
5213
|
if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
|
|
5285
5214
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
5286
5215
|
}
|
|
5287
|
-
const reviewPath =
|
|
5288
|
-
if (
|
|
5289
|
-
const review =
|
|
5216
|
+
const reviewPath = path25.join(ctx.taskDir, "review.md");
|
|
5217
|
+
if (fs27.existsSync(reviewPath)) {
|
|
5218
|
+
const review = fs27.readFileSync(reviewPath, "utf-8");
|
|
5290
5219
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
5291
5220
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
5292
5221
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
5293
5222
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
5294
5223
|
}
|
|
5295
|
-
const taskJsonPath =
|
|
5296
|
-
if (
|
|
5224
|
+
const taskJsonPath = path25.join(ctx.taskDir, "task.json");
|
|
5225
|
+
if (fs27.existsSync(taskJsonPath)) {
|
|
5297
5226
|
try {
|
|
5298
|
-
const raw = stripAnsi(
|
|
5227
|
+
const raw = stripAnsi(fs27.readFileSync(taskJsonPath, "utf-8"));
|
|
5299
5228
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
5300
5229
|
const task = JSON.parse(cleaned);
|
|
5301
5230
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -5306,12 +5235,12 @@ function autoLearn(ctx) {
|
|
|
5306
5235
|
}
|
|
5307
5236
|
}
|
|
5308
5237
|
if (learnings.length > 0) {
|
|
5309
|
-
const conventionsPath =
|
|
5238
|
+
const conventionsPath = path25.join(memoryDir, "conventions.md");
|
|
5310
5239
|
const entry = `
|
|
5311
5240
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
5312
5241
|
${learnings.join("\n")}
|
|
5313
5242
|
`;
|
|
5314
|
-
|
|
5243
|
+
fs27.appendFileSync(conventionsPath, entry);
|
|
5315
5244
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
5316
5245
|
}
|
|
5317
5246
|
autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
|
|
@@ -5319,8 +5248,8 @@ ${learnings.join("\n")}
|
|
|
5319
5248
|
}
|
|
5320
5249
|
}
|
|
5321
5250
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
5322
|
-
const archPath =
|
|
5323
|
-
if (
|
|
5251
|
+
const archPath = path25.join(memoryDir, "architecture.md");
|
|
5252
|
+
if (fs27.existsSync(archPath)) return;
|
|
5324
5253
|
const detected = detectArchitectureBasic(projectDir);
|
|
5325
5254
|
if (detected.length > 0) {
|
|
5326
5255
|
const content = `# Architecture (auto-detected ${timestamp2})
|
|
@@ -5328,7 +5257,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
5328
5257
|
## Overview
|
|
5329
5258
|
${detected.join("\n")}
|
|
5330
5259
|
`;
|
|
5331
|
-
|
|
5260
|
+
fs27.writeFileSync(archPath, content);
|
|
5332
5261
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
5333
5262
|
}
|
|
5334
5263
|
}
|
|
@@ -5341,13 +5270,13 @@ var init_auto_learn = __esm({
|
|
|
5341
5270
|
});
|
|
5342
5271
|
|
|
5343
5272
|
// src/retrospective.ts
|
|
5344
|
-
import * as
|
|
5345
|
-
import * as
|
|
5273
|
+
import * as fs28 from "fs";
|
|
5274
|
+
import * as path26 from "path";
|
|
5346
5275
|
function readArtifact(taskDir, filename, maxChars) {
|
|
5347
|
-
const p =
|
|
5348
|
-
if (!
|
|
5276
|
+
const p = path26.join(taskDir, filename);
|
|
5277
|
+
if (!fs28.existsSync(p)) return null;
|
|
5349
5278
|
try {
|
|
5350
|
-
const content =
|
|
5279
|
+
const content = fs28.readFileSync(p, "utf-8");
|
|
5351
5280
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
5352
5281
|
} catch {
|
|
5353
5282
|
return null;
|
|
@@ -5400,13 +5329,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
|
|
|
5400
5329
|
return lines.join("\n");
|
|
5401
5330
|
}
|
|
5402
5331
|
function getLogPath(projectDir) {
|
|
5403
|
-
return
|
|
5332
|
+
return path26.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
5404
5333
|
}
|
|
5405
5334
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
5406
5335
|
const logPath = getLogPath(projectDir);
|
|
5407
|
-
if (!
|
|
5336
|
+
if (!fs28.existsSync(logPath)) return [];
|
|
5408
5337
|
try {
|
|
5409
|
-
const content =
|
|
5338
|
+
const content = fs28.readFileSync(logPath, "utf-8");
|
|
5410
5339
|
const lines = content.split("\n").filter(Boolean);
|
|
5411
5340
|
const entries = [];
|
|
5412
5341
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -5433,11 +5362,11 @@ function formatPreviousEntries(entries) {
|
|
|
5433
5362
|
}
|
|
5434
5363
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
5435
5364
|
const logPath = getLogPath(projectDir);
|
|
5436
|
-
const dir =
|
|
5437
|
-
if (!
|
|
5438
|
-
|
|
5365
|
+
const dir = path26.dirname(logPath);
|
|
5366
|
+
if (!fs28.existsSync(dir)) {
|
|
5367
|
+
fs28.mkdirSync(dir, { recursive: true });
|
|
5439
5368
|
}
|
|
5440
|
-
|
|
5369
|
+
fs28.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
5441
5370
|
}
|
|
5442
5371
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
5443
5372
|
if (ctx.input.dryRun) return;
|
|
@@ -5604,9 +5533,75 @@ var init_summary = __esm({
|
|
|
5604
5533
|
}
|
|
5605
5534
|
});
|
|
5606
5535
|
|
|
5536
|
+
// src/tools.ts
|
|
5537
|
+
import * as fs29 from "fs";
|
|
5538
|
+
import * as path27 from "path";
|
|
5539
|
+
import { execSync as execSync3 } from "child_process";
|
|
5540
|
+
import { parse as parseYaml } from "yaml";
|
|
5541
|
+
function loadToolDeclarations(projectDir) {
|
|
5542
|
+
const toolsPath = path27.join(projectDir, ".kody", "tools.yml");
|
|
5543
|
+
if (!fs29.existsSync(toolsPath)) return [];
|
|
5544
|
+
try {
|
|
5545
|
+
const raw = fs29.readFileSync(toolsPath, "utf-8");
|
|
5546
|
+
const parsed = parseYaml(raw);
|
|
5547
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
5548
|
+
return Object.entries(parsed).map(([name, value]) => {
|
|
5549
|
+
const v = value;
|
|
5550
|
+
return {
|
|
5551
|
+
name,
|
|
5552
|
+
detect: Array.isArray(v.detect) ? v.detect : [],
|
|
5553
|
+
stages: Array.isArray(v.stages) ? v.stages : [],
|
|
5554
|
+
setup: typeof v.setup === "string" ? v.setup : ""
|
|
5555
|
+
};
|
|
5556
|
+
});
|
|
5557
|
+
} catch (err) {
|
|
5558
|
+
logger.warn(`Failed to parse .kody/tools.yml: ${err instanceof Error ? err.message : String(err)}`);
|
|
5559
|
+
return [];
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
function detectTools(declarations, projectDir) {
|
|
5563
|
+
const resolved = [];
|
|
5564
|
+
for (const decl of declarations) {
|
|
5565
|
+
const detected = decl.detect.some((pattern) => fs29.existsSync(path27.join(projectDir, pattern)));
|
|
5566
|
+
if (!detected) continue;
|
|
5567
|
+
resolved.push({
|
|
5568
|
+
name: decl.name,
|
|
5569
|
+
stages: decl.stages,
|
|
5570
|
+
setup: decl.setup
|
|
5571
|
+
});
|
|
5572
|
+
}
|
|
5573
|
+
return resolved;
|
|
5574
|
+
}
|
|
5575
|
+
function runToolSetup(tools, projectDir) {
|
|
5576
|
+
for (const tool of tools) {
|
|
5577
|
+
if (tool.setup) {
|
|
5578
|
+
try {
|
|
5579
|
+
logger.info(` Setting up ${tool.name}: ${tool.setup}`);
|
|
5580
|
+
execSync3(tool.setup, { cwd: projectDir, timeout: 12e4, stdio: "pipe" });
|
|
5581
|
+
logger.info(` \u2713 ${tool.name} setup complete`);
|
|
5582
|
+
} catch (err) {
|
|
5583
|
+
logger.warn(` \u26A0 ${tool.name} setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
try {
|
|
5587
|
+
logger.info(` Installing skill for ${tool.name} from skills.sh`);
|
|
5588
|
+
execSync3(`npx skills add --skill ${tool.name} --yes`, { cwd: projectDir, timeout: 6e4, stdio: "pipe" });
|
|
5589
|
+
logger.info(` \u2713 ${tool.name} skill installed`);
|
|
5590
|
+
} catch (err) {
|
|
5591
|
+
logger.warn(` \u26A0 ${tool.name} skill install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
var init_tools = __esm({
|
|
5596
|
+
"src/tools.ts"() {
|
|
5597
|
+
"use strict";
|
|
5598
|
+
init_logger();
|
|
5599
|
+
}
|
|
5600
|
+
});
|
|
5601
|
+
|
|
5607
5602
|
// src/pipeline.ts
|
|
5608
|
-
import * as
|
|
5609
|
-
import * as
|
|
5603
|
+
import * as fs30 from "fs";
|
|
5604
|
+
import * as path28 from "path";
|
|
5610
5605
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
5611
5606
|
if (ctx.input.dryRun) return;
|
|
5612
5607
|
if (ctx.input.prNumber) {
|
|
@@ -5619,8 +5614,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
5619
5614
|
}
|
|
5620
5615
|
if (!ctx.input.issueNumber) return;
|
|
5621
5616
|
try {
|
|
5622
|
-
const taskMdPath =
|
|
5623
|
-
const title =
|
|
5617
|
+
const taskMdPath = path28.join(ctx.taskDir, "task.md");
|
|
5618
|
+
const title = fs30.existsSync(taskMdPath) ? fs30.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
5624
5619
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
5625
5620
|
syncWithDefault(ctx.projectDir);
|
|
5626
5621
|
} catch (err) {
|
|
@@ -5634,10 +5629,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
5634
5629
|
}
|
|
5635
5630
|
}
|
|
5636
5631
|
function acquireLock(taskDir) {
|
|
5637
|
-
const lockPath =
|
|
5638
|
-
if (
|
|
5632
|
+
const lockPath = path28.join(taskDir, ".lock");
|
|
5633
|
+
if (fs30.existsSync(lockPath)) {
|
|
5639
5634
|
try {
|
|
5640
|
-
const pid = parseInt(
|
|
5635
|
+
const pid = parseInt(fs30.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
5641
5636
|
if (!isNaN(pid)) {
|
|
5642
5637
|
try {
|
|
5643
5638
|
process.kill(pid, 0);
|
|
@@ -5654,14 +5649,14 @@ function acquireLock(taskDir) {
|
|
|
5654
5649
|
logger.warn(` Corrupt lock file \u2014 overwriting`);
|
|
5655
5650
|
}
|
|
5656
5651
|
try {
|
|
5657
|
-
|
|
5652
|
+
fs30.unlinkSync(lockPath);
|
|
5658
5653
|
} catch {
|
|
5659
5654
|
}
|
|
5660
5655
|
}
|
|
5661
5656
|
try {
|
|
5662
|
-
const fd =
|
|
5663
|
-
|
|
5664
|
-
|
|
5657
|
+
const fd = fs30.openSync(lockPath, fs30.constants.O_WRONLY | fs30.constants.O_CREAT | fs30.constants.O_EXCL);
|
|
5658
|
+
fs30.writeSync(fd, String(process.pid));
|
|
5659
|
+
fs30.closeSync(fd);
|
|
5665
5660
|
} catch (err) {
|
|
5666
5661
|
if (err.code === "EEXIST") {
|
|
5667
5662
|
throw new Error("Pipeline already running (lock acquired by another process)");
|
|
@@ -5671,7 +5666,7 @@ function acquireLock(taskDir) {
|
|
|
5671
5666
|
}
|
|
5672
5667
|
function releaseLock(taskDir) {
|
|
5673
5668
|
try {
|
|
5674
|
-
|
|
5669
|
+
fs30.unlinkSync(path28.join(taskDir, ".lock"));
|
|
5675
5670
|
} catch {
|
|
5676
5671
|
}
|
|
5677
5672
|
}
|
|
@@ -5885,8 +5880,8 @@ var init_pipeline = __esm({
|
|
|
5885
5880
|
});
|
|
5886
5881
|
|
|
5887
5882
|
// src/preflight.ts
|
|
5888
|
-
import { execFileSync as
|
|
5889
|
-
import * as
|
|
5883
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
5884
|
+
import * as fs31 from "fs";
|
|
5890
5885
|
function check(name, fn) {
|
|
5891
5886
|
try {
|
|
5892
5887
|
const detail = fn() ?? void 0;
|
|
@@ -5898,7 +5893,7 @@ function check(name, fn) {
|
|
|
5898
5893
|
function runPreflight() {
|
|
5899
5894
|
const checks = [
|
|
5900
5895
|
check("claude CLI", () => {
|
|
5901
|
-
const v =
|
|
5896
|
+
const v = execFileSync15("claude", ["--version"], {
|
|
5902
5897
|
encoding: "utf-8",
|
|
5903
5898
|
timeout: 1e4,
|
|
5904
5899
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5906,14 +5901,14 @@ function runPreflight() {
|
|
|
5906
5901
|
return v;
|
|
5907
5902
|
}),
|
|
5908
5903
|
check("git repo", () => {
|
|
5909
|
-
|
|
5904
|
+
execFileSync15("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
5910
5905
|
encoding: "utf-8",
|
|
5911
5906
|
timeout: 5e3,
|
|
5912
5907
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5913
5908
|
});
|
|
5914
5909
|
}),
|
|
5915
5910
|
check("pnpm", () => {
|
|
5916
|
-
const v =
|
|
5911
|
+
const v = execFileSync15("pnpm", ["--version"], {
|
|
5917
5912
|
encoding: "utf-8",
|
|
5918
5913
|
timeout: 5e3,
|
|
5919
5914
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5921,7 +5916,7 @@ function runPreflight() {
|
|
|
5921
5916
|
return v;
|
|
5922
5917
|
}),
|
|
5923
5918
|
check("node >= 18", () => {
|
|
5924
|
-
const v =
|
|
5919
|
+
const v = execFileSync15("node", ["--version"], {
|
|
5925
5920
|
encoding: "utf-8",
|
|
5926
5921
|
timeout: 5e3,
|
|
5927
5922
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5931,7 +5926,7 @@ function runPreflight() {
|
|
|
5931
5926
|
return v;
|
|
5932
5927
|
}),
|
|
5933
5928
|
check("gh CLI", () => {
|
|
5934
|
-
const v =
|
|
5929
|
+
const v = execFileSync15("gh", ["--version"], {
|
|
5935
5930
|
encoding: "utf-8",
|
|
5936
5931
|
timeout: 5e3,
|
|
5937
5932
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5939,7 +5934,7 @@ function runPreflight() {
|
|
|
5939
5934
|
return v;
|
|
5940
5935
|
}),
|
|
5941
5936
|
check("package.json", () => {
|
|
5942
|
-
if (!
|
|
5937
|
+
if (!fs31.existsSync("package.json")) throw new Error("not found");
|
|
5943
5938
|
})
|
|
5944
5939
|
];
|
|
5945
5940
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -6016,8 +6011,8 @@ var init_args = __esm({
|
|
|
6016
6011
|
});
|
|
6017
6012
|
|
|
6018
6013
|
// src/cli/task-state.ts
|
|
6019
|
-
import * as
|
|
6020
|
-
import * as
|
|
6014
|
+
import * as fs32 from "fs";
|
|
6015
|
+
import * as path29 from "path";
|
|
6021
6016
|
function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
6022
6017
|
if (!existingTaskId || !existingState) {
|
|
6023
6018
|
return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
|
|
@@ -6049,11 +6044,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
|
6049
6044
|
function resolveForIssue(issueNumber, projectDir) {
|
|
6050
6045
|
const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
|
|
6051
6046
|
if (existingTaskId) {
|
|
6052
|
-
const statusPath =
|
|
6047
|
+
const statusPath = path29.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
6053
6048
|
let existingState = null;
|
|
6054
|
-
if (
|
|
6049
|
+
if (fs32.existsSync(statusPath)) {
|
|
6055
6050
|
try {
|
|
6056
|
-
existingState = JSON.parse(
|
|
6051
|
+
existingState = JSON.parse(fs32.readFileSync(statusPath, "utf-8"));
|
|
6057
6052
|
} catch {
|
|
6058
6053
|
}
|
|
6059
6054
|
}
|
|
@@ -6086,12 +6081,12 @@ var resolve_exports = {};
|
|
|
6086
6081
|
__export(resolve_exports, {
|
|
6087
6082
|
runResolve: () => runResolve
|
|
6088
6083
|
});
|
|
6089
|
-
import { execFileSync as
|
|
6084
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
6090
6085
|
function getConflictContext(cwd, files) {
|
|
6091
6086
|
const parts = [];
|
|
6092
6087
|
for (const file of files.slice(0, 10)) {
|
|
6093
6088
|
try {
|
|
6094
|
-
const content =
|
|
6089
|
+
const content = execFileSync16("git", ["diff", file], {
|
|
6095
6090
|
cwd,
|
|
6096
6091
|
encoding: "utf-8",
|
|
6097
6092
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -6210,8 +6205,8 @@ var init_resolve = __esm({
|
|
|
6210
6205
|
|
|
6211
6206
|
// src/entry.ts
|
|
6212
6207
|
var entry_exports = {};
|
|
6213
|
-
import * as
|
|
6214
|
-
import * as
|
|
6208
|
+
import * as fs33 from "fs";
|
|
6209
|
+
import * as path30 from "path";
|
|
6215
6210
|
async function ensureLitellmProxy(config, projectDir) {
|
|
6216
6211
|
if (!anyStageNeedsProxy(config)) return null;
|
|
6217
6212
|
const litellmUrl = getLitellmUrl();
|
|
@@ -6266,9 +6261,9 @@ async function runModelHealthCheck(config) {
|
|
|
6266
6261
|
}
|
|
6267
6262
|
async function main() {
|
|
6268
6263
|
const input = parseArgs();
|
|
6269
|
-
const projectDir = input.cwd ?
|
|
6264
|
+
const projectDir = input.cwd ? path30.resolve(input.cwd) : process.cwd();
|
|
6270
6265
|
if (input.cwd) {
|
|
6271
|
-
if (!
|
|
6266
|
+
if (!fs33.existsSync(projectDir)) {
|
|
6272
6267
|
console.error(`--cwd path does not exist: ${projectDir}`);
|
|
6273
6268
|
process.exit(1);
|
|
6274
6269
|
}
|
|
@@ -6328,8 +6323,8 @@ async function main() {
|
|
|
6328
6323
|
process.exit(1);
|
|
6329
6324
|
}
|
|
6330
6325
|
}
|
|
6331
|
-
const taskDir =
|
|
6332
|
-
|
|
6326
|
+
const taskDir = path30.join(projectDir, ".kody", "tasks", taskId);
|
|
6327
|
+
fs33.mkdirSync(taskDir, { recursive: true });
|
|
6333
6328
|
if (input.command === "rerun" && isTaskifyRun(taskDir)) {
|
|
6334
6329
|
const marker = readTaskifyMarker(taskDir);
|
|
6335
6330
|
if (marker) {
|
|
@@ -6461,31 +6456,31 @@ async function main() {
|
|
|
6461
6456
|
logger.info("Preflight checks:");
|
|
6462
6457
|
runPreflight();
|
|
6463
6458
|
if (input.task) {
|
|
6464
|
-
|
|
6459
|
+
fs33.writeFileSync(path30.join(taskDir, "task.md"), input.task);
|
|
6465
6460
|
}
|
|
6466
|
-
const taskMdPath =
|
|
6467
|
-
if (!
|
|
6461
|
+
const taskMdPath = path30.join(taskDir, "task.md");
|
|
6462
|
+
if (!fs33.existsSync(taskMdPath) && isPRFix && input.prNumber) {
|
|
6468
6463
|
logger.info(`Fetching PR #${input.prNumber} details as task context...`);
|
|
6469
6464
|
const prDetails = getPRDetails(input.prNumber);
|
|
6470
6465
|
if (prDetails) {
|
|
6471
6466
|
const taskContent = `# ${prDetails.title}
|
|
6472
6467
|
|
|
6473
6468
|
${prDetails.body ?? ""}`;
|
|
6474
|
-
|
|
6469
|
+
fs33.writeFileSync(taskMdPath, taskContent);
|
|
6475
6470
|
logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
|
|
6476
6471
|
}
|
|
6477
|
-
} else if (!
|
|
6472
|
+
} else if (!fs33.existsSync(taskMdPath) && input.issueNumber) {
|
|
6478
6473
|
logger.info(`Fetching issue #${input.issueNumber} body as task...`);
|
|
6479
6474
|
const issue = getIssue(input.issueNumber);
|
|
6480
6475
|
if (issue) {
|
|
6481
6476
|
const taskContent = `# ${issue.title}
|
|
6482
6477
|
|
|
6483
6478
|
${issue.body ?? ""}`;
|
|
6484
|
-
|
|
6479
|
+
fs33.writeFileSync(taskMdPath, taskContent);
|
|
6485
6480
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
6486
6481
|
}
|
|
6487
6482
|
}
|
|
6488
|
-
if (!
|
|
6483
|
+
if (!fs33.existsSync(taskMdPath)) {
|
|
6489
6484
|
console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
|
|
6490
6485
|
process.exit(1);
|
|
6491
6486
|
}
|
|
@@ -6629,7 +6624,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
|
|
|
6629
6624
|
}
|
|
6630
6625
|
}
|
|
6631
6626
|
const state = await runPipeline(ctx);
|
|
6632
|
-
const files =
|
|
6627
|
+
const files = fs33.readdirSync(taskDir);
|
|
6633
6628
|
console.log(`
|
|
6634
6629
|
Artifacts in ${taskDir}:`);
|
|
6635
6630
|
for (const f of files) {
|
|
@@ -6695,8 +6690,8 @@ var init_entry = __esm({
|
|
|
6695
6690
|
});
|
|
6696
6691
|
|
|
6697
6692
|
// src/bin/cli.ts
|
|
6698
|
-
import * as
|
|
6699
|
-
import * as
|
|
6693
|
+
import * as fs34 from "fs";
|
|
6694
|
+
import * as path31 from "path";
|
|
6700
6695
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6701
6696
|
|
|
6702
6697
|
// src/bin/commands/init.ts
|
|
@@ -7068,9 +7063,9 @@ function initCommand(opts, pkgRoot) {
|
|
|
7068
7063
|
|
|
7069
7064
|
// src/bin/commands/bootstrap.ts
|
|
7070
7065
|
init_architecture_detection();
|
|
7071
|
-
import * as
|
|
7072
|
-
import * as
|
|
7073
|
-
import { execFileSync as
|
|
7066
|
+
import * as fs8 from "fs";
|
|
7067
|
+
import * as path7 from "path";
|
|
7068
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
7074
7069
|
|
|
7075
7070
|
// src/bin/qa-guide.ts
|
|
7076
7071
|
import * as fs6 from "fs";
|
|
@@ -7580,100 +7575,6 @@ Required env vars: ${discovery.envVars.join(", ")}`);
|
|
|
7580
7575
|
return result2;
|
|
7581
7576
|
}
|
|
7582
7577
|
|
|
7583
|
-
// src/bin/skills.ts
|
|
7584
|
-
import * as fs7 from "fs";
|
|
7585
|
-
import * as path6 from "path";
|
|
7586
|
-
import { execFileSync as execFileSync4 } from "child_process";
|
|
7587
|
-
var SKILL_MAPPINGS = [
|
|
7588
|
-
{
|
|
7589
|
-
detect: (deps) => "next" in deps,
|
|
7590
|
-
skills: [
|
|
7591
|
-
{ package: "vercel-labs/agent-skills@vercel-react-best-practices", label: "React best practices (Vercel)" }
|
|
7592
|
-
]
|
|
7593
|
-
},
|
|
7594
|
-
{
|
|
7595
|
-
detect: (deps) => "react" in deps && !("next" in deps),
|
|
7596
|
-
skills: [
|
|
7597
|
-
{ package: "vercel-labs/agent-skills@vercel-react-best-practices", label: "React best practices (Vercel)" }
|
|
7598
|
-
]
|
|
7599
|
-
},
|
|
7600
|
-
{
|
|
7601
|
-
detect: (deps) => FRONTEND_DEPS.some((d) => d in deps),
|
|
7602
|
-
skills: [
|
|
7603
|
-
{ package: "microsoft/playwright-cli@playwright-cli", label: "Playwright browser automation" }
|
|
7604
|
-
]
|
|
7605
|
-
}
|
|
7606
|
-
];
|
|
7607
|
-
function detectSkillsForProject(cwd) {
|
|
7608
|
-
const pkgPath = path6.join(cwd, "package.json");
|
|
7609
|
-
if (!fs7.existsSync(pkgPath)) return [];
|
|
7610
|
-
try {
|
|
7611
|
-
const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
|
|
7612
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7613
|
-
const seen = /* @__PURE__ */ new Set();
|
|
7614
|
-
const skills = [];
|
|
7615
|
-
for (const mapping of SKILL_MAPPINGS) {
|
|
7616
|
-
if (mapping.detect(allDeps)) {
|
|
7617
|
-
for (const skill of mapping.skills) {
|
|
7618
|
-
if (!seen.has(skill.package)) {
|
|
7619
|
-
seen.add(skill.package);
|
|
7620
|
-
skills.push(skill);
|
|
7621
|
-
}
|
|
7622
|
-
}
|
|
7623
|
-
}
|
|
7624
|
-
}
|
|
7625
|
-
return skills;
|
|
7626
|
-
} catch {
|
|
7627
|
-
return [];
|
|
7628
|
-
}
|
|
7629
|
-
}
|
|
7630
|
-
function installSkillsForProject(cwd) {
|
|
7631
|
-
const skills = detectSkillsForProject(cwd);
|
|
7632
|
-
if (skills.length === 0) {
|
|
7633
|
-
console.log(" \u25CB No skills to install (no frontend framework detected)");
|
|
7634
|
-
return [];
|
|
7635
|
-
}
|
|
7636
|
-
let installedSkills = {};
|
|
7637
|
-
const lockPath = path6.join(cwd, "skills-lock.json");
|
|
7638
|
-
if (fs7.existsSync(lockPath)) {
|
|
7639
|
-
try {
|
|
7640
|
-
const lock = JSON.parse(fs7.readFileSync(lockPath, "utf-8"));
|
|
7641
|
-
installedSkills = lock.skills ?? {};
|
|
7642
|
-
} catch {
|
|
7643
|
-
}
|
|
7644
|
-
}
|
|
7645
|
-
const installedPaths = [];
|
|
7646
|
-
for (const skill of skills) {
|
|
7647
|
-
const skillName = skill.package.split("@").pop() ?? "";
|
|
7648
|
-
if (skillName in installedSkills) {
|
|
7649
|
-
console.log(` \u25CB ${skill.label} \u2014 already installed`);
|
|
7650
|
-
const agentPath = `.agents/skills/${skillName}`;
|
|
7651
|
-
const claudePath = `.claude/skills/${skillName}`;
|
|
7652
|
-
if (fs7.existsSync(path6.join(cwd, agentPath))) installedPaths.push(agentPath);
|
|
7653
|
-
if (fs7.existsSync(path6.join(cwd, claudePath))) installedPaths.push(claudePath);
|
|
7654
|
-
continue;
|
|
7655
|
-
}
|
|
7656
|
-
try {
|
|
7657
|
-
console.log(` Installing: ${skill.label} (${skill.package})`);
|
|
7658
|
-
execFileSync4("npx", ["skills", "add", skill.package, "--yes"], {
|
|
7659
|
-
cwd,
|
|
7660
|
-
encoding: "utf-8",
|
|
7661
|
-
timeout: 6e4,
|
|
7662
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
7663
|
-
});
|
|
7664
|
-
const installedName = skill.package.split("@").pop() ?? "";
|
|
7665
|
-
const agentPath = `.agents/skills/${installedName}`;
|
|
7666
|
-
const claudePath = `.claude/skills/${installedName}`;
|
|
7667
|
-
if (fs7.existsSync(path6.join(cwd, agentPath))) installedPaths.push(agentPath);
|
|
7668
|
-
if (fs7.existsSync(path6.join(cwd, claudePath))) installedPaths.push(claudePath);
|
|
7669
|
-
console.log(` \u2713 ${skill.label}`);
|
|
7670
|
-
} catch {
|
|
7671
|
-
console.log(` \u2717 ${skill.label} \u2014 failed to install`);
|
|
7672
|
-
}
|
|
7673
|
-
}
|
|
7674
|
-
return installedPaths;
|
|
7675
|
-
}
|
|
7676
|
-
|
|
7677
7578
|
// src/bin/commands/bootstrap.ts
|
|
7678
7579
|
init_config();
|
|
7679
7580
|
|
|
@@ -7703,20 +7604,20 @@ ${content}
|
|
|
7703
7604
|
// src/bin/commands/bootstrap.ts
|
|
7704
7605
|
var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
|
|
7705
7606
|
function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
7706
|
-
const srcDir =
|
|
7707
|
-
const baseDir =
|
|
7607
|
+
const srcDir = path7.join(cwd, "src");
|
|
7608
|
+
const baseDir = fs8.existsSync(srcDir) ? srcDir : cwd;
|
|
7708
7609
|
const results = [];
|
|
7709
7610
|
function walk(dir) {
|
|
7710
7611
|
const entries = [];
|
|
7711
7612
|
try {
|
|
7712
|
-
for (const entry of
|
|
7613
|
+
for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
7713
7614
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
7714
|
-
const full =
|
|
7615
|
+
const full = path7.join(dir, entry.name);
|
|
7715
7616
|
if (entry.isDirectory()) {
|
|
7716
7617
|
entries.push(...walk(full));
|
|
7717
7618
|
} else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
|
|
7718
7619
|
try {
|
|
7719
|
-
const stat =
|
|
7620
|
+
const stat = fs8.statSync(full);
|
|
7720
7621
|
if (stat.size >= 200 && stat.size <= 5e3) {
|
|
7721
7622
|
entries.push({ filePath: full, size: stat.size });
|
|
7722
7623
|
}
|
|
@@ -7730,8 +7631,8 @@ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
|
7730
7631
|
}
|
|
7731
7632
|
const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
|
|
7732
7633
|
for (const { filePath } of files) {
|
|
7733
|
-
const rel =
|
|
7734
|
-
const content =
|
|
7634
|
+
const rel = path7.relative(cwd, filePath);
|
|
7635
|
+
const content = fs8.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
|
|
7735
7636
|
results.push(`### File: ${rel}
|
|
7736
7637
|
\`\`\`typescript
|
|
7737
7638
|
${content}
|
|
@@ -7743,9 +7644,9 @@ function ghComment(issueNumber, body, cwd) {
|
|
|
7743
7644
|
try {
|
|
7744
7645
|
let repoSlug = "";
|
|
7745
7646
|
try {
|
|
7746
|
-
const configPath =
|
|
7747
|
-
if (
|
|
7748
|
-
const config = JSON.parse(
|
|
7647
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
7648
|
+
if (fs8.existsSync(configPath)) {
|
|
7649
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
7749
7650
|
if (config.github?.owner && config.github?.repo) {
|
|
7750
7651
|
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
7751
7652
|
}
|
|
@@ -7753,7 +7654,7 @@ function ghComment(issueNumber, body, cwd) {
|
|
|
7753
7654
|
} catch {
|
|
7754
7655
|
}
|
|
7755
7656
|
if (!repoSlug) return;
|
|
7756
|
-
|
|
7657
|
+
execFileSync4("gh", [
|
|
7757
7658
|
"issue",
|
|
7758
7659
|
"comment",
|
|
7759
7660
|
String(issueNumber),
|
|
@@ -7782,8 +7683,8 @@ function bootstrapCommand(opts, pkgRoot) {
|
|
|
7782
7683
|
ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
|
|
7783
7684
|
}
|
|
7784
7685
|
const readIfExists = (rel, maxChars = 3e3) => {
|
|
7785
|
-
const p =
|
|
7786
|
-
if (
|
|
7686
|
+
const p = path7.join(cwd, rel);
|
|
7687
|
+
if (fs8.existsSync(p)) return fs8.readFileSync(p, "utf-8").slice(0, maxChars);
|
|
7787
7688
|
return null;
|
|
7788
7689
|
};
|
|
7789
7690
|
let repoContext = "";
|
|
@@ -7818,14 +7719,14 @@ ${sampleFiles}
|
|
|
7818
7719
|
|
|
7819
7720
|
`;
|
|
7820
7721
|
try {
|
|
7821
|
-
const topDirs =
|
|
7722
|
+
const topDirs = fs8.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
7822
7723
|
repoContext += `## Top-level directories
|
|
7823
7724
|
${topDirs.join(", ")}
|
|
7824
7725
|
|
|
7825
7726
|
`;
|
|
7826
|
-
const srcDir =
|
|
7827
|
-
if (
|
|
7828
|
-
const srcDirs =
|
|
7727
|
+
const srcDir = path7.join(cwd, "src");
|
|
7728
|
+
if (fs8.existsSync(srcDir)) {
|
|
7729
|
+
const srcDirs = fs8.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
7829
7730
|
if (srcDirs.length > 0) repoContext += `## src/ subdirectories
|
|
7830
7731
|
${srcDirs.join(", ")}
|
|
7831
7732
|
|
|
@@ -7835,19 +7736,19 @@ ${srcDirs.join(", ")}
|
|
|
7835
7736
|
}
|
|
7836
7737
|
const existingFiles = [];
|
|
7837
7738
|
for (const f of [".env.example", "CLAUDE.md", ".ai-docs", "vitest.config.ts", "vitest.config.mts", "jest.config.ts", "playwright.config.ts", ".eslintrc.js", "eslint.config.mjs", ".prettierrc"]) {
|
|
7838
|
-
if (
|
|
7739
|
+
if (fs8.existsSync(path7.join(cwd, f))) existingFiles.push(f);
|
|
7839
7740
|
}
|
|
7840
7741
|
if (existingFiles.length) repoContext += `## Config files present
|
|
7841
7742
|
${existingFiles.join(", ")}
|
|
7842
7743
|
|
|
7843
7744
|
`;
|
|
7844
7745
|
console.log("\u2500\u2500 Project Memory \u2500\u2500");
|
|
7845
|
-
const memoryDir =
|
|
7846
|
-
|
|
7847
|
-
const archPath =
|
|
7848
|
-
const conventionsPath =
|
|
7849
|
-
const existingArch =
|
|
7850
|
-
const existingConv =
|
|
7746
|
+
const memoryDir = path7.join(cwd, ".kody", "memory");
|
|
7747
|
+
fs8.mkdirSync(memoryDir, { recursive: true });
|
|
7748
|
+
const archPath = path7.join(memoryDir, "architecture.md");
|
|
7749
|
+
const conventionsPath = path7.join(memoryDir, "conventions.md");
|
|
7750
|
+
const existingArch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
|
|
7751
|
+
const existingConv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
|
|
7851
7752
|
const hasExisting = !!(existingArch || existingConv);
|
|
7852
7753
|
const extendInstruction = hasExisting && !opts.force ? buildExtendInstruction(
|
|
7853
7754
|
`### architecture.md:
|
|
@@ -7882,7 +7783,7 @@ Output ONLY valid JSON. No markdown fences. No explanation.
|
|
|
7882
7783
|
${repoContext}`;
|
|
7883
7784
|
console.log(" \u23F3 Analyzing project...");
|
|
7884
7785
|
try {
|
|
7885
|
-
const output =
|
|
7786
|
+
const output = execFileSync4("claude", [
|
|
7886
7787
|
"--print",
|
|
7887
7788
|
"--model",
|
|
7888
7789
|
bootstrapModel,
|
|
@@ -7897,12 +7798,12 @@ ${repoContext}`;
|
|
|
7897
7798
|
const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
7898
7799
|
const parsed = JSON.parse(cleaned);
|
|
7899
7800
|
if (parsed.architecture) {
|
|
7900
|
-
|
|
7801
|
+
fs8.writeFileSync(archPath, parsed.architecture);
|
|
7901
7802
|
const lineCount = parsed.architecture.split("\n").length;
|
|
7902
7803
|
console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
|
|
7903
7804
|
}
|
|
7904
7805
|
if (parsed.conventions) {
|
|
7905
|
-
|
|
7806
|
+
fs8.writeFileSync(conventionsPath, parsed.conventions);
|
|
7906
7807
|
const lineCount = parsed.conventions.split("\n").length;
|
|
7907
7808
|
console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
|
|
7908
7809
|
}
|
|
@@ -7911,37 +7812,37 @@ ${repoContext}`;
|
|
|
7911
7812
|
const detected = detectArchitectureBasic(cwd);
|
|
7912
7813
|
if (detected.length > 0) {
|
|
7913
7814
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7914
|
-
|
|
7815
|
+
fs8.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
|
|
7915
7816
|
|
|
7916
7817
|
## Overview
|
|
7917
7818
|
${detected.join("\n")}
|
|
7918
7819
|
`);
|
|
7919
7820
|
console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
|
|
7920
7821
|
}
|
|
7921
|
-
|
|
7822
|
+
fs8.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
|
|
7922
7823
|
console.log(" \u2713 .kody/memory/conventions.md (seed)");
|
|
7923
7824
|
}
|
|
7924
7825
|
console.log("\n\u2500\u2500 Step Files \u2500\u2500");
|
|
7925
|
-
const stepsDir =
|
|
7926
|
-
|
|
7927
|
-
const arch =
|
|
7928
|
-
const conv =
|
|
7826
|
+
const stepsDir = path7.join(cwd, ".kody", "steps");
|
|
7827
|
+
fs8.mkdirSync(stepsDir, { recursive: true });
|
|
7828
|
+
const arch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
|
|
7829
|
+
const conv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
|
|
7929
7830
|
console.log(" \u23F3 Customizing step files...");
|
|
7930
7831
|
let stepCount = 0;
|
|
7931
7832
|
for (const stage of STEP_STAGES) {
|
|
7932
|
-
const templatePath =
|
|
7933
|
-
if (!
|
|
7833
|
+
const templatePath = path7.join(pkgRoot, "prompts", `${stage}.md`);
|
|
7834
|
+
if (!fs8.existsSync(templatePath)) {
|
|
7934
7835
|
console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
|
|
7935
7836
|
continue;
|
|
7936
7837
|
}
|
|
7937
|
-
const stepOutputPath =
|
|
7938
|
-
const existingStep =
|
|
7838
|
+
const stepOutputPath = path7.join(stepsDir, `${stage}.md`);
|
|
7839
|
+
const existingStep = fs8.existsSync(stepOutputPath) ? fs8.readFileSync(stepOutputPath, "utf-8") : "";
|
|
7939
7840
|
const isExtend = !!existingStep && !opts.force;
|
|
7940
|
-
const defaultPrompt =
|
|
7841
|
+
const defaultPrompt = fs8.readFileSync(templatePath, "utf-8");
|
|
7941
7842
|
const contextPlaceholder = "{{TASK_CONTEXT}}";
|
|
7942
7843
|
const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
|
|
7943
7844
|
if (placeholderIdx === -1) {
|
|
7944
|
-
|
|
7845
|
+
fs8.copyFileSync(templatePath, stepOutputPath);
|
|
7945
7846
|
stepCount++;
|
|
7946
7847
|
console.log(` \u2713 ${stage}.md`);
|
|
7947
7848
|
continue;
|
|
@@ -7984,7 +7885,7 @@ ${repoContext}
|
|
|
7984
7885
|
|
|
7985
7886
|
REMINDER: Output the full prompt template first (unchanged), then your three appended sections. Do NOT include "${contextPlaceholder}".`;
|
|
7986
7887
|
try {
|
|
7987
|
-
const output =
|
|
7888
|
+
const output = execFileSync4("claude", [
|
|
7988
7889
|
"--print",
|
|
7989
7890
|
"--model",
|
|
7990
7891
|
bootstrapModel,
|
|
@@ -7999,13 +7900,13 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
7999
7900
|
let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
8000
7901
|
cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
|
|
8001
7902
|
const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
|
|
8002
|
-
|
|
7903
|
+
fs8.writeFileSync(stepOutputPath, finalPrompt);
|
|
8003
7904
|
stepCount++;
|
|
8004
7905
|
console.log(` \u2713 ${stage}.md (${isExtend ? "extended" : "generated"})`);
|
|
8005
7906
|
} catch {
|
|
8006
7907
|
if (!isExtend) {
|
|
8007
7908
|
console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
|
|
8008
|
-
|
|
7909
|
+
fs8.copyFileSync(templatePath, stepOutputPath);
|
|
8009
7910
|
stepCount++;
|
|
8010
7911
|
} else {
|
|
8011
7912
|
console.log(` \u26A0 ${stage}.md \u2014 extend failed, keeping existing`);
|
|
@@ -8014,11 +7915,11 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
8014
7915
|
}
|
|
8015
7916
|
console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
|
|
8016
7917
|
console.log("\n\u2500\u2500 QA Guide \u2500\u2500");
|
|
8017
|
-
const qaGuidePath =
|
|
7918
|
+
const qaGuidePath = path7.join(cwd, ".kody", "qa-guide.md");
|
|
8018
7919
|
const discovery = discoverQaContext(cwd);
|
|
8019
7920
|
const hasRoutes = discovery.routes.length > 0 || discovery.collections.length > 0;
|
|
8020
7921
|
if (hasRoutes) {
|
|
8021
|
-
const existingQaGuide =
|
|
7922
|
+
const existingQaGuide = fs8.existsSync(qaGuidePath) ? fs8.readFileSync(qaGuidePath, "utf-8") : "";
|
|
8022
7923
|
const isQaExtend = !!existingQaGuide && !opts.force;
|
|
8023
7924
|
const serializedDiscovery = serializeDiscoveryForLLM(discovery);
|
|
8024
7925
|
const qaExtendBlock = isQaExtend ? buildExtendInstruction(existingQaGuide, "QA guide") : "";
|
|
@@ -8087,7 +7988,7 @@ Command and URL.
|
|
|
8087
7988
|
- Output ONLY the markdown. No explanation before or after.`;
|
|
8088
7989
|
console.log(" \u23F3 Generating QA guide...");
|
|
8089
7990
|
try {
|
|
8090
|
-
const output =
|
|
7991
|
+
const output = execFileSync4("claude", [
|
|
8091
7992
|
"--print",
|
|
8092
7993
|
"--model",
|
|
8093
7994
|
bootstrapModel,
|
|
@@ -8100,12 +8001,12 @@ Command and URL.
|
|
|
8100
8001
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8101
8002
|
}).trim();
|
|
8102
8003
|
const cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
8103
|
-
|
|
8004
|
+
fs8.writeFileSync(qaGuidePath, cleaned);
|
|
8104
8005
|
console.log(` \u2713 .kody/qa-guide.md (${isQaExtend ? "extended" : "generated"}, ${discovery.routes.length} routes, ${discovery.collections.length} collections)`);
|
|
8105
8006
|
} catch {
|
|
8106
8007
|
console.log(" \u26A0 LLM QA generation failed \u2014 using template fallback");
|
|
8107
8008
|
const qaGuide = generateQaGuideFallback(discovery);
|
|
8108
|
-
|
|
8009
|
+
fs8.writeFileSync(qaGuidePath, qaGuide);
|
|
8109
8010
|
console.log(` \u2713 .kody/qa-guide.md (fallback, ${discovery.routes.length} routes)`);
|
|
8110
8011
|
}
|
|
8111
8012
|
if (discovery.loginPage) console.log(` \u2713 Login page detected: ${discovery.loginPage}`);
|
|
@@ -8120,9 +8021,9 @@ Command and URL.
|
|
|
8120
8021
|
try {
|
|
8121
8022
|
let repoSlug = "";
|
|
8122
8023
|
try {
|
|
8123
|
-
const configPath =
|
|
8124
|
-
if (
|
|
8125
|
-
const config = JSON.parse(
|
|
8024
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
8025
|
+
if (fs8.existsSync(configPath)) {
|
|
8026
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
8126
8027
|
if (config.github?.owner && config.github?.repo) {
|
|
8127
8028
|
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
8128
8029
|
}
|
|
@@ -8152,7 +8053,7 @@ Command and URL.
|
|
|
8152
8053
|
];
|
|
8153
8054
|
for (const label of labels) {
|
|
8154
8055
|
try {
|
|
8155
|
-
|
|
8056
|
+
execFileSync4("gh", [
|
|
8156
8057
|
"label",
|
|
8157
8058
|
"create",
|
|
8158
8059
|
label.name,
|
|
@@ -8172,7 +8073,7 @@ Command and URL.
|
|
|
8172
8073
|
console.log(` \u2713 ${label.name}`);
|
|
8173
8074
|
} catch {
|
|
8174
8075
|
try {
|
|
8175
|
-
|
|
8076
|
+
execFileSync4("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
|
|
8176
8077
|
cwd,
|
|
8177
8078
|
encoding: "utf-8",
|
|
8178
8079
|
timeout: 1e4,
|
|
@@ -8191,47 +8092,40 @@ Command and URL.
|
|
|
8191
8092
|
console.log(" \u25CB Label creation skipped");
|
|
8192
8093
|
}
|
|
8193
8094
|
console.log("\n\u2500\u2500 Tools \u2500\u2500");
|
|
8194
|
-
const toolsYmlPath =
|
|
8195
|
-
if (!
|
|
8095
|
+
const toolsYmlPath = path7.join(cwd, ".kody", "tools.yml");
|
|
8096
|
+
if (!fs8.existsSync(toolsYmlPath) || opts.force) {
|
|
8196
8097
|
const toolsTemplate = `# Kody Tools Configuration
|
|
8197
8098
|
# Uncomment and configure tools that your project uses.
|
|
8198
|
-
# The engine
|
|
8099
|
+
# The engine detects tools, runs setup commands, and installs matching skills from skills.sh.
|
|
8199
8100
|
#
|
|
8200
8101
|
# playwright:
|
|
8201
8102
|
# detect: ["playwright.config.ts", "playwright.config.js"]
|
|
8202
8103
|
# stages: [verify]
|
|
8203
8104
|
# setup: "npx playwright install --with-deps chromium"
|
|
8204
|
-
# skill: playwright-cli.md
|
|
8205
8105
|
`;
|
|
8206
|
-
|
|
8106
|
+
fs8.writeFileSync(toolsYmlPath, toolsTemplate);
|
|
8207
8107
|
console.log(" \u2713 .kody/tools.yml (template created)");
|
|
8208
8108
|
} else {
|
|
8209
8109
|
console.log(" \u25CB .kody/tools.yml (already exists, keeping)");
|
|
8210
8110
|
}
|
|
8211
|
-
console.log("\n\u2500\u2500 Skills \u2500\u2500");
|
|
8212
|
-
const installedSkillPaths = installSkillsForProject(cwd);
|
|
8213
8111
|
console.log("\n\u2500\u2500 Git \u2500\u2500");
|
|
8214
8112
|
const filesToCommit = [
|
|
8215
8113
|
".kody/memory/architecture.md",
|
|
8216
8114
|
".kody/memory/conventions.md",
|
|
8217
8115
|
".kody/qa-guide.md",
|
|
8218
|
-
".kody/tools.yml"
|
|
8219
|
-
|
|
8220
|
-
].filter((f) => fs9.existsSync(path8.join(cwd, f)));
|
|
8221
|
-
if (fs9.existsSync(path8.join(cwd, "skills-lock.json"))) {
|
|
8222
|
-
filesToCommit.push("skills-lock.json");
|
|
8223
|
-
}
|
|
8116
|
+
".kody/tools.yml"
|
|
8117
|
+
].filter((f) => fs8.existsSync(path7.join(cwd, f)));
|
|
8224
8118
|
for (const stage of STEP_STAGES) {
|
|
8225
8119
|
const stepFile = `.kody/steps/${stage}.md`;
|
|
8226
|
-
if (
|
|
8120
|
+
if (fs8.existsSync(path7.join(cwd, stepFile))) {
|
|
8227
8121
|
filesToCommit.push(stepFile);
|
|
8228
8122
|
}
|
|
8229
8123
|
}
|
|
8230
8124
|
if (filesToCommit.length > 0) {
|
|
8231
8125
|
try {
|
|
8232
|
-
const fullPaths = filesToCommit.map((f) =>
|
|
8126
|
+
const fullPaths = filesToCommit.map((f) => path7.join(cwd, f));
|
|
8233
8127
|
for (let pass = 0; pass < 2; pass++) {
|
|
8234
|
-
|
|
8128
|
+
execFileSync4("npx", ["prettier", "--write", ...fullPaths], {
|
|
8235
8129
|
cwd,
|
|
8236
8130
|
encoding: "utf-8",
|
|
8237
8131
|
timeout: 3e4,
|
|
@@ -8247,24 +8141,24 @@ Command and URL.
|
|
|
8247
8141
|
try {
|
|
8248
8142
|
if (isCI3) {
|
|
8249
8143
|
const branchName = `kody-bootstrap-${Date.now()}`;
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
const staged =
|
|
8144
|
+
execFileSync4("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
8145
|
+
execFileSync4("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
|
|
8146
|
+
const staged = execFileSync4("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
|
|
8253
8147
|
if (staged) {
|
|
8254
|
-
|
|
8255
|
-
|
|
8148
|
+
execFileSync4("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
|
|
8149
|
+
execFileSync4("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
|
|
8256
8150
|
console.log(` \u2713 Pushed branch: ${branchName}`);
|
|
8257
8151
|
let baseBranch = "main";
|
|
8258
8152
|
try {
|
|
8259
|
-
const configPath =
|
|
8260
|
-
if (
|
|
8261
|
-
const config = JSON.parse(
|
|
8153
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
8154
|
+
if (fs8.existsSync(configPath)) {
|
|
8155
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
8262
8156
|
baseBranch = config.git?.defaultBranch ?? "main";
|
|
8263
8157
|
}
|
|
8264
8158
|
} catch {
|
|
8265
8159
|
}
|
|
8266
8160
|
try {
|
|
8267
|
-
const prUrl =
|
|
8161
|
+
const prUrl = execFileSync4("gh", [
|
|
8268
8162
|
"pr",
|
|
8269
8163
|
"create",
|
|
8270
8164
|
"--title",
|
|
@@ -8303,13 +8197,13 @@ Create it manually.`, cwd);
|
|
|
8303
8197
|
console.log(" \u25CB No new changes to commit");
|
|
8304
8198
|
}
|
|
8305
8199
|
} else {
|
|
8306
|
-
|
|
8307
|
-
const staged =
|
|
8200
|
+
execFileSync4("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
|
|
8201
|
+
const staged = execFileSync4("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
|
|
8308
8202
|
if (staged) {
|
|
8309
|
-
|
|
8203
|
+
execFileSync4("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
|
|
8310
8204
|
console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
|
|
8311
8205
|
try {
|
|
8312
|
-
|
|
8206
|
+
execFileSync4("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
|
|
8313
8207
|
console.log(" \u2713 Pushed to origin");
|
|
8314
8208
|
} catch {
|
|
8315
8209
|
console.log(" \u25CB Push failed \u2014 run 'git push' manually");
|
|
@@ -8332,11 +8226,11 @@ Create it manually.`, cwd);
|
|
|
8332
8226
|
|
|
8333
8227
|
// src/bin/cli.ts
|
|
8334
8228
|
init_architecture_detection();
|
|
8335
|
-
var __dirname2 =
|
|
8336
|
-
var PKG_ROOT =
|
|
8229
|
+
var __dirname2 = path31.dirname(fileURLToPath2(import.meta.url));
|
|
8230
|
+
var PKG_ROOT = path31.resolve(__dirname2, "..", "..");
|
|
8337
8231
|
function getVersion() {
|
|
8338
|
-
const pkgPath =
|
|
8339
|
-
const pkg = JSON.parse(
|
|
8232
|
+
const pkgPath = path31.join(PKG_ROOT, "package.json");
|
|
8233
|
+
const pkg = JSON.parse(fs34.readFileSync(pkgPath, "utf-8"));
|
|
8340
8234
|
return pkg.version;
|
|
8341
8235
|
}
|
|
8342
8236
|
var args = process.argv.slice(2);
|
|
@@ -8364,6 +8258,5 @@ export {
|
|
|
8364
8258
|
checkGhRepoAccess,
|
|
8365
8259
|
checkGhSecret,
|
|
8366
8260
|
detectArchitectureBasic,
|
|
8367
|
-
detectBasicConfig
|
|
8368
|
-
detectSkillsForProject
|
|
8261
|
+
detectBasicConfig
|
|
8369
8262
|
};
|