@kody-ade/kody-engine-lite 0.1.115 → 0.1.117

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.
Files changed (72) hide show
  1. package/dist/bin/cli.js +455 -1439
  2. package/kody.config.schema.json +0 -31
  3. package/package.json +1 -1
  4. package/templates/kody.yml +0 -1
  5. package/dist/agent-runner.d.ts +0 -4
  6. package/dist/agent-runner.js +0 -122
  7. package/dist/ci/parse-inputs.d.ts +0 -6
  8. package/dist/ci/parse-inputs.js +0 -76
  9. package/dist/ci/parse-safety.d.ts +0 -6
  10. package/dist/ci/parse-safety.js +0 -22
  11. package/dist/cli/args.d.ts +0 -13
  12. package/dist/cli/args.js +0 -42
  13. package/dist/cli/litellm.d.ts +0 -2
  14. package/dist/cli/litellm.js +0 -85
  15. package/dist/cli/task-resolution.d.ts +0 -2
  16. package/dist/cli/task-resolution.js +0 -41
  17. package/dist/config.d.ts +0 -49
  18. package/dist/config.js +0 -72
  19. package/dist/context.d.ts +0 -4
  20. package/dist/context.js +0 -83
  21. package/dist/definitions.d.ts +0 -3
  22. package/dist/definitions.js +0 -59
  23. package/dist/entry.d.ts +0 -1
  24. package/dist/entry.js +0 -236
  25. package/dist/git-utils.d.ts +0 -13
  26. package/dist/git-utils.js +0 -174
  27. package/dist/github-api.d.ts +0 -14
  28. package/dist/github-api.js +0 -114
  29. package/dist/kody-utils.d.ts +0 -1
  30. package/dist/kody-utils.js +0 -9
  31. package/dist/learning/auto-learn.d.ts +0 -2
  32. package/dist/learning/auto-learn.js +0 -169
  33. package/dist/logger.d.ts +0 -14
  34. package/dist/logger.js +0 -51
  35. package/dist/memory.d.ts +0 -1
  36. package/dist/memory.js +0 -20
  37. package/dist/observer.d.ts +0 -9
  38. package/dist/observer.js +0 -80
  39. package/dist/pipeline/complexity.d.ts +0 -3
  40. package/dist/pipeline/complexity.js +0 -12
  41. package/dist/pipeline/executor-registry.d.ts +0 -3
  42. package/dist/pipeline/executor-registry.js +0 -20
  43. package/dist/pipeline/hooks.d.ts +0 -17
  44. package/dist/pipeline/hooks.js +0 -110
  45. package/dist/pipeline/questions.d.ts +0 -2
  46. package/dist/pipeline/questions.js +0 -44
  47. package/dist/pipeline/runner-selection.d.ts +0 -2
  48. package/dist/pipeline/runner-selection.js +0 -13
  49. package/dist/pipeline/state.d.ts +0 -4
  50. package/dist/pipeline/state.js +0 -37
  51. package/dist/pipeline.d.ts +0 -3
  52. package/dist/pipeline.js +0 -213
  53. package/dist/preflight.d.ts +0 -1
  54. package/dist/preflight.js +0 -69
  55. package/dist/retrospective.d.ts +0 -26
  56. package/dist/retrospective.js +0 -211
  57. package/dist/stages/agent.d.ts +0 -2
  58. package/dist/stages/agent.js +0 -94
  59. package/dist/stages/gate.d.ts +0 -2
  60. package/dist/stages/gate.js +0 -32
  61. package/dist/stages/review.d.ts +0 -2
  62. package/dist/stages/review.js +0 -32
  63. package/dist/stages/ship.d.ts +0 -3
  64. package/dist/stages/ship.js +0 -154
  65. package/dist/stages/verify.d.ts +0 -2
  66. package/dist/stages/verify.js +0 -94
  67. package/dist/types.d.ts +0 -61
  68. package/dist/types.js +0 -1
  69. package/dist/validators.d.ts +0 -8
  70. package/dist/validators.js +0 -42
  71. package/dist/verify-runner.d.ts +0 -11
  72. package/dist/verify-runner.js +0 -110
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 fs7 from "fs";
184
- import * as path6 from "path";
183
+ import * as fs8 from "fs";
184
+ import * as path7 from "path";
185
185
  function resolveStageConfig(config, stageName, modelTier) {
186
186
  const stageOverride = config.agent.stages?.[stageName];
187
187
  if (stageOverride) return stageOverride;
@@ -223,16 +223,16 @@ function setConfigDir(dir) {
223
223
  }
224
224
  function getProjectConfig() {
225
225
  if (_config) return _config;
226
- const configPath = path6.join(_configDir ?? process.cwd(), "kody.config.json");
227
- if (fs7.existsSync(configPath)) {
226
+ const configPath = path7.join(_configDir ?? process.cwd(), "kody.config.json");
227
+ if (fs8.existsSync(configPath)) {
228
228
  try {
229
- const result2 = parseJsonSafe(fs7.readFileSync(configPath, "utf-8"));
230
- if (!result2.ok) {
231
- logger.warn(`kody.config.json: ${result2.error} \u2014 using defaults`);
229
+ const result = parseJsonSafe(fs8.readFileSync(configPath, "utf-8"));
230
+ if (!result.ok) {
231
+ logger.warn(`kody.config.json: ${result.error} \u2014 using defaults`);
232
232
  _config = { ...DEFAULT_CONFIG };
233
233
  return _config;
234
234
  }
235
- const raw = result2.data;
235
+ const raw = result.data;
236
236
  _config = {
237
237
  quality: { ...DEFAULT_CONFIG.quality, ...raw.quality },
238
238
  git: { ...DEFAULT_CONFIG.git, ...raw.git },
@@ -283,7 +283,7 @@ var init_config = __esm({
283
283
  repo: ""
284
284
  },
285
285
  agent: {
286
- modelMap: { cheap: "claude-haiku-4-5-20251001", mid: "claude-sonnet-4-6", strong: "claude-opus-4-6" }
286
+ modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
287
287
  },
288
288
  contextTiers: {
289
289
  enabled: true,
@@ -389,11 +389,10 @@ function createClaudeCodeRunner() {
389
389
  model,
390
390
  "--dangerously-skip-permissions"
391
391
  ];
392
- const baseTools = "Bash,Edit,Read,Write,Glob,Grep";
393
392
  if (options?.mcpConfigJson) {
394
393
  args2.push("--mcp-config", options.mcpConfigJson);
395
394
  } else {
396
- args2.push("--allowedTools", baseTools);
395
+ args2.push("--allowedTools", "Bash,Edit,Read,Write,Glob,Grep");
397
396
  }
398
397
  if (options?.sessionId) {
399
398
  if (options.resumeSession) {
@@ -966,8 +965,8 @@ function findLatestTaskForIssue(issueNumber, projectDir) {
966
965
  }
967
966
  function generateTaskId() {
968
967
  const now = /* @__PURE__ */ new Date();
969
- const pad2 = (n) => String(n).padStart(2, "0");
970
- return `${String(now.getFullYear()).slice(2)}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}-${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
968
+ const pad = (n) => String(n).padStart(2, "0");
969
+ return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
971
970
  }
972
971
  function resolveTaskIdFromComments(issueNumber) {
973
972
  try {
@@ -1193,7 +1192,6 @@ var init_litellm = __esm({
1193
1192
  // src/cli/taskify-command.ts
1194
1193
  var taskify_command_exports = {};
1195
1194
  __export(taskify_command_exports, {
1196
- TaskifyError: () => TaskifyError,
1197
1195
  isTaskifyRun: () => isTaskifyRun,
1198
1196
  readTaskifyMarker: () => readTaskifyMarker,
1199
1197
  runTaskifyCommand: () => runTaskifyCommand,
@@ -1285,17 +1283,8 @@ async function runTaskifyCommand() {
1285
1283
  ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "dummy"
1286
1284
  };
1287
1285
  }
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
- }
1286
+ await taskifyCommand({ ticketId, prdFile, issueNumber, feedback, local, projectDir, taskId, runnerEnv });
1287
+ litellmProcess?.kill();
1299
1288
  }
1300
1289
  async function taskifyCommand(opts) {
1301
1290
  const { ticketId, prdFile, issueNumber, feedback, local, projectDir, taskId } = opts;
@@ -1310,6 +1299,7 @@ async function taskifyCommand(opts) {
1310
1299
  mcpConfigJson = buildTaskifyMcpConfigJson(config);
1311
1300
  } catch (err) {
1312
1301
  const msg = err instanceof Error ? err.message : String(err);
1302
+ logger.error(`[taskify] MCP config error: ${msg}`);
1313
1303
  if (issueNumber && !local) {
1314
1304
  postComment(
1315
1305
  issueNumber,
@@ -1320,7 +1310,7 @@ async function taskifyCommand(opts) {
1320
1310
  Add the required MCP server config to \`kody.config.json\` and try again.`
1321
1311
  );
1322
1312
  }
1323
- throw new TaskifyError(`MCP config error: ${msg}`);
1313
+ process.exit(1);
1324
1314
  }
1325
1315
  }
1326
1316
  const sc = resolveStageConfig(config, "taskify", "strong");
@@ -1361,44 +1351,47 @@ Kody is decomposing ${src} into tasks...`);
1361
1351
  fs11.writeFileSync(path10.join(taskDir, MARKER_FILE), JSON.stringify({ ticketId, prdFile, issueNumber }));
1362
1352
  const runner = opts.runner ?? createClaudeCodeRunner();
1363
1353
  logger.info(` model=${model} timeout=${TASKIFY_TIMEOUT_MS / 1e3}s`);
1364
- const result2 = await runner.run("taskify", prompt, model, TASKIFY_TIMEOUT_MS, taskDir, {
1354
+ const result = await runner.run("taskify", prompt, model, TASKIFY_TIMEOUT_MS, taskDir, {
1365
1355
  cwd: projectDir,
1366
1356
  mcpConfigJson,
1367
1357
  env: opts.runnerEnv
1368
1358
  });
1369
- if (result2.outcome !== "completed") {
1370
- const errMsg = result2.outcome === "timed_out" ? "Taskify timed out after 5 minutes." : `Taskify failed: ${result2.error}`;
1359
+ if (result.outcome !== "completed") {
1360
+ const errMsg = result.outcome === "timed_out" ? "Taskify timed out after 5 minutes." : `Taskify failed: ${result.error}`;
1361
+ logger.error(`[taskify] ${errMsg}`);
1371
1362
  if (issueNumber && !local) {
1372
1363
  postComment(issueNumber, `Kody taskify failed:
1373
1364
 
1374
1365
  > ${errMsg}`);
1375
1366
  setLifecycleLabel(issueNumber, "failed");
1376
1367
  }
1377
- throw new TaskifyError(errMsg);
1368
+ process.exit(1);
1378
1369
  }
1379
1370
  const resultPath = path10.join(taskDir, RESULT_FILE);
1380
1371
  if (!fs11.existsSync(resultPath)) {
1381
1372
  const errMsg = `Claude did not write ${RESULT_FILE}. Output:
1382
1373
 
1383
- ${result2.output?.slice(0, 500) ?? "(none)"}`;
1374
+ ${result.output?.slice(0, 500) ?? "(none)"}`;
1375
+ logger.error(`[taskify] ${errMsg}`);
1384
1376
  if (issueNumber && !local) {
1385
1377
  postComment(issueNumber, `Kody taskify failed: result file not found.
1386
1378
 
1387
1379
  ${errMsg}`);
1388
1380
  setLifecycleLabel(issueNumber, "failed");
1389
1381
  }
1390
- throw new TaskifyError(errMsg);
1382
+ process.exit(1);
1391
1383
  }
1392
1384
  let parsed;
1393
1385
  try {
1394
1386
  parsed = JSON.parse(fs11.readFileSync(resultPath, "utf-8"));
1395
1387
  } catch {
1396
1388
  const errMsg = `Could not parse ${RESULT_FILE} as JSON.`;
1389
+ logger.error(`[taskify] ${errMsg}`);
1397
1390
  if (issueNumber && !local) {
1398
1391
  postComment(issueNumber, `Kody taskify failed: ${errMsg}`);
1399
1392
  setLifecycleLabel(issueNumber, "failed");
1400
1393
  }
1401
- throw new TaskifyError(errMsg);
1394
+ process.exit(1);
1402
1395
  }
1403
1396
  const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : "spec");
1404
1397
  if (parsed.status === "questions") {
@@ -1407,11 +1400,12 @@ ${errMsg}`);
1407
1400
  await handleTasks(parsed, sourceLabel, issueNumber, local ?? false);
1408
1401
  } else {
1409
1402
  const errMsg = `Unexpected status in ${RESULT_FILE}: ${JSON.stringify(parsed)}`;
1403
+ logger.error(`[taskify] ${errMsg}`);
1410
1404
  if (issueNumber && !local) {
1411
1405
  postComment(issueNumber, `Kody taskify failed: ${errMsg}`);
1412
1406
  setLifecycleLabel(issueNumber, "failed");
1413
1407
  }
1414
- throw new TaskifyError(errMsg);
1408
+ process.exit(1);
1415
1409
  }
1416
1410
  }
1417
1411
  function handleQuestions(parsed, ticketId, issueNumber, local) {
@@ -1542,7 +1536,7 @@ function readTaskifyMarker(taskDir) {
1542
1536
  return null;
1543
1537
  }
1544
1538
  }
1545
- var __dirname, TaskifyError, AUTO_TRIGGER_THRESHOLD, MAX_TASKS_GUARD, TASKIFY_TIMEOUT_MS, MARKER_FILE, RESULT_FILE;
1539
+ var __dirname, AUTO_TRIGGER_THRESHOLD, MAX_TASKS_GUARD, TASKIFY_TIMEOUT_MS, MARKER_FILE, RESULT_FILE;
1546
1540
  var init_taskify_command = __esm({
1547
1541
  "src/cli/taskify-command.ts"() {
1548
1542
  "use strict";
@@ -1554,12 +1548,6 @@ var init_taskify_command = __esm({
1554
1548
  init_task_resolution();
1555
1549
  init_litellm();
1556
1550
  __dirname = path10.dirname(fileURLToPath(import.meta.url));
1557
- TaskifyError = class extends Error {
1558
- constructor(message) {
1559
- super(message);
1560
- this.name = "TaskifyError";
1561
- }
1562
- };
1563
1551
  AUTO_TRIGGER_THRESHOLD = 5;
1564
1552
  MAX_TASKS_GUARD = 20;
1565
1553
  TASKIFY_TIMEOUT_MS = 5 * 60 * 1e3;
@@ -1568,999 +1556,6 @@ var init_taskify_command = __esm({
1568
1556
  }
1569
1557
  });
1570
1558
 
1571
- // src/cli/test-model-tests.ts
1572
- import * as fs12 from "fs";
1573
- import * as os2 from "os";
1574
- import * as path11 from "path";
1575
- import * as zlib from "zlib";
1576
- import { spawnSync, execSync as execSync2 } from "child_process";
1577
- async function apiCall(ctx, body) {
1578
- try {
1579
- const res = await fetch(`${ctx.proxyUrl}/v1/messages`, {
1580
- method: "POST",
1581
- headers: {
1582
- "Content-Type": "application/json",
1583
- "x-api-key": ctx.apiKey,
1584
- "anthropic-version": "2023-06-01"
1585
- },
1586
- body: JSON.stringify({ model: ctx.model, ...body }),
1587
- signal: AbortSignal.timeout(6e4)
1588
- });
1589
- const data = await res.json();
1590
- if (!res.ok) {
1591
- return { ok: false, data, status: res.status, errorMsg: data?.error?.message ?? `HTTP ${res.status}` };
1592
- }
1593
- return { ok: true, data, status: res.status };
1594
- } catch (err) {
1595
- return { ok: false, data: null, status: 0, errorMsg: err instanceof Error ? err.message : String(err) };
1596
- }
1597
- }
1598
- function extractText(data) {
1599
- if (!data?.content) return "";
1600
- return data.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
1601
- }
1602
- async function runToolConversation(ctx, tools, userPrompt, simulate, opts) {
1603
- const messages = [{ role: "user", content: userPrompt }];
1604
- const allCalls = [];
1605
- for (let turn = 0; turn < (opts?.maxTurns ?? 5); turn++) {
1606
- const body = {
1607
- max_tokens: 1024,
1608
- temperature: 0,
1609
- messages,
1610
- tools
1611
- };
1612
- if (opts?.system) body.system = opts.system;
1613
- const res = await apiCall(ctx, body);
1614
- if (!res.ok) return { finalText: "", toolCalls: allCalls, error: res.errorMsg };
1615
- const content = res.data.content ?? [];
1616
- const toolBlocks = content.filter((b) => b.type === "tool_use");
1617
- const textBlocks = content.filter((b) => b.type === "text");
1618
- if (toolBlocks.length === 0) {
1619
- return { finalText: textBlocks.map((b) => b.text ?? "").join(""), toolCalls: allCalls };
1620
- }
1621
- for (const tc of toolBlocks) allCalls.push({ name: tc.name, input: tc.input });
1622
- messages.push({ role: "assistant", content });
1623
- messages.push({
1624
- role: "user",
1625
- content: toolBlocks.map((tc) => ({
1626
- type: "tool_result",
1627
- tool_use_id: tc.id,
1628
- content: simulate(tc.name, tc.input)
1629
- }))
1630
- });
1631
- }
1632
- return { finalText: "", toolCalls: allCalls, error: "Max turns reached" };
1633
- }
1634
- function filterStderr(stderr) {
1635
- return stderr.split("\n").filter((l) => !l.includes("CPU lacks AVX") && !l.includes("bun-darwin") && !l.includes("Warning: no stdin data") && l.trim().length > 0).join("\n").trim();
1636
- }
1637
- function runClaudeTest(ctx, prompt, extraFlags = [], timeout = 9e4) {
1638
- try {
1639
- const result2 = spawnSync("claude", [
1640
- "--print",
1641
- "--model",
1642
- ctx.model,
1643
- "--dangerously-skip-permissions",
1644
- ...extraFlags,
1645
- "-p",
1646
- prompt
1647
- ], {
1648
- env: { ...process.env, ANTHROPIC_BASE_URL: ctx.proxyUrl, ANTHROPIC_API_KEY: ctx.apiKey },
1649
- timeout,
1650
- encoding: "utf-8",
1651
- cwd: ctx.projectDir
1652
- });
1653
- return {
1654
- stdout: result2.stdout ?? "",
1655
- stderr: filterStderr(result2.stderr ?? ""),
1656
- exitCode: result2.status ?? 1
1657
- };
1658
- } catch (err) {
1659
- return { stdout: "", stderr: String(err), exitCode: 1 };
1660
- }
1661
- }
1662
- function isGitClean(dir) {
1663
- try {
1664
- const out = execSync2("git diff --name-only", { cwd: dir, encoding: "utf-8", timeout: 5e3 });
1665
- return out.trim().length === 0;
1666
- } catch {
1667
- return false;
1668
- }
1669
- }
1670
- function revertChanges(dir) {
1671
- try {
1672
- execSync2("git checkout -- src/logger.ts", { cwd: dir, timeout: 5e3, stdio: "pipe" });
1673
- } catch {
1674
- }
1675
- }
1676
- function result(name, category, status, accuracy, durationMs, detail, metrics) {
1677
- return { name, category, status, accuracy, durationMs, detail, metrics };
1678
- }
1679
- function crc32(buf) {
1680
- let c = 4294967295;
1681
- for (const b of buf) c = CRC_TABLE[(c ^ b) & 255] ^ c >>> 8;
1682
- return (c ^ 4294967295) >>> 0;
1683
- }
1684
- function createRedPng() {
1685
- const w = 4, h = 4;
1686
- const scanlines = Buffer.alloc(h * (1 + w * 3));
1687
- for (let y = 0; y < h; y++) {
1688
- const off = y * (1 + w * 3);
1689
- scanlines[off] = 0;
1690
- for (let x = 0; x < w; x++) {
1691
- scanlines[off + 1 + x * 3] = 255;
1692
- scanlines[off + 1 + x * 3 + 1] = 0;
1693
- scanlines[off + 1 + x * 3 + 2] = 0;
1694
- }
1695
- }
1696
- function chunk(type, data) {
1697
- const tb = Buffer.from(type, "ascii");
1698
- const merged = Buffer.concat([tb, data]);
1699
- const len = Buffer.alloc(4);
1700
- len.writeUInt32BE(data.length);
1701
- const crcBuf = Buffer.alloc(4);
1702
- crcBuf.writeUInt32BE(crc32(merged));
1703
- return Buffer.concat([len, tb, data, crcBuf]);
1704
- }
1705
- const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
1706
- const ihdr = Buffer.alloc(13);
1707
- ihdr.writeUInt32BE(w, 0);
1708
- ihdr.writeUInt32BE(h, 4);
1709
- ihdr[8] = 8;
1710
- ihdr[9] = 2;
1711
- return Buffer.concat([sig, chunk("IHDR", ihdr), chunk("IDAT", zlib.deflateSync(scanlines)), chunk("IEND", Buffer.alloc(0))]);
1712
- }
1713
- async function testSimplePrompt(ctx) {
1714
- const t = Date.now();
1715
- const res = await apiCall(ctx, {
1716
- max_tokens: 50,
1717
- temperature: 0,
1718
- messages: [{ role: "user", content: "Reply with exactly: KODY_TEST_OK" }]
1719
- });
1720
- if (!res.ok) return result("simple_prompt", "basic", "fail", 0, Date.now() - t, `API error: ${res.errorMsg}`);
1721
- const text = extractText(res.data);
1722
- const ok = text.includes("KODY_TEST_OK");
1723
- return result(
1724
- "simple_prompt",
1725
- "basic",
1726
- ok ? "pass" : "fail",
1727
- ok ? 100 : 0,
1728
- Date.now() - t,
1729
- ok ? "Model responded correctly" : `Expected KODY_TEST_OK, got: ${text.slice(0, 80)}`
1730
- );
1731
- }
1732
- async function testJsonOutput(ctx) {
1733
- const t = Date.now();
1734
- const res = await apiCall(ctx, {
1735
- max_tokens: 200,
1736
- temperature: 0,
1737
- system: "Respond with ONLY valid JSON. No markdown fences, no explanation. Just raw JSON.",
1738
- messages: [{ role: "user", content: 'Return a JSON object with keys "status" (string "ok") and "model" (string, your model name).' }]
1739
- });
1740
- if (!res.ok) return result("json_output", "basic", "fail", 0, Date.now() - t, `API error: ${res.errorMsg}`);
1741
- let text = extractText(res.data).trim();
1742
- text = text.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
1743
- try {
1744
- const parsed = JSON.parse(text);
1745
- const hasKeys = typeof parsed.status === "string" && typeof parsed.model === "string";
1746
- return result(
1747
- "json_output",
1748
- "basic",
1749
- "pass",
1750
- hasKeys ? 100 : 70,
1751
- Date.now() - t,
1752
- hasKeys ? "Valid JSON with correct keys" : "Valid JSON but missing expected keys"
1753
- );
1754
- } catch {
1755
- return result("json_output", "basic", "fail", 0, Date.now() - t, `Invalid JSON: ${text.slice(0, 80)}`);
1756
- }
1757
- }
1758
- async function testSystemPromptRules(ctx) {
1759
- const t = Date.now();
1760
- const res = await apiCall(ctx, {
1761
- max_tokens: 200,
1762
- temperature: 0,
1763
- system: [
1764
- "STRICT RULES \u2014 violating ANY will crash the system:",
1765
- "1) Start every response with 'KODY:'",
1766
- "2) Never use the word 'the'",
1767
- "3) Keep response under 50 words",
1768
- "4) End your response with 'END'",
1769
- "5) Use ONLY lowercase letters (no uppercase anywhere)"
1770
- ].join("\n"),
1771
- messages: [{ role: "user", content: "Describe what a compiler does." }]
1772
- });
1773
- if (!res.ok) return result("system_prompt_rules", "basic", "fail", 0, Date.now() - t, `API error: ${res.errorMsg}`);
1774
- const text = extractText(res.data).trim();
1775
- let score = 0;
1776
- const checks = [];
1777
- if (text.startsWith("KODY:") || text.startsWith("kody:")) {
1778
- score += 20;
1779
- checks.push("starts-with-kody");
1780
- }
1781
- if (!text.toLowerCase().split(/\s+/).includes("the")) {
1782
- score += 20;
1783
- checks.push("no-the");
1784
- }
1785
- if (text.split(/\s+/).length <= 55) {
1786
- score += 20;
1787
- checks.push("under-50-words");
1788
- }
1789
- if (text.endsWith("END") || text.endsWith("end")) {
1790
- score += 20;
1791
- checks.push("ends-with-end");
1792
- }
1793
- if (text === text.toLowerCase()) {
1794
- score += 20;
1795
- checks.push("all-lowercase");
1796
- }
1797
- const status = score >= 80 ? "pass" : score >= 40 ? "warn" : "fail";
1798
- return result(
1799
- "system_prompt_rules",
1800
- "basic",
1801
- status,
1802
- score,
1803
- Date.now() - t,
1804
- `${score / 20}/5 rules followed: ${checks.join(", ")}`,
1805
- { instructionCompliance: score }
1806
- );
1807
- }
1808
- async function testExtendedThinking(ctx) {
1809
- const t = Date.now();
1810
- const res = await apiCall(ctx, {
1811
- max_tokens: 200,
1812
- thinking: { type: "enabled", budget_tokens: 2e3 },
1813
- messages: [{ role: "user", content: "What is 15 * 23?" }]
1814
- });
1815
- if (!res.ok) return result(
1816
- "extended_thinking",
1817
- "infrastructure",
1818
- "warn",
1819
- 50,
1820
- Date.now() - t,
1821
- `Request failed (model may not support thinking): ${res.errorMsg?.slice(0, 80)}`
1822
- );
1823
- const hasThinking = Array.isArray(res.data.content) && res.data.content.some((b) => b.type === "thinking");
1824
- const hasText = extractText(res.data).length > 0;
1825
- if (hasThinking) return result("extended_thinking", "infrastructure", "pass", 100, Date.now() - t, "Thinking block present in response");
1826
- if (hasText) return result("extended_thinking", "infrastructure", "warn", 70, Date.now() - t, "Response OK but no thinking block");
1827
- return result("extended_thinking", "infrastructure", "fail", 0, Date.now() - t, "No content in response");
1828
- }
1829
- async function testToolRead(ctx) {
1830
- const t = Date.now();
1831
- const testFile = path11.join(os2.tmpdir(), "kody-test-model-read.txt");
1832
- fs12.writeFileSync(testFile, "KODY_SECRET_CONTENT_42");
1833
- try {
1834
- const conv = await runToolConversation(
1835
- ctx,
1836
- [TOOL_READ],
1837
- `Read the file ${testFile} and tell me what it contains.`,
1838
- (name, input) => {
1839
- if (name === "Read" && input.path === testFile) return "KODY_SECRET_CONTENT_42";
1840
- return "Error: File not found";
1841
- }
1842
- );
1843
- if (conv.error) return result("tool_read", "tool-use", "fail", 0, Date.now() - t, `Error: ${conv.error}`);
1844
- const calledRead = conv.toolCalls.some((tc) => tc.name === "Read");
1845
- const correctPath = conv.toolCalls.some((tc) => tc.name === "Read" && tc.input.path === testFile);
1846
- const mentionsContent = conv.finalText.includes("KODY_SECRET_CONTENT_42") || conv.finalText.includes("42");
1847
- let acc = 0;
1848
- if (calledRead) acc += 30;
1849
- if (correctPath) acc += 30;
1850
- if (mentionsContent) acc += 40;
1851
- return result(
1852
- "tool_read",
1853
- "tool-use",
1854
- acc >= 60 ? "pass" : "fail",
1855
- acc,
1856
- Date.now() - t,
1857
- `Read called: ${calledRead}, correct path: ${correctPath}, content referenced: ${mentionsContent}`,
1858
- { toolSelection: calledRead ? 100 : 0 }
1859
- );
1860
- } finally {
1861
- fs12.rmSync(testFile, { force: true });
1862
- }
1863
- }
1864
- async function testToolEdit(ctx) {
1865
- const t = Date.now();
1866
- const conv = await runToolConversation(
1867
- ctx,
1868
- [TOOL_READ, TOOL_EDIT],
1869
- 'Read the file /tmp/kody-edit-test.txt, then use Edit to replace "hello" with "goodbye" in it.',
1870
- (name, input) => {
1871
- if (name === "Read") return "hello world";
1872
- if (name === "Edit") return "File edited successfully";
1873
- return "Unknown tool";
1874
- }
1875
- );
1876
- if (conv.error) return result("tool_edit", "tool-use", "fail", 0, Date.now() - t, `Error: ${conv.error}`);
1877
- const editCall = conv.toolCalls.find((tc) => tc.name === "Edit");
1878
- let acc = 0;
1879
- if (editCall) {
1880
- acc += 40;
1881
- if (editCall.input.old_string === "hello") acc += 30;
1882
- if (editCall.input.new_string === "goodbye") acc += 30;
1883
- }
1884
- return result(
1885
- "tool_edit",
1886
- "tool-use",
1887
- acc >= 70 ? "pass" : acc > 0 ? "warn" : "fail",
1888
- acc,
1889
- Date.now() - t,
1890
- editCall ? `Edit called with old="${editCall.input.old_string}" new="${editCall.input.new_string}"` : "Edit tool was not called",
1891
- { toolSelection: editCall ? 100 : 0 }
1892
- );
1893
- }
1894
- async function testToolBash(ctx) {
1895
- const t = Date.now();
1896
- const conv = await runToolConversation(
1897
- ctx,
1898
- [TOOL_BASH],
1899
- "Run this exact bash command: echo KODY_BASH_OK",
1900
- (name, input) => {
1901
- if (name === "Bash") return "KODY_BASH_OK\n";
1902
- return "Error";
1903
- }
1904
- );
1905
- if (conv.error) return result("tool_bash", "tool-use", "fail", 0, Date.now() - t, `Error: ${conv.error}`);
1906
- const bashCall = conv.toolCalls.find((tc) => tc.name === "Bash");
1907
- const correctCmd = bashCall && String(bashCall.input.command).includes("echo KODY_BASH_OK");
1908
- const acc = bashCall ? correctCmd ? 100 : 50 : 0;
1909
- return result(
1910
- "tool_bash",
1911
- "tool-use",
1912
- acc >= 50 ? "pass" : "fail",
1913
- acc,
1914
- Date.now() - t,
1915
- bashCall ? `Bash called: ${bashCall.input.command}` : "Bash tool was not called",
1916
- { toolSelection: bashCall ? 100 : 0 }
1917
- );
1918
- }
1919
- async function testImageAttachment(ctx) {
1920
- const t = Date.now();
1921
- const pngData = createRedPng().toString("base64");
1922
- const res = await apiCall(ctx, {
1923
- max_tokens: 100,
1924
- temperature: 0,
1925
- messages: [{
1926
- role: "user",
1927
- content: [
1928
- { type: "image", source: { type: "base64", media_type: "image/png", data: pngData } },
1929
- { type: "text", text: "What color is this image? Reply with just the color name." }
1930
- ]
1931
- }]
1932
- });
1933
- if (!res.ok) return result(
1934
- "image_attachment",
1935
- "tool-use",
1936
- "fail",
1937
- 0,
1938
- Date.now() - t,
1939
- `API error (model may not support vision): ${res.errorMsg?.slice(0, 80)}`
1940
- );
1941
- const text = extractText(res.data).toLowerCase();
1942
- const mentionsRed = text.includes("red");
1943
- const mentionsColor = mentionsRed || text.includes("color") || text.includes("image") || text.includes("pixel");
1944
- const acc = mentionsRed ? 100 : mentionsColor ? 50 : 20;
1945
- return result(
1946
- "image_attachment",
1947
- "tool-use",
1948
- mentionsRed ? "pass" : mentionsColor ? "warn" : "fail",
1949
- acc,
1950
- Date.now() - t,
1951
- `Response: ${text.slice(0, 80)}`
1952
- );
1953
- }
1954
- async function testErrorRecovery(ctx) {
1955
- const t = Date.now();
1956
- let errorGiven = false;
1957
- const conv = await runToolConversation(
1958
- ctx,
1959
- [TOOL_READ, TOOL_BASH],
1960
- "Read the file /tmp/nonexistent-kody-file.txt and tell me what's in it. If the file doesn't exist, say so.",
1961
- (name, input) => {
1962
- if (name === "Read" && !errorGiven) {
1963
- errorGiven = true;
1964
- return "Error: ENOENT: no such file or directory";
1965
- }
1966
- if (name === "Bash") return "ls: /tmp/nonexistent-kody-file.txt: No such file or directory";
1967
- return "Error: File not found";
1968
- }
1969
- );
1970
- if (conv.error) return result("error_recovery", "advanced", "fail", 0, Date.now() - t, `Error: ${conv.error}`);
1971
- const reported = conv.finalText.toLowerCase().includes("not found") || conv.finalText.toLowerCase().includes("doesn't exist") || conv.finalText.toLowerCase().includes("does not exist") || conv.finalText.toLowerCase().includes("no such file");
1972
- const tried = conv.toolCalls.length >= 1;
1973
- const acc = reported ? tried ? 100 : 70 : 20;
1974
- return result(
1975
- "error_recovery",
1976
- "advanced",
1977
- reported ? "pass" : "warn",
1978
- acc,
1979
- Date.now() - t,
1980
- reported ? "Gracefully reported missing file" : `Response: ${conv.finalText.slice(0, 80)}`
1981
- );
1982
- }
1983
- async function testToolMultiStep(ctx) {
1984
- const t = Date.now();
1985
- const r = runClaudeTest(
1986
- ctx,
1987
- "Do these steps in order: 1) Read kody.config.json 2) Tell me the value of git.defaultBranch. Reply with ONLY the branch name, nothing else."
1988
- );
1989
- if (!r.stdout.trim() && r.exitCode !== 0) return result(
1990
- "tool_multi_step",
1991
- "tool-use",
1992
- "fail",
1993
- 0,
1994
- Date.now() - t,
1995
- `CLI failed: ${r.stderr.slice(0, 200) || "no output"}`
1996
- );
1997
- const out = r.stdout.trim().toLowerCase();
1998
- const correct = out.includes("main");
1999
- return result(
2000
- "tool_multi_step",
2001
- "tool-use",
2002
- correct ? "pass" : "fail",
2003
- correct ? 100 : 20,
2004
- Date.now() - t,
2005
- correct ? "Correct: main" : `Got: ${out.slice(0, 80)}`
2006
- );
2007
- }
2008
- async function testPlanStage(ctx) {
2009
- const t = Date.now();
2010
- const wasClean = isGitClean(ctx.projectDir);
2011
- const r = runClaudeTest(ctx, [
2012
- "You are a planning agent. Your ONLY job is to output a markdown plan.",
2013
- "CRITICAL: Do NOT use Edit, Write, or Bash tools. Do NOT modify any files. ONLY use Read, Glob, and Grep for research.",
2014
- "If you modify any files, the system will crash.",
2015
- "",
2016
- "Task: Plan adding a /health endpoint to an Express app.",
2017
- "Output a markdown plan with ## Step N sections. Each step must have File, Change, and Why fields.",
2018
- "Keep it to 3 steps maximum."
2019
- ].join("\n"), [], 12e4);
2020
- const filesModified = wasClean && !isGitClean(ctx.projectDir);
2021
- if (filesModified) revertChanges(ctx.projectDir);
2022
- if (!r.stdout.trim() && r.exitCode !== 0) return result(
2023
- "plan_stage",
2024
- "stage-simulation",
2025
- "fail",
2026
- 0,
2027
- Date.now() - t,
2028
- `CLI failed: ${r.stderr.slice(0, 200) || "no output"}`
2029
- );
2030
- const out = r.stdout;
2031
- const hasStepFormat = /##\s*Step/i.test(out);
2032
- const hasStructure = hasStepFormat || /\*\*File\*\*/i.test(out) && /\*\*Change\*\*/i.test(out);
2033
- const boundary = filesModified ? 0 : 100;
2034
- const format = hasStructure ? 100 : hasStepFormat ? 70 : out.length > 50 ? 30 : 0;
2035
- const acc = Math.round(boundary * 0.6 + format * 0.4);
2036
- const status = filesModified ? "fail" : hasStructure ? "pass" : "warn";
2037
- return result(
2038
- "plan_stage",
2039
- "stage-simulation",
2040
- status,
2041
- acc,
2042
- Date.now() - t,
2043
- filesModified ? "FAIL: Model modified files during plan stage (instruction violation)" : hasStructure ? "Plan output with correct structure, no files modified" : "Output lacks expected ## Step structure",
2044
- { boundaryRespect: boundary, outputFormat: format, instructionCompliance: boundary }
2045
- );
2046
- }
2047
- async function testBuildStage(ctx) {
2048
- const t = Date.now();
2049
- const r = runClaudeTest(ctx, "Add a comment '// kody-build-test' as the very first line of src/logger.ts. That is your only task.");
2050
- const diff = (() => {
2051
- try {
2052
- return execSync2("git diff src/logger.ts", { cwd: ctx.projectDir, encoding: "utf-8", timeout: 5e3 });
2053
- } catch {
2054
- return "";
2055
- }
2056
- })();
2057
- const edited = diff.includes("kody-build-test");
2058
- revertChanges(ctx.projectDir);
2059
- if (!r.stdout.trim() && r.exitCode !== 0 && !edited) return result(
2060
- "build_stage",
2061
- "stage-simulation",
2062
- "fail",
2063
- 0,
2064
- Date.now() - t,
2065
- `CLI failed: ${r.stderr.slice(0, 200) || "no output"}`
2066
- );
2067
- return result(
2068
- "build_stage",
2069
- "stage-simulation",
2070
- edited ? "pass" : "fail",
2071
- edited ? 100 : 0,
2072
- Date.now() - t,
2073
- edited ? "File correctly modified with expected comment" : "File was not modified as expected"
2074
- );
2075
- }
2076
- async function testReviewStage(ctx) {
2077
- const t = Date.now();
2078
- const wasClean = isGitClean(ctx.projectDir);
2079
- const r = runClaudeTest(ctx, [
2080
- "You are a code review agent. Review the file src/logger.ts.",
2081
- "CRITICAL: Do NOT modify any files. Only READ and analyze.",
2082
- "Output your review as markdown with this exact format:",
2083
- "## Summary",
2084
- "<1-2 sentence summary>",
2085
- "## Issues Found",
2086
- "- <issues>",
2087
- "## Verdict",
2088
- "APPROVE or REQUEST_CHANGES"
2089
- ].join("\n"));
2090
- const filesModified = wasClean && !isGitClean(ctx.projectDir);
2091
- if (filesModified) revertChanges(ctx.projectDir);
2092
- if (!r.stdout.trim() && r.exitCode !== 0) return result(
2093
- "review_stage",
2094
- "stage-simulation",
2095
- "fail",
2096
- 0,
2097
- Date.now() - t,
2098
- `CLI failed: ${r.stderr.slice(0, 200) || "no output"}`
2099
- );
2100
- const out = r.stdout;
2101
- const hasVerdict = /verdict/i.test(out);
2102
- const hasSummary = /summary/i.test(out);
2103
- const boundary = filesModified ? 0 : 100;
2104
- const format = (hasVerdict ? 50 : 0) + (hasSummary ? 50 : 0);
2105
- const acc = Math.round(boundary * 0.5 + format * 0.5);
2106
- const status = filesModified ? "fail" : hasVerdict && hasSummary ? "pass" : "warn";
2107
- return result(
2108
- "review_stage",
2109
- "stage-simulation",
2110
- status,
2111
- acc,
2112
- Date.now() - t,
2113
- filesModified ? "FAIL: Model modified files during review (instruction violation)" : `Summary: ${hasSummary}, Verdict: ${hasVerdict}, no files modified`,
2114
- { boundaryRespect: boundary, outputFormat: format }
2115
- );
2116
- }
2117
- async function testMcpTools(ctx) {
2118
- const t = Date.now();
2119
- const mcpConfig = path11.join(os2.tmpdir(), `kody-test-mcp-${Date.now()}.json`);
2120
- const testFile = path11.join(ctx.projectDir, "kody-mcp-compat-test.txt");
2121
- try {
2122
- fs12.writeFileSync(mcpConfig, JSON.stringify({
2123
- mcpServers: {
2124
- filesystem: { command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", ctx.projectDir] }
2125
- }
2126
- }));
2127
- const r = runClaudeTest(
2128
- ctx,
2129
- `Use the MCP filesystem write_file tool to create a file at ${testFile} with the content 'mcp-ok'. Do not use the built-in Write tool.`,
2130
- ["--mcp-config", mcpConfig],
2131
- 12e4
2132
- );
2133
- const created = fs12.existsSync(testFile);
2134
- const content = created ? fs12.readFileSync(testFile, "utf-8").trim() : "";
2135
- const correct = content.includes("mcp-ok");
2136
- return result(
2137
- "mcp_tools",
2138
- "advanced",
2139
- created ? "pass" : "fail",
2140
- correct ? 100 : created ? 70 : 0,
2141
- Date.now() - t,
2142
- created ? `File created, content: ${content.slice(0, 50)}` : `MCP test failed: ${r.stderr.slice(0, 80)}`
2143
- );
2144
- } catch (err) {
2145
- return result("mcp_tools", "advanced", "warn", 0, Date.now() - t, `MCP test error: ${err instanceof Error ? err.message : String(err)}`);
2146
- } finally {
2147
- fs12.rmSync(mcpConfig, { force: true });
2148
- fs12.rmSync(testFile, { force: true });
2149
- revertChanges(ctx.projectDir);
2150
- }
2151
- }
2152
- var TOOL_READ, TOOL_EDIT, TOOL_BASH, CRC_TABLE, ALL_TESTS;
2153
- var init_test_model_tests = __esm({
2154
- "src/cli/test-model-tests.ts"() {
2155
- "use strict";
2156
- TOOL_READ = {
2157
- name: "Read",
2158
- description: "Read a file from the filesystem",
2159
- input_schema: {
2160
- type: "object",
2161
- properties: { path: { type: "string", description: "Absolute file path" } },
2162
- required: ["path"]
2163
- }
2164
- };
2165
- TOOL_EDIT = {
2166
- name: "Edit",
2167
- description: "Replace old_string with new_string in a file",
2168
- input_schema: {
2169
- type: "object",
2170
- properties: {
2171
- file_path: { type: "string" },
2172
- old_string: { type: "string" },
2173
- new_string: { type: "string" }
2174
- },
2175
- required: ["file_path", "old_string", "new_string"]
2176
- }
2177
- };
2178
- TOOL_BASH = {
2179
- name: "Bash",
2180
- description: "Execute a bash command and return output",
2181
- input_schema: {
2182
- type: "object",
2183
- properties: { command: { type: "string", description: "The command to run" } },
2184
- required: ["command"]
2185
- }
2186
- };
2187
- CRC_TABLE = new Uint32Array(256);
2188
- for (let n = 0; n < 256; n++) {
2189
- let c = n;
2190
- for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
2191
- CRC_TABLE[n] = c >>> 0;
2192
- }
2193
- ALL_TESTS = [
2194
- // Infrastructure
2195
- { name: "extended_thinking", category: "infrastructure", description: "Extended thinking parameter support", run: testExtendedThinking },
2196
- // Basic
2197
- { name: "simple_prompt", category: "basic", description: "Basic text prompt and response", run: testSimplePrompt },
2198
- { name: "json_output", category: "basic", description: "JSON-only output constraint", run: testJsonOutput },
2199
- { name: "system_prompt_rules", category: "basic", description: "Multi-rule system prompt adherence", run: testSystemPromptRules },
2200
- // Tool use
2201
- { name: "tool_read", category: "tool-use", description: "Read tool: file reading", run: testToolRead },
2202
- { name: "tool_edit", category: "tool-use", description: "Edit tool: old/new string replacement", run: testToolEdit },
2203
- { name: "tool_bash", category: "tool-use", description: "Bash tool: command execution", run: testToolBash },
2204
- { name: "tool_multi_step", category: "tool-use", description: "Multi-step tool chain via CLI", run: testToolMultiStep },
2205
- { name: "image_attachment", category: "tool-use", description: "Vision: image content processing", run: testImageAttachment },
2206
- // Stage simulation
2207
- { name: "plan_stage", category: "stage-simulation", description: "Plan stage: read-only research + structured output", run: testPlanStage },
2208
- { name: "build_stage", category: "stage-simulation", description: "Build stage: code editing", run: testBuildStage },
2209
- { name: "review_stage", category: "stage-simulation", description: "Review stage: read-only + structured verdict", run: testReviewStage },
2210
- // Advanced
2211
- { name: "mcp_tools", category: "advanced", description: "MCP server tool integration", run: testMcpTools },
2212
- { name: "error_recovery", category: "advanced", description: "Graceful error handling on tool failure", run: testErrorRecovery }
2213
- ];
2214
- }
2215
- });
2216
-
2217
- // src/cli/test-model-report.ts
2218
- function pad(str, len) {
2219
- return str.padEnd(len);
2220
- }
2221
- function fmtDuration(ms) {
2222
- return `${(ms / 1e3).toFixed(1)}s`;
2223
- }
2224
- function formatReport(report) {
2225
- const W = 74;
2226
- const lines = [];
2227
- lines.push("=".repeat(W));
2228
- lines.push("");
2229
- lines.push(" Model Compatibility Report");
2230
- lines.push(` Provider: ${report.provider} | Model: ${report.model}`);
2231
- lines.push(` Date: ${report.timestamp}`);
2232
- lines.push(` Duration: ${fmtDuration(report.totalDurationMs)}`);
2233
- lines.push("");
2234
- lines.push("-".repeat(W));
2235
- for (const cat of CATEGORY_ORDER) {
2236
- const catResults = report.results.filter((r) => r.category === cat);
2237
- if (catResults.length === 0) continue;
2238
- lines.push("");
2239
- lines.push(` ${CATEGORY_LABELS[cat]}`);
2240
- lines.push("");
2241
- for (const r of catResults) {
2242
- const icon = r.status === "pass" ? "+" : r.status === "fail" ? "x" : "!";
2243
- const name = pad(r.name, 28);
2244
- const status = pad(r.status.toUpperCase(), 6);
2245
- const acc = pad(`${r.accuracy}%`, 5);
2246
- const dur = fmtDuration(r.durationMs);
2247
- lines.push(` [${icon}] ${name} ${status} ${acc} ${dur}`);
2248
- if (r.status !== "pass" && r.detail) {
2249
- lines.push(` ${r.detail.slice(0, W - 8)}`);
2250
- }
2251
- }
2252
- }
2253
- const passed = report.results.filter((r) => r.status === "pass").length;
2254
- const failed = report.results.filter((r) => r.status === "fail").length;
2255
- const warned = report.results.filter((r) => r.status === "warn").length;
2256
- const total = report.results.length;
2257
- const avgAccuracy = total > 0 ? Math.round(report.results.reduce((s, r) => s + r.accuracy, 0) / total) : 0;
2258
- lines.push("");
2259
- lines.push("-".repeat(W));
2260
- lines.push("");
2261
- lines.push(` RESULTS: ${passed}/${total} PASS | ${failed} FAIL | ${warned} WARN`);
2262
- lines.push(` OVERALL ACCURACY: ${avgAccuracy}%`);
2263
- lines.push(` drop_params required: ${report.dropParamsRequired ? "YES" : "NO"}`);
2264
- lines.push("");
2265
- lines.push(" ACCURACY BY CATEGORY:");
2266
- for (const cat of CATEGORY_ORDER) {
2267
- const cr = report.results.filter((r) => r.category === cat);
2268
- if (cr.length === 0) continue;
2269
- const avg = Math.round(cr.reduce((s, r) => s + r.accuracy, 0) / cr.length);
2270
- lines.push(` ${pad(CATEGORY_LABELS[cat], 22)} ${avg}%`);
2271
- }
2272
- lines.push("");
2273
- lines.push(" RECOMMENDATION:");
2274
- for (const line of getRecommendation(report)) {
2275
- lines.push(` ${line}`);
2276
- }
2277
- lines.push("");
2278
- lines.push("=".repeat(W));
2279
- return lines.join("\n");
2280
- }
2281
- function getRecommendation(report) {
2282
- const lines = [];
2283
- const failedTests = report.results.filter((r) => r.status === "fail");
2284
- const avg = report.results.length > 0 ? Math.round(report.results.reduce((s, r) => s + r.accuracy, 0) / report.results.length) : 0;
2285
- if (avg >= 90 && failedTests.length === 0) {
2286
- lines.push("[+] Fully compatible -- suitable for all pipeline stages");
2287
- return lines;
2288
- }
2289
- const stageResults = report.results.filter((r) => r.category === "stage-simulation");
2290
- const workingStages = stageResults.filter((r) => r.status === "pass").map((r) => r.name.replace("_stage", ""));
2291
- const failingStages = stageResults.filter((r) => r.status !== "pass").map((r) => r.name.replace("_stage", ""));
2292
- if (workingStages.length > 0) {
2293
- lines.push(`[+] Suitable for: ${workingStages.join(", ")} stages`);
2294
- }
2295
- if (failingStages.length > 0) {
2296
- lines.push(`[x] Not recommended for: ${failingStages.join(", ")} stages`);
2297
- }
2298
- if (failedTests.length > 0) {
2299
- lines.push("");
2300
- lines.push("Failed tests:");
2301
- for (const t of failedTests) {
2302
- lines.push(` - ${t.name}: ${t.detail.slice(0, 60)}`);
2303
- }
2304
- }
2305
- return lines;
2306
- }
2307
- var CATEGORY_ORDER, CATEGORY_LABELS;
2308
- var init_test_model_report = __esm({
2309
- "src/cli/test-model-report.ts"() {
2310
- "use strict";
2311
- CATEGORY_ORDER = ["infrastructure", "basic", "tool-use", "stage-simulation", "advanced"];
2312
- CATEGORY_LABELS = {
2313
- infrastructure: "INFRASTRUCTURE",
2314
- basic: "BASIC CAPABILITIES",
2315
- "tool-use": "TOOL USE",
2316
- "stage-simulation": "STAGE SIMULATION",
2317
- advanced: "ADVANCED"
2318
- };
2319
- }
2320
- });
2321
-
2322
- // src/cli/test-model-command.ts
2323
- var test_model_command_exports = {};
2324
- __export(test_model_command_exports, {
2325
- runTestModelCommand: () => runTestModelCommand
2326
- });
2327
- import * as fs13 from "fs";
2328
- import * as os3 from "os";
2329
- import * as path12 from "path";
2330
- import { execFileSync as execFileSync10 } from "child_process";
2331
- function parseTestModelArgs() {
2332
- const args2 = process.argv.slice(3);
2333
- function getArg3(flag) {
2334
- const idx = args2.indexOf(flag);
2335
- if (idx !== -1 && args2[idx + 1] && !args2[idx + 1].startsWith("--")) return args2[idx + 1];
2336
- return void 0;
2337
- }
2338
- const hasFlag3 = (f) => args2.includes(f);
2339
- if (hasFlag3("--help") || hasFlag3("-h")) {
2340
- logger.info([
2341
- "Usage: kody test-model --provider <provider> --model <model> --key <api-key> [options]",
2342
- "",
2343
- "Options:",
2344
- " --provider LLM provider name (e.g. gemini, openai, mistral)",
2345
- " --model Model identifier (e.g. gemini-2.5-flash)",
2346
- " --key API key for the provider",
2347
- " --key-env Read API key from this environment variable",
2348
- " --skip-proxy Use an already-running LiteLLM proxy (don't start one)",
2349
- " --litellm-url LiteLLM proxy URL (default: http://localhost:4099)",
2350
- " --filter Comma-separated test names to run (default: all)",
2351
- " --list List all available tests and exit"
2352
- ].join("\n"));
2353
- process.exit(0);
2354
- }
2355
- if (hasFlag3("--list")) {
2356
- for (const t of ALL_TESTS) {
2357
- logger.info(` ${t.name.padEnd(24)} [${t.category}] ${t.description}`);
2358
- }
2359
- process.exit(0);
2360
- }
2361
- const provider = getArg3("--provider");
2362
- const model = getArg3("--model");
2363
- const key = getArg3("--key");
2364
- const keyEnv = getArg3("--key-env");
2365
- if (!provider || !model) {
2366
- logger.error("Required: --provider <provider> --model <model> --key <key>");
2367
- logger.error("Run with --help for usage.");
2368
- process.exit(1);
2369
- }
2370
- let apiKey = key;
2371
- if (!apiKey && keyEnv) apiKey = process.env[keyEnv];
2372
- if (!apiKey) {
2373
- logger.error("API key required: use --key <value> or --key-env <ENV_VAR>");
2374
- process.exit(1);
2375
- }
2376
- return {
2377
- provider,
2378
- model,
2379
- apiKey,
2380
- proxyUrl: getArg3("--litellm-url") ?? TEST_URL,
2381
- skipProxy: hasFlag3("--skip-proxy"),
2382
- filter: getArg3("--filter")?.split(",")
2383
- };
2384
- }
2385
- function generateConfig(provider, model, dropParams) {
2386
- const lines = [];
2387
- if (dropParams) {
2388
- lines.push("litellm_settings:");
2389
- lines.push(" drop_params: true");
2390
- lines.push("");
2391
- }
2392
- lines.push("model_list:");
2393
- lines.push(` - model_name: ${model}`);
2394
- lines.push(" litellm_params:");
2395
- lines.push(` model: ${provider}/${model}`);
2396
- lines.push(" api_key: os.environ/ANTHROPIC_COMPATIBLE_API_KEY");
2397
- return lines.join("\n") + "\n";
2398
- }
2399
- async function startProxy(config, url) {
2400
- try {
2401
- execFileSync10("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
2402
- } catch {
2403
- try {
2404
- execFileSync10("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
2405
- } catch {
2406
- logger.error("litellm not installed. Install: pip install 'litellm[proxy]'");
2407
- return null;
2408
- }
2409
- }
2410
- fs13.writeFileSync(CONFIG_PATH, config);
2411
- const portMatch = url.match(/:(\d+)/);
2412
- const port = portMatch ? portMatch[1] : "4099";
2413
- const { spawn: spawn2 } = await import("child_process");
2414
- const child = spawn2("litellm", ["--config", CONFIG_PATH, "--port", port], {
2415
- stdio: ["ignore", "pipe", "pipe"],
2416
- detached: true,
2417
- env: process.env
2418
- });
2419
- for (let i = 0; i < 30; i++) {
2420
- await delay(2e3);
2421
- if (await checkLitellmHealth(url)) {
2422
- logger.info(`LiteLLM proxy ready at ${url}`);
2423
- return child;
2424
- }
2425
- }
2426
- child.kill();
2427
- return null;
2428
- }
2429
- async function quickApiTest(url, model, apiKey) {
2430
- try {
2431
- const res = await fetch(`${url}/v1/messages`, {
2432
- method: "POST",
2433
- headers: { "Content-Type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
2434
- body: JSON.stringify({
2435
- model,
2436
- max_tokens: 10,
2437
- messages: [{ role: "user", content: "Say ok" }],
2438
- context_management: { policy: "smart" }
2439
- }),
2440
- signal: AbortSignal.timeout(3e4)
2441
- });
2442
- if (!res.ok) {
2443
- const body = await res.text();
2444
- return { ok: false, error: body.slice(0, 200) };
2445
- }
2446
- return { ok: true };
2447
- } catch (err) {
2448
- return { ok: false, error: String(err) };
2449
- }
2450
- }
2451
- function delay(ms) {
2452
- return new Promise((resolve5) => setTimeout(resolve5, ms));
2453
- }
2454
- async function runTestModelCommand() {
2455
- const opts = parseTestModelArgs();
2456
- const startTime = Date.now();
2457
- logger.info(`Testing model compatibility: ${opts.provider}/${opts.model}`);
2458
- logger.info("");
2459
- let proxyProcess = null;
2460
- let dropParamsRequired = false;
2461
- const cleanup = () => {
2462
- if (proxyProcess) {
2463
- proxyProcess.kill();
2464
- proxyProcess = null;
2465
- }
2466
- fs13.rmSync(CONFIG_PATH, { force: true });
2467
- };
2468
- process.on("SIGINT", () => {
2469
- cleanup();
2470
- process.exit(1);
2471
- });
2472
- process.on("SIGTERM", () => {
2473
- cleanup();
2474
- process.exit(1);
2475
- });
2476
- try {
2477
- if (!opts.skipProxy) {
2478
- process.env.ANTHROPIC_COMPATIBLE_API_KEY = opts.apiKey;
2479
- logger.info("Starting LiteLLM proxy (without drop_params)...");
2480
- proxyProcess = await startProxy(generateConfig(opts.provider, opts.model, false), opts.proxyUrl);
2481
- if (!proxyProcess) {
2482
- logger.error("Failed to start LiteLLM proxy");
2483
- process.exit(1);
2484
- }
2485
- const quickRes = await quickApiTest(opts.proxyUrl, opts.model, opts.apiKey);
2486
- if (!quickRes.ok) {
2487
- logger.info("Model needs drop_params: true -- restarting proxy...");
2488
- proxyProcess.kill();
2489
- proxyProcess = null;
2490
- await delay(2e3);
2491
- proxyProcess = await startProxy(generateConfig(opts.provider, opts.model, true), opts.proxyUrl);
2492
- dropParamsRequired = true;
2493
- if (!proxyProcess) {
2494
- logger.error("Failed to start LiteLLM proxy with drop_params");
2495
- process.exit(1);
2496
- }
2497
- const retry = await quickApiTest(opts.proxyUrl, opts.model, opts.apiKey);
2498
- if (!retry.ok) {
2499
- logger.error(`Model not accessible: ${retry.error}`);
2500
- process.exit(1);
2501
- }
2502
- logger.info("Proxy restarted with drop_params: true");
2503
- } else {
2504
- logger.info("drop_params not required");
2505
- }
2506
- } else {
2507
- logger.info(`Using existing proxy at ${opts.proxyUrl}`);
2508
- }
2509
- const tests = opts.filter ? ALL_TESTS.filter((t) => opts.filter.includes(t.name)) : ALL_TESTS;
2510
- logger.info(`Running ${tests.length} compatibility tests...`);
2511
- logger.info("");
2512
- const ctx = { proxyUrl: opts.proxyUrl, model: opts.model, apiKey: opts.apiKey, projectDir: process.cwd() };
2513
- const results = [];
2514
- for (const test of tests) {
2515
- process.stdout.write(` ${test.name.padEnd(28)} `);
2516
- try {
2517
- const r = await test.run(ctx);
2518
- results.push(r);
2519
- const icon = r.status === "pass" ? "+" : r.status === "fail" ? "x" : "!";
2520
- logger.info(`[${icon}] ${r.status.toUpperCase()} ${r.accuracy}% (${(r.durationMs / 1e3).toFixed(1)}s)`);
2521
- } catch (err) {
2522
- const r = {
2523
- name: test.name,
2524
- category: test.category,
2525
- status: "fail",
2526
- accuracy: 0,
2527
- durationMs: 0,
2528
- detail: `Crash: ${err instanceof Error ? err.message : String(err)}`
2529
- };
2530
- results.push(r);
2531
- logger.info("[x] CRASH");
2532
- }
2533
- }
2534
- const report = {
2535
- provider: opts.provider,
2536
- model: opts.model,
2537
- results,
2538
- totalDurationMs: Date.now() - startTime,
2539
- dropParamsRequired,
2540
- timestamp: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
2541
- };
2542
- console.log("");
2543
- console.log(formatReport(report));
2544
- const failed = results.filter((r) => r.status === "fail").length;
2545
- process.exit(failed > 0 ? 1 : 0);
2546
- } finally {
2547
- cleanup();
2548
- }
2549
- }
2550
- var TEST_PORT, TEST_URL, CONFIG_PATH;
2551
- var init_test_model_command = __esm({
2552
- "src/cli/test-model-command.ts"() {
2553
- "use strict";
2554
- init_logger();
2555
- init_litellm();
2556
- init_test_model_tests();
2557
- init_test_model_report();
2558
- TEST_PORT = 4099;
2559
- TEST_URL = `http://localhost:${TEST_PORT}`;
2560
- CONFIG_PATH = path12.join(os3.tmpdir(), "kody-test-model-config.yaml");
2561
- }
2562
- });
2563
-
2564
1559
  // src/ci/parse-inputs.ts
2565
1560
  var parse_inputs_exports = {};
2566
1561
  __export(parse_inputs_exports, {
@@ -2568,16 +1563,16 @@ __export(parse_inputs_exports, {
2568
1563
  runCiParse: () => runCiParse,
2569
1564
  writeOutputs: () => writeOutputs
2570
1565
  });
2571
- import * as fs14 from "fs";
1566
+ import * as fs12 from "fs";
2572
1567
  function generateTimestamp() {
2573
1568
  const now = /* @__PURE__ */ new Date();
2574
- const pad2 = (n) => String(n).padStart(2, "0");
1569
+ const pad = (n) => String(n).padStart(2, "0");
2575
1570
  const y = String(now.getFullYear()).slice(2);
2576
- const m = pad2(now.getMonth() + 1);
2577
- const d = pad2(now.getDate());
2578
- const H = pad2(now.getHours());
2579
- const M = pad2(now.getMinutes());
2580
- const S = pad2(now.getSeconds());
1571
+ const m = pad(now.getMonth() + 1);
1572
+ const d = pad(now.getDate());
1573
+ const H = pad(now.getHours());
1574
+ const M = pad(now.getMinutes());
1575
+ const S = pad(now.getSeconds());
2581
1576
  return `${y}${m}${d}-${H}${M}${S}`;
2582
1577
  }
2583
1578
  function parseCommentInputs() {
@@ -2729,40 +1724,40 @@ function parseCommentInputs() {
2729
1724
  trigger_type: "comment"
2730
1725
  };
2731
1726
  }
2732
- function writeOutputs(result2) {
1727
+ function writeOutputs(result) {
2733
1728
  const outputFile = process.env.GITHUB_OUTPUT;
2734
1729
  function output(key, value) {
2735
1730
  if (outputFile) {
2736
1731
  if (value.includes("\n")) {
2737
- fs14.appendFileSync(outputFile, `${key}<<KODY_EOF
1732
+ fs12.appendFileSync(outputFile, `${key}<<KODY_EOF
2738
1733
  ${value}
2739
1734
  KODY_EOF
2740
1735
  `);
2741
1736
  } else {
2742
- fs14.appendFileSync(outputFile, `${key}=${value}
1737
+ fs12.appendFileSync(outputFile, `${key}=${value}
2743
1738
  `);
2744
1739
  }
2745
1740
  }
2746
1741
  const display = value.includes("\n") ? value.split("\n")[0] + "..." : value;
2747
1742
  console.log(`${key}=${display}`);
2748
1743
  }
2749
- output("task_id", result2.task_id);
2750
- output("mode", result2.mode);
2751
- output("from_stage", result2.from_stage);
2752
- output("issue_number", result2.issue_number);
2753
- output("pr_number", result2.pr_number);
2754
- output("feedback", result2.feedback);
2755
- output("complexity", result2.complexity);
2756
- output("ci_run_id", result2.ci_run_id);
2757
- output("ticket_id", result2.ticket_id);
2758
- output("prd_file", result2.prd_file);
2759
- output("dry_run", result2.dry_run ? "true" : "false");
2760
- output("valid", result2.valid ? "true" : "false");
2761
- output("trigger_type", result2.trigger_type);
1744
+ output("task_id", result.task_id);
1745
+ output("mode", result.mode);
1746
+ output("from_stage", result.from_stage);
1747
+ output("issue_number", result.issue_number);
1748
+ output("pr_number", result.pr_number);
1749
+ output("feedback", result.feedback);
1750
+ output("complexity", result.complexity);
1751
+ output("ci_run_id", result.ci_run_id);
1752
+ output("ticket_id", result.ticket_id);
1753
+ output("prd_file", result.prd_file);
1754
+ output("dry_run", result.dry_run ? "true" : "false");
1755
+ output("valid", result.valid ? "true" : "false");
1756
+ output("trigger_type", result.trigger_type);
2762
1757
  }
2763
1758
  function runCiParse() {
2764
- const result2 = parseCommentInputs();
2765
- writeOutputs(result2);
1759
+ const result = parseCommentInputs();
1760
+ writeOutputs(result);
2766
1761
  }
2767
1762
  var VALID_MODES;
2768
1763
  var init_parse_inputs = __esm({
@@ -2864,7 +1859,7 @@ var init_definitions = __esm({
2864
1859
  });
2865
1860
 
2866
1861
  // src/git-utils.ts
2867
- import { execFileSync as execFileSync11 } from "child_process";
1862
+ import { execFileSync as execFileSync10 } from "child_process";
2868
1863
  function getHookSafeEnv() {
2869
1864
  if (!_hookSafeEnv) {
2870
1865
  _hookSafeEnv = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
@@ -2872,7 +1867,7 @@ function getHookSafeEnv() {
2872
1867
  return _hookSafeEnv;
2873
1868
  }
2874
1869
  function git(args2, options) {
2875
- return execFileSync11("git", args2, {
1870
+ return execFileSync10("git", args2, {
2876
1871
  encoding: "utf-8",
2877
1872
  timeout: options?.timeout ?? 3e4,
2878
1873
  cwd: options?.cwd,
@@ -3058,22 +2053,22 @@ var init_git_utils = __esm({
3058
2053
  });
3059
2054
 
3060
2055
  // src/pipeline/state.ts
3061
- import * as fs15 from "fs";
3062
- import * as path13 from "path";
2056
+ import * as fs13 from "fs";
2057
+ import * as path11 from "path";
3063
2058
  function loadState(taskId, taskDir) {
3064
- const p = path13.join(taskDir, "status.json");
3065
- if (!fs15.existsSync(p)) return null;
2059
+ const p = path11.join(taskDir, "status.json");
2060
+ if (!fs13.existsSync(p)) return null;
3066
2061
  try {
3067
- const result2 = parseJsonSafe(
3068
- fs15.readFileSync(p, "utf-8"),
2062
+ const result = parseJsonSafe(
2063
+ fs13.readFileSync(p, "utf-8"),
3069
2064
  ["taskId", "state", "stages", "createdAt", "updatedAt"]
3070
2065
  );
3071
- if (!result2.ok) {
3072
- logger.warn(` Corrupt status.json: ${result2.error}`);
2066
+ if (!result.ok) {
2067
+ logger.warn(` Corrupt status.json: ${result.error}`);
3073
2068
  return null;
3074
2069
  }
3075
- if (result2.data.taskId !== taskId) return null;
3076
- return result2.data;
2070
+ if (result.data.taskId !== taskId) return null;
2071
+ return result.data;
3077
2072
  } catch {
3078
2073
  return null;
3079
2074
  }
@@ -3083,10 +2078,10 @@ function writeState(state, taskDir) {
3083
2078
  ...state,
3084
2079
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3085
2080
  };
3086
- const target = path13.join(taskDir, "status.json");
2081
+ const target = path11.join(taskDir, "status.json");
3087
2082
  const tmp = target + ".tmp";
3088
- fs15.writeFileSync(tmp, JSON.stringify(updated, null, 2));
3089
- fs15.renameSync(tmp, target);
2083
+ fs13.writeFileSync(tmp, JSON.stringify(updated, null, 2));
2084
+ fs13.renameSync(tmp, target);
3090
2085
  return updated;
3091
2086
  }
3092
2087
  function initState(taskId) {
@@ -3127,16 +2122,16 @@ var init_complexity = __esm({
3127
2122
  });
3128
2123
 
3129
2124
  // src/memory.ts
3130
- import * as fs16 from "fs";
3131
- import * as path14 from "path";
2125
+ import * as fs14 from "fs";
2126
+ import * as path12 from "path";
3132
2127
  function readProjectMemory(projectDir) {
3133
- const memoryDir = path14.join(projectDir, ".kody", "memory");
3134
- if (!fs16.existsSync(memoryDir)) return "";
3135
- const files = fs16.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
2128
+ const memoryDir = path12.join(projectDir, ".kody", "memory");
2129
+ if (!fs14.existsSync(memoryDir)) return "";
2130
+ const files = fs14.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
3136
2131
  if (files.length === 0) return "";
3137
2132
  const sections = [];
3138
2133
  for (const file of files) {
3139
- const content = fs16.readFileSync(path14.join(memoryDir, file), "utf-8").trim();
2134
+ const content = fs14.readFileSync(path12.join(memoryDir, file), "utf-8").trim();
3140
2135
  if (content) {
3141
2136
  sections.push(`## ${file.replace(".md", "")}
3142
2137
  ${content}`);
@@ -3155,8 +2150,8 @@ var init_memory = __esm({
3155
2150
  });
3156
2151
 
3157
2152
  // src/context-tiers.ts
3158
- import * as fs17 from "fs";
3159
- import * as path15 from "path";
2153
+ import * as fs15 from "fs";
2154
+ import * as path13 from "path";
3160
2155
  function estimateTokens(text) {
3161
2156
  return Math.ceil(text.length / 4);
3162
2157
  }
@@ -3183,8 +2178,8 @@ function generateL0(content, filename) {
3183
2178
  break;
3184
2179
  }
3185
2180
  }
3186
- const result2 = parts.join("\n");
3187
- return result2.slice(0, L0_MAX_CHARS);
2181
+ const result = parts.join("\n");
2182
+ return result.slice(0, L0_MAX_CHARS);
3188
2183
  }
3189
2184
  function generateL0Json(content) {
3190
2185
  try {
@@ -3226,8 +2221,8 @@ function generateL1(content, filename) {
3226
2221
  inSection = false;
3227
2222
  }
3228
2223
  }
3229
- const result2 = parts.join("\n");
3230
- return result2.slice(0, L1_MAX_CHARS);
2224
+ const result = parts.join("\n");
2225
+ return result.slice(0, L1_MAX_CHARS);
3231
2226
  }
3232
2227
  function generateL1Json(content) {
3233
2228
  try {
@@ -3247,7 +2242,7 @@ function generateL1Json(content) {
3247
2242
  }
3248
2243
  }
3249
2244
  function getTieredContent(filePath, content) {
3250
- const key = path15.basename(filePath);
2245
+ const key = path13.basename(filePath);
3251
2246
  return {
3252
2247
  source: filePath,
3253
2248
  L0: generateL0(content, key),
@@ -3259,15 +2254,15 @@ function selectTier(tiered, tier) {
3259
2254
  return tiered[tier];
3260
2255
  }
3261
2256
  function readProjectMemoryTiered(projectDir, tier) {
3262
- const memoryDir = path15.join(projectDir, ".kody", "memory");
3263
- if (!fs17.existsSync(memoryDir)) return "";
3264
- const files = fs17.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
2257
+ const memoryDir = path13.join(projectDir, ".kody", "memory");
2258
+ if (!fs15.existsSync(memoryDir)) return "";
2259
+ const files = fs15.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
3265
2260
  if (files.length === 0) return "";
3266
2261
  const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
3267
2262
  const sections = [];
3268
2263
  for (const file of files) {
3269
- const filePath = path15.join(memoryDir, file);
3270
- const content = fs17.readFileSync(filePath, "utf-8").trim();
2264
+ const filePath = path13.join(memoryDir, file);
2265
+ const content = fs15.readFileSync(filePath, "utf-8").trim();
3271
2266
  if (!content) continue;
3272
2267
  const tiered = getTieredContent(filePath, content);
3273
2268
  const selected = selectTier(tiered, tier);
@@ -3290,9 +2285,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
3290
2285
  `;
3291
2286
  context += `Task Directory: ${taskDir}
3292
2287
  `;
3293
- const taskMdPath = path15.join(taskDir, "task.md");
3294
- if (fs17.existsSync(taskMdPath)) {
3295
- const content = fs17.readFileSync(taskMdPath, "utf-8");
2288
+ const taskMdPath = path13.join(taskDir, "task.md");
2289
+ if (fs15.existsSync(taskMdPath)) {
2290
+ const content = fs15.readFileSync(taskMdPath, "utf-8");
3296
2291
  const selected = selectContent(taskMdPath, content, policy.taskDescription);
3297
2292
  const label = tierLabel("Task Description", policy.taskDescription);
3298
2293
  context += `
@@ -3300,9 +2295,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
3300
2295
  ${selected}
3301
2296
  `;
3302
2297
  }
3303
- const taskJsonPath = path15.join(taskDir, "task.json");
3304
- if (fs17.existsSync(taskJsonPath)) {
3305
- const content = fs17.readFileSync(taskJsonPath, "utf-8");
2298
+ const taskJsonPath = path13.join(taskDir, "task.json");
2299
+ if (fs15.existsSync(taskJsonPath)) {
2300
+ const content = fs15.readFileSync(taskJsonPath, "utf-8");
3306
2301
  if (policy.taskClassification === "L2") {
3307
2302
  try {
3308
2303
  const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
@@ -3328,9 +2323,9 @@ ${selected}
3328
2323
  }
3329
2324
  }
3330
2325
  }
3331
- const specPath = path15.join(taskDir, "spec.md");
3332
- if (fs17.existsSync(specPath)) {
3333
- const content = fs17.readFileSync(specPath, "utf-8");
2326
+ const specPath = path13.join(taskDir, "spec.md");
2327
+ if (fs15.existsSync(specPath)) {
2328
+ const content = fs15.readFileSync(specPath, "utf-8");
3334
2329
  const selected = selectContent(specPath, content, policy.spec);
3335
2330
  const label = tierLabel("Spec", policy.spec);
3336
2331
  context += `
@@ -3338,9 +2333,9 @@ ${selected}
3338
2333
  ${selected}
3339
2334
  `;
3340
2335
  }
3341
- const planPath = path15.join(taskDir, "plan.md");
3342
- if (fs17.existsSync(planPath)) {
3343
- const content = fs17.readFileSync(planPath, "utf-8");
2336
+ const planPath = path13.join(taskDir, "plan.md");
2337
+ if (fs15.existsSync(planPath)) {
2338
+ const content = fs15.readFileSync(planPath, "utf-8");
3344
2339
  const selected = selectContent(planPath, content, policy.plan);
3345
2340
  const label = tierLabel("Plan", policy.plan);
3346
2341
  context += `
@@ -3348,9 +2343,9 @@ ${selected}
3348
2343
  ${selected}
3349
2344
  `;
3350
2345
  }
3351
- const contextMdPath = path15.join(taskDir, "context.md");
3352
- if (fs17.existsSync(contextMdPath)) {
3353
- const content = fs17.readFileSync(contextMdPath, "utf-8");
2346
+ const contextMdPath = path13.join(taskDir, "context.md");
2347
+ if (fs15.existsSync(contextMdPath)) {
2348
+ const content = fs15.readFileSync(contextMdPath, "utf-8");
3354
2349
  const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
3355
2350
  const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
3356
2351
  context += `
@@ -3436,24 +2431,24 @@ var init_context_tiers = __esm({
3436
2431
  });
3437
2432
 
3438
2433
  // src/context.ts
3439
- import * as fs18 from "fs";
3440
- import * as path16 from "path";
2434
+ import * as fs16 from "fs";
2435
+ import * as path14 from "path";
3441
2436
  function readPromptFile(stageName, projectDir) {
3442
2437
  if (projectDir) {
3443
- const stepFile = path16.join(projectDir, ".kody", "steps", `${stageName}.md`);
3444
- if (fs18.existsSync(stepFile)) {
3445
- return fs18.readFileSync(stepFile, "utf-8");
2438
+ const stepFile = path14.join(projectDir, ".kody", "steps", `${stageName}.md`);
2439
+ if (fs16.existsSync(stepFile)) {
2440
+ return fs16.readFileSync(stepFile, "utf-8");
3446
2441
  }
3447
2442
  console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
3448
2443
  }
3449
2444
  const scriptDir = new URL(".", import.meta.url).pathname;
3450
2445
  const candidates = [
3451
- path16.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
3452
- path16.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
2446
+ path14.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
2447
+ path14.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
3453
2448
  ];
3454
2449
  for (const candidate of candidates) {
3455
- if (fs18.existsSync(candidate)) {
3456
- return fs18.readFileSync(candidate, "utf-8");
2450
+ if (fs16.existsSync(candidate)) {
2451
+ return fs16.readFileSync(candidate, "utf-8");
3457
2452
  }
3458
2453
  }
3459
2454
  throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
@@ -3465,18 +2460,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
3465
2460
  `;
3466
2461
  context += `Task Directory: ${taskDir}
3467
2462
  `;
3468
- const taskMdPath = path16.join(taskDir, "task.md");
3469
- if (fs18.existsSync(taskMdPath)) {
3470
- const taskMd = fs18.readFileSync(taskMdPath, "utf-8");
2463
+ const taskMdPath = path14.join(taskDir, "task.md");
2464
+ if (fs16.existsSync(taskMdPath)) {
2465
+ const taskMd = fs16.readFileSync(taskMdPath, "utf-8");
3471
2466
  context += `
3472
2467
  ## Task Description
3473
2468
  ${taskMd}
3474
2469
  `;
3475
2470
  }
3476
- const taskJsonPath = path16.join(taskDir, "task.json");
3477
- if (fs18.existsSync(taskJsonPath)) {
2471
+ const taskJsonPath = path14.join(taskDir, "task.json");
2472
+ if (fs16.existsSync(taskJsonPath)) {
3478
2473
  try {
3479
- const taskDef = JSON.parse(fs18.readFileSync(taskJsonPath, "utf-8"));
2474
+ const taskDef = JSON.parse(fs16.readFileSync(taskJsonPath, "utf-8"));
3480
2475
  context += `
3481
2476
  ## Task Classification
3482
2477
  `;
@@ -3489,27 +2484,27 @@ ${taskMd}
3489
2484
  } catch {
3490
2485
  }
3491
2486
  }
3492
- const specPath = path16.join(taskDir, "spec.md");
3493
- if (fs18.existsSync(specPath)) {
3494
- const spec = fs18.readFileSync(specPath, "utf-8");
2487
+ const specPath = path14.join(taskDir, "spec.md");
2488
+ if (fs16.existsSync(specPath)) {
2489
+ const spec = fs16.readFileSync(specPath, "utf-8");
3495
2490
  const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
3496
2491
  context += `
3497
2492
  ## Spec Summary
3498
2493
  ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
3499
2494
  `;
3500
2495
  }
3501
- const planPath = path16.join(taskDir, "plan.md");
3502
- if (fs18.existsSync(planPath)) {
3503
- const plan = fs18.readFileSync(planPath, "utf-8");
2496
+ const planPath = path14.join(taskDir, "plan.md");
2497
+ if (fs16.existsSync(planPath)) {
2498
+ const plan = fs16.readFileSync(planPath, "utf-8");
3504
2499
  const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
3505
2500
  context += `
3506
2501
  ## Plan Summary
3507
2502
  ${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
3508
2503
  `;
3509
2504
  }
3510
- const contextMdPath = path16.join(taskDir, "context.md");
3511
- if (fs18.existsSync(contextMdPath)) {
3512
- const accumulated = fs18.readFileSync(contextMdPath, "utf-8");
2505
+ const contextMdPath = path14.join(taskDir, "context.md");
2506
+ if (fs16.existsSync(contextMdPath)) {
2507
+ const accumulated = fs16.readFileSync(contextMdPath, "utf-8");
3513
2508
  const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
3514
2509
  const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
3515
2510
  context += `
@@ -3527,17 +2522,17 @@ ${feedback}
3527
2522
  }
3528
2523
  function inferHasUIFromScope(scope) {
3529
2524
  return scope.some((filePath) => {
3530
- const ext = path16.extname(filePath).toLowerCase();
2525
+ const ext = path14.extname(filePath).toLowerCase();
3531
2526
  if (UI_EXTENSIONS.has(ext)) return true;
3532
2527
  const normalized = filePath.replace(/\\/g, "/");
3533
2528
  return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
3534
2529
  });
3535
2530
  }
3536
2531
  function taskHasUI(taskDir) {
3537
- const taskJsonPath = path16.join(taskDir, "task.json");
3538
- if (!fs18.existsSync(taskJsonPath)) return true;
2532
+ const taskJsonPath = path14.join(taskDir, "task.json");
2533
+ if (!fs16.existsSync(taskJsonPath)) return true;
3539
2534
  try {
3540
- const taskDef = JSON.parse(fs18.readFileSync(taskJsonPath, "utf-8"));
2535
+ const taskDef = JSON.parse(fs16.readFileSync(taskJsonPath, "utf-8"));
3541
2536
  const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
3542
2537
  if (scope.length === 0) return true;
3543
2538
  return inferHasUIFromScope(scope);
@@ -3659,9 +2654,9 @@ ${prompt}` : prompt;
3659
2654
  }
3660
2655
  if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
3661
2656
  assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
3662
- const qaGuidePath = path16.join(projectDir, ".kody", "qa-guide.md");
3663
- if (fs18.existsSync(qaGuidePath)) {
3664
- const qaGuide = fs18.readFileSync(qaGuidePath, "utf-8").trim();
2657
+ const qaGuidePath = path14.join(projectDir, ".kody", "qa-guide.md");
2658
+ if (fs16.existsSync(qaGuidePath)) {
2659
+ const qaGuide = fs16.readFileSync(qaGuidePath, "utf-8").trim();
3665
2660
  assembled = assembled + "\n\n" + qaGuide;
3666
2661
  }
3667
2662
  }
@@ -3691,12 +2686,10 @@ function escalateModelTier(currentTier) {
3691
2686
  function resolveModel(modelTier, stageName) {
3692
2687
  const config = getProjectConfig();
3693
2688
  const mapped = config.agent.modelMap[modelTier];
3694
- if (!mapped) {
3695
- throw new Error(`No model configured for tier '${modelTier}'. Set agent.modelMap.${modelTier} in kody.config.json`);
3696
- }
3697
- return mapped;
2689
+ if (mapped) return mapped;
2690
+ return DEFAULT_MODEL_MAP[modelTier] ?? "sonnet";
3698
2691
  }
3699
- var MAX_TASK_CONTEXT_PLAN, MAX_TASK_CONTEXT_SPEC, MAX_ACCUMULATED_CONTEXT, UI_EXTENSIONS, UI_PATH_SEGMENTS, TIER_ESCALATION;
2692
+ var MAX_TASK_CONTEXT_PLAN, MAX_TASK_CONTEXT_SPEC, MAX_ACCUMULATED_CONTEXT, UI_EXTENSIONS, UI_PATH_SEGMENTS, TIER_ESCALATION, DEFAULT_MODEL_MAP;
3700
2693
  var init_context = __esm({
3701
2694
  "src/context.ts"() {
3702
2695
  "use strict";
@@ -3730,6 +2723,11 @@ var init_context = __esm({
3730
2723
  mid: "strong",
3731
2724
  strong: "strong"
3732
2725
  };
2726
+ DEFAULT_MODEL_MAP = {
2727
+ cheap: "haiku",
2728
+ mid: "sonnet",
2729
+ strong: "opus"
2730
+ };
3733
2731
  }
3734
2732
  });
3735
2733
 
@@ -3753,8 +2751,8 @@ var init_runner_selection = __esm({
3753
2751
  });
3754
2752
 
3755
2753
  // src/stages/agent.ts
3756
- import * as fs19 from "fs";
3757
- import * as path17 from "path";
2754
+ import * as fs17 from "fs";
2755
+ import * as path15 from "path";
3758
2756
  function getSessionInfo(stageName, sessions) {
3759
2757
  const group = SESSION_GROUP[stageName];
3760
2758
  if (!group) return void 0;
@@ -3839,29 +2837,29 @@ async function executeAgentStage(ctx, def) {
3839
2837
  if (lastResult.outcome !== "completed") {
3840
2838
  return { outcome: lastResult.outcome, error: lastResult.error, retries };
3841
2839
  }
3842
- const result2 = lastResult;
3843
- if (def.outputFile && result2.output) {
3844
- fs19.writeFileSync(path17.join(ctx.taskDir, def.outputFile), result2.output);
2840
+ const result = lastResult;
2841
+ if (def.outputFile && result.output) {
2842
+ fs17.writeFileSync(path15.join(ctx.taskDir, def.outputFile), result.output);
3845
2843
  }
3846
2844
  if (def.outputFile) {
3847
- const outputPath = path17.join(ctx.taskDir, def.outputFile);
3848
- if (!fs19.existsSync(outputPath)) {
3849
- const ext = path17.extname(def.outputFile);
3850
- const base = path17.basename(def.outputFile, ext);
3851
- const files = fs19.readdirSync(ctx.taskDir);
2845
+ const outputPath = path15.join(ctx.taskDir, def.outputFile);
2846
+ if (!fs17.existsSync(outputPath)) {
2847
+ const ext = path15.extname(def.outputFile);
2848
+ const base = path15.basename(def.outputFile, ext);
2849
+ const files = fs17.readdirSync(ctx.taskDir);
3852
2850
  const variant = files.find(
3853
2851
  (f) => f.startsWith(base + "-") && f.endsWith(ext)
3854
2852
  );
3855
2853
  if (variant) {
3856
- fs19.renameSync(path17.join(ctx.taskDir, variant), outputPath);
2854
+ fs17.renameSync(path15.join(ctx.taskDir, variant), outputPath);
3857
2855
  logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
3858
2856
  }
3859
2857
  }
3860
2858
  }
3861
2859
  if (def.outputFile) {
3862
- const outputPath = path17.join(ctx.taskDir, def.outputFile);
3863
- if (fs19.existsSync(outputPath)) {
3864
- const content = fs19.readFileSync(outputPath, "utf-8");
2860
+ const outputPath = path15.join(ctx.taskDir, def.outputFile);
2861
+ if (fs17.existsSync(outputPath)) {
2862
+ const content = fs17.readFileSync(outputPath, "utf-8");
3865
2863
  const validation = validateStageOutput(def.name, content);
3866
2864
  if (!validation.valid) {
3867
2865
  if (def.name === "taskify") {
@@ -3875,7 +2873,7 @@ async function executeAgentStage(ctx, def) {
3875
2873
  const stripped = stripFences(retryResult.output);
3876
2874
  const retryValidation = validateTaskJson(stripped);
3877
2875
  if (retryValidation.valid) {
3878
- fs19.writeFileSync(outputPath, retryResult.output);
2876
+ fs17.writeFileSync(outputPath, retryResult.output);
3879
2877
  logger.info(` taskify retry produced valid JSON`);
3880
2878
  } else {
3881
2879
  logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
@@ -3888,7 +2886,7 @@ async function executeAgentStage(ctx, def) {
3888
2886
  risk_level: "low",
3889
2887
  questions: []
3890
2888
  }, null, 2);
3891
- fs19.writeFileSync(outputPath, fallback);
2889
+ fs17.writeFileSync(outputPath, fallback);
3892
2890
  logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
3893
2891
  }
3894
2892
  }
@@ -3898,11 +2896,11 @@ async function executeAgentStage(ctx, def) {
3898
2896
  }
3899
2897
  }
3900
2898
  }
3901
- appendStageContext(ctx.taskDir, def.name, result2.output);
2899
+ appendStageContext(ctx.taskDir, def.name, result.output);
3902
2900
  return { outcome: "completed", outputFile: def.outputFile, retries };
3903
2901
  }
3904
2902
  function appendStageContext(taskDir, stageName, output) {
3905
- const contextPath = path17.join(taskDir, "context.md");
2903
+ const contextPath = path15.join(taskDir, "context.md");
3906
2904
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
3907
2905
  let summary;
3908
2906
  if (output && output.trim()) {
@@ -3915,7 +2913,7 @@ function appendStageContext(taskDir, stageName, output) {
3915
2913
  ### ${stageName} (${timestamp2})
3916
2914
  ${summary}
3917
2915
  `;
3918
- fs19.appendFileSync(contextPath, entry);
2916
+ fs17.appendFileSync(contextPath, entry);
3919
2917
  }
3920
2918
  var SESSION_GROUP;
3921
2919
  var init_agent = __esm({
@@ -3938,7 +2936,7 @@ var init_agent = __esm({
3938
2936
  });
3939
2937
 
3940
2938
  // src/verify-runner.ts
3941
- import { execFileSync as execFileSync12 } from "child_process";
2939
+ import { execFileSync as execFileSync11 } from "child_process";
3942
2940
  function isExecError(err) {
3943
2941
  return typeof err === "object" && err !== null;
3944
2942
  }
@@ -3974,7 +2972,7 @@ function runCommand(cmd, cwd, timeout) {
3974
2972
  return { success: true, output: "", timedOut: false };
3975
2973
  }
3976
2974
  try {
3977
- const output = execFileSync12(parts[0], parts.slice(1), {
2975
+ const output = execFileSync11(parts[0], parts.slice(1), {
3978
2976
  cwd,
3979
2977
  timeout,
3980
2978
  encoding: "utf-8",
@@ -4020,19 +3018,19 @@ function runQualityGates(taskDir, projectRoot) {
4020
3018
  for (const { name, cmd } of commands) {
4021
3019
  if (!cmd) continue;
4022
3020
  logger.info(` Running ${name}: ${cmd}`);
4023
- const result2 = runCommand(cmd, cwd, VERIFY_COMMAND_TIMEOUT_MS);
4024
- if (result2.timedOut) {
3021
+ const result = runCommand(cmd, cwd, VERIFY_COMMAND_TIMEOUT_MS);
3022
+ if (result.timedOut) {
4025
3023
  allErrors.push(`${name}: timed out after ${VERIFY_COMMAND_TIMEOUT_MS / 1e3}s`);
4026
3024
  allPass = false;
4027
3025
  continue;
4028
3026
  }
4029
- if (!result2.success) {
3027
+ if (!result.success) {
4030
3028
  allPass = false;
4031
- const errors = parseErrors(result2.output);
3029
+ const errors = parseErrors(result.output);
4032
3030
  allErrors.push(...errors.map((e) => `[${name}] ${e}`));
4033
- rawOutputs.push({ name, output: result2.output.slice(-3e3) });
3031
+ rawOutputs.push({ name, output: result.output.slice(-3e3) });
4034
3032
  }
4035
- allSummary.push(...extractSummary(result2.output, name));
3033
+ allSummary.push(...extractSummary(result.output, name));
4036
3034
  }
4037
3035
  return { pass: allPass, errors: allErrors, summary: allSummary, rawOutputs };
4038
3036
  }
@@ -4045,7 +3043,7 @@ var init_verify_runner = __esm({
4045
3043
  });
4046
3044
 
4047
3045
  // src/observer.ts
4048
- import { execFileSync as execFileSync13 } from "child_process";
3046
+ import { execFileSync as execFileSync12 } from "child_process";
4049
3047
  async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, model, options) {
4050
3048
  const context = [
4051
3049
  `Stage: ${stageName}`,
@@ -4059,17 +3057,17 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
4059
3057
  ].join("\n");
4060
3058
  const prompt = DIAGNOSIS_PROMPT + context;
4061
3059
  try {
4062
- const result2 = await runner.run(
3060
+ const result = await runner.run(
4063
3061
  "diagnosis",
4064
3062
  prompt,
4065
3063
  model,
4066
- 3e4,
4067
- // 30s timeout — this should be fast
3064
+ 9e4,
3065
+ // 90s timeout — MiniMax can be slow to respond
4068
3066
  "",
4069
3067
  options
4070
3068
  );
4071
- if (result2.outcome === "completed" && result2.output) {
4072
- const cleaned = result2.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
3069
+ if (result.outcome === "completed" && result.output) {
3070
+ const cleaned = result.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
4073
3071
  const parseResult = parseJsonSafe(cleaned, ["classification"]);
4074
3072
  if (parseResult.ok) {
4075
3073
  const { data } = parseResult;
@@ -4096,6 +3094,29 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
4096
3094
  } catch (err) {
4097
3095
  logger.warn(` Diagnosis error: ${err instanceof Error ? err.message : err}`);
4098
3096
  }
3097
+ if (modifiedFiles.length > 0) {
3098
+ const errorLines = errorOutput.split("\n").filter(
3099
+ (l) => /error|Error|ERROR|failed|Failed|FAIL/i.test(l)
3100
+ );
3101
+ const errorFilePaths = errorLines.flatMap((line) => {
3102
+ const matches = line.match(/src\/[^\s(:]+\.[a-z]+/g);
3103
+ return matches ?? [];
3104
+ });
3105
+ if (errorFilePaths.length > 0) {
3106
+ const modifiedSet = new Set(modifiedFiles);
3107
+ const allPreExisting = errorFilePaths.every(
3108
+ (f) => !modifiedSet.has(f) && !modifiedFiles.some((m) => m.endsWith(f))
3109
+ );
3110
+ if (allPreExisting) {
3111
+ logger.warn(" Diagnosis fallback: all errors in unmodified files \u2192 pre-existing");
3112
+ return {
3113
+ classification: "pre-existing",
3114
+ reason: "All errors are in files not modified by the build stage",
3115
+ resolution: `The following files have pre-existing errors not introduced by this task: ${[...new Set(errorFilePaths)].join(", ")}`
3116
+ };
3117
+ }
3118
+ }
3119
+ }
4099
3120
  logger.warn(" Diagnosis failed \u2014 defaulting to retry");
4100
3121
  return {
4101
3122
  classification: "retry",
@@ -4105,13 +3126,13 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
4105
3126
  }
4106
3127
  function getModifiedFiles(projectDir) {
4107
3128
  try {
4108
- const staged = execFileSync13("git", ["diff", "--name-only", "--cached"], {
3129
+ const staged = execFileSync12("git", ["diff", "--name-only", "--cached"], {
4109
3130
  encoding: "utf-8",
4110
3131
  cwd: projectDir,
4111
3132
  timeout: 5e3,
4112
3133
  stdio: ["pipe", "pipe", "pipe"]
4113
3134
  }).trim();
4114
- const unstaged = execFileSync13("git", ["diff", "--name-only"], {
3135
+ const unstaged = execFileSync12("git", ["diff", "--name-only"], {
4115
3136
  encoding: "utf-8",
4116
3137
  cwd: projectDir,
4117
3138
  timeout: 5e3,
@@ -4154,8 +3175,8 @@ Error context:
4154
3175
  });
4155
3176
 
4156
3177
  // src/stages/gate.ts
4157
- import * as fs20 from "fs";
4158
- import * as path18 from "path";
3178
+ import * as fs18 from "fs";
3179
+ import * as path16 from "path";
4159
3180
  function executeGateStage(ctx, def) {
4160
3181
  if (ctx.input.dryRun) {
4161
3182
  logger.info(` [dry-run] skipping ${def.name}`);
@@ -4198,7 +3219,7 @@ ${output}
4198
3219
  `);
4199
3220
  }
4200
3221
  }
4201
- fs20.writeFileSync(path18.join(ctx.taskDir, "verify.md"), lines.join(""));
3222
+ fs18.writeFileSync(path16.join(ctx.taskDir, "verify.md"), lines.join(""));
4202
3223
  return {
4203
3224
  outcome: verifyResult.pass ? "completed" : "failed",
4204
3225
  retries: 0
@@ -4213,9 +3234,9 @@ var init_gate = __esm({
4213
3234
  });
4214
3235
 
4215
3236
  // src/stages/verify.ts
4216
- import * as fs21 from "fs";
4217
- import * as path19 from "path";
4218
- import { execFileSync as execFileSync14 } from "child_process";
3237
+ import * as fs19 from "fs";
3238
+ import * as path17 from "path";
3239
+ import { execFileSync as execFileSync13 } from "child_process";
4219
3240
  async function executeVerifyWithAutofix(ctx, def) {
4220
3241
  const maxAttempts = def.maxRetries ?? 2;
4221
3242
  for (let attempt = 0; attempt <= maxAttempts; attempt++) {
@@ -4225,8 +3246,8 @@ async function executeVerifyWithAutofix(ctx, def) {
4225
3246
  return { ...gateResult, retries: attempt };
4226
3247
  }
4227
3248
  if (attempt < maxAttempts) {
4228
- const verifyPath = path19.join(ctx.taskDir, "verify.md");
4229
- const errorOutput = fs21.existsSync(verifyPath) ? fs21.readFileSync(verifyPath, "utf-8") : "Unknown error";
3249
+ const verifyPath = path17.join(ctx.taskDir, "verify.md");
3250
+ const errorOutput = fs19.existsSync(verifyPath) ? fs19.readFileSync(verifyPath, "utf-8") : "Unknown error";
4230
3251
  const modifiedFiles = getModifiedFiles(ctx.projectDir);
4231
3252
  const defaultRunner = getRunnerForStage(ctx, "taskify");
4232
3253
  const diagConfig = getProjectConfig();
@@ -4269,7 +3290,7 @@ ${diagnosis.resolution}`);
4269
3290
  const parts = parseCommand(cmd);
4270
3291
  if (parts.length === 0) return;
4271
3292
  try {
4272
- execFileSync14(parts[0], parts.slice(1), {
3293
+ execFileSync13(parts[0], parts.slice(1), {
4273
3294
  stdio: "pipe",
4274
3295
  timeout: FIX_COMMAND_TIMEOUT_MS
4275
3296
  });
@@ -4322,8 +3343,8 @@ var init_verify = __esm({
4322
3343
  });
4323
3344
 
4324
3345
  // src/review-standalone.ts
4325
- import * as fs22 from "fs";
4326
- import * as path20 from "path";
3346
+ import * as fs20 from "fs";
3347
+ import * as path18 from "path";
4327
3348
  function resolveReviewTarget(input) {
4328
3349
  if (input.prs.length === 0) {
4329
3350
  return {
@@ -4347,8 +3368,8 @@ Or comment on the specific PR: \`@kody review\``
4347
3368
  }
4348
3369
  async function runStandaloneReview(input) {
4349
3370
  const taskId = input.taskId ?? `review-${generateTaskId()}`;
4350
- const taskDir = path20.join(input.projectDir, ".kody", "tasks", taskId);
4351
- fs22.mkdirSync(taskDir, { recursive: true });
3371
+ const taskDir = path18.join(input.projectDir, ".kody", "tasks", taskId);
3372
+ fs20.mkdirSync(taskDir, { recursive: true });
4352
3373
  let diffInstruction = "";
4353
3374
  let filesChangedSection = "";
4354
3375
  if (input.baseBranch) {
@@ -4375,7 +3396,7 @@ ${fileList}`;
4375
3396
  const taskContent = `# ${input.prTitle}
4376
3397
 
4377
3398
  ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
4378
- fs22.writeFileSync(path20.join(taskDir, "task.md"), taskContent);
3399
+ fs20.writeFileSync(path18.join(taskDir, "task.md"), taskContent);
4379
3400
  const reviewDef = STAGES.find((s) => s.name === "review");
4380
3401
  const ctx = {
4381
3402
  taskId,
@@ -4389,18 +3410,18 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
4389
3410
  }
4390
3411
  };
4391
3412
  logger.info(`[review] standalone review for: ${input.prTitle}`);
4392
- const result2 = await executeAgentStage(ctx, reviewDef);
4393
- if (result2.outcome !== "completed") {
3413
+ const result = await executeAgentStage(ctx, reviewDef);
3414
+ if (result.outcome !== "completed") {
4394
3415
  return {
4395
3416
  outcome: "failed",
4396
3417
  taskDir,
4397
- error: result2.error ?? "Review stage failed"
3418
+ error: result.error ?? "Review stage failed"
4398
3419
  };
4399
3420
  }
4400
- const reviewPath = path20.join(taskDir, "review.md");
3421
+ const reviewPath = path18.join(taskDir, "review.md");
4401
3422
  let reviewContent;
4402
- if (fs22.existsSync(reviewPath)) {
4403
- reviewContent = fs22.readFileSync(reviewPath, "utf-8");
3423
+ if (fs20.existsSync(reviewPath)) {
3424
+ reviewContent = fs20.readFileSync(reviewPath, "utf-8");
4404
3425
  }
4405
3426
  return {
4406
3427
  outcome: "completed",
@@ -4440,8 +3461,8 @@ var init_review_standalone = __esm({
4440
3461
  });
4441
3462
 
4442
3463
  // src/stages/review.ts
4443
- import * as fs23 from "fs";
4444
- import * as path21 from "path";
3464
+ import * as fs21 from "fs";
3465
+ import * as path19 from "path";
4445
3466
  async function executeReviewWithFix(ctx, def) {
4446
3467
  if (ctx.input.dryRun) {
4447
3468
  return { outcome: "completed", retries: 0 };
@@ -4455,11 +3476,11 @@ async function executeReviewWithFix(ctx, def) {
4455
3476
  if (reviewResult.outcome !== "completed") {
4456
3477
  return reviewResult;
4457
3478
  }
4458
- const reviewFile = path21.join(ctx.taskDir, "review.md");
4459
- if (!fs23.existsSync(reviewFile)) {
3479
+ const reviewFile = path19.join(ctx.taskDir, "review.md");
3480
+ if (!fs21.existsSync(reviewFile)) {
4460
3481
  return { outcome: "failed", retries: iteration, error: "review.md not found" };
4461
3482
  }
4462
- const content = fs23.readFileSync(reviewFile, "utf-8");
3483
+ const content = fs21.readFileSync(reviewFile, "utf-8");
4463
3484
  if (detectReviewVerdict(content) !== "fail") {
4464
3485
  return { ...reviewResult, retries: iteration };
4465
3486
  }
@@ -4488,15 +3509,15 @@ var init_review = __esm({
4488
3509
  });
4489
3510
 
4490
3511
  // src/stages/ship.ts
4491
- import * as fs24 from "fs";
4492
- import * as path22 from "path";
4493
- import { execFileSync as execFileSync15 } from "child_process";
3512
+ import * as fs22 from "fs";
3513
+ import * as path20 from "path";
3514
+ import { execFileSync as execFileSync14 } from "child_process";
4494
3515
  function buildPrBody(ctx) {
4495
3516
  const sections = [];
4496
- const taskJsonPath = path22.join(ctx.taskDir, "task.json");
4497
- if (fs24.existsSync(taskJsonPath)) {
3517
+ const taskJsonPath = path20.join(ctx.taskDir, "task.json");
3518
+ if (fs22.existsSync(taskJsonPath)) {
4498
3519
  try {
4499
- const raw = fs24.readFileSync(taskJsonPath, "utf-8");
3520
+ const raw = fs22.readFileSync(taskJsonPath, "utf-8");
4500
3521
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4501
3522
  const task = JSON.parse(cleaned);
4502
3523
  if (task.description) {
@@ -4515,9 +3536,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
4515
3536
  } catch {
4516
3537
  }
4517
3538
  }
4518
- const reviewPath = path22.join(ctx.taskDir, "review.md");
4519
- if (fs24.existsSync(reviewPath)) {
4520
- const review = fs24.readFileSync(reviewPath, "utf-8");
3539
+ const reviewPath = path20.join(ctx.taskDir, "review.md");
3540
+ if (fs22.existsSync(reviewPath)) {
3541
+ const review = fs22.readFileSync(reviewPath, "utf-8");
4521
3542
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
4522
3543
  if (summaryMatch) {
4523
3544
  const summary = summaryMatch[1].trim();
@@ -4534,14 +3555,14 @@ ${summary}`);
4534
3555
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
4535
3556
  }
4536
3557
  }
4537
- const verifyPath = path22.join(ctx.taskDir, "verify.md");
4538
- if (fs24.existsSync(verifyPath)) {
4539
- const verify = fs24.readFileSync(verifyPath, "utf-8");
3558
+ const verifyPath = path20.join(ctx.taskDir, "verify.md");
3559
+ if (fs22.existsSync(verifyPath)) {
3560
+ const verify = fs22.readFileSync(verifyPath, "utf-8");
4540
3561
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
4541
3562
  }
4542
- const planPath = path22.join(ctx.taskDir, "plan.md");
4543
- if (fs24.existsSync(planPath)) {
4544
- const plan = fs24.readFileSync(planPath, "utf-8").trim();
3563
+ const planPath = path20.join(ctx.taskDir, "plan.md");
3564
+ if (fs22.existsSync(planPath)) {
3565
+ const plan = fs22.readFileSync(planPath, "utf-8").trim();
4545
3566
  if (plan) {
4546
3567
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
4547
3568
  sections.push(`
@@ -4561,25 +3582,25 @@ Closes #${ctx.input.issueNumber}`);
4561
3582
  return sections.join("\n");
4562
3583
  }
4563
3584
  function executeShipStage(ctx, _def) {
4564
- const shipPath = path22.join(ctx.taskDir, "ship.md");
3585
+ const shipPath = path20.join(ctx.taskDir, "ship.md");
4565
3586
  if (ctx.input.dryRun) {
4566
- fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
3587
+ fs22.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
4567
3588
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
4568
3589
  }
4569
3590
  if (ctx.input.local && !ctx.input.issueNumber) {
4570
- fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
3591
+ fs22.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
4571
3592
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
4572
3593
  }
4573
3594
  try {
4574
3595
  const head = getCurrentBranch(ctx.projectDir);
4575
3596
  const base = getDefaultBranch(ctx.projectDir);
4576
3597
  try {
4577
- execFileSync15("git", ["add", ctx.taskDir], {
3598
+ execFileSync14("git", ["add", ctx.taskDir], {
4578
3599
  cwd: ctx.projectDir,
4579
3600
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
4580
3601
  stdio: "pipe"
4581
3602
  });
4582
- execFileSync15("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
3603
+ execFileSync14("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
4583
3604
  cwd: ctx.projectDir,
4584
3605
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
4585
3606
  stdio: "pipe"
@@ -4593,7 +3614,7 @@ function executeShipStage(ctx, _def) {
4593
3614
  let repo = config.github?.repo;
4594
3615
  if (!owner || !repo) {
4595
3616
  try {
4596
- const remoteUrl = execFileSync15("git", ["remote", "get-url", "origin"], {
3617
+ const remoteUrl = execFileSync14("git", ["remote", "get-url", "origin"], {
4597
3618
  encoding: "utf-8",
4598
3619
  cwd: ctx.projectDir
4599
3620
  }).trim();
@@ -4614,28 +3635,28 @@ function executeShipStage(ctx, _def) {
4614
3635
  chore: "chore"
4615
3636
  };
4616
3637
  let prefix = "chore";
4617
- const taskJsonPath = path22.join(ctx.taskDir, "task.json");
4618
- if (fs24.existsSync(taskJsonPath)) {
3638
+ const taskJsonPath = path20.join(ctx.taskDir, "task.json");
3639
+ if (fs22.existsSync(taskJsonPath)) {
4619
3640
  try {
4620
- const raw = fs24.readFileSync(taskJsonPath, "utf-8");
3641
+ const raw = fs22.readFileSync(taskJsonPath, "utf-8");
4621
3642
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4622
3643
  const task = JSON.parse(cleaned);
4623
3644
  prefix = TYPE_PREFIX[task.task_type] ?? "chore";
4624
3645
  } catch {
4625
3646
  }
4626
3647
  }
4627
- const taskMdPath = path22.join(ctx.taskDir, "task.md");
4628
- if (fs24.existsSync(taskMdPath)) {
4629
- const content = fs24.readFileSync(taskMdPath, "utf-8");
3648
+ const taskMdPath = path20.join(ctx.taskDir, "task.md");
3649
+ if (fs22.existsSync(taskMdPath)) {
3650
+ const content = fs22.readFileSync(taskMdPath, "utf-8");
4630
3651
  const heading = content.split("\n").find((l) => l.startsWith("# "));
4631
3652
  if (heading) {
4632
3653
  title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
4633
3654
  }
4634
3655
  }
4635
3656
  if (title === "Update") {
4636
- if (fs24.existsSync(taskJsonPath)) {
3657
+ if (fs22.existsSync(taskJsonPath)) {
4637
3658
  try {
4638
- const raw = fs24.readFileSync(taskJsonPath, "utf-8");
3659
+ const raw = fs22.readFileSync(taskJsonPath, "utf-8");
4639
3660
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4640
3661
  const task = JSON.parse(cleaned);
4641
3662
  if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
@@ -4658,7 +3679,7 @@ function executeShipStage(ctx, _def) {
4658
3679
  } catch {
4659
3680
  }
4660
3681
  }
4661
- fs24.writeFileSync(shipPath, `# Ship
3682
+ fs22.writeFileSync(shipPath, `# Ship
4662
3683
 
4663
3684
  Updated existing PR: ${existingPr.url}
4664
3685
  PR #${existingPr.number}
@@ -4679,20 +3700,20 @@ PR #${existingPr.number}
4679
3700
  } catch {
4680
3701
  }
4681
3702
  }
4682
- fs24.writeFileSync(shipPath, `# Ship
3703
+ fs22.writeFileSync(shipPath, `# Ship
4683
3704
 
4684
3705
  PR created: ${pr.url}
4685
3706
  PR #${pr.number}
4686
3707
  `);
4687
3708
  } else {
4688
- fs24.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
3709
+ fs22.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
4689
3710
  }
4690
3711
  }
4691
3712
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
4692
3713
  } catch (err) {
4693
3714
  const msg = err instanceof Error ? err.message : String(err);
4694
3715
  try {
4695
- fs24.writeFileSync(shipPath, `# Ship
3716
+ fs22.writeFileSync(shipPath, `# Ship
4696
3717
 
4697
3718
  Failed: ${msg}
4698
3719
  `);
@@ -4741,15 +3762,15 @@ var init_executor_registry = __esm({
4741
3762
  });
4742
3763
 
4743
3764
  // src/pipeline/questions.ts
4744
- import * as fs25 from "fs";
4745
- import * as path23 from "path";
3765
+ import * as fs23 from "fs";
3766
+ import * as path21 from "path";
4746
3767
  function checkForQuestions(ctx, stageName) {
4747
3768
  if (ctx.input.local || !ctx.input.issueNumber) return false;
4748
3769
  try {
4749
3770
  if (stageName === "taskify") {
4750
- const taskJsonPath = path23.join(ctx.taskDir, "task.json");
4751
- if (!fs25.existsSync(taskJsonPath)) return false;
4752
- const raw = fs25.readFileSync(taskJsonPath, "utf-8");
3771
+ const taskJsonPath = path21.join(ctx.taskDir, "task.json");
3772
+ if (!fs23.existsSync(taskJsonPath)) return false;
3773
+ const raw = fs23.readFileSync(taskJsonPath, "utf-8");
4753
3774
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4754
3775
  const taskJson = JSON.parse(cleaned);
4755
3776
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -4764,9 +3785,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
4764
3785
  }
4765
3786
  }
4766
3787
  if (stageName === "plan") {
4767
- const planPath = path23.join(ctx.taskDir, "plan.md");
4768
- if (!fs25.existsSync(planPath)) return false;
4769
- const plan = fs25.readFileSync(planPath, "utf-8");
3788
+ const planPath = path21.join(ctx.taskDir, "plan.md");
3789
+ if (!fs23.existsSync(planPath)) return false;
3790
+ const plan = fs23.readFileSync(planPath, "utf-8");
4770
3791
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
4771
3792
  if (questionsMatch) {
4772
3793
  const questionsText = questionsMatch[1].trim();
@@ -4795,8 +3816,8 @@ var init_questions = __esm({
4795
3816
  });
4796
3817
 
4797
3818
  // src/pipeline/hooks.ts
4798
- import * as fs26 from "fs";
4799
- import * as path24 from "path";
3819
+ import * as fs24 from "fs";
3820
+ import * as path22 from "path";
4800
3821
  function applyPreStageLabel(ctx, def) {
4801
3822
  if (!ctx.input.issueNumber || ctx.input.local) return;
4802
3823
  if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
@@ -4834,9 +3855,9 @@ function autoDetectComplexity(ctx, def) {
4834
3855
  return { complexity, activeStages };
4835
3856
  }
4836
3857
  try {
4837
- const taskJsonPath = path24.join(ctx.taskDir, "task.json");
4838
- if (!fs26.existsSync(taskJsonPath)) return null;
4839
- const raw = fs26.readFileSync(taskJsonPath, "utf-8");
3858
+ const taskJsonPath = path22.join(ctx.taskDir, "task.json");
3859
+ if (!fs24.existsSync(taskJsonPath)) return null;
3860
+ const raw = fs24.readFileSync(taskJsonPath, "utf-8");
4840
3861
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4841
3862
  const taskJson = JSON.parse(cleaned);
4842
3863
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -4866,8 +3887,8 @@ function checkRiskGate(ctx, def, state, complexity) {
4866
3887
  if (ctx.input.dryRun || ctx.input.local) return null;
4867
3888
  if (ctx.input.mode === "rerun") return null;
4868
3889
  if (!ctx.input.issueNumber) return null;
4869
- const planPath = path24.join(ctx.taskDir, "plan.md");
4870
- const plan = fs26.existsSync(planPath) ? fs26.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
3890
+ const planPath = path22.join(ctx.taskDir, "plan.md");
3891
+ const plan = fs24.existsSync(planPath) ? fs24.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
4871
3892
  try {
4872
3893
  postComment(
4873
3894
  ctx.input.issueNumber,
@@ -4934,22 +3955,22 @@ var init_hooks = __esm({
4934
3955
  });
4935
3956
 
4936
3957
  // src/learning/auto-learn.ts
4937
- import * as fs27 from "fs";
4938
- import * as path25 from "path";
3958
+ import * as fs25 from "fs";
3959
+ import * as path23 from "path";
4939
3960
  function stripAnsi(str) {
4940
3961
  return str.replace(/\x1b\[[0-9;]*m/g, "");
4941
3962
  }
4942
3963
  function autoLearn(ctx) {
4943
3964
  try {
4944
- const memoryDir = path25.join(ctx.projectDir, ".kody", "memory");
4945
- if (!fs27.existsSync(memoryDir)) {
4946
- fs27.mkdirSync(memoryDir, { recursive: true });
3965
+ const memoryDir = path23.join(ctx.projectDir, ".kody", "memory");
3966
+ if (!fs25.existsSync(memoryDir)) {
3967
+ fs25.mkdirSync(memoryDir, { recursive: true });
4947
3968
  }
4948
3969
  const learnings = [];
4949
3970
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4950
- const verifyPath = path25.join(ctx.taskDir, "verify.md");
4951
- if (fs27.existsSync(verifyPath)) {
4952
- const verify = stripAnsi(fs27.readFileSync(verifyPath, "utf-8"));
3971
+ const verifyPath = path23.join(ctx.taskDir, "verify.md");
3972
+ if (fs25.existsSync(verifyPath)) {
3973
+ const verify = stripAnsi(fs25.readFileSync(verifyPath, "utf-8"));
4953
3974
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
4954
3975
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
4955
3976
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -4958,18 +3979,18 @@ function autoLearn(ctx) {
4958
3979
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
4959
3980
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
4960
3981
  }
4961
- const reviewPath = path25.join(ctx.taskDir, "review.md");
4962
- if (fs27.existsSync(reviewPath)) {
4963
- const review = fs27.readFileSync(reviewPath, "utf-8");
3982
+ const reviewPath = path23.join(ctx.taskDir, "review.md");
3983
+ if (fs25.existsSync(reviewPath)) {
3984
+ const review = fs25.readFileSync(reviewPath, "utf-8");
4964
3985
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
4965
3986
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
4966
3987
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
4967
3988
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
4968
3989
  }
4969
- const taskJsonPath = path25.join(ctx.taskDir, "task.json");
4970
- if (fs27.existsSync(taskJsonPath)) {
3990
+ const taskJsonPath = path23.join(ctx.taskDir, "task.json");
3991
+ if (fs25.existsSync(taskJsonPath)) {
4971
3992
  try {
4972
- const raw = stripAnsi(fs27.readFileSync(taskJsonPath, "utf-8"));
3993
+ const raw = stripAnsi(fs25.readFileSync(taskJsonPath, "utf-8"));
4973
3994
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4974
3995
  const task = JSON.parse(cleaned);
4975
3996
  if (task.scope && Array.isArray(task.scope)) {
@@ -4980,12 +4001,12 @@ function autoLearn(ctx) {
4980
4001
  }
4981
4002
  }
4982
4003
  if (learnings.length > 0) {
4983
- const conventionsPath = path25.join(memoryDir, "conventions.md");
4004
+ const conventionsPath = path23.join(memoryDir, "conventions.md");
4984
4005
  const entry = `
4985
4006
  ## Learned ${timestamp2} (task: ${ctx.taskId})
4986
4007
  ${learnings.join("\n")}
4987
4008
  `;
4988
- fs27.appendFileSync(conventionsPath, entry);
4009
+ fs25.appendFileSync(conventionsPath, entry);
4989
4010
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
4990
4011
  }
4991
4012
  autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
@@ -4993,8 +4014,8 @@ ${learnings.join("\n")}
4993
4014
  }
4994
4015
  }
4995
4016
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
4996
- const archPath = path25.join(memoryDir, "architecture.md");
4997
- if (fs27.existsSync(archPath)) return;
4017
+ const archPath = path23.join(memoryDir, "architecture.md");
4018
+ if (fs25.existsSync(archPath)) return;
4998
4019
  const detected = detectArchitectureBasic(projectDir);
4999
4020
  if (detected.length > 0) {
5000
4021
  const content = `# Architecture (auto-detected ${timestamp2})
@@ -5002,7 +4023,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
5002
4023
  ## Overview
5003
4024
  ${detected.join("\n")}
5004
4025
  `;
5005
- fs27.writeFileSync(archPath, content);
4026
+ fs25.writeFileSync(archPath, content);
5006
4027
  logger.info(`Auto-detected architecture (${detected.length} items)`);
5007
4028
  }
5008
4029
  }
@@ -5015,13 +4036,13 @@ var init_auto_learn = __esm({
5015
4036
  });
5016
4037
 
5017
4038
  // src/retrospective.ts
5018
- import * as fs28 from "fs";
5019
- import * as path26 from "path";
4039
+ import * as fs26 from "fs";
4040
+ import * as path24 from "path";
5020
4041
  function readArtifact(taskDir, filename, maxChars) {
5021
- const p = path26.join(taskDir, filename);
5022
- if (!fs28.existsSync(p)) return null;
4042
+ const p = path24.join(taskDir, filename);
4043
+ if (!fs26.existsSync(p)) return null;
5023
4044
  try {
5024
- const content = fs28.readFileSync(p, "utf-8");
4045
+ const content = fs26.readFileSync(p, "utf-8");
5025
4046
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
5026
4047
  } catch {
5027
4048
  return null;
@@ -5074,13 +4095,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
5074
4095
  return lines.join("\n");
5075
4096
  }
5076
4097
  function getLogPath(projectDir) {
5077
- return path26.join(projectDir, ".kody", "memory", "observer-log.jsonl");
4098
+ return path24.join(projectDir, ".kody", "memory", "observer-log.jsonl");
5078
4099
  }
5079
4100
  function readPreviousRetrospectives(projectDir, limit = 10) {
5080
4101
  const logPath = getLogPath(projectDir);
5081
- if (!fs28.existsSync(logPath)) return [];
4102
+ if (!fs26.existsSync(logPath)) return [];
5082
4103
  try {
5083
- const content = fs28.readFileSync(logPath, "utf-8");
4104
+ const content = fs26.readFileSync(logPath, "utf-8");
5084
4105
  const lines = content.split("\n").filter(Boolean);
5085
4106
  const entries = [];
5086
4107
  const start = Math.max(0, lines.length - limit);
@@ -5107,11 +4128,11 @@ function formatPreviousEntries(entries) {
5107
4128
  }
5108
4129
  function appendRetrospectiveEntry(projectDir, entry) {
5109
4130
  const logPath = getLogPath(projectDir);
5110
- const dir = path26.dirname(logPath);
5111
- if (!fs28.existsSync(dir)) {
5112
- fs28.mkdirSync(dir, { recursive: true });
4131
+ const dir = path24.dirname(logPath);
4132
+ if (!fs26.existsSync(dir)) {
4133
+ fs26.mkdirSync(dir, { recursive: true });
5113
4134
  }
5114
- fs28.appendFileSync(logPath, JSON.stringify(entry) + "\n");
4135
+ fs26.appendFileSync(logPath, JSON.stringify(entry) + "\n");
5115
4136
  }
5116
4137
  async function runRetrospective(ctx, state, pipelineStartTime) {
5117
4138
  if (ctx.input.dryRun) return;
@@ -5133,7 +4154,7 @@ ${previousText}
5133
4154
  if (needsLitellmProxy(config)) {
5134
4155
  extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
5135
4156
  }
5136
- const result2 = await runner.run("retrospective", prompt, model, 3e4, "", {
4157
+ const result = await runner.run("retrospective", prompt, model, 3e4, "", {
5137
4158
  cwd: ctx.projectDir,
5138
4159
  env: extraEnv
5139
4160
  });
@@ -5141,8 +4162,8 @@ ${previousText}
5141
4162
  let patternMatch = null;
5142
4163
  let suggestion = "No suggestion";
5143
4164
  let pipelineFlaw = null;
5144
- if (result2.outcome === "completed" && result2.output) {
5145
- const cleaned = result2.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
4165
+ if (result.outcome === "completed" && result.output) {
4166
+ const cleaned = result.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
5146
4167
  try {
5147
4168
  const parsed = JSON.parse(cleaned);
5148
4169
  observation = parsed.observation ?? observation;
@@ -5279,8 +4300,8 @@ var init_summary = __esm({
5279
4300
  });
5280
4301
 
5281
4302
  // src/pipeline.ts
5282
- import * as fs29 from "fs";
5283
- import * as path27 from "path";
4303
+ import * as fs27 from "fs";
4304
+ import * as path25 from "path";
5284
4305
  function ensureFeatureBranchIfNeeded(ctx) {
5285
4306
  if (ctx.input.dryRun) return;
5286
4307
  if (ctx.input.prNumber) {
@@ -5293,8 +4314,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
5293
4314
  }
5294
4315
  if (!ctx.input.issueNumber) return;
5295
4316
  try {
5296
- const taskMdPath = path27.join(ctx.taskDir, "task.md");
5297
- const title = fs29.existsSync(taskMdPath) ? fs29.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
4317
+ const taskMdPath = path25.join(ctx.taskDir, "task.md");
4318
+ const title = fs27.existsSync(taskMdPath) ? fs27.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
5298
4319
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
5299
4320
  syncWithDefault(ctx.projectDir);
5300
4321
  } catch (err) {
@@ -5308,10 +4329,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
5308
4329
  }
5309
4330
  }
5310
4331
  function acquireLock(taskDir) {
5311
- const lockPath = path27.join(taskDir, ".lock");
5312
- if (fs29.existsSync(lockPath)) {
4332
+ const lockPath = path25.join(taskDir, ".lock");
4333
+ if (fs27.existsSync(lockPath)) {
5313
4334
  try {
5314
- const pid = parseInt(fs29.readFileSync(lockPath, "utf-8").trim(), 10);
4335
+ const pid = parseInt(fs27.readFileSync(lockPath, "utf-8").trim(), 10);
5315
4336
  if (!isNaN(pid)) {
5316
4337
  try {
5317
4338
  process.kill(pid, 0);
@@ -5328,14 +4349,14 @@ function acquireLock(taskDir) {
5328
4349
  logger.warn(` Corrupt lock file \u2014 overwriting`);
5329
4350
  }
5330
4351
  try {
5331
- fs29.unlinkSync(lockPath);
4352
+ fs27.unlinkSync(lockPath);
5332
4353
  } catch {
5333
4354
  }
5334
4355
  }
5335
4356
  try {
5336
- const fd = fs29.openSync(lockPath, fs29.constants.O_WRONLY | fs29.constants.O_CREAT | fs29.constants.O_EXCL);
5337
- fs29.writeSync(fd, String(process.pid));
5338
- fs29.closeSync(fd);
4357
+ const fd = fs27.openSync(lockPath, fs27.constants.O_WRONLY | fs27.constants.O_CREAT | fs27.constants.O_EXCL);
4358
+ fs27.writeSync(fd, String(process.pid));
4359
+ fs27.closeSync(fd);
5339
4360
  } catch (err) {
5340
4361
  if (err.code === "EEXIST") {
5341
4362
  throw new Error("Pipeline already running (lock acquired by another process)");
@@ -5345,7 +4366,7 @@ function acquireLock(taskDir) {
5345
4366
  }
5346
4367
  function releaseLock(taskDir) {
5347
4368
  try {
5348
- fs29.unlinkSync(path27.join(taskDir, ".lock"));
4369
+ fs27.unlinkSync(path25.join(taskDir, ".lock"));
5349
4370
  } catch {
5350
4371
  }
5351
4372
  }
@@ -5434,23 +4455,23 @@ async function runPipelineInner(ctx) {
5434
4455
  writeState(state, ctx.taskDir);
5435
4456
  logger.info(`[${def.name}] starting...`);
5436
4457
  applyPreStageLabel(ctx, def);
5437
- let result2;
4458
+ let result;
5438
4459
  try {
5439
- result2 = await getExecutor(def.name)(ctx, def);
4460
+ result = await getExecutor(def.name)(ctx, def);
5440
4461
  } catch (error) {
5441
- result2 = {
4462
+ result = {
5442
4463
  outcome: "failed",
5443
4464
  retries: 0,
5444
4465
  error: error instanceof Error ? error.message : String(error)
5445
4466
  };
5446
4467
  }
5447
4468
  ciGroupEnd();
5448
- if (result2.outcome === "completed") {
4469
+ if (result.outcome === "completed") {
5449
4470
  state.stages[def.name] = {
5450
4471
  state: "completed",
5451
4472
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
5452
- retries: result2.retries,
5453
- outputFile: result2.outputFile
4473
+ retries: result.retries,
4474
+ outputFile: result.outputFile
5454
4475
  };
5455
4476
  logger.info(`[${def.name}] \u2713 completed`);
5456
4477
  const detected = autoDetectComplexity(ctx, def);
@@ -5464,16 +4485,16 @@ async function runPipelineInner(ctx) {
5464
4485
  if (gated) return gated;
5465
4486
  commitAfterStage(ctx, def);
5466
4487
  } else {
5467
- const isTimeout = result2.outcome === "timed_out";
4488
+ const isTimeout = result.outcome === "timed_out";
5468
4489
  state.stages[def.name] = {
5469
4490
  state: isTimeout ? "timeout" : "failed",
5470
- retries: result2.retries,
5471
- error: isTimeout ? "Stage timed out" : result2.error ?? "Stage failed"
4491
+ retries: result.retries,
4492
+ error: isTimeout ? "Stage timed out" : result.error ?? "Stage failed"
5472
4493
  };
5473
4494
  state.state = "failed";
5474
4495
  state.sessions = ctx.sessions;
5475
4496
  writeState(state, ctx.taskDir);
5476
- logger.error(`[${def.name}] ${isTimeout ? "\u23F1 timed out" : `\u2717 failed: ${result2.error}`}`);
4497
+ logger.error(`[${def.name}] ${isTimeout ? "\u23F1 timed out" : `\u2717 failed: ${result.error}`}`);
5477
4498
  if (ctx.input.issueNumber && !ctx.input.local) {
5478
4499
  setLifecycleLabel(ctx.input.issueNumber, "failed");
5479
4500
  }
@@ -5553,8 +4574,8 @@ var init_pipeline = __esm({
5553
4574
  });
5554
4575
 
5555
4576
  // src/preflight.ts
5556
- import { execFileSync as execFileSync16 } from "child_process";
5557
- import * as fs30 from "fs";
4577
+ import { execFileSync as execFileSync15 } from "child_process";
4578
+ import * as fs28 from "fs";
5558
4579
  function check(name, fn) {
5559
4580
  try {
5560
4581
  const detail = fn() ?? void 0;
@@ -5566,7 +4587,7 @@ function check(name, fn) {
5566
4587
  function runPreflight() {
5567
4588
  const checks = [
5568
4589
  check("claude CLI", () => {
5569
- const v = execFileSync16("claude", ["--version"], {
4590
+ const v = execFileSync15("claude", ["--version"], {
5570
4591
  encoding: "utf-8",
5571
4592
  timeout: 1e4,
5572
4593
  stdio: ["pipe", "pipe", "pipe"]
@@ -5574,14 +4595,14 @@ function runPreflight() {
5574
4595
  return v;
5575
4596
  }),
5576
4597
  check("git repo", () => {
5577
- execFileSync16("git", ["rev-parse", "--is-inside-work-tree"], {
4598
+ execFileSync15("git", ["rev-parse", "--is-inside-work-tree"], {
5578
4599
  encoding: "utf-8",
5579
4600
  timeout: 5e3,
5580
4601
  stdio: ["pipe", "pipe", "pipe"]
5581
4602
  });
5582
4603
  }),
5583
4604
  check("pnpm", () => {
5584
- const v = execFileSync16("pnpm", ["--version"], {
4605
+ const v = execFileSync15("pnpm", ["--version"], {
5585
4606
  encoding: "utf-8",
5586
4607
  timeout: 5e3,
5587
4608
  stdio: ["pipe", "pipe", "pipe"]
@@ -5589,7 +4610,7 @@ function runPreflight() {
5589
4610
  return v;
5590
4611
  }),
5591
4612
  check("node >= 18", () => {
5592
- const v = execFileSync16("node", ["--version"], {
4613
+ const v = execFileSync15("node", ["--version"], {
5593
4614
  encoding: "utf-8",
5594
4615
  timeout: 5e3,
5595
4616
  stdio: ["pipe", "pipe", "pipe"]
@@ -5599,7 +4620,7 @@ function runPreflight() {
5599
4620
  return v;
5600
4621
  }),
5601
4622
  check("gh CLI", () => {
5602
- const v = execFileSync16("gh", ["--version"], {
4623
+ const v = execFileSync15("gh", ["--version"], {
5603
4624
  encoding: "utf-8",
5604
4625
  timeout: 5e3,
5605
4626
  stdio: ["pipe", "pipe", "pipe"]
@@ -5607,7 +4628,7 @@ function runPreflight() {
5607
4628
  return v;
5608
4629
  }),
5609
4630
  check("package.json", () => {
5610
- if (!fs30.existsSync("package.json")) throw new Error("not found");
4631
+ if (!fs28.existsSync("package.json")) throw new Error("not found");
5611
4632
  })
5612
4633
  ];
5613
4634
  const failed = checks.filter((c) => !c.ok);
@@ -5684,8 +4705,8 @@ var init_args = __esm({
5684
4705
  });
5685
4706
 
5686
4707
  // src/cli/task-state.ts
5687
- import * as fs31 from "fs";
5688
- import * as path28 from "path";
4708
+ import * as fs29 from "fs";
4709
+ import * as path26 from "path";
5689
4710
  function resolveTaskAction(issueNumber, existingTaskId, existingState) {
5690
4711
  if (!existingTaskId || !existingState) {
5691
4712
  return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
@@ -5717,11 +4738,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
5717
4738
  function resolveForIssue(issueNumber, projectDir) {
5718
4739
  const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
5719
4740
  if (existingTaskId) {
5720
- const statusPath = path28.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
4741
+ const statusPath = path26.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
5721
4742
  let existingState = null;
5722
- if (fs31.existsSync(statusPath)) {
4743
+ if (fs29.existsSync(statusPath)) {
5723
4744
  try {
5724
- existingState = JSON.parse(fs31.readFileSync(statusPath, "utf-8"));
4745
+ existingState = JSON.parse(fs29.readFileSync(statusPath, "utf-8"));
5725
4746
  } catch {
5726
4747
  }
5727
4748
  }
@@ -5754,12 +4775,12 @@ var resolve_exports = {};
5754
4775
  __export(resolve_exports, {
5755
4776
  runResolve: () => runResolve
5756
4777
  });
5757
- import { execFileSync as execFileSync17 } from "child_process";
4778
+ import { execFileSync as execFileSync16 } from "child_process";
5758
4779
  function getConflictContext(cwd, files) {
5759
4780
  const parts = [];
5760
4781
  for (const file of files.slice(0, 10)) {
5761
4782
  try {
5762
- const content = execFileSync17("git", ["diff", file], {
4783
+ const content = execFileSync16("git", ["diff", file], {
5763
4784
  cwd,
5764
4785
  encoding: "utf-8",
5765
4786
  stdio: ["pipe", "pipe", "pipe"]
@@ -5809,12 +4830,12 @@ async function runResolve(options) {
5809
4830
  extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
5810
4831
  }
5811
4832
  logger.info(` Running agent to resolve conflicts (model=${model})...`);
5812
- const result2 = await runner.run("resolve", prompt, model, 3e5, projectDir, {
4833
+ const result = await runner.run("resolve", prompt, model, 3e5, projectDir, {
5813
4834
  cwd: projectDir,
5814
4835
  env: extraEnv
5815
4836
  });
5816
- if (result2.outcome !== "completed") {
5817
- return { outcome: "failed", error: `Agent failed: ${result2.error}` };
4837
+ if (result.outcome !== "completed") {
4838
+ return { outcome: "failed", error: `Agent failed: ${result.error}` };
5818
4839
  }
5819
4840
  logger.info(" Verifying resolution...");
5820
4841
  const verify = runQualityGates(projectDir, projectDir);
@@ -5878,8 +4899,8 @@ var init_resolve = __esm({
5878
4899
 
5879
4900
  // src/entry.ts
5880
4901
  var entry_exports = {};
5881
- import * as fs32 from "fs";
5882
- import * as path29 from "path";
4902
+ import * as fs30 from "fs";
4903
+ import * as path27 from "path";
5883
4904
  async function ensureLitellmProxy(config, projectDir) {
5884
4905
  if (!anyStageNeedsProxy(config)) return null;
5885
4906
  const litellmUrl = getLitellmUrl();
@@ -5914,7 +4935,7 @@ async function ensureLitellmProxy(config, projectDir) {
5914
4935
  return litellmProcess;
5915
4936
  }
5916
4937
  async function runModelHealthCheck(config) {
5917
- const usesProxy = anyStageNeedsProxy(config);
4938
+ const usesProxy = needsLitellmProxy(config);
5918
4939
  const baseUrl = usesProxy ? getLitellmUrl() : "https://api.anthropic.com";
5919
4940
  const apiKey = usesProxy ? process.env.ANTHROPIC_COMPATIBLE_API_KEY : process.env.ANTHROPIC_API_KEY;
5920
4941
  if (!apiKey) {
@@ -5924,19 +4945,19 @@ async function runModelHealthCheck(config) {
5924
4945
  }
5925
4946
  const model = config.agent.modelMap.cheap;
5926
4947
  logger.info(`Model health check (${model} via ${usesProxy ? "LiteLLM" : "Anthropic"})...`);
5927
- const result2 = await checkModelHealth(baseUrl, apiKey, model);
5928
- if (result2.ok) {
4948
+ const result = await checkModelHealth(baseUrl, apiKey, model);
4949
+ if (result.ok) {
5929
4950
  logger.info(" \u2713 Model responded");
5930
4951
  } else {
5931
- logger.error(` \u2717 Model health check failed: ${result2.error}`);
4952
+ logger.error(` \u2717 Model health check failed: ${result.error}`);
5932
4953
  process.exit(1);
5933
4954
  }
5934
4955
  }
5935
4956
  async function main() {
5936
4957
  const input = parseArgs();
5937
- const projectDir = input.cwd ? path29.resolve(input.cwd) : process.cwd();
4958
+ const projectDir = input.cwd ? path27.resolve(input.cwd) : process.cwd();
5938
4959
  if (input.cwd) {
5939
- if (!fs32.existsSync(projectDir)) {
4960
+ if (!fs30.existsSync(projectDir)) {
5940
4961
  console.error(`--cwd path does not exist: ${projectDir}`);
5941
4962
  process.exit(1);
5942
4963
  }
@@ -5945,7 +4966,7 @@ async function main() {
5945
4966
  logger.info(`Working directory: ${projectDir}`);
5946
4967
  }
5947
4968
  const isPRFix = (input.command === "fix" || input.command === "fix-ci") && !!input.prNumber;
5948
- const skipStateCheck = input.command === "review" || input.command === "resolve" || input.command === "rerun" || input.command === "status";
4969
+ const skipStateCheck = input.command === "review" || input.command === "resolve" || input.command === "rerun";
5949
4970
  if (input.issueNumber && !skipStateCheck && !isPRFix) {
5950
4971
  const taskAction = resolveForIssue(input.issueNumber, projectDir);
5951
4972
  logger.info(`Task action: ${taskAction.action}`);
@@ -6002,8 +5023,8 @@ async function main() {
6002
5023
  process.exit(1);
6003
5024
  }
6004
5025
  }
6005
- const taskDir = path29.join(projectDir, ".kody", "tasks", taskId);
6006
- fs32.mkdirSync(taskDir, { recursive: true });
5026
+ const taskDir = path27.join(projectDir, ".kody", "tasks", taskId);
5027
+ fs30.mkdirSync(taskDir, { recursive: true });
6007
5028
  if (input.command === "rerun" && isTaskifyRun(taskDir)) {
6008
5029
  const marker = readTaskifyMarker(taskDir);
6009
5030
  if (marker) {
@@ -6068,7 +5089,7 @@ async function main() {
6068
5089
  console.error(`Runner "${defaultRunnerName2}" health check failed`);
6069
5090
  process.exit(1);
6070
5091
  }
6071
- const result2 = await runStandaloneReview({
5092
+ const result = await runStandaloneReview({
6072
5093
  projectDir,
6073
5094
  runners: runners2,
6074
5095
  prTitle,
@@ -6078,15 +5099,15 @@ async function main() {
6078
5099
  taskId
6079
5100
  });
6080
5101
  if (litellmProcess2) litellmProcess2.kill();
6081
- if (result2.outcome === "failed") {
6082
- console.error(`Review failed: ${result2.error}`);
5102
+ if (result.outcome === "failed") {
5103
+ console.error(`Review failed: ${result.error}`);
6083
5104
  process.exit(1);
6084
5105
  }
6085
- if (result2.reviewContent) {
6086
- console.log(result2.reviewContent);
5106
+ if (result.reviewContent) {
5107
+ console.log(result.reviewContent);
6087
5108
  if (!input.local && prNumber) {
6088
- const comment = formatReviewComment(result2.reviewContent, taskId);
6089
- const verdict = detectReviewVerdict(result2.reviewContent);
5109
+ const comment = formatReviewComment(result.reviewContent, taskId);
5110
+ const verdict = detectReviewVerdict(result.reviewContent);
6090
5111
  const event = verdict === "fail" ? "request-changes" : "approve";
6091
5112
  const posted = submitPRReview(prNumber, comment, event);
6092
5113
  if (!posted) {
@@ -6118,48 +5139,48 @@ async function main() {
6118
5139
  process.exit(1);
6119
5140
  }
6120
5141
  const { runResolve: runResolve2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
6121
- const result2 = await runResolve2({
5142
+ const result = await runResolve2({
6122
5143
  prNumber: input.prNumber,
6123
5144
  projectDir,
6124
5145
  runners: runners2,
6125
5146
  local: input.local ?? true
6126
5147
  });
6127
5148
  if (litellmProcess2) litellmProcess2.kill();
6128
- if (result2.outcome === "failed") {
6129
- console.error(`Resolve failed: ${result2.error}`);
5149
+ if (result.outcome === "failed") {
5150
+ console.error(`Resolve failed: ${result.error}`);
6130
5151
  process.exit(1);
6131
5152
  }
6132
- console.log(`Resolve: ${result2.outcome}`);
5153
+ console.log(`Resolve: ${result.outcome}`);
6133
5154
  process.exit(0);
6134
5155
  }
6135
5156
  logger.info("Preflight checks:");
6136
5157
  runPreflight();
6137
5158
  if (input.task) {
6138
- fs32.writeFileSync(path29.join(taskDir, "task.md"), input.task);
5159
+ fs30.writeFileSync(path27.join(taskDir, "task.md"), input.task);
6139
5160
  }
6140
- const taskMdPath = path29.join(taskDir, "task.md");
6141
- if (!fs32.existsSync(taskMdPath) && isPRFix && input.prNumber) {
5161
+ const taskMdPath = path27.join(taskDir, "task.md");
5162
+ if (!fs30.existsSync(taskMdPath) && isPRFix && input.prNumber) {
6142
5163
  logger.info(`Fetching PR #${input.prNumber} details as task context...`);
6143
5164
  const prDetails = getPRDetails(input.prNumber);
6144
5165
  if (prDetails) {
6145
5166
  const taskContent = `# ${prDetails.title}
6146
5167
 
6147
5168
  ${prDetails.body ?? ""}`;
6148
- fs32.writeFileSync(taskMdPath, taskContent);
5169
+ fs30.writeFileSync(taskMdPath, taskContent);
6149
5170
  logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
6150
5171
  }
6151
- } else if (!fs32.existsSync(taskMdPath) && input.issueNumber) {
5172
+ } else if (!fs30.existsSync(taskMdPath) && input.issueNumber) {
6152
5173
  logger.info(`Fetching issue #${input.issueNumber} body as task...`);
6153
5174
  const issue = getIssue(input.issueNumber);
6154
5175
  if (issue) {
6155
5176
  const taskContent = `# ${issue.title}
6156
5177
 
6157
5178
  ${issue.body ?? ""}`;
6158
- fs32.writeFileSync(taskMdPath, taskContent);
5179
+ fs30.writeFileSync(taskMdPath, taskContent);
6159
5180
  logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
6160
5181
  }
6161
5182
  }
6162
- if (!fs32.existsSync(taskMdPath)) {
5183
+ if (!fs30.existsSync(taskMdPath)) {
6163
5184
  console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
6164
5185
  process.exit(1);
6165
5186
  }
@@ -6297,7 +5318,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
6297
5318
  }
6298
5319
  }
6299
5320
  const state = await runPipeline(ctx);
6300
- const files = fs32.readdirSync(taskDir);
5321
+ const files = fs30.readdirSync(taskDir);
6301
5322
  console.log(`
6302
5323
  Artifacts in ${taskDir}:`);
6303
5324
  for (const f of files) {
@@ -6362,8 +5383,8 @@ var init_entry = __esm({
6362
5383
  });
6363
5384
 
6364
5385
  // src/bin/cli.ts
6365
- import * as fs33 from "fs";
6366
- import * as path30 from "path";
5386
+ import * as fs31 from "fs";
5387
+ import * as path28 from "path";
6367
5388
  import { fileURLToPath as fileURLToPath2 } from "url";
6368
5389
 
6369
5390
  // src/bin/commands/init.ts
@@ -6535,7 +5556,7 @@ function buildConfig(cwd, basic) {
6535
5556
  github: { owner: basic.owner, repo: basic.repo },
6536
5557
  agent: {
6537
5558
  provider: "anthropic",
6538
- modelMap: { cheap: "claude-haiku-4-5-20251001", mid: "claude-sonnet-4-6", strong: "claude-opus-4-6" }
5559
+ modelMap: { cheap: "haiku", mid: "sonnet", strong: "opus" }
6539
5560
  }
6540
5561
  };
6541
5562
  const mcp = detectMcpConfig(cwd, basic.pm, pkg);
@@ -6735,15 +5756,15 @@ function initCommand(opts, pkgRoot) {
6735
5756
 
6736
5757
  // src/bin/commands/bootstrap.ts
6737
5758
  init_architecture_detection();
6738
- import * as fs8 from "fs";
6739
- import * as path7 from "path";
5759
+ import * as fs7 from "fs";
5760
+ import * as path6 from "path";
6740
5761
  import { execFileSync as execFileSync5 } from "child_process";
6741
5762
 
6742
5763
  // src/bin/qa-guide.ts
6743
5764
  import * as fs5 from "fs";
6744
5765
  import * as path4 from "path";
6745
5766
  function discoverQaContext(cwd) {
6746
- const result2 = {
5767
+ const result = {
6747
5768
  routes: [],
6748
5769
  authFiles: [],
6749
5770
  loginPage: null,
@@ -6756,21 +5777,21 @@ function discoverQaContext(cwd) {
6756
5777
  const pkg = JSON.parse(fs5.readFileSync(path4.join(cwd, "package.json"), "utf-8"));
6757
5778
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
6758
5779
  const pm = fs5.existsSync(path4.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs5.existsSync(path4.join(cwd, "yarn.lock")) ? "yarn" : "npm";
6759
- if (pkg.scripts?.dev) result2.devCommand = `${pm} dev`;
6760
- if (allDeps.next || allDeps.nuxt) result2.devPort = 3e3;
6761
- else if (allDeps.vite) result2.devPort = 5173;
5780
+ if (pkg.scripts?.dev) result.devCommand = `${pm} dev`;
5781
+ if (allDeps.next || allDeps.nuxt) result.devPort = 3e3;
5782
+ else if (allDeps.vite) result.devPort = 5173;
6762
5783
  } catch {
6763
5784
  }
6764
5785
  const appDirs = ["src/app", "app"];
6765
5786
  for (const appDir of appDirs) {
6766
5787
  const fullAppDir = path4.join(cwd, appDir);
6767
5788
  if (!fs5.existsSync(fullAppDir)) continue;
6768
- scanRoutes(fullAppDir, appDir, "", result2);
5789
+ scanRoutes(fullAppDir, appDir, "", result);
6769
5790
  break;
6770
5791
  }
6771
5792
  const authPatterns = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
6772
5793
  for (const p of authPatterns) {
6773
- if (fs5.existsSync(path4.join(cwd, p))) result2.authFiles.push(p);
5794
+ if (fs5.existsSync(path4.join(cwd, p))) result.authFiles.push(p);
6774
5795
  }
6775
5796
  const authConfigGlobs = [
6776
5797
  "src/app/api/auth",
@@ -6781,7 +5802,7 @@ function discoverQaContext(cwd) {
6781
5802
  "src/app/api/oauth"
6782
5803
  ];
6783
5804
  for (const g of authConfigGlobs) {
6784
- if (fs5.existsSync(path4.join(cwd, g))) result2.authFiles.push(g);
5805
+ if (fs5.existsSync(path4.join(cwd, g))) result.authFiles.push(g);
6785
5806
  }
6786
5807
  try {
6787
5808
  const rolePaths = [
@@ -6803,7 +5824,7 @@ function discoverQaContext(cwd) {
6803
5824
  if (roleMatches) {
6804
5825
  for (const m of roleMatches) {
6805
5826
  const val = m.match(/['"](\w+)['"]/);
6806
- if (val && !result2.roles.includes(val[1])) result2.roles.push(val[1]);
5827
+ if (val && !result.roles.includes(val[1])) result.roles.push(val[1]);
6807
5828
  }
6808
5829
  }
6809
5830
  const enumMatch = content.match(/(?:enum|type)\s+\w*[Rr]ole\w*\s*[={]([^}]+)/s);
@@ -6812,7 +5833,7 @@ function discoverQaContext(cwd) {
6812
5833
  if (vals) {
6813
5834
  for (const v of vals) {
6814
5835
  const clean = v.replace(/['"]/g, "");
6815
- if (!result2.roles.includes(clean)) result2.roles.push(clean);
5836
+ if (!result.roles.includes(clean)) result.roles.push(clean);
6816
5837
  }
6817
5838
  }
6818
5839
  }
@@ -6822,9 +5843,9 @@ function discoverQaContext(cwd) {
6822
5843
  }
6823
5844
  } catch {
6824
5845
  }
6825
- return result2;
5846
+ return result;
6826
5847
  }
6827
- function scanRoutes(dir, baseDir, prefix, result2) {
5848
+ function scanRoutes(dir, baseDir, prefix, result) {
6828
5849
  let entries;
6829
5850
  try {
6830
5851
  entries = fs5.readdirSync(dir, { withFileTypes: true });
@@ -6835,16 +5856,16 @@ function scanRoutes(dir, baseDir, prefix, result2) {
6835
5856
  if (hasPage) {
6836
5857
  const routePath = prefix || "/";
6837
5858
  const group = prefix.startsWith("/admin") ? "admin" : prefix.includes("/login") ? "auth" : prefix.includes("/signup") ? "auth" : prefix.includes("/api") ? "api" : "frontend";
6838
- result2.routes.push({ path: routePath, group });
6839
- if (prefix.includes("/login")) result2.loginPage = routePath;
6840
- if (prefix.startsWith("/admin") && !result2.adminPath) result2.adminPath = prefix;
5859
+ result.routes.push({ path: routePath, group });
5860
+ if (prefix.includes("/login")) result.loginPage = routePath;
5861
+ if (prefix.startsWith("/admin") && !result.adminPath) result.adminPath = prefix;
6841
5862
  }
6842
5863
  for (const entry of entries) {
6843
5864
  if (!entry.isDirectory()) continue;
6844
5865
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6845
5866
  let segment = entry.name;
6846
5867
  if (segment.startsWith("(") && segment.endsWith(")")) {
6847
- scanRoutes(path4.join(dir, entry.name), baseDir, prefix, result2);
5868
+ scanRoutes(path4.join(dir, entry.name), baseDir, prefix, result);
6848
5869
  continue;
6849
5870
  }
6850
5871
  if (segment.startsWith("[") && segment.endsWith("]")) {
@@ -6853,7 +5874,7 @@ function scanRoutes(dir, baseDir, prefix, result2) {
6853
5874
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
6854
5875
  segment = `:${segment.slice(2, -2)}?`;
6855
5876
  }
6856
- scanRoutes(path4.join(dir, entry.name), baseDir, `${prefix}/${segment}`, result2);
5877
+ scanRoutes(path4.join(dir, entry.name), baseDir, `${prefix}/${segment}`, result);
6857
5878
  }
6858
5879
  }
6859
5880
  function generateQaGuide(discovery) {
@@ -7010,23 +6031,22 @@ function installSkillsForProject(cwd) {
7010
6031
  }
7011
6032
 
7012
6033
  // src/bin/commands/bootstrap.ts
7013
- init_config();
7014
6034
  var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
7015
6035
  function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
7016
- const srcDir = path7.join(cwd, "src");
7017
- const baseDir = fs8.existsSync(srcDir) ? srcDir : cwd;
6036
+ const srcDir = path6.join(cwd, "src");
6037
+ const baseDir = fs7.existsSync(srcDir) ? srcDir : cwd;
7018
6038
  const results = [];
7019
6039
  function walk(dir) {
7020
6040
  const entries = [];
7021
6041
  try {
7022
- for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
6042
+ for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
7023
6043
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
7024
- const full = path7.join(dir, entry.name);
6044
+ const full = path6.join(dir, entry.name);
7025
6045
  if (entry.isDirectory()) {
7026
6046
  entries.push(...walk(full));
7027
6047
  } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
7028
6048
  try {
7029
- const stat = fs8.statSync(full);
6049
+ const stat = fs7.statSync(full);
7030
6050
  if (stat.size >= 200 && stat.size <= 5e3) {
7031
6051
  entries.push({ filePath: full, size: stat.size });
7032
6052
  }
@@ -7040,8 +6060,8 @@ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
7040
6060
  }
7041
6061
  const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
7042
6062
  for (const { filePath } of files) {
7043
- const rel = path7.relative(cwd, filePath);
7044
- const content = fs8.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
6063
+ const rel = path6.relative(cwd, filePath);
6064
+ const content = fs7.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
7045
6065
  results.push(`### File: ${rel}
7046
6066
  \`\`\`typescript
7047
6067
  ${content}
@@ -7053,9 +6073,9 @@ function ghComment(issueNumber, body, cwd) {
7053
6073
  try {
7054
6074
  let repoSlug = "";
7055
6075
  try {
7056
- const configPath = path7.join(cwd, "kody.config.json");
7057
- if (fs8.existsSync(configPath)) {
7058
- const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
6076
+ const configPath = path6.join(cwd, "kody.config.json");
6077
+ if (fs7.existsSync(configPath)) {
6078
+ const config = JSON.parse(fs7.readFileSync(configPath, "utf-8"));
7059
6079
  if (config.github?.owner && config.github?.repo) {
7060
6080
  repoSlug = `${config.github.owner}/${config.github.repo}`;
7061
6081
  }
@@ -7082,9 +6102,7 @@ function ghComment(issueNumber, body, cwd) {
7082
6102
  }
7083
6103
  function bootstrapCommand(opts, pkgRoot) {
7084
6104
  const cwd = process.cwd();
7085
- setConfigDir(cwd);
7086
6105
  const issueNumber = parseInt(process.env.ISSUE_NUMBER ?? "", 10) || 0;
7087
- const bootstrapModel = resolveStageConfig(getProjectConfig(), "bootstrap", "cheap").model;
7088
6106
  console.log(`
7089
6107
  \u{1F527} Kody Bootstrap \u2014 Generating project memory + step files
7090
6108
  `);
@@ -7092,8 +6110,8 @@ function bootstrapCommand(opts, pkgRoot) {
7092
6110
  ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
7093
6111
  }
7094
6112
  const readIfExists = (rel, maxChars = 3e3) => {
7095
- const p = path7.join(cwd, rel);
7096
- if (fs8.existsSync(p)) return fs8.readFileSync(p, "utf-8").slice(0, maxChars);
6113
+ const p = path6.join(cwd, rel);
6114
+ if (fs7.existsSync(p)) return fs7.readFileSync(p, "utf-8").slice(0, maxChars);
7097
6115
  return null;
7098
6116
  };
7099
6117
  let repoContext = "";
@@ -7128,14 +6146,14 @@ ${sampleFiles}
7128
6146
 
7129
6147
  `;
7130
6148
  try {
7131
- const topDirs = fs8.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
6149
+ const topDirs = fs7.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
7132
6150
  repoContext += `## Top-level directories
7133
6151
  ${topDirs.join(", ")}
7134
6152
 
7135
6153
  `;
7136
- const srcDir = path7.join(cwd, "src");
7137
- if (fs8.existsSync(srcDir)) {
7138
- const srcDirs = fs8.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
6154
+ const srcDir = path6.join(cwd, "src");
6155
+ if (fs7.existsSync(srcDir)) {
6156
+ const srcDirs = fs7.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
7139
6157
  if (srcDirs.length > 0) repoContext += `## src/ subdirectories
7140
6158
  ${srcDirs.join(", ")}
7141
6159
 
@@ -7145,19 +6163,19 @@ ${srcDirs.join(", ")}
7145
6163
  }
7146
6164
  const existingFiles = [];
7147
6165
  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"]) {
7148
- if (fs8.existsSync(path7.join(cwd, f))) existingFiles.push(f);
6166
+ if (fs7.existsSync(path6.join(cwd, f))) existingFiles.push(f);
7149
6167
  }
7150
6168
  if (existingFiles.length) repoContext += `## Config files present
7151
6169
  ${existingFiles.join(", ")}
7152
6170
 
7153
6171
  `;
7154
6172
  console.log("\u2500\u2500 Project Memory \u2500\u2500");
7155
- const memoryDir = path7.join(cwd, ".kody", "memory");
7156
- fs8.mkdirSync(memoryDir, { recursive: true });
7157
- const archPath = path7.join(memoryDir, "architecture.md");
7158
- const conventionsPath = path7.join(memoryDir, "conventions.md");
7159
- const existingArch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
7160
- const existingConv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
6173
+ const memoryDir = path6.join(cwd, ".kody", "memory");
6174
+ fs7.mkdirSync(memoryDir, { recursive: true });
6175
+ const archPath = path6.join(memoryDir, "architecture.md");
6176
+ const conventionsPath = path6.join(memoryDir, "conventions.md");
6177
+ const existingArch = fs7.existsSync(archPath) ? fs7.readFileSync(archPath, "utf-8") : "";
6178
+ const existingConv = fs7.existsSync(conventionsPath) ? fs7.readFileSync(conventionsPath, "utf-8") : "";
7161
6179
  const hasExisting = !!(existingArch || existingConv);
7162
6180
  const extendInstruction = hasExisting && !opts.force ? `
7163
6181
  ## Existing Documentation (EXTEND, do not replace)
@@ -7201,7 +6219,7 @@ ${repoContext}`;
7201
6219
  const output = execFileSync5("claude", [
7202
6220
  "--print",
7203
6221
  "--model",
7204
- bootstrapModel,
6222
+ "claude-haiku-4-5-20251001",
7205
6223
  "--dangerously-skip-permissions",
7206
6224
  memoryPrompt
7207
6225
  ], {
@@ -7213,12 +6231,12 @@ ${repoContext}`;
7213
6231
  const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
7214
6232
  const parsed = JSON.parse(cleaned);
7215
6233
  if (parsed.architecture) {
7216
- fs8.writeFileSync(archPath, parsed.architecture);
6234
+ fs7.writeFileSync(archPath, parsed.architecture);
7217
6235
  const lineCount = parsed.architecture.split("\n").length;
7218
6236
  console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
7219
6237
  }
7220
6238
  if (parsed.conventions) {
7221
- fs8.writeFileSync(conventionsPath, parsed.conventions);
6239
+ fs7.writeFileSync(conventionsPath, parsed.conventions);
7222
6240
  const lineCount = parsed.conventions.split("\n").length;
7223
6241
  console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
7224
6242
  }
@@ -7227,39 +6245,39 @@ ${repoContext}`;
7227
6245
  const detected = detectArchitectureBasic(cwd);
7228
6246
  if (detected.length > 0) {
7229
6247
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7230
- fs8.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
6248
+ fs7.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
7231
6249
 
7232
6250
  ## Overview
7233
6251
  ${detected.join("\n")}
7234
6252
  `);
7235
6253
  console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
7236
6254
  }
7237
- fs8.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
6255
+ fs7.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
7238
6256
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
7239
6257
  }
7240
6258
  console.log("\n\u2500\u2500 Step Files \u2500\u2500");
7241
- const stepsDir = path7.join(cwd, ".kody", "steps");
7242
- fs8.mkdirSync(stepsDir, { recursive: true });
7243
- const arch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
7244
- const conv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
6259
+ const stepsDir = path6.join(cwd, ".kody", "steps");
6260
+ fs7.mkdirSync(stepsDir, { recursive: true });
6261
+ const arch = fs7.existsSync(archPath) ? fs7.readFileSync(archPath, "utf-8") : "";
6262
+ const conv = fs7.existsSync(conventionsPath) ? fs7.readFileSync(conventionsPath, "utf-8") : "";
7245
6263
  console.log(" \u23F3 Customizing step files...");
7246
6264
  let stepCount = 0;
7247
6265
  for (const stage of STEP_STAGES) {
7248
- const templatePath = path7.join(pkgRoot, "prompts", `${stage}.md`);
7249
- if (!fs8.existsSync(templatePath)) {
6266
+ const templatePath = path6.join(pkgRoot, "prompts", `${stage}.md`);
6267
+ if (!fs7.existsSync(templatePath)) {
7250
6268
  console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
7251
6269
  continue;
7252
6270
  }
7253
- const stepOutputPath = path7.join(stepsDir, `${stage}.md`);
7254
- if (fs8.existsSync(stepOutputPath) && !opts.force) {
6271
+ const stepOutputPath = path6.join(stepsDir, `${stage}.md`);
6272
+ if (fs7.existsSync(stepOutputPath) && !opts.force) {
7255
6273
  console.log(` \u25CB ${stage}.md \u2014 already exists (use --force to regenerate)`);
7256
6274
  continue;
7257
6275
  }
7258
- const defaultPrompt = fs8.readFileSync(templatePath, "utf-8");
6276
+ const defaultPrompt = fs7.readFileSync(templatePath, "utf-8");
7259
6277
  const contextPlaceholder = "{{TASK_CONTEXT}}";
7260
6278
  const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
7261
6279
  if (placeholderIdx === -1) {
7262
- fs8.copyFileSync(templatePath, stepOutputPath);
6280
+ fs7.copyFileSync(templatePath, stepOutputPath);
7263
6281
  stepCount++;
7264
6282
  console.log(` \u2713 ${stage}.md`);
7265
6283
  continue;
@@ -7304,7 +6322,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
7304
6322
  const output = execFileSync5("claude", [
7305
6323
  "--print",
7306
6324
  "--model",
7307
- bootstrapModel,
6325
+ "claude-haiku-4-5-20251001",
7308
6326
  "--dangerously-skip-permissions",
7309
6327
  customizationPrompt
7310
6328
  ], {
@@ -7316,23 +6334,23 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
7316
6334
  let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
7317
6335
  cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
7318
6336
  const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
7319
- fs8.writeFileSync(stepOutputPath, finalPrompt);
6337
+ fs7.writeFileSync(stepOutputPath, finalPrompt);
7320
6338
  stepCount++;
7321
6339
  console.log(` \u2713 ${stage}.md`);
7322
6340
  } catch {
7323
6341
  console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
7324
- fs8.copyFileSync(templatePath, stepOutputPath);
6342
+ fs7.copyFileSync(templatePath, stepOutputPath);
7325
6343
  stepCount++;
7326
6344
  }
7327
6345
  }
7328
6346
  console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
7329
6347
  console.log("\n\u2500\u2500 QA Guide \u2500\u2500");
7330
- const qaGuidePath = path7.join(cwd, ".kody", "qa-guide.md");
7331
- if (!fs8.existsSync(qaGuidePath) || opts.force) {
6348
+ const qaGuidePath = path6.join(cwd, ".kody", "qa-guide.md");
6349
+ if (!fs7.existsSync(qaGuidePath) || opts.force) {
7332
6350
  const discovery = discoverQaContext(cwd);
7333
6351
  if (discovery.routes.length > 0) {
7334
6352
  const qaGuide = generateQaGuide(discovery);
7335
- fs8.writeFileSync(qaGuidePath, qaGuide);
6353
+ fs7.writeFileSync(qaGuidePath, qaGuide);
7336
6354
  console.log(` \u2713 .kody/qa-guide.md (${discovery.routes.length} routes, ${discovery.roles.length} roles)`);
7337
6355
  if (discovery.loginPage) console.log(` \u2713 Login page detected: ${discovery.loginPage}`);
7338
6356
  if (discovery.adminPath) console.log(` \u2713 Admin panel detected: ${discovery.adminPath}`);
@@ -7347,9 +6365,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
7347
6365
  try {
7348
6366
  let repoSlug = "";
7349
6367
  try {
7350
- const configPath = path7.join(cwd, "kody.config.json");
7351
- if (fs8.existsSync(configPath)) {
7352
- const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
6368
+ const configPath = path6.join(cwd, "kody.config.json");
6369
+ if (fs7.existsSync(configPath)) {
6370
+ const config = JSON.parse(fs7.readFileSync(configPath, "utf-8"));
7353
6371
  if (config.github?.owner && config.github?.repo) {
7354
6372
  repoSlug = `${config.github.owner}/${config.github.repo}`;
7355
6373
  }
@@ -7422,19 +6440,19 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
7422
6440
  ".kody/memory/conventions.md",
7423
6441
  ".kody/qa-guide.md",
7424
6442
  ...installedSkillPaths
7425
- ].filter((f) => fs8.existsSync(path7.join(cwd, f)));
7426
- if (fs8.existsSync(path7.join(cwd, "skills-lock.json"))) {
6443
+ ].filter((f) => fs7.existsSync(path6.join(cwd, f)));
6444
+ if (fs7.existsSync(path6.join(cwd, "skills-lock.json"))) {
7427
6445
  filesToCommit.push("skills-lock.json");
7428
6446
  }
7429
6447
  for (const stage of STEP_STAGES) {
7430
6448
  const stepFile = `.kody/steps/${stage}.md`;
7431
- if (fs8.existsSync(path7.join(cwd, stepFile))) {
6449
+ if (fs7.existsSync(path6.join(cwd, stepFile))) {
7432
6450
  filesToCommit.push(stepFile);
7433
6451
  }
7434
6452
  }
7435
6453
  if (filesToCommit.length > 0) {
7436
6454
  try {
7437
- const fullPaths = filesToCommit.map((f) => path7.join(cwd, f));
6455
+ const fullPaths = filesToCommit.map((f) => path6.join(cwd, f));
7438
6456
  for (let pass = 0; pass < 2; pass++) {
7439
6457
  execFileSync5("npx", ["prettier", "--write", ...fullPaths], {
7440
6458
  cwd,
@@ -7461,9 +6479,9 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
7461
6479
  console.log(` \u2713 Pushed branch: ${branchName}`);
7462
6480
  let baseBranch = "main";
7463
6481
  try {
7464
- const configPath = path7.join(cwd, "kody.config.json");
7465
- if (fs8.existsSync(configPath)) {
7466
- const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
6482
+ const configPath = path6.join(cwd, "kody.config.json");
6483
+ if (fs7.existsSync(configPath)) {
6484
+ const config = JSON.parse(fs7.readFileSync(configPath, "utf-8"));
7467
6485
  baseBranch = config.git?.defaultBranch ?? "main";
7468
6486
  }
7469
6487
  } catch {
@@ -7537,11 +6555,11 @@ Create it manually.`, cwd);
7537
6555
 
7538
6556
  // src/bin/cli.ts
7539
6557
  init_architecture_detection();
7540
- var __dirname2 = path30.dirname(fileURLToPath2(import.meta.url));
7541
- var PKG_ROOT = path30.resolve(__dirname2, "..", "..");
6558
+ var __dirname2 = path28.dirname(fileURLToPath2(import.meta.url));
6559
+ var PKG_ROOT = path28.resolve(__dirname2, "..", "..");
7542
6560
  function getVersion() {
7543
- const pkgPath = path30.join(PKG_ROOT, "package.json");
7544
- const pkg = JSON.parse(fs33.readFileSync(pkgPath, "utf-8"));
6561
+ const pkgPath = path28.join(PKG_ROOT, "package.json");
6562
+ const pkg = JSON.parse(fs31.readFileSync(pkgPath, "utf-8"));
7545
6563
  return pkg.version;
7546
6564
  }
7547
6565
  var args = process.argv.slice(2);
@@ -7552,8 +6570,6 @@ if (command === "init") {
7552
6570
  bootstrapCommand({ force: args.includes("--force") }, PKG_ROOT);
7553
6571
  } else if (command === "taskify") {
7554
6572
  Promise.resolve().then(() => (init_taskify_command(), taskify_command_exports)).then(({ runTaskifyCommand: runTaskifyCommand2 }) => runTaskifyCommand2());
7555
- } else if (command === "test-model") {
7556
- Promise.resolve().then(() => (init_test_model_command(), test_model_command_exports)).then(({ runTestModelCommand: runTestModelCommand2 }) => runTestModelCommand2());
7557
6573
  } else if (command === "ci-parse") {
7558
6574
  Promise.resolve().then(() => (init_parse_inputs(), parse_inputs_exports)).then(({ runCiParse: runCiParse2 }) => runCiParse2());
7559
6575
  } else if (command === "version" || command === "--version" || command === "-v") {