@kody-ade/kody-engine 0.4.88 → 0.4.90

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/kody.js CHANGED
@@ -19,8 +19,8 @@ __export(events_exports, {
19
19
  resolveRunId: () => resolveRunId
20
20
  });
21
21
  import * as crypto from "crypto";
22
- import * as fs3 from "fs";
23
- import * as path3 from "path";
22
+ import * as fs4 from "fs";
23
+ import * as path4 from "path";
24
24
  function resolveRunId() {
25
25
  if (cachedRunId) return cachedRunId;
26
26
  if (process.env.KODY_RUN_ID) {
@@ -49,17 +49,17 @@ function emitEvent(cwd, ev) {
49
49
  runId,
50
50
  ...ev
51
51
  };
52
- const eventsPath2 = path3.join(cwd, ".kody", "runs", runId, "events.jsonl");
53
- fs3.mkdirSync(path3.dirname(eventsPath2), { recursive: true });
54
- fs3.appendFileSync(eventsPath2, `${JSON.stringify(fullEvent)}
52
+ const eventsPath2 = path4.join(cwd, ".kody", "runs", runId, "events.jsonl");
53
+ fs4.mkdirSync(path4.dirname(eventsPath2), { recursive: true });
54
+ fs4.appendFileSync(eventsPath2, `${JSON.stringify(fullEvent)}
55
55
  `);
56
56
  } catch {
57
57
  }
58
58
  }
59
59
  function readEvents(cwd, runId) {
60
- const eventsPath2 = path3.join(cwd, ".kody", "runs", runId, "events.jsonl");
61
- if (!fs3.existsSync(eventsPath2)) return [];
62
- const lines = fs3.readFileSync(eventsPath2, "utf-8").split("\n");
60
+ const eventsPath2 = path4.join(cwd, ".kody", "runs", runId, "events.jsonl");
61
+ if (!fs4.existsSync(eventsPath2)) return [];
62
+ const lines = fs4.readFileSync(eventsPath2, "utf-8").split("\n");
63
63
  const out = [];
64
64
  for (const line of lines) {
65
65
  const trimmed = line.trim();
@@ -72,11 +72,11 @@ function readEvents(cwd, runId) {
72
72
  return out;
73
73
  }
74
74
  function listRuns(cwd) {
75
- const runsDir = path3.join(cwd, ".kody", "runs");
76
- if (!fs3.existsSync(runsDir)) return [];
77
- return fs3.readdirSync(runsDir).filter((name) => {
75
+ const runsDir = path4.join(cwd, ".kody", "runs");
76
+ if (!fs4.existsSync(runsDir)) return [];
77
+ return fs4.readdirSync(runsDir).filter((name) => {
78
78
  try {
79
- return fs3.statSync(path3.join(runsDir, name)).isDirectory();
79
+ return fs4.statSync(path4.join(runsDir, name)).isDirectory();
80
80
  } catch {
81
81
  return false;
82
82
  }
@@ -469,16 +469,16 @@ var init_issue = __esm({
469
469
  });
470
470
 
471
471
  // src/prompt.ts
472
- import * as fs16 from "fs";
473
- import * as path14 from "path";
472
+ import * as fs17 from "fs";
473
+ import * as path15 from "path";
474
474
  function loadProjectConventions(projectDir) {
475
475
  const out = [];
476
476
  for (const rel of CONVENTION_FILES) {
477
- const abs = path14.join(projectDir, rel);
478
- if (!fs16.existsSync(abs)) continue;
477
+ const abs = path15.join(projectDir, rel);
478
+ if (!fs17.existsSync(abs)) continue;
479
479
  let content;
480
480
  try {
481
- content = fs16.readFileSync(abs, "utf-8");
481
+ content = fs17.readFileSync(abs, "utf-8");
482
482
  } catch {
483
483
  continue;
484
484
  }
@@ -626,28 +626,28 @@ var loadMemoryContext_exports = {};
626
626
  __export(loadMemoryContext_exports, {
627
627
  loadMemoryContext: () => loadMemoryContext
628
628
  });
629
- import * as fs30 from "fs";
630
- import * as path28 from "path";
629
+ import * as fs31 from "fs";
630
+ import * as path29 from "path";
631
631
  function collectPages(memoryAbs) {
632
632
  const out = [];
633
633
  walkMd(memoryAbs, (file) => {
634
634
  let stat;
635
635
  try {
636
- stat = fs30.statSync(file);
636
+ stat = fs31.statSync(file);
637
637
  } catch {
638
638
  return;
639
639
  }
640
640
  let raw;
641
641
  try {
642
- raw = fs30.readFileSync(file, "utf-8");
642
+ raw = fs31.readFileSync(file, "utf-8");
643
643
  } catch {
644
644
  return;
645
645
  }
646
646
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
647
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path28.basename(file, ".md");
647
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path29.basename(file, ".md");
648
648
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
649
649
  out.push({
650
- relPath: path28.relative(memoryAbs, file),
650
+ relPath: path29.relative(memoryAbs, file),
651
651
  title,
652
652
  updated,
653
653
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
@@ -715,16 +715,16 @@ function walkMd(root, visit) {
715
715
  const dir = stack.pop();
716
716
  let names;
717
717
  try {
718
- names = fs30.readdirSync(dir);
718
+ names = fs31.readdirSync(dir);
719
719
  } catch {
720
720
  continue;
721
721
  }
722
722
  for (const name of names) {
723
723
  if (name.startsWith(".")) continue;
724
- const full = path28.join(dir, name);
724
+ const full = path29.join(dir, name);
725
725
  let stat;
726
726
  try {
727
- stat = fs30.statSync(full);
727
+ stat = fs31.statSync(full);
728
728
  } catch {
729
729
  continue;
730
730
  }
@@ -747,8 +747,8 @@ var init_loadMemoryContext = __esm({
747
747
  TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
748
748
  loadMemoryContext = async (ctx) => {
749
749
  if (typeof ctx.data.memoryContext === "string") return;
750
- const memoryAbs = path28.join(ctx.cwd, MEMORY_DIR_RELATIVE);
751
- if (!fs30.existsSync(memoryAbs)) {
750
+ const memoryAbs = path29.join(ctx.cwd, MEMORY_DIR_RELATIVE);
751
+ if (!fs31.existsSync(memoryAbs)) {
752
752
  ctx.data.memoryContext = "";
753
753
  return;
754
754
  }
@@ -877,7 +877,7 @@ var init_loadPriorArt = __esm({
877
877
  // package.json
878
878
  var package_default = {
879
879
  name: "@kody-ade/kody-engine",
880
- version: "0.4.88",
880
+ version: "0.4.90",
881
881
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
882
882
  license: "MIT",
883
883
  type: "module",
@@ -932,8 +932,8 @@ var package_default = {
932
932
 
933
933
  // src/chat-cli.ts
934
934
  import { execFileSync as execFileSync31 } from "child_process";
935
- import * as fs36 from "fs";
936
- import * as path33 from "path";
935
+ import * as fs37 from "fs";
936
+ import * as path34 from "path";
937
937
 
938
938
  // src/chat/events.ts
939
939
  import * as fs from "fs";
@@ -999,16 +999,79 @@ function makeRunId(sessionId, suffix) {
999
999
  }
1000
1000
 
1001
1001
  // src/chat/loop.ts
1002
- import * as fs7 from "fs";
1002
+ import * as fs8 from "fs";
1003
1003
 
1004
1004
  // src/agent.ts
1005
- import * as fs4 from "fs";
1006
- import * as path4 from "path";
1005
+ import * as fs5 from "fs";
1006
+ import * as path5 from "path";
1007
1007
  import { query } from "@anthropic-ai/claude-agent-sdk";
1008
1008
 
1009
- // src/config.ts
1009
+ // src/claudeBinary.ts
1010
1010
  import * as fs2 from "fs";
1011
+ import { createRequire } from "module";
1012
+ import * as os from "os";
1011
1013
  import * as path2 from "path";
1014
+ var SDK_PKG = "@anthropic-ai/claude-agent-sdk";
1015
+ function candidateSpecs(platform, arch) {
1016
+ const ext = platform === "win32" ? ".exe" : "";
1017
+ const pkgs = platform === "linux" ? [`${SDK_PKG}-linux-${arch}-musl`, `${SDK_PKG}-linux-${arch}`] : [`${SDK_PKG}-${platform}-${arch}`];
1018
+ return pkgs.map((p) => `${p}/claude${ext}`);
1019
+ }
1020
+ function readSdkVersion(req) {
1021
+ try {
1022
+ const entry = req.resolve(SDK_PKG);
1023
+ const pkgDir = path2.dirname(entry);
1024
+ const raw = fs2.readFileSync(path2.join(pkgDir, "package.json"), "utf8");
1025
+ const v = JSON.parse(raw)?.version;
1026
+ return typeof v === "string" && v.length > 0 ? v : "unknown";
1027
+ } catch {
1028
+ return "unknown";
1029
+ }
1030
+ }
1031
+ var cached;
1032
+ function ensureStableClaudeBinary() {
1033
+ if (cached !== void 0) return cached;
1034
+ try {
1035
+ const req = createRequire(import.meta.url);
1036
+ const sdkEntry = req.resolve(SDK_PKG);
1037
+ const sdkReq = createRequire(sdkEntry);
1038
+ let source = null;
1039
+ for (const spec of candidateSpecs(process.platform, process.arch)) {
1040
+ try {
1041
+ source = sdkReq.resolve(spec);
1042
+ break;
1043
+ } catch {
1044
+ }
1045
+ }
1046
+ if (!source || !fs2.existsSync(source)) {
1047
+ cached = null;
1048
+ return cached;
1049
+ }
1050
+ const ext = process.platform === "win32" ? ".exe" : "";
1051
+ const version = readSdkVersion(req);
1052
+ const destDir = path2.join(os.tmpdir(), "kody-claude-sdk", version);
1053
+ const dest = path2.join(destDir, `claude${ext}`);
1054
+ const srcSize = fs2.statSync(source).size;
1055
+ if (fs2.existsSync(dest) && fs2.statSync(dest).size === srcSize) {
1056
+ cached = dest;
1057
+ return cached;
1058
+ }
1059
+ fs2.mkdirSync(destDir, { recursive: true });
1060
+ const tmp = path2.join(destDir, `.claude.${process.pid}.${Date.now()}.tmp`);
1061
+ fs2.copyFileSync(source, tmp);
1062
+ fs2.chmodSync(tmp, 493);
1063
+ fs2.renameSync(tmp, dest);
1064
+ cached = dest;
1065
+ return cached;
1066
+ } catch {
1067
+ cached = null;
1068
+ return cached;
1069
+ }
1070
+ }
1071
+
1072
+ // src/config.ts
1073
+ import * as fs3 from "fs";
1074
+ import * as path3 from "path";
1012
1075
  var LITELLM_DEFAULT_PORT = 4e3;
1013
1076
  var LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
1014
1077
  function parseProviderModel(s) {
@@ -1026,13 +1089,13 @@ function needsLitellmProxy(model) {
1026
1089
  return model.provider !== "claude" && model.provider !== "anthropic";
1027
1090
  }
1028
1091
  function loadConfig(projectDir = process.cwd()) {
1029
- const configPath = path2.join(projectDir, "kody.config.json");
1030
- if (!fs2.existsSync(configPath)) {
1092
+ const configPath = path3.join(projectDir, "kody.config.json");
1093
+ if (!fs3.existsSync(configPath)) {
1031
1094
  throw new Error(`kody.config.json not found at ${configPath}`);
1032
1095
  }
1033
1096
  let raw;
1034
1097
  try {
1035
- raw = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
1098
+ raw = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1036
1099
  } catch (err) {
1037
1100
  const msg = err instanceof Error ? err.message : String(err);
1038
1101
  throw new Error(`kody.config.json is invalid JSON: ${msg}`);
@@ -1295,10 +1358,10 @@ function resolveTurnTimeoutMs(opts) {
1295
1358
  return DEFAULT_TURN_TIMEOUT_MS;
1296
1359
  }
1297
1360
  async function runAgent(opts) {
1298
- const ndjsonDir = opts.ndjsonDir ?? path4.join(opts.cwd, ".kody");
1299
- fs4.mkdirSync(ndjsonDir, { recursive: true });
1300
- const ndjsonPath = path4.join(ndjsonDir, "last-run.jsonl");
1301
- const fullLog = fs4.createWriteStream(ndjsonPath, { flags: "w" });
1361
+ const ndjsonDir = opts.ndjsonDir ?? path5.join(opts.cwd, ".kody");
1362
+ fs5.mkdirSync(ndjsonDir, { recursive: true });
1363
+ const ndjsonPath = path5.join(ndjsonDir, "last-run.jsonl");
1364
+ const fullLog = fs5.createWriteStream(ndjsonPath, { flags: "w" });
1302
1365
  const env = {
1303
1366
  ...process.env,
1304
1367
  SKIP_HOOKS: "1",
@@ -1382,6 +1445,10 @@ async function runAgent(opts) {
1382
1445
  };
1383
1446
  }
1384
1447
  queryOptions.settingSources = opts.settingSources ?? ["project", "local"];
1448
+ const stableBinary = ensureStableClaudeBinary();
1449
+ if (stableBinary) {
1450
+ queryOptions.pathToClaudeCodeExecutable = stableBinary;
1451
+ }
1385
1452
  const result = query({
1386
1453
  prompt: opts.prompt,
1387
1454
  // biome-ignore lint/suspicious/noExplicitAny: SDK options type is narrow; mcpServers is runtime-passthrough.
@@ -1525,48 +1592,48 @@ async function runAgent(opts) {
1525
1592
  }
1526
1593
 
1527
1594
  // src/registry.ts
1528
- import * as fs5 from "fs";
1529
- import * as path5 from "path";
1595
+ import * as fs6 from "fs";
1596
+ import * as path6 from "path";
1530
1597
  function getExecutablesRoot() {
1531
- const here = path5.dirname(new URL(import.meta.url).pathname);
1598
+ const here = path6.dirname(new URL(import.meta.url).pathname);
1532
1599
  const candidates = [
1533
- path5.join(here, "executables"),
1600
+ path6.join(here, "executables"),
1534
1601
  // dev: src/
1535
- path5.join(here, "..", "executables"),
1602
+ path6.join(here, "..", "executables"),
1536
1603
  // built: dist/bin → dist/executables
1537
- path5.join(here, "..", "src", "executables")
1604
+ path6.join(here, "..", "src", "executables")
1538
1605
  // fallback
1539
1606
  ];
1540
1607
  for (const c of candidates) {
1541
- if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) return c;
1608
+ if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
1542
1609
  }
1543
1610
  return candidates[0];
1544
1611
  }
1545
1612
  function getProjectExecutablesRoot() {
1546
- return path5.join(process.cwd(), ".kody", "executables");
1613
+ return path6.join(process.cwd(), ".kody", "executables");
1547
1614
  }
1548
1615
  function getBuiltinJobsRoot() {
1549
- const here = path5.dirname(new URL(import.meta.url).pathname);
1616
+ const here = path6.dirname(new URL(import.meta.url).pathname);
1550
1617
  const candidates = [
1551
- path5.join(here, "jobs"),
1618
+ path6.join(here, "jobs"),
1552
1619
  // dev: src/
1553
- path5.join(here, "..", "jobs"),
1620
+ path6.join(here, "..", "jobs"),
1554
1621
  // built: dist/bin → dist/jobs
1555
- path5.join(here, "..", "src", "jobs")
1622
+ path6.join(here, "..", "src", "jobs")
1556
1623
  // fallback
1557
1624
  ];
1558
1625
  for (const c of candidates) {
1559
- if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) return c;
1626
+ if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
1560
1627
  }
1561
1628
  return candidates[0];
1562
1629
  }
1563
1630
  function listBuiltinJobs(root = getBuiltinJobsRoot()) {
1564
- if (!fs5.existsSync(root) || !fs5.statSync(root).isDirectory()) return [];
1631
+ if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
1565
1632
  const out = [];
1566
- for (const ent of fs5.readdirSync(root, { withFileTypes: true })) {
1633
+ for (const ent of fs6.readdirSync(root, { withFileTypes: true })) {
1567
1634
  if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
1568
1635
  const slug = ent.name.slice(0, -3);
1569
- out.push({ slug, filePath: path5.join(root, ent.name) });
1636
+ out.push({ slug, filePath: path6.join(root, ent.name) });
1570
1637
  }
1571
1638
  out.sort((a, b) => a.slug.localeCompare(b.slug));
1572
1639
  return out;
@@ -1579,13 +1646,13 @@ function listExecutables(roots = getExecutableRoots()) {
1579
1646
  const seen = /* @__PURE__ */ new Set();
1580
1647
  const out = [];
1581
1648
  for (const root of rootList) {
1582
- if (!fs5.existsSync(root)) continue;
1583
- const entries = fs5.readdirSync(root, { withFileTypes: true });
1649
+ if (!fs6.existsSync(root)) continue;
1650
+ const entries = fs6.readdirSync(root, { withFileTypes: true });
1584
1651
  for (const ent of entries) {
1585
1652
  if (!ent.isDirectory()) continue;
1586
1653
  if (seen.has(ent.name)) continue;
1587
- const profilePath = path5.join(root, ent.name, "profile.json");
1588
- if (fs5.existsSync(profilePath) && fs5.statSync(profilePath).isFile()) {
1654
+ const profilePath = path6.join(root, ent.name, "profile.json");
1655
+ if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
1589
1656
  out.push({ name: ent.name, profilePath });
1590
1657
  seen.add(ent.name);
1591
1658
  }
@@ -1597,8 +1664,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
1597
1664
  if (!isSafeName(name)) return null;
1598
1665
  const rootList = typeof roots === "string" ? [roots] : roots;
1599
1666
  for (const root of rootList) {
1600
- const profilePath = path5.join(root, name, "profile.json");
1601
- if (fs5.existsSync(profilePath) && fs5.statSync(profilePath).isFile()) {
1667
+ const profilePath = path6.join(root, name, "profile.json");
1668
+ if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
1602
1669
  return profilePath;
1603
1670
  }
1604
1671
  }
@@ -1614,7 +1681,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
1614
1681
  const profilePath = resolveExecutable(name, roots);
1615
1682
  if (!profilePath) return null;
1616
1683
  try {
1617
- const raw = JSON.parse(fs5.readFileSync(profilePath, "utf-8"));
1684
+ const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
1618
1685
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
1619
1686
  return raw.inputs;
1620
1687
  } catch {
@@ -1644,14 +1711,14 @@ function parseGenericFlags(argv) {
1644
1711
  }
1645
1712
 
1646
1713
  // src/chat/session.ts
1647
- import * as fs6 from "fs";
1648
- import * as path6 from "path";
1714
+ import * as fs7 from "fs";
1715
+ import * as path7 from "path";
1649
1716
  function sessionFilePath(cwd, sessionId) {
1650
- return path6.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
1717
+ return path7.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
1651
1718
  }
1652
1719
  function readMeta(file) {
1653
- if (!fs6.existsSync(file)) return null;
1654
- const raw = fs6.readFileSync(file, "utf-8");
1720
+ if (!fs7.existsSync(file)) return null;
1721
+ const raw = fs7.readFileSync(file, "utf-8");
1655
1722
  const firstLine2 = raw.split("\n", 1)[0]?.trim();
1656
1723
  if (!firstLine2) return null;
1657
1724
  try {
@@ -1664,8 +1731,8 @@ function readMeta(file) {
1664
1731
  }
1665
1732
  }
1666
1733
  function readSession(file) {
1667
- if (!fs6.existsSync(file)) return [];
1668
- const raw = fs6.readFileSync(file, "utf-8").trim();
1734
+ if (!fs7.existsSync(file)) return [];
1735
+ const raw = fs7.readFileSync(file, "utf-8").trim();
1669
1736
  if (!raw) return [];
1670
1737
  const turns = [];
1671
1738
  for (const line of raw.split("\n")) {
@@ -1681,14 +1748,14 @@ function readSession(file) {
1681
1748
  return turns;
1682
1749
  }
1683
1750
  function appendTurn(file, turn) {
1684
- fs6.mkdirSync(path6.dirname(file), { recursive: true });
1751
+ fs7.mkdirSync(path7.dirname(file), { recursive: true });
1685
1752
  const line = JSON.stringify({
1686
1753
  role: turn.role,
1687
1754
  content: turn.content,
1688
1755
  timestamp: turn.timestamp,
1689
1756
  toolCalls: turn.toolCalls ?? []
1690
1757
  });
1691
- fs6.appendFileSync(file, `${line}
1758
+ fs7.appendFileSync(file, `${line}
1692
1759
  `);
1693
1760
  }
1694
1761
  function seedInitialMessage(file, message) {
@@ -1798,7 +1865,7 @@ function buildExecutableCatalog() {
1798
1865
  const entries = [];
1799
1866
  for (const { name, profilePath } of discovered) {
1800
1867
  try {
1801
- const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
1868
+ const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
1802
1869
  const describe = typeof raw.describe === "string" ? raw.describe : "";
1803
1870
  const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
1804
1871
  entries.push({ name, describe: firstSentence.trim() });
@@ -1922,9 +1989,9 @@ async function emit(sink, type, sessionId, suffix, payload) {
1922
1989
 
1923
1990
  // src/chat/modes/interactive.ts
1924
1991
  import { execFileSync as execFileSync2 } from "child_process";
1925
- import * as fs8 from "fs";
1926
- import * as os from "os";
1927
- import * as path7 from "path";
1992
+ import * as fs9 from "fs";
1993
+ import * as os2 from "os";
1994
+ import * as path8 from "path";
1928
1995
 
1929
1996
  // src/chat/inbox.ts
1930
1997
  import { execFileSync } from "child_process";
@@ -2081,9 +2148,9 @@ function findNextUserTurn(turns, fromIdx) {
2081
2148
  return -1;
2082
2149
  }
2083
2150
  function commitTurn(cwd, sessionId, verbose) {
2084
- const sessionRel = path7.relative(cwd, sessionFilePath(cwd, sessionId));
2085
- const eventsRel = path7.relative(cwd, eventsFilePath(cwd, sessionId));
2086
- const paths = [sessionRel, eventsRel].filter((p) => fs8.existsSync(path7.join(cwd, p)));
2151
+ const sessionRel = path8.relative(cwd, sessionFilePath(cwd, sessionId));
2152
+ const eventsRel = path8.relative(cwd, eventsFilePath(cwd, sessionId));
2153
+ const paths = [sessionRel, eventsRel].filter((p) => fs9.existsSync(path8.join(cwd, p)));
2087
2154
  if (paths.length === 0) return;
2088
2155
  const startBranch = currentBranch2(cwd);
2089
2156
  const eventsBranch = defaultBranch(cwd) ?? "main";
@@ -2093,17 +2160,17 @@ function commitTurn(cwd, sessionId, verbose) {
2093
2160
  }
2094
2161
  const stdio = verbose ? "inherit" : "pipe";
2095
2162
  const exec = (args) => execFileSync2("git", args, { cwd, stdio });
2096
- const worktreeDir = fs8.mkdtempSync(path7.join(os.tmpdir(), "kody-events-"));
2163
+ const worktreeDir = fs9.mkdtempSync(path8.join(os2.tmpdir(), "kody-events-"));
2097
2164
  let worktreeAdded = false;
2098
2165
  try {
2099
2166
  exec(["fetch", "--quiet", "origin", eventsBranch]);
2100
2167
  exec(["worktree", "add", "--detach", "--quiet", worktreeDir, `origin/${eventsBranch}`]);
2101
2168
  worktreeAdded = true;
2102
2169
  for (const rel of paths) {
2103
- const src = path7.join(cwd, rel);
2104
- const dst = path7.join(worktreeDir, rel);
2105
- fs8.mkdirSync(path7.dirname(dst), { recursive: true });
2106
- fs8.copyFileSync(src, dst);
2170
+ const src = path8.join(cwd, rel);
2171
+ const dst = path8.join(worktreeDir, rel);
2172
+ fs9.mkdirSync(path8.dirname(dst), { recursive: true });
2173
+ fs9.copyFileSync(src, dst);
2107
2174
  }
2108
2175
  commitPathsAndPush(worktreeDir, paths, sessionId, verbose, `HEAD:${eventsBranch}`);
2109
2176
  } catch (err) {
@@ -2118,7 +2185,7 @@ function commitTurn(cwd, sessionId, verbose) {
2118
2185
  }
2119
2186
  }
2120
2187
  try {
2121
- fs8.rmSync(worktreeDir, { recursive: true, force: true });
2188
+ fs9.rmSync(worktreeDir, { recursive: true, force: true });
2122
2189
  } catch {
2123
2190
  }
2124
2191
  }
@@ -2214,11 +2281,11 @@ async function emit2(sink, type, sessionId, suffix, payload) {
2214
2281
 
2215
2282
  // src/kody-cli.ts
2216
2283
  import { execFileSync as execFileSync30 } from "child_process";
2217
- import * as fs35 from "fs";
2218
- import * as path32 from "path";
2284
+ import * as fs36 from "fs";
2285
+ import * as path33 from "path";
2219
2286
 
2220
2287
  // src/dispatch.ts
2221
- import * as fs9 from "fs";
2288
+ import * as fs10 from "fs";
2222
2289
 
2223
2290
  // src/cron-match.ts
2224
2291
  var FIELD_BOUNDS = [
@@ -2302,10 +2369,10 @@ function autoDispatch(opts) {
2302
2369
  }
2303
2370
  const eventName = process.env.GITHUB_EVENT_NAME;
2304
2371
  const eventPath = process.env.GITHUB_EVENT_PATH;
2305
- if (!eventName || !eventPath || !fs9.existsSync(eventPath)) return null;
2372
+ if (!eventName || !eventPath || !fs10.existsSync(eventPath)) return null;
2306
2373
  let event = {};
2307
2374
  try {
2308
- event = JSON.parse(fs9.readFileSync(eventPath, "utf-8"));
2375
+ event = JSON.parse(fs10.readFileSync(eventPath, "utf-8"));
2309
2376
  } catch {
2310
2377
  return null;
2311
2378
  }
@@ -2378,7 +2445,7 @@ function autoDispatchTyped(opts) {
2378
2445
  if (legacy) return { kind: "route", ...legacy };
2379
2446
  const eventName = process.env.GITHUB_EVENT_NAME;
2380
2447
  const eventPath = process.env.GITHUB_EVENT_PATH;
2381
- if (!eventName || !eventPath || !fs9.existsSync(eventPath)) {
2448
+ if (!eventName || !eventPath || !fs10.existsSync(eventPath)) {
2382
2449
  return { kind: "silent", reason: "no GHA event context" };
2383
2450
  }
2384
2451
  if (eventName !== "issue_comment") {
@@ -2386,7 +2453,7 @@ function autoDispatchTyped(opts) {
2386
2453
  }
2387
2454
  let event = {};
2388
2455
  try {
2389
- event = JSON.parse(fs9.readFileSync(eventPath, "utf-8"));
2456
+ event = JSON.parse(fs10.readFileSync(eventPath, "utf-8"));
2390
2457
  } catch {
2391
2458
  return { kind: "silent", reason: "GHA event payload unreadable" };
2392
2459
  }
@@ -2420,7 +2487,7 @@ function dispatchScheduledWatches(opts) {
2420
2487
  for (const exe of listExecutables()) {
2421
2488
  let raw;
2422
2489
  try {
2423
- raw = fs9.readFileSync(exe.profilePath, "utf-8");
2490
+ raw = fs10.readFileSync(exe.profilePath, "utf-8");
2424
2491
  } catch {
2425
2492
  continue;
2426
2493
  }
@@ -2537,16 +2604,16 @@ init_issue();
2537
2604
 
2538
2605
  // src/executor.ts
2539
2606
  import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
2540
- import * as fs34 from "fs";
2541
- import * as path31 from "path";
2607
+ import * as fs35 from "fs";
2608
+ import * as path32 from "path";
2542
2609
  init_events();
2543
2610
 
2544
2611
  // src/lifecycleLabels.ts
2545
2612
  init_issue();
2546
2613
 
2547
2614
  // src/profile.ts
2548
- import * as fs10 from "fs";
2549
- import * as path8 from "path";
2615
+ import * as fs11 from "fs";
2616
+ import * as path9 from "path";
2550
2617
 
2551
2618
  // src/profile-error.ts
2552
2619
  var ProfileError = class extends Error {
@@ -2593,6 +2660,7 @@ function prBranchLifecycle(profile, profilePath) {
2593
2660
  ];
2594
2661
  if (cfg.mirrorState) tail.push({ script: "mirrorStateToPr" });
2595
2662
  if (cfg.advance) tail.push({ script: "advanceFlow" });
2663
+ if (cfg.finalize) tail.push({ script: "finalizeTerminal" });
2596
2664
  profile.scripts.postflight = [...beforePostflight, ...profile.scripts.postflight, ...tail];
2597
2665
  }
2598
2666
  function buildContextBundle(context, extras) {
@@ -2654,7 +2722,8 @@ function validateConfig(raw, profilePath) {
2654
2722
  sync: parseBool(raw, profilePath, "sync", true),
2655
2723
  verify: parseBool(raw, profilePath, "verify", true),
2656
2724
  advance: parseBool(raw, profilePath, "advance", true),
2657
- mirrorState: parseBool(raw, profilePath, "mirrorState", false)
2725
+ mirrorState: parseBool(raw, profilePath, "mirrorState", false),
2726
+ finalize: parseBool(raw, profilePath, "finalize", false)
2658
2727
  };
2659
2728
  }
2660
2729
  function parseBool(raw, profilePath, key, def) {
@@ -2712,12 +2781,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
2712
2781
  "preloadContext"
2713
2782
  ]);
2714
2783
  function loadProfile(profilePath) {
2715
- if (!fs10.existsSync(profilePath)) {
2784
+ if (!fs11.existsSync(profilePath)) {
2716
2785
  throw new ProfileError(profilePath, "file not found");
2717
2786
  }
2718
2787
  let raw;
2719
2788
  try {
2720
- raw = JSON.parse(fs10.readFileSync(profilePath, "utf-8"));
2789
+ raw = JSON.parse(fs11.readFileSync(profilePath, "utf-8"));
2721
2790
  } catch (err) {
2722
2791
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
2723
2792
  }
@@ -2728,7 +2797,7 @@ function loadProfile(profilePath) {
2728
2797
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
2729
2798
  if (unknownKeys.length > 0) {
2730
2799
  process.stderr.write(
2731
- `[kody profile] ${path8.basename(path8.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
2800
+ `[kody profile] ${path9.basename(path9.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
2732
2801
  `
2733
2802
  );
2734
2803
  }
@@ -2789,7 +2858,7 @@ function loadProfile(profilePath) {
2789
2858
  // Phase 5 in-process handoff opt-in. Default false; containers
2790
2859
  // flip to true after end-to-end verification.
2791
2860
  preloadContext: r.preloadContext === true,
2792
- dir: path8.dirname(profilePath)
2861
+ dir: path9.dirname(profilePath)
2793
2862
  };
2794
2863
  if (lifecycle) {
2795
2864
  applyLifecycle(profile, profilePath);
@@ -3159,9 +3228,9 @@ function errMsg(err) {
3159
3228
 
3160
3229
  // src/litellm.ts
3161
3230
  import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
3162
- import * as fs11 from "fs";
3163
- import * as os2 from "os";
3164
- import * as path9 from "path";
3231
+ import * as fs12 from "fs";
3232
+ import * as os3 from "os";
3233
+ import * as path10 from "path";
3165
3234
  async function checkLitellmHealth(url) {
3166
3235
  try {
3167
3236
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -3208,20 +3277,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3208
3277
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
3209
3278
  }
3210
3279
  }
3211
- const configPath = path9.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3212
- fs11.writeFileSync(configPath, generateLitellmConfigYaml(model));
3280
+ const configPath = path10.join(os3.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3281
+ fs12.writeFileSync(configPath, generateLitellmConfigYaml(model));
3213
3282
  const portMatch = url.match(/:(\d+)/);
3214
3283
  const port = portMatch ? portMatch[1] : "4000";
3215
3284
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
3216
3285
  const dotenvVars = readDotenvApiKeys(projectDir);
3217
- const logPath = path9.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3218
- const outFd = fs11.openSync(logPath, "w");
3286
+ const logPath = path10.join(os3.tmpdir(), `kody-litellm-${Date.now()}.log`);
3287
+ const outFd = fs12.openSync(logPath, "w");
3219
3288
  const child = spawn2(cmd, args, {
3220
3289
  stdio: ["ignore", outFd, outFd],
3221
3290
  detached: true,
3222
3291
  env: stripBlockingEnv({ ...process.env, ...dotenvVars })
3223
3292
  });
3224
- fs11.closeSync(outFd);
3293
+ fs12.closeSync(outFd);
3225
3294
  const timeoutMs = resolveLitellmTimeoutMs();
3226
3295
  const deadline = Date.now() + timeoutMs;
3227
3296
  while (Date.now() < deadline) {
@@ -3240,7 +3309,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3240
3309
  }
3241
3310
  let logTail = "";
3242
3311
  try {
3243
- logTail = fs11.readFileSync(logPath, "utf-8").slice(-2e3);
3312
+ logTail = fs12.readFileSync(logPath, "utf-8").slice(-2e3);
3244
3313
  } catch {
3245
3314
  }
3246
3315
  try {
@@ -3252,10 +3321,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3252
3321
  ${logTail}`);
3253
3322
  }
3254
3323
  function readDotenvApiKeys(projectDir) {
3255
- const dotenvPath = path9.join(projectDir, ".env");
3256
- if (!fs11.existsSync(dotenvPath)) return {};
3324
+ const dotenvPath = path10.join(projectDir, ".env");
3325
+ if (!fs12.existsSync(dotenvPath)) return {};
3257
3326
  const result = {};
3258
- for (const rawLine of fs11.readFileSync(dotenvPath, "utf-8").split("\n")) {
3327
+ for (const rawLine of fs12.readFileSync(dotenvPath, "utf-8").split("\n")) {
3259
3328
  const line = rawLine.trim();
3260
3329
  if (!line || line.startsWith("#")) continue;
3261
3330
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -3279,8 +3348,8 @@ function stripBlockingEnv(env) {
3279
3348
 
3280
3349
  // src/commit.ts
3281
3350
  import { execFileSync as execFileSync5 } from "child_process";
3282
- import * as fs12 from "fs";
3283
- import * as path10 from "path";
3351
+ import * as fs13 from "fs";
3352
+ import * as path11 from "path";
3284
3353
  var FORBIDDEN_PATH_PREFIXES = [
3285
3354
  ".kody/",
3286
3355
  ".kody-engine/",
@@ -3340,18 +3409,18 @@ function tryGit(args, cwd) {
3340
3409
  }
3341
3410
  function abortUnfinishedGitOps(cwd) {
3342
3411
  const aborted = [];
3343
- const gitDir = path10.join(cwd ?? process.cwd(), ".git");
3344
- if (!fs12.existsSync(gitDir)) return aborted;
3345
- if (fs12.existsSync(path10.join(gitDir, "MERGE_HEAD"))) {
3412
+ const gitDir = path11.join(cwd ?? process.cwd(), ".git");
3413
+ if (!fs13.existsSync(gitDir)) return aborted;
3414
+ if (fs13.existsSync(path11.join(gitDir, "MERGE_HEAD"))) {
3346
3415
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
3347
3416
  }
3348
- if (fs12.existsSync(path10.join(gitDir, "CHERRY_PICK_HEAD"))) {
3417
+ if (fs13.existsSync(path11.join(gitDir, "CHERRY_PICK_HEAD"))) {
3349
3418
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
3350
3419
  }
3351
- if (fs12.existsSync(path10.join(gitDir, "REVERT_HEAD"))) {
3420
+ if (fs13.existsSync(path11.join(gitDir, "REVERT_HEAD"))) {
3352
3421
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
3353
3422
  }
3354
- if (fs12.existsSync(path10.join(gitDir, "rebase-merge")) || fs12.existsSync(path10.join(gitDir, "rebase-apply"))) {
3423
+ if (fs13.existsSync(path11.join(gitDir, "rebase-merge")) || fs13.existsSync(path11.join(gitDir, "rebase-apply"))) {
3355
3424
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
3356
3425
  }
3357
3426
  try {
@@ -3407,7 +3476,7 @@ function normalizeCommitMessage(raw) {
3407
3476
  function commitAndPush(branch, agentMessage, cwd) {
3408
3477
  const allChanged = listChangedFiles(cwd);
3409
3478
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
3410
- const mergeHeadExists = fs12.existsSync(path10.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3479
+ const mergeHeadExists = fs13.existsSync(path11.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3411
3480
  if (allowedFiles.length === 0 && !mergeHeadExists) {
3412
3481
  return { committed: false, pushed: false, sha: "", message: "" };
3413
3482
  }
@@ -3723,20 +3792,20 @@ var advanceFlow = async (ctx, profile) => {
3723
3792
 
3724
3793
  // src/scripts/brainServe.ts
3725
3794
  import { createServer } from "http";
3726
- import * as fs14 from "fs";
3727
- import * as path12 from "path";
3795
+ import * as fs15 from "fs";
3796
+ import * as path13 from "path";
3728
3797
 
3729
3798
  // src/scripts/brainTurnLog.ts
3730
- import * as fs13 from "fs";
3731
- import * as path11 from "path";
3799
+ import * as fs14 from "fs";
3800
+ import * as path12 from "path";
3732
3801
  var live = /* @__PURE__ */ new Map();
3733
3802
  function eventsPath(dir, chatId) {
3734
- return path11.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
3803
+ return path12.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
3735
3804
  }
3736
3805
  function lastPersistedSeq(dir, chatId) {
3737
3806
  const p = eventsPath(dir, chatId);
3738
- if (!fs13.existsSync(p)) return 0;
3739
- const lines = fs13.readFileSync(p, "utf-8").split("\n").filter(Boolean);
3807
+ if (!fs14.existsSync(p)) return 0;
3808
+ const lines = fs14.readFileSync(p, "utf-8").split("\n").filter(Boolean);
3740
3809
  if (lines.length === 0) return 0;
3741
3810
  try {
3742
3811
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -3746,9 +3815,9 @@ function lastPersistedSeq(dir, chatId) {
3746
3815
  }
3747
3816
  function readSince(dir, chatId, since) {
3748
3817
  const p = eventsPath(dir, chatId);
3749
- if (!fs13.existsSync(p)) return [];
3818
+ if (!fs14.existsSync(p)) return [];
3750
3819
  const out = [];
3751
- for (const line of fs13.readFileSync(p, "utf-8").split("\n")) {
3820
+ for (const line of fs14.readFileSync(p, "utf-8").split("\n")) {
3752
3821
  if (!line) continue;
3753
3822
  try {
3754
3823
  const rec = JSON.parse(line);
@@ -3774,12 +3843,12 @@ function beginTurn(dir, chatId) {
3774
3843
  };
3775
3844
  live.set(chatId, state);
3776
3845
  const p = eventsPath(dir, chatId);
3777
- fs13.mkdirSync(path11.dirname(p), { recursive: true });
3846
+ fs14.mkdirSync(path12.dirname(p), { recursive: true });
3778
3847
  return (event) => {
3779
3848
  state.seq += 1;
3780
3849
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
3781
3850
  try {
3782
- fs13.appendFileSync(p, JSON.stringify(rec) + "\n");
3851
+ fs14.appendFileSync(p, JSON.stringify(rec) + "\n");
3783
3852
  } catch (err) {
3784
3853
  process.stderr.write(
3785
3854
  `[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
@@ -3817,7 +3886,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
3817
3886
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
3818
3887
  };
3819
3888
  try {
3820
- fs13.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
3889
+ fs14.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
3821
3890
  } catch {
3822
3891
  }
3823
3892
  state.status = "ended";
@@ -4034,7 +4103,7 @@ async function handleChatTurn(req, res, chatId, opts) {
4034
4103
  return;
4035
4104
  }
4036
4105
  const sessionFile = sessionFilePath(opts.cwd, chatId);
4037
- fs14.mkdirSync(path12.dirname(sessionFile), { recursive: true });
4106
+ fs15.mkdirSync(path13.dirname(sessionFile), { recursive: true });
4038
4107
  appendTurn(sessionFile, {
4039
4108
  role: "user",
4040
4109
  content: message,
@@ -4174,21 +4243,21 @@ var brainServe = async (ctx) => {
4174
4243
  };
4175
4244
 
4176
4245
  // src/scripts/buildSyntheticPlugin.ts
4177
- import * as fs15 from "fs";
4178
- import * as os3 from "os";
4179
- import * as path13 from "path";
4246
+ import * as fs16 from "fs";
4247
+ import * as os4 from "os";
4248
+ import * as path14 from "path";
4180
4249
  function getPluginsCatalogRoot() {
4181
- const here = path13.dirname(new URL(import.meta.url).pathname);
4250
+ const here = path14.dirname(new URL(import.meta.url).pathname);
4182
4251
  const candidates = [
4183
- path13.join(here, "..", "plugins"),
4252
+ path14.join(here, "..", "plugins"),
4184
4253
  // dev: src/scripts → src/plugins
4185
- path13.join(here, "..", "..", "plugins"),
4254
+ path14.join(here, "..", "..", "plugins"),
4186
4255
  // built: dist/scripts → dist/plugins
4187
- path13.join(here, "..", "..", "src", "plugins")
4256
+ path14.join(here, "..", "..", "src", "plugins")
4188
4257
  // fallback
4189
4258
  ];
4190
4259
  for (const c of candidates) {
4191
- if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
4260
+ if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
4192
4261
  }
4193
4262
  return candidates[0];
4194
4263
  }
@@ -4198,52 +4267,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4198
4267
  if (!needsSynthetic) return;
4199
4268
  const catalog = getPluginsCatalogRoot();
4200
4269
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4201
- const root = path13.join(os3.tmpdir(), `kody-synth-${runId}`);
4202
- fs15.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
4270
+ const root = path14.join(os4.tmpdir(), `kody-synth-${runId}`);
4271
+ fs16.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
4203
4272
  const resolvePart = (bucket, entry) => {
4204
- const local = path13.join(profile.dir, bucket, entry);
4205
- if (fs15.existsSync(local)) return local;
4206
- const central = path13.join(catalog, bucket, entry);
4207
- if (fs15.existsSync(central)) return central;
4273
+ const local = path14.join(profile.dir, bucket, entry);
4274
+ if (fs16.existsSync(local)) return local;
4275
+ const central = path14.join(catalog, bucket, entry);
4276
+ if (fs16.existsSync(central)) return central;
4208
4277
  throw new Error(
4209
4278
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
4210
4279
  );
4211
4280
  };
4212
4281
  if (cc.skills.length > 0) {
4213
- const dst = path13.join(root, "skills");
4214
- fs15.mkdirSync(dst, { recursive: true });
4282
+ const dst = path14.join(root, "skills");
4283
+ fs16.mkdirSync(dst, { recursive: true });
4215
4284
  for (const name of cc.skills) {
4216
- copyDir(resolvePart("skills", name), path13.join(dst, name));
4285
+ copyDir(resolvePart("skills", name), path14.join(dst, name));
4217
4286
  }
4218
4287
  }
4219
4288
  if (cc.commands.length > 0) {
4220
- const dst = path13.join(root, "commands");
4221
- fs15.mkdirSync(dst, { recursive: true });
4289
+ const dst = path14.join(root, "commands");
4290
+ fs16.mkdirSync(dst, { recursive: true });
4222
4291
  for (const name of cc.commands) {
4223
- fs15.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
4292
+ fs16.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
4224
4293
  }
4225
4294
  }
4226
4295
  if (cc.subagents.length > 0) {
4227
- const dst = path13.join(root, "agents");
4228
- fs15.mkdirSync(dst, { recursive: true });
4296
+ const dst = path14.join(root, "agents");
4297
+ fs16.mkdirSync(dst, { recursive: true });
4229
4298
  for (const name of cc.subagents) {
4230
- fs15.copyFileSync(resolvePart("agents", `${name}.md`), path13.join(dst, `${name}.md`));
4299
+ fs16.copyFileSync(resolvePart("agents", `${name}.md`), path14.join(dst, `${name}.md`));
4231
4300
  }
4232
4301
  }
4233
4302
  if (cc.hooks.length > 0) {
4234
- const dst = path13.join(root, "hooks");
4235
- fs15.mkdirSync(dst, { recursive: true });
4303
+ const dst = path14.join(root, "hooks");
4304
+ fs16.mkdirSync(dst, { recursive: true });
4236
4305
  const merged = { hooks: {} };
4237
4306
  for (const name of cc.hooks) {
4238
4307
  const src = resolvePart("hooks", `${name}.json`);
4239
- const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
4308
+ const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
4240
4309
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
4241
4310
  if (!Array.isArray(entries)) continue;
4242
4311
  if (!merged.hooks[event]) merged.hooks[event] = [];
4243
4312
  merged.hooks[event].push(...entries);
4244
4313
  }
4245
4314
  }
4246
- fs15.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4315
+ fs16.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4247
4316
  `);
4248
4317
  }
4249
4318
  const manifest = {
@@ -4254,17 +4323,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4254
4323
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
4255
4324
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
4256
4325
  if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
4257
- fs15.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
4326
+ fs16.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
4258
4327
  `);
4259
4328
  ctx.data.syntheticPluginPath = root;
4260
4329
  };
4261
4330
  function copyDir(src, dst) {
4262
- fs15.mkdirSync(dst, { recursive: true });
4263
- for (const ent of fs15.readdirSync(src, { withFileTypes: true })) {
4264
- const s = path13.join(src, ent.name);
4265
- const d = path13.join(dst, ent.name);
4331
+ fs16.mkdirSync(dst, { recursive: true });
4332
+ for (const ent of fs16.readdirSync(src, { withFileTypes: true })) {
4333
+ const s = path14.join(src, ent.name);
4334
+ const d = path14.join(dst, ent.name);
4266
4335
  if (ent.isDirectory()) copyDir(s, d);
4267
- else if (ent.isFile()) fs15.copyFileSync(s, d);
4336
+ else if (ent.isFile()) fs16.copyFileSync(s, d);
4268
4337
  }
4269
4338
  }
4270
4339
 
@@ -4405,13 +4474,13 @@ function defaultLabelMap() {
4405
4474
  }
4406
4475
 
4407
4476
  // src/scripts/commitAndPush.ts
4408
- import * as fs17 from "fs";
4409
- import * as path15 from "path";
4477
+ import * as fs18 from "fs";
4478
+ import * as path16 from "path";
4410
4479
  init_events();
4411
4480
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
4412
4481
  function sentinelPathForStage(cwd, profileName) {
4413
4482
  const runId = resolveRunId();
4414
- return path15.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4483
+ return path16.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4415
4484
  }
4416
4485
  var commitAndPush2 = async (ctx, profile) => {
4417
4486
  const branch = ctx.data.branch;
@@ -4421,9 +4490,9 @@ var commitAndPush2 = async (ctx, profile) => {
4421
4490
  }
4422
4491
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
4423
4492
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
4424
- if (sentinel && fs17.existsSync(sentinel)) {
4493
+ if (sentinel && fs18.existsSync(sentinel)) {
4425
4494
  try {
4426
- const replay = JSON.parse(fs17.readFileSync(sentinel, "utf-8"));
4495
+ const replay = JSON.parse(fs18.readFileSync(sentinel, "utf-8"));
4427
4496
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
4428
4497
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
4429
4498
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -4476,8 +4545,8 @@ var commitAndPush2 = async (ctx, profile) => {
4476
4545
  const result = ctx.data.commitResult;
4477
4546
  if (sentinel && result?.committed) {
4478
4547
  try {
4479
- fs17.mkdirSync(path15.dirname(sentinel), { recursive: true });
4480
- fs17.writeFileSync(
4548
+ fs18.mkdirSync(path16.dirname(sentinel), { recursive: true });
4549
+ fs18.writeFileSync(
4481
4550
  sentinel,
4482
4551
  JSON.stringify(
4483
4552
  {
@@ -4498,11 +4567,11 @@ var commitAndPush2 = async (ctx, profile) => {
4498
4567
 
4499
4568
  // src/scripts/commitGoalState.ts
4500
4569
  import { execFileSync as execFileSync9 } from "child_process";
4501
- import * as path16 from "path";
4570
+ import * as path17 from "path";
4502
4571
  var commitGoalState = async (ctx) => {
4503
4572
  const goal = ctx.data.goal;
4504
4573
  if (!goal) return;
4505
- const stateRel = path16.posix.join(".kody", "goals", goal.id, "state.json");
4574
+ const stateRel = path17.posix.join(".kody", "goals", goal.id, "state.json");
4506
4575
  try {
4507
4576
  execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
4508
4577
  } catch (err) {
@@ -4547,20 +4616,20 @@ function describeCommitMessage(goal) {
4547
4616
  }
4548
4617
 
4549
4618
  // src/scripts/composePrompt.ts
4550
- import * as fs18 from "fs";
4551
- import * as path17 from "path";
4619
+ import * as fs19 from "fs";
4620
+ import * as path18 from "path";
4552
4621
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
4553
4622
  var composePrompt = async (ctx, profile) => {
4554
4623
  const explicit = ctx.data.promptTemplate;
4555
4624
  const mode = ctx.args.mode;
4556
4625
  const candidates = [
4557
- explicit ? path17.join(profile.dir, explicit) : null,
4558
- mode ? path17.join(profile.dir, "prompts", `${mode}.md`) : null,
4559
- path17.join(profile.dir, "prompt.md")
4626
+ explicit ? path18.join(profile.dir, explicit) : null,
4627
+ mode ? path18.join(profile.dir, "prompts", `${mode}.md`) : null,
4628
+ path18.join(profile.dir, "prompt.md")
4560
4629
  ].filter(Boolean);
4561
4630
  let templatePath = "";
4562
4631
  for (const c of candidates) {
4563
- if (fs18.existsSync(c)) {
4632
+ if (fs19.existsSync(c)) {
4564
4633
  templatePath = c;
4565
4634
  break;
4566
4635
  }
@@ -4568,7 +4637,7 @@ var composePrompt = async (ctx, profile) => {
4568
4637
  if (!templatePath) {
4569
4638
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
4570
4639
  }
4571
- const template = fs18.readFileSync(templatePath, "utf-8");
4640
+ const template = fs19.readFileSync(templatePath, "utf-8");
4572
4641
  const tokens = {
4573
4642
  ...stringifyAll(ctx.args, "args."),
4574
4643
  ...stringifyAll(ctx.data, ""),
@@ -4647,8 +4716,8 @@ function formatToolsUsage(profile) {
4647
4716
  // src/scripts/createQaGoal.ts
4648
4717
  init_issue();
4649
4718
  import { execFileSync as execFileSync10 } from "child_process";
4650
- import * as fs19 from "fs";
4651
- import * as path18 from "path";
4719
+ import * as fs20 from "fs";
4720
+ import * as path19 from "path";
4652
4721
 
4653
4722
  // src/scripts/postReviewResult.ts
4654
4723
  init_issue();
@@ -4901,8 +4970,8 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
4901
4970
  return { number: Number(m[1]), created: true };
4902
4971
  }
4903
4972
  function writeStateFile(cwd, goalId, lastDispatchedIssue) {
4904
- const dir = path18.join(cwd, ".kody", "goals", goalId);
4905
- fs19.mkdirSync(dir, { recursive: true });
4973
+ const dir = path19.join(cwd, ".kody", "goals", goalId);
4974
+ fs20.mkdirSync(dir, { recursive: true });
4906
4975
  const state = {
4907
4976
  version: 1,
4908
4977
  state: "active",
@@ -4910,8 +4979,8 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
4910
4979
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4911
4980
  ...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
4912
4981
  };
4913
- const filePath = path18.join(dir, "state.json");
4914
- fs19.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
4982
+ const filePath = path19.join(dir, "state.json");
4983
+ fs20.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
4915
4984
  `);
4916
4985
  return filePath;
4917
4986
  }
@@ -5420,15 +5489,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
5420
5489
 
5421
5490
  // src/scripts/diagMcp.ts
5422
5491
  import { execFileSync as execFileSync11 } from "child_process";
5423
- import * as fs20 from "fs";
5424
- import * as os4 from "os";
5425
- import * as path19 from "path";
5492
+ import * as fs21 from "fs";
5493
+ import * as os5 from "os";
5494
+ import * as path20 from "path";
5426
5495
  var diagMcp = async (_ctx) => {
5427
- const home = os4.homedir();
5428
- const cacheDir = path19.join(home, ".cache", "ms-playwright");
5496
+ const home = os5.homedir();
5497
+ const cacheDir = path20.join(home, ".cache", "ms-playwright");
5429
5498
  let entries = [];
5430
5499
  try {
5431
- entries = fs20.readdirSync(cacheDir);
5500
+ entries = fs21.readdirSync(cacheDir);
5432
5501
  } catch {
5433
5502
  }
5434
5503
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -5454,17 +5523,17 @@ var diagMcp = async (_ctx) => {
5454
5523
  };
5455
5524
 
5456
5525
  // src/scripts/discoverQaContext.ts
5457
- import * as fs22 from "fs";
5458
- import * as path21 from "path";
5526
+ import * as fs23 from "fs";
5527
+ import * as path22 from "path";
5459
5528
 
5460
5529
  // src/scripts/frameworkDetectors.ts
5461
- import * as fs21 from "fs";
5462
- import * as path20 from "path";
5530
+ import * as fs22 from "fs";
5531
+ import * as path21 from "path";
5463
5532
  function detectFrameworks(cwd) {
5464
5533
  const out = [];
5465
5534
  let deps = {};
5466
5535
  try {
5467
- const pkg = JSON.parse(fs21.readFileSync(path20.join(cwd, "package.json"), "utf-8"));
5536
+ const pkg = JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
5468
5537
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
5469
5538
  } catch {
5470
5539
  return out;
@@ -5501,7 +5570,7 @@ function detectFrameworks(cwd) {
5501
5570
  }
5502
5571
  function findFile(cwd, candidates) {
5503
5572
  for (const c of candidates) {
5504
- if (fs21.existsSync(path20.join(cwd, c))) return c;
5573
+ if (fs22.existsSync(path21.join(cwd, c))) return c;
5505
5574
  }
5506
5575
  return null;
5507
5576
  }
@@ -5514,18 +5583,18 @@ var COLLECTION_DIRS = [
5514
5583
  function discoverPayloadCollections(cwd) {
5515
5584
  const out = [];
5516
5585
  for (const dir of COLLECTION_DIRS) {
5517
- const full = path20.join(cwd, dir);
5518
- if (!fs21.existsSync(full)) continue;
5586
+ const full = path21.join(cwd, dir);
5587
+ if (!fs22.existsSync(full)) continue;
5519
5588
  let files;
5520
5589
  try {
5521
- files = fs21.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5590
+ files = fs22.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5522
5591
  } catch {
5523
5592
  continue;
5524
5593
  }
5525
5594
  for (const file of files) {
5526
5595
  try {
5527
- const filePath = path20.join(full, file);
5528
- const content = fs21.readFileSync(filePath, "utf-8").slice(0, 1e4);
5596
+ const filePath = path21.join(full, file);
5597
+ const content = fs22.readFileSync(filePath, "utf-8").slice(0, 1e4);
5529
5598
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
5530
5599
  if (!slugMatch) continue;
5531
5600
  const slug = slugMatch[1];
@@ -5539,7 +5608,7 @@ function discoverPayloadCollections(cwd) {
5539
5608
  out.push({
5540
5609
  name,
5541
5610
  slug,
5542
- filePath: path20.relative(cwd, filePath),
5611
+ filePath: path21.relative(cwd, filePath),
5543
5612
  fields: fields.slice(0, 20),
5544
5613
  hasAdmin
5545
5614
  });
@@ -5553,28 +5622,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
5553
5622
  function discoverAdminComponents(cwd, collections) {
5554
5623
  const out = [];
5555
5624
  for (const dir of ADMIN_COMPONENT_DIRS) {
5556
- const full = path20.join(cwd, dir);
5557
- if (!fs21.existsSync(full)) continue;
5625
+ const full = path21.join(cwd, dir);
5626
+ if (!fs22.existsSync(full)) continue;
5558
5627
  let entries;
5559
5628
  try {
5560
- entries = fs21.readdirSync(full, { withFileTypes: true });
5629
+ entries = fs22.readdirSync(full, { withFileTypes: true });
5561
5630
  } catch {
5562
5631
  continue;
5563
5632
  }
5564
5633
  for (const entry of entries) {
5565
- const entryPath = path20.join(full, entry.name);
5634
+ const entryPath = path21.join(full, entry.name);
5566
5635
  let name;
5567
5636
  let filePath;
5568
5637
  if (entry.isDirectory()) {
5569
5638
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
5570
- (f) => fs21.existsSync(path20.join(entryPath, f))
5639
+ (f) => fs22.existsSync(path21.join(entryPath, f))
5571
5640
  );
5572
5641
  if (!indexFile) continue;
5573
5642
  name = entry.name;
5574
- filePath = path20.relative(cwd, path20.join(entryPath, indexFile));
5643
+ filePath = path21.relative(cwd, path21.join(entryPath, indexFile));
5575
5644
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
5576
5645
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
5577
- filePath = path20.relative(cwd, entryPath);
5646
+ filePath = path21.relative(cwd, entryPath);
5578
5647
  } else {
5579
5648
  continue;
5580
5649
  }
@@ -5582,7 +5651,7 @@ function discoverAdminComponents(cwd, collections) {
5582
5651
  if (collections) {
5583
5652
  for (const col of collections) {
5584
5653
  try {
5585
- const colContent = fs21.readFileSync(path20.join(cwd, col.filePath), "utf-8");
5654
+ const colContent = fs22.readFileSync(path21.join(cwd, col.filePath), "utf-8");
5586
5655
  if (colContent.includes(name)) {
5587
5656
  usedInCollection = col.slug;
5588
5657
  break;
@@ -5601,8 +5670,8 @@ function scanApiRoutes(cwd) {
5601
5670
  const out = [];
5602
5671
  const appDirs = ["src/app", "app"];
5603
5672
  for (const appDir of appDirs) {
5604
- const apiDir = path20.join(cwd, appDir, "api");
5605
- if (!fs21.existsSync(apiDir)) continue;
5673
+ const apiDir = path21.join(cwd, appDir, "api");
5674
+ if (!fs22.existsSync(apiDir)) continue;
5606
5675
  walkApiRoutes(apiDir, "/api", cwd, out);
5607
5676
  break;
5608
5677
  }
@@ -5611,14 +5680,14 @@ function scanApiRoutes(cwd) {
5611
5680
  function walkApiRoutes(dir, prefix, cwd, out) {
5612
5681
  let entries;
5613
5682
  try {
5614
- entries = fs21.readdirSync(dir, { withFileTypes: true });
5683
+ entries = fs22.readdirSync(dir, { withFileTypes: true });
5615
5684
  } catch {
5616
5685
  return;
5617
5686
  }
5618
5687
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
5619
5688
  if (routeFile) {
5620
5689
  try {
5621
- const content = fs21.readFileSync(path20.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
5690
+ const content = fs22.readFileSync(path21.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
5622
5691
  const methods = HTTP_METHODS.filter(
5623
5692
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
5624
5693
  );
@@ -5626,7 +5695,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5626
5695
  out.push({
5627
5696
  path: prefix,
5628
5697
  methods,
5629
- filePath: path20.relative(cwd, path20.join(dir, routeFile.name))
5698
+ filePath: path21.relative(cwd, path21.join(dir, routeFile.name))
5630
5699
  });
5631
5700
  }
5632
5701
  } catch {
@@ -5637,7 +5706,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5637
5706
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5638
5707
  let segment = entry.name;
5639
5708
  if (segment.startsWith("(") && segment.endsWith(")")) {
5640
- walkApiRoutes(path20.join(dir, entry.name), prefix, cwd, out);
5709
+ walkApiRoutes(path21.join(dir, entry.name), prefix, cwd, out);
5641
5710
  continue;
5642
5711
  }
5643
5712
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -5645,7 +5714,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5645
5714
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
5646
5715
  segment = `:${segment.slice(1, -1)}`;
5647
5716
  }
5648
- walkApiRoutes(path20.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
5717
+ walkApiRoutes(path21.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
5649
5718
  }
5650
5719
  }
5651
5720
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -5665,10 +5734,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
5665
5734
  function scanEnvVars(cwd) {
5666
5735
  const candidates = [".env.example", ".env.local.example", ".env.template"];
5667
5736
  for (const envFile of candidates) {
5668
- const envPath = path20.join(cwd, envFile);
5669
- if (!fs21.existsSync(envPath)) continue;
5737
+ const envPath = path21.join(cwd, envFile);
5738
+ if (!fs22.existsSync(envPath)) continue;
5670
5739
  try {
5671
- const content = fs21.readFileSync(envPath, "utf-8");
5740
+ const content = fs22.readFileSync(envPath, "utf-8");
5672
5741
  const vars = [];
5673
5742
  for (const line of content.split("\n")) {
5674
5743
  const trimmed = line.trim();
@@ -5716,9 +5785,9 @@ function runQaDiscovery(cwd) {
5716
5785
  }
5717
5786
  function detectDevServer(cwd, out) {
5718
5787
  try {
5719
- const pkg = JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
5788
+ const pkg = JSON.parse(fs23.readFileSync(path22.join(cwd, "package.json"), "utf-8"));
5720
5789
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
5721
- const pm = fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs22.existsSync(path21.join(cwd, "yarn.lock")) ? "yarn" : fs22.existsSync(path21.join(cwd, "bun.lockb")) ? "bun" : "npm";
5790
+ const pm = fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs23.existsSync(path22.join(cwd, "yarn.lock")) ? "yarn" : fs23.existsSync(path22.join(cwd, "bun.lockb")) ? "bun" : "npm";
5722
5791
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
5723
5792
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
5724
5793
  else if (allDeps.vite) out.devPort = 5173;
@@ -5728,8 +5797,8 @@ function detectDevServer(cwd, out) {
5728
5797
  function scanFrontendRoutes(cwd, out) {
5729
5798
  const appDirs = ["src/app", "app"];
5730
5799
  for (const appDir of appDirs) {
5731
- const full = path21.join(cwd, appDir);
5732
- if (!fs22.existsSync(full)) continue;
5800
+ const full = path22.join(cwd, appDir);
5801
+ if (!fs23.existsSync(full)) continue;
5733
5802
  walkFrontendRoutes(full, "", out);
5734
5803
  break;
5735
5804
  }
@@ -5737,7 +5806,7 @@ function scanFrontendRoutes(cwd, out) {
5737
5806
  function walkFrontendRoutes(dir, prefix, out) {
5738
5807
  let entries;
5739
5808
  try {
5740
- entries = fs22.readdirSync(dir, { withFileTypes: true });
5809
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
5741
5810
  } catch {
5742
5811
  return;
5743
5812
  }
@@ -5754,7 +5823,7 @@ function walkFrontendRoutes(dir, prefix, out) {
5754
5823
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5755
5824
  let segment = entry.name;
5756
5825
  if (segment.startsWith("(") && segment.endsWith(")")) {
5757
- walkFrontendRoutes(path21.join(dir, entry.name), prefix, out);
5826
+ walkFrontendRoutes(path22.join(dir, entry.name), prefix, out);
5758
5827
  continue;
5759
5828
  }
5760
5829
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -5762,7 +5831,7 @@ function walkFrontendRoutes(dir, prefix, out) {
5762
5831
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
5763
5832
  segment = `:${segment.slice(1, -1)}`;
5764
5833
  }
5765
- walkFrontendRoutes(path21.join(dir, entry.name), `${prefix}/${segment}`, out);
5834
+ walkFrontendRoutes(path22.join(dir, entry.name), `${prefix}/${segment}`, out);
5766
5835
  }
5767
5836
  }
5768
5837
  function detectAuthFiles(cwd, out) {
@@ -5779,23 +5848,23 @@ function detectAuthFiles(cwd, out) {
5779
5848
  "src/app/api/oauth"
5780
5849
  ];
5781
5850
  for (const c of candidates) {
5782
- if (fs22.existsSync(path21.join(cwd, c))) out.authFiles.push(c);
5851
+ if (fs23.existsSync(path22.join(cwd, c))) out.authFiles.push(c);
5783
5852
  }
5784
5853
  }
5785
5854
  function detectRoles(cwd, out) {
5786
5855
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
5787
5856
  for (const rp of rolePaths) {
5788
- const dir = path21.join(cwd, rp);
5789
- if (!fs22.existsSync(dir)) continue;
5857
+ const dir = path22.join(cwd, rp);
5858
+ if (!fs23.existsSync(dir)) continue;
5790
5859
  let files;
5791
5860
  try {
5792
- files = fs22.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5861
+ files = fs23.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5793
5862
  } catch {
5794
5863
  continue;
5795
5864
  }
5796
5865
  for (const f of files) {
5797
5866
  try {
5798
- const content = fs22.readFileSync(path21.join(dir, f), "utf-8").slice(0, 5e3);
5867
+ const content = fs23.readFileSync(path22.join(dir, f), "utf-8").slice(0, 5e3);
5799
5868
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
5800
5869
  if (roleMatches) {
5801
5870
  for (const m of roleMatches) {
@@ -6028,8 +6097,8 @@ function failedAction3(reason) {
6028
6097
  }
6029
6098
 
6030
6099
  // src/scripts/dispatchJobFileTicks.ts
6031
- import * as fs24 from "fs";
6032
- import * as path23 from "path";
6100
+ import * as fs25 from "fs";
6101
+ import * as path24 from "path";
6033
6102
 
6034
6103
  // src/scripts/jobFrontmatter.ts
6035
6104
  var SCHEDULE_EVERY_VALUES = [
@@ -6288,8 +6357,8 @@ var ContentsApiBackend = class {
6288
6357
  };
6289
6358
 
6290
6359
  // src/scripts/jobState/localFileBackend.ts
6291
- import * as fs23 from "fs";
6292
- import * as path22 from "path";
6360
+ import * as fs24 from "fs";
6361
+ import * as path23 from "path";
6293
6362
  var LocalFileBackend = class {
6294
6363
  name = "local-file";
6295
6364
  cwd;
@@ -6304,7 +6373,7 @@ var LocalFileBackend = class {
6304
6373
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
6305
6374
  this.cwd = opts.cwd;
6306
6375
  this.jobsDir = opts.jobsDir;
6307
- this.absDir = path22.join(opts.cwd, opts.jobsDir);
6376
+ this.absDir = path23.join(opts.cwd, opts.jobsDir);
6308
6377
  this.owner = opts.owner;
6309
6378
  this.repo = opts.repo;
6310
6379
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -6319,7 +6388,7 @@ var LocalFileBackend = class {
6319
6388
  `);
6320
6389
  return;
6321
6390
  }
6322
- fs23.mkdirSync(this.absDir, { recursive: true });
6391
+ fs24.mkdirSync(this.absDir, { recursive: true });
6323
6392
  const prefix = this.cacheKeyPrefix();
6324
6393
  const probeKey = `${prefix}probe-${Date.now()}`;
6325
6394
  try {
@@ -6348,7 +6417,7 @@ var LocalFileBackend = class {
6348
6417
  `);
6349
6418
  return;
6350
6419
  }
6351
- if (!fs23.existsSync(this.absDir)) {
6420
+ if (!fs24.existsSync(this.absDir)) {
6352
6421
  return;
6353
6422
  }
6354
6423
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -6364,11 +6433,11 @@ var LocalFileBackend = class {
6364
6433
  }
6365
6434
  load(slug) {
6366
6435
  const relPath = stateFilePath(this.jobsDir, slug);
6367
- const absPath = path22.join(this.cwd, relPath);
6368
- if (!fs23.existsSync(absPath)) {
6436
+ const absPath = path23.join(this.cwd, relPath);
6437
+ if (!fs24.existsSync(absPath)) {
6369
6438
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
6370
6439
  }
6371
- const raw = fs23.readFileSync(absPath, "utf-8");
6440
+ const raw = fs24.readFileSync(absPath, "utf-8");
6372
6441
  let parsed;
6373
6442
  try {
6374
6443
  parsed = JSON.parse(raw);
@@ -6385,10 +6454,10 @@ var LocalFileBackend = class {
6385
6454
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
6386
6455
  return false;
6387
6456
  }
6388
- const absPath = path22.join(this.cwd, loaded.path);
6389
- fs23.mkdirSync(path22.dirname(absPath), { recursive: true });
6457
+ const absPath = path23.join(this.cwd, loaded.path);
6458
+ fs24.mkdirSync(path23.dirname(absPath), { recursive: true });
6390
6459
  const body = JSON.stringify(next, null, 2) + "\n";
6391
- fs23.writeFileSync(absPath, body, "utf-8");
6460
+ fs24.writeFileSync(absPath, body, "utf-8");
6392
6461
  return true;
6393
6462
  }
6394
6463
  cacheKeyPrefix() {
@@ -6466,7 +6535,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
6466
6535
  await backend.hydrate();
6467
6536
  }
6468
6537
  try {
6469
- const slugs = listJobSlugs(path23.join(ctx.cwd, jobsDir));
6538
+ const slugs = listJobSlugs(path24.join(ctx.cwd, jobsDir));
6470
6539
  ctx.data.jobSlugCount = slugs.length;
6471
6540
  if (slugs.length === 0) {
6472
6541
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -6571,17 +6640,17 @@ function formatAgo(ms) {
6571
6640
  }
6572
6641
  function readJobFrontmatter(cwd, jobsDir, slug) {
6573
6642
  try {
6574
- const raw = fs24.readFileSync(path23.join(cwd, jobsDir, `${slug}.md`), "utf-8");
6643
+ const raw = fs25.readFileSync(path24.join(cwd, jobsDir, `${slug}.md`), "utf-8");
6575
6644
  return splitFrontmatter(raw).frontmatter;
6576
6645
  } catch {
6577
6646
  return {};
6578
6647
  }
6579
6648
  }
6580
6649
  function listJobSlugs(absDir) {
6581
- if (!fs24.existsSync(absDir)) return [];
6650
+ if (!fs25.existsSync(absDir)) return [];
6582
6651
  let entries;
6583
6652
  try {
6584
- entries = fs24.readdirSync(absDir, { withFileTypes: true });
6653
+ entries = fs25.readdirSync(absDir, { withFileTypes: true });
6585
6654
  } catch {
6586
6655
  return [];
6587
6656
  }
@@ -7049,6 +7118,50 @@ var finalizeGoal = async (ctx) => {
7049
7118
  goal.state = "done";
7050
7119
  };
7051
7120
 
7121
+ // src/scripts/finalizeTerminal.ts
7122
+ init_issue();
7123
+ var DONE = {
7124
+ label: "kody:done",
7125
+ color: "0e8a16",
7126
+ description: "kody: PR ready for human review/merge"
7127
+ };
7128
+ var FAILED = {
7129
+ label: "kody:failed",
7130
+ color: "e11d21",
7131
+ description: "kody: flow failed"
7132
+ };
7133
+ var finalizeTerminal = async (ctx) => {
7134
+ const target = ctx.data.commentTargetType ?? "issue";
7135
+ const issueNumber = ctx.args.issue;
7136
+ const targetNumber = ctx.data.commentTargetNumber ?? issueNumber;
7137
+ if (!targetNumber) return;
7138
+ let prUrl;
7139
+ try {
7140
+ prUrl = readTaskState(target, targetNumber, ctx.cwd).core.prUrl;
7141
+ } catch {
7142
+ prUrl = void 0;
7143
+ }
7144
+ const delivered = ctx.output.exitCode === 0 && !!prUrl;
7145
+ const spec = delivered ? DONE : FAILED;
7146
+ const phase = delivered ? "shipped" : "failed";
7147
+ const status = delivered ? "succeeded" : "failed";
7148
+ if (issueNumber) setKodyLabel(issueNumber, spec, ctx.cwd);
7149
+ const prNumber = prUrl ? parsePrNumber(prUrl) : null;
7150
+ if (prNumber && prNumber !== issueNumber) setKodyLabel(prNumber, spec, ctx.cwd);
7151
+ try {
7152
+ const state = readTaskState(target, targetNumber, ctx.cwd);
7153
+ state.core.phase = phase;
7154
+ state.core.status = status;
7155
+ state.core.currentExecutable = null;
7156
+ writeTaskState(target, targetNumber, state, ctx.cwd);
7157
+ } catch (err) {
7158
+ process.stderr.write(
7159
+ `[kody finalizeTerminal] failed to write terminal state on ${target} #${targetNumber}: ${err instanceof Error ? err.message : String(err)}
7160
+ `
7161
+ );
7162
+ }
7163
+ };
7164
+
7052
7165
  // src/scripts/finishFlow.ts
7053
7166
  init_issue();
7054
7167
  import { execFileSync as execFileSync14 } from "child_process";
@@ -7290,7 +7403,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
7290
7403
 
7291
7404
  // src/gha.ts
7292
7405
  import { execFileSync as execFileSync16 } from "child_process";
7293
- import * as fs25 from "fs";
7406
+ import * as fs26 from "fs";
7294
7407
  function getRunUrl() {
7295
7408
  const server = process.env.GITHUB_SERVER_URL;
7296
7409
  const repo = process.env.GITHUB_REPOSITORY;
@@ -7301,10 +7414,10 @@ function getRunUrl() {
7301
7414
  function reactToTriggerComment(cwd) {
7302
7415
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
7303
7416
  const eventPath = process.env.GITHUB_EVENT_PATH;
7304
- if (!eventPath || !fs25.existsSync(eventPath)) return;
7417
+ if (!eventPath || !fs26.existsSync(eventPath)) return;
7305
7418
  let event = null;
7306
7419
  try {
7307
- event = JSON.parse(fs25.readFileSync(eventPath, "utf-8"));
7420
+ event = JSON.parse(fs26.readFileSync(eventPath, "utf-8"));
7308
7421
  } catch {
7309
7422
  return;
7310
7423
  }
@@ -7597,22 +7710,22 @@ var handleAbandonedGoal = async (ctx) => {
7597
7710
 
7598
7711
  // src/scripts/initFlow.ts
7599
7712
  import { execFileSync as execFileSync18 } from "child_process";
7600
- import * as fs27 from "fs";
7601
- import * as path25 from "path";
7713
+ import * as fs28 from "fs";
7714
+ import * as path26 from "path";
7602
7715
 
7603
7716
  // src/scripts/loadQaGuide.ts
7604
- import * as fs26 from "fs";
7605
- import * as path24 from "path";
7717
+ import * as fs27 from "fs";
7718
+ import * as path25 from "path";
7606
7719
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
7607
7720
  var loadQaGuide = async (ctx) => {
7608
- const full = path24.join(ctx.cwd, QA_GUIDE_REL_PATH);
7609
- if (!fs26.existsSync(full)) {
7721
+ const full = path25.join(ctx.cwd, QA_GUIDE_REL_PATH);
7722
+ if (!fs27.existsSync(full)) {
7610
7723
  ctx.data.qaGuide = "";
7611
7724
  ctx.data.qaGuidePath = "";
7612
7725
  return;
7613
7726
  }
7614
7727
  try {
7615
- ctx.data.qaGuide = fs26.readFileSync(full, "utf-8");
7728
+ ctx.data.qaGuide = fs27.readFileSync(full, "utf-8");
7616
7729
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
7617
7730
  } catch {
7618
7731
  ctx.data.qaGuide = "";
@@ -7622,9 +7735,9 @@ var loadQaGuide = async (ctx) => {
7622
7735
 
7623
7736
  // src/scripts/initFlow.ts
7624
7737
  function detectPackageManager(cwd) {
7625
- if (fs27.existsSync(path25.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7626
- if (fs27.existsSync(path25.join(cwd, "yarn.lock"))) return "yarn";
7627
- if (fs27.existsSync(path25.join(cwd, "bun.lockb"))) return "bun";
7738
+ if (fs28.existsSync(path26.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7739
+ if (fs28.existsSync(path26.join(cwd, "yarn.lock"))) return "yarn";
7740
+ if (fs28.existsSync(path26.join(cwd, "bun.lockb"))) return "bun";
7628
7741
  return "npm";
7629
7742
  }
7630
7743
  function qualityCommandsFor(pm) {
@@ -7746,48 +7859,48 @@ function performInit(cwd, force) {
7746
7859
  const pm = detectPackageManager(cwd);
7747
7860
  const ownerRepo = detectOwnerRepo(cwd);
7748
7861
  const defaultBranch2 = defaultBranchFromGit(cwd);
7749
- const configPath = path25.join(cwd, "kody.config.json");
7750
- if (fs27.existsSync(configPath) && !force) {
7862
+ const configPath = path26.join(cwd, "kody.config.json");
7863
+ if (fs28.existsSync(configPath) && !force) {
7751
7864
  skipped.push("kody.config.json");
7752
7865
  } else {
7753
7866
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
7754
- fs27.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7867
+ fs28.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7755
7868
  `);
7756
7869
  wrote.push("kody.config.json");
7757
7870
  }
7758
- const workflowDir = path25.join(cwd, ".github", "workflows");
7759
- const workflowPath = path25.join(workflowDir, "kody.yml");
7760
- if (fs27.existsSync(workflowPath) && !force) {
7871
+ const workflowDir = path26.join(cwd, ".github", "workflows");
7872
+ const workflowPath = path26.join(workflowDir, "kody.yml");
7873
+ if (fs28.existsSync(workflowPath) && !force) {
7761
7874
  skipped.push(".github/workflows/kody.yml");
7762
7875
  } else {
7763
- fs27.mkdirSync(workflowDir, { recursive: true });
7764
- fs27.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
7876
+ fs28.mkdirSync(workflowDir, { recursive: true });
7877
+ fs28.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
7765
7878
  wrote.push(".github/workflows/kody.yml");
7766
7879
  }
7767
- const hasUi = fs27.existsSync(path25.join(cwd, "src/app")) || fs27.existsSync(path25.join(cwd, "app")) || fs27.existsSync(path25.join(cwd, "pages"));
7880
+ const hasUi = fs28.existsSync(path26.join(cwd, "src/app")) || fs28.existsSync(path26.join(cwd, "app")) || fs28.existsSync(path26.join(cwd, "pages"));
7768
7881
  if (hasUi) {
7769
- const qaGuidePath = path25.join(cwd, QA_GUIDE_REL_PATH);
7770
- if (fs27.existsSync(qaGuidePath) && !force) {
7882
+ const qaGuidePath = path26.join(cwd, QA_GUIDE_REL_PATH);
7883
+ if (fs28.existsSync(qaGuidePath) && !force) {
7771
7884
  skipped.push(QA_GUIDE_REL_PATH);
7772
7885
  } else {
7773
- fs27.mkdirSync(path25.dirname(qaGuidePath), { recursive: true });
7886
+ fs28.mkdirSync(path26.dirname(qaGuidePath), { recursive: true });
7774
7887
  const discovery = runQaDiscovery(cwd);
7775
- fs27.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
7888
+ fs28.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
7776
7889
  wrote.push(QA_GUIDE_REL_PATH);
7777
7890
  }
7778
7891
  }
7779
7892
  const builtinJobs = listBuiltinJobs();
7780
7893
  if (builtinJobs.length > 0) {
7781
- const jobsDir = path25.join(cwd, ".kody", "jobs");
7782
- fs27.mkdirSync(jobsDir, { recursive: true });
7894
+ const jobsDir = path26.join(cwd, ".kody", "jobs");
7895
+ fs28.mkdirSync(jobsDir, { recursive: true });
7783
7896
  for (const job of builtinJobs) {
7784
- const rel = path25.join(".kody", "jobs", `${job.slug}.md`);
7785
- const target = path25.join(cwd, rel);
7786
- if (fs27.existsSync(target) && !force) {
7897
+ const rel = path26.join(".kody", "jobs", `${job.slug}.md`);
7898
+ const target = path26.join(cwd, rel);
7899
+ if (fs28.existsSync(target) && !force) {
7787
7900
  skipped.push(rel);
7788
7901
  continue;
7789
7902
  }
7790
- fs27.writeFileSync(target, fs27.readFileSync(job.filePath, "utf-8"));
7903
+ fs28.writeFileSync(target, fs28.readFileSync(job.filePath, "utf-8"));
7791
7904
  wrote.push(rel);
7792
7905
  }
7793
7906
  }
@@ -7799,12 +7912,12 @@ function performInit(cwd, force) {
7799
7912
  continue;
7800
7913
  }
7801
7914
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
7802
- const target = path25.join(workflowDir, `kody-${exe.name}.yml`);
7803
- if (fs27.existsSync(target) && !force) {
7915
+ const target = path26.join(workflowDir, `kody-${exe.name}.yml`);
7916
+ if (fs28.existsSync(target) && !force) {
7804
7917
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
7805
7918
  continue;
7806
7919
  }
7807
- fs27.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
7920
+ fs28.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
7808
7921
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
7809
7922
  }
7810
7923
  let labels;
@@ -7882,14 +7995,14 @@ init_loadConventions();
7882
7995
  init_loadCoverageRules();
7883
7996
 
7884
7997
  // src/goal/state.ts
7885
- import * as fs28 from "fs";
7886
- import * as path26 from "path";
7998
+ import * as fs29 from "fs";
7999
+ import * as path27 from "path";
7887
8000
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
7888
8001
  var GoalStateError = class extends Error {
7889
- constructor(path34, message) {
7890
- super(`Invalid goal state at ${path34}:
8002
+ constructor(path35, message) {
8003
+ super(`Invalid goal state at ${path35}:
7891
8004
  ${message}`);
7892
- this.path = path34;
8005
+ this.path = path35;
7893
8006
  this.name = "GoalStateError";
7894
8007
  }
7895
8008
  path;
@@ -7937,16 +8050,16 @@ function serializeGoalState(s) {
7937
8050
  `;
7938
8051
  }
7939
8052
  function goalStatePath(cwd, goalId) {
7940
- return path26.join(cwd, ".kody", "goals", goalId, "state.json");
8053
+ return path27.join(cwd, ".kody", "goals", goalId, "state.json");
7941
8054
  }
7942
8055
  function readGoalState(cwd, goalId) {
7943
8056
  const file = goalStatePath(cwd, goalId);
7944
- if (!fs28.existsSync(file)) {
8057
+ if (!fs29.existsSync(file)) {
7945
8058
  throw new GoalStateError(file, "file not found");
7946
8059
  }
7947
8060
  let raw;
7948
8061
  try {
7949
- raw = JSON.parse(fs28.readFileSync(file, "utf-8"));
8062
+ raw = JSON.parse(fs29.readFileSync(file, "utf-8"));
7950
8063
  } catch (err) {
7951
8064
  throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
7952
8065
  }
@@ -7954,8 +8067,8 @@ function readGoalState(cwd, goalId) {
7954
8067
  }
7955
8068
  function writeGoalState(cwd, goalId, state) {
7956
8069
  const file = goalStatePath(cwd, goalId);
7957
- fs28.mkdirSync(path26.dirname(file), { recursive: true });
7958
- fs28.writeFileSync(file, serializeGoalState(state), "utf-8");
8070
+ fs29.mkdirSync(path27.dirname(file), { recursive: true });
8071
+ fs29.writeFileSync(file, serializeGoalState(state), "utf-8");
7959
8072
  }
7960
8073
  function nowIso() {
7961
8074
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -8052,8 +8165,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
8052
8165
  };
8053
8166
 
8054
8167
  // src/scripts/loadJobFromFile.ts
8055
- import * as fs29 from "fs";
8056
- import * as path27 from "path";
8168
+ import * as fs30 from "fs";
8169
+ import * as path28 from "path";
8057
8170
  var loadJobFromFile = async (ctx, _profile, args) => {
8058
8171
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
8059
8172
  const slugArg = String(args?.slugArg ?? "job");
@@ -8061,11 +8174,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8061
8174
  if (!slug) {
8062
8175
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
8063
8176
  }
8064
- const absPath = path27.join(ctx.cwd, jobsDir, `${slug}.md`);
8065
- if (!fs29.existsSync(absPath)) {
8177
+ const absPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
8178
+ if (!fs30.existsSync(absPath)) {
8066
8179
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
8067
8180
  }
8068
- const raw = fs29.readFileSync(absPath, "utf-8");
8181
+ const raw = fs30.readFileSync(absPath, "utf-8");
8069
8182
  const { title, body } = parseJobFile(raw, slug);
8070
8183
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
8071
8184
  const loaded = await backend.load(slug);
@@ -8104,8 +8217,8 @@ init_loadPriorArt();
8104
8217
  init_events();
8105
8218
 
8106
8219
  // src/taskContext.ts
8107
- import * as fs31 from "fs";
8108
- import * as path29 from "path";
8220
+ import * as fs32 from "fs";
8221
+ import * as path30 from "path";
8109
8222
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
8110
8223
  function buildTaskContext(args) {
8111
8224
  return {
@@ -8121,10 +8234,10 @@ function buildTaskContext(args) {
8121
8234
  }
8122
8235
  function persistTaskContext(cwd, ctx) {
8123
8236
  try {
8124
- const dir = path29.join(cwd, ".kody", "runs", ctx.runId);
8125
- fs31.mkdirSync(dir, { recursive: true });
8126
- const file = path29.join(dir, "task-context.json");
8127
- fs31.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8237
+ const dir = path30.join(cwd, ".kody", "runs", ctx.runId);
8238
+ fs32.mkdirSync(dir, { recursive: true });
8239
+ const file = path30.join(dir, "task-context.json");
8240
+ fs32.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8128
8241
  `);
8129
8242
  return file;
8130
8243
  } catch (err) {
@@ -9487,8 +9600,8 @@ function resolveBaseOverride(value) {
9487
9600
 
9488
9601
  // src/scripts/runTickScript.ts
9489
9602
  import { spawnSync } from "child_process";
9490
- import * as fs32 from "fs";
9491
- import * as path30 from "path";
9603
+ import * as fs33 from "fs";
9604
+ import * as path31 from "path";
9492
9605
  var runTickScript = async (ctx, _profile, args) => {
9493
9606
  ctx.skipAgent = true;
9494
9607
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
@@ -9500,13 +9613,13 @@ var runTickScript = async (ctx, _profile, args) => {
9500
9613
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
9501
9614
  return;
9502
9615
  }
9503
- const jobPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
9504
- if (!fs32.existsSync(jobPath)) {
9616
+ const jobPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
9617
+ if (!fs33.existsSync(jobPath)) {
9505
9618
  ctx.output.exitCode = 99;
9506
9619
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
9507
9620
  return;
9508
9621
  }
9509
- const raw = fs32.readFileSync(jobPath, "utf-8");
9622
+ const raw = fs33.readFileSync(jobPath, "utf-8");
9510
9623
  const { frontmatter } = splitFrontmatter(raw);
9511
9624
  const tickScript = frontmatter.tickScript;
9512
9625
  if (!tickScript) {
@@ -9514,8 +9627,8 @@ var runTickScript = async (ctx, _profile, args) => {
9514
9627
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
9515
9628
  return;
9516
9629
  }
9517
- const scriptPath = path30.isAbsolute(tickScript) ? tickScript : path30.join(ctx.cwd, tickScript);
9518
- if (!fs32.existsSync(scriptPath)) {
9630
+ const scriptPath = path31.isAbsolute(tickScript) ? tickScript : path31.join(ctx.cwd, tickScript);
9631
+ if (!fs33.existsSync(scriptPath)) {
9519
9632
  ctx.output.exitCode = 99;
9520
9633
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
9521
9634
  return;
@@ -10537,7 +10650,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
10537
10650
  };
10538
10651
 
10539
10652
  // src/scripts/writeRunSummary.ts
10540
- import * as fs33 from "fs";
10653
+ import * as fs34 from "fs";
10541
10654
  var writeRunSummary = async (ctx, profile) => {
10542
10655
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
10543
10656
  if (!summaryPath) return;
@@ -10559,7 +10672,7 @@ var writeRunSummary = async (ctx, profile) => {
10559
10672
  if (reason) lines.push(`- **Reason:** ${reason}`);
10560
10673
  lines.push("");
10561
10674
  try {
10562
- fs33.appendFileSync(summaryPath, `${lines.join("\n")}
10675
+ fs34.appendFileSync(summaryPath, `${lines.join("\n")}
10563
10676
  `);
10564
10677
  } catch {
10565
10678
  }
@@ -10637,6 +10750,7 @@ var postflightScripts = {
10637
10750
  startFlow,
10638
10751
  dispatch,
10639
10752
  finishFlow,
10753
+ finalizeTerminal,
10640
10754
  advanceFlow,
10641
10755
  persistFlowState,
10642
10756
  recordClassification,
@@ -10783,9 +10897,9 @@ async function runExecutable(profileName, input) {
10783
10897
  data: { ...input.preloadedData ?? {} },
10784
10898
  output: { exitCode: 0 }
10785
10899
  };
10786
- const ndjsonDir = path31.join(input.cwd, ".kody");
10900
+ const ndjsonDir = path32.join(input.cwd, ".kody");
10787
10901
  const invokeAgent = async (prompt) => {
10788
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path31.isAbsolute(p) ? p : path31.resolve(profile.dir, p)).filter((p) => p.length > 0);
10902
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path32.isAbsolute(p) ? p : path32.resolve(profile.dir, p)).filter((p) => p.length > 0);
10789
10903
  const syntheticPath = ctx.data.syntheticPluginPath;
10790
10904
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
10791
10905
  return runAgent({
@@ -10980,7 +11094,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
10980
11094
  function getProfileInputsForChild(profileName, _cwd) {
10981
11095
  try {
10982
11096
  const profilePath = resolveProfilePath(profileName);
10983
- if (!fs34.existsSync(profilePath)) return null;
11097
+ if (!fs35.existsSync(profilePath)) return null;
10984
11098
  return loadProfile(profilePath).inputs;
10985
11099
  } catch {
10986
11100
  return null;
@@ -10989,17 +11103,17 @@ function getProfileInputsForChild(profileName, _cwd) {
10989
11103
  function resolveProfilePath(profileName) {
10990
11104
  const found = resolveExecutable(profileName);
10991
11105
  if (found) return found;
10992
- const here = path31.dirname(new URL(import.meta.url).pathname);
11106
+ const here = path32.dirname(new URL(import.meta.url).pathname);
10993
11107
  const candidates = [
10994
- path31.join(here, "executables", profileName, "profile.json"),
11108
+ path32.join(here, "executables", profileName, "profile.json"),
10995
11109
  // same-dir sibling (dev)
10996
- path31.join(here, "..", "executables", profileName, "profile.json"),
11110
+ path32.join(here, "..", "executables", profileName, "profile.json"),
10997
11111
  // up one (prod: dist/bin → dist/executables)
10998
- path31.join(here, "..", "src", "executables", profileName, "profile.json")
11112
+ path32.join(here, "..", "src", "executables", profileName, "profile.json")
10999
11113
  // fallback
11000
11114
  ];
11001
11115
  for (const c of candidates) {
11002
- if (fs34.existsSync(c)) return c;
11116
+ if (fs35.existsSync(c)) return c;
11003
11117
  }
11004
11118
  return candidates[0];
11005
11119
  }
@@ -11099,8 +11213,8 @@ function resolveShellTimeoutMs(entry) {
11099
11213
  var SIGKILL_GRACE_MS = 5e3;
11100
11214
  async function runShellEntry(entry, ctx, profile) {
11101
11215
  const shellName = entry.shell;
11102
- const shellPath = path31.join(profile.dir, shellName);
11103
- if (!fs34.existsSync(shellPath)) {
11216
+ const shellPath = path32.join(profile.dir, shellName);
11217
+ if (!fs35.existsSync(shellPath)) {
11104
11218
  ctx.skipAgent = true;
11105
11219
  ctx.output.exitCode = 99;
11106
11220
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -11440,8 +11554,8 @@ function resetWorkingTree2(cwd) {
11440
11554
  }
11441
11555
  function readContainerState(ctx, child, reader) {
11442
11556
  const issueNumber = ctx.args.issue;
11443
- const cached = ctx.data.taskState;
11444
- const prUrl = cached?.core?.prUrl;
11557
+ const cached2 = ctx.data.taskState;
11558
+ const prUrl = cached2?.core?.prUrl;
11445
11559
  const prNumber = prUrl ? parsePrNumber4(prUrl) : null;
11446
11560
  if (child.target === "pr" && prNumber) {
11447
11561
  try {
@@ -11455,8 +11569,8 @@ function readContainerState(ctx, child, reader) {
11455
11569
  } catch {
11456
11570
  }
11457
11571
  }
11458
- if (cached && typeof cached === "object") {
11459
- return cached;
11572
+ if (cached2 && typeof cached2 === "object") {
11573
+ return cached2;
11460
11574
  }
11461
11575
  return {
11462
11576
  schemaVersion: 1,
@@ -11579,9 +11693,9 @@ function resolveAuthToken(env = process.env) {
11579
11693
  return token;
11580
11694
  }
11581
11695
  function detectPackageManager2(cwd) {
11582
- if (fs35.existsSync(path32.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
11583
- if (fs35.existsSync(path32.join(cwd, "yarn.lock"))) return "yarn";
11584
- if (fs35.existsSync(path32.join(cwd, "bun.lockb"))) return "bun";
11696
+ if (fs36.existsSync(path33.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
11697
+ if (fs36.existsSync(path33.join(cwd, "yarn.lock"))) return "yarn";
11698
+ if (fs36.existsSync(path33.join(cwd, "bun.lockb"))) return "bun";
11585
11699
  return "npm";
11586
11700
  }
11587
11701
  function shellOut(cmd, args, cwd, stream = true) {
@@ -11668,11 +11782,11 @@ function configureGitIdentity(cwd) {
11668
11782
  }
11669
11783
  function postFailureTail(issueNumber, cwd, reason) {
11670
11784
  if (!issueNumber) return;
11671
- const logPath = path32.join(cwd, ".kody", "last-run.jsonl");
11785
+ const logPath = path33.join(cwd, ".kody", "last-run.jsonl");
11672
11786
  let tail = "";
11673
11787
  try {
11674
- if (fs35.existsSync(logPath)) {
11675
- const content = fs35.readFileSync(logPath, "utf-8");
11788
+ if (fs36.existsSync(logPath)) {
11789
+ const content = fs36.readFileSync(logPath, "utf-8");
11676
11790
  tail = content.slice(-3e3);
11677
11791
  }
11678
11792
  } catch {
@@ -11697,7 +11811,7 @@ async function runCi(argv) {
11697
11811
  return 0;
11698
11812
  }
11699
11813
  const args = parseCiArgs(argv);
11700
- const cwd = args.cwd ? path32.resolve(args.cwd) : process.cwd();
11814
+ const cwd = args.cwd ? path33.resolve(args.cwd) : process.cwd();
11701
11815
  let earlyConfig;
11702
11816
  try {
11703
11817
  earlyConfig = loadConfig(cwd);
@@ -11707,9 +11821,9 @@ async function runCi(argv) {
11707
11821
  const eventName = process.env.GITHUB_EVENT_NAME;
11708
11822
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
11709
11823
  let manualWorkflowDispatch = false;
11710
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs35.existsSync(dispatchEventPath)) {
11824
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs36.existsSync(dispatchEventPath)) {
11711
11825
  try {
11712
- const evt = JSON.parse(fs35.readFileSync(dispatchEventPath, "utf-8"));
11826
+ const evt = JSON.parse(fs36.readFileSync(dispatchEventPath, "utf-8"));
11713
11827
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
11714
11828
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
11715
11829
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -11968,9 +12082,9 @@ function parseChatArgs(argv, env = process.env) {
11968
12082
  return result;
11969
12083
  }
11970
12084
  function commitChatFiles(cwd, sessionId, verbose) {
11971
- const sessionFile = path33.relative(cwd, sessionFilePath(cwd, sessionId));
11972
- const eventsFile = path33.relative(cwd, eventsFilePath(cwd, sessionId));
11973
- const paths = [sessionFile, eventsFile].filter((p) => fs36.existsSync(path33.join(cwd, p)));
12085
+ const sessionFile = path34.relative(cwd, sessionFilePath(cwd, sessionId));
12086
+ const eventsFile = path34.relative(cwd, eventsFilePath(cwd, sessionId));
12087
+ const paths = [sessionFile, eventsFile].filter((p) => fs37.existsSync(path34.join(cwd, p)));
11974
12088
  if (paths.length === 0) return;
11975
12089
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
11976
12090
  try {
@@ -12008,7 +12122,7 @@ async function runChat(argv) {
12008
12122
  ${CHAT_HELP}`);
12009
12123
  return 64;
12010
12124
  }
12011
- const cwd = args.cwd ? path33.resolve(args.cwd) : process.cwd();
12125
+ const cwd = args.cwd ? path34.resolve(args.cwd) : process.cwd();
12012
12126
  const sessionId = args.sessionId;
12013
12127
  const unpackedSecrets = unpackAllSecrets();
12014
12128
  if (unpackedSecrets > 0) {
@@ -12060,7 +12174,7 @@ ${CHAT_HELP}`);
12060
12174
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
12061
12175
  const meta = readMeta(sessionFile);
12062
12176
  process.stdout.write(
12063
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs36.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
12177
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs37.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
12064
12178
  `
12065
12179
  );
12066
12180
  try {