@kody-ade/kody-engine-lite 0.1.111 → 0.1.113
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/agent-runner.d.ts +4 -0
- package/dist/agent-runner.js +122 -0
- package/dist/bin/cli.js +106 -88
- package/dist/ci/parse-inputs.d.ts +6 -0
- package/dist/ci/parse-inputs.js +76 -0
- package/dist/ci/parse-safety.d.ts +6 -0
- package/dist/ci/parse-safety.js +22 -0
- package/dist/cli/args.d.ts +13 -0
- package/dist/cli/args.js +42 -0
- package/dist/cli/litellm.d.ts +2 -0
- package/dist/cli/litellm.js +85 -0
- package/dist/cli/task-resolution.d.ts +2 -0
- package/dist/cli/task-resolution.js +41 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.js +72 -0
- package/dist/context.d.ts +4 -0
- package/dist/context.js +83 -0
- package/dist/definitions.d.ts +3 -0
- package/dist/definitions.js +59 -0
- package/dist/entry.d.ts +1 -0
- package/dist/entry.js +236 -0
- package/dist/git-utils.d.ts +13 -0
- package/dist/git-utils.js +174 -0
- package/dist/github-api.d.ts +14 -0
- package/dist/github-api.js +114 -0
- package/dist/kody-utils.d.ts +1 -0
- package/dist/kody-utils.js +9 -0
- package/dist/learning/auto-learn.d.ts +2 -0
- package/dist/learning/auto-learn.js +169 -0
- package/dist/logger.d.ts +14 -0
- package/dist/logger.js +51 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.js +20 -0
- package/dist/observer.d.ts +9 -0
- package/dist/observer.js +80 -0
- package/dist/pipeline/complexity.d.ts +3 -0
- package/dist/pipeline/complexity.js +12 -0
- package/dist/pipeline/executor-registry.d.ts +3 -0
- package/dist/pipeline/executor-registry.js +20 -0
- package/dist/pipeline/hooks.d.ts +17 -0
- package/dist/pipeline/hooks.js +110 -0
- package/dist/pipeline/questions.d.ts +2 -0
- package/dist/pipeline/questions.js +44 -0
- package/dist/pipeline/runner-selection.d.ts +2 -0
- package/dist/pipeline/runner-selection.js +13 -0
- package/dist/pipeline/state.d.ts +4 -0
- package/dist/pipeline/state.js +37 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.js +213 -0
- package/dist/preflight.d.ts +1 -0
- package/dist/preflight.js +69 -0
- package/dist/retrospective.d.ts +26 -0
- package/dist/retrospective.js +211 -0
- package/dist/stages/agent.d.ts +2 -0
- package/dist/stages/agent.js +94 -0
- package/dist/stages/gate.d.ts +2 -0
- package/dist/stages/gate.js +32 -0
- package/dist/stages/review.d.ts +2 -0
- package/dist/stages/review.js +32 -0
- package/dist/stages/ship.d.ts +3 -0
- package/dist/stages/ship.js +154 -0
- package/dist/stages/verify.d.ts +2 -0
- package/dist/stages/verify.js +94 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +1 -0
- package/dist/validators.d.ts +8 -0
- package/dist/validators.js +42 -0
- package/dist/verify-runner.d.ts +11 -0
- package/dist/verify-runner.js +110 -0
- package/kody.config.schema.json +31 -0
- package/package.json +1 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { spawn, execFileSync } from "child_process";
|
|
2
|
+
const SIGKILL_GRACE_MS = 5000;
|
|
3
|
+
const STDERR_TAIL_CHARS = 500;
|
|
4
|
+
function writeStdin(child, prompt) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
if (!child.stdin) {
|
|
7
|
+
resolve();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
child.stdin.write(prompt, (err) => {
|
|
11
|
+
if (err)
|
|
12
|
+
reject(err);
|
|
13
|
+
else {
|
|
14
|
+
child.stdin.end();
|
|
15
|
+
resolve();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function waitForProcess(child, timeout) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const stdoutChunks = [];
|
|
23
|
+
const stderrChunks = [];
|
|
24
|
+
child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
25
|
+
child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
|
|
26
|
+
const timer = setTimeout(() => {
|
|
27
|
+
child.kill("SIGTERM");
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
if (!child.killed)
|
|
30
|
+
child.kill("SIGKILL");
|
|
31
|
+
}, SIGKILL_GRACE_MS);
|
|
32
|
+
}, timeout);
|
|
33
|
+
child.on("exit", (code) => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
resolve({
|
|
36
|
+
code,
|
|
37
|
+
stdout: Buffer.concat(stdoutChunks).toString(),
|
|
38
|
+
stderr: Buffer.concat(stderrChunks).toString(),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
child.on("error", (err) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
resolve({ code: -1, stdout: "", stderr: err.message });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function runSubprocess(command, args, prompt, timeout, options) {
|
|
48
|
+
const child = spawn(command, args, {
|
|
49
|
+
cwd: options?.cwd ?? process.cwd(),
|
|
50
|
+
env: {
|
|
51
|
+
...process.env,
|
|
52
|
+
SKIP_BUILD: "1",
|
|
53
|
+
SKIP_HOOKS: "1",
|
|
54
|
+
...options?.env,
|
|
55
|
+
},
|
|
56
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
await writeStdin(child, prompt);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
return {
|
|
63
|
+
outcome: "failed",
|
|
64
|
+
error: `Failed to send prompt: ${err instanceof Error ? err.message : String(err)}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const { code, stdout, stderr } = await waitForProcess(child, timeout);
|
|
68
|
+
if (code === 0) {
|
|
69
|
+
return { outcome: "completed", output: stdout };
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
outcome: code === null ? "timed_out" : "failed",
|
|
73
|
+
error: `Exit code ${code}\n${stderr.slice(-STDERR_TAIL_CHARS)}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function checkCommand(command, args) {
|
|
77
|
+
try {
|
|
78
|
+
execFileSync(command, args, { timeout: 10_000, stdio: "pipe" });
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ─── Claude Code Runner ──────────────────────────────────────────────────────
|
|
86
|
+
export function createClaudeCodeRunner() {
|
|
87
|
+
return {
|
|
88
|
+
async run(_stageName, prompt, model, timeout, _taskDir, options) {
|
|
89
|
+
return runSubprocess("claude", [
|
|
90
|
+
"--print",
|
|
91
|
+
"--model", model,
|
|
92
|
+
"--dangerously-skip-permissions",
|
|
93
|
+
"--allowedTools", "Bash,Edit,Read,Write,Glob,Grep",
|
|
94
|
+
], prompt, timeout, options);
|
|
95
|
+
},
|
|
96
|
+
async healthCheck() {
|
|
97
|
+
return checkCommand("claude", ["--version"]);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// ─── Runner Factory ──────────────────────────────────────────────────────────
|
|
102
|
+
const RUNNER_FACTORIES = {
|
|
103
|
+
"claude-code": createClaudeCodeRunner,
|
|
104
|
+
};
|
|
105
|
+
export function createRunners(config) {
|
|
106
|
+
// New multi-runner config
|
|
107
|
+
if (config.agent.runners && Object.keys(config.agent.runners).length > 0) {
|
|
108
|
+
const runners = {};
|
|
109
|
+
for (const [name, runnerConfig] of Object.entries(config.agent.runners)) {
|
|
110
|
+
const factory = RUNNER_FACTORIES[runnerConfig.type];
|
|
111
|
+
if (factory) {
|
|
112
|
+
runners[name] = factory();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return runners;
|
|
116
|
+
}
|
|
117
|
+
// Legacy single-runner fallback
|
|
118
|
+
const runnerType = config.agent.runner ?? "claude-code";
|
|
119
|
+
const factory = RUNNER_FACTORIES[runnerType];
|
|
120
|
+
const defaultName = config.agent.defaultRunner ?? "claude";
|
|
121
|
+
return { [defaultName]: factory ? factory() : createClaudeCodeRunner() };
|
|
122
|
+
}
|
package/dist/bin/cli.js
CHANGED
|
@@ -180,8 +180,8 @@ var init_validators = __esm({
|
|
|
180
180
|
});
|
|
181
181
|
|
|
182
182
|
// src/config.ts
|
|
183
|
-
import * as
|
|
184
|
-
import * as
|
|
183
|
+
import * as fs7 from "fs";
|
|
184
|
+
import * as path6 from "path";
|
|
185
185
|
function resolveStageConfig(config, stageName, modelTier) {
|
|
186
186
|
const stageOverride = config.agent.stages?.[stageName];
|
|
187
187
|
if (stageOverride) return stageOverride;
|
|
@@ -223,10 +223,10 @@ function setConfigDir(dir) {
|
|
|
223
223
|
}
|
|
224
224
|
function getProjectConfig() {
|
|
225
225
|
if (_config) return _config;
|
|
226
|
-
const configPath =
|
|
227
|
-
if (
|
|
226
|
+
const configPath = path6.join(_configDir ?? process.cwd(), "kody.config.json");
|
|
227
|
+
if (fs7.existsSync(configPath)) {
|
|
228
228
|
try {
|
|
229
|
-
const result = parseJsonSafe(
|
|
229
|
+
const result = parseJsonSafe(fs7.readFileSync(configPath, "utf-8"));
|
|
230
230
|
if (!result.ok) {
|
|
231
231
|
logger.warn(`kody.config.json: ${result.error} \u2014 using defaults`);
|
|
232
232
|
_config = { ...DEFAULT_CONFIG };
|
|
@@ -283,7 +283,7 @@ var init_config = __esm({
|
|
|
283
283
|
repo: ""
|
|
284
284
|
},
|
|
285
285
|
agent: {
|
|
286
|
-
modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
|
|
286
|
+
modelMap: { cheap: "claude-haiku-4-5-20251001", mid: "claude-sonnet-4-6", strong: "claude-opus-4-6" }
|
|
287
287
|
},
|
|
288
288
|
contextTiers: {
|
|
289
289
|
enabled: true,
|
|
@@ -389,10 +389,11 @@ function createClaudeCodeRunner() {
|
|
|
389
389
|
model,
|
|
390
390
|
"--dangerously-skip-permissions"
|
|
391
391
|
];
|
|
392
|
+
const baseTools = "Bash,Edit,Read,Write,Glob,Grep";
|
|
392
393
|
if (options?.mcpConfigJson) {
|
|
393
394
|
args2.push("--mcp-config", options.mcpConfigJson);
|
|
394
395
|
} else {
|
|
395
|
-
args2.push("--allowedTools",
|
|
396
|
+
args2.push("--allowedTools", baseTools);
|
|
396
397
|
}
|
|
397
398
|
if (options?.sessionId) {
|
|
398
399
|
if (options.resumeSession) {
|
|
@@ -1192,6 +1193,7 @@ var init_litellm = __esm({
|
|
|
1192
1193
|
// src/cli/taskify-command.ts
|
|
1193
1194
|
var taskify_command_exports = {};
|
|
1194
1195
|
__export(taskify_command_exports, {
|
|
1196
|
+
TaskifyError: () => TaskifyError,
|
|
1195
1197
|
isTaskifyRun: () => isTaskifyRun,
|
|
1196
1198
|
readTaskifyMarker: () => readTaskifyMarker,
|
|
1197
1199
|
runTaskifyCommand: () => runTaskifyCommand,
|
|
@@ -1283,8 +1285,17 @@ async function runTaskifyCommand() {
|
|
|
1283
1285
|
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "dummy"
|
|
1284
1286
|
};
|
|
1285
1287
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
+
try {
|
|
1289
|
+
await taskifyCommand({ ticketId, prdFile, issueNumber, feedback, local, projectDir, taskId, runnerEnv });
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
if (err instanceof TaskifyError) {
|
|
1292
|
+
logger.error(`[taskify] ${err.message}`);
|
|
1293
|
+
process.exit(1);
|
|
1294
|
+
}
|
|
1295
|
+
throw err;
|
|
1296
|
+
} finally {
|
|
1297
|
+
litellmProcess?.kill();
|
|
1298
|
+
}
|
|
1288
1299
|
}
|
|
1289
1300
|
async function taskifyCommand(opts) {
|
|
1290
1301
|
const { ticketId, prdFile, issueNumber, feedback, local, projectDir, taskId } = opts;
|
|
@@ -1299,7 +1310,6 @@ async function taskifyCommand(opts) {
|
|
|
1299
1310
|
mcpConfigJson = buildTaskifyMcpConfigJson(config);
|
|
1300
1311
|
} catch (err) {
|
|
1301
1312
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1302
|
-
logger.error(`[taskify] MCP config error: ${msg}`);
|
|
1303
1313
|
if (issueNumber && !local) {
|
|
1304
1314
|
postComment(
|
|
1305
1315
|
issueNumber,
|
|
@@ -1310,7 +1320,7 @@ async function taskifyCommand(opts) {
|
|
|
1310
1320
|
Add the required MCP server config to \`kody.config.json\` and try again.`
|
|
1311
1321
|
);
|
|
1312
1322
|
}
|
|
1313
|
-
|
|
1323
|
+
throw new TaskifyError(`MCP config error: ${msg}`);
|
|
1314
1324
|
}
|
|
1315
1325
|
}
|
|
1316
1326
|
const sc = resolveStageConfig(config, "taskify", "strong");
|
|
@@ -1358,40 +1368,37 @@ Kody is decomposing ${src} into tasks...`);
|
|
|
1358
1368
|
});
|
|
1359
1369
|
if (result.outcome !== "completed") {
|
|
1360
1370
|
const errMsg = result.outcome === "timed_out" ? "Taskify timed out after 5 minutes." : `Taskify failed: ${result.error}`;
|
|
1361
|
-
logger.error(`[taskify] ${errMsg}`);
|
|
1362
1371
|
if (issueNumber && !local) {
|
|
1363
1372
|
postComment(issueNumber, `Kody taskify failed:
|
|
1364
1373
|
|
|
1365
1374
|
> ${errMsg}`);
|
|
1366
1375
|
setLifecycleLabel(issueNumber, "failed");
|
|
1367
1376
|
}
|
|
1368
|
-
|
|
1377
|
+
throw new TaskifyError(errMsg);
|
|
1369
1378
|
}
|
|
1370
1379
|
const resultPath = path10.join(taskDir, RESULT_FILE);
|
|
1371
1380
|
if (!fs11.existsSync(resultPath)) {
|
|
1372
1381
|
const errMsg = `Claude did not write ${RESULT_FILE}. Output:
|
|
1373
1382
|
|
|
1374
1383
|
${result.output?.slice(0, 500) ?? "(none)"}`;
|
|
1375
|
-
logger.error(`[taskify] ${errMsg}`);
|
|
1376
1384
|
if (issueNumber && !local) {
|
|
1377
1385
|
postComment(issueNumber, `Kody taskify failed: result file not found.
|
|
1378
1386
|
|
|
1379
1387
|
${errMsg}`);
|
|
1380
1388
|
setLifecycleLabel(issueNumber, "failed");
|
|
1381
1389
|
}
|
|
1382
|
-
|
|
1390
|
+
throw new TaskifyError(errMsg);
|
|
1383
1391
|
}
|
|
1384
1392
|
let parsed;
|
|
1385
1393
|
try {
|
|
1386
1394
|
parsed = JSON.parse(fs11.readFileSync(resultPath, "utf-8"));
|
|
1387
1395
|
} catch {
|
|
1388
1396
|
const errMsg = `Could not parse ${RESULT_FILE} as JSON.`;
|
|
1389
|
-
logger.error(`[taskify] ${errMsg}`);
|
|
1390
1397
|
if (issueNumber && !local) {
|
|
1391
1398
|
postComment(issueNumber, `Kody taskify failed: ${errMsg}`);
|
|
1392
1399
|
setLifecycleLabel(issueNumber, "failed");
|
|
1393
1400
|
}
|
|
1394
|
-
|
|
1401
|
+
throw new TaskifyError(errMsg);
|
|
1395
1402
|
}
|
|
1396
1403
|
const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : "spec");
|
|
1397
1404
|
if (parsed.status === "questions") {
|
|
@@ -1400,12 +1407,11 @@ ${errMsg}`);
|
|
|
1400
1407
|
await handleTasks(parsed, sourceLabel, issueNumber, local ?? false);
|
|
1401
1408
|
} else {
|
|
1402
1409
|
const errMsg = `Unexpected status in ${RESULT_FILE}: ${JSON.stringify(parsed)}`;
|
|
1403
|
-
logger.error(`[taskify] ${errMsg}`);
|
|
1404
1410
|
if (issueNumber && !local) {
|
|
1405
1411
|
postComment(issueNumber, `Kody taskify failed: ${errMsg}`);
|
|
1406
1412
|
setLifecycleLabel(issueNumber, "failed");
|
|
1407
1413
|
}
|
|
1408
|
-
|
|
1414
|
+
throw new TaskifyError(errMsg);
|
|
1409
1415
|
}
|
|
1410
1416
|
}
|
|
1411
1417
|
function handleQuestions(parsed, ticketId, issueNumber, local) {
|
|
@@ -1447,8 +1453,14 @@ async function handleTasks(parsed, ticketId, issueNumber, local) {
|
|
|
1447
1453
|
filed.push({ number: 0, url: "#", title: task.title });
|
|
1448
1454
|
continue;
|
|
1449
1455
|
}
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1456
|
+
const safeLabels = [
|
|
1457
|
+
...(task.labels ?? []).filter((l) => l.startsWith("priority:") || l.startsWith("kody:")),
|
|
1458
|
+
...task.priority ? [`priority:${task.priority}`] : []
|
|
1459
|
+
];
|
|
1460
|
+
let issue = createIssue(task.title, task.body, safeLabels);
|
|
1461
|
+
if (!issue && safeLabels.length > 0) {
|
|
1462
|
+
issue = createIssue(task.title, task.body, []);
|
|
1463
|
+
}
|
|
1452
1464
|
if (issue) {
|
|
1453
1465
|
filed.push({ number: issue.number, url: issue.url, title: task.title });
|
|
1454
1466
|
} else {
|
|
@@ -1530,7 +1542,7 @@ function readTaskifyMarker(taskDir) {
|
|
|
1530
1542
|
return null;
|
|
1531
1543
|
}
|
|
1532
1544
|
}
|
|
1533
|
-
var __dirname, AUTO_TRIGGER_THRESHOLD, MAX_TASKS_GUARD, TASKIFY_TIMEOUT_MS, MARKER_FILE, RESULT_FILE;
|
|
1545
|
+
var __dirname, TaskifyError, AUTO_TRIGGER_THRESHOLD, MAX_TASKS_GUARD, TASKIFY_TIMEOUT_MS, MARKER_FILE, RESULT_FILE;
|
|
1534
1546
|
var init_taskify_command = __esm({
|
|
1535
1547
|
"src/cli/taskify-command.ts"() {
|
|
1536
1548
|
"use strict";
|
|
@@ -1542,6 +1554,12 @@ var init_taskify_command = __esm({
|
|
|
1542
1554
|
init_task_resolution();
|
|
1543
1555
|
init_litellm();
|
|
1544
1556
|
__dirname = path10.dirname(fileURLToPath(import.meta.url));
|
|
1557
|
+
TaskifyError = class extends Error {
|
|
1558
|
+
constructor(message) {
|
|
1559
|
+
super(message);
|
|
1560
|
+
this.name = "TaskifyError";
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1545
1563
|
AUTO_TRIGGER_THRESHOLD = 5;
|
|
1546
1564
|
MAX_TASKS_GUARD = 20;
|
|
1547
1565
|
TASKIFY_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -2680,10 +2698,12 @@ function escalateModelTier(currentTier) {
|
|
|
2680
2698
|
function resolveModel(modelTier, stageName) {
|
|
2681
2699
|
const config = getProjectConfig();
|
|
2682
2700
|
const mapped = config.agent.modelMap[modelTier];
|
|
2683
|
-
if (mapped)
|
|
2684
|
-
|
|
2701
|
+
if (!mapped) {
|
|
2702
|
+
throw new Error(`No model configured for tier '${modelTier}'. Set agent.modelMap.${modelTier} in kody.config.json`);
|
|
2703
|
+
}
|
|
2704
|
+
return mapped;
|
|
2685
2705
|
}
|
|
2686
|
-
var MAX_TASK_CONTEXT_PLAN, MAX_TASK_CONTEXT_SPEC, MAX_ACCUMULATED_CONTEXT, UI_EXTENSIONS, UI_PATH_SEGMENTS, TIER_ESCALATION
|
|
2706
|
+
var MAX_TASK_CONTEXT_PLAN, MAX_TASK_CONTEXT_SPEC, MAX_ACCUMULATED_CONTEXT, UI_EXTENSIONS, UI_PATH_SEGMENTS, TIER_ESCALATION;
|
|
2687
2707
|
var init_context = __esm({
|
|
2688
2708
|
"src/context.ts"() {
|
|
2689
2709
|
"use strict";
|
|
@@ -2717,11 +2737,6 @@ var init_context = __esm({
|
|
|
2717
2737
|
mid: "strong",
|
|
2718
2738
|
strong: "strong"
|
|
2719
2739
|
};
|
|
2720
|
-
DEFAULT_MODEL_MAP = {
|
|
2721
|
-
cheap: "haiku",
|
|
2722
|
-
mid: "sonnet",
|
|
2723
|
-
strong: "opus"
|
|
2724
|
-
};
|
|
2725
2740
|
}
|
|
2726
2741
|
});
|
|
2727
2742
|
|
|
@@ -4906,7 +4921,7 @@ async function ensureLitellmProxy(config, projectDir) {
|
|
|
4906
4921
|
return litellmProcess;
|
|
4907
4922
|
}
|
|
4908
4923
|
async function runModelHealthCheck(config) {
|
|
4909
|
-
const usesProxy =
|
|
4924
|
+
const usesProxy = anyStageNeedsProxy(config);
|
|
4910
4925
|
const baseUrl = usesProxy ? getLitellmUrl() : "https://api.anthropic.com";
|
|
4911
4926
|
const apiKey = usesProxy ? process.env.ANTHROPIC_COMPATIBLE_API_KEY : process.env.ANTHROPIC_API_KEY;
|
|
4912
4927
|
if (!apiKey) {
|
|
@@ -5527,7 +5542,7 @@ function buildConfig(cwd, basic) {
|
|
|
5527
5542
|
github: { owner: basic.owner, repo: basic.repo },
|
|
5528
5543
|
agent: {
|
|
5529
5544
|
provider: "anthropic",
|
|
5530
|
-
modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
|
|
5545
|
+
modelMap: { cheap: "claude-haiku-4-5-20251001", mid: "claude-sonnet-4-6", strong: "claude-opus-4-6" }
|
|
5531
5546
|
}
|
|
5532
5547
|
};
|
|
5533
5548
|
const mcp = detectMcpConfig(cwd, basic.pm, pkg);
|
|
@@ -5727,8 +5742,8 @@ function initCommand(opts, pkgRoot) {
|
|
|
5727
5742
|
|
|
5728
5743
|
// src/bin/commands/bootstrap.ts
|
|
5729
5744
|
init_architecture_detection();
|
|
5730
|
-
import * as
|
|
5731
|
-
import * as
|
|
5745
|
+
import * as fs8 from "fs";
|
|
5746
|
+
import * as path7 from "path";
|
|
5732
5747
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
5733
5748
|
|
|
5734
5749
|
// src/bin/qa-guide.ts
|
|
@@ -6002,22 +6017,23 @@ function installSkillsForProject(cwd) {
|
|
|
6002
6017
|
}
|
|
6003
6018
|
|
|
6004
6019
|
// src/bin/commands/bootstrap.ts
|
|
6020
|
+
init_config();
|
|
6005
6021
|
var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
|
|
6006
6022
|
function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
6007
|
-
const srcDir =
|
|
6008
|
-
const baseDir =
|
|
6023
|
+
const srcDir = path7.join(cwd, "src");
|
|
6024
|
+
const baseDir = fs8.existsSync(srcDir) ? srcDir : cwd;
|
|
6009
6025
|
const results = [];
|
|
6010
6026
|
function walk(dir) {
|
|
6011
6027
|
const entries = [];
|
|
6012
6028
|
try {
|
|
6013
|
-
for (const entry of
|
|
6029
|
+
for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
|
|
6014
6030
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
6015
|
-
const full =
|
|
6031
|
+
const full = path7.join(dir, entry.name);
|
|
6016
6032
|
if (entry.isDirectory()) {
|
|
6017
6033
|
entries.push(...walk(full));
|
|
6018
6034
|
} else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
|
|
6019
6035
|
try {
|
|
6020
|
-
const stat =
|
|
6036
|
+
const stat = fs8.statSync(full);
|
|
6021
6037
|
if (stat.size >= 200 && stat.size <= 5e3) {
|
|
6022
6038
|
entries.push({ filePath: full, size: stat.size });
|
|
6023
6039
|
}
|
|
@@ -6031,8 +6047,8 @@ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
|
|
|
6031
6047
|
}
|
|
6032
6048
|
const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
|
|
6033
6049
|
for (const { filePath } of files) {
|
|
6034
|
-
const rel =
|
|
6035
|
-
const content =
|
|
6050
|
+
const rel = path7.relative(cwd, filePath);
|
|
6051
|
+
const content = fs8.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
|
|
6036
6052
|
results.push(`### File: ${rel}
|
|
6037
6053
|
\`\`\`typescript
|
|
6038
6054
|
${content}
|
|
@@ -6044,9 +6060,9 @@ function ghComment(issueNumber, body, cwd) {
|
|
|
6044
6060
|
try {
|
|
6045
6061
|
let repoSlug = "";
|
|
6046
6062
|
try {
|
|
6047
|
-
const configPath =
|
|
6048
|
-
if (
|
|
6049
|
-
const config = JSON.parse(
|
|
6063
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
6064
|
+
if (fs8.existsSync(configPath)) {
|
|
6065
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
6050
6066
|
if (config.github?.owner && config.github?.repo) {
|
|
6051
6067
|
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
6052
6068
|
}
|
|
@@ -6073,7 +6089,9 @@ function ghComment(issueNumber, body, cwd) {
|
|
|
6073
6089
|
}
|
|
6074
6090
|
function bootstrapCommand(opts, pkgRoot) {
|
|
6075
6091
|
const cwd = process.cwd();
|
|
6092
|
+
setConfigDir(cwd);
|
|
6076
6093
|
const issueNumber = parseInt(process.env.ISSUE_NUMBER ?? "", 10) || 0;
|
|
6094
|
+
const bootstrapModel = resolveStageConfig(getProjectConfig(), "bootstrap", "cheap").model;
|
|
6077
6095
|
console.log(`
|
|
6078
6096
|
\u{1F527} Kody Bootstrap \u2014 Generating project memory + step files
|
|
6079
6097
|
`);
|
|
@@ -6081,8 +6099,8 @@ function bootstrapCommand(opts, pkgRoot) {
|
|
|
6081
6099
|
ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
|
|
6082
6100
|
}
|
|
6083
6101
|
const readIfExists = (rel, maxChars = 3e3) => {
|
|
6084
|
-
const p =
|
|
6085
|
-
if (
|
|
6102
|
+
const p = path7.join(cwd, rel);
|
|
6103
|
+
if (fs8.existsSync(p)) return fs8.readFileSync(p, "utf-8").slice(0, maxChars);
|
|
6086
6104
|
return null;
|
|
6087
6105
|
};
|
|
6088
6106
|
let repoContext = "";
|
|
@@ -6117,14 +6135,14 @@ ${sampleFiles}
|
|
|
6117
6135
|
|
|
6118
6136
|
`;
|
|
6119
6137
|
try {
|
|
6120
|
-
const topDirs =
|
|
6138
|
+
const topDirs = fs8.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
|
|
6121
6139
|
repoContext += `## Top-level directories
|
|
6122
6140
|
${topDirs.join(", ")}
|
|
6123
6141
|
|
|
6124
6142
|
`;
|
|
6125
|
-
const srcDir =
|
|
6126
|
-
if (
|
|
6127
|
-
const srcDirs =
|
|
6143
|
+
const srcDir = path7.join(cwd, "src");
|
|
6144
|
+
if (fs8.existsSync(srcDir)) {
|
|
6145
|
+
const srcDirs = fs8.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
6128
6146
|
if (srcDirs.length > 0) repoContext += `## src/ subdirectories
|
|
6129
6147
|
${srcDirs.join(", ")}
|
|
6130
6148
|
|
|
@@ -6134,19 +6152,19 @@ ${srcDirs.join(", ")}
|
|
|
6134
6152
|
}
|
|
6135
6153
|
const existingFiles = [];
|
|
6136
6154
|
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"]) {
|
|
6137
|
-
if (
|
|
6155
|
+
if (fs8.existsSync(path7.join(cwd, f))) existingFiles.push(f);
|
|
6138
6156
|
}
|
|
6139
6157
|
if (existingFiles.length) repoContext += `## Config files present
|
|
6140
6158
|
${existingFiles.join(", ")}
|
|
6141
6159
|
|
|
6142
6160
|
`;
|
|
6143
6161
|
console.log("\u2500\u2500 Project Memory \u2500\u2500");
|
|
6144
|
-
const memoryDir =
|
|
6145
|
-
|
|
6146
|
-
const archPath =
|
|
6147
|
-
const conventionsPath =
|
|
6148
|
-
const existingArch =
|
|
6149
|
-
const existingConv =
|
|
6162
|
+
const memoryDir = path7.join(cwd, ".kody", "memory");
|
|
6163
|
+
fs8.mkdirSync(memoryDir, { recursive: true });
|
|
6164
|
+
const archPath = path7.join(memoryDir, "architecture.md");
|
|
6165
|
+
const conventionsPath = path7.join(memoryDir, "conventions.md");
|
|
6166
|
+
const existingArch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
|
|
6167
|
+
const existingConv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
|
|
6150
6168
|
const hasExisting = !!(existingArch || existingConv);
|
|
6151
6169
|
const extendInstruction = hasExisting && !opts.force ? `
|
|
6152
6170
|
## Existing Documentation (EXTEND, do not replace)
|
|
@@ -6190,7 +6208,7 @@ ${repoContext}`;
|
|
|
6190
6208
|
const output = execFileSync5("claude", [
|
|
6191
6209
|
"--print",
|
|
6192
6210
|
"--model",
|
|
6193
|
-
|
|
6211
|
+
bootstrapModel,
|
|
6194
6212
|
"--dangerously-skip-permissions",
|
|
6195
6213
|
memoryPrompt
|
|
6196
6214
|
], {
|
|
@@ -6202,12 +6220,12 @@ ${repoContext}`;
|
|
|
6202
6220
|
const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
|
|
6203
6221
|
const parsed = JSON.parse(cleaned);
|
|
6204
6222
|
if (parsed.architecture) {
|
|
6205
|
-
|
|
6223
|
+
fs8.writeFileSync(archPath, parsed.architecture);
|
|
6206
6224
|
const lineCount = parsed.architecture.split("\n").length;
|
|
6207
6225
|
console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
|
|
6208
6226
|
}
|
|
6209
6227
|
if (parsed.conventions) {
|
|
6210
|
-
|
|
6228
|
+
fs8.writeFileSync(conventionsPath, parsed.conventions);
|
|
6211
6229
|
const lineCount = parsed.conventions.split("\n").length;
|
|
6212
6230
|
console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
|
|
6213
6231
|
}
|
|
@@ -6216,39 +6234,39 @@ ${repoContext}`;
|
|
|
6216
6234
|
const detected = detectArchitectureBasic(cwd);
|
|
6217
6235
|
if (detected.length > 0) {
|
|
6218
6236
|
const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6219
|
-
|
|
6237
|
+
fs8.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
|
|
6220
6238
|
|
|
6221
6239
|
## Overview
|
|
6222
6240
|
${detected.join("\n")}
|
|
6223
6241
|
`);
|
|
6224
6242
|
console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
|
|
6225
6243
|
}
|
|
6226
|
-
|
|
6244
|
+
fs8.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
|
|
6227
6245
|
console.log(" \u2713 .kody/memory/conventions.md (seed)");
|
|
6228
6246
|
}
|
|
6229
6247
|
console.log("\n\u2500\u2500 Step Files \u2500\u2500");
|
|
6230
|
-
const stepsDir =
|
|
6231
|
-
|
|
6232
|
-
const arch =
|
|
6233
|
-
const conv =
|
|
6248
|
+
const stepsDir = path7.join(cwd, ".kody", "steps");
|
|
6249
|
+
fs8.mkdirSync(stepsDir, { recursive: true });
|
|
6250
|
+
const arch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
|
|
6251
|
+
const conv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
|
|
6234
6252
|
console.log(" \u23F3 Customizing step files...");
|
|
6235
6253
|
let stepCount = 0;
|
|
6236
6254
|
for (const stage of STEP_STAGES) {
|
|
6237
|
-
const templatePath =
|
|
6238
|
-
if (!
|
|
6255
|
+
const templatePath = path7.join(pkgRoot, "prompts", `${stage}.md`);
|
|
6256
|
+
if (!fs8.existsSync(templatePath)) {
|
|
6239
6257
|
console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
|
|
6240
6258
|
continue;
|
|
6241
6259
|
}
|
|
6242
|
-
const stepOutputPath =
|
|
6243
|
-
if (
|
|
6260
|
+
const stepOutputPath = path7.join(stepsDir, `${stage}.md`);
|
|
6261
|
+
if (fs8.existsSync(stepOutputPath) && !opts.force) {
|
|
6244
6262
|
console.log(` \u25CB ${stage}.md \u2014 already exists (use --force to regenerate)`);
|
|
6245
6263
|
continue;
|
|
6246
6264
|
}
|
|
6247
|
-
const defaultPrompt =
|
|
6265
|
+
const defaultPrompt = fs8.readFileSync(templatePath, "utf-8");
|
|
6248
6266
|
const contextPlaceholder = "{{TASK_CONTEXT}}";
|
|
6249
6267
|
const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
|
|
6250
6268
|
if (placeholderIdx === -1) {
|
|
6251
|
-
|
|
6269
|
+
fs8.copyFileSync(templatePath, stepOutputPath);
|
|
6252
6270
|
stepCount++;
|
|
6253
6271
|
console.log(` \u2713 ${stage}.md`);
|
|
6254
6272
|
continue;
|
|
@@ -6293,7 +6311,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
6293
6311
|
const output = execFileSync5("claude", [
|
|
6294
6312
|
"--print",
|
|
6295
6313
|
"--model",
|
|
6296
|
-
|
|
6314
|
+
bootstrapModel,
|
|
6297
6315
|
"--dangerously-skip-permissions",
|
|
6298
6316
|
customizationPrompt
|
|
6299
6317
|
], {
|
|
@@ -6305,23 +6323,23 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
6305
6323
|
let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
6306
6324
|
cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
|
|
6307
6325
|
const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
|
|
6308
|
-
|
|
6326
|
+
fs8.writeFileSync(stepOutputPath, finalPrompt);
|
|
6309
6327
|
stepCount++;
|
|
6310
6328
|
console.log(` \u2713 ${stage}.md`);
|
|
6311
6329
|
} catch {
|
|
6312
6330
|
console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
|
|
6313
|
-
|
|
6331
|
+
fs8.copyFileSync(templatePath, stepOutputPath);
|
|
6314
6332
|
stepCount++;
|
|
6315
6333
|
}
|
|
6316
6334
|
}
|
|
6317
6335
|
console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
|
|
6318
6336
|
console.log("\n\u2500\u2500 QA Guide \u2500\u2500");
|
|
6319
|
-
const qaGuidePath =
|
|
6320
|
-
if (!
|
|
6337
|
+
const qaGuidePath = path7.join(cwd, ".kody", "qa-guide.md");
|
|
6338
|
+
if (!fs8.existsSync(qaGuidePath) || opts.force) {
|
|
6321
6339
|
const discovery = discoverQaContext(cwd);
|
|
6322
6340
|
if (discovery.routes.length > 0) {
|
|
6323
6341
|
const qaGuide = generateQaGuide(discovery);
|
|
6324
|
-
|
|
6342
|
+
fs8.writeFileSync(qaGuidePath, qaGuide);
|
|
6325
6343
|
console.log(` \u2713 .kody/qa-guide.md (${discovery.routes.length} routes, ${discovery.roles.length} roles)`);
|
|
6326
6344
|
if (discovery.loginPage) console.log(` \u2713 Login page detected: ${discovery.loginPage}`);
|
|
6327
6345
|
if (discovery.adminPath) console.log(` \u2713 Admin panel detected: ${discovery.adminPath}`);
|
|
@@ -6336,9 +6354,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
6336
6354
|
try {
|
|
6337
6355
|
let repoSlug = "";
|
|
6338
6356
|
try {
|
|
6339
|
-
const configPath =
|
|
6340
|
-
if (
|
|
6341
|
-
const config = JSON.parse(
|
|
6357
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
6358
|
+
if (fs8.existsSync(configPath)) {
|
|
6359
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
6342
6360
|
if (config.github?.owner && config.github?.repo) {
|
|
6343
6361
|
repoSlug = `${config.github.owner}/${config.github.repo}`;
|
|
6344
6362
|
}
|
|
@@ -6411,19 +6429,19 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
6411
6429
|
".kody/memory/conventions.md",
|
|
6412
6430
|
".kody/qa-guide.md",
|
|
6413
6431
|
...installedSkillPaths
|
|
6414
|
-
].filter((f) =>
|
|
6415
|
-
if (
|
|
6432
|
+
].filter((f) => fs8.existsSync(path7.join(cwd, f)));
|
|
6433
|
+
if (fs8.existsSync(path7.join(cwd, "skills-lock.json"))) {
|
|
6416
6434
|
filesToCommit.push("skills-lock.json");
|
|
6417
6435
|
}
|
|
6418
6436
|
for (const stage of STEP_STAGES) {
|
|
6419
6437
|
const stepFile = `.kody/steps/${stage}.md`;
|
|
6420
|
-
if (
|
|
6438
|
+
if (fs8.existsSync(path7.join(cwd, stepFile))) {
|
|
6421
6439
|
filesToCommit.push(stepFile);
|
|
6422
6440
|
}
|
|
6423
6441
|
}
|
|
6424
6442
|
if (filesToCommit.length > 0) {
|
|
6425
6443
|
try {
|
|
6426
|
-
const fullPaths = filesToCommit.map((f) =>
|
|
6444
|
+
const fullPaths = filesToCommit.map((f) => path7.join(cwd, f));
|
|
6427
6445
|
for (let pass = 0; pass < 2; pass++) {
|
|
6428
6446
|
execFileSync5("npx", ["prettier", "--write", ...fullPaths], {
|
|
6429
6447
|
cwd,
|
|
@@ -6450,9 +6468,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
|
|
|
6450
6468
|
console.log(` \u2713 Pushed branch: ${branchName}`);
|
|
6451
6469
|
let baseBranch = "main";
|
|
6452
6470
|
try {
|
|
6453
|
-
const configPath =
|
|
6454
|
-
if (
|
|
6455
|
-
const config = JSON.parse(
|
|
6471
|
+
const configPath = path7.join(cwd, "kody.config.json");
|
|
6472
|
+
if (fs8.existsSync(configPath)) {
|
|
6473
|
+
const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
|
|
6456
6474
|
baseBranch = config.git?.defaultBranch ?? "main";
|
|
6457
6475
|
}
|
|
6458
6476
|
} catch {
|