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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/cli.js CHANGED
@@ -226,13 +226,13 @@ function getProjectConfig() {
226
226
  const configPath = path6.join(_configDir ?? process.cwd(), "kody.config.json");
227
227
  if (fs7.existsSync(configPath)) {
228
228
  try {
229
- const result = parseJsonSafe(fs7.readFileSync(configPath, "utf-8"));
230
- if (!result.ok) {
231
- logger.warn(`kody.config.json: ${result.error} \u2014 using defaults`);
229
+ const result2 = parseJsonSafe(fs7.readFileSync(configPath, "utf-8"));
230
+ if (!result2.ok) {
231
+ logger.warn(`kody.config.json: ${result2.error} \u2014 using defaults`);
232
232
  _config = { ...DEFAULT_CONFIG };
233
233
  return _config;
234
234
  }
235
- const raw = result.data;
235
+ const raw = result2.data;
236
236
  _config = {
237
237
  quality: { ...DEFAULT_CONFIG.quality, ...raw.quality },
238
238
  git: { ...DEFAULT_CONFIG.git, ...raw.git },
@@ -966,8 +966,8 @@ function findLatestTaskForIssue(issueNumber, projectDir) {
966
966
  }
967
967
  function generateTaskId() {
968
968
  const now = /* @__PURE__ */ new Date();
969
- const pad = (n) => String(n).padStart(2, "0");
970
- return `${String(now.getFullYear()).slice(2)}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
969
+ const pad2 = (n) => String(n).padStart(2, "0");
970
+ return `${String(now.getFullYear()).slice(2)}${pad2(now.getMonth() + 1)}${pad2(now.getDate())}-${pad2(now.getHours())}${pad2(now.getMinutes())}${pad2(now.getSeconds())}`;
971
971
  }
972
972
  function resolveTaskIdFromComments(issueNumber) {
973
973
  try {
@@ -1361,13 +1361,13 @@ Kody is decomposing ${src} into tasks...`);
1361
1361
  fs11.writeFileSync(path10.join(taskDir, MARKER_FILE), JSON.stringify({ ticketId, prdFile, issueNumber }));
1362
1362
  const runner = opts.runner ?? createClaudeCodeRunner();
1363
1363
  logger.info(` model=${model} timeout=${TASKIFY_TIMEOUT_MS / 1e3}s`);
1364
- const result = await runner.run("taskify", prompt, model, TASKIFY_TIMEOUT_MS, taskDir, {
1364
+ const result2 = await runner.run("taskify", prompt, model, TASKIFY_TIMEOUT_MS, taskDir, {
1365
1365
  cwd: projectDir,
1366
1366
  mcpConfigJson,
1367
1367
  env: opts.runnerEnv
1368
1368
  });
1369
- if (result.outcome !== "completed") {
1370
- const errMsg = result.outcome === "timed_out" ? "Taskify timed out after 5 minutes." : `Taskify failed: ${result.error}`;
1369
+ if (result2.outcome !== "completed") {
1370
+ const errMsg = result2.outcome === "timed_out" ? "Taskify timed out after 5 minutes." : `Taskify failed: ${result2.error}`;
1371
1371
  if (issueNumber && !local) {
1372
1372
  postComment(issueNumber, `Kody taskify failed:
1373
1373
 
@@ -1380,7 +1380,7 @@ Kody is decomposing ${src} into tasks...`);
1380
1380
  if (!fs11.existsSync(resultPath)) {
1381
1381
  const errMsg = `Claude did not write ${RESULT_FILE}. Output:
1382
1382
 
1383
- ${result.output?.slice(0, 500) ?? "(none)"}`;
1383
+ ${result2.output?.slice(0, 500) ?? "(none)"}`;
1384
1384
  if (issueNumber && !local) {
1385
1385
  postComment(issueNumber, `Kody taskify failed: result file not found.
1386
1386
 
@@ -1568,6 +1568,999 @@ var init_taskify_command = __esm({
1568
1568
  }
1569
1569
  });
1570
1570
 
1571
+ // src/cli/test-model-tests.ts
1572
+ import * as fs12 from "fs";
1573
+ import * as os2 from "os";
1574
+ import * as path11 from "path";
1575
+ import * as zlib from "zlib";
1576
+ import { spawnSync, execSync as execSync2 } from "child_process";
1577
+ async function apiCall(ctx, body) {
1578
+ try {
1579
+ const res = await fetch(`${ctx.proxyUrl}/v1/messages`, {
1580
+ method: "POST",
1581
+ headers: {
1582
+ "Content-Type": "application/json",
1583
+ "x-api-key": ctx.apiKey,
1584
+ "anthropic-version": "2023-06-01"
1585
+ },
1586
+ body: JSON.stringify({ model: ctx.model, ...body }),
1587
+ signal: AbortSignal.timeout(6e4)
1588
+ });
1589
+ const data = await res.json();
1590
+ if (!res.ok) {
1591
+ return { ok: false, data, status: res.status, errorMsg: data?.error?.message ?? `HTTP ${res.status}` };
1592
+ }
1593
+ return { ok: true, data, status: res.status };
1594
+ } catch (err) {
1595
+ return { ok: false, data: null, status: 0, errorMsg: err instanceof Error ? err.message : String(err) };
1596
+ }
1597
+ }
1598
+ function extractText(data) {
1599
+ if (!data?.content) return "";
1600
+ return data.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("");
1601
+ }
1602
+ async function runToolConversation(ctx, tools, userPrompt, simulate, opts) {
1603
+ const messages = [{ role: "user", content: userPrompt }];
1604
+ const allCalls = [];
1605
+ for (let turn = 0; turn < (opts?.maxTurns ?? 5); turn++) {
1606
+ const body = {
1607
+ max_tokens: 1024,
1608
+ temperature: 0,
1609
+ messages,
1610
+ tools
1611
+ };
1612
+ if (opts?.system) body.system = opts.system;
1613
+ const res = await apiCall(ctx, body);
1614
+ if (!res.ok) return { finalText: "", toolCalls: allCalls, error: res.errorMsg };
1615
+ const content = res.data.content ?? [];
1616
+ const toolBlocks = content.filter((b) => b.type === "tool_use");
1617
+ const textBlocks = content.filter((b) => b.type === "text");
1618
+ if (toolBlocks.length === 0) {
1619
+ return { finalText: textBlocks.map((b) => b.text ?? "").join(""), toolCalls: allCalls };
1620
+ }
1621
+ for (const tc of toolBlocks) allCalls.push({ name: tc.name, input: tc.input });
1622
+ messages.push({ role: "assistant", content });
1623
+ messages.push({
1624
+ role: "user",
1625
+ content: toolBlocks.map((tc) => ({
1626
+ type: "tool_result",
1627
+ tool_use_id: tc.id,
1628
+ content: simulate(tc.name, tc.input)
1629
+ }))
1630
+ });
1631
+ }
1632
+ return { finalText: "", toolCalls: allCalls, error: "Max turns reached" };
1633
+ }
1634
+ function filterStderr(stderr) {
1635
+ return stderr.split("\n").filter((l) => !l.includes("CPU lacks AVX") && !l.includes("bun-darwin") && !l.includes("Warning: no stdin data") && l.trim().length > 0).join("\n").trim();
1636
+ }
1637
+ function runClaudeTest(ctx, prompt, extraFlags = [], timeout = 9e4) {
1638
+ try {
1639
+ const result2 = spawnSync("claude", [
1640
+ "--print",
1641
+ "--model",
1642
+ ctx.model,
1643
+ "--dangerously-skip-permissions",
1644
+ ...extraFlags,
1645
+ "-p",
1646
+ prompt
1647
+ ], {
1648
+ env: { ...process.env, ANTHROPIC_BASE_URL: ctx.proxyUrl, ANTHROPIC_API_KEY: ctx.apiKey },
1649
+ timeout,
1650
+ encoding: "utf-8",
1651
+ cwd: ctx.projectDir
1652
+ });
1653
+ return {
1654
+ stdout: result2.stdout ?? "",
1655
+ stderr: filterStderr(result2.stderr ?? ""),
1656
+ exitCode: result2.status ?? 1
1657
+ };
1658
+ } catch (err) {
1659
+ return { stdout: "", stderr: String(err), exitCode: 1 };
1660
+ }
1661
+ }
1662
+ function isGitClean(dir) {
1663
+ try {
1664
+ const out = execSync2("git diff --name-only", { cwd: dir, encoding: "utf-8", timeout: 5e3 });
1665
+ return out.trim().length === 0;
1666
+ } catch {
1667
+ return false;
1668
+ }
1669
+ }
1670
+ function revertChanges(dir) {
1671
+ try {
1672
+ execSync2("git checkout -- src/logger.ts", { cwd: dir, timeout: 5e3, stdio: "pipe" });
1673
+ } catch {
1674
+ }
1675
+ }
1676
+ function result(name, category, status, accuracy, durationMs, detail, metrics) {
1677
+ return { name, category, status, accuracy, durationMs, detail, metrics };
1678
+ }
1679
+ function crc32(buf) {
1680
+ let c = 4294967295;
1681
+ for (const b of buf) c = CRC_TABLE[(c ^ b) & 255] ^ c >>> 8;
1682
+ return (c ^ 4294967295) >>> 0;
1683
+ }
1684
+ function createRedPng() {
1685
+ const w = 4, h = 4;
1686
+ const scanlines = Buffer.alloc(h * (1 + w * 3));
1687
+ for (let y = 0; y < h; y++) {
1688
+ const off = y * (1 + w * 3);
1689
+ scanlines[off] = 0;
1690
+ for (let x = 0; x < w; x++) {
1691
+ scanlines[off + 1 + x * 3] = 255;
1692
+ scanlines[off + 1 + x * 3 + 1] = 0;
1693
+ scanlines[off + 1 + x * 3 + 2] = 0;
1694
+ }
1695
+ }
1696
+ function chunk(type, data) {
1697
+ const tb = Buffer.from(type, "ascii");
1698
+ const merged = Buffer.concat([tb, data]);
1699
+ const len = Buffer.alloc(4);
1700
+ len.writeUInt32BE(data.length);
1701
+ const crcBuf = Buffer.alloc(4);
1702
+ crcBuf.writeUInt32BE(crc32(merged));
1703
+ return Buffer.concat([len, tb, data, crcBuf]);
1704
+ }
1705
+ const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
1706
+ const ihdr = Buffer.alloc(13);
1707
+ ihdr.writeUInt32BE(w, 0);
1708
+ ihdr.writeUInt32BE(h, 4);
1709
+ ihdr[8] = 8;
1710
+ ihdr[9] = 2;
1711
+ return Buffer.concat([sig, chunk("IHDR", ihdr), chunk("IDAT", zlib.deflateSync(scanlines)), chunk("IEND", Buffer.alloc(0))]);
1712
+ }
1713
+ async function testSimplePrompt(ctx) {
1714
+ const t = Date.now();
1715
+ const res = await apiCall(ctx, {
1716
+ max_tokens: 50,
1717
+ temperature: 0,
1718
+ messages: [{ role: "user", content: "Reply with exactly: KODY_TEST_OK" }]
1719
+ });
1720
+ if (!res.ok) return result("simple_prompt", "basic", "fail", 0, Date.now() - t, `API error: ${res.errorMsg}`);
1721
+ const text = extractText(res.data);
1722
+ const ok = text.includes("KODY_TEST_OK");
1723
+ return result(
1724
+ "simple_prompt",
1725
+ "basic",
1726
+ ok ? "pass" : "fail",
1727
+ ok ? 100 : 0,
1728
+ Date.now() - t,
1729
+ ok ? "Model responded correctly" : `Expected KODY_TEST_OK, got: ${text.slice(0, 80)}`
1730
+ );
1731
+ }
1732
+ async function testJsonOutput(ctx) {
1733
+ const t = Date.now();
1734
+ const res = await apiCall(ctx, {
1735
+ max_tokens: 200,
1736
+ temperature: 0,
1737
+ system: "Respond with ONLY valid JSON. No markdown fences, no explanation. Just raw JSON.",
1738
+ messages: [{ role: "user", content: 'Return a JSON object with keys "status" (string "ok") and "model" (string, your model name).' }]
1739
+ });
1740
+ if (!res.ok) return result("json_output", "basic", "fail", 0, Date.now() - t, `API error: ${res.errorMsg}`);
1741
+ let text = extractText(res.data).trim();
1742
+ text = text.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "").trim();
1743
+ try {
1744
+ const parsed = JSON.parse(text);
1745
+ const hasKeys = typeof parsed.status === "string" && typeof parsed.model === "string";
1746
+ return result(
1747
+ "json_output",
1748
+ "basic",
1749
+ "pass",
1750
+ hasKeys ? 100 : 70,
1751
+ Date.now() - t,
1752
+ hasKeys ? "Valid JSON with correct keys" : "Valid JSON but missing expected keys"
1753
+ );
1754
+ } catch {
1755
+ return result("json_output", "basic", "fail", 0, Date.now() - t, `Invalid JSON: ${text.slice(0, 80)}`);
1756
+ }
1757
+ }
1758
+ async function testSystemPromptRules(ctx) {
1759
+ const t = Date.now();
1760
+ const res = await apiCall(ctx, {
1761
+ max_tokens: 200,
1762
+ temperature: 0,
1763
+ system: [
1764
+ "STRICT RULES \u2014 violating ANY will crash the system:",
1765
+ "1) Start every response with 'KODY:'",
1766
+ "2) Never use the word 'the'",
1767
+ "3) Keep response under 50 words",
1768
+ "4) End your response with 'END'",
1769
+ "5) Use ONLY lowercase letters (no uppercase anywhere)"
1770
+ ].join("\n"),
1771
+ messages: [{ role: "user", content: "Describe what a compiler does." }]
1772
+ });
1773
+ if (!res.ok) return result("system_prompt_rules", "basic", "fail", 0, Date.now() - t, `API error: ${res.errorMsg}`);
1774
+ const text = extractText(res.data).trim();
1775
+ let score = 0;
1776
+ const checks = [];
1777
+ if (text.startsWith("KODY:") || text.startsWith("kody:")) {
1778
+ score += 20;
1779
+ checks.push("starts-with-kody");
1780
+ }
1781
+ if (!text.toLowerCase().split(/\s+/).includes("the")) {
1782
+ score += 20;
1783
+ checks.push("no-the");
1784
+ }
1785
+ if (text.split(/\s+/).length <= 55) {
1786
+ score += 20;
1787
+ checks.push("under-50-words");
1788
+ }
1789
+ if (text.endsWith("END") || text.endsWith("end")) {
1790
+ score += 20;
1791
+ checks.push("ends-with-end");
1792
+ }
1793
+ if (text === text.toLowerCase()) {
1794
+ score += 20;
1795
+ checks.push("all-lowercase");
1796
+ }
1797
+ const status = score >= 80 ? "pass" : score >= 40 ? "warn" : "fail";
1798
+ return result(
1799
+ "system_prompt_rules",
1800
+ "basic",
1801
+ status,
1802
+ score,
1803
+ Date.now() - t,
1804
+ `${score / 20}/5 rules followed: ${checks.join(", ")}`,
1805
+ { instructionCompliance: score }
1806
+ );
1807
+ }
1808
+ async function testExtendedThinking(ctx) {
1809
+ const t = Date.now();
1810
+ const res = await apiCall(ctx, {
1811
+ max_tokens: 200,
1812
+ thinking: { type: "enabled", budget_tokens: 2e3 },
1813
+ messages: [{ role: "user", content: "What is 15 * 23?" }]
1814
+ });
1815
+ if (!res.ok) return result(
1816
+ "extended_thinking",
1817
+ "infrastructure",
1818
+ "warn",
1819
+ 50,
1820
+ Date.now() - t,
1821
+ `Request failed (model may not support thinking): ${res.errorMsg?.slice(0, 80)}`
1822
+ );
1823
+ const hasThinking = Array.isArray(res.data.content) && res.data.content.some((b) => b.type === "thinking");
1824
+ const hasText = extractText(res.data).length > 0;
1825
+ if (hasThinking) return result("extended_thinking", "infrastructure", "pass", 100, Date.now() - t, "Thinking block present in response");
1826
+ if (hasText) return result("extended_thinking", "infrastructure", "warn", 70, Date.now() - t, "Response OK but no thinking block");
1827
+ return result("extended_thinking", "infrastructure", "fail", 0, Date.now() - t, "No content in response");
1828
+ }
1829
+ async function testToolRead(ctx) {
1830
+ const t = Date.now();
1831
+ const testFile = path11.join(os2.tmpdir(), "kody-test-model-read.txt");
1832
+ fs12.writeFileSync(testFile, "KODY_SECRET_CONTENT_42");
1833
+ try {
1834
+ const conv = await runToolConversation(
1835
+ ctx,
1836
+ [TOOL_READ],
1837
+ `Read the file ${testFile} and tell me what it contains.`,
1838
+ (name, input) => {
1839
+ if (name === "Read" && input.path === testFile) return "KODY_SECRET_CONTENT_42";
1840
+ return "Error: File not found";
1841
+ }
1842
+ );
1843
+ if (conv.error) return result("tool_read", "tool-use", "fail", 0, Date.now() - t, `Error: ${conv.error}`);
1844
+ const calledRead = conv.toolCalls.some((tc) => tc.name === "Read");
1845
+ const correctPath = conv.toolCalls.some((tc) => tc.name === "Read" && tc.input.path === testFile);
1846
+ const mentionsContent = conv.finalText.includes("KODY_SECRET_CONTENT_42") || conv.finalText.includes("42");
1847
+ let acc = 0;
1848
+ if (calledRead) acc += 30;
1849
+ if (correctPath) acc += 30;
1850
+ if (mentionsContent) acc += 40;
1851
+ return result(
1852
+ "tool_read",
1853
+ "tool-use",
1854
+ acc >= 60 ? "pass" : "fail",
1855
+ acc,
1856
+ Date.now() - t,
1857
+ `Read called: ${calledRead}, correct path: ${correctPath}, content referenced: ${mentionsContent}`,
1858
+ { toolSelection: calledRead ? 100 : 0 }
1859
+ );
1860
+ } finally {
1861
+ fs12.rmSync(testFile, { force: true });
1862
+ }
1863
+ }
1864
+ async function testToolEdit(ctx) {
1865
+ const t = Date.now();
1866
+ const conv = await runToolConversation(
1867
+ ctx,
1868
+ [TOOL_READ, TOOL_EDIT],
1869
+ 'Read the file /tmp/kody-edit-test.txt, then use Edit to replace "hello" with "goodbye" in it.',
1870
+ (name, input) => {
1871
+ if (name === "Read") return "hello world";
1872
+ if (name === "Edit") return "File edited successfully";
1873
+ return "Unknown tool";
1874
+ }
1875
+ );
1876
+ if (conv.error) return result("tool_edit", "tool-use", "fail", 0, Date.now() - t, `Error: ${conv.error}`);
1877
+ const editCall = conv.toolCalls.find((tc) => tc.name === "Edit");
1878
+ let acc = 0;
1879
+ if (editCall) {
1880
+ acc += 40;
1881
+ if (editCall.input.old_string === "hello") acc += 30;
1882
+ if (editCall.input.new_string === "goodbye") acc += 30;
1883
+ }
1884
+ return result(
1885
+ "tool_edit",
1886
+ "tool-use",
1887
+ acc >= 70 ? "pass" : acc > 0 ? "warn" : "fail",
1888
+ acc,
1889
+ Date.now() - t,
1890
+ editCall ? `Edit called with old="${editCall.input.old_string}" new="${editCall.input.new_string}"` : "Edit tool was not called",
1891
+ { toolSelection: editCall ? 100 : 0 }
1892
+ );
1893
+ }
1894
+ async function testToolBash(ctx) {
1895
+ const t = Date.now();
1896
+ const conv = await runToolConversation(
1897
+ ctx,
1898
+ [TOOL_BASH],
1899
+ "Run this exact bash command: echo KODY_BASH_OK",
1900
+ (name, input) => {
1901
+ if (name === "Bash") return "KODY_BASH_OK\n";
1902
+ return "Error";
1903
+ }
1904
+ );
1905
+ if (conv.error) return result("tool_bash", "tool-use", "fail", 0, Date.now() - t, `Error: ${conv.error}`);
1906
+ const bashCall = conv.toolCalls.find((tc) => tc.name === "Bash");
1907
+ const correctCmd = bashCall && String(bashCall.input.command).includes("echo KODY_BASH_OK");
1908
+ const acc = bashCall ? correctCmd ? 100 : 50 : 0;
1909
+ return result(
1910
+ "tool_bash",
1911
+ "tool-use",
1912
+ acc >= 50 ? "pass" : "fail",
1913
+ acc,
1914
+ Date.now() - t,
1915
+ bashCall ? `Bash called: ${bashCall.input.command}` : "Bash tool was not called",
1916
+ { toolSelection: bashCall ? 100 : 0 }
1917
+ );
1918
+ }
1919
+ async function testImageAttachment(ctx) {
1920
+ const t = Date.now();
1921
+ const pngData = createRedPng().toString("base64");
1922
+ const res = await apiCall(ctx, {
1923
+ max_tokens: 100,
1924
+ temperature: 0,
1925
+ messages: [{
1926
+ role: "user",
1927
+ content: [
1928
+ { type: "image", source: { type: "base64", media_type: "image/png", data: pngData } },
1929
+ { type: "text", text: "What color is this image? Reply with just the color name." }
1930
+ ]
1931
+ }]
1932
+ });
1933
+ if (!res.ok) return result(
1934
+ "image_attachment",
1935
+ "tool-use",
1936
+ "fail",
1937
+ 0,
1938
+ Date.now() - t,
1939
+ `API error (model may not support vision): ${res.errorMsg?.slice(0, 80)}`
1940
+ );
1941
+ const text = extractText(res.data).toLowerCase();
1942
+ const mentionsRed = text.includes("red");
1943
+ const mentionsColor = mentionsRed || text.includes("color") || text.includes("image") || text.includes("pixel");
1944
+ const acc = mentionsRed ? 100 : mentionsColor ? 50 : 20;
1945
+ return result(
1946
+ "image_attachment",
1947
+ "tool-use",
1948
+ mentionsRed ? "pass" : mentionsColor ? "warn" : "fail",
1949
+ acc,
1950
+ Date.now() - t,
1951
+ `Response: ${text.slice(0, 80)}`
1952
+ );
1953
+ }
1954
+ async function testErrorRecovery(ctx) {
1955
+ const t = Date.now();
1956
+ let errorGiven = false;
1957
+ const conv = await runToolConversation(
1958
+ ctx,
1959
+ [TOOL_READ, TOOL_BASH],
1960
+ "Read the file /tmp/nonexistent-kody-file.txt and tell me what's in it. If the file doesn't exist, say so.",
1961
+ (name, input) => {
1962
+ if (name === "Read" && !errorGiven) {
1963
+ errorGiven = true;
1964
+ return "Error: ENOENT: no such file or directory";
1965
+ }
1966
+ if (name === "Bash") return "ls: /tmp/nonexistent-kody-file.txt: No such file or directory";
1967
+ return "Error: File not found";
1968
+ }
1969
+ );
1970
+ if (conv.error) return result("error_recovery", "advanced", "fail", 0, Date.now() - t, `Error: ${conv.error}`);
1971
+ const reported = conv.finalText.toLowerCase().includes("not found") || conv.finalText.toLowerCase().includes("doesn't exist") || conv.finalText.toLowerCase().includes("does not exist") || conv.finalText.toLowerCase().includes("no such file");
1972
+ const tried = conv.toolCalls.length >= 1;
1973
+ const acc = reported ? tried ? 100 : 70 : 20;
1974
+ return result(
1975
+ "error_recovery",
1976
+ "advanced",
1977
+ reported ? "pass" : "warn",
1978
+ acc,
1979
+ Date.now() - t,
1980
+ reported ? "Gracefully reported missing file" : `Response: ${conv.finalText.slice(0, 80)}`
1981
+ );
1982
+ }
1983
+ async function testToolMultiStep(ctx) {
1984
+ const t = Date.now();
1985
+ const r = runClaudeTest(
1986
+ ctx,
1987
+ "Do these steps in order: 1) Read kody.config.json 2) Tell me the value of git.defaultBranch. Reply with ONLY the branch name, nothing else."
1988
+ );
1989
+ if (!r.stdout.trim() && r.exitCode !== 0) return result(
1990
+ "tool_multi_step",
1991
+ "tool-use",
1992
+ "fail",
1993
+ 0,
1994
+ Date.now() - t,
1995
+ `CLI failed: ${r.stderr.slice(0, 200) || "no output"}`
1996
+ );
1997
+ const out = r.stdout.trim().toLowerCase();
1998
+ const correct = out.includes("main");
1999
+ return result(
2000
+ "tool_multi_step",
2001
+ "tool-use",
2002
+ correct ? "pass" : "fail",
2003
+ correct ? 100 : 20,
2004
+ Date.now() - t,
2005
+ correct ? "Correct: main" : `Got: ${out.slice(0, 80)}`
2006
+ );
2007
+ }
2008
+ async function testPlanStage(ctx) {
2009
+ const t = Date.now();
2010
+ const wasClean = isGitClean(ctx.projectDir);
2011
+ const r = runClaudeTest(ctx, [
2012
+ "You are a planning agent. Your ONLY job is to output a markdown plan.",
2013
+ "CRITICAL: Do NOT use Edit, Write, or Bash tools. Do NOT modify any files. ONLY use Read, Glob, and Grep for research.",
2014
+ "If you modify any files, the system will crash.",
2015
+ "",
2016
+ "Task: Plan adding a /health endpoint to an Express app.",
2017
+ "Output a markdown plan with ## Step N sections. Each step must have File, Change, and Why fields.",
2018
+ "Keep it to 3 steps maximum."
2019
+ ].join("\n"), [], 12e4);
2020
+ const filesModified = wasClean && !isGitClean(ctx.projectDir);
2021
+ if (filesModified) revertChanges(ctx.projectDir);
2022
+ if (!r.stdout.trim() && r.exitCode !== 0) return result(
2023
+ "plan_stage",
2024
+ "stage-simulation",
2025
+ "fail",
2026
+ 0,
2027
+ Date.now() - t,
2028
+ `CLI failed: ${r.stderr.slice(0, 200) || "no output"}`
2029
+ );
2030
+ const out = r.stdout;
2031
+ const hasStepFormat = /##\s*Step/i.test(out);
2032
+ const hasStructure = hasStepFormat || /\*\*File\*\*/i.test(out) && /\*\*Change\*\*/i.test(out);
2033
+ const boundary = filesModified ? 0 : 100;
2034
+ const format = hasStructure ? 100 : hasStepFormat ? 70 : out.length > 50 ? 30 : 0;
2035
+ const acc = Math.round(boundary * 0.6 + format * 0.4);
2036
+ const status = filesModified ? "fail" : hasStructure ? "pass" : "warn";
2037
+ return result(
2038
+ "plan_stage",
2039
+ "stage-simulation",
2040
+ status,
2041
+ acc,
2042
+ Date.now() - t,
2043
+ filesModified ? "FAIL: Model modified files during plan stage (instruction violation)" : hasStructure ? "Plan output with correct structure, no files modified" : "Output lacks expected ## Step structure",
2044
+ { boundaryRespect: boundary, outputFormat: format, instructionCompliance: boundary }
2045
+ );
2046
+ }
2047
+ async function testBuildStage(ctx) {
2048
+ const t = Date.now();
2049
+ const r = runClaudeTest(ctx, "Add a comment '// kody-build-test' as the very first line of src/logger.ts. That is your only task.");
2050
+ const diff = (() => {
2051
+ try {
2052
+ return execSync2("git diff src/logger.ts", { cwd: ctx.projectDir, encoding: "utf-8", timeout: 5e3 });
2053
+ } catch {
2054
+ return "";
2055
+ }
2056
+ })();
2057
+ const edited = diff.includes("kody-build-test");
2058
+ revertChanges(ctx.projectDir);
2059
+ if (!r.stdout.trim() && r.exitCode !== 0 && !edited) return result(
2060
+ "build_stage",
2061
+ "stage-simulation",
2062
+ "fail",
2063
+ 0,
2064
+ Date.now() - t,
2065
+ `CLI failed: ${r.stderr.slice(0, 200) || "no output"}`
2066
+ );
2067
+ return result(
2068
+ "build_stage",
2069
+ "stage-simulation",
2070
+ edited ? "pass" : "fail",
2071
+ edited ? 100 : 0,
2072
+ Date.now() - t,
2073
+ edited ? "File correctly modified with expected comment" : "File was not modified as expected"
2074
+ );
2075
+ }
2076
+ async function testReviewStage(ctx) {
2077
+ const t = Date.now();
2078
+ const wasClean = isGitClean(ctx.projectDir);
2079
+ const r = runClaudeTest(ctx, [
2080
+ "You are a code review agent. Review the file src/logger.ts.",
2081
+ "CRITICAL: Do NOT modify any files. Only READ and analyze.",
2082
+ "Output your review as markdown with this exact format:",
2083
+ "## Summary",
2084
+ "<1-2 sentence summary>",
2085
+ "## Issues Found",
2086
+ "- <issues>",
2087
+ "## Verdict",
2088
+ "APPROVE or REQUEST_CHANGES"
2089
+ ].join("\n"));
2090
+ const filesModified = wasClean && !isGitClean(ctx.projectDir);
2091
+ if (filesModified) revertChanges(ctx.projectDir);
2092
+ if (!r.stdout.trim() && r.exitCode !== 0) return result(
2093
+ "review_stage",
2094
+ "stage-simulation",
2095
+ "fail",
2096
+ 0,
2097
+ Date.now() - t,
2098
+ `CLI failed: ${r.stderr.slice(0, 200) || "no output"}`
2099
+ );
2100
+ const out = r.stdout;
2101
+ const hasVerdict = /verdict/i.test(out);
2102
+ const hasSummary = /summary/i.test(out);
2103
+ const boundary = filesModified ? 0 : 100;
2104
+ const format = (hasVerdict ? 50 : 0) + (hasSummary ? 50 : 0);
2105
+ const acc = Math.round(boundary * 0.5 + format * 0.5);
2106
+ const status = filesModified ? "fail" : hasVerdict && hasSummary ? "pass" : "warn";
2107
+ return result(
2108
+ "review_stage",
2109
+ "stage-simulation",
2110
+ status,
2111
+ acc,
2112
+ Date.now() - t,
2113
+ filesModified ? "FAIL: Model modified files during review (instruction violation)" : `Summary: ${hasSummary}, Verdict: ${hasVerdict}, no files modified`,
2114
+ { boundaryRespect: boundary, outputFormat: format }
2115
+ );
2116
+ }
2117
+ async function testMcpTools(ctx) {
2118
+ const t = Date.now();
2119
+ const mcpConfig = path11.join(os2.tmpdir(), `kody-test-mcp-${Date.now()}.json`);
2120
+ const testFile = path11.join(ctx.projectDir, "kody-mcp-compat-test.txt");
2121
+ try {
2122
+ fs12.writeFileSync(mcpConfig, JSON.stringify({
2123
+ mcpServers: {
2124
+ filesystem: { command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", ctx.projectDir] }
2125
+ }
2126
+ }));
2127
+ const r = runClaudeTest(
2128
+ ctx,
2129
+ `Use the MCP filesystem write_file tool to create a file at ${testFile} with the content 'mcp-ok'. Do not use the built-in Write tool.`,
2130
+ ["--mcp-config", mcpConfig],
2131
+ 12e4
2132
+ );
2133
+ const created = fs12.existsSync(testFile);
2134
+ const content = created ? fs12.readFileSync(testFile, "utf-8").trim() : "";
2135
+ const correct = content.includes("mcp-ok");
2136
+ return result(
2137
+ "mcp_tools",
2138
+ "advanced",
2139
+ created ? "pass" : "fail",
2140
+ correct ? 100 : created ? 70 : 0,
2141
+ Date.now() - t,
2142
+ created ? `File created, content: ${content.slice(0, 50)}` : `MCP test failed: ${r.stderr.slice(0, 80)}`
2143
+ );
2144
+ } catch (err) {
2145
+ return result("mcp_tools", "advanced", "warn", 0, Date.now() - t, `MCP test error: ${err instanceof Error ? err.message : String(err)}`);
2146
+ } finally {
2147
+ fs12.rmSync(mcpConfig, { force: true });
2148
+ fs12.rmSync(testFile, { force: true });
2149
+ revertChanges(ctx.projectDir);
2150
+ }
2151
+ }
2152
+ var TOOL_READ, TOOL_EDIT, TOOL_BASH, CRC_TABLE, ALL_TESTS;
2153
+ var init_test_model_tests = __esm({
2154
+ "src/cli/test-model-tests.ts"() {
2155
+ "use strict";
2156
+ TOOL_READ = {
2157
+ name: "Read",
2158
+ description: "Read a file from the filesystem",
2159
+ input_schema: {
2160
+ type: "object",
2161
+ properties: { path: { type: "string", description: "Absolute file path" } },
2162
+ required: ["path"]
2163
+ }
2164
+ };
2165
+ TOOL_EDIT = {
2166
+ name: "Edit",
2167
+ description: "Replace old_string with new_string in a file",
2168
+ input_schema: {
2169
+ type: "object",
2170
+ properties: {
2171
+ file_path: { type: "string" },
2172
+ old_string: { type: "string" },
2173
+ new_string: { type: "string" }
2174
+ },
2175
+ required: ["file_path", "old_string", "new_string"]
2176
+ }
2177
+ };
2178
+ TOOL_BASH = {
2179
+ name: "Bash",
2180
+ description: "Execute a bash command and return output",
2181
+ input_schema: {
2182
+ type: "object",
2183
+ properties: { command: { type: "string", description: "The command to run" } },
2184
+ required: ["command"]
2185
+ }
2186
+ };
2187
+ CRC_TABLE = new Uint32Array(256);
2188
+ for (let n = 0; n < 256; n++) {
2189
+ let c = n;
2190
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
2191
+ CRC_TABLE[n] = c >>> 0;
2192
+ }
2193
+ ALL_TESTS = [
2194
+ // Infrastructure
2195
+ { name: "extended_thinking", category: "infrastructure", description: "Extended thinking parameter support", run: testExtendedThinking },
2196
+ // Basic
2197
+ { name: "simple_prompt", category: "basic", description: "Basic text prompt and response", run: testSimplePrompt },
2198
+ { name: "json_output", category: "basic", description: "JSON-only output constraint", run: testJsonOutput },
2199
+ { name: "system_prompt_rules", category: "basic", description: "Multi-rule system prompt adherence", run: testSystemPromptRules },
2200
+ // Tool use
2201
+ { name: "tool_read", category: "tool-use", description: "Read tool: file reading", run: testToolRead },
2202
+ { name: "tool_edit", category: "tool-use", description: "Edit tool: old/new string replacement", run: testToolEdit },
2203
+ { name: "tool_bash", category: "tool-use", description: "Bash tool: command execution", run: testToolBash },
2204
+ { name: "tool_multi_step", category: "tool-use", description: "Multi-step tool chain via CLI", run: testToolMultiStep },
2205
+ { name: "image_attachment", category: "tool-use", description: "Vision: image content processing", run: testImageAttachment },
2206
+ // Stage simulation
2207
+ { name: "plan_stage", category: "stage-simulation", description: "Plan stage: read-only research + structured output", run: testPlanStage },
2208
+ { name: "build_stage", category: "stage-simulation", description: "Build stage: code editing", run: testBuildStage },
2209
+ { name: "review_stage", category: "stage-simulation", description: "Review stage: read-only + structured verdict", run: testReviewStage },
2210
+ // Advanced
2211
+ { name: "mcp_tools", category: "advanced", description: "MCP server tool integration", run: testMcpTools },
2212
+ { name: "error_recovery", category: "advanced", description: "Graceful error handling on tool failure", run: testErrorRecovery }
2213
+ ];
2214
+ }
2215
+ });
2216
+
2217
+ // src/cli/test-model-report.ts
2218
+ function pad(str, len) {
2219
+ return str.padEnd(len);
2220
+ }
2221
+ function fmtDuration(ms) {
2222
+ return `${(ms / 1e3).toFixed(1)}s`;
2223
+ }
2224
+ function formatReport(report) {
2225
+ const W = 74;
2226
+ const lines = [];
2227
+ lines.push("=".repeat(W));
2228
+ lines.push("");
2229
+ lines.push(" Model Compatibility Report");
2230
+ lines.push(` Provider: ${report.provider} | Model: ${report.model}`);
2231
+ lines.push(` Date: ${report.timestamp}`);
2232
+ lines.push(` Duration: ${fmtDuration(report.totalDurationMs)}`);
2233
+ lines.push("");
2234
+ lines.push("-".repeat(W));
2235
+ for (const cat of CATEGORY_ORDER) {
2236
+ const catResults = report.results.filter((r) => r.category === cat);
2237
+ if (catResults.length === 0) continue;
2238
+ lines.push("");
2239
+ lines.push(` ${CATEGORY_LABELS[cat]}`);
2240
+ lines.push("");
2241
+ for (const r of catResults) {
2242
+ const icon = r.status === "pass" ? "+" : r.status === "fail" ? "x" : "!";
2243
+ const name = pad(r.name, 28);
2244
+ const status = pad(r.status.toUpperCase(), 6);
2245
+ const acc = pad(`${r.accuracy}%`, 5);
2246
+ const dur = fmtDuration(r.durationMs);
2247
+ lines.push(` [${icon}] ${name} ${status} ${acc} ${dur}`);
2248
+ if (r.status !== "pass" && r.detail) {
2249
+ lines.push(` ${r.detail.slice(0, W - 8)}`);
2250
+ }
2251
+ }
2252
+ }
2253
+ const passed = report.results.filter((r) => r.status === "pass").length;
2254
+ const failed = report.results.filter((r) => r.status === "fail").length;
2255
+ const warned = report.results.filter((r) => r.status === "warn").length;
2256
+ const total = report.results.length;
2257
+ const avgAccuracy = total > 0 ? Math.round(report.results.reduce((s, r) => s + r.accuracy, 0) / total) : 0;
2258
+ lines.push("");
2259
+ lines.push("-".repeat(W));
2260
+ lines.push("");
2261
+ lines.push(` RESULTS: ${passed}/${total} PASS | ${failed} FAIL | ${warned} WARN`);
2262
+ lines.push(` OVERALL ACCURACY: ${avgAccuracy}%`);
2263
+ lines.push(` drop_params required: ${report.dropParamsRequired ? "YES" : "NO"}`);
2264
+ lines.push("");
2265
+ lines.push(" ACCURACY BY CATEGORY:");
2266
+ for (const cat of CATEGORY_ORDER) {
2267
+ const cr = report.results.filter((r) => r.category === cat);
2268
+ if (cr.length === 0) continue;
2269
+ const avg = Math.round(cr.reduce((s, r) => s + r.accuracy, 0) / cr.length);
2270
+ lines.push(` ${pad(CATEGORY_LABELS[cat], 22)} ${avg}%`);
2271
+ }
2272
+ lines.push("");
2273
+ lines.push(" RECOMMENDATION:");
2274
+ for (const line of getRecommendation(report)) {
2275
+ lines.push(` ${line}`);
2276
+ }
2277
+ lines.push("");
2278
+ lines.push("=".repeat(W));
2279
+ return lines.join("\n");
2280
+ }
2281
+ function getRecommendation(report) {
2282
+ const lines = [];
2283
+ const failedTests = report.results.filter((r) => r.status === "fail");
2284
+ const avg = report.results.length > 0 ? Math.round(report.results.reduce((s, r) => s + r.accuracy, 0) / report.results.length) : 0;
2285
+ if (avg >= 90 && failedTests.length === 0) {
2286
+ lines.push("[+] Fully compatible -- suitable for all pipeline stages");
2287
+ return lines;
2288
+ }
2289
+ const stageResults = report.results.filter((r) => r.category === "stage-simulation");
2290
+ const workingStages = stageResults.filter((r) => r.status === "pass").map((r) => r.name.replace("_stage", ""));
2291
+ const failingStages = stageResults.filter((r) => r.status !== "pass").map((r) => r.name.replace("_stage", ""));
2292
+ if (workingStages.length > 0) {
2293
+ lines.push(`[+] Suitable for: ${workingStages.join(", ")} stages`);
2294
+ }
2295
+ if (failingStages.length > 0) {
2296
+ lines.push(`[x] Not recommended for: ${failingStages.join(", ")} stages`);
2297
+ }
2298
+ if (failedTests.length > 0) {
2299
+ lines.push("");
2300
+ lines.push("Failed tests:");
2301
+ for (const t of failedTests) {
2302
+ lines.push(` - ${t.name}: ${t.detail.slice(0, 60)}`);
2303
+ }
2304
+ }
2305
+ return lines;
2306
+ }
2307
+ var CATEGORY_ORDER, CATEGORY_LABELS;
2308
+ var init_test_model_report = __esm({
2309
+ "src/cli/test-model-report.ts"() {
2310
+ "use strict";
2311
+ CATEGORY_ORDER = ["infrastructure", "basic", "tool-use", "stage-simulation", "advanced"];
2312
+ CATEGORY_LABELS = {
2313
+ infrastructure: "INFRASTRUCTURE",
2314
+ basic: "BASIC CAPABILITIES",
2315
+ "tool-use": "TOOL USE",
2316
+ "stage-simulation": "STAGE SIMULATION",
2317
+ advanced: "ADVANCED"
2318
+ };
2319
+ }
2320
+ });
2321
+
2322
+ // src/cli/test-model-command.ts
2323
+ var test_model_command_exports = {};
2324
+ __export(test_model_command_exports, {
2325
+ runTestModelCommand: () => runTestModelCommand
2326
+ });
2327
+ import * as fs13 from "fs";
2328
+ import * as os3 from "os";
2329
+ import * as path12 from "path";
2330
+ import { execFileSync as execFileSync10 } from "child_process";
2331
+ function parseTestModelArgs() {
2332
+ const args2 = process.argv.slice(3);
2333
+ function getArg3(flag) {
2334
+ const idx = args2.indexOf(flag);
2335
+ if (idx !== -1 && args2[idx + 1] && !args2[idx + 1].startsWith("--")) return args2[idx + 1];
2336
+ return void 0;
2337
+ }
2338
+ const hasFlag3 = (f) => args2.includes(f);
2339
+ if (hasFlag3("--help") || hasFlag3("-h")) {
2340
+ logger.info([
2341
+ "Usage: kody test-model --provider <provider> --model <model> --key <api-key> [options]",
2342
+ "",
2343
+ "Options:",
2344
+ " --provider LLM provider name (e.g. gemini, openai, mistral)",
2345
+ " --model Model identifier (e.g. gemini-2.5-flash)",
2346
+ " --key API key for the provider",
2347
+ " --key-env Read API key from this environment variable",
2348
+ " --skip-proxy Use an already-running LiteLLM proxy (don't start one)",
2349
+ " --litellm-url LiteLLM proxy URL (default: http://localhost:4099)",
2350
+ " --filter Comma-separated test names to run (default: all)",
2351
+ " --list List all available tests and exit"
2352
+ ].join("\n"));
2353
+ process.exit(0);
2354
+ }
2355
+ if (hasFlag3("--list")) {
2356
+ for (const t of ALL_TESTS) {
2357
+ logger.info(` ${t.name.padEnd(24)} [${t.category}] ${t.description}`);
2358
+ }
2359
+ process.exit(0);
2360
+ }
2361
+ const provider = getArg3("--provider");
2362
+ const model = getArg3("--model");
2363
+ const key = getArg3("--key");
2364
+ const keyEnv = getArg3("--key-env");
2365
+ if (!provider || !model) {
2366
+ logger.error("Required: --provider <provider> --model <model> --key <key>");
2367
+ logger.error("Run with --help for usage.");
2368
+ process.exit(1);
2369
+ }
2370
+ let apiKey = key;
2371
+ if (!apiKey && keyEnv) apiKey = process.env[keyEnv];
2372
+ if (!apiKey) {
2373
+ logger.error("API key required: use --key <value> or --key-env <ENV_VAR>");
2374
+ process.exit(1);
2375
+ }
2376
+ return {
2377
+ provider,
2378
+ model,
2379
+ apiKey,
2380
+ proxyUrl: getArg3("--litellm-url") ?? TEST_URL,
2381
+ skipProxy: hasFlag3("--skip-proxy"),
2382
+ filter: getArg3("--filter")?.split(",")
2383
+ };
2384
+ }
2385
+ function generateConfig(provider, model, dropParams) {
2386
+ const lines = [];
2387
+ if (dropParams) {
2388
+ lines.push("litellm_settings:");
2389
+ lines.push(" drop_params: true");
2390
+ lines.push("");
2391
+ }
2392
+ lines.push("model_list:");
2393
+ lines.push(` - model_name: ${model}`);
2394
+ lines.push(" litellm_params:");
2395
+ lines.push(` model: ${provider}/${model}`);
2396
+ lines.push(" api_key: os.environ/ANTHROPIC_COMPATIBLE_API_KEY");
2397
+ return lines.join("\n") + "\n";
2398
+ }
2399
+ async function startProxy(config, url) {
2400
+ try {
2401
+ execFileSync10("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
2402
+ } catch {
2403
+ try {
2404
+ execFileSync10("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
2405
+ } catch {
2406
+ logger.error("litellm not installed. Install: pip install 'litellm[proxy]'");
2407
+ return null;
2408
+ }
2409
+ }
2410
+ fs13.writeFileSync(CONFIG_PATH, config);
2411
+ const portMatch = url.match(/:(\d+)/);
2412
+ const port = portMatch ? portMatch[1] : "4099";
2413
+ const { spawn: spawn2 } = await import("child_process");
2414
+ const child = spawn2("litellm", ["--config", CONFIG_PATH, "--port", port], {
2415
+ stdio: ["ignore", "pipe", "pipe"],
2416
+ detached: true,
2417
+ env: process.env
2418
+ });
2419
+ for (let i = 0; i < 30; i++) {
2420
+ await delay(2e3);
2421
+ if (await checkLitellmHealth(url)) {
2422
+ logger.info(`LiteLLM proxy ready at ${url}`);
2423
+ return child;
2424
+ }
2425
+ }
2426
+ child.kill();
2427
+ return null;
2428
+ }
2429
+ async function quickApiTest(url, model, apiKey) {
2430
+ try {
2431
+ const res = await fetch(`${url}/v1/messages`, {
2432
+ method: "POST",
2433
+ headers: { "Content-Type": "application/json", "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
2434
+ body: JSON.stringify({
2435
+ model,
2436
+ max_tokens: 10,
2437
+ messages: [{ role: "user", content: "Say ok" }],
2438
+ context_management: { policy: "smart" }
2439
+ }),
2440
+ signal: AbortSignal.timeout(3e4)
2441
+ });
2442
+ if (!res.ok) {
2443
+ const body = await res.text();
2444
+ return { ok: false, error: body.slice(0, 200) };
2445
+ }
2446
+ return { ok: true };
2447
+ } catch (err) {
2448
+ return { ok: false, error: String(err) };
2449
+ }
2450
+ }
2451
+ function delay(ms) {
2452
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
2453
+ }
2454
+ async function runTestModelCommand() {
2455
+ const opts = parseTestModelArgs();
2456
+ const startTime = Date.now();
2457
+ logger.info(`Testing model compatibility: ${opts.provider}/${opts.model}`);
2458
+ logger.info("");
2459
+ let proxyProcess = null;
2460
+ let dropParamsRequired = false;
2461
+ const cleanup = () => {
2462
+ if (proxyProcess) {
2463
+ proxyProcess.kill();
2464
+ proxyProcess = null;
2465
+ }
2466
+ fs13.rmSync(CONFIG_PATH, { force: true });
2467
+ };
2468
+ process.on("SIGINT", () => {
2469
+ cleanup();
2470
+ process.exit(1);
2471
+ });
2472
+ process.on("SIGTERM", () => {
2473
+ cleanup();
2474
+ process.exit(1);
2475
+ });
2476
+ try {
2477
+ if (!opts.skipProxy) {
2478
+ process.env.ANTHROPIC_COMPATIBLE_API_KEY = opts.apiKey;
2479
+ logger.info("Starting LiteLLM proxy (without drop_params)...");
2480
+ proxyProcess = await startProxy(generateConfig(opts.provider, opts.model, false), opts.proxyUrl);
2481
+ if (!proxyProcess) {
2482
+ logger.error("Failed to start LiteLLM proxy");
2483
+ process.exit(1);
2484
+ }
2485
+ const quickRes = await quickApiTest(opts.proxyUrl, opts.model, opts.apiKey);
2486
+ if (!quickRes.ok) {
2487
+ logger.info("Model needs drop_params: true -- restarting proxy...");
2488
+ proxyProcess.kill();
2489
+ proxyProcess = null;
2490
+ await delay(2e3);
2491
+ proxyProcess = await startProxy(generateConfig(opts.provider, opts.model, true), opts.proxyUrl);
2492
+ dropParamsRequired = true;
2493
+ if (!proxyProcess) {
2494
+ logger.error("Failed to start LiteLLM proxy with drop_params");
2495
+ process.exit(1);
2496
+ }
2497
+ const retry = await quickApiTest(opts.proxyUrl, opts.model, opts.apiKey);
2498
+ if (!retry.ok) {
2499
+ logger.error(`Model not accessible: ${retry.error}`);
2500
+ process.exit(1);
2501
+ }
2502
+ logger.info("Proxy restarted with drop_params: true");
2503
+ } else {
2504
+ logger.info("drop_params not required");
2505
+ }
2506
+ } else {
2507
+ logger.info(`Using existing proxy at ${opts.proxyUrl}`);
2508
+ }
2509
+ const tests = opts.filter ? ALL_TESTS.filter((t) => opts.filter.includes(t.name)) : ALL_TESTS;
2510
+ logger.info(`Running ${tests.length} compatibility tests...`);
2511
+ logger.info("");
2512
+ const ctx = { proxyUrl: opts.proxyUrl, model: opts.model, apiKey: opts.apiKey, projectDir: process.cwd() };
2513
+ const results = [];
2514
+ for (const test of tests) {
2515
+ process.stdout.write(` ${test.name.padEnd(28)} `);
2516
+ try {
2517
+ const r = await test.run(ctx);
2518
+ results.push(r);
2519
+ const icon = r.status === "pass" ? "+" : r.status === "fail" ? "x" : "!";
2520
+ logger.info(`[${icon}] ${r.status.toUpperCase()} ${r.accuracy}% (${(r.durationMs / 1e3).toFixed(1)}s)`);
2521
+ } catch (err) {
2522
+ const r = {
2523
+ name: test.name,
2524
+ category: test.category,
2525
+ status: "fail",
2526
+ accuracy: 0,
2527
+ durationMs: 0,
2528
+ detail: `Crash: ${err instanceof Error ? err.message : String(err)}`
2529
+ };
2530
+ results.push(r);
2531
+ logger.info("[x] CRASH");
2532
+ }
2533
+ }
2534
+ const report = {
2535
+ provider: opts.provider,
2536
+ model: opts.model,
2537
+ results,
2538
+ totalDurationMs: Date.now() - startTime,
2539
+ dropParamsRequired,
2540
+ timestamp: (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
2541
+ };
2542
+ console.log("");
2543
+ console.log(formatReport(report));
2544
+ const failed = results.filter((r) => r.status === "fail").length;
2545
+ process.exit(failed > 0 ? 1 : 0);
2546
+ } finally {
2547
+ cleanup();
2548
+ }
2549
+ }
2550
+ var TEST_PORT, TEST_URL, CONFIG_PATH;
2551
+ var init_test_model_command = __esm({
2552
+ "src/cli/test-model-command.ts"() {
2553
+ "use strict";
2554
+ init_logger();
2555
+ init_litellm();
2556
+ init_test_model_tests();
2557
+ init_test_model_report();
2558
+ TEST_PORT = 4099;
2559
+ TEST_URL = `http://localhost:${TEST_PORT}`;
2560
+ CONFIG_PATH = path12.join(os3.tmpdir(), "kody-test-model-config.yaml");
2561
+ }
2562
+ });
2563
+
1571
2564
  // src/ci/parse-inputs.ts
1572
2565
  var parse_inputs_exports = {};
1573
2566
  __export(parse_inputs_exports, {
@@ -1575,16 +2568,16 @@ __export(parse_inputs_exports, {
1575
2568
  runCiParse: () => runCiParse,
1576
2569
  writeOutputs: () => writeOutputs
1577
2570
  });
1578
- import * as fs12 from "fs";
2571
+ import * as fs14 from "fs";
1579
2572
  function generateTimestamp() {
1580
2573
  const now = /* @__PURE__ */ new Date();
1581
- const pad = (n) => String(n).padStart(2, "0");
2574
+ const pad2 = (n) => String(n).padStart(2, "0");
1582
2575
  const y = String(now.getFullYear()).slice(2);
1583
- const m = pad(now.getMonth() + 1);
1584
- const d = pad(now.getDate());
1585
- const H = pad(now.getHours());
1586
- const M = pad(now.getMinutes());
1587
- const S = pad(now.getSeconds());
2576
+ const m = pad2(now.getMonth() + 1);
2577
+ const d = pad2(now.getDate());
2578
+ const H = pad2(now.getHours());
2579
+ const M = pad2(now.getMinutes());
2580
+ const S = pad2(now.getSeconds());
1588
2581
  return `${y}${m}${d}-${H}${M}${S}`;
1589
2582
  }
1590
2583
  function parseCommentInputs() {
@@ -1736,40 +2729,40 @@ function parseCommentInputs() {
1736
2729
  trigger_type: "comment"
1737
2730
  };
1738
2731
  }
1739
- function writeOutputs(result) {
2732
+ function writeOutputs(result2) {
1740
2733
  const outputFile = process.env.GITHUB_OUTPUT;
1741
2734
  function output(key, value) {
1742
2735
  if (outputFile) {
1743
2736
  if (value.includes("\n")) {
1744
- fs12.appendFileSync(outputFile, `${key}<<KODY_EOF
2737
+ fs14.appendFileSync(outputFile, `${key}<<KODY_EOF
1745
2738
  ${value}
1746
2739
  KODY_EOF
1747
2740
  `);
1748
2741
  } else {
1749
- fs12.appendFileSync(outputFile, `${key}=${value}
2742
+ fs14.appendFileSync(outputFile, `${key}=${value}
1750
2743
  `);
1751
2744
  }
1752
2745
  }
1753
2746
  const display = value.includes("\n") ? value.split("\n")[0] + "..." : value;
1754
2747
  console.log(`${key}=${display}`);
1755
2748
  }
1756
- output("task_id", result.task_id);
1757
- output("mode", result.mode);
1758
- output("from_stage", result.from_stage);
1759
- output("issue_number", result.issue_number);
1760
- output("pr_number", result.pr_number);
1761
- output("feedback", result.feedback);
1762
- output("complexity", result.complexity);
1763
- output("ci_run_id", result.ci_run_id);
1764
- output("ticket_id", result.ticket_id);
1765
- output("prd_file", result.prd_file);
1766
- output("dry_run", result.dry_run ? "true" : "false");
1767
- output("valid", result.valid ? "true" : "false");
1768
- output("trigger_type", result.trigger_type);
2749
+ output("task_id", result2.task_id);
2750
+ output("mode", result2.mode);
2751
+ output("from_stage", result2.from_stage);
2752
+ output("issue_number", result2.issue_number);
2753
+ output("pr_number", result2.pr_number);
2754
+ output("feedback", result2.feedback);
2755
+ output("complexity", result2.complexity);
2756
+ output("ci_run_id", result2.ci_run_id);
2757
+ output("ticket_id", result2.ticket_id);
2758
+ output("prd_file", result2.prd_file);
2759
+ output("dry_run", result2.dry_run ? "true" : "false");
2760
+ output("valid", result2.valid ? "true" : "false");
2761
+ output("trigger_type", result2.trigger_type);
1769
2762
  }
1770
2763
  function runCiParse() {
1771
- const result = parseCommentInputs();
1772
- writeOutputs(result);
2764
+ const result2 = parseCommentInputs();
2765
+ writeOutputs(result2);
1773
2766
  }
1774
2767
  var VALID_MODES;
1775
2768
  var init_parse_inputs = __esm({
@@ -1871,7 +2864,7 @@ var init_definitions = __esm({
1871
2864
  });
1872
2865
 
1873
2866
  // src/git-utils.ts
1874
- import { execFileSync as execFileSync10 } from "child_process";
2867
+ import { execFileSync as execFileSync11 } from "child_process";
1875
2868
  function getHookSafeEnv() {
1876
2869
  if (!_hookSafeEnv) {
1877
2870
  _hookSafeEnv = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
@@ -1879,7 +2872,7 @@ function getHookSafeEnv() {
1879
2872
  return _hookSafeEnv;
1880
2873
  }
1881
2874
  function git(args2, options) {
1882
- return execFileSync10("git", args2, {
2875
+ return execFileSync11("git", args2, {
1883
2876
  encoding: "utf-8",
1884
2877
  timeout: options?.timeout ?? 3e4,
1885
2878
  cwd: options?.cwd,
@@ -2065,22 +3058,22 @@ var init_git_utils = __esm({
2065
3058
  });
2066
3059
 
2067
3060
  // src/pipeline/state.ts
2068
- import * as fs13 from "fs";
2069
- import * as path11 from "path";
3061
+ import * as fs15 from "fs";
3062
+ import * as path13 from "path";
2070
3063
  function loadState(taskId, taskDir) {
2071
- const p = path11.join(taskDir, "status.json");
2072
- if (!fs13.existsSync(p)) return null;
3064
+ const p = path13.join(taskDir, "status.json");
3065
+ if (!fs15.existsSync(p)) return null;
2073
3066
  try {
2074
- const result = parseJsonSafe(
2075
- fs13.readFileSync(p, "utf-8"),
3067
+ const result2 = parseJsonSafe(
3068
+ fs15.readFileSync(p, "utf-8"),
2076
3069
  ["taskId", "state", "stages", "createdAt", "updatedAt"]
2077
3070
  );
2078
- if (!result.ok) {
2079
- logger.warn(` Corrupt status.json: ${result.error}`);
3071
+ if (!result2.ok) {
3072
+ logger.warn(` Corrupt status.json: ${result2.error}`);
2080
3073
  return null;
2081
3074
  }
2082
- if (result.data.taskId !== taskId) return null;
2083
- return result.data;
3075
+ if (result2.data.taskId !== taskId) return null;
3076
+ return result2.data;
2084
3077
  } catch {
2085
3078
  return null;
2086
3079
  }
@@ -2090,10 +3083,10 @@ function writeState(state, taskDir) {
2090
3083
  ...state,
2091
3084
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2092
3085
  };
2093
- const target = path11.join(taskDir, "status.json");
3086
+ const target = path13.join(taskDir, "status.json");
2094
3087
  const tmp = target + ".tmp";
2095
- fs13.writeFileSync(tmp, JSON.stringify(updated, null, 2));
2096
- fs13.renameSync(tmp, target);
3088
+ fs15.writeFileSync(tmp, JSON.stringify(updated, null, 2));
3089
+ fs15.renameSync(tmp, target);
2097
3090
  return updated;
2098
3091
  }
2099
3092
  function initState(taskId) {
@@ -2134,16 +3127,16 @@ var init_complexity = __esm({
2134
3127
  });
2135
3128
 
2136
3129
  // src/memory.ts
2137
- import * as fs14 from "fs";
2138
- import * as path12 from "path";
3130
+ import * as fs16 from "fs";
3131
+ import * as path14 from "path";
2139
3132
  function readProjectMemory(projectDir) {
2140
- const memoryDir = path12.join(projectDir, ".kody", "memory");
2141
- if (!fs14.existsSync(memoryDir)) return "";
2142
- const files = fs14.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
3133
+ const memoryDir = path14.join(projectDir, ".kody", "memory");
3134
+ if (!fs16.existsSync(memoryDir)) return "";
3135
+ const files = fs16.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
2143
3136
  if (files.length === 0) return "";
2144
3137
  const sections = [];
2145
3138
  for (const file of files) {
2146
- const content = fs14.readFileSync(path12.join(memoryDir, file), "utf-8").trim();
3139
+ const content = fs16.readFileSync(path14.join(memoryDir, file), "utf-8").trim();
2147
3140
  if (content) {
2148
3141
  sections.push(`## ${file.replace(".md", "")}
2149
3142
  ${content}`);
@@ -2162,8 +3155,8 @@ var init_memory = __esm({
2162
3155
  });
2163
3156
 
2164
3157
  // src/context-tiers.ts
2165
- import * as fs15 from "fs";
2166
- import * as path13 from "path";
3158
+ import * as fs17 from "fs";
3159
+ import * as path15 from "path";
2167
3160
  function estimateTokens(text) {
2168
3161
  return Math.ceil(text.length / 4);
2169
3162
  }
@@ -2190,8 +3183,8 @@ function generateL0(content, filename) {
2190
3183
  break;
2191
3184
  }
2192
3185
  }
2193
- const result = parts.join("\n");
2194
- return result.slice(0, L0_MAX_CHARS);
3186
+ const result2 = parts.join("\n");
3187
+ return result2.slice(0, L0_MAX_CHARS);
2195
3188
  }
2196
3189
  function generateL0Json(content) {
2197
3190
  try {
@@ -2233,8 +3226,8 @@ function generateL1(content, filename) {
2233
3226
  inSection = false;
2234
3227
  }
2235
3228
  }
2236
- const result = parts.join("\n");
2237
- return result.slice(0, L1_MAX_CHARS);
3229
+ const result2 = parts.join("\n");
3230
+ return result2.slice(0, L1_MAX_CHARS);
2238
3231
  }
2239
3232
  function generateL1Json(content) {
2240
3233
  try {
@@ -2254,7 +3247,7 @@ function generateL1Json(content) {
2254
3247
  }
2255
3248
  }
2256
3249
  function getTieredContent(filePath, content) {
2257
- const key = path13.basename(filePath);
3250
+ const key = path15.basename(filePath);
2258
3251
  return {
2259
3252
  source: filePath,
2260
3253
  L0: generateL0(content, key),
@@ -2266,15 +3259,15 @@ function selectTier(tiered, tier) {
2266
3259
  return tiered[tier];
2267
3260
  }
2268
3261
  function readProjectMemoryTiered(projectDir, tier) {
2269
- const memoryDir = path13.join(projectDir, ".kody", "memory");
2270
- if (!fs15.existsSync(memoryDir)) return "";
2271
- const files = fs15.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
3262
+ const memoryDir = path15.join(projectDir, ".kody", "memory");
3263
+ if (!fs17.existsSync(memoryDir)) return "";
3264
+ const files = fs17.readdirSync(memoryDir).filter((f) => f.endsWith(".md")).sort();
2272
3265
  if (files.length === 0) return "";
2273
3266
  const tierLabel2 = tier === "L2" ? "full" : tier === "L1" ? "overview" : "abstract";
2274
3267
  const sections = [];
2275
3268
  for (const file of files) {
2276
- const filePath = path13.join(memoryDir, file);
2277
- const content = fs15.readFileSync(filePath, "utf-8").trim();
3269
+ const filePath = path15.join(memoryDir, file);
3270
+ const content = fs17.readFileSync(filePath, "utf-8").trim();
2278
3271
  if (!content) continue;
2279
3272
  const tiered = getTieredContent(filePath, content);
2280
3273
  const selected = selectTier(tiered, tier);
@@ -2297,9 +3290,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
2297
3290
  `;
2298
3291
  context += `Task Directory: ${taskDir}
2299
3292
  `;
2300
- const taskMdPath = path13.join(taskDir, "task.md");
2301
- if (fs15.existsSync(taskMdPath)) {
2302
- const content = fs15.readFileSync(taskMdPath, "utf-8");
3293
+ const taskMdPath = path15.join(taskDir, "task.md");
3294
+ if (fs17.existsSync(taskMdPath)) {
3295
+ const content = fs17.readFileSync(taskMdPath, "utf-8");
2303
3296
  const selected = selectContent(taskMdPath, content, policy.taskDescription);
2304
3297
  const label = tierLabel("Task Description", policy.taskDescription);
2305
3298
  context += `
@@ -2307,9 +3300,9 @@ function injectTaskContextTiered(prompt, taskId, taskDir, policy, feedback) {
2307
3300
  ${selected}
2308
3301
  `;
2309
3302
  }
2310
- const taskJsonPath = path13.join(taskDir, "task.json");
2311
- if (fs15.existsSync(taskJsonPath)) {
2312
- const content = fs15.readFileSync(taskJsonPath, "utf-8");
3303
+ const taskJsonPath = path15.join(taskDir, "task.json");
3304
+ if (fs17.existsSync(taskJsonPath)) {
3305
+ const content = fs17.readFileSync(taskJsonPath, "utf-8");
2313
3306
  if (policy.taskClassification === "L2") {
2314
3307
  try {
2315
3308
  const taskDef = JSON.parse(content.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, ""));
@@ -2335,9 +3328,9 @@ ${selected}
2335
3328
  }
2336
3329
  }
2337
3330
  }
2338
- const specPath = path13.join(taskDir, "spec.md");
2339
- if (fs15.existsSync(specPath)) {
2340
- const content = fs15.readFileSync(specPath, "utf-8");
3331
+ const specPath = path15.join(taskDir, "spec.md");
3332
+ if (fs17.existsSync(specPath)) {
3333
+ const content = fs17.readFileSync(specPath, "utf-8");
2341
3334
  const selected = selectContent(specPath, content, policy.spec);
2342
3335
  const label = tierLabel("Spec", policy.spec);
2343
3336
  context += `
@@ -2345,9 +3338,9 @@ ${selected}
2345
3338
  ${selected}
2346
3339
  `;
2347
3340
  }
2348
- const planPath = path13.join(taskDir, "plan.md");
2349
- if (fs15.existsSync(planPath)) {
2350
- const content = fs15.readFileSync(planPath, "utf-8");
3341
+ const planPath = path15.join(taskDir, "plan.md");
3342
+ if (fs17.existsSync(planPath)) {
3343
+ const content = fs17.readFileSync(planPath, "utf-8");
2351
3344
  const selected = selectContent(planPath, content, policy.plan);
2352
3345
  const label = tierLabel("Plan", policy.plan);
2353
3346
  context += `
@@ -2355,9 +3348,9 @@ ${selected}
2355
3348
  ${selected}
2356
3349
  `;
2357
3350
  }
2358
- const contextMdPath = path13.join(taskDir, "context.md");
2359
- if (fs15.existsSync(contextMdPath)) {
2360
- const content = fs15.readFileSync(contextMdPath, "utf-8");
3351
+ const contextMdPath = path15.join(taskDir, "context.md");
3352
+ if (fs17.existsSync(contextMdPath)) {
3353
+ const content = fs17.readFileSync(contextMdPath, "utf-8");
2361
3354
  const selected = selectContent(contextMdPath, content, policy.accumulatedContext);
2362
3355
  const label = tierLabel("Previous Stage Context", policy.accumulatedContext);
2363
3356
  context += `
@@ -2443,24 +3436,24 @@ var init_context_tiers = __esm({
2443
3436
  });
2444
3437
 
2445
3438
  // src/context.ts
2446
- import * as fs16 from "fs";
2447
- import * as path14 from "path";
3439
+ import * as fs18 from "fs";
3440
+ import * as path16 from "path";
2448
3441
  function readPromptFile(stageName, projectDir) {
2449
3442
  if (projectDir) {
2450
- const stepFile = path14.join(projectDir, ".kody", "steps", `${stageName}.md`);
2451
- if (fs16.existsSync(stepFile)) {
2452
- return fs16.readFileSync(stepFile, "utf-8");
3443
+ const stepFile = path16.join(projectDir, ".kody", "steps", `${stageName}.md`);
3444
+ if (fs18.existsSync(stepFile)) {
3445
+ return fs18.readFileSync(stepFile, "utf-8");
2453
3446
  }
2454
3447
  console.warn(` \u26A0 No step file at ${stepFile}, falling back to engine defaults. Run 'kody-engine-lite init --force' to generate step files.`);
2455
3448
  }
2456
3449
  const scriptDir = new URL(".", import.meta.url).pathname;
2457
3450
  const candidates = [
2458
- path14.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
2459
- path14.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
3451
+ path16.resolve(scriptDir, "..", "prompts", `${stageName}.md`),
3452
+ path16.resolve(scriptDir, "..", "..", "prompts", `${stageName}.md`)
2460
3453
  ];
2461
3454
  for (const candidate of candidates) {
2462
- if (fs16.existsSync(candidate)) {
2463
- return fs16.readFileSync(candidate, "utf-8");
3455
+ if (fs18.existsSync(candidate)) {
3456
+ return fs18.readFileSync(candidate, "utf-8");
2464
3457
  }
2465
3458
  }
2466
3459
  throw new Error(`Prompt file not found: tried ${candidates.join(", ")}`);
@@ -2472,18 +3465,18 @@ function injectTaskContext(prompt, taskId, taskDir, feedback) {
2472
3465
  `;
2473
3466
  context += `Task Directory: ${taskDir}
2474
3467
  `;
2475
- const taskMdPath = path14.join(taskDir, "task.md");
2476
- if (fs16.existsSync(taskMdPath)) {
2477
- const taskMd = fs16.readFileSync(taskMdPath, "utf-8");
3468
+ const taskMdPath = path16.join(taskDir, "task.md");
3469
+ if (fs18.existsSync(taskMdPath)) {
3470
+ const taskMd = fs18.readFileSync(taskMdPath, "utf-8");
2478
3471
  context += `
2479
3472
  ## Task Description
2480
3473
  ${taskMd}
2481
3474
  `;
2482
3475
  }
2483
- const taskJsonPath = path14.join(taskDir, "task.json");
2484
- if (fs16.existsSync(taskJsonPath)) {
3476
+ const taskJsonPath = path16.join(taskDir, "task.json");
3477
+ if (fs18.existsSync(taskJsonPath)) {
2485
3478
  try {
2486
- const taskDef = JSON.parse(fs16.readFileSync(taskJsonPath, "utf-8"));
3479
+ const taskDef = JSON.parse(fs18.readFileSync(taskJsonPath, "utf-8"));
2487
3480
  context += `
2488
3481
  ## Task Classification
2489
3482
  `;
@@ -2496,27 +3489,27 @@ ${taskMd}
2496
3489
  } catch {
2497
3490
  }
2498
3491
  }
2499
- const specPath = path14.join(taskDir, "spec.md");
2500
- if (fs16.existsSync(specPath)) {
2501
- const spec = fs16.readFileSync(specPath, "utf-8");
3492
+ const specPath = path16.join(taskDir, "spec.md");
3493
+ if (fs18.existsSync(specPath)) {
3494
+ const spec = fs18.readFileSync(specPath, "utf-8");
2502
3495
  const truncated = spec.slice(0, MAX_TASK_CONTEXT_SPEC);
2503
3496
  context += `
2504
3497
  ## Spec Summary
2505
3498
  ${truncated}${spec.length > MAX_TASK_CONTEXT_SPEC ? "\n..." : ""}
2506
3499
  `;
2507
3500
  }
2508
- const planPath = path14.join(taskDir, "plan.md");
2509
- if (fs16.existsSync(planPath)) {
2510
- const plan = fs16.readFileSync(planPath, "utf-8");
3501
+ const planPath = path16.join(taskDir, "plan.md");
3502
+ if (fs18.existsSync(planPath)) {
3503
+ const plan = fs18.readFileSync(planPath, "utf-8");
2511
3504
  const truncated = plan.slice(0, MAX_TASK_CONTEXT_PLAN);
2512
3505
  context += `
2513
3506
  ## Plan Summary
2514
3507
  ${truncated}${plan.length > MAX_TASK_CONTEXT_PLAN ? "\n..." : ""}
2515
3508
  `;
2516
3509
  }
2517
- const contextMdPath = path14.join(taskDir, "context.md");
2518
- if (fs16.existsSync(contextMdPath)) {
2519
- const accumulated = fs16.readFileSync(contextMdPath, "utf-8");
3510
+ const contextMdPath = path16.join(taskDir, "context.md");
3511
+ if (fs18.existsSync(contextMdPath)) {
3512
+ const accumulated = fs18.readFileSync(contextMdPath, "utf-8");
2520
3513
  const truncated = accumulated.slice(-MAX_ACCUMULATED_CONTEXT);
2521
3514
  const prefix = accumulated.length > MAX_ACCUMULATED_CONTEXT ? "...(earlier context truncated)\n" : "";
2522
3515
  context += `
@@ -2534,17 +3527,17 @@ ${feedback}
2534
3527
  }
2535
3528
  function inferHasUIFromScope(scope) {
2536
3529
  return scope.some((filePath) => {
2537
- const ext = path14.extname(filePath).toLowerCase();
3530
+ const ext = path16.extname(filePath).toLowerCase();
2538
3531
  if (UI_EXTENSIONS.has(ext)) return true;
2539
3532
  const normalized = filePath.replace(/\\/g, "/");
2540
3533
  return UI_PATH_SEGMENTS.some((seg) => normalized.includes(seg));
2541
3534
  });
2542
3535
  }
2543
3536
  function taskHasUI(taskDir) {
2544
- const taskJsonPath = path14.join(taskDir, "task.json");
2545
- if (!fs16.existsSync(taskJsonPath)) return true;
3537
+ const taskJsonPath = path16.join(taskDir, "task.json");
3538
+ if (!fs18.existsSync(taskJsonPath)) return true;
2546
3539
  try {
2547
- const taskDef = JSON.parse(fs16.readFileSync(taskJsonPath, "utf-8"));
3540
+ const taskDef = JSON.parse(fs18.readFileSync(taskJsonPath, "utf-8"));
2548
3541
  const scope = Array.isArray(taskDef.scope) ? taskDef.scope : [];
2549
3542
  if (scope.length === 0) return true;
2550
3543
  return inferHasUIFromScope(scope);
@@ -2666,9 +3659,9 @@ ${prompt}` : prompt;
2666
3659
  }
2667
3660
  if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
2668
3661
  assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
2669
- const qaGuidePath = path14.join(projectDir, ".kody", "qa-guide.md");
2670
- if (fs16.existsSync(qaGuidePath)) {
2671
- const qaGuide = fs16.readFileSync(qaGuidePath, "utf-8").trim();
3662
+ const qaGuidePath = path16.join(projectDir, ".kody", "qa-guide.md");
3663
+ if (fs18.existsSync(qaGuidePath)) {
3664
+ const qaGuide = fs18.readFileSync(qaGuidePath, "utf-8").trim();
2672
3665
  assembled = assembled + "\n\n" + qaGuide;
2673
3666
  }
2674
3667
  }
@@ -2760,8 +3753,8 @@ var init_runner_selection = __esm({
2760
3753
  });
2761
3754
 
2762
3755
  // src/stages/agent.ts
2763
- import * as fs17 from "fs";
2764
- import * as path15 from "path";
3756
+ import * as fs19 from "fs";
3757
+ import * as path17 from "path";
2765
3758
  function getSessionInfo(stageName, sessions) {
2766
3759
  const group = SESSION_GROUP[stageName];
2767
3760
  if (!group) return void 0;
@@ -2846,29 +3839,29 @@ async function executeAgentStage(ctx, def) {
2846
3839
  if (lastResult.outcome !== "completed") {
2847
3840
  return { outcome: lastResult.outcome, error: lastResult.error, retries };
2848
3841
  }
2849
- const result = lastResult;
2850
- if (def.outputFile && result.output) {
2851
- fs17.writeFileSync(path15.join(ctx.taskDir, def.outputFile), result.output);
3842
+ const result2 = lastResult;
3843
+ if (def.outputFile && result2.output) {
3844
+ fs19.writeFileSync(path17.join(ctx.taskDir, def.outputFile), result2.output);
2852
3845
  }
2853
3846
  if (def.outputFile) {
2854
- const outputPath = path15.join(ctx.taskDir, def.outputFile);
2855
- if (!fs17.existsSync(outputPath)) {
2856
- const ext = path15.extname(def.outputFile);
2857
- const base = path15.basename(def.outputFile, ext);
2858
- const files = fs17.readdirSync(ctx.taskDir);
3847
+ const outputPath = path17.join(ctx.taskDir, def.outputFile);
3848
+ if (!fs19.existsSync(outputPath)) {
3849
+ const ext = path17.extname(def.outputFile);
3850
+ const base = path17.basename(def.outputFile, ext);
3851
+ const files = fs19.readdirSync(ctx.taskDir);
2859
3852
  const variant = files.find(
2860
3853
  (f) => f.startsWith(base + "-") && f.endsWith(ext)
2861
3854
  );
2862
3855
  if (variant) {
2863
- fs17.renameSync(path15.join(ctx.taskDir, variant), outputPath);
3856
+ fs19.renameSync(path17.join(ctx.taskDir, variant), outputPath);
2864
3857
  logger.info(` Renamed variant ${variant} \u2192 ${def.outputFile}`);
2865
3858
  }
2866
3859
  }
2867
3860
  }
2868
3861
  if (def.outputFile) {
2869
- const outputPath = path15.join(ctx.taskDir, def.outputFile);
2870
- if (fs17.existsSync(outputPath)) {
2871
- const content = fs17.readFileSync(outputPath, "utf-8");
3862
+ const outputPath = path17.join(ctx.taskDir, def.outputFile);
3863
+ if (fs19.existsSync(outputPath)) {
3864
+ const content = fs19.readFileSync(outputPath, "utf-8");
2872
3865
  const validation = validateStageOutput(def.name, content);
2873
3866
  if (!validation.valid) {
2874
3867
  if (def.name === "taskify") {
@@ -2882,7 +3875,7 @@ async function executeAgentStage(ctx, def) {
2882
3875
  const stripped = stripFences(retryResult.output);
2883
3876
  const retryValidation = validateTaskJson(stripped);
2884
3877
  if (retryValidation.valid) {
2885
- fs17.writeFileSync(outputPath, retryResult.output);
3878
+ fs19.writeFileSync(outputPath, retryResult.output);
2886
3879
  logger.info(` taskify retry produced valid JSON`);
2887
3880
  } else {
2888
3881
  logger.warn(` taskify retry still invalid: ${retryValidation.error}`);
@@ -2895,7 +3888,7 @@ async function executeAgentStage(ctx, def) {
2895
3888
  risk_level: "low",
2896
3889
  questions: []
2897
3890
  }, null, 2);
2898
- fs17.writeFileSync(outputPath, fallback);
3891
+ fs19.writeFileSync(outputPath, fallback);
2899
3892
  logger.info(` taskify fallback: generated minimal task.json (risk_level=low)`);
2900
3893
  }
2901
3894
  }
@@ -2905,11 +3898,11 @@ async function executeAgentStage(ctx, def) {
2905
3898
  }
2906
3899
  }
2907
3900
  }
2908
- appendStageContext(ctx.taskDir, def.name, result.output);
3901
+ appendStageContext(ctx.taskDir, def.name, result2.output);
2909
3902
  return { outcome: "completed", outputFile: def.outputFile, retries };
2910
3903
  }
2911
3904
  function appendStageContext(taskDir, stageName, output) {
2912
- const contextPath = path15.join(taskDir, "context.md");
3905
+ const contextPath = path17.join(taskDir, "context.md");
2913
3906
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19);
2914
3907
  let summary;
2915
3908
  if (output && output.trim()) {
@@ -2922,7 +3915,7 @@ function appendStageContext(taskDir, stageName, output) {
2922
3915
  ### ${stageName} (${timestamp2})
2923
3916
  ${summary}
2924
3917
  `;
2925
- fs17.appendFileSync(contextPath, entry);
3918
+ fs19.appendFileSync(contextPath, entry);
2926
3919
  }
2927
3920
  var SESSION_GROUP;
2928
3921
  var init_agent = __esm({
@@ -2945,7 +3938,7 @@ var init_agent = __esm({
2945
3938
  });
2946
3939
 
2947
3940
  // src/verify-runner.ts
2948
- import { execFileSync as execFileSync11 } from "child_process";
3941
+ import { execFileSync as execFileSync12 } from "child_process";
2949
3942
  function isExecError(err) {
2950
3943
  return typeof err === "object" && err !== null;
2951
3944
  }
@@ -2981,7 +3974,7 @@ function runCommand(cmd, cwd, timeout) {
2981
3974
  return { success: true, output: "", timedOut: false };
2982
3975
  }
2983
3976
  try {
2984
- const output = execFileSync11(parts[0], parts.slice(1), {
3977
+ const output = execFileSync12(parts[0], parts.slice(1), {
2985
3978
  cwd,
2986
3979
  timeout,
2987
3980
  encoding: "utf-8",
@@ -3027,19 +4020,19 @@ function runQualityGates(taskDir, projectRoot) {
3027
4020
  for (const { name, cmd } of commands) {
3028
4021
  if (!cmd) continue;
3029
4022
  logger.info(` Running ${name}: ${cmd}`);
3030
- const result = runCommand(cmd, cwd, VERIFY_COMMAND_TIMEOUT_MS);
3031
- if (result.timedOut) {
4023
+ const result2 = runCommand(cmd, cwd, VERIFY_COMMAND_TIMEOUT_MS);
4024
+ if (result2.timedOut) {
3032
4025
  allErrors.push(`${name}: timed out after ${VERIFY_COMMAND_TIMEOUT_MS / 1e3}s`);
3033
4026
  allPass = false;
3034
4027
  continue;
3035
4028
  }
3036
- if (!result.success) {
4029
+ if (!result2.success) {
3037
4030
  allPass = false;
3038
- const errors = parseErrors(result.output);
4031
+ const errors = parseErrors(result2.output);
3039
4032
  allErrors.push(...errors.map((e) => `[${name}] ${e}`));
3040
- rawOutputs.push({ name, output: result.output.slice(-3e3) });
4033
+ rawOutputs.push({ name, output: result2.output.slice(-3e3) });
3041
4034
  }
3042
- allSummary.push(...extractSummary(result.output, name));
4035
+ allSummary.push(...extractSummary(result2.output, name));
3043
4036
  }
3044
4037
  return { pass: allPass, errors: allErrors, summary: allSummary, rawOutputs };
3045
4038
  }
@@ -3052,7 +4045,7 @@ var init_verify_runner = __esm({
3052
4045
  });
3053
4046
 
3054
4047
  // src/observer.ts
3055
- import { execFileSync as execFileSync12 } from "child_process";
4048
+ import { execFileSync as execFileSync13 } from "child_process";
3056
4049
  async function diagnoseFailure(stageName, errorOutput, modifiedFiles, runner, model, options) {
3057
4050
  const context = [
3058
4051
  `Stage: ${stageName}`,
@@ -3066,7 +4059,7 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
3066
4059
  ].join("\n");
3067
4060
  const prompt = DIAGNOSIS_PROMPT + context;
3068
4061
  try {
3069
- const result = await runner.run(
4062
+ const result2 = await runner.run(
3070
4063
  "diagnosis",
3071
4064
  prompt,
3072
4065
  model,
@@ -3075,8 +4068,8 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
3075
4068
  "",
3076
4069
  options
3077
4070
  );
3078
- if (result.outcome === "completed" && result.output) {
3079
- const cleaned = result.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
4071
+ if (result2.outcome === "completed" && result2.output) {
4072
+ const cleaned = result2.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
3080
4073
  const parseResult = parseJsonSafe(cleaned, ["classification"]);
3081
4074
  if (parseResult.ok) {
3082
4075
  const { data } = parseResult;
@@ -3112,13 +4105,13 @@ ${modifiedFiles.map((f) => `- ${f}`).join("\n")}` : "No files were modified (bui
3112
4105
  }
3113
4106
  function getModifiedFiles(projectDir) {
3114
4107
  try {
3115
- const staged = execFileSync12("git", ["diff", "--name-only", "--cached"], {
4108
+ const staged = execFileSync13("git", ["diff", "--name-only", "--cached"], {
3116
4109
  encoding: "utf-8",
3117
4110
  cwd: projectDir,
3118
4111
  timeout: 5e3,
3119
4112
  stdio: ["pipe", "pipe", "pipe"]
3120
4113
  }).trim();
3121
- const unstaged = execFileSync12("git", ["diff", "--name-only"], {
4114
+ const unstaged = execFileSync13("git", ["diff", "--name-only"], {
3122
4115
  encoding: "utf-8",
3123
4116
  cwd: projectDir,
3124
4117
  timeout: 5e3,
@@ -3161,8 +4154,8 @@ Error context:
3161
4154
  });
3162
4155
 
3163
4156
  // src/stages/gate.ts
3164
- import * as fs18 from "fs";
3165
- import * as path16 from "path";
4157
+ import * as fs20 from "fs";
4158
+ import * as path18 from "path";
3166
4159
  function executeGateStage(ctx, def) {
3167
4160
  if (ctx.input.dryRun) {
3168
4161
  logger.info(` [dry-run] skipping ${def.name}`);
@@ -3205,7 +4198,7 @@ ${output}
3205
4198
  `);
3206
4199
  }
3207
4200
  }
3208
- fs18.writeFileSync(path16.join(ctx.taskDir, "verify.md"), lines.join(""));
4201
+ fs20.writeFileSync(path18.join(ctx.taskDir, "verify.md"), lines.join(""));
3209
4202
  return {
3210
4203
  outcome: verifyResult.pass ? "completed" : "failed",
3211
4204
  retries: 0
@@ -3220,9 +4213,9 @@ var init_gate = __esm({
3220
4213
  });
3221
4214
 
3222
4215
  // src/stages/verify.ts
3223
- import * as fs19 from "fs";
3224
- import * as path17 from "path";
3225
- import { execFileSync as execFileSync13 } from "child_process";
4216
+ import * as fs21 from "fs";
4217
+ import * as path19 from "path";
4218
+ import { execFileSync as execFileSync14 } from "child_process";
3226
4219
  async function executeVerifyWithAutofix(ctx, def) {
3227
4220
  const maxAttempts = def.maxRetries ?? 2;
3228
4221
  for (let attempt = 0; attempt <= maxAttempts; attempt++) {
@@ -3232,8 +4225,8 @@ async function executeVerifyWithAutofix(ctx, def) {
3232
4225
  return { ...gateResult, retries: attempt };
3233
4226
  }
3234
4227
  if (attempt < maxAttempts) {
3235
- const verifyPath = path17.join(ctx.taskDir, "verify.md");
3236
- const errorOutput = fs19.existsSync(verifyPath) ? fs19.readFileSync(verifyPath, "utf-8") : "Unknown error";
4228
+ const verifyPath = path19.join(ctx.taskDir, "verify.md");
4229
+ const errorOutput = fs21.existsSync(verifyPath) ? fs21.readFileSync(verifyPath, "utf-8") : "Unknown error";
3237
4230
  const modifiedFiles = getModifiedFiles(ctx.projectDir);
3238
4231
  const defaultRunner = getRunnerForStage(ctx, "taskify");
3239
4232
  const diagConfig = getProjectConfig();
@@ -3276,7 +4269,7 @@ ${diagnosis.resolution}`);
3276
4269
  const parts = parseCommand(cmd);
3277
4270
  if (parts.length === 0) return;
3278
4271
  try {
3279
- execFileSync13(parts[0], parts.slice(1), {
4272
+ execFileSync14(parts[0], parts.slice(1), {
3280
4273
  stdio: "pipe",
3281
4274
  timeout: FIX_COMMAND_TIMEOUT_MS
3282
4275
  });
@@ -3329,8 +4322,8 @@ var init_verify = __esm({
3329
4322
  });
3330
4323
 
3331
4324
  // src/review-standalone.ts
3332
- import * as fs20 from "fs";
3333
- import * as path18 from "path";
4325
+ import * as fs22 from "fs";
4326
+ import * as path20 from "path";
3334
4327
  function resolveReviewTarget(input) {
3335
4328
  if (input.prs.length === 0) {
3336
4329
  return {
@@ -3354,8 +4347,8 @@ Or comment on the specific PR: \`@kody review\``
3354
4347
  }
3355
4348
  async function runStandaloneReview(input) {
3356
4349
  const taskId = input.taskId ?? `review-${generateTaskId()}`;
3357
- const taskDir = path18.join(input.projectDir, ".kody", "tasks", taskId);
3358
- fs20.mkdirSync(taskDir, { recursive: true });
4350
+ const taskDir = path20.join(input.projectDir, ".kody", "tasks", taskId);
4351
+ fs22.mkdirSync(taskDir, { recursive: true });
3359
4352
  let diffInstruction = "";
3360
4353
  let filesChangedSection = "";
3361
4354
  if (input.baseBranch) {
@@ -3382,7 +4375,7 @@ ${fileList}`;
3382
4375
  const taskContent = `# ${input.prTitle}
3383
4376
 
3384
4377
  ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
3385
- fs20.writeFileSync(path18.join(taskDir, "task.md"), taskContent);
4378
+ fs22.writeFileSync(path20.join(taskDir, "task.md"), taskContent);
3386
4379
  const reviewDef = STAGES.find((s) => s.name === "review");
3387
4380
  const ctx = {
3388
4381
  taskId,
@@ -3396,18 +4389,18 @@ ${input.prBody ?? ""}${diffInstruction}${filesChangedSection}`;
3396
4389
  }
3397
4390
  };
3398
4391
  logger.info(`[review] standalone review for: ${input.prTitle}`);
3399
- const result = await executeAgentStage(ctx, reviewDef);
3400
- if (result.outcome !== "completed") {
4392
+ const result2 = await executeAgentStage(ctx, reviewDef);
4393
+ if (result2.outcome !== "completed") {
3401
4394
  return {
3402
4395
  outcome: "failed",
3403
4396
  taskDir,
3404
- error: result.error ?? "Review stage failed"
4397
+ error: result2.error ?? "Review stage failed"
3405
4398
  };
3406
4399
  }
3407
- const reviewPath = path18.join(taskDir, "review.md");
4400
+ const reviewPath = path20.join(taskDir, "review.md");
3408
4401
  let reviewContent;
3409
- if (fs20.existsSync(reviewPath)) {
3410
- reviewContent = fs20.readFileSync(reviewPath, "utf-8");
4402
+ if (fs22.existsSync(reviewPath)) {
4403
+ reviewContent = fs22.readFileSync(reviewPath, "utf-8");
3411
4404
  }
3412
4405
  return {
3413
4406
  outcome: "completed",
@@ -3447,8 +4440,8 @@ var init_review_standalone = __esm({
3447
4440
  });
3448
4441
 
3449
4442
  // src/stages/review.ts
3450
- import * as fs21 from "fs";
3451
- import * as path19 from "path";
4443
+ import * as fs23 from "fs";
4444
+ import * as path21 from "path";
3452
4445
  async function executeReviewWithFix(ctx, def) {
3453
4446
  if (ctx.input.dryRun) {
3454
4447
  return { outcome: "completed", retries: 0 };
@@ -3462,11 +4455,11 @@ async function executeReviewWithFix(ctx, def) {
3462
4455
  if (reviewResult.outcome !== "completed") {
3463
4456
  return reviewResult;
3464
4457
  }
3465
- const reviewFile = path19.join(ctx.taskDir, "review.md");
3466
- if (!fs21.existsSync(reviewFile)) {
4458
+ const reviewFile = path21.join(ctx.taskDir, "review.md");
4459
+ if (!fs23.existsSync(reviewFile)) {
3467
4460
  return { outcome: "failed", retries: iteration, error: "review.md not found" };
3468
4461
  }
3469
- const content = fs21.readFileSync(reviewFile, "utf-8");
4462
+ const content = fs23.readFileSync(reviewFile, "utf-8");
3470
4463
  if (detectReviewVerdict(content) !== "fail") {
3471
4464
  return { ...reviewResult, retries: iteration };
3472
4465
  }
@@ -3495,15 +4488,15 @@ var init_review = __esm({
3495
4488
  });
3496
4489
 
3497
4490
  // src/stages/ship.ts
3498
- import * as fs22 from "fs";
3499
- import * as path20 from "path";
3500
- import { execFileSync as execFileSync14 } from "child_process";
4491
+ import * as fs24 from "fs";
4492
+ import * as path22 from "path";
4493
+ import { execFileSync as execFileSync15 } from "child_process";
3501
4494
  function buildPrBody(ctx) {
3502
4495
  const sections = [];
3503
- const taskJsonPath = path20.join(ctx.taskDir, "task.json");
3504
- if (fs22.existsSync(taskJsonPath)) {
4496
+ const taskJsonPath = path22.join(ctx.taskDir, "task.json");
4497
+ if (fs24.existsSync(taskJsonPath)) {
3505
4498
  try {
3506
- const raw = fs22.readFileSync(taskJsonPath, "utf-8");
4499
+ const raw = fs24.readFileSync(taskJsonPath, "utf-8");
3507
4500
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3508
4501
  const task = JSON.parse(cleaned);
3509
4502
  if (task.description) {
@@ -3522,9 +4515,9 @@ ${task.scope.map((s) => `- \`${s}\``).join("\n")}`);
3522
4515
  } catch {
3523
4516
  }
3524
4517
  }
3525
- const reviewPath = path20.join(ctx.taskDir, "review.md");
3526
- if (fs22.existsSync(reviewPath)) {
3527
- const review = fs22.readFileSync(reviewPath, "utf-8");
4518
+ const reviewPath = path22.join(ctx.taskDir, "review.md");
4519
+ if (fs24.existsSync(reviewPath)) {
4520
+ const review = fs24.readFileSync(reviewPath, "utf-8");
3528
4521
  const summaryMatch = review.match(/## Summary\s*\n([\s\S]*?)(?=\n## |\n*$)/);
3529
4522
  if (summaryMatch) {
3530
4523
  const summary = summaryMatch[1].trim();
@@ -3541,14 +4534,14 @@ ${summary}`);
3541
4534
  **Review:** ${verdictMatch[1].toUpperCase() === "PASS" ? "\u2705 PASS" : "\u274C FAIL"}`);
3542
4535
  }
3543
4536
  }
3544
- const verifyPath = path20.join(ctx.taskDir, "verify.md");
3545
- if (fs22.existsSync(verifyPath)) {
3546
- const verify = fs22.readFileSync(verifyPath, "utf-8");
4537
+ const verifyPath = path22.join(ctx.taskDir, "verify.md");
4538
+ if (fs24.existsSync(verifyPath)) {
4539
+ const verify = fs24.readFileSync(verifyPath, "utf-8");
3547
4540
  if (/PASS/i.test(verify)) sections.push(`**Verify:** \u2705 typecheck + tests + lint passed`);
3548
4541
  }
3549
- const planPath = path20.join(ctx.taskDir, "plan.md");
3550
- if (fs22.existsSync(planPath)) {
3551
- const plan = fs22.readFileSync(planPath, "utf-8").trim();
4542
+ const planPath = path22.join(ctx.taskDir, "plan.md");
4543
+ if (fs24.existsSync(planPath)) {
4544
+ const plan = fs24.readFileSync(planPath, "utf-8").trim();
3552
4545
  if (plan) {
3553
4546
  const truncated = plan.length > 800 ? plan.slice(0, 800) + "\n..." : plan;
3554
4547
  sections.push(`
@@ -3568,25 +4561,25 @@ Closes #${ctx.input.issueNumber}`);
3568
4561
  return sections.join("\n");
3569
4562
  }
3570
4563
  function executeShipStage(ctx, _def) {
3571
- const shipPath = path20.join(ctx.taskDir, "ship.md");
4564
+ const shipPath = path22.join(ctx.taskDir, "ship.md");
3572
4565
  if (ctx.input.dryRun) {
3573
- fs22.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
4566
+ fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 dry run.\n");
3574
4567
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
3575
4568
  }
3576
4569
  if (ctx.input.local && !ctx.input.issueNumber) {
3577
- fs22.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
4570
+ fs24.writeFileSync(shipPath, "# Ship\n\nShip stage skipped \u2014 local mode, no issue number.\n");
3578
4571
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
3579
4572
  }
3580
4573
  try {
3581
4574
  const head = getCurrentBranch(ctx.projectDir);
3582
4575
  const base = getDefaultBranch(ctx.projectDir);
3583
4576
  try {
3584
- execFileSync14("git", ["add", ctx.taskDir], {
4577
+ execFileSync15("git", ["add", ctx.taskDir], {
3585
4578
  cwd: ctx.projectDir,
3586
4579
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
3587
4580
  stdio: "pipe"
3588
4581
  });
3589
- execFileSync14("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
4582
+ execFileSync15("git", ["commit", "--no-gpg-sign", "-m", `chore: add kody task artifacts [skip ci]`], {
3590
4583
  cwd: ctx.projectDir,
3591
4584
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
3592
4585
  stdio: "pipe"
@@ -3600,7 +4593,7 @@ function executeShipStage(ctx, _def) {
3600
4593
  let repo = config.github?.repo;
3601
4594
  if (!owner || !repo) {
3602
4595
  try {
3603
- const remoteUrl = execFileSync14("git", ["remote", "get-url", "origin"], {
4596
+ const remoteUrl = execFileSync15("git", ["remote", "get-url", "origin"], {
3604
4597
  encoding: "utf-8",
3605
4598
  cwd: ctx.projectDir
3606
4599
  }).trim();
@@ -3621,28 +4614,28 @@ function executeShipStage(ctx, _def) {
3621
4614
  chore: "chore"
3622
4615
  };
3623
4616
  let prefix = "chore";
3624
- const taskJsonPath = path20.join(ctx.taskDir, "task.json");
3625
- if (fs22.existsSync(taskJsonPath)) {
4617
+ const taskJsonPath = path22.join(ctx.taskDir, "task.json");
4618
+ if (fs24.existsSync(taskJsonPath)) {
3626
4619
  try {
3627
- const raw = fs22.readFileSync(taskJsonPath, "utf-8");
4620
+ const raw = fs24.readFileSync(taskJsonPath, "utf-8");
3628
4621
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3629
4622
  const task = JSON.parse(cleaned);
3630
4623
  prefix = TYPE_PREFIX[task.task_type] ?? "chore";
3631
4624
  } catch {
3632
4625
  }
3633
4626
  }
3634
- const taskMdPath = path20.join(ctx.taskDir, "task.md");
3635
- if (fs22.existsSync(taskMdPath)) {
3636
- const content = fs22.readFileSync(taskMdPath, "utf-8");
4627
+ const taskMdPath = path22.join(ctx.taskDir, "task.md");
4628
+ if (fs24.existsSync(taskMdPath)) {
4629
+ const content = fs24.readFileSync(taskMdPath, "utf-8");
3637
4630
  const heading = content.split("\n").find((l) => l.startsWith("# "));
3638
4631
  if (heading) {
3639
4632
  title = `${prefix}: ${heading.replace(/^#\s*/, "").trim()}`.slice(0, 72);
3640
4633
  }
3641
4634
  }
3642
4635
  if (title === "Update") {
3643
- if (fs22.existsSync(taskJsonPath)) {
4636
+ if (fs24.existsSync(taskJsonPath)) {
3644
4637
  try {
3645
- const raw = fs22.readFileSync(taskJsonPath, "utf-8");
4638
+ const raw = fs24.readFileSync(taskJsonPath, "utf-8");
3646
4639
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3647
4640
  const task = JSON.parse(cleaned);
3648
4641
  if (task.title) title = `${prefix}: ${task.title}`.slice(0, 72);
@@ -3665,7 +4658,7 @@ function executeShipStage(ctx, _def) {
3665
4658
  } catch {
3666
4659
  }
3667
4660
  }
3668
- fs22.writeFileSync(shipPath, `# Ship
4661
+ fs24.writeFileSync(shipPath, `# Ship
3669
4662
 
3670
4663
  Updated existing PR: ${existingPr.url}
3671
4664
  PR #${existingPr.number}
@@ -3686,20 +4679,20 @@ PR #${existingPr.number}
3686
4679
  } catch {
3687
4680
  }
3688
4681
  }
3689
- fs22.writeFileSync(shipPath, `# Ship
4682
+ fs24.writeFileSync(shipPath, `# Ship
3690
4683
 
3691
4684
  PR created: ${pr.url}
3692
4685
  PR #${pr.number}
3693
4686
  `);
3694
4687
  } else {
3695
- fs22.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
4688
+ fs24.writeFileSync(shipPath, "# Ship\n\nPushed branch but failed to create PR.\n");
3696
4689
  }
3697
4690
  }
3698
4691
  return { outcome: "completed", outputFile: "ship.md", retries: 0 };
3699
4692
  } catch (err) {
3700
4693
  const msg = err instanceof Error ? err.message : String(err);
3701
4694
  try {
3702
- fs22.writeFileSync(shipPath, `# Ship
4695
+ fs24.writeFileSync(shipPath, `# Ship
3703
4696
 
3704
4697
  Failed: ${msg}
3705
4698
  `);
@@ -3748,15 +4741,15 @@ var init_executor_registry = __esm({
3748
4741
  });
3749
4742
 
3750
4743
  // src/pipeline/questions.ts
3751
- import * as fs23 from "fs";
3752
- import * as path21 from "path";
4744
+ import * as fs25 from "fs";
4745
+ import * as path23 from "path";
3753
4746
  function checkForQuestions(ctx, stageName) {
3754
4747
  if (ctx.input.local || !ctx.input.issueNumber) return false;
3755
4748
  try {
3756
4749
  if (stageName === "taskify") {
3757
- const taskJsonPath = path21.join(ctx.taskDir, "task.json");
3758
- if (!fs23.existsSync(taskJsonPath)) return false;
3759
- const raw = fs23.readFileSync(taskJsonPath, "utf-8");
4750
+ const taskJsonPath = path23.join(ctx.taskDir, "task.json");
4751
+ if (!fs25.existsSync(taskJsonPath)) return false;
4752
+ const raw = fs25.readFileSync(taskJsonPath, "utf-8");
3760
4753
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3761
4754
  const taskJson = JSON.parse(cleaned);
3762
4755
  if (taskJson.questions && Array.isArray(taskJson.questions) && taskJson.questions.length > 0) {
@@ -3771,9 +4764,9 @@ Reply with \`@kody approve\` and your answers in the comment body.`;
3771
4764
  }
3772
4765
  }
3773
4766
  if (stageName === "plan") {
3774
- const planPath = path21.join(ctx.taskDir, "plan.md");
3775
- if (!fs23.existsSync(planPath)) return false;
3776
- const plan = fs23.readFileSync(planPath, "utf-8");
4767
+ const planPath = path23.join(ctx.taskDir, "plan.md");
4768
+ if (!fs25.existsSync(planPath)) return false;
4769
+ const plan = fs25.readFileSync(planPath, "utf-8");
3777
4770
  const questionsMatch = plan.match(/## Questions\s*\n([\s\S]*?)(?=\n## |\n*$)/);
3778
4771
  if (questionsMatch) {
3779
4772
  const questionsText = questionsMatch[1].trim();
@@ -3802,8 +4795,8 @@ var init_questions = __esm({
3802
4795
  });
3803
4796
 
3804
4797
  // src/pipeline/hooks.ts
3805
- import * as fs24 from "fs";
3806
- import * as path22 from "path";
4798
+ import * as fs26 from "fs";
4799
+ import * as path24 from "path";
3807
4800
  function applyPreStageLabel(ctx, def) {
3808
4801
  if (!ctx.input.issueNumber || ctx.input.local) return;
3809
4802
  if (def.name === "build") setLifecycleLabel(ctx.input.issueNumber, "building");
@@ -3841,9 +4834,9 @@ function autoDetectComplexity(ctx, def) {
3841
4834
  return { complexity, activeStages };
3842
4835
  }
3843
4836
  try {
3844
- const taskJsonPath = path22.join(ctx.taskDir, "task.json");
3845
- if (!fs24.existsSync(taskJsonPath)) return null;
3846
- const raw = fs24.readFileSync(taskJsonPath, "utf-8");
4837
+ const taskJsonPath = path24.join(ctx.taskDir, "task.json");
4838
+ if (!fs26.existsSync(taskJsonPath)) return null;
4839
+ const raw = fs26.readFileSync(taskJsonPath, "utf-8");
3847
4840
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3848
4841
  const taskJson = JSON.parse(cleaned);
3849
4842
  if (!taskJson.risk_level || !isValidComplexity(taskJson.risk_level)) return null;
@@ -3873,8 +4866,8 @@ function checkRiskGate(ctx, def, state, complexity) {
3873
4866
  if (ctx.input.dryRun || ctx.input.local) return null;
3874
4867
  if (ctx.input.mode === "rerun") return null;
3875
4868
  if (!ctx.input.issueNumber) return null;
3876
- const planPath = path22.join(ctx.taskDir, "plan.md");
3877
- const plan = fs24.existsSync(planPath) ? fs24.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
4869
+ const planPath = path24.join(ctx.taskDir, "plan.md");
4870
+ const plan = fs26.existsSync(planPath) ? fs26.readFileSync(planPath, "utf-8").slice(0, 1500) : "(plan not available)";
3878
4871
  try {
3879
4872
  postComment(
3880
4873
  ctx.input.issueNumber,
@@ -3941,22 +4934,22 @@ var init_hooks = __esm({
3941
4934
  });
3942
4935
 
3943
4936
  // src/learning/auto-learn.ts
3944
- import * as fs25 from "fs";
3945
- import * as path23 from "path";
4937
+ import * as fs27 from "fs";
4938
+ import * as path25 from "path";
3946
4939
  function stripAnsi(str) {
3947
4940
  return str.replace(/\x1b\[[0-9;]*m/g, "");
3948
4941
  }
3949
4942
  function autoLearn(ctx) {
3950
4943
  try {
3951
- const memoryDir = path23.join(ctx.projectDir, ".kody", "memory");
3952
- if (!fs25.existsSync(memoryDir)) {
3953
- fs25.mkdirSync(memoryDir, { recursive: true });
4944
+ const memoryDir = path25.join(ctx.projectDir, ".kody", "memory");
4945
+ if (!fs27.existsSync(memoryDir)) {
4946
+ fs27.mkdirSync(memoryDir, { recursive: true });
3954
4947
  }
3955
4948
  const learnings = [];
3956
4949
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3957
- const verifyPath = path23.join(ctx.taskDir, "verify.md");
3958
- if (fs25.existsSync(verifyPath)) {
3959
- const verify = stripAnsi(fs25.readFileSync(verifyPath, "utf-8"));
4950
+ const verifyPath = path25.join(ctx.taskDir, "verify.md");
4951
+ if (fs27.existsSync(verifyPath)) {
4952
+ const verify = stripAnsi(fs27.readFileSync(verifyPath, "utf-8"));
3960
4953
  if (/vitest/i.test(verify)) learnings.push("- Uses vitest for testing");
3961
4954
  if (/jest/i.test(verify)) learnings.push("- Uses jest for testing");
3962
4955
  if (/eslint/i.test(verify)) learnings.push("- Uses eslint for linting");
@@ -3965,18 +4958,18 @@ function autoLearn(ctx) {
3965
4958
  if (/jsdom/i.test(verify)) learnings.push("- Test environment: jsdom");
3966
4959
  if (/node/i.test(verify) && /environment/i.test(verify)) learnings.push("- Test environment: node");
3967
4960
  }
3968
- const reviewPath = path23.join(ctx.taskDir, "review.md");
3969
- if (fs25.existsSync(reviewPath)) {
3970
- const review = fs25.readFileSync(reviewPath, "utf-8");
4961
+ const reviewPath = path25.join(ctx.taskDir, "review.md");
4962
+ if (fs27.existsSync(reviewPath)) {
4963
+ const review = fs27.readFileSync(reviewPath, "utf-8");
3971
4964
  if (/\.js extension/i.test(review)) learnings.push("- Imports use .js extensions (ESM)");
3972
4965
  if (/barrel export/i.test(review)) learnings.push("- Uses barrel exports (index.ts)");
3973
4966
  if (/timezone/i.test(review)) learnings.push("- Timezone handling is a concern in this codebase");
3974
4967
  if (/UTC/i.test(review)) learnings.push("- Date operations should consider UTC vs local time");
3975
4968
  }
3976
- const taskJsonPath = path23.join(ctx.taskDir, "task.json");
3977
- if (fs25.existsSync(taskJsonPath)) {
4969
+ const taskJsonPath = path25.join(ctx.taskDir, "task.json");
4970
+ if (fs27.existsSync(taskJsonPath)) {
3978
4971
  try {
3979
- const raw = stripAnsi(fs25.readFileSync(taskJsonPath, "utf-8"));
4972
+ const raw = stripAnsi(fs27.readFileSync(taskJsonPath, "utf-8"));
3980
4973
  const cleaned = raw.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "");
3981
4974
  const task = JSON.parse(cleaned);
3982
4975
  if (task.scope && Array.isArray(task.scope)) {
@@ -3987,12 +4980,12 @@ function autoLearn(ctx) {
3987
4980
  }
3988
4981
  }
3989
4982
  if (learnings.length > 0) {
3990
- const conventionsPath = path23.join(memoryDir, "conventions.md");
4983
+ const conventionsPath = path25.join(memoryDir, "conventions.md");
3991
4984
  const entry = `
3992
4985
  ## Learned ${timestamp2} (task: ${ctx.taskId})
3993
4986
  ${learnings.join("\n")}
3994
4987
  `;
3995
- fs25.appendFileSync(conventionsPath, entry);
4988
+ fs27.appendFileSync(conventionsPath, entry);
3996
4989
  logger.info(`Auto-learned ${learnings.length} convention(s)`);
3997
4990
  }
3998
4991
  autoLearnArchitecture(ctx.projectDir, memoryDir, timestamp2);
@@ -4000,8 +4993,8 @@ ${learnings.join("\n")}
4000
4993
  }
4001
4994
  }
4002
4995
  function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
4003
- const archPath = path23.join(memoryDir, "architecture.md");
4004
- if (fs25.existsSync(archPath)) return;
4996
+ const archPath = path25.join(memoryDir, "architecture.md");
4997
+ if (fs27.existsSync(archPath)) return;
4005
4998
  const detected = detectArchitectureBasic(projectDir);
4006
4999
  if (detected.length > 0) {
4007
5000
  const content = `# Architecture (auto-detected ${timestamp2})
@@ -4009,7 +5002,7 @@ function autoLearnArchitecture(projectDir, memoryDir, timestamp2) {
4009
5002
  ## Overview
4010
5003
  ${detected.join("\n")}
4011
5004
  `;
4012
- fs25.writeFileSync(archPath, content);
5005
+ fs27.writeFileSync(archPath, content);
4013
5006
  logger.info(`Auto-detected architecture (${detected.length} items)`);
4014
5007
  }
4015
5008
  }
@@ -4022,13 +5015,13 @@ var init_auto_learn = __esm({
4022
5015
  });
4023
5016
 
4024
5017
  // src/retrospective.ts
4025
- import * as fs26 from "fs";
4026
- import * as path24 from "path";
5018
+ import * as fs28 from "fs";
5019
+ import * as path26 from "path";
4027
5020
  function readArtifact(taskDir, filename, maxChars) {
4028
- const p = path24.join(taskDir, filename);
4029
- if (!fs26.existsSync(p)) return null;
5021
+ const p = path26.join(taskDir, filename);
5022
+ if (!fs28.existsSync(p)) return null;
4030
5023
  try {
4031
- const content = fs26.readFileSync(p, "utf-8");
5024
+ const content = fs28.readFileSync(p, "utf-8");
4032
5025
  return content.length > maxChars ? content.slice(0, maxChars) + "\n...(truncated)" : content;
4033
5026
  } catch {
4034
5027
  return null;
@@ -4081,13 +5074,13 @@ function collectRunContext(ctx, state, pipelineStartTime) {
4081
5074
  return lines.join("\n");
4082
5075
  }
4083
5076
  function getLogPath(projectDir) {
4084
- return path24.join(projectDir, ".kody", "memory", "observer-log.jsonl");
5077
+ return path26.join(projectDir, ".kody", "memory", "observer-log.jsonl");
4085
5078
  }
4086
5079
  function readPreviousRetrospectives(projectDir, limit = 10) {
4087
5080
  const logPath = getLogPath(projectDir);
4088
- if (!fs26.existsSync(logPath)) return [];
5081
+ if (!fs28.existsSync(logPath)) return [];
4089
5082
  try {
4090
- const content = fs26.readFileSync(logPath, "utf-8");
5083
+ const content = fs28.readFileSync(logPath, "utf-8");
4091
5084
  const lines = content.split("\n").filter(Boolean);
4092
5085
  const entries = [];
4093
5086
  const start = Math.max(0, lines.length - limit);
@@ -4114,11 +5107,11 @@ function formatPreviousEntries(entries) {
4114
5107
  }
4115
5108
  function appendRetrospectiveEntry(projectDir, entry) {
4116
5109
  const logPath = getLogPath(projectDir);
4117
- const dir = path24.dirname(logPath);
4118
- if (!fs26.existsSync(dir)) {
4119
- fs26.mkdirSync(dir, { recursive: true });
5110
+ const dir = path26.dirname(logPath);
5111
+ if (!fs28.existsSync(dir)) {
5112
+ fs28.mkdirSync(dir, { recursive: true });
4120
5113
  }
4121
- fs26.appendFileSync(logPath, JSON.stringify(entry) + "\n");
5114
+ fs28.appendFileSync(logPath, JSON.stringify(entry) + "\n");
4122
5115
  }
4123
5116
  async function runRetrospective(ctx, state, pipelineStartTime) {
4124
5117
  if (ctx.input.dryRun) return;
@@ -4140,7 +5133,7 @@ ${previousText}
4140
5133
  if (needsLitellmProxy(config)) {
4141
5134
  extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
4142
5135
  }
4143
- const result = await runner.run("retrospective", prompt, model, 3e4, "", {
5136
+ const result2 = await runner.run("retrospective", prompt, model, 3e4, "", {
4144
5137
  cwd: ctx.projectDir,
4145
5138
  env: extraEnv
4146
5139
  });
@@ -4148,8 +5141,8 @@ ${previousText}
4148
5141
  let patternMatch = null;
4149
5142
  let suggestion = "No suggestion";
4150
5143
  let pipelineFlaw = null;
4151
- if (result.outcome === "completed" && result.output) {
4152
- const cleaned = result.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
5144
+ if (result2.outcome === "completed" && result2.output) {
5145
+ const cleaned = result2.output.replace(/^```json\s*\n?/m, "").replace(/\n?```\s*$/m, "").trim();
4153
5146
  try {
4154
5147
  const parsed = JSON.parse(cleaned);
4155
5148
  observation = parsed.observation ?? observation;
@@ -4286,8 +5279,8 @@ var init_summary = __esm({
4286
5279
  });
4287
5280
 
4288
5281
  // src/pipeline.ts
4289
- import * as fs27 from "fs";
4290
- import * as path25 from "path";
5282
+ import * as fs29 from "fs";
5283
+ import * as path27 from "path";
4291
5284
  function ensureFeatureBranchIfNeeded(ctx) {
4292
5285
  if (ctx.input.dryRun) return;
4293
5286
  if (ctx.input.prNumber) {
@@ -4300,8 +5293,8 @@ function ensureFeatureBranchIfNeeded(ctx) {
4300
5293
  }
4301
5294
  if (!ctx.input.issueNumber) return;
4302
5295
  try {
4303
- const taskMdPath = path25.join(ctx.taskDir, "task.md");
4304
- const title = fs27.existsSync(taskMdPath) ? fs27.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
5296
+ const taskMdPath = path27.join(ctx.taskDir, "task.md");
5297
+ const title = fs29.existsSync(taskMdPath) ? fs29.readFileSync(taskMdPath, "utf-8").split("\n")[0].slice(0, 50) : ctx.taskId;
4305
5298
  ensureFeatureBranch(ctx.input.issueNumber, title, ctx.projectDir);
4306
5299
  syncWithDefault(ctx.projectDir);
4307
5300
  } catch (err) {
@@ -4315,10 +5308,10 @@ function ensureFeatureBranchIfNeeded(ctx) {
4315
5308
  }
4316
5309
  }
4317
5310
  function acquireLock(taskDir) {
4318
- const lockPath = path25.join(taskDir, ".lock");
4319
- if (fs27.existsSync(lockPath)) {
5311
+ const lockPath = path27.join(taskDir, ".lock");
5312
+ if (fs29.existsSync(lockPath)) {
4320
5313
  try {
4321
- const pid = parseInt(fs27.readFileSync(lockPath, "utf-8").trim(), 10);
5314
+ const pid = parseInt(fs29.readFileSync(lockPath, "utf-8").trim(), 10);
4322
5315
  if (!isNaN(pid)) {
4323
5316
  try {
4324
5317
  process.kill(pid, 0);
@@ -4335,14 +5328,14 @@ function acquireLock(taskDir) {
4335
5328
  logger.warn(` Corrupt lock file \u2014 overwriting`);
4336
5329
  }
4337
5330
  try {
4338
- fs27.unlinkSync(lockPath);
5331
+ fs29.unlinkSync(lockPath);
4339
5332
  } catch {
4340
5333
  }
4341
5334
  }
4342
5335
  try {
4343
- const fd = fs27.openSync(lockPath, fs27.constants.O_WRONLY | fs27.constants.O_CREAT | fs27.constants.O_EXCL);
4344
- fs27.writeSync(fd, String(process.pid));
4345
- fs27.closeSync(fd);
5336
+ const fd = fs29.openSync(lockPath, fs29.constants.O_WRONLY | fs29.constants.O_CREAT | fs29.constants.O_EXCL);
5337
+ fs29.writeSync(fd, String(process.pid));
5338
+ fs29.closeSync(fd);
4346
5339
  } catch (err) {
4347
5340
  if (err.code === "EEXIST") {
4348
5341
  throw new Error("Pipeline already running (lock acquired by another process)");
@@ -4352,7 +5345,7 @@ function acquireLock(taskDir) {
4352
5345
  }
4353
5346
  function releaseLock(taskDir) {
4354
5347
  try {
4355
- fs27.unlinkSync(path25.join(taskDir, ".lock"));
5348
+ fs29.unlinkSync(path27.join(taskDir, ".lock"));
4356
5349
  } catch {
4357
5350
  }
4358
5351
  }
@@ -4441,23 +5434,23 @@ async function runPipelineInner(ctx) {
4441
5434
  writeState(state, ctx.taskDir);
4442
5435
  logger.info(`[${def.name}] starting...`);
4443
5436
  applyPreStageLabel(ctx, def);
4444
- let result;
5437
+ let result2;
4445
5438
  try {
4446
- result = await getExecutor(def.name)(ctx, def);
5439
+ result2 = await getExecutor(def.name)(ctx, def);
4447
5440
  } catch (error) {
4448
- result = {
5441
+ result2 = {
4449
5442
  outcome: "failed",
4450
5443
  retries: 0,
4451
5444
  error: error instanceof Error ? error.message : String(error)
4452
5445
  };
4453
5446
  }
4454
5447
  ciGroupEnd();
4455
- if (result.outcome === "completed") {
5448
+ if (result2.outcome === "completed") {
4456
5449
  state.stages[def.name] = {
4457
5450
  state: "completed",
4458
5451
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
4459
- retries: result.retries,
4460
- outputFile: result.outputFile
5452
+ retries: result2.retries,
5453
+ outputFile: result2.outputFile
4461
5454
  };
4462
5455
  logger.info(`[${def.name}] \u2713 completed`);
4463
5456
  const detected = autoDetectComplexity(ctx, def);
@@ -4471,16 +5464,16 @@ async function runPipelineInner(ctx) {
4471
5464
  if (gated) return gated;
4472
5465
  commitAfterStage(ctx, def);
4473
5466
  } else {
4474
- const isTimeout = result.outcome === "timed_out";
5467
+ const isTimeout = result2.outcome === "timed_out";
4475
5468
  state.stages[def.name] = {
4476
5469
  state: isTimeout ? "timeout" : "failed",
4477
- retries: result.retries,
4478
- error: isTimeout ? "Stage timed out" : result.error ?? "Stage failed"
5470
+ retries: result2.retries,
5471
+ error: isTimeout ? "Stage timed out" : result2.error ?? "Stage failed"
4479
5472
  };
4480
5473
  state.state = "failed";
4481
5474
  state.sessions = ctx.sessions;
4482
5475
  writeState(state, ctx.taskDir);
4483
- logger.error(`[${def.name}] ${isTimeout ? "\u23F1 timed out" : `\u2717 failed: ${result.error}`}`);
5476
+ logger.error(`[${def.name}] ${isTimeout ? "\u23F1 timed out" : `\u2717 failed: ${result2.error}`}`);
4484
5477
  if (ctx.input.issueNumber && !ctx.input.local) {
4485
5478
  setLifecycleLabel(ctx.input.issueNumber, "failed");
4486
5479
  }
@@ -4560,8 +5553,8 @@ var init_pipeline = __esm({
4560
5553
  });
4561
5554
 
4562
5555
  // src/preflight.ts
4563
- import { execFileSync as execFileSync15 } from "child_process";
4564
- import * as fs28 from "fs";
5556
+ import { execFileSync as execFileSync16 } from "child_process";
5557
+ import * as fs30 from "fs";
4565
5558
  function check(name, fn) {
4566
5559
  try {
4567
5560
  const detail = fn() ?? void 0;
@@ -4573,7 +5566,7 @@ function check(name, fn) {
4573
5566
  function runPreflight() {
4574
5567
  const checks = [
4575
5568
  check("claude CLI", () => {
4576
- const v = execFileSync15("claude", ["--version"], {
5569
+ const v = execFileSync16("claude", ["--version"], {
4577
5570
  encoding: "utf-8",
4578
5571
  timeout: 1e4,
4579
5572
  stdio: ["pipe", "pipe", "pipe"]
@@ -4581,14 +5574,14 @@ function runPreflight() {
4581
5574
  return v;
4582
5575
  }),
4583
5576
  check("git repo", () => {
4584
- execFileSync15("git", ["rev-parse", "--is-inside-work-tree"], {
5577
+ execFileSync16("git", ["rev-parse", "--is-inside-work-tree"], {
4585
5578
  encoding: "utf-8",
4586
5579
  timeout: 5e3,
4587
5580
  stdio: ["pipe", "pipe", "pipe"]
4588
5581
  });
4589
5582
  }),
4590
5583
  check("pnpm", () => {
4591
- const v = execFileSync15("pnpm", ["--version"], {
5584
+ const v = execFileSync16("pnpm", ["--version"], {
4592
5585
  encoding: "utf-8",
4593
5586
  timeout: 5e3,
4594
5587
  stdio: ["pipe", "pipe", "pipe"]
@@ -4596,7 +5589,7 @@ function runPreflight() {
4596
5589
  return v;
4597
5590
  }),
4598
5591
  check("node >= 18", () => {
4599
- const v = execFileSync15("node", ["--version"], {
5592
+ const v = execFileSync16("node", ["--version"], {
4600
5593
  encoding: "utf-8",
4601
5594
  timeout: 5e3,
4602
5595
  stdio: ["pipe", "pipe", "pipe"]
@@ -4606,7 +5599,7 @@ function runPreflight() {
4606
5599
  return v;
4607
5600
  }),
4608
5601
  check("gh CLI", () => {
4609
- const v = execFileSync15("gh", ["--version"], {
5602
+ const v = execFileSync16("gh", ["--version"], {
4610
5603
  encoding: "utf-8",
4611
5604
  timeout: 5e3,
4612
5605
  stdio: ["pipe", "pipe", "pipe"]
@@ -4614,7 +5607,7 @@ function runPreflight() {
4614
5607
  return v;
4615
5608
  }),
4616
5609
  check("package.json", () => {
4617
- if (!fs28.existsSync("package.json")) throw new Error("not found");
5610
+ if (!fs30.existsSync("package.json")) throw new Error("not found");
4618
5611
  })
4619
5612
  ];
4620
5613
  const failed = checks.filter((c) => !c.ok);
@@ -4691,8 +5684,8 @@ var init_args = __esm({
4691
5684
  });
4692
5685
 
4693
5686
  // src/cli/task-state.ts
4694
- import * as fs29 from "fs";
4695
- import * as path26 from "path";
5687
+ import * as fs31 from "fs";
5688
+ import * as path28 from "path";
4696
5689
  function resolveTaskAction(issueNumber, existingTaskId, existingState) {
4697
5690
  if (!existingTaskId || !existingState) {
4698
5691
  return { action: "start-fresh", taskId: `${issueNumber}-${generateTaskId()}` };
@@ -4724,11 +5717,11 @@ function resolveTaskAction(issueNumber, existingTaskId, existingState) {
4724
5717
  function resolveForIssue(issueNumber, projectDir) {
4725
5718
  const existingTaskId = findLatestTaskForIssue(issueNumber, projectDir);
4726
5719
  if (existingTaskId) {
4727
- const statusPath = path26.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
5720
+ const statusPath = path28.join(projectDir, ".kody", "tasks", existingTaskId, "status.json");
4728
5721
  let existingState = null;
4729
- if (fs29.existsSync(statusPath)) {
5722
+ if (fs31.existsSync(statusPath)) {
4730
5723
  try {
4731
- existingState = JSON.parse(fs29.readFileSync(statusPath, "utf-8"));
5724
+ existingState = JSON.parse(fs31.readFileSync(statusPath, "utf-8"));
4732
5725
  } catch {
4733
5726
  }
4734
5727
  }
@@ -4761,12 +5754,12 @@ var resolve_exports = {};
4761
5754
  __export(resolve_exports, {
4762
5755
  runResolve: () => runResolve
4763
5756
  });
4764
- import { execFileSync as execFileSync16 } from "child_process";
5757
+ import { execFileSync as execFileSync17 } from "child_process";
4765
5758
  function getConflictContext(cwd, files) {
4766
5759
  const parts = [];
4767
5760
  for (const file of files.slice(0, 10)) {
4768
5761
  try {
4769
- const content = execFileSync16("git", ["diff", file], {
5762
+ const content = execFileSync17("git", ["diff", file], {
4770
5763
  cwd,
4771
5764
  encoding: "utf-8",
4772
5765
  stdio: ["pipe", "pipe", "pipe"]
@@ -4816,12 +5809,12 @@ async function runResolve(options) {
4816
5809
  extraEnv.ANTHROPIC_BASE_URL = getLitellmUrl();
4817
5810
  }
4818
5811
  logger.info(` Running agent to resolve conflicts (model=${model})...`);
4819
- const result = await runner.run("resolve", prompt, model, 3e5, projectDir, {
5812
+ const result2 = await runner.run("resolve", prompt, model, 3e5, projectDir, {
4820
5813
  cwd: projectDir,
4821
5814
  env: extraEnv
4822
5815
  });
4823
- if (result.outcome !== "completed") {
4824
- return { outcome: "failed", error: `Agent failed: ${result.error}` };
5816
+ if (result2.outcome !== "completed") {
5817
+ return { outcome: "failed", error: `Agent failed: ${result2.error}` };
4825
5818
  }
4826
5819
  logger.info(" Verifying resolution...");
4827
5820
  const verify = runQualityGates(projectDir, projectDir);
@@ -4885,8 +5878,8 @@ var init_resolve = __esm({
4885
5878
 
4886
5879
  // src/entry.ts
4887
5880
  var entry_exports = {};
4888
- import * as fs30 from "fs";
4889
- import * as path27 from "path";
5881
+ import * as fs32 from "fs";
5882
+ import * as path29 from "path";
4890
5883
  async function ensureLitellmProxy(config, projectDir) {
4891
5884
  if (!anyStageNeedsProxy(config)) return null;
4892
5885
  const litellmUrl = getLitellmUrl();
@@ -4931,19 +5924,19 @@ async function runModelHealthCheck(config) {
4931
5924
  }
4932
5925
  const model = config.agent.modelMap.cheap;
4933
5926
  logger.info(`Model health check (${model} via ${usesProxy ? "LiteLLM" : "Anthropic"})...`);
4934
- const result = await checkModelHealth(baseUrl, apiKey, model);
4935
- if (result.ok) {
5927
+ const result2 = await checkModelHealth(baseUrl, apiKey, model);
5928
+ if (result2.ok) {
4936
5929
  logger.info(" \u2713 Model responded");
4937
5930
  } else {
4938
- logger.error(` \u2717 Model health check failed: ${result.error}`);
5931
+ logger.error(` \u2717 Model health check failed: ${result2.error}`);
4939
5932
  process.exit(1);
4940
5933
  }
4941
5934
  }
4942
5935
  async function main() {
4943
5936
  const input = parseArgs();
4944
- const projectDir = input.cwd ? path27.resolve(input.cwd) : process.cwd();
5937
+ const projectDir = input.cwd ? path29.resolve(input.cwd) : process.cwd();
4945
5938
  if (input.cwd) {
4946
- if (!fs30.existsSync(projectDir)) {
5939
+ if (!fs32.existsSync(projectDir)) {
4947
5940
  console.error(`--cwd path does not exist: ${projectDir}`);
4948
5941
  process.exit(1);
4949
5942
  }
@@ -4952,7 +5945,7 @@ async function main() {
4952
5945
  logger.info(`Working directory: ${projectDir}`);
4953
5946
  }
4954
5947
  const isPRFix = (input.command === "fix" || input.command === "fix-ci") && !!input.prNumber;
4955
- const skipStateCheck = input.command === "review" || input.command === "resolve" || input.command === "rerun";
5948
+ const skipStateCheck = input.command === "review" || input.command === "resolve" || input.command === "rerun" || input.command === "status";
4956
5949
  if (input.issueNumber && !skipStateCheck && !isPRFix) {
4957
5950
  const taskAction = resolveForIssue(input.issueNumber, projectDir);
4958
5951
  logger.info(`Task action: ${taskAction.action}`);
@@ -5009,8 +6002,8 @@ async function main() {
5009
6002
  process.exit(1);
5010
6003
  }
5011
6004
  }
5012
- const taskDir = path27.join(projectDir, ".kody", "tasks", taskId);
5013
- fs30.mkdirSync(taskDir, { recursive: true });
6005
+ const taskDir = path29.join(projectDir, ".kody", "tasks", taskId);
6006
+ fs32.mkdirSync(taskDir, { recursive: true });
5014
6007
  if (input.command === "rerun" && isTaskifyRun(taskDir)) {
5015
6008
  const marker = readTaskifyMarker(taskDir);
5016
6009
  if (marker) {
@@ -5075,7 +6068,7 @@ async function main() {
5075
6068
  console.error(`Runner "${defaultRunnerName2}" health check failed`);
5076
6069
  process.exit(1);
5077
6070
  }
5078
- const result = await runStandaloneReview({
6071
+ const result2 = await runStandaloneReview({
5079
6072
  projectDir,
5080
6073
  runners: runners2,
5081
6074
  prTitle,
@@ -5085,15 +6078,15 @@ async function main() {
5085
6078
  taskId
5086
6079
  });
5087
6080
  if (litellmProcess2) litellmProcess2.kill();
5088
- if (result.outcome === "failed") {
5089
- console.error(`Review failed: ${result.error}`);
6081
+ if (result2.outcome === "failed") {
6082
+ console.error(`Review failed: ${result2.error}`);
5090
6083
  process.exit(1);
5091
6084
  }
5092
- if (result.reviewContent) {
5093
- console.log(result.reviewContent);
6085
+ if (result2.reviewContent) {
6086
+ console.log(result2.reviewContent);
5094
6087
  if (!input.local && prNumber) {
5095
- const comment = formatReviewComment(result.reviewContent, taskId);
5096
- const verdict = detectReviewVerdict(result.reviewContent);
6088
+ const comment = formatReviewComment(result2.reviewContent, taskId);
6089
+ const verdict = detectReviewVerdict(result2.reviewContent);
5097
6090
  const event = verdict === "fail" ? "request-changes" : "approve";
5098
6091
  const posted = submitPRReview(prNumber, comment, event);
5099
6092
  if (!posted) {
@@ -5125,48 +6118,48 @@ async function main() {
5125
6118
  process.exit(1);
5126
6119
  }
5127
6120
  const { runResolve: runResolve2 } = await Promise.resolve().then(() => (init_resolve(), resolve_exports));
5128
- const result = await runResolve2({
6121
+ const result2 = await runResolve2({
5129
6122
  prNumber: input.prNumber,
5130
6123
  projectDir,
5131
6124
  runners: runners2,
5132
6125
  local: input.local ?? true
5133
6126
  });
5134
6127
  if (litellmProcess2) litellmProcess2.kill();
5135
- if (result.outcome === "failed") {
5136
- console.error(`Resolve failed: ${result.error}`);
6128
+ if (result2.outcome === "failed") {
6129
+ console.error(`Resolve failed: ${result2.error}`);
5137
6130
  process.exit(1);
5138
6131
  }
5139
- console.log(`Resolve: ${result.outcome}`);
6132
+ console.log(`Resolve: ${result2.outcome}`);
5140
6133
  process.exit(0);
5141
6134
  }
5142
6135
  logger.info("Preflight checks:");
5143
6136
  runPreflight();
5144
6137
  if (input.task) {
5145
- fs30.writeFileSync(path27.join(taskDir, "task.md"), input.task);
6138
+ fs32.writeFileSync(path29.join(taskDir, "task.md"), input.task);
5146
6139
  }
5147
- const taskMdPath = path27.join(taskDir, "task.md");
5148
- if (!fs30.existsSync(taskMdPath) && isPRFix && input.prNumber) {
6140
+ const taskMdPath = path29.join(taskDir, "task.md");
6141
+ if (!fs32.existsSync(taskMdPath) && isPRFix && input.prNumber) {
5149
6142
  logger.info(`Fetching PR #${input.prNumber} details as task context...`);
5150
6143
  const prDetails = getPRDetails(input.prNumber);
5151
6144
  if (prDetails) {
5152
6145
  const taskContent = `# ${prDetails.title}
5153
6146
 
5154
6147
  ${prDetails.body ?? ""}`;
5155
- fs30.writeFileSync(taskMdPath, taskContent);
6148
+ fs32.writeFileSync(taskMdPath, taskContent);
5156
6149
  logger.info(` Task loaded from PR #${input.prNumber}: ${prDetails.title}`);
5157
6150
  }
5158
- } else if (!fs30.existsSync(taskMdPath) && input.issueNumber) {
6151
+ } else if (!fs32.existsSync(taskMdPath) && input.issueNumber) {
5159
6152
  logger.info(`Fetching issue #${input.issueNumber} body as task...`);
5160
6153
  const issue = getIssue(input.issueNumber);
5161
6154
  if (issue) {
5162
6155
  const taskContent = `# ${issue.title}
5163
6156
 
5164
6157
  ${issue.body ?? ""}`;
5165
- fs30.writeFileSync(taskMdPath, taskContent);
6158
+ fs32.writeFileSync(taskMdPath, taskContent);
5166
6159
  logger.info(` Task loaded from issue #${input.issueNumber}: ${issue.title}`);
5167
6160
  }
5168
6161
  }
5169
- if (!fs30.existsSync(taskMdPath)) {
6162
+ if (!fs32.existsSync(taskMdPath)) {
5170
6163
  console.error("No task.md found. Provide --task, --issue-number, or ensure .kody/tasks/<id>/task.md exists.");
5171
6164
  process.exit(1);
5172
6165
  }
@@ -5304,7 +6297,7 @@ To rerun: \`@kody rerun ${taskId} --from <stage>\``
5304
6297
  }
5305
6298
  }
5306
6299
  const state = await runPipeline(ctx);
5307
- const files = fs30.readdirSync(taskDir);
6300
+ const files = fs32.readdirSync(taskDir);
5308
6301
  console.log(`
5309
6302
  Artifacts in ${taskDir}:`);
5310
6303
  for (const f of files) {
@@ -5369,8 +6362,8 @@ var init_entry = __esm({
5369
6362
  });
5370
6363
 
5371
6364
  // src/bin/cli.ts
5372
- import * as fs31 from "fs";
5373
- import * as path28 from "path";
6365
+ import * as fs33 from "fs";
6366
+ import * as path30 from "path";
5374
6367
  import { fileURLToPath as fileURLToPath2 } from "url";
5375
6368
 
5376
6369
  // src/bin/commands/init.ts
@@ -5750,7 +6743,7 @@ import { execFileSync as execFileSync5 } from "child_process";
5750
6743
  import * as fs5 from "fs";
5751
6744
  import * as path4 from "path";
5752
6745
  function discoverQaContext(cwd) {
5753
- const result = {
6746
+ const result2 = {
5754
6747
  routes: [],
5755
6748
  authFiles: [],
5756
6749
  loginPage: null,
@@ -5763,21 +6756,21 @@ function discoverQaContext(cwd) {
5763
6756
  const pkg = JSON.parse(fs5.readFileSync(path4.join(cwd, "package.json"), "utf-8"));
5764
6757
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
5765
6758
  const pm = fs5.existsSync(path4.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs5.existsSync(path4.join(cwd, "yarn.lock")) ? "yarn" : "npm";
5766
- if (pkg.scripts?.dev) result.devCommand = `${pm} dev`;
5767
- if (allDeps.next || allDeps.nuxt) result.devPort = 3e3;
5768
- else if (allDeps.vite) result.devPort = 5173;
6759
+ if (pkg.scripts?.dev) result2.devCommand = `${pm} dev`;
6760
+ if (allDeps.next || allDeps.nuxt) result2.devPort = 3e3;
6761
+ else if (allDeps.vite) result2.devPort = 5173;
5769
6762
  } catch {
5770
6763
  }
5771
6764
  const appDirs = ["src/app", "app"];
5772
6765
  for (const appDir of appDirs) {
5773
6766
  const fullAppDir = path4.join(cwd, appDir);
5774
6767
  if (!fs5.existsSync(fullAppDir)) continue;
5775
- scanRoutes(fullAppDir, appDir, "", result);
6768
+ scanRoutes(fullAppDir, appDir, "", result2);
5776
6769
  break;
5777
6770
  }
5778
6771
  const authPatterns = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
5779
6772
  for (const p of authPatterns) {
5780
- if (fs5.existsSync(path4.join(cwd, p))) result.authFiles.push(p);
6773
+ if (fs5.existsSync(path4.join(cwd, p))) result2.authFiles.push(p);
5781
6774
  }
5782
6775
  const authConfigGlobs = [
5783
6776
  "src/app/api/auth",
@@ -5788,7 +6781,7 @@ function discoverQaContext(cwd) {
5788
6781
  "src/app/api/oauth"
5789
6782
  ];
5790
6783
  for (const g of authConfigGlobs) {
5791
- if (fs5.existsSync(path4.join(cwd, g))) result.authFiles.push(g);
6784
+ if (fs5.existsSync(path4.join(cwd, g))) result2.authFiles.push(g);
5792
6785
  }
5793
6786
  try {
5794
6787
  const rolePaths = [
@@ -5810,7 +6803,7 @@ function discoverQaContext(cwd) {
5810
6803
  if (roleMatches) {
5811
6804
  for (const m of roleMatches) {
5812
6805
  const val = m.match(/['"](\w+)['"]/);
5813
- if (val && !result.roles.includes(val[1])) result.roles.push(val[1]);
6806
+ if (val && !result2.roles.includes(val[1])) result2.roles.push(val[1]);
5814
6807
  }
5815
6808
  }
5816
6809
  const enumMatch = content.match(/(?:enum|type)\s+\w*[Rr]ole\w*\s*[={]([^}]+)/s);
@@ -5819,7 +6812,7 @@ function discoverQaContext(cwd) {
5819
6812
  if (vals) {
5820
6813
  for (const v of vals) {
5821
6814
  const clean = v.replace(/['"]/g, "");
5822
- if (!result.roles.includes(clean)) result.roles.push(clean);
6815
+ if (!result2.roles.includes(clean)) result2.roles.push(clean);
5823
6816
  }
5824
6817
  }
5825
6818
  }
@@ -5829,9 +6822,9 @@ function discoverQaContext(cwd) {
5829
6822
  }
5830
6823
  } catch {
5831
6824
  }
5832
- return result;
6825
+ return result2;
5833
6826
  }
5834
- function scanRoutes(dir, baseDir, prefix, result) {
6827
+ function scanRoutes(dir, baseDir, prefix, result2) {
5835
6828
  let entries;
5836
6829
  try {
5837
6830
  entries = fs5.readdirSync(dir, { withFileTypes: true });
@@ -5842,16 +6835,16 @@ function scanRoutes(dir, baseDir, prefix, result) {
5842
6835
  if (hasPage) {
5843
6836
  const routePath = prefix || "/";
5844
6837
  const group = prefix.startsWith("/admin") ? "admin" : prefix.includes("/login") ? "auth" : prefix.includes("/signup") ? "auth" : prefix.includes("/api") ? "api" : "frontend";
5845
- result.routes.push({ path: routePath, group });
5846
- if (prefix.includes("/login")) result.loginPage = routePath;
5847
- if (prefix.startsWith("/admin") && !result.adminPath) result.adminPath = prefix;
6838
+ result2.routes.push({ path: routePath, group });
6839
+ if (prefix.includes("/login")) result2.loginPage = routePath;
6840
+ if (prefix.startsWith("/admin") && !result2.adminPath) result2.adminPath = prefix;
5848
6841
  }
5849
6842
  for (const entry of entries) {
5850
6843
  if (!entry.isDirectory()) continue;
5851
6844
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5852
6845
  let segment = entry.name;
5853
6846
  if (segment.startsWith("(") && segment.endsWith(")")) {
5854
- scanRoutes(path4.join(dir, entry.name), baseDir, prefix, result);
6847
+ scanRoutes(path4.join(dir, entry.name), baseDir, prefix, result2);
5855
6848
  continue;
5856
6849
  }
5857
6850
  if (segment.startsWith("[") && segment.endsWith("]")) {
@@ -5860,7 +6853,7 @@ function scanRoutes(dir, baseDir, prefix, result) {
5860
6853
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
5861
6854
  segment = `:${segment.slice(2, -2)}?`;
5862
6855
  }
5863
- scanRoutes(path4.join(dir, entry.name), baseDir, `${prefix}/${segment}`, result);
6856
+ scanRoutes(path4.join(dir, entry.name), baseDir, `${prefix}/${segment}`, result2);
5864
6857
  }
5865
6858
  }
5866
6859
  function generateQaGuide(discovery) {
@@ -6544,11 +7537,11 @@ Create it manually.`, cwd);
6544
7537
 
6545
7538
  // src/bin/cli.ts
6546
7539
  init_architecture_detection();
6547
- var __dirname2 = path28.dirname(fileURLToPath2(import.meta.url));
6548
- var PKG_ROOT = path28.resolve(__dirname2, "..", "..");
7540
+ var __dirname2 = path30.dirname(fileURLToPath2(import.meta.url));
7541
+ var PKG_ROOT = path30.resolve(__dirname2, "..", "..");
6549
7542
  function getVersion() {
6550
- const pkgPath = path28.join(PKG_ROOT, "package.json");
6551
- const pkg = JSON.parse(fs31.readFileSync(pkgPath, "utf-8"));
7543
+ const pkgPath = path30.join(PKG_ROOT, "package.json");
7544
+ const pkg = JSON.parse(fs33.readFileSync(pkgPath, "utf-8"));
6552
7545
  return pkg.version;
6553
7546
  }
6554
7547
  var args = process.argv.slice(2);
@@ -6559,6 +7552,8 @@ if (command === "init") {
6559
7552
  bootstrapCommand({ force: args.includes("--force") }, PKG_ROOT);
6560
7553
  } else if (command === "taskify") {
6561
7554
  Promise.resolve().then(() => (init_taskify_command(), taskify_command_exports)).then(({ runTaskifyCommand: runTaskifyCommand2 }) => runTaskifyCommand2());
7555
+ } else if (command === "test-model") {
7556
+ Promise.resolve().then(() => (init_test_model_command(), test_model_command_exports)).then(({ runTestModelCommand: runTestModelCommand2 }) => runTestModelCommand2());
6562
7557
  } else if (command === "ci-parse") {
6563
7558
  Promise.resolve().then(() => (init_parse_inputs(), parse_inputs_exports)).then(({ runCiParse: runCiParse2 }) => runCiParse2());
6564
7559
  } else if (command === "version" || command === "--version" || command === "-v") {