@kody-ade/kody-engine-lite 0.1.109 → 0.1.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +442 -419
- package/package.json +1 -1
package/dist/bin/cli.js
CHANGED
|
@@ -998,6 +998,180 @@ var init_task_resolution = __esm({
|
|
|
998
998
|
}
|
|
999
999
|
});
|
|
1000
1000
|
|
|
1001
|
+
// src/cli/litellm.ts
|
|
1002
|
+
import * as fs10 from "fs";
|
|
1003
|
+
import * as os from "os";
|
|
1004
|
+
import * as path9 from "path";
|
|
1005
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
1006
|
+
async function checkLitellmHealth(url) {
|
|
1007
|
+
try {
|
|
1008
|
+
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
1009
|
+
return response.ok;
|
|
1010
|
+
} catch {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
async function checkModelHealth(baseUrl, apiKey, model) {
|
|
1015
|
+
try {
|
|
1016
|
+
const res = await fetch(`${baseUrl}/v1/messages`, {
|
|
1017
|
+
method: "POST",
|
|
1018
|
+
headers: {
|
|
1019
|
+
"Content-Type": "application/json",
|
|
1020
|
+
"x-api-key": apiKey,
|
|
1021
|
+
"anthropic-version": "2023-06-01"
|
|
1022
|
+
},
|
|
1023
|
+
body: JSON.stringify({
|
|
1024
|
+
model,
|
|
1025
|
+
max_tokens: 4,
|
|
1026
|
+
messages: [{ role: "user", content: "Reply with: ok" }]
|
|
1027
|
+
}),
|
|
1028
|
+
signal: AbortSignal.timeout(3e4)
|
|
1029
|
+
});
|
|
1030
|
+
if (!res.ok) {
|
|
1031
|
+
const body2 = await res.text().catch(() => "");
|
|
1032
|
+
return { ok: false, error: `HTTP ${res.status}: ${body2.slice(0, 200)}` };
|
|
1033
|
+
}
|
|
1034
|
+
const body = await res.json();
|
|
1035
|
+
const hasAnthropicContent = Array.isArray(body.content) && body.content.some((b) => b.type === "text");
|
|
1036
|
+
const hasThinkingContent = Array.isArray(body.content) && body.content.some((b) => b.type === "thinking");
|
|
1037
|
+
const hasOpenAIContent = !!body.choices?.[0]?.message?.content;
|
|
1038
|
+
if (!hasAnthropicContent && !hasThinkingContent && !hasOpenAIContent) {
|
|
1039
|
+
return { ok: false, error: `Unexpected response format: ${JSON.stringify(body).slice(0, 200)}` };
|
|
1040
|
+
}
|
|
1041
|
+
return { ok: true };
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function generateLitellmConfig(provider, modelMap) {
|
|
1047
|
+
const apiKeyVar = providerApiKeyEnvVar(provider);
|
|
1048
|
+
const entries = ["model_list:"];
|
|
1049
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1050
|
+
for (const providerModel of Object.values(modelMap)) {
|
|
1051
|
+
if (seen.has(providerModel)) continue;
|
|
1052
|
+
seen.add(providerModel);
|
|
1053
|
+
entries.push(` - model_name: ${providerModel}`);
|
|
1054
|
+
entries.push(` litellm_params:`);
|
|
1055
|
+
entries.push(` model: ${provider}/${providerModel}`);
|
|
1056
|
+
entries.push(` api_key: os.environ/${apiKeyVar}`);
|
|
1057
|
+
}
|
|
1058
|
+
return entries.join("\n") + "\n";
|
|
1059
|
+
}
|
|
1060
|
+
function generateLitellmConfigFromStages(defaultConfig, stages) {
|
|
1061
|
+
const proxyModels = [];
|
|
1062
|
+
if (defaultConfig && defaultConfig.provider !== "claude" && defaultConfig.provider !== "anthropic") {
|
|
1063
|
+
proxyModels.push(defaultConfig);
|
|
1064
|
+
}
|
|
1065
|
+
if (stages) {
|
|
1066
|
+
for (const sc of Object.values(stages)) {
|
|
1067
|
+
if (sc.provider !== "claude" && sc.provider !== "anthropic") {
|
|
1068
|
+
proxyModels.push(sc);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
if (proxyModels.length === 0) return void 0;
|
|
1073
|
+
const entries = ["model_list:"];
|
|
1074
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1075
|
+
for (const { provider, model } of proxyModels) {
|
|
1076
|
+
const key = `${provider}/${model}`;
|
|
1077
|
+
if (seen.has(key)) continue;
|
|
1078
|
+
seen.add(key);
|
|
1079
|
+
const apiKeyVar = providerApiKeyEnvVar(provider);
|
|
1080
|
+
entries.push(` - model_name: ${model}`);
|
|
1081
|
+
entries.push(` litellm_params:`);
|
|
1082
|
+
entries.push(` model: ${provider}/${model}`);
|
|
1083
|
+
entries.push(` api_key: os.environ/${apiKeyVar}`);
|
|
1084
|
+
}
|
|
1085
|
+
return entries.join("\n") + "\n";
|
|
1086
|
+
}
|
|
1087
|
+
async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
1088
|
+
if (!generatedConfig) {
|
|
1089
|
+
logger.warn("No provider configured in kody.config.json \u2014 cannot start LiteLLM proxy");
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
const configPath = path9.join(os.tmpdir(), "kody-litellm-config.yaml");
|
|
1093
|
+
fs10.writeFileSync(configPath, generatedConfig);
|
|
1094
|
+
const portMatch = url.match(/:(\d+)/);
|
|
1095
|
+
const port = portMatch ? portMatch[1] : "4000";
|
|
1096
|
+
let litellmFound = false;
|
|
1097
|
+
try {
|
|
1098
|
+
execFileSync9("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
1099
|
+
litellmFound = true;
|
|
1100
|
+
} catch {
|
|
1101
|
+
try {
|
|
1102
|
+
execFileSync9("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
|
|
1103
|
+
litellmFound = true;
|
|
1104
|
+
} catch {
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (!litellmFound) {
|
|
1108
|
+
logger.warn("litellm not installed (pip install 'litellm[proxy]')");
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
logger.info(`Starting LiteLLM proxy on port ${port}...`);
|
|
1112
|
+
let cmd;
|
|
1113
|
+
let args2;
|
|
1114
|
+
try {
|
|
1115
|
+
execFileSync9("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
1116
|
+
cmd = "litellm";
|
|
1117
|
+
args2 = ["--config", configPath, "--port", port];
|
|
1118
|
+
} catch {
|
|
1119
|
+
cmd = "python3";
|
|
1120
|
+
args2 = ["-m", "litellm", "--config", configPath, "--port", port];
|
|
1121
|
+
}
|
|
1122
|
+
const dotenvPath = path9.join(projectDir, ".env");
|
|
1123
|
+
const dotenvVars = {};
|
|
1124
|
+
if (fs10.existsSync(dotenvPath)) {
|
|
1125
|
+
for (const rawLine of fs10.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
1126
|
+
const line = rawLine.trim();
|
|
1127
|
+
if (!line || line.startsWith("#")) continue;
|
|
1128
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
1129
|
+
if (match) {
|
|
1130
|
+
let value = match[2].trim();
|
|
1131
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1132
|
+
value = value.slice(1, -1);
|
|
1133
|
+
}
|
|
1134
|
+
const commentIdx = value.indexOf(" #");
|
|
1135
|
+
if (commentIdx !== -1) value = value.slice(0, commentIdx).trim();
|
|
1136
|
+
if (value) dotenvVars[match[1]] = value;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (Object.keys(dotenvVars).length > 0) {
|
|
1140
|
+
logger.info(` Loaded API keys: ${Object.keys(dotenvVars).join(", ")}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
1144
|
+
const child = spawn2(cmd, args2, {
|
|
1145
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1146
|
+
detached: true,
|
|
1147
|
+
env: { ...process.env, ...dotenvVars }
|
|
1148
|
+
});
|
|
1149
|
+
let proxyStderr = "";
|
|
1150
|
+
child.stderr?.on("data", (chunk) => {
|
|
1151
|
+
proxyStderr += chunk.toString();
|
|
1152
|
+
});
|
|
1153
|
+
for (let i = 0; i < 30; i++) {
|
|
1154
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
1155
|
+
if (await checkLitellmHealth(url)) {
|
|
1156
|
+
logger.info(`LiteLLM proxy ready at ${url}`);
|
|
1157
|
+
return child;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (proxyStderr) {
|
|
1161
|
+
logger.warn(`LiteLLM stderr: ${proxyStderr.slice(-1e3)}`);
|
|
1162
|
+
}
|
|
1163
|
+
logger.warn("LiteLLM proxy failed to start within 60s");
|
|
1164
|
+
child.kill();
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
var init_litellm = __esm({
|
|
1168
|
+
"src/cli/litellm.ts"() {
|
|
1169
|
+
"use strict";
|
|
1170
|
+
init_logger();
|
|
1171
|
+
init_config();
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1001
1175
|
// src/cli/taskify-command.ts
|
|
1002
1176
|
var taskify_command_exports = {};
|
|
1003
1177
|
__export(taskify_command_exports, {
|
|
@@ -1007,8 +1181,8 @@ __export(taskify_command_exports, {
|
|
|
1007
1181
|
taskifyCommand: () => taskifyCommand,
|
|
1008
1182
|
topoSort: () => topoSort
|
|
1009
1183
|
});
|
|
1010
|
-
import * as
|
|
1011
|
-
import * as
|
|
1184
|
+
import * as fs11 from "fs";
|
|
1185
|
+
import * as path10 from "path";
|
|
1012
1186
|
import { fileURLToPath } from "url";
|
|
1013
1187
|
import { execSync } from "child_process";
|
|
1014
1188
|
function topoSort(tasks) {
|
|
@@ -1052,10 +1226,10 @@ function hasFlag(args2, flag) {
|
|
|
1052
1226
|
async function runTaskifyCommand() {
|
|
1053
1227
|
const args2 = process.argv.slice(3);
|
|
1054
1228
|
const cwdArg = getArg(args2, "--cwd") ?? process.cwd();
|
|
1055
|
-
const projectDir =
|
|
1229
|
+
const projectDir = path10.resolve(cwdArg);
|
|
1056
1230
|
const ticketId = getArg(args2, "--ticket") ?? process.env.TICKET_ID;
|
|
1057
1231
|
const prdFileArg = getArg(args2, "--file") ?? process.env.PRD_FILE;
|
|
1058
|
-
const prdFile = prdFileArg ?
|
|
1232
|
+
const prdFile = prdFileArg ? path10.resolve(projectDir, prdFileArg) : void 0;
|
|
1059
1233
|
const issueNumberStr = getArg(args2, "--issue-number") ?? process.env.ISSUE_NUMBER ?? "";
|
|
1060
1234
|
const issueNumber = issueNumberStr ? parseInt(issueNumberStr, 10) : void 0;
|
|
1061
1235
|
const feedback = getArg(args2, "--feedback") ?? process.env.FEEDBACK;
|
|
@@ -1066,19 +1240,40 @@ async function runTaskifyCommand() {
|
|
|
1066
1240
|
logger.error("Usage: kody taskify --ticket <ticket-id> OR kody taskify --file <prd.md>");
|
|
1067
1241
|
process.exit(1);
|
|
1068
1242
|
}
|
|
1069
|
-
if (prdFile && !
|
|
1243
|
+
if (prdFile && !fs11.existsSync(prdFile)) {
|
|
1070
1244
|
logger.error(`File not found: ${prdFile}`);
|
|
1071
1245
|
process.exit(1);
|
|
1072
1246
|
}
|
|
1073
1247
|
setConfigDir(projectDir);
|
|
1074
1248
|
setGhCwd(projectDir);
|
|
1075
|
-
|
|
1249
|
+
const config = getProjectConfig();
|
|
1250
|
+
let litellmProcess = null;
|
|
1251
|
+
let runnerEnv;
|
|
1252
|
+
if (anyStageNeedsProxy(config)) {
|
|
1253
|
+
const litellmUrl = getLitellmUrl();
|
|
1254
|
+
const proxyRunning = await checkLitellmHealth(litellmUrl);
|
|
1255
|
+
if (!proxyRunning) {
|
|
1256
|
+
let generatedConfig;
|
|
1257
|
+
if (config.agent.stages || config.agent.default) {
|
|
1258
|
+
generatedConfig = generateLitellmConfigFromStages(config.agent.default, config.agent.stages);
|
|
1259
|
+
} else if (config.agent.provider && config.agent.provider !== "anthropic") {
|
|
1260
|
+
generatedConfig = generateLitellmConfig(config.agent.provider, config.agent.modelMap);
|
|
1261
|
+
}
|
|
1262
|
+
litellmProcess = await tryStartLitellm(litellmUrl, projectDir, generatedConfig);
|
|
1263
|
+
}
|
|
1264
|
+
runnerEnv = {
|
|
1265
|
+
ANTHROPIC_BASE_URL: litellmUrl,
|
|
1266
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "dummy"
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
await taskifyCommand({ ticketId, prdFile, issueNumber, feedback, local, projectDir, taskId, runnerEnv });
|
|
1270
|
+
litellmProcess?.kill();
|
|
1076
1271
|
}
|
|
1077
1272
|
async function taskifyCommand(opts) {
|
|
1078
1273
|
const { ticketId, prdFile, issueNumber, feedback, local, projectDir, taskId } = opts;
|
|
1079
1274
|
const config = getProjectConfig();
|
|
1080
|
-
const taskDir =
|
|
1081
|
-
|
|
1275
|
+
const taskDir = path10.join(projectDir, ".kody", "tasks", taskId);
|
|
1276
|
+
fs11.mkdirSync(taskDir, { recursive: true });
|
|
1082
1277
|
const mode = prdFile ? "file" : "ticket";
|
|
1083
1278
|
logger.info(`[taskify] mode=${mode} source=${ticketId ?? prdFile} issue=${issueNumber ?? "none"} task=${taskId}`);
|
|
1084
1279
|
let mcpConfigJson;
|
|
@@ -1103,14 +1298,14 @@ Add the required MCP server config to \`kody.config.json\` and try again.`
|
|
|
1103
1298
|
}
|
|
1104
1299
|
const sc = resolveStageConfig(config, "taskify", "strong");
|
|
1105
1300
|
const model = sc.model;
|
|
1106
|
-
const fileContent = prdFile ?
|
|
1301
|
+
const fileContent = prdFile ? fs11.readFileSync(prdFile, "utf-8") : void 0;
|
|
1107
1302
|
let projectContext;
|
|
1108
1303
|
{
|
|
1109
1304
|
const parts = [];
|
|
1110
|
-
const memoryPath =
|
|
1111
|
-
if (
|
|
1305
|
+
const memoryPath = path10.join(projectDir, ".kody", "memory.md");
|
|
1306
|
+
if (fs11.existsSync(memoryPath)) {
|
|
1112
1307
|
try {
|
|
1113
|
-
const content =
|
|
1308
|
+
const content = fs11.readFileSync(memoryPath, "utf-8").slice(0, 2e3);
|
|
1114
1309
|
if (content.trim()) parts.push(`### Project Memory
|
|
1115
1310
|
${content}`);
|
|
1116
1311
|
} catch {
|
|
@@ -1129,16 +1324,17 @@ ${lines.join("\n")}
|
|
|
1129
1324
|
}
|
|
1130
1325
|
const prompt = buildPrompt({ ticketId, fileContent, taskDir, feedback, projectContext });
|
|
1131
1326
|
if (issueNumber && !local) {
|
|
1132
|
-
const src = mode === "file" ? `file \`${
|
|
1327
|
+
const src = mode === "file" ? `file \`${path10.basename(prdFile)}\`` : `ticket **${ticketId}**`;
|
|
1133
1328
|
postComment(issueNumber, `Kody is decomposing ${src} into tasks...`);
|
|
1134
1329
|
setLifecycleLabel(issueNumber, "planning");
|
|
1135
1330
|
}
|
|
1136
|
-
|
|
1331
|
+
fs11.writeFileSync(path10.join(taskDir, MARKER_FILE), JSON.stringify({ ticketId, prdFile, issueNumber }));
|
|
1137
1332
|
const runner = opts.runner ?? createClaudeCodeRunner();
|
|
1138
1333
|
logger.info(` model=${model} timeout=${TASKIFY_TIMEOUT_MS / 1e3}s`);
|
|
1139
1334
|
const result = await runner.run("taskify", prompt, model, TASKIFY_TIMEOUT_MS, taskDir, {
|
|
1140
1335
|
cwd: projectDir,
|
|
1141
|
-
mcpConfigJson
|
|
1336
|
+
mcpConfigJson,
|
|
1337
|
+
env: opts.runnerEnv
|
|
1142
1338
|
});
|
|
1143
1339
|
if (result.outcome !== "completed") {
|
|
1144
1340
|
const errMsg = result.outcome === "timed_out" ? "Taskify timed out after 5 minutes." : `Taskify failed: ${result.error}`;
|
|
@@ -1151,8 +1347,8 @@ ${lines.join("\n")}
|
|
|
1151
1347
|
}
|
|
1152
1348
|
process.exit(1);
|
|
1153
1349
|
}
|
|
1154
|
-
const resultPath =
|
|
1155
|
-
if (!
|
|
1350
|
+
const resultPath = path10.join(taskDir, RESULT_FILE);
|
|
1351
|
+
if (!fs11.existsSync(resultPath)) {
|
|
1156
1352
|
const errMsg = `Claude did not write ${RESULT_FILE}. Output:
|
|
1157
1353
|
|
|
1158
1354
|
${result.output?.slice(0, 500) ?? "(none)"}`;
|
|
@@ -1167,7 +1363,7 @@ ${errMsg}`);
|
|
|
1167
1363
|
}
|
|
1168
1364
|
let parsed;
|
|
1169
1365
|
try {
|
|
1170
|
-
parsed = JSON.parse(
|
|
1366
|
+
parsed = JSON.parse(fs11.readFileSync(resultPath, "utf-8"));
|
|
1171
1367
|
} catch {
|
|
1172
1368
|
const errMsg = `Could not parse ${RESULT_FILE} as JSON.`;
|
|
1173
1369
|
logger.error(`[taskify] ${errMsg}`);
|
|
@@ -1177,7 +1373,7 @@ ${errMsg}`);
|
|
|
1177
1373
|
}
|
|
1178
1374
|
process.exit(1);
|
|
1179
1375
|
}
|
|
1180
|
-
const sourceLabel = ticketId ?? (prdFile ?
|
|
1376
|
+
const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : "spec");
|
|
1181
1377
|
if (parsed.status === "questions") {
|
|
1182
1378
|
handleQuestions(parsed, sourceLabel, issueNumber, local ?? false);
|
|
1183
1379
|
} else if (parsed.status === "ready") {
|
|
@@ -1272,15 +1468,15 @@ function buildPrompt(opts) {
|
|
|
1272
1468
|
const { ticketId, fileContent, taskDir, feedback, projectContext } = opts;
|
|
1273
1469
|
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
1274
1470
|
const candidates = [
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1471
|
+
path10.resolve(scriptDir, "..", "prompts", "taskify-ticket.md"),
|
|
1472
|
+
path10.resolve(scriptDir, "..", "..", "prompts", "taskify-ticket.md"),
|
|
1473
|
+
path10.resolve(__dirname, "..", "..", "prompts", "taskify-ticket.md"),
|
|
1474
|
+
path10.resolve(__dirname, "..", "prompts", "taskify-ticket.md")
|
|
1279
1475
|
];
|
|
1280
1476
|
let template = "";
|
|
1281
1477
|
for (const candidate of candidates) {
|
|
1282
|
-
if (
|
|
1283
|
-
template =
|
|
1478
|
+
if (fs11.existsSync(candidate)) {
|
|
1479
|
+
template = fs11.readFileSync(candidate, "utf-8");
|
|
1284
1480
|
break;
|
|
1285
1481
|
}
|
|
1286
1482
|
}
|
|
@@ -1303,13 +1499,13 @@ function buildPrompt(opts) {
|
|
|
1303
1499
|
return template;
|
|
1304
1500
|
}
|
|
1305
1501
|
function isTaskifyRun(taskDir) {
|
|
1306
|
-
return
|
|
1502
|
+
return fs11.existsSync(path10.join(taskDir, MARKER_FILE));
|
|
1307
1503
|
}
|
|
1308
1504
|
function readTaskifyMarker(taskDir) {
|
|
1309
|
-
const markerPath =
|
|
1310
|
-
if (!
|
|
1505
|
+
const markerPath = path10.join(taskDir, MARKER_FILE);
|
|
1506
|
+
if (!fs11.existsSync(markerPath)) return null;
|
|
1311
1507
|
try {
|
|
1312
|
-
return JSON.parse(
|
|
1508
|
+
return JSON.parse(fs11.readFileSync(markerPath, "utf-8"));
|
|
1313
1509
|
} catch {
|
|
1314
1510
|
return null;
|
|
1315
1511
|
}
|
|
@@ -1324,7 +1520,8 @@ var init_taskify_command = __esm({
|
|
|
1324
1520
|
init_github_api();
|
|
1325
1521
|
init_logger();
|
|
1326
1522
|
init_task_resolution();
|
|
1327
|
-
|
|
1523
|
+
init_litellm();
|
|
1524
|
+
__dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
1328
1525
|
AUTO_TRIGGER_THRESHOLD = 5;
|
|
1329
1526
|
MAX_TASKS_GUARD = 20;
|
|
1330
1527
|
TASKIFY_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -1340,7 +1537,7 @@ __export(parse_inputs_exports, {
|
|
|
1340
1537
|
runCiParse: () => runCiParse,
|
|
1341
1538
|
writeOutputs: () => writeOutputs
|
|
1342
1539
|
});
|
|
1343
|
-
import * as
|
|
1540
|
+
import * as fs12 from "fs";
|
|
1344
1541
|
function generateTimestamp() {
|
|
1345
1542
|
const now = /* @__PURE__ */ new Date();
|
|
1346
1543
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -1506,12 +1703,12 @@ function writeOutputs(result) {
|
|
|
1506
1703
|
function output(key, value) {
|
|
1507
1704
|
if (outputFile) {
|
|
1508
1705
|
if (value.includes("\n")) {
|
|
1509
|
-
|
|
1706
|
+
fs12.appendFileSync(outputFile, `${key}<<KODY_EOF
|
|
1510
1707
|
${value}
|
|
1511
1708
|
KODY_EOF
|
|
1512
1709
|
`);
|
|
1513
1710
|
} else {
|
|
1514
|
-
|
|
1711
|
+
fs12.appendFileSync(outputFile, `${key}=${value}
|
|
1515
1712
|
`);
|
|
1516
1713
|
}
|
|
1517
1714
|
}
|
|
@@ -1636,7 +1833,7 @@ var init_definitions = __esm({
|
|
|
1636
1833
|
});
|
|
1637
1834
|
|
|
1638
1835
|
// src/git-utils.ts
|
|
1639
|
-
import { execFileSync as
|
|
1836
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
1640
1837
|
function getHookSafeEnv() {
|
|
1641
1838
|
if (!_hookSafeEnv) {
|
|
1642
1839
|
_hookSafeEnv = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
|
|
@@ -1644,7 +1841,7 @@ function getHookSafeEnv() {
|
|
|
1644
1841
|
return _hookSafeEnv;
|
|
1645
1842
|
}
|
|
1646
1843
|
function git(args2, options) {
|
|
1647
|
-
return
|
|
1844
|
+
return execFileSync10("git", args2, {
|
|
1648
1845
|
encoding: "utf-8",
|
|
1649
1846
|
timeout: options?.timeout ?? 3e4,
|
|
1650
1847
|
cwd: options?.cwd,
|
|
@@ -1830,14 +2027,14 @@ var init_git_utils = __esm({
|
|
|
1830
2027
|
});
|
|
1831
2028
|
|
|
1832
2029
|
// src/pipeline/state.ts
|
|
1833
|
-
import * as
|
|
1834
|
-
import * as
|
|
2030
|
+
import * as fs13 from "fs";
|
|
2031
|
+
import * as path11 from "path";
|
|
1835
2032
|
function loadState(taskId, taskDir) {
|
|
1836
|
-
const p =
|
|
1837
|
-
if (!
|
|
2033
|
+
const p = path11.join(taskDir, "status.json");
|
|
2034
|
+
if (!fs13.existsSync(p)) return null;
|
|
1838
2035
|
try {
|
|
1839
2036
|
const result = parseJsonSafe(
|
|
1840
|
-
|
|
2037
|
+
fs13.readFileSync(p, "utf-8"),
|
|
1841
2038
|
["taskId", "state", "stages", "createdAt", "updatedAt"]
|
|
1842
2039
|
);
|
|
1843
2040
|
if (!result.ok) {
|
|
@@ -1855,10 +2052,10 @@ function writeState(state, taskDir) {
|
|
|
1855
2052
|
...state,
|
|
1856
2053
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1857
2054
|
};
|
|
1858
|
-
const target =
|
|
2055
|
+
const target = path11.join(taskDir, "status.json");
|
|
1859
2056
|
const tmp = target + ".tmp";
|
|
1860
|
-
|
|
1861
|
-
|
|
2057
|
+
fs13.writeFileSync(tmp, JSON.stringify(updated, null, 2));
|
|
2058
|
+
fs13.renameSync(tmp, target);
|
|
1862
2059
|
return updated;
|
|
1863
2060
|
}
|
|
1864
2061
|
function initState(taskId) {
|
|
@@ -1899,16 +2096,16 @@ var init_complexity = __esm({
|
|
|
1899
2096
|
});
|
|
1900
2097
|
|
|
1901
2098
|
// src/memory.ts
|
|
1902
|
-
import * as
|
|
1903
|
-
import * as
|
|
2099
|
+
import * as fs14 from "fs";
|
|
2100
|
+
import * as path12 from "path";
|
|
1904
2101
|
function readProjectMemory(projectDir) {
|
|
1905
|
-
const memoryDir =
|
|
1906
|
-
if (!
|
|
1907
|
-
const files =
|
|
2102
|
+
const memoryDir = path12.join(projectDir, ".kody", "memory");
|
|
2103
|
+
if (!fs14.existsSync(memoryDir)) return "";
|
|
2104
|
+
const files = fs14.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
1908
2105
|
if (files.length === 0) return "";
|
|
1909
2106
|
const sections = [];
|
|
1910
2107
|
for (const file of files) {
|
|
1911
|
-
const content =
|
|
2108
|
+
const content = fs14.readFileSync(path12.join(memoryDir, file), "utf-8").trim();
|
|
1912
2109
|
if (content) {
|
|
1913
2110
|
sections.push(`## ${file.replace(".md", "")}
|
|
1914
2111
|
${content}`);
|
|
@@ -1927,8 +2124,8 @@ var init_memory = __esm({
|
|
|
1927
2124
|
});
|
|
1928
2125
|
|
|
1929
2126
|
// src/context-tiers.ts
|
|
1930
|
-
import * as
|
|
1931
|
-
import * as
|
|
2127
|
+
import * as fs15 from "fs";
|
|
2128
|
+
import * as path13 from "path";
|
|
1932
2129
|
function estimateTokens(text) {
|
|
1933
2130
|
return Math.ceil(text.length / 4);
|
|
1934
2131
|
}
|
|
@@ -2019,7 +2216,7 @@ function generateL1Json(content) {
|
|
|
2019
2216
|
}
|
|
2020
2217
|
}
|
|
2021
2218
|
function getTieredContent(filePath, content) {
|
|
2022
|
-
const key =
|
|
2219
|
+
const key = path13.basename(filePath);
|
|
2023
2220
|
return {
|
|
2024
2221
|
source: filePath,
|
|
2025
2222
|
L0: generateL0(content, key),
|
|
@@ -2031,15 +2228,15 @@ function selectTier(tiered, tier) {
|
|
|
2031
2228
|
return tiered[tier];
|
|
2032
2229
|
}
|
|
2033
2230
|
function readProjectMemoryTiered(projectDir, tier) {
|
|
2034
|
-
const memoryDir =
|
|
2035
|
-
if (!
|
|
2036
|
-
const files =
|
|
2231
|
+
const memoryDir = path13.join(projectDir, ".kody", "memory");
|
|
2232
|
+
if (!fs15.existsSync(memoryDir)) return "";
|
|
2233
|
+
const files = fs15.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
2037
2234
|
if (files.length === 0) return "";
|
|
2038
2235
|
const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
|
|
2039
2236
|
const sections = [];
|
|
2040
2237
|
for (const file of files) {
|
|
2041
|
-
const filePath =
|
|
2042
|
-
const content =
|
|
2238
|
+
const filePath = path13.join(memoryDir, file);
|
|
2239
|
+
const content = fs15.readFileSync(filePath, "utf-8").trim();
|
|
2043
2240
|
if (!content) continue;
|
|
2044
2241
|
const tiered = getTieredContent(filePath, content);
|
|
2045
2242
|
const selected = selectTier(tiered, tier);
|
|
@@ -2062,9 +2259,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
2062
2259
|
`;
|
|
2063
2260
|
context += `Task Directory: ${taskDir}
|
|
2064
2261
|
`;
|
|
2065
|
-
const taskMdPath =
|
|
2066
|
-
if (
|
|
2067
|
-
const content =
|
|
2262
|
+
const taskMdPath = path13.join(taskDir, "task.md");
|
|
2263
|
+
if (fs15.existsSync(taskMdPath)) {
|
|
2264
|
+
const content = fs15.readFileSync(taskMdPath, "utf-8");
|
|
2068
2265
|
const selected = selectContent(taskMdPath, content, policy.taskDescription);
|
|
2069
2266
|
const label = tierLabel("Task Description", policy.taskDescription);
|
|
2070
2267
|
context += `
|
|
@@ -2072,9 +2269,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
|
|
|
2072
2269
|
${selected}
|
|
2073
2270
|
`;
|
|
2074
2271
|
}
|
|
2075
|
-
const taskJsonPath =
|
|
2076
|
-
if (
|
|
2077
|
-
const content =
|
|
2272
|
+
const taskJsonPath = path13.join(taskDir, "task.json");
|
|
2273
|
+
if (fs15.existsSync(taskJsonPath)) {
|
|
2274
|
+
const content = fs15.readFileSync(taskJsonPath, "utf-8");
|
|
2078
2275
|
if (policy.taskClassification === "L2") {
|
|
2079
2276
|
try {
|
|
2080
2277
|
const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
|
|
@@ -2100,9 +2297,9 @@ ${selected}
|
|
|
2100
2297
|
}
|
|
2101
2298
|
}
|
|
2102
2299
|
}
|
|
2103
|
-
const specPath =
|
|
2104
|
-
if (
|
|
2105
|
-
const content =
|
|
2300
|
+
const specPath = path13.join(taskDir, "spec.md");
|
|
2301
|
+
if (fs15.existsSync(specPath)) {
|
|
2302
|
+
const content = fs15.readFileSync(specPath, "utf-8");
|
|
2106
2303
|
const selected = selectContent(specPath, content, policy.spec);
|
|
2107
2304
|
const label = tierLabel("Spec", policy.spec);
|
|
2108
2305
|
context += `
|
|
@@ -2110,9 +2307,9 @@ ${selected}
|
|
|
2110
2307
|
${selected}
|
|
2111
2308
|
`;
|
|
2112
2309
|
}
|
|
2113
|
-
const planPath =
|
|
2114
|
-
if (
|
|
2115
|
-
const content =
|
|
2310
|
+
const planPath = path13.join(taskDir, "plan.md");
|
|
2311
|
+
if (fs15.existsSync(planPath)) {
|
|
2312
|
+
const content = fs15.readFileSync(planPath, "utf-8");
|
|
2116
2313
|
const selected = selectContent(planPath, content, policy.plan);
|
|
2117
2314
|
const label = tierLabel("Plan", policy.plan);
|
|
2118
2315
|
context += `
|
|
@@ -2120,9 +2317,9 @@ ${selected}
|
|
|
2120
2317
|
${selected}
|
|
2121
2318
|
`;
|
|
2122
2319
|
}
|
|
2123
|
-
const contextMdPath =
|
|
2124
|
-
if (
|
|
2125
|
-
const content =
|
|
2320
|
+
const contextMdPath = path13.join(taskDir, "context.md");
|
|
2321
|
+
if (fs15.existsSync(contextMdPath)) {
|
|
2322
|
+
const content = fs15.readFileSync(contextMdPath, "utf-8");
|
|
2126
2323
|
const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
|
|
2127
2324
|
const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
|
|
2128
2325
|
context += `
|
|
@@ -2208,24 +2405,24 @@ var init_context_tiers = __esm({
|
|
|
2208
2405
|
});
|
|
2209
2406
|
|
|
2210
2407
|
// src/context.ts
|
|
2211
|
-
import * as
|
|
2212
|
-
import * as
|
|
2408
|
+
import * as fs16 from "fs";
|
|
2409
|
+
import * as path14 from "path";
|
|
2213
2410
|
function readPromptFile(stageName, projectDir) {
|
|
2214
2411
|
if (projectDir) {
|
|
2215
|
-
const stepFile =
|
|
2216
|
-
if (
|
|
2217
|
-
return
|
|
2412
|
+
const stepFile = path14.join(projectDir, ".kody", "steps", `${stageName}.md`);
|
|
2413
|
+
if (fs16.existsSync(stepFile)) {
|
|
2414
|
+
return fs16.readFileSync(stepFile, "utf-8");
|
|
2218
2415
|
}
|
|
2219
2416
|
console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
|
|
2220
2417
|
}
|
|
2221
2418
|
const scriptDir = new URL(".", import.meta.url).pathname;
|
|
2222
2419
|
const candidates = [
|
|
2223
|
-
|
|
2224
|
-
|
|
2420
|
+
path14.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
|
|
2421
|
+
path14.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
|
|
2225
2422
|
];
|
|
2226
2423
|
for (const candidate of candidates) {
|
|
2227
|
-
if (
|
|
2228
|
-
return
|
|
2424
|
+
if (fs16.existsSync(candidate)) {
|
|
2425
|
+
return fs16.readFileSync(candidate, "utf-8");
|
|
2229
2426
|
}
|
|
2230
2427
|
}
|
|
2231
2428
|
throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
|
|
@@ -2237,18 +2434,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
|
|
|
2237
2434
|
`;
|
|
2238
2435
|
context += `Task Directory: ${taskDir}
|
|
2239
2436
|
`;
|
|
2240
|
-
const taskMdPath =
|
|
2241
|
-
if (
|
|
2242
|
-
const taskMd =
|
|
2437
|
+
const taskMdPath = path14.join(taskDir, "task.md");
|
|
2438
|
+
if (fs16.existsSync(taskMdPath)) {
|
|
2439
|
+
const taskMd = fs16.readFileSync(taskMdPath, "utf-8");
|
|
2243
2440
|
context += `
|
|
2244
2441
|
## Task Description
|
|
2245
2442
|
${taskMd}
|
|
2246
2443
|
`;
|
|
2247
2444
|
}
|
|
2248
|
-
const taskJsonPath =
|
|
2249
|
-
if (
|
|
2445
|
+
const taskJsonPath = path14.join(taskDir, "task.json");
|
|
2446
|
+
if (fs16.existsSync(taskJsonPath)) {
|
|
2250
2447
|
try {
|
|
2251
|
-
const taskDef = JSON.parse(
|
|
2448
|
+
const taskDef = JSON.parse(fs16.readFileSync(taskJsonPath, "utf-8"));
|
|
2252
2449
|
context += `
|
|
2253
2450
|
## Task Classification
|
|
2254
2451
|
`;
|
|
@@ -2261,27 +2458,27 @@ ${taskMd}
|
|
|
2261
2458
|
} catch {
|
|
2262
2459
|
}
|
|
2263
2460
|
}
|
|
2264
|
-
const specPath =
|
|
2265
|
-
if (
|
|
2266
|
-
const spec =
|
|
2461
|
+
const specPath = path14.join(taskDir, "spec.md");
|
|
2462
|
+
if (fs16.existsSync(specPath)) {
|
|
2463
|
+
const spec = fs16.readFileSync(specPath, "utf-8");
|
|
2267
2464
|
const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
|
|
2268
2465
|
context += `
|
|
2269
2466
|
## Spec Summary
|
|
2270
2467
|
${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
|
|
2271
2468
|
`;
|
|
2272
2469
|
}
|
|
2273
|
-
const planPath =
|
|
2274
|
-
if (
|
|
2275
|
-
const plan =
|
|
2470
|
+
const planPath = path14.join(taskDir, "plan.md");
|
|
2471
|
+
if (fs16.existsSync(planPath)) {
|
|
2472
|
+
const plan = fs16.readFileSync(planPath, "utf-8");
|
|
2276
2473
|
const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
|
|
2277
2474
|
context += `
|
|
2278
2475
|
## Plan Summary
|
|
2279
2476
|
${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
|
|
2280
2477
|
`;
|
|
2281
2478
|
}
|
|
2282
|
-
const contextMdPath =
|
|
2283
|
-
if (
|
|
2284
|
-
const accumulated =
|
|
2479
|
+
const contextMdPath = path14.join(taskDir, "context.md");
|
|
2480
|
+
if (fs16.existsSync(contextMdPath)) {
|
|
2481
|
+
const accumulated = fs16.readFileSync(contextMdPath, "utf-8");
|
|
2285
2482
|
const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
|
|
2286
2483
|
const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
|
|
2287
2484
|
context += `
|
|
@@ -2299,17 +2496,17 @@ ${feedback}
|
|
|
2299
2496
|
}
|
|
2300
2497
|
function inferHasUIFromScope(scope) {
|
|
2301
2498
|
return scope.some((filePath) => {
|
|
2302
|
-
const ext =
|
|
2499
|
+
const ext = path14.extname(filePath).toLowerCase();
|
|
2303
2500
|
if (UI_EXTENSIONS.has(ext)) return true;
|
|
2304
2501
|
const normalized = filePath.replace(/\\/g, "/");
|
|
2305
2502
|
return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
|
|
2306
2503
|
});
|
|
2307
2504
|
}
|
|
2308
2505
|
function taskHasUI(taskDir) {
|
|
2309
|
-
const taskJsonPath =
|
|
2310
|
-
if (!
|
|
2506
|
+
const taskJsonPath = path14.join(taskDir, "task.json");
|
|
2507
|
+
if (!fs16.existsSync(taskJsonPath)) return true;
|
|
2311
2508
|
try {
|
|
2312
|
-
const taskDef = JSON.parse(
|
|
2509
|
+
const taskDef = JSON.parse(fs16.readFileSync(taskJsonPath, "utf-8"));
|
|
2313
2510
|
const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
|
|
2314
2511
|
if (scope.length === 0) return true;
|
|
2315
2512
|
return inferHasUIFromScope(scope);
|
|
@@ -2431,9 +2628,9 @@ ${prompt}` : prompt;
|
|
|
2431
2628
|
}
|
|
2432
2629
|
if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
|
|
2433
2630
|
assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
|
|
2434
|
-
const qaGuidePath =
|
|
2435
|
-
if (
|
|
2436
|
-
const qaGuide =
|
|
2631
|
+
const qaGuidePath = path14.join(projectDir, ".kody", "qa-guide.md");
|
|
2632
|
+
if (fs16.existsSync(qaGuidePath)) {
|
|
2633
|
+
const qaGuide = fs16.readFileSync(qaGuidePath, "utf-8").trim();
|
|
2437
2634
|
assembled = assembled + "\n\n" + qaGuide;
|
|
2438
2635
|
}
|
|
2439
2636
|
}
|
|
@@ -2528,8 +2725,8 @@ var init_runner_selection = __esm({
|
|
|
2528
2725
|
});
|
|
2529
2726
|
|
|
2530
2727
|
// src/stages/agent.ts
|
|
2531
|
-
import * as
|
|
2532
|
-
import * as
|
|
2728
|
+
import * as fs17 from "fs";
|
|
2729
|
+
import * as path15 from "path";
|
|
2533
2730
|
function getSessionInfo(stageName, sessions) {
|
|
2534
2731
|
const group = SESSION_GROUP[stageName];
|
|
2535
2732
|
if (!group) return void 0;
|
|
@@ -2616,27 +2813,27 @@ async function executeAgentStage(ctx, def) {
|
|
|
2616
2813
|
}
|
|
2617
2814
|
const result = lastResult;
|
|
2618
2815
|
if (def.outputFile && result.output) {
|
|
2619
|
-
|
|
2816
|
+
fs17.writeFileSync(path15.join(ctx.taskDir, def.outputFile), result.output);
|
|
2620
2817
|
}
|
|
2621
2818
|
if (def.outputFile) {
|
|
2622
|
-
const outputPath =
|
|
2623
|
-
if (!
|
|
2624
|
-
const ext =
|
|
2625
|
-
const base =
|
|
2626
|
-
const files =
|
|
2819
|
+
const outputPath = path15.join(ctx.taskDir, def.outputFile);
|
|
2820
|
+
if (!fs17.existsSync(outputPath)) {
|
|
2821
|
+
const ext = path15.extname(def.outputFile);
|
|
2822
|
+
const base = path15.basename(def.outputFile, ext);
|
|
2823
|
+
const files = fs17.readdirSync(ctx.taskDir);
|
|
2627
2824
|
const variant = files.find(
|
|
2628
2825
|
(f) => f.startsWith(base + "-") && f.endsWith(ext)
|
|
2629
2826
|
);
|
|
2630
2827
|
if (variant) {
|
|
2631
|
-
|
|
2828
|
+
fs17.renameSync(path15.join(ctx.taskDir, variant), outputPath);
|
|
2632
2829
|
logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
|
|
2633
2830
|
}
|
|
2634
2831
|
}
|
|
2635
2832
|
}
|
|
2636
2833
|
if (def.outputFile) {
|
|
2637
|
-
const outputPath =
|
|
2638
|
-
if (
|
|
2639
|
-
const content =
|
|
2834
|
+
const outputPath = path15.join(ctx.taskDir, def.outputFile);
|
|
2835
|
+
if (fs17.existsSync(outputPath)) {
|
|
2836
|
+
const content = fs17.readFileSync(outputPath, "utf-8");
|
|
2640
2837
|
const validation = validateStageOutput(def.name, content);
|
|
2641
2838
|
if (!validation.valid) {
|
|
2642
2839
|
if (def.name === "taskify") {
|
|
@@ -2650,7 +2847,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
2650
2847
|
const stripped = stripFences(retryResult.output);
|
|
2651
2848
|
const retryValidation = validateTaskJson(stripped);
|
|
2652
2849
|
if (retryValidation.valid) {
|
|
2653
|
-
|
|
2850
|
+
fs17.writeFileSync(outputPath, retryResult.output);
|
|
2654
2851
|
logger.info(` taskify retry produced valid JSON`);
|
|
2655
2852
|
} else {
|
|
2656
2853
|
logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
|
|
@@ -2663,7 +2860,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
2663
2860
|
risk_level: "low",
|
|
2664
2861
|
questions: []
|
|
2665
2862
|
}, null, 2);
|
|
2666
|
-
|
|
2863
|
+
fs17.writeFileSync(outputPath, fallback);
|
|
2667
2864
|
logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
|
|
2668
2865
|
}
|
|
2669
2866
|
}
|
|
@@ -2677,7 +2874,7 @@ async function executeAgentStage(ctx, def) {
|
|
|
2677
2874
|
return { outcome: "completed", outputFile: def.outputFile, retries };
|
|
2678
2875
|
}
|
|
2679
2876
|
function appendStageContext(taskDir, stageName, output) {
|
|
2680
|
-
const contextPath =
|
|
2877
|
+
const contextPath = path15.join(taskDir, "context.md");
|
|
2681
2878
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
|
|
2682
2879
|
let summary;
|
|
2683
2880
|
if (output && output.trim()) {
|
|
@@ -2690,7 +2887,7 @@ function appendStageContext(taskDir, stageName, output) {
|
|
|
2690
2887
|
### ${stageName} (${timestamp2})
|
|
2691
2888
|
${summary}
|
|
2692
2889
|
`;
|
|
2693
|
-
|
|
2890
|
+
fs17.appendFileSync(contextPath, entry);
|
|
2694
2891
|
}
|
|
2695
2892
|
var SESSION_GROUP;
|
|
2696
2893
|
var init_agent = __esm({
|
|
@@ -2713,7 +2910,7 @@ var init_agent = __esm({
|
|
|
2713
2910
|
});
|
|
2714
2911
|
|
|
2715
2912
|
// src/verify-runner.ts
|
|
2716
|
-
import { execFileSync as
|
|
2913
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
2717
2914
|
function isExecError(err) {
|
|
2718
2915
|
return typeof err === "object" && err !== null;
|
|
2719
2916
|
}
|
|
@@ -2749,7 +2946,7 @@ function runCommand(cmd, cwd, timeout) {
|
|
|
2749
2946
|
return { success: true, output: "", timedOut: false };
|
|
2750
2947
|
}
|
|
2751
2948
|
try {
|
|
2752
|
-
const output =
|
|
2949
|
+
const output = execFileSync11(parts[0], parts.slice(1), {
|
|
2753
2950
|
cwd,
|
|
2754
2951
|
timeout,
|
|
2755
2952
|
encoding: "utf-8",
|
|
@@ -2820,7 +3017,7 @@ var init_verify_runner = __esm({
|
|
|
2820
3017
|
});
|
|
2821
3018
|
|
|
2822
3019
|
// src/observer.ts
|
|
2823
|
-
import { execFileSync as
|
|
3020
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
2824
3021
|
async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, model, options) {
|
|
2825
3022
|
const context = [
|
|
2826
3023
|
`Stage: ${stageName}`,
|
|
@@ -2880,13 +3077,13 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
|
|
|
2880
3077
|
}
|
|
2881
3078
|
function getModifiedFiles(projectDir) {
|
|
2882
3079
|
try {
|
|
2883
|
-
const staged =
|
|
3080
|
+
const staged = execFileSync12("git", ["diff", "--name-only", "--cached"], {
|
|
2884
3081
|
encoding: "utf-8",
|
|
2885
3082
|
cwd: projectDir,
|
|
2886
3083
|
timeout: 5e3,
|
|
2887
3084
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2888
3085
|
}).trim();
|
|
2889
|
-
const unstaged =
|
|
3086
|
+
const unstaged = execFileSync12("git", ["diff", "--name-only"], {
|
|
2890
3087
|
encoding: "utf-8",
|
|
2891
3088
|
cwd: projectDir,
|
|
2892
3089
|
timeout: 5e3,
|
|
@@ -2929,8 +3126,8 @@ Error context:
|
|
|
2929
3126
|
});
|
|
2930
3127
|
|
|
2931
3128
|
// src/stages/gate.ts
|
|
2932
|
-
import * as
|
|
2933
|
-
import * as
|
|
3129
|
+
import * as fs18 from "fs";
|
|
3130
|
+
import * as path16 from "path";
|
|
2934
3131
|
function executeGateStage(ctx, def) {
|
|
2935
3132
|
if (ctx.input.dryRun) {
|
|
2936
3133
|
logger.info(` [dry-run] skipping ${def.name}`);
|
|
@@ -2973,7 +3170,7 @@ ${output}
|
|
|
2973
3170
|
`);
|
|
2974
3171
|
}
|
|
2975
3172
|
}
|
|
2976
|
-
|
|
3173
|
+
fs18.writeFileSync(path16.join(ctx.taskDir, "verify.md"), lines.join(""));
|
|
2977
3174
|
return {
|
|
2978
3175
|
outcome: verifyResult.pass ? "completed" : "failed",
|
|
2979
3176
|
retries: 0
|
|
@@ -2988,9 +3185,9 @@ var init_gate = __esm({
|
|
|
2988
3185
|
});
|
|
2989
3186
|
|
|
2990
3187
|
// src/stages/verify.ts
|
|
2991
|
-
import * as
|
|
2992
|
-
import * as
|
|
2993
|
-
import { execFileSync as
|
|
3188
|
+
import * as fs19 from "fs";
|
|
3189
|
+
import * as path17 from "path";
|
|
3190
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
2994
3191
|
async function executeVerifyWithAutofix(ctx, def) {
|
|
2995
3192
|
const maxAttempts = def.maxRetries ?? 2;
|
|
2996
3193
|
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
@@ -3000,8 +3197,8 @@ async function executeVerifyWithAutofix(ctx, def) {
|
|
|
3000
3197
|
return { ...gateResult, retries: attempt };
|
|
3001
3198
|
}
|
|
3002
3199
|
if (attempt < maxAttempts) {
|
|
3003
|
-
const verifyPath =
|
|
3004
|
-
const errorOutput =
|
|
3200
|
+
const verifyPath = path17.join(ctx.taskDir, "verify.md");
|
|
3201
|
+
const errorOutput = fs19.existsSync(verifyPath) ? fs19.readFileSync(verifyPath, "utf-8") : "Unknown error";
|
|
3005
3202
|
const modifiedFiles = getModifiedFiles(ctx.projectDir);
|
|
3006
3203
|
const defaultRunner = getRunnerForStage(ctx, "taskify");
|
|
3007
3204
|
const diagConfig = getProjectConfig();
|
|
@@ -3044,7 +3241,7 @@ ${diagnosis.resolution}`);
|
|
|
3044
3241
|
const parts = parseCommand(cmd);
|
|
3045
3242
|
if (parts.length === 0) return;
|
|
3046
3243
|
try {
|
|
3047
|
-
|
|
3244
|
+
execFileSync13(parts[0], parts.slice(1), {
|
|
3048
3245
|
stdio: "pipe",
|
|
3049
3246
|
timeout: FIX_COMMAND_TIMEOUT_MS
|
|
3050
3247
|
});
|
|
@@ -3097,8 +3294,8 @@ var init_verify = __esm({
|
|
|
3097
3294
|
});
|
|
3098
3295
|
|
|
3099
3296
|
// src/review-standalone.ts
|
|
3100
|
-
import * as
|
|
3101
|
-
import * as
|
|
3297
|
+
import * as fs20 from "fs";
|
|
3298
|
+
import * as path18 from "path";
|
|
3102
3299
|
function resolveReviewTarget(input) {
|
|
3103
3300
|
if (input.prs.length === 0) {
|
|
3104
3301
|
return {
|
|
@@ -3122,8 +3319,8 @@ Or comment on the specific PR: \`@kody review\``
|
|
|
3122
3319
|
}
|
|
3123
3320
|
async function runStandaloneReview(input) {
|
|
3124
3321
|
const taskId = input.taskId ?? `review-${generateTaskId()}`;
|
|
3125
|
-
const taskDir =
|
|
3126
|
-
|
|
3322
|
+
const taskDir = path18.join(input.projectDir, ".kody", "tasks", taskId);
|
|
3323
|
+
fs20.mkdirSync(taskDir, { recursive: true });
|
|
3127
3324
|
let diffInstruction = "";
|
|
3128
3325
|
let filesChangedSection = "";
|
|
3129
3326
|
if (input.baseBranch) {
|
|
@@ -3150,7 +3347,7 @@ ${fileList}`;
|
|
|
3150
3347
|
const taskContent = `# ${input.prTitle}
|
|
3151
3348
|
|
|
3152
3349
|
${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
3153
|
-
|
|
3350
|
+
fs20.writeFileSync(path18.join(taskDir, "task.md"), taskContent);
|
|
3154
3351
|
const reviewDef = STAGES.find((s) => s.name === "review");
|
|
3155
3352
|
const ctx = {
|
|
3156
3353
|
taskId,
|
|
@@ -3172,10 +3369,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
|
|
|
3172
3369
|
error: result.error ?? "Review stage failed"
|
|
3173
3370
|
};
|
|
3174
3371
|
}
|
|
3175
|
-
const reviewPath =
|
|
3372
|
+
const reviewPath = path18.join(taskDir, "review.md");
|
|
3176
3373
|
let reviewContent;
|
|
3177
|
-
if (
|
|
3178
|
-
reviewContent =
|
|
3374
|
+
if (fs20.existsSync(reviewPath)) {
|
|
3375
|
+
reviewContent = fs20.readFileSync(reviewPath, "utf-8");
|
|
3179
3376
|
}
|
|
3180
3377
|
return {
|
|
3181
3378
|
outcome: "completed",
|
|
@@ -3215,8 +3412,8 @@ var init_review_standalone = __esm({
|
|
|
3215
3412
|
});
|
|
3216
3413
|
|
|
3217
3414
|
// src/stages/review.ts
|
|
3218
|
-
import * as
|
|
3219
|
-
import * as
|
|
3415
|
+
import * as fs21 from "fs";
|
|
3416
|
+
import * as path19 from "path";
|
|
3220
3417
|
async function executeReviewWithFix(ctx, def) {
|
|
3221
3418
|
if (ctx.input.dryRun) {
|
|
3222
3419
|
return { outcome: "completed", retries: 0 };
|
|
@@ -3230,11 +3427,11 @@ async function executeReviewWithFix(ctx, def) {
|
|
|
3230
3427
|
if (reviewResult.outcome !== "completed") {
|
|
3231
3428
|
return reviewResult;
|
|
3232
3429
|
}
|
|
3233
|
-
const reviewFile =
|
|
3234
|
-
if (!
|
|
3430
|
+
const reviewFile = path19.join(ctx.taskDir, "review.md");
|
|
3431
|
+
if (!fs21.existsSync(reviewFile)) {
|
|
3235
3432
|
return { outcome: "failed", retries: iteration, error: "review.md not found" };
|
|
3236
3433
|
}
|
|
3237
|
-
const content =
|
|
3434
|
+
const content = fs21.readFileSync(reviewFile, "utf-8");
|
|
3238
3435
|
if (detectReviewVerdict(content) !== "fail") {
|
|
3239
3436
|
return { ...reviewResult, retries: iteration };
|
|
3240
3437
|
}
|
|
@@ -3263,15 +3460,15 @@ var init_review = __esm({
|
|
|
3263
3460
|
});
|
|
3264
3461
|
|
|
3265
3462
|
// src/stages/ship.ts
|
|
3266
|
-
import * as
|
|
3267
|
-
import * as
|
|
3268
|
-
import { execFileSync as
|
|
3463
|
+
import * as fs22 from "fs";
|
|
3464
|
+
import * as path20 from "path";
|
|
3465
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
3269
3466
|
function buildPrBody(ctx) {
|
|
3270
3467
|
const sections = [];
|
|
3271
|
-
const taskJsonPath =
|
|
3272
|
-
if (
|
|
3468
|
+
const taskJsonPath = path20.join(ctx.taskDir, "task.json");
|
|
3469
|
+
if (fs22.existsSync(taskJsonPath)) {
|
|
3273
3470
|
try {
|
|
3274
|
-
const raw =
|
|
3471
|
+
const raw = fs22.readFileSync(taskJsonPath, "utf-8");
|
|
3275
3472
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
3276
3473
|
const task = JSON.parse(cleaned);
|
|
3277
3474
|
if (task.description) {
|
|
@@ -3290,9 +3487,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
|
|
|
3290
3487
|
} catch {
|
|
3291
3488
|
}
|
|
3292
3489
|
}
|
|
3293
|
-
const reviewPath =
|
|
3294
|
-
if (
|
|
3295
|
-
const review =
|
|
3490
|
+
const reviewPath = path20.join(ctx.taskDir, "review.md");
|
|
3491
|
+
if (fs22.existsSync(reviewPath)) {
|
|
3492
|
+
const review = fs22.readFileSync(reviewPath, "utf-8");
|
|
3296
3493
|
const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
3297
3494
|
if (summaryMatch) {
|
|
3298
3495
|
const summary = summaryMatch[1].trim();
|
|
@@ -3309,14 +3506,14 @@ ${summary}`);
|
|
|
3309
3506
|
**Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
|
|
3310
3507
|
}
|
|
3311
3508
|
}
|
|
3312
|
-
const verifyPath =
|
|
3313
|
-
if (
|
|
3314
|
-
const verify =
|
|
3509
|
+
const verifyPath = path20.join(ctx.taskDir, "verify.md");
|
|
3510
|
+
if (fs22.existsSync(verifyPath)) {
|
|
3511
|
+
const verify = fs22.readFileSync(verifyPath, "utf-8");
|
|
3315
3512
|
if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
|
|
3316
3513
|
}
|
|
3317
|
-
const planPath =
|
|
3318
|
-
if (
|
|
3319
|
-
const plan =
|
|
3514
|
+
const planPath = path20.join(ctx.taskDir, "plan.md");
|
|
3515
|
+
if (fs22.existsSync(planPath)) {
|
|
3516
|
+
const plan = fs22.readFileSync(planPath, "utf-8").trim();
|
|
3320
3517
|
if (plan) {
|
|
3321
3518
|
const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
|
|
3322
3519
|
sections.push(`
|
|
@@ -3336,25 +3533,25 @@ Closes #${ctx.input.issueNumber}`);
|
|
|
3336
3533
|
return sections.join("\n");
|
|
3337
3534
|
}
|
|
3338
3535
|
function executeShipStage(ctx, _def) {
|
|
3339
|
-
const shipPath =
|
|
3536
|
+
const shipPath = path20.join(ctx.taskDir, "ship.md");
|
|
3340
3537
|
if (ctx.input.dryRun) {
|
|
3341
|
-
|
|
3538
|
+
fs22.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
|
|
3342
3539
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
3343
3540
|
}
|
|
3344
3541
|
if (ctx.input.local && !ctx.input.issueNumber) {
|
|
3345
|
-
|
|
3542
|
+
fs22.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
|
|
3346
3543
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
3347
3544
|
}
|
|
3348
3545
|
try {
|
|
3349
3546
|
const head = getCurrentBranch(ctx.projectDir);
|
|
3350
3547
|
const base = getDefaultBranch(ctx.projectDir);
|
|
3351
3548
|
try {
|
|
3352
|
-
|
|
3549
|
+
execFileSync14("git", ["add", ctx.taskDir], {
|
|
3353
3550
|
cwd: ctx.projectDir,
|
|
3354
3551
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
3355
3552
|
stdio: "pipe"
|
|
3356
3553
|
});
|
|
3357
|
-
|
|
3554
|
+
execFileSync14("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
|
|
3358
3555
|
cwd: ctx.projectDir,
|
|
3359
3556
|
env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
|
|
3360
3557
|
stdio: "pipe"
|
|
@@ -3368,7 +3565,7 @@ function executeShipStage(ctx, _def) {
|
|
|
3368
3565
|
let repo = config.github?.repo;
|
|
3369
3566
|
if (!owner || !repo) {
|
|
3370
3567
|
try {
|
|
3371
|
-
const remoteUrl =
|
|
3568
|
+
const remoteUrl = execFileSync14("git", ["remote", "get-url", "origin"], {
|
|
3372
3569
|
encoding: "utf-8",
|
|
3373
3570
|
cwd: ctx.projectDir
|
|
3374
3571
|
}).trim();
|
|
@@ -3389,28 +3586,28 @@ function executeShipStage(ctx, _def) {
|
|
|
3389
3586
|
chore: "chore"
|
|
3390
3587
|
};
|
|
3391
3588
|
let prefix = "chore";
|
|
3392
|
-
const taskJsonPath =
|
|
3393
|
-
if (
|
|
3589
|
+
const taskJsonPath = path20.join(ctx.taskDir, "task.json");
|
|
3590
|
+
if (fs22.existsSync(taskJsonPath)) {
|
|
3394
3591
|
try {
|
|
3395
|
-
const raw =
|
|
3592
|
+
const raw = fs22.readFileSync(taskJsonPath, "utf-8");
|
|
3396
3593
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
3397
3594
|
const task = JSON.parse(cleaned);
|
|
3398
3595
|
prefix = TYPE_PREFIX[task.task_type] ?? "chore";
|
|
3399
3596
|
} catch {
|
|
3400
3597
|
}
|
|
3401
3598
|
}
|
|
3402
|
-
const taskMdPath =
|
|
3403
|
-
if (
|
|
3404
|
-
const content =
|
|
3599
|
+
const taskMdPath = path20.join(ctx.taskDir, "task.md");
|
|
3600
|
+
if (fs22.existsSync(taskMdPath)) {
|
|
3601
|
+
const content = fs22.readFileSync(taskMdPath, "utf-8");
|
|
3405
3602
|
const heading = content.split("\n").find((l) => l.startsWith("# "));
|
|
3406
3603
|
if (heading) {
|
|
3407
3604
|
title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
|
|
3408
3605
|
}
|
|
3409
3606
|
}
|
|
3410
3607
|
if (title === "Update") {
|
|
3411
|
-
if (
|
|
3608
|
+
if (fs22.existsSync(taskJsonPath)) {
|
|
3412
3609
|
try {
|
|
3413
|
-
const raw =
|
|
3610
|
+
const raw = fs22.readFileSync(taskJsonPath, "utf-8");
|
|
3414
3611
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
3415
3612
|
const task = JSON.parse(cleaned);
|
|
3416
3613
|
if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
|
|
@@ -3433,7 +3630,7 @@ function executeShipStage(ctx, _def) {
|
|
|
3433
3630
|
} catch {
|
|
3434
3631
|
}
|
|
3435
3632
|
}
|
|
3436
|
-
|
|
3633
|
+
fs22.writeFileSync(shipPath, `# Ship
|
|
3437
3634
|
|
|
3438
3635
|
Updated existing PR: ${existingPr.url}
|
|
3439
3636
|
PR #${existingPr.number}
|
|
@@ -3454,20 +3651,20 @@ PR #${existingPr.number}
|
|
|
3454
3651
|
} catch {
|
|
3455
3652
|
}
|
|
3456
3653
|
}
|
|
3457
|
-
|
|
3654
|
+
fs22.writeFileSync(shipPath, `# Ship
|
|
3458
3655
|
|
|
3459
3656
|
PR created: ${pr.url}
|
|
3460
3657
|
PR #${pr.number}
|
|
3461
3658
|
`);
|
|
3462
3659
|
} else {
|
|
3463
|
-
|
|
3660
|
+
fs22.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
|
|
3464
3661
|
}
|
|
3465
3662
|
}
|
|
3466
3663
|
return { outcome: "completed", outputFile: "ship.md", retries: 0 };
|
|
3467
3664
|
} catch (err) {
|
|
3468
3665
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3469
3666
|
try {
|
|
3470
|
-
|
|
3667
|
+
fs22.writeFileSync(shipPath, `# Ship
|
|
3471
3668
|
|
|
3472
3669
|
Failed: ${msg}
|
|
3473
3670
|
`);
|
|
@@ -3516,15 +3713,15 @@ var init_executor_registry = __esm({
|
|
|
3516
3713
|
});
|
|
3517
3714
|
|
|
3518
3715
|
// src/pipeline/questions.ts
|
|
3519
|
-
import * as
|
|
3520
|
-
import * as
|
|
3716
|
+
import * as fs23 from "fs";
|
|
3717
|
+
import * as path21 from "path";
|
|
3521
3718
|
function checkForQuestions(ctx, stageName) {
|
|
3522
3719
|
if (ctx.input.local || !ctx.input.issueNumber) return false;
|
|
3523
3720
|
try {
|
|
3524
3721
|
if (stageName === "taskify") {
|
|
3525
|
-
const taskJsonPath =
|
|
3526
|
-
if (!
|
|
3527
|
-
const raw =
|
|
3722
|
+
const taskJsonPath = path21.join(ctx.taskDir, "task.json");
|
|
3723
|
+
if (!fs23.existsSync(taskJsonPath)) return false;
|
|
3724
|
+
const raw = fs23.readFileSync(taskJsonPath, "utf-8");
|
|
3528
3725
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
3529
3726
|
const taskJson = JSON.parse(cleaned);
|
|
3530
3727
|
if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
|
|
@@ -3539,9 +3736,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
|
|
|
3539
3736
|
}
|
|
3540
3737
|
}
|
|
3541
3738
|
if (stageName === "plan") {
|
|
3542
|
-
const planPath =
|
|
3543
|
-
if (!
|
|
3544
|
-
const plan =
|
|
3739
|
+
const planPath = path21.join(ctx.taskDir, "plan.md");
|
|
3740
|
+
if (!fs23.existsSync(planPath)) return false;
|
|
3741
|
+
const plan = fs23.readFileSync(planPath, "utf-8");
|
|
3545
3742
|
const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
|
|
3546
3743
|
if (questionsMatch) {
|
|
3547
3744
|
const questionsText = questionsMatch[1].trim();
|
|
@@ -3570,8 +3767,8 @@ var init_questions = __esm({
|
|
|
3570
3767
|
});
|
|
3571
3768
|
|
|
3572
3769
|
// src/pipeline/hooks.ts
|
|
3573
|
-
import * as
|
|
3574
|
-
import * as
|
|
3770
|
+
import * as fs24 from "fs";
|
|
3771
|
+
import * as path22 from "path";
|
|
3575
3772
|
function applyPreStageLabel(ctx, def) {
|
|
3576
3773
|
if (!ctx.input.issueNumber || ctx.input.local) return;
|
|
3577
3774
|
if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
|
|
@@ -3609,9 +3806,9 @@ function autoDetectComplexity(ctx, def) {
|
|
|
3609
3806
|
return { complexity, activeStages };
|
|
3610
3807
|
}
|
|
3611
3808
|
try {
|
|
3612
|
-
const taskJsonPath =
|
|
3613
|
-
if (!
|
|
3614
|
-
const raw =
|
|
3809
|
+
const taskJsonPath = path22.join(ctx.taskDir, "task.json");
|
|
3810
|
+
if (!fs24.existsSync(taskJsonPath)) return null;
|
|
3811
|
+
const raw = fs24.readFileSync(taskJsonPath, "utf-8");
|
|
3615
3812
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
3616
3813
|
const taskJson = JSON.parse(cleaned);
|
|
3617
3814
|
if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
|
|
@@ -3641,8 +3838,8 @@ function checkRiskGate(ctx, def, state, complexity) {
|
|
|
3641
3838
|
if (ctx.input.dryRun || ctx.input.local) return null;
|
|
3642
3839
|
if (ctx.input.mode === "rerun") return null;
|
|
3643
3840
|
if (!ctx.input.issueNumber) return null;
|
|
3644
|
-
const planPath =
|
|
3645
|
-
const plan =
|
|
3841
|
+
const planPath = path22.join(ctx.taskDir, "plan.md");
|
|
3842
|
+
const plan = fs24.existsSync(planPath) ? fs24.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
|
|
3646
3843
|
try {
|
|
3647
3844
|
postComment(
|
|
3648
3845
|
ctx.input.issueNumber,
|
|
@@ -3709,22 +3906,22 @@ var init_hooks = __esm({
|
|
|
3709
3906
|
});
|
|
3710
3907
|
|
|
3711
3908
|
// src/learning/auto-learn.ts
|
|
3712
|
-
import * as
|
|
3713
|
-
import * as
|
|
3909
|
+
import * as fs25 from "fs";
|
|
3910
|
+
import * as path23 from "path";
|
|
3714
3911
|
function stripAnsi(str) {
|
|
3715
3912
|
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
3716
3913
|
}
|
|
3717
3914
|
function autoLearn(ctx) {
|
|
3718
3915
|
try {
|
|
3719
|
-
const memoryDir =
|
|
3720
|
-
if (!
|
|
3721
|
-
|
|
3916
|
+
const memoryDir = path23.join(ctx.projectDir, ".kody", "memory");
|
|
3917
|
+
if (!fs25.existsSync(memoryDir)) {
|
|
3918
|
+
fs25.mkdirSync(memoryDir, { recursive: true });
|
|
3722
3919
|
}
|
|
3723
3920
|
const learnings = [];
|
|
3724
3921
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3725
|
-
const verifyPath =
|
|
3726
|
-
if (
|
|
3727
|
-
const verify = stripAnsi(
|
|
3922
|
+
const verifyPath = path23.join(ctx.taskDir, "verify.md");
|
|
3923
|
+
if (fs25.existsSync(verifyPath)) {
|
|
3924
|
+
const verify = stripAnsi(fs25.readFileSync(verifyPath, "utf-8"));
|
|
3728
3925
|
if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
|
|
3729
3926
|
if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
|
|
3730
3927
|
if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
|
|
@@ -3733,18 +3930,18 @@ function autoLearn(ctx) {
|
|
|
3733
3930
|
if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
|
|
3734
3931
|
if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
|
|
3735
3932
|
}
|
|
3736
|
-
const reviewPath =
|
|
3737
|
-
if (
|
|
3738
|
-
const review =
|
|
3933
|
+
const reviewPath = path23.join(ctx.taskDir, "review.md");
|
|
3934
|
+
if (fs25.existsSync(reviewPath)) {
|
|
3935
|
+
const review = fs25.readFileSync(reviewPath, "utf-8");
|
|
3739
3936
|
if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
|
|
3740
3937
|
if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
|
|
3741
3938
|
if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
|
|
3742
3939
|
if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
|
|
3743
3940
|
}
|
|
3744
|
-
const taskJsonPath =
|
|
3745
|
-
if (
|
|
3941
|
+
const taskJsonPath = path23.join(ctx.taskDir, "task.json");
|
|
3942
|
+
if (fs25.existsSync(taskJsonPath)) {
|
|
3746
3943
|
try {
|
|
3747
|
-
const raw = stripAnsi(
|
|
3944
|
+
const raw = stripAnsi(fs25.readFileSync(taskJsonPath, "utf-8"));
|
|
3748
3945
|
const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
3749
3946
|
const task = JSON.parse(cleaned);
|
|
3750
3947
|
if (task.scope && Array.isArray(task.scope)) {
|
|
@@ -3755,12 +3952,12 @@ function autoLearn(ctx) {
|
|
|
3755
3952
|
}
|
|
3756
3953
|
}
|
|
3757
3954
|
if (learnings.length > 0) {
|
|
3758
|
-
const conventionsPath =
|
|
3955
|
+
const conventionsPath = path23.join(memoryDir, "conventions.md");
|
|
3759
3956
|
const entry = `
|
|
3760
3957
|
## Learned ${timestamp2} (task: ${ctx.taskId})
|
|
3761
3958
|
${learnings.join("\n")}
|
|
3762
3959
|
`;
|
|
3763
|
-
|
|
3960
|
+
fs25.appendFileSync(conventionsPath, entry);
|
|
3764
3961
|
logger.info(`Auto-learned ${learnings.length} convention(s)`);
|
|
3765
3962
|
}
|
|
3766
3963
|
autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
|
|
@@ -3768,8 +3965,8 @@ ${learnings.join("\n")}
|
|
|
3768
3965
|
}
|
|
3769
3966
|
}
|
|
3770
3967
|
function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
3771
|
-
const archPath =
|
|
3772
|
-
if (
|
|
3968
|
+
const archPath = path23.join(memoryDir, "architecture.md");
|
|
3969
|
+
if (fs25.existsSync(archPath)) return;
|
|
3773
3970
|
const detected = detectArchitectureBasic(projectDir);
|
|
3774
3971
|
if (detected.length > 0) {
|
|
3775
3972
|
const content = `# Architecture (auto-detected ${timestamp2})
|
|
@@ -3777,7 +3974,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
|
|
|
3777
3974
|
## Overview
|
|
3778
3975
|
${detected.join("\n")}
|
|
3779
3976
|
`;
|
|
3780
|
-
|
|
3977
|
+
fs25.writeFileSync(archPath, content);
|
|
3781
3978
|
logger.info(`Auto-detected architecture (${detected.length} items)`);
|
|
3782
3979
|
}
|
|
3783
3980
|
}
|
|
@@ -3790,13 +3987,13 @@ var init_auto_learn = __esm({
|
|
|
3790
3987
|
});
|
|
3791
3988
|
|
|
3792
3989
|
// src/retrospective.ts
|
|
3793
|
-
import * as
|
|
3794
|
-
import * as
|
|
3990
|
+
import * as fs26 from "fs";
|
|
3991
|
+
import * as path24 from "path";
|
|
3795
3992
|
function readArtifact(taskDir, filename, maxChars) {
|
|
3796
|
-
const p =
|
|
3797
|
-
if (!
|
|
3993
|
+
const p = path24.join(taskDir, filename);
|
|
3994
|
+
if (!fs26.existsSync(p)) return null;
|
|
3798
3995
|
try {
|
|
3799
|
-
const content =
|
|
3996
|
+
const content = fs26.readFileSync(p, "utf-8");
|
|
3800
3997
|
return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
|
|
3801
3998
|
} catch {
|
|
3802
3999
|
return null;
|
|
@@ -3849,13 +4046,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
|
|
|
3849
4046
|
return lines.join("\n");
|
|
3850
4047
|
}
|
|
3851
4048
|
function getLogPath(projectDir) {
|
|
3852
|
-
return
|
|
4049
|
+
return path24.join(projectDir, ".kody", "memory", "observer-log.jsonl");
|
|
3853
4050
|
}
|
|
3854
4051
|
function readPreviousRetrospectives(projectDir, limit = 10) {
|
|
3855
4052
|
const logPath = getLogPath(projectDir);
|
|
3856
|
-
if (!
|
|
4053
|
+
if (!fs26.existsSync(logPath)) return [];
|
|
3857
4054
|
try {
|
|
3858
|
-
const content =
|
|
4055
|
+
const content = fs26.readFileSync(logPath, "utf-8");
|
|
3859
4056
|
const lines = content.split("\n").filter(Boolean);
|
|
3860
4057
|
const entries = [];
|
|
3861
4058
|
const start = Math.max(0, lines.length - limit);
|
|
@@ -3882,11 +4079,11 @@ function formatPreviousEntries(entries) {
|
|
|
3882
4079
|
}
|
|
3883
4080
|
function appendRetrospectiveEntry(projectDir, entry) {
|
|
3884
4081
|
const logPath = getLogPath(projectDir);
|
|
3885
|
-
const dir =
|
|
3886
|
-
if (!
|
|
3887
|
-
|
|
4082
|
+
const dir = path24.dirname(logPath);
|
|
4083
|
+
if (!fs26.existsSync(dir)) {
|
|
4084
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
3888
4085
|
}
|
|
3889
|
-
|
|
4086
|
+
fs26.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
3890
4087
|
}
|
|
3891
4088
|
async function runRetrospective(ctx, state, pipelineStartTime) {
|
|
3892
4089
|
if (ctx.input.dryRun) return;
|
|
@@ -4054,8 +4251,8 @@ var init_summary = __esm({
|
|
|
4054
4251
|
});
|
|
4055
4252
|
|
|
4056
4253
|
// src/pipeline.ts
|
|
4057
|
-
import * as
|
|
4058
|
-
import * as
|
|
4254
|
+
import * as fs27 from "fs";
|
|
4255
|
+
import * as path25 from "path";
|
|
4059
4256
|
function ensureFeatureBranchIfNeeded(ctx) {
|
|
4060
4257
|
if (ctx.input.dryRun) return;
|
|
4061
4258
|
if (ctx.input.prNumber) {
|
|
@@ -4068,8 +4265,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
4068
4265
|
}
|
|
4069
4266
|
if (!ctx.input.issueNumber) return;
|
|
4070
4267
|
try {
|
|
4071
|
-
const taskMdPath =
|
|
4072
|
-
const title =
|
|
4268
|
+
const taskMdPath = path25.join(ctx.taskDir, "task.md");
|
|
4269
|
+
const title = fs27.existsSync(taskMdPath) ? fs27.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
|
|
4073
4270
|
ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
|
|
4074
4271
|
syncWithDefault(ctx.projectDir);
|
|
4075
4272
|
} catch (err) {
|
|
@@ -4083,10 +4280,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
|
|
|
4083
4280
|
}
|
|
4084
4281
|
}
|
|
4085
4282
|
function acquireLock(taskDir) {
|
|
4086
|
-
const lockPath =
|
|
4087
|
-
if (
|
|
4283
|
+
const lockPath = path25.join(taskDir, ".lock");
|
|
4284
|
+
if (fs27.existsSync(lockPath)) {
|
|
4088
4285
|
try {
|
|
4089
|
-
const pid = parseInt(
|
|
4286
|
+
const pid = parseInt(fs27.readFileSync(lockPath, "utf-8").trim(), 10);
|
|
4090
4287
|
if (!isNaN(pid)) {
|
|
4091
4288
|
try {
|
|
4092
4289
|
process.kill(pid, 0);
|
|
@@ -4103,14 +4300,14 @@ function acquireLock(taskDir) {
|
|
|
4103
4300
|
logger.warn(` Corrupt lock file \u2014 overwriting`);
|
|
4104
4301
|
}
|
|
4105
4302
|
try {
|
|
4106
|
-
|
|
4303
|
+
fs27.unlinkSync(lockPath);
|
|
4107
4304
|
} catch {
|
|
4108
4305
|
}
|
|
4109
4306
|
}
|
|
4110
4307
|
try {
|
|
4111
|
-
const fd =
|
|
4112
|
-
|
|
4113
|
-
|
|
4308
|
+
const fd = fs27.openSync(lockPath, fs27.constants.O_WRONLY | fs27.constants.O_CREAT | fs27.constants.O_EXCL);
|
|
4309
|
+
fs27.writeSync(fd, String(process.pid));
|
|
4310
|
+
fs27.closeSync(fd);
|
|
4114
4311
|
} catch (err) {
|
|
4115
4312
|
if (err.code === "EEXIST") {
|
|
4116
4313
|
throw new Error("Pipeline already running (lock acquired by another process)");
|
|
@@ -4120,7 +4317,7 @@ function acquireLock(taskDir) {
|
|
|
4120
4317
|
}
|
|
4121
4318
|
function releaseLock(taskDir) {
|
|
4122
4319
|
try {
|
|
4123
|
-
|
|
4320
|
+
fs27.unlinkSync(path25.join(taskDir, ".lock"));
|
|
4124
4321
|
} catch {
|
|
4125
4322
|
}
|
|
4126
4323
|
}
|
|
@@ -4328,8 +4525,8 @@ var init_pipeline = __esm({
|
|
|
4328
4525
|
});
|
|
4329
4526
|
|
|
4330
4527
|
// src/preflight.ts
|
|
4331
|
-
import { execFileSync as
|
|
4332
|
-
import * as
|
|
4528
|
+
import { execFileSync as execFileSync15 } from "child_process";
|
|
4529
|
+
import * as fs28 from "fs";
|
|
4333
4530
|
function check(name, fn) {
|
|
4334
4531
|
try {
|
|
4335
4532
|
const detail = fn() ?? void 0;
|
|
@@ -4341,7 +4538,7 @@ function check(name, fn) {
|
|
|
4341
4538
|
function runPreflight() {
|
|
4342
4539
|
const checks = [
|
|
4343
4540
|
check("claude CLI", () => {
|
|
4344
|
-
const v =
|
|
4541
|
+
const v = execFileSync15("claude", ["--version"], {
|
|
4345
4542
|
encoding: "utf-8",
|
|
4346
4543
|
timeout: 1e4,
|
|
4347
4544
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4349,14 +4546,14 @@ function runPreflight() {
|
|
|
4349
4546
|
return v;
|
|
4350
4547
|
}),
|
|
4351
4548
|
check("git repo", () => {
|
|
4352
|
-
|
|
4549
|
+
execFileSync15("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
4353
4550
|
encoding: "utf-8",
|
|
4354
4551
|
timeout: 5e3,
|
|
4355
4552
|
stdio: ["pipe", "pipe", "pipe"]
|
|
4356
4553
|
});
|
|
4357
4554
|
}),
|
|
4358
4555
|
check("pnpm", () => {
|
|
4359
|
-
const v =
|
|
4556
|
+
const v = execFileSync15("pnpm", ["--version"], {
|
|
4360
4557
|
encoding: "utf-8",
|
|
4361
4558
|
timeout: 5e3,
|
|
4362
4559
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4364,7 +4561,7 @@ function runPreflight() {
|
|
|
4364
4561
|
return v;
|
|
4365
4562
|
}),
|
|
4366
4563
|
check("node >= 18", () => {
|
|
4367
|
-
const v =
|
|
4564
|
+
const v = execFileSync15("node", ["--version"], {
|
|
4368
4565
|
encoding: "utf-8",
|
|
4369
4566
|
timeout: 5e3,
|
|
4370
4567
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4374,7 +4571,7 @@ function runPreflight() {
|
|
|
4374
4571
|
return v;
|
|
4375
4572
|
}),
|
|
4376
4573
|
check("gh CLI", () => {
|
|
4377
|
-
const v =
|
|
4574
|
+
const v = execFileSync15("gh", ["--version"], {
|
|
4378
4575
|
encoding: "utf-8",
|
|
4379
4576
|
timeout: 5e3,
|
|
4380
4577
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -4382,7 +4579,7 @@ function runPreflight() {
|
|
|
4382
4579
|
return v;
|
|
4383
4580
|
}),
|
|
4384
4581
|
check("package.json", () => {
|
|
4385
|
-
if (!
|
|
4582
|
+
if (!fs28.existsSync("package.json")) throw new Error("not found");
|
|
4386
4583
|
})
|
|
4387
4584
|
];
|
|
4388
4585
|
const failed = checks.filter((c) => !c.ok);
|
|
@@ -4458,180 +4655,6 @@ var init_args = __esm({
|
|
|
4458
4655
|
}
|
|
4459
4656
|
});
|
|
4460
4657
|
|
|
4461
|
-
// src/cli/litellm.ts
|
|
4462
|
-
import * as fs28 from "fs";
|
|
4463
|
-
import * as os from "os";
|
|
4464
|
-
import * as path25 from "path";
|
|
4465
|
-
import { execFileSync as execFileSync15 } from "child_process";
|
|
4466
|
-
async function checkLitellmHealth(url) {
|
|
4467
|
-
try {
|
|
4468
|
-
const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
4469
|
-
return response.ok;
|
|
4470
|
-
} catch {
|
|
4471
|
-
return false;
|
|
4472
|
-
}
|
|
4473
|
-
}
|
|
4474
|
-
async function checkModelHealth(baseUrl, apiKey, model) {
|
|
4475
|
-
try {
|
|
4476
|
-
const res = await fetch(`${baseUrl}/v1/messages`, {
|
|
4477
|
-
method: "POST",
|
|
4478
|
-
headers: {
|
|
4479
|
-
"Content-Type": "application/json",
|
|
4480
|
-
"x-api-key": apiKey,
|
|
4481
|
-
"anthropic-version": "2023-06-01"
|
|
4482
|
-
},
|
|
4483
|
-
body: JSON.stringify({
|
|
4484
|
-
model,
|
|
4485
|
-
max_tokens: 4,
|
|
4486
|
-
messages: [{ role: "user", content: "Reply with: ok" }]
|
|
4487
|
-
}),
|
|
4488
|
-
signal: AbortSignal.timeout(3e4)
|
|
4489
|
-
});
|
|
4490
|
-
if (!res.ok) {
|
|
4491
|
-
const body2 = await res.text().catch(() => "");
|
|
4492
|
-
return { ok: false, error: `HTTP ${res.status}: ${body2.slice(0, 200)}` };
|
|
4493
|
-
}
|
|
4494
|
-
const body = await res.json();
|
|
4495
|
-
const hasAnthropicContent = Array.isArray(body.content) && body.content.some((b) => b.type === "text");
|
|
4496
|
-
const hasThinkingContent = Array.isArray(body.content) && body.content.some((b) => b.type === "thinking");
|
|
4497
|
-
const hasOpenAIContent = !!body.choices?.[0]?.message?.content;
|
|
4498
|
-
if (!hasAnthropicContent && !hasThinkingContent && !hasOpenAIContent) {
|
|
4499
|
-
return { ok: false, error: `Unexpected response format: ${JSON.stringify(body).slice(0, 200)}` };
|
|
4500
|
-
}
|
|
4501
|
-
return { ok: true };
|
|
4502
|
-
} catch (err) {
|
|
4503
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
function generateLitellmConfig(provider, modelMap) {
|
|
4507
|
-
const apiKeyVar = providerApiKeyEnvVar(provider);
|
|
4508
|
-
const entries = ["model_list:"];
|
|
4509
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4510
|
-
for (const providerModel of Object.values(modelMap)) {
|
|
4511
|
-
if (seen.has(providerModel)) continue;
|
|
4512
|
-
seen.add(providerModel);
|
|
4513
|
-
entries.push(` - model_name: ${providerModel}`);
|
|
4514
|
-
entries.push(` litellm_params:`);
|
|
4515
|
-
entries.push(` model: ${provider}/${providerModel}`);
|
|
4516
|
-
entries.push(` api_key: os.environ/${apiKeyVar}`);
|
|
4517
|
-
}
|
|
4518
|
-
return entries.join("\n") + "\n";
|
|
4519
|
-
}
|
|
4520
|
-
function generateLitellmConfigFromStages(defaultConfig, stages) {
|
|
4521
|
-
const proxyModels = [];
|
|
4522
|
-
if (defaultConfig && defaultConfig.provider !== "claude" && defaultConfig.provider !== "anthropic") {
|
|
4523
|
-
proxyModels.push(defaultConfig);
|
|
4524
|
-
}
|
|
4525
|
-
if (stages) {
|
|
4526
|
-
for (const sc of Object.values(stages)) {
|
|
4527
|
-
if (sc.provider !== "claude" && sc.provider !== "anthropic") {
|
|
4528
|
-
proxyModels.push(sc);
|
|
4529
|
-
}
|
|
4530
|
-
}
|
|
4531
|
-
}
|
|
4532
|
-
if (proxyModels.length === 0) return void 0;
|
|
4533
|
-
const entries = ["model_list:"];
|
|
4534
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4535
|
-
for (const { provider, model } of proxyModels) {
|
|
4536
|
-
const key = `${provider}/${model}`;
|
|
4537
|
-
if (seen.has(key)) continue;
|
|
4538
|
-
seen.add(key);
|
|
4539
|
-
const apiKeyVar = providerApiKeyEnvVar(provider);
|
|
4540
|
-
entries.push(` - model_name: ${model}`);
|
|
4541
|
-
entries.push(` litellm_params:`);
|
|
4542
|
-
entries.push(` model: ${provider}/${model}`);
|
|
4543
|
-
entries.push(` api_key: os.environ/${apiKeyVar}`);
|
|
4544
|
-
}
|
|
4545
|
-
return entries.join("\n") + "\n";
|
|
4546
|
-
}
|
|
4547
|
-
async function tryStartLitellm(url, projectDir, generatedConfig) {
|
|
4548
|
-
if (!generatedConfig) {
|
|
4549
|
-
logger.warn("No provider configured in kody.config.json \u2014 cannot start LiteLLM proxy");
|
|
4550
|
-
return null;
|
|
4551
|
-
}
|
|
4552
|
-
const configPath = path25.join(os.tmpdir(), "kody-litellm-config.yaml");
|
|
4553
|
-
fs28.writeFileSync(configPath, generatedConfig);
|
|
4554
|
-
const portMatch = url.match(/:(\d+)/);
|
|
4555
|
-
const port = portMatch ? portMatch[1] : "4000";
|
|
4556
|
-
let litellmFound = false;
|
|
4557
|
-
try {
|
|
4558
|
-
execFileSync15("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
4559
|
-
litellmFound = true;
|
|
4560
|
-
} catch {
|
|
4561
|
-
try {
|
|
4562
|
-
execFileSync15("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
|
|
4563
|
-
litellmFound = true;
|
|
4564
|
-
} catch {
|
|
4565
|
-
}
|
|
4566
|
-
}
|
|
4567
|
-
if (!litellmFound) {
|
|
4568
|
-
logger.warn("litellm not installed (pip install 'litellm[proxy]')");
|
|
4569
|
-
return null;
|
|
4570
|
-
}
|
|
4571
|
-
logger.info(`Starting LiteLLM proxy on port ${port}...`);
|
|
4572
|
-
let cmd;
|
|
4573
|
-
let args2;
|
|
4574
|
-
try {
|
|
4575
|
-
execFileSync15("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
|
|
4576
|
-
cmd = "litellm";
|
|
4577
|
-
args2 = ["--config", configPath, "--port", port];
|
|
4578
|
-
} catch {
|
|
4579
|
-
cmd = "python3";
|
|
4580
|
-
args2 = ["-m", "litellm", "--config", configPath, "--port", port];
|
|
4581
|
-
}
|
|
4582
|
-
const dotenvPath = path25.join(projectDir, ".env");
|
|
4583
|
-
const dotenvVars = {};
|
|
4584
|
-
if (fs28.existsSync(dotenvPath)) {
|
|
4585
|
-
for (const rawLine of fs28.readFileSync(dotenvPath, "utf-8").split("\n")) {
|
|
4586
|
-
const line = rawLine.trim();
|
|
4587
|
-
if (!line || line.startsWith("#")) continue;
|
|
4588
|
-
const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
|
|
4589
|
-
if (match) {
|
|
4590
|
-
let value = match[2].trim();
|
|
4591
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
4592
|
-
value = value.slice(1, -1);
|
|
4593
|
-
}
|
|
4594
|
-
const commentIdx = value.indexOf(" #");
|
|
4595
|
-
if (commentIdx !== -1) value = value.slice(0, commentIdx).trim();
|
|
4596
|
-
if (value) dotenvVars[match[1]] = value;
|
|
4597
|
-
}
|
|
4598
|
-
}
|
|
4599
|
-
if (Object.keys(dotenvVars).length > 0) {
|
|
4600
|
-
logger.info(` Loaded API keys: ${Object.keys(dotenvVars).join(", ")}`);
|
|
4601
|
-
}
|
|
4602
|
-
}
|
|
4603
|
-
const { spawn: spawn2 } = await import("child_process");
|
|
4604
|
-
const child = spawn2(cmd, args2, {
|
|
4605
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
4606
|
-
detached: true,
|
|
4607
|
-
env: { ...process.env, ...dotenvVars }
|
|
4608
|
-
});
|
|
4609
|
-
let proxyStderr = "";
|
|
4610
|
-
child.stderr?.on("data", (chunk) => {
|
|
4611
|
-
proxyStderr += chunk.toString();
|
|
4612
|
-
});
|
|
4613
|
-
for (let i = 0; i < 30; i++) {
|
|
4614
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
4615
|
-
if (await checkLitellmHealth(url)) {
|
|
4616
|
-
logger.info(`LiteLLM proxy ready at ${url}`);
|
|
4617
|
-
return child;
|
|
4618
|
-
}
|
|
4619
|
-
}
|
|
4620
|
-
if (proxyStderr) {
|
|
4621
|
-
logger.warn(`LiteLLM stderr: ${proxyStderr.slice(-1e3)}`);
|
|
4622
|
-
}
|
|
4623
|
-
logger.warn("LiteLLM proxy failed to start within 60s");
|
|
4624
|
-
child.kill();
|
|
4625
|
-
return null;
|
|
4626
|
-
}
|
|
4627
|
-
var init_litellm = __esm({
|
|
4628
|
-
"src/cli/litellm.ts"() {
|
|
4629
|
-
"use strict";
|
|
4630
|
-
init_logger();
|
|
4631
|
-
init_config();
|
|
4632
|
-
}
|
|
4633
|
-
});
|
|
4634
|
-
|
|
4635
4658
|
// src/cli/task-state.ts
|
|
4636
4659
|
import * as fs29 from "fs";
|
|
4637
4660
|
import * as path26 from "path";
|