@kody-ade/kody-engine 0.3.71 → 0.3.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.3.71",
6
+ version: "0.3.74",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -51,9 +51,9 @@ var package_default = {
51
51
  };
52
52
 
53
53
  // src/chat-cli.ts
54
- import { execFileSync as execFileSync26 } from "child_process";
55
- import * as fs27 from "fs";
56
- import * as path24 from "path";
54
+ import { execFileSync as execFileSync28 } from "child_process";
55
+ import * as fs28 from "fs";
56
+ import * as path25 from "path";
57
57
 
58
58
  // src/chat/events.ts
59
59
  import * as fs from "fs";
@@ -473,6 +473,20 @@ import * as path4 from "path";
473
473
  function sessionFilePath(cwd, sessionId) {
474
474
  return path4.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
475
475
  }
476
+ function readMeta(file) {
477
+ if (!fs4.existsSync(file)) return null;
478
+ const raw = fs4.readFileSync(file, "utf-8");
479
+ const firstLine2 = raw.split("\n", 1)[0]?.trim();
480
+ if (!firstLine2) return null;
481
+ try {
482
+ const parsed = JSON.parse(firstLine2);
483
+ if (parsed.type !== "meta") return null;
484
+ if (parsed.mode !== "one-shot" && parsed.mode !== "interactive") return null;
485
+ return parsed;
486
+ } catch {
487
+ return null;
488
+ }
489
+ }
476
490
  function readSession(file) {
477
491
  if (!fs4.existsSync(file)) return [];
478
492
  const raw = fs4.readFileSync(file, "utf-8").trim();
@@ -641,13 +655,169 @@ async function emit(sink, type, sessionId, suffix, payload) {
641
655
  });
642
656
  }
643
657
 
658
+ // src/chat/modes/interactive.ts
659
+ import { execFileSync as execFileSync2 } from "child_process";
660
+ import * as fs5 from "fs";
661
+ import * as path5 from "path";
662
+
663
+ // src/chat/inbox.ts
664
+ import { execFileSync } from "child_process";
665
+ var DEFAULT_POLL_MS = 3e4;
666
+ async function waitForNextUserMessage(opts) {
667
+ const pollMs = opts.pollIntervalMs ?? DEFAULT_POLL_MS;
668
+ const logger = opts.logger ?? {
669
+ warn: (m) => process.stderr.write(`[kody:chat:inbox] ${m}
670
+ `)
671
+ };
672
+ const idleStart = Date.now();
673
+ while (true) {
674
+ const now = Date.now();
675
+ if (now >= opts.deadlineMs) return { kind: "deadline" };
676
+ if (now - idleStart >= opts.idleTimeoutMs) return { kind: "idle-timeout" };
677
+ if (!opts.skipPull) {
678
+ try {
679
+ execFileSync("git", ["pull", "--ff-only", "--quiet", "origin", "HEAD"], {
680
+ cwd: opts.cwd,
681
+ stdio: "pipe"
682
+ });
683
+ } catch (err) {
684
+ const msg = err instanceof Error ? err.message : String(err);
685
+ logger.warn(`git pull failed (will retry): ${msg}`);
686
+ }
687
+ }
688
+ const turns = readSession(opts.sessionFile);
689
+ for (let i = opts.watermark; i < turns.length; i++) {
690
+ const t = turns[i];
691
+ if (t.role === "user") {
692
+ return { kind: "message", turn: t, turnIndex: i };
693
+ }
694
+ }
695
+ const remainingDeadline = opts.deadlineMs - Date.now();
696
+ const remainingIdle = opts.idleTimeoutMs - (Date.now() - idleStart);
697
+ const sleepMs2 = Math.max(0, Math.min(pollMs, remainingDeadline, remainingIdle));
698
+ if (sleepMs2 === 0) continue;
699
+ await sleep(sleepMs2);
700
+ }
701
+ }
702
+ function sleep(ms) {
703
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
704
+ }
705
+
706
+ // src/chat/modes/interactive.ts
707
+ var DEFAULT_IDLE_EXIT_MS = 5 * 6e4;
708
+ var DEFAULT_HARD_CAP_MS = 30 * 6e4;
709
+ var DEFAULT_POLL_MS2 = 3e4;
710
+ async function runInteractiveMode(opts) {
711
+ const sessionFile = sessionFilePath(opts.cwd, opts.sessionId);
712
+ const idleExitMs = opts.meta.idleExitMs ?? DEFAULT_IDLE_EXIT_MS;
713
+ const hardCapMs = opts.meta.hardCapMs ?? DEFAULT_HARD_CAP_MS;
714
+ const startedAt = Date.now();
715
+ const deadlineMs = startedAt + hardCapMs;
716
+ await emit2(opts.sink, "chat.ready", opts.sessionId, "ready", {
717
+ sessionId: opts.sessionId,
718
+ startedAt: new Date(startedAt).toISOString(),
719
+ idleExitMs,
720
+ hardCapMs
721
+ });
722
+ let watermark = 0;
723
+ let turnsCompleted = 0;
724
+ while (true) {
725
+ const turns = readSession(sessionFile);
726
+ const pendingIdx = findNextUserTurn(turns, watermark);
727
+ if (pendingIdx === -1) {
728
+ const result = await waitForNextUserMessage({
729
+ sessionFile,
730
+ cwd: opts.cwd,
731
+ watermark,
732
+ idleTimeoutMs: idleExitMs,
733
+ deadlineMs,
734
+ pollIntervalMs: opts.pollIntervalMs ?? DEFAULT_POLL_MS2,
735
+ skipPull: opts.skipGit
736
+ });
737
+ if (result.kind === "idle-timeout") {
738
+ await emitExit(opts, "idle-timeout", turnsCompleted);
739
+ return { exitCode: 0, reason: "idle-timeout", turnsCompleted };
740
+ }
741
+ if (result.kind === "deadline") {
742
+ await emitExit(opts, "deadline", turnsCompleted);
743
+ return { exitCode: 0, reason: "deadline", turnsCompleted };
744
+ }
745
+ }
746
+ let turnResult;
747
+ try {
748
+ turnResult = await runChatTurn({
749
+ sessionId: opts.sessionId,
750
+ sessionFile,
751
+ cwd: opts.cwd,
752
+ model: opts.model,
753
+ litellmUrl: opts.litellmUrl,
754
+ sink: opts.sink,
755
+ verbose: opts.verbose,
756
+ quiet: opts.quiet,
757
+ invokeAgent: opts.invokeAgent
758
+ });
759
+ } catch (err) {
760
+ const msg = err instanceof Error ? err.message : String(err);
761
+ await emit2(opts.sink, "chat.error", opts.sessionId, `loop-${turnsCompleted}`, { error: msg });
762
+ await emitExit(opts, "fatal", turnsCompleted);
763
+ return { exitCode: 99, reason: "fatal", turnsCompleted };
764
+ }
765
+ if (turnResult.exitCode === 64) {
766
+ } else if (turnResult.exitCode !== 0) {
767
+ } else {
768
+ turnsCompleted += 1;
769
+ if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false);
770
+ }
771
+ watermark = readSession(sessionFile).length;
772
+ }
773
+ }
774
+ function findNextUserTurn(turns, fromIdx) {
775
+ for (let i = fromIdx; i < turns.length; i++) {
776
+ if (turns[i].role === "user") return i;
777
+ }
778
+ if (turns.length > 0 && turns[turns.length - 1].role === "user") return turns.length - 1;
779
+ return -1;
780
+ }
781
+ function commitTurn(cwd, sessionId, verbose) {
782
+ const sessionRel = path5.relative(cwd, sessionFilePath(cwd, sessionId));
783
+ const eventsRel = path5.relative(cwd, eventsFilePath(cwd, sessionId));
784
+ const paths = [sessionRel, eventsRel].filter((p) => fs5.existsSync(path5.join(cwd, p)));
785
+ if (paths.length === 0) return;
786
+ const stdio = verbose ? "inherit" : "pipe";
787
+ try {
788
+ execFileSync2("git", ["add", ...paths], { cwd, stdio });
789
+ execFileSync2("git", ["commit", "--quiet", "-m", `chat: interactive turn for ${sessionId}`], { cwd, stdio });
790
+ execFileSync2("git", ["push", "--quiet", "origin", "HEAD"], { cwd, stdio });
791
+ } catch (err) {
792
+ const msg = err instanceof Error ? err.message : String(err);
793
+ process.stderr.write(`[kody:chat:interactive] commit/push skipped: ${msg}
794
+ `);
795
+ }
796
+ }
797
+ async function emitExit(opts, reason, turnsCompleted) {
798
+ await emit2(opts.sink, "chat.exit", opts.sessionId, "exit", {
799
+ sessionId: opts.sessionId,
800
+ reason,
801
+ turnsCompleted,
802
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
803
+ });
804
+ }
805
+ async function emit2(sink, type, sessionId, suffix, payload) {
806
+ await sink.emit({
807
+ event: type,
808
+ payload,
809
+ runId: makeRunId(sessionId, suffix),
810
+ emittedAt: (/* @__PURE__ */ new Date()).toISOString()
811
+ });
812
+ }
813
+
644
814
  // src/kody-cli.ts
645
- import { execFileSync as execFileSync25 } from "child_process";
646
- import * as fs26 from "fs";
647
- import * as path23 from "path";
815
+ import { execFileSync as execFileSync27 } from "child_process";
816
+ import * as fs27 from "fs";
817
+ import * as path24 from "path";
648
818
 
649
819
  // src/dispatch.ts
650
- import * as fs6 from "fs";
820
+ import * as fs7 from "fs";
651
821
 
652
822
  // src/cron-match.ts
653
823
  var FIELD_BOUNDS = [
@@ -712,25 +882,25 @@ function cronMatchesInWindow(spec, end, windowSec) {
712
882
  }
713
883
 
714
884
  // src/registry.ts
715
- import * as fs5 from "fs";
716
- import * as path5 from "path";
885
+ import * as fs6 from "fs";
886
+ import * as path6 from "path";
717
887
  function getExecutablesRoot() {
718
- const here = path5.dirname(new URL(import.meta.url).pathname);
888
+ const here = path6.dirname(new URL(import.meta.url).pathname);
719
889
  const candidates = [
720
- path5.join(here, "executables"),
890
+ path6.join(here, "executables"),
721
891
  // dev: src/
722
- path5.join(here, "..", "executables"),
892
+ path6.join(here, "..", "executables"),
723
893
  // built: dist/bin → dist/executables
724
- path5.join(here, "..", "src", "executables")
894
+ path6.join(here, "..", "src", "executables")
725
895
  // fallback
726
896
  ];
727
897
  for (const c of candidates) {
728
- if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) return c;
898
+ if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
729
899
  }
730
900
  return candidates[0];
731
901
  }
732
902
  function getProjectExecutablesRoot() {
733
- return path5.join(process.cwd(), ".kody", "executables");
903
+ return path6.join(process.cwd(), ".kody", "executables");
734
904
  }
735
905
  function getExecutableRoots() {
736
906
  return [getProjectExecutablesRoot(), getExecutablesRoot()];
@@ -740,13 +910,13 @@ function listExecutables(roots = getExecutableRoots()) {
740
910
  const seen = /* @__PURE__ */ new Set();
741
911
  const out = [];
742
912
  for (const root of rootList) {
743
- if (!fs5.existsSync(root)) continue;
744
- const entries = fs5.readdirSync(root, { withFileTypes: true });
913
+ if (!fs6.existsSync(root)) continue;
914
+ const entries = fs6.readdirSync(root, { withFileTypes: true });
745
915
  for (const ent of entries) {
746
916
  if (!ent.isDirectory()) continue;
747
917
  if (seen.has(ent.name)) continue;
748
- const profilePath = path5.join(root, ent.name, "profile.json");
749
- if (fs5.existsSync(profilePath) && fs5.statSync(profilePath).isFile()) {
918
+ const profilePath = path6.join(root, ent.name, "profile.json");
919
+ if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
750
920
  out.push({ name: ent.name, profilePath });
751
921
  seen.add(ent.name);
752
922
  }
@@ -758,8 +928,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
758
928
  if (!isSafeName(name)) return null;
759
929
  const rootList = typeof roots === "string" ? [roots] : roots;
760
930
  for (const root of rootList) {
761
- const profilePath = path5.join(root, name, "profile.json");
762
- if (fs5.existsSync(profilePath) && fs5.statSync(profilePath).isFile()) {
931
+ const profilePath = path6.join(root, name, "profile.json");
932
+ if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
763
933
  return profilePath;
764
934
  }
765
935
  }
@@ -775,7 +945,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
775
945
  const profilePath = resolveExecutable(name, roots);
776
946
  if (!profilePath) return null;
777
947
  try {
778
- const raw = JSON.parse(fs5.readFileSync(profilePath, "utf-8"));
948
+ const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
779
949
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
780
950
  return raw.inputs;
781
951
  } catch {
@@ -812,10 +982,10 @@ function autoDispatch(opts) {
812
982
  }
813
983
  const eventName = process.env.GITHUB_EVENT_NAME;
814
984
  const eventPath = process.env.GITHUB_EVENT_PATH;
815
- if (!eventName || !eventPath || !fs6.existsSync(eventPath)) return null;
985
+ if (!eventName || !eventPath || !fs7.existsSync(eventPath)) return null;
816
986
  let event = {};
817
987
  try {
818
- event = JSON.parse(fs6.readFileSync(eventPath, "utf-8"));
988
+ event = JSON.parse(fs7.readFileSync(eventPath, "utf-8"));
819
989
  } catch {
820
990
  return null;
821
991
  }
@@ -890,7 +1060,7 @@ function dispatchScheduledWatches(opts) {
890
1060
  for (const exe of listExecutables()) {
891
1061
  let raw;
892
1062
  try {
893
- raw = fs6.readFileSync(exe.profilePath, "utf-8");
1063
+ raw = fs7.readFileSync(exe.profilePath, "utf-8");
894
1064
  } catch {
895
1065
  continue;
896
1066
  }
@@ -1004,14 +1174,14 @@ function coerceBare(spec, value) {
1004
1174
 
1005
1175
  // src/executor.ts
1006
1176
  import { spawn as spawn3 } from "child_process";
1007
- import * as fs25 from "fs";
1008
- import * as path22 from "path";
1177
+ import * as fs26 from "fs";
1178
+ import * as path23 from "path";
1009
1179
 
1010
1180
  // src/litellm.ts
1011
- import { execFileSync, spawn } from "child_process";
1012
- import * as fs7 from "fs";
1181
+ import { execFileSync as execFileSync3, spawn } from "child_process";
1182
+ import * as fs8 from "fs";
1013
1183
  import * as os from "os";
1014
- import * as path6 from "path";
1184
+ import * as path7 from "path";
1015
1185
  async function checkLitellmHealth(url) {
1016
1186
  try {
1017
1187
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -1042,29 +1212,29 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
1042
1212
  }
1043
1213
  let cmd = "litellm";
1044
1214
  try {
1045
- execFileSync("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
1215
+ execFileSync3("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
1046
1216
  } catch {
1047
1217
  try {
1048
- execFileSync("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
1218
+ execFileSync3("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
1049
1219
  cmd = "python3";
1050
1220
  } catch {
1051
1221
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
1052
1222
  }
1053
1223
  }
1054
- const configPath = path6.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
1055
- fs7.writeFileSync(configPath, generateLitellmConfigYaml(model));
1224
+ const configPath = path7.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
1225
+ fs8.writeFileSync(configPath, generateLitellmConfigYaml(model));
1056
1226
  const portMatch = url.match(/:(\d+)/);
1057
1227
  const port = portMatch ? portMatch[1] : "4000";
1058
1228
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
1059
1229
  const dotenvVars = readDotenvApiKeys(projectDir);
1060
- const logPath = path6.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
1061
- const outFd = fs7.openSync(logPath, "w");
1230
+ const logPath = path7.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
1231
+ const outFd = fs8.openSync(logPath, "w");
1062
1232
  const child = spawn(cmd, args, {
1063
1233
  stdio: ["ignore", outFd, outFd],
1064
1234
  detached: true,
1065
1235
  env: stripBlockingEnv({ ...process.env, ...dotenvVars })
1066
1236
  });
1067
- fs7.closeSync(outFd);
1237
+ fs8.closeSync(outFd);
1068
1238
  for (let i = 0; i < 30; i++) {
1069
1239
  await new Promise((r) => setTimeout(r, 2e3));
1070
1240
  if (await checkLitellmHealth(url)) {
@@ -1081,7 +1251,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
1081
1251
  }
1082
1252
  let logTail = "";
1083
1253
  try {
1084
- logTail = fs7.readFileSync(logPath, "utf-8").slice(-2e3);
1254
+ logTail = fs8.readFileSync(logPath, "utf-8").slice(-2e3);
1085
1255
  } catch {
1086
1256
  }
1087
1257
  try {
@@ -1092,10 +1262,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
1092
1262
  ${logTail}`);
1093
1263
  }
1094
1264
  function readDotenvApiKeys(projectDir) {
1095
- const dotenvPath = path6.join(projectDir, ".env");
1096
- if (!fs7.existsSync(dotenvPath)) return {};
1265
+ const dotenvPath = path7.join(projectDir, ".env");
1266
+ if (!fs8.existsSync(dotenvPath)) return {};
1097
1267
  const result = {};
1098
- for (const rawLine of fs7.readFileSync(dotenvPath, "utf-8").split("\n")) {
1268
+ for (const rawLine of fs8.readFileSync(dotenvPath, "utf-8").split("\n")) {
1099
1269
  const line = rawLine.trim();
1100
1270
  if (!line || line.startsWith("#")) continue;
1101
1271
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -1118,8 +1288,8 @@ function stripBlockingEnv(env) {
1118
1288
  }
1119
1289
 
1120
1290
  // src/profile.ts
1121
- import * as fs8 from "fs";
1122
- import * as path7 from "path";
1291
+ import * as fs9 from "fs";
1292
+ import * as path8 from "path";
1123
1293
  var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
1124
1294
  var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
1125
1295
  var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
@@ -1135,12 +1305,12 @@ var ProfileError = class extends Error {
1135
1305
  profilePath;
1136
1306
  };
1137
1307
  function loadProfile(profilePath) {
1138
- if (!fs8.existsSync(profilePath)) {
1308
+ if (!fs9.existsSync(profilePath)) {
1139
1309
  throw new ProfileError(profilePath, "file not found");
1140
1310
  }
1141
1311
  let raw;
1142
1312
  try {
1143
- raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
1313
+ raw = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
1144
1314
  } catch (err) {
1145
1315
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
1146
1316
  }
@@ -1179,7 +1349,7 @@ function loadProfile(profilePath) {
1179
1349
  inputArtifacts: parseInputArtifacts(profilePath, r.input),
1180
1350
  outputArtifacts: parseOutputArtifacts(profilePath, r.output),
1181
1351
  children,
1182
- dir: path7.dirname(profilePath)
1352
+ dir: path8.dirname(profilePath)
1183
1353
  };
1184
1354
  return profile;
1185
1355
  }
@@ -1423,9 +1593,9 @@ function parseScriptList(p, key, raw) {
1423
1593
  }
1424
1594
 
1425
1595
  // src/commit.ts
1426
- import { execFileSync as execFileSync2 } from "child_process";
1427
- import * as fs9 from "fs";
1428
- import * as path8 from "path";
1596
+ import { execFileSync as execFileSync4 } from "child_process";
1597
+ import * as fs10 from "fs";
1598
+ import * as path9 from "path";
1429
1599
  var FORBIDDEN_PATH_PREFIXES = [
1430
1600
  ".kody/",
1431
1601
  ".kody-engine/",
@@ -1454,7 +1624,7 @@ var CONVENTIONAL_PREFIXES = [
1454
1624
  ];
1455
1625
  function git(args, cwd) {
1456
1626
  try {
1457
- return execFileSync2("git", args, {
1627
+ return execFileSync4("git", args, {
1458
1628
  encoding: "utf-8",
1459
1629
  timeout: 12e4,
1460
1630
  cwd,
@@ -1481,18 +1651,18 @@ function tryGit(args, cwd) {
1481
1651
  }
1482
1652
  function abortUnfinishedGitOps(cwd) {
1483
1653
  const aborted = [];
1484
- const gitDir = path8.join(cwd ?? process.cwd(), ".git");
1485
- if (!fs9.existsSync(gitDir)) return aborted;
1486
- if (fs9.existsSync(path8.join(gitDir, "MERGE_HEAD"))) {
1654
+ const gitDir = path9.join(cwd ?? process.cwd(), ".git");
1655
+ if (!fs10.existsSync(gitDir)) return aborted;
1656
+ if (fs10.existsSync(path9.join(gitDir, "MERGE_HEAD"))) {
1487
1657
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
1488
1658
  }
1489
- if (fs9.existsSync(path8.join(gitDir, "CHERRY_PICK_HEAD"))) {
1659
+ if (fs10.existsSync(path9.join(gitDir, "CHERRY_PICK_HEAD"))) {
1490
1660
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
1491
1661
  }
1492
- if (fs9.existsSync(path8.join(gitDir, "REVERT_HEAD"))) {
1662
+ if (fs10.existsSync(path9.join(gitDir, "REVERT_HEAD"))) {
1493
1663
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
1494
1664
  }
1495
- if (fs9.existsSync(path8.join(gitDir, "rebase-merge")) || fs9.existsSync(path8.join(gitDir, "rebase-apply"))) {
1665
+ if (fs10.existsSync(path9.join(gitDir, "rebase-merge")) || fs10.existsSync(path9.join(gitDir, "rebase-apply"))) {
1496
1666
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
1497
1667
  }
1498
1668
  try {
@@ -1513,7 +1683,7 @@ function isForbiddenPath(p) {
1513
1683
  return false;
1514
1684
  }
1515
1685
  function listChangedFiles(cwd) {
1516
- const raw = execFileSync2("git", ["status", "--porcelain=v1", "-z"], {
1686
+ const raw = execFileSync4("git", ["status", "--porcelain=v1", "-z"], {
1517
1687
  encoding: "utf-8",
1518
1688
  cwd,
1519
1689
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1525,7 +1695,7 @@ function listChangedFiles(cwd) {
1525
1695
  }
1526
1696
  function listFilesInCommit(ref = "HEAD", cwd) {
1527
1697
  try {
1528
- const raw = execFileSync2("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1698
+ const raw = execFileSync4("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1529
1699
  encoding: "utf-8",
1530
1700
  cwd,
1531
1701
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1548,7 +1718,7 @@ function normalizeCommitMessage(raw) {
1548
1718
  function commitAndPush(branch, agentMessage, cwd) {
1549
1719
  const allChanged = listChangedFiles(cwd);
1550
1720
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
1551
- const mergeHeadExists = fs9.existsSync(path8.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
1721
+ const mergeHeadExists = fs10.existsSync(path9.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
1552
1722
  if (allowedFiles.length === 0 && !mergeHeadExists) {
1553
1723
  return { committed: false, pushed: false, sha: "", message: "" };
1554
1724
  }
@@ -1614,10 +1784,10 @@ var abortUnfinishedGitOps2 = async (ctx) => {
1614
1784
  };
1615
1785
 
1616
1786
  // src/scripts/advanceFlow.ts
1617
- import { execFileSync as execFileSync4 } from "child_process";
1787
+ import { execFileSync as execFileSync6 } from "child_process";
1618
1788
 
1619
1789
  // src/state.ts
1620
- import { execFileSync as execFileSync3 } from "child_process";
1790
+ import { execFileSync as execFileSync5 } from "child_process";
1621
1791
  var STATE_BEGIN = "<!-- kody:state:v1:begin -->";
1622
1792
  var STATE_END = "<!-- kody:state:v1:end -->";
1623
1793
  var HISTORY_MAX_ENTRIES = 20;
@@ -1643,7 +1813,7 @@ function ghToken() {
1643
1813
  function gh(args, input, cwd) {
1644
1814
  const token = ghToken();
1645
1815
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
1646
- return execFileSync3("gh", args, {
1816
+ return execFileSync5("gh", args, {
1647
1817
  encoding: "utf-8",
1648
1818
  timeout: API_TIMEOUT_MS,
1649
1819
  cwd,
@@ -1846,7 +2016,7 @@ var advanceFlow = async (ctx, profile) => {
1846
2016
  }
1847
2017
  const body = `@kody ${flow.name}`;
1848
2018
  try {
1849
- execFileSync4("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
2019
+ execFileSync6("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
1850
2020
  timeout: API_TIMEOUT_MS2,
1851
2021
  cwd: ctx.cwd,
1852
2022
  stdio: ["ignore", "pipe", "pipe"]
@@ -1860,21 +2030,21 @@ var advanceFlow = async (ctx, profile) => {
1860
2030
  };
1861
2031
 
1862
2032
  // src/scripts/buildSyntheticPlugin.ts
1863
- import * as fs10 from "fs";
2033
+ import * as fs11 from "fs";
1864
2034
  import * as os2 from "os";
1865
- import * as path9 from "path";
2035
+ import * as path10 from "path";
1866
2036
  function getPluginsCatalogRoot() {
1867
- const here = path9.dirname(new URL(import.meta.url).pathname);
2037
+ const here = path10.dirname(new URL(import.meta.url).pathname);
1868
2038
  const candidates = [
1869
- path9.join(here, "..", "plugins"),
2039
+ path10.join(here, "..", "plugins"),
1870
2040
  // dev: src/scripts → src/plugins
1871
- path9.join(here, "..", "..", "plugins"),
2041
+ path10.join(here, "..", "..", "plugins"),
1872
2042
  // built: dist/scripts → dist/plugins
1873
- path9.join(here, "..", "..", "src", "plugins")
2043
+ path10.join(here, "..", "..", "src", "plugins")
1874
2044
  // fallback
1875
2045
  ];
1876
2046
  for (const c of candidates) {
1877
- if (fs10.existsSync(c) && fs10.statSync(c).isDirectory()) return c;
2047
+ if (fs11.existsSync(c) && fs11.statSync(c).isDirectory()) return c;
1878
2048
  }
1879
2049
  return candidates[0];
1880
2050
  }
@@ -1884,52 +2054,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
1884
2054
  if (!needsSynthetic) return;
1885
2055
  const catalog = getPluginsCatalogRoot();
1886
2056
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1887
- const root = path9.join(os2.tmpdir(), `kody-synth-${runId}`);
1888
- fs10.mkdirSync(path9.join(root, ".claude-plugin"), { recursive: true });
2057
+ const root = path10.join(os2.tmpdir(), `kody-synth-${runId}`);
2058
+ fs11.mkdirSync(path10.join(root, ".claude-plugin"), { recursive: true });
1889
2059
  const resolvePart = (bucket, entry) => {
1890
- const local = path9.join(profile.dir, bucket, entry);
1891
- if (fs10.existsSync(local)) return local;
1892
- const central = path9.join(catalog, bucket, entry);
1893
- if (fs10.existsSync(central)) return central;
2060
+ const local = path10.join(profile.dir, bucket, entry);
2061
+ if (fs11.existsSync(local)) return local;
2062
+ const central = path10.join(catalog, bucket, entry);
2063
+ if (fs11.existsSync(central)) return central;
1894
2064
  throw new Error(
1895
2065
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
1896
2066
  );
1897
2067
  };
1898
2068
  if (cc.skills.length > 0) {
1899
- const dst = path9.join(root, "skills");
1900
- fs10.mkdirSync(dst, { recursive: true });
2069
+ const dst = path10.join(root, "skills");
2070
+ fs11.mkdirSync(dst, { recursive: true });
1901
2071
  for (const name of cc.skills) {
1902
- copyDir(resolvePart("skills", name), path9.join(dst, name));
2072
+ copyDir(resolvePart("skills", name), path10.join(dst, name));
1903
2073
  }
1904
2074
  }
1905
2075
  if (cc.commands.length > 0) {
1906
- const dst = path9.join(root, "commands");
1907
- fs10.mkdirSync(dst, { recursive: true });
2076
+ const dst = path10.join(root, "commands");
2077
+ fs11.mkdirSync(dst, { recursive: true });
1908
2078
  for (const name of cc.commands) {
1909
- fs10.copyFileSync(resolvePart("commands", `${name}.md`), path9.join(dst, `${name}.md`));
2079
+ fs11.copyFileSync(resolvePart("commands", `${name}.md`), path10.join(dst, `${name}.md`));
1910
2080
  }
1911
2081
  }
1912
2082
  if (cc.subagents.length > 0) {
1913
- const dst = path9.join(root, "agents");
1914
- fs10.mkdirSync(dst, { recursive: true });
2083
+ const dst = path10.join(root, "agents");
2084
+ fs11.mkdirSync(dst, { recursive: true });
1915
2085
  for (const name of cc.subagents) {
1916
- fs10.copyFileSync(resolvePart("agents", `${name}.md`), path9.join(dst, `${name}.md`));
2086
+ fs11.copyFileSync(resolvePart("agents", `${name}.md`), path10.join(dst, `${name}.md`));
1917
2087
  }
1918
2088
  }
1919
2089
  if (cc.hooks.length > 0) {
1920
- const dst = path9.join(root, "hooks");
1921
- fs10.mkdirSync(dst, { recursive: true });
2090
+ const dst = path10.join(root, "hooks");
2091
+ fs11.mkdirSync(dst, { recursive: true });
1922
2092
  const merged = { hooks: {} };
1923
2093
  for (const name of cc.hooks) {
1924
2094
  const src = resolvePart("hooks", `${name}.json`);
1925
- const parsed = JSON.parse(fs10.readFileSync(src, "utf-8"));
2095
+ const parsed = JSON.parse(fs11.readFileSync(src, "utf-8"));
1926
2096
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
1927
2097
  if (!Array.isArray(entries)) continue;
1928
2098
  if (!merged.hooks[event]) merged.hooks[event] = [];
1929
2099
  merged.hooks[event].push(...entries);
1930
2100
  }
1931
2101
  }
1932
- fs10.writeFileSync(path9.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2102
+ fs11.writeFileSync(path10.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
1933
2103
  `);
1934
2104
  }
1935
2105
  const manifest = {
@@ -1940,22 +2110,22 @@ var buildSyntheticPlugin = async (ctx, profile) => {
1940
2110
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
1941
2111
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
1942
2112
  if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
1943
- fs10.writeFileSync(path9.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2113
+ fs11.writeFileSync(path10.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
1944
2114
  `);
1945
2115
  ctx.data.syntheticPluginPath = root;
1946
2116
  };
1947
2117
  function copyDir(src, dst) {
1948
- fs10.mkdirSync(dst, { recursive: true });
1949
- for (const ent of fs10.readdirSync(src, { withFileTypes: true })) {
1950
- const s = path9.join(src, ent.name);
1951
- const d = path9.join(dst, ent.name);
2118
+ fs11.mkdirSync(dst, { recursive: true });
2119
+ for (const ent of fs11.readdirSync(src, { withFileTypes: true })) {
2120
+ const s = path10.join(src, ent.name);
2121
+ const d = path10.join(dst, ent.name);
1952
2122
  if (ent.isDirectory()) copyDir(s, d);
1953
- else if (ent.isFile()) fs10.copyFileSync(s, d);
2123
+ else if (ent.isFile()) fs11.copyFileSync(s, d);
1954
2124
  }
1955
2125
  }
1956
2126
 
1957
2127
  // src/coverage.ts
1958
- import { execFileSync as execFileSync5 } from "child_process";
2128
+ import { execFileSync as execFileSync7 } from "child_process";
1959
2129
  function patternToRegex(pattern) {
1960
2130
  let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1961
2131
  s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
@@ -1973,7 +2143,7 @@ function renderSiblingPath(file, requireSibling) {
1973
2143
  }
1974
2144
  function safeGit(args, cwd) {
1975
2145
  try {
1976
- return execFileSync5("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
2146
+ return execFileSync7("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
1977
2147
  } catch {
1978
2148
  return "";
1979
2149
  }
@@ -2016,18 +2186,18 @@ function formatMissesForFeedback(misses) {
2016
2186
  }
2017
2187
 
2018
2188
  // src/prompt.ts
2019
- import * as fs11 from "fs";
2020
- import * as path10 from "path";
2189
+ import * as fs12 from "fs";
2190
+ import * as path11 from "path";
2021
2191
  var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
2022
2192
  var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
2023
2193
  function loadProjectConventions(projectDir) {
2024
2194
  const out = [];
2025
2195
  for (const rel of CONVENTION_FILES) {
2026
- const abs = path10.join(projectDir, rel);
2027
- if (!fs11.existsSync(abs)) continue;
2196
+ const abs = path11.join(projectDir, rel);
2197
+ if (!fs12.existsSync(abs)) continue;
2028
2198
  let content;
2029
2199
  try {
2030
- content = fs11.readFileSync(abs, "utf-8");
2200
+ content = fs12.readFileSync(abs, "utf-8");
2031
2201
  } catch {
2032
2202
  continue;
2033
2203
  }
@@ -2232,20 +2402,20 @@ var commitAndPush2 = async (ctx) => {
2232
2402
  };
2233
2403
 
2234
2404
  // src/scripts/composePrompt.ts
2235
- import * as fs12 from "fs";
2236
- import * as path11 from "path";
2405
+ import * as fs13 from "fs";
2406
+ import * as path12 from "path";
2237
2407
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
2238
2408
  var composePrompt = async (ctx, profile) => {
2239
2409
  const explicit = ctx.data.promptTemplate;
2240
2410
  const mode = ctx.args.mode;
2241
2411
  const candidates = [
2242
- explicit ? path11.join(profile.dir, explicit) : null,
2243
- mode ? path11.join(profile.dir, "prompts", `${mode}.md`) : null,
2244
- path11.join(profile.dir, "prompt.md")
2412
+ explicit ? path12.join(profile.dir, explicit) : null,
2413
+ mode ? path12.join(profile.dir, "prompts", `${mode}.md`) : null,
2414
+ path12.join(profile.dir, "prompt.md")
2245
2415
  ].filter(Boolean);
2246
2416
  let templatePath = "";
2247
2417
  for (const c of candidates) {
2248
- if (fs12.existsSync(c)) {
2418
+ if (fs13.existsSync(c)) {
2249
2419
  templatePath = c;
2250
2420
  break;
2251
2421
  }
@@ -2253,7 +2423,7 @@ var composePrompt = async (ctx, profile) => {
2253
2423
  if (!templatePath) {
2254
2424
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
2255
2425
  }
2256
- const template = fs12.readFileSync(templatePath, "utf-8");
2426
+ const template = fs13.readFileSync(templatePath, "utf-8");
2257
2427
  const tokens = {
2258
2428
  ...stringifyAll(ctx.args, "args."),
2259
2429
  ...stringifyAll(ctx.data, ""),
@@ -2330,16 +2500,16 @@ function formatToolsUsage(profile) {
2330
2500
  }
2331
2501
 
2332
2502
  // src/scripts/diagMcp.ts
2333
- import { execFileSync as execFileSync6 } from "child_process";
2334
- import * as fs13 from "fs";
2503
+ import { execFileSync as execFileSync8 } from "child_process";
2504
+ import * as fs14 from "fs";
2335
2505
  import * as os3 from "os";
2336
- import * as path12 from "path";
2506
+ import * as path13 from "path";
2337
2507
  var diagMcp = async (_ctx) => {
2338
2508
  const home = os3.homedir();
2339
- const cacheDir = path12.join(home, ".cache", "ms-playwright");
2509
+ const cacheDir = path13.join(home, ".cache", "ms-playwright");
2340
2510
  let entries = [];
2341
2511
  try {
2342
- entries = fs13.readdirSync(cacheDir);
2512
+ entries = fs14.readdirSync(cacheDir);
2343
2513
  } catch {
2344
2514
  }
2345
2515
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -2350,7 +2520,7 @@ var diagMcp = async (_ctx) => {
2350
2520
  process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
2351
2521
  `);
2352
2522
  try {
2353
- const v = execFileSync6("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
2523
+ const v = execFileSync8("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
2354
2524
  stdio: "pipe",
2355
2525
  timeout: 6e4,
2356
2526
  encoding: "utf8"
@@ -2365,17 +2535,17 @@ var diagMcp = async (_ctx) => {
2365
2535
  };
2366
2536
 
2367
2537
  // src/scripts/discoverQaContext.ts
2368
- import * as fs15 from "fs";
2369
- import * as path14 from "path";
2538
+ import * as fs16 from "fs";
2539
+ import * as path15 from "path";
2370
2540
 
2371
2541
  // src/scripts/frameworkDetectors.ts
2372
- import * as fs14 from "fs";
2373
- import * as path13 from "path";
2542
+ import * as fs15 from "fs";
2543
+ import * as path14 from "path";
2374
2544
  function detectFrameworks(cwd) {
2375
2545
  const out = [];
2376
2546
  let deps = {};
2377
2547
  try {
2378
- const pkg = JSON.parse(fs14.readFileSync(path13.join(cwd, "package.json"), "utf-8"));
2548
+ const pkg = JSON.parse(fs15.readFileSync(path14.join(cwd, "package.json"), "utf-8"));
2379
2549
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
2380
2550
  } catch {
2381
2551
  return out;
@@ -2412,7 +2582,7 @@ function detectFrameworks(cwd) {
2412
2582
  }
2413
2583
  function findFile(cwd, candidates) {
2414
2584
  for (const c of candidates) {
2415
- if (fs14.existsSync(path13.join(cwd, c))) return c;
2585
+ if (fs15.existsSync(path14.join(cwd, c))) return c;
2416
2586
  }
2417
2587
  return null;
2418
2588
  }
@@ -2425,18 +2595,18 @@ var COLLECTION_DIRS = [
2425
2595
  function discoverPayloadCollections(cwd) {
2426
2596
  const out = [];
2427
2597
  for (const dir of COLLECTION_DIRS) {
2428
- const full = path13.join(cwd, dir);
2429
- if (!fs14.existsSync(full)) continue;
2598
+ const full = path14.join(cwd, dir);
2599
+ if (!fs15.existsSync(full)) continue;
2430
2600
  let files;
2431
2601
  try {
2432
- files = fs14.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
2602
+ files = fs15.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
2433
2603
  } catch {
2434
2604
  continue;
2435
2605
  }
2436
2606
  for (const file of files) {
2437
2607
  try {
2438
- const filePath = path13.join(full, file);
2439
- const content = fs14.readFileSync(filePath, "utf-8").slice(0, 1e4);
2608
+ const filePath = path14.join(full, file);
2609
+ const content = fs15.readFileSync(filePath, "utf-8").slice(0, 1e4);
2440
2610
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
2441
2611
  if (!slugMatch) continue;
2442
2612
  const slug = slugMatch[1];
@@ -2450,7 +2620,7 @@ function discoverPayloadCollections(cwd) {
2450
2620
  out.push({
2451
2621
  name,
2452
2622
  slug,
2453
- filePath: path13.relative(cwd, filePath),
2623
+ filePath: path14.relative(cwd, filePath),
2454
2624
  fields: fields.slice(0, 20),
2455
2625
  hasAdmin
2456
2626
  });
@@ -2464,28 +2634,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
2464
2634
  function discoverAdminComponents(cwd, collections) {
2465
2635
  const out = [];
2466
2636
  for (const dir of ADMIN_COMPONENT_DIRS) {
2467
- const full = path13.join(cwd, dir);
2468
- if (!fs14.existsSync(full)) continue;
2637
+ const full = path14.join(cwd, dir);
2638
+ if (!fs15.existsSync(full)) continue;
2469
2639
  let entries;
2470
2640
  try {
2471
- entries = fs14.readdirSync(full, { withFileTypes: true });
2641
+ entries = fs15.readdirSync(full, { withFileTypes: true });
2472
2642
  } catch {
2473
2643
  continue;
2474
2644
  }
2475
2645
  for (const entry of entries) {
2476
- const entryPath = path13.join(full, entry.name);
2646
+ const entryPath = path14.join(full, entry.name);
2477
2647
  let name;
2478
2648
  let filePath;
2479
2649
  if (entry.isDirectory()) {
2480
2650
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
2481
- (f) => fs14.existsSync(path13.join(entryPath, f))
2651
+ (f) => fs15.existsSync(path14.join(entryPath, f))
2482
2652
  );
2483
2653
  if (!indexFile) continue;
2484
2654
  name = entry.name;
2485
- filePath = path13.relative(cwd, path13.join(entryPath, indexFile));
2655
+ filePath = path14.relative(cwd, path14.join(entryPath, indexFile));
2486
2656
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
2487
2657
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
2488
- filePath = path13.relative(cwd, entryPath);
2658
+ filePath = path14.relative(cwd, entryPath);
2489
2659
  } else {
2490
2660
  continue;
2491
2661
  }
@@ -2493,7 +2663,7 @@ function discoverAdminComponents(cwd, collections) {
2493
2663
  if (collections) {
2494
2664
  for (const col of collections) {
2495
2665
  try {
2496
- const colContent = fs14.readFileSync(path13.join(cwd, col.filePath), "utf-8");
2666
+ const colContent = fs15.readFileSync(path14.join(cwd, col.filePath), "utf-8");
2497
2667
  if (colContent.includes(name)) {
2498
2668
  usedInCollection = col.slug;
2499
2669
  break;
@@ -2512,8 +2682,8 @@ function scanApiRoutes(cwd) {
2512
2682
  const out = [];
2513
2683
  const appDirs = ["src/app", "app"];
2514
2684
  for (const appDir of appDirs) {
2515
- const apiDir = path13.join(cwd, appDir, "api");
2516
- if (!fs14.existsSync(apiDir)) continue;
2685
+ const apiDir = path14.join(cwd, appDir, "api");
2686
+ if (!fs15.existsSync(apiDir)) continue;
2517
2687
  walkApiRoutes(apiDir, "/api", cwd, out);
2518
2688
  break;
2519
2689
  }
@@ -2522,14 +2692,14 @@ function scanApiRoutes(cwd) {
2522
2692
  function walkApiRoutes(dir, prefix, cwd, out) {
2523
2693
  let entries;
2524
2694
  try {
2525
- entries = fs14.readdirSync(dir, { withFileTypes: true });
2695
+ entries = fs15.readdirSync(dir, { withFileTypes: true });
2526
2696
  } catch {
2527
2697
  return;
2528
2698
  }
2529
2699
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
2530
2700
  if (routeFile) {
2531
2701
  try {
2532
- const content = fs14.readFileSync(path13.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
2702
+ const content = fs15.readFileSync(path14.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
2533
2703
  const methods = HTTP_METHODS.filter(
2534
2704
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
2535
2705
  );
@@ -2537,7 +2707,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
2537
2707
  out.push({
2538
2708
  path: prefix,
2539
2709
  methods,
2540
- filePath: path13.relative(cwd, path13.join(dir, routeFile.name))
2710
+ filePath: path14.relative(cwd, path14.join(dir, routeFile.name))
2541
2711
  });
2542
2712
  }
2543
2713
  } catch {
@@ -2548,7 +2718,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
2548
2718
  if (entry.name === "node_modules" || entry.name === ".next") continue;
2549
2719
  let segment = entry.name;
2550
2720
  if (segment.startsWith("(") && segment.endsWith(")")) {
2551
- walkApiRoutes(path13.join(dir, entry.name), prefix, cwd, out);
2721
+ walkApiRoutes(path14.join(dir, entry.name), prefix, cwd, out);
2552
2722
  continue;
2553
2723
  }
2554
2724
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -2556,7 +2726,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
2556
2726
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
2557
2727
  segment = `:${segment.slice(1, -1)}`;
2558
2728
  }
2559
- walkApiRoutes(path13.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
2729
+ walkApiRoutes(path14.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
2560
2730
  }
2561
2731
  }
2562
2732
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -2576,10 +2746,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
2576
2746
  function scanEnvVars(cwd) {
2577
2747
  const candidates = [".env.example", ".env.local.example", ".env.template"];
2578
2748
  for (const envFile of candidates) {
2579
- const envPath = path13.join(cwd, envFile);
2580
- if (!fs14.existsSync(envPath)) continue;
2749
+ const envPath = path14.join(cwd, envFile);
2750
+ if (!fs15.existsSync(envPath)) continue;
2581
2751
  try {
2582
- const content = fs14.readFileSync(envPath, "utf-8");
2752
+ const content = fs15.readFileSync(envPath, "utf-8");
2583
2753
  const vars = [];
2584
2754
  for (const line of content.split("\n")) {
2585
2755
  const trimmed = line.trim();
@@ -2627,9 +2797,9 @@ function runQaDiscovery(cwd) {
2627
2797
  }
2628
2798
  function detectDevServer(cwd, out) {
2629
2799
  try {
2630
- const pkg = JSON.parse(fs15.readFileSync(path14.join(cwd, "package.json"), "utf-8"));
2800
+ const pkg = JSON.parse(fs16.readFileSync(path15.join(cwd, "package.json"), "utf-8"));
2631
2801
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
2632
- const pm = fs15.existsSync(path14.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs15.existsSync(path14.join(cwd, "yarn.lock")) ? "yarn" : fs15.existsSync(path14.join(cwd, "bun.lockb")) ? "bun" : "npm";
2802
+ const pm = fs16.existsSync(path15.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs16.existsSync(path15.join(cwd, "yarn.lock")) ? "yarn" : fs16.existsSync(path15.join(cwd, "bun.lockb")) ? "bun" : "npm";
2633
2803
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
2634
2804
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
2635
2805
  else if (allDeps.vite) out.devPort = 5173;
@@ -2639,8 +2809,8 @@ function detectDevServer(cwd, out) {
2639
2809
  function scanFrontendRoutes(cwd, out) {
2640
2810
  const appDirs = ["src/app", "app"];
2641
2811
  for (const appDir of appDirs) {
2642
- const full = path14.join(cwd, appDir);
2643
- if (!fs15.existsSync(full)) continue;
2812
+ const full = path15.join(cwd, appDir);
2813
+ if (!fs16.existsSync(full)) continue;
2644
2814
  walkFrontendRoutes(full, "", out);
2645
2815
  break;
2646
2816
  }
@@ -2648,7 +2818,7 @@ function scanFrontendRoutes(cwd, out) {
2648
2818
  function walkFrontendRoutes(dir, prefix, out) {
2649
2819
  let entries;
2650
2820
  try {
2651
- entries = fs15.readdirSync(dir, { withFileTypes: true });
2821
+ entries = fs16.readdirSync(dir, { withFileTypes: true });
2652
2822
  } catch {
2653
2823
  return;
2654
2824
  }
@@ -2665,7 +2835,7 @@ function walkFrontendRoutes(dir, prefix, out) {
2665
2835
  if (entry.name === "node_modules" || entry.name === ".next") continue;
2666
2836
  let segment = entry.name;
2667
2837
  if (segment.startsWith("(") && segment.endsWith(")")) {
2668
- walkFrontendRoutes(path14.join(dir, entry.name), prefix, out);
2838
+ walkFrontendRoutes(path15.join(dir, entry.name), prefix, out);
2669
2839
  continue;
2670
2840
  }
2671
2841
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -2673,7 +2843,7 @@ function walkFrontendRoutes(dir, prefix, out) {
2673
2843
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
2674
2844
  segment = `:${segment.slice(1, -1)}`;
2675
2845
  }
2676
- walkFrontendRoutes(path14.join(dir, entry.name), `${prefix}/${segment}`, out);
2846
+ walkFrontendRoutes(path15.join(dir, entry.name), `${prefix}/${segment}`, out);
2677
2847
  }
2678
2848
  }
2679
2849
  function detectAuthFiles(cwd, out) {
@@ -2690,23 +2860,23 @@ function detectAuthFiles(cwd, out) {
2690
2860
  "src/app/api/oauth"
2691
2861
  ];
2692
2862
  for (const c of candidates) {
2693
- if (fs15.existsSync(path14.join(cwd, c))) out.authFiles.push(c);
2863
+ if (fs16.existsSync(path15.join(cwd, c))) out.authFiles.push(c);
2694
2864
  }
2695
2865
  }
2696
2866
  function detectRoles(cwd, out) {
2697
2867
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
2698
2868
  for (const rp of rolePaths) {
2699
- const dir = path14.join(cwd, rp);
2700
- if (!fs15.existsSync(dir)) continue;
2869
+ const dir = path15.join(cwd, rp);
2870
+ if (!fs16.existsSync(dir)) continue;
2701
2871
  let files;
2702
2872
  try {
2703
- files = fs15.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
2873
+ files = fs16.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
2704
2874
  } catch {
2705
2875
  continue;
2706
2876
  }
2707
2877
  for (const f of files) {
2708
2878
  try {
2709
- const content = fs15.readFileSync(path14.join(dir, f), "utf-8").slice(0, 5e3);
2879
+ const content = fs16.readFileSync(path15.join(dir, f), "utf-8").slice(0, 5e3);
2710
2880
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
2711
2881
  if (roleMatches) {
2712
2882
  for (const m of roleMatches) {
@@ -2852,7 +3022,7 @@ var discoverQaContext = async (ctx) => {
2852
3022
  };
2853
3023
 
2854
3024
  // src/scripts/dispatch.ts
2855
- import { execFileSync as execFileSync7 } from "child_process";
3025
+ import { execFileSync as execFileSync9 } from "child_process";
2856
3026
  var API_TIMEOUT_MS3 = 3e4;
2857
3027
  var dispatch = async (ctx, _profile, _agentResult, args) => {
2858
3028
  const next = args?.next;
@@ -2888,7 +3058,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
2888
3058
  const sub = usePr ? "pr" : "issue";
2889
3059
  const body = `@kody ${next}`;
2890
3060
  try {
2891
- execFileSync7("gh", [sub, "comment", String(targetNumber), "--body", body], {
3061
+ execFileSync9("gh", [sub, "comment", String(targetNumber), "--body", body], {
2892
3062
  timeout: API_TIMEOUT_MS3,
2893
3063
  cwd: ctx.cwd,
2894
3064
  stdio: ["ignore", "pipe", "pipe"]
@@ -2908,7 +3078,7 @@ function parsePr(url) {
2908
3078
  }
2909
3079
 
2910
3080
  // src/scripts/dispatchClassified.ts
2911
- import { execFileSync as execFileSync8 } from "child_process";
3081
+ import { execFileSync as execFileSync10 } from "child_process";
2912
3082
  var API_TIMEOUT_MS4 = 3e4;
2913
3083
  var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
2914
3084
  var dispatchClassified = async (ctx) => {
@@ -2917,7 +3087,7 @@ var dispatchClassified = async (ctx) => {
2917
3087
  const classification = ctx.data.classification;
2918
3088
  if (!classification || !VALID_CLASSES2.has(classification)) return;
2919
3089
  try {
2920
- execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
3090
+ execFileSync10("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
2921
3091
  cwd: ctx.cwd,
2922
3092
  timeout: API_TIMEOUT_MS4,
2923
3093
  stdio: ["ignore", "pipe", "pipe"]
@@ -2937,11 +3107,11 @@ function failedAction(reason) {
2937
3107
  }
2938
3108
 
2939
3109
  // src/scripts/dispatchMissionFileTicks.ts
2940
- import * as fs17 from "fs";
2941
- import * as path16 from "path";
3110
+ import * as fs18 from "fs";
3111
+ import * as path17 from "path";
2942
3112
 
2943
3113
  // src/issue.ts
2944
- import { execFileSync as execFileSync9 } from "child_process";
3114
+ import { execFileSync as execFileSync11 } from "child_process";
2945
3115
  var API_TIMEOUT_MS5 = 3e4;
2946
3116
  function ghToken2() {
2947
3117
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
@@ -2949,7 +3119,7 @@ function ghToken2() {
2949
3119
  function gh2(args, options) {
2950
3120
  const token = ghToken2();
2951
3121
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
2952
- return execFileSync9("gh", args, {
3122
+ return execFileSync11("gh", args, {
2953
3123
  encoding: "utf-8",
2954
3124
  timeout: API_TIMEOUT_MS5,
2955
3125
  cwd: options?.cwd,
@@ -3251,8 +3421,8 @@ var ContentsApiBackend = class {
3251
3421
  };
3252
3422
 
3253
3423
  // src/scripts/missionState/localFileBackend.ts
3254
- import * as fs16 from "fs";
3255
- import * as path15 from "path";
3424
+ import * as fs17 from "fs";
3425
+ import * as path16 from "path";
3256
3426
  var LocalFileBackend = class {
3257
3427
  name = "local-file";
3258
3428
  cwd;
@@ -3267,7 +3437,7 @@ var LocalFileBackend = class {
3267
3437
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
3268
3438
  this.cwd = opts.cwd;
3269
3439
  this.missionsDir = opts.missionsDir;
3270
- this.absDir = path15.join(opts.cwd, opts.missionsDir);
3440
+ this.absDir = path16.join(opts.cwd, opts.missionsDir);
3271
3441
  this.owner = opts.owner;
3272
3442
  this.repo = opts.repo;
3273
3443
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -3282,7 +3452,7 @@ var LocalFileBackend = class {
3282
3452
  `);
3283
3453
  return;
3284
3454
  }
3285
- fs16.mkdirSync(this.absDir, { recursive: true });
3455
+ fs17.mkdirSync(this.absDir, { recursive: true });
3286
3456
  const prefix = this.cacheKeyPrefix();
3287
3457
  const probeKey = `${prefix}probe-${Date.now()}`;
3288
3458
  try {
@@ -3311,7 +3481,7 @@ var LocalFileBackend = class {
3311
3481
  `);
3312
3482
  return;
3313
3483
  }
3314
- if (!fs16.existsSync(this.absDir)) {
3484
+ if (!fs17.existsSync(this.absDir)) {
3315
3485
  return;
3316
3486
  }
3317
3487
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -3327,11 +3497,11 @@ var LocalFileBackend = class {
3327
3497
  }
3328
3498
  load(slug) {
3329
3499
  const relPath = stateFilePath(this.missionsDir, slug);
3330
- const absPath = path15.join(this.cwd, relPath);
3331
- if (!fs16.existsSync(absPath)) {
3500
+ const absPath = path16.join(this.cwd, relPath);
3501
+ if (!fs17.existsSync(absPath)) {
3332
3502
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
3333
3503
  }
3334
- const raw = fs16.readFileSync(absPath, "utf-8");
3504
+ const raw = fs17.readFileSync(absPath, "utf-8");
3335
3505
  let parsed;
3336
3506
  try {
3337
3507
  parsed = JSON.parse(raw);
@@ -3348,10 +3518,10 @@ var LocalFileBackend = class {
3348
3518
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
3349
3519
  return false;
3350
3520
  }
3351
- const absPath = path15.join(this.cwd, loaded.path);
3352
- fs16.mkdirSync(path15.dirname(absPath), { recursive: true });
3521
+ const absPath = path16.join(this.cwd, loaded.path);
3522
+ fs17.mkdirSync(path16.dirname(absPath), { recursive: true });
3353
3523
  const body = JSON.stringify(next, null, 2) + "\n";
3354
- fs16.writeFileSync(absPath, body, "utf-8");
3524
+ fs17.writeFileSync(absPath, body, "utf-8");
3355
3525
  return true;
3356
3526
  }
3357
3527
  cacheKeyPrefix() {
@@ -3428,7 +3598,7 @@ var dispatchMissionFileTicks = async (ctx, _profile, args) => {
3428
3598
  await backend.hydrate();
3429
3599
  }
3430
3600
  try {
3431
- const slugs = listMissionSlugs(path16.join(ctx.cwd, missionsDir));
3601
+ const slugs = listMissionSlugs(path17.join(ctx.cwd, missionsDir));
3432
3602
  ctx.data.missionSlugCount = slugs.length;
3433
3603
  if (slugs.length === 0) {
3434
3604
  process.stdout.write(`[missions] no mission files in ${missionsDir}
@@ -3476,10 +3646,10 @@ var dispatchMissionFileTicks = async (ctx, _profile, args) => {
3476
3646
  }
3477
3647
  };
3478
3648
  function listMissionSlugs(absDir) {
3479
- if (!fs17.existsSync(absDir)) return [];
3649
+ if (!fs18.existsSync(absDir)) return [];
3480
3650
  let entries;
3481
3651
  try {
3482
- entries = fs17.readdirSync(absDir, { withFileTypes: true });
3652
+ entries = fs18.readdirSync(absDir, { withFileTypes: true });
3483
3653
  } catch {
3484
3654
  return [];
3485
3655
  }
@@ -3848,7 +4018,7 @@ function collectExpectedTests(raw) {
3848
4018
  }
3849
4019
 
3850
4020
  // src/scripts/finishFlow.ts
3851
- import { execFileSync as execFileSync10 } from "child_process";
4021
+ import { execFileSync as execFileSync12 } from "child_process";
3852
4022
 
3853
4023
  // src/lifecycleLabels.ts
3854
4024
  var KODY_NAMESPACE = "kody";
@@ -4001,7 +4171,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
4001
4171
  **PR:** ${state.core.prUrl}` : "";
4002
4172
  const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
4003
4173
  try {
4004
- execFileSync10("gh", ["issue", "comment", String(issueNumber), "--body", body], {
4174
+ execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
4005
4175
  timeout: API_TIMEOUT_MS6,
4006
4176
  cwd: ctx.cwd,
4007
4177
  stdio: ["ignore", "pipe", "pipe"]
@@ -4015,7 +4185,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
4015
4185
  };
4016
4186
 
4017
4187
  // src/branch.ts
4018
- import { execFileSync as execFileSync11 } from "child_process";
4188
+ import { execFileSync as execFileSync13 } from "child_process";
4019
4189
  var UncommittedChangesError = class extends Error {
4020
4190
  constructor(branch) {
4021
4191
  super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
@@ -4025,7 +4195,7 @@ var UncommittedChangesError = class extends Error {
4025
4195
  branch;
4026
4196
  };
4027
4197
  function git2(args, cwd) {
4028
- return execFileSync11("git", args, {
4198
+ return execFileSync13("git", args, {
4029
4199
  encoding: "utf-8",
4030
4200
  timeout: 3e4,
4031
4201
  cwd,
@@ -4050,7 +4220,7 @@ function checkoutPrBranch(prNumber, cwd) {
4050
4220
  SKIP_HOOKS: "1",
4051
4221
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
4052
4222
  };
4053
- execFileSync11("gh", ["pr", "checkout", String(prNumber)], {
4223
+ execFileSync13("gh", ["pr", "checkout", String(prNumber)], {
4054
4224
  cwd,
4055
4225
  env,
4056
4226
  stdio: ["ignore", "pipe", "pipe"],
@@ -4117,8 +4287,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
4117
4287
  }
4118
4288
 
4119
4289
  // src/gha.ts
4120
- import { execFileSync as execFileSync12 } from "child_process";
4121
- import * as fs18 from "fs";
4290
+ import { execFileSync as execFileSync14 } from "child_process";
4291
+ import * as fs19 from "fs";
4122
4292
  function getRunUrl() {
4123
4293
  const server = process.env.GITHUB_SERVER_URL;
4124
4294
  const repo = process.env.GITHUB_REPOSITORY;
@@ -4129,10 +4299,10 @@ function getRunUrl() {
4129
4299
  function reactToTriggerComment(cwd) {
4130
4300
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
4131
4301
  const eventPath = process.env.GITHUB_EVENT_PATH;
4132
- if (!eventPath || !fs18.existsSync(eventPath)) return;
4302
+ if (!eventPath || !fs19.existsSync(eventPath)) return;
4133
4303
  let event = null;
4134
4304
  try {
4135
- event = JSON.parse(fs18.readFileSync(eventPath, "utf-8"));
4305
+ event = JSON.parse(fs19.readFileSync(eventPath, "utf-8"));
4136
4306
  } catch {
4137
4307
  return;
4138
4308
  }
@@ -4160,7 +4330,7 @@ function reactToTriggerComment(cwd) {
4160
4330
  for (let attempt = 0; attempt < 3; attempt++) {
4161
4331
  if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
4162
4332
  try {
4163
- execFileSync12("gh", args, opts);
4333
+ execFileSync14("gh", args, opts);
4164
4334
  return;
4165
4335
  } catch (err) {
4166
4336
  lastErr = err;
@@ -4173,13 +4343,13 @@ function reactToTriggerComment(cwd) {
4173
4343
  }
4174
4344
  function sleepMs(ms) {
4175
4345
  try {
4176
- execFileSync12("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
4346
+ execFileSync14("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
4177
4347
  } catch {
4178
4348
  }
4179
4349
  }
4180
4350
 
4181
4351
  // src/workflow.ts
4182
- import { execFileSync as execFileSync13 } from "child_process";
4352
+ import { execFileSync as execFileSync15 } from "child_process";
4183
4353
  var GH_TIMEOUT_MS = 3e4;
4184
4354
  function ghToken3() {
4185
4355
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
@@ -4187,7 +4357,7 @@ function ghToken3() {
4187
4357
  function gh3(args, cwd) {
4188
4358
  const token = ghToken3();
4189
4359
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
4190
- return execFileSync13("gh", args, {
4360
+ return execFileSync15("gh", args, {
4191
4361
  encoding: "utf-8",
4192
4362
  timeout: GH_TIMEOUT_MS,
4193
4363
  cwd,
@@ -4371,23 +4541,23 @@ function tryPostPr2(prNumber, body, cwd) {
4371
4541
  }
4372
4542
 
4373
4543
  // src/scripts/initFlow.ts
4374
- import { execFileSync as execFileSync14 } from "child_process";
4375
- import * as fs20 from "fs";
4376
- import * as path18 from "path";
4544
+ import { execFileSync as execFileSync16 } from "child_process";
4545
+ import * as fs21 from "fs";
4546
+ import * as path19 from "path";
4377
4547
 
4378
4548
  // src/scripts/loadQaGuide.ts
4379
- import * as fs19 from "fs";
4380
- import * as path17 from "path";
4549
+ import * as fs20 from "fs";
4550
+ import * as path18 from "path";
4381
4551
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
4382
4552
  var loadQaGuide = async (ctx) => {
4383
- const full = path17.join(ctx.cwd, QA_GUIDE_REL_PATH);
4384
- if (!fs19.existsSync(full)) {
4553
+ const full = path18.join(ctx.cwd, QA_GUIDE_REL_PATH);
4554
+ if (!fs20.existsSync(full)) {
4385
4555
  ctx.data.qaGuide = "";
4386
4556
  ctx.data.qaGuidePath = "";
4387
4557
  return;
4388
4558
  }
4389
4559
  try {
4390
- ctx.data.qaGuide = fs19.readFileSync(full, "utf-8");
4560
+ ctx.data.qaGuide = fs20.readFileSync(full, "utf-8");
4391
4561
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
4392
4562
  } catch {
4393
4563
  ctx.data.qaGuide = "";
@@ -4397,9 +4567,9 @@ var loadQaGuide = async (ctx) => {
4397
4567
 
4398
4568
  // src/scripts/initFlow.ts
4399
4569
  function detectPackageManager(cwd) {
4400
- if (fs20.existsSync(path18.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
4401
- if (fs20.existsSync(path18.join(cwd, "yarn.lock"))) return "yarn";
4402
- if (fs20.existsSync(path18.join(cwd, "bun.lockb"))) return "bun";
4570
+ if (fs21.existsSync(path19.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
4571
+ if (fs21.existsSync(path19.join(cwd, "yarn.lock"))) return "yarn";
4572
+ if (fs21.existsSync(path19.join(cwd, "bun.lockb"))) return "bun";
4403
4573
  return "npm";
4404
4574
  }
4405
4575
  function qualityCommandsFor(pm) {
@@ -4412,7 +4582,7 @@ function qualityCommandsFor(pm) {
4412
4582
  function detectOwnerRepo(cwd) {
4413
4583
  let url;
4414
4584
  try {
4415
- url = execFileSync14("git", ["remote", "get-url", "origin"], {
4585
+ url = execFileSync16("git", ["remote", "get-url", "origin"], {
4416
4586
  cwd,
4417
4587
  encoding: "utf-8",
4418
4588
  stdio: ["ignore", "pipe", "pipe"]
@@ -4497,7 +4667,7 @@ jobs:
4497
4667
  `;
4498
4668
  function defaultBranchFromGit(cwd) {
4499
4669
  try {
4500
- const ref = execFileSync14("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
4670
+ const ref = execFileSync16("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
4501
4671
  cwd,
4502
4672
  encoding: "utf-8",
4503
4673
  stdio: ["ignore", "pipe", "pipe"]
@@ -4505,7 +4675,7 @@ function defaultBranchFromGit(cwd) {
4505
4675
  return ref.replace("refs/remotes/origin/", "");
4506
4676
  } catch {
4507
4677
  try {
4508
- return execFileSync14("git", ["branch", "--show-current"], {
4678
+ return execFileSync16("git", ["branch", "--show-current"], {
4509
4679
  cwd,
4510
4680
  encoding: "utf-8",
4511
4681
  stdio: ["ignore", "pipe", "pipe"]
@@ -4521,33 +4691,33 @@ function performInit(cwd, force) {
4521
4691
  const pm = detectPackageManager(cwd);
4522
4692
  const ownerRepo = detectOwnerRepo(cwd);
4523
4693
  const defaultBranch = defaultBranchFromGit(cwd);
4524
- const configPath = path18.join(cwd, "kody.config.json");
4525
- if (fs20.existsSync(configPath) && !force) {
4694
+ const configPath = path19.join(cwd, "kody.config.json");
4695
+ if (fs21.existsSync(configPath) && !force) {
4526
4696
  skipped.push("kody.config.json");
4527
4697
  } else {
4528
4698
  const cfg = makeConfig(pm, ownerRepo, defaultBranch);
4529
- fs20.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
4699
+ fs21.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
4530
4700
  `);
4531
4701
  wrote.push("kody.config.json");
4532
4702
  }
4533
- const workflowDir = path18.join(cwd, ".github", "workflows");
4534
- const workflowPath = path18.join(workflowDir, "kody.yml");
4535
- if (fs20.existsSync(workflowPath) && !force) {
4703
+ const workflowDir = path19.join(cwd, ".github", "workflows");
4704
+ const workflowPath = path19.join(workflowDir, "kody.yml");
4705
+ if (fs21.existsSync(workflowPath) && !force) {
4536
4706
  skipped.push(".github/workflows/kody.yml");
4537
4707
  } else {
4538
- fs20.mkdirSync(workflowDir, { recursive: true });
4539
- fs20.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
4708
+ fs21.mkdirSync(workflowDir, { recursive: true });
4709
+ fs21.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
4540
4710
  wrote.push(".github/workflows/kody.yml");
4541
4711
  }
4542
- const hasUi = fs20.existsSync(path18.join(cwd, "src/app")) || fs20.existsSync(path18.join(cwd, "app")) || fs20.existsSync(path18.join(cwd, "pages"));
4712
+ const hasUi = fs21.existsSync(path19.join(cwd, "src/app")) || fs21.existsSync(path19.join(cwd, "app")) || fs21.existsSync(path19.join(cwd, "pages"));
4543
4713
  if (hasUi) {
4544
- const qaGuidePath = path18.join(cwd, QA_GUIDE_REL_PATH);
4545
- if (fs20.existsSync(qaGuidePath) && !force) {
4714
+ const qaGuidePath = path19.join(cwd, QA_GUIDE_REL_PATH);
4715
+ if (fs21.existsSync(qaGuidePath) && !force) {
4546
4716
  skipped.push(QA_GUIDE_REL_PATH);
4547
4717
  } else {
4548
- fs20.mkdirSync(path18.dirname(qaGuidePath), { recursive: true });
4718
+ fs21.mkdirSync(path19.dirname(qaGuidePath), { recursive: true });
4549
4719
  const discovery = runQaDiscovery(cwd);
4550
- fs20.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
4720
+ fs21.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
4551
4721
  wrote.push(QA_GUIDE_REL_PATH);
4552
4722
  }
4553
4723
  }
@@ -4559,12 +4729,12 @@ function performInit(cwd, force) {
4559
4729
  continue;
4560
4730
  }
4561
4731
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
4562
- const target = path18.join(workflowDir, `kody-${exe.name}.yml`);
4563
- if (fs20.existsSync(target) && !force) {
4732
+ const target = path19.join(workflowDir, `kody-${exe.name}.yml`);
4733
+ if (fs21.existsSync(target) && !force) {
4564
4734
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
4565
4735
  continue;
4566
4736
  }
4567
- fs20.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
4737
+ fs21.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
4568
4738
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
4569
4739
  }
4570
4740
  let labels;
@@ -4702,8 +4872,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
4702
4872
  };
4703
4873
 
4704
4874
  // src/scripts/loadMissionFromFile.ts
4705
- import * as fs21 from "fs";
4706
- import * as path19 from "path";
4875
+ import * as fs22 from "fs";
4876
+ import * as path20 from "path";
4707
4877
  var loadMissionFromFile = async (ctx, _profile, args) => {
4708
4878
  const missionsDir = String(args?.missionsDir ?? ".kody/missions");
4709
4879
  const slugArg = String(args?.slugArg ?? "mission");
@@ -4711,11 +4881,11 @@ var loadMissionFromFile = async (ctx, _profile, args) => {
4711
4881
  if (!slug) {
4712
4882
  throw new Error(`loadMissionFromFile: ctx.args.${slugArg} must be a non-empty slug`);
4713
4883
  }
4714
- const absPath = path19.join(ctx.cwd, missionsDir, `${slug}.md`);
4715
- if (!fs21.existsSync(absPath)) {
4884
+ const absPath = path20.join(ctx.cwd, missionsDir, `${slug}.md`);
4885
+ if (!fs22.existsSync(absPath)) {
4716
4886
  throw new Error(`loadMissionFromFile: mission file not found: ${absPath}`);
4717
4887
  }
4718
- const raw = fs21.readFileSync(absPath, "utf-8");
4888
+ const raw = fs22.readFileSync(absPath, "utf-8");
4719
4889
  const { title, body } = parseMissionFile(raw, slug);
4720
4890
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, missionsDir });
4721
4891
  const loaded = await backend.load(slug);
@@ -4849,16 +5019,16 @@ var loadTaskState = async (ctx) => {
4849
5019
  };
4850
5020
 
4851
5021
  // src/scripts/loadVaultContext.ts
4852
- import * as fs22 from "fs";
4853
- import * as path20 from "path";
5022
+ import * as fs23 from "fs";
5023
+ import * as path21 from "path";
4854
5024
  var VAULT_DIR_RELATIVE = ".kody/vault";
4855
5025
  var MAX_PAGES = 8;
4856
5026
  var PER_PAGE_MAX_BYTES = 4e3;
4857
5027
  var TOTAL_MAX_BYTES2 = 24e3;
4858
5028
  var TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
4859
5029
  var loadVaultContext = async (ctx) => {
4860
- const vaultAbs = path20.join(ctx.cwd, VAULT_DIR_RELATIVE);
4861
- if (!fs22.existsSync(vaultAbs)) {
5030
+ const vaultAbs = path21.join(ctx.cwd, VAULT_DIR_RELATIVE);
5031
+ if (!fs23.existsSync(vaultAbs)) {
4862
5032
  ctx.data.vaultContext = "";
4863
5033
  return;
4864
5034
  }
@@ -4883,21 +5053,21 @@ function collectPages(vaultAbs) {
4883
5053
  walkMd(vaultAbs, (file) => {
4884
5054
  let stat;
4885
5055
  try {
4886
- stat = fs22.statSync(file);
5056
+ stat = fs23.statSync(file);
4887
5057
  } catch {
4888
5058
  return;
4889
5059
  }
4890
5060
  let raw;
4891
5061
  try {
4892
- raw = fs22.readFileSync(file, "utf-8");
5062
+ raw = fs23.readFileSync(file, "utf-8");
4893
5063
  } catch {
4894
5064
  return;
4895
5065
  }
4896
5066
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
4897
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path20.basename(file, ".md");
5067
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path21.basename(file, ".md");
4898
5068
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
4899
5069
  out.push({
4900
- relPath: path20.relative(vaultAbs, file),
5070
+ relPath: path21.relative(vaultAbs, file),
4901
5071
  title,
4902
5072
  updated,
4903
5073
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
@@ -4965,16 +5135,16 @@ function walkMd(root, visit) {
4965
5135
  const dir = stack.pop();
4966
5136
  let names;
4967
5137
  try {
4968
- names = fs22.readdirSync(dir);
5138
+ names = fs23.readdirSync(dir);
4969
5139
  } catch {
4970
5140
  continue;
4971
5141
  }
4972
5142
  for (const name of names) {
4973
5143
  if (name.startsWith(".")) continue;
4974
- const full = path20.join(dir, name);
5144
+ const full = path21.join(dir, name);
4975
5145
  let stat;
4976
5146
  try {
4977
- stat = fs22.statSync(full);
5147
+ stat = fs23.statSync(full);
4978
5148
  } catch {
4979
5149
  continue;
4980
5150
  }
@@ -4996,16 +5166,16 @@ var markFlowSuccess = async (ctx) => {
4996
5166
  };
4997
5167
 
4998
5168
  // src/scripts/memorizeFlow.ts
4999
- import { execFileSync as execFileSync15 } from "child_process";
5000
- import * as fs23 from "fs";
5001
- import * as path21 from "path";
5169
+ import { execFileSync as execFileSync17 } from "child_process";
5170
+ import * as fs24 from "fs";
5171
+ import * as path22 from "path";
5002
5172
  var VAULT_DIR_RELATIVE2 = ".kody/vault";
5003
5173
  var DEFAULT_LOOKBACK_HOURS = 36;
5004
5174
  var MAX_RECENT_PRS = 25;
5005
5175
  var MAX_VAULT_INDEX_ENTRIES = 200;
5006
5176
  var PR_BODY_TRUNC = 2e3;
5007
5177
  var memorizeFlow = async (ctx) => {
5008
- const vaultAbs = path21.join(ctx.cwd, VAULT_DIR_RELATIVE2);
5178
+ const vaultAbs = path22.join(ctx.cwd, VAULT_DIR_RELATIVE2);
5009
5179
  ensureBranch(ctx, vaultAbs);
5010
5180
  if (ctx.skipAgent) return;
5011
5181
  const sinceIso = computeSinceIso(vaultAbs);
@@ -5015,8 +5185,8 @@ var memorizeFlow = async (ctx) => {
5015
5185
  const recent = fetchRecentPrs(ctx.cwd, sinceIso);
5016
5186
  ctx.data.recentPrs = formatRecentPrs(recent);
5017
5187
  ctx.data.recentPrCount = recent.length;
5018
- if (!fs23.existsSync(vaultAbs)) {
5019
- fs23.mkdirSync(vaultAbs, { recursive: true });
5188
+ if (!fs24.existsSync(vaultAbs)) {
5189
+ fs24.mkdirSync(vaultAbs, { recursive: true });
5020
5190
  }
5021
5191
  ctx.data.vaultIndex = formatVaultIndex(vaultAbs);
5022
5192
  if (recent.length === 0) {
@@ -5048,18 +5218,18 @@ function ensureBranch(ctx, vaultAbs) {
5048
5218
  }
5049
5219
  }
5050
5220
  ctx.data.branch = branch;
5051
- if (!fs23.existsSync(vaultAbs)) {
5052
- fs23.mkdirSync(vaultAbs, { recursive: true });
5221
+ if (!fs24.existsSync(vaultAbs)) {
5222
+ fs24.mkdirSync(vaultAbs, { recursive: true });
5053
5223
  }
5054
5224
  }
5055
5225
  function computeSinceIso(vaultAbs) {
5056
5226
  const fallback = new Date(Date.now() - DEFAULT_LOOKBACK_HOURS * 60 * 60 * 1e3).toISOString();
5057
- if (!fs23.existsSync(vaultAbs)) return fallback;
5227
+ if (!fs24.existsSync(vaultAbs)) return fallback;
5058
5228
  let latest = "";
5059
5229
  walkMd2(vaultAbs, (file) => {
5060
5230
  let raw;
5061
5231
  try {
5062
- raw = fs23.readFileSync(file, "utf-8");
5232
+ raw = fs24.readFileSync(file, "utf-8");
5063
5233
  } catch {
5064
5234
  return;
5065
5235
  }
@@ -5136,10 +5306,10 @@ function formatVaultIndex(vaultAbs) {
5136
5306
  const entries = [];
5137
5307
  walkMd2(vaultAbs, (file) => {
5138
5308
  if (entries.length >= MAX_VAULT_INDEX_ENTRIES) return;
5139
- const rel = path21.relative(vaultAbs, file);
5309
+ const rel = path22.relative(vaultAbs, file);
5140
5310
  let title = rel;
5141
5311
  try {
5142
- const raw = fs23.readFileSync(file, "utf-8");
5312
+ const raw = fs24.readFileSync(file, "utf-8");
5143
5313
  const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
5144
5314
  const titleMatch = m?.[1]?.match(/^title:\s*(.+)$/m);
5145
5315
  if (titleMatch) title = `${titleMatch[1].trim()} (${rel})`;
@@ -5151,22 +5321,22 @@ function formatVaultIndex(vaultAbs) {
5151
5321
  return entries.join("\n");
5152
5322
  }
5153
5323
  function walkMd2(root, visit) {
5154
- if (!fs23.existsSync(root)) return;
5324
+ if (!fs24.existsSync(root)) return;
5155
5325
  const stack = [root];
5156
5326
  while (stack.length > 0) {
5157
5327
  const dir = stack.pop();
5158
5328
  let names;
5159
5329
  try {
5160
- names = fs23.readdirSync(dir);
5330
+ names = fs24.readdirSync(dir);
5161
5331
  } catch {
5162
5332
  continue;
5163
5333
  }
5164
5334
  for (const name of names) {
5165
5335
  if (name.startsWith(".")) continue;
5166
- const full = path21.join(dir, name);
5336
+ const full = path22.join(dir, name);
5167
5337
  let stat;
5168
5338
  try {
5169
- stat = fs23.statSync(full);
5339
+ stat = fs24.statSync(full);
5170
5340
  } catch {
5171
5341
  continue;
5172
5342
  }
@@ -5179,7 +5349,7 @@ function walkMd2(root, visit) {
5179
5349
  }
5180
5350
  }
5181
5351
  function git3(args, cwd) {
5182
- return execFileSync15("git", args, {
5352
+ return execFileSync17("git", args, {
5183
5353
  encoding: "utf-8",
5184
5354
  timeout: 3e4,
5185
5355
  cwd,
@@ -5189,7 +5359,7 @@ function git3(args, cwd) {
5189
5359
  }
5190
5360
 
5191
5361
  // src/scripts/mergeReleasePr.ts
5192
- import { execFileSync as execFileSync16 } from "child_process";
5362
+ import { execFileSync as execFileSync18 } from "child_process";
5193
5363
  var API_TIMEOUT_MS7 = 6e4;
5194
5364
  var mergeReleasePr = async (ctx) => {
5195
5365
  const state = ctx.data.taskState;
@@ -5208,7 +5378,7 @@ var mergeReleasePr = async (ctx) => {
5208
5378
  process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
5209
5379
  `);
5210
5380
  try {
5211
- const out = execFileSync16("gh", ["pr", "merge", String(prNumber), "--merge"], {
5381
+ const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
5212
5382
  timeout: API_TIMEOUT_MS7,
5213
5383
  cwd: ctx.cwd,
5214
5384
  stdio: ["ignore", "pipe", "pipe"]
@@ -5663,7 +5833,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
5663
5833
  };
5664
5834
 
5665
5835
  // src/scripts/recordClassification.ts
5666
- import { execFileSync as execFileSync17 } from "child_process";
5836
+ import { execFileSync as execFileSync19 } from "child_process";
5667
5837
  var API_TIMEOUT_MS8 = 3e4;
5668
5838
  var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
5669
5839
  var recordClassification = async (ctx) => {
@@ -5711,7 +5881,7 @@ function parseClassification(prSummary) {
5711
5881
  }
5712
5882
  function tryAuditComment(issueNumber, body, cwd) {
5713
5883
  try {
5714
- execFileSync17("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5884
+ execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5715
5885
  cwd,
5716
5886
  timeout: API_TIMEOUT_MS8,
5717
5887
  stdio: ["ignore", "pipe", "pipe"]
@@ -5839,7 +6009,7 @@ var resolveArtifacts = async (ctx, profile) => {
5839
6009
  };
5840
6010
 
5841
6011
  // src/scripts/resolveFlow.ts
5842
- import { execFileSync as execFileSync18 } from "child_process";
6012
+ import { execFileSync as execFileSync20 } from "child_process";
5843
6013
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
5844
6014
  var resolveFlow = async (ctx) => {
5845
6015
  const prNumber = ctx.args.pr;
@@ -5909,7 +6079,7 @@ function buildPreferBlock(prefer, baseBranch) {
5909
6079
  }
5910
6080
  function getConflictedFiles(cwd) {
5911
6081
  try {
5912
- const out = execFileSync18("git", ["diff", "--name-only", "--diff-filter=U"], {
6082
+ const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
5913
6083
  encoding: "utf-8",
5914
6084
  cwd,
5915
6085
  env: { ...process.env, HUSKY: "0" }
@@ -5924,7 +6094,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
5924
6094
  let total = 0;
5925
6095
  for (const f of files) {
5926
6096
  try {
5927
- const content = execFileSync18("cat", [f], { encoding: "utf-8", cwd }).toString();
6097
+ const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
5928
6098
  const snippet = `### ${f}
5929
6099
 
5930
6100
  \`\`\`
@@ -6025,7 +6195,7 @@ var resolvePreviewUrl = async (ctx) => {
6025
6195
  };
6026
6196
 
6027
6197
  // src/scripts/revertFlow.ts
6028
- import { execFileSync as execFileSync19 } from "child_process";
6198
+ import { execFileSync as execFileSync21 } from "child_process";
6029
6199
  var SHA_RE = /^[0-9a-f]{4,40}$/i;
6030
6200
  var revertFlow = async (ctx) => {
6031
6201
  const prNumber = ctx.args.pr;
@@ -6107,7 +6277,7 @@ function buildPrSummary(resolved) {
6107
6277
  return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
6108
6278
  }
6109
6279
  function git4(args, cwd) {
6110
- return execFileSync19("git", args, {
6280
+ return execFileSync21("git", args, {
6111
6281
  encoding: "utf-8",
6112
6282
  timeout: 3e4,
6113
6283
  cwd,
@@ -6117,7 +6287,7 @@ function git4(args, cwd) {
6117
6287
  }
6118
6288
  function isAncestorOfHead(sha, cwd) {
6119
6289
  try {
6120
- execFileSync19("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
6290
+ execFileSync21("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
6121
6291
  cwd,
6122
6292
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
6123
6293
  stdio: ["ignore", "ignore", "ignore"]
@@ -6270,11 +6440,11 @@ var skipAgent = async (ctx) => {
6270
6440
  };
6271
6441
 
6272
6442
  // src/scripts/stageMergeConflicts.ts
6273
- import { execFileSync as execFileSync20 } from "child_process";
6443
+ import { execFileSync as execFileSync22 } from "child_process";
6274
6444
  var stageMergeConflicts = async (ctx) => {
6275
6445
  if (ctx.data.agentDone === false) return;
6276
6446
  try {
6277
- execFileSync20("git", ["add", "-A"], {
6447
+ execFileSync22("git", ["add", "-A"], {
6278
6448
  cwd: ctx.cwd,
6279
6449
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
6280
6450
  stdio: "pipe"
@@ -6284,7 +6454,7 @@ var stageMergeConflicts = async (ctx) => {
6284
6454
  };
6285
6455
 
6286
6456
  // src/scripts/startFlow.ts
6287
- import { execFileSync as execFileSync21 } from "child_process";
6457
+ import { execFileSync as execFileSync23 } from "child_process";
6288
6458
  var API_TIMEOUT_MS9 = 3e4;
6289
6459
  var startFlow = async (ctx, profile, _agentResult, args) => {
6290
6460
  const entry = args?.entry;
@@ -6318,7 +6488,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
6318
6488
  const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
6319
6489
  const body = `@kody ${next}`;
6320
6490
  try {
6321
- execFileSync21("gh", [sub, "comment", String(targetNumber), "--body", body], {
6491
+ execFileSync23("gh", [sub, "comment", String(targetNumber), "--body", body], {
6322
6492
  timeout: API_TIMEOUT_MS9,
6323
6493
  cwd,
6324
6494
  stdio: ["ignore", "pipe", "pipe"]
@@ -6332,7 +6502,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
6332
6502
  }
6333
6503
 
6334
6504
  // src/scripts/syncFlow.ts
6335
- import { execFileSync as execFileSync22 } from "child_process";
6505
+ import { execFileSync as execFileSync24 } from "child_process";
6336
6506
  var syncFlow = async (ctx, _profile, args) => {
6337
6507
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
6338
6508
  const prNumber = ctx.args.pr;
@@ -6404,7 +6574,7 @@ function bail2(ctx, prNumber, reason) {
6404
6574
  }
6405
6575
  function revParseHead(cwd) {
6406
6576
  try {
6407
- return execFileSync22("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
6577
+ return execFileSync24("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
6408
6578
  } catch {
6409
6579
  return "";
6410
6580
  }
@@ -6412,9 +6582,9 @@ function revParseHead(cwd) {
6412
6582
  function pushBranch(branch, cwd) {
6413
6583
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
6414
6584
  try {
6415
- execFileSync22("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
6585
+ execFileSync24("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
6416
6586
  } catch {
6417
- execFileSync22("git", ["push", "--force-with-lease", "-u", "origin", branch], {
6587
+ execFileSync24("git", ["push", "--force-with-lease", "-u", "origin", branch], {
6418
6588
  cwd,
6419
6589
  env,
6420
6590
  stdio: ["ignore", "pipe", "pipe"]
@@ -6525,7 +6695,7 @@ var verify = async (ctx) => {
6525
6695
  };
6526
6696
 
6527
6697
  // src/scripts/waitForCi.ts
6528
- import { execFileSync as execFileSync23 } from "child_process";
6698
+ import { execFileSync as execFileSync25 } from "child_process";
6529
6699
  var API_TIMEOUT_MS10 = 3e4;
6530
6700
  var waitForCi = async (ctx, _profile, _agentResult, args) => {
6531
6701
  const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
@@ -6540,17 +6710,17 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
6540
6710
  return;
6541
6711
  }
6542
6712
  const fixCiAttempts = state?.core.attempts?.["fix-ci"] ?? 0;
6543
- await sleep(initialWaitSeconds * 1e3);
6713
+ await sleep2(initialWaitSeconds * 1e3);
6544
6714
  const deadline = Date.now() + timeoutMinutes * 6e4;
6545
6715
  let lastSummary = "";
6546
6716
  while (Date.now() < deadline) {
6547
6717
  const rows = fetchChecks(prNumber, ctx.cwd);
6548
6718
  if (rows === null) {
6549
- await sleep(pollSeconds * 1e3);
6719
+ await sleep2(pollSeconds * 1e3);
6550
6720
  continue;
6551
6721
  }
6552
6722
  if (rows.length === 0) {
6553
- await sleep(pollSeconds * 1e3);
6723
+ await sleep2(pollSeconds * 1e3);
6554
6724
  continue;
6555
6725
  }
6556
6726
  const summary = summarize(rows);
@@ -6593,7 +6763,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
6593
6763
  tryPostPr7(prNumber, `\u2705 kody waitForCi: all ${rows.length} checks green on PR #${prNumber}`, ctx.cwd);
6594
6764
  return;
6595
6765
  }
6596
- await sleep(pollSeconds * 1e3);
6766
+ await sleep2(pollSeconds * 1e3);
6597
6767
  }
6598
6768
  ctx.data.action = mkAction("CI_TIMEOUT", {
6599
6769
  reason: `CI did not complete within ${timeoutMinutes} minutes`,
@@ -6603,7 +6773,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
6603
6773
  };
6604
6774
  function fetchChecks(prNumber, cwd) {
6605
6775
  try {
6606
- const raw = execFileSync23("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
6776
+ const raw = execFileSync25("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
6607
6777
  encoding: "utf-8",
6608
6778
  timeout: API_TIMEOUT_MS10,
6609
6779
  cwd,
@@ -6645,7 +6815,7 @@ function tryPostPr7(prNumber, body, cwd) {
6645
6815
  } catch {
6646
6816
  }
6647
6817
  }
6648
- function sleep(ms) {
6818
+ function sleep2(ms) {
6649
6819
  return new Promise((res) => setTimeout(res, ms));
6650
6820
  }
6651
6821
 
@@ -6775,7 +6945,7 @@ var writeMissionStateFile = async (ctx, _profile, _agentResult, args) => {
6775
6945
  };
6776
6946
 
6777
6947
  // src/scripts/writeRunSummary.ts
6778
- import * as fs24 from "fs";
6948
+ import * as fs25 from "fs";
6779
6949
  var writeRunSummary = async (ctx, profile) => {
6780
6950
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
6781
6951
  if (!summaryPath) return;
@@ -6797,7 +6967,7 @@ var writeRunSummary = async (ctx, profile) => {
6797
6967
  if (reason) lines.push(`- **Reason:** ${reason}`);
6798
6968
  lines.push("");
6799
6969
  try {
6800
- fs24.appendFileSync(summaryPath, `${lines.join("\n")}
6970
+ fs25.appendFileSync(summaryPath, `${lines.join("\n")}
6801
6971
  `);
6802
6972
  } catch {
6803
6973
  }
@@ -6879,7 +7049,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
6879
7049
  ]);
6880
7050
 
6881
7051
  // src/tools.ts
6882
- import { execFileSync as execFileSync24 } from "child_process";
7052
+ import { execFileSync as execFileSync26 } from "child_process";
6883
7053
  function verifyCliTools(tools, cwd) {
6884
7054
  const out = [];
6885
7055
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -6912,7 +7082,7 @@ function verifyOne(tool, cwd) {
6912
7082
  }
6913
7083
  function runShell(cmd, cwd, timeoutMs = 3e4) {
6914
7084
  try {
6915
- execFileSync24("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
7085
+ execFileSync26("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
6916
7086
  return true;
6917
7087
  } catch {
6918
7088
  return false;
@@ -6981,9 +7151,9 @@ async function runExecutable(profileName, input) {
6981
7151
  data: {},
6982
7152
  output: { exitCode: 0 }
6983
7153
  };
6984
- const ndjsonDir = path22.join(input.cwd, ".kody");
7154
+ const ndjsonDir = path23.join(input.cwd, ".kody");
6985
7155
  const invokeAgent = async (prompt) => {
6986
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path22.isAbsolute(p) ? p : path22.resolve(profile.dir, p)).filter((p) => p.length > 0);
7156
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path23.isAbsolute(p) ? p : path23.resolve(profile.dir, p)).filter((p) => p.length > 0);
6987
7157
  const syntheticPath = ctx.data.syntheticPluginPath;
6988
7158
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
6989
7159
  return runAgent({
@@ -7078,17 +7248,17 @@ async function runExecutable(profileName, input) {
7078
7248
  function resolveProfilePath(profileName) {
7079
7249
  const found = resolveExecutable(profileName);
7080
7250
  if (found) return found;
7081
- const here = path22.dirname(new URL(import.meta.url).pathname);
7251
+ const here = path23.dirname(new URL(import.meta.url).pathname);
7082
7252
  const candidates = [
7083
- path22.join(here, "executables", profileName, "profile.json"),
7253
+ path23.join(here, "executables", profileName, "profile.json"),
7084
7254
  // same-dir sibling (dev)
7085
- path22.join(here, "..", "executables", profileName, "profile.json"),
7255
+ path23.join(here, "..", "executables", profileName, "profile.json"),
7086
7256
  // up one (prod: dist/bin → dist/executables)
7087
- path22.join(here, "..", "src", "executables", profileName, "profile.json")
7257
+ path23.join(here, "..", "src", "executables", profileName, "profile.json")
7088
7258
  // fallback
7089
7259
  ];
7090
7260
  for (const c of candidates) {
7091
- if (fs25.existsSync(c)) return c;
7261
+ if (fs26.existsSync(c)) return c;
7092
7262
  }
7093
7263
  return candidates[0];
7094
7264
  }
@@ -7192,8 +7362,8 @@ function resolveShellTimeoutMs(entry) {
7192
7362
  var SIGKILL_GRACE_MS = 5e3;
7193
7363
  async function runShellEntry(entry, ctx, profile) {
7194
7364
  const shellName = entry.shell;
7195
- const shellPath = path22.join(profile.dir, shellName);
7196
- if (!fs25.existsSync(shellPath)) {
7365
+ const shellPath = path23.join(profile.dir, shellName);
7366
+ if (!fs26.existsSync(shellPath)) {
7197
7367
  ctx.skipAgent = true;
7198
7368
  ctx.output.exitCode = 99;
7199
7369
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -7314,6 +7484,7 @@ async function runContainerLoop(profile, ctx, input) {
7314
7484
  const issueNumber = ctx.args.issue;
7315
7485
  let currentIdx = 0;
7316
7486
  let iteration = 0;
7487
+ let knownPrUrl;
7317
7488
  while (currentIdx >= 0 && currentIdx < children.length) {
7318
7489
  iteration++;
7319
7490
  if (iteration > CONTAINER_MAX_ITERATIONS) {
@@ -7328,6 +7499,7 @@ async function runContainerLoop(profile, ctx, input) {
7328
7499
  process.stderr.write(`[kody container] step ${iteration}: invoking ${child.exec}
7329
7500
  `);
7330
7501
  const priorState = readContainerState(ctx, child, reader);
7502
+ if (priorState.core?.prUrl) knownPrUrl = priorState.core.prUrl;
7331
7503
  const priorAction = priorState.executables?.[child.exec]?.lastAction;
7332
7504
  let actionType;
7333
7505
  if (priorAction && /_COMPLETED$/i.test(priorAction.type)) {
@@ -7337,8 +7509,7 @@ async function runContainerLoop(profile, ctx, input) {
7337
7509
  } else {
7338
7510
  let cliArgs;
7339
7511
  if (child.target === "pr") {
7340
- const prUrl = priorState.core?.prUrl;
7341
- const prNumber = prUrl ? parsePrNumber4(prUrl) : null;
7512
+ const prNumber = knownPrUrl ? parsePrNumber4(knownPrUrl) : null;
7342
7513
  if (!prNumber) {
7343
7514
  const reason = `container child "${child.exec}" needs --pr but state.core.prUrl is unset`;
7344
7515
  process.stderr.write(`[kody container] aborting: ${reason}
@@ -7384,6 +7555,7 @@ async function runContainerLoop(profile, ctx, input) {
7384
7555
  return;
7385
7556
  }
7386
7557
  const next = readContainerState(ctx, child, reader);
7558
+ if (next.core?.prUrl) knownPrUrl = next.core.prUrl;
7387
7559
  ctx.data.taskState = next;
7388
7560
  const actionFromState = next.core?.lastOutcome?.type;
7389
7561
  actionType = actionFromState ?? (childOut.exitCode === 0 ? "RUN_COMPLETED" : "RUN_FAILED");
@@ -7420,16 +7592,25 @@ async function runContainerLoop(profile, ctx, input) {
7420
7592
  currentIdx = nextIdx;
7421
7593
  }
7422
7594
  }
7423
- function readContainerState(ctx, _child, reader) {
7595
+ function readContainerState(ctx, child, reader) {
7424
7596
  const issueNumber = ctx.args.issue;
7597
+ const cached = ctx.data.taskState;
7598
+ const prUrl = cached?.core?.prUrl;
7599
+ const prNumber = prUrl ? parsePrNumber4(prUrl) : null;
7600
+ if (child.target === "pr" && prNumber) {
7601
+ try {
7602
+ return reader("pr", prNumber, ctx.cwd);
7603
+ } catch {
7604
+ }
7605
+ }
7425
7606
  if (issueNumber !== void 0) {
7426
7607
  try {
7427
7608
  return reader("issue", issueNumber, ctx.cwd);
7428
7609
  } catch {
7429
7610
  }
7430
7611
  }
7431
- if (ctx.data.taskState && typeof ctx.data.taskState === "object") {
7432
- return ctx.data.taskState;
7612
+ if (cached && typeof cached === "object") {
7613
+ return cached;
7433
7614
  }
7434
7615
  return {
7435
7616
  schemaVersion: 1,
@@ -7552,14 +7733,14 @@ function resolveAuthToken(env = process.env) {
7552
7733
  return token;
7553
7734
  }
7554
7735
  function detectPackageManager2(cwd) {
7555
- if (fs26.existsSync(path23.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7556
- if (fs26.existsSync(path23.join(cwd, "yarn.lock"))) return "yarn";
7557
- if (fs26.existsSync(path23.join(cwd, "bun.lockb"))) return "bun";
7736
+ if (fs27.existsSync(path24.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7737
+ if (fs27.existsSync(path24.join(cwd, "yarn.lock"))) return "yarn";
7738
+ if (fs27.existsSync(path24.join(cwd, "bun.lockb"))) return "bun";
7558
7739
  return "npm";
7559
7740
  }
7560
7741
  function shellOut(cmd, args, cwd, stream = true) {
7561
7742
  try {
7562
- execFileSync25(cmd, args, {
7743
+ execFileSync27(cmd, args, {
7563
7744
  cwd,
7564
7745
  stdio: stream ? "inherit" : "pipe",
7565
7746
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -7572,7 +7753,7 @@ function shellOut(cmd, args, cwd, stream = true) {
7572
7753
  }
7573
7754
  function isOnPath(bin) {
7574
7755
  try {
7575
- execFileSync25("which", [bin], { stdio: "pipe" });
7756
+ execFileSync27("which", [bin], { stdio: "pipe" });
7576
7757
  return true;
7577
7758
  } catch {
7578
7759
  return false;
@@ -7606,7 +7787,7 @@ function installLitellmIfNeeded(cwd) {
7606
7787
  } catch {
7607
7788
  }
7608
7789
  try {
7609
- execFileSync25("python3", ["-c", "import litellm"], { stdio: "pipe" });
7790
+ execFileSync27("python3", ["-c", "import litellm"], { stdio: "pipe" });
7610
7791
  process.stdout.write("\u2192 kody: litellm already installed\n");
7611
7792
  return 0;
7612
7793
  } catch {
@@ -7616,16 +7797,16 @@ function installLitellmIfNeeded(cwd) {
7616
7797
  }
7617
7798
  function configureGitIdentity(cwd) {
7618
7799
  try {
7619
- const name = execFileSync25("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
7800
+ const name = execFileSync27("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
7620
7801
  if (name) return;
7621
7802
  } catch {
7622
7803
  }
7623
7804
  try {
7624
- execFileSync25("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
7805
+ execFileSync27("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
7625
7806
  } catch {
7626
7807
  }
7627
7808
  try {
7628
- execFileSync25("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
7809
+ execFileSync27("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
7629
7810
  cwd,
7630
7811
  stdio: "pipe"
7631
7812
  });
@@ -7634,11 +7815,11 @@ function configureGitIdentity(cwd) {
7634
7815
  }
7635
7816
  function postFailureTail(issueNumber, cwd, reason) {
7636
7817
  if (!issueNumber) return;
7637
- const logPath = path23.join(cwd, ".kody", "last-run.jsonl");
7818
+ const logPath = path24.join(cwd, ".kody", "last-run.jsonl");
7638
7819
  let tail = "";
7639
7820
  try {
7640
- if (fs26.existsSync(logPath)) {
7641
- const content = fs26.readFileSync(logPath, "utf-8");
7821
+ if (fs27.existsSync(logPath)) {
7822
+ const content = fs27.readFileSync(logPath, "utf-8");
7642
7823
  tail = content.slice(-3e3);
7643
7824
  }
7644
7825
  } catch {
@@ -7663,7 +7844,7 @@ async function runCi(argv) {
7663
7844
  return 0;
7664
7845
  }
7665
7846
  const args = parseCiArgs(argv);
7666
- const cwd = args.cwd ? path23.resolve(args.cwd) : process.cwd();
7847
+ const cwd = args.cwd ? path24.resolve(args.cwd) : process.cwd();
7667
7848
  let earlyConfig;
7668
7849
  try {
7669
7850
  earlyConfig = loadConfig(cwd);
@@ -7673,9 +7854,9 @@ async function runCi(argv) {
7673
7854
  const eventName = process.env.GITHUB_EVENT_NAME;
7674
7855
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
7675
7856
  let manualWorkflowDispatch = false;
7676
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs26.existsSync(dispatchEventPath)) {
7857
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs27.existsSync(dispatchEventPath)) {
7677
7858
  try {
7678
- const evt = JSON.parse(fs26.readFileSync(dispatchEventPath, "utf-8"));
7859
+ const evt = JSON.parse(fs27.readFileSync(dispatchEventPath, "utf-8"));
7679
7860
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
7680
7861
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
7681
7862
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -7890,15 +8071,15 @@ function parseChatArgs(argv, env = process.env) {
7890
8071
  return result;
7891
8072
  }
7892
8073
  function commitChatFiles(cwd, sessionId, verbose) {
7893
- const sessionFile = path24.relative(cwd, sessionFilePath(cwd, sessionId));
7894
- const eventsFile = path24.relative(cwd, eventsFilePath(cwd, sessionId));
7895
- const paths = [sessionFile, eventsFile].filter((p) => fs27.existsSync(path24.join(cwd, p)));
8074
+ const sessionFile = path25.relative(cwd, sessionFilePath(cwd, sessionId));
8075
+ const eventsFile = path25.relative(cwd, eventsFilePath(cwd, sessionId));
8076
+ const paths = [sessionFile, eventsFile].filter((p) => fs28.existsSync(path25.join(cwd, p)));
7896
8077
  if (paths.length === 0) return;
7897
8078
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
7898
8079
  try {
7899
- execFileSync26("git", ["add", ...paths], opts);
7900
- execFileSync26("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
7901
- execFileSync26("git", ["push", "--quiet", "origin", "HEAD"], opts);
8080
+ execFileSync28("git", ["add", ...paths], opts);
8081
+ execFileSync28("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
8082
+ execFileSync28("git", ["push", "--quiet", "origin", "HEAD"], opts);
7902
8083
  } catch (err) {
7903
8084
  const msg = err instanceof Error ? err.message : String(err);
7904
8085
  process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
@@ -7930,7 +8111,7 @@ async function runChat(argv) {
7930
8111
  ${CHAT_HELP}`);
7931
8112
  return 64;
7932
8113
  }
7933
- const cwd = args.cwd ? path24.resolve(args.cwd) : process.cwd();
8114
+ const cwd = args.cwd ? path25.resolve(args.cwd) : process.cwd();
7934
8115
  const sessionId = args.sessionId;
7935
8116
  const unpackedSecrets = unpackAllSecrets();
7936
8117
  if (unpackedSecrets > 0) {
@@ -7974,7 +8155,21 @@ ${CHAT_HELP}`);
7974
8155
  const sessionFile = sessionFilePath(cwd, sessionId);
7975
8156
  if (args.initMessage) seedInitialMessage(sessionFile, args.initMessage);
7976
8157
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
8158
+ const meta = readMeta(sessionFile);
7977
8159
  try {
8160
+ if (meta?.mode === "interactive") {
8161
+ const result2 = await runInteractiveMode({
8162
+ sessionId,
8163
+ cwd,
8164
+ model,
8165
+ litellmUrl: litellm?.url ?? null,
8166
+ sink,
8167
+ meta,
8168
+ verbose: args.verbose,
8169
+ quiet: args.quiet
8170
+ });
8171
+ return result2.exitCode;
8172
+ }
7978
8173
  const result = await runChatTurn({
7979
8174
  sessionId,
7980
8175
  sessionFile,