@kody-ade/kody-engine-lite 0.1.126 → 0.1.128

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin/cli.js +486 -621
  2. package/package.json +1 -1
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 fs8 from "fs";
184
- import * as path7 from "path";
183
+ import * as fs7 from "fs";
184
+ import * as path6 from "path";
185
185
  function resolveStageConfig(config, stageName, modelTier) {
186
186
  const stageOverride = config.agent.stages?.[stageName];
187
187
  if (stageOverride) return stageOverride;
@@ -225,10 +225,10 @@ function setConfigDir(dir) {
225
225
  }
226
226
  function getProjectConfig() {
227
227
  if (_config) return _config;
228
- const configPath = path7.join(_configDir ?? process.cwd(), "kody.config.json");
229
- if (fs8.existsSync(configPath)) {
228
+ const configPath = path6.join(_configDir ?? process.cwd(), "kody.config.json");
229
+ if (fs7.existsSync(configPath)) {
230
230
  try {
231
- const result2 = parseJsonSafe(fs8.readFileSync(configPath, "utf-8"));
231
+ const result2 = parseJsonSafe(fs7.readFileSync(configPath, "utf-8"));
232
232
  if (!result2.ok) {
233
233
  logger.warn(`kody.config.json: ${result2.error} \u2014 using defaults`);
234
234
  _config = { ...DEFAULT_CONFIG };
@@ -302,24 +302,24 @@ var init_config = __esm({
302
302
  });
303
303
 
304
304
  // src/agent-runner.ts
305
- import { spawn, execFileSync as execFileSync6 } from "child_process";
305
+ import { spawn, execFileSync as execFileSync5 } from "child_process";
306
306
  function writeStdin(child, prompt) {
307
- return new Promise((resolve6, reject) => {
307
+ return new Promise((resolve5, reject) => {
308
308
  if (!child.stdin) {
309
- resolve6();
309
+ resolve5();
310
310
  return;
311
311
  }
312
312
  child.stdin.write(prompt, (err) => {
313
313
  if (err) reject(err);
314
314
  else {
315
315
  child.stdin.end();
316
- resolve6();
316
+ resolve5();
317
317
  }
318
318
  });
319
319
  });
320
320
  }
321
321
  function waitForProcess(child, timeout) {
322
- return new Promise((resolve6) => {
322
+ return new Promise((resolve5) => {
323
323
  const stdoutChunks = [];
324
324
  const stderrChunks = [];
325
325
  child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
@@ -332,7 +332,7 @@ function waitForProcess(child, timeout) {
332
332
  }, timeout);
333
333
  child.on("exit", (code) => {
334
334
  clearTimeout(timer);
335
- resolve6({
335
+ resolve5({
336
336
  code,
337
337
  stdout: Buffer.concat(stdoutChunks).toString(),
338
338
  stderr: Buffer.concat(stderrChunks).toString()
@@ -340,7 +340,7 @@ function waitForProcess(child, timeout) {
340
340
  });
341
341
  child.on("error", (err) => {
342
342
  clearTimeout(timer);
343
- resolve6({ code: -1, stdout: "", stderr: err.message });
343
+ resolve5({ code: -1, stdout: "", stderr: err.message });
344
344
  });
345
345
  });
346
346
  }
@@ -376,7 +376,7 @@ ${errDetail}`
376
376
  }
377
377
  function checkCommand2(command2, args2) {
378
378
  try {
379
- execFileSync6(command2, args2, { timeout: 1e4, stdio: "pipe" });
379
+ execFileSync5(command2, args2, { timeout: 1e4, stdio: "pipe" });
380
380
  return true;
381
381
  } catch {
382
382
  return false;
@@ -521,7 +521,7 @@ var init_mcp_config = __esm({
521
521
  });
522
522
 
523
523
  // src/github-api.ts
524
- import { execFileSync as execFileSync7 } from "child_process";
524
+ import { execFileSync as execFileSync6 } from "child_process";
525
525
  function isGhExecError(err) {
526
526
  return typeof err === "object" && err !== null;
527
527
  }
@@ -545,7 +545,7 @@ function ghToken() {
545
545
  function gh(args2, options) {
546
546
  const token = ghToken();
547
547
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
548
- return execFileSync7("gh", args2, {
548
+ return execFileSync6("gh", args2, {
549
549
  encoding: "utf-8",
550
550
  timeout: API_TIMEOUT_MS,
551
551
  cwd: _ghCwd,
@@ -949,18 +949,18 @@ var init_github_api = __esm({
949
949
  });
950
950
 
951
951
  // src/cli/task-resolution.ts
952
- import * as fs10 from "fs";
953
- import * as path9 from "path";
954
- import { execFileSync as execFileSync8 } from "child_process";
952
+ import * as fs9 from "fs";
953
+ import * as path8 from "path";
954
+ import { execFileSync as execFileSync7 } from "child_process";
955
955
  function findLatestTaskForIssue(issueNumber, projectDir) {
956
- const tasksDir = path9.join(projectDir, ".kody", "tasks");
957
- if (!fs10.existsSync(tasksDir)) return null;
958
- const allDirs = fs10.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
956
+ const tasksDir = path8.join(projectDir, ".kody", "tasks");
957
+ if (!fs9.existsSync(tasksDir)) return null;
958
+ const allDirs = fs9.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
959
959
  const prefix = `${issueNumber}-`;
960
960
  const direct = allDirs.find((d) => d.startsWith(prefix));
961
961
  if (direct) return direct;
962
962
  try {
963
- const branch = execFileSync8("git", ["branch", "--show-current"], {
963
+ const branch = execFileSync7("git", ["branch", "--show-current"], {
964
964
  encoding: "utf-8",
965
965
  cwd: projectDir,
966
966
  timeout: 5e3,
@@ -999,14 +999,14 @@ function resolveTaskIdFromComments(issueNumber) {
999
999
  }
1000
1000
  }
1001
1001
  function findPausedTaskifyForIssue(issueNumber, projectDir) {
1002
- const tasksDir = path9.join(projectDir, ".kody", "tasks");
1003
- if (!fs10.existsSync(tasksDir)) return null;
1004
- const allDirs = fs10.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
1002
+ const tasksDir = path8.join(projectDir, ".kody", "tasks");
1003
+ if (!fs9.existsSync(tasksDir)) return null;
1004
+ const allDirs = fs9.readdirSync(tasksDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort().reverse();
1005
1005
  for (const dir of allDirs) {
1006
- const markerPath = path9.join(tasksDir, dir, "taskify.marker");
1007
- if (!fs10.existsSync(markerPath)) continue;
1006
+ const markerPath = path8.join(tasksDir, dir, "taskify.marker");
1007
+ if (!fs9.existsSync(markerPath)) continue;
1008
1008
  try {
1009
- const marker = JSON.parse(fs10.readFileSync(markerPath, "utf-8"));
1009
+ const marker = JSON.parse(fs9.readFileSync(markerPath, "utf-8"));
1010
1010
  if (marker.issueNumber === issueNumber) return dir;
1011
1011
  } catch {
1012
1012
  }
@@ -1030,10 +1030,10 @@ var init_task_resolution = __esm({
1030
1030
  });
1031
1031
 
1032
1032
  // src/cli/litellm.ts
1033
- import * as fs11 from "fs";
1033
+ import * as fs10 from "fs";
1034
1034
  import * as os from "os";
1035
- import * as path10 from "path";
1036
- import { execFileSync as execFileSync9 } from "child_process";
1035
+ import * as path9 from "path";
1036
+ import { execFileSync as execFileSync8 } from "child_process";
1037
1037
  async function checkLitellmHealth(url) {
1038
1038
  try {
1039
1039
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -1132,17 +1132,17 @@ async function tryStartLitellm(url, projectDir, generatedConfig) {
1132
1132
  logger.warn("No provider configured in kody.config.json \u2014 cannot start LiteLLM proxy");
1133
1133
  return null;
1134
1134
  }
1135
- const configPath = path10.join(os.tmpdir(), "kody-litellm-config.yaml");
1136
- fs11.writeFileSync(configPath, generatedConfig);
1135
+ const configPath = path9.join(os.tmpdir(), "kody-litellm-config.yaml");
1136
+ fs10.writeFileSync(configPath, generatedConfig);
1137
1137
  const portMatch = url.match(/:(\d+)/);
1138
1138
  const port = portMatch ? portMatch[1] : "4000";
1139
1139
  let litellmFound = false;
1140
1140
  try {
1141
- execFileSync9("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
1141
+ execFileSync8("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
1142
1142
  litellmFound = true;
1143
1143
  } catch {
1144
1144
  try {
1145
- execFileSync9("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
1145
+ execFileSync8("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
1146
1146
  litellmFound = true;
1147
1147
  } catch {
1148
1148
  }
@@ -1155,17 +1155,17 @@ async function tryStartLitellm(url, projectDir, generatedConfig) {
1155
1155
  let cmd;
1156
1156
  let args2;
1157
1157
  try {
1158
- execFileSync9("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
1158
+ execFileSync8("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
1159
1159
  cmd = "litellm";
1160
1160
  args2 = ["--config", configPath, "--port", port];
1161
1161
  } catch {
1162
1162
  cmd = "python3";
1163
1163
  args2 = ["-m", "litellm", "--config", configPath, "--port", port];
1164
1164
  }
1165
- const dotenvPath = path10.join(projectDir, ".env");
1165
+ const dotenvPath = path9.join(projectDir, ".env");
1166
1166
  const dotenvVars = {};
1167
- if (fs11.existsSync(dotenvPath)) {
1168
- for (const rawLine of fs11.readFileSync(dotenvPath, "utf-8").split("\n")) {
1167
+ if (fs10.existsSync(dotenvPath)) {
1168
+ for (const rawLine of fs10.readFileSync(dotenvPath, "utf-8").split("\n")) {
1169
1169
  const line = rawLine.trim();
1170
1170
  if (!line || line.startsWith("#")) continue;
1171
1171
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -1225,8 +1225,8 @@ __export(taskify_command_exports, {
1225
1225
  taskifyCommand: () => taskifyCommand,
1226
1226
  topoSort: () => topoSort
1227
1227
  });
1228
- import * as fs12 from "fs";
1229
- import * as path11 from "path";
1228
+ import * as fs11 from "fs";
1229
+ import * as path10 from "path";
1230
1230
  import { fileURLToPath } from "url";
1231
1231
  import { execSync } from "child_process";
1232
1232
  function topoSort(tasks) {
@@ -1270,10 +1270,10 @@ function hasFlag(args2, flag) {
1270
1270
  async function runTaskifyCommand() {
1271
1271
  const args2 = process.argv.slice(3);
1272
1272
  const cwdArg = getArg(args2, "--cwd") ?? process.cwd();
1273
- const projectDir = path11.resolve(cwdArg);
1273
+ const projectDir = path10.resolve(cwdArg);
1274
1274
  const ticketId = getArg(args2, "--ticket") ?? process.env.TICKET_ID;
1275
1275
  const prdFileArg = getArg(args2, "--file") ?? process.env.PRD_FILE;
1276
- const prdFile = prdFileArg ? path11.resolve(projectDir, prdFileArg) : void 0;
1276
+ const prdFile = prdFileArg ? path10.resolve(projectDir, prdFileArg) : void 0;
1277
1277
  const issueNumberStr = getArg(args2, "--issue-number") ?? process.env.ISSUE_NUMBER ?? "";
1278
1278
  const issueNumber = issueNumberStr ? parseInt(issueNumberStr, 10) : void 0;
1279
1279
  const feedback = getArg(args2, "--feedback") ?? process.env.FEEDBACK;
@@ -1284,7 +1284,7 @@ async function runTaskifyCommand() {
1284
1284
  logger.error("Usage: kody taskify --ticket <ticket-id> OR kody taskify --file <prd.md> OR kody taskify --issue-number <n>");
1285
1285
  process.exit(1);
1286
1286
  }
1287
- if (prdFile && !fs12.existsSync(prdFile)) {
1287
+ if (prdFile && !fs11.existsSync(prdFile)) {
1288
1288
  logger.error(`File not found: ${prdFile}`);
1289
1289
  process.exit(1);
1290
1290
  }
@@ -1325,8 +1325,8 @@ async function runTaskifyCommand() {
1325
1325
  async function taskifyCommand(opts) {
1326
1326
  const { ticketId, prdFile, issueNumber, feedback, local, projectDir, taskId } = opts;
1327
1327
  const config = getProjectConfig();
1328
- const taskDir = path11.join(projectDir, ".kody", "tasks", taskId);
1329
- fs12.mkdirSync(taskDir, { recursive: true });
1328
+ const taskDir = path10.join(projectDir, ".kody", "tasks", taskId);
1329
+ fs11.mkdirSync(taskDir, { recursive: true });
1330
1330
  const mode = prdFile ? "file" : ticketId ? "ticket" : "issue";
1331
1331
  logger.info(`[taskify] mode=${mode} source=${ticketId ?? prdFile ?? `issue#${issueNumber}`} issue=${issueNumber ?? "none"} task=${taskId}`);
1332
1332
  let mcpConfigJson;
@@ -1350,7 +1350,7 @@ Add the required MCP server config to \`kody.config.json\` and try again.`
1350
1350
  }
1351
1351
  const sc = resolveStageConfig(config, "taskify", "strong");
1352
1352
  const model = sc.model;
1353
- const fileContent = prdFile ? fs12.readFileSync(prdFile, "utf-8") : void 0;
1353
+ const fileContent = prdFile ? fs11.readFileSync(prdFile, "utf-8") : void 0;
1354
1354
  let issueBody;
1355
1355
  if (mode === "issue" && issueNumber) {
1356
1356
  const issue = getIssue(issueNumber);
@@ -1366,10 +1366,10 @@ ${issue.body}`;
1366
1366
  let projectContext;
1367
1367
  {
1368
1368
  const parts = [];
1369
- const memoryPath = path11.join(projectDir, ".kody", "memory.md");
1370
- if (fs12.existsSync(memoryPath)) {
1369
+ const memoryPath = path10.join(projectDir, ".kody", "memory.md");
1370
+ if (fs11.existsSync(memoryPath)) {
1371
1371
  try {
1372
- const content = fs12.readFileSync(memoryPath, "utf-8").slice(0, 2e3);
1372
+ const content = fs11.readFileSync(memoryPath, "utf-8").slice(0, 2e3);
1373
1373
  if (content.trim()) parts.push(`### Project Memory
1374
1374
  ${content}`);
1375
1375
  } catch {
@@ -1388,14 +1388,14 @@ ${lines.join("\n")}
1388
1388
  }
1389
1389
  const prompt = buildPrompt({ ticketId, fileContent, issueBody, taskDir, feedback, projectContext });
1390
1390
  if (issueNumber && !local) {
1391
- const src = mode === "file" ? `file \`${path11.basename(prdFile)}\`` : mode === "ticket" ? `ticket **${ticketId}**` : `issue #${issueNumber} description`;
1391
+ const src = mode === "file" ? `file \`${path10.basename(prdFile)}\`` : mode === "ticket" ? `ticket **${ticketId}**` : `issue #${issueNumber} description`;
1392
1392
  const runUrl = process.env.RUN_URL ? ` ([logs](${process.env.RUN_URL}))` : "";
1393
1393
  postComment(issueNumber, `\u{1F680} Kody pipeline started: \`${taskId}\`${runUrl}
1394
1394
 
1395
1395
  Kody is decomposing ${src} into tasks...`);
1396
1396
  setLifecycleLabel(issueNumber, "planning");
1397
1397
  }
1398
- fs12.writeFileSync(path11.join(taskDir, MARKER_FILE), JSON.stringify({ ticketId, prdFile, issueNumber }));
1398
+ fs11.writeFileSync(path10.join(taskDir, MARKER_FILE), JSON.stringify({ ticketId, prdFile, issueNumber }));
1399
1399
  const runner = opts.runner ?? createClaudeCodeRunner();
1400
1400
  logger.info(` model=${model} timeout=${TASKIFY_TIMEOUT_MS / 1e3}s`);
1401
1401
  const result2 = await runner.run("taskify", prompt, model, TASKIFY_TIMEOUT_MS, taskDir, {
@@ -1413,8 +1413,8 @@ Kody is decomposing ${src} into tasks...`);
1413
1413
  }
1414
1414
  throw new TaskifyError(errMsg);
1415
1415
  }
1416
- const resultPath = path11.join(taskDir, RESULT_FILE);
1417
- if (!fs12.existsSync(resultPath)) {
1416
+ const resultPath = path10.join(taskDir, RESULT_FILE);
1417
+ if (!fs11.existsSync(resultPath)) {
1418
1418
  const errMsg = `Claude did not write ${RESULT_FILE}. Output:
1419
1419
 
1420
1420
  ${result2.output?.slice(0, 500) ?? "(none)"}`;
@@ -1428,7 +1428,7 @@ ${errMsg}`);
1428
1428
  }
1429
1429
  let parsed;
1430
1430
  try {
1431
- parsed = JSON.parse(fs12.readFileSync(resultPath, "utf-8"));
1431
+ parsed = JSON.parse(fs11.readFileSync(resultPath, "utf-8"));
1432
1432
  } catch {
1433
1433
  const errMsg = `Could not parse ${RESULT_FILE} as JSON.`;
1434
1434
  if (issueNumber && !local) {
@@ -1437,7 +1437,7 @@ ${errMsg}`);
1437
1437
  }
1438
1438
  throw new TaskifyError(errMsg);
1439
1439
  }
1440
- const sourceLabel = ticketId ?? (prdFile ? path11.basename(prdFile) : issueNumber ? `issue #${issueNumber}` : "spec");
1440
+ const sourceLabel = ticketId ?? (prdFile ? path10.basename(prdFile) : issueNumber ? `issue #${issueNumber}` : "spec");
1441
1441
  if (parsed.status === "questions") {
1442
1442
  handleQuestions(parsed, sourceLabel, issueNumber, local ?? false);
1443
1443
  } else if (parsed.status === "ready") {
@@ -1537,15 +1537,15 @@ function buildPrompt(opts) {
1537
1537
  const { ticketId, fileContent, issueBody, taskDir, feedback, projectContext } = opts;
1538
1538
  const scriptDir = new URL(".", import.meta.url).pathname;
1539
1539
  const candidates = [
1540
- path11.resolve(scriptDir, "..", "prompts", "taskify-ticket.md"),
1541
- path11.resolve(scriptDir, "..", "..", "prompts", "taskify-ticket.md"),
1542
- path11.resolve(__dirname, "..", "..", "prompts", "taskify-ticket.md"),
1543
- path11.resolve(__dirname, "..", "prompts", "taskify-ticket.md")
1540
+ path10.resolve(scriptDir, "..", "prompts", "taskify-ticket.md"),
1541
+ path10.resolve(scriptDir, "..", "..", "prompts", "taskify-ticket.md"),
1542
+ path10.resolve(__dirname, "..", "..", "prompts", "taskify-ticket.md"),
1543
+ path10.resolve(__dirname, "..", "prompts", "taskify-ticket.md")
1544
1544
  ];
1545
1545
  let template = "";
1546
1546
  for (const candidate of candidates) {
1547
- if (fs12.existsSync(candidate)) {
1548
- template = fs12.readFileSync(candidate, "utf-8");
1547
+ if (fs11.existsSync(candidate)) {
1548
+ template = fs11.readFileSync(candidate, "utf-8");
1549
1549
  break;
1550
1550
  }
1551
1551
  }
@@ -1569,13 +1569,13 @@ function buildPrompt(opts) {
1569
1569
  return template;
1570
1570
  }
1571
1571
  function isTaskifyRun(taskDir) {
1572
- return fs12.existsSync(path11.join(taskDir, MARKER_FILE));
1572
+ return fs11.existsSync(path10.join(taskDir, MARKER_FILE));
1573
1573
  }
1574
1574
  function readTaskifyMarker(taskDir) {
1575
- const markerPath = path11.join(taskDir, MARKER_FILE);
1576
- if (!fs12.existsSync(markerPath)) return null;
1575
+ const markerPath = path10.join(taskDir, MARKER_FILE);
1576
+ if (!fs11.existsSync(markerPath)) return null;
1577
1577
  try {
1578
- return JSON.parse(fs12.readFileSync(markerPath, "utf-8"));
1578
+ return JSON.parse(fs11.readFileSync(markerPath, "utf-8"));
1579
1579
  } catch {
1580
1580
  return null;
1581
1581
  }
@@ -1591,7 +1591,7 @@ var init_taskify_command = __esm({
1591
1591
  init_logger();
1592
1592
  init_task_resolution();
1593
1593
  init_litellm();
1594
- __dirname = path11.dirname(fileURLToPath(import.meta.url));
1594
+ __dirname = path10.dirname(fileURLToPath(import.meta.url));
1595
1595
  TaskifyError = class extends Error {
1596
1596
  constructor(message) {
1597
1597
  super(message);
@@ -1607,9 +1607,9 @@ var init_taskify_command = __esm({
1607
1607
  });
1608
1608
 
1609
1609
  // src/cli/test-model-tests.ts
1610
- import * as fs13 from "fs";
1610
+ import * as fs12 from "fs";
1611
1611
  import * as os2 from "os";
1612
- import * as path12 from "path";
1612
+ import * as path11 from "path";
1613
1613
  import * as zlib from "zlib";
1614
1614
  import { spawnSync, execSync as execSync2 } from "child_process";
1615
1615
  function canRunApiTests(ctx) {
@@ -1937,8 +1937,8 @@ async function testExtendedThinking(ctx) {
1937
1937
  async function testToolRead(ctx) {
1938
1938
  if (!canRunApiTests(ctx)) {
1939
1939
  const t2 = Date.now();
1940
- const testFile2 = path12.join(os2.tmpdir(), "kody-test-model-read.txt");
1941
- fs13.writeFileSync(testFile2, "KODY_SECRET_CONTENT_42");
1940
+ const testFile2 = path11.join(os2.tmpdir(), "kody-test-model-read.txt");
1941
+ fs12.writeFileSync(testFile2, "KODY_SECRET_CONTENT_42");
1942
1942
  try {
1943
1943
  const r = runClaudeTest(ctx, `Read the file ${testFile2} and tell me its exact contents. Reply with ONLY the file contents.`);
1944
1944
  const ok = r.stdout.includes("KODY_SECRET_CONTENT_42");
@@ -1952,12 +1952,12 @@ async function testToolRead(ctx) {
1952
1952
  { toolSelection: ok ? 100 : 0 }
1953
1953
  );
1954
1954
  } finally {
1955
- fs13.rmSync(testFile2, { force: true });
1955
+ fs12.rmSync(testFile2, { force: true });
1956
1956
  }
1957
1957
  }
1958
1958
  const t = Date.now();
1959
- const testFile = path12.join(os2.tmpdir(), "kody-test-model-read.txt");
1960
- fs13.writeFileSync(testFile, "KODY_SECRET_CONTENT_42");
1959
+ const testFile = path11.join(os2.tmpdir(), "kody-test-model-read.txt");
1960
+ fs12.writeFileSync(testFile, "KODY_SECRET_CONTENT_42");
1961
1961
  try {
1962
1962
  const conv = await runToolConversation(
1963
1963
  ctx,
@@ -1986,17 +1986,17 @@ async function testToolRead(ctx) {
1986
1986
  { toolSelection: calledRead ? 100 : 0 }
1987
1987
  );
1988
1988
  } finally {
1989
- fs13.rmSync(testFile, { force: true });
1989
+ fs12.rmSync(testFile, { force: true });
1990
1990
  }
1991
1991
  }
1992
1992
  async function testToolEdit(ctx) {
1993
1993
  if (!canRunApiTests(ctx)) {
1994
1994
  const t2 = Date.now();
1995
- const testFile = path12.join(os2.tmpdir(), "kody-test-model-edit.txt");
1996
- fs13.writeFileSync(testFile, "hello world");
1995
+ const testFile = path11.join(os2.tmpdir(), "kody-test-model-edit.txt");
1996
+ fs12.writeFileSync(testFile, "hello world");
1997
1997
  try {
1998
1998
  const r = runClaudeTest(ctx, `Use the Edit tool to replace "hello" with "goodbye" in ${testFile}. Do nothing else.`);
1999
- const content = fs13.existsSync(testFile) ? fs13.readFileSync(testFile, "utf-8") : "";
1999
+ const content = fs12.existsSync(testFile) ? fs12.readFileSync(testFile, "utf-8") : "";
2000
2000
  const ok = content.includes("goodbye");
2001
2001
  return result(
2002
2002
  "tool_edit",
@@ -2008,7 +2008,7 @@ async function testToolEdit(ctx) {
2008
2008
  { toolSelection: ok ? 100 : 0 }
2009
2009
  );
2010
2010
  } finally {
2011
- fs13.rmSync(testFile, { force: true });
2011
+ fs12.rmSync(testFile, { force: true });
2012
2012
  }
2013
2013
  }
2014
2014
  const t = Date.now();
@@ -2082,8 +2082,8 @@ async function testToolBash(ctx) {
2082
2082
  async function testImageAttachment(ctx) {
2083
2083
  if (!canRunApiTests(ctx)) {
2084
2084
  const t2 = Date.now();
2085
- const tmpPng = path12.join(os2.tmpdir(), "kody-test-image.png");
2086
- fs13.writeFileSync(tmpPng, createRedPng());
2085
+ const tmpPng = path11.join(os2.tmpdir(), "kody-test-image.png");
2086
+ fs12.writeFileSync(tmpPng, createRedPng());
2087
2087
  try {
2088
2088
  const r = runClaudeTest(ctx, `Read the image file at ${tmpPng} and tell me what color it is. Reply with just the color name.`);
2089
2089
  const text2 = r.stdout.toLowerCase();
@@ -2097,7 +2097,7 @@ async function testImageAttachment(ctx) {
2097
2097
  ok ? "Image processed correctly via CLI" : `Got: ${text2.slice(0, 80)}`
2098
2098
  );
2099
2099
  } finally {
2100
- fs13.rmSync(tmpPng, { force: true });
2100
+ fs12.rmSync(tmpPng, { force: true });
2101
2101
  }
2102
2102
  }
2103
2103
  const t = Date.now();
@@ -2312,10 +2312,10 @@ async function testReviewStage(ctx) {
2312
2312
  }
2313
2313
  async function testMcpTools(ctx) {
2314
2314
  const t = Date.now();
2315
- const mcpConfig = path12.join(os2.tmpdir(), `kody-test-mcp-${Date.now()}.json`);
2316
- const testFile = path12.join(ctx.projectDir, "kody-mcp-compat-test.txt");
2315
+ const mcpConfig = path11.join(os2.tmpdir(), `kody-test-mcp-${Date.now()}.json`);
2316
+ const testFile = path11.join(ctx.projectDir, "kody-mcp-compat-test.txt");
2317
2317
  try {
2318
- fs13.writeFileSync(mcpConfig, JSON.stringify({
2318
+ fs12.writeFileSync(mcpConfig, JSON.stringify({
2319
2319
  mcpServers: {
2320
2320
  filesystem: { command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", ctx.projectDir] }
2321
2321
  }
@@ -2326,8 +2326,8 @@ async function testMcpTools(ctx) {
2326
2326
  ["--mcp-config", mcpConfig],
2327
2327
  12e4
2328
2328
  );
2329
- const created = fs13.existsSync(testFile);
2330
- const content = created ? fs13.readFileSync(testFile, "utf-8").trim() : "";
2329
+ const created = fs12.existsSync(testFile);
2330
+ const content = created ? fs12.readFileSync(testFile, "utf-8").trim() : "";
2331
2331
  const correct = content.includes("mcp-ok");
2332
2332
  return result(
2333
2333
  "mcp_tools",
@@ -2340,8 +2340,8 @@ async function testMcpTools(ctx) {
2340
2340
  } catch (err) {
2341
2341
  return result("mcp_tools", "advanced", "warn", 0, Date.now() - t, `MCP test error: ${err instanceof Error ? err.message : String(err)}`);
2342
2342
  } finally {
2343
- fs13.rmSync(mcpConfig, { force: true });
2344
- fs13.rmSync(testFile, { force: true });
2343
+ fs12.rmSync(mcpConfig, { force: true });
2344
+ fs12.rmSync(testFile, { force: true });
2345
2345
  revertChanges(ctx.projectDir);
2346
2346
  }
2347
2347
  }
@@ -2522,10 +2522,10 @@ var test_model_command_exports = {};
2522
2522
  __export(test_model_command_exports, {
2523
2523
  runTestModelCommand: () => runTestModelCommand
2524
2524
  });
2525
- import * as fs14 from "fs";
2525
+ import * as fs13 from "fs";
2526
2526
  import * as os3 from "os";
2527
- import * as path13 from "path";
2528
- import { execFileSync as execFileSync10 } from "child_process";
2527
+ import * as path12 from "path";
2528
+ import { execFileSync as execFileSync9 } from "child_process";
2529
2529
  function parseTestModelArgs() {
2530
2530
  const args2 = process.argv.slice(3);
2531
2531
  function getArg3(flag) {
@@ -2598,16 +2598,16 @@ function generateConfig(provider, model, dropParams) {
2598
2598
  }
2599
2599
  async function startProxy(config, url) {
2600
2600
  try {
2601
- execFileSync10("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
2601
+ execFileSync9("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
2602
2602
  } catch {
2603
2603
  try {
2604
- execFileSync10("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
2604
+ execFileSync9("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
2605
2605
  } catch {
2606
2606
  logger.error("litellm not installed. Install: pip install 'litellm[proxy]'");
2607
2607
  return null;
2608
2608
  }
2609
2609
  }
2610
- fs14.writeFileSync(CONFIG_PATH, config);
2610
+ fs13.writeFileSync(CONFIG_PATH, config);
2611
2611
  const portMatch = url.match(/:(\d+)/);
2612
2612
  const port = portMatch ? portMatch[1] : "4099";
2613
2613
  const { spawn: spawn2 } = await import("child_process");
@@ -2649,7 +2649,7 @@ async function quickApiTest(url, model, apiKey) {
2649
2649
  }
2650
2650
  }
2651
2651
  function delay(ms) {
2652
- return new Promise((resolve6) => setTimeout(resolve6, ms));
2652
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
2653
2653
  }
2654
2654
  async function runTestModelCommand() {
2655
2655
  const opts = parseTestModelArgs();
@@ -2663,7 +2663,7 @@ async function runTestModelCommand() {
2663
2663
  proxyProcess.kill();
2664
2664
  proxyProcess = null;
2665
2665
  }
2666
- fs14.rmSync(CONFIG_PATH, { force: true });
2666
+ fs13.rmSync(CONFIG_PATH, { force: true });
2667
2667
  };
2668
2668
  process.on("SIGINT", () => {
2669
2669
  cleanup();
@@ -2757,7 +2757,7 @@ var init_test_model_command = __esm({
2757
2757
  init_test_model_report();
2758
2758
  TEST_PORT = 4099;
2759
2759
  TEST_URL = `http://localhost:${TEST_PORT}`;
2760
- CONFIG_PATH = path13.join(os3.tmpdir(), "kody-test-model-config.yaml");
2760
+ CONFIG_PATH = path12.join(os3.tmpdir(), "kody-test-model-config.yaml");
2761
2761
  }
2762
2762
  });
2763
2763
 
@@ -2768,7 +2768,7 @@ __export(parse_inputs_exports, {
2768
2768
  runCiParse: () => runCiParse,
2769
2769
  writeOutputs: () => writeOutputs
2770
2770
  });
2771
- import * as fs15 from "fs";
2771
+ import * as fs14 from "fs";
2772
2772
  function generateTimestamp() {
2773
2773
  const now = /* @__PURE__ */ new Date();
2774
2774
  const pad2 = (n) => String(n).padStart(2, "0");
@@ -2917,12 +2917,12 @@ function writeOutputs(result2) {
2917
2917
  function output(key, value) {
2918
2918
  if (outputFile) {
2919
2919
  if (value.includes("\n")) {
2920
- fs15.appendFileSync(outputFile, `${key}<<KODY_EOF
2920
+ fs14.appendFileSync(outputFile, `${key}<<KODY_EOF
2921
2921
  ${value}
2922
2922
  KODY_EOF
2923
2923
  `);
2924
2924
  } else {
2925
- fs15.appendFileSync(outputFile, `${key}=${value}
2925
+ fs14.appendFileSync(outputFile, `${key}=${value}
2926
2926
  `);
2927
2927
  }
2928
2928
  }
@@ -3047,7 +3047,7 @@ var init_definitions = __esm({
3047
3047
  });
3048
3048
 
3049
3049
  // src/git-utils.ts
3050
- import { execFileSync as execFileSync11 } from "child_process";
3050
+ import { execFileSync as execFileSync10 } from "child_process";
3051
3051
  function getHookSafeEnv() {
3052
3052
  if (!_hookSafeEnv) {
3053
3053
  _hookSafeEnv = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
@@ -3055,7 +3055,7 @@ function getHookSafeEnv() {
3055
3055
  return _hookSafeEnv;
3056
3056
  }
3057
3057
  function git(args2, options) {
3058
- return execFileSync11("git", args2, {
3058
+ return execFileSync10("git", args2, {
3059
3059
  encoding: "utf-8",
3060
3060
  timeout: options?.timeout ?? 3e4,
3061
3061
  cwd: options?.cwd,
@@ -3241,14 +3241,14 @@ var init_git_utils = __esm({
3241
3241
  });
3242
3242
 
3243
3243
  // src/pipeline/state.ts
3244
- import * as fs16 from "fs";
3245
- import * as path14 from "path";
3244
+ import * as fs15 from "fs";
3245
+ import * as path13 from "path";
3246
3246
  function loadState(taskId, taskDir) {
3247
- const p = path14.join(taskDir, "status.json");
3248
- if (!fs16.existsSync(p)) return null;
3247
+ const p = path13.join(taskDir, "status.json");
3248
+ if (!fs15.existsSync(p)) return null;
3249
3249
  try {
3250
3250
  const result2 = parseJsonSafe(
3251
- fs16.readFileSync(p, "utf-8"),
3251
+ fs15.readFileSync(p, "utf-8"),
3252
3252
  ["taskId", "state", "stages", "createdAt", "updatedAt"]
3253
3253
  );
3254
3254
  if (!result2.ok) {
@@ -3266,10 +3266,10 @@ function writeState(state, taskDir) {
3266
3266
  ...state,
3267
3267
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3268
3268
  };
3269
- const target = path14.join(taskDir, "status.json");
3269
+ const target = path13.join(taskDir, "status.json");
3270
3270
  const tmp = target + ".tmp";
3271
- fs16.writeFileSync(tmp, JSON.stringify(updated, null, 2));
3272
- fs16.renameSync(tmp, target);
3271
+ fs15.writeFileSync(tmp, JSON.stringify(updated, null, 2));
3272
+ fs15.renameSync(tmp, target);
3273
3273
  return updated;
3274
3274
  }
3275
3275
  function initState(taskId) {
@@ -3310,16 +3310,16 @@ var init_complexity = __esm({
3310
3310
  });
3311
3311
 
3312
3312
  // src/memory.ts
3313
- import * as fs17 from "fs";
3314
- import * as path15 from "path";
3313
+ import * as fs16 from "fs";
3314
+ import * as path14 from "path";
3315
3315
  function readProjectMemory(projectDir) {
3316
- const memoryDir = path15.join(projectDir, ".kody", "memory");
3317
- if (!fs17.existsSync(memoryDir)) return "";
3318
- const files = fs17.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
3316
+ const memoryDir = path14.join(projectDir, ".kody", "memory");
3317
+ if (!fs16.existsSync(memoryDir)) return "";
3318
+ const files = fs16.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
3319
3319
  if (files.length === 0) return "";
3320
3320
  const sections = [];
3321
3321
  for (const file of files) {
3322
- const content = fs17.readFileSync(path15.join(memoryDir, file), "utf-8").trim();
3322
+ const content = fs16.readFileSync(path14.join(memoryDir, file), "utf-8").trim();
3323
3323
  if (content) {
3324
3324
  sections.push(`## ${file.replace(".md", "")}
3325
3325
  ${content}`);
@@ -3338,8 +3338,8 @@ var init_memory = __esm({
3338
3338
  });
3339
3339
 
3340
3340
  // src/context-tiers.ts
3341
- import * as fs18 from "fs";
3342
- import * as path16 from "path";
3341
+ import * as fs17 from "fs";
3342
+ import * as path15 from "path";
3343
3343
  function estimateTokens(text) {
3344
3344
  return Math.ceil(text.length / 4);
3345
3345
  }
@@ -3430,7 +3430,7 @@ function generateL1Json(content) {
3430
3430
  }
3431
3431
  }
3432
3432
  function getTieredContent(filePath, content) {
3433
- const key = path16.basename(filePath);
3433
+ const key = path15.basename(filePath);
3434
3434
  return {
3435
3435
  source: filePath,
3436
3436
  L0: generateL0(content, key),
@@ -3442,15 +3442,15 @@ function selectTier(tiered, tier) {
3442
3442
  return tiered[tier];
3443
3443
  }
3444
3444
  function readProjectMemoryTiered(projectDir, tier) {
3445
- const memoryDir = path16.join(projectDir, ".kody", "memory");
3446
- if (!fs18.existsSync(memoryDir)) return "";
3447
- const files = fs18.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
3445
+ const memoryDir = path15.join(projectDir, ".kody", "memory");
3446
+ if (!fs17.existsSync(memoryDir)) return "";
3447
+ const files = fs17.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
3448
3448
  if (files.length === 0) return "";
3449
3449
  const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
3450
3450
  const sections = [];
3451
3451
  for (const file of files) {
3452
- const filePath = path16.join(memoryDir, file);
3453
- const content = fs18.readFileSync(filePath, "utf-8").trim();
3452
+ const filePath = path15.join(memoryDir, file);
3453
+ const content = fs17.readFileSync(filePath, "utf-8").trim();
3454
3454
  if (!content) continue;
3455
3455
  const tiered = getTieredContent(filePath, content);
3456
3456
  const selected = selectTier(tiered, tier);
@@ -3473,9 +3473,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
3473
3473
  `;
3474
3474
  context += `Task Directory: ${taskDir}
3475
3475
  `;
3476
- const taskMdPath = path16.join(taskDir, "task.md");
3477
- if (fs18.existsSync(taskMdPath)) {
3478
- const content = fs18.readFileSync(taskMdPath, "utf-8");
3476
+ const taskMdPath = path15.join(taskDir, "task.md");
3477
+ if (fs17.existsSync(taskMdPath)) {
3478
+ const content = fs17.readFileSync(taskMdPath, "utf-8");
3479
3479
  const selected = selectContent(taskMdPath, content, policy.taskDescription);
3480
3480
  const label = tierLabel("Task Description", policy.taskDescription);
3481
3481
  context += `
@@ -3483,9 +3483,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
3483
3483
  ${selected}
3484
3484
  `;
3485
3485
  }
3486
- const taskJsonPath = path16.join(taskDir, "task.json");
3487
- if (fs18.existsSync(taskJsonPath)) {
3488
- const content = fs18.readFileSync(taskJsonPath, "utf-8");
3486
+ const taskJsonPath = path15.join(taskDir, "task.json");
3487
+ if (fs17.existsSync(taskJsonPath)) {
3488
+ const content = fs17.readFileSync(taskJsonPath, "utf-8");
3489
3489
  if (policy.taskClassification === "L2") {
3490
3490
  try {
3491
3491
  const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
@@ -3511,9 +3511,9 @@ ${selected}
3511
3511
  }
3512
3512
  }
3513
3513
  }
3514
- const specPath = path16.join(taskDir, "spec.md");
3515
- if (fs18.existsSync(specPath)) {
3516
- const content = fs18.readFileSync(specPath, "utf-8");
3514
+ const specPath = path15.join(taskDir, "spec.md");
3515
+ if (fs17.existsSync(specPath)) {
3516
+ const content = fs17.readFileSync(specPath, "utf-8");
3517
3517
  const selected = selectContent(specPath, content, policy.spec);
3518
3518
  const label = tierLabel("Spec", policy.spec);
3519
3519
  context += `
@@ -3521,9 +3521,9 @@ ${selected}
3521
3521
  ${selected}
3522
3522
  `;
3523
3523
  }
3524
- const planPath = path16.join(taskDir, "plan.md");
3525
- if (fs18.existsSync(planPath)) {
3526
- const content = fs18.readFileSync(planPath, "utf-8");
3524
+ const planPath = path15.join(taskDir, "plan.md");
3525
+ if (fs17.existsSync(planPath)) {
3526
+ const content = fs17.readFileSync(planPath, "utf-8");
3527
3527
  const selected = selectContent(planPath, content, policy.plan);
3528
3528
  const label = tierLabel("Plan", policy.plan);
3529
3529
  context += `
@@ -3531,9 +3531,9 @@ ${selected}
3531
3531
  ${selected}
3532
3532
  `;
3533
3533
  }
3534
- const contextMdPath = path16.join(taskDir, "context.md");
3535
- if (fs18.existsSync(contextMdPath)) {
3536
- const content = fs18.readFileSync(contextMdPath, "utf-8");
3534
+ const contextMdPath = path15.join(taskDir, "context.md");
3535
+ if (fs17.existsSync(contextMdPath)) {
3536
+ const content = fs17.readFileSync(contextMdPath, "utf-8");
3537
3537
  const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
3538
3538
  const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
3539
3539
  context += `
@@ -3618,117 +3618,25 @@ var init_context_tiers = __esm({
3618
3618
  }
3619
3619
  });
3620
3620
 
3621
- // src/tools.ts
3622
- import * as fs19 from "fs";
3623
- import * as path17 from "path";
3624
- import { execSync as execSync3 } from "child_process";
3625
- import { parse as parseYaml } from "yaml";
3626
- function loadToolDeclarations(projectDir) {
3627
- const toolsPath = path17.join(projectDir, ".kody", "tools.yml");
3628
- if (!fs19.existsSync(toolsPath)) return [];
3629
- try {
3630
- const raw = fs19.readFileSync(toolsPath, "utf-8");
3631
- const parsed = parseYaml(raw);
3632
- if (!parsed || typeof parsed !== "object") return [];
3633
- return Object.entries(parsed).map(([name, value]) => {
3634
- const v = value;
3635
- return {
3636
- name,
3637
- detect: Array.isArray(v.detect) ? v.detect : [],
3638
- stages: Array.isArray(v.stages) ? v.stages : [],
3639
- setup: typeof v.setup === "string" ? v.setup : "",
3640
- skill: typeof v.skill === "string" ? v.skill : ""
3641
- };
3642
- });
3643
- } catch (err) {
3644
- logger.warn(`Failed to parse .kody/tools.yml: ${err instanceof Error ? err.message : String(err)}`);
3645
- return [];
3646
- }
3647
- }
3648
- function resolveSkillContent(skillFilename, projectDir) {
3649
- if (!skillFilename) return "";
3650
- const projectSkill = path17.join(projectDir, ".kody", "skills", skillFilename);
3651
- if (fs19.existsSync(projectSkill)) {
3652
- return fs19.readFileSync(projectSkill, "utf-8");
3653
- }
3654
- const scriptDir = new URL(".", import.meta.url).pathname;
3655
- const candidates = [
3656
- path17.resolve(scriptDir, "..", "skills", skillFilename),
3657
- path17.resolve(scriptDir, "..", "..", "skills", skillFilename)
3658
- ];
3659
- for (const candidate of candidates) {
3660
- if (fs19.existsSync(candidate)) {
3661
- return fs19.readFileSync(candidate, "utf-8");
3662
- }
3663
- }
3664
- logger.warn(`Skill file not found: ${skillFilename}`);
3665
- return "";
3666
- }
3667
- function detectTools(declarations, projectDir) {
3668
- const resolved = [];
3669
- for (const decl of declarations) {
3670
- const detected = decl.detect.some((pattern) => fs19.existsSync(path17.join(projectDir, pattern)));
3671
- if (!detected) continue;
3672
- const skillContent = resolveSkillContent(decl.skill, projectDir);
3673
- resolved.push({
3674
- name: decl.name,
3675
- stages: decl.stages,
3676
- setup: decl.setup,
3677
- skillContent
3678
- });
3679
- }
3680
- return resolved;
3681
- }
3682
- function runToolSetup(tools, projectDir) {
3683
- for (const tool of tools) {
3684
- if (!tool.setup) continue;
3685
- try {
3686
- logger.info(` Setting up ${tool.name}: ${tool.setup}`);
3687
- execSync3(tool.setup, { cwd: projectDir, timeout: 12e4, stdio: "pipe" });
3688
- logger.info(` \u2713 ${tool.name} setup complete`);
3689
- } catch (err) {
3690
- logger.warn(` \u26A0 ${tool.name} setup failed: ${err instanceof Error ? err.message : String(err)}`);
3691
- }
3692
- }
3693
- }
3694
- function getToolSkillsForStage(tools, stageName) {
3695
- const matched = tools.filter((t) => t.stages.includes(stageName) && t.skillContent);
3696
- if (matched.length === 0) return "";
3697
- const sections = matched.map((t) => `### ${t.name}
3698
-
3699
- ${t.skillContent}`);
3700
- return `## Available Tools
3701
-
3702
- The following tools are installed and ready to use in this environment.
3703
-
3704
- ${sections.join("\n\n")}`;
3705
- }
3706
- var init_tools = __esm({
3707
- "src/tools.ts"() {
3708
- "use strict";
3709
- init_logger();
3710
- }
3711
- });
3712
-
3713
3621
  // src/context.ts
3714
- import * as fs20 from "fs";
3715
- import * as path18 from "path";
3622
+ import * as fs18 from "fs";
3623
+ import * as path16 from "path";
3716
3624
  function readPromptFile(stageName, projectDir) {
3717
3625
  if (projectDir) {
3718
- const stepFile = path18.join(projectDir, ".kody", "steps", `${stageName}.md`);
3719
- if (fs20.existsSync(stepFile)) {
3720
- return fs20.readFileSync(stepFile, "utf-8");
3626
+ const stepFile = path16.join(projectDir, ".kody", "steps", `${stageName}.md`);
3627
+ if (fs18.existsSync(stepFile)) {
3628
+ return fs18.readFileSync(stepFile, "utf-8");
3721
3629
  }
3722
3630
  console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
3723
3631
  }
3724
3632
  const scriptDir = new URL(".", import.meta.url).pathname;
3725
3633
  const candidates = [
3726
- path18.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
3727
- path18.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
3634
+ path16.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
3635
+ path16.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
3728
3636
  ];
3729
3637
  for (const candidate of candidates) {
3730
- if (fs20.existsSync(candidate)) {
3731
- return fs20.readFileSync(candidate, "utf-8");
3638
+ if (fs18.existsSync(candidate)) {
3639
+ return fs18.readFileSync(candidate, "utf-8");
3732
3640
  }
3733
3641
  }
3734
3642
  throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
@@ -3740,18 +3648,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
3740
3648
  `;
3741
3649
  context += `Task Directory: ${taskDir}
3742
3650
  `;
3743
- const taskMdPath = path18.join(taskDir, "task.md");
3744
- if (fs20.existsSync(taskMdPath)) {
3745
- const taskMd = fs20.readFileSync(taskMdPath, "utf-8");
3651
+ const taskMdPath = path16.join(taskDir, "task.md");
3652
+ if (fs18.existsSync(taskMdPath)) {
3653
+ const taskMd = fs18.readFileSync(taskMdPath, "utf-8");
3746
3654
  context += `
3747
3655
  ## Task Description
3748
3656
  ${taskMd}
3749
3657
  `;
3750
3658
  }
3751
- const taskJsonPath = path18.join(taskDir, "task.json");
3752
- if (fs20.existsSync(taskJsonPath)) {
3659
+ const taskJsonPath = path16.join(taskDir, "task.json");
3660
+ if (fs18.existsSync(taskJsonPath)) {
3753
3661
  try {
3754
- const taskDef = JSON.parse(fs20.readFileSync(taskJsonPath, "utf-8"));
3662
+ const taskDef = JSON.parse(fs18.readFileSync(taskJsonPath, "utf-8"));
3755
3663
  context += `
3756
3664
  ## Task Classification
3757
3665
  `;
@@ -3764,27 +3672,27 @@ ${taskMd}
3764
3672
  } catch {
3765
3673
  }
3766
3674
  }
3767
- const specPath = path18.join(taskDir, "spec.md");
3768
- if (fs20.existsSync(specPath)) {
3769
- const spec = fs20.readFileSync(specPath, "utf-8");
3675
+ const specPath = path16.join(taskDir, "spec.md");
3676
+ if (fs18.existsSync(specPath)) {
3677
+ const spec = fs18.readFileSync(specPath, "utf-8");
3770
3678
  const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
3771
3679
  context += `
3772
3680
  ## Spec Summary
3773
3681
  ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
3774
3682
  `;
3775
3683
  }
3776
- const planPath = path18.join(taskDir, "plan.md");
3777
- if (fs20.existsSync(planPath)) {
3778
- const plan = fs20.readFileSync(planPath, "utf-8");
3684
+ const planPath = path16.join(taskDir, "plan.md");
3685
+ if (fs18.existsSync(planPath)) {
3686
+ const plan = fs18.readFileSync(planPath, "utf-8");
3779
3687
  const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
3780
3688
  context += `
3781
3689
  ## Plan Summary
3782
3690
  ${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
3783
3691
  `;
3784
3692
  }
3785
- const contextMdPath = path18.join(taskDir, "context.md");
3786
- if (fs20.existsSync(contextMdPath)) {
3787
- const accumulated = fs20.readFileSync(contextMdPath, "utf-8");
3693
+ const contextMdPath = path16.join(taskDir, "context.md");
3694
+ if (fs18.existsSync(contextMdPath)) {
3695
+ const accumulated = fs18.readFileSync(contextMdPath, "utf-8");
3788
3696
  const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
3789
3697
  const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
3790
3698
  context += `
@@ -3802,17 +3710,17 @@ ${feedback}
3802
3710
  }
3803
3711
  function inferHasUIFromScope(scope) {
3804
3712
  return scope.some((filePath) => {
3805
- const ext = path18.extname(filePath).toLowerCase();
3713
+ const ext = path16.extname(filePath).toLowerCase();
3806
3714
  if (UI_EXTENSIONS.has(ext)) return true;
3807
3715
  const normalized = filePath.replace(/\\/g, "/");
3808
3716
  return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
3809
3717
  });
3810
3718
  }
3811
3719
  function taskHasUI(taskDir) {
3812
- const taskJsonPath = path18.join(taskDir, "task.json");
3813
- if (!fs20.existsSync(taskJsonPath)) return true;
3720
+ const taskJsonPath = path16.join(taskDir, "task.json");
3721
+ if (!fs18.existsSync(taskJsonPath)) return true;
3814
3722
  try {
3815
- const taskDef = JSON.parse(fs20.readFileSync(taskJsonPath, "utf-8"));
3723
+ const taskDef = JSON.parse(fs18.readFileSync(taskJsonPath, "utf-8"));
3816
3724
  const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
3817
3725
  if (scope.length === 0) return true;
3818
3726
  return inferHasUIFromScope(scope);
@@ -3918,7 +3826,7 @@ ${devServerBlock}
3918
3826
 
3919
3827
  Use browser tools to navigate to pages and take snapshots to verify UI output.`;
3920
3828
  }
3921
- function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback, tools) {
3829
+ function buildFullPrompt(stageName, taskId, taskDir, projectDir, feedback) {
3922
3830
  const config = getProjectConfig();
3923
3831
  let assembled;
3924
3832
  if (config.contextTiers?.enabled) {
@@ -3934,18 +3842,12 @@ ${prompt}` : prompt;
3934
3842
  }
3935
3843
  if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
3936
3844
  assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
3937
- const qaGuidePath = path18.join(projectDir, ".kody", "qa-guide.md");
3938
- if (fs20.existsSync(qaGuidePath)) {
3939
- const qaGuide = fs20.readFileSync(qaGuidePath, "utf-8").trim();
3845
+ const qaGuidePath = path16.join(projectDir, ".kody", "qa-guide.md");
3846
+ if (fs18.existsSync(qaGuidePath)) {
3847
+ const qaGuide = fs18.readFileSync(qaGuidePath, "utf-8").trim();
3940
3848
  assembled = assembled + "\n\n" + qaGuide;
3941
3849
  }
3942
3850
  }
3943
- if (tools?.length) {
3944
- const toolSkills = getToolSkillsForStage(tools, stageName);
3945
- if (toolSkills) {
3946
- assembled = assembled + "\n\n" + toolSkills;
3947
- }
3948
- }
3949
3851
  return assembled;
3950
3852
  }
3951
3853
  function buildFullPromptTiered(stageName, taskId, taskDir, projectDir, feedback) {
@@ -3985,7 +3887,6 @@ var init_context = __esm({
3985
3887
  init_config();
3986
3888
  init_context_tiers();
3987
3889
  init_mcp_config();
3988
- init_tools();
3989
3890
  MAX_TASK_CONTEXT_PLAN = 1500;
3990
3891
  MAX_TASK_CONTEXT_SPEC = 2e3;
3991
3892
  MAX_ACCUMULATED_CONTEXT = 4e3;
@@ -4035,8 +3936,8 @@ var init_runner_selection = __esm({
4035
3936
  });
4036
3937
 
4037
3938
  // src/stages/agent.ts
4038
- import * as fs21 from "fs";
4039
- import * as path19 from "path";
3939
+ import * as fs19 from "fs";
3940
+ import * as path17 from "path";
4040
3941
  function getSessionInfo(stageName, sessions) {
4041
3942
  const group = SESSION_GROUP[stageName];
4042
3943
  if (!group) return void 0;
@@ -4065,7 +3966,7 @@ async function executeAgentStage(ctx, def) {
4065
3966
  logger.info(` [dry-run] skipping ${def.name}`);
4066
3967
  return { outcome: "completed", retries: 0 };
4067
3968
  }
4068
- const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback, ctx.tools);
3969
+ const prompt = buildFullPrompt(def.name, ctx.taskId, ctx.taskDir, ctx.projectDir, ctx.input.feedback);
4069
3970
  let currentModelTier = def.modelTier;
4070
3971
  if (ctx.input.feedback && def.name === "build") {
4071
3972
  logger.info(` feedback: ${ctx.input.feedback.slice(0, 200)}${ctx.input.feedback.length > 200 ? "..." : ""}`);
@@ -4123,27 +4024,27 @@ async function executeAgentStage(ctx, def) {
4123
4024
  }
4124
4025
  const result2 = lastResult;
4125
4026
  if (def.outputFile && result2.output) {
4126
- fs21.writeFileSync(path19.join(ctx.taskDir, def.outputFile), result2.output);
4027
+ fs19.writeFileSync(path17.join(ctx.taskDir, def.outputFile), result2.output);
4127
4028
  }
4128
4029
  if (def.outputFile) {
4129
- const outputPath = path19.join(ctx.taskDir, def.outputFile);
4130
- if (!fs21.existsSync(outputPath)) {
4131
- const ext = path19.extname(def.outputFile);
4132
- const base = path19.basename(def.outputFile, ext);
4133
- const files = fs21.readdirSync(ctx.taskDir);
4030
+ const outputPath = path17.join(ctx.taskDir, def.outputFile);
4031
+ if (!fs19.existsSync(outputPath)) {
4032
+ const ext = path17.extname(def.outputFile);
4033
+ const base = path17.basename(def.outputFile, ext);
4034
+ const files = fs19.readdirSync(ctx.taskDir);
4134
4035
  const variant = files.find(
4135
4036
  (f) => f.startsWith(base + "-") && f.endsWith(ext)
4136
4037
  );
4137
4038
  if (variant) {
4138
- fs21.renameSync(path19.join(ctx.taskDir, variant), outputPath);
4039
+ fs19.renameSync(path17.join(ctx.taskDir, variant), outputPath);
4139
4040
  logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
4140
4041
  }
4141
4042
  }
4142
4043
  }
4143
4044
  if (def.outputFile) {
4144
- const outputPath = path19.join(ctx.taskDir, def.outputFile);
4145
- if (fs21.existsSync(outputPath)) {
4146
- const content = fs21.readFileSync(outputPath, "utf-8");
4045
+ const outputPath = path17.join(ctx.taskDir, def.outputFile);
4046
+ if (fs19.existsSync(outputPath)) {
4047
+ const content = fs19.readFileSync(outputPath, "utf-8");
4147
4048
  const validation = validateStageOutput(def.name, content);
4148
4049
  if (!validation.valid) {
4149
4050
  if (def.name === "taskify") {
@@ -4157,7 +4058,7 @@ async function executeAgentStage(ctx, def) {
4157
4058
  const stripped = stripFences(retryResult.output);
4158
4059
  const retryValidation = validateTaskJson(stripped);
4159
4060
  if (retryValidation.valid) {
4160
- fs21.writeFileSync(outputPath, retryResult.output);
4061
+ fs19.writeFileSync(outputPath, retryResult.output);
4161
4062
  logger.info(` taskify retry produced valid JSON`);
4162
4063
  } else {
4163
4064
  logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
@@ -4170,7 +4071,7 @@ async function executeAgentStage(ctx, def) {
4170
4071
  risk_level: "low",
4171
4072
  questions: []
4172
4073
  }, null, 2);
4173
- fs21.writeFileSync(outputPath, fallback);
4074
+ fs19.writeFileSync(outputPath, fallback);
4174
4075
  logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
4175
4076
  }
4176
4077
  }
@@ -4184,7 +4085,7 @@ async function executeAgentStage(ctx, def) {
4184
4085
  return { outcome: "completed", outputFile: def.outputFile, retries };
4185
4086
  }
4186
4087
  function appendStageContext(taskDir, stageName, output) {
4187
- const contextPath = path19.join(taskDir, "context.md");
4088
+ const contextPath = path17.join(taskDir, "context.md");
4188
4089
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
4189
4090
  let summary;
4190
4091
  if (output && output.trim()) {
@@ -4197,7 +4098,7 @@ function appendStageContext(taskDir, stageName, output) {
4197
4098
  ### ${stageName} (${timestamp2})
4198
4099
  ${summary}
4199
4100
  `;
4200
- fs21.appendFileSync(contextPath, entry);
4101
+ fs19.appendFileSync(contextPath, entry);
4201
4102
  }
4202
4103
  var SESSION_GROUP;
4203
4104
  var init_agent = __esm({
@@ -4220,7 +4121,7 @@ var init_agent = __esm({
4220
4121
  });
4221
4122
 
4222
4123
  // src/verify-runner.ts
4223
- import { execFileSync as execFileSync12 } from "child_process";
4124
+ import { execFileSync as execFileSync11 } from "child_process";
4224
4125
  function isExecError(err) {
4225
4126
  return typeof err === "object" && err !== null;
4226
4127
  }
@@ -4256,7 +4157,7 @@ function runCommand(cmd, cwd, timeout) {
4256
4157
  return { success: true, output: "", timedOut: false };
4257
4158
  }
4258
4159
  try {
4259
- const output = execFileSync12(parts[0], parts.slice(1), {
4160
+ const output = execFileSync11(parts[0], parts.slice(1), {
4260
4161
  cwd,
4261
4162
  timeout,
4262
4163
  encoding: "utf-8",
@@ -4345,7 +4246,7 @@ var init_verify_runner = __esm({
4345
4246
  });
4346
4247
 
4347
4248
  // src/observer.ts
4348
- import { execFileSync as execFileSync13 } from "child_process";
4249
+ import { execFileSync as execFileSync12 } from "child_process";
4349
4250
  async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, model, options) {
4350
4251
  const context = [
4351
4252
  `Stage: ${stageName}`,
@@ -4428,13 +4329,13 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
4428
4329
  }
4429
4330
  function getModifiedFiles(projectDir) {
4430
4331
  try {
4431
- const staged = execFileSync13("git", ["diff", "--name-only", "--cached"], {
4332
+ const staged = execFileSync12("git", ["diff", "--name-only", "--cached"], {
4432
4333
  encoding: "utf-8",
4433
4334
  cwd: projectDir,
4434
4335
  timeout: 5e3,
4435
4336
  stdio: ["pipe", "pipe", "pipe"]
4436
4337
  }).trim();
4437
- const unstaged = execFileSync13("git", ["diff", "--name-only"], {
4338
+ const unstaged = execFileSync12("git", ["diff", "--name-only"], {
4438
4339
  encoding: "utf-8",
4439
4340
  cwd: projectDir,
4440
4341
  timeout: 5e3,
@@ -4477,8 +4378,8 @@ Error context:
4477
4378
  });
4478
4379
 
4479
4380
  // src/stages/gate.ts
4480
- import * as fs22 from "fs";
4481
- import * as path20 from "path";
4381
+ import * as fs20 from "fs";
4382
+ import * as path18 from "path";
4482
4383
  function executeGateStage(ctx, def) {
4483
4384
  if (ctx.input.dryRun) {
4484
4385
  logger.info(` [dry-run] skipping ${def.name}`);
@@ -4521,7 +4422,7 @@ ${output}
4521
4422
  `);
4522
4423
  }
4523
4424
  }
4524
- fs22.writeFileSync(path20.join(ctx.taskDir, "verify.md"), lines.join(""));
4425
+ fs20.writeFileSync(path18.join(ctx.taskDir, "verify.md"), lines.join(""));
4525
4426
  return {
4526
4427
  outcome: verifyResult.pass ? "completed" : "failed",
4527
4428
  retries: 0
@@ -4536,9 +4437,9 @@ var init_gate = __esm({
4536
4437
  });
4537
4438
 
4538
4439
  // src/stages/verify.ts
4539
- import * as fs23 from "fs";
4540
- import * as path21 from "path";
4541
- import { execFileSync as execFileSync14 } from "child_process";
4440
+ import * as fs21 from "fs";
4441
+ import * as path19 from "path";
4442
+ import { execFileSync as execFileSync13 } from "child_process";
4542
4443
  async function executeVerifyWithAutofix(ctx, def) {
4543
4444
  const maxAttempts = def.maxRetries ?? 2;
4544
4445
  for (let attempt = 0; attempt <= maxAttempts; attempt++) {
@@ -4548,8 +4449,8 @@ async function executeVerifyWithAutofix(ctx, def) {
4548
4449
  return { ...gateResult, retries: attempt };
4549
4450
  }
4550
4451
  if (attempt < maxAttempts) {
4551
- const verifyPath = path21.join(ctx.taskDir, "verify.md");
4552
- const errorOutput = fs23.existsSync(verifyPath) ? fs23.readFileSync(verifyPath, "utf-8") : "Unknown error";
4452
+ const verifyPath = path19.join(ctx.taskDir, "verify.md");
4453
+ const errorOutput = fs21.existsSync(verifyPath) ? fs21.readFileSync(verifyPath, "utf-8") : "Unknown error";
4553
4454
  const modifiedFiles = getModifiedFiles(ctx.projectDir);
4554
4455
  const defaultRunner = getRunnerForStage(ctx, "taskify");
4555
4456
  const diagConfig = getProjectConfig();
@@ -4592,7 +4493,7 @@ ${diagnosis.resolution}`);
4592
4493
  const parts = parseCommand(cmd);
4593
4494
  if (parts.length === 0) return;
4594
4495
  try {
4595
- execFileSync14(parts[0], parts.slice(1), {
4496
+ execFileSync13(parts[0], parts.slice(1), {
4596
4497
  stdio: "pipe",
4597
4498
  timeout: FIX_COMMAND_TIMEOUT_MS
4598
4499
  });
@@ -4645,8 +4546,8 @@ var init_verify = __esm({
4645
4546
  });
4646
4547
 
4647
4548
  // src/review-standalone.ts
4648
- import * as fs24 from "fs";
4649
- import * as path22 from "path";
4549
+ import * as fs22 from "fs";
4550
+ import * as path20 from "path";
4650
4551
  function resolveReviewTarget(input) {
4651
4552
  if (input.prs.length === 0) {
4652
4553
  return {
@@ -4670,8 +4571,8 @@ Or comment on the specific PR: \`@kody review\``
4670
4571
  }
4671
4572
  async function runStandaloneReview(input) {
4672
4573
  const taskId = input.taskId ?? `review-${generateTaskId()}`;
4673
- const taskDir = path22.join(input.projectDir, ".kody", "tasks", taskId);
4674
- fs24.mkdirSync(taskDir, { recursive: true });
4574
+ const taskDir = path20.join(input.projectDir, ".kody", "tasks", taskId);
4575
+ fs22.mkdirSync(taskDir, { recursive: true });
4675
4576
  let diffInstruction = "";
4676
4577
  let filesChangedSection = "";
4677
4578
  if (input.baseBranch) {
@@ -4698,7 +4599,7 @@ ${fileList}`;
4698
4599
  const taskContent = `# ${input.prTitle}
4699
4600
 
4700
4601
  ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
4701
- fs24.writeFileSync(path22.join(taskDir, "task.md"), taskContent);
4602
+ fs22.writeFileSync(path20.join(taskDir, "task.md"), taskContent);
4702
4603
  const reviewDef = STAGES.find((s) => s.name === "review");
4703
4604
  const ctx = {
4704
4605
  taskId,
@@ -4720,10 +4621,10 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
4720
4621
  error: result2.error ?? "Review stage failed"
4721
4622
  };
4722
4623
  }
4723
- const reviewPath = path22.join(taskDir, "review.md");
4624
+ const reviewPath = path20.join(taskDir, "review.md");
4724
4625
  let reviewContent;
4725
- if (fs24.existsSync(reviewPath)) {
4726
- reviewContent = fs24.readFileSync(reviewPath, "utf-8");
4626
+ if (fs22.existsSync(reviewPath)) {
4627
+ reviewContent = fs22.readFileSync(reviewPath, "utf-8");
4727
4628
  }
4728
4629
  return {
4729
4630
  outcome: "completed",
@@ -4763,8 +4664,8 @@ var init_review_standalone = __esm({
4763
4664
  });
4764
4665
 
4765
4666
  // src/stages/review.ts
4766
- import * as fs25 from "fs";
4767
- import * as path23 from "path";
4667
+ import * as fs23 from "fs";
4668
+ import * as path21 from "path";
4768
4669
  async function executeReviewWithFix(ctx, def) {
4769
4670
  if (ctx.input.dryRun) {
4770
4671
  return { outcome: "completed", retries: 0 };
@@ -4778,11 +4679,11 @@ async function executeReviewWithFix(ctx, def) {
4778
4679
  if (reviewResult.outcome !== "completed") {
4779
4680
  return reviewResult;
4780
4681
  }
4781
- const reviewFile = path23.join(ctx.taskDir, "review.md");
4782
- if (!fs25.existsSync(reviewFile)) {
4682
+ const reviewFile = path21.join(ctx.taskDir, "review.md");
4683
+ if (!fs23.existsSync(reviewFile)) {
4783
4684
  return { outcome: "failed", retries: iteration, error: "review.md not found" };
4784
4685
  }
4785
- const content = fs25.readFileSync(reviewFile, "utf-8");
4686
+ const content = fs23.readFileSync(reviewFile, "utf-8");
4786
4687
  if (detectReviewVerdict(content) !== "fail") {
4787
4688
  return { ...reviewResult, retries: iteration };
4788
4689
  }
@@ -4811,15 +4712,15 @@ var init_review = __esm({
4811
4712
  });
4812
4713
 
4813
4714
  // src/stages/ship.ts
4814
- import * as fs26 from "fs";
4815
- import * as path24 from "path";
4816
- import { execFileSync as execFileSync15 } from "child_process";
4715
+ import * as fs24 from "fs";
4716
+ import * as path22 from "path";
4717
+ import { execFileSync as execFileSync14 } from "child_process";
4817
4718
  function buildPrBody(ctx) {
4818
4719
  const sections = [];
4819
- const taskJsonPath = path24.join(ctx.taskDir, "task.json");
4820
- if (fs26.existsSync(taskJsonPath)) {
4720
+ const taskJsonPath = path22.join(ctx.taskDir, "task.json");
4721
+ if (fs24.existsSync(taskJsonPath)) {
4821
4722
  try {
4822
- const raw = fs26.readFileSync(taskJsonPath, "utf-8");
4723
+ const raw = fs24.readFileSync(taskJsonPath, "utf-8");
4823
4724
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4824
4725
  const task = JSON.parse(cleaned);
4825
4726
  if (task.description) {
@@ -4838,9 +4739,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
4838
4739
  } catch {
4839
4740
  }
4840
4741
  }
4841
- const reviewPath = path24.join(ctx.taskDir, "review.md");
4842
- if (fs26.existsSync(reviewPath)) {
4843
- const review = fs26.readFileSync(reviewPath, "utf-8");
4742
+ const reviewPath = path22.join(ctx.taskDir, "review.md");
4743
+ if (fs24.existsSync(reviewPath)) {
4744
+ const review = fs24.readFileSync(reviewPath, "utf-8");
4844
4745
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
4845
4746
  if (summaryMatch) {
4846
4747
  const summary = summaryMatch[1].trim();
@@ -4857,14 +4758,14 @@ ${summary}`);
4857
4758
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
4858
4759
  }
4859
4760
  }
4860
- const verifyPath = path24.join(ctx.taskDir, "verify.md");
4861
- if (fs26.existsSync(verifyPath)) {
4862
- const verify = fs26.readFileSync(verifyPath, "utf-8");
4761
+ const verifyPath = path22.join(ctx.taskDir, "verify.md");
4762
+ if (fs24.existsSync(verifyPath)) {
4763
+ const verify = fs24.readFileSync(verifyPath, "utf-8");
4863
4764
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
4864
4765
  }
4865
- const planPath = path24.join(ctx.taskDir, "plan.md");
4866
- if (fs26.existsSync(planPath)) {
4867
- const plan = fs26.readFileSync(planPath, "utf-8").trim();
4766
+ const planPath = path22.join(ctx.taskDir, "plan.md");
4767
+ if (fs24.existsSync(planPath)) {
4768
+ const plan = fs24.readFileSync(planPath, "utf-8").trim();
4868
4769
  if (plan) {
4869
4770
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
4870
4771
  sections.push(`
@@ -4884,25 +4785,25 @@ Closes #${ctx.input.issueNumber}`);
4884
4785
  return sections.join("\n");
4885
4786
  }
4886
4787
  function executeShipStage(ctx, _def) {
4887
- const shipPath = path24.join(ctx.taskDir, "ship.md");
4788
+ const shipPath = path22.join(ctx.taskDir, "ship.md");
4888
4789
  if (ctx.input.dryRun) {
4889
- fs26.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
4790
+ fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
4890
4791
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
4891
4792
  }
4892
4793
  if (ctx.input.local && !ctx.input.issueNumber) {
4893
- fs26.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
4794
+ fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
4894
4795
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
4895
4796
  }
4896
4797
  try {
4897
4798
  const head = getCurrentBranch(ctx.projectDir);
4898
4799
  const base = getDefaultBranch(ctx.projectDir);
4899
4800
  try {
4900
- execFileSync15("git", ["add", ctx.taskDir], {
4801
+ execFileSync14("git", ["add", ctx.taskDir], {
4901
4802
  cwd: ctx.projectDir,
4902
4803
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
4903
4804
  stdio: "pipe"
4904
4805
  });
4905
- execFileSync15("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
4806
+ execFileSync14("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
4906
4807
  cwd: ctx.projectDir,
4907
4808
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
4908
4809
  stdio: "pipe"
@@ -4916,7 +4817,7 @@ function executeShipStage(ctx, _def) {
4916
4817
  let repo = config.github?.repo;
4917
4818
  if (!owner || !repo) {
4918
4819
  try {
4919
- const remoteUrl = execFileSync15("git", ["remote", "get-url", "origin"], {
4820
+ const remoteUrl = execFileSync14("git", ["remote", "get-url", "origin"], {
4920
4821
  encoding: "utf-8",
4921
4822
  cwd: ctx.projectDir
4922
4823
  }).trim();
@@ -4937,28 +4838,28 @@ function executeShipStage(ctx, _def) {
4937
4838
  chore: "chore"
4938
4839
  };
4939
4840
  let prefix = "chore";
4940
- const taskJsonPath = path24.join(ctx.taskDir, "task.json");
4941
- if (fs26.existsSync(taskJsonPath)) {
4841
+ const taskJsonPath = path22.join(ctx.taskDir, "task.json");
4842
+ if (fs24.existsSync(taskJsonPath)) {
4942
4843
  try {
4943
- const raw = fs26.readFileSync(taskJsonPath, "utf-8");
4844
+ const raw = fs24.readFileSync(taskJsonPath, "utf-8");
4944
4845
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4945
4846
  const task = JSON.parse(cleaned);
4946
4847
  prefix = TYPE_PREFIX[task.task_type] ?? "chore";
4947
4848
  } catch {
4948
4849
  }
4949
4850
  }
4950
- const taskMdPath = path24.join(ctx.taskDir, "task.md");
4951
- if (fs26.existsSync(taskMdPath)) {
4952
- const content = fs26.readFileSync(taskMdPath, "utf-8");
4851
+ const taskMdPath = path22.join(ctx.taskDir, "task.md");
4852
+ if (fs24.existsSync(taskMdPath)) {
4853
+ const content = fs24.readFileSync(taskMdPath, "utf-8");
4953
4854
  const heading = content.split("\n").find((l) => l.startsWith("# "));
4954
4855
  if (heading) {
4955
4856
  title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
4956
4857
  }
4957
4858
  }
4958
4859
  if (title === "Update") {
4959
- if (fs26.existsSync(taskJsonPath)) {
4860
+ if (fs24.existsSync(taskJsonPath)) {
4960
4861
  try {
4961
- const raw = fs26.readFileSync(taskJsonPath, "utf-8");
4862
+ const raw = fs24.readFileSync(taskJsonPath, "utf-8");
4962
4863
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
4963
4864
  const task = JSON.parse(cleaned);
4964
4865
  if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
@@ -4981,7 +4882,7 @@ function executeShipStage(ctx, _def) {
4981
4882
  } catch {
4982
4883
  }
4983
4884
  }
4984
- fs26.writeFileSync(shipPath, `# Ship
4885
+ fs24.writeFileSync(shipPath, `# Ship
4985
4886
 
4986
4887
  Updated existing PR: ${existingPr.url}
4987
4888
  PR #${existingPr.number}
@@ -5002,20 +4903,20 @@ PR #${existingPr.number}
5002
4903
  } catch {
5003
4904
  }
5004
4905
  }
5005
- fs26.writeFileSync(shipPath, `# Ship
4906
+ fs24.writeFileSync(shipPath, `# Ship
5006
4907
 
5007
4908
  PR created: ${pr.url}
5008
4909
  PR #${pr.number}
5009
4910
  `);
5010
4911
  } else {
5011
- fs26.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
4912
+ fs24.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
5012
4913
  }
5013
4914
  }
5014
4915
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
5015
4916
  } catch (err) {
5016
4917
  const msg = err instanceof Error ? err.message : String(err);
5017
4918
  try {
5018
- fs26.writeFileSync(shipPath, `# Ship
4919
+ fs24.writeFileSync(shipPath, `# Ship
5019
4920
 
5020
4921
  Failed: ${msg}
5021
4922
  `);
@@ -5064,15 +4965,15 @@ var init_executor_registry = __esm({
5064
4965
  });
5065
4966
 
5066
4967
  // src/pipeline/questions.ts
5067
- import * as fs27 from "fs";
5068
- import * as path25 from "path";
4968
+ import * as fs25 from "fs";
4969
+ import * as path23 from "path";
5069
4970
  function checkForQuestions(ctx, stageName) {
5070
4971
  if (ctx.input.local || !ctx.input.issueNumber) return false;
5071
4972
  try {
5072
4973
  if (stageName === "taskify") {
5073
- const taskJsonPath = path25.join(ctx.taskDir, "task.json");
5074
- if (!fs27.existsSync(taskJsonPath)) return false;
5075
- const raw = fs27.readFileSync(taskJsonPath, "utf-8");
4974
+ const taskJsonPath = path23.join(ctx.taskDir, "task.json");
4975
+ if (!fs25.existsSync(taskJsonPath)) return false;
4976
+ const raw = fs25.readFileSync(taskJsonPath, "utf-8");
5076
4977
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
5077
4978
  const taskJson = JSON.parse(cleaned);
5078
4979
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -5087,9 +4988,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
5087
4988
  }
5088
4989
  }
5089
4990
  if (stageName === "plan") {
5090
- const planPath = path25.join(ctx.taskDir, "plan.md");
5091
- if (!fs27.existsSync(planPath)) return false;
5092
- const plan = fs27.readFileSync(planPath, "utf-8");
4991
+ const planPath = path23.join(ctx.taskDir, "plan.md");
4992
+ if (!fs25.existsSync(planPath)) return false;
4993
+ const plan = fs25.readFileSync(planPath, "utf-8");
5093
4994
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
5094
4995
  if (questionsMatch) {
5095
4996
  const questionsText = questionsMatch[1].trim();
@@ -5118,8 +5019,8 @@ var init_questions = __esm({
5118
5019
  });
5119
5020
 
5120
5021
  // src/pipeline/hooks.ts
5121
- import * as fs28 from "fs";
5122
- import * as path26 from "path";
5022
+ import * as fs26 from "fs";
5023
+ import * as path24 from "path";
5123
5024
  function applyPreStageLabel(ctx, def) {
5124
5025
  if (!ctx.input.issueNumber || ctx.input.local) return;
5125
5026
  if (def.name === "plan") setLifecycleLabel(ctx.input.issueNumber, "planning");
@@ -5160,9 +5061,9 @@ function autoDetectComplexity(ctx, def) {
5160
5061
  return { complexity, activeStages };
5161
5062
  }
5162
5063
  try {
5163
- const taskJsonPath = path26.join(ctx.taskDir, "task.json");
5164
- if (!fs28.existsSync(taskJsonPath)) return null;
5165
- const raw = fs28.readFileSync(taskJsonPath, "utf-8");
5064
+ const taskJsonPath = path24.join(ctx.taskDir, "task.json");
5065
+ if (!fs26.existsSync(taskJsonPath)) return null;
5066
+ const raw = fs26.readFileSync(taskJsonPath, "utf-8");
5166
5067
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
5167
5068
  const taskJson = JSON.parse(cleaned);
5168
5069
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -5192,8 +5093,8 @@ function checkRiskGate(ctx, def, state, complexity) {
5192
5093
  if (ctx.input.dryRun || ctx.input.local) return null;
5193
5094
  if (ctx.input.mode === "rerun") return null;
5194
5095
  if (!ctx.input.issueNumber) return null;
5195
- const planPath = path26.join(ctx.taskDir, "plan.md");
5196
- const plan = fs28.existsSync(planPath) ? fs28.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
5096
+ const planPath = path24.join(ctx.taskDir, "plan.md");
5097
+ const plan = fs26.existsSync(planPath) ? fs26.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
5197
5098
  try {
5198
5099
  postComment(
5199
5100
  ctx.input.issueNumber,
@@ -5260,22 +5161,22 @@ var init_hooks = __esm({
5260
5161
  });
5261
5162
 
5262
5163
  // src/learning/auto-learn.ts
5263
- import * as fs29 from "fs";
5264
- import * as path27 from "path";
5164
+ import * as fs27 from "fs";
5165
+ import * as path25 from "path";
5265
5166
  function stripAnsi(str) {
5266
5167
  return str.replace(/\x1b\[[0-9;]*m/g, "");
5267
5168
  }
5268
5169
  function autoLearn(ctx) {
5269
5170
  try {
5270
- const memoryDir = path27.join(ctx.projectDir, ".kody", "memory");
5271
- if (!fs29.existsSync(memoryDir)) {
5272
- fs29.mkdirSync(memoryDir, { recursive: true });
5171
+ const memoryDir = path25.join(ctx.projectDir, ".kody", "memory");
5172
+ if (!fs27.existsSync(memoryDir)) {
5173
+ fs27.mkdirSync(memoryDir, { recursive: true });
5273
5174
  }
5274
5175
  const learnings = [];
5275
5176
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5276
- const verifyPath = path27.join(ctx.taskDir, "verify.md");
5277
- if (fs29.existsSync(verifyPath)) {
5278
- const verify = stripAnsi(fs29.readFileSync(verifyPath, "utf-8"));
5177
+ const verifyPath = path25.join(ctx.taskDir, "verify.md");
5178
+ if (fs27.existsSync(verifyPath)) {
5179
+ const verify = stripAnsi(fs27.readFileSync(verifyPath, "utf-8"));
5279
5180
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
5280
5181
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
5281
5182
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -5284,18 +5185,18 @@ function autoLearn(ctx) {
5284
5185
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
5285
5186
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
5286
5187
  }
5287
- const reviewPath = path27.join(ctx.taskDir, "review.md");
5288
- if (fs29.existsSync(reviewPath)) {
5289
- const review = fs29.readFileSync(reviewPath, "utf-8");
5188
+ const reviewPath = path25.join(ctx.taskDir, "review.md");
5189
+ if (fs27.existsSync(reviewPath)) {
5190
+ const review = fs27.readFileSync(reviewPath, "utf-8");
5290
5191
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
5291
5192
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
5292
5193
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
5293
5194
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
5294
5195
  }
5295
- const taskJsonPath = path27.join(ctx.taskDir, "task.json");
5296
- if (fs29.existsSync(taskJsonPath)) {
5196
+ const taskJsonPath = path25.join(ctx.taskDir, "task.json");
5197
+ if (fs27.existsSync(taskJsonPath)) {
5297
5198
  try {
5298
- const raw = stripAnsi(fs29.readFileSync(taskJsonPath, "utf-8"));
5199
+ const raw = stripAnsi(fs27.readFileSync(taskJsonPath, "utf-8"));
5299
5200
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
5300
5201
  const task = JSON.parse(cleaned);
5301
5202
  if (task.scope && Array.isArray(task.scope)) {
@@ -5306,12 +5207,12 @@ function autoLearn(ctx) {
5306
5207
  }
5307
5208
  }
5308
5209
  if (learnings.length > 0) {
5309
- const conventionsPath = path27.join(memoryDir, "conventions.md");
5210
+ const conventionsPath = path25.join(memoryDir, "conventions.md");
5310
5211
  const entry = `
5311
5212
  ## Learned ${timestamp2} (task: ${ctx.taskId})
5312
5213
  ${learnings.join("\n")}
5313
5214
  `;
5314
- fs29.appendFileSync(conventionsPath, entry);
5215
+ fs27.appendFileSync(conventionsPath, entry);
5315
5216
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
5316
5217
  }
5317
5218
  autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
@@ -5319,8 +5220,8 @@ ${learnings.join("\n")}
5319
5220
  }
5320
5221
  }
5321
5222
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
5322
- const archPath = path27.join(memoryDir, "architecture.md");
5323
- if (fs29.existsSync(archPath)) return;
5223
+ const archPath = path25.join(memoryDir, "architecture.md");
5224
+ if (fs27.existsSync(archPath)) return;
5324
5225
  const detected = detectArchitectureBasic(projectDir);
5325
5226
  if (detected.length > 0) {
5326
5227
  const content = `# Architecture (auto-detected ${timestamp2})
@@ -5328,7 +5229,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
5328
5229
  ## Overview
5329
5230
  ${detected.join("\n")}
5330
5231
  `;
5331
- fs29.writeFileSync(archPath, content);
5232
+ fs27.writeFileSync(archPath, content);
5332
5233
  logger.info(`Auto-detected architecture (${detected.length} items)`);
5333
5234
  }
5334
5235
  }
@@ -5341,13 +5242,13 @@ var init_auto_learn = __esm({
5341
5242
  });
5342
5243
 
5343
5244
  // src/retrospective.ts
5344
- import * as fs30 from "fs";
5345
- import * as path28 from "path";
5245
+ import * as fs28 from "fs";
5246
+ import * as path26 from "path";
5346
5247
  function readArtifact(taskDir, filename, maxChars) {
5347
- const p = path28.join(taskDir, filename);
5348
- if (!fs30.existsSync(p)) return null;
5248
+ const p = path26.join(taskDir, filename);
5249
+ if (!fs28.existsSync(p)) return null;
5349
5250
  try {
5350
- const content = fs30.readFileSync(p, "utf-8");
5251
+ const content = fs28.readFileSync(p, "utf-8");
5351
5252
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
5352
5253
  } catch {
5353
5254
  return null;
@@ -5400,13 +5301,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
5400
5301
  return lines.join("\n");
5401
5302
  }
5402
5303
  function getLogPath(projectDir) {
5403
- return path28.join(projectDir, ".kody", "memory", "observer-log.jsonl");
5304
+ return path26.join(projectDir, ".kody", "memory", "observer-log.jsonl");
5404
5305
  }
5405
5306
  function readPreviousRetrospectives(projectDir, limit = 10) {
5406
5307
  const logPath = getLogPath(projectDir);
5407
- if (!fs30.existsSync(logPath)) return [];
5308
+ if (!fs28.existsSync(logPath)) return [];
5408
5309
  try {
5409
- const content = fs30.readFileSync(logPath, "utf-8");
5310
+ const content = fs28.readFileSync(logPath, "utf-8");
5410
5311
  const lines = content.split("\n").filter(Boolean);
5411
5312
  const entries = [];
5412
5313
  const start = Math.max(0, lines.length - limit);
@@ -5433,11 +5334,11 @@ function formatPreviousEntries(entries) {
5433
5334
  }
5434
5335
  function appendRetrospectiveEntry(projectDir, entry) {
5435
5336
  const logPath = getLogPath(projectDir);
5436
- const dir = path28.dirname(logPath);
5437
- if (!fs30.existsSync(dir)) {
5438
- fs30.mkdirSync(dir, { recursive: true });
5337
+ const dir = path26.dirname(logPath);
5338
+ if (!fs28.existsSync(dir)) {
5339
+ fs28.mkdirSync(dir, { recursive: true });
5439
5340
  }
5440
- fs30.appendFileSync(logPath, JSON.stringify(entry) + "\n");
5341
+ fs28.appendFileSync(logPath, JSON.stringify(entry) + "\n");
5441
5342
  }
5442
5343
  async function runRetrospective(ctx, state, pipelineStartTime) {
5443
5344
  if (ctx.input.dryRun) return;
@@ -5604,9 +5505,75 @@ var init_summary = __esm({
5604
5505
  }
5605
5506
  });
5606
5507
 
5508
+ // src/tools.ts
5509
+ import * as fs29 from "fs";
5510
+ import * as path27 from "path";
5511
+ import { execSync as execSync3 } from "child_process";
5512
+ import { parse as parseYaml } from "yaml";
5513
+ function loadToolDeclarations(projectDir) {
5514
+ const toolsPath = path27.join(projectDir, ".kody", "tools.yml");
5515
+ if (!fs29.existsSync(toolsPath)) return [];
5516
+ try {
5517
+ const raw = fs29.readFileSync(toolsPath, "utf-8");
5518
+ const parsed = parseYaml(raw);
5519
+ if (!parsed || typeof parsed !== "object") return [];
5520
+ return Object.entries(parsed).map(([name, value]) => {
5521
+ const v = value;
5522
+ return {
5523
+ name,
5524
+ detect: Array.isArray(v.detect) ? v.detect : [],
5525
+ stages: Array.isArray(v.stages) ? v.stages : [],
5526
+ setup: typeof v.setup === "string" ? v.setup : ""
5527
+ };
5528
+ });
5529
+ } catch (err) {
5530
+ logger.warn(`Failed to parse .kody/tools.yml: ${err instanceof Error ? err.message : String(err)}`);
5531
+ return [];
5532
+ }
5533
+ }
5534
+ function detectTools(declarations, projectDir) {
5535
+ const resolved = [];
5536
+ for (const decl of declarations) {
5537
+ const detected = decl.detect.some((pattern) => fs29.existsSync(path27.join(projectDir, pattern)));
5538
+ if (!detected) continue;
5539
+ resolved.push({
5540
+ name: decl.name,
5541
+ stages: decl.stages,
5542
+ setup: decl.setup
5543
+ });
5544
+ }
5545
+ return resolved;
5546
+ }
5547
+ function runToolSetup(tools, projectDir) {
5548
+ for (const tool of tools) {
5549
+ if (tool.setup) {
5550
+ try {
5551
+ logger.info(` Setting up ${tool.name}: ${tool.setup}`);
5552
+ execSync3(tool.setup, { cwd: projectDir, timeout: 12e4, stdio: "pipe" });
5553
+ logger.info(` \u2713 ${tool.name} setup complete`);
5554
+ } catch (err) {
5555
+ logger.warn(` \u26A0 ${tool.name} setup failed: ${err instanceof Error ? err.message : String(err)}`);
5556
+ }
5557
+ }
5558
+ try {
5559
+ logger.info(` Installing skill for ${tool.name} from skills.sh`);
5560
+ execSync3(`npx skills add --skill ${tool.name} --yes`, { cwd: projectDir, timeout: 6e4, stdio: "pipe" });
5561
+ logger.info(` \u2713 ${tool.name} skill installed`);
5562
+ } catch (err) {
5563
+ logger.warn(` \u26A0 ${tool.name} skill install failed: ${err instanceof Error ? err.message : String(err)}`);
5564
+ }
5565
+ }
5566
+ }
5567
+ var init_tools = __esm({
5568
+ "src/tools.ts"() {
5569
+ "use strict";
5570
+ init_logger();
5571
+ }
5572
+ });
5573
+
5607
5574
  // src/pipeline.ts
5608
- import * as fs31 from "fs";
5609
- import * as path29 from "path";
5575
+ import * as fs30 from "fs";
5576
+ import * as path28 from "path";
5610
5577
  function ensureFeatureBranchIfNeeded(ctx) {
5611
5578
  if (ctx.input.dryRun) return;
5612
5579
  if (ctx.input.prNumber) {
@@ -5619,8 +5586,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
5619
5586
  }
5620
5587
  if (!ctx.input.issueNumber) return;
5621
5588
  try {
5622
- const taskMdPath = path29.join(ctx.taskDir, "task.md");
5623
- const title = fs31.existsSync(taskMdPath) ? fs31.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
5589
+ const taskMdPath = path28.join(ctx.taskDir, "task.md");
5590
+ const title = fs30.existsSync(taskMdPath) ? fs30.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
5624
5591
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
5625
5592
  syncWithDefault(ctx.projectDir);
5626
5593
  } catch (err) {
@@ -5634,10 +5601,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
5634
5601
  }
5635
5602
  }
5636
5603
  function acquireLock(taskDir) {
5637
- const lockPath = path29.join(taskDir, ".lock");
5638
- if (fs31.existsSync(lockPath)) {
5604
+ const lockPath = path28.join(taskDir, ".lock");
5605
+ if (fs30.existsSync(lockPath)) {
5639
5606
  try {
5640
- const pid = parseInt(fs31.readFileSync(lockPath, "utf-8").trim(), 10);
5607
+ const pid = parseInt(fs30.readFileSync(lockPath, "utf-8").trim(), 10);
5641
5608
  if (!isNaN(pid)) {
5642
5609
  try {
5643
5610
  process.kill(pid, 0);
@@ -5654,14 +5621,14 @@ function acquireLock(taskDir) {
5654
5621
  logger.warn(` Corrupt lock file \u2014 overwriting`);
5655
5622
  }
5656
5623
  try {
5657
- fs31.unlinkSync(lockPath);
5624
+ fs30.unlinkSync(lockPath);
5658
5625
  } catch {
5659
5626
  }
5660
5627
  }
5661
5628
  try {
5662
- const fd = fs31.openSync(lockPath, fs31.constants.O_WRONLY | fs31.constants.O_CREAT | fs31.constants.O_EXCL);
5663
- fs31.writeSync(fd, String(process.pid));
5664
- fs31.closeSync(fd);
5629
+ const fd = fs30.openSync(lockPath, fs30.constants.O_WRONLY | fs30.constants.O_CREAT | fs30.constants.O_EXCL);
5630
+ fs30.writeSync(fd, String(process.pid));
5631
+ fs30.closeSync(fd);
5665
5632
  } catch (err) {
5666
5633
  if (err.code === "EEXIST") {
5667
5634
  throw new Error("Pipeline already running (lock acquired by another process)");
@@ -5671,7 +5638,7 @@ function acquireLock(taskDir) {
5671
5638
  }
5672
5639
  function releaseLock(taskDir) {
5673
5640
  try {
5674
- fs31.unlinkSync(path29.join(taskDir, ".lock"));
5641
+ fs30.unlinkSync(path28.join(taskDir, ".lock"));
5675
5642
  } catch {
5676
5643
  }
5677
5644
  }
@@ -5885,8 +5852,8 @@ var init_pipeline = __esm({
5885
5852
  });
5886
5853
 
5887
5854
  // src/preflight.ts
5888
- import { execFileSync as execFileSync16 } from "child_process";
5889
- import * as fs32 from "fs";
5855
+ import { execFileSync as execFileSync15 } from "child_process";
5856
+ import * as fs31 from "fs";
5890
5857
  function check(name, fn) {
5891
5858
  try {
5892
5859
  const detail = fn() ?? void 0;
@@ -5898,7 +5865,7 @@ function check(name, fn) {
5898
5865
  function runPreflight() {
5899
5866
  const checks = [
5900
5867
  check("claude CLI", () => {
5901
- const v = execFileSync16("claude", ["--version"], {
5868
+ const v = execFileSync15("claude", ["--version"], {
5902
5869
  encoding: "utf-8",
5903
5870
  timeout: 1e4,
5904
5871
  stdio: ["pipe", "pipe", "pipe"]
@@ -5906,14 +5873,14 @@ function runPreflight() {
5906
5873
  return v;
5907
5874
  }),
5908
5875
  check("git repo", () => {
5909
- execFileSync16("git", ["rev-parse", "--is-inside-work-tree"], {
5876
+ execFileSync15("git", ["rev-parse", "--is-inside-work-tree"], {
5910
5877
  encoding: "utf-8",
5911
5878
  timeout: 5e3,
5912
5879
  stdio: ["pipe", "pipe", "pipe"]
5913
5880
  });
5914
5881
  }),
5915
5882
  check("pnpm", () => {
5916
- const v = execFileSync16("pnpm", ["--version"], {
5883
+ const v = execFileSync15("pnpm", ["--version"], {
5917
5884
  encoding: "utf-8",
5918
5885
  timeout: 5e3,
5919
5886
  stdio: ["pipe", "pipe", "pipe"]
@@ -5921,7 +5888,7 @@ function runPreflight() {
5921
5888
  return v;
5922
5889
  }),
5923
5890
  check("node >= 18", () => {
5924
- const v = execFileSync16("node", ["--version"], {
5891
+ const v = execFileSync15("node", ["--version"], {
5925
5892
  encoding: "utf-8",
5926
5893
  timeout: 5e3,
5927
5894
  stdio: ["pipe", "pipe", "pipe"]
@@ -5931,7 +5898,7 @@ function runPreflight() {
5931
5898
  return v;
5932
5899
  }),
5933
5900
  check("gh CLI", () => {
5934
- const v = execFileSync16("gh", ["--version"], {
5901
+ const v = execFileSync15("gh", ["--version"], {
5935
5902
  encoding: "utf-8",
5936
5903
  timeout: 5e3,
5937
5904
  stdio: ["pipe", "pipe", "pipe"]
@@ -5939,7 +5906,7 @@ function runPreflight() {
5939
5906
  return v;
5940
5907
  }),
5941
5908
  check("package.json", () => {
5942
- if (!fs32.existsSync("package.json")) throw new Error("not found");
5909
+ if (!fs31.existsSync("package.json")) throw new Error("not found");
5943
5910
  })
5944
5911
  ];
5945
5912
  const failed = checks.filter((c) => !c.ok);
@@ -6016,8 +5983,8 @@ var init_args = __esm({
6016
5983
  });
6017
5984
 
6018
5985
  // src/cli/task-state.ts
6019
- import * as fs33 from "fs";
6020
- import * as path30 from "path";
5986
+ import * as fs32 from "fs";
5987
+ import * as path29 from "path";
6021
5988
  function resolveTaskAction(issueNumber, existingTaskId, existingState) {
6022
5989
  if (!existingTaskId || !existingState) {
6023
5990
  return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
@@ -6049,11 +6016,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
6049
6016
  function resolveForIssue(issueNumber, projectDir) {
6050
6017
  const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
6051
6018
  if (existingTaskId) {
6052
- const statusPath = path30.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
6019
+ const statusPath = path29.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
6053
6020
  let existingState = null;
6054
- if (fs33.existsSync(statusPath)) {
6021
+ if (fs32.existsSync(statusPath)) {
6055
6022
  try {
6056
- existingState = JSON.parse(fs33.readFileSync(statusPath, "utf-8"));
6023
+ existingState = JSON.parse(fs32.readFileSync(statusPath, "utf-8"));
6057
6024
  } catch {
6058
6025
  }
6059
6026
  }
@@ -6086,12 +6053,12 @@ var resolve_exports = {};
6086
6053
  __export(resolve_exports, {
6087
6054
  runResolve: () => runResolve
6088
6055
  });
6089
- import { execFileSync as execFileSync17 } from "child_process";
6056
+ import { execFileSync as execFileSync16 } from "child_process";
6090
6057
  function getConflictContext(cwd, files) {
6091
6058
  const parts = [];
6092
6059
  for (const file of files.slice(0, 10)) {
6093
6060
  try {
6094
- const content = execFileSync17("git", ["diff", file], {
6061
+ const content = execFileSync16("git", ["diff", file], {
6095
6062
  cwd,
6096
6063
  encoding: "utf-8",
6097
6064
  stdio: ["pipe", "pipe", "pipe"]
@@ -6210,8 +6177,8 @@ var init_resolve = __esm({
6210
6177
 
6211
6178
  // src/entry.ts
6212
6179
  var entry_exports = {};
6213
- import * as fs34 from "fs";
6214
- import * as path31 from "path";
6180
+ import * as fs33 from "fs";
6181
+ import * as path30 from "path";
6215
6182
  async function ensureLitellmProxy(config, projectDir) {
6216
6183
  if (!anyStageNeedsProxy(config)) return null;
6217
6184
  const litellmUrl = getLitellmUrl();
@@ -6266,9 +6233,9 @@ async function runModelHealthCheck(config) {
6266
6233
  }
6267
6234
  async function main() {
6268
6235
  const input = parseArgs();
6269
- const projectDir = input.cwd ? path31.resolve(input.cwd) : process.cwd();
6236
+ const projectDir = input.cwd ? path30.resolve(input.cwd) : process.cwd();
6270
6237
  if (input.cwd) {
6271
- if (!fs34.existsSync(projectDir)) {
6238
+ if (!fs33.existsSync(projectDir)) {
6272
6239
  console.error(`--cwd path does not exist: ${projectDir}`);
6273
6240
  process.exit(1);
6274
6241
  }
@@ -6328,8 +6295,8 @@ async function main() {
6328
6295
  process.exit(1);
6329
6296
  }
6330
6297
  }
6331
- const taskDir = path31.join(projectDir, ".kody", "tasks", taskId);
6332
- fs34.mkdirSync(taskDir, { recursive: true });
6298
+ const taskDir = path30.join(projectDir, ".kody", "tasks", taskId);
6299
+ fs33.mkdirSync(taskDir, { recursive: true });
6333
6300
  if (input.command === "rerun" && isTaskifyRun(taskDir)) {
6334
6301
  const marker = readTaskifyMarker(taskDir);
6335
6302
  if (marker) {
@@ -6461,31 +6428,31 @@ async function main() {
6461
6428
  logger.info("Preflight checks:");
6462
6429
  runPreflight();
6463
6430
  if (input.task) {
6464
- fs34.writeFileSync(path31.join(taskDir, "task.md"), input.task);
6431
+ fs33.writeFileSync(path30.join(taskDir, "task.md"), input.task);
6465
6432
  }
6466
- const taskMdPath = path31.join(taskDir, "task.md");
6467
- if (!fs34.existsSync(taskMdPath) && isPRFix && input.prNumber) {
6433
+ const taskMdPath = path30.join(taskDir, "task.md");
6434
+ if (!fs33.existsSync(taskMdPath) && isPRFix && input.prNumber) {
6468
6435
  logger.info(`Fetching PR #${input.prNumber} details as task context...`);
6469
6436
  const prDetails = getPRDetails(input.prNumber);
6470
6437
  if (prDetails) {
6471
6438
  const taskContent = `# ${prDetails.title}
6472
6439
 
6473
6440
  ${prDetails.body ?? ""}`;
6474
- fs34.writeFileSync(taskMdPath, taskContent);
6441
+ fs33.writeFileSync(taskMdPath, taskContent);
6475
6442
  logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
6476
6443
  }
6477
- } else if (!fs34.existsSync(taskMdPath) && input.issueNumber) {
6444
+ } else if (!fs33.existsSync(taskMdPath) && input.issueNumber) {
6478
6445
  logger.info(`Fetching issue #${input.issueNumber} body as task...`);
6479
6446
  const issue = getIssue(input.issueNumber);
6480
6447
  if (issue) {
6481
6448
  const taskContent = `# ${issue.title}
6482
6449
 
6483
6450
  ${issue.body ?? ""}`;
6484
- fs34.writeFileSync(taskMdPath, taskContent);
6451
+ fs33.writeFileSync(taskMdPath, taskContent);
6485
6452
  logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
6486
6453
  }
6487
6454
  }
6488
- if (!fs34.existsSync(taskMdPath)) {
6455
+ if (!fs33.existsSync(taskMdPath)) {
6489
6456
  console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
6490
6457
  process.exit(1);
6491
6458
  }
@@ -6629,7 +6596,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
6629
6596
  }
6630
6597
  }
6631
6598
  const state = await runPipeline(ctx);
6632
- const files = fs34.readdirSync(taskDir);
6599
+ const files = fs33.readdirSync(taskDir);
6633
6600
  console.log(`
6634
6601
  Artifacts in ${taskDir}:`);
6635
6602
  for (const f of files) {
@@ -6695,8 +6662,8 @@ var init_entry = __esm({
6695
6662
  });
6696
6663
 
6697
6664
  // src/bin/cli.ts
6698
- import * as fs35 from "fs";
6699
- import * as path32 from "path";
6665
+ import * as fs34 from "fs";
6666
+ import * as path31 from "path";
6700
6667
  import { fileURLToPath as fileURLToPath2 } from "url";
6701
6668
 
6702
6669
  // src/bin/commands/init.ts
@@ -7068,9 +7035,9 @@ function initCommand(opts, pkgRoot) {
7068
7035
 
7069
7036
  // src/bin/commands/bootstrap.ts
7070
7037
  init_architecture_detection();
7071
- import * as fs9 from "fs";
7072
- import * as path8 from "path";
7073
- import { execFileSync as execFileSync5 } from "child_process";
7038
+ import * as fs8 from "fs";
7039
+ import * as path7 from "path";
7040
+ import { execFileSync as execFileSync4 } from "child_process";
7074
7041
 
7075
7042
  // src/bin/qa-guide.ts
7076
7043
  import * as fs6 from "fs";
@@ -7580,100 +7547,6 @@ Required env vars: ${discovery.envVars.join(", ")}`);
7580
7547
  return result2;
7581
7548
  }
7582
7549
 
7583
- // src/bin/skills.ts
7584
- import * as fs7 from "fs";
7585
- import * as path6 from "path";
7586
- import { execFileSync as execFileSync4 } from "child_process";
7587
- var SKILL_MAPPINGS = [
7588
- {
7589
- detect: (deps) => "next" in deps,
7590
- skills: [
7591
- { package: "vercel-labs/agent-skills@vercel-react-best-practices", label: "React best practices (Vercel)" }
7592
- ]
7593
- },
7594
- {
7595
- detect: (deps) => "react" in deps && !("next" in deps),
7596
- skills: [
7597
- { package: "vercel-labs/agent-skills@vercel-react-best-practices", label: "React best practices (Vercel)" }
7598
- ]
7599
- },
7600
- {
7601
- detect: (deps) => FRONTEND_DEPS.some((d) => d in deps),
7602
- skills: [
7603
- { package: "microsoft/playwright-cli@playwright-cli", label: "Playwright browser automation" }
7604
- ]
7605
- }
7606
- ];
7607
- function detectSkillsForProject(cwd) {
7608
- const pkgPath = path6.join(cwd, "package.json");
7609
- if (!fs7.existsSync(pkgPath)) return [];
7610
- try {
7611
- const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
7612
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
7613
- const seen = /* @__PURE__ */ new Set();
7614
- const skills = [];
7615
- for (const mapping of SKILL_MAPPINGS) {
7616
- if (mapping.detect(allDeps)) {
7617
- for (const skill of mapping.skills) {
7618
- if (!seen.has(skill.package)) {
7619
- seen.add(skill.package);
7620
- skills.push(skill);
7621
- }
7622
- }
7623
- }
7624
- }
7625
- return skills;
7626
- } catch {
7627
- return [];
7628
- }
7629
- }
7630
- function installSkillsForProject(cwd) {
7631
- const skills = detectSkillsForProject(cwd);
7632
- if (skills.length === 0) {
7633
- console.log(" \u25CB No skills to install (no frontend framework detected)");
7634
- return [];
7635
- }
7636
- let installedSkills = {};
7637
- const lockPath = path6.join(cwd, "skills-lock.json");
7638
- if (fs7.existsSync(lockPath)) {
7639
- try {
7640
- const lock = JSON.parse(fs7.readFileSync(lockPath, "utf-8"));
7641
- installedSkills = lock.skills ?? {};
7642
- } catch {
7643
- }
7644
- }
7645
- const installedPaths = [];
7646
- for (const skill of skills) {
7647
- const skillName = skill.package.split("@").pop() ?? "";
7648
- if (skillName in installedSkills) {
7649
- console.log(` \u25CB ${skill.label} \u2014 already installed`);
7650
- const agentPath = `.agents/skills/${skillName}`;
7651
- const claudePath = `.claude/skills/${skillName}`;
7652
- if (fs7.existsSync(path6.join(cwd, agentPath))) installedPaths.push(agentPath);
7653
- if (fs7.existsSync(path6.join(cwd, claudePath))) installedPaths.push(claudePath);
7654
- continue;
7655
- }
7656
- try {
7657
- console.log(` Installing: ${skill.label} (${skill.package})`);
7658
- execFileSync4("npx", ["skills", "add", skill.package, "--yes"], {
7659
- cwd,
7660
- encoding: "utf-8",
7661
- timeout: 6e4,
7662
- stdio: ["pipe", "pipe", "pipe"]
7663
- });
7664
- const installedName = skill.package.split("@").pop() ?? "";
7665
- const agentPath = `.agents/skills/${installedName}`;
7666
- const claudePath = `.claude/skills/${installedName}`;
7667
- if (fs7.existsSync(path6.join(cwd, agentPath))) installedPaths.push(agentPath);
7668
- if (fs7.existsSync(path6.join(cwd, claudePath))) installedPaths.push(claudePath);
7669
- console.log(` \u2713 ${skill.label}`);
7670
- } catch {
7671
- console.log(` \u2717 ${skill.label} \u2014 failed to install`);
7672
- }
7673
- }
7674
- return installedPaths;
7675
- }
7676
-
7677
7550
  // src/bin/commands/bootstrap.ts
7678
7551
  init_config();
7679
7552
 
@@ -7703,20 +7576,20 @@ ${content}
7703
7576
  // src/bin/commands/bootstrap.ts
7704
7577
  var STEP_STAGES = ["taskify", "plan", "build", "autofix", "review", "review-fix"];
7705
7578
  function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
7706
- const srcDir = path8.join(cwd, "src");
7707
- const baseDir = fs9.existsSync(srcDir) ? srcDir : cwd;
7579
+ const srcDir = path7.join(cwd, "src");
7580
+ const baseDir = fs8.existsSync(srcDir) ? srcDir : cwd;
7708
7581
  const results = [];
7709
7582
  function walk(dir) {
7710
7583
  const entries = [];
7711
7584
  try {
7712
- for (const entry of fs9.readdirSync(dir, { withFileTypes: true })) {
7585
+ for (const entry of fs8.readdirSync(dir, { withFileTypes: true })) {
7713
7586
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
7714
- const full = path8.join(dir, entry.name);
7587
+ const full = path7.join(dir, entry.name);
7715
7588
  if (entry.isDirectory()) {
7716
7589
  entries.push(...walk(full));
7717
7590
  } else if (/\.(ts|js)$/.test(entry.name) && !/\.(test|spec|config|d)\.(ts|js)$/.test(entry.name)) {
7718
7591
  try {
7719
- const stat = fs9.statSync(full);
7592
+ const stat = fs8.statSync(full);
7720
7593
  if (stat.size >= 200 && stat.size <= 5e3) {
7721
7594
  entries.push({ filePath: full, size: stat.size });
7722
7595
  }
@@ -7730,8 +7603,8 @@ function gatherSampleSourceFiles(cwd, maxFiles = 3, maxCharsEach = 2e3) {
7730
7603
  }
7731
7604
  const files = walk(baseDir).sort((a, b) => b.size - a.size).slice(0, maxFiles);
7732
7605
  for (const { filePath } of files) {
7733
- const rel = path8.relative(cwd, filePath);
7734
- const content = fs9.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
7606
+ const rel = path7.relative(cwd, filePath);
7607
+ const content = fs8.readFileSync(filePath, "utf-8").slice(0, maxCharsEach);
7735
7608
  results.push(`### File: ${rel}
7736
7609
  \`\`\`typescript
7737
7610
  ${content}
@@ -7743,9 +7616,9 @@ function ghComment(issueNumber, body, cwd) {
7743
7616
  try {
7744
7617
  let repoSlug = "";
7745
7618
  try {
7746
- const configPath = path8.join(cwd, "kody.config.json");
7747
- if (fs9.existsSync(configPath)) {
7748
- const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
7619
+ const configPath = path7.join(cwd, "kody.config.json");
7620
+ if (fs8.existsSync(configPath)) {
7621
+ const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
7749
7622
  if (config.github?.owner && config.github?.repo) {
7750
7623
  repoSlug = `${config.github.owner}/${config.github.repo}`;
7751
7624
  }
@@ -7753,7 +7626,7 @@ function ghComment(issueNumber, body, cwd) {
7753
7626
  } catch {
7754
7627
  }
7755
7628
  if (!repoSlug) return;
7756
- execFileSync5("gh", [
7629
+ execFileSync4("gh", [
7757
7630
  "issue",
7758
7631
  "comment",
7759
7632
  String(issueNumber),
@@ -7782,8 +7655,8 @@ function bootstrapCommand(opts, pkgRoot) {
7782
7655
  ghComment(issueNumber, "\u{1F527} **Bootstrap started** \u2014 analyzing project and generating configuration...", cwd);
7783
7656
  }
7784
7657
  const readIfExists = (rel, maxChars = 3e3) => {
7785
- const p = path8.join(cwd, rel);
7786
- if (fs9.existsSync(p)) return fs9.readFileSync(p, "utf-8").slice(0, maxChars);
7658
+ const p = path7.join(cwd, rel);
7659
+ if (fs8.existsSync(p)) return fs8.readFileSync(p, "utf-8").slice(0, maxChars);
7787
7660
  return null;
7788
7661
  };
7789
7662
  let repoContext = "";
@@ -7818,14 +7691,14 @@ ${sampleFiles}
7818
7691
 
7819
7692
  `;
7820
7693
  try {
7821
- const topDirs = fs9.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
7694
+ const topDirs = fs8.readdirSync(cwd, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules").map((e) => e.name);
7822
7695
  repoContext += `## Top-level directories
7823
7696
  ${topDirs.join(", ")}
7824
7697
 
7825
7698
  `;
7826
- const srcDir = path8.join(cwd, "src");
7827
- if (fs9.existsSync(srcDir)) {
7828
- const srcDirs = fs9.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
7699
+ const srcDir = path7.join(cwd, "src");
7700
+ if (fs8.existsSync(srcDir)) {
7701
+ const srcDirs = fs8.readdirSync(srcDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
7829
7702
  if (srcDirs.length > 0) repoContext += `## src/ subdirectories
7830
7703
  ${srcDirs.join(", ")}
7831
7704
 
@@ -7835,19 +7708,19 @@ ${srcDirs.join(", ")}
7835
7708
  }
7836
7709
  const existingFiles = [];
7837
7710
  for (const f of [".env.example", "CLAUDE.md", ".ai-docs", "vitest.config.ts", "vitest.config.mts", "jest.config.ts", "playwright.config.ts", ".eslintrc.js", "eslint.config.mjs", ".prettierrc"]) {
7838
- if (fs9.existsSync(path8.join(cwd, f))) existingFiles.push(f);
7711
+ if (fs8.existsSync(path7.join(cwd, f))) existingFiles.push(f);
7839
7712
  }
7840
7713
  if (existingFiles.length) repoContext += `## Config files present
7841
7714
  ${existingFiles.join(", ")}
7842
7715
 
7843
7716
  `;
7844
7717
  console.log("\u2500\u2500 Project Memory \u2500\u2500");
7845
- const memoryDir = path8.join(cwd, ".kody", "memory");
7846
- fs9.mkdirSync(memoryDir, { recursive: true });
7847
- const archPath = path8.join(memoryDir, "architecture.md");
7848
- const conventionsPath = path8.join(memoryDir, "conventions.md");
7849
- const existingArch = fs9.existsSync(archPath) ? fs9.readFileSync(archPath, "utf-8") : "";
7850
- const existingConv = fs9.existsSync(conventionsPath) ? fs9.readFileSync(conventionsPath, "utf-8") : "";
7718
+ const memoryDir = path7.join(cwd, ".kody", "memory");
7719
+ fs8.mkdirSync(memoryDir, { recursive: true });
7720
+ const archPath = path7.join(memoryDir, "architecture.md");
7721
+ const conventionsPath = path7.join(memoryDir, "conventions.md");
7722
+ const existingArch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
7723
+ const existingConv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
7851
7724
  const hasExisting = !!(existingArch || existingConv);
7852
7725
  const extendInstruction = hasExisting && !opts.force ? buildExtendInstruction(
7853
7726
  `### architecture.md:
@@ -7882,7 +7755,7 @@ Output ONLY valid JSON. No markdown fences. No explanation.
7882
7755
  ${repoContext}`;
7883
7756
  console.log(" \u23F3 Analyzing project...");
7884
7757
  try {
7885
- const output = execFileSync5("claude", [
7758
+ const output = execFileSync4("claude", [
7886
7759
  "--print",
7887
7760
  "--model",
7888
7761
  bootstrapModel,
@@ -7897,12 +7770,12 @@ ${repoContext}`;
7897
7770
  const cleaned = output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
7898
7771
  const parsed = JSON.parse(cleaned);
7899
7772
  if (parsed.architecture) {
7900
- fs9.writeFileSync(archPath, parsed.architecture);
7773
+ fs8.writeFileSync(archPath, parsed.architecture);
7901
7774
  const lineCount = parsed.architecture.split("\n").length;
7902
7775
  console.log(` \u2713 .kody/memory/architecture.md (${lineCount} lines)`);
7903
7776
  }
7904
7777
  if (parsed.conventions) {
7905
- fs9.writeFileSync(conventionsPath, parsed.conventions);
7778
+ fs8.writeFileSync(conventionsPath, parsed.conventions);
7906
7779
  const lineCount = parsed.conventions.split("\n").length;
7907
7780
  console.log(` \u2713 .kody/memory/conventions.md (${lineCount} lines)`);
7908
7781
  }
@@ -7911,37 +7784,37 @@ ${repoContext}`;
7911
7784
  const detected = detectArchitectureBasic(cwd);
7912
7785
  if (detected.length > 0) {
7913
7786
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
7914
- fs9.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
7787
+ fs8.writeFileSync(archPath, `# Architecture (auto-detected ${timestamp2})
7915
7788
 
7916
7789
  ## Overview
7917
7790
  ${detected.join("\n")}
7918
7791
  `);
7919
7792
  console.log(` \u2713 .kody/memory/architecture.md (${detected.length} items, basic detection)`);
7920
7793
  }
7921
- fs9.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
7794
+ fs8.writeFileSync(conventionsPath, "# Conventions\n\n<!-- Auto-learned conventions will be appended here -->\n");
7922
7795
  console.log(" \u2713 .kody/memory/conventions.md (seed)");
7923
7796
  }
7924
7797
  console.log("\n\u2500\u2500 Step Files \u2500\u2500");
7925
- const stepsDir = path8.join(cwd, ".kody", "steps");
7926
- fs9.mkdirSync(stepsDir, { recursive: true });
7927
- const arch = fs9.existsSync(archPath) ? fs9.readFileSync(archPath, "utf-8") : "";
7928
- const conv = fs9.existsSync(conventionsPath) ? fs9.readFileSync(conventionsPath, "utf-8") : "";
7798
+ const stepsDir = path7.join(cwd, ".kody", "steps");
7799
+ fs8.mkdirSync(stepsDir, { recursive: true });
7800
+ const arch = fs8.existsSync(archPath) ? fs8.readFileSync(archPath, "utf-8") : "";
7801
+ const conv = fs8.existsSync(conventionsPath) ? fs8.readFileSync(conventionsPath, "utf-8") : "";
7929
7802
  console.log(" \u23F3 Customizing step files...");
7930
7803
  let stepCount = 0;
7931
7804
  for (const stage of STEP_STAGES) {
7932
- const templatePath = path8.join(pkgRoot, "prompts", `${stage}.md`);
7933
- if (!fs9.existsSync(templatePath)) {
7805
+ const templatePath = path7.join(pkgRoot, "prompts", `${stage}.md`);
7806
+ if (!fs8.existsSync(templatePath)) {
7934
7807
  console.log(` \u2717 ${stage}.md \u2014 template not found in engine`);
7935
7808
  continue;
7936
7809
  }
7937
- const stepOutputPath = path8.join(stepsDir, `${stage}.md`);
7938
- const existingStep = fs9.existsSync(stepOutputPath) ? fs9.readFileSync(stepOutputPath, "utf-8") : "";
7810
+ const stepOutputPath = path7.join(stepsDir, `${stage}.md`);
7811
+ const existingStep = fs8.existsSync(stepOutputPath) ? fs8.readFileSync(stepOutputPath, "utf-8") : "";
7939
7812
  const isExtend = !!existingStep && !opts.force;
7940
- const defaultPrompt = fs9.readFileSync(templatePath, "utf-8");
7813
+ const defaultPrompt = fs8.readFileSync(templatePath, "utf-8");
7941
7814
  const contextPlaceholder = "{{TASK_CONTEXT}}";
7942
7815
  const placeholderIdx = defaultPrompt.indexOf(contextPlaceholder);
7943
7816
  if (placeholderIdx === -1) {
7944
- fs9.copyFileSync(templatePath, stepOutputPath);
7817
+ fs8.copyFileSync(templatePath, stepOutputPath);
7945
7818
  stepCount++;
7946
7819
  console.log(` \u2713 ${stage}.md`);
7947
7820
  continue;
@@ -7984,7 +7857,7 @@ ${repoContext}
7984
7857
 
7985
7858
  REMINDER: Output the full prompt template first (unchanged), then your three appended sections. Do NOT include "${contextPlaceholder}".`;
7986
7859
  try {
7987
- const output = execFileSync5("claude", [
7860
+ const output = execFileSync4("claude", [
7988
7861
  "--print",
7989
7862
  "--model",
7990
7863
  bootstrapModel,
@@ -7999,13 +7872,13 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
7999
7872
  let cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
8000
7873
  cleaned = cleaned.replace(/\n*\{\{TASK_CONTEXT\}\}\s*$/, "").trimEnd();
8001
7874
  const finalPrompt = cleaned + "\n\n" + afterPlaceholder;
8002
- fs9.writeFileSync(stepOutputPath, finalPrompt);
7875
+ fs8.writeFileSync(stepOutputPath, finalPrompt);
8003
7876
  stepCount++;
8004
7877
  console.log(` \u2713 ${stage}.md (${isExtend ? "extended" : "generated"})`);
8005
7878
  } catch {
8006
7879
  if (!isExtend) {
8007
7880
  console.log(` \u26A0 ${stage}.md \u2014 customization failed, using default template`);
8008
- fs9.copyFileSync(templatePath, stepOutputPath);
7881
+ fs8.copyFileSync(templatePath, stepOutputPath);
8009
7882
  stepCount++;
8010
7883
  } else {
8011
7884
  console.log(` \u26A0 ${stage}.md \u2014 extend failed, keeping existing`);
@@ -8014,11 +7887,11 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
8014
7887
  }
8015
7888
  console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
8016
7889
  console.log("\n\u2500\u2500 QA Guide \u2500\u2500");
8017
- const qaGuidePath = path8.join(cwd, ".kody", "qa-guide.md");
7890
+ const qaGuidePath = path7.join(cwd, ".kody", "qa-guide.md");
8018
7891
  const discovery = discoverQaContext(cwd);
8019
7892
  const hasRoutes = discovery.routes.length > 0 || discovery.collections.length > 0;
8020
7893
  if (hasRoutes) {
8021
- const existingQaGuide = fs9.existsSync(qaGuidePath) ? fs9.readFileSync(qaGuidePath, "utf-8") : "";
7894
+ const existingQaGuide = fs8.existsSync(qaGuidePath) ? fs8.readFileSync(qaGuidePath, "utf-8") : "";
8022
7895
  const isQaExtend = !!existingQaGuide && !opts.force;
8023
7896
  const serializedDiscovery = serializeDiscoveryForLLM(discovery);
8024
7897
  const qaExtendBlock = isQaExtend ? buildExtendInstruction(existingQaGuide, "QA guide") : "";
@@ -8087,7 +7960,7 @@ Command and URL.
8087
7960
  - Output ONLY the markdown. No explanation before or after.`;
8088
7961
  console.log(" \u23F3 Generating QA guide...");
8089
7962
  try {
8090
- const output = execFileSync5("claude", [
7963
+ const output = execFileSync4("claude", [
8091
7964
  "--print",
8092
7965
  "--model",
8093
7966
  bootstrapModel,
@@ -8100,12 +7973,12 @@ Command and URL.
8100
7973
  stdio: ["pipe", "pipe", "pipe"]
8101
7974
  }).trim();
8102
7975
  const cleaned = output.replace(/^```(?:markdown|md)?\s*\n?/, "").replace(/\n?```\s*$/, "");
8103
- fs9.writeFileSync(qaGuidePath, cleaned);
7976
+ fs8.writeFileSync(qaGuidePath, cleaned);
8104
7977
  console.log(` \u2713 .kody/qa-guide.md (${isQaExtend ? "extended" : "generated"}, ${discovery.routes.length} routes, ${discovery.collections.length} collections)`);
8105
7978
  } catch {
8106
7979
  console.log(" \u26A0 LLM QA generation failed \u2014 using template fallback");
8107
7980
  const qaGuide = generateQaGuideFallback(discovery);
8108
- fs9.writeFileSync(qaGuidePath, qaGuide);
7981
+ fs8.writeFileSync(qaGuidePath, qaGuide);
8109
7982
  console.log(` \u2713 .kody/qa-guide.md (fallback, ${discovery.routes.length} routes)`);
8110
7983
  }
8111
7984
  if (discovery.loginPage) console.log(` \u2713 Login page detected: ${discovery.loginPage}`);
@@ -8120,9 +7993,9 @@ Command and URL.
8120
7993
  try {
8121
7994
  let repoSlug = "";
8122
7995
  try {
8123
- const configPath = path8.join(cwd, "kody.config.json");
8124
- if (fs9.existsSync(configPath)) {
8125
- const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
7996
+ const configPath = path7.join(cwd, "kody.config.json");
7997
+ if (fs8.existsSync(configPath)) {
7998
+ const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
8126
7999
  if (config.github?.owner && config.github?.repo) {
8127
8000
  repoSlug = `${config.github.owner}/${config.github.repo}`;
8128
8001
  }
@@ -8152,7 +8025,7 @@ Command and URL.
8152
8025
  ];
8153
8026
  for (const label of labels) {
8154
8027
  try {
8155
- execFileSync5("gh", [
8028
+ execFileSync4("gh", [
8156
8029
  "label",
8157
8030
  "create",
8158
8031
  label.name,
@@ -8172,7 +8045,7 @@ Command and URL.
8172
8045
  console.log(` \u2713 ${label.name}`);
8173
8046
  } catch {
8174
8047
  try {
8175
- execFileSync5("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
8048
+ execFileSync4("gh", ["label", "list", "--repo", repoSlug, "--search", label.name], {
8176
8049
  cwd,
8177
8050
  encoding: "utf-8",
8178
8051
  timeout: 1e4,
@@ -8191,47 +8064,40 @@ Command and URL.
8191
8064
  console.log(" \u25CB Label creation skipped");
8192
8065
  }
8193
8066
  console.log("\n\u2500\u2500 Tools \u2500\u2500");
8194
- const toolsYmlPath = path8.join(cwd, ".kody", "tools.yml");
8195
- if (!fs9.existsSync(toolsYmlPath) || opts.force) {
8067
+ const toolsYmlPath = path7.join(cwd, ".kody", "tools.yml");
8068
+ if (!fs8.existsSync(toolsYmlPath) || opts.force) {
8196
8069
  const toolsTemplate = `# Kody Tools Configuration
8197
8070
  # Uncomment and configure tools that your project uses.
8198
- # The engine will detect, install, and inject tool skills into pipeline stages.
8071
+ # The engine detects tools, runs setup commands, and installs matching skills from skills.sh.
8199
8072
  #
8200
8073
  # playwright:
8201
8074
  # detect: ["playwright.config.ts", "playwright.config.js"]
8202
8075
  # stages: [verify]
8203
8076
  # setup: "npx playwright install --with-deps chromium"
8204
- # skill: playwright-cli.md
8205
8077
  `;
8206
- fs9.writeFileSync(toolsYmlPath, toolsTemplate);
8078
+ fs8.writeFileSync(toolsYmlPath, toolsTemplate);
8207
8079
  console.log(" \u2713 .kody/tools.yml (template created)");
8208
8080
  } else {
8209
8081
  console.log(" \u25CB .kody/tools.yml (already exists, keeping)");
8210
8082
  }
8211
- console.log("\n\u2500\u2500 Skills \u2500\u2500");
8212
- const installedSkillPaths = installSkillsForProject(cwd);
8213
8083
  console.log("\n\u2500\u2500 Git \u2500\u2500");
8214
8084
  const filesToCommit = [
8215
8085
  ".kody/memory/architecture.md",
8216
8086
  ".kody/memory/conventions.md",
8217
8087
  ".kody/qa-guide.md",
8218
- ".kody/tools.yml",
8219
- ...installedSkillPaths
8220
- ].filter((f) => fs9.existsSync(path8.join(cwd, f)));
8221
- if (fs9.existsSync(path8.join(cwd, "skills-lock.json"))) {
8222
- filesToCommit.push("skills-lock.json");
8223
- }
8088
+ ".kody/tools.yml"
8089
+ ].filter((f) => fs8.existsSync(path7.join(cwd, f)));
8224
8090
  for (const stage of STEP_STAGES) {
8225
8091
  const stepFile = `.kody/steps/${stage}.md`;
8226
- if (fs9.existsSync(path8.join(cwd, stepFile))) {
8092
+ if (fs8.existsSync(path7.join(cwd, stepFile))) {
8227
8093
  filesToCommit.push(stepFile);
8228
8094
  }
8229
8095
  }
8230
8096
  if (filesToCommit.length > 0) {
8231
8097
  try {
8232
- const fullPaths = filesToCommit.map((f) => path8.join(cwd, f));
8098
+ const fullPaths = filesToCommit.map((f) => path7.join(cwd, f));
8233
8099
  for (let pass = 0; pass < 2; pass++) {
8234
- execFileSync5("npx", ["prettier", "--write", ...fullPaths], {
8100
+ execFileSync4("npx", ["prettier", "--write", ...fullPaths], {
8235
8101
  cwd,
8236
8102
  encoding: "utf-8",
8237
8103
  timeout: 3e4,
@@ -8247,24 +8113,24 @@ Command and URL.
8247
8113
  try {
8248
8114
  if (isCI3) {
8249
8115
  const branchName = `kody-bootstrap-${Date.now()}`;
8250
- execFileSync5("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
8251
- execFileSync5("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
8252
- const staged = execFileSync5("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
8116
+ execFileSync4("git", ["checkout", "-b", branchName], { cwd, stdio: "pipe" });
8117
+ execFileSync4("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
8118
+ const staged = execFileSync4("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
8253
8119
  if (staged) {
8254
- execFileSync5("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
8255
- execFileSync5("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
8120
+ execFileSync4("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
8121
+ execFileSync4("git", ["push", "-u", "origin", branchName], { cwd, stdio: "pipe", timeout: 6e4 });
8256
8122
  console.log(` \u2713 Pushed branch: ${branchName}`);
8257
8123
  let baseBranch = "main";
8258
8124
  try {
8259
- const configPath = path8.join(cwd, "kody.config.json");
8260
- if (fs9.existsSync(configPath)) {
8261
- const config = JSON.parse(fs9.readFileSync(configPath, "utf-8"));
8125
+ const configPath = path7.join(cwd, "kody.config.json");
8126
+ if (fs8.existsSync(configPath)) {
8127
+ const config = JSON.parse(fs8.readFileSync(configPath, "utf-8"));
8262
8128
  baseBranch = config.git?.defaultBranch ?? "main";
8263
8129
  }
8264
8130
  } catch {
8265
8131
  }
8266
8132
  try {
8267
- const prUrl = execFileSync5("gh", [
8133
+ const prUrl = execFileSync4("gh", [
8268
8134
  "pr",
8269
8135
  "create",
8270
8136
  "--title",
@@ -8303,13 +8169,13 @@ Create it manually.`, cwd);
8303
8169
  console.log(" \u25CB No new changes to commit");
8304
8170
  }
8305
8171
  } else {
8306
- execFileSync5("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
8307
- const staged = execFileSync5("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
8172
+ execFileSync4("git", ["add", ...filesToCommit], { cwd, stdio: "pipe" });
8173
+ const staged = execFileSync4("git", ["diff", "--cached", "--name-only"], { cwd, encoding: "utf-8" }).trim();
8308
8174
  if (staged) {
8309
- execFileSync5("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
8175
+ execFileSync4("git", ["commit", "-m", "chore: Add Kody project memory and step files\n\nBootstrap Kody Engine with project-specific architecture, conventions, and pipeline step files."], { cwd, stdio: "pipe" });
8310
8176
  console.log(` \u2713 Committed: ${filesToCommit.join(", ")}`);
8311
8177
  try {
8312
- execFileSync5("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
8178
+ execFileSync4("git", ["push"], { cwd, stdio: "pipe", timeout: 6e4 });
8313
8179
  console.log(" \u2713 Pushed to origin");
8314
8180
  } catch {
8315
8181
  console.log(" \u25CB Push failed \u2014 run 'git push' manually");
@@ -8332,11 +8198,11 @@ Create it manually.`, cwd);
8332
8198
 
8333
8199
  // src/bin/cli.ts
8334
8200
  init_architecture_detection();
8335
- var __dirname2 = path32.dirname(fileURLToPath2(import.meta.url));
8336
- var PKG_ROOT = path32.resolve(__dirname2, "..", "..");
8201
+ var __dirname2 = path31.dirname(fileURLToPath2(import.meta.url));
8202
+ var PKG_ROOT = path31.resolve(__dirname2, "..", "..");
8337
8203
  function getVersion() {
8338
- const pkgPath = path32.join(PKG_ROOT, "package.json");
8339
- const pkg = JSON.parse(fs35.readFileSync(pkgPath, "utf-8"));
8204
+ const pkgPath = path31.join(PKG_ROOT, "package.json");
8205
+ const pkg = JSON.parse(fs34.readFileSync(pkgPath, "utf-8"));
8340
8206
  return pkg.version;
8341
8207
  }
8342
8208
  var args = process.argv.slice(2);
@@ -8364,6 +8230,5 @@ export {
8364
8230
  checkGhRepoAccess,
8365
8231
  checkGhSecret,
8366
8232
  detectArchitectureBasic,
8367
- detectBasicConfig,
8368
- detectSkillsForProject
8233
+ detectBasicConfig
8369
8234
  };