@kody-ade/kody-engine-lite 0.1.125 → 0.1.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/bin/cli.js +1000 -423
- package/package.json +3 -2
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 fs8 from "fs";
|
|
184
|
+
import * as path7 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 = path7.join(_configDir ?? process.cwd(), "kody.config.json");
|
|
229
|
+
if (fs8.existsSync(configPath)) {
|
|
230
230
|
try {
|
|
231
|
-
const result2 = parseJsonSafe(
|
|
231
|
+
const result2 = parseJsonSafe(fs8.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 };
|
|
@@ -304,22 +304,22 @@ var init_config = __esm({
|
|
|
304
304
|
// src/agent-runner.ts
|
|
305
305
|
import { spawn, execFileSync as execFileSync6 } from "child_process";
|
|
306
306
|
function writeStdin(child, prompt) {
|
|
307
|
-
return new Promise((
|
|
307
|
+
return new Promise((resolve6, reject) => {
|
|
308
308
|
if (!child.stdin) {
|
|
309
|
-
|
|
309
|
+
resolve6();
|
|
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
|
+
resolve6();
|
|
317
317
|
}
|
|
318
318
|
});
|
|
319
319
|
});
|
|
320
320
|
}
|
|
321
321
|
function waitForProcess(child, timeout) {
|
|
322
|
-
return new Promise((
|
|
322
|
+
return new Promise((resolve6) => {
|
|
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
|
+
resolve6({
|
|
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
|
+
resolve6({ code: -1, stdout: "", stderr: err.message });
|
|
344
344
|
});
|
|
345
345
|
});
|
|
346
346
|
}
|
|
@@ -934,7 +934,7 @@ var init_github_api = __esm({
|
|
|
934
934
|
"use strict";
|
|
935
935
|
init_logger();
|
|
936
936
|
API_TIMEOUT_MS = 3e4;
|
|
937
|
-
LIFECYCLE_LABELS = ["planning", "building", "review", "shipping", "done", "failed", "waiting", "low", "medium", "high"];
|
|
937
|
+
LIFECYCLE_LABELS = ["backlog", "planning", "building", "verifying", "review", "fixing", "shipping", "done", "failed", "waiting", "low", "medium", "high"];
|
|
938
938
|
KODY_MARKERS = [
|
|
939
939
|
"Kody Review",
|
|
940
940
|
"\u{1F916} Generated by Kody",
|
|
@@ -949,13 +949,13 @@ var init_github_api = __esm({
|
|
|
949
949
|
});
|
|
950
950
|
|
|
951
951
|
// src/cli/task-resolution.ts
|
|
952
|
-
import * as
|
|
953
|
-
import * as
|
|
952
|
+
import * as fs10 from "fs";
|
|
953
|
+
import * as path9 from "path";
|
|
954
954
|
import { execFileSync as execFileSync8 } from "child_process";
|
|
955
955
|
function findLatestTaskForIssue(issueNumber, projectDir) {
|
|
956
|
-
const tasksDir =
|
|
957
|
-
if (!
|
|
958
|
-
const allDirs =
|
|
956
|
+
const tasksDir = path9.join(projectDir, ".kody", "tasks");
|
|
957
|
+
if (!fs10.existsSync(tasksDir)) return null;
|
|
958
|
+
const allDirs = fs10.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;
|
|
@@ -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 = path9.join(projectDir, ".kody", "tasks");
|
|
1003
|
+
if (!fs10.existsSync(tasksDir)) return null;
|
|
1004
|
+
const allDirs = fs10.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 = path9.join(tasksDir, dir, "taskify.marker");
|
|
1007
|
+
if (!fs10.existsSync(markerPath)) continue;
|
|
1008
1008
|
try {
|
|
1009
|
-
const marker = JSON.parse(
|
|
1009
|
+
const marker = JSON.parse(fs10.readFileSync(markerPath, "utf-8"));
|
|
1010
1010
|
if (marker.issueNumber === issueNumber) return dir;
|
|
1011
1011
|
} catch {
|
|
1012
1012
|
}
|
|
@@ -1030,9 +1030,9 @@ var init_task_resolution = __esm({
|
|
|
1030
1030
|
});
|
|
1031
1031
|
|
|
1032
1032
|
// src/cli/litellm.ts
|
|
1033
|
-
import * as
|
|
1033
|
+
import * as fs11 from "fs";
|
|
1034
1034
|
import * as os from "os";
|
|
1035
|
-
import * as
|
|
1035
|
+
import * as path10 from "path";
|
|
1036
1036
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
1037
1037
|
async function checkLitellmHealth(url) {
|
|
1038
1038
|
try {
|
|
@@ -1132,8 +1132,8 @@ 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 = path10.join(os.tmpdir(), "kody-litellm-config.yaml");
|
|
1136
|
+
fs11.writeFileSync(configPath, generatedConfig);
|
|
1137
1137
|
const portMatch = url.match(/:(\d+)/);
|
|
1138
1138
|
const port = portMatch ? portMatch[1] : "4000";
|
|
1139
1139
|
let litellmFound = false;
|
|
@@ -1162,10 +1162,10 @@ async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
|
1162
1162
|
cmd = "python3";
|
|
1163
1163
|
args2 = ["-m", "litellm", "--config", configPath, "--port", port];
|
|
1164
1164
|
}
|
|
1165
|
-
const dotenvPath =
|
|
1165
|
+
const dotenvPath = path10.join(projectDir, ".env");
|
|
1166
1166
|
const dotenvVars = {};
|
|
1167
|
-
if (
|
|
1168
|
-
for (const rawLine of
|
|
1167
|
+
if (fs11.existsSync(dotenvPath)) {
|
|
1168
|
+
for (const rawLine of fs11.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 fs12 from "fs";
|
|
1229
|
+
import * as path11 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 = path11.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 ? path11.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 && !fs12.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 = path11.join(projectDir, ".kody", "tasks", taskId);
|
|
1329
|
+
fs12.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 ? fs12.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 = path11.join(projectDir, ".kody", "memory.md");
|
|
1370
|
+
if (fs12.existsSync(memoryPath)) {
|
|
1371
1371
|
try {
|
|
1372
|
-
const content =
|
|
1372
|
+
const content = fs12.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 \`${path11.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
|
+
fs12.writeFileSync(path11.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 = path11.join(taskDir, RESULT_FILE);
|
|
1417
|
+
if (!fs12.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(fs12.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 ? path11.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
|
+
path11.resolve(scriptDir, "..", "prompts", "taskify-ticket.md"),
|
|
1541
|
+
path11.resolve(scriptDir, "..", "..", "prompts", "taskify-ticket.md"),
|
|
1542
|
+
path11.resolve(__dirname, "..", "..", "prompts", "taskify-ticket.md"),
|
|
1543
|
+
path11.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 (fs12.existsSync(candidate)) {
|
|
1548
|
+
template = fs12.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 fs12.existsSync(path11.join(taskDir, MARKER_FILE));
|
|
1573
1573
|
}
|
|
1574
1574
|
function readTaskifyMarker(taskDir) {
|
|
1575
|
-
const markerPath =
|
|
1576
|
-
if (!
|
|
1575
|
+
const markerPath = path11.join(taskDir, MARKER_FILE);
|
|
1576
|
+
if (!fs12.existsSync(markerPath)) return null;
|
|
1577
1577
|
try {
|
|
1578
|
-
return JSON.parse(
|
|
1578
|
+
return JSON.parse(fs12.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 = path11.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 fs13 from "fs";
|
|
1611
1611
|
import * as os2 from "os";
|
|
1612
|
-
import * as
|
|
1612
|
+
import * as path12 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 = path12.join(os2.tmpdir(), "kody-test-model-read.txt");
|
|
1941
|
+
fs13.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
|
+
fs13.rmSync(testFile2, { force: true });
|
|
1956
1956
|
}
|
|
1957
1957
|
}
|
|
1958
1958
|
const t = Date.now();
|
|
1959
|
-
const testFile =
|
|
1960
|
-
|
|
1959
|
+
const testFile = path12.join(os2.tmpdir(), "kody-test-model-read.txt");
|
|
1960
|
+
fs13.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
|
+
fs13.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 = path12.join(os2.tmpdir(), "kody-test-model-edit.txt");
|
|
1996
|
+
fs13.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 = fs13.existsSync(testFile) ? fs13.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
|
+
fs13.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 = path12.join(os2.tmpdir(), "kody-test-image.png");
|
|
2086
|
+
fs13.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
|
+
fs13.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 = path12.join(os2.tmpdir(), `kody-test-mcp-${Date.now()}.json`);
|
|
2316
|
+
const testFile = path12.join(ctx.projectDir, "kody-mcp-compat-test.txt");
|
|
2317
2317
|
try {
|
|
2318
|
-
|
|
2318
|
+
fs13.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 = fs13.existsSync(testFile);
|
|
2330
|
+
const content = created ? fs13.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
|
+
fs13.rmSync(mcpConfig, { force: true });
|
|
2344
|
+
fs13.rmSync(testFile, { force: true });
|
|
2345
2345
|
revertChanges(ctx.projectDir);
|
|
2346
2346
|
}
|
|
2347
2347
|
}
|
|
@@ -2522,9 +2522,9 @@ var test_model_command_exports = {};
|
|
|
2522
2522
|
__export(test_model_command_exports, {
|
|
2523
2523
|
runTestModelCommand: () => runTestModelCommand
|
|
2524
2524
|
});
|
|
2525
|
-
import * as
|
|
2525
|
+
import * as fs14 from "fs";
|
|
2526
2526
|
import * as os3 from "os";
|
|
2527
|
-
import * as
|
|
2527
|
+
import * as path13 from "path";
|
|
2528
2528
|
import { execFileSync as execFileSync10 } from "child_process";
|
|
2529
2529
|
function parseTestModelArgs() {
|
|
2530
2530
|
const args2 = process.argv.slice(3);
|
|
@@ -2607,7 +2607,7 @@ async function startProxy(config, url) {
|
|
|
2607
2607
|
return null;
|
|
2608
2608
|
}
|
|
2609
2609
|
}
|
|
2610
|
-
|
|
2610
|
+
fs14.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((resolve6) => setTimeout(resolve6, 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
|
+
fs14.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 = path13.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 fs15 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
|
+
fs15.appendFileSync(outputFile, `${key}<<KODY_EOF
|
|
2921
2921
|
${value}
|
|
2922
2922
|
KODY_EOF
|
|
2923
2923
|
`);
|
|
2924
2924
|
} else {
|
|
2925
|
-
|
|
2925
|
+
fs15.appendFileSync(outputFile, `${key}=${value}
|
|
2926
2926
|
`);
|
|
2927
2927
|
}
|
|
2928
2928
|
}
|
|
@@ -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 fs16 from "fs";
|
|
3245
|
+
import * as path14 from "path";
|
|
3246
3246
|
function loadState(taskId, taskDir) {
|
|
3247
|
-
const p =
|
|
3248
|
-
if (!
|
|
3247
|
+
const p = path14.join(taskDir, "status.json");
|
|
3248
|
+
if (!fs16.existsSync(p)) return null;
|
|
3249
3249
|
try {
|
|
3250
3250
|
const result2 = parseJsonSafe(
|
|
3251
|
-
|
|
3251
|
+
fs16.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 = path14.join(taskDir, "status.json");
|
|
3270
3270
|
const tmp = target + ".tmp";
|
|
3271
|
-
|
|
3272
|
-
|
|
3271
|
+
fs16.writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
3272
|
+
fs16.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 fs17 from "fs";
|
|
3314
|
+
import * as path15 from "path";
|
|
3315
3315
|
function readProjectMemory(projectDir) {
|
|
3316
|
-
const memoryDir =
|
|
3317
|
-
if (!
|
|
3318
|
-
const files =
|
|
3316
|
+
const memoryDir = path15.join(projectDir, ".kody", "memory");
|
|
3317
|
+
if (!fs17.existsSync(memoryDir)) return "";
|
|
3318
|
+
const files = fs17.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 = fs17.readFileSync(path15.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 fs18 from "fs";
|
|
3342
|
+
import * as path16 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 = path16.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 = path16.join(projectDir, ".kody", "memory");
|
|
3446
|
+
if (!fs18.existsSync(memoryDir)) return "";
|
|
3447
|
+
const files = fs18.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 = path16.join(memoryDir, file);
|
|
3453
|
+
const content = fs18.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 = path16.join(taskDir, "task.md");
|
|
3477
|
+
if (fs18.existsSync(taskMdPath)) {
|
|
3478
|
+
const content = fs18.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 = path16.join(taskDir, "task.json");
|
|
3487
|
+
if (fs18.existsSync(taskJsonPath)) {
|
|
3488
|
+
const content = fs18.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 = path16.join(taskDir, "spec.md");
|
|
3515
|
+
if (fs18.existsSync(specPath)) {
|
|
3516
|
+
const content = fs18.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 = path16.join(taskDir, "plan.md");
|
|
3525
|
+
if (fs18.existsSync(planPath)) {
|
|
3526
|
+
const content = fs18.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 = path16.join(taskDir, "context.md");
|
|
3535
|
+
if (fs18.existsSync(contextMdPath)) {
|
|
3536
|
+
const content = fs18.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,25 +3618,117 @@ 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
|
+
|
|
3621
3713
|
// src/context.ts
|
|
3622
|
-
import * as
|
|
3623
|
-
import * as
|
|
3714
|
+
import * as fs20 from "fs";
|
|
3715
|
+
import * as path18 from "path";
|
|
3624
3716
|
function readPromptFile(stageName, projectDir) {
|
|
3625
3717
|
if (projectDir) {
|
|
3626
|
-
const stepFile =
|
|
3627
|
-
if (
|
|
3628
|
-
return
|
|
3718
|
+
const stepFile = path18.join(projectDir, ".kody", "steps", `${stageName}.md`);
|
|
3719
|
+
if (fs20.existsSync(stepFile)) {
|
|
3720
|
+
return fs20.readFileSync(stepFile, "utf-8");
|
|
3629
3721
|
}
|
|
3630
3722
|
console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
|
|
3631
3723
|
}
|
|
3632
3724
|
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
3633
3725
|
const candidates = [
|
|
3634
|
-
|
|
3635
|
-
|
|
3726
|
+
path18.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
|
|
3727
|
+
path18.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
|
|
3636
3728
|
];
|
|
3637
3729
|
for (const candidate of candidates) {
|
|
3638
|
-
if (
|
|
3639
|
-
return
|
|
3730
|
+
if (fs20.existsSync(candidate)) {
|
|
3731
|
+
return fs20.readFileSync(candidate, "utf-8");
|
|
3640
3732
|
}
|
|
3641
3733
|
}
|
|
3642
3734
|
throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
|
|
@@ -3648,18 +3740,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
|
|
|
3648
3740
|
`;
|
|
3649
3741
|
context += `Task Directory: ${taskDir}
|
|
3650
3742
|
`;
|
|
3651
|
-
const taskMdPath =
|
|
3652
|
-
if (
|
|
3653
|
-
const taskMd =
|
|
3743
|
+
const taskMdPath = path18.join(taskDir, "task.md");
|
|
3744
|
+
if (fs20.existsSync(taskMdPath)) {
|
|
3745
|
+
const taskMd = fs20.readFileSync(taskMdPath, "utf-8");
|
|
3654
3746
|
context += `
|
|
3655
3747
|
## Task Description
|
|
3656
3748
|
${taskMd}
|
|
3657
3749
|
`;
|
|
3658
3750
|
}
|
|
3659
|
-
const taskJsonPath =
|
|
3660
|
-
if (
|
|
3751
|
+
const taskJsonPath = path18.join(taskDir, "task.json");
|
|
3752
|
+
if (fs20.existsSync(taskJsonPath)) {
|
|
3661
3753
|
try {
|
|
3662
|
-
const taskDef = JSON.parse(
|
|
3754
|
+
const taskDef = JSON.parse(fs20.readFileSync(taskJsonPath, "utf-8"));
|
|
3663
3755
|
context += `
|
|
3664
3756
|
## Task Classification
|
|
3665
3757
|
`;
|
|
@@ -3672,27 +3764,27 @@ ${taskMd}
|
|
|
3672
3764
|
} catch {
|
|
3673
3765
|
}
|
|
3674
3766
|
}
|
|
3675
|
-
const specPath =
|
|
3676
|
-
if (
|
|
3677
|
-
const spec =
|
|
3767
|
+
const specPath = path18.join(taskDir, "spec.md");
|
|
3768
|
+
if (fs20.existsSync(specPath)) {
|
|
3769
|
+
const spec = fs20.readFileSync(specPath, "utf-8");
|
|
3678
3770
|
const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
|
|
3679
3771
|
context += `
|
|
3680
3772
|
## Spec Summary
|
|
3681
3773
|
${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
|
|
3682
3774
|
`;
|
|
3683
3775
|
}
|
|
3684
|
-
const planPath =
|
|
3685
|
-
if (
|
|
3686
|
-
const plan =
|
|
3776
|
+
const planPath = path18.join(taskDir, "plan.md");
|
|
3777
|
+
if (fs20.existsSync(planPath)) {
|
|
3778
|
+
const plan = fs20.readFileSync(planPath, "utf-8");
|
|
3687
3779
|
const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
|
|
3688
3780
|
context += `
|
|
3689
3781
|
## Plan Summary
|
|
3690
3782
|
${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
|
|
3691
3783
|
`;
|
|
3692
3784
|
}
|
|
3693
|
-
const contextMdPath =
|
|
3694
|
-
if (
|
|
3695
|
-
const accumulated =
|
|
3785
|
+
const contextMdPath = path18.join(taskDir, "context.md");
|
|
3786
|
+
if (fs20.existsSync(contextMdPath)) {
|
|
3787
|
+
const accumulated = fs20.readFileSync(contextMdPath, "utf-8");
|
|
3696
3788
|
const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
|
|
3697
3789
|
const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
|
|
3698
3790
|
context += `
|
|
@@ -3710,17 +3802,17 @@ ${feedback}
|
|
|
3710
3802
|
}
|
|
3711
3803
|
function inferHasUIFromScope(scope) {
|
|
3712
3804
|
return scope.some((filePath) => {
|
|
3713
|
-
const ext =
|
|
3805
|
+
const ext = path18.extname(filePath).toLowerCase();
|
|
3714
3806
|
if (UI_EXTENSIONS.has(ext)) return true;
|
|
3715
3807
|
const normalized = filePath.replace(/\\/g, "/");
|
|
3716
3808
|
return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
|
|
3717
3809
|
});
|
|
3718
3810
|
}
|
|
3719
3811
|
function taskHasUI(taskDir) {
|
|
3720
|
-
const taskJsonPath =
|
|
3721
|
-
if (!
|
|
3812
|
+
const taskJsonPath = path18.join(taskDir, "task.json");
|
|
3813
|
+
if (!fs20.existsSync(taskJsonPath)) return true;
|
|
3722
3814
|
try {
|
|
3723
|
-
const taskDef = JSON.parse(
|
|
3815
|
+
const taskDef = JSON.parse(fs20.readFileSync(taskJsonPath, "utf-8"));
|
|
3724
3816
|
const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
|
|
3725
3817
|
if (scope.length === 0) return true;
|
|
3726
3818
|
return inferHasUIFromScope(scope);
|
|
@@ -3826,7 +3918,7 @@ ${devServerBlock}
|
|
|
3826
3918
|
|
|
3827
3919
|
Use browser tools to navigate to pages and take snapshots to verify UI output.`;
|
|
3828
3920
|
}
|
|
3829
|
-
function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
|
|
3921
|
+
function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback, tools) {
|
|
3830
3922
|
const config = getProjectConfig();
|
|
3831
3923
|
let assembled;
|
|
3832
3924
|
if (config.contextTiers?.enabled) {
|
|
@@ -3842,12 +3934,18 @@ ${prompt}` : prompt;
|
|
|
3842
3934
|
}
|
|
3843
3935
|
if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
|
|
3844
3936
|
assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
|
|
3845
|
-
const qaGuidePath =
|
|
3846
|
-
if (
|
|
3847
|
-
const qaGuide =
|
|
3937
|
+
const qaGuidePath = path18.join(projectDir, ".kody", "qa-guide.md");
|
|
3938
|
+
if (fs20.existsSync(qaGuidePath)) {
|
|
3939
|
+
const qaGuide = fs20.readFileSync(qaGuidePath, "utf-8").trim();
|
|
3848
3940
|
assembled = assembled + "\n\n" + qaGuide;
|
|
3849
3941
|
}
|
|
3850
3942
|
}
|
|
3943
|
+
if (tools?.length) {
|
|
3944
|
+
const toolSkills = getToolSkillsForStage(tools, stageName);
|
|
3945
|
+
if (toolSkills) {
|
|
3946
|
+
assembled = assembled + "\n\n" + toolSkills;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3851
3949
|
return assembled;
|
|
3852
3950
|
}
|
|
3853
3951
|
function buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback) {
|
|
@@ -3887,6 +3985,7 @@ var init_context = __esm({
|
|
|
3887
3985
|
init_config();
|
|
3888
3986
|
init_context_tiers();
|
|
3889
3987
|
init_mcp_config();
|
|
3988
|
+
init_tools();
|
|
3890
3989
|
MAX_TASK_CONTEXT_PLAN = 1500;
|
|
3891
3990
|
MAX_TASK_CONTEXT_SPEC = 2e3;
|
|
3892
3991
|
MAX_ACCUMULATED_CONTEXT = 4e3;
|
|
@@ -3936,8 +4035,8 @@ var init_runner_selection = __esm({
|
|
|
3936
4035
|
});
|
|
3937
4036
|
|
|
3938
4037
|
// src/stages/agent.ts
|
|
3939
|
-
import * as
|
|
3940
|
-
import * as
|
|
4038
|
+
import * as fs21 from "fs";
|
|
4039
|
+
import * as path19 from "path";
|
|
3941
4040
|
function getSessionInfo(stageName, sessions) {
|
|
3942
4041
|
const group = SESSION_GROUP[stageName];
|
|
3943
4042
|
if (!group) return void 0;
|
|
@@ -3966,7 +4065,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
3966
4065
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
3967
4066
|
return { outcome: "completed", retries: 0 };
|
|
3968
4067
|
}
|
|
3969
|
-
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
|
|
4068
|
+
const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback, ctx.tools);
|
|
3970
4069
|
let currentModelTier = def.modelTier;
|
|
3971
4070
|
if (ctx.input.feedback && def.name === "build") {
|
|
3972
4071
|
logger.info(` feedback: ${ctx.input.feedback.slice(0, 200)}${ctx.input.feedback.length > 200 ? "..." : ""}`);
|
|
@@ -4024,27 +4123,27 @@ async function executeAgentStage(ctx, def) {
|
|
|
4024
4123
|
}
|
|
4025
4124
|
const result2 = lastResult;
|
|
4026
4125
|
if (def.outputFile && result2.output) {
|
|
4027
|
-
|
|
4126
|
+
fs21.writeFileSync(path19.join(ctx.taskDir, def.outputFile), result2.output);
|
|
4028
4127
|
}
|
|
4029
4128
|
if (def.outputFile) {
|
|
4030
|
-
const outputPath =
|
|
4031
|
-
if (!
|
|
4032
|
-
const ext =
|
|
4033
|
-
const base =
|
|
4034
|
-
const files =
|
|
4129
|
+
const outputPath = path19.join(ctx.taskDir, def.outputFile);
|
|
4130
|
+
if (!fs21.existsSync(outputPath)) {
|
|
4131
|
+
const ext = path19.extname(def.outputFile);
|
|
4132
|
+
const base = path19.basename(def.outputFile, ext);
|
|
4133
|
+
const files = fs21.readdirSync(ctx.taskDir);
|
|
4035
4134
|
const variant = files.find(
|
|
4036
4135
|
(f) => f.startsWith(base + "-") && f.endsWith(ext)
|
|
4037
4136
|
);
|
|
4038
4137
|
if (variant) {
|
|
4039
|
-
|
|
4138
|
+
fs21.renameSync(path19.join(ctx.taskDir, variant), outputPath);
|
|
4040
4139
|
logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
|
|
4041
4140
|
}
|
|
4042
4141
|
}
|
|
4043
4142
|
}
|
|
4044
4143
|
if (def.outputFile) {
|
|
4045
|
-
const outputPath =
|
|
4046
|
-
if (
|
|
4047
|
-
const content =
|
|
4144
|
+
const outputPath = path19.join(ctx.taskDir, def.outputFile);
|
|
4145
|
+
if (fs21.existsSync(outputPath)) {
|
|
4146
|
+
const content = fs21.readFileSync(outputPath, "utf-8");
|
|
4048
4147
|
const validation = validateStageOutput(def.name, content);
|
|
4049
4148
|
if (!validation.valid) {
|
|
4050
4149
|
if (def.name === "taskify") {
|
|
@@ -4058,7 +4157,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4058
4157
|
const stripped = stripFences(retryResult.output);
|
|
4059
4158
|
const retryValidation = validateTaskJson(stripped);
|
|
4060
4159
|
if (retryValidation.valid) {
|
|
4061
|
-
|
|
4160
|
+
fs21.writeFileSync(outputPath, retryResult.output);
|
|
4062
4161
|
logger.info(` taskify retry produced valid JSON`);
|
|
4063
4162
|
} else {
|
|
4064
4163
|
logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
|
|
@@ -4071,7 +4170,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4071
4170
|
risk_level: "low",
|
|
4072
4171
|
questions: []
|
|
4073
4172
|
}, null, 2);
|
|
4074
|
-
|
|
4173
|
+
fs21.writeFileSync(outputPath, fallback);
|
|
4075
4174
|
logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
|
|
4076
4175
|
}
|
|
4077
4176
|
}
|
|
@@ -4085,7 +4184,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
4085
4184
|
return { outcome: "completed", outputFile: def.outputFile, retries };
|
|
4086
4185
|
}
|
|
4087
4186
|
function appendStageContext(taskDir, stageName, output) {
|
|
4088
|
-
const contextPath =
|
|
4187
|
+
const contextPath = path19.join(taskDir, "context.md");
|
|
4089
4188
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
|
|
4090
4189
|
let summary;
|
|
4091
4190
|
if (output && output.trim()) {
|
|
@@ -4098,7 +4197,7 @@ function appendStageContext(taskDir, stageName, output) {
|
|
|
4098
4197
|
### ${stageName} (${timestamp2})
|
|
4099
4198
|
${summary}
|
|
4100
4199
|
`;
|
|
4101
|
-
|
|
4200
|
+
fs21.appendFileSync(contextPath, entry);
|
|
4102
4201
|
}
|
|
4103
4202
|
var SESSION_GROUP;
|
|
4104
4203
|
var init_agent = __esm({
|
|
@@ -4378,8 +4477,8 @@ Error context:
|
|
|
4378
4477
|
});
|
|
4379
4478
|
|
|
4380
4479
|
// src/stages/gate.ts
|
|
4381
|
-
import * as
|
|
4382
|
-
import * as
|
|
4480
|
+
import * as fs22 from "fs";
|
|
4481
|
+
import * as path20 from "path";
|
|
4383
4482
|
function executeGateStage(ctx, def) {
|
|
4384
4483
|
if (ctx.input.dryRun) {
|
|
4385
4484
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
@@ -4422,7 +4521,7 @@ ${output}
|
|
|
4422
4521
|
`);
|
|
4423
4522
|
}
|
|
4424
4523
|
}
|
|
4425
|
-
|
|
4524
|
+
fs22.writeFileSync(path20.join(ctx.taskDir, "verify.md"), lines.join(""));
|
|
4426
4525
|
return {
|
|
4427
4526
|
outcome: verifyResult.pass ? "completed" : "failed",
|
|
4428
4527
|
retries: 0
|
|
@@ -4437,8 +4536,8 @@ var init_gate = __esm({
|
|
|
4437
4536
|
});
|
|
4438
4537
|
|
|
4439
4538
|
// src/stages/verify.ts
|
|
4440
|
-
import * as
|
|
4441
|
-
import * as
|
|
4539
|
+
import * as fs23 from "fs";
|
|
4540
|
+
import * as path21 from "path";
|
|
4442
4541
|
import { execFileSync as execFileSync14 } from "child_process";
|
|
4443
4542
|
async function executeVerifyWithAutofix(ctx, def) {
|
|
4444
4543
|
const maxAttempts = def.maxRetries ?? 2;
|
|
@@ -4449,8 +4548,8 @@ async function executeVerifyWithAutofix(ctx, def) {
|
|
|
4449
4548
|
return { ...gateResult, retries: attempt };
|
|
4450
4549
|
}
|
|
4451
4550
|
if (attempt < maxAttempts) {
|
|
4452
|
-
const verifyPath =
|
|
4453
|
-
const errorOutput =
|
|
4551
|
+
const verifyPath = path21.join(ctx.taskDir, "verify.md");
|
|
4552
|
+
const errorOutput = fs23.existsSync(verifyPath) ? fs23.readFileSync(verifyPath, "utf-8") : "Unknown error";
|
|
4454
4553
|
const modifiedFiles = getModifiedFiles(ctx.projectDir);
|
|
4455
4554
|
const defaultRunner = getRunnerForStage(ctx, "taskify");
|
|
4456
4555
|
const diagConfig = getProjectConfig();
|
|
@@ -4546,8 +4645,8 @@ var init_verify = __esm({
|
|
|
4546
4645
|
});
|
|
4547
4646
|
|
|
4548
4647
|
// src/review-standalone.ts
|
|
4549
|
-
import * as
|
|
4550
|
-
import * as
|
|
4648
|
+
import * as fs24 from "fs";
|
|
4649
|
+
import * as path22 from "path";
|
|
4551
4650
|
function resolveReviewTarget(input) {
|
|
4552
4651
|
if (input.prs.length === 0) {
|
|
4553
4652
|
return {
|
|
@@ -4571,8 +4670,8 @@ Or comment on the specific PR: \`@kody review\``
|
|
|
4571
4670
|
}
|
|
4572
4671
|
async function runStandaloneReview(input) {
|
|
4573
4672
|
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
4574
|
-
const taskDir =
|
|
4575
|
-
|
|
4673
|
+
const taskDir = path22.join(input.projectDir, ".kody", "tasks", taskId);
|
|
4674
|
+
fs24.mkdirSync(taskDir, { recursive: true });
|
|
4576
4675
|
let diffInstruction = "";
|
|
4577
4676
|
let filesChangedSection = "";
|
|
4578
4677
|
if (input.baseBranch) {
|
|
@@ -4599,7 +4698,7 @@ ${fileList}`;
|
|
|
4599
4698
|
const taskContent = `# ${input.prTitle}
|
|
4600
4699
|
|
|
4601
4700
|
${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
4602
|
-
|
|
4701
|
+
fs24.writeFileSync(path22.join(taskDir, "task.md"), taskContent);
|
|
4603
4702
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
4604
4703
|
const ctx = {
|
|
4605
4704
|
taskId,
|
|
@@ -4621,10 +4720,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
|
4621
4720
|
error: result2.error ?? "Review stage failed"
|
|
4622
4721
|
};
|
|
4623
4722
|
}
|
|
4624
|
-
const reviewPath =
|
|
4723
|
+
const reviewPath = path22.join(taskDir, "review.md");
|
|
4625
4724
|
let reviewContent;
|
|
4626
|
-
if (
|
|
4627
|
-
reviewContent =
|
|
4725
|
+
if (fs24.existsSync(reviewPath)) {
|
|
4726
|
+
reviewContent = fs24.readFileSync(reviewPath, "utf-8");
|
|
4628
4727
|
}
|
|
4629
4728
|
return {
|
|
4630
4729
|
outcome: "completed",
|
|
@@ -4664,8 +4763,8 @@ var init_review_standalone = __esm({
|
|
|
4664
4763
|
});
|
|
4665
4764
|
|
|
4666
4765
|
// src/stages/review.ts
|
|
4667
|
-
import * as
|
|
4668
|
-
import * as
|
|
4766
|
+
import * as fs25 from "fs";
|
|
4767
|
+
import * as path23 from "path";
|
|
4669
4768
|
async function executeReviewWithFix(ctx, def) {
|
|
4670
4769
|
if (ctx.input.dryRun) {
|
|
4671
4770
|
return { outcome: "completed", retries: 0 };
|
|
@@ -4679,11 +4778,11 @@ async function executeReviewWithFix(ctx, def) {
|
|
|
4679
4778
|
if (reviewResult.outcome !== "completed") {
|
|
4680
4779
|
return reviewResult;
|
|
4681
4780
|
}
|
|
4682
|
-
const reviewFile =
|
|
4683
|
-
if (!
|
|
4781
|
+
const reviewFile = path23.join(ctx.taskDir, "review.md");
|
|
4782
|
+
if (!fs25.existsSync(reviewFile)) {
|
|
4684
4783
|
return { outcome: "failed", retries: iteration, error: "review.md not found" };
|
|
4685
4784
|
}
|
|
4686
|
-
const content =
|
|
4785
|
+
const content = fs25.readFileSync(reviewFile, "utf-8");
|
|
4687
4786
|
if (detectReviewVerdict(content) !== "fail") {
|
|
4688
4787
|
return { ...reviewResult, retries: iteration };
|
|
4689
4788
|
}
|
|
@@ -4712,15 +4811,15 @@ var init_review = __esm({
|
|
|
4712
4811
|
});
|
|
4713
4812
|
|
|
4714
4813
|
// src/stages/ship.ts
|
|
4715
|
-
import * as
|
|
4716
|
-
import * as
|
|
4814
|
+
import * as fs26 from "fs";
|
|
4815
|
+
import * as path24 from "path";
|
|
4717
4816
|
import { execFileSync as execFileSync15 } from "child_process";
|
|
4718
4817
|
function buildPrBody(ctx) {
|
|
4719
4818
|
const sections = [];
|
|
4720
|
-
const taskJsonPath =
|
|
4721
|
-
if (
|
|
4819
|
+
const taskJsonPath = path24.join(ctx.taskDir, "task.json");
|
|
4820
|
+
if (fs26.existsSync(taskJsonPath)) {
|
|
4722
4821
|
try {
|
|
4723
|
-
const raw =
|
|
4822
|
+
const raw = fs26.readFileSync(taskJsonPath, "utf-8");
|
|
4724
4823
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4725
4824
|
const task = JSON.parse(cleaned);
|
|
4726
4825
|
if (task.description) {
|
|
@@ -4739,9 +4838,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
4739
4838
|
} catch {
|
|
4740
4839
|
}
|
|
4741
4840
|
}
|
|
4742
|
-
const reviewPath =
|
|
4743
|
-
if (
|
|
4744
|
-
const review =
|
|
4841
|
+
const reviewPath = path24.join(ctx.taskDir, "review.md");
|
|
4842
|
+
if (fs26.existsSync(reviewPath)) {
|
|
4843
|
+
const review = fs26.readFileSync(reviewPath, "utf-8");
|
|
4745
4844
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
4746
4845
|
if (summaryMatch) {
|
|
4747
4846
|
const summary = summaryMatch[1].trim();
|
|
@@ -4758,14 +4857,14 @@ ${summary}`);
|
|
|
4758
4857
|
**Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
|
|
4759
4858
|
}
|
|
4760
4859
|
}
|
|
4761
|
-
const verifyPath =
|
|
4762
|
-
if (
|
|
4763
|
-
const verify =
|
|
4860
|
+
const verifyPath = path24.join(ctx.taskDir, "verify.md");
|
|
4861
|
+
if (fs26.existsSync(verifyPath)) {
|
|
4862
|
+
const verify = fs26.readFileSync(verifyPath, "utf-8");
|
|
4764
4863
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
4765
4864
|
}
|
|
4766
|
-
const planPath =
|
|
4767
|
-
if (
|
|
4768
|
-
const plan =
|
|
4865
|
+
const planPath = path24.join(ctx.taskDir, "plan.md");
|
|
4866
|
+
if (fs26.existsSync(planPath)) {
|
|
4867
|
+
const plan = fs26.readFileSync(planPath, "utf-8").trim();
|
|
4769
4868
|
if (plan) {
|
|
4770
4869
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
4771
4870
|
sections.push(`
|
|
@@ -4785,13 +4884,13 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
4785
4884
|
return sections.join("\n");
|
|
4786
4885
|
}
|
|
4787
4886
|
function executeShipStage(ctx, _def) {
|
|
4788
|
-
const shipPath =
|
|
4887
|
+
const shipPath = path24.join(ctx.taskDir, "ship.md");
|
|
4789
4888
|
if (ctx.input.dryRun) {
|
|
4790
|
-
|
|
4889
|
+
fs26.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
4791
4890
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
4792
4891
|
}
|
|
4793
4892
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
4794
|
-
|
|
4893
|
+
fs26.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
4795
4894
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
4796
4895
|
}
|
|
4797
4896
|
try {
|
|
@@ -4838,28 +4937,28 @@ function executeShipStage(ctx, _def) {
|
|
|
4838
4937
|
chore: "chore"
|
|
4839
4938
|
};
|
|
4840
4939
|
let prefix = "chore";
|
|
4841
|
-
const taskJsonPath =
|
|
4842
|
-
if (
|
|
4940
|
+
const taskJsonPath = path24.join(ctx.taskDir, "task.json");
|
|
4941
|
+
if (fs26.existsSync(taskJsonPath)) {
|
|
4843
4942
|
try {
|
|
4844
|
-
const raw =
|
|
4943
|
+
const raw = fs26.readFileSync(taskJsonPath, "utf-8");
|
|
4845
4944
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4846
4945
|
const task = JSON.parse(cleaned);
|
|
4847
4946
|
prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
4848
4947
|
} catch {
|
|
4849
4948
|
}
|
|
4850
4949
|
}
|
|
4851
|
-
const taskMdPath =
|
|
4852
|
-
if (
|
|
4853
|
-
const content =
|
|
4950
|
+
const taskMdPath = path24.join(ctx.taskDir, "task.md");
|
|
4951
|
+
if (fs26.existsSync(taskMdPath)) {
|
|
4952
|
+
const content = fs26.readFileSync(taskMdPath, "utf-8");
|
|
4854
4953
|
const heading = content.split("\n").find((l) => l.startsWith("# "));
|
|
4855
4954
|
if (heading) {
|
|
4856
4955
|
title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
|
|
4857
4956
|
}
|
|
4858
4957
|
}
|
|
4859
4958
|
if (title === "Update") {
|
|
4860
|
-
if (
|
|
4959
|
+
if (fs26.existsSync(taskJsonPath)) {
|
|
4861
4960
|
try {
|
|
4862
|
-
const raw =
|
|
4961
|
+
const raw = fs26.readFileSync(taskJsonPath, "utf-8");
|
|
4863
4962
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4864
4963
|
const task = JSON.parse(cleaned);
|
|
4865
4964
|
if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
|
|
@@ -4882,7 +4981,7 @@ function executeShipStage(ctx, _def) {
|
|
|
4882
4981
|
} catch {
|
|
4883
4982
|
}
|
|
4884
4983
|
}
|
|
4885
|
-
|
|
4984
|
+
fs26.writeFileSync(shipPath, `# Ship
|
|
4886
4985
|
|
|
4887
4986
|
Updated existing PR: ${existingPr.url}
|
|
4888
4987
|
PR #${existingPr.number}
|
|
@@ -4903,20 +5002,20 @@ PR #${existingPr.number}
|
|
|
4903
5002
|
} catch {
|
|
4904
5003
|
}
|
|
4905
5004
|
}
|
|
4906
|
-
|
|
5005
|
+
fs26.writeFileSync(shipPath, `# Ship
|
|
4907
5006
|
|
|
4908
5007
|
PR created: ${pr.url}
|
|
4909
5008
|
PR #${pr.number}
|
|
4910
5009
|
`);
|
|
4911
5010
|
} else {
|
|
4912
|
-
|
|
5011
|
+
fs26.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
4913
5012
|
}
|
|
4914
5013
|
}
|
|
4915
5014
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
4916
5015
|
} catch (err) {
|
|
4917
5016
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4918
5017
|
try {
|
|
4919
|
-
|
|
5018
|
+
fs26.writeFileSync(shipPath, `# Ship
|
|
4920
5019
|
|
|
4921
5020
|
Failed: ${msg}
|
|
4922
5021
|
`);
|
|
@@ -4965,15 +5064,15 @@ var init_executor_registry = __esm({
|
|
|
4965
5064
|
});
|
|
4966
5065
|
|
|
4967
5066
|
// src/pipeline/questions.ts
|
|
4968
|
-
import * as
|
|
4969
|
-
import * as
|
|
5067
|
+
import * as fs27 from "fs";
|
|
5068
|
+
import * as path25 from "path";
|
|
4970
5069
|
function checkForQuestions(ctx, stageName) {
|
|
4971
5070
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
4972
5071
|
try {
|
|
4973
5072
|
if (stageName === "taskify") {
|
|
4974
|
-
const taskJsonPath =
|
|
4975
|
-
if (!
|
|
4976
|
-
const raw =
|
|
5073
|
+
const taskJsonPath = path25.join(ctx.taskDir, "task.json");
|
|
5074
|
+
if (!fs27.existsSync(taskJsonPath)) return false;
|
|
5075
|
+
const raw = fs27.readFileSync(taskJsonPath, "utf-8");
|
|
4977
5076
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
4978
5077
|
const taskJson = JSON.parse(cleaned);
|
|
4979
5078
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -4988,9 +5087,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
4988
5087
|
}
|
|
4989
5088
|
}
|
|
4990
5089
|
if (stageName === "plan") {
|
|
4991
|
-
const planPath =
|
|
4992
|
-
if (!
|
|
4993
|
-
const plan =
|
|
5090
|
+
const planPath = path25.join(ctx.taskDir, "plan.md");
|
|
5091
|
+
if (!fs27.existsSync(planPath)) return false;
|
|
5092
|
+
const plan = fs27.readFileSync(planPath, "utf-8");
|
|
4994
5093
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
4995
5094
|
if (questionsMatch) {
|
|
4996
5095
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -5019,12 +5118,15 @@ var init_questions = __esm({
|
|
|
5019
5118
|
});
|
|
5020
5119
|
|
|
5021
5120
|
// src/pipeline/hooks.ts
|
|
5022
|
-
import * as
|
|
5023
|
-
import * as
|
|
5121
|
+
import * as fs28 from "fs";
|
|
5122
|
+
import * as path26 from "path";
|
|
5024
5123
|
function applyPreStageLabel(ctx, def) {
|
|
5025
5124
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
5125
|
+
if (def.name === "plan") setLifecycleLabel(ctx.input.issueNumber, "planning");
|
|
5026
5126
|
if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
|
|
5127
|
+
if (def.name === "verify") setLifecycleLabel(ctx.input.issueNumber, "verifying");
|
|
5027
5128
|
if (def.name === "review") setLifecycleLabel(ctx.input.issueNumber, "review");
|
|
5129
|
+
if (def.name === "review-fix") setLifecycleLabel(ctx.input.issueNumber, "fixing");
|
|
5028
5130
|
if (def.name === "ship") setLifecycleLabel(ctx.input.issueNumber, "shipping");
|
|
5029
5131
|
}
|
|
5030
5132
|
function checkQuestionsAfterStage(ctx, def, state) {
|
|
@@ -5058,9 +5160,9 @@ function autoDetectComplexity(ctx, def) {
|
|
|
5058
5160
|
return { complexity, activeStages };
|
|
5059
5161
|
}
|
|
5060
5162
|
try {
|
|
5061
|
-
const taskJsonPath =
|
|
5062
|
-
if (!
|
|
5063
|
-
const raw =
|
|
5163
|
+
const taskJsonPath = path26.join(ctx.taskDir, "task.json");
|
|
5164
|
+
if (!fs28.existsSync(taskJsonPath)) return null;
|
|
5165
|
+
const raw = fs28.readFileSync(taskJsonPath, "utf-8");
|
|
5064
5166
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
5065
5167
|
const taskJson = JSON.parse(cleaned);
|
|
5066
5168
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -5090,8 +5192,8 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
5090
5192
|
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
5091
5193
|
if (ctx.input.mode === "rerun") return null;
|
|
5092
5194
|
if (!ctx.input.issueNumber) return null;
|
|
5093
|
-
const planPath =
|
|
5094
|
-
const plan =
|
|
5195
|
+
const planPath = path26.join(ctx.taskDir, "plan.md");
|
|
5196
|
+
const plan = fs28.existsSync(planPath) ? fs28.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
|
|
5095
5197
|
try {
|
|
5096
5198
|
postComment(
|
|
5097
5199
|
ctx.input.issueNumber,
|
|
@@ -5158,22 +5260,22 @@ var init_hooks = __esm({
|
|
|
5158
5260
|
});
|
|
5159
5261
|
|
|
5160
5262
|
// src/learning/auto-learn.ts
|
|
5161
|
-
import * as
|
|
5162
|
-
import * as
|
|
5263
|
+
import * as fs29 from "fs";
|
|
5264
|
+
import * as path27 from "path";
|
|
5163
5265
|
function stripAnsi(str) {
|
|
5164
5266
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
5165
5267
|
}
|
|
5166
5268
|
function autoLearn(ctx) {
|
|
5167
5269
|
try {
|
|
5168
|
-
const memoryDir =
|
|
5169
|
-
if (!
|
|
5170
|
-
|
|
5270
|
+
const memoryDir = path27.join(ctx.projectDir, ".kody", "memory");
|
|
5271
|
+
if (!fs29.existsSync(memoryDir)) {
|
|
5272
|
+
fs29.mkdirSync(memoryDir, { recursive: true });
|
|
5171
5273
|
}
|
|
5172
5274
|
const learnings = [];
|
|
5173
5275
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5174
|
-
const verifyPath =
|
|
5175
|
-
if (
|
|
5176
|
-
const verify = stripAnsi(
|
|
5276
|
+
const verifyPath = path27.join(ctx.taskDir, "verify.md");
|
|
5277
|
+
if (fs29.existsSync(verifyPath)) {
|
|
5278
|
+
const verify = stripAnsi(fs29.readFileSync(verifyPath, "utf-8"));
|
|
5177
5279
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
5178
5280
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
5179
5281
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -5182,18 +5284,18 @@ function autoLearn(ctx) {
|
|
|
5182
5284
|
if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
|
|
5183
5285
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
5184
5286
|
}
|
|
5185
|
-
const reviewPath =
|
|
5186
|
-
if (
|
|
5187
|
-
const review =
|
|
5287
|
+
const reviewPath = path27.join(ctx.taskDir, "review.md");
|
|
5288
|
+
if (fs29.existsSync(reviewPath)) {
|
|
5289
|
+
const review = fs29.readFileSync(reviewPath, "utf-8");
|
|
5188
5290
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
5189
5291
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
5190
5292
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
5191
5293
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
5192
5294
|
}
|
|
5193
|
-
const taskJsonPath =
|
|
5194
|
-
if (
|
|
5295
|
+
const taskJsonPath = path27.join(ctx.taskDir, "task.json");
|
|
5296
|
+
if (fs29.existsSync(taskJsonPath)) {
|
|
5195
5297
|
try {
|
|
5196
|
-
const raw = stripAnsi(
|
|
5298
|
+
const raw = stripAnsi(fs29.readFileSync(taskJsonPath, "utf-8"));
|
|
5197
5299
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
5198
5300
|
const task = JSON.parse(cleaned);
|
|
5199
5301
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -5204,12 +5306,12 @@ function autoLearn(ctx) {
|
|
|
5204
5306
|
}
|
|
5205
5307
|
}
|
|
5206
5308
|
if (learnings.length > 0) {
|
|
5207
|
-
const conventionsPath =
|
|
5309
|
+
const conventionsPath = path27.join(memoryDir, "conventions.md");
|
|
5208
5310
|
const entry = `
|
|
5209
5311
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
5210
5312
|
${learnings.join("\n")}
|
|
5211
5313
|
`;
|
|
5212
|
-
|
|
5314
|
+
fs29.appendFileSync(conventionsPath, entry);
|
|
5213
5315
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
5214
5316
|
}
|
|
5215
5317
|
autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
|
|
@@ -5217,8 +5319,8 @@ ${learnings.join("\n")}
|
|
|
5217
5319
|
}
|
|
5218
5320
|
}
|
|
5219
5321
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
5220
|
-
const archPath =
|
|
5221
|
-
if (
|
|
5322
|
+
const archPath = path27.join(memoryDir, "architecture.md");
|
|
5323
|
+
if (fs29.existsSync(archPath)) return;
|
|
5222
5324
|
const detected = detectArchitectureBasic(projectDir);
|
|
5223
5325
|
if (detected.length > 0) {
|
|
5224
5326
|
const content = `# Architecture (auto-detected ${timestamp2})
|
|
@@ -5226,7 +5328,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
5226
5328
|
## Overview
|
|
5227
5329
|
${detected.join("\n")}
|
|
5228
5330
|
`;
|
|
5229
|
-
|
|
5331
|
+
fs29.writeFileSync(archPath, content);
|
|
5230
5332
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
5231
5333
|
}
|
|
5232
5334
|
}
|
|
@@ -5239,13 +5341,13 @@ var init_auto_learn = __esm({
|
|
|
5239
5341
|
});
|
|
5240
5342
|
|
|
5241
5343
|
// src/retrospective.ts
|
|
5242
|
-
import * as
|
|
5243
|
-
import * as
|
|
5344
|
+
import * as fs30 from "fs";
|
|
5345
|
+
import * as path28 from "path";
|
|
5244
5346
|
function readArtifact(taskDir, filename, maxChars) {
|
|
5245
|
-
const p =
|
|
5246
|
-
if (!
|
|
5347
|
+
const p = path28.join(taskDir, filename);
|
|
5348
|
+
if (!fs30.existsSync(p)) return null;
|
|
5247
5349
|
try {
|
|
5248
|
-
const content =
|
|
5350
|
+
const content = fs30.readFileSync(p, "utf-8");
|
|
5249
5351
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
5250
5352
|
} catch {
|
|
5251
5353
|
return null;
|
|
@@ -5298,13 +5400,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
|
|
|
5298
5400
|
return lines.join("\n");
|
|
5299
5401
|
}
|
|
5300
5402
|
function getLogPath(projectDir) {
|
|
5301
|
-
return
|
|
5403
|
+
return path28.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
5302
5404
|
}
|
|
5303
5405
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
5304
5406
|
const logPath = getLogPath(projectDir);
|
|
5305
|
-
if (!
|
|
5407
|
+
if (!fs30.existsSync(logPath)) return [];
|
|
5306
5408
|
try {
|
|
5307
|
-
const content =
|
|
5409
|
+
const content = fs30.readFileSync(logPath, "utf-8");
|
|
5308
5410
|
const lines = content.split("\n").filter(Boolean);
|
|
5309
5411
|
const entries = [];
|
|
5310
5412
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -5331,11 +5433,11 @@ function formatPreviousEntries(entries) {
|
|
|
5331
5433
|
}
|
|
5332
5434
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
5333
5435
|
const logPath = getLogPath(projectDir);
|
|
5334
|
-
const dir =
|
|
5335
|
-
if (!
|
|
5336
|
-
|
|
5436
|
+
const dir = path28.dirname(logPath);
|
|
5437
|
+
if (!fs30.existsSync(dir)) {
|
|
5438
|
+
fs30.mkdirSync(dir, { recursive: true });
|
|
5337
5439
|
}
|
|
5338
|
-
|
|
5440
|
+
fs30.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
5339
5441
|
}
|
|
5340
5442
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
5341
5443
|
if (ctx.input.dryRun) return;
|
|
@@ -5503,8 +5605,8 @@ var init_summary = __esm({
|
|
|
5503
5605
|
});
|
|
5504
5606
|
|
|
5505
5607
|
// src/pipeline.ts
|
|
5506
|
-
import * as
|
|
5507
|
-
import * as
|
|
5608
|
+
import * as fs31 from "fs";
|
|
5609
|
+
import * as path29 from "path";
|
|
5508
5610
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
5509
5611
|
if (ctx.input.dryRun) return;
|
|
5510
5612
|
if (ctx.input.prNumber) {
|
|
@@ -5517,8 +5619,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
5517
5619
|
}
|
|
5518
5620
|
if (!ctx.input.issueNumber) return;
|
|
5519
5621
|
try {
|
|
5520
|
-
const taskMdPath =
|
|
5521
|
-
const title =
|
|
5622
|
+
const taskMdPath = path29.join(ctx.taskDir, "task.md");
|
|
5623
|
+
const title = fs31.existsSync(taskMdPath) ? fs31.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
5522
5624
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
5523
5625
|
syncWithDefault(ctx.projectDir);
|
|
5524
5626
|
} catch (err) {
|
|
@@ -5532,10 +5634,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
5532
5634
|
}
|
|
5533
5635
|
}
|
|
5534
5636
|
function acquireLock(taskDir) {
|
|
5535
|
-
const lockPath =
|
|
5536
|
-
if (
|
|
5637
|
+
const lockPath = path29.join(taskDir, ".lock");
|
|
5638
|
+
if (fs31.existsSync(lockPath)) {
|
|
5537
5639
|
try {
|
|
5538
|
-
const pid = parseInt(
|
|
5640
|
+
const pid = parseInt(fs31.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
5539
5641
|
if (!isNaN(pid)) {
|
|
5540
5642
|
try {
|
|
5541
5643
|
process.kill(pid, 0);
|
|
@@ -5552,14 +5654,14 @@ function acquireLock(taskDir) {
|
|
|
5552
5654
|
logger.warn(` Corrupt lock file \u2014 overwriting`);
|
|
5553
5655
|
}
|
|
5554
5656
|
try {
|
|
5555
|
-
|
|
5657
|
+
fs31.unlinkSync(lockPath);
|
|
5556
5658
|
} catch {
|
|
5557
5659
|
}
|
|
5558
5660
|
}
|
|
5559
5661
|
try {
|
|
5560
|
-
const fd =
|
|
5561
|
-
|
|
5562
|
-
|
|
5662
|
+
const fd = fs31.openSync(lockPath, fs31.constants.O_WRONLY | fs31.constants.O_CREAT | fs31.constants.O_EXCL);
|
|
5663
|
+
fs31.writeSync(fd, String(process.pid));
|
|
5664
|
+
fs31.closeSync(fd);
|
|
5563
5665
|
} catch (err) {
|
|
5564
5666
|
if (err.code === "EEXIST") {
|
|
5565
5667
|
throw new Error("Pipeline already running (lock acquired by another process)");
|
|
@@ -5569,7 +5671,7 @@ function acquireLock(taskDir) {
|
|
|
5569
5671
|
}
|
|
5570
5672
|
function releaseLock(taskDir) {
|
|
5571
5673
|
try {
|
|
5572
|
-
|
|
5674
|
+
fs31.unlinkSync(path29.join(taskDir, ".lock"));
|
|
5573
5675
|
} catch {
|
|
5574
5676
|
}
|
|
5575
5677
|
}
|
|
@@ -5628,6 +5730,11 @@ async function runPipelineInner(ctx) {
|
|
|
5628
5730
|
setLifecycleLabel(ctx.input.issueNumber, initialPhase);
|
|
5629
5731
|
}
|
|
5630
5732
|
ensureFeatureBranchIfNeeded(ctx);
|
|
5733
|
+
if (ctx.tools?.length) {
|
|
5734
|
+
ciGroup("Tool Setup");
|
|
5735
|
+
runToolSetup(ctx.tools, ctx.projectDir);
|
|
5736
|
+
ciGroupEnd();
|
|
5737
|
+
}
|
|
5631
5738
|
let complexity = ctx.input.complexity ?? "high";
|
|
5632
5739
|
let activeStages = filterByComplexity(STAGES, complexity);
|
|
5633
5740
|
let skippedStagesCommentPosted = false;
|
|
@@ -5773,12 +5880,13 @@ var init_pipeline = __esm({
|
|
|
5773
5880
|
init_retrospective();
|
|
5774
5881
|
init_summary();
|
|
5775
5882
|
init_config();
|
|
5883
|
+
init_tools();
|
|
5776
5884
|
}
|
|
5777
5885
|
});
|
|
5778
5886
|
|
|
5779
5887
|
// src/preflight.ts
|
|
5780
5888
|
import { execFileSync as execFileSync16 } from "child_process";
|
|
5781
|
-
import * as
|
|
5889
|
+
import * as fs32 from "fs";
|
|
5782
5890
|
function check(name, fn) {
|
|
5783
5891
|
try {
|
|
5784
5892
|
const detail = fn() ?? void 0;
|
|
@@ -5831,7 +5939,7 @@ function runPreflight() {
|
|
|
5831
5939
|
return v;
|
|
5832
5940
|
}),
|
|
5833
5941
|
check("package.json", () => {
|
|
5834
|
-
if (!
|
|
5942
|
+
if (!fs32.existsSync("package.json")) throw new Error("not found");
|
|
5835
5943
|
})
|
|
5836
5944
|
];
|
|
5837
5945
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -5908,8 +6016,8 @@ var init_args = __esm({
|
|
|
5908
6016
|
});
|
|
5909
6017
|
|
|
5910
6018
|
// src/cli/task-state.ts
|
|
5911
|
-
import * as
|
|
5912
|
-
import * as
|
|
6019
|
+
import * as fs33 from "fs";
|
|
6020
|
+
import * as path30 from "path";
|
|
5913
6021
|
function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
5914
6022
|
if (!existingTaskId || !existingState) {
|
|
5915
6023
|
return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
|
|
@@ -5941,11 +6049,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
|
|
|
5941
6049
|
function resolveForIssue(issueNumber, projectDir) {
|
|
5942
6050
|
const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
|
|
5943
6051
|
if (existingTaskId) {
|
|
5944
|
-
const statusPath =
|
|
6052
|
+
const statusPath = path30.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
|
|
5945
6053
|
let existingState = null;
|
|
5946
|
-
if (
|
|
6054
|
+
if (fs33.existsSync(statusPath)) {
|
|
5947
6055
|
try {
|
|
5948
|
-
existingState = JSON.parse(
|
|
6056
|
+
existingState = JSON.parse(fs33.readFileSync(statusPath, "utf-8"));
|
|
5949
6057
|
} catch {
|
|
5950
6058
|
}
|
|
5951
6059
|
}
|
|
@@ -6102,8 +6210,8 @@ var init_resolve = __esm({
|
|
|
6102
6210
|
|
|
6103
6211
|
// src/entry.ts
|
|
6104
6212
|
var entry_exports = {};
|
|
6105
|
-
import * as
|
|
6106
|
-
import * as
|
|
6213
|
+
import * as fs34 from "fs";
|
|
6214
|
+
import * as path31 from "path";
|
|
6107
6215
|
async function ensureLitellmProxy(config, projectDir) {
|
|
6108
6216
|
if (!anyStageNeedsProxy(config)) return null;
|
|
6109
6217
|
const litellmUrl = getLitellmUrl();
|
|
@@ -6158,9 +6266,9 @@ async function runModelHealthCheck(config) {
|
|
|
6158
6266
|
}
|
|
6159
6267
|
async function main() {
|
|
6160
6268
|
const input = parseArgs();
|
|
6161
|
-
const projectDir = input.cwd ?
|
|
6269
|
+
const projectDir = input.cwd ? path31.resolve(input.cwd) : process.cwd();
|
|
6162
6270
|
if (input.cwd) {
|
|
6163
|
-
if (!
|
|
6271
|
+
if (!fs34.existsSync(projectDir)) {
|
|
6164
6272
|
console.error(`--cwd path does not exist: ${projectDir}`);
|
|
6165
6273
|
process.exit(1);
|
|
6166
6274
|
}
|
|
@@ -6220,8 +6328,8 @@ async function main() {
|
|
|
6220
6328
|
process.exit(1);
|
|
6221
6329
|
}
|
|
6222
6330
|
}
|
|
6223
|
-
const taskDir =
|
|
6224
|
-
|
|
6331
|
+
const taskDir = path31.join(projectDir, ".kody", "tasks", taskId);
|
|
6332
|
+
fs34.mkdirSync(taskDir, { recursive: true });
|
|
6225
6333
|
if (input.command === "rerun" && isTaskifyRun(taskDir)) {
|
|
6226
6334
|
const marker = readTaskifyMarker(taskDir);
|
|
6227
6335
|
if (marker) {
|
|
@@ -6353,31 +6461,31 @@ async function main() {
|
|
|
6353
6461
|
logger.info("Preflight checks:");
|
|
6354
6462
|
runPreflight();
|
|
6355
6463
|
if (input.task) {
|
|
6356
|
-
|
|
6464
|
+
fs34.writeFileSync(path31.join(taskDir, "task.md"), input.task);
|
|
6357
6465
|
}
|
|
6358
|
-
const taskMdPath =
|
|
6359
|
-
if (!
|
|
6466
|
+
const taskMdPath = path31.join(taskDir, "task.md");
|
|
6467
|
+
if (!fs34.existsSync(taskMdPath) && isPRFix && input.prNumber) {
|
|
6360
6468
|
logger.info(`Fetching PR #${input.prNumber} details as task context...`);
|
|
6361
6469
|
const prDetails = getPRDetails(input.prNumber);
|
|
6362
6470
|
if (prDetails) {
|
|
6363
6471
|
const taskContent = `# ${prDetails.title}
|
|
6364
6472
|
|
|
6365
6473
|
${prDetails.body ?? ""}`;
|
|
6366
|
-
|
|
6474
|
+
fs34.writeFileSync(taskMdPath, taskContent);
|
|
6367
6475
|
logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
|
|
6368
6476
|
}
|
|
6369
|
-
} else if (!
|
|
6477
|
+
} else if (!fs34.existsSync(taskMdPath) && input.issueNumber) {
|
|
6370
6478
|
logger.info(`Fetching issue #${input.issueNumber} body as task...`);
|
|
6371
6479
|
const issue = getIssue(input.issueNumber);
|
|
6372
6480
|
if (issue) {
|
|
6373
6481
|
const taskContent = `# ${issue.title}
|
|
6374
6482
|
|
|
6375
6483
|
${issue.body ?? ""}`;
|
|
6376
|
-
|
|
6484
|
+
fs34.writeFileSync(taskMdPath, taskContent);
|
|
6377
6485
|
logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
|
|
6378
6486
|
}
|
|
6379
6487
|
}
|
|
6380
|
-
if (!
|
|
6488
|
+
if (!fs34.existsSync(taskMdPath)) {
|
|
6381
6489
|
console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
|
|
6382
6490
|
process.exit(1);
|
|
6383
6491
|
}
|
|
@@ -6481,11 +6589,17 @@ ${input.feedback}`);
|
|
|
6481
6589
|
prBaseBranch = prDetails.baseBranch;
|
|
6482
6590
|
}
|
|
6483
6591
|
}
|
|
6592
|
+
const toolDeclarations = loadToolDeclarations(projectDir);
|
|
6593
|
+
const detectedTools = detectTools(toolDeclarations, projectDir);
|
|
6594
|
+
if (detectedTools.length > 0) {
|
|
6595
|
+
logger.info(`Tools detected: ${detectedTools.map((t) => t.name).join(", ")}`);
|
|
6596
|
+
}
|
|
6484
6597
|
const ctx = {
|
|
6485
6598
|
taskId,
|
|
6486
6599
|
taskDir,
|
|
6487
6600
|
projectDir,
|
|
6488
6601
|
runners,
|
|
6602
|
+
tools: detectedTools.length > 0 ? detectedTools : void 0,
|
|
6489
6603
|
input: {
|
|
6490
6604
|
mode: input.command === "rerun" || input.command === "fix" || input.command === "fix-ci" ? "rerun" : "full",
|
|
6491
6605
|
fromStage: input.fromStage,
|
|
@@ -6515,7 +6629,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
|
|
|
6515
6629
|
}
|
|
6516
6630
|
}
|
|
6517
6631
|
const state = await runPipeline(ctx);
|
|
6518
|
-
const files =
|
|
6632
|
+
const files = fs34.readdirSync(taskDir);
|
|
6519
6633
|
console.log(`
|
|
6520
6634
|
Artifacts in ${taskDir}:`);
|
|
6521
6635
|
for (const f of files) {
|
|
@@ -6563,6 +6677,7 @@ var init_entry = __esm({
|
|
|
6563
6677
|
init_task_state();
|
|
6564
6678
|
init_taskify_command();
|
|
6565
6679
|
init_config();
|
|
6680
|
+
init_tools();
|
|
6566
6681
|
main().catch(async (err) => {
|
|
6567
6682
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6568
6683
|
console.error(msg);
|
|
@@ -6580,8 +6695,8 @@ var init_entry = __esm({
|
|
|
6580
6695
|
});
|
|
6581
6696
|
|
|
6582
6697
|
// src/bin/cli.ts
|
|
6583
|
-
import * as
|
|
6584
|
-
import * as
|
|
6698
|
+
import * as fs35 from "fs";
|
|
6699
|
+
import * as path32 from "path";
|
|
6585
6700
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6586
6701
|
|
|
6587
6702
|
// src/bin/commands/init.ts
|
|
@@ -6953,13 +7068,239 @@ function initCommand(opts, pkgRoot) {
|
|
|
6953
7068
|
|
|
6954
7069
|
// src/bin/commands/bootstrap.ts
|
|
6955
7070
|
init_architecture_detection();
|
|
6956
|
-
import * as
|
|
6957
|
-
import * as
|
|
7071
|
+
import * as fs9 from "fs";
|
|
7072
|
+
import * as path8 from "path";
|
|
6958
7073
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
6959
7074
|
|
|
6960
7075
|
// src/bin/qa-guide.ts
|
|
7076
|
+
import * as fs6 from "fs";
|
|
7077
|
+
import * as path5 from "path";
|
|
7078
|
+
|
|
7079
|
+
// src/bin/framework-detectors.ts
|
|
6961
7080
|
import * as fs5 from "fs";
|
|
6962
7081
|
import * as path4 from "path";
|
|
7082
|
+
function detectFrameworks(cwd) {
|
|
7083
|
+
const frameworks = [];
|
|
7084
|
+
let deps = {};
|
|
7085
|
+
try {
|
|
7086
|
+
const pkg = JSON.parse(fs5.readFileSync(path4.join(cwd, "package.json"), "utf-8"));
|
|
7087
|
+
deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7088
|
+
} catch {
|
|
7089
|
+
return frameworks;
|
|
7090
|
+
}
|
|
7091
|
+
if (deps.payload || deps["@payloadcms/next"]) {
|
|
7092
|
+
frameworks.push({
|
|
7093
|
+
name: "payload-cms",
|
|
7094
|
+
version: deps.payload ?? deps["@payloadcms/next"] ?? null,
|
|
7095
|
+
configFile: findFile(cwd, ["payload.config.ts", "payload-config.ts", "src/payload.config.ts"])
|
|
7096
|
+
});
|
|
7097
|
+
}
|
|
7098
|
+
if (deps["next-auth"]) {
|
|
7099
|
+
frameworks.push({
|
|
7100
|
+
name: "nextauth",
|
|
7101
|
+
version: deps["next-auth"] ?? null,
|
|
7102
|
+
configFile: findFile(cwd, ["auth.ts", "auth.config.ts", "src/auth.ts", "src/auth.config.ts"])
|
|
7103
|
+
});
|
|
7104
|
+
}
|
|
7105
|
+
if (deps.prisma || deps["@prisma/client"]) {
|
|
7106
|
+
frameworks.push({
|
|
7107
|
+
name: "prisma",
|
|
7108
|
+
version: deps.prisma ?? deps["@prisma/client"] ?? null,
|
|
7109
|
+
configFile: findFile(cwd, ["prisma/schema.prisma"])
|
|
7110
|
+
});
|
|
7111
|
+
}
|
|
7112
|
+
return frameworks;
|
|
7113
|
+
}
|
|
7114
|
+
function findFile(cwd, candidates) {
|
|
7115
|
+
for (const c of candidates) {
|
|
7116
|
+
if (fs5.existsSync(path4.join(cwd, c))) return c;
|
|
7117
|
+
}
|
|
7118
|
+
return null;
|
|
7119
|
+
}
|
|
7120
|
+
var COLLECTION_DIRS = [
|
|
7121
|
+
"src/server/payload/collections",
|
|
7122
|
+
"src/payload/collections",
|
|
7123
|
+
"src/collections",
|
|
7124
|
+
"payload/collections"
|
|
7125
|
+
];
|
|
7126
|
+
function discoverPayloadCollections(cwd) {
|
|
7127
|
+
const collections = [];
|
|
7128
|
+
for (const dir of COLLECTION_DIRS) {
|
|
7129
|
+
const fullDir = path4.join(cwd, dir);
|
|
7130
|
+
if (!fs5.existsSync(fullDir)) continue;
|
|
7131
|
+
let files;
|
|
7132
|
+
try {
|
|
7133
|
+
files = fs5.readdirSync(fullDir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7134
|
+
} catch {
|
|
7135
|
+
continue;
|
|
7136
|
+
}
|
|
7137
|
+
for (const file of files) {
|
|
7138
|
+
try {
|
|
7139
|
+
const filePath = path4.join(fullDir, file);
|
|
7140
|
+
const content = fs5.readFileSync(filePath, "utf-8").slice(0, 1e4);
|
|
7141
|
+
const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
|
|
7142
|
+
if (!slugMatch) continue;
|
|
7143
|
+
const slug = slugMatch[1];
|
|
7144
|
+
const name = file.replace(/\.(ts|tsx)$/, "");
|
|
7145
|
+
const fields = [];
|
|
7146
|
+
const fieldMatches = content.matchAll(/name:\s*['"]([a-zA-Z_][a-zA-Z0-9_]*)['"]/g);
|
|
7147
|
+
for (const m of fieldMatches) {
|
|
7148
|
+
if (!fields.includes(m[1])) fields.push(m[1]);
|
|
7149
|
+
}
|
|
7150
|
+
const hasAdmin = /components:\s*\{/.test(content) || /Field:\s*['"]/.test(content) || /Cell:\s*['"]/.test(content) || /views:\s*\{/.test(content);
|
|
7151
|
+
collections.push({
|
|
7152
|
+
name,
|
|
7153
|
+
slug,
|
|
7154
|
+
filePath: path4.relative(cwd, filePath),
|
|
7155
|
+
fields: fields.slice(0, 20),
|
|
7156
|
+
hasAdmin
|
|
7157
|
+
});
|
|
7158
|
+
} catch {
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
}
|
|
7162
|
+
return collections;
|
|
7163
|
+
}
|
|
7164
|
+
var ADMIN_COMPONENT_DIRS = [
|
|
7165
|
+
"src/ui/admin",
|
|
7166
|
+
"src/admin/components",
|
|
7167
|
+
"src/components/admin"
|
|
7168
|
+
];
|
|
7169
|
+
function discoverAdminComponents(cwd, collections) {
|
|
7170
|
+
const components = [];
|
|
7171
|
+
for (const dir of ADMIN_COMPONENT_DIRS) {
|
|
7172
|
+
const fullDir = path4.join(cwd, dir);
|
|
7173
|
+
if (!fs5.existsSync(fullDir)) continue;
|
|
7174
|
+
let entries;
|
|
7175
|
+
try {
|
|
7176
|
+
entries = fs5.readdirSync(fullDir, { withFileTypes: true });
|
|
7177
|
+
} catch {
|
|
7178
|
+
continue;
|
|
7179
|
+
}
|
|
7180
|
+
for (const entry of entries) {
|
|
7181
|
+
const entryPath = path4.join(fullDir, entry.name);
|
|
7182
|
+
let name;
|
|
7183
|
+
let filePath;
|
|
7184
|
+
if (entry.isDirectory()) {
|
|
7185
|
+
const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find((f) => fs5.existsSync(path4.join(entryPath, f)));
|
|
7186
|
+
if (!indexFile) continue;
|
|
7187
|
+
name = entry.name;
|
|
7188
|
+
filePath = path4.relative(cwd, path4.join(entryPath, indexFile));
|
|
7189
|
+
} else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
7190
|
+
name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
|
|
7191
|
+
filePath = path4.relative(cwd, entryPath);
|
|
7192
|
+
} else {
|
|
7193
|
+
continue;
|
|
7194
|
+
}
|
|
7195
|
+
let usedInCollection = null;
|
|
7196
|
+
if (collections) {
|
|
7197
|
+
for (const col of collections) {
|
|
7198
|
+
try {
|
|
7199
|
+
const colContent = fs5.readFileSync(path4.join(cwd, col.filePath), "utf-8");
|
|
7200
|
+
if (colContent.includes(name)) {
|
|
7201
|
+
usedInCollection = col.slug;
|
|
7202
|
+
break;
|
|
7203
|
+
}
|
|
7204
|
+
} catch {
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
components.push({ name, filePath, usedInCollection });
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
return components;
|
|
7212
|
+
}
|
|
7213
|
+
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
7214
|
+
function scanApiRoutes(cwd) {
|
|
7215
|
+
const routes = [];
|
|
7216
|
+
const appDirs = ["src/app", "app"];
|
|
7217
|
+
for (const appDir of appDirs) {
|
|
7218
|
+
const apiDir = path4.join(cwd, appDir, "api");
|
|
7219
|
+
if (!fs5.existsSync(apiDir)) continue;
|
|
7220
|
+
walkApiRoutes(apiDir, "/api", cwd, routes);
|
|
7221
|
+
break;
|
|
7222
|
+
}
|
|
7223
|
+
return routes;
|
|
7224
|
+
}
|
|
7225
|
+
function walkApiRoutes(dir, prefix, cwd, routes) {
|
|
7226
|
+
let entries;
|
|
7227
|
+
try {
|
|
7228
|
+
entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
7229
|
+
} catch {
|
|
7230
|
+
return;
|
|
7231
|
+
}
|
|
7232
|
+
const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
|
|
7233
|
+
if (routeFile) {
|
|
7234
|
+
try {
|
|
7235
|
+
const content = fs5.readFileSync(path4.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
|
|
7236
|
+
const methods = HTTP_METHODS.filter(
|
|
7237
|
+
(m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
|
|
7238
|
+
);
|
|
7239
|
+
if (methods.length > 0) {
|
|
7240
|
+
routes.push({
|
|
7241
|
+
path: prefix,
|
|
7242
|
+
methods,
|
|
7243
|
+
filePath: path4.relative(cwd, path4.join(dir, routeFile.name))
|
|
7244
|
+
});
|
|
7245
|
+
}
|
|
7246
|
+
} catch {
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
for (const entry of entries) {
|
|
7250
|
+
if (!entry.isDirectory()) continue;
|
|
7251
|
+
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7252
|
+
let segment = entry.name;
|
|
7253
|
+
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7254
|
+
walkApiRoutes(path4.join(dir, entry.name), prefix, cwd, routes);
|
|
7255
|
+
continue;
|
|
7256
|
+
}
|
|
7257
|
+
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
7258
|
+
segment = `:${segment.slice(2, -2)}?`;
|
|
7259
|
+
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7260
|
+
segment = `:${segment.slice(1, -1)}`;
|
|
7261
|
+
}
|
|
7262
|
+
walkApiRoutes(path4.join(dir, entry.name), `${prefix}/${segment}`, cwd, routes);
|
|
7263
|
+
}
|
|
7264
|
+
}
|
|
7265
|
+
var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
|
|
7266
|
+
"NODE_ENV",
|
|
7267
|
+
"HOME",
|
|
7268
|
+
"PATH",
|
|
7269
|
+
"USER",
|
|
7270
|
+
"SHELL",
|
|
7271
|
+
"TERM",
|
|
7272
|
+
"LANG",
|
|
7273
|
+
"PWD",
|
|
7274
|
+
"HOSTNAME",
|
|
7275
|
+
"PORT",
|
|
7276
|
+
"CI",
|
|
7277
|
+
"GITHUB_ACTIONS"
|
|
7278
|
+
]);
|
|
7279
|
+
function scanEnvVars(cwd) {
|
|
7280
|
+
const envFiles = [".env.example", ".env.local.example", ".env.template"];
|
|
7281
|
+
for (const envFile of envFiles) {
|
|
7282
|
+
const envPath = path4.join(cwd, envFile);
|
|
7283
|
+
if (!fs5.existsSync(envPath)) continue;
|
|
7284
|
+
try {
|
|
7285
|
+
const content = fs5.readFileSync(envPath, "utf-8");
|
|
7286
|
+
const vars = [];
|
|
7287
|
+
for (const line of content.split("\n")) {
|
|
7288
|
+
const trimmed = line.trim();
|
|
7289
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
7290
|
+
const match = trimmed.match(/^([A-Z][A-Z0-9_]*)=/);
|
|
7291
|
+
if (match && !BUILTIN_ENV_VARS.has(match[1])) {
|
|
7292
|
+
vars.push(match[1]);
|
|
7293
|
+
}
|
|
7294
|
+
}
|
|
7295
|
+
return vars;
|
|
7296
|
+
} catch {
|
|
7297
|
+
return [];
|
|
7298
|
+
}
|
|
7299
|
+
}
|
|
7300
|
+
return [];
|
|
7301
|
+
}
|
|
7302
|
+
|
|
7303
|
+
// src/bin/qa-guide.ts
|
|
6963
7304
|
function discoverQaContext(cwd) {
|
|
6964
7305
|
const result2 = {
|
|
6965
7306
|
routes: [],
|
|
@@ -6968,12 +7309,17 @@ function discoverQaContext(cwd) {
|
|
|
6968
7309
|
adminPath: null,
|
|
6969
7310
|
roles: [],
|
|
6970
7311
|
devCommand: "",
|
|
6971
|
-
devPort: 3e3
|
|
7312
|
+
devPort: 3e3,
|
|
7313
|
+
frameworks: [],
|
|
7314
|
+
collections: [],
|
|
7315
|
+
adminComponents: [],
|
|
7316
|
+
apiRoutes: [],
|
|
7317
|
+
envVars: []
|
|
6972
7318
|
};
|
|
6973
7319
|
try {
|
|
6974
|
-
const pkg = JSON.parse(
|
|
7320
|
+
const pkg = JSON.parse(fs6.readFileSync(path5.join(cwd, "package.json"), "utf-8"));
|
|
6975
7321
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
6976
|
-
const pm =
|
|
7322
|
+
const pm = fs6.existsSync(path5.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs6.existsSync(path5.join(cwd, "yarn.lock")) ? "yarn" : "npm";
|
|
6977
7323
|
if (pkg.scripts?.dev) result2.devCommand = `${pm} dev`;
|
|
6978
7324
|
if (allDeps.next || allDeps.nuxt) result2.devPort = 3e3;
|
|
6979
7325
|
else if (allDeps.vite) result2.devPort = 5173;
|
|
@@ -6981,14 +7327,14 @@ function discoverQaContext(cwd) {
|
|
|
6981
7327
|
}
|
|
6982
7328
|
const appDirs = ["src/app", "app"];
|
|
6983
7329
|
for (const appDir of appDirs) {
|
|
6984
|
-
const fullAppDir =
|
|
6985
|
-
if (!
|
|
7330
|
+
const fullAppDir = path5.join(cwd, appDir);
|
|
7331
|
+
if (!fs6.existsSync(fullAppDir)) continue;
|
|
6986
7332
|
scanRoutes(fullAppDir, appDir, "", result2);
|
|
6987
7333
|
break;
|
|
6988
7334
|
}
|
|
6989
7335
|
const authPatterns = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
|
|
6990
7336
|
for (const p of authPatterns) {
|
|
6991
|
-
if (
|
|
7337
|
+
if (fs6.existsSync(path5.join(cwd, p))) result2.authFiles.push(p);
|
|
6992
7338
|
}
|
|
6993
7339
|
const authConfigGlobs = [
|
|
6994
7340
|
"src/app/api/auth",
|
|
@@ -6999,7 +7345,7 @@ function discoverQaContext(cwd) {
|
|
|
6999
7345
|
"src/app/api/oauth"
|
|
7000
7346
|
];
|
|
7001
7347
|
for (const g of authConfigGlobs) {
|
|
7002
|
-
if (
|
|
7348
|
+
if (fs6.existsSync(path5.join(cwd, g))) result2.authFiles.push(g);
|
|
7003
7349
|
}
|
|
7004
7350
|
try {
|
|
7005
7351
|
const rolePaths = [
|
|
@@ -7011,12 +7357,12 @@ function discoverQaContext(cwd) {
|
|
|
7011
7357
|
"src/collections"
|
|
7012
7358
|
];
|
|
7013
7359
|
for (const rp of rolePaths) {
|
|
7014
|
-
const dir =
|
|
7015
|
-
if (!
|
|
7016
|
-
const files =
|
|
7360
|
+
const dir = path5.join(cwd, rp);
|
|
7361
|
+
if (!fs6.existsSync(dir)) continue;
|
|
7362
|
+
const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
|
|
7017
7363
|
for (const f of files) {
|
|
7018
7364
|
try {
|
|
7019
|
-
const content =
|
|
7365
|
+
const content = fs6.readFileSync(path5.join(dir, f), "utf-8").slice(0, 5e3);
|
|
7020
7366
|
const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
|
|
7021
7367
|
if (roleMatches) {
|
|
7022
7368
|
for (const m of roleMatches) {
|
|
@@ -7040,12 +7386,20 @@ function discoverQaContext(cwd) {
|
|
|
7040
7386
|
}
|
|
7041
7387
|
} catch {
|
|
7042
7388
|
}
|
|
7389
|
+
result2.frameworks = detectFrameworks(cwd);
|
|
7390
|
+
const hasPayload = result2.frameworks.some((f) => f.name === "payload-cms");
|
|
7391
|
+
if (hasPayload) {
|
|
7392
|
+
result2.collections = discoverPayloadCollections(cwd);
|
|
7393
|
+
}
|
|
7394
|
+
result2.adminComponents = discoverAdminComponents(cwd, result2.collections.length > 0 ? result2.collections : void 0);
|
|
7395
|
+
result2.apiRoutes = scanApiRoutes(cwd);
|
|
7396
|
+
result2.envVars = scanEnvVars(cwd);
|
|
7043
7397
|
return result2;
|
|
7044
7398
|
}
|
|
7045
7399
|
function scanRoutes(dir, baseDir, prefix, result2) {
|
|
7046
7400
|
let entries;
|
|
7047
7401
|
try {
|
|
7048
|
-
entries =
|
|
7402
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
7049
7403
|
} catch {
|
|
7050
7404
|
return;
|
|
7051
7405
|
}
|
|
@@ -7062,19 +7416,18 @@ function scanRoutes(dir, baseDir, prefix, result2) {
|
|
|
7062
7416
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
7063
7417
|
let segment = entry.name;
|
|
7064
7418
|
if (segment.startsWith("(") && segment.endsWith(")")) {
|
|
7065
|
-
scanRoutes(
|
|
7419
|
+
scanRoutes(path5.join(dir, entry.name), baseDir, prefix, result2);
|
|
7066
7420
|
continue;
|
|
7067
7421
|
}
|
|
7068
|
-
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7069
|
-
segment = `:${segment.slice(1, -1)}`;
|
|
7070
|
-
}
|
|
7071
7422
|
if (segment.startsWith("[[") && segment.endsWith("]]")) {
|
|
7072
7423
|
segment = `:${segment.slice(2, -2)}?`;
|
|
7424
|
+
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
7425
|
+
segment = `:${segment.slice(1, -1)}`;
|
|
7073
7426
|
}
|
|
7074
|
-
scanRoutes(
|
|
7427
|
+
scanRoutes(path5.join(dir, entry.name), baseDir, `${prefix}/${segment}`, result2);
|
|
7075
7428
|
}
|
|
7076
7429
|
}
|
|
7077
|
-
function
|
|
7430
|
+
function generateQaGuideFallback(discovery) {
|
|
7078
7431
|
const lines = ["# QA Guide", "", "## Authentication", ""];
|
|
7079
7432
|
if (discovery.loginPage) {
|
|
7080
7433
|
lines.push(`- Login page: \`${discovery.loginPage}\``);
|
|
@@ -7123,6 +7476,39 @@ function generateQaGuide(discovery) {
|
|
|
7123
7476
|
}
|
|
7124
7477
|
lines.push("");
|
|
7125
7478
|
}
|
|
7479
|
+
if (discovery.collections.length > 0) {
|
|
7480
|
+
lines.push("## Admin Collections", "");
|
|
7481
|
+
for (const col of discovery.collections) {
|
|
7482
|
+
lines.push(`### \`/admin/collections/${col.slug}\``);
|
|
7483
|
+
lines.push(`- **Name:** ${col.name}`);
|
|
7484
|
+
lines.push(`- **Fields:** ${col.fields.join(", ")}`);
|
|
7485
|
+
if (col.hasAdmin) lines.push("- **Custom admin components:** yes");
|
|
7486
|
+
lines.push("");
|
|
7487
|
+
}
|
|
7488
|
+
}
|
|
7489
|
+
if (discovery.apiRoutes.length > 0) {
|
|
7490
|
+
lines.push("## API Endpoints", "");
|
|
7491
|
+
for (const route of discovery.apiRoutes) {
|
|
7492
|
+
lines.push(`- \`${route.methods.join(", ")} ${route.path}\` \u2014 \`${route.filePath}\``);
|
|
7493
|
+
}
|
|
7494
|
+
lines.push("");
|
|
7495
|
+
}
|
|
7496
|
+
if (discovery.adminComponents.length > 0) {
|
|
7497
|
+
lines.push("## Custom Admin Components", "");
|
|
7498
|
+
for (const comp of discovery.adminComponents) {
|
|
7499
|
+
let line = `- **${comp.name}** (\`${comp.filePath}\`)`;
|
|
7500
|
+
if (comp.usedInCollection) line += ` \u2014 used in \`${comp.usedInCollection}\` collection`;
|
|
7501
|
+
lines.push(line);
|
|
7502
|
+
}
|
|
7503
|
+
lines.push("");
|
|
7504
|
+
}
|
|
7505
|
+
if (discovery.envVars.length > 0) {
|
|
7506
|
+
lines.push("## Required Environment Variables", "");
|
|
7507
|
+
for (const v of discovery.envVars) {
|
|
7508
|
+
lines.push(`- \`${v}\``);
|
|
7509
|
+
}
|
|
7510
|
+
lines.push("");
|
|
7511
|
+
}
|
|
7126
7512
|
lines.push(
|
|
7127
7513
|
"## Dev Server",
|
|
7128
7514
|
"",
|
|
@@ -7132,10 +7518,71 @@ function generateQaGuide(discovery) {
|
|
|
7132
7518
|
);
|
|
7133
7519
|
return lines.join("\n");
|
|
7134
7520
|
}
|
|
7521
|
+
var MAX_SERIALIZED_LENGTH = 8e3;
|
|
7522
|
+
function serializeDiscoveryForLLM(discovery) {
|
|
7523
|
+
const sections = [];
|
|
7524
|
+
sections.push(`Dev server: ${discovery.devCommand || "pnpm dev"} at http://localhost:${discovery.devPort}`);
|
|
7525
|
+
if (discovery.loginPage) sections.push(`Login page: ${discovery.loginPage}`);
|
|
7526
|
+
if (discovery.adminPath) sections.push(`Admin panel: ${discovery.adminPath}`);
|
|
7527
|
+
if (discovery.roles.length > 0) sections.push(`Roles: ${discovery.roles.join(", ")}`);
|
|
7528
|
+
if (discovery.frameworks.length > 0) {
|
|
7529
|
+
sections.push(`
|
|
7530
|
+
Frameworks: ${discovery.frameworks.map((f) => `${f.name}${f.version ? ` (${f.version})` : ""}`).join(", ")}`);
|
|
7531
|
+
}
|
|
7532
|
+
if (discovery.collections.length > 0) {
|
|
7533
|
+
sections.push("\nCollections (Payload CMS):");
|
|
7534
|
+
for (const col of discovery.collections.slice(0, 15)) {
|
|
7535
|
+
const fields = col.fields.slice(0, 10).join(", ");
|
|
7536
|
+
let line = `- ${col.slug}: fields=[${fields}]`;
|
|
7537
|
+
if (col.hasAdmin) line += " (has custom admin components)";
|
|
7538
|
+
line += ` \u2014 ${col.filePath}`;
|
|
7539
|
+
sections.push(line);
|
|
7540
|
+
}
|
|
7541
|
+
if (discovery.collections.length > 15) {
|
|
7542
|
+
sections.push(`- ... and ${discovery.collections.length - 15} more collections`);
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
if (discovery.adminComponents.length > 0) {
|
|
7546
|
+
sections.push("\nCustom Admin Components:");
|
|
7547
|
+
for (const comp of discovery.adminComponents.slice(0, 10)) {
|
|
7548
|
+
let line = `- ${comp.name} (${comp.filePath})`;
|
|
7549
|
+
if (comp.usedInCollection) line += ` \u2192 used in "${comp.usedInCollection}" collection`;
|
|
7550
|
+
sections.push(line);
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
if (discovery.apiRoutes.length > 0) {
|
|
7554
|
+
sections.push("\nAPI Routes:");
|
|
7555
|
+
for (const route of discovery.apiRoutes.slice(0, 20)) {
|
|
7556
|
+
sections.push(`- ${route.methods.join("/")} ${route.path} \u2014 ${route.filePath}`);
|
|
7557
|
+
}
|
|
7558
|
+
if (discovery.apiRoutes.length > 20) {
|
|
7559
|
+
sections.push(`- ... and ${discovery.apiRoutes.length - 20} more routes`);
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7562
|
+
if (discovery.routes.length > 0) {
|
|
7563
|
+
sections.push("\nFrontend Routes:");
|
|
7564
|
+
for (const route of discovery.routes.slice(0, 30)) {
|
|
7565
|
+
sections.push(`- [${route.group}] ${route.path}`);
|
|
7566
|
+
}
|
|
7567
|
+
if (discovery.routes.length > 30) {
|
|
7568
|
+
sections.push(`- ... and ${discovery.routes.length - 30} more routes`);
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7571
|
+
if (discovery.envVars.length > 0) {
|
|
7572
|
+
sections.push(`
|
|
7573
|
+
Required env vars: ${discovery.envVars.join(", ")}`);
|
|
7574
|
+
}
|
|
7575
|
+
let result2 = sections.join("\n");
|
|
7576
|
+
if (result2.length > MAX_SERIALIZED_LENGTH) {
|
|
7577
|
+
const cutoff = result2.lastIndexOf("\n", MAX_SERIALIZED_LENGTH - 20);
|
|
7578
|
+
result2 = result2.slice(0, cutoff > 0 ? cutoff : MAX_SERIALIZED_LENGTH - 20) + "\n... (truncated)";
|
|
7579
|
+
}
|
|
7580
|
+
return result2;
|
|
7581
|
+
}
|
|
7135
7582
|
|
|
7136
7583
|
// src/bin/skills.ts
|
|
7137
|
-
import * as
|
|
7138
|
-
import * as
|
|
7584
|
+
import * as fs7 from "fs";
|
|
7585
|
+
import * as path6 from "path";
|
|
7139
7586
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
7140
7587
|
var SKILL_MAPPINGS = [
|
|
7141
7588
|
{
|
|
@@ -7158,10 +7605,10 @@ var SKILL_MAPPINGS = [
|
|
|
7158
7605
|
}
|
|
7159
7606
|
];
|
|
7160
7607
|
function detectSkillsForProject(cwd) {
|
|
7161
|
-
const pkgPath =
|
|
7162
|
-
if (!
|
|
7608
|
+
const pkgPath = path6.join(cwd, "package.json");
|
|
7609
|
+
if (!fs7.existsSync(pkgPath)) return [];
|
|
7163
7610
|
try {
|
|
7164
|
-
const pkg = JSON.parse(
|
|
7611
|
+
const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
|
|
7165
7612
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7166
7613
|
const seen = /* @__PURE__ */ new Set();
|
|
7167
7614
|
const skills = [];
|
|
@@ -7187,10 +7634,10 @@ function installSkillsForProject(cwd) {
|
|
|
7187
7634
|
return [];
|
|
7188
7635
|
}
|
|
7189
7636
|
let installedSkills = {};
|
|
7190
|
-
const lockPath =
|
|
7191
|
-
if (
|
|
7637
|
+
const lockPath = path6.join(cwd, "skills-lock.json");
|
|
7638
|
+
if (fs7.existsSync(lockPath)) {
|
|
7192
7639
|
try {
|
|
7193
|
-
const lock = JSON.parse(
|
|
7640
|
+
const lock = JSON.parse(fs7.readFileSync(lockPath, "utf-8"));
|
|
7194
7641
|
installedSkills = lock.skills ?? {};
|
|
7195
7642
|
} catch {
|
|
7196
7643
|
}
|
|
@@ -7202,8 +7649,8 @@ function installSkillsForProject(cwd) {
|
|
|
7202
7649
|
console.log(` \u25CB ${skill.label} \u2014 already installed`);
|
|
7203
7650
|
const agentPath = `.agents/skills/${skillName}`;
|
|
7204
7651
|
const claudePath = `.claude/skills/${skillName}`;
|
|
7205
|
-
if (
|
|
7206
|
-
if (
|
|
7652
|
+
if (fs7.existsSync(path6.join(cwd, agentPath))) installedPaths.push(agentPath);
|
|
7653
|
+
if (fs7.existsSync(path6.join(cwd, claudePath))) installedPaths.push(claudePath);
|
|
7207
7654
|
continue;
|
|
7208
7655
|
}
|
|
7209
7656
|
try {
|
|
@@ -7217,8 +7664,8 @@ function installSkillsForProject(cwd) {
|
|
|
7217
7664
|
const installedName = skill.package.split("@").pop() ?? "";
|
|
7218
7665
|
const agentPath = `.agents/skills/${installedName}`;
|
|
7219
7666
|
const claudePath = `.claude/skills/${installedName}`;
|
|
7220
|
-
if (
|
|
7221
|
-
if (
|
|
7667
|
+
if (fs7.existsSync(path6.join(cwd, agentPath))) installedPaths.push(agentPath);
|
|
7668
|
+
if (fs7.existsSync(path6.join(cwd, claudePath))) installedPaths.push(claudePath);
|
|
7222
7669
|
console.log(` \u2713 ${skill.label}`);
|
|
7223
7670
|
} catch {
|
|
7224
7671
|
console.log(` \u2717 ${skill.label} \u2014 failed to install`);
|
|
@@ -7229,22 +7676,47 @@ function installSkillsForProject(cwd) {
|
|
|
7229
7676
|
|
|
7230
7677
|
// src/bin/commands/bootstrap.ts
|
|
7231
7678
|
init_config();
|
|
7679
|
+
|
|
7680
|
+
// src/bin/extend-helpers.ts
|
|
7681
|
+
var MAX_EXISTING_CONTENT_LENGTH = 6e3;
|
|
7682
|
+
function buildExtendInstruction(existingContent, fileDescription) {
|
|
7683
|
+
if (!existingContent.trim()) return "";
|
|
7684
|
+
let content = existingContent;
|
|
7685
|
+
if (content.length > MAX_EXISTING_CONTENT_LENGTH) {
|
|
7686
|
+
const cutoff = content.lastIndexOf("\n", MAX_EXISTING_CONTENT_LENGTH);
|
|
7687
|
+
content = content.slice(0, cutoff > 0 ? cutoff : MAX_EXISTING_CONTENT_LENGTH) + "\n... (truncated)";
|
|
7688
|
+
}
|
|
7689
|
+
return `
|
|
7690
|
+
## Existing ${fileDescription} (EXTEND, do not replace)
|
|
7691
|
+
You are UPDATING an existing ${fileDescription}. Follow these rules strictly:
|
|
7692
|
+
- PRESERVE all existing sections and content that are still accurate \u2014 keep them verbatim
|
|
7693
|
+
- PRESERVE any manually-added sections, custom notes, or user edits
|
|
7694
|
+
- REMOVE only lines that reference files, patterns, or dependencies that no longer exist in the project
|
|
7695
|
+
- APPEND new sections or lines for newly discovered patterns, files, or conventions
|
|
7696
|
+
- Do NOT rewrite sections that are still correct
|
|
7697
|
+
|
|
7698
|
+
### Existing content:
|
|
7699
|
+
${content}
|
|
7700
|
+
`;
|
|
7701
|
+
}
|
|
7702
|
+
|
|
7703
|
+
// src/bin/commands/bootstrap.ts
|
|
7232
7704
|
var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
|
|
7233
7705
|
function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
7234
|
-
const srcDir =
|
|
7235
|
-
const baseDir =
|
|
7706
|
+
const srcDir = path8.join(cwd, "src");
|
|
7707
|
+
const baseDir = fs9.existsSync(srcDir) ? srcDir : cwd;
|
|
7236
7708
|
const results = [];
|
|
7237
7709
|
function walk(dir) {
|
|
7238
7710
|
const entries = [];
|
|
7239
7711
|
try {
|
|
7240
|
-
for (const entry of
|
|
7712
|
+
for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
|
|
7241
7713
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
7242
|
-
const full =
|
|
7714
|
+
const full = path8.join(dir, entry.name);
|
|
7243
7715
|
if (entry.isDirectory()) {
|
|
7244
7716
|
entries.push(...walk(full));
|
|
7245
7717
|
} else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
|
|
7246
7718
|
try {
|
|
7247
|
-
const stat =
|
|
7719
|
+
const stat = fs9.statSync(full);
|
|
7248
7720
|
if (stat.size >= 200 && stat.size <= 5e3) {
|
|
7249
7721
|
entries.push({ filePath: full, size: stat.size });
|
|
7250
7722
|
}
|
|
@@ -7258,8 +7730,8 @@ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
|
7258
7730
|
}
|
|
7259
7731
|
const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
|
|
7260
7732
|
for (const { filePath } of files) {
|
|
7261
|
-
const rel =
|
|
7262
|
-
const content =
|
|
7733
|
+
const rel = path8.relative(cwd, filePath);
|
|
7734
|
+
const content = fs9.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
|
|
7263
7735
|
results.push(`### File: ${rel}
|
|
7264
7736
|
\`\`\`typescript
|
|
7265
7737
|
${content}
|
|
@@ -7271,9 +7743,9 @@ function ghComment(issueNumber, body, cwd) {
|
|
|
7271
7743
|
try {
|
|
7272
7744
|
let repoSlug = "";
|
|
7273
7745
|
try {
|
|
7274
|
-
const configPath =
|
|
7275
|
-
if (
|
|
7276
|
-
const config = JSON.parse(
|
|
7746
|
+
const configPath = path8.join(cwd, "kody.config.json");
|
|
7747
|
+
if (fs9.existsSync(configPath)) {
|
|
7748
|
+
const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
7277
7749
|
if (config.github?.owner && config.github?.repo) {
|
|
7278
7750
|
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
7279
7751
|
}
|
|
@@ -7310,8 +7782,8 @@ function bootstrapCommand(opts, pkgRoot) {
|
|
|
7310
7782
|
ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
|
|
7311
7783
|
}
|
|
7312
7784
|
const readIfExists = (rel, maxChars = 3e3) => {
|
|
7313
|
-
const p =
|
|
7314
|
-
if (
|
|
7785
|
+
const p = path8.join(cwd, rel);
|
|
7786
|
+
if (fs9.existsSync(p)) return fs9.readFileSync(p, "utf-8").slice(0, maxChars);
|
|
7315
7787
|
return null;
|
|
7316
7788
|
};
|
|
7317
7789
|
let repoContext = "";
|
|
@@ -7346,14 +7818,14 @@ ${sampleFiles}
|
|
|
7346
7818
|
|
|
7347
7819
|
`;
|
|
7348
7820
|
try {
|
|
7349
|
-
const topDirs =
|
|
7821
|
+
const topDirs = fs9.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
7350
7822
|
repoContext += `## Top-level directories
|
|
7351
7823
|
${topDirs.join(", ")}
|
|
7352
7824
|
|
|
7353
7825
|
`;
|
|
7354
|
-
const srcDir =
|
|
7355
|
-
if (
|
|
7356
|
-
const srcDirs =
|
|
7826
|
+
const srcDir = path8.join(cwd, "src");
|
|
7827
|
+
if (fs9.existsSync(srcDir)) {
|
|
7828
|
+
const srcDirs = fs9.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
7357
7829
|
if (srcDirs.length > 0) repoContext += `## src/ subdirectories
|
|
7358
7830
|
${srcDirs.join(", ")}
|
|
7359
7831
|
|
|
@@ -7363,34 +7835,28 @@ ${srcDirs.join(", ")}
|
|
|
7363
7835
|
}
|
|
7364
7836
|
const existingFiles = [];
|
|
7365
7837
|
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"]) {
|
|
7366
|
-
if (
|
|
7838
|
+
if (fs9.existsSync(path8.join(cwd, f))) existingFiles.push(f);
|
|
7367
7839
|
}
|
|
7368
7840
|
if (existingFiles.length) repoContext += `## Config files present
|
|
7369
7841
|
${existingFiles.join(", ")}
|
|
7370
7842
|
|
|
7371
7843
|
`;
|
|
7372
7844
|
console.log("\u2500\u2500 Project Memory \u2500\u2500");
|
|
7373
|
-
const memoryDir =
|
|
7374
|
-
|
|
7375
|
-
const archPath =
|
|
7376
|
-
const conventionsPath =
|
|
7377
|
-
const existingArch =
|
|
7378
|
-
const existingConv =
|
|
7845
|
+
const memoryDir = path8.join(cwd, ".kody", "memory");
|
|
7846
|
+
fs9.mkdirSync(memoryDir, { recursive: true });
|
|
7847
|
+
const archPath = path8.join(memoryDir, "architecture.md");
|
|
7848
|
+
const conventionsPath = path8.join(memoryDir, "conventions.md");
|
|
7849
|
+
const existingArch = fs9.existsSync(archPath) ? fs9.readFileSync(archPath, "utf-8") : "";
|
|
7850
|
+
const existingConv = fs9.existsSync(conventionsPath) ? fs9.readFileSync(conventionsPath, "utf-8") : "";
|
|
7379
7851
|
const hasExisting = !!(existingArch || existingConv);
|
|
7380
|
-
const extendInstruction = hasExisting && !opts.force ?
|
|
7381
|
-
|
|
7382
|
-
You are UPDATING existing documentation. Follow these rules strictly:
|
|
7383
|
-
- PRESERVE all existing sections and content that are still accurate
|
|
7384
|
-
- REMOVE only lines that reference files, patterns, or dependencies that no longer exist in the project
|
|
7385
|
-
- APPEND new sections or lines for newly discovered patterns, files, or conventions
|
|
7386
|
-
- Do NOT rewrite sections that are still correct \u2014 keep them verbatim
|
|
7387
|
-
|
|
7388
|
-
### Existing architecture.md:
|
|
7852
|
+
const extendInstruction = hasExisting && !opts.force ? buildExtendInstruction(
|
|
7853
|
+
`### architecture.md:
|
|
7389
7854
|
${existingArch}
|
|
7390
7855
|
|
|
7391
|
-
###
|
|
7392
|
-
${existingConv}
|
|
7393
|
-
|
|
7856
|
+
### conventions.md:
|
|
7857
|
+
${existingConv}`,
|
|
7858
|
+
"project documentation"
|
|
7859
|
+
) : "";
|
|
7394
7860
|
const memoryPrompt = `You are analyzing a project to generate documentation for an autonomous SDLC pipeline.
|
|
7395
7861
|
|
|
7396
7862
|
Given this project context, output ONLY a JSON object with EXACTLY this structure:
|
|
@@ -7431,12 +7897,12 @@ ${repoContext}`;
|
|
|
7431
7897
|
const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
7432
7898
|
const parsed = JSON.parse(cleaned);
|
|
7433
7899
|
if (parsed.architecture) {
|
|
7434
|
-
|
|
7900
|
+
fs9.writeFileSync(archPath, parsed.architecture);
|
|
7435
7901
|
const lineCount = parsed.architecture.split("\n").length;
|
|
7436
7902
|
console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
|
|
7437
7903
|
}
|
|
7438
7904
|
if (parsed.conventions) {
|
|
7439
|
-
|
|
7905
|
+
fs9.writeFileSync(conventionsPath, parsed.conventions);
|
|
7440
7906
|
const lineCount = parsed.conventions.split("\n").length;
|
|
7441
7907
|
console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
|
|
7442
7908
|
}
|
|
@@ -7445,45 +7911,44 @@ ${repoContext}`;
|
|
|
7445
7911
|
const detected = detectArchitectureBasic(cwd);
|
|
7446
7912
|
if (detected.length > 0) {
|
|
7447
7913
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7448
|
-
|
|
7914
|
+
fs9.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
|
|
7449
7915
|
|
|
7450
7916
|
## Overview
|
|
7451
7917
|
${detected.join("\n")}
|
|
7452
7918
|
`);
|
|
7453
7919
|
console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
|
|
7454
7920
|
}
|
|
7455
|
-
|
|
7921
|
+
fs9.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
|
|
7456
7922
|
console.log(" \u2713 .kody/memory/conventions.md (seed)");
|
|
7457
7923
|
}
|
|
7458
7924
|
console.log("\n\u2500\u2500 Step Files \u2500\u2500");
|
|
7459
|
-
const stepsDir =
|
|
7460
|
-
|
|
7461
|
-
const arch =
|
|
7462
|
-
const conv =
|
|
7925
|
+
const stepsDir = path8.join(cwd, ".kody", "steps");
|
|
7926
|
+
fs9.mkdirSync(stepsDir, { recursive: true });
|
|
7927
|
+
const arch = fs9.existsSync(archPath) ? fs9.readFileSync(archPath, "utf-8") : "";
|
|
7928
|
+
const conv = fs9.existsSync(conventionsPath) ? fs9.readFileSync(conventionsPath, "utf-8") : "";
|
|
7463
7929
|
console.log(" \u23F3 Customizing step files...");
|
|
7464
7930
|
let stepCount = 0;
|
|
7465
7931
|
for (const stage of STEP_STAGES) {
|
|
7466
|
-
const templatePath =
|
|
7467
|
-
if (!
|
|
7932
|
+
const templatePath = path8.join(pkgRoot, "prompts", `${stage}.md`);
|
|
7933
|
+
if (!fs9.existsSync(templatePath)) {
|
|
7468
7934
|
console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
|
|
7469
7935
|
continue;
|
|
7470
7936
|
}
|
|
7471
|
-
const stepOutputPath =
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
}
|
|
7476
|
-
const defaultPrompt = fs8.readFileSync(templatePath, "utf-8");
|
|
7937
|
+
const stepOutputPath = path8.join(stepsDir, `${stage}.md`);
|
|
7938
|
+
const existingStep = fs9.existsSync(stepOutputPath) ? fs9.readFileSync(stepOutputPath, "utf-8") : "";
|
|
7939
|
+
const isExtend = !!existingStep && !opts.force;
|
|
7940
|
+
const defaultPrompt = fs9.readFileSync(templatePath, "utf-8");
|
|
7477
7941
|
const contextPlaceholder = "{{TASK_CONTEXT}}";
|
|
7478
7942
|
const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
|
|
7479
7943
|
if (placeholderIdx === -1) {
|
|
7480
|
-
|
|
7944
|
+
fs9.copyFileSync(templatePath, stepOutputPath);
|
|
7481
7945
|
stepCount++;
|
|
7482
7946
|
console.log(` \u2713 ${stage}.md`);
|
|
7483
7947
|
continue;
|
|
7484
7948
|
}
|
|
7485
7949
|
const beforePlaceholder = defaultPrompt.slice(0, placeholderIdx).trimEnd();
|
|
7486
7950
|
const afterPlaceholder = defaultPrompt.slice(placeholderIdx);
|
|
7951
|
+
const stepExtendBlock = isExtend ? buildExtendInstruction(existingStep, `${stage} step file`) : "";
|
|
7487
7952
|
const customizationPrompt = `You are customizing a Kody pipeline prompt for a specific repository.
|
|
7488
7953
|
|
|
7489
7954
|
## Your Task
|
|
@@ -7499,7 +7964,7 @@ Take the prompt template below and APPEND repository-specific sections to it.
|
|
|
7499
7964
|
4. Keep each appended section concise (10-20 lines max).
|
|
7500
7965
|
5. Output ONLY the customized prompt markdown. No explanation before or after.
|
|
7501
7966
|
6. Do NOT include the text "${contextPlaceholder}" \u2014 it will be appended automatically after your output.
|
|
7502
|
-
|
|
7967
|
+
${stepExtendBlock}
|
|
7503
7968
|
## Stage Being Customized
|
|
7504
7969
|
Stage: ${stage}
|
|
7505
7970
|
|
|
@@ -7534,40 +7999,130 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
7534
7999
|
let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
7535
8000
|
cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
|
|
7536
8001
|
const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
|
|
7537
|
-
|
|
8002
|
+
fs9.writeFileSync(stepOutputPath, finalPrompt);
|
|
7538
8003
|
stepCount++;
|
|
7539
|
-
console.log(` \u2713 ${stage}.md`);
|
|
8004
|
+
console.log(` \u2713 ${stage}.md (${isExtend ? "extended" : "generated"})`);
|
|
7540
8005
|
} catch {
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
8006
|
+
if (!isExtend) {
|
|
8007
|
+
console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
|
|
8008
|
+
fs9.copyFileSync(templatePath, stepOutputPath);
|
|
8009
|
+
stepCount++;
|
|
8010
|
+
} else {
|
|
8011
|
+
console.log(` \u26A0 ${stage}.md \u2014 extend failed, keeping existing`);
|
|
8012
|
+
}
|
|
7544
8013
|
}
|
|
7545
8014
|
}
|
|
7546
8015
|
console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
|
|
7547
8016
|
console.log("\n\u2500\u2500 QA Guide \u2500\u2500");
|
|
7548
|
-
const qaGuidePath =
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
8017
|
+
const qaGuidePath = path8.join(cwd, ".kody", "qa-guide.md");
|
|
8018
|
+
const discovery = discoverQaContext(cwd);
|
|
8019
|
+
const hasRoutes = discovery.routes.length > 0 || discovery.collections.length > 0;
|
|
8020
|
+
if (hasRoutes) {
|
|
8021
|
+
const existingQaGuide = fs9.existsSync(qaGuidePath) ? fs9.readFileSync(qaGuidePath, "utf-8") : "";
|
|
8022
|
+
const isQaExtend = !!existingQaGuide && !opts.force;
|
|
8023
|
+
const serializedDiscovery = serializeDiscoveryForLLM(discovery);
|
|
8024
|
+
const qaExtendBlock = isQaExtend ? buildExtendInstruction(existingQaGuide, "QA guide") : "";
|
|
8025
|
+
const qaPrompt = `You are generating a QA guide for an autonomous coding agent that will use Playwright browser tools to visually verify UI changes.
|
|
8026
|
+
|
|
8027
|
+
## Discovery Data
|
|
8028
|
+
${serializedDiscovery}
|
|
8029
|
+
|
|
8030
|
+
## Architecture
|
|
8031
|
+
${arch}
|
|
8032
|
+
|
|
8033
|
+
## Conventions
|
|
8034
|
+
${conv}
|
|
8035
|
+
${qaExtendBlock}
|
|
8036
|
+
## Output Format
|
|
8037
|
+
Generate a markdown QA guide with EXACTLY these sections:
|
|
8038
|
+
|
|
8039
|
+
# QA Guide
|
|
8040
|
+
|
|
8041
|
+
## Quick Reference
|
|
8042
|
+
- Dev server command and URL
|
|
8043
|
+
- Login page URL
|
|
8044
|
+
- Admin panel URL (if applicable)
|
|
8045
|
+
|
|
8046
|
+
## Authentication
|
|
8047
|
+
### Test Accounts
|
|
8048
|
+
Table with Role, Email, Password columns.
|
|
8049
|
+
Use env var references (QA_ADMIN_EMAIL, QA_ADMIN_PASSWORD, etc.) \u2014 NOT hardcoded credentials.
|
|
8050
|
+
If the existing guide has real credentials, PRESERVE them exactly.
|
|
8051
|
+
|
|
8052
|
+
### Login Steps
|
|
8053
|
+
Specific steps for this project's auth system.
|
|
8054
|
+
|
|
8055
|
+
### Auth Files
|
|
8056
|
+
List of auth-related source files.
|
|
8057
|
+
|
|
8058
|
+
## Navigation Map
|
|
8059
|
+
### Admin Panel
|
|
8060
|
+
For each collection/admin page: the exact URL path, what elements to expect on the page, key fields visible in the form, any custom components.
|
|
8061
|
+
Example: "/admin/collections/courses \u2014 Course edit form with title, slug, description fields. Custom CourseLessonsSorter component shows drag-sortable lessons grouped by chapter."
|
|
8062
|
+
|
|
8063
|
+
### Frontend Pages
|
|
8064
|
+
For each key public route: path, expected content, key interactions to test.
|
|
8065
|
+
|
|
8066
|
+
### API Endpoints
|
|
8067
|
+
For each API route: path, HTTP methods, purpose.
|
|
8068
|
+
|
|
8069
|
+
## Component Verification Patterns
|
|
8070
|
+
For each custom admin component: where to find it in the UI, how to navigate there, what visual elements to verify, interaction tests (click, drag, type).
|
|
8071
|
+
|
|
8072
|
+
## Common Test Scenarios
|
|
8073
|
+
CRUD workflows, auth flows, specific feature verification patterns relevant to this project.
|
|
8074
|
+
|
|
8075
|
+
## Environment Setup
|
|
8076
|
+
Required env vars to start the dev server successfully.
|
|
8077
|
+
|
|
8078
|
+
## Dev Server
|
|
8079
|
+
Command and URL.
|
|
8080
|
+
|
|
8081
|
+
## Rules
|
|
8082
|
+
- Be SPECIFIC to this project \u2014 reference actual URLs, collection names, component names
|
|
8083
|
+
- For admin panels (Payload CMS, etc.), include the exact /admin/collections/{slug} paths
|
|
8084
|
+
- Include visual assertions: "you should see X", "verify Y is visible"
|
|
8085
|
+
- Include interaction tests: "click button X", "fill field Y", "drag item Z"
|
|
8086
|
+
- Keep under 200 lines total
|
|
8087
|
+
- Output ONLY the markdown. No explanation before or after.`;
|
|
8088
|
+
console.log(" \u23F3 Generating QA guide...");
|
|
8089
|
+
try {
|
|
8090
|
+
const output = execFileSync5("claude", [
|
|
8091
|
+
"--print",
|
|
8092
|
+
"--model",
|
|
8093
|
+
bootstrapModel,
|
|
8094
|
+
"--dangerously-skip-permissions",
|
|
8095
|
+
qaPrompt
|
|
8096
|
+
], {
|
|
8097
|
+
encoding: "utf-8",
|
|
8098
|
+
timeout: 9e4,
|
|
8099
|
+
cwd,
|
|
8100
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
8101
|
+
}).trim();
|
|
8102
|
+
const cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
8103
|
+
fs9.writeFileSync(qaGuidePath, cleaned);
|
|
8104
|
+
console.log(` \u2713 .kody/qa-guide.md (${isQaExtend ? "extended" : "generated"}, ${discovery.routes.length} routes, ${discovery.collections.length} collections)`);
|
|
8105
|
+
} catch {
|
|
8106
|
+
console.log(" \u26A0 LLM QA generation failed \u2014 using template fallback");
|
|
8107
|
+
const qaGuide = generateQaGuideFallback(discovery);
|
|
8108
|
+
fs9.writeFileSync(qaGuidePath, qaGuide);
|
|
8109
|
+
console.log(` \u2713 .kody/qa-guide.md (fallback, ${discovery.routes.length} routes)`);
|
|
8110
|
+
}
|
|
8111
|
+
if (discovery.loginPage) console.log(` \u2713 Login page detected: ${discovery.loginPage}`);
|
|
8112
|
+
if (discovery.adminPath) console.log(` \u2713 Admin panel detected: ${discovery.adminPath}`);
|
|
8113
|
+
if (discovery.collections.length > 0) console.log(` \u2713 ${discovery.collections.length} Payload CMS collections detected`);
|
|
8114
|
+
if (discovery.adminComponents.length > 0) console.log(` \u2713 ${discovery.adminComponents.length} custom admin components detected`);
|
|
8115
|
+
console.log(" \u2139 Add QA_ADMIN_EMAIL, QA_ADMIN_PASSWORD, QA_USER_EMAIL, QA_USER_PASSWORD as GitHub secrets");
|
|
7561
8116
|
} else {
|
|
7562
|
-
console.log(" \u25CB
|
|
8117
|
+
console.log(" \u25CB No routes or collections detected \u2014 skipping QA guide");
|
|
7563
8118
|
}
|
|
7564
8119
|
console.log("\n\u2500\u2500 Labels \u2500\u2500");
|
|
7565
8120
|
try {
|
|
7566
8121
|
let repoSlug = "";
|
|
7567
8122
|
try {
|
|
7568
|
-
const configPath =
|
|
7569
|
-
if (
|
|
7570
|
-
const config = JSON.parse(
|
|
8123
|
+
const configPath = path8.join(cwd, "kody.config.json");
|
|
8124
|
+
if (fs9.existsSync(configPath)) {
|
|
8125
|
+
const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
7571
8126
|
if (config.github?.owner && config.github?.repo) {
|
|
7572
8127
|
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
7573
8128
|
}
|
|
@@ -7576,9 +8131,12 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
7576
8131
|
}
|
|
7577
8132
|
if (repoSlug) {
|
|
7578
8133
|
const labels = [
|
|
8134
|
+
{ name: "kody:backlog", color: "e4e669", description: "Issue created, not yet assigned to Kody" },
|
|
7579
8135
|
{ name: "kody:planning", color: "c5def5", description: "Kody is analyzing and planning" },
|
|
7580
8136
|
{ name: "kody:building", color: "0e8a16", description: "Kody is building code" },
|
|
8137
|
+
{ name: "kody:verifying", color: "fbca04", description: "Kody is verifying (lint/test/typecheck)" },
|
|
7581
8138
|
{ name: "kody:review", color: "fbca04", description: "Kody is reviewing code" },
|
|
8139
|
+
{ name: "kody:fixing", color: "0e8a16", description: "Kody is applying review fixes" },
|
|
7582
8140
|
{ name: "kody:shipping", color: "1d76db", description: "Kody is creating the pull request" },
|
|
7583
8141
|
{ name: "kody:done", color: "0e8a16", description: "Kody completed successfully" },
|
|
7584
8142
|
{ name: "kody:failed", color: "d93f0b", description: "Kody pipeline failed" },
|
|
@@ -7632,6 +8190,24 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
7632
8190
|
} catch {
|
|
7633
8191
|
console.log(" \u25CB Label creation skipped");
|
|
7634
8192
|
}
|
|
8193
|
+
console.log("\n\u2500\u2500 Tools \u2500\u2500");
|
|
8194
|
+
const toolsYmlPath = path8.join(cwd, ".kody", "tools.yml");
|
|
8195
|
+
if (!fs9.existsSync(toolsYmlPath) || opts.force) {
|
|
8196
|
+
const toolsTemplate = `# Kody Tools Configuration
|
|
8197
|
+
# Uncomment and configure tools that your project uses.
|
|
8198
|
+
# The engine will detect, install, and inject tool skills into pipeline stages.
|
|
8199
|
+
#
|
|
8200
|
+
# playwright:
|
|
8201
|
+
# detect: ["playwright.config.ts", "playwright.config.js"]
|
|
8202
|
+
# stages: [verify]
|
|
8203
|
+
# setup: "npx playwright install --with-deps chromium"
|
|
8204
|
+
# skill: playwright-cli.md
|
|
8205
|
+
`;
|
|
8206
|
+
fs9.writeFileSync(toolsYmlPath, toolsTemplate);
|
|
8207
|
+
console.log(" \u2713 .kody/tools.yml (template created)");
|
|
8208
|
+
} else {
|
|
8209
|
+
console.log(" \u25CB .kody/tools.yml (already exists, keeping)");
|
|
8210
|
+
}
|
|
7635
8211
|
console.log("\n\u2500\u2500 Skills \u2500\u2500");
|
|
7636
8212
|
const installedSkillPaths = installSkillsForProject(cwd);
|
|
7637
8213
|
console.log("\n\u2500\u2500 Git \u2500\u2500");
|
|
@@ -7639,20 +8215,21 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
7639
8215
|
".kody/memory/architecture.md",
|
|
7640
8216
|
".kody/memory/conventions.md",
|
|
7641
8217
|
".kody/qa-guide.md",
|
|
8218
|
+
".kody/tools.yml",
|
|
7642
8219
|
...installedSkillPaths
|
|
7643
|
-
].filter((f) =>
|
|
7644
|
-
if (
|
|
8220
|
+
].filter((f) => fs9.existsSync(path8.join(cwd, f)));
|
|
8221
|
+
if (fs9.existsSync(path8.join(cwd, "skills-lock.json"))) {
|
|
7645
8222
|
filesToCommit.push("skills-lock.json");
|
|
7646
8223
|
}
|
|
7647
8224
|
for (const stage of STEP_STAGES) {
|
|
7648
8225
|
const stepFile = `.kody/steps/${stage}.md`;
|
|
7649
|
-
if (
|
|
8226
|
+
if (fs9.existsSync(path8.join(cwd, stepFile))) {
|
|
7650
8227
|
filesToCommit.push(stepFile);
|
|
7651
8228
|
}
|
|
7652
8229
|
}
|
|
7653
8230
|
if (filesToCommit.length > 0) {
|
|
7654
8231
|
try {
|
|
7655
|
-
const fullPaths = filesToCommit.map((f) =>
|
|
8232
|
+
const fullPaths = filesToCommit.map((f) => path8.join(cwd, f));
|
|
7656
8233
|
for (let pass = 0; pass < 2; pass++) {
|
|
7657
8234
|
execFileSync5("npx", ["prettier", "--write", ...fullPaths], {
|
|
7658
8235
|
cwd,
|
|
@@ -7679,9 +8256,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
7679
8256
|
console.log(` \u2713 Pushed branch: ${branchName}`);
|
|
7680
8257
|
let baseBranch = "main";
|
|
7681
8258
|
try {
|
|
7682
|
-
const configPath =
|
|
7683
|
-
if (
|
|
7684
|
-
const config = JSON.parse(
|
|
8259
|
+
const configPath = path8.join(cwd, "kody.config.json");
|
|
8260
|
+
if (fs9.existsSync(configPath)) {
|
|
8261
|
+
const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
|
|
7685
8262
|
baseBranch = config.git?.defaultBranch ?? "main";
|
|
7686
8263
|
}
|
|
7687
8264
|
} catch {
|
|
@@ -7755,11 +8332,11 @@ Create it manually.`, cwd);
|
|
|
7755
8332
|
|
|
7756
8333
|
// src/bin/cli.ts
|
|
7757
8334
|
init_architecture_detection();
|
|
7758
|
-
var __dirname2 =
|
|
7759
|
-
var PKG_ROOT =
|
|
8335
|
+
var __dirname2 = path32.dirname(fileURLToPath2(import.meta.url));
|
|
8336
|
+
var PKG_ROOT = path32.resolve(__dirname2, "..", "..");
|
|
7760
8337
|
function getVersion() {
|
|
7761
|
-
const pkgPath =
|
|
7762
|
-
const pkg = JSON.parse(
|
|
8338
|
+
const pkgPath = path32.join(PKG_ROOT, "package.json");
|
|
8339
|
+
const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
|
|
7763
8340
|
return pkg.version;
|
|
7764
8341
|
}
|
|
7765
8342
|
var args = process.argv.slice(2);
|