@kody-ade/kody-engine-lite 0.1.127 → 0.1.128
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 +486 -621
- 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);
|
|
@@ -3918,7 +3826,7 @@ ${devServerBlock}
|
|
|
3918
3826
|
|
|
3919
3827
|
Use browser tools to navigate to pages and take snapshots to verify UI output.`;
|
|
3920
3828
|
}
|
|
3921
|
-
function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback
|
|
3829
|
+
function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
|
|
3922
3830
|
const config = getProjectConfig();
|
|
3923
3831
|
let assembled;
|
|
3924
3832
|
if (config.contextTiers?.enabled) {
|
|
@@ -3934,18 +3842,12 @@ ${prompt}` : prompt;
|
|
|
3934
3842
|
}
|
|
3935
3843
|
if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
|
|
3936
3844
|
assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
|
|
3937
|
-
const qaGuidePath =
|
|
3938
|
-
if (
|
|
3939
|
-
const qaGuide =
|
|
3845
|
+
const qaGuidePath = path16.join(projectDir, ".kody", "qa-guide.md");
|
|
3846
|
+
if (fs18.existsSync(qaGuidePath)) {
|
|
3847
|
+
const qaGuide = fs18.readFileSync(qaGuidePath, "utf-8").trim();
|
|
3940
3848
|
assembled = assembled + "\n\n" + qaGuide;
|
|
3941
3849
|
}
|
|
3942
3850
|
}
|
|
3943
|
-
if (tools?.length) {
|
|
3944
|
-
const toolSkills = getToolSkillsForStage(tools, stageName);
|
|
3945
|
-
if (toolSkills) {
|
|
3946
|
-
assembled = assembled + "\n\n" + toolSkills;
|
|
3947
|
-
}
|
|
3948
|
-
}
|
|
3949
3851
|
return assembled;
|
|
3950
3852
|
}
|
|
3951
3853
|
function buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback) {
|
|
@@ -3985,7 +3887,6 @@ var init_context = __esm({
|
|
|
3985
3887
|
init_config();
|
|
3986
3888
|
init_context_tiers();
|
|
3987
3889
|
init_mcp_config();
|
|
3988
|
-
init_tools();
|
|
3989
3890
|
MAX_TASK_CONTEXT_PLAN = 1500;
|
|
3990
3891
|
MAX_TASK_CONTEXT_SPEC = 2e3;
|
|
3991
3892
|
MAX_ACCUMULATED_CONTEXT = 4e3;
|
|
@@ -4035,8 +3936,8 @@ var init_runner_selection = __esm({
|
|
|
4035
3936
|
});
|
|
4036
3937
|
|
|
4037
3938
|
// src/stages/agent.ts
|
|
4038
|
-
import * as
|
|
4039
|
-
import * as
|
|
3939
|
+
import * as fs19 from "fs";
|
|
3940
|
+
import * as path17 from "path";
|
|
4040
3941
|
function getSessionInfo(stageName, sessions) {
|
|
4041
3942
|
const group = SESSION_GROUP[stageName];
|
|
4042
3943
|
if (!group) return void 0;
|
|
@@ -4065,7 +3966,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4065
3966
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
4066
3967
|
return { outcome: "completed", retries: 0 };
|
|
4067
3968
|
}
|
|
4068
|
-
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback
|
|
3969
|
+
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
|
|
4069
3970
|
let currentModelTier = def.modelTier;
|
|
4070
3971
|
if (ctx.input.feedback && def.name === "build") {
|
|
4071
3972
|
logger.info(` feedback: ${ctx.input.feedback.slice(0, 200)}${ctx.input.feedback.length > 200 ? "..." : ""}`);
|
|
@@ -4123,27 +4024,27 @@ async function executeAgentStage(ctx, def) {
|
|
|
4123
4024
|
}
|
|
4124
4025
|
const result2 = lastResult;
|
|
4125
4026
|
if (def.outputFile && result2.output) {
|
|
4126
|
-
|
|
4027
|
+
fs19.writeFileSync(path17.join(ctx.taskDir, def.outputFile), result2.output);
|
|
4127
4028
|
}
|
|
4128
4029
|
if (def.outputFile) {
|
|
4129
|
-
const outputPath =
|
|
4130
|
-
if (!
|
|
4131
|
-
const ext =
|
|
4132
|
-
const base =
|
|
4133
|
-
const files =
|
|
4030
|
+
const outputPath = path17.join(ctx.taskDir, def.outputFile);
|
|
4031
|
+
if (!fs19.existsSync(outputPath)) {
|
|
4032
|
+
const ext = path17.extname(def.outputFile);
|
|
4033
|
+
const base = path17.basename(def.outputFile, ext);
|
|
4034
|
+
const files = fs19.readdirSync(ctx.taskDir);
|
|
4134
4035
|
const variant = files.find(
|
|
4135
4036
|
(f) => f.startsWith(base + "-") && f.endsWith(ext)
|
|
4136
4037
|
);
|
|
4137
4038
|
if (variant) {
|
|
4138
|
-
|
|
4039
|
+
fs19.renameSync(path17.join(ctx.taskDir, variant), outputPath);
|
|
4139
4040
|
logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
|
|
4140
4041
|
}
|
|
4141
4042
|
}
|
|
4142
4043
|
}
|
|
4143
4044
|
if (def.outputFile) {
|
|
4144
|
-
const outputPath =
|
|
4145
|
-
if (
|
|
4146
|
-
const content =
|
|
4045
|
+
const outputPath = path17.join(ctx.taskDir, def.outputFile);
|
|
4046
|
+
if (fs19.existsSync(outputPath)) {
|
|
4047
|
+
const content = fs19.readFileSync(outputPath, "utf-8");
|
|
4147
4048
|
const validation = validateStageOutput(def.name, content);
|
|
4148
4049
|
if (!validation.valid) {
|
|
4149
4050
|
if (def.name === "taskify") {
|
|
@@ -4157,7 +4058,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4157
4058
|
const stripped = stripFences(retryResult.output);
|
|
4158
4059
|
const retryValidation = validateTaskJson(stripped);
|
|
4159
4060
|
if (retryValidation.valid) {
|
|
4160
|
-
|
|
4061
|
+
fs19.writeFileSync(outputPath, retryResult.output);
|
|
4161
4062
|
logger.info(` taskify retry produced valid JSON`);
|
|
4162
4063
|
} else {
|
|
4163
4064
|
logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
|
|
@@ -4170,7 +4071,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4170
4071
|
risk_level: "low",
|
|
4171
4072
|
questions: []
|
|
4172
4073
|
}, null, 2);
|
|
4173
|
-
|
|
4074
|
+
fs19.writeFileSync(outputPath, fallback);
|
|
4174
4075
|
logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
|
|
4175
4076
|
}
|
|
4176
4077
|
}
|
|
@@ -4184,7 +4085,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4184
4085
|
return { outcome: "completed", outputFile: def.outputFile, retries };
|
|
4185
4086
|
}
|
|
4186
4087
|
function appendStageContext(taskDir, stageName, output) {
|
|
4187
|
-
const contextPath =
|
|
4088
|
+
const contextPath = path17.join(taskDir, "context.md");
|
|
4188
4089
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
|
|
4189
4090
|
let summary;
|
|
4190
4091
|
if (output && output.trim()) {
|
|
@@ -4197,7 +4098,7 @@ function appendStageContext(taskDir, stageName, output) {
|
|
|
4197
4098
|
### ${stageName} (${timestamp2})
|
|
4198
4099
|
${summary}
|
|
4199
4100
|
`;
|
|
4200
|
-
|
|
4101
|
+
fs19.appendFileSync(contextPath, entry);
|
|
4201
4102
|
}
|
|
4202
4103
|
var SESSION_GROUP;
|
|
4203
4104
|
var init_agent = __esm({
|
|
@@ -4220,7 +4121,7 @@ var init_agent = __esm({
|
|
|
4220
4121
|
});
|
|
4221
4122
|
|
|
4222
4123
|
// src/verify-runner.ts
|
|
4223
|
-
import { execFileSync as
|
|
4124
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
4224
4125
|
function isExecError(err) {
|
|
4225
4126
|
return typeof err === "object" && err !== null;
|
|
4226
4127
|
}
|
|
@@ -4256,7 +4157,7 @@ function runCommand(cmd, cwd, timeout) {
|
|
|
4256
4157
|
return { success: true, output: "", timedOut: false };
|
|
4257
4158
|
}
|
|
4258
4159
|
try {
|
|
4259
|
-
const output =
|
|
4160
|
+
const output = execFileSync11(parts[0], parts.slice(1), {
|
|
4260
4161
|
cwd,
|
|
4261
4162
|
timeout,
|
|
4262
4163
|
encoding: "utf-8",
|
|
@@ -4345,7 +4246,7 @@ var init_verify_runner = __esm({
|
|
|
4345
4246
|
});
|
|
4346
4247
|
|
|
4347
4248
|
// src/observer.ts
|
|
4348
|
-
import { execFileSync as
|
|
4249
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
4349
4250
|
async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, model, options) {
|
|
4350
4251
|
const context = [
|
|
4351
4252
|
`Stage: ${stageName}`,
|
|
@@ -4428,13 +4329,13 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
|
|
|
4428
4329
|
}
|
|
4429
4330
|
function getModifiedFiles(projectDir) {
|
|
4430
4331
|
try {
|
|
4431
|
-
const staged =
|
|
4332
|
+
const staged = execFileSync12("git", ["diff", "--name-only", "--cached"], {
|
|
4432
4333
|
encoding: "utf-8",
|
|
4433
4334
|
cwd: projectDir,
|
|
4434
4335
|
timeout: 5e3,
|
|
4435
4336
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4436
4337
|
}).trim();
|
|
4437
|
-
const unstaged =
|
|
4338
|
+
const unstaged = execFileSync12("git", ["diff", "--name-only"], {
|
|
4438
4339
|
encoding: "utf-8",
|
|
4439
4340
|
cwd: projectDir,
|
|
4440
4341
|
timeout: 5e3,
|
|
@@ -4477,8 +4378,8 @@ Error context:
|
|
|
4477
4378
|
});
|
|
4478
4379
|
|
|
4479
4380
|
// src/stages/gate.ts
|
|
4480
|
-
import * as
|
|
4481
|
-
import * as
|
|
4381
|
+
import * as fs20 from "fs";
|
|
4382
|
+
import * as path18 from "path";
|
|
4482
4383
|
function executeGateStage(ctx, def) {
|
|
4483
4384
|
if (ctx.input.dryRun) {
|
|
4484
4385
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
@@ -4521,7 +4422,7 @@ ${output}
|
|
|
4521
4422
|
`);
|
|
4522
4423
|
}
|
|
4523
4424
|
}
|
|
4524
|
-
|
|
4425
|
+
fs20.writeFileSync(path18.join(ctx.taskDir, "verify.md"), lines.join(""));
|
|
4525
4426
|
return {
|
|
4526
4427
|
outcome: verifyResult.pass ? "completed" : "failed",
|
|
4527
4428
|
retries: 0
|
|
@@ -4536,9 +4437,9 @@ var init_gate = __esm({
|
|
|
4536
4437
|
});
|
|
4537
4438
|
|
|
4538
4439
|
// src/stages/verify.ts
|
|
4539
|
-
import * as
|
|
4540
|
-
import * as
|
|
4541
|
-
import { execFileSync as
|
|
4440
|
+
import * as fs21 from "fs";
|
|
4441
|
+
import * as path19 from "path";
|
|
4442
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
4542
4443
|
async function executeVerifyWithAutofix(ctx, def) {
|
|
4543
4444
|
const maxAttempts = def.maxRetries ?? 2;
|
|
4544
4445
|
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
@@ -4548,8 +4449,8 @@ async function executeVerifyWithAutofix(ctx, def) {
|
|
|
4548
4449
|
return { ...gateResult, retries: attempt };
|
|
4549
4450
|
}
|
|
4550
4451
|
if (attempt < maxAttempts) {
|
|
4551
|
-
const verifyPath =
|
|
4552
|
-
const errorOutput =
|
|
4452
|
+
const verifyPath = path19.join(ctx.taskDir, "verify.md");
|
|
4453
|
+
const errorOutput = fs21.existsSync(verifyPath) ? fs21.readFileSync(verifyPath, "utf-8") : "Unknown error";
|
|
4553
4454
|
const modifiedFiles = getModifiedFiles(ctx.projectDir);
|
|
4554
4455
|
const defaultRunner = getRunnerForStage(ctx, "taskify");
|
|
4555
4456
|
const diagConfig = getProjectConfig();
|
|
@@ -4592,7 +4493,7 @@ ${diagnosis.resolution}`);
|
|
|
4592
4493
|
const parts = parseCommand(cmd);
|
|
4593
4494
|
if (parts.length === 0) return;
|
|
4594
4495
|
try {
|
|
4595
|
-
|
|
4496
|
+
execFileSync13(parts[0], parts.slice(1), {
|
|
4596
4497
|
stdio: "pipe",
|
|
4597
4498
|
timeout: FIX_COMMAND_TIMEOUT_MS
|
|
4598
4499
|
});
|
|
@@ -4645,8 +4546,8 @@ var init_verify = __esm({
|
|
|
4645
4546
|
});
|
|
4646
4547
|
|
|
4647
4548
|
// src/review-standalone.ts
|
|
4648
|
-
import * as
|
|
4649
|
-
import * as
|
|
4549
|
+
import * as fs22 from "fs";
|
|
4550
|
+
import * as path20 from "path";
|
|
4650
4551
|
function resolveReviewTarget(input) {
|
|
4651
4552
|
if (input.prs.length === 0) {
|
|
4652
4553
|
return {
|
|
@@ -4670,8 +4571,8 @@ Or comment on the specific PR: \`@kody review\``
|
|
|
4670
4571
|
}
|
|
4671
4572
|
async function runStandaloneReview(input) {
|
|
4672
4573
|
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
4673
|
-
const taskDir =
|
|
4674
|
-
|
|
4574
|
+
const taskDir = path20.join(input.projectDir, ".kody", "tasks", taskId);
|
|
4575
|
+
fs22.mkdirSync(taskDir, { recursive: true });
|
|
4675
4576
|
let diffInstruction = "";
|
|
4676
4577
|
let filesChangedSection = "";
|
|
4677
4578
|
if (input.baseBranch) {
|
|
@@ -4698,7 +4599,7 @@ ${fileList}`;
|
|
|
4698
4599
|
const taskContent = `# ${input.prTitle}
|
|
4699
4600
|
|
|
4700
4601
|
${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
4701
|
-
|
|
4602
|
+
fs22.writeFileSync(path20.join(taskDir, "task.md"), taskContent);
|
|
4702
4603
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
4703
4604
|
const ctx = {
|
|
4704
4605
|
taskId,
|
|
@@ -4720,10 +4621,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
|
4720
4621
|
error: result2.error ?? "Review stage failed"
|
|
4721
4622
|
};
|
|
4722
4623
|
}
|
|
4723
|
-
const reviewPath =
|
|
4624
|
+
const reviewPath = path20.join(taskDir, "review.md");
|
|
4724
4625
|
let reviewContent;
|
|
4725
|
-
if (
|
|
4726
|
-
reviewContent =
|
|
4626
|
+
if (fs22.existsSync(reviewPath)) {
|
|
4627
|
+
reviewContent = fs22.readFileSync(reviewPath, "utf-8");
|
|
4727
4628
|
}
|
|
4728
4629
|
return {
|
|
4729
4630
|
outcome: "completed",
|
|
@@ -4763,8 +4664,8 @@ var init_review_standalone = __esm({
|
|
|
4763
4664
|
});
|
|
4764
4665
|
|
|
4765
4666
|
// src/stages/review.ts
|
|
4766
|
-
import * as
|
|
4767
|
-
import * as
|
|
4667
|
+
import * as fs23 from "fs";
|
|
4668
|
+
import * as path21 from "path";
|
|
4768
4669
|
async function executeReviewWithFix(ctx, def) {
|
|
4769
4670
|
if (ctx.input.dryRun) {
|
|
4770
4671
|
return { outcome: "completed", retries: 0 };
|
|
@@ -4778,11 +4679,11 @@ async function executeReviewWithFix(ctx, def) {
|
|
|
4778
4679
|
if (reviewResult.outcome !== "completed") {
|
|
4779
4680
|
return reviewResult;
|
|
4780
4681
|
}
|
|
4781
|
-
const reviewFile =
|
|
4782
|
-
if (!
|
|
4682
|
+
const reviewFile = path21.join(ctx.taskDir, "review.md");
|
|
4683
|
+
if (!fs23.existsSync(reviewFile)) {
|
|
4783
4684
|
return { outcome: "failed", retries: iteration, error: "review.md not found" };
|
|
4784
4685
|
}
|
|
4785
|
-
const content =
|
|
4686
|
+
const content = fs23.readFileSync(reviewFile, "utf-8");
|
|
4786
4687
|
if (detectReviewVerdict(content) !== "fail") {
|
|
4787
4688
|
return { ...reviewResult, retries: iteration };
|
|
4788
4689
|
}
|
|
@@ -4811,15 +4712,15 @@ var init_review = __esm({
|
|
|
4811
4712
|
});
|
|
4812
4713
|
|
|
4813
4714
|
// src/stages/ship.ts
|
|
4814
|
-
import * as
|
|
4815
|
-
import * as
|
|
4816
|
-
import { execFileSync as
|
|
4715
|
+
import * as fs24 from "fs";
|
|
4716
|
+
import * as path22 from "path";
|
|
4717
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
4817
4718
|
function buildPrBody(ctx) {
|
|
4818
4719
|
const sections = [];
|
|
4819
|
-
const taskJsonPath =
|
|
4820
|
-
if (
|
|
4720
|
+
const taskJsonPath = path22.join(ctx.taskDir, "task.json");
|
|
4721
|
+
if (fs24.existsSync(taskJsonPath)) {
|
|
4821
4722
|
try {
|
|
4822
|
-
const raw =
|
|
4723
|
+
const raw = fs24.readFileSync(taskJsonPath, "utf-8");
|
|
4823
4724
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4824
4725
|
const task = JSON.parse(cleaned);
|
|
4825
4726
|
if (task.description) {
|
|
@@ -4838,9 +4739,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
4838
4739
|
} catch {
|
|
4839
4740
|
}
|
|
4840
4741
|
}
|
|
4841
|
-
const reviewPath =
|
|
4842
|
-
if (
|
|
4843
|
-
const review =
|
|
4742
|
+
const reviewPath = path22.join(ctx.taskDir, "review.md");
|
|
4743
|
+
if (fs24.existsSync(reviewPath)) {
|
|
4744
|
+
const review = fs24.readFileSync(reviewPath, "utf-8");
|
|
4844
4745
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
4845
4746
|
if (summaryMatch) {
|
|
4846
4747
|
const summary = summaryMatch[1].trim();
|
|
@@ -4857,14 +4758,14 @@ ${summary}`);
|
|
|
4857
4758
|
**Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
|
|
4858
4759
|
}
|
|
4859
4760
|
}
|
|
4860
|
-
const verifyPath =
|
|
4861
|
-
if (
|
|
4862
|
-
const verify =
|
|
4761
|
+
const verifyPath = path22.join(ctx.taskDir, "verify.md");
|
|
4762
|
+
if (fs24.existsSync(verifyPath)) {
|
|
4763
|
+
const verify = fs24.readFileSync(verifyPath, "utf-8");
|
|
4863
4764
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
4864
4765
|
}
|
|
4865
|
-
const planPath =
|
|
4866
|
-
if (
|
|
4867
|
-
const plan =
|
|
4766
|
+
const planPath = path22.join(ctx.taskDir, "plan.md");
|
|
4767
|
+
if (fs24.existsSync(planPath)) {
|
|
4768
|
+
const plan = fs24.readFileSync(planPath, "utf-8").trim();
|
|
4868
4769
|
if (plan) {
|
|
4869
4770
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
4870
4771
|
sections.push(`
|
|
@@ -4884,25 +4785,25 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
4884
4785
|
return sections.join("\n");
|
|
4885
4786
|
}
|
|
4886
4787
|
function executeShipStage(ctx, _def) {
|
|
4887
|
-
const shipPath =
|
|
4788
|
+
const shipPath = path22.join(ctx.taskDir, "ship.md");
|
|
4888
4789
|
if (ctx.input.dryRun) {
|
|
4889
|
-
|
|
4790
|
+
fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
4890
4791
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
4891
4792
|
}
|
|
4892
4793
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
4893
|
-
|
|
4794
|
+
fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
4894
4795
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
4895
4796
|
}
|
|
4896
4797
|
try {
|
|
4897
4798
|
const head = getCurrentBranch(ctx.projectDir);
|
|
4898
4799
|
const base = getDefaultBranch(ctx.projectDir);
|
|
4899
4800
|
try {
|
|
4900
|
-
|
|
4801
|
+
execFileSync14("git", ["add", ctx.taskDir], {
|
|
4901
4802
|
cwd: ctx.projectDir,
|
|
4902
4803
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
4903
4804
|
stdio: "pipe"
|
|
4904
4805
|
});
|
|
4905
|
-
|
|
4806
|
+
execFileSync14("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
|
|
4906
4807
|
cwd: ctx.projectDir,
|
|
4907
4808
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
4908
4809
|
stdio: "pipe"
|
|
@@ -4916,7 +4817,7 @@ function executeShipStage(ctx, _def) {
|
|
|
4916
4817
|
let repo = config.github?.repo;
|
|
4917
4818
|
if (!owner || !repo) {
|
|
4918
4819
|
try {
|
|
4919
|
-
const remoteUrl =
|
|
4820
|
+
const remoteUrl = execFileSync14("git", ["remote", "get-url", "origin"], {
|
|
4920
4821
|
encoding: "utf-8",
|
|
4921
4822
|
cwd: ctx.projectDir
|
|
4922
4823
|
}).trim();
|
|
@@ -4937,28 +4838,28 @@ function executeShipStage(ctx, _def) {
|
|
|
4937
4838
|
chore: "chore"
|
|
4938
4839
|
};
|
|
4939
4840
|
let prefix = "chore";
|
|
4940
|
-
const taskJsonPath =
|
|
4941
|
-
if (
|
|
4841
|
+
const taskJsonPath = path22.join(ctx.taskDir, "task.json");
|
|
4842
|
+
if (fs24.existsSync(taskJsonPath)) {
|
|
4942
4843
|
try {
|
|
4943
|
-
const raw =
|
|
4844
|
+
const raw = fs24.readFileSync(taskJsonPath, "utf-8");
|
|
4944
4845
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4945
4846
|
const task = JSON.parse(cleaned);
|
|
4946
4847
|
prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
4947
4848
|
} catch {
|
|
4948
4849
|
}
|
|
4949
4850
|
}
|
|
4950
|
-
const taskMdPath =
|
|
4951
|
-
if (
|
|
4952
|
-
const content =
|
|
4851
|
+
const taskMdPath = path22.join(ctx.taskDir, "task.md");
|
|
4852
|
+
if (fs24.existsSync(taskMdPath)) {
|
|
4853
|
+
const content = fs24.readFileSync(taskMdPath, "utf-8");
|
|
4953
4854
|
const heading = content.split("\n").find((l) => l.startsWith("# "));
|
|
4954
4855
|
if (heading) {
|
|
4955
4856
|
title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
|
|
4956
4857
|
}
|
|
4957
4858
|
}
|
|
4958
4859
|
if (title === "Update") {
|
|
4959
|
-
if (
|
|
4860
|
+
if (fs24.existsSync(taskJsonPath)) {
|
|
4960
4861
|
try {
|
|
4961
|
-
const raw =
|
|
4862
|
+
const raw = fs24.readFileSync(taskJsonPath, "utf-8");
|
|
4962
4863
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4963
4864
|
const task = JSON.parse(cleaned);
|
|
4964
4865
|
if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
|
|
@@ -4981,7 +4882,7 @@ function executeShipStage(ctx, _def) {
|
|
|
4981
4882
|
} catch {
|
|
4982
4883
|
}
|
|
4983
4884
|
}
|
|
4984
|
-
|
|
4885
|
+
fs24.writeFileSync(shipPath, `# Ship
|
|
4985
4886
|
|
|
4986
4887
|
Updated existing PR: ${existingPr.url}
|
|
4987
4888
|
PR #${existingPr.number}
|
|
@@ -5002,20 +4903,20 @@ PR #${existingPr.number}
|
|
|
5002
4903
|
} catch {
|
|
5003
4904
|
}
|
|
5004
4905
|
}
|
|
5005
|
-
|
|
4906
|
+
fs24.writeFileSync(shipPath, `# Ship
|
|
5006
4907
|
|
|
5007
4908
|
PR created: ${pr.url}
|
|
5008
4909
|
PR #${pr.number}
|
|
5009
4910
|
`);
|
|
5010
4911
|
} else {
|
|
5011
|
-
|
|
4912
|
+
fs24.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
5012
4913
|
}
|
|
5013
4914
|
}
|
|
5014
4915
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
5015
4916
|
} catch (err) {
|
|
5016
4917
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5017
4918
|
try {
|
|
5018
|
-
|
|
4919
|
+
fs24.writeFileSync(shipPath, `# Ship
|
|
5019
4920
|
|
|
5020
4921
|
Failed: ${msg}
|
|
5021
4922
|
`);
|
|
@@ -5064,15 +4965,15 @@ var init_executor_registry = __esm({
|
|
|
5064
4965
|
});
|
|
5065
4966
|
|
|
5066
4967
|
// src/pipeline/questions.ts
|
|
5067
|
-
import * as
|
|
5068
|
-
import * as
|
|
4968
|
+
import * as fs25 from "fs";
|
|
4969
|
+
import * as path23 from "path";
|
|
5069
4970
|
function checkForQuestions(ctx, stageName) {
|
|
5070
4971
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
5071
4972
|
try {
|
|
5072
4973
|
if (stageName === "taskify") {
|
|
5073
|
-
const taskJsonPath =
|
|
5074
|
-
if (!
|
|
5075
|
-
const raw =
|
|
4974
|
+
const taskJsonPath = path23.join(ctx.taskDir, "task.json");
|
|
4975
|
+
if (!fs25.existsSync(taskJsonPath)) return false;
|
|
4976
|
+
const raw = fs25.readFileSync(taskJsonPath, "utf-8");
|
|
5076
4977
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
5077
4978
|
const taskJson = JSON.parse(cleaned);
|
|
5078
4979
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -5087,9 +4988,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
5087
4988
|
}
|
|
5088
4989
|
}
|
|
5089
4990
|
if (stageName === "plan") {
|
|
5090
|
-
const planPath =
|
|
5091
|
-
if (!
|
|
5092
|
-
const plan =
|
|
4991
|
+
const planPath = path23.join(ctx.taskDir, "plan.md");
|
|
4992
|
+
if (!fs25.existsSync(planPath)) return false;
|
|
4993
|
+
const plan = fs25.readFileSync(planPath, "utf-8");
|
|
5093
4994
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
5094
4995
|
if (questionsMatch) {
|
|
5095
4996
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -5118,8 +5019,8 @@ var init_questions = __esm({
|
|
|
5118
5019
|
});
|
|
5119
5020
|
|
|
5120
5021
|
// src/pipeline/hooks.ts
|
|
5121
|
-
import * as
|
|
5122
|
-
import * as
|
|
5022
|
+
import * as fs26 from "fs";
|
|
5023
|
+
import * as path24 from "path";
|
|
5123
5024
|
function applyPreStageLabel(ctx, def) {
|
|
5124
5025
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
5125
5026
|
if (def.name === "plan") setLifecycleLabel(ctx.input.issueNumber, "planning");
|
|
@@ -5160,9 +5061,9 @@ function autoDetectComplexity(ctx, def) {
|
|
|
5160
5061
|
return { complexity, activeStages };
|
|
5161
5062
|
}
|
|
5162
5063
|
try {
|
|
5163
|
-
const taskJsonPath =
|
|
5164
|
-
if (!
|
|
5165
|
-
const raw =
|
|
5064
|
+
const taskJsonPath = path24.join(ctx.taskDir, "task.json");
|
|
5065
|
+
if (!fs26.existsSync(taskJsonPath)) return null;
|
|
5066
|
+
const raw = fs26.readFileSync(taskJsonPath, "utf-8");
|
|
5166
5067
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
5167
5068
|
const taskJson = JSON.parse(cleaned);
|
|
5168
5069
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -5192,8 +5093,8 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
5192
5093
|
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
5193
5094
|
if (ctx.input.mode === "rerun") return null;
|
|
5194
5095
|
if (!ctx.input.issueNumber) return null;
|
|
5195
|
-
const planPath =
|
|
5196
|
-
const plan =
|
|
5096
|
+
const planPath = path24.join(ctx.taskDir, "plan.md");
|
|
5097
|
+
const plan = fs26.existsSync(planPath) ? fs26.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
|
|
5197
5098
|
try {
|
|
5198
5099
|
postComment(
|
|
5199
5100
|
ctx.input.issueNumber,
|
|
@@ -5260,22 +5161,22 @@ var init_hooks = __esm({
|
|
|
5260
5161
|
});
|
|
5261
5162
|
|
|
5262
5163
|
// src/learning/auto-learn.ts
|
|
5263
|
-
import * as
|
|
5264
|
-
import * as
|
|
5164
|
+
import * as fs27 from "fs";
|
|
5165
|
+
import * as path25 from "path";
|
|
5265
5166
|
function stripAnsi(str) {
|
|
5266
5167
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
5267
5168
|
}
|
|
5268
5169
|
function autoLearn(ctx) {
|
|
5269
5170
|
try {
|
|
5270
|
-
const memoryDir =
|
|
5271
|
-
if (!
|
|
5272
|
-
|
|
5171
|
+
const memoryDir = path25.join(ctx.projectDir, ".kody", "memory");
|
|
5172
|
+
if (!fs27.existsSync(memoryDir)) {
|
|
5173
|
+
fs27.mkdirSync(memoryDir, { recursive: true });
|
|
5273
5174
|
}
|
|
5274
5175
|
const learnings = [];
|
|
5275
5176
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5276
|
-
const verifyPath =
|
|
5277
|
-
if (
|
|
5278
|
-
const verify = stripAnsi(
|
|
5177
|
+
const verifyPath = path25.join(ctx.taskDir, "verify.md");
|
|
5178
|
+
if (fs27.existsSync(verifyPath)) {
|
|
5179
|
+
const verify = stripAnsi(fs27.readFileSync(verifyPath, "utf-8"));
|
|
5279
5180
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
5280
5181
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
5281
5182
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -5284,18 +5185,18 @@ function autoLearn(ctx) {
|
|
|
5284
5185
|
if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
|
|
5285
5186
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
5286
5187
|
}
|
|
5287
|
-
const reviewPath =
|
|
5288
|
-
if (
|
|
5289
|
-
const review =
|
|
5188
|
+
const reviewPath = path25.join(ctx.taskDir, "review.md");
|
|
5189
|
+
if (fs27.existsSync(reviewPath)) {
|
|
5190
|
+
const review = fs27.readFileSync(reviewPath, "utf-8");
|
|
5290
5191
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
5291
5192
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
5292
5193
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
5293
5194
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
5294
5195
|
}
|
|
5295
|
-
const taskJsonPath =
|
|
5296
|
-
if (
|
|
5196
|
+
const taskJsonPath = path25.join(ctx.taskDir, "task.json");
|
|
5197
|
+
if (fs27.existsSync(taskJsonPath)) {
|
|
5297
5198
|
try {
|
|
5298
|
-
const raw = stripAnsi(
|
|
5199
|
+
const raw = stripAnsi(fs27.readFileSync(taskJsonPath, "utf-8"));
|
|
5299
5200
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
5300
5201
|
const task = JSON.parse(cleaned);
|
|
5301
5202
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -5306,12 +5207,12 @@ function autoLearn(ctx) {
|
|
|
5306
5207
|
}
|
|
5307
5208
|
}
|
|
5308
5209
|
if (learnings.length > 0) {
|
|
5309
|
-
const conventionsPath =
|
|
5210
|
+
const conventionsPath = path25.join(memoryDir, "conventions.md");
|
|
5310
5211
|
const entry = `
|
|
5311
5212
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
5312
5213
|
${learnings.join("\n")}
|
|
5313
5214
|
`;
|
|
5314
|
-
|
|
5215
|
+
fs27.appendFileSync(conventionsPath, entry);
|
|
5315
5216
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
5316
5217
|
}
|
|
5317
5218
|
autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
|
|
@@ -5319,8 +5220,8 @@ ${learnings.join("\n")}
|
|
|
5319
5220
|
}
|
|
5320
5221
|
}
|
|
5321
5222
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
5322
|
-
const archPath =
|
|
5323
|
-
if (
|
|
5223
|
+
const archPath = path25.join(memoryDir, "architecture.md");
|
|
5224
|
+
if (fs27.existsSync(archPath)) return;
|
|
5324
5225
|
const detected = detectArchitectureBasic(projectDir);
|
|
5325
5226
|
if (detected.length > 0) {
|
|
5326
5227
|
const content = `# Architecture (auto-detected ${timestamp2})
|
|
@@ -5328,7 +5229,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
5328
5229
|
## Overview
|
|
5329
5230
|
${detected.join("\n")}
|
|
5330
5231
|
`;
|
|
5331
|
-
|
|
5232
|
+
fs27.writeFileSync(archPath, content);
|
|
5332
5233
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
5333
5234
|
}
|
|
5334
5235
|
}
|
|
@@ -5341,13 +5242,13 @@ var init_auto_learn = __esm({
|
|
|
5341
5242
|
});
|
|
5342
5243
|
|
|
5343
5244
|
// src/retrospective.ts
|
|
5344
|
-
import * as
|
|
5345
|
-
import * as
|
|
5245
|
+
import * as fs28 from "fs";
|
|
5246
|
+
import * as path26 from "path";
|
|
5346
5247
|
function readArtifact(taskDir, filename, maxChars) {
|
|
5347
|
-
const p =
|
|
5348
|
-
if (!
|
|
5248
|
+
const p = path26.join(taskDir, filename);
|
|
5249
|
+
if (!fs28.existsSync(p)) return null;
|
|
5349
5250
|
try {
|
|
5350
|
-
const content =
|
|
5251
|
+
const content = fs28.readFileSync(p, "utf-8");
|
|
5351
5252
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
5352
5253
|
} catch {
|
|
5353
5254
|
return null;
|
|
@@ -5400,13 +5301,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
|
|
|
5400
5301
|
return lines.join("\n");
|
|
5401
5302
|
}
|
|
5402
5303
|
function getLogPath(projectDir) {
|
|
5403
|
-
return
|
|
5304
|
+
return path26.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
5404
5305
|
}
|
|
5405
5306
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
5406
5307
|
const logPath = getLogPath(projectDir);
|
|
5407
|
-
if (!
|
|
5308
|
+
if (!fs28.existsSync(logPath)) return [];
|
|
5408
5309
|
try {
|
|
5409
|
-
const content =
|
|
5310
|
+
const content = fs28.readFileSync(logPath, "utf-8");
|
|
5410
5311
|
const lines = content.split("\n").filter(Boolean);
|
|
5411
5312
|
const entries = [];
|
|
5412
5313
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -5433,11 +5334,11 @@ function formatPreviousEntries(entries) {
|
|
|
5433
5334
|
}
|
|
5434
5335
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
5435
5336
|
const logPath = getLogPath(projectDir);
|
|
5436
|
-
const dir =
|
|
5437
|
-
if (!
|
|
5438
|
-
|
|
5337
|
+
const dir = path26.dirname(logPath);
|
|
5338
|
+
if (!fs28.existsSync(dir)) {
|
|
5339
|
+
fs28.mkdirSync(dir, { recursive: true });
|
|
5439
5340
|
}
|
|
5440
|
-
|
|
5341
|
+
fs28.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
5441
5342
|
}
|
|
5442
5343
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
5443
5344
|
if (ctx.input.dryRun) return;
|
|
@@ -5604,9 +5505,75 @@ var init_summary = __esm({
|
|
|
5604
5505
|
}
|
|
5605
5506
|
});
|
|
5606
5507
|
|
|
5508
|
+
// src/tools.ts
|
|
5509
|
+
import * as fs29 from "fs";
|
|
5510
|
+
import * as path27 from "path";
|
|
5511
|
+
import { execSync as execSync3 } from "child_process";
|
|
5512
|
+
import { parse as parseYaml } from "yaml";
|
|
5513
|
+
function loadToolDeclarations(projectDir) {
|
|
5514
|
+
const toolsPath = path27.join(projectDir, ".kody", "tools.yml");
|
|
5515
|
+
if (!fs29.existsSync(toolsPath)) return [];
|
|
5516
|
+
try {
|
|
5517
|
+
const raw = fs29.readFileSync(toolsPath, "utf-8");
|
|
5518
|
+
const parsed = parseYaml(raw);
|
|
5519
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
5520
|
+
return Object.entries(parsed).map(([name, value]) => {
|
|
5521
|
+
const v = value;
|
|
5522
|
+
return {
|
|
5523
|
+
name,
|
|
5524
|
+
detect: Array.isArray(v.detect) ? v.detect : [],
|
|
5525
|
+
stages: Array.isArray(v.stages) ? v.stages : [],
|
|
5526
|
+
setup: typeof v.setup === "string" ? v.setup : ""
|
|
5527
|
+
};
|
|
5528
|
+
});
|
|
5529
|
+
} catch (err) {
|
|
5530
|
+
logger.warn(`Failed to parse .kody/tools.yml: ${err instanceof Error ? err.message : String(err)}`);
|
|
5531
|
+
return [];
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
function detectTools(declarations, projectDir) {
|
|
5535
|
+
const resolved = [];
|
|
5536
|
+
for (const decl of declarations) {
|
|
5537
|
+
const detected = decl.detect.some((pattern) => fs29.existsSync(path27.join(projectDir, pattern)));
|
|
5538
|
+
if (!detected) continue;
|
|
5539
|
+
resolved.push({
|
|
5540
|
+
name: decl.name,
|
|
5541
|
+
stages: decl.stages,
|
|
5542
|
+
setup: decl.setup
|
|
5543
|
+
});
|
|
5544
|
+
}
|
|
5545
|
+
return resolved;
|
|
5546
|
+
}
|
|
5547
|
+
function runToolSetup(tools, projectDir) {
|
|
5548
|
+
for (const tool of tools) {
|
|
5549
|
+
if (tool.setup) {
|
|
5550
|
+
try {
|
|
5551
|
+
logger.info(` Setting up ${tool.name}: ${tool.setup}`);
|
|
5552
|
+
execSync3(tool.setup, { cwd: projectDir, timeout: 12e4, stdio: "pipe" });
|
|
5553
|
+
logger.info(` \u2713 ${tool.name} setup complete`);
|
|
5554
|
+
} catch (err) {
|
|
5555
|
+
logger.warn(` \u26A0 ${tool.name} setup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
try {
|
|
5559
|
+
logger.info(` Installing skill for ${tool.name} from skills.sh`);
|
|
5560
|
+
execSync3(`npx skills add --skill ${tool.name} --yes`, { cwd: projectDir, timeout: 6e4, stdio: "pipe" });
|
|
5561
|
+
logger.info(` \u2713 ${tool.name} skill installed`);
|
|
5562
|
+
} catch (err) {
|
|
5563
|
+
logger.warn(` \u26A0 ${tool.name} skill install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5564
|
+
}
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
var init_tools = __esm({
|
|
5568
|
+
"src/tools.ts"() {
|
|
5569
|
+
"use strict";
|
|
5570
|
+
init_logger();
|
|
5571
|
+
}
|
|
5572
|
+
});
|
|
5573
|
+
|
|
5607
5574
|
// src/pipeline.ts
|
|
5608
|
-
import * as
|
|
5609
|
-
import * as
|
|
5575
|
+
import * as fs30 from "fs";
|
|
5576
|
+
import * as path28 from "path";
|
|
5610
5577
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
5611
5578
|
if (ctx.input.dryRun) return;
|
|
5612
5579
|
if (ctx.input.prNumber) {
|
|
@@ -5619,8 +5586,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
5619
5586
|
}
|
|
5620
5587
|
if (!ctx.input.issueNumber) return;
|
|
5621
5588
|
try {
|
|
5622
|
-
const taskMdPath =
|
|
5623
|
-
const title =
|
|
5589
|
+
const taskMdPath = path28.join(ctx.taskDir, "task.md");
|
|
5590
|
+
const title = fs30.existsSync(taskMdPath) ? fs30.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
5624
5591
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
5625
5592
|
syncWithDefault(ctx.projectDir);
|
|
5626
5593
|
} catch (err) {
|
|
@@ -5634,10 +5601,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
5634
5601
|
}
|
|
5635
5602
|
}
|
|
5636
5603
|
function acquireLock(taskDir) {
|
|
5637
|
-
const lockPath =
|
|
5638
|
-
if (
|
|
5604
|
+
const lockPath = path28.join(taskDir, ".lock");
|
|
5605
|
+
if (fs30.existsSync(lockPath)) {
|
|
5639
5606
|
try {
|
|
5640
|
-
const pid = parseInt(
|
|
5607
|
+
const pid = parseInt(fs30.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
5641
5608
|
if (!isNaN(pid)) {
|
|
5642
5609
|
try {
|
|
5643
5610
|
process.kill(pid, 0);
|
|
@@ -5654,14 +5621,14 @@ function acquireLock(taskDir) {
|
|
|
5654
5621
|
logger.warn(` Corrupt lock file \u2014 overwriting`);
|
|
5655
5622
|
}
|
|
5656
5623
|
try {
|
|
5657
|
-
|
|
5624
|
+
fs30.unlinkSync(lockPath);
|
|
5658
5625
|
} catch {
|
|
5659
5626
|
}
|
|
5660
5627
|
}
|
|
5661
5628
|
try {
|
|
5662
|
-
const fd =
|
|
5663
|
-
|
|
5664
|
-
|
|
5629
|
+
const fd = fs30.openSync(lockPath, fs30.constants.O_WRONLY | fs30.constants.O_CREAT | fs30.constants.O_EXCL);
|
|
5630
|
+
fs30.writeSync(fd, String(process.pid));
|
|
5631
|
+
fs30.closeSync(fd);
|
|
5665
5632
|
} catch (err) {
|
|
5666
5633
|
if (err.code === "EEXIST") {
|
|
5667
5634
|
throw new Error("Pipeline already running (lock acquired by another process)");
|
|
@@ -5671,7 +5638,7 @@ function acquireLock(taskDir) {
|
|
|
5671
5638
|
}
|
|
5672
5639
|
function releaseLock(taskDir) {
|
|
5673
5640
|
try {
|
|
5674
|
-
|
|
5641
|
+
fs30.unlinkSync(path28.join(taskDir, ".lock"));
|
|
5675
5642
|
} catch {
|
|
5676
5643
|
}
|
|
5677
5644
|
}
|
|
@@ -5885,8 +5852,8 @@ var init_pipeline = __esm({
|
|
|
5885
5852
|
});
|
|
5886
5853
|
|
|
5887
5854
|
// src/preflight.ts
|
|
5888
|
-
import { execFileSync as
|
|
5889
|
-
import * as
|
|
5855
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
5856
|
+
import * as fs31 from "fs";
|
|
5890
5857
|
function check(name, fn) {
|
|
5891
5858
|
try {
|
|
5892
5859
|
const detail = fn() ?? void 0;
|
|
@@ -5898,7 +5865,7 @@ function check(name, fn) {
|
|
|
5898
5865
|
function runPreflight() {
|
|
5899
5866
|
const checks = [
|
|
5900
5867
|
check("claude CLI", () => {
|
|
5901
|
-
const v =
|
|
5868
|
+
const v = execFileSync15("claude", ["--version"], {
|
|
5902
5869
|
encoding: "utf-8",
|
|
5903
5870
|
timeout: 1e4,
|
|
5904
5871
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5906,14 +5873,14 @@ function runPreflight() {
|
|
|
5906
5873
|
return v;
|
|
5907
5874
|
}),
|
|
5908
5875
|
check("git repo", () => {
|
|
5909
|
-
|
|
5876
|
+
execFileSync15("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
5910
5877
|
encoding: "utf-8",
|
|
5911
5878
|
timeout: 5e3,
|
|
5912
5879
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5913
5880
|
});
|
|
5914
5881
|
}),
|
|
5915
5882
|
check("pnpm", () => {
|
|
5916
|
-
const v =
|
|
5883
|
+
const v = execFileSync15("pnpm", ["--version"], {
|
|
5917
5884
|
encoding: "utf-8",
|
|
5918
5885
|
timeout: 5e3,
|
|
5919
5886
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5921,7 +5888,7 @@ function runPreflight() {
|
|
|
5921
5888
|
return v;
|
|
5922
5889
|
}),
|
|
5923
5890
|
check("node >= 18", () => {
|
|
5924
|
-
const v =
|
|
5891
|
+
const v = execFileSync15("node", ["--version"], {
|
|
5925
5892
|
encoding: "utf-8",
|
|
5926
5893
|
timeout: 5e3,
|
|
5927
5894
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5931,7 +5898,7 @@ function runPreflight() {
|
|
|
5931
5898
|
return v;
|
|
5932
5899
|
}),
|
|
5933
5900
|
check("gh CLI", () => {
|
|
5934
|
-
const v =
|
|
5901
|
+
const v = execFileSync15("gh", ["--version"], {
|
|
5935
5902
|
encoding: "utf-8",
|
|
5936
5903
|
timeout: 5e3,
|
|
5937
5904
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -5939,7 +5906,7 @@ function runPreflight() {
|
|
|
5939
5906
|
return v;
|
|
5940
5907
|
}),
|
|
5941
5908
|
check("package.json", () => {
|
|
5942
|
-
if (!
|
|
5909
|
+
if (!fs31.existsSync("package.json")) throw new Error("not found");
|
|
5943
5910
|
})
|
|
5944
5911
|
];
|
|
5945
5912
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -6016,8 +5983,8 @@ var init_args = __esm({
|
|
|
6016
5983
|
});
|
|
6017
5984
|
|
|
6018
5985
|
// src/cli/task-state.ts
|
|
6019
|
-
import * as
|
|
6020
|
-
import * as
|
|
5986
|
+
import * as fs32 from "fs";
|
|
5987
|
+
import * as path29 from "path";
|
|
6021
5988
|
function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
6022
5989
|
if (!existingTaskId || !existingState) {
|
|
6023
5990
|
return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
|
|
@@ -6049,11 +6016,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
|
6049
6016
|
function resolveForIssue(issueNumber, projectDir) {
|
|
6050
6017
|
const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
|
|
6051
6018
|
if (existingTaskId) {
|
|
6052
|
-
const statusPath =
|
|
6019
|
+
const statusPath = path29.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
6053
6020
|
let existingState = null;
|
|
6054
|
-
if (
|
|
6021
|
+
if (fs32.existsSync(statusPath)) {
|
|
6055
6022
|
try {
|
|
6056
|
-
existingState = JSON.parse(
|
|
6023
|
+
existingState = JSON.parse(fs32.readFileSync(statusPath, "utf-8"));
|
|
6057
6024
|
} catch {
|
|
6058
6025
|
}
|
|
6059
6026
|
}
|
|
@@ -6086,12 +6053,12 @@ var resolve_exports = {};
|
|
|
6086
6053
|
__export(resolve_exports, {
|
|
6087
6054
|
runResolve: () => runResolve
|
|
6088
6055
|
});
|
|
6089
|
-
import { execFileSync as
|
|
6056
|
+
import { execFileSync as execFileSync16 } from "child_process";
|
|
6090
6057
|
function getConflictContext(cwd, files) {
|
|
6091
6058
|
const parts = [];
|
|
6092
6059
|
for (const file of files.slice(0, 10)) {
|
|
6093
6060
|
try {
|
|
6094
|
-
const content =
|
|
6061
|
+
const content = execFileSync16("git", ["diff", file], {
|
|
6095
6062
|
cwd,
|
|
6096
6063
|
encoding: "utf-8",
|
|
6097
6064
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -6210,8 +6177,8 @@ var init_resolve = __esm({
|
|
|
6210
6177
|
|
|
6211
6178
|
// src/entry.ts
|
|
6212
6179
|
var entry_exports = {};
|
|
6213
|
-
import * as
|
|
6214
|
-
import * as
|
|
6180
|
+
import * as fs33 from "fs";
|
|
6181
|
+
import * as path30 from "path";
|
|
6215
6182
|
async function ensureLitellmProxy(config, projectDir) {
|
|
6216
6183
|
if (!anyStageNeedsProxy(config)) return null;
|
|
6217
6184
|
const litellmUrl = getLitellmUrl();
|
|
@@ -6266,9 +6233,9 @@ async function runModelHealthCheck(config) {
|
|
|
6266
6233
|
}
|
|
6267
6234
|
async function main() {
|
|
6268
6235
|
const input = parseArgs();
|
|
6269
|
-
const projectDir = input.cwd ?
|
|
6236
|
+
const projectDir = input.cwd ? path30.resolve(input.cwd) : process.cwd();
|
|
6270
6237
|
if (input.cwd) {
|
|
6271
|
-
if (!
|
|
6238
|
+
if (!fs33.existsSync(projectDir)) {
|
|
6272
6239
|
console.error(`--cwd path does not exist: ${projectDir}`);
|
|
6273
6240
|
process.exit(1);
|
|
6274
6241
|
}
|
|
@@ -6328,8 +6295,8 @@ async function main() {
|
|
|
6328
6295
|
process.exit(1);
|
|
6329
6296
|
}
|
|
6330
6297
|
}
|
|
6331
|
-
const taskDir =
|
|
6332
|
-
|
|
6298
|
+
const taskDir = path30.join(projectDir, ".kody", "tasks", taskId);
|
|
6299
|
+
fs33.mkdirSync(taskDir, { recursive: true });
|
|
6333
6300
|
if (input.command === "rerun" && isTaskifyRun(taskDir)) {
|
|
6334
6301
|
const marker = readTaskifyMarker(taskDir);
|
|
6335
6302
|
if (marker) {
|
|
@@ -6461,31 +6428,31 @@ async function main() {
|
|
|
6461
6428
|
logger.info("Preflight checks:");
|
|
6462
6429
|
runPreflight();
|
|
6463
6430
|
if (input.task) {
|
|
6464
|
-
|
|
6431
|
+
fs33.writeFileSync(path30.join(taskDir, "task.md"), input.task);
|
|
6465
6432
|
}
|
|
6466
|
-
const taskMdPath =
|
|
6467
|
-
if (!
|
|
6433
|
+
const taskMdPath = path30.join(taskDir, "task.md");
|
|
6434
|
+
if (!fs33.existsSync(taskMdPath) && isPRFix && input.prNumber) {
|
|
6468
6435
|
logger.info(`Fetching PR #${input.prNumber} details as task context...`);
|
|
6469
6436
|
const prDetails = getPRDetails(input.prNumber);
|
|
6470
6437
|
if (prDetails) {
|
|
6471
6438
|
const taskContent = `# ${prDetails.title}
|
|
6472
6439
|
|
|
6473
6440
|
${prDetails.body ?? ""}`;
|
|
6474
|
-
|
|
6441
|
+
fs33.writeFileSync(taskMdPath, taskContent);
|
|
6475
6442
|
logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
|
|
6476
6443
|
}
|
|
6477
|
-
} else if (!
|
|
6444
|
+
} else if (!fs33.existsSync(taskMdPath) && input.issueNumber) {
|
|
6478
6445
|
logger.info(`Fetching issue #${input.issueNumber} body as task...`);
|
|
6479
6446
|
const issue = getIssue(input.issueNumber);
|
|
6480
6447
|
if (issue) {
|
|
6481
6448
|
const taskContent = `# ${issue.title}
|
|
6482
6449
|
|
|
6483
6450
|
${issue.body ?? ""}`;
|
|
6484
|
-
|
|
6451
|
+
fs33.writeFileSync(taskMdPath, taskContent);
|
|
6485
6452
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
6486
6453
|
}
|
|
6487
6454
|
}
|
|
6488
|
-
if (!
|
|
6455
|
+
if (!fs33.existsSync(taskMdPath)) {
|
|
6489
6456
|
console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
|
|
6490
6457
|
process.exit(1);
|
|
6491
6458
|
}
|
|
@@ -6629,7 +6596,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
|
|
|
6629
6596
|
}
|
|
6630
6597
|
}
|
|
6631
6598
|
const state = await runPipeline(ctx);
|
|
6632
|
-
const files =
|
|
6599
|
+
const files = fs33.readdirSync(taskDir);
|
|
6633
6600
|
console.log(`
|
|
6634
6601
|
Artifacts in ${taskDir}:`);
|
|
6635
6602
|
for (const f of files) {
|
|
@@ -6695,8 +6662,8 @@ var init_entry = __esm({
|
|
|
6695
6662
|
});
|
|
6696
6663
|
|
|
6697
6664
|
// src/bin/cli.ts
|
|
6698
|
-
import * as
|
|
6699
|
-
import * as
|
|
6665
|
+
import * as fs34 from "fs";
|
|
6666
|
+
import * as path31 from "path";
|
|
6700
6667
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6701
6668
|
|
|
6702
6669
|
// src/bin/commands/init.ts
|
|
@@ -7068,9 +7035,9 @@ function initCommand(opts, pkgRoot) {
|
|
|
7068
7035
|
|
|
7069
7036
|
// src/bin/commands/bootstrap.ts
|
|
7070
7037
|
init_architecture_detection();
|
|
7071
|
-
import * as
|
|
7072
|
-
import * as
|
|
7073
|
-
import { execFileSync as
|
|
7038
|
+
import * as fs8 from "fs";
|
|
7039
|
+
import * as path7 from "path";
|
|
7040
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
7074
7041
|
|
|
7075
7042
|
// src/bin/qa-guide.ts
|
|
7076
7043
|
import * as fs6 from "fs";
|
|
@@ -7580,100 +7547,6 @@ Required env vars: ${discovery.envVars.join(", ")}`);
|
|
|
7580
7547
|
return result2;
|
|
7581
7548
|
}
|
|
7582
7549
|
|
|
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
7550
|
// src/bin/commands/bootstrap.ts
|
|
7678
7551
|
init_config();
|
|
7679
7552
|
|
|
@@ -7703,20 +7576,20 @@ ${content}
|
|
|
7703
7576
|
// src/bin/commands/bootstrap.ts
|
|
7704
7577
|
var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
|
|
7705
7578
|
function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
7706
|
-
const srcDir =
|
|
7707
|
-
const baseDir =
|
|
7579
|
+
const srcDir = path7.join(cwd, "src");
|
|
7580
|
+
const baseDir = fs8.existsSync(srcDir) ? srcDir : cwd;
|
|
7708
7581
|
const results = [];
|
|
7709
7582
|
function walk(dir) {
|
|
7710
7583
|
const entries = [];
|
|
7711
7584
|
try {
|
|
7712
|
-
for (const entry of
|
|
7585
|
+
for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
7713
7586
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
7714
|
-
const full =
|
|
7587
|
+
const full = path7.join(dir, entry.name);
|
|
7715
7588
|
if (entry.isDirectory()) {
|
|
7716
7589
|
entries.push(...walk(full));
|
|
7717
7590
|
} else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
|
|
7718
7591
|
try {
|
|
7719
|
-
const stat =
|
|
7592
|
+
const stat = fs8.statSync(full);
|
|
7720
7593
|
if (stat.size >= 200 && stat.size <= 5e3) {
|
|
7721
7594
|
entries.push({ filePath: full, size: stat.size });
|
|
7722
7595
|
}
|
|
@@ -7730,8 +7603,8 @@ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
|
7730
7603
|
}
|
|
7731
7604
|
const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
|
|
7732
7605
|
for (const { filePath } of files) {
|
|
7733
|
-
const rel =
|
|
7734
|
-
const content =
|
|
7606
|
+
const rel = path7.relative(cwd, filePath);
|
|
7607
|
+
const content = fs8.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
|
|
7735
7608
|
results.push(`### File: ${rel}
|
|
7736
7609
|
\`\`\`typescript
|
|
7737
7610
|
${content}
|
|
@@ -7743,9 +7616,9 @@ function ghComment(issueNumber, body, cwd) {
|
|
|
7743
7616
|
try {
|
|
7744
7617
|
let repoSlug = "";
|
|
7745
7618
|
try {
|
|
7746
|
-
const configPath =
|
|
7747
|
-
if (
|
|
7748
|
-
const config = JSON.parse(
|
|
7619
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
7620
|
+
if (fs8.existsSync(configPath)) {
|
|
7621
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
7749
7622
|
if (config.github?.owner && config.github?.repo) {
|
|
7750
7623
|
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
7751
7624
|
}
|
|
@@ -7753,7 +7626,7 @@ function ghComment(issueNumber, body, cwd) {
|
|
|
7753
7626
|
} catch {
|
|
7754
7627
|
}
|
|
7755
7628
|
if (!repoSlug) return;
|
|
7756
|
-
|
|
7629
|
+
execFileSync4("gh", [
|
|
7757
7630
|
"issue",
|
|
7758
7631
|
"comment",
|
|
7759
7632
|
String(issueNumber),
|
|
@@ -7782,8 +7655,8 @@ function bootstrapCommand(opts, pkgRoot) {
|
|
|
7782
7655
|
ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
|
|
7783
7656
|
}
|
|
7784
7657
|
const readIfExists = (rel, maxChars = 3e3) => {
|
|
7785
|
-
const p =
|
|
7786
|
-
if (
|
|
7658
|
+
const p = path7.join(cwd, rel);
|
|
7659
|
+
if (fs8.existsSync(p)) return fs8.readFileSync(p, "utf-8").slice(0, maxChars);
|
|
7787
7660
|
return null;
|
|
7788
7661
|
};
|
|
7789
7662
|
let repoContext = "";
|
|
@@ -7818,14 +7691,14 @@ ${sampleFiles}
|
|
|
7818
7691
|
|
|
7819
7692
|
`;
|
|
7820
7693
|
try {
|
|
7821
|
-
const topDirs =
|
|
7694
|
+
const topDirs = fs8.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
7822
7695
|
repoContext += `## Top-level directories
|
|
7823
7696
|
${topDirs.join(", ")}
|
|
7824
7697
|
|
|
7825
7698
|
`;
|
|
7826
|
-
const srcDir =
|
|
7827
|
-
if (
|
|
7828
|
-
const srcDirs =
|
|
7699
|
+
const srcDir = path7.join(cwd, "src");
|
|
7700
|
+
if (fs8.existsSync(srcDir)) {
|
|
7701
|
+
const srcDirs = fs8.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
7829
7702
|
if (srcDirs.length > 0) repoContext += `## src/ subdirectories
|
|
7830
7703
|
${srcDirs.join(", ")}
|
|
7831
7704
|
|
|
@@ -7835,19 +7708,19 @@ ${srcDirs.join(", ")}
|
|
|
7835
7708
|
}
|
|
7836
7709
|
const existingFiles = [];
|
|
7837
7710
|
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 (
|
|
7711
|
+
if (fs8.existsSync(path7.join(cwd, f))) existingFiles.push(f);
|
|
7839
7712
|
}
|
|
7840
7713
|
if (existingFiles.length) repoContext += `## Config files present
|
|
7841
7714
|
${existingFiles.join(", ")}
|
|
7842
7715
|
|
|
7843
7716
|
`;
|
|
7844
7717
|
console.log("\u2500\u2500 Project Memory \u2500\u2500");
|
|
7845
|
-
const memoryDir =
|
|
7846
|
-
|
|
7847
|
-
const archPath =
|
|
7848
|
-
const conventionsPath =
|
|
7849
|
-
const existingArch =
|
|
7850
|
-
const existingConv =
|
|
7718
|
+
const memoryDir = path7.join(cwd, ".kody", "memory");
|
|
7719
|
+
fs8.mkdirSync(memoryDir, { recursive: true });
|
|
7720
|
+
const archPath = path7.join(memoryDir, "architecture.md");
|
|
7721
|
+
const conventionsPath = path7.join(memoryDir, "conventions.md");
|
|
7722
|
+
const existingArch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
|
|
7723
|
+
const existingConv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
|
|
7851
7724
|
const hasExisting = !!(existingArch || existingConv);
|
|
7852
7725
|
const extendInstruction = hasExisting && !opts.force ? buildExtendInstruction(
|
|
7853
7726
|
`### architecture.md:
|
|
@@ -7882,7 +7755,7 @@ Output ONLY valid JSON. No markdown fences. No explanation.
|
|
|
7882
7755
|
${repoContext}`;
|
|
7883
7756
|
console.log(" \u23F3 Analyzing project...");
|
|
7884
7757
|
try {
|
|
7885
|
-
const output =
|
|
7758
|
+
const output = execFileSync4("claude", [
|
|
7886
7759
|
"--print",
|
|
7887
7760
|
"--model",
|
|
7888
7761
|
bootstrapModel,
|
|
@@ -7897,12 +7770,12 @@ ${repoContext}`;
|
|
|
7897
7770
|
const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
7898
7771
|
const parsed = JSON.parse(cleaned);
|
|
7899
7772
|
if (parsed.architecture) {
|
|
7900
|
-
|
|
7773
|
+
fs8.writeFileSync(archPath, parsed.architecture);
|
|
7901
7774
|
const lineCount = parsed.architecture.split("\n").length;
|
|
7902
7775
|
console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
|
|
7903
7776
|
}
|
|
7904
7777
|
if (parsed.conventions) {
|
|
7905
|
-
|
|
7778
|
+
fs8.writeFileSync(conventionsPath, parsed.conventions);
|
|
7906
7779
|
const lineCount = parsed.conventions.split("\n").length;
|
|
7907
7780
|
console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
|
|
7908
7781
|
}
|
|
@@ -7911,37 +7784,37 @@ ${repoContext}`;
|
|
|
7911
7784
|
const detected = detectArchitectureBasic(cwd);
|
|
7912
7785
|
if (detected.length > 0) {
|
|
7913
7786
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7914
|
-
|
|
7787
|
+
fs8.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
|
|
7915
7788
|
|
|
7916
7789
|
## Overview
|
|
7917
7790
|
${detected.join("\n")}
|
|
7918
7791
|
`);
|
|
7919
7792
|
console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
|
|
7920
7793
|
}
|
|
7921
|
-
|
|
7794
|
+
fs8.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
|
|
7922
7795
|
console.log(" \u2713 .kody/memory/conventions.md (seed)");
|
|
7923
7796
|
}
|
|
7924
7797
|
console.log("\n\u2500\u2500 Step Files \u2500\u2500");
|
|
7925
|
-
const stepsDir =
|
|
7926
|
-
|
|
7927
|
-
const arch =
|
|
7928
|
-
const conv =
|
|
7798
|
+
const stepsDir = path7.join(cwd, ".kody", "steps");
|
|
7799
|
+
fs8.mkdirSync(stepsDir, { recursive: true });
|
|
7800
|
+
const arch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
|
|
7801
|
+
const conv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
|
|
7929
7802
|
console.log(" \u23F3 Customizing step files...");
|
|
7930
7803
|
let stepCount = 0;
|
|
7931
7804
|
for (const stage of STEP_STAGES) {
|
|
7932
|
-
const templatePath =
|
|
7933
|
-
if (!
|
|
7805
|
+
const templatePath = path7.join(pkgRoot, "prompts", `${stage}.md`);
|
|
7806
|
+
if (!fs8.existsSync(templatePath)) {
|
|
7934
7807
|
console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
|
|
7935
7808
|
continue;
|
|
7936
7809
|
}
|
|
7937
|
-
const stepOutputPath =
|
|
7938
|
-
const existingStep =
|
|
7810
|
+
const stepOutputPath = path7.join(stepsDir, `${stage}.md`);
|
|
7811
|
+
const existingStep = fs8.existsSync(stepOutputPath) ? fs8.readFileSync(stepOutputPath, "utf-8") : "";
|
|
7939
7812
|
const isExtend = !!existingStep && !opts.force;
|
|
7940
|
-
const defaultPrompt =
|
|
7813
|
+
const defaultPrompt = fs8.readFileSync(templatePath, "utf-8");
|
|
7941
7814
|
const contextPlaceholder = "{{TASK_CONTEXT}}";
|
|
7942
7815
|
const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
|
|
7943
7816
|
if (placeholderIdx === -1) {
|
|
7944
|
-
|
|
7817
|
+
fs8.copyFileSync(templatePath, stepOutputPath);
|
|
7945
7818
|
stepCount++;
|
|
7946
7819
|
console.log(` \u2713 ${stage}.md`);
|
|
7947
7820
|
continue;
|
|
@@ -7984,7 +7857,7 @@ ${repoContext}
|
|
|
7984
7857
|
|
|
7985
7858
|
REMINDER: Output the full prompt template first (unchanged), then your three appended sections. Do NOT include "${contextPlaceholder}".`;
|
|
7986
7859
|
try {
|
|
7987
|
-
const output =
|
|
7860
|
+
const output = execFileSync4("claude", [
|
|
7988
7861
|
"--print",
|
|
7989
7862
|
"--model",
|
|
7990
7863
|
bootstrapModel,
|
|
@@ -7999,13 +7872,13 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
7999
7872
|
let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
8000
7873
|
cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
|
|
8001
7874
|
const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
|
|
8002
|
-
|
|
7875
|
+
fs8.writeFileSync(stepOutputPath, finalPrompt);
|
|
8003
7876
|
stepCount++;
|
|
8004
7877
|
console.log(` \u2713 ${stage}.md (${isExtend ? "extended" : "generated"})`);
|
|
8005
7878
|
} catch {
|
|
8006
7879
|
if (!isExtend) {
|
|
8007
7880
|
console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
|
|
8008
|
-
|
|
7881
|
+
fs8.copyFileSync(templatePath, stepOutputPath);
|
|
8009
7882
|
stepCount++;
|
|
8010
7883
|
} else {
|
|
8011
7884
|
console.log(` \u26A0 ${stage}.md \u2014 extend failed, keeping existing`);
|
|
@@ -8014,11 +7887,11 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
8014
7887
|
}
|
|
8015
7888
|
console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
|
|
8016
7889
|
console.log("\n\u2500\u2500 QA Guide \u2500\u2500");
|
|
8017
|
-
const qaGuidePath =
|
|
7890
|
+
const qaGuidePath = path7.join(cwd, ".kody", "qa-guide.md");
|
|
8018
7891
|
const discovery = discoverQaContext(cwd);
|
|
8019
7892
|
const hasRoutes = discovery.routes.length > 0 || discovery.collections.length > 0;
|
|
8020
7893
|
if (hasRoutes) {
|
|
8021
|
-
const existingQaGuide =
|
|
7894
|
+
const existingQaGuide = fs8.existsSync(qaGuidePath) ? fs8.readFileSync(qaGuidePath, "utf-8") : "";
|
|
8022
7895
|
const isQaExtend = !!existingQaGuide && !opts.force;
|
|
8023
7896
|
const serializedDiscovery = serializeDiscoveryForLLM(discovery);
|
|
8024
7897
|
const qaExtendBlock = isQaExtend ? buildExtendInstruction(existingQaGuide, "QA guide") : "";
|
|
@@ -8087,7 +7960,7 @@ Command and URL.
|
|
|
8087
7960
|
- Output ONLY the markdown. No explanation before or after.`;
|
|
8088
7961
|
console.log(" \u23F3 Generating QA guide...");
|
|
8089
7962
|
try {
|
|
8090
|
-
const output =
|
|
7963
|
+
const output = execFileSync4("claude", [
|
|
8091
7964
|
"--print",
|
|
8092
7965
|
"--model",
|
|
8093
7966
|
bootstrapModel,
|
|
@@ -8100,12 +7973,12 @@ Command and URL.
|
|
|
8100
7973
|
stdio: ["pipe", "pipe", "pipe"]
|
|
8101
7974
|
}).trim();
|
|
8102
7975
|
const cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
8103
|
-
|
|
7976
|
+
fs8.writeFileSync(qaGuidePath, cleaned);
|
|
8104
7977
|
console.log(` \u2713 .kody/qa-guide.md (${isQaExtend ? "extended" : "generated"}, ${discovery.routes.length} routes, ${discovery.collections.length} collections)`);
|
|
8105
7978
|
} catch {
|
|
8106
7979
|
console.log(" \u26A0 LLM QA generation failed \u2014 using template fallback");
|
|
8107
7980
|
const qaGuide = generateQaGuideFallback(discovery);
|
|
8108
|
-
|
|
7981
|
+
fs8.writeFileSync(qaGuidePath, qaGuide);
|
|
8109
7982
|
console.log(` \u2713 .kody/qa-guide.md (fallback, ${discovery.routes.length} routes)`);
|
|
8110
7983
|
}
|
|
8111
7984
|
if (discovery.loginPage) console.log(` \u2713 Login page detected: ${discovery.loginPage}`);
|
|
@@ -8120,9 +7993,9 @@ Command and URL.
|
|
|
8120
7993
|
try {
|
|
8121
7994
|
let repoSlug = "";
|
|
8122
7995
|
try {
|
|
8123
|
-
const configPath =
|
|
8124
|
-
if (
|
|
8125
|
-
const config = JSON.parse(
|
|
7996
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
7997
|
+
if (fs8.existsSync(configPath)) {
|
|
7998
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
8126
7999
|
if (config.github?.owner && config.github?.repo) {
|
|
8127
8000
|
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
8128
8001
|
}
|
|
@@ -8152,7 +8025,7 @@ Command and URL.
|
|
|
8152
8025
|
];
|
|
8153
8026
|
for (const label of labels) {
|
|
8154
8027
|
try {
|
|
8155
|
-
|
|
8028
|
+
execFileSync4("gh", [
|
|
8156
8029
|
"label",
|
|
8157
8030
|
"create",
|
|
8158
8031
|
label.name,
|
|
@@ -8172,7 +8045,7 @@ Command and URL.
|
|
|
8172
8045
|
console.log(` \u2713 ${label.name}`);
|
|
8173
8046
|
} catch {
|
|
8174
8047
|
try {
|
|
8175
|
-
|
|
8048
|
+
execFileSync4("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
|
|
8176
8049
|
cwd,
|
|
8177
8050
|
encoding: "utf-8",
|
|
8178
8051
|
timeout: 1e4,
|
|
@@ -8191,47 +8064,40 @@ Command and URL.
|
|
|
8191
8064
|
console.log(" \u25CB Label creation skipped");
|
|
8192
8065
|
}
|
|
8193
8066
|
console.log("\n\u2500\u2500 Tools \u2500\u2500");
|
|
8194
|
-
const toolsYmlPath =
|
|
8195
|
-
if (!
|
|
8067
|
+
const toolsYmlPath = path7.join(cwd, ".kody", "tools.yml");
|
|
8068
|
+
if (!fs8.existsSync(toolsYmlPath) || opts.force) {
|
|
8196
8069
|
const toolsTemplate = `# Kody Tools Configuration
|
|
8197
8070
|
# Uncomment and configure tools that your project uses.
|
|
8198
|
-
# The engine
|
|
8071
|
+
# The engine detects tools, runs setup commands, and installs matching skills from skills.sh.
|
|
8199
8072
|
#
|
|
8200
8073
|
# playwright:
|
|
8201
8074
|
# detect: ["playwright.config.ts", "playwright.config.js"]
|
|
8202
8075
|
# stages: [verify]
|
|
8203
8076
|
# setup: "npx playwright install --with-deps chromium"
|
|
8204
|
-
# skill: playwright-cli.md
|
|
8205
8077
|
`;
|
|
8206
|
-
|
|
8078
|
+
fs8.writeFileSync(toolsYmlPath, toolsTemplate);
|
|
8207
8079
|
console.log(" \u2713 .kody/tools.yml (template created)");
|
|
8208
8080
|
} else {
|
|
8209
8081
|
console.log(" \u25CB .kody/tools.yml (already exists, keeping)");
|
|
8210
8082
|
}
|
|
8211
|
-
console.log("\n\u2500\u2500 Skills \u2500\u2500");
|
|
8212
|
-
const installedSkillPaths = installSkillsForProject(cwd);
|
|
8213
8083
|
console.log("\n\u2500\u2500 Git \u2500\u2500");
|
|
8214
8084
|
const filesToCommit = [
|
|
8215
8085
|
".kody/memory/architecture.md",
|
|
8216
8086
|
".kody/memory/conventions.md",
|
|
8217
8087
|
".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
|
-
}
|
|
8088
|
+
".kody/tools.yml"
|
|
8089
|
+
].filter((f) => fs8.existsSync(path7.join(cwd, f)));
|
|
8224
8090
|
for (const stage of STEP_STAGES) {
|
|
8225
8091
|
const stepFile = `.kody/steps/${stage}.md`;
|
|
8226
|
-
if (
|
|
8092
|
+
if (fs8.existsSync(path7.join(cwd, stepFile))) {
|
|
8227
8093
|
filesToCommit.push(stepFile);
|
|
8228
8094
|
}
|
|
8229
8095
|
}
|
|
8230
8096
|
if (filesToCommit.length > 0) {
|
|
8231
8097
|
try {
|
|
8232
|
-
const fullPaths = filesToCommit.map((f) =>
|
|
8098
|
+
const fullPaths = filesToCommit.map((f) => path7.join(cwd, f));
|
|
8233
8099
|
for (let pass = 0; pass < 2; pass++) {
|
|
8234
|
-
|
|
8100
|
+
execFileSync4("npx", ["prettier", "--write", ...fullPaths], {
|
|
8235
8101
|
cwd,
|
|
8236
8102
|
encoding: "utf-8",
|
|
8237
8103
|
timeout: 3e4,
|
|
@@ -8247,24 +8113,24 @@ Command and URL.
|
|
|
8247
8113
|
try {
|
|
8248
8114
|
if (isCI3) {
|
|
8249
8115
|
const branchName = `kody-bootstrap-${Date.now()}`;
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
const staged =
|
|
8116
|
+
execFileSync4("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
|
|
8117
|
+
execFileSync4("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
|
|
8118
|
+
const staged = execFileSync4("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
|
|
8253
8119
|
if (staged) {
|
|
8254
|
-
|
|
8255
|
-
|
|
8120
|
+
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" });
|
|
8121
|
+
execFileSync4("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
|
|
8256
8122
|
console.log(` \u2713 Pushed branch: ${branchName}`);
|
|
8257
8123
|
let baseBranch = "main";
|
|
8258
8124
|
try {
|
|
8259
|
-
const configPath =
|
|
8260
|
-
if (
|
|
8261
|
-
const config = JSON.parse(
|
|
8125
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
8126
|
+
if (fs8.existsSync(configPath)) {
|
|
8127
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
8262
8128
|
baseBranch = config.git?.defaultBranch ?? "main";
|
|
8263
8129
|
}
|
|
8264
8130
|
} catch {
|
|
8265
8131
|
}
|
|
8266
8132
|
try {
|
|
8267
|
-
const prUrl =
|
|
8133
|
+
const prUrl = execFileSync4("gh", [
|
|
8268
8134
|
"pr",
|
|
8269
8135
|
"create",
|
|
8270
8136
|
"--title",
|
|
@@ -8303,13 +8169,13 @@ Create it manually.`, cwd);
|
|
|
8303
8169
|
console.log(" \u25CB No new changes to commit");
|
|
8304
8170
|
}
|
|
8305
8171
|
} else {
|
|
8306
|
-
|
|
8307
|
-
const staged =
|
|
8172
|
+
execFileSync4("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
|
|
8173
|
+
const staged = execFileSync4("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
|
|
8308
8174
|
if (staged) {
|
|
8309
|
-
|
|
8175
|
+
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
8176
|
console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
|
|
8311
8177
|
try {
|
|
8312
|
-
|
|
8178
|
+
execFileSync4("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
|
|
8313
8179
|
console.log(" \u2713 Pushed to origin");
|
|
8314
8180
|
} catch {
|
|
8315
8181
|
console.log(" \u25CB Push failed \u2014 run 'git push' manually");
|
|
@@ -8332,11 +8198,11 @@ Create it manually.`, cwd);
|
|
|
8332
8198
|
|
|
8333
8199
|
// src/bin/cli.ts
|
|
8334
8200
|
init_architecture_detection();
|
|
8335
|
-
var __dirname2 =
|
|
8336
|
-
var PKG_ROOT =
|
|
8201
|
+
var __dirname2 = path31.dirname(fileURLToPath2(import.meta.url));
|
|
8202
|
+
var PKG_ROOT = path31.resolve(__dirname2, "..", "..");
|
|
8337
8203
|
function getVersion() {
|
|
8338
|
-
const pkgPath =
|
|
8339
|
-
const pkg = JSON.parse(
|
|
8204
|
+
const pkgPath = path31.join(PKG_ROOT, "package.json");
|
|
8205
|
+
const pkg = JSON.parse(fs34.readFileSync(pkgPath, "utf-8"));
|
|
8340
8206
|
return pkg.version;
|
|
8341
8207
|
}
|
|
8342
8208
|
var args = process.argv.slice(2);
|
|
@@ -8364,6 +8230,5 @@ export {
|
|
|
8364
8230
|
checkGhRepoAccess,
|
|
8365
8231
|
checkGhSecret,
|
|
8366
8232
|
detectArchitectureBasic,
|
|
8367
|
-
detectBasicConfig
|
|
8368
|
-
detectSkillsForProject
|
|
8233
|
+
detectBasicConfig
|
|
8369
8234
|
};
|