@kody-ade/kody-engine 0.4.87 → 0.4.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin/kody.js +466 -350
  2. package/package.json +1 -1
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.87",
880
+ version: "0.4.89",
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 {
@@ -2712,12 +2779,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
2712
2779
  "preloadContext"
2713
2780
  ]);
2714
2781
  function loadProfile(profilePath) {
2715
- if (!fs10.existsSync(profilePath)) {
2782
+ if (!fs11.existsSync(profilePath)) {
2716
2783
  throw new ProfileError(profilePath, "file not found");
2717
2784
  }
2718
2785
  let raw;
2719
2786
  try {
2720
- raw = JSON.parse(fs10.readFileSync(profilePath, "utf-8"));
2787
+ raw = JSON.parse(fs11.readFileSync(profilePath, "utf-8"));
2721
2788
  } catch (err) {
2722
2789
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
2723
2790
  }
@@ -2728,7 +2795,7 @@ function loadProfile(profilePath) {
2728
2795
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
2729
2796
  if (unknownKeys.length > 0) {
2730
2797
  process.stderr.write(
2731
- `[kody profile] ${path8.basename(path8.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
2798
+ `[kody profile] ${path9.basename(path9.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
2732
2799
  `
2733
2800
  );
2734
2801
  }
@@ -2789,7 +2856,7 @@ function loadProfile(profilePath) {
2789
2856
  // Phase 5 in-process handoff opt-in. Default false; containers
2790
2857
  // flip to true after end-to-end verification.
2791
2858
  preloadContext: r.preloadContext === true,
2792
- dir: path8.dirname(profilePath)
2859
+ dir: path9.dirname(profilePath)
2793
2860
  };
2794
2861
  if (lifecycle) {
2795
2862
  applyLifecycle(profile, profilePath);
@@ -3159,9 +3226,9 @@ function errMsg(err) {
3159
3226
 
3160
3227
  // src/litellm.ts
3161
3228
  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";
3229
+ import * as fs12 from "fs";
3230
+ import * as os3 from "os";
3231
+ import * as path10 from "path";
3165
3232
  async function checkLitellmHealth(url) {
3166
3233
  try {
3167
3234
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -3208,20 +3275,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3208
3275
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
3209
3276
  }
3210
3277
  }
3211
- const configPath = path9.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3212
- fs11.writeFileSync(configPath, generateLitellmConfigYaml(model));
3278
+ const configPath = path10.join(os3.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3279
+ fs12.writeFileSync(configPath, generateLitellmConfigYaml(model));
3213
3280
  const portMatch = url.match(/:(\d+)/);
3214
3281
  const port = portMatch ? portMatch[1] : "4000";
3215
3282
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
3216
3283
  const dotenvVars = readDotenvApiKeys(projectDir);
3217
- const logPath = path9.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3218
- const outFd = fs11.openSync(logPath, "w");
3284
+ const logPath = path10.join(os3.tmpdir(), `kody-litellm-${Date.now()}.log`);
3285
+ const outFd = fs12.openSync(logPath, "w");
3219
3286
  const child = spawn2(cmd, args, {
3220
3287
  stdio: ["ignore", outFd, outFd],
3221
3288
  detached: true,
3222
3289
  env: stripBlockingEnv({ ...process.env, ...dotenvVars })
3223
3290
  });
3224
- fs11.closeSync(outFd);
3291
+ fs12.closeSync(outFd);
3225
3292
  const timeoutMs = resolveLitellmTimeoutMs();
3226
3293
  const deadline = Date.now() + timeoutMs;
3227
3294
  while (Date.now() < deadline) {
@@ -3240,7 +3307,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3240
3307
  }
3241
3308
  let logTail = "";
3242
3309
  try {
3243
- logTail = fs11.readFileSync(logPath, "utf-8").slice(-2e3);
3310
+ logTail = fs12.readFileSync(logPath, "utf-8").slice(-2e3);
3244
3311
  } catch {
3245
3312
  }
3246
3313
  try {
@@ -3252,10 +3319,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3252
3319
  ${logTail}`);
3253
3320
  }
3254
3321
  function readDotenvApiKeys(projectDir) {
3255
- const dotenvPath = path9.join(projectDir, ".env");
3256
- if (!fs11.existsSync(dotenvPath)) return {};
3322
+ const dotenvPath = path10.join(projectDir, ".env");
3323
+ if (!fs12.existsSync(dotenvPath)) return {};
3257
3324
  const result = {};
3258
- for (const rawLine of fs11.readFileSync(dotenvPath, "utf-8").split("\n")) {
3325
+ for (const rawLine of fs12.readFileSync(dotenvPath, "utf-8").split("\n")) {
3259
3326
  const line = rawLine.trim();
3260
3327
  if (!line || line.startsWith("#")) continue;
3261
3328
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -3279,8 +3346,8 @@ function stripBlockingEnv(env) {
3279
3346
 
3280
3347
  // src/commit.ts
3281
3348
  import { execFileSync as execFileSync5 } from "child_process";
3282
- import * as fs12 from "fs";
3283
- import * as path10 from "path";
3349
+ import * as fs13 from "fs";
3350
+ import * as path11 from "path";
3284
3351
  var FORBIDDEN_PATH_PREFIXES = [
3285
3352
  ".kody/",
3286
3353
  ".kody-engine/",
@@ -3340,18 +3407,18 @@ function tryGit(args, cwd) {
3340
3407
  }
3341
3408
  function abortUnfinishedGitOps(cwd) {
3342
3409
  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"))) {
3410
+ const gitDir = path11.join(cwd ?? process.cwd(), ".git");
3411
+ if (!fs13.existsSync(gitDir)) return aborted;
3412
+ if (fs13.existsSync(path11.join(gitDir, "MERGE_HEAD"))) {
3346
3413
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
3347
3414
  }
3348
- if (fs12.existsSync(path10.join(gitDir, "CHERRY_PICK_HEAD"))) {
3415
+ if (fs13.existsSync(path11.join(gitDir, "CHERRY_PICK_HEAD"))) {
3349
3416
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
3350
3417
  }
3351
- if (fs12.existsSync(path10.join(gitDir, "REVERT_HEAD"))) {
3418
+ if (fs13.existsSync(path11.join(gitDir, "REVERT_HEAD"))) {
3352
3419
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
3353
3420
  }
3354
- if (fs12.existsSync(path10.join(gitDir, "rebase-merge")) || fs12.existsSync(path10.join(gitDir, "rebase-apply"))) {
3421
+ if (fs13.existsSync(path11.join(gitDir, "rebase-merge")) || fs13.existsSync(path11.join(gitDir, "rebase-apply"))) {
3355
3422
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
3356
3423
  }
3357
3424
  try {
@@ -3407,7 +3474,7 @@ function normalizeCommitMessage(raw) {
3407
3474
  function commitAndPush(branch, agentMessage, cwd) {
3408
3475
  const allChanged = listChangedFiles(cwd);
3409
3476
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
3410
- const mergeHeadExists = fs12.existsSync(path10.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3477
+ const mergeHeadExists = fs13.existsSync(path11.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3411
3478
  if (allowedFiles.length === 0 && !mergeHeadExists) {
3412
3479
  return { committed: false, pushed: false, sha: "", message: "" };
3413
3480
  }
@@ -3723,20 +3790,20 @@ var advanceFlow = async (ctx, profile) => {
3723
3790
 
3724
3791
  // src/scripts/brainServe.ts
3725
3792
  import { createServer } from "http";
3726
- import * as fs14 from "fs";
3727
- import * as path12 from "path";
3793
+ import * as fs15 from "fs";
3794
+ import * as path13 from "path";
3728
3795
 
3729
3796
  // src/scripts/brainTurnLog.ts
3730
- import * as fs13 from "fs";
3731
- import * as path11 from "path";
3797
+ import * as fs14 from "fs";
3798
+ import * as path12 from "path";
3732
3799
  var live = /* @__PURE__ */ new Map();
3733
3800
  function eventsPath(dir, chatId) {
3734
- return path11.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
3801
+ return path12.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
3735
3802
  }
3736
3803
  function lastPersistedSeq(dir, chatId) {
3737
3804
  const p = eventsPath(dir, chatId);
3738
- if (!fs13.existsSync(p)) return 0;
3739
- const lines = fs13.readFileSync(p, "utf-8").split("\n").filter(Boolean);
3805
+ if (!fs14.existsSync(p)) return 0;
3806
+ const lines = fs14.readFileSync(p, "utf-8").split("\n").filter(Boolean);
3740
3807
  if (lines.length === 0) return 0;
3741
3808
  try {
3742
3809
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -3746,9 +3813,9 @@ function lastPersistedSeq(dir, chatId) {
3746
3813
  }
3747
3814
  function readSince(dir, chatId, since) {
3748
3815
  const p = eventsPath(dir, chatId);
3749
- if (!fs13.existsSync(p)) return [];
3816
+ if (!fs14.existsSync(p)) return [];
3750
3817
  const out = [];
3751
- for (const line of fs13.readFileSync(p, "utf-8").split("\n")) {
3818
+ for (const line of fs14.readFileSync(p, "utf-8").split("\n")) {
3752
3819
  if (!line) continue;
3753
3820
  try {
3754
3821
  const rec = JSON.parse(line);
@@ -3774,12 +3841,12 @@ function beginTurn(dir, chatId) {
3774
3841
  };
3775
3842
  live.set(chatId, state);
3776
3843
  const p = eventsPath(dir, chatId);
3777
- fs13.mkdirSync(path11.dirname(p), { recursive: true });
3844
+ fs14.mkdirSync(path12.dirname(p), { recursive: true });
3778
3845
  return (event) => {
3779
3846
  state.seq += 1;
3780
3847
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
3781
3848
  try {
3782
- fs13.appendFileSync(p, JSON.stringify(rec) + "\n");
3849
+ fs14.appendFileSync(p, JSON.stringify(rec) + "\n");
3783
3850
  } catch (err) {
3784
3851
  process.stderr.write(
3785
3852
  `[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
@@ -3817,7 +3884,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
3817
3884
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
3818
3885
  };
3819
3886
  try {
3820
- fs13.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
3887
+ fs14.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
3821
3888
  } catch {
3822
3889
  }
3823
3890
  state.status = "ended";
@@ -4034,7 +4101,7 @@ async function handleChatTurn(req, res, chatId, opts) {
4034
4101
  return;
4035
4102
  }
4036
4103
  const sessionFile = sessionFilePath(opts.cwd, chatId);
4037
- fs14.mkdirSync(path12.dirname(sessionFile), { recursive: true });
4104
+ fs15.mkdirSync(path13.dirname(sessionFile), { recursive: true });
4038
4105
  appendTurn(sessionFile, {
4039
4106
  role: "user",
4040
4107
  content: message,
@@ -4174,21 +4241,21 @@ var brainServe = async (ctx) => {
4174
4241
  };
4175
4242
 
4176
4243
  // src/scripts/buildSyntheticPlugin.ts
4177
- import * as fs15 from "fs";
4178
- import * as os3 from "os";
4179
- import * as path13 from "path";
4244
+ import * as fs16 from "fs";
4245
+ import * as os4 from "os";
4246
+ import * as path14 from "path";
4180
4247
  function getPluginsCatalogRoot() {
4181
- const here = path13.dirname(new URL(import.meta.url).pathname);
4248
+ const here = path14.dirname(new URL(import.meta.url).pathname);
4182
4249
  const candidates = [
4183
- path13.join(here, "..", "plugins"),
4250
+ path14.join(here, "..", "plugins"),
4184
4251
  // dev: src/scripts → src/plugins
4185
- path13.join(here, "..", "..", "plugins"),
4252
+ path14.join(here, "..", "..", "plugins"),
4186
4253
  // built: dist/scripts → dist/plugins
4187
- path13.join(here, "..", "..", "src", "plugins")
4254
+ path14.join(here, "..", "..", "src", "plugins")
4188
4255
  // fallback
4189
4256
  ];
4190
4257
  for (const c of candidates) {
4191
- if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
4258
+ if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
4192
4259
  }
4193
4260
  return candidates[0];
4194
4261
  }
@@ -4198,52 +4265,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4198
4265
  if (!needsSynthetic) return;
4199
4266
  const catalog = getPluginsCatalogRoot();
4200
4267
  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 });
4268
+ const root = path14.join(os4.tmpdir(), `kody-synth-${runId}`);
4269
+ fs16.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
4203
4270
  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;
4271
+ const local = path14.join(profile.dir, bucket, entry);
4272
+ if (fs16.existsSync(local)) return local;
4273
+ const central = path14.join(catalog, bucket, entry);
4274
+ if (fs16.existsSync(central)) return central;
4208
4275
  throw new Error(
4209
4276
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
4210
4277
  );
4211
4278
  };
4212
4279
  if (cc.skills.length > 0) {
4213
- const dst = path13.join(root, "skills");
4214
- fs15.mkdirSync(dst, { recursive: true });
4280
+ const dst = path14.join(root, "skills");
4281
+ fs16.mkdirSync(dst, { recursive: true });
4215
4282
  for (const name of cc.skills) {
4216
- copyDir(resolvePart("skills", name), path13.join(dst, name));
4283
+ copyDir(resolvePart("skills", name), path14.join(dst, name));
4217
4284
  }
4218
4285
  }
4219
4286
  if (cc.commands.length > 0) {
4220
- const dst = path13.join(root, "commands");
4221
- fs15.mkdirSync(dst, { recursive: true });
4287
+ const dst = path14.join(root, "commands");
4288
+ fs16.mkdirSync(dst, { recursive: true });
4222
4289
  for (const name of cc.commands) {
4223
- fs15.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
4290
+ fs16.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
4224
4291
  }
4225
4292
  }
4226
4293
  if (cc.subagents.length > 0) {
4227
- const dst = path13.join(root, "agents");
4228
- fs15.mkdirSync(dst, { recursive: true });
4294
+ const dst = path14.join(root, "agents");
4295
+ fs16.mkdirSync(dst, { recursive: true });
4229
4296
  for (const name of cc.subagents) {
4230
- fs15.copyFileSync(resolvePart("agents", `${name}.md`), path13.join(dst, `${name}.md`));
4297
+ fs16.copyFileSync(resolvePart("agents", `${name}.md`), path14.join(dst, `${name}.md`));
4231
4298
  }
4232
4299
  }
4233
4300
  if (cc.hooks.length > 0) {
4234
- const dst = path13.join(root, "hooks");
4235
- fs15.mkdirSync(dst, { recursive: true });
4301
+ const dst = path14.join(root, "hooks");
4302
+ fs16.mkdirSync(dst, { recursive: true });
4236
4303
  const merged = { hooks: {} };
4237
4304
  for (const name of cc.hooks) {
4238
4305
  const src = resolvePart("hooks", `${name}.json`);
4239
- const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
4306
+ const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
4240
4307
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
4241
4308
  if (!Array.isArray(entries)) continue;
4242
4309
  if (!merged.hooks[event]) merged.hooks[event] = [];
4243
4310
  merged.hooks[event].push(...entries);
4244
4311
  }
4245
4312
  }
4246
- fs15.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4313
+ fs16.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4247
4314
  `);
4248
4315
  }
4249
4316
  const manifest = {
@@ -4254,17 +4321,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4254
4321
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
4255
4322
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
4256
4323
  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)}
4324
+ fs16.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
4258
4325
  `);
4259
4326
  ctx.data.syntheticPluginPath = root;
4260
4327
  };
4261
4328
  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);
4329
+ fs16.mkdirSync(dst, { recursive: true });
4330
+ for (const ent of fs16.readdirSync(src, { withFileTypes: true })) {
4331
+ const s = path14.join(src, ent.name);
4332
+ const d = path14.join(dst, ent.name);
4266
4333
  if (ent.isDirectory()) copyDir(s, d);
4267
- else if (ent.isFile()) fs15.copyFileSync(s, d);
4334
+ else if (ent.isFile()) fs16.copyFileSync(s, d);
4268
4335
  }
4269
4336
  }
4270
4337
 
@@ -4405,13 +4472,13 @@ function defaultLabelMap() {
4405
4472
  }
4406
4473
 
4407
4474
  // src/scripts/commitAndPush.ts
4408
- import * as fs17 from "fs";
4409
- import * as path15 from "path";
4475
+ import * as fs18 from "fs";
4476
+ import * as path16 from "path";
4410
4477
  init_events();
4411
4478
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
4412
4479
  function sentinelPathForStage(cwd, profileName) {
4413
4480
  const runId = resolveRunId();
4414
- return path15.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4481
+ return path16.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4415
4482
  }
4416
4483
  var commitAndPush2 = async (ctx, profile) => {
4417
4484
  const branch = ctx.data.branch;
@@ -4421,9 +4488,9 @@ var commitAndPush2 = async (ctx, profile) => {
4421
4488
  }
4422
4489
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
4423
4490
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
4424
- if (sentinel && fs17.existsSync(sentinel)) {
4491
+ if (sentinel && fs18.existsSync(sentinel)) {
4425
4492
  try {
4426
- const replay = JSON.parse(fs17.readFileSync(sentinel, "utf-8"));
4493
+ const replay = JSON.parse(fs18.readFileSync(sentinel, "utf-8"));
4427
4494
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
4428
4495
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
4429
4496
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -4476,8 +4543,8 @@ var commitAndPush2 = async (ctx, profile) => {
4476
4543
  const result = ctx.data.commitResult;
4477
4544
  if (sentinel && result?.committed) {
4478
4545
  try {
4479
- fs17.mkdirSync(path15.dirname(sentinel), { recursive: true });
4480
- fs17.writeFileSync(
4546
+ fs18.mkdirSync(path16.dirname(sentinel), { recursive: true });
4547
+ fs18.writeFileSync(
4481
4548
  sentinel,
4482
4549
  JSON.stringify(
4483
4550
  {
@@ -4498,11 +4565,11 @@ var commitAndPush2 = async (ctx, profile) => {
4498
4565
 
4499
4566
  // src/scripts/commitGoalState.ts
4500
4567
  import { execFileSync as execFileSync9 } from "child_process";
4501
- import * as path16 from "path";
4568
+ import * as path17 from "path";
4502
4569
  var commitGoalState = async (ctx) => {
4503
4570
  const goal = ctx.data.goal;
4504
4571
  if (!goal) return;
4505
- const stateRel = path16.posix.join(".kody", "goals", goal.id, "state.json");
4572
+ const stateRel = path17.posix.join(".kody", "goals", goal.id, "state.json");
4506
4573
  try {
4507
4574
  execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
4508
4575
  } catch (err) {
@@ -4547,20 +4614,20 @@ function describeCommitMessage(goal) {
4547
4614
  }
4548
4615
 
4549
4616
  // src/scripts/composePrompt.ts
4550
- import * as fs18 from "fs";
4551
- import * as path17 from "path";
4617
+ import * as fs19 from "fs";
4618
+ import * as path18 from "path";
4552
4619
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
4553
4620
  var composePrompt = async (ctx, profile) => {
4554
4621
  const explicit = ctx.data.promptTemplate;
4555
4622
  const mode = ctx.args.mode;
4556
4623
  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")
4624
+ explicit ? path18.join(profile.dir, explicit) : null,
4625
+ mode ? path18.join(profile.dir, "prompts", `${mode}.md`) : null,
4626
+ path18.join(profile.dir, "prompt.md")
4560
4627
  ].filter(Boolean);
4561
4628
  let templatePath = "";
4562
4629
  for (const c of candidates) {
4563
- if (fs18.existsSync(c)) {
4630
+ if (fs19.existsSync(c)) {
4564
4631
  templatePath = c;
4565
4632
  break;
4566
4633
  }
@@ -4568,7 +4635,7 @@ var composePrompt = async (ctx, profile) => {
4568
4635
  if (!templatePath) {
4569
4636
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
4570
4637
  }
4571
- const template = fs18.readFileSync(templatePath, "utf-8");
4638
+ const template = fs19.readFileSync(templatePath, "utf-8");
4572
4639
  const tokens = {
4573
4640
  ...stringifyAll(ctx.args, "args."),
4574
4641
  ...stringifyAll(ctx.data, ""),
@@ -4647,8 +4714,8 @@ function formatToolsUsage(profile) {
4647
4714
  // src/scripts/createQaGoal.ts
4648
4715
  init_issue();
4649
4716
  import { execFileSync as execFileSync10 } from "child_process";
4650
- import * as fs19 from "fs";
4651
- import * as path18 from "path";
4717
+ import * as fs20 from "fs";
4718
+ import * as path19 from "path";
4652
4719
 
4653
4720
  // src/scripts/postReviewResult.ts
4654
4721
  init_issue();
@@ -4901,8 +4968,8 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
4901
4968
  return { number: Number(m[1]), created: true };
4902
4969
  }
4903
4970
  function writeStateFile(cwd, goalId, lastDispatchedIssue) {
4904
- const dir = path18.join(cwd, ".kody", "goals", goalId);
4905
- fs19.mkdirSync(dir, { recursive: true });
4971
+ const dir = path19.join(cwd, ".kody", "goals", goalId);
4972
+ fs20.mkdirSync(dir, { recursive: true });
4906
4973
  const state = {
4907
4974
  version: 1,
4908
4975
  state: "active",
@@ -4910,8 +4977,8 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
4910
4977
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4911
4978
  ...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
4912
4979
  };
4913
- const filePath = path18.join(dir, "state.json");
4914
- fs19.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
4980
+ const filePath = path19.join(dir, "state.json");
4981
+ fs20.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
4915
4982
  `);
4916
4983
  return filePath;
4917
4984
  }
@@ -5321,6 +5388,23 @@ function editPrBase(prNumber, baseBranch, cwd) {
5321
5388
  return fail(err);
5322
5389
  }
5323
5390
  }
5391
+ function branchContains(leafHead, candidateHead, cwd) {
5392
+ if (leafHead === candidateHead) return { ok: true, value: true };
5393
+ try {
5394
+ const out = gh(
5395
+ [
5396
+ "api",
5397
+ `repos/{owner}/{repo}/compare/${encodeURIComponent(leafHead)}...${encodeURIComponent(candidateHead)}`,
5398
+ "--jq",
5399
+ ".ahead_by"
5400
+ ],
5401
+ { cwd }
5402
+ );
5403
+ return { ok: true, value: Number.parseInt(out.trim(), 10) === 0 };
5404
+ } catch (err) {
5405
+ return fail(err);
5406
+ }
5407
+ }
5324
5408
  function markPrReady(prNumber, cwd) {
5325
5409
  try {
5326
5410
  gh(["pr", "ready", String(prNumber)], { cwd });
@@ -5403,15 +5487,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
5403
5487
 
5404
5488
  // src/scripts/diagMcp.ts
5405
5489
  import { execFileSync as execFileSync11 } from "child_process";
5406
- import * as fs20 from "fs";
5407
- import * as os4 from "os";
5408
- import * as path19 from "path";
5490
+ import * as fs21 from "fs";
5491
+ import * as os5 from "os";
5492
+ import * as path20 from "path";
5409
5493
  var diagMcp = async (_ctx) => {
5410
- const home = os4.homedir();
5411
- const cacheDir = path19.join(home, ".cache", "ms-playwright");
5494
+ const home = os5.homedir();
5495
+ const cacheDir = path20.join(home, ".cache", "ms-playwright");
5412
5496
  let entries = [];
5413
5497
  try {
5414
- entries = fs20.readdirSync(cacheDir);
5498
+ entries = fs21.readdirSync(cacheDir);
5415
5499
  } catch {
5416
5500
  }
5417
5501
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -5437,17 +5521,17 @@ var diagMcp = async (_ctx) => {
5437
5521
  };
5438
5522
 
5439
5523
  // src/scripts/discoverQaContext.ts
5440
- import * as fs22 from "fs";
5441
- import * as path21 from "path";
5524
+ import * as fs23 from "fs";
5525
+ import * as path22 from "path";
5442
5526
 
5443
5527
  // src/scripts/frameworkDetectors.ts
5444
- import * as fs21 from "fs";
5445
- import * as path20 from "path";
5528
+ import * as fs22 from "fs";
5529
+ import * as path21 from "path";
5446
5530
  function detectFrameworks(cwd) {
5447
5531
  const out = [];
5448
5532
  let deps = {};
5449
5533
  try {
5450
- const pkg = JSON.parse(fs21.readFileSync(path20.join(cwd, "package.json"), "utf-8"));
5534
+ const pkg = JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
5451
5535
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
5452
5536
  } catch {
5453
5537
  return out;
@@ -5484,7 +5568,7 @@ function detectFrameworks(cwd) {
5484
5568
  }
5485
5569
  function findFile(cwd, candidates) {
5486
5570
  for (const c of candidates) {
5487
- if (fs21.existsSync(path20.join(cwd, c))) return c;
5571
+ if (fs22.existsSync(path21.join(cwd, c))) return c;
5488
5572
  }
5489
5573
  return null;
5490
5574
  }
@@ -5497,18 +5581,18 @@ var COLLECTION_DIRS = [
5497
5581
  function discoverPayloadCollections(cwd) {
5498
5582
  const out = [];
5499
5583
  for (const dir of COLLECTION_DIRS) {
5500
- const full = path20.join(cwd, dir);
5501
- if (!fs21.existsSync(full)) continue;
5584
+ const full = path21.join(cwd, dir);
5585
+ if (!fs22.existsSync(full)) continue;
5502
5586
  let files;
5503
5587
  try {
5504
- files = fs21.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5588
+ files = fs22.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5505
5589
  } catch {
5506
5590
  continue;
5507
5591
  }
5508
5592
  for (const file of files) {
5509
5593
  try {
5510
- const filePath = path20.join(full, file);
5511
- const content = fs21.readFileSync(filePath, "utf-8").slice(0, 1e4);
5594
+ const filePath = path21.join(full, file);
5595
+ const content = fs22.readFileSync(filePath, "utf-8").slice(0, 1e4);
5512
5596
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
5513
5597
  if (!slugMatch) continue;
5514
5598
  const slug = slugMatch[1];
@@ -5522,7 +5606,7 @@ function discoverPayloadCollections(cwd) {
5522
5606
  out.push({
5523
5607
  name,
5524
5608
  slug,
5525
- filePath: path20.relative(cwd, filePath),
5609
+ filePath: path21.relative(cwd, filePath),
5526
5610
  fields: fields.slice(0, 20),
5527
5611
  hasAdmin
5528
5612
  });
@@ -5536,28 +5620,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
5536
5620
  function discoverAdminComponents(cwd, collections) {
5537
5621
  const out = [];
5538
5622
  for (const dir of ADMIN_COMPONENT_DIRS) {
5539
- const full = path20.join(cwd, dir);
5540
- if (!fs21.existsSync(full)) continue;
5623
+ const full = path21.join(cwd, dir);
5624
+ if (!fs22.existsSync(full)) continue;
5541
5625
  let entries;
5542
5626
  try {
5543
- entries = fs21.readdirSync(full, { withFileTypes: true });
5627
+ entries = fs22.readdirSync(full, { withFileTypes: true });
5544
5628
  } catch {
5545
5629
  continue;
5546
5630
  }
5547
5631
  for (const entry of entries) {
5548
- const entryPath = path20.join(full, entry.name);
5632
+ const entryPath = path21.join(full, entry.name);
5549
5633
  let name;
5550
5634
  let filePath;
5551
5635
  if (entry.isDirectory()) {
5552
5636
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
5553
- (f) => fs21.existsSync(path20.join(entryPath, f))
5637
+ (f) => fs22.existsSync(path21.join(entryPath, f))
5554
5638
  );
5555
5639
  if (!indexFile) continue;
5556
5640
  name = entry.name;
5557
- filePath = path20.relative(cwd, path20.join(entryPath, indexFile));
5641
+ filePath = path21.relative(cwd, path21.join(entryPath, indexFile));
5558
5642
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
5559
5643
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
5560
- filePath = path20.relative(cwd, entryPath);
5644
+ filePath = path21.relative(cwd, entryPath);
5561
5645
  } else {
5562
5646
  continue;
5563
5647
  }
@@ -5565,7 +5649,7 @@ function discoverAdminComponents(cwd, collections) {
5565
5649
  if (collections) {
5566
5650
  for (const col of collections) {
5567
5651
  try {
5568
- const colContent = fs21.readFileSync(path20.join(cwd, col.filePath), "utf-8");
5652
+ const colContent = fs22.readFileSync(path21.join(cwd, col.filePath), "utf-8");
5569
5653
  if (colContent.includes(name)) {
5570
5654
  usedInCollection = col.slug;
5571
5655
  break;
@@ -5584,8 +5668,8 @@ function scanApiRoutes(cwd) {
5584
5668
  const out = [];
5585
5669
  const appDirs = ["src/app", "app"];
5586
5670
  for (const appDir of appDirs) {
5587
- const apiDir = path20.join(cwd, appDir, "api");
5588
- if (!fs21.existsSync(apiDir)) continue;
5671
+ const apiDir = path21.join(cwd, appDir, "api");
5672
+ if (!fs22.existsSync(apiDir)) continue;
5589
5673
  walkApiRoutes(apiDir, "/api", cwd, out);
5590
5674
  break;
5591
5675
  }
@@ -5594,14 +5678,14 @@ function scanApiRoutes(cwd) {
5594
5678
  function walkApiRoutes(dir, prefix, cwd, out) {
5595
5679
  let entries;
5596
5680
  try {
5597
- entries = fs21.readdirSync(dir, { withFileTypes: true });
5681
+ entries = fs22.readdirSync(dir, { withFileTypes: true });
5598
5682
  } catch {
5599
5683
  return;
5600
5684
  }
5601
5685
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
5602
5686
  if (routeFile) {
5603
5687
  try {
5604
- const content = fs21.readFileSync(path20.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
5688
+ const content = fs22.readFileSync(path21.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
5605
5689
  const methods = HTTP_METHODS.filter(
5606
5690
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
5607
5691
  );
@@ -5609,7 +5693,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5609
5693
  out.push({
5610
5694
  path: prefix,
5611
5695
  methods,
5612
- filePath: path20.relative(cwd, path20.join(dir, routeFile.name))
5696
+ filePath: path21.relative(cwd, path21.join(dir, routeFile.name))
5613
5697
  });
5614
5698
  }
5615
5699
  } catch {
@@ -5620,7 +5704,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5620
5704
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5621
5705
  let segment = entry.name;
5622
5706
  if (segment.startsWith("(") && segment.endsWith(")")) {
5623
- walkApiRoutes(path20.join(dir, entry.name), prefix, cwd, out);
5707
+ walkApiRoutes(path21.join(dir, entry.name), prefix, cwd, out);
5624
5708
  continue;
5625
5709
  }
5626
5710
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -5628,7 +5712,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5628
5712
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
5629
5713
  segment = `:${segment.slice(1, -1)}`;
5630
5714
  }
5631
- walkApiRoutes(path20.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
5715
+ walkApiRoutes(path21.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
5632
5716
  }
5633
5717
  }
5634
5718
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -5648,10 +5732,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
5648
5732
  function scanEnvVars(cwd) {
5649
5733
  const candidates = [".env.example", ".env.local.example", ".env.template"];
5650
5734
  for (const envFile of candidates) {
5651
- const envPath = path20.join(cwd, envFile);
5652
- if (!fs21.existsSync(envPath)) continue;
5735
+ const envPath = path21.join(cwd, envFile);
5736
+ if (!fs22.existsSync(envPath)) continue;
5653
5737
  try {
5654
- const content = fs21.readFileSync(envPath, "utf-8");
5738
+ const content = fs22.readFileSync(envPath, "utf-8");
5655
5739
  const vars = [];
5656
5740
  for (const line of content.split("\n")) {
5657
5741
  const trimmed = line.trim();
@@ -5699,9 +5783,9 @@ function runQaDiscovery(cwd) {
5699
5783
  }
5700
5784
  function detectDevServer(cwd, out) {
5701
5785
  try {
5702
- const pkg = JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
5786
+ const pkg = JSON.parse(fs23.readFileSync(path22.join(cwd, "package.json"), "utf-8"));
5703
5787
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
5704
- 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";
5788
+ 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";
5705
5789
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
5706
5790
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
5707
5791
  else if (allDeps.vite) out.devPort = 5173;
@@ -5711,8 +5795,8 @@ function detectDevServer(cwd, out) {
5711
5795
  function scanFrontendRoutes(cwd, out) {
5712
5796
  const appDirs = ["src/app", "app"];
5713
5797
  for (const appDir of appDirs) {
5714
- const full = path21.join(cwd, appDir);
5715
- if (!fs22.existsSync(full)) continue;
5798
+ const full = path22.join(cwd, appDir);
5799
+ if (!fs23.existsSync(full)) continue;
5716
5800
  walkFrontendRoutes(full, "", out);
5717
5801
  break;
5718
5802
  }
@@ -5720,7 +5804,7 @@ function scanFrontendRoutes(cwd, out) {
5720
5804
  function walkFrontendRoutes(dir, prefix, out) {
5721
5805
  let entries;
5722
5806
  try {
5723
- entries = fs22.readdirSync(dir, { withFileTypes: true });
5807
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
5724
5808
  } catch {
5725
5809
  return;
5726
5810
  }
@@ -5737,7 +5821,7 @@ function walkFrontendRoutes(dir, prefix, out) {
5737
5821
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5738
5822
  let segment = entry.name;
5739
5823
  if (segment.startsWith("(") && segment.endsWith(")")) {
5740
- walkFrontendRoutes(path21.join(dir, entry.name), prefix, out);
5824
+ walkFrontendRoutes(path22.join(dir, entry.name), prefix, out);
5741
5825
  continue;
5742
5826
  }
5743
5827
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -5745,7 +5829,7 @@ function walkFrontendRoutes(dir, prefix, out) {
5745
5829
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
5746
5830
  segment = `:${segment.slice(1, -1)}`;
5747
5831
  }
5748
- walkFrontendRoutes(path21.join(dir, entry.name), `${prefix}/${segment}`, out);
5832
+ walkFrontendRoutes(path22.join(dir, entry.name), `${prefix}/${segment}`, out);
5749
5833
  }
5750
5834
  }
5751
5835
  function detectAuthFiles(cwd, out) {
@@ -5762,23 +5846,23 @@ function detectAuthFiles(cwd, out) {
5762
5846
  "src/app/api/oauth"
5763
5847
  ];
5764
5848
  for (const c of candidates) {
5765
- if (fs22.existsSync(path21.join(cwd, c))) out.authFiles.push(c);
5849
+ if (fs23.existsSync(path22.join(cwd, c))) out.authFiles.push(c);
5766
5850
  }
5767
5851
  }
5768
5852
  function detectRoles(cwd, out) {
5769
5853
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
5770
5854
  for (const rp of rolePaths) {
5771
- const dir = path21.join(cwd, rp);
5772
- if (!fs22.existsSync(dir)) continue;
5855
+ const dir = path22.join(cwd, rp);
5856
+ if (!fs23.existsSync(dir)) continue;
5773
5857
  let files;
5774
5858
  try {
5775
- files = fs22.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5859
+ files = fs23.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5776
5860
  } catch {
5777
5861
  continue;
5778
5862
  }
5779
5863
  for (const f of files) {
5780
5864
  try {
5781
- const content = fs22.readFileSync(path21.join(dir, f), "utf-8").slice(0, 5e3);
5865
+ const content = fs23.readFileSync(path22.join(dir, f), "utf-8").slice(0, 5e3);
5782
5866
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
5783
5867
  if (roleMatches) {
5784
5868
  for (const m of roleMatches) {
@@ -6011,8 +6095,8 @@ function failedAction3(reason) {
6011
6095
  }
6012
6096
 
6013
6097
  // src/scripts/dispatchJobFileTicks.ts
6014
- import * as fs24 from "fs";
6015
- import * as path23 from "path";
6098
+ import * as fs25 from "fs";
6099
+ import * as path24 from "path";
6016
6100
 
6017
6101
  // src/scripts/jobFrontmatter.ts
6018
6102
  var SCHEDULE_EVERY_VALUES = [
@@ -6271,8 +6355,8 @@ var ContentsApiBackend = class {
6271
6355
  };
6272
6356
 
6273
6357
  // src/scripts/jobState/localFileBackend.ts
6274
- import * as fs23 from "fs";
6275
- import * as path22 from "path";
6358
+ import * as fs24 from "fs";
6359
+ import * as path23 from "path";
6276
6360
  var LocalFileBackend = class {
6277
6361
  name = "local-file";
6278
6362
  cwd;
@@ -6287,7 +6371,7 @@ var LocalFileBackend = class {
6287
6371
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
6288
6372
  this.cwd = opts.cwd;
6289
6373
  this.jobsDir = opts.jobsDir;
6290
- this.absDir = path22.join(opts.cwd, opts.jobsDir);
6374
+ this.absDir = path23.join(opts.cwd, opts.jobsDir);
6291
6375
  this.owner = opts.owner;
6292
6376
  this.repo = opts.repo;
6293
6377
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -6302,7 +6386,7 @@ var LocalFileBackend = class {
6302
6386
  `);
6303
6387
  return;
6304
6388
  }
6305
- fs23.mkdirSync(this.absDir, { recursive: true });
6389
+ fs24.mkdirSync(this.absDir, { recursive: true });
6306
6390
  const prefix = this.cacheKeyPrefix();
6307
6391
  const probeKey = `${prefix}probe-${Date.now()}`;
6308
6392
  try {
@@ -6331,7 +6415,7 @@ var LocalFileBackend = class {
6331
6415
  `);
6332
6416
  return;
6333
6417
  }
6334
- if (!fs23.existsSync(this.absDir)) {
6418
+ if (!fs24.existsSync(this.absDir)) {
6335
6419
  return;
6336
6420
  }
6337
6421
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -6347,11 +6431,11 @@ var LocalFileBackend = class {
6347
6431
  }
6348
6432
  load(slug) {
6349
6433
  const relPath = stateFilePath(this.jobsDir, slug);
6350
- const absPath = path22.join(this.cwd, relPath);
6351
- if (!fs23.existsSync(absPath)) {
6434
+ const absPath = path23.join(this.cwd, relPath);
6435
+ if (!fs24.existsSync(absPath)) {
6352
6436
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
6353
6437
  }
6354
- const raw = fs23.readFileSync(absPath, "utf-8");
6438
+ const raw = fs24.readFileSync(absPath, "utf-8");
6355
6439
  let parsed;
6356
6440
  try {
6357
6441
  parsed = JSON.parse(raw);
@@ -6368,10 +6452,10 @@ var LocalFileBackend = class {
6368
6452
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
6369
6453
  return false;
6370
6454
  }
6371
- const absPath = path22.join(this.cwd, loaded.path);
6372
- fs23.mkdirSync(path22.dirname(absPath), { recursive: true });
6455
+ const absPath = path23.join(this.cwd, loaded.path);
6456
+ fs24.mkdirSync(path23.dirname(absPath), { recursive: true });
6373
6457
  const body = JSON.stringify(next, null, 2) + "\n";
6374
- fs23.writeFileSync(absPath, body, "utf-8");
6458
+ fs24.writeFileSync(absPath, body, "utf-8");
6375
6459
  return true;
6376
6460
  }
6377
6461
  cacheKeyPrefix() {
@@ -6449,7 +6533,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
6449
6533
  await backend.hydrate();
6450
6534
  }
6451
6535
  try {
6452
- const slugs = listJobSlugs(path23.join(ctx.cwd, jobsDir));
6536
+ const slugs = listJobSlugs(path24.join(ctx.cwd, jobsDir));
6453
6537
  ctx.data.jobSlugCount = slugs.length;
6454
6538
  if (slugs.length === 0) {
6455
6539
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -6554,17 +6638,17 @@ function formatAgo(ms) {
6554
6638
  }
6555
6639
  function readJobFrontmatter(cwd, jobsDir, slug) {
6556
6640
  try {
6557
- const raw = fs24.readFileSync(path23.join(cwd, jobsDir, `${slug}.md`), "utf-8");
6641
+ const raw = fs25.readFileSync(path24.join(cwd, jobsDir, `${slug}.md`), "utf-8");
6558
6642
  return splitFrontmatter(raw).frontmatter;
6559
6643
  } catch {
6560
6644
  return {};
6561
6645
  }
6562
6646
  }
6563
6647
  function listJobSlugs(absDir) {
6564
- if (!fs24.existsSync(absDir)) return [];
6648
+ if (!fs25.existsSync(absDir)) return [];
6565
6649
  let entries;
6566
6650
  try {
6567
- entries = fs24.readdirSync(absDir, { withFileTypes: true });
6651
+ entries = fs25.readdirSync(absDir, { withFileTypes: true });
6568
6652
  } catch {
6569
6653
  return [];
6570
6654
  }
@@ -6928,6 +7012,15 @@ function collectExpectedTests(raw) {
6928
7012
  }
6929
7013
 
6930
7014
  // src/scripts/finalizeGoal.ts
7015
+ function prIssueNumbers(pr) {
7016
+ const nums = new Set(extractClosesIssues(pr.body));
7017
+ const headMatch = pr.headRefName.match(/^(\d+)-/);
7018
+ if (headMatch) {
7019
+ const n = Number.parseInt(headMatch[1], 10);
7020
+ if (Number.isFinite(n)) nums.add(n);
7021
+ }
7022
+ return [...nums];
7023
+ }
6931
7024
  var finalizeGoal = async (ctx) => {
6932
7025
  const goal = ctx.data.goal;
6933
7026
  if (!goal) return;
@@ -6966,8 +7059,24 @@ var finalizeGoal = async (ctx) => {
6966
7059
  `[goal-tick] leaf PR #${leaf.number} is the deliverable (cumulative goal diff vs ${goal.defaultBranch}) \u2014 left open for human merge
6967
7060
  `
6968
7061
  );
7062
+ const uncarriedIssues = /* @__PURE__ */ new Set();
6969
7063
  const others = (goal.openTaskPrs ?? []).filter((p) => p.number !== leaf.number);
6970
7064
  for (const pr of others) {
7065
+ const contained = branchContains(leaf.headRefName, pr.headRefName, ctx.cwd);
7066
+ if (!contained.ok || contained.value !== true) {
7067
+ const why = contained.ok ? `commits on \`${pr.headRefName}\` are NOT reachable from the deliverable leaf \`${leaf.headRefName}\`` : `could not verify containment (${contained.error})`;
7068
+ process.stderr.write(
7069
+ `[goal-tick] finalizeGoal: NOT closing PR #${pr.number} \u2014 ${why}; leaving it open (broken stack)
7070
+ `
7071
+ );
7072
+ for (const n of prIssueNumbers(pr)) uncarriedIssues.add(n);
7073
+ commentOnIssue(
7074
+ pr.number,
7075
+ `\u26A0\uFE0F _Stacked-PR finalize: this PR's commits are **not** carried by the goal's deliverable PR #${leaf.number} (the stack chain was broken). Leaving this PR open so its work isn't lost \u2014 review and land it manually._`,
7076
+ ctx.cwd
7077
+ );
7078
+ continue;
7079
+ }
6971
7080
  process.stdout.write(`[goal-tick] closing intermediate stacked PR #${pr.number} (carried by deliverable leaf)
6972
7081
  `);
6973
7082
  const closed = closePr(
@@ -6982,6 +7091,13 @@ var finalizeGoal = async (ctx) => {
6982
7091
  }
6983
7092
  const openIssues = (goal.childTasks ?? []).filter((t) => t.state === "OPEN");
6984
7093
  for (const t of openIssues) {
7094
+ if (uncarriedIssues.has(t.number)) {
7095
+ process.stderr.write(
7096
+ `[goal-tick] finalizeGoal: NOT closing task issue #${t.number} \u2014 its PR's work is not carried by the deliverable (broken stack)
7097
+ `
7098
+ );
7099
+ continue;
7100
+ }
6985
7101
  process.stdout.write(`[goal-tick] closing task issue #${t.number} (goal finalized \u2014 carried by PR #${leaf.number})
6986
7102
  `);
6987
7103
  const closed = closeIssue(
@@ -7241,7 +7357,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
7241
7357
 
7242
7358
  // src/gha.ts
7243
7359
  import { execFileSync as execFileSync16 } from "child_process";
7244
- import * as fs25 from "fs";
7360
+ import * as fs26 from "fs";
7245
7361
  function getRunUrl() {
7246
7362
  const server = process.env.GITHUB_SERVER_URL;
7247
7363
  const repo = process.env.GITHUB_REPOSITORY;
@@ -7252,10 +7368,10 @@ function getRunUrl() {
7252
7368
  function reactToTriggerComment(cwd) {
7253
7369
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
7254
7370
  const eventPath = process.env.GITHUB_EVENT_PATH;
7255
- if (!eventPath || !fs25.existsSync(eventPath)) return;
7371
+ if (!eventPath || !fs26.existsSync(eventPath)) return;
7256
7372
  let event = null;
7257
7373
  try {
7258
- event = JSON.parse(fs25.readFileSync(eventPath, "utf-8"));
7374
+ event = JSON.parse(fs26.readFileSync(eventPath, "utf-8"));
7259
7375
  } catch {
7260
7376
  return;
7261
7377
  }
@@ -7548,22 +7664,22 @@ var handleAbandonedGoal = async (ctx) => {
7548
7664
 
7549
7665
  // src/scripts/initFlow.ts
7550
7666
  import { execFileSync as execFileSync18 } from "child_process";
7551
- import * as fs27 from "fs";
7552
- import * as path25 from "path";
7667
+ import * as fs28 from "fs";
7668
+ import * as path26 from "path";
7553
7669
 
7554
7670
  // src/scripts/loadQaGuide.ts
7555
- import * as fs26 from "fs";
7556
- import * as path24 from "path";
7671
+ import * as fs27 from "fs";
7672
+ import * as path25 from "path";
7557
7673
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
7558
7674
  var loadQaGuide = async (ctx) => {
7559
- const full = path24.join(ctx.cwd, QA_GUIDE_REL_PATH);
7560
- if (!fs26.existsSync(full)) {
7675
+ const full = path25.join(ctx.cwd, QA_GUIDE_REL_PATH);
7676
+ if (!fs27.existsSync(full)) {
7561
7677
  ctx.data.qaGuide = "";
7562
7678
  ctx.data.qaGuidePath = "";
7563
7679
  return;
7564
7680
  }
7565
7681
  try {
7566
- ctx.data.qaGuide = fs26.readFileSync(full, "utf-8");
7682
+ ctx.data.qaGuide = fs27.readFileSync(full, "utf-8");
7567
7683
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
7568
7684
  } catch {
7569
7685
  ctx.data.qaGuide = "";
@@ -7573,9 +7689,9 @@ var loadQaGuide = async (ctx) => {
7573
7689
 
7574
7690
  // src/scripts/initFlow.ts
7575
7691
  function detectPackageManager(cwd) {
7576
- if (fs27.existsSync(path25.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7577
- if (fs27.existsSync(path25.join(cwd, "yarn.lock"))) return "yarn";
7578
- if (fs27.existsSync(path25.join(cwd, "bun.lockb"))) return "bun";
7692
+ if (fs28.existsSync(path26.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7693
+ if (fs28.existsSync(path26.join(cwd, "yarn.lock"))) return "yarn";
7694
+ if (fs28.existsSync(path26.join(cwd, "bun.lockb"))) return "bun";
7579
7695
  return "npm";
7580
7696
  }
7581
7697
  function qualityCommandsFor(pm) {
@@ -7697,48 +7813,48 @@ function performInit(cwd, force) {
7697
7813
  const pm = detectPackageManager(cwd);
7698
7814
  const ownerRepo = detectOwnerRepo(cwd);
7699
7815
  const defaultBranch2 = defaultBranchFromGit(cwd);
7700
- const configPath = path25.join(cwd, "kody.config.json");
7701
- if (fs27.existsSync(configPath) && !force) {
7816
+ const configPath = path26.join(cwd, "kody.config.json");
7817
+ if (fs28.existsSync(configPath) && !force) {
7702
7818
  skipped.push("kody.config.json");
7703
7819
  } else {
7704
7820
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
7705
- fs27.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7821
+ fs28.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7706
7822
  `);
7707
7823
  wrote.push("kody.config.json");
7708
7824
  }
7709
- const workflowDir = path25.join(cwd, ".github", "workflows");
7710
- const workflowPath = path25.join(workflowDir, "kody.yml");
7711
- if (fs27.existsSync(workflowPath) && !force) {
7825
+ const workflowDir = path26.join(cwd, ".github", "workflows");
7826
+ const workflowPath = path26.join(workflowDir, "kody.yml");
7827
+ if (fs28.existsSync(workflowPath) && !force) {
7712
7828
  skipped.push(".github/workflows/kody.yml");
7713
7829
  } else {
7714
- fs27.mkdirSync(workflowDir, { recursive: true });
7715
- fs27.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
7830
+ fs28.mkdirSync(workflowDir, { recursive: true });
7831
+ fs28.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
7716
7832
  wrote.push(".github/workflows/kody.yml");
7717
7833
  }
7718
- const hasUi = fs27.existsSync(path25.join(cwd, "src/app")) || fs27.existsSync(path25.join(cwd, "app")) || fs27.existsSync(path25.join(cwd, "pages"));
7834
+ const hasUi = fs28.existsSync(path26.join(cwd, "src/app")) || fs28.existsSync(path26.join(cwd, "app")) || fs28.existsSync(path26.join(cwd, "pages"));
7719
7835
  if (hasUi) {
7720
- const qaGuidePath = path25.join(cwd, QA_GUIDE_REL_PATH);
7721
- if (fs27.existsSync(qaGuidePath) && !force) {
7836
+ const qaGuidePath = path26.join(cwd, QA_GUIDE_REL_PATH);
7837
+ if (fs28.existsSync(qaGuidePath) && !force) {
7722
7838
  skipped.push(QA_GUIDE_REL_PATH);
7723
7839
  } else {
7724
- fs27.mkdirSync(path25.dirname(qaGuidePath), { recursive: true });
7840
+ fs28.mkdirSync(path26.dirname(qaGuidePath), { recursive: true });
7725
7841
  const discovery = runQaDiscovery(cwd);
7726
- fs27.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
7842
+ fs28.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
7727
7843
  wrote.push(QA_GUIDE_REL_PATH);
7728
7844
  }
7729
7845
  }
7730
7846
  const builtinJobs = listBuiltinJobs();
7731
7847
  if (builtinJobs.length > 0) {
7732
- const jobsDir = path25.join(cwd, ".kody", "jobs");
7733
- fs27.mkdirSync(jobsDir, { recursive: true });
7848
+ const jobsDir = path26.join(cwd, ".kody", "jobs");
7849
+ fs28.mkdirSync(jobsDir, { recursive: true });
7734
7850
  for (const job of builtinJobs) {
7735
- const rel = path25.join(".kody", "jobs", `${job.slug}.md`);
7736
- const target = path25.join(cwd, rel);
7737
- if (fs27.existsSync(target) && !force) {
7851
+ const rel = path26.join(".kody", "jobs", `${job.slug}.md`);
7852
+ const target = path26.join(cwd, rel);
7853
+ if (fs28.existsSync(target) && !force) {
7738
7854
  skipped.push(rel);
7739
7855
  continue;
7740
7856
  }
7741
- fs27.writeFileSync(target, fs27.readFileSync(job.filePath, "utf-8"));
7857
+ fs28.writeFileSync(target, fs28.readFileSync(job.filePath, "utf-8"));
7742
7858
  wrote.push(rel);
7743
7859
  }
7744
7860
  }
@@ -7750,12 +7866,12 @@ function performInit(cwd, force) {
7750
7866
  continue;
7751
7867
  }
7752
7868
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
7753
- const target = path25.join(workflowDir, `kody-${exe.name}.yml`);
7754
- if (fs27.existsSync(target) && !force) {
7869
+ const target = path26.join(workflowDir, `kody-${exe.name}.yml`);
7870
+ if (fs28.existsSync(target) && !force) {
7755
7871
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
7756
7872
  continue;
7757
7873
  }
7758
- fs27.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
7874
+ fs28.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
7759
7875
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
7760
7876
  }
7761
7877
  let labels;
@@ -7833,14 +7949,14 @@ init_loadConventions();
7833
7949
  init_loadCoverageRules();
7834
7950
 
7835
7951
  // src/goal/state.ts
7836
- import * as fs28 from "fs";
7837
- import * as path26 from "path";
7952
+ import * as fs29 from "fs";
7953
+ import * as path27 from "path";
7838
7954
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
7839
7955
  var GoalStateError = class extends Error {
7840
- constructor(path34, message) {
7841
- super(`Invalid goal state at ${path34}:
7956
+ constructor(path35, message) {
7957
+ super(`Invalid goal state at ${path35}:
7842
7958
  ${message}`);
7843
- this.path = path34;
7959
+ this.path = path35;
7844
7960
  this.name = "GoalStateError";
7845
7961
  }
7846
7962
  path;
@@ -7888,16 +8004,16 @@ function serializeGoalState(s) {
7888
8004
  `;
7889
8005
  }
7890
8006
  function goalStatePath(cwd, goalId) {
7891
- return path26.join(cwd, ".kody", "goals", goalId, "state.json");
8007
+ return path27.join(cwd, ".kody", "goals", goalId, "state.json");
7892
8008
  }
7893
8009
  function readGoalState(cwd, goalId) {
7894
8010
  const file = goalStatePath(cwd, goalId);
7895
- if (!fs28.existsSync(file)) {
8011
+ if (!fs29.existsSync(file)) {
7896
8012
  throw new GoalStateError(file, "file not found");
7897
8013
  }
7898
8014
  let raw;
7899
8015
  try {
7900
- raw = JSON.parse(fs28.readFileSync(file, "utf-8"));
8016
+ raw = JSON.parse(fs29.readFileSync(file, "utf-8"));
7901
8017
  } catch (err) {
7902
8018
  throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
7903
8019
  }
@@ -7905,8 +8021,8 @@ function readGoalState(cwd, goalId) {
7905
8021
  }
7906
8022
  function writeGoalState(cwd, goalId, state) {
7907
8023
  const file = goalStatePath(cwd, goalId);
7908
- fs28.mkdirSync(path26.dirname(file), { recursive: true });
7909
- fs28.writeFileSync(file, serializeGoalState(state), "utf-8");
8024
+ fs29.mkdirSync(path27.dirname(file), { recursive: true });
8025
+ fs29.writeFileSync(file, serializeGoalState(state), "utf-8");
7910
8026
  }
7911
8027
  function nowIso() {
7912
8028
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -8003,8 +8119,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
8003
8119
  };
8004
8120
 
8005
8121
  // src/scripts/loadJobFromFile.ts
8006
- import * as fs29 from "fs";
8007
- import * as path27 from "path";
8122
+ import * as fs30 from "fs";
8123
+ import * as path28 from "path";
8008
8124
  var loadJobFromFile = async (ctx, _profile, args) => {
8009
8125
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
8010
8126
  const slugArg = String(args?.slugArg ?? "job");
@@ -8012,11 +8128,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8012
8128
  if (!slug) {
8013
8129
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
8014
8130
  }
8015
- const absPath = path27.join(ctx.cwd, jobsDir, `${slug}.md`);
8016
- if (!fs29.existsSync(absPath)) {
8131
+ const absPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
8132
+ if (!fs30.existsSync(absPath)) {
8017
8133
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
8018
8134
  }
8019
- const raw = fs29.readFileSync(absPath, "utf-8");
8135
+ const raw = fs30.readFileSync(absPath, "utf-8");
8020
8136
  const { title, body } = parseJobFile(raw, slug);
8021
8137
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
8022
8138
  const loaded = await backend.load(slug);
@@ -8055,8 +8171,8 @@ init_loadPriorArt();
8055
8171
  init_events();
8056
8172
 
8057
8173
  // src/taskContext.ts
8058
- import * as fs31 from "fs";
8059
- import * as path29 from "path";
8174
+ import * as fs32 from "fs";
8175
+ import * as path30 from "path";
8060
8176
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
8061
8177
  function buildTaskContext(args) {
8062
8178
  return {
@@ -8072,10 +8188,10 @@ function buildTaskContext(args) {
8072
8188
  }
8073
8189
  function persistTaskContext(cwd, ctx) {
8074
8190
  try {
8075
- const dir = path29.join(cwd, ".kody", "runs", ctx.runId);
8076
- fs31.mkdirSync(dir, { recursive: true });
8077
- const file = path29.join(dir, "task-context.json");
8078
- fs31.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8191
+ const dir = path30.join(cwd, ".kody", "runs", ctx.runId);
8192
+ fs32.mkdirSync(dir, { recursive: true });
8193
+ const file = path30.join(dir, "task-context.json");
8194
+ fs32.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8079
8195
  `);
8080
8196
  return file;
8081
8197
  } catch (err) {
@@ -9438,8 +9554,8 @@ function resolveBaseOverride(value) {
9438
9554
 
9439
9555
  // src/scripts/runTickScript.ts
9440
9556
  import { spawnSync } from "child_process";
9441
- import * as fs32 from "fs";
9442
- import * as path30 from "path";
9557
+ import * as fs33 from "fs";
9558
+ import * as path31 from "path";
9443
9559
  var runTickScript = async (ctx, _profile, args) => {
9444
9560
  ctx.skipAgent = true;
9445
9561
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
@@ -9451,13 +9567,13 @@ var runTickScript = async (ctx, _profile, args) => {
9451
9567
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
9452
9568
  return;
9453
9569
  }
9454
- const jobPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
9455
- if (!fs32.existsSync(jobPath)) {
9570
+ const jobPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
9571
+ if (!fs33.existsSync(jobPath)) {
9456
9572
  ctx.output.exitCode = 99;
9457
9573
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
9458
9574
  return;
9459
9575
  }
9460
- const raw = fs32.readFileSync(jobPath, "utf-8");
9576
+ const raw = fs33.readFileSync(jobPath, "utf-8");
9461
9577
  const { frontmatter } = splitFrontmatter(raw);
9462
9578
  const tickScript = frontmatter.tickScript;
9463
9579
  if (!tickScript) {
@@ -9465,8 +9581,8 @@ var runTickScript = async (ctx, _profile, args) => {
9465
9581
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
9466
9582
  return;
9467
9583
  }
9468
- const scriptPath = path30.isAbsolute(tickScript) ? tickScript : path30.join(ctx.cwd, tickScript);
9469
- if (!fs32.existsSync(scriptPath)) {
9584
+ const scriptPath = path31.isAbsolute(tickScript) ? tickScript : path31.join(ctx.cwd, tickScript);
9585
+ if (!fs33.existsSync(scriptPath)) {
9470
9586
  ctx.output.exitCode = 99;
9471
9587
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
9472
9588
  return;
@@ -10488,7 +10604,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
10488
10604
  };
10489
10605
 
10490
10606
  // src/scripts/writeRunSummary.ts
10491
- import * as fs33 from "fs";
10607
+ import * as fs34 from "fs";
10492
10608
  var writeRunSummary = async (ctx, profile) => {
10493
10609
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
10494
10610
  if (!summaryPath) return;
@@ -10510,7 +10626,7 @@ var writeRunSummary = async (ctx, profile) => {
10510
10626
  if (reason) lines.push(`- **Reason:** ${reason}`);
10511
10627
  lines.push("");
10512
10628
  try {
10513
- fs33.appendFileSync(summaryPath, `${lines.join("\n")}
10629
+ fs34.appendFileSync(summaryPath, `${lines.join("\n")}
10514
10630
  `);
10515
10631
  } catch {
10516
10632
  }
@@ -10734,9 +10850,9 @@ async function runExecutable(profileName, input) {
10734
10850
  data: { ...input.preloadedData ?? {} },
10735
10851
  output: { exitCode: 0 }
10736
10852
  };
10737
- const ndjsonDir = path31.join(input.cwd, ".kody");
10853
+ const ndjsonDir = path32.join(input.cwd, ".kody");
10738
10854
  const invokeAgent = async (prompt) => {
10739
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path31.isAbsolute(p) ? p : path31.resolve(profile.dir, p)).filter((p) => p.length > 0);
10855
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path32.isAbsolute(p) ? p : path32.resolve(profile.dir, p)).filter((p) => p.length > 0);
10740
10856
  const syntheticPath = ctx.data.syntheticPluginPath;
10741
10857
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
10742
10858
  return runAgent({
@@ -10931,7 +11047,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
10931
11047
  function getProfileInputsForChild(profileName, _cwd) {
10932
11048
  try {
10933
11049
  const profilePath = resolveProfilePath(profileName);
10934
- if (!fs34.existsSync(profilePath)) return null;
11050
+ if (!fs35.existsSync(profilePath)) return null;
10935
11051
  return loadProfile(profilePath).inputs;
10936
11052
  } catch {
10937
11053
  return null;
@@ -10940,17 +11056,17 @@ function getProfileInputsForChild(profileName, _cwd) {
10940
11056
  function resolveProfilePath(profileName) {
10941
11057
  const found = resolveExecutable(profileName);
10942
11058
  if (found) return found;
10943
- const here = path31.dirname(new URL(import.meta.url).pathname);
11059
+ const here = path32.dirname(new URL(import.meta.url).pathname);
10944
11060
  const candidates = [
10945
- path31.join(here, "executables", profileName, "profile.json"),
11061
+ path32.join(here, "executables", profileName, "profile.json"),
10946
11062
  // same-dir sibling (dev)
10947
- path31.join(here, "..", "executables", profileName, "profile.json"),
11063
+ path32.join(here, "..", "executables", profileName, "profile.json"),
10948
11064
  // up one (prod: dist/bin → dist/executables)
10949
- path31.join(here, "..", "src", "executables", profileName, "profile.json")
11065
+ path32.join(here, "..", "src", "executables", profileName, "profile.json")
10950
11066
  // fallback
10951
11067
  ];
10952
11068
  for (const c of candidates) {
10953
- if (fs34.existsSync(c)) return c;
11069
+ if (fs35.existsSync(c)) return c;
10954
11070
  }
10955
11071
  return candidates[0];
10956
11072
  }
@@ -11050,8 +11166,8 @@ function resolveShellTimeoutMs(entry) {
11050
11166
  var SIGKILL_GRACE_MS = 5e3;
11051
11167
  async function runShellEntry(entry, ctx, profile) {
11052
11168
  const shellName = entry.shell;
11053
- const shellPath = path31.join(profile.dir, shellName);
11054
- if (!fs34.existsSync(shellPath)) {
11169
+ const shellPath = path32.join(profile.dir, shellName);
11170
+ if (!fs35.existsSync(shellPath)) {
11055
11171
  ctx.skipAgent = true;
11056
11172
  ctx.output.exitCode = 99;
11057
11173
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -11391,8 +11507,8 @@ function resetWorkingTree2(cwd) {
11391
11507
  }
11392
11508
  function readContainerState(ctx, child, reader) {
11393
11509
  const issueNumber = ctx.args.issue;
11394
- const cached = ctx.data.taskState;
11395
- const prUrl = cached?.core?.prUrl;
11510
+ const cached2 = ctx.data.taskState;
11511
+ const prUrl = cached2?.core?.prUrl;
11396
11512
  const prNumber = prUrl ? parsePrNumber4(prUrl) : null;
11397
11513
  if (child.target === "pr" && prNumber) {
11398
11514
  try {
@@ -11406,8 +11522,8 @@ function readContainerState(ctx, child, reader) {
11406
11522
  } catch {
11407
11523
  }
11408
11524
  }
11409
- if (cached && typeof cached === "object") {
11410
- return cached;
11525
+ if (cached2 && typeof cached2 === "object") {
11526
+ return cached2;
11411
11527
  }
11412
11528
  return {
11413
11529
  schemaVersion: 1,
@@ -11530,9 +11646,9 @@ function resolveAuthToken(env = process.env) {
11530
11646
  return token;
11531
11647
  }
11532
11648
  function detectPackageManager2(cwd) {
11533
- if (fs35.existsSync(path32.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
11534
- if (fs35.existsSync(path32.join(cwd, "yarn.lock"))) return "yarn";
11535
- if (fs35.existsSync(path32.join(cwd, "bun.lockb"))) return "bun";
11649
+ if (fs36.existsSync(path33.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
11650
+ if (fs36.existsSync(path33.join(cwd, "yarn.lock"))) return "yarn";
11651
+ if (fs36.existsSync(path33.join(cwd, "bun.lockb"))) return "bun";
11536
11652
  return "npm";
11537
11653
  }
11538
11654
  function shellOut(cmd, args, cwd, stream = true) {
@@ -11619,11 +11735,11 @@ function configureGitIdentity(cwd) {
11619
11735
  }
11620
11736
  function postFailureTail(issueNumber, cwd, reason) {
11621
11737
  if (!issueNumber) return;
11622
- const logPath = path32.join(cwd, ".kody", "last-run.jsonl");
11738
+ const logPath = path33.join(cwd, ".kody", "last-run.jsonl");
11623
11739
  let tail = "";
11624
11740
  try {
11625
- if (fs35.existsSync(logPath)) {
11626
- const content = fs35.readFileSync(logPath, "utf-8");
11741
+ if (fs36.existsSync(logPath)) {
11742
+ const content = fs36.readFileSync(logPath, "utf-8");
11627
11743
  tail = content.slice(-3e3);
11628
11744
  }
11629
11745
  } catch {
@@ -11648,7 +11764,7 @@ async function runCi(argv) {
11648
11764
  return 0;
11649
11765
  }
11650
11766
  const args = parseCiArgs(argv);
11651
- const cwd = args.cwd ? path32.resolve(args.cwd) : process.cwd();
11767
+ const cwd = args.cwd ? path33.resolve(args.cwd) : process.cwd();
11652
11768
  let earlyConfig;
11653
11769
  try {
11654
11770
  earlyConfig = loadConfig(cwd);
@@ -11658,9 +11774,9 @@ async function runCi(argv) {
11658
11774
  const eventName = process.env.GITHUB_EVENT_NAME;
11659
11775
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
11660
11776
  let manualWorkflowDispatch = false;
11661
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs35.existsSync(dispatchEventPath)) {
11777
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs36.existsSync(dispatchEventPath)) {
11662
11778
  try {
11663
- const evt = JSON.parse(fs35.readFileSync(dispatchEventPath, "utf-8"));
11779
+ const evt = JSON.parse(fs36.readFileSync(dispatchEventPath, "utf-8"));
11664
11780
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
11665
11781
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
11666
11782
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -11919,9 +12035,9 @@ function parseChatArgs(argv, env = process.env) {
11919
12035
  return result;
11920
12036
  }
11921
12037
  function commitChatFiles(cwd, sessionId, verbose) {
11922
- const sessionFile = path33.relative(cwd, sessionFilePath(cwd, sessionId));
11923
- const eventsFile = path33.relative(cwd, eventsFilePath(cwd, sessionId));
11924
- const paths = [sessionFile, eventsFile].filter((p) => fs36.existsSync(path33.join(cwd, p)));
12038
+ const sessionFile = path34.relative(cwd, sessionFilePath(cwd, sessionId));
12039
+ const eventsFile = path34.relative(cwd, eventsFilePath(cwd, sessionId));
12040
+ const paths = [sessionFile, eventsFile].filter((p) => fs37.existsSync(path34.join(cwd, p)));
11925
12041
  if (paths.length === 0) return;
11926
12042
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
11927
12043
  try {
@@ -11959,7 +12075,7 @@ async function runChat(argv) {
11959
12075
  ${CHAT_HELP}`);
11960
12076
  return 64;
11961
12077
  }
11962
- const cwd = args.cwd ? path33.resolve(args.cwd) : process.cwd();
12078
+ const cwd = args.cwd ? path34.resolve(args.cwd) : process.cwd();
11963
12079
  const sessionId = args.sessionId;
11964
12080
  const unpackedSecrets = unpackAllSecrets();
11965
12081
  if (unpackedSecrets > 0) {
@@ -12011,7 +12127,7 @@ ${CHAT_HELP}`);
12011
12127
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
12012
12128
  const meta = readMeta(sessionFile);
12013
12129
  process.stdout.write(
12014
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs36.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
12130
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs37.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
12015
12131
  `
12016
12132
  );
12017
12133
  try {