@kody-ade/kody-engine 0.4.141 → 0.4.143

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 +603 -443
  2. package/package.json +1 -1
package/dist/bin/kody.js CHANGED
@@ -93,7 +93,7 @@ var init_events = __esm({
93
93
  // src/verify.ts
94
94
  import { spawn } from "child_process";
95
95
  function runCommand(command, cwd) {
96
- return new Promise((resolve5) => {
96
+ return new Promise((resolve6) => {
97
97
  const start = Date.now();
98
98
  const child = spawn(command, {
99
99
  cwd,
@@ -122,11 +122,11 @@ function runCommand(command, cwd) {
122
122
  child.on("exit", (code) => {
123
123
  clearTimeout(timer);
124
124
  const tail = Buffer.concat(buffers).toString("utf-8").slice(-TAIL_CHARS);
125
- resolve5({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
125
+ resolve6({ exitCode: code ?? -1, durationMs: Date.now() - start, tail });
126
126
  });
127
127
  child.on("error", (err) => {
128
128
  clearTimeout(timer);
129
- resolve5({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
129
+ resolve6({ exitCode: -1, durationMs: Date.now() - start, tail: err.message });
130
130
  });
131
131
  });
132
132
  }
@@ -358,6 +358,141 @@ var init_submitMcp = __esm({
358
358
  }
359
359
  });
360
360
 
361
+ // src/repoWorkspace.ts
362
+ import { spawn as spawn2, spawnSync } from "child_process";
363
+ import * as fs6 from "fs";
364
+ import * as path6 from "path";
365
+ async function resolveAndClone(reposRoot, repo, repoToken, cloneRepo) {
366
+ const name = repo?.trim();
367
+ if (!name || !REPO_RE.test(name)) return null;
368
+ const root = path6.resolve(reposRoot);
369
+ const dir = path6.resolve(root, name);
370
+ if (dir !== root && !dir.startsWith(root + path6.sep)) return null;
371
+ if (fs6.existsSync(path6.join(dir, ".git"))) return dir;
372
+ const inflight = repoClones.get(dir);
373
+ if (inflight) {
374
+ await inflight;
375
+ return dir;
376
+ }
377
+ const p = cloneRepo(name, repoToken, dir).finally(() => {
378
+ if (repoClones.get(dir) === p) repoClones.delete(dir);
379
+ });
380
+ repoClones.set(dir, p);
381
+ await p;
382
+ return dir;
383
+ }
384
+ async function ensureRepoCwd(opts) {
385
+ const dir = await resolveAndClone(
386
+ opts.reposRoot,
387
+ opts.repo,
388
+ opts.repoToken,
389
+ opts.cloneRepo
390
+ );
391
+ return dir ?? opts.baseCwd;
392
+ }
393
+ async function fetchRepo(opts) {
394
+ const dir = await resolveAndClone(
395
+ opts.reposRoot,
396
+ opts.repo,
397
+ opts.repoToken,
398
+ opts.cloneRepo ?? defaultCloneRepo
399
+ );
400
+ if (!dir) {
401
+ throw new Error(
402
+ `invalid repo "${opts.repo}" \u2014 expected "owner/name" with no path escapes`
403
+ );
404
+ }
405
+ return dir;
406
+ }
407
+ var REPO_RE, repoClones, defaultCloneRepo;
408
+ var init_repoWorkspace = __esm({
409
+ "src/repoWorkspace.ts"() {
410
+ "use strict";
411
+ REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
412
+ repoClones = /* @__PURE__ */ new Map();
413
+ defaultCloneRepo = (repo, token, dir) => {
414
+ fs6.mkdirSync(path6.dirname(dir), { recursive: true });
415
+ const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
416
+ return new Promise((resolve6, reject) => {
417
+ const child = spawn2("git", ["clone", "--depth=1", authUrl, dir], {
418
+ stdio: "inherit"
419
+ });
420
+ child.on("exit", (code) => {
421
+ if (code !== 0) {
422
+ reject(new Error(`git clone ${repo} failed (exit ${code})`));
423
+ return;
424
+ }
425
+ try {
426
+ const name = process.env.GIT_AUTHOR_NAME ?? "Kody Bot";
427
+ const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
428
+ spawnSync("git", ["-C", dir, "config", "user.name", name]);
429
+ spawnSync("git", ["-C", dir, "config", "user.email", email]);
430
+ } catch {
431
+ }
432
+ resolve6();
433
+ });
434
+ child.on("error", reject);
435
+ });
436
+ };
437
+ }
438
+ });
439
+
440
+ // src/fetchRepoMcp.ts
441
+ var fetchRepoMcp_exports = {};
442
+ __export(fetchRepoMcp_exports, {
443
+ buildFetchRepoMcpServer: () => buildFetchRepoMcpServer
444
+ });
445
+ import {
446
+ createSdkMcpServer as createSdkMcpServer3,
447
+ tool as tool3
448
+ } from "@anthropic-ai/claude-agent-sdk";
449
+ import { z as z3 } from "zod";
450
+ function buildFetchRepoMcpServer(opts) {
451
+ const fetchTool = tool3(
452
+ "fetch_repo",
453
+ 'Clone another GitHub repository into your workspace so you can read and work on it. Pass `repo` as "owner/name" (e.g. "A-Guy-educ/A-Guy"). Returns the absolute path of the clone \u2014 then use your Read/Grep/Glob/Bash tools at that path to inspect it. Already-fetched repos are reused instantly. Use this whenever the user asks about a repository other than your current one \u2014 you are NOT limited to a single repo.',
454
+ {
455
+ repo: z3.string().describe('GitHub repository as "owner/name", e.g. "A-Guy-educ/A-Guy".')
456
+ },
457
+ async (args) => {
458
+ const repo = String(args.repo ?? "").trim();
459
+ try {
460
+ const dir = await fetchRepo({
461
+ reposRoot: opts.reposRoot,
462
+ repo,
463
+ repoToken: opts.repoToken
464
+ });
465
+ return {
466
+ content: [
467
+ {
468
+ type: "text",
469
+ text: `Cloned ${repo} \u2192 ${dir}
470
+ Use Read/Grep/Glob/Bash at that absolute path to explore it. It now lives in your workspace alongside any other repos you've fetched.`
471
+ }
472
+ ]
473
+ };
474
+ } catch (err) {
475
+ const msg = err instanceof Error ? err.message : String(err);
476
+ return {
477
+ content: [{ type: "text", text: `Could not fetch ${repo}: ${msg}` }],
478
+ isError: true
479
+ };
480
+ }
481
+ }
482
+ );
483
+ return createSdkMcpServer3({
484
+ name: "kody-fetch-repo",
485
+ version: "0.1.0",
486
+ tools: [fetchTool]
487
+ });
488
+ }
489
+ var init_fetchRepoMcp = __esm({
490
+ "src/fetchRepoMcp.ts"() {
491
+ "use strict";
492
+ init_repoWorkspace();
493
+ }
494
+ });
495
+
361
496
  // src/issue.ts
362
497
  import { execFileSync } from "child_process";
363
498
  function ghToken() {
@@ -515,16 +650,16 @@ var init_issue = __esm({
515
650
  });
516
651
 
517
652
  // src/prompt.ts
518
- import * as fs19 from "fs";
519
- import * as path18 from "path";
653
+ import * as fs20 from "fs";
654
+ import * as path19 from "path";
520
655
  function loadProjectConventions(projectDir) {
521
656
  const out = [];
522
657
  for (const rel of CONVENTION_FILES) {
523
- const abs = path18.join(projectDir, rel);
524
- if (!fs19.existsSync(abs)) continue;
658
+ const abs = path19.join(projectDir, rel);
659
+ if (!fs20.existsSync(abs)) continue;
525
660
  let content;
526
661
  try {
527
- content = fs19.readFileSync(abs, "utf-8");
662
+ content = fs20.readFileSync(abs, "utf-8");
528
663
  } catch {
529
664
  continue;
530
665
  }
@@ -672,28 +807,28 @@ var loadMemoryContext_exports = {};
672
807
  __export(loadMemoryContext_exports, {
673
808
  loadMemoryContext: () => loadMemoryContext
674
809
  });
675
- import * as fs31 from "fs";
676
- import * as path29 from "path";
810
+ import * as fs32 from "fs";
811
+ import * as path30 from "path";
677
812
  function collectPages(memoryAbs) {
678
813
  const out = [];
679
814
  walkMd(memoryAbs, (file) => {
680
815
  let stat;
681
816
  try {
682
- stat = fs31.statSync(file);
817
+ stat = fs32.statSync(file);
683
818
  } catch {
684
819
  return;
685
820
  }
686
821
  let raw;
687
822
  try {
688
- raw = fs31.readFileSync(file, "utf-8");
823
+ raw = fs32.readFileSync(file, "utf-8");
689
824
  } catch {
690
825
  return;
691
826
  }
692
827
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
693
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path29.basename(file, ".md");
828
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path30.basename(file, ".md");
694
829
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
695
830
  out.push({
696
- relPath: path29.relative(memoryAbs, file),
831
+ relPath: path30.relative(memoryAbs, file),
697
832
  title,
698
833
  updated,
699
834
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
@@ -761,16 +896,16 @@ function walkMd(root, visit) {
761
896
  const dir = stack.pop();
762
897
  let names;
763
898
  try {
764
- names = fs31.readdirSync(dir);
899
+ names = fs32.readdirSync(dir);
765
900
  } catch {
766
901
  continue;
767
902
  }
768
903
  for (const name of names) {
769
904
  if (name.startsWith(".")) continue;
770
- const full = path29.join(dir, name);
905
+ const full = path30.join(dir, name);
771
906
  let stat;
772
907
  try {
773
- stat = fs31.statSync(full);
908
+ stat = fs32.statSync(full);
774
909
  } catch {
775
910
  continue;
776
911
  }
@@ -793,8 +928,8 @@ var init_loadMemoryContext = __esm({
793
928
  TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
794
929
  loadMemoryContext = async (ctx) => {
795
930
  if (typeof ctx.data.memoryContext === "string") return;
796
- const memoryAbs = path29.join(ctx.cwd, MEMORY_DIR_RELATIVE);
797
- if (!fs31.existsSync(memoryAbs)) {
931
+ const memoryAbs = path30.join(ctx.cwd, MEMORY_DIR_RELATIVE);
932
+ if (!fs32.existsSync(memoryAbs)) {
798
933
  ctx.data.memoryContext = "";
799
934
  return;
800
935
  }
@@ -926,7 +1061,7 @@ var init_loadPriorArt = __esm({
926
1061
  // package.json
927
1062
  var package_default = {
928
1063
  name: "@kody-ade/kody-engine",
929
- version: "0.4.141",
1064
+ version: "0.4.143",
930
1065
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
931
1066
  license: "MIT",
932
1067
  type: "module",
@@ -983,8 +1118,8 @@ var package_default = {
983
1118
 
984
1119
  // src/chat-cli.ts
985
1120
  import { execFileSync as execFileSync30 } from "child_process";
986
- import * as fs41 from "fs";
987
- import * as path37 from "path";
1121
+ import * as fs42 from "fs";
1122
+ import * as path38 from "path";
988
1123
 
989
1124
  // src/chat/events.ts
990
1125
  import * as fs from "fs";
@@ -1050,8 +1185,8 @@ function makeRunId(sessionId, suffix) {
1050
1185
  }
1051
1186
 
1052
1187
  // src/chat/loop.ts
1053
- import * as fs9 from "fs";
1054
- import * as path9 from "path";
1188
+ import * as fs10 from "fs";
1189
+ import * as path10 from "path";
1055
1190
 
1056
1191
  // src/task-artifacts.ts
1057
1192
  import fs2 from "fs";
@@ -1142,8 +1277,8 @@ function taskArtifactsPromptAddendum(opts) {
1142
1277
  }
1143
1278
 
1144
1279
  // src/agent.ts
1145
- import * as fs6 from "fs";
1146
- import * as path6 from "path";
1280
+ import * as fs7 from "fs";
1281
+ import * as path7 from "path";
1147
1282
  import { query } from "@anthropic-ai/claude-agent-sdk";
1148
1283
 
1149
1284
  // src/claudeBinary.ts
@@ -1528,10 +1663,10 @@ function resolveTurnTimeoutMs(opts) {
1528
1663
  return DEFAULT_TURN_TIMEOUT_MS;
1529
1664
  }
1530
1665
  async function runAgent(opts) {
1531
- const ndjsonDir = opts.ndjsonDir ?? path6.join(opts.cwd, ".kody");
1532
- fs6.mkdirSync(ndjsonDir, { recursive: true });
1533
- const ndjsonPath = path6.join(ndjsonDir, "last-run.jsonl");
1534
- const fullLog = fs6.createWriteStream(ndjsonPath, { flags: "w" });
1666
+ const ndjsonDir = opts.ndjsonDir ?? path7.join(opts.cwd, ".kody");
1667
+ fs7.mkdirSync(ndjsonDir, { recursive: true });
1668
+ const ndjsonPath = path7.join(ndjsonDir, "last-run.jsonl");
1669
+ const fullLog = fs7.createWriteStream(ndjsonPath, { flags: "w" });
1535
1670
  const env = {
1536
1671
  ...process.env,
1537
1672
  SKIP_HOOKS: "1",
@@ -1565,7 +1700,9 @@ async function runAgent(opts) {
1565
1700
  const queryOptions = {
1566
1701
  model: opts.model.model,
1567
1702
  cwd: opts.cwd,
1568
- allowedTools: opts.allowedToolsOverride ?? DEFAULT_ALLOWED_TOOLS,
1703
+ // Fresh array (never mutate the shared DEFAULT_ALLOWED_TOOLS const) so
1704
+ // opt-in tools like fetch_repo can be appended below.
1705
+ allowedTools: [...opts.allowedToolsOverride ?? DEFAULT_ALLOWED_TOOLS],
1569
1706
  permissionMode: opts.permissionModeOverride ?? "acceptEdits",
1570
1707
  env
1571
1708
  };
@@ -1594,6 +1731,16 @@ async function runAgent(opts) {
1594
1731
  getSubmitted = submitHandle.getSubmitted;
1595
1732
  mcpEntries.push(["kody-submit", submitHandle.server]);
1596
1733
  }
1734
+ if (opts.enableFetchRepoTool && opts.reposRoot) {
1735
+ const { buildFetchRepoMcpServer: buildFetchRepoMcpServer2 } = await Promise.resolve().then(() => (init_fetchRepoMcp(), fetchRepoMcp_exports));
1736
+ const fetchServer = buildFetchRepoMcpServer2({
1737
+ reposRoot: opts.reposRoot,
1738
+ repoToken: opts.repoToken
1739
+ });
1740
+ mcpEntries.push(["kody-fetch-repo", fetchServer]);
1741
+ queryOptions.allowedTools.push("mcp__kody-fetch-repo__fetch_repo");
1742
+ queryOptions.additionalDirectories = [opts.reposRoot];
1743
+ }
1597
1744
  if (mcpEntries.length > 0) {
1598
1745
  queryOptions.mcpServers = Object.fromEntries(mcpEntries);
1599
1746
  }
@@ -1641,10 +1788,10 @@ async function runAgent(opts) {
1641
1788
  let timer;
1642
1789
  let next;
1643
1790
  if (turnTimeoutMs > 0) {
1644
- const timeoutPromise = new Promise((resolve5) => {
1791
+ const timeoutPromise = new Promise((resolve6) => {
1645
1792
  timer = setTimeout(() => {
1646
1793
  timedOut = true;
1647
- resolve5({ done: true, value: void 0 });
1794
+ resolve6({ done: true, value: void 0 });
1648
1795
  }, turnTimeoutMs);
1649
1796
  });
1650
1797
  next = await Promise.race([nextPromise, timeoutPromise]);
@@ -1774,48 +1921,48 @@ async function runAgent(opts) {
1774
1921
  }
1775
1922
 
1776
1923
  // src/registry.ts
1777
- import * as fs7 from "fs";
1778
- import * as path7 from "path";
1924
+ import * as fs8 from "fs";
1925
+ import * as path8 from "path";
1779
1926
  function getExecutablesRoot() {
1780
- const here = path7.dirname(new URL(import.meta.url).pathname);
1927
+ const here = path8.dirname(new URL(import.meta.url).pathname);
1781
1928
  const candidates = [
1782
- path7.join(here, "executables"),
1929
+ path8.join(here, "executables"),
1783
1930
  // dev: src/
1784
- path7.join(here, "..", "executables"),
1931
+ path8.join(here, "..", "executables"),
1785
1932
  // built: dist/bin → dist/executables
1786
- path7.join(here, "..", "src", "executables")
1933
+ path8.join(here, "..", "src", "executables")
1787
1934
  // fallback
1788
1935
  ];
1789
1936
  for (const c of candidates) {
1790
- if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
1937
+ if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
1791
1938
  }
1792
1939
  return candidates[0];
1793
1940
  }
1794
1941
  function getProjectExecutablesRoot() {
1795
- return path7.join(process.cwd(), ".kody", "executables");
1942
+ return path8.join(process.cwd(), ".kody", "executables");
1796
1943
  }
1797
1944
  function getBuiltinJobsRoot() {
1798
- const here = path7.dirname(new URL(import.meta.url).pathname);
1945
+ const here = path8.dirname(new URL(import.meta.url).pathname);
1799
1946
  const candidates = [
1800
- path7.join(here, "jobs"),
1947
+ path8.join(here, "jobs"),
1801
1948
  // dev: src/
1802
- path7.join(here, "..", "jobs"),
1949
+ path8.join(here, "..", "jobs"),
1803
1950
  // built: dist/bin → dist/jobs
1804
- path7.join(here, "..", "src", "jobs")
1951
+ path8.join(here, "..", "src", "jobs")
1805
1952
  // fallback
1806
1953
  ];
1807
1954
  for (const c of candidates) {
1808
- if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
1955
+ if (fs8.existsSync(c) && fs8.statSync(c).isDirectory()) return c;
1809
1956
  }
1810
1957
  return candidates[0];
1811
1958
  }
1812
1959
  function listBuiltinJobs(root = getBuiltinJobsRoot()) {
1813
- if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
1960
+ if (!fs8.existsSync(root) || !fs8.statSync(root).isDirectory()) return [];
1814
1961
  const out = [];
1815
- for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
1962
+ for (const ent of fs8.readdirSync(root, { withFileTypes: true })) {
1816
1963
  if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
1817
1964
  const slug = ent.name.slice(0, -3);
1818
- out.push({ slug, filePath: path7.join(root, ent.name) });
1965
+ out.push({ slug, filePath: path8.join(root, ent.name) });
1819
1966
  }
1820
1967
  out.sort((a, b) => a.slug.localeCompare(b.slug));
1821
1968
  return out;
@@ -1828,13 +1975,13 @@ function listExecutables(roots = getExecutableRoots()) {
1828
1975
  const seen = /* @__PURE__ */ new Set();
1829
1976
  const out = [];
1830
1977
  for (const root of rootList) {
1831
- if (!fs7.existsSync(root)) continue;
1832
- const entries = fs7.readdirSync(root, { withFileTypes: true });
1978
+ if (!fs8.existsSync(root)) continue;
1979
+ const entries = fs8.readdirSync(root, { withFileTypes: true });
1833
1980
  for (const ent of entries) {
1834
1981
  if (!ent.isDirectory()) continue;
1835
1982
  if (seen.has(ent.name)) continue;
1836
- const profilePath = path7.join(root, ent.name, "profile.json");
1837
- if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
1983
+ const profilePath = path8.join(root, ent.name, "profile.json");
1984
+ if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
1838
1985
  out.push({ name: ent.name, profilePath });
1839
1986
  seen.add(ent.name);
1840
1987
  }
@@ -1846,8 +1993,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
1846
1993
  if (!isSafeName(name)) return null;
1847
1994
  const rootList = typeof roots === "string" ? [roots] : roots;
1848
1995
  for (const root of rootList) {
1849
- const profilePath = path7.join(root, name, "profile.json");
1850
- if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
1996
+ const profilePath = path8.join(root, name, "profile.json");
1997
+ if (fs8.existsSync(profilePath) && fs8.statSync(profilePath).isFile()) {
1851
1998
  return profilePath;
1852
1999
  }
1853
2000
  }
@@ -1863,7 +2010,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
1863
2010
  const profilePath = resolveExecutable(name, roots);
1864
2011
  if (!profilePath) return null;
1865
2012
  try {
1866
- const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
2013
+ const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
1867
2014
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
1868
2015
  return raw.inputs;
1869
2016
  } catch {
@@ -1893,14 +2040,14 @@ function parseGenericFlags(argv) {
1893
2040
  }
1894
2041
 
1895
2042
  // src/chat/session.ts
1896
- import * as fs8 from "fs";
1897
- import * as path8 from "path";
2043
+ import * as fs9 from "fs";
2044
+ import * as path9 from "path";
1898
2045
  function sessionFilePath(cwd, sessionId) {
1899
- return path8.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
2046
+ return path9.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
1900
2047
  }
1901
2048
  function readMeta(file) {
1902
- if (!fs8.existsSync(file)) return null;
1903
- const raw = fs8.readFileSync(file, "utf-8");
2049
+ if (!fs9.existsSync(file)) return null;
2050
+ const raw = fs9.readFileSync(file, "utf-8");
1904
2051
  const firstLine2 = raw.split("\n", 1)[0]?.trim();
1905
2052
  if (!firstLine2) return null;
1906
2053
  try {
@@ -1913,8 +2060,8 @@ function readMeta(file) {
1913
2060
  }
1914
2061
  }
1915
2062
  function readSession(file) {
1916
- if (!fs8.existsSync(file)) return [];
1917
- const raw = fs8.readFileSync(file, "utf-8").trim();
2063
+ if (!fs9.existsSync(file)) return [];
2064
+ const raw = fs9.readFileSync(file, "utf-8").trim();
1918
2065
  if (!raw) return [];
1919
2066
  const turns = [];
1920
2067
  for (const line of raw.split("\n")) {
@@ -1930,14 +2077,14 @@ function readSession(file) {
1930
2077
  return turns;
1931
2078
  }
1932
2079
  function appendTurn(file, turn) {
1933
- fs8.mkdirSync(path8.dirname(file), { recursive: true });
2080
+ fs9.mkdirSync(path9.dirname(file), { recursive: true });
1934
2081
  const line = JSON.stringify({
1935
2082
  role: turn.role,
1936
2083
  content: turn.content,
1937
2084
  timestamp: turn.timestamp,
1938
2085
  toolCalls: turn.toolCalls ?? []
1939
2086
  });
1940
- fs8.appendFileSync(file, `${line}
2087
+ fs9.appendFileSync(file, `${line}
1941
2088
  `);
1942
2089
  }
1943
2090
  function seedInitialMessage(file, message) {
@@ -2037,6 +2184,15 @@ var CHAT_SYSTEM_PROMPT = [
2037
2184
  "Do not invent file paths, commit SHAs, line numbers, or command output. If you",
2038
2185
  "cite something concrete, you must have just read or run it in this session."
2039
2186
  ].join("\n");
2187
+ var CROSS_REPO_PROMPT = [
2188
+ "# Working across repositories",
2189
+ "You are NOT limited to the repository at your current working directory. You",
2190
+ 'have a `fetch_repo` tool: call fetch_repo("owner/name") to clone another repo',
2191
+ "into your workspace; it returns an absolute path. Then use Read/Grep/Glob/Bash",
2192
+ "at that path to inspect or work on it. Already-fetched repos are reused",
2193
+ "instantly. When the user asks about a different repo \u2014 or to compare repos \u2014",
2194
+ "fetch it instead of saying you are scoped to a single repo."
2195
+ ].join("\n");
2040
2196
  function buildExecutableCatalog() {
2041
2197
  let discovered;
2042
2198
  try {
@@ -2047,7 +2203,7 @@ function buildExecutableCatalog() {
2047
2203
  const entries = [];
2048
2204
  for (const { name, profilePath } of discovered) {
2049
2205
  try {
2050
- const raw = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
2206
+ const raw = JSON.parse(fs10.readFileSync(profilePath, "utf-8"));
2051
2207
  const describe = typeof raw.describe === "string" ? raw.describe : "";
2052
2208
  const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
2053
2209
  entries.push({ name, describe: firstSentence.trim() });
@@ -2093,11 +2249,13 @@ async function runChatTurn(opts) {
2093
2249
  const contextBlock = readContextBlock(opts.cwd);
2094
2250
  const memoryBlock = readMemoryIndexBlock(opts.cwd);
2095
2251
  const instructionsBlock = readInstructionsBlock(opts.cwd);
2252
+ const crossRepoBlock = opts.reposRoot ? CROSS_REPO_PROMPT : null;
2096
2253
  const systemPrompt = [
2097
2254
  basePrompt,
2098
2255
  contextBlock,
2099
2256
  memoryBlock,
2100
2257
  instructionsBlock,
2258
+ crossRepoBlock,
2101
2259
  catalog,
2102
2260
  artifactAddendum
2103
2261
  ].filter((s) => typeof s === "string" && s.length > 0).join("\n\n");
@@ -2111,6 +2269,14 @@ async function runChatTurn(opts) {
2111
2269
  verbose: opts.verbose,
2112
2270
  quiet: opts.quiet,
2113
2271
  systemPromptAppend: systemPrompt,
2272
+ // Let the agent clone + work on OTHER repos mid-conversation (a
2273
+ // repo-less Brain serves many). Enabled whenever we know where repos
2274
+ // live; grants read access to that root via additionalDirectories.
2275
+ ...opts.reposRoot ? {
2276
+ enableFetchRepoTool: true,
2277
+ reposRoot: opts.reposRoot,
2278
+ repoToken: opts.repoToken
2279
+ } : {},
2114
2280
  onProgress: async (ev) => {
2115
2281
  progressSeq += 1;
2116
2282
  if (ev.kind === "thinking") {
@@ -2195,10 +2361,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
2195
2361
  var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
2196
2362
  var MAX_INDEX_BYTES = 8e3;
2197
2363
  function readMemoryIndexBlock(cwd) {
2198
- const indexPath = path9.join(cwd, MEMORY_INDEX_REL);
2364
+ const indexPath = path10.join(cwd, MEMORY_INDEX_REL);
2199
2365
  let raw;
2200
2366
  try {
2201
- raw = fs9.readFileSync(indexPath, "utf-8");
2367
+ raw = fs10.readFileSync(indexPath, "utf-8");
2202
2368
  } catch {
2203
2369
  return "";
2204
2370
  }
@@ -2216,17 +2382,17 @@ function readMemoryIndexBlock(cwd) {
2216
2382
  var CONTEXT_DIR_REL = ".kody/context";
2217
2383
  var MAX_CONTEXT_BYTES = 12e3;
2218
2384
  function readContextBlock(cwd) {
2219
- const dir = path9.join(cwd, CONTEXT_DIR_REL);
2385
+ const dir = path10.join(cwd, CONTEXT_DIR_REL);
2220
2386
  let files;
2221
2387
  try {
2222
- files = fs9.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
2388
+ files = fs10.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
2223
2389
  } catch {
2224
2390
  return "";
2225
2391
  }
2226
2392
  const sections = [];
2227
2393
  for (const file of files) {
2228
2394
  try {
2229
- const content = fs9.readFileSync(path9.join(dir, file), "utf-8").trim();
2395
+ const content = fs10.readFileSync(path10.join(dir, file), "utf-8").trim();
2230
2396
  if (content) sections.push(`### ${file.replace(/\.md$/, "")}
2231
2397
 
2232
2398
  ${content}`);
@@ -2247,10 +2413,10 @@ ${content}`);
2247
2413
  var INSTRUCTIONS_REL = ".kody/instructions.md";
2248
2414
  var MAX_INSTRUCTIONS_BYTES = 8e3;
2249
2415
  function readInstructionsBlock(cwd) {
2250
- const instructionsPath = path9.join(cwd, INSTRUCTIONS_REL);
2416
+ const instructionsPath = path10.join(cwd, INSTRUCTIONS_REL);
2251
2417
  let raw;
2252
2418
  try {
2253
- raw = fs9.readFileSync(instructionsPath, "utf-8");
2419
+ raw = fs10.readFileSync(instructionsPath, "utf-8");
2254
2420
  } catch {
2255
2421
  return "";
2256
2422
  }
@@ -2269,8 +2435,8 @@ function readInstructionsBlock(cwd) {
2269
2435
  // src/chat/modes/interactive.ts
2270
2436
  init_issue();
2271
2437
  import { execFileSync as execFileSync3 } from "child_process";
2272
- import * as fs10 from "fs";
2273
- import * as path10 from "path";
2438
+ import * as fs11 from "fs";
2439
+ import * as path11 from "path";
2274
2440
 
2275
2441
  // src/chat/inbox.ts
2276
2442
  import { execFileSync as execFileSync2 } from "child_process";
@@ -2318,7 +2484,7 @@ async function waitForNextUserMessage(opts) {
2318
2484
  }
2319
2485
  }
2320
2486
  function sleep(ms) {
2321
- return new Promise((resolve5) => setTimeout(resolve5, ms));
2487
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
2322
2488
  }
2323
2489
  function currentBranch(cwd) {
2324
2490
  try {
@@ -2429,9 +2595,9 @@ function findNextUserTurn(turns, fromIdx) {
2429
2595
  return -1;
2430
2596
  }
2431
2597
  function commitTurn(cwd, sessionId, _verbose) {
2432
- const sessionRel = path10.relative(cwd, sessionFilePath(cwd, sessionId));
2433
- const eventsRel = path10.relative(cwd, eventsFilePath(cwd, sessionId));
2434
- const rels = [sessionRel, eventsRel].filter((p) => fs10.existsSync(path10.join(cwd, p)));
2598
+ const sessionRel = path11.relative(cwd, sessionFilePath(cwd, sessionId));
2599
+ const eventsRel = path11.relative(cwd, eventsFilePath(cwd, sessionId));
2600
+ const rels = [sessionRel, eventsRel].filter((p) => fs11.existsSync(path11.join(cwd, p)));
2435
2601
  if (rels.length === 0) return;
2436
2602
  const repository = process.env.GITHUB_REPOSITORY;
2437
2603
  if (!repository) {
@@ -2443,8 +2609,8 @@ function commitTurn(cwd, sessionId, _verbose) {
2443
2609
  }
2444
2610
  const branch = defaultBranch(cwd) ?? "main";
2445
2611
  for (const rel of rels) {
2446
- const repoPath = rel.split(path10.sep).join("/");
2447
- const localText = fs10.readFileSync(path10.join(cwd, rel), "utf-8");
2612
+ const repoPath = rel.split(path11.sep).join("/");
2613
+ const localText = fs11.readFileSync(path11.join(cwd, rel), "utf-8");
2448
2614
  putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
2449
2615
  }
2450
2616
  }
@@ -2534,11 +2700,11 @@ async function emit2(sink, type, sessionId, suffix, payload) {
2534
2700
 
2535
2701
  // src/kody-cli.ts
2536
2702
  import { execFileSync as execFileSync29 } from "child_process";
2537
- import * as fs40 from "fs";
2538
- import * as path36 from "path";
2703
+ import * as fs41 from "fs";
2704
+ import * as path37 from "path";
2539
2705
 
2540
2706
  // src/dispatch.ts
2541
- import * as fs11 from "fs";
2707
+ import * as fs12 from "fs";
2542
2708
 
2543
2709
  // src/cron-match.ts
2544
2710
  var FIELD_BOUNDS = [
@@ -2622,10 +2788,10 @@ function autoDispatch(opts) {
2622
2788
  }
2623
2789
  const eventName = process.env.GITHUB_EVENT_NAME;
2624
2790
  const eventPath = process.env.GITHUB_EVENT_PATH;
2625
- if (!eventName || !eventPath || !fs11.existsSync(eventPath)) return null;
2791
+ if (!eventName || !eventPath || !fs12.existsSync(eventPath)) return null;
2626
2792
  let event = {};
2627
2793
  try {
2628
- event = JSON.parse(fs11.readFileSync(eventPath, "utf-8"));
2794
+ event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
2629
2795
  } catch {
2630
2796
  return null;
2631
2797
  }
@@ -2699,7 +2865,7 @@ function autoDispatchTyped(opts) {
2699
2865
  if (legacy) return { kind: "route", ...legacy };
2700
2866
  const eventName = process.env.GITHUB_EVENT_NAME;
2701
2867
  const eventPath = process.env.GITHUB_EVENT_PATH;
2702
- if (!eventName || !eventPath || !fs11.existsSync(eventPath)) {
2868
+ if (!eventName || !eventPath || !fs12.existsSync(eventPath)) {
2703
2869
  return { kind: "silent", reason: "no GHA event context" };
2704
2870
  }
2705
2871
  if (eventName !== "issue_comment") {
@@ -2707,7 +2873,7 @@ function autoDispatchTyped(opts) {
2707
2873
  }
2708
2874
  let event = {};
2709
2875
  try {
2710
- event = JSON.parse(fs11.readFileSync(eventPath, "utf-8"));
2876
+ event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
2711
2877
  } catch {
2712
2878
  return { kind: "silent", reason: "GHA event payload unreadable" };
2713
2879
  }
@@ -2745,7 +2911,7 @@ function dispatchScheduledWatches(opts) {
2745
2911
  for (const exe of listExecutables()) {
2746
2912
  let raw;
2747
2913
  try {
2748
- raw = fs11.readFileSync(exe.profilePath, "utf-8");
2914
+ raw = fs12.readFileSync(exe.profilePath, "utf-8");
2749
2915
  } catch {
2750
2916
  continue;
2751
2917
  }
@@ -2868,8 +3034,8 @@ init_issue();
2868
3034
 
2869
3035
  // src/executor.ts
2870
3036
  import { execFileSync as execFileSync28, spawn as spawn9 } from "child_process";
2871
- import * as fs39 from "fs";
2872
- import * as path35 from "path";
3037
+ import * as fs40 from "fs";
3038
+ import * as path36 from "path";
2873
3039
 
2874
3040
  // src/discipline.ts
2875
3041
  var DISCIPLINE = `# Working discipline (applies to this entire task)
@@ -2920,8 +3086,8 @@ init_events();
2920
3086
  init_issue();
2921
3087
 
2922
3088
  // src/profile.ts
2923
- import * as fs12 from "fs";
2924
- import * as path11 from "path";
3089
+ import * as fs13 from "fs";
3090
+ import * as path12 from "path";
2925
3091
 
2926
3092
  // src/profile-error.ts
2927
3093
  var ProfileError = class extends Error {
@@ -3089,12 +3255,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
3089
3255
  "preloadContext"
3090
3256
  ]);
3091
3257
  function loadProfile(profilePath) {
3092
- if (!fs12.existsSync(profilePath)) {
3258
+ if (!fs13.existsSync(profilePath)) {
3093
3259
  throw new ProfileError(profilePath, "file not found");
3094
3260
  }
3095
3261
  let raw;
3096
3262
  try {
3097
- raw = JSON.parse(fs12.readFileSync(profilePath, "utf-8"));
3263
+ raw = JSON.parse(fs13.readFileSync(profilePath, "utf-8"));
3098
3264
  } catch (err) {
3099
3265
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
3100
3266
  }
@@ -3105,7 +3271,7 @@ function loadProfile(profilePath) {
3105
3271
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
3106
3272
  if (unknownKeys.length > 0) {
3107
3273
  process.stderr.write(
3108
- `[kody profile] ${path11.basename(path11.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
3274
+ `[kody profile] ${path12.basename(path12.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
3109
3275
  `
3110
3276
  );
3111
3277
  }
@@ -3166,7 +3332,7 @@ function loadProfile(profilePath) {
3166
3332
  // Phase 5 in-process handoff opt-in. Default false; containers
3167
3333
  // flip to true after end-to-end verification.
3168
3334
  preloadContext: r.preloadContext === true,
3169
- dir: path11.dirname(profilePath)
3335
+ dir: path12.dirname(profilePath)
3170
3336
  };
3171
3337
  if (lifecycle) {
3172
3338
  applyLifecycle(profile, profilePath);
@@ -3536,10 +3702,10 @@ function errMsg(err) {
3536
3702
  }
3537
3703
 
3538
3704
  // src/litellm.ts
3539
- import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
3540
- import * as fs13 from "fs";
3705
+ import { execFileSync as execFileSync4, spawn as spawn3 } from "child_process";
3706
+ import * as fs14 from "fs";
3541
3707
  import * as os2 from "os";
3542
- import * as path12 from "path";
3708
+ import * as path13 from "path";
3543
3709
  async function checkLitellmHealth(url) {
3544
3710
  try {
3545
3711
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -3586,20 +3752,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3586
3752
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
3587
3753
  }
3588
3754
  }
3589
- const configPath = path12.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3590
- fs13.writeFileSync(configPath, generateLitellmConfigYaml(model));
3755
+ const configPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3756
+ fs14.writeFileSync(configPath, generateLitellmConfigYaml(model));
3591
3757
  const portMatch = url.match(/:(\d+)/);
3592
3758
  const port = portMatch ? portMatch[1] : "4000";
3593
3759
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
3594
3760
  const dotenvVars = readDotenvApiKeys(projectDir);
3595
- const logPath = path12.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3596
- const outFd = fs13.openSync(logPath, "w");
3597
- const child = spawn2(cmd, args, {
3761
+ const logPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3762
+ const outFd = fs14.openSync(logPath, "w");
3763
+ const child = spawn3(cmd, args, {
3598
3764
  stdio: ["ignore", outFd, outFd],
3599
3765
  detached: true,
3600
3766
  env: stripBlockingEnv({ ...process.env, ...dotenvVars })
3601
3767
  });
3602
- fs13.closeSync(outFd);
3768
+ fs14.closeSync(outFd);
3603
3769
  const timeoutMs = resolveLitellmTimeoutMs();
3604
3770
  const deadline = Date.now() + timeoutMs;
3605
3771
  while (Date.now() < deadline) {
@@ -3618,7 +3784,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3618
3784
  }
3619
3785
  let logTail = "";
3620
3786
  try {
3621
- logTail = fs13.readFileSync(logPath, "utf-8").slice(-2e3);
3787
+ logTail = fs14.readFileSync(logPath, "utf-8").slice(-2e3);
3622
3788
  } catch {
3623
3789
  }
3624
3790
  try {
@@ -3630,10 +3796,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3630
3796
  ${logTail}`);
3631
3797
  }
3632
3798
  function readDotenvApiKeys(projectDir) {
3633
- const dotenvPath = path12.join(projectDir, ".env");
3634
- if (!fs13.existsSync(dotenvPath)) return {};
3799
+ const dotenvPath = path13.join(projectDir, ".env");
3800
+ if (!fs14.existsSync(dotenvPath)) return {};
3635
3801
  const result = {};
3636
- for (const rawLine of fs13.readFileSync(dotenvPath, "utf-8").split("\n")) {
3802
+ for (const rawLine of fs14.readFileSync(dotenvPath, "utf-8").split("\n")) {
3637
3803
  const line = rawLine.trim();
3638
3804
  if (!line || line.startsWith("#")) continue;
3639
3805
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -3656,25 +3822,25 @@ function stripBlockingEnv(env) {
3656
3822
  }
3657
3823
 
3658
3824
  // src/subagents.ts
3659
- import * as fs15 from "fs";
3660
- import * as path14 from "path";
3825
+ import * as fs16 from "fs";
3826
+ import * as path15 from "path";
3661
3827
 
3662
3828
  // src/scripts/buildSyntheticPlugin.ts
3663
- import * as fs14 from "fs";
3829
+ import * as fs15 from "fs";
3664
3830
  import * as os3 from "os";
3665
- import * as path13 from "path";
3831
+ import * as path14 from "path";
3666
3832
  function getPluginsCatalogRoot() {
3667
- const here = path13.dirname(new URL(import.meta.url).pathname);
3833
+ const here = path14.dirname(new URL(import.meta.url).pathname);
3668
3834
  const candidates = [
3669
- path13.join(here, "..", "plugins"),
3835
+ path14.join(here, "..", "plugins"),
3670
3836
  // dev: src/scripts → src/plugins
3671
- path13.join(here, "..", "..", "plugins"),
3837
+ path14.join(here, "..", "..", "plugins"),
3672
3838
  // built: dist/scripts → dist/plugins
3673
- path13.join(here, "..", "..", "src", "plugins")
3839
+ path14.join(here, "..", "..", "src", "plugins")
3674
3840
  // fallback
3675
3841
  ];
3676
3842
  for (const c of candidates) {
3677
- if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
3843
+ if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
3678
3844
  }
3679
3845
  return candidates[0];
3680
3846
  }
@@ -3684,45 +3850,45 @@ var buildSyntheticPlugin = async (ctx, profile) => {
3684
3850
  if (!needsSynthetic) return;
3685
3851
  const catalog = getPluginsCatalogRoot();
3686
3852
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3687
- const root = path13.join(os3.tmpdir(), `kody-synth-${runId}`);
3688
- fs14.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
3853
+ const root = path14.join(os3.tmpdir(), `kody-synth-${runId}`);
3854
+ fs15.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
3689
3855
  const resolvePart = (bucket, entry) => {
3690
- const local = path13.join(profile.dir, bucket, entry);
3691
- if (fs14.existsSync(local)) return local;
3692
- const central = path13.join(catalog, bucket, entry);
3693
- if (fs14.existsSync(central)) return central;
3856
+ const local = path14.join(profile.dir, bucket, entry);
3857
+ if (fs15.existsSync(local)) return local;
3858
+ const central = path14.join(catalog, bucket, entry);
3859
+ if (fs15.existsSync(central)) return central;
3694
3860
  throw new Error(
3695
3861
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
3696
3862
  );
3697
3863
  };
3698
3864
  if (cc.skills.length > 0) {
3699
- const dst = path13.join(root, "skills");
3700
- fs14.mkdirSync(dst, { recursive: true });
3865
+ const dst = path14.join(root, "skills");
3866
+ fs15.mkdirSync(dst, { recursive: true });
3701
3867
  for (const name of cc.skills) {
3702
- copyDir(resolvePart("skills", name), path13.join(dst, name));
3868
+ copyDir(resolvePart("skills", name), path14.join(dst, name));
3703
3869
  }
3704
3870
  }
3705
3871
  if (cc.commands.length > 0) {
3706
- const dst = path13.join(root, "commands");
3707
- fs14.mkdirSync(dst, { recursive: true });
3872
+ const dst = path14.join(root, "commands");
3873
+ fs15.mkdirSync(dst, { recursive: true });
3708
3874
  for (const name of cc.commands) {
3709
- fs14.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
3875
+ fs15.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
3710
3876
  }
3711
3877
  }
3712
3878
  if (cc.hooks.length > 0) {
3713
- const dst = path13.join(root, "hooks");
3714
- fs14.mkdirSync(dst, { recursive: true });
3879
+ const dst = path14.join(root, "hooks");
3880
+ fs15.mkdirSync(dst, { recursive: true });
3715
3881
  const merged = { hooks: {} };
3716
3882
  for (const name of cc.hooks) {
3717
3883
  const src = resolvePart("hooks", `${name}.json`);
3718
- const parsed = JSON.parse(fs14.readFileSync(src, "utf-8"));
3884
+ const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
3719
3885
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
3720
3886
  if (!Array.isArray(entries)) continue;
3721
3887
  if (!merged.hooks[event]) merged.hooks[event] = [];
3722
3888
  merged.hooks[event].push(...entries);
3723
3889
  }
3724
3890
  }
3725
- fs14.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
3891
+ fs15.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
3726
3892
  `);
3727
3893
  }
3728
3894
  const manifest = {
@@ -3732,17 +3898,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
3732
3898
  };
3733
3899
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
3734
3900
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
3735
- fs14.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
3901
+ fs15.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
3736
3902
  `);
3737
3903
  ctx.data.syntheticPluginPath = root;
3738
3904
  };
3739
3905
  function copyDir(src, dst) {
3740
- fs14.mkdirSync(dst, { recursive: true });
3741
- for (const ent of fs14.readdirSync(src, { withFileTypes: true })) {
3742
- const s = path13.join(src, ent.name);
3743
- const d = path13.join(dst, ent.name);
3906
+ fs15.mkdirSync(dst, { recursive: true });
3907
+ for (const ent of fs15.readdirSync(src, { withFileTypes: true })) {
3908
+ const s = path14.join(src, ent.name);
3909
+ const d = path14.join(dst, ent.name);
3744
3910
  if (ent.isDirectory()) copyDir(s, d);
3745
- else if (ent.isFile()) fs14.copyFileSync(s, d);
3911
+ else if (ent.isFile()) fs15.copyFileSync(s, d);
3746
3912
  }
3747
3913
  }
3748
3914
 
@@ -3759,10 +3925,10 @@ function splitFrontmatter(raw) {
3759
3925
  return { fm, body: (match[2] ?? "").trim() };
3760
3926
  }
3761
3927
  function resolveAgentFile(profileDir, name) {
3762
- const local = path14.join(profileDir, "agents", `${name}.md`);
3763
- if (fs15.existsSync(local)) return local;
3764
- const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
3765
- if (fs15.existsSync(central)) return central;
3928
+ const local = path15.join(profileDir, "agents", `${name}.md`);
3929
+ if (fs16.existsSync(local)) return local;
3930
+ const central = path15.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
3931
+ if (fs16.existsSync(central)) return central;
3766
3932
  throw new Error(
3767
3933
  `loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`
3768
3934
  );
@@ -3772,7 +3938,7 @@ function loadSubagents(profile) {
3772
3938
  if (!names || names.length === 0) return void 0;
3773
3939
  const agents = {};
3774
3940
  for (const name of names) {
3775
- const { fm, body } = splitFrontmatter(fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
3941
+ const { fm, body } = splitFrontmatter(fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
3776
3942
  if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
3777
3943
  const def = {
3778
3944
  description: fm.description ?? `Subagent ${name}`,
@@ -3868,8 +4034,8 @@ function pushWithRetry(opts = {}) {
3868
4034
  }
3869
4035
 
3870
4036
  // src/commit.ts
3871
- import * as fs16 from "fs";
3872
- import * as path15 from "path";
4037
+ import * as fs17 from "fs";
4038
+ import * as path16 from "path";
3873
4039
  var FORBIDDEN_PATH_PREFIXES = [
3874
4040
  ".kody/",
3875
4041
  ".kody-engine/",
@@ -3925,18 +4091,18 @@ function tryGit(args, cwd) {
3925
4091
  }
3926
4092
  function abortUnfinishedGitOps(cwd) {
3927
4093
  const aborted = [];
3928
- const gitDir = path15.join(cwd ?? process.cwd(), ".git");
3929
- if (!fs16.existsSync(gitDir)) return aborted;
3930
- if (fs16.existsSync(path15.join(gitDir, "MERGE_HEAD"))) {
4094
+ const gitDir = path16.join(cwd ?? process.cwd(), ".git");
4095
+ if (!fs17.existsSync(gitDir)) return aborted;
4096
+ if (fs17.existsSync(path16.join(gitDir, "MERGE_HEAD"))) {
3931
4097
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
3932
4098
  }
3933
- if (fs16.existsSync(path15.join(gitDir, "CHERRY_PICK_HEAD"))) {
4099
+ if (fs17.existsSync(path16.join(gitDir, "CHERRY_PICK_HEAD"))) {
3934
4100
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
3935
4101
  }
3936
- if (fs16.existsSync(path15.join(gitDir, "REVERT_HEAD"))) {
4102
+ if (fs17.existsSync(path16.join(gitDir, "REVERT_HEAD"))) {
3937
4103
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
3938
4104
  }
3939
- if (fs16.existsSync(path15.join(gitDir, "rebase-merge")) || fs16.existsSync(path15.join(gitDir, "rebase-apply"))) {
4105
+ if (fs17.existsSync(path16.join(gitDir, "rebase-merge")) || fs17.existsSync(path16.join(gitDir, "rebase-apply"))) {
3940
4106
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
3941
4107
  }
3942
4108
  try {
@@ -3992,7 +4158,7 @@ function normalizeCommitMessage(raw) {
3992
4158
  function commitAndPush(branch, agentMessage, cwd) {
3993
4159
  const allChanged = listChangedFiles(cwd);
3994
4160
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
3995
- const mergeHeadExists = fs16.existsSync(path15.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
4161
+ const mergeHeadExists = fs17.existsSync(path16.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3996
4162
  if (allowedFiles.length === 0 && !mergeHeadExists) {
3997
4163
  return { committed: false, pushed: false, sha: "", message: "" };
3998
4164
  }
@@ -4290,22 +4456,22 @@ var advanceFlow = async (ctx, profile) => {
4290
4456
  };
4291
4457
 
4292
4458
  // src/scripts/brainServe.ts
4459
+ init_repoWorkspace();
4293
4460
  import { createServer } from "http";
4294
- import { spawn as spawn3, spawnSync } from "child_process";
4295
- import * as fs18 from "fs";
4296
- import * as path17 from "path";
4461
+ import * as fs19 from "fs";
4462
+ import * as path18 from "path";
4297
4463
 
4298
4464
  // src/scripts/brainTurnLog.ts
4299
- import * as fs17 from "fs";
4300
- import * as path16 from "path";
4465
+ import * as fs18 from "fs";
4466
+ import * as path17 from "path";
4301
4467
  var live = /* @__PURE__ */ new Map();
4302
4468
  function eventsPath(dir, chatId) {
4303
- return path16.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
4469
+ return path17.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
4304
4470
  }
4305
4471
  function lastPersistedSeq(dir, chatId) {
4306
4472
  const p = eventsPath(dir, chatId);
4307
- if (!fs17.existsSync(p)) return 0;
4308
- const lines = fs17.readFileSync(p, "utf-8").split("\n").filter(Boolean);
4473
+ if (!fs18.existsSync(p)) return 0;
4474
+ const lines = fs18.readFileSync(p, "utf-8").split("\n").filter(Boolean);
4309
4475
  if (lines.length === 0) return 0;
4310
4476
  try {
4311
4477
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -4315,9 +4481,9 @@ function lastPersistedSeq(dir, chatId) {
4315
4481
  }
4316
4482
  function readSince(dir, chatId, since) {
4317
4483
  const p = eventsPath(dir, chatId);
4318
- if (!fs17.existsSync(p)) return [];
4484
+ if (!fs18.existsSync(p)) return [];
4319
4485
  const out = [];
4320
- for (const line of fs17.readFileSync(p, "utf-8").split("\n")) {
4486
+ for (const line of fs18.readFileSync(p, "utf-8").split("\n")) {
4321
4487
  if (!line) continue;
4322
4488
  try {
4323
4489
  const rec = JSON.parse(line);
@@ -4343,12 +4509,12 @@ function beginTurn(dir, chatId) {
4343
4509
  };
4344
4510
  live.set(chatId, state);
4345
4511
  const p = eventsPath(dir, chatId);
4346
- fs17.mkdirSync(path16.dirname(p), { recursive: true });
4512
+ fs18.mkdirSync(path17.dirname(p), { recursive: true });
4347
4513
  return (event) => {
4348
4514
  state.seq += 1;
4349
4515
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
4350
4516
  try {
4351
- fs17.appendFileSync(p, JSON.stringify(rec) + "\n");
4517
+ fs18.appendFileSync(p, JSON.stringify(rec) + "\n");
4352
4518
  } catch (err) {
4353
4519
  process.stderr.write(
4354
4520
  `[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
@@ -4386,7 +4552,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
4386
4552
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
4387
4553
  };
4388
4554
  try {
4389
- fs17.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
4555
+ fs18.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
4390
4556
  } catch {
4391
4557
  }
4392
4558
  state.status = "ended";
@@ -4474,17 +4640,17 @@ function authOk(req, expected) {
4474
4640
  return false;
4475
4641
  }
4476
4642
  function readJsonBody(req) {
4477
- return new Promise((resolve5, reject) => {
4643
+ return new Promise((resolve6, reject) => {
4478
4644
  const chunks = [];
4479
4645
  req.on("data", (c) => chunks.push(c));
4480
4646
  req.on("end", () => {
4481
4647
  const raw = Buffer.concat(chunks).toString("utf-8");
4482
4648
  if (!raw.trim()) {
4483
- resolve5({});
4649
+ resolve6({});
4484
4650
  return;
4485
4651
  }
4486
4652
  try {
4487
- resolve5(JSON.parse(raw));
4653
+ resolve6(JSON.parse(raw));
4488
4654
  } catch (err) {
4489
4655
  reject(err instanceof Error ? err : new Error(String(err)));
4490
4656
  }
@@ -4596,51 +4762,6 @@ function streamToRes(res, dir, chatId, since) {
4596
4762
  );
4597
4763
  res.on("close", unsubscribe);
4598
4764
  }
4599
- var REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
4600
- var repoClones = /* @__PURE__ */ new Map();
4601
- async function ensureRepoCwd(opts) {
4602
- const repo = opts.repo?.trim();
4603
- if (!repo || !REPO_RE.test(repo)) return opts.baseCwd;
4604
- const root = path17.resolve(opts.reposRoot);
4605
- const dir = path17.resolve(root, repo);
4606
- if (dir !== root && !dir.startsWith(root + path17.sep)) return opts.baseCwd;
4607
- if (fs18.existsSync(path17.join(dir, ".git"))) return dir;
4608
- const inflight = repoClones.get(dir);
4609
- if (inflight) {
4610
- await inflight;
4611
- return dir;
4612
- }
4613
- const p = opts.cloneRepo(repo, opts.repoToken, dir).finally(() => {
4614
- if (repoClones.get(dir) === p) repoClones.delete(dir);
4615
- });
4616
- repoClones.set(dir, p);
4617
- await p;
4618
- return dir;
4619
- }
4620
- var defaultCloneRepo = (repo, token, dir) => {
4621
- fs18.mkdirSync(path17.dirname(dir), { recursive: true });
4622
- const authUrl = token ? `https://x-access-token:${token}@github.com/${repo}.git` : `https://github.com/${repo}.git`;
4623
- return new Promise((resolve5, reject) => {
4624
- const child = spawn3("git", ["clone", "--depth=1", authUrl, dir], {
4625
- stdio: "inherit"
4626
- });
4627
- child.on("exit", (code) => {
4628
- if (code !== 0) {
4629
- reject(new Error(`git clone ${repo} failed (exit ${code})`));
4630
- return;
4631
- }
4632
- try {
4633
- const name = process.env.GIT_AUTHOR_NAME ?? "Kody Bot";
4634
- const email = process.env.GIT_AUTHOR_EMAIL ?? "kody-bot@users.noreply.github.com";
4635
- spawnSync("git", ["-C", dir, "config", "user.name", name]);
4636
- spawnSync("git", ["-C", dir, "config", "user.email", email]);
4637
- } catch {
4638
- }
4639
- resolve5();
4640
- });
4641
- child.on("error", reject);
4642
- });
4643
- };
4644
4765
  async function handleChatTurn(req, res, chatId, opts) {
4645
4766
  let body;
4646
4767
  try {
@@ -4657,7 +4778,7 @@ async function handleChatTurn(req, res, chatId, opts) {
4657
4778
  const repo = strField(body, "repo");
4658
4779
  const repoToken = strField(body, "repoToken");
4659
4780
  const sessionFile = sessionFilePath(opts.cwd, chatId);
4660
- fs18.mkdirSync(path17.dirname(sessionFile), { recursive: true });
4781
+ fs19.mkdirSync(path18.dirname(sessionFile), { recursive: true });
4661
4782
  appendTurn(sessionFile, {
4662
4783
  role: "user",
4663
4784
  content: message,
@@ -4681,7 +4802,10 @@ async function handleChatTurn(req, res, chatId, opts) {
4681
4802
  cwd: agentCwd,
4682
4803
  model: opts.model,
4683
4804
  litellmUrl: opts.litellmUrl,
4684
- sink
4805
+ sink,
4806
+ // Let the agent clone + work on OTHER repos via the fetch_repo tool.
4807
+ reposRoot: opts.reposRoot,
4808
+ repoToken
4685
4809
  });
4686
4810
  } catch (err) {
4687
4811
  const errMsg3 = err instanceof Error ? err.message : String(err);
@@ -4701,7 +4825,7 @@ async function handleChatTurn(req, res, chatId, opts) {
4701
4825
  function buildServer(opts) {
4702
4826
  const runTurn = opts.runTurn ?? runChatTurn;
4703
4827
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
4704
- const reposRoot = opts.reposRoot ?? path17.join(path17.dirname(path17.resolve(opts.cwd)), "repos");
4828
+ const reposRoot = opts.reposRoot ?? path18.join(path18.dirname(path18.resolve(opts.cwd)), "repos");
4705
4829
  return createServer(async (req, res) => {
4706
4830
  if (!req.method || !req.url) {
4707
4831
  sendJson(res, 400, { error: "bad request" });
@@ -4782,13 +4906,13 @@ var brainServe = async (ctx) => {
4782
4906
  model,
4783
4907
  litellmUrl
4784
4908
  });
4785
- await new Promise((resolve5) => {
4909
+ await new Promise((resolve6) => {
4786
4910
  server.listen(port, "0.0.0.0", () => {
4787
4911
  process.stdout.write(
4788
4912
  `[brain-serve] listening on 0.0.0.0:${port} (cwd=${ctx.cwd})
4789
4913
  `
4790
4914
  );
4791
- resolve5();
4915
+ resolve6();
4792
4916
  });
4793
4917
  });
4794
4918
  const shutdown = (signal) => {
@@ -4956,13 +5080,13 @@ function defaultLabelMap() {
4956
5080
  }
4957
5081
 
4958
5082
  // src/scripts/commitAndPush.ts
4959
- import * as fs20 from "fs";
4960
- import * as path19 from "path";
5083
+ import * as fs21 from "fs";
5084
+ import * as path20 from "path";
4961
5085
  init_events();
4962
5086
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
4963
5087
  function sentinelPathForStage(cwd, profileName) {
4964
5088
  const runId = resolveRunId();
4965
- return path19.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
5089
+ return path20.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4966
5090
  }
4967
5091
  var commitAndPush2 = async (ctx, profile) => {
4968
5092
  const branch = ctx.data.branch;
@@ -4972,9 +5096,9 @@ var commitAndPush2 = async (ctx, profile) => {
4972
5096
  }
4973
5097
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
4974
5098
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
4975
- if (sentinel && fs20.existsSync(sentinel)) {
5099
+ if (sentinel && fs21.existsSync(sentinel)) {
4976
5100
  try {
4977
- const replay = JSON.parse(fs20.readFileSync(sentinel, "utf-8"));
5101
+ const replay = JSON.parse(fs21.readFileSync(sentinel, "utf-8"));
4978
5102
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
4979
5103
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
4980
5104
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -5027,8 +5151,8 @@ var commitAndPush2 = async (ctx, profile) => {
5027
5151
  const result = ctx.data.commitResult;
5028
5152
  if (sentinel && result?.committed) {
5029
5153
  try {
5030
- fs20.mkdirSync(path19.dirname(sentinel), { recursive: true });
5031
- fs20.writeFileSync(
5154
+ fs21.mkdirSync(path20.dirname(sentinel), { recursive: true });
5155
+ fs21.writeFileSync(
5032
5156
  sentinel,
5033
5157
  JSON.stringify(
5034
5158
  {
@@ -5089,14 +5213,14 @@ function ensureStateBranch(owner, repo, cwd) {
5089
5213
  }
5090
5214
 
5091
5215
  // src/goal/state.ts
5092
- import * as fs21 from "fs";
5093
- import * as path20 from "path";
5216
+ import * as fs22 from "fs";
5217
+ import * as path21 from "path";
5094
5218
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
5095
5219
  var GoalStateError = class extends Error {
5096
- constructor(path38, message) {
5097
- super(`Invalid goal state at ${path38}:
5220
+ constructor(path39, message) {
5221
+ super(`Invalid goal state at ${path39}:
5098
5222
  ${message}`);
5099
- this.path = path38;
5223
+ this.path = path39;
5100
5224
  this.name = "GoalStateError";
5101
5225
  }
5102
5226
  path;
@@ -5236,20 +5360,20 @@ function describeCommitMessage(goal) {
5236
5360
  }
5237
5361
 
5238
5362
  // src/scripts/composePrompt.ts
5239
- import * as fs22 from "fs";
5240
- import * as path21 from "path";
5363
+ import * as fs23 from "fs";
5364
+ import * as path22 from "path";
5241
5365
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
5242
5366
  var composePrompt = async (ctx, profile) => {
5243
5367
  const explicit = ctx.data.promptTemplate;
5244
5368
  const mode = ctx.args.mode;
5245
5369
  const candidates = [
5246
- explicit ? path21.join(profile.dir, explicit) : null,
5247
- mode ? path21.join(profile.dir, "prompts", `${mode}.md`) : null,
5248
- path21.join(profile.dir, "prompt.md")
5370
+ explicit ? path22.join(profile.dir, explicit) : null,
5371
+ mode ? path22.join(profile.dir, "prompts", `${mode}.md`) : null,
5372
+ path22.join(profile.dir, "prompt.md")
5249
5373
  ].filter(Boolean);
5250
5374
  let templatePath = "";
5251
5375
  for (const c of candidates) {
5252
- if (fs22.existsSync(c)) {
5376
+ if (fs23.existsSync(c)) {
5253
5377
  templatePath = c;
5254
5378
  break;
5255
5379
  }
@@ -5257,7 +5381,7 @@ var composePrompt = async (ctx, profile) => {
5257
5381
  if (!templatePath) {
5258
5382
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
5259
5383
  }
5260
- const template = fs22.readFileSync(templatePath, "utf-8");
5384
+ const template = fs23.readFileSync(templatePath, "utf-8");
5261
5385
  const tokens = {
5262
5386
  ...stringifyAll(ctx.args, "args."),
5263
5387
  ...stringifyAll(ctx.data, ""),
@@ -6035,15 +6159,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
6035
6159
 
6036
6160
  // src/scripts/diagMcp.ts
6037
6161
  import { execFileSync as execFileSync10 } from "child_process";
6038
- import * as fs23 from "fs";
6162
+ import * as fs24 from "fs";
6039
6163
  import * as os4 from "os";
6040
- import * as path22 from "path";
6164
+ import * as path23 from "path";
6041
6165
  var diagMcp = async (_ctx) => {
6042
6166
  const home = os4.homedir();
6043
- const cacheDir = path22.join(home, ".cache", "ms-playwright");
6167
+ const cacheDir = path23.join(home, ".cache", "ms-playwright");
6044
6168
  let entries = [];
6045
6169
  try {
6046
- entries = fs23.readdirSync(cacheDir);
6170
+ entries = fs24.readdirSync(cacheDir);
6047
6171
  } catch {
6048
6172
  }
6049
6173
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -6069,17 +6193,17 @@ var diagMcp = async (_ctx) => {
6069
6193
  };
6070
6194
 
6071
6195
  // src/scripts/discoverQaContext.ts
6072
- import * as fs25 from "fs";
6073
- import * as path24 from "path";
6196
+ import * as fs26 from "fs";
6197
+ import * as path25 from "path";
6074
6198
 
6075
6199
  // src/scripts/frameworkDetectors.ts
6076
- import * as fs24 from "fs";
6077
- import * as path23 from "path";
6200
+ import * as fs25 from "fs";
6201
+ import * as path24 from "path";
6078
6202
  function detectFrameworks(cwd) {
6079
6203
  const out = [];
6080
6204
  let deps = {};
6081
6205
  try {
6082
- const pkg = JSON.parse(fs24.readFileSync(path23.join(cwd, "package.json"), "utf-8"));
6206
+ const pkg = JSON.parse(fs25.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
6083
6207
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
6084
6208
  } catch {
6085
6209
  return out;
@@ -6116,7 +6240,7 @@ function detectFrameworks(cwd) {
6116
6240
  }
6117
6241
  function findFile(cwd, candidates) {
6118
6242
  for (const c of candidates) {
6119
- if (fs24.existsSync(path23.join(cwd, c))) return c;
6243
+ if (fs25.existsSync(path24.join(cwd, c))) return c;
6120
6244
  }
6121
6245
  return null;
6122
6246
  }
@@ -6129,18 +6253,18 @@ var COLLECTION_DIRS = [
6129
6253
  function discoverPayloadCollections(cwd) {
6130
6254
  const out = [];
6131
6255
  for (const dir of COLLECTION_DIRS) {
6132
- const full = path23.join(cwd, dir);
6133
- if (!fs24.existsSync(full)) continue;
6256
+ const full = path24.join(cwd, dir);
6257
+ if (!fs25.existsSync(full)) continue;
6134
6258
  let files;
6135
6259
  try {
6136
- files = fs24.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6260
+ files = fs25.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6137
6261
  } catch {
6138
6262
  continue;
6139
6263
  }
6140
6264
  for (const file of files) {
6141
6265
  try {
6142
- const filePath = path23.join(full, file);
6143
- const content = fs24.readFileSync(filePath, "utf-8").slice(0, 1e4);
6266
+ const filePath = path24.join(full, file);
6267
+ const content = fs25.readFileSync(filePath, "utf-8").slice(0, 1e4);
6144
6268
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
6145
6269
  if (!slugMatch) continue;
6146
6270
  const slug = slugMatch[1];
@@ -6154,7 +6278,7 @@ function discoverPayloadCollections(cwd) {
6154
6278
  out.push({
6155
6279
  name,
6156
6280
  slug,
6157
- filePath: path23.relative(cwd, filePath),
6281
+ filePath: path24.relative(cwd, filePath),
6158
6282
  fields: fields.slice(0, 20),
6159
6283
  hasAdmin
6160
6284
  });
@@ -6168,28 +6292,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
6168
6292
  function discoverAdminComponents(cwd, collections) {
6169
6293
  const out = [];
6170
6294
  for (const dir of ADMIN_COMPONENT_DIRS) {
6171
- const full = path23.join(cwd, dir);
6172
- if (!fs24.existsSync(full)) continue;
6295
+ const full = path24.join(cwd, dir);
6296
+ if (!fs25.existsSync(full)) continue;
6173
6297
  let entries;
6174
6298
  try {
6175
- entries = fs24.readdirSync(full, { withFileTypes: true });
6299
+ entries = fs25.readdirSync(full, { withFileTypes: true });
6176
6300
  } catch {
6177
6301
  continue;
6178
6302
  }
6179
6303
  for (const entry of entries) {
6180
- const entryPath = path23.join(full, entry.name);
6304
+ const entryPath = path24.join(full, entry.name);
6181
6305
  let name;
6182
6306
  let filePath;
6183
6307
  if (entry.isDirectory()) {
6184
6308
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
6185
- (f) => fs24.existsSync(path23.join(entryPath, f))
6309
+ (f) => fs25.existsSync(path24.join(entryPath, f))
6186
6310
  );
6187
6311
  if (!indexFile) continue;
6188
6312
  name = entry.name;
6189
- filePath = path23.relative(cwd, path23.join(entryPath, indexFile));
6313
+ filePath = path24.relative(cwd, path24.join(entryPath, indexFile));
6190
6314
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
6191
6315
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
6192
- filePath = path23.relative(cwd, entryPath);
6316
+ filePath = path24.relative(cwd, entryPath);
6193
6317
  } else {
6194
6318
  continue;
6195
6319
  }
@@ -6197,7 +6321,7 @@ function discoverAdminComponents(cwd, collections) {
6197
6321
  if (collections) {
6198
6322
  for (const col of collections) {
6199
6323
  try {
6200
- const colContent = fs24.readFileSync(path23.join(cwd, col.filePath), "utf-8");
6324
+ const colContent = fs25.readFileSync(path24.join(cwd, col.filePath), "utf-8");
6201
6325
  if (colContent.includes(name)) {
6202
6326
  usedInCollection = col.slug;
6203
6327
  break;
@@ -6216,8 +6340,8 @@ function scanApiRoutes(cwd) {
6216
6340
  const out = [];
6217
6341
  const appDirs = ["src/app", "app"];
6218
6342
  for (const appDir of appDirs) {
6219
- const apiDir = path23.join(cwd, appDir, "api");
6220
- if (!fs24.existsSync(apiDir)) continue;
6343
+ const apiDir = path24.join(cwd, appDir, "api");
6344
+ if (!fs25.existsSync(apiDir)) continue;
6221
6345
  walkApiRoutes(apiDir, "/api", cwd, out);
6222
6346
  break;
6223
6347
  }
@@ -6226,14 +6350,14 @@ function scanApiRoutes(cwd) {
6226
6350
  function walkApiRoutes(dir, prefix, cwd, out) {
6227
6351
  let entries;
6228
6352
  try {
6229
- entries = fs24.readdirSync(dir, { withFileTypes: true });
6353
+ entries = fs25.readdirSync(dir, { withFileTypes: true });
6230
6354
  } catch {
6231
6355
  return;
6232
6356
  }
6233
6357
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
6234
6358
  if (routeFile) {
6235
6359
  try {
6236
- const content = fs24.readFileSync(path23.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
6360
+ const content = fs25.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
6237
6361
  const methods = HTTP_METHODS.filter(
6238
6362
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
6239
6363
  );
@@ -6241,7 +6365,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6241
6365
  out.push({
6242
6366
  path: prefix,
6243
6367
  methods,
6244
- filePath: path23.relative(cwd, path23.join(dir, routeFile.name))
6368
+ filePath: path24.relative(cwd, path24.join(dir, routeFile.name))
6245
6369
  });
6246
6370
  }
6247
6371
  } catch {
@@ -6252,7 +6376,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6252
6376
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6253
6377
  let segment = entry.name;
6254
6378
  if (segment.startsWith("(") && segment.endsWith(")")) {
6255
- walkApiRoutes(path23.join(dir, entry.name), prefix, cwd, out);
6379
+ walkApiRoutes(path24.join(dir, entry.name), prefix, cwd, out);
6256
6380
  continue;
6257
6381
  }
6258
6382
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -6260,7 +6384,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6260
6384
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
6261
6385
  segment = `:${segment.slice(1, -1)}`;
6262
6386
  }
6263
- walkApiRoutes(path23.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
6387
+ walkApiRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
6264
6388
  }
6265
6389
  }
6266
6390
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -6280,10 +6404,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
6280
6404
  function scanEnvVars(cwd) {
6281
6405
  const candidates = [".env.example", ".env.local.example", ".env.template"];
6282
6406
  for (const envFile of candidates) {
6283
- const envPath = path23.join(cwd, envFile);
6284
- if (!fs24.existsSync(envPath)) continue;
6407
+ const envPath = path24.join(cwd, envFile);
6408
+ if (!fs25.existsSync(envPath)) continue;
6285
6409
  try {
6286
- const content = fs24.readFileSync(envPath, "utf-8");
6410
+ const content = fs25.readFileSync(envPath, "utf-8");
6287
6411
  const vars = [];
6288
6412
  for (const line of content.split("\n")) {
6289
6413
  const trimmed = line.trim();
@@ -6331,9 +6455,9 @@ function runQaDiscovery(cwd) {
6331
6455
  }
6332
6456
  function detectDevServer(cwd, out) {
6333
6457
  try {
6334
- const pkg = JSON.parse(fs25.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
6458
+ const pkg = JSON.parse(fs26.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
6335
6459
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
6336
- const pm = fs25.existsSync(path24.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs25.existsSync(path24.join(cwd, "yarn.lock")) ? "yarn" : fs25.existsSync(path24.join(cwd, "bun.lockb")) ? "bun" : "npm";
6460
+ const pm = fs26.existsSync(path25.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs26.existsSync(path25.join(cwd, "yarn.lock")) ? "yarn" : fs26.existsSync(path25.join(cwd, "bun.lockb")) ? "bun" : "npm";
6337
6461
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
6338
6462
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
6339
6463
  else if (allDeps.vite) out.devPort = 5173;
@@ -6343,8 +6467,8 @@ function detectDevServer(cwd, out) {
6343
6467
  function scanFrontendRoutes(cwd, out) {
6344
6468
  const appDirs = ["src/app", "app"];
6345
6469
  for (const appDir of appDirs) {
6346
- const full = path24.join(cwd, appDir);
6347
- if (!fs25.existsSync(full)) continue;
6470
+ const full = path25.join(cwd, appDir);
6471
+ if (!fs26.existsSync(full)) continue;
6348
6472
  walkFrontendRoutes(full, "", out);
6349
6473
  break;
6350
6474
  }
@@ -6352,7 +6476,7 @@ function scanFrontendRoutes(cwd, out) {
6352
6476
  function walkFrontendRoutes(dir, prefix, out) {
6353
6477
  let entries;
6354
6478
  try {
6355
- entries = fs25.readdirSync(dir, { withFileTypes: true });
6479
+ entries = fs26.readdirSync(dir, { withFileTypes: true });
6356
6480
  } catch {
6357
6481
  return;
6358
6482
  }
@@ -6369,7 +6493,7 @@ function walkFrontendRoutes(dir, prefix, out) {
6369
6493
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6370
6494
  let segment = entry.name;
6371
6495
  if (segment.startsWith("(") && segment.endsWith(")")) {
6372
- walkFrontendRoutes(path24.join(dir, entry.name), prefix, out);
6496
+ walkFrontendRoutes(path25.join(dir, entry.name), prefix, out);
6373
6497
  continue;
6374
6498
  }
6375
6499
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -6377,7 +6501,7 @@ function walkFrontendRoutes(dir, prefix, out) {
6377
6501
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
6378
6502
  segment = `:${segment.slice(1, -1)}`;
6379
6503
  }
6380
- walkFrontendRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, out);
6504
+ walkFrontendRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, out);
6381
6505
  }
6382
6506
  }
6383
6507
  function detectAuthFiles(cwd, out) {
@@ -6394,23 +6518,23 @@ function detectAuthFiles(cwd, out) {
6394
6518
  "src/app/api/oauth"
6395
6519
  ];
6396
6520
  for (const c of candidates) {
6397
- if (fs25.existsSync(path24.join(cwd, c))) out.authFiles.push(c);
6521
+ if (fs26.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
6398
6522
  }
6399
6523
  }
6400
6524
  function detectRoles(cwd, out) {
6401
6525
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
6402
6526
  for (const rp of rolePaths) {
6403
- const dir = path24.join(cwd, rp);
6404
- if (!fs25.existsSync(dir)) continue;
6527
+ const dir = path25.join(cwd, rp);
6528
+ if (!fs26.existsSync(dir)) continue;
6405
6529
  let files;
6406
6530
  try {
6407
- files = fs25.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6531
+ files = fs26.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6408
6532
  } catch {
6409
6533
  continue;
6410
6534
  }
6411
6535
  for (const f of files) {
6412
6536
  try {
6413
- const content = fs25.readFileSync(path24.join(dir, f), "utf-8").slice(0, 5e3);
6537
+ const content = fs26.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
6414
6538
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
6415
6539
  if (roleMatches) {
6416
6540
  for (const m of roleMatches) {
@@ -6594,8 +6718,8 @@ function failedAction3(reason) {
6594
6718
  }
6595
6719
 
6596
6720
  // src/scripts/dispatchJobFileTicks.ts
6597
- import * as fs27 from "fs";
6598
- import * as path26 from "path";
6721
+ import * as fs28 from "fs";
6722
+ import * as path27 from "path";
6599
6723
 
6600
6724
  // src/scripts/jobFrontmatter.ts
6601
6725
  var SCHEDULE_EVERY_VALUES = [
@@ -6864,8 +6988,8 @@ var ContentsApiBackend = class {
6864
6988
  };
6865
6989
 
6866
6990
  // src/scripts/jobState/localFileBackend.ts
6867
- import * as fs26 from "fs";
6868
- import * as path25 from "path";
6991
+ import * as fs27 from "fs";
6992
+ import * as path26 from "path";
6869
6993
  var LocalFileBackend = class {
6870
6994
  name = "local-file";
6871
6995
  cwd;
@@ -6880,7 +7004,7 @@ var LocalFileBackend = class {
6880
7004
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
6881
7005
  this.cwd = opts.cwd;
6882
7006
  this.jobsDir = opts.jobsDir;
6883
- this.absDir = path25.join(opts.cwd, opts.jobsDir);
7007
+ this.absDir = path26.join(opts.cwd, opts.jobsDir);
6884
7008
  this.owner = opts.owner;
6885
7009
  this.repo = opts.repo;
6886
7010
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -6895,7 +7019,7 @@ var LocalFileBackend = class {
6895
7019
  `);
6896
7020
  return;
6897
7021
  }
6898
- fs26.mkdirSync(this.absDir, { recursive: true });
7022
+ fs27.mkdirSync(this.absDir, { recursive: true });
6899
7023
  const prefix = this.cacheKeyPrefix();
6900
7024
  const probeKey = `${prefix}probe-${Date.now()}`;
6901
7025
  try {
@@ -6924,7 +7048,7 @@ var LocalFileBackend = class {
6924
7048
  `);
6925
7049
  return;
6926
7050
  }
6927
- if (!fs26.existsSync(this.absDir)) {
7051
+ if (!fs27.existsSync(this.absDir)) {
6928
7052
  return;
6929
7053
  }
6930
7054
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -6940,11 +7064,11 @@ var LocalFileBackend = class {
6940
7064
  }
6941
7065
  load(slug) {
6942
7066
  const relPath = stateFilePath(this.jobsDir, slug);
6943
- const absPath = path25.join(this.cwd, relPath);
6944
- if (!fs26.existsSync(absPath)) {
7067
+ const absPath = path26.join(this.cwd, relPath);
7068
+ if (!fs27.existsSync(absPath)) {
6945
7069
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
6946
7070
  }
6947
- const raw = fs26.readFileSync(absPath, "utf-8");
7071
+ const raw = fs27.readFileSync(absPath, "utf-8");
6948
7072
  let parsed;
6949
7073
  try {
6950
7074
  parsed = JSON.parse(raw);
@@ -6961,10 +7085,10 @@ var LocalFileBackend = class {
6961
7085
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
6962
7086
  return false;
6963
7087
  }
6964
- const absPath = path25.join(this.cwd, loaded.path);
6965
- fs26.mkdirSync(path25.dirname(absPath), { recursive: true });
7088
+ const absPath = path26.join(this.cwd, loaded.path);
7089
+ fs27.mkdirSync(path26.dirname(absPath), { recursive: true });
6966
7090
  const body = JSON.stringify(next, null, 2) + "\n";
6967
- fs26.writeFileSync(absPath, body, "utf-8");
7091
+ fs27.writeFileSync(absPath, body, "utf-8");
6968
7092
  return true;
6969
7093
  }
6970
7094
  cacheKeyPrefix() {
@@ -7042,7 +7166,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
7042
7166
  await backend.hydrate();
7043
7167
  }
7044
7168
  try {
7045
- const slugs = listJobSlugs(path26.join(ctx.cwd, jobsDir));
7169
+ const slugs = listJobSlugs(path27.join(ctx.cwd, jobsDir));
7046
7170
  ctx.data.jobSlugCount = slugs.length;
7047
7171
  if (slugs.length === 0) {
7048
7172
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -7155,17 +7279,17 @@ function formatAgo(ms) {
7155
7279
  }
7156
7280
  function readJobFrontmatter(cwd, jobsDir, slug) {
7157
7281
  try {
7158
- const raw = fs27.readFileSync(path26.join(cwd, jobsDir, `${slug}.md`), "utf-8");
7282
+ const raw = fs28.readFileSync(path27.join(cwd, jobsDir, `${slug}.md`), "utf-8");
7159
7283
  return splitFrontmatter2(raw).frontmatter;
7160
7284
  } catch {
7161
7285
  return {};
7162
7286
  }
7163
7287
  }
7164
7288
  function listJobSlugs(absDir) {
7165
- if (!fs27.existsSync(absDir)) return [];
7289
+ if (!fs28.existsSync(absDir)) return [];
7166
7290
  let entries;
7167
7291
  try {
7168
- entries = fs27.readdirSync(absDir, { withFileTypes: true });
7292
+ entries = fs28.readdirSync(absDir, { withFileTypes: true });
7169
7293
  } catch {
7170
7294
  return [];
7171
7295
  }
@@ -7931,7 +8055,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
7931
8055
 
7932
8056
  // src/gha.ts
7933
8057
  import { execFileSync as execFileSync15 } from "child_process";
7934
- import * as fs28 from "fs";
8058
+ import * as fs29 from "fs";
7935
8059
  function getRunUrl() {
7936
8060
  const server = process.env.GITHUB_SERVER_URL;
7937
8061
  const repo = process.env.GITHUB_REPOSITORY;
@@ -7942,10 +8066,10 @@ function getRunUrl() {
7942
8066
  function reactToTriggerComment(cwd) {
7943
8067
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
7944
8068
  const eventPath = process.env.GITHUB_EVENT_PATH;
7945
- if (!eventPath || !fs28.existsSync(eventPath)) return;
8069
+ if (!eventPath || !fs29.existsSync(eventPath)) return;
7946
8070
  let event = null;
7947
8071
  try {
7948
- event = JSON.parse(fs28.readFileSync(eventPath, "utf-8"));
8072
+ event = JSON.parse(fs29.readFileSync(eventPath, "utf-8"));
7949
8073
  } catch {
7950
8074
  return;
7951
8075
  }
@@ -8239,12 +8363,12 @@ var handleAbandonedGoal = async (ctx) => {
8239
8363
 
8240
8364
  // src/scripts/initFlow.ts
8241
8365
  import { execFileSync as execFileSync17 } from "child_process";
8242
- import * as fs29 from "fs";
8243
- import * as path27 from "path";
8366
+ import * as fs30 from "fs";
8367
+ import * as path28 from "path";
8244
8368
  function detectPackageManager(cwd) {
8245
- if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
8246
- if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
8247
- if (fs29.existsSync(path27.join(cwd, "bun.lockb"))) return "bun";
8369
+ if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
8370
+ if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
8371
+ if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
8248
8372
  return "npm";
8249
8373
  }
8250
8374
  function qualityCommandsFor(pm) {
@@ -8373,36 +8497,36 @@ function performInit(cwd, force) {
8373
8497
  const pm = detectPackageManager(cwd);
8374
8498
  const ownerRepo = detectOwnerRepo(cwd);
8375
8499
  const defaultBranch2 = defaultBranchFromGit(cwd);
8376
- const configPath = path27.join(cwd, "kody.config.json");
8377
- if (fs29.existsSync(configPath) && !force) {
8500
+ const configPath = path28.join(cwd, "kody.config.json");
8501
+ if (fs30.existsSync(configPath) && !force) {
8378
8502
  skipped.push("kody.config.json");
8379
8503
  } else {
8380
8504
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
8381
- fs29.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
8505
+ fs30.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
8382
8506
  `);
8383
8507
  wrote.push("kody.config.json");
8384
8508
  }
8385
- const workflowDir = path27.join(cwd, ".github", "workflows");
8386
- const workflowPath = path27.join(workflowDir, "kody.yml");
8387
- if (fs29.existsSync(workflowPath) && !force) {
8509
+ const workflowDir = path28.join(cwd, ".github", "workflows");
8510
+ const workflowPath = path28.join(workflowDir, "kody.yml");
8511
+ if (fs30.existsSync(workflowPath) && !force) {
8388
8512
  skipped.push(".github/workflows/kody.yml");
8389
8513
  } else {
8390
- fs29.mkdirSync(workflowDir, { recursive: true });
8391
- fs29.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
8514
+ fs30.mkdirSync(workflowDir, { recursive: true });
8515
+ fs30.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
8392
8516
  wrote.push(".github/workflows/kody.yml");
8393
8517
  }
8394
8518
  const builtinJobs = listBuiltinJobs();
8395
8519
  if (builtinJobs.length > 0) {
8396
- const jobsDir = path27.join(cwd, ".kody", "duties");
8397
- fs29.mkdirSync(jobsDir, { recursive: true });
8520
+ const jobsDir = path28.join(cwd, ".kody", "duties");
8521
+ fs30.mkdirSync(jobsDir, { recursive: true });
8398
8522
  for (const job of builtinJobs) {
8399
- const rel = path27.join(".kody", "duties", `${job.slug}.md`);
8400
- const target = path27.join(cwd, rel);
8401
- if (fs29.existsSync(target) && !force) {
8523
+ const rel = path28.join(".kody", "duties", `${job.slug}.md`);
8524
+ const target = path28.join(cwd, rel);
8525
+ if (fs30.existsSync(target) && !force) {
8402
8526
  skipped.push(rel);
8403
8527
  continue;
8404
8528
  }
8405
- fs29.writeFileSync(target, fs29.readFileSync(job.filePath, "utf-8"));
8529
+ fs30.writeFileSync(target, fs30.readFileSync(job.filePath, "utf-8"));
8406
8530
  wrote.push(rel);
8407
8531
  }
8408
8532
  }
@@ -8414,12 +8538,12 @@ function performInit(cwd, force) {
8414
8538
  continue;
8415
8539
  }
8416
8540
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
8417
- const target = path27.join(workflowDir, `kody-${exe.name}.yml`);
8418
- if (fs29.existsSync(target) && !force) {
8541
+ const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
8542
+ if (fs30.existsSync(target) && !force) {
8419
8543
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
8420
8544
  continue;
8421
8545
  }
8422
- fs29.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
8546
+ fs30.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
8423
8547
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
8424
8548
  }
8425
8549
  let labels;
@@ -8603,8 +8727,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
8603
8727
  };
8604
8728
 
8605
8729
  // src/scripts/loadJobFromFile.ts
8606
- import * as fs30 from "fs";
8607
- import * as path28 from "path";
8730
+ import * as fs31 from "fs";
8731
+ import * as path29 from "path";
8608
8732
  var loadJobFromFile = async (ctx, _profile, args) => {
8609
8733
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
8610
8734
  const workersDir = String(args?.workersDir ?? ".kody/staff");
@@ -8613,11 +8737,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8613
8737
  if (!slug) {
8614
8738
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
8615
8739
  }
8616
- const absPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
8617
- if (!fs30.existsSync(absPath)) {
8740
+ const absPath = path29.join(ctx.cwd, jobsDir, `${slug}.md`);
8741
+ if (!fs31.existsSync(absPath)) {
8618
8742
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
8619
8743
  }
8620
- const raw = fs30.readFileSync(absPath, "utf-8");
8744
+ const raw = fs31.readFileSync(absPath, "utf-8");
8621
8745
  const { title, body } = parseJobFile(raw, slug);
8622
8746
  const frontmatter = splitFrontmatter2(raw).frontmatter;
8623
8747
  const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
@@ -8625,13 +8749,13 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8625
8749
  let workerTitle = "";
8626
8750
  let workerPersona = "";
8627
8751
  if (workerSlug) {
8628
- const workerPath = path28.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8629
- if (!fs30.existsSync(workerPath)) {
8752
+ const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8753
+ if (!fs31.existsSync(workerPath)) {
8630
8754
  throw new Error(
8631
8755
  `loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`
8632
8756
  );
8633
8757
  }
8634
- const workerRaw = fs30.readFileSync(workerPath, "utf-8");
8758
+ const workerRaw = fs31.readFileSync(workerPath, "utf-8");
8635
8759
  const parsed = parseJobFile(workerRaw, workerSlug);
8636
8760
  workerTitle = parsed.title;
8637
8761
  workerPersona = parsed.body;
@@ -8674,18 +8798,18 @@ init_loadMemoryContext();
8674
8798
  init_loadPriorArt();
8675
8799
 
8676
8800
  // src/scripts/loadQaContext.ts
8677
- import * as fs33 from "fs";
8678
- import * as path31 from "path";
8801
+ import * as fs34 from "fs";
8802
+ import * as path32 from "path";
8679
8803
 
8680
8804
  // src/scripts/kodyVariables.ts
8681
- import * as fs32 from "fs";
8682
- import * as path30 from "path";
8805
+ import * as fs33 from "fs";
8806
+ import * as path31 from "path";
8683
8807
  var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
8684
8808
  function readKodyVariables(cwd) {
8685
- const full = path30.join(cwd, KODY_VARIABLES_REL_PATH);
8809
+ const full = path31.join(cwd, KODY_VARIABLES_REL_PATH);
8686
8810
  let raw;
8687
8811
  try {
8688
- raw = fs32.readFileSync(full, "utf-8");
8812
+ raw = fs33.readFileSync(full, "utf-8");
8689
8813
  } catch {
8690
8814
  return {};
8691
8815
  }
@@ -8734,18 +8858,18 @@ function readProfileStaff(raw) {
8734
8858
  return { staff: staff ?? legacy ?? ["kody"], body };
8735
8859
  }
8736
8860
  function readProfile(cwd) {
8737
- const dir = path31.join(cwd, CONTEXT_DIR_REL_PATH);
8738
- if (!fs33.existsSync(dir)) return "";
8861
+ const dir = path32.join(cwd, CONTEXT_DIR_REL_PATH);
8862
+ if (!fs34.existsSync(dir)) return "";
8739
8863
  let entries;
8740
8864
  try {
8741
- entries = fs33.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
8865
+ entries = fs34.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
8742
8866
  } catch {
8743
8867
  return "";
8744
8868
  }
8745
8869
  const blocks = [];
8746
8870
  for (const file of entries) {
8747
8871
  try {
8748
- const raw = fs33.readFileSync(path31.join(dir, file), "utf-8");
8872
+ const raw = fs34.readFileSync(path32.join(dir, file), "utf-8");
8749
8873
  const { staff, body } = readProfileStaff(raw);
8750
8874
  if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
8751
8875
  blocks.push(`## ${file}
@@ -8782,8 +8906,8 @@ var loadQaContext = async (ctx) => {
8782
8906
  init_events();
8783
8907
 
8784
8908
  // src/taskContext.ts
8785
- import * as fs34 from "fs";
8786
- import * as path32 from "path";
8909
+ import * as fs35 from "fs";
8910
+ import * as path33 from "path";
8787
8911
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
8788
8912
  function buildTaskContext(args) {
8789
8913
  return {
@@ -8799,10 +8923,10 @@ function buildTaskContext(args) {
8799
8923
  }
8800
8924
  function persistTaskContext(cwd, ctx) {
8801
8925
  try {
8802
- const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
8803
- fs34.mkdirSync(dir, { recursive: true });
8804
- const file = path32.join(dir, "task-context.json");
8805
- fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8926
+ const dir = path33.join(cwd, ".kody", "runs", ctx.runId);
8927
+ fs35.mkdirSync(dir, { recursive: true });
8928
+ const file = path33.join(dir, "task-context.json");
8929
+ fs35.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8806
8930
  `);
8807
8931
  return file;
8808
8932
  } catch (err) {
@@ -8850,19 +8974,19 @@ var loadTaskState = async (ctx) => {
8850
8974
  };
8851
8975
 
8852
8976
  // src/scripts/loadWorkerAdhoc.ts
8853
- import * as fs35 from "fs";
8854
- import * as path33 from "path";
8977
+ import * as fs36 from "fs";
8978
+ import * as path34 from "path";
8855
8979
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
8856
8980
  const workersDir = String(args?.workersDir ?? ".kody/staff");
8857
8981
  const workerSlug = String(ctx.args.worker ?? "").trim();
8858
8982
  if (!workerSlug) {
8859
8983
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
8860
8984
  }
8861
- const workerPath = path33.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8862
- if (!fs35.existsSync(workerPath)) {
8985
+ const workerPath = path34.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8986
+ if (!fs36.existsSync(workerPath)) {
8863
8987
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
8864
8988
  }
8865
- const { title, body } = parsePersona(fs35.readFileSync(workerPath, "utf-8"), workerSlug);
8989
+ const { title, body } = parsePersona(fs36.readFileSync(workerPath, "utf-8"), workerSlug);
8866
8990
  const message = resolveMessage(ctx.args.message);
8867
8991
  if (!message) {
8868
8992
  throw new Error(
@@ -8882,9 +9006,9 @@ function resolveMessage(messageArg) {
8882
9006
  }
8883
9007
  function readCommentBody() {
8884
9008
  const eventPath = process.env.GITHUB_EVENT_PATH;
8885
- if (!eventPath || !fs35.existsSync(eventPath)) return "";
9009
+ if (!eventPath || !fs36.existsSync(eventPath)) return "";
8886
9010
  try {
8887
- const event = JSON.parse(fs35.readFileSync(eventPath, "utf-8"));
9011
+ const event = JSON.parse(fs36.readFileSync(eventPath, "utf-8"));
8888
9012
  return String(event.comment?.body ?? "");
8889
9013
  } catch {
8890
9014
  return "";
@@ -9554,8 +9678,8 @@ var FlyClient = class {
9554
9678
  get fetch() {
9555
9679
  return this.opts.fetchImpl ?? fetch;
9556
9680
  }
9557
- async call(path38, init = {}) {
9558
- const res = await this.fetch(`${FLY_API_BASE}${path38}`, {
9681
+ async call(path39, init = {}) {
9682
+ const res = await this.fetch(`${FLY_API_BASE}${path39}`, {
9559
9683
  method: init.method ?? "GET",
9560
9684
  headers: {
9561
9685
  Authorization: `Bearer ${this.opts.token}`,
@@ -9566,7 +9690,7 @@ var FlyClient = class {
9566
9690
  if (res.status === 404 && init.allow404) return null;
9567
9691
  if (!res.ok) {
9568
9692
  const text = await res.text().catch(() => "");
9569
- throw new Error(`Fly API ${res.status} on ${path38}: ${text.slice(0, 200) || res.statusText}`);
9693
+ throw new Error(`Fly API ${res.status} on ${path39}: ${text.slice(0, 200) || res.statusText}`);
9570
9694
  }
9571
9695
  if (res.status === 204) return null;
9572
9696
  const raw = await res.text();
@@ -10106,14 +10230,14 @@ function sendJson2(res, status, body) {
10106
10230
  res.end(JSON.stringify(body));
10107
10231
  }
10108
10232
  function readJsonBody2(req) {
10109
- return new Promise((resolve5, reject) => {
10233
+ return new Promise((resolve6, reject) => {
10110
10234
  const chunks = [];
10111
10235
  req.on("data", (c) => chunks.push(c));
10112
10236
  req.on("end", () => {
10113
10237
  const raw = Buffer.concat(chunks).toString("utf-8");
10114
- if (!raw.trim()) return resolve5({});
10238
+ if (!raw.trim()) return resolve6({});
10115
10239
  try {
10116
- resolve5(JSON.parse(raw));
10240
+ resolve6(JSON.parse(raw));
10117
10241
  } catch (err) {
10118
10242
  reject(err instanceof Error ? err : new Error(String(err)));
10119
10243
  }
@@ -10260,10 +10384,10 @@ var poolServe = async (ctx) => {
10260
10384
  }
10261
10385
  });
10262
10386
  const apiHost = process.env.POOL_API_HOST ?? "::";
10263
- await new Promise((resolve5) => {
10387
+ await new Promise((resolve6) => {
10264
10388
  server.listen(apiPort, apiHost, () => {
10265
10389
  log(`listening on ${apiHost}:${apiPort} (min=${min}, app=${app}, region=${region})`);
10266
- resolve5();
10390
+ resolve6();
10267
10391
  });
10268
10392
  });
10269
10393
  const shutdown = (signal) => {
@@ -11159,7 +11283,7 @@ function resolveBaseOverride(value) {
11159
11283
  // src/scripts/runnerServe.ts
11160
11284
  import { spawn as spawn5 } from "child_process";
11161
11285
  import { createServer as createServer3 } from "http";
11162
- import * as fs36 from "fs";
11286
+ import * as fs37 from "fs";
11163
11287
  var DEFAULT_PORT2 = 8080;
11164
11288
  var DEFAULT_WORKDIR = "/workspace/repo";
11165
11289
  function getApiKey2() {
@@ -11181,17 +11305,17 @@ function authOk2(req, expected) {
11181
11305
  return false;
11182
11306
  }
11183
11307
  function readJsonBody3(req) {
11184
- return new Promise((resolve5, reject) => {
11308
+ return new Promise((resolve6, reject) => {
11185
11309
  const chunks = [];
11186
11310
  req.on("data", (c) => chunks.push(c));
11187
11311
  req.on("end", () => {
11188
11312
  const raw = Buffer.concat(chunks).toString("utf-8");
11189
11313
  if (!raw.trim()) {
11190
- resolve5({});
11314
+ resolve6({});
11191
11315
  return;
11192
11316
  }
11193
11317
  try {
11194
- resolve5(JSON.parse(raw));
11318
+ resolve6(JSON.parse(raw));
11195
11319
  } catch (err) {
11196
11320
  reject(err instanceof Error ? err : new Error(String(err)));
11197
11321
  }
@@ -11240,8 +11364,8 @@ async function defaultRunJob(job) {
11240
11364
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
11241
11365
  const branch = job.ref ?? "main";
11242
11366
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
11243
- fs36.rmSync(workdir, { recursive: true, force: true });
11244
- fs36.mkdirSync(workdir, { recursive: true });
11367
+ fs37.rmSync(workdir, { recursive: true, force: true });
11368
+ fs37.mkdirSync(workdir, { recursive: true });
11245
11369
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
11246
11370
  const interactive = job.mode === "interactive";
11247
11371
  const childEnv = {
@@ -11266,13 +11390,13 @@ async function defaultRunJob(job) {
11266
11390
  ...interactive && job.idleExitMs ? { KODY_IDLE_EXIT_MS: String(job.idleExitMs) } : {},
11267
11391
  ...interactive && job.hardCapMs ? { KODY_HARD_CAP_MS: String(job.hardCapMs) } : {}
11268
11392
  };
11269
- const run = (cmd, args, cwd) => new Promise((resolve5) => {
11393
+ const run = (cmd, args, cwd) => new Promise((resolve6) => {
11270
11394
  const child = spawn5(cmd, args, { stdio: "inherit", env: childEnv, cwd });
11271
- child.on("exit", (code) => resolve5(code ?? 0));
11395
+ child.on("exit", (code) => resolve6(code ?? 0));
11272
11396
  child.on("error", (err) => {
11273
11397
  process.stderr.write(`[runner-serve] ${cmd} failed: ${err.message}
11274
11398
  `);
11275
- resolve5(1);
11399
+ resolve6(1);
11276
11400
  });
11277
11401
  });
11278
11402
  process.stdout.write(`[runner-serve] job ${job.jobId}: cloning ${job.repo}@${branch}
@@ -11359,11 +11483,11 @@ var runnerServe = async (ctx) => {
11359
11483
  const port = Number(process.env.PORT ?? DEFAULT_PORT2);
11360
11484
  const server = buildServer2({ apiKey });
11361
11485
  const host = process.env.RUNNER_HOST ?? "::";
11362
- await new Promise((resolve5) => {
11486
+ await new Promise((resolve6) => {
11363
11487
  server.listen(port, host, () => {
11364
11488
  process.stdout.write(`[runner-serve] listening on ${host}:${port} (idle, awaiting job)
11365
11489
  `);
11366
- resolve5();
11490
+ resolve6();
11367
11491
  });
11368
11492
  });
11369
11493
  const shutdown = (signal) => {
@@ -11379,8 +11503,8 @@ var runnerServe = async (ctx) => {
11379
11503
 
11380
11504
  // src/scripts/runTickScript.ts
11381
11505
  import { spawnSync as spawnSync2 } from "child_process";
11382
- import * as fs37 from "fs";
11383
- import * as path34 from "path";
11506
+ import * as fs38 from "fs";
11507
+ import * as path35 from "path";
11384
11508
  var runTickScript = async (ctx, _profile, args) => {
11385
11509
  ctx.skipAgent = true;
11386
11510
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -11392,13 +11516,13 @@ var runTickScript = async (ctx, _profile, args) => {
11392
11516
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
11393
11517
  return;
11394
11518
  }
11395
- const jobPath = path34.join(ctx.cwd, jobsDir, `${slug}.md`);
11396
- if (!fs37.existsSync(jobPath)) {
11519
+ const jobPath = path35.join(ctx.cwd, jobsDir, `${slug}.md`);
11520
+ if (!fs38.existsSync(jobPath)) {
11397
11521
  ctx.output.exitCode = 99;
11398
11522
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
11399
11523
  return;
11400
11524
  }
11401
- const raw = fs37.readFileSync(jobPath, "utf-8");
11525
+ const raw = fs38.readFileSync(jobPath, "utf-8");
11402
11526
  const { frontmatter } = splitFrontmatter2(raw);
11403
11527
  const tickScript = frontmatter.tickScript;
11404
11528
  if (!tickScript) {
@@ -11406,8 +11530,8 @@ var runTickScript = async (ctx, _profile, args) => {
11406
11530
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
11407
11531
  return;
11408
11532
  }
11409
- const scriptPath = path34.isAbsolute(tickScript) ? tickScript : path34.join(ctx.cwd, tickScript);
11410
- if (!fs37.existsSync(scriptPath)) {
11533
+ const scriptPath = path35.isAbsolute(tickScript) ? tickScript : path35.join(ctx.cwd, tickScript);
11534
+ if (!fs38.existsSync(scriptPath)) {
11411
11535
  ctx.output.exitCode = 99;
11412
11536
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
11413
11537
  return;
@@ -11608,14 +11732,14 @@ var serveFlow = async (ctx) => {
11608
11732
  `);
11609
11733
  const args = ["--dangerously-skip-permissions", "--model", model.model];
11610
11734
  const child = spawn6("claude", args, { stdio: "inherit", env: editorEnv, cwd: ctx.cwd });
11611
- const exitCode = await new Promise((resolve5) => {
11612
- child.on("exit", (code) => resolve5(code ?? 0));
11735
+ const exitCode = await new Promise((resolve6) => {
11736
+ child.on("exit", (code) => resolve6(code ?? 0));
11613
11737
  child.on("error", (err) => {
11614
11738
  process.stderr.write(`[kody serve] failed to launch Claude Code: ${err.message}
11615
11739
  `);
11616
11740
  process.stderr.write(` Install: https://docs.anthropic.com/claude/docs/claude-code
11617
11741
  `);
11618
- resolve5(1);
11742
+ resolve6(1);
11619
11743
  });
11620
11744
  });
11621
11745
  killProxy();
@@ -11951,7 +12075,7 @@ function stripAnsi2(s) {
11951
12075
  return s.replace(ANSI_RE2, "");
11952
12076
  }
11953
12077
  function runCommand2(command, cwd) {
11954
- return new Promise((resolve5) => {
12078
+ return new Promise((resolve6) => {
11955
12079
  const child = spawn7(command, {
11956
12080
  cwd,
11957
12081
  shell: true,
@@ -11978,11 +12102,11 @@ function runCommand2(command, cwd) {
11978
12102
  }, TEST_TIMEOUT_MS);
11979
12103
  child.on("exit", (code) => {
11980
12104
  clearTimeout(timer);
11981
- resolve5({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
12105
+ resolve6({ exitCode: code ?? -1, output: Buffer.concat(buffers).toString("utf-8") });
11982
12106
  });
11983
12107
  child.on("error", (err) => {
11984
12108
  clearTimeout(timer);
11985
- resolve5({ exitCode: -1, output: err.message });
12109
+ resolve6({ exitCode: -1, output: err.message });
11986
12110
  });
11987
12111
  });
11988
12112
  }
@@ -12404,20 +12528,20 @@ function lineStream(stream) {
12404
12528
  tryDeliver();
12405
12529
  });
12406
12530
  return {
12407
- next: (timeoutMs) => new Promise((resolve5) => {
12531
+ next: (timeoutMs) => new Promise((resolve6) => {
12408
12532
  if (queue.length > 0) {
12409
- resolve5(queue.shift());
12533
+ resolve6(queue.shift());
12410
12534
  return;
12411
12535
  }
12412
12536
  if (ended) {
12413
- resolve5(null);
12537
+ resolve6(null);
12414
12538
  return;
12415
12539
  }
12416
- waiter = resolve5;
12540
+ waiter = resolve6;
12417
12541
  const t = setTimeout(() => {
12418
- if (waiter === resolve5) {
12542
+ if (waiter === resolve6) {
12419
12543
  waiter = null;
12420
- resolve5(null);
12544
+ resolve6(null);
12421
12545
  }
12422
12546
  }, Math.max(0, timeoutMs));
12423
12547
  t.unref?.();
@@ -12515,7 +12639,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
12515
12639
  };
12516
12640
 
12517
12641
  // src/scripts/writeRunSummary.ts
12518
- import * as fs38 from "fs";
12642
+ import * as fs39 from "fs";
12519
12643
  var writeRunSummary = async (ctx, profile) => {
12520
12644
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
12521
12645
  if (!summaryPath) return;
@@ -12537,7 +12661,7 @@ var writeRunSummary = async (ctx, profile) => {
12537
12661
  if (reason) lines.push(`- **Reason:** ${reason}`);
12538
12662
  lines.push("");
12539
12663
  try {
12540
- fs38.appendFileSync(summaryPath, `${lines.join("\n")}
12664
+ fs39.appendFileSync(summaryPath, `${lines.join("\n")}
12541
12665
  `);
12542
12666
  } catch {
12543
12667
  }
@@ -12655,31 +12779,49 @@ function firstRequiredFailure(results, tools) {
12655
12779
  }
12656
12780
  return null;
12657
12781
  }
12658
- function verifyOne(tool3, cwd) {
12659
- const result = { name: tool3.name, present: false, verified: false };
12660
- let present = runShell(tool3.install.checkCommand, cwd);
12661
- if (!present && tool3.install.installCommand) {
12662
- runShell(tool3.install.installCommand, cwd, 12e4);
12663
- present = runShell(tool3.install.checkCommand, cwd);
12782
+ function verifyOne(tool4, cwd) {
12783
+ const result = { name: tool4.name, present: false, verified: false };
12784
+ const checkRes = runShell(tool4.install.checkCommand, cwd);
12785
+ let present = checkRes.ok;
12786
+ if (!present && tool4.install.installCommand) {
12787
+ runShell(tool4.install.installCommand, cwd, 12e4);
12788
+ present = runShell(tool4.install.checkCommand, cwd).ok;
12664
12789
  }
12665
12790
  result.present = present;
12666
12791
  if (!present) {
12667
- result.error = `tool "${tool3.name}" not on PATH (check: ${tool3.install.checkCommand})`;
12792
+ result.error = `tool "${tool4.name}" not on PATH (check: ${tool4.install.checkCommand})`;
12668
12793
  return result;
12669
12794
  }
12670
- const verified = runShell(tool3.verify, cwd);
12671
- result.verified = verified;
12672
- if (!verified) result.error = `tool "${tool3.name}" failed verify: ${tool3.verify}`;
12795
+ const verifyRes = runShell(tool4.verify, cwd);
12796
+ result.verified = verifyRes.ok;
12797
+ if (!verifyRes.ok) {
12798
+ const tail = formatStderrTail(verifyRes.stderr, verifyRes.stdout);
12799
+ result.error = `tool "${tool4.name}" failed verify: ${tool4.verify}${tail ? ` \u2014 ${tail}` : ""}`;
12800
+ }
12673
12801
  return result;
12674
12802
  }
12675
12803
  function runShell(cmd, cwd, timeoutMs = 3e4) {
12676
12804
  try {
12677
- execFileSync27("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
12678
- return true;
12679
- } catch {
12680
- return false;
12805
+ const stdout = execFileSync27("sh", ["-c", cmd], {
12806
+ cwd,
12807
+ stdio: ["ignore", "pipe", "pipe"],
12808
+ timeout: timeoutMs,
12809
+ encoding: "utf-8"
12810
+ });
12811
+ return { ok: true, stdout: stdout ?? "", stderr: "" };
12812
+ } catch (err) {
12813
+ const e = err;
12814
+ const stdout = e.stdout ? e.stdout.toString() : "";
12815
+ const stderr = e.stderr ? e.stderr.toString() : "";
12816
+ return { ok: false, stdout, stderr };
12681
12817
  }
12682
12818
  }
12819
+ function formatStderrTail(stderr, stdout) {
12820
+ const source = stderr.trim() || stdout.trim();
12821
+ if (!source) return "";
12822
+ const flat = source.replace(/\s+/g, " ").trim();
12823
+ return flat.length > 400 ? `\u2026${flat.slice(-400)}` : flat;
12824
+ }
12683
12825
 
12684
12826
  // src/executor.ts
12685
12827
  var CONTAINER_MAX_ITERATIONS = 50;
@@ -12725,11 +12867,12 @@ async function runExecutable(profileName, input) {
12725
12867
  if (input.config) {
12726
12868
  config = input.config;
12727
12869
  } else if (input.skipConfig) {
12870
+ const envModel = process.env.MODEL?.trim();
12728
12871
  config = {
12729
12872
  quality: { typecheck: "", lint: "", testUnit: "", format: "" },
12730
12873
  git: { defaultBranch: "main" },
12731
12874
  github: { owner: "", repo: "" },
12732
- agent: { model: "claude/claude-haiku-4-5-20251001" }
12875
+ agent: { model: envModel || "claude/claude-haiku-4-5-20251001" }
12733
12876
  };
12734
12877
  } else {
12735
12878
  try {
@@ -12782,9 +12925,9 @@ async function runExecutable(profileName, input) {
12782
12925
  })
12783
12926
  };
12784
12927
  })() : null;
12785
- const ndjsonDir = path35.join(input.cwd, ".kody");
12928
+ const ndjsonDir = path36.join(input.cwd, ".kody");
12786
12929
  const invokeAgent = async (prompt) => {
12787
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path35.isAbsolute(p) ? p : path35.resolve(profile.dir, p)).filter((p) => p.length > 0);
12930
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path36.isAbsolute(p) ? p : path36.resolve(profile.dir, p)).filter((p) => p.length > 0);
12788
12931
  const syntheticPath = ctx.data.syntheticPluginPath;
12789
12932
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
12790
12933
  const agents = loadSubagents(profile);
@@ -12996,7 +13139,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
12996
13139
  function getProfileInputsForChild(profileName, _cwd) {
12997
13140
  try {
12998
13141
  const profilePath = resolveProfilePath(profileName);
12999
- if (!fs39.existsSync(profilePath)) return null;
13142
+ if (!fs40.existsSync(profilePath)) return null;
13000
13143
  return loadProfile(profilePath).inputs;
13001
13144
  } catch {
13002
13145
  return null;
@@ -13005,17 +13148,17 @@ function getProfileInputsForChild(profileName, _cwd) {
13005
13148
  function resolveProfilePath(profileName) {
13006
13149
  const found = resolveExecutable(profileName);
13007
13150
  if (found) return found;
13008
- const here = path35.dirname(new URL(import.meta.url).pathname);
13151
+ const here = path36.dirname(new URL(import.meta.url).pathname);
13009
13152
  const candidates = [
13010
- path35.join(here, "executables", profileName, "profile.json"),
13153
+ path36.join(here, "executables", profileName, "profile.json"),
13011
13154
  // same-dir sibling (dev)
13012
- path35.join(here, "..", "executables", profileName, "profile.json"),
13155
+ path36.join(here, "..", "executables", profileName, "profile.json"),
13013
13156
  // up one (prod: dist/bin → dist/executables)
13014
- path35.join(here, "..", "src", "executables", profileName, "profile.json")
13157
+ path36.join(here, "..", "src", "executables", profileName, "profile.json")
13015
13158
  // fallback
13016
13159
  ];
13017
13160
  for (const c of candidates) {
13018
- if (fs39.existsSync(c)) return c;
13161
+ if (fs40.existsSync(c)) return c;
13019
13162
  }
13020
13163
  return candidates[0];
13021
13164
  }
@@ -13115,8 +13258,8 @@ function resolveShellTimeoutMs(entry) {
13115
13258
  var SIGKILL_GRACE_MS = 5e3;
13116
13259
  async function runShellEntry(entry, ctx, profile) {
13117
13260
  const shellName = entry.shell;
13118
- const shellPath = path35.join(profile.dir, shellName);
13119
- if (!fs39.existsSync(shellPath)) {
13261
+ const shellPath = path36.join(profile.dir, shellName);
13262
+ if (!fs40.existsSync(shellPath)) {
13120
13263
  ctx.skipAgent = true;
13121
13264
  ctx.output.exitCode = 99;
13122
13265
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -13154,14 +13297,14 @@ async function runShellEntry(entry, ctx, profile) {
13154
13297
  let killTimer;
13155
13298
  let escalateTimer;
13156
13299
  const result = await new Promise(
13157
- (resolve5) => {
13300
+ (resolve6) => {
13158
13301
  let settled = false;
13159
13302
  const settle = (code, signal, spawnErr) => {
13160
13303
  if (settled) return;
13161
13304
  settled = true;
13162
13305
  if (killTimer) clearTimeout(killTimer);
13163
13306
  if (escalateTimer) clearTimeout(escalateTimer);
13164
- resolve5({ code, signal, spawnErr });
13307
+ resolve6({ code, signal, spawnErr });
13165
13308
  };
13166
13309
  child.on("error", (err) => settle(null, null, err));
13167
13310
  child.on("close", (code, signal) => settle(code, signal));
@@ -13590,14 +13733,31 @@ function unpackAllSecrets(env = process.env) {
13590
13733
  return count;
13591
13734
  }
13592
13735
  function resolveAuthToken(env = process.env) {
13593
- const token = env.KODY_TOKEN || env.GH_TOKEN || env.GITHUB_TOKEN || env.GH_PAT;
13736
+ const sources = [
13737
+ ["KODY_TOKEN", env.KODY_TOKEN],
13738
+ ["GH_TOKEN", env.GH_TOKEN],
13739
+ ["GITHUB_TOKEN", env.GITHUB_TOKEN],
13740
+ ["GH_PAT", env.GH_PAT]
13741
+ ];
13742
+ const picked = sources.find(([, v]) => !!v);
13743
+ const token = picked?.[1];
13594
13744
  if (token && !env.GH_TOKEN) env.GH_TOKEN = token;
13745
+ if (token) {
13746
+ const trimmed = token.trim();
13747
+ const lenDiff = token.length - trimmed.length;
13748
+ process.stdout.write(
13749
+ `\u2192 kody: GH_TOKEN sourced from env.${picked[0]} (length=${token.length}, prefix=${token.slice(0, 4)}, trailingWhitespace=${lenDiff > 0 ? "YES " + lenDiff + " chars" : "no"})
13750
+ `
13751
+ );
13752
+ } else {
13753
+ process.stdout.write("\u2192 kody: WARNING no auth token found (KODY_TOKEN/GH_TOKEN/GITHUB_TOKEN/GH_PAT all empty)\n");
13754
+ }
13595
13755
  return token;
13596
13756
  }
13597
13757
  function detectPackageManager2(cwd) {
13598
- if (fs40.existsSync(path36.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
13599
- if (fs40.existsSync(path36.join(cwd, "yarn.lock"))) return "yarn";
13600
- if (fs40.existsSync(path36.join(cwd, "bun.lockb"))) return "bun";
13758
+ if (fs41.existsSync(path37.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
13759
+ if (fs41.existsSync(path37.join(cwd, "yarn.lock"))) return "yarn";
13760
+ if (fs41.existsSync(path37.join(cwd, "bun.lockb"))) return "bun";
13601
13761
  return "npm";
13602
13762
  }
13603
13763
  function shellOut(cmd, args, cwd, stream = true) {
@@ -13684,11 +13844,11 @@ function configureGitIdentity(cwd) {
13684
13844
  }
13685
13845
  function postFailureTail(issueNumber, cwd, reason) {
13686
13846
  if (!issueNumber) return;
13687
- const logPath = path36.join(cwd, ".kody", "last-run.jsonl");
13847
+ const logPath = path37.join(cwd, ".kody", "last-run.jsonl");
13688
13848
  let tail = "";
13689
13849
  try {
13690
- if (fs40.existsSync(logPath)) {
13691
- const content = fs40.readFileSync(logPath, "utf-8");
13850
+ if (fs41.existsSync(logPath)) {
13851
+ const content = fs41.readFileSync(logPath, "utf-8");
13692
13852
  tail = content.slice(-3e3);
13693
13853
  }
13694
13854
  } catch {
@@ -13713,7 +13873,7 @@ async function runCi(argv) {
13713
13873
  return 0;
13714
13874
  }
13715
13875
  const args = parseCiArgs(argv);
13716
- const cwd = args.cwd ? path36.resolve(args.cwd) : process.cwd();
13876
+ const cwd = args.cwd ? path37.resolve(args.cwd) : process.cwd();
13717
13877
  let earlyConfig;
13718
13878
  try {
13719
13879
  earlyConfig = loadConfig(cwd);
@@ -13723,9 +13883,9 @@ async function runCi(argv) {
13723
13883
  const eventName = process.env.GITHUB_EVENT_NAME;
13724
13884
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
13725
13885
  let manualWorkflowDispatch = false;
13726
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs40.existsSync(dispatchEventPath)) {
13886
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs41.existsSync(dispatchEventPath)) {
13727
13887
  try {
13728
- const evt = JSON.parse(fs40.readFileSync(dispatchEventPath, "utf-8"));
13888
+ const evt = JSON.parse(fs41.readFileSync(dispatchEventPath, "utf-8"));
13729
13889
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
13730
13890
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
13731
13891
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -13984,12 +14144,12 @@ function parseChatArgs(argv, env = process.env) {
13984
14144
  return result;
13985
14145
  }
13986
14146
  function commitChatFiles(cwd, sessionId, verbose) {
13987
- const sessionFile = path37.relative(cwd, sessionFilePath(cwd, sessionId));
13988
- const eventsFile = path37.relative(cwd, eventsFilePath(cwd, sessionId));
14147
+ const sessionFile = path38.relative(cwd, sessionFilePath(cwd, sessionId));
14148
+ const eventsFile = path38.relative(cwd, eventsFilePath(cwd, sessionId));
13989
14149
  const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
13990
- const tasksDir = path37.join(".kody", "tasks", safeSession);
14150
+ const tasksDir = path38.join(".kody", "tasks", safeSession);
13991
14151
  const candidatePaths = [sessionFile, eventsFile, tasksDir];
13992
- const paths = candidatePaths.filter((p) => fs41.existsSync(path37.join(cwd, p)));
14152
+ const paths = candidatePaths.filter((p) => fs42.existsSync(path38.join(cwd, p)));
13993
14153
  if (paths.length === 0) return;
13994
14154
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
13995
14155
  try {
@@ -14033,7 +14193,7 @@ async function runChat(argv) {
14033
14193
  ${CHAT_HELP}`);
14034
14194
  return 64;
14035
14195
  }
14036
- const cwd = args.cwd ? path37.resolve(args.cwd) : process.cwd();
14196
+ const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
14037
14197
  const sessionId = args.sessionId;
14038
14198
  const unpackedSecrets = unpackAllSecrets();
14039
14199
  if (unpackedSecrets > 0) {
@@ -14085,7 +14245,7 @@ ${CHAT_HELP}`);
14085
14245
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
14086
14246
  const meta = readMeta(sessionFile);
14087
14247
  process.stdout.write(
14088
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs41.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
14248
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs42.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
14089
14249
  `
14090
14250
  );
14091
14251
  try {
@@ -14441,7 +14601,7 @@ ${HELP_TEXT}`);
14441
14601
  }
14442
14602
  }
14443
14603
  const cwd = args.cwd ?? process.cwd();
14444
- const configlessCommands = /* @__PURE__ */ new Set(["init", "goal-scheduler"]);
14604
+ const configlessCommands = /* @__PURE__ */ new Set(["init", "goal-scheduler", "brain-serve"]);
14445
14605
  const skipConfig = configlessCommands.has(args.executableName ?? "");
14446
14606
  try {
14447
14607
  const result = await runExecutable(args.executableName, {