@kody-ade/kody-engine 0.3.72 → 0.3.75

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.72",
6
+ version: "0.3.75",
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,187 @@ 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
+ const branch = currentBranch(opts.cwd);
680
+ if (branch) {
681
+ execFileSync("git", ["fetch", "--quiet", "origin", branch], { cwd: opts.cwd, stdio: "pipe" });
682
+ execFileSync("git", ["merge", "--ff-only", "--quiet", `origin/${branch}`], {
683
+ cwd: opts.cwd,
684
+ stdio: "pipe"
685
+ });
686
+ } else {
687
+ execFileSync("git", ["fetch", "--quiet", "--all"], { cwd: opts.cwd, stdio: "pipe" });
688
+ }
689
+ } catch (err) {
690
+ const msg = err instanceof Error ? err.message : String(err);
691
+ logger.warn(`git pull failed (will retry): ${msg}`);
692
+ }
693
+ }
694
+ const turns = readSession(opts.sessionFile);
695
+ for (let i = opts.watermark; i < turns.length; i++) {
696
+ const t = turns[i];
697
+ if (t.role === "user") {
698
+ return { kind: "message", turn: t, turnIndex: i };
699
+ }
700
+ }
701
+ const remainingDeadline = opts.deadlineMs - Date.now();
702
+ const remainingIdle = opts.idleTimeoutMs - (Date.now() - idleStart);
703
+ const sleepMs2 = Math.max(0, Math.min(pollMs, remainingDeadline, remainingIdle));
704
+ if (sleepMs2 === 0) continue;
705
+ await sleep(sleepMs2);
706
+ }
707
+ }
708
+ function sleep(ms) {
709
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
710
+ }
711
+ function currentBranch(cwd) {
712
+ try {
713
+ const out = execFileSync("git", ["symbolic-ref", "--short", "HEAD"], {
714
+ cwd,
715
+ stdio: ["ignore", "pipe", "ignore"]
716
+ });
717
+ const branch = out.toString("utf-8").trim();
718
+ return branch || null;
719
+ } catch {
720
+ return null;
721
+ }
722
+ }
723
+
724
+ // src/chat/modes/interactive.ts
725
+ var DEFAULT_IDLE_EXIT_MS = 5 * 6e4;
726
+ var DEFAULT_HARD_CAP_MS = 30 * 6e4;
727
+ var DEFAULT_POLL_MS2 = 3e4;
728
+ async function runInteractiveMode(opts) {
729
+ const sessionFile = sessionFilePath(opts.cwd, opts.sessionId);
730
+ const idleExitMs = opts.meta.idleExitMs ?? DEFAULT_IDLE_EXIT_MS;
731
+ const hardCapMs = opts.meta.hardCapMs ?? DEFAULT_HARD_CAP_MS;
732
+ const startedAt = Date.now();
733
+ const deadlineMs = startedAt + hardCapMs;
734
+ await emit2(opts.sink, "chat.ready", opts.sessionId, "ready", {
735
+ sessionId: opts.sessionId,
736
+ startedAt: new Date(startedAt).toISOString(),
737
+ idleExitMs,
738
+ hardCapMs
739
+ });
740
+ let watermark = 0;
741
+ let turnsCompleted = 0;
742
+ while (true) {
743
+ const turns = readSession(sessionFile);
744
+ const pendingIdx = findNextUserTurn(turns, watermark);
745
+ if (pendingIdx === -1) {
746
+ const result = await waitForNextUserMessage({
747
+ sessionFile,
748
+ cwd: opts.cwd,
749
+ watermark,
750
+ idleTimeoutMs: idleExitMs,
751
+ deadlineMs,
752
+ pollIntervalMs: opts.pollIntervalMs ?? DEFAULT_POLL_MS2,
753
+ skipPull: opts.skipGit
754
+ });
755
+ if (result.kind === "idle-timeout") {
756
+ await emitExit(opts, "idle-timeout", turnsCompleted);
757
+ return { exitCode: 0, reason: "idle-timeout", turnsCompleted };
758
+ }
759
+ if (result.kind === "deadline") {
760
+ await emitExit(opts, "deadline", turnsCompleted);
761
+ return { exitCode: 0, reason: "deadline", turnsCompleted };
762
+ }
763
+ }
764
+ let turnResult;
765
+ try {
766
+ turnResult = await runChatTurn({
767
+ sessionId: opts.sessionId,
768
+ sessionFile,
769
+ cwd: opts.cwd,
770
+ model: opts.model,
771
+ litellmUrl: opts.litellmUrl,
772
+ sink: opts.sink,
773
+ verbose: opts.verbose,
774
+ quiet: opts.quiet,
775
+ invokeAgent: opts.invokeAgent
776
+ });
777
+ } catch (err) {
778
+ const msg = err instanceof Error ? err.message : String(err);
779
+ await emit2(opts.sink, "chat.error", opts.sessionId, `loop-${turnsCompleted}`, { error: msg });
780
+ await emitExit(opts, "fatal", turnsCompleted);
781
+ return { exitCode: 99, reason: "fatal", turnsCompleted };
782
+ }
783
+ if (turnResult.exitCode === 64) {
784
+ } else if (turnResult.exitCode !== 0) {
785
+ } else {
786
+ turnsCompleted += 1;
787
+ if (!opts.skipGit) commitTurn(opts.cwd, opts.sessionId, opts.verbose ?? false);
788
+ }
789
+ watermark = readSession(sessionFile).length;
790
+ }
791
+ }
792
+ function findNextUserTurn(turns, fromIdx) {
793
+ for (let i = fromIdx; i < turns.length; i++) {
794
+ if (turns[i].role === "user") return i;
795
+ }
796
+ if (turns.length > 0 && turns[turns.length - 1].role === "user") return turns.length - 1;
797
+ return -1;
798
+ }
799
+ function commitTurn(cwd, sessionId, verbose) {
800
+ const sessionRel = path5.relative(cwd, sessionFilePath(cwd, sessionId));
801
+ const eventsRel = path5.relative(cwd, eventsFilePath(cwd, sessionId));
802
+ const paths = [sessionRel, eventsRel].filter((p) => fs5.existsSync(path5.join(cwd, p)));
803
+ if (paths.length === 0) return;
804
+ const stdio = verbose ? "inherit" : "pipe";
805
+ try {
806
+ execFileSync2("git", ["add", "-f", ...paths], { cwd, stdio });
807
+ execFileSync2("git", ["commit", "--quiet", "-m", `chat: interactive turn for ${sessionId}`], { cwd, stdio });
808
+ execFileSync2("git", ["push", "--quiet", "origin", "HEAD"], { cwd, stdio });
809
+ } catch (err) {
810
+ const msg = err instanceof Error ? err.message : String(err);
811
+ process.stderr.write(`[kody:chat:interactive] commit/push skipped: ${msg}
812
+ `);
813
+ }
814
+ }
815
+ async function emitExit(opts, reason, turnsCompleted) {
816
+ await emit2(opts.sink, "chat.exit", opts.sessionId, "exit", {
817
+ sessionId: opts.sessionId,
818
+ reason,
819
+ turnsCompleted,
820
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
821
+ });
822
+ }
823
+ async function emit2(sink, type, sessionId, suffix, payload) {
824
+ await sink.emit({
825
+ event: type,
826
+ payload,
827
+ runId: makeRunId(sessionId, suffix),
828
+ emittedAt: (/* @__PURE__ */ new Date()).toISOString()
829
+ });
830
+ }
831
+
644
832
  // src/kody-cli.ts
645
- import { execFileSync as execFileSync25 } from "child_process";
646
- import * as fs26 from "fs";
647
- import * as path23 from "path";
833
+ import { execFileSync as execFileSync27 } from "child_process";
834
+ import * as fs27 from "fs";
835
+ import * as path24 from "path";
648
836
 
649
837
  // src/dispatch.ts
650
- import * as fs6 from "fs";
838
+ import * as fs7 from "fs";
651
839
 
652
840
  // src/cron-match.ts
653
841
  var FIELD_BOUNDS = [
@@ -712,25 +900,25 @@ function cronMatchesInWindow(spec, end, windowSec) {
712
900
  }
713
901
 
714
902
  // src/registry.ts
715
- import * as fs5 from "fs";
716
- import * as path5 from "path";
903
+ import * as fs6 from "fs";
904
+ import * as path6 from "path";
717
905
  function getExecutablesRoot() {
718
- const here = path5.dirname(new URL(import.meta.url).pathname);
906
+ const here = path6.dirname(new URL(import.meta.url).pathname);
719
907
  const candidates = [
720
- path5.join(here, "executables"),
908
+ path6.join(here, "executables"),
721
909
  // dev: src/
722
- path5.join(here, "..", "executables"),
910
+ path6.join(here, "..", "executables"),
723
911
  // built: dist/bin → dist/executables
724
- path5.join(here, "..", "src", "executables")
912
+ path6.join(here, "..", "src", "executables")
725
913
  // fallback
726
914
  ];
727
915
  for (const c of candidates) {
728
- if (fs5.existsSync(c) && fs5.statSync(c).isDirectory()) return c;
916
+ if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
729
917
  }
730
918
  return candidates[0];
731
919
  }
732
920
  function getProjectExecutablesRoot() {
733
- return path5.join(process.cwd(), ".kody", "executables");
921
+ return path6.join(process.cwd(), ".kody", "executables");
734
922
  }
735
923
  function getExecutableRoots() {
736
924
  return [getProjectExecutablesRoot(), getExecutablesRoot()];
@@ -740,13 +928,13 @@ function listExecutables(roots = getExecutableRoots()) {
740
928
  const seen = /* @__PURE__ */ new Set();
741
929
  const out = [];
742
930
  for (const root of rootList) {
743
- if (!fs5.existsSync(root)) continue;
744
- const entries = fs5.readdirSync(root, { withFileTypes: true });
931
+ if (!fs6.existsSync(root)) continue;
932
+ const entries = fs6.readdirSync(root, { withFileTypes: true });
745
933
  for (const ent of entries) {
746
934
  if (!ent.isDirectory()) continue;
747
935
  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()) {
936
+ const profilePath = path6.join(root, ent.name, "profile.json");
937
+ if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
750
938
  out.push({ name: ent.name, profilePath });
751
939
  seen.add(ent.name);
752
940
  }
@@ -758,8 +946,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
758
946
  if (!isSafeName(name)) return null;
759
947
  const rootList = typeof roots === "string" ? [roots] : roots;
760
948
  for (const root of rootList) {
761
- const profilePath = path5.join(root, name, "profile.json");
762
- if (fs5.existsSync(profilePath) && fs5.statSync(profilePath).isFile()) {
949
+ const profilePath = path6.join(root, name, "profile.json");
950
+ if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
763
951
  return profilePath;
764
952
  }
765
953
  }
@@ -775,7 +963,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
775
963
  const profilePath = resolveExecutable(name, roots);
776
964
  if (!profilePath) return null;
777
965
  try {
778
- const raw = JSON.parse(fs5.readFileSync(profilePath, "utf-8"));
966
+ const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
779
967
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
780
968
  return raw.inputs;
781
969
  } catch {
@@ -812,10 +1000,10 @@ function autoDispatch(opts) {
812
1000
  }
813
1001
  const eventName = process.env.GITHUB_EVENT_NAME;
814
1002
  const eventPath = process.env.GITHUB_EVENT_PATH;
815
- if (!eventName || !eventPath || !fs6.existsSync(eventPath)) return null;
1003
+ if (!eventName || !eventPath || !fs7.existsSync(eventPath)) return null;
816
1004
  let event = {};
817
1005
  try {
818
- event = JSON.parse(fs6.readFileSync(eventPath, "utf-8"));
1006
+ event = JSON.parse(fs7.readFileSync(eventPath, "utf-8"));
819
1007
  } catch {
820
1008
  return null;
821
1009
  }
@@ -890,7 +1078,7 @@ function dispatchScheduledWatches(opts) {
890
1078
  for (const exe of listExecutables()) {
891
1079
  let raw;
892
1080
  try {
893
- raw = fs6.readFileSync(exe.profilePath, "utf-8");
1081
+ raw = fs7.readFileSync(exe.profilePath, "utf-8");
894
1082
  } catch {
895
1083
  continue;
896
1084
  }
@@ -1004,14 +1192,14 @@ function coerceBare(spec, value) {
1004
1192
 
1005
1193
  // src/executor.ts
1006
1194
  import { spawn as spawn3 } from "child_process";
1007
- import * as fs25 from "fs";
1008
- import * as path22 from "path";
1195
+ import * as fs26 from "fs";
1196
+ import * as path23 from "path";
1009
1197
 
1010
1198
  // src/litellm.ts
1011
- import { execFileSync, spawn } from "child_process";
1012
- import * as fs7 from "fs";
1199
+ import { execFileSync as execFileSync3, spawn } from "child_process";
1200
+ import * as fs8 from "fs";
1013
1201
  import * as os from "os";
1014
- import * as path6 from "path";
1202
+ import * as path7 from "path";
1015
1203
  async function checkLitellmHealth(url) {
1016
1204
  try {
1017
1205
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -1042,29 +1230,29 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
1042
1230
  }
1043
1231
  let cmd = "litellm";
1044
1232
  try {
1045
- execFileSync("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
1233
+ execFileSync3("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
1046
1234
  } catch {
1047
1235
  try {
1048
- execFileSync("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
1236
+ execFileSync3("python3", ["-c", "import litellm"], { timeout: 1e4, stdio: "pipe" });
1049
1237
  cmd = "python3";
1050
1238
  } catch {
1051
1239
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
1052
1240
  }
1053
1241
  }
1054
- const configPath = path6.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
1055
- fs7.writeFileSync(configPath, generateLitellmConfigYaml(model));
1242
+ const configPath = path7.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
1243
+ fs8.writeFileSync(configPath, generateLitellmConfigYaml(model));
1056
1244
  const portMatch = url.match(/:(\d+)/);
1057
1245
  const port = portMatch ? portMatch[1] : "4000";
1058
1246
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
1059
1247
  const dotenvVars = readDotenvApiKeys(projectDir);
1060
- const logPath = path6.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
1061
- const outFd = fs7.openSync(logPath, "w");
1248
+ const logPath = path7.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
1249
+ const outFd = fs8.openSync(logPath, "w");
1062
1250
  const child = spawn(cmd, args, {
1063
1251
  stdio: ["ignore", outFd, outFd],
1064
1252
  detached: true,
1065
1253
  env: stripBlockingEnv({ ...process.env, ...dotenvVars })
1066
1254
  });
1067
- fs7.closeSync(outFd);
1255
+ fs8.closeSync(outFd);
1068
1256
  for (let i = 0; i < 30; i++) {
1069
1257
  await new Promise((r) => setTimeout(r, 2e3));
1070
1258
  if (await checkLitellmHealth(url)) {
@@ -1081,7 +1269,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
1081
1269
  }
1082
1270
  let logTail = "";
1083
1271
  try {
1084
- logTail = fs7.readFileSync(logPath, "utf-8").slice(-2e3);
1272
+ logTail = fs8.readFileSync(logPath, "utf-8").slice(-2e3);
1085
1273
  } catch {
1086
1274
  }
1087
1275
  try {
@@ -1092,10 +1280,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
1092
1280
  ${logTail}`);
1093
1281
  }
1094
1282
  function readDotenvApiKeys(projectDir) {
1095
- const dotenvPath = path6.join(projectDir, ".env");
1096
- if (!fs7.existsSync(dotenvPath)) return {};
1283
+ const dotenvPath = path7.join(projectDir, ".env");
1284
+ if (!fs8.existsSync(dotenvPath)) return {};
1097
1285
  const result = {};
1098
- for (const rawLine of fs7.readFileSync(dotenvPath, "utf-8").split("\n")) {
1286
+ for (const rawLine of fs8.readFileSync(dotenvPath, "utf-8").split("\n")) {
1099
1287
  const line = rawLine.trim();
1100
1288
  if (!line || line.startsWith("#")) continue;
1101
1289
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -1118,8 +1306,8 @@ function stripBlockingEnv(env) {
1118
1306
  }
1119
1307
 
1120
1308
  // src/profile.ts
1121
- import * as fs8 from "fs";
1122
- import * as path7 from "path";
1309
+ import * as fs9 from "fs";
1310
+ import * as path8 from "path";
1123
1311
  var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
1124
1312
  var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
1125
1313
  var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
@@ -1135,12 +1323,12 @@ var ProfileError = class extends Error {
1135
1323
  profilePath;
1136
1324
  };
1137
1325
  function loadProfile(profilePath) {
1138
- if (!fs8.existsSync(profilePath)) {
1326
+ if (!fs9.existsSync(profilePath)) {
1139
1327
  throw new ProfileError(profilePath, "file not found");
1140
1328
  }
1141
1329
  let raw;
1142
1330
  try {
1143
- raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
1331
+ raw = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
1144
1332
  } catch (err) {
1145
1333
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
1146
1334
  }
@@ -1179,7 +1367,7 @@ function loadProfile(profilePath) {
1179
1367
  inputArtifacts: parseInputArtifacts(profilePath, r.input),
1180
1368
  outputArtifacts: parseOutputArtifacts(profilePath, r.output),
1181
1369
  children,
1182
- dir: path7.dirname(profilePath)
1370
+ dir: path8.dirname(profilePath)
1183
1371
  };
1184
1372
  return profile;
1185
1373
  }
@@ -1423,9 +1611,9 @@ function parseScriptList(p, key, raw) {
1423
1611
  }
1424
1612
 
1425
1613
  // src/commit.ts
1426
- import { execFileSync as execFileSync2 } from "child_process";
1427
- import * as fs9 from "fs";
1428
- import * as path8 from "path";
1614
+ import { execFileSync as execFileSync4 } from "child_process";
1615
+ import * as fs10 from "fs";
1616
+ import * as path9 from "path";
1429
1617
  var FORBIDDEN_PATH_PREFIXES = [
1430
1618
  ".kody/",
1431
1619
  ".kody-engine/",
@@ -1454,7 +1642,7 @@ var CONVENTIONAL_PREFIXES = [
1454
1642
  ];
1455
1643
  function git(args, cwd) {
1456
1644
  try {
1457
- return execFileSync2("git", args, {
1645
+ return execFileSync4("git", args, {
1458
1646
  encoding: "utf-8",
1459
1647
  timeout: 12e4,
1460
1648
  cwd,
@@ -1481,18 +1669,18 @@ function tryGit(args, cwd) {
1481
1669
  }
1482
1670
  function abortUnfinishedGitOps(cwd) {
1483
1671
  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"))) {
1672
+ const gitDir = path9.join(cwd ?? process.cwd(), ".git");
1673
+ if (!fs10.existsSync(gitDir)) return aborted;
1674
+ if (fs10.existsSync(path9.join(gitDir, "MERGE_HEAD"))) {
1487
1675
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
1488
1676
  }
1489
- if (fs9.existsSync(path8.join(gitDir, "CHERRY_PICK_HEAD"))) {
1677
+ if (fs10.existsSync(path9.join(gitDir, "CHERRY_PICK_HEAD"))) {
1490
1678
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
1491
1679
  }
1492
- if (fs9.existsSync(path8.join(gitDir, "REVERT_HEAD"))) {
1680
+ if (fs10.existsSync(path9.join(gitDir, "REVERT_HEAD"))) {
1493
1681
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
1494
1682
  }
1495
- if (fs9.existsSync(path8.join(gitDir, "rebase-merge")) || fs9.existsSync(path8.join(gitDir, "rebase-apply"))) {
1683
+ if (fs10.existsSync(path9.join(gitDir, "rebase-merge")) || fs10.existsSync(path9.join(gitDir, "rebase-apply"))) {
1496
1684
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
1497
1685
  }
1498
1686
  try {
@@ -1513,7 +1701,7 @@ function isForbiddenPath(p) {
1513
1701
  return false;
1514
1702
  }
1515
1703
  function listChangedFiles(cwd) {
1516
- const raw = execFileSync2("git", ["status", "--porcelain=v1", "-z"], {
1704
+ const raw = execFileSync4("git", ["status", "--porcelain=v1", "-z"], {
1517
1705
  encoding: "utf-8",
1518
1706
  cwd,
1519
1707
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1525,7 +1713,7 @@ function listChangedFiles(cwd) {
1525
1713
  }
1526
1714
  function listFilesInCommit(ref = "HEAD", cwd) {
1527
1715
  try {
1528
- const raw = execFileSync2("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1716
+ const raw = execFileSync4("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
1529
1717
  encoding: "utf-8",
1530
1718
  cwd,
1531
1719
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -1548,7 +1736,7 @@ function normalizeCommitMessage(raw) {
1548
1736
  function commitAndPush(branch, agentMessage, cwd) {
1549
1737
  const allChanged = listChangedFiles(cwd);
1550
1738
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
1551
- const mergeHeadExists = fs9.existsSync(path8.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
1739
+ const mergeHeadExists = fs10.existsSync(path9.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
1552
1740
  if (allowedFiles.length === 0 && !mergeHeadExists) {
1553
1741
  return { committed: false, pushed: false, sha: "", message: "" };
1554
1742
  }
@@ -1614,10 +1802,10 @@ var abortUnfinishedGitOps2 = async (ctx) => {
1614
1802
  };
1615
1803
 
1616
1804
  // src/scripts/advanceFlow.ts
1617
- import { execFileSync as execFileSync4 } from "child_process";
1805
+ import { execFileSync as execFileSync6 } from "child_process";
1618
1806
 
1619
1807
  // src/state.ts
1620
- import { execFileSync as execFileSync3 } from "child_process";
1808
+ import { execFileSync as execFileSync5 } from "child_process";
1621
1809
  var STATE_BEGIN = "<!-- kody:state:v1:begin -->";
1622
1810
  var STATE_END = "<!-- kody:state:v1:end -->";
1623
1811
  var HISTORY_MAX_ENTRIES = 20;
@@ -1643,7 +1831,7 @@ function ghToken() {
1643
1831
  function gh(args, input, cwd) {
1644
1832
  const token = ghToken();
1645
1833
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
1646
- return execFileSync3("gh", args, {
1834
+ return execFileSync5("gh", args, {
1647
1835
  encoding: "utf-8",
1648
1836
  timeout: API_TIMEOUT_MS,
1649
1837
  cwd,
@@ -1846,7 +2034,7 @@ var advanceFlow = async (ctx, profile) => {
1846
2034
  }
1847
2035
  const body = `@kody ${flow.name}`;
1848
2036
  try {
1849
- execFileSync4("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
2037
+ execFileSync6("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
1850
2038
  timeout: API_TIMEOUT_MS2,
1851
2039
  cwd: ctx.cwd,
1852
2040
  stdio: ["ignore", "pipe", "pipe"]
@@ -1860,21 +2048,21 @@ var advanceFlow = async (ctx, profile) => {
1860
2048
  };
1861
2049
 
1862
2050
  // src/scripts/buildSyntheticPlugin.ts
1863
- import * as fs10 from "fs";
2051
+ import * as fs11 from "fs";
1864
2052
  import * as os2 from "os";
1865
- import * as path9 from "path";
2053
+ import * as path10 from "path";
1866
2054
  function getPluginsCatalogRoot() {
1867
- const here = path9.dirname(new URL(import.meta.url).pathname);
2055
+ const here = path10.dirname(new URL(import.meta.url).pathname);
1868
2056
  const candidates = [
1869
- path9.join(here, "..", "plugins"),
2057
+ path10.join(here, "..", "plugins"),
1870
2058
  // dev: src/scripts → src/plugins
1871
- path9.join(here, "..", "..", "plugins"),
2059
+ path10.join(here, "..", "..", "plugins"),
1872
2060
  // built: dist/scripts → dist/plugins
1873
- path9.join(here, "..", "..", "src", "plugins")
2061
+ path10.join(here, "..", "..", "src", "plugins")
1874
2062
  // fallback
1875
2063
  ];
1876
2064
  for (const c of candidates) {
1877
- if (fs10.existsSync(c) && fs10.statSync(c).isDirectory()) return c;
2065
+ if (fs11.existsSync(c) && fs11.statSync(c).isDirectory()) return c;
1878
2066
  }
1879
2067
  return candidates[0];
1880
2068
  }
@@ -1884,52 +2072,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
1884
2072
  if (!needsSynthetic) return;
1885
2073
  const catalog = getPluginsCatalogRoot();
1886
2074
  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 });
2075
+ const root = path10.join(os2.tmpdir(), `kody-synth-${runId}`);
2076
+ fs11.mkdirSync(path10.join(root, ".claude-plugin"), { recursive: true });
1889
2077
  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;
2078
+ const local = path10.join(profile.dir, bucket, entry);
2079
+ if (fs11.existsSync(local)) return local;
2080
+ const central = path10.join(catalog, bucket, entry);
2081
+ if (fs11.existsSync(central)) return central;
1894
2082
  throw new Error(
1895
2083
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
1896
2084
  );
1897
2085
  };
1898
2086
  if (cc.skills.length > 0) {
1899
- const dst = path9.join(root, "skills");
1900
- fs10.mkdirSync(dst, { recursive: true });
2087
+ const dst = path10.join(root, "skills");
2088
+ fs11.mkdirSync(dst, { recursive: true });
1901
2089
  for (const name of cc.skills) {
1902
- copyDir(resolvePart("skills", name), path9.join(dst, name));
2090
+ copyDir(resolvePart("skills", name), path10.join(dst, name));
1903
2091
  }
1904
2092
  }
1905
2093
  if (cc.commands.length > 0) {
1906
- const dst = path9.join(root, "commands");
1907
- fs10.mkdirSync(dst, { recursive: true });
2094
+ const dst = path10.join(root, "commands");
2095
+ fs11.mkdirSync(dst, { recursive: true });
1908
2096
  for (const name of cc.commands) {
1909
- fs10.copyFileSync(resolvePart("commands", `${name}.md`), path9.join(dst, `${name}.md`));
2097
+ fs11.copyFileSync(resolvePart("commands", `${name}.md`), path10.join(dst, `${name}.md`));
1910
2098
  }
1911
2099
  }
1912
2100
  if (cc.subagents.length > 0) {
1913
- const dst = path9.join(root, "agents");
1914
- fs10.mkdirSync(dst, { recursive: true });
2101
+ const dst = path10.join(root, "agents");
2102
+ fs11.mkdirSync(dst, { recursive: true });
1915
2103
  for (const name of cc.subagents) {
1916
- fs10.copyFileSync(resolvePart("agents", `${name}.md`), path9.join(dst, `${name}.md`));
2104
+ fs11.copyFileSync(resolvePart("agents", `${name}.md`), path10.join(dst, `${name}.md`));
1917
2105
  }
1918
2106
  }
1919
2107
  if (cc.hooks.length > 0) {
1920
- const dst = path9.join(root, "hooks");
1921
- fs10.mkdirSync(dst, { recursive: true });
2108
+ const dst = path10.join(root, "hooks");
2109
+ fs11.mkdirSync(dst, { recursive: true });
1922
2110
  const merged = { hooks: {} };
1923
2111
  for (const name of cc.hooks) {
1924
2112
  const src = resolvePart("hooks", `${name}.json`);
1925
- const parsed = JSON.parse(fs10.readFileSync(src, "utf-8"));
2113
+ const parsed = JSON.parse(fs11.readFileSync(src, "utf-8"));
1926
2114
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
1927
2115
  if (!Array.isArray(entries)) continue;
1928
2116
  if (!merged.hooks[event]) merged.hooks[event] = [];
1929
2117
  merged.hooks[event].push(...entries);
1930
2118
  }
1931
2119
  }
1932
- fs10.writeFileSync(path9.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2120
+ fs11.writeFileSync(path10.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
1933
2121
  `);
1934
2122
  }
1935
2123
  const manifest = {
@@ -1940,22 +2128,22 @@ var buildSyntheticPlugin = async (ctx, profile) => {
1940
2128
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
1941
2129
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
1942
2130
  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)}
2131
+ fs11.writeFileSync(path10.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
1944
2132
  `);
1945
2133
  ctx.data.syntheticPluginPath = root;
1946
2134
  };
1947
2135
  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);
2136
+ fs11.mkdirSync(dst, { recursive: true });
2137
+ for (const ent of fs11.readdirSync(src, { withFileTypes: true })) {
2138
+ const s = path10.join(src, ent.name);
2139
+ const d = path10.join(dst, ent.name);
1952
2140
  if (ent.isDirectory()) copyDir(s, d);
1953
- else if (ent.isFile()) fs10.copyFileSync(s, d);
2141
+ else if (ent.isFile()) fs11.copyFileSync(s, d);
1954
2142
  }
1955
2143
  }
1956
2144
 
1957
2145
  // src/coverage.ts
1958
- import { execFileSync as execFileSync5 } from "child_process";
2146
+ import { execFileSync as execFileSync7 } from "child_process";
1959
2147
  function patternToRegex(pattern) {
1960
2148
  let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
1961
2149
  s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
@@ -1973,7 +2161,7 @@ function renderSiblingPath(file, requireSibling) {
1973
2161
  }
1974
2162
  function safeGit(args, cwd) {
1975
2163
  try {
1976
- return execFileSync5("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
2164
+ return execFileSync7("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
1977
2165
  } catch {
1978
2166
  return "";
1979
2167
  }
@@ -2016,18 +2204,18 @@ function formatMissesForFeedback(misses) {
2016
2204
  }
2017
2205
 
2018
2206
  // src/prompt.ts
2019
- import * as fs11 from "fs";
2020
- import * as path10 from "path";
2207
+ import * as fs12 from "fs";
2208
+ import * as path11 from "path";
2021
2209
  var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
2022
2210
  var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
2023
2211
  function loadProjectConventions(projectDir) {
2024
2212
  const out = [];
2025
2213
  for (const rel of CONVENTION_FILES) {
2026
- const abs = path10.join(projectDir, rel);
2027
- if (!fs11.existsSync(abs)) continue;
2214
+ const abs = path11.join(projectDir, rel);
2215
+ if (!fs12.existsSync(abs)) continue;
2028
2216
  let content;
2029
2217
  try {
2030
- content = fs11.readFileSync(abs, "utf-8");
2218
+ content = fs12.readFileSync(abs, "utf-8");
2031
2219
  } catch {
2032
2220
  continue;
2033
2221
  }
@@ -2232,20 +2420,20 @@ var commitAndPush2 = async (ctx) => {
2232
2420
  };
2233
2421
 
2234
2422
  // src/scripts/composePrompt.ts
2235
- import * as fs12 from "fs";
2236
- import * as path11 from "path";
2423
+ import * as fs13 from "fs";
2424
+ import * as path12 from "path";
2237
2425
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
2238
2426
  var composePrompt = async (ctx, profile) => {
2239
2427
  const explicit = ctx.data.promptTemplate;
2240
2428
  const mode = ctx.args.mode;
2241
2429
  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")
2430
+ explicit ? path12.join(profile.dir, explicit) : null,
2431
+ mode ? path12.join(profile.dir, "prompts", `${mode}.md`) : null,
2432
+ path12.join(profile.dir, "prompt.md")
2245
2433
  ].filter(Boolean);
2246
2434
  let templatePath = "";
2247
2435
  for (const c of candidates) {
2248
- if (fs12.existsSync(c)) {
2436
+ if (fs13.existsSync(c)) {
2249
2437
  templatePath = c;
2250
2438
  break;
2251
2439
  }
@@ -2253,7 +2441,7 @@ var composePrompt = async (ctx, profile) => {
2253
2441
  if (!templatePath) {
2254
2442
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
2255
2443
  }
2256
- const template = fs12.readFileSync(templatePath, "utf-8");
2444
+ const template = fs13.readFileSync(templatePath, "utf-8");
2257
2445
  const tokens = {
2258
2446
  ...stringifyAll(ctx.args, "args."),
2259
2447
  ...stringifyAll(ctx.data, ""),
@@ -2330,16 +2518,16 @@ function formatToolsUsage(profile) {
2330
2518
  }
2331
2519
 
2332
2520
  // src/scripts/diagMcp.ts
2333
- import { execFileSync as execFileSync6 } from "child_process";
2334
- import * as fs13 from "fs";
2521
+ import { execFileSync as execFileSync8 } from "child_process";
2522
+ import * as fs14 from "fs";
2335
2523
  import * as os3 from "os";
2336
- import * as path12 from "path";
2524
+ import * as path13 from "path";
2337
2525
  var diagMcp = async (_ctx) => {
2338
2526
  const home = os3.homedir();
2339
- const cacheDir = path12.join(home, ".cache", "ms-playwright");
2527
+ const cacheDir = path13.join(home, ".cache", "ms-playwright");
2340
2528
  let entries = [];
2341
2529
  try {
2342
- entries = fs13.readdirSync(cacheDir);
2530
+ entries = fs14.readdirSync(cacheDir);
2343
2531
  } catch {
2344
2532
  }
2345
2533
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -2350,7 +2538,7 @@ var diagMcp = async (_ctx) => {
2350
2538
  process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
2351
2539
  `);
2352
2540
  try {
2353
- const v = execFileSync6("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
2541
+ const v = execFileSync8("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
2354
2542
  stdio: "pipe",
2355
2543
  timeout: 6e4,
2356
2544
  encoding: "utf8"
@@ -2365,17 +2553,17 @@ var diagMcp = async (_ctx) => {
2365
2553
  };
2366
2554
 
2367
2555
  // src/scripts/discoverQaContext.ts
2368
- import * as fs15 from "fs";
2369
- import * as path14 from "path";
2556
+ import * as fs16 from "fs";
2557
+ import * as path15 from "path";
2370
2558
 
2371
2559
  // src/scripts/frameworkDetectors.ts
2372
- import * as fs14 from "fs";
2373
- import * as path13 from "path";
2560
+ import * as fs15 from "fs";
2561
+ import * as path14 from "path";
2374
2562
  function detectFrameworks(cwd) {
2375
2563
  const out = [];
2376
2564
  let deps = {};
2377
2565
  try {
2378
- const pkg = JSON.parse(fs14.readFileSync(path13.join(cwd, "package.json"), "utf-8"));
2566
+ const pkg = JSON.parse(fs15.readFileSync(path14.join(cwd, "package.json"), "utf-8"));
2379
2567
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
2380
2568
  } catch {
2381
2569
  return out;
@@ -2412,7 +2600,7 @@ function detectFrameworks(cwd) {
2412
2600
  }
2413
2601
  function findFile(cwd, candidates) {
2414
2602
  for (const c of candidates) {
2415
- if (fs14.existsSync(path13.join(cwd, c))) return c;
2603
+ if (fs15.existsSync(path14.join(cwd, c))) return c;
2416
2604
  }
2417
2605
  return null;
2418
2606
  }
@@ -2425,18 +2613,18 @@ var COLLECTION_DIRS = [
2425
2613
  function discoverPayloadCollections(cwd) {
2426
2614
  const out = [];
2427
2615
  for (const dir of COLLECTION_DIRS) {
2428
- const full = path13.join(cwd, dir);
2429
- if (!fs14.existsSync(full)) continue;
2616
+ const full = path14.join(cwd, dir);
2617
+ if (!fs15.existsSync(full)) continue;
2430
2618
  let files;
2431
2619
  try {
2432
- files = fs14.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
2620
+ files = fs15.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
2433
2621
  } catch {
2434
2622
  continue;
2435
2623
  }
2436
2624
  for (const file of files) {
2437
2625
  try {
2438
- const filePath = path13.join(full, file);
2439
- const content = fs14.readFileSync(filePath, "utf-8").slice(0, 1e4);
2626
+ const filePath = path14.join(full, file);
2627
+ const content = fs15.readFileSync(filePath, "utf-8").slice(0, 1e4);
2440
2628
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
2441
2629
  if (!slugMatch) continue;
2442
2630
  const slug = slugMatch[1];
@@ -2450,7 +2638,7 @@ function discoverPayloadCollections(cwd) {
2450
2638
  out.push({
2451
2639
  name,
2452
2640
  slug,
2453
- filePath: path13.relative(cwd, filePath),
2641
+ filePath: path14.relative(cwd, filePath),
2454
2642
  fields: fields.slice(0, 20),
2455
2643
  hasAdmin
2456
2644
  });
@@ -2464,28 +2652,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
2464
2652
  function discoverAdminComponents(cwd, collections) {
2465
2653
  const out = [];
2466
2654
  for (const dir of ADMIN_COMPONENT_DIRS) {
2467
- const full = path13.join(cwd, dir);
2468
- if (!fs14.existsSync(full)) continue;
2655
+ const full = path14.join(cwd, dir);
2656
+ if (!fs15.existsSync(full)) continue;
2469
2657
  let entries;
2470
2658
  try {
2471
- entries = fs14.readdirSync(full, { withFileTypes: true });
2659
+ entries = fs15.readdirSync(full, { withFileTypes: true });
2472
2660
  } catch {
2473
2661
  continue;
2474
2662
  }
2475
2663
  for (const entry of entries) {
2476
- const entryPath = path13.join(full, entry.name);
2664
+ const entryPath = path14.join(full, entry.name);
2477
2665
  let name;
2478
2666
  let filePath;
2479
2667
  if (entry.isDirectory()) {
2480
2668
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
2481
- (f) => fs14.existsSync(path13.join(entryPath, f))
2669
+ (f) => fs15.existsSync(path14.join(entryPath, f))
2482
2670
  );
2483
2671
  if (!indexFile) continue;
2484
2672
  name = entry.name;
2485
- filePath = path13.relative(cwd, path13.join(entryPath, indexFile));
2673
+ filePath = path14.relative(cwd, path14.join(entryPath, indexFile));
2486
2674
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
2487
2675
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
2488
- filePath = path13.relative(cwd, entryPath);
2676
+ filePath = path14.relative(cwd, entryPath);
2489
2677
  } else {
2490
2678
  continue;
2491
2679
  }
@@ -2493,7 +2681,7 @@ function discoverAdminComponents(cwd, collections) {
2493
2681
  if (collections) {
2494
2682
  for (const col of collections) {
2495
2683
  try {
2496
- const colContent = fs14.readFileSync(path13.join(cwd, col.filePath), "utf-8");
2684
+ const colContent = fs15.readFileSync(path14.join(cwd, col.filePath), "utf-8");
2497
2685
  if (colContent.includes(name)) {
2498
2686
  usedInCollection = col.slug;
2499
2687
  break;
@@ -2512,8 +2700,8 @@ function scanApiRoutes(cwd) {
2512
2700
  const out = [];
2513
2701
  const appDirs = ["src/app", "app"];
2514
2702
  for (const appDir of appDirs) {
2515
- const apiDir = path13.join(cwd, appDir, "api");
2516
- if (!fs14.existsSync(apiDir)) continue;
2703
+ const apiDir = path14.join(cwd, appDir, "api");
2704
+ if (!fs15.existsSync(apiDir)) continue;
2517
2705
  walkApiRoutes(apiDir, "/api", cwd, out);
2518
2706
  break;
2519
2707
  }
@@ -2522,14 +2710,14 @@ function scanApiRoutes(cwd) {
2522
2710
  function walkApiRoutes(dir, prefix, cwd, out) {
2523
2711
  let entries;
2524
2712
  try {
2525
- entries = fs14.readdirSync(dir, { withFileTypes: true });
2713
+ entries = fs15.readdirSync(dir, { withFileTypes: true });
2526
2714
  } catch {
2527
2715
  return;
2528
2716
  }
2529
2717
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
2530
2718
  if (routeFile) {
2531
2719
  try {
2532
- const content = fs14.readFileSync(path13.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
2720
+ const content = fs15.readFileSync(path14.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
2533
2721
  const methods = HTTP_METHODS.filter(
2534
2722
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
2535
2723
  );
@@ -2537,7 +2725,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
2537
2725
  out.push({
2538
2726
  path: prefix,
2539
2727
  methods,
2540
- filePath: path13.relative(cwd, path13.join(dir, routeFile.name))
2728
+ filePath: path14.relative(cwd, path14.join(dir, routeFile.name))
2541
2729
  });
2542
2730
  }
2543
2731
  } catch {
@@ -2548,7 +2736,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
2548
2736
  if (entry.name === "node_modules" || entry.name === ".next") continue;
2549
2737
  let segment = entry.name;
2550
2738
  if (segment.startsWith("(") && segment.endsWith(")")) {
2551
- walkApiRoutes(path13.join(dir, entry.name), prefix, cwd, out);
2739
+ walkApiRoutes(path14.join(dir, entry.name), prefix, cwd, out);
2552
2740
  continue;
2553
2741
  }
2554
2742
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -2556,7 +2744,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
2556
2744
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
2557
2745
  segment = `:${segment.slice(1, -1)}`;
2558
2746
  }
2559
- walkApiRoutes(path13.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
2747
+ walkApiRoutes(path14.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
2560
2748
  }
2561
2749
  }
2562
2750
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -2576,10 +2764,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
2576
2764
  function scanEnvVars(cwd) {
2577
2765
  const candidates = [".env.example", ".env.local.example", ".env.template"];
2578
2766
  for (const envFile of candidates) {
2579
- const envPath = path13.join(cwd, envFile);
2580
- if (!fs14.existsSync(envPath)) continue;
2767
+ const envPath = path14.join(cwd, envFile);
2768
+ if (!fs15.existsSync(envPath)) continue;
2581
2769
  try {
2582
- const content = fs14.readFileSync(envPath, "utf-8");
2770
+ const content = fs15.readFileSync(envPath, "utf-8");
2583
2771
  const vars = [];
2584
2772
  for (const line of content.split("\n")) {
2585
2773
  const trimmed = line.trim();
@@ -2627,9 +2815,9 @@ function runQaDiscovery(cwd) {
2627
2815
  }
2628
2816
  function detectDevServer(cwd, out) {
2629
2817
  try {
2630
- const pkg = JSON.parse(fs15.readFileSync(path14.join(cwd, "package.json"), "utf-8"));
2818
+ const pkg = JSON.parse(fs16.readFileSync(path15.join(cwd, "package.json"), "utf-8"));
2631
2819
  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";
2820
+ 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
2821
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
2634
2822
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
2635
2823
  else if (allDeps.vite) out.devPort = 5173;
@@ -2639,8 +2827,8 @@ function detectDevServer(cwd, out) {
2639
2827
  function scanFrontendRoutes(cwd, out) {
2640
2828
  const appDirs = ["src/app", "app"];
2641
2829
  for (const appDir of appDirs) {
2642
- const full = path14.join(cwd, appDir);
2643
- if (!fs15.existsSync(full)) continue;
2830
+ const full = path15.join(cwd, appDir);
2831
+ if (!fs16.existsSync(full)) continue;
2644
2832
  walkFrontendRoutes(full, "", out);
2645
2833
  break;
2646
2834
  }
@@ -2648,7 +2836,7 @@ function scanFrontendRoutes(cwd, out) {
2648
2836
  function walkFrontendRoutes(dir, prefix, out) {
2649
2837
  let entries;
2650
2838
  try {
2651
- entries = fs15.readdirSync(dir, { withFileTypes: true });
2839
+ entries = fs16.readdirSync(dir, { withFileTypes: true });
2652
2840
  } catch {
2653
2841
  return;
2654
2842
  }
@@ -2665,7 +2853,7 @@ function walkFrontendRoutes(dir, prefix, out) {
2665
2853
  if (entry.name === "node_modules" || entry.name === ".next") continue;
2666
2854
  let segment = entry.name;
2667
2855
  if (segment.startsWith("(") && segment.endsWith(")")) {
2668
- walkFrontendRoutes(path14.join(dir, entry.name), prefix, out);
2856
+ walkFrontendRoutes(path15.join(dir, entry.name), prefix, out);
2669
2857
  continue;
2670
2858
  }
2671
2859
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -2673,7 +2861,7 @@ function walkFrontendRoutes(dir, prefix, out) {
2673
2861
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
2674
2862
  segment = `:${segment.slice(1, -1)}`;
2675
2863
  }
2676
- walkFrontendRoutes(path14.join(dir, entry.name), `${prefix}/${segment}`, out);
2864
+ walkFrontendRoutes(path15.join(dir, entry.name), `${prefix}/${segment}`, out);
2677
2865
  }
2678
2866
  }
2679
2867
  function detectAuthFiles(cwd, out) {
@@ -2690,23 +2878,23 @@ function detectAuthFiles(cwd, out) {
2690
2878
  "src/app/api/oauth"
2691
2879
  ];
2692
2880
  for (const c of candidates) {
2693
- if (fs15.existsSync(path14.join(cwd, c))) out.authFiles.push(c);
2881
+ if (fs16.existsSync(path15.join(cwd, c))) out.authFiles.push(c);
2694
2882
  }
2695
2883
  }
2696
2884
  function detectRoles(cwd, out) {
2697
2885
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
2698
2886
  for (const rp of rolePaths) {
2699
- const dir = path14.join(cwd, rp);
2700
- if (!fs15.existsSync(dir)) continue;
2887
+ const dir = path15.join(cwd, rp);
2888
+ if (!fs16.existsSync(dir)) continue;
2701
2889
  let files;
2702
2890
  try {
2703
- files = fs15.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
2891
+ files = fs16.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
2704
2892
  } catch {
2705
2893
  continue;
2706
2894
  }
2707
2895
  for (const f of files) {
2708
2896
  try {
2709
- const content = fs15.readFileSync(path14.join(dir, f), "utf-8").slice(0, 5e3);
2897
+ const content = fs16.readFileSync(path15.join(dir, f), "utf-8").slice(0, 5e3);
2710
2898
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
2711
2899
  if (roleMatches) {
2712
2900
  for (const m of roleMatches) {
@@ -2852,7 +3040,7 @@ var discoverQaContext = async (ctx) => {
2852
3040
  };
2853
3041
 
2854
3042
  // src/scripts/dispatch.ts
2855
- import { execFileSync as execFileSync7 } from "child_process";
3043
+ import { execFileSync as execFileSync9 } from "child_process";
2856
3044
  var API_TIMEOUT_MS3 = 3e4;
2857
3045
  var dispatch = async (ctx, _profile, _agentResult, args) => {
2858
3046
  const next = args?.next;
@@ -2888,7 +3076,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
2888
3076
  const sub = usePr ? "pr" : "issue";
2889
3077
  const body = `@kody ${next}`;
2890
3078
  try {
2891
- execFileSync7("gh", [sub, "comment", String(targetNumber), "--body", body], {
3079
+ execFileSync9("gh", [sub, "comment", String(targetNumber), "--body", body], {
2892
3080
  timeout: API_TIMEOUT_MS3,
2893
3081
  cwd: ctx.cwd,
2894
3082
  stdio: ["ignore", "pipe", "pipe"]
@@ -2908,7 +3096,7 @@ function parsePr(url) {
2908
3096
  }
2909
3097
 
2910
3098
  // src/scripts/dispatchClassified.ts
2911
- import { execFileSync as execFileSync8 } from "child_process";
3099
+ import { execFileSync as execFileSync10 } from "child_process";
2912
3100
  var API_TIMEOUT_MS4 = 3e4;
2913
3101
  var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
2914
3102
  var dispatchClassified = async (ctx) => {
@@ -2917,7 +3105,7 @@ var dispatchClassified = async (ctx) => {
2917
3105
  const classification = ctx.data.classification;
2918
3106
  if (!classification || !VALID_CLASSES2.has(classification)) return;
2919
3107
  try {
2920
- execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
3108
+ execFileSync10("gh", ["issue", "comment", String(issueNumber), "--body", `@kody ${classification}`], {
2921
3109
  cwd: ctx.cwd,
2922
3110
  timeout: API_TIMEOUT_MS4,
2923
3111
  stdio: ["ignore", "pipe", "pipe"]
@@ -2937,11 +3125,11 @@ function failedAction(reason) {
2937
3125
  }
2938
3126
 
2939
3127
  // src/scripts/dispatchMissionFileTicks.ts
2940
- import * as fs17 from "fs";
2941
- import * as path16 from "path";
3128
+ import * as fs18 from "fs";
3129
+ import * as path17 from "path";
2942
3130
 
2943
3131
  // src/issue.ts
2944
- import { execFileSync as execFileSync9 } from "child_process";
3132
+ import { execFileSync as execFileSync11 } from "child_process";
2945
3133
  var API_TIMEOUT_MS5 = 3e4;
2946
3134
  function ghToken2() {
2947
3135
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
@@ -2949,7 +3137,7 @@ function ghToken2() {
2949
3137
  function gh2(args, options) {
2950
3138
  const token = ghToken2();
2951
3139
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
2952
- return execFileSync9("gh", args, {
3140
+ return execFileSync11("gh", args, {
2953
3141
  encoding: "utf-8",
2954
3142
  timeout: API_TIMEOUT_MS5,
2955
3143
  cwd: options?.cwd,
@@ -3251,8 +3439,8 @@ var ContentsApiBackend = class {
3251
3439
  };
3252
3440
 
3253
3441
  // src/scripts/missionState/localFileBackend.ts
3254
- import * as fs16 from "fs";
3255
- import * as path15 from "path";
3442
+ import * as fs17 from "fs";
3443
+ import * as path16 from "path";
3256
3444
  var LocalFileBackend = class {
3257
3445
  name = "local-file";
3258
3446
  cwd;
@@ -3267,7 +3455,7 @@ var LocalFileBackend = class {
3267
3455
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
3268
3456
  this.cwd = opts.cwd;
3269
3457
  this.missionsDir = opts.missionsDir;
3270
- this.absDir = path15.join(opts.cwd, opts.missionsDir);
3458
+ this.absDir = path16.join(opts.cwd, opts.missionsDir);
3271
3459
  this.owner = opts.owner;
3272
3460
  this.repo = opts.repo;
3273
3461
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -3282,7 +3470,7 @@ var LocalFileBackend = class {
3282
3470
  `);
3283
3471
  return;
3284
3472
  }
3285
- fs16.mkdirSync(this.absDir, { recursive: true });
3473
+ fs17.mkdirSync(this.absDir, { recursive: true });
3286
3474
  const prefix = this.cacheKeyPrefix();
3287
3475
  const probeKey = `${prefix}probe-${Date.now()}`;
3288
3476
  try {
@@ -3311,7 +3499,7 @@ var LocalFileBackend = class {
3311
3499
  `);
3312
3500
  return;
3313
3501
  }
3314
- if (!fs16.existsSync(this.absDir)) {
3502
+ if (!fs17.existsSync(this.absDir)) {
3315
3503
  return;
3316
3504
  }
3317
3505
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -3327,11 +3515,11 @@ var LocalFileBackend = class {
3327
3515
  }
3328
3516
  load(slug) {
3329
3517
  const relPath = stateFilePath(this.missionsDir, slug);
3330
- const absPath = path15.join(this.cwd, relPath);
3331
- if (!fs16.existsSync(absPath)) {
3518
+ const absPath = path16.join(this.cwd, relPath);
3519
+ if (!fs17.existsSync(absPath)) {
3332
3520
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
3333
3521
  }
3334
- const raw = fs16.readFileSync(absPath, "utf-8");
3522
+ const raw = fs17.readFileSync(absPath, "utf-8");
3335
3523
  let parsed;
3336
3524
  try {
3337
3525
  parsed = JSON.parse(raw);
@@ -3348,10 +3536,10 @@ var LocalFileBackend = class {
3348
3536
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
3349
3537
  return false;
3350
3538
  }
3351
- const absPath = path15.join(this.cwd, loaded.path);
3352
- fs16.mkdirSync(path15.dirname(absPath), { recursive: true });
3539
+ const absPath = path16.join(this.cwd, loaded.path);
3540
+ fs17.mkdirSync(path16.dirname(absPath), { recursive: true });
3353
3541
  const body = JSON.stringify(next, null, 2) + "\n";
3354
- fs16.writeFileSync(absPath, body, "utf-8");
3542
+ fs17.writeFileSync(absPath, body, "utf-8");
3355
3543
  return true;
3356
3544
  }
3357
3545
  cacheKeyPrefix() {
@@ -3428,7 +3616,7 @@ var dispatchMissionFileTicks = async (ctx, _profile, args) => {
3428
3616
  await backend.hydrate();
3429
3617
  }
3430
3618
  try {
3431
- const slugs = listMissionSlugs(path16.join(ctx.cwd, missionsDir));
3619
+ const slugs = listMissionSlugs(path17.join(ctx.cwd, missionsDir));
3432
3620
  ctx.data.missionSlugCount = slugs.length;
3433
3621
  if (slugs.length === 0) {
3434
3622
  process.stdout.write(`[missions] no mission files in ${missionsDir}
@@ -3476,10 +3664,10 @@ var dispatchMissionFileTicks = async (ctx, _profile, args) => {
3476
3664
  }
3477
3665
  };
3478
3666
  function listMissionSlugs(absDir) {
3479
- if (!fs17.existsSync(absDir)) return [];
3667
+ if (!fs18.existsSync(absDir)) return [];
3480
3668
  let entries;
3481
3669
  try {
3482
- entries = fs17.readdirSync(absDir, { withFileTypes: true });
3670
+ entries = fs18.readdirSync(absDir, { withFileTypes: true });
3483
3671
  } catch {
3484
3672
  return [];
3485
3673
  }
@@ -3848,7 +4036,7 @@ function collectExpectedTests(raw) {
3848
4036
  }
3849
4037
 
3850
4038
  // src/scripts/finishFlow.ts
3851
- import { execFileSync as execFileSync10 } from "child_process";
4039
+ import { execFileSync as execFileSync12 } from "child_process";
3852
4040
 
3853
4041
  // src/lifecycleLabels.ts
3854
4042
  var KODY_NAMESPACE = "kody";
@@ -4001,7 +4189,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
4001
4189
  **PR:** ${state.core.prUrl}` : "";
4002
4190
  const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
4003
4191
  try {
4004
- execFileSync10("gh", ["issue", "comment", String(issueNumber), "--body", body], {
4192
+ execFileSync12("gh", ["issue", "comment", String(issueNumber), "--body", body], {
4005
4193
  timeout: API_TIMEOUT_MS6,
4006
4194
  cwd: ctx.cwd,
4007
4195
  stdio: ["ignore", "pipe", "pipe"]
@@ -4015,7 +4203,7 @@ var finishFlow = async (ctx, _profile, _agentResult, args) => {
4015
4203
  };
4016
4204
 
4017
4205
  // src/branch.ts
4018
- import { execFileSync as execFileSync11 } from "child_process";
4206
+ import { execFileSync as execFileSync13 } from "child_process";
4019
4207
  var UncommittedChangesError = class extends Error {
4020
4208
  constructor(branch) {
4021
4209
  super(`Uncommitted changes on branch '${branch}' \u2014 refusing to run to protect work in progress`);
@@ -4025,7 +4213,7 @@ var UncommittedChangesError = class extends Error {
4025
4213
  branch;
4026
4214
  };
4027
4215
  function git2(args, cwd) {
4028
- return execFileSync11("git", args, {
4216
+ return execFileSync13("git", args, {
4029
4217
  encoding: "utf-8",
4030
4218
  timeout: 3e4,
4031
4219
  cwd,
@@ -4050,7 +4238,7 @@ function checkoutPrBranch(prNumber, cwd) {
4050
4238
  SKIP_HOOKS: "1",
4051
4239
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
4052
4240
  };
4053
- execFileSync11("gh", ["pr", "checkout", String(prNumber)], {
4241
+ execFileSync13("gh", ["pr", "checkout", String(prNumber)], {
4054
4242
  cwd,
4055
4243
  env,
4056
4244
  stdio: ["ignore", "pipe", "pipe"],
@@ -4117,8 +4305,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd) {
4117
4305
  }
4118
4306
 
4119
4307
  // src/gha.ts
4120
- import { execFileSync as execFileSync12 } from "child_process";
4121
- import * as fs18 from "fs";
4308
+ import { execFileSync as execFileSync14 } from "child_process";
4309
+ import * as fs19 from "fs";
4122
4310
  function getRunUrl() {
4123
4311
  const server = process.env.GITHUB_SERVER_URL;
4124
4312
  const repo = process.env.GITHUB_REPOSITORY;
@@ -4129,10 +4317,10 @@ function getRunUrl() {
4129
4317
  function reactToTriggerComment(cwd) {
4130
4318
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
4131
4319
  const eventPath = process.env.GITHUB_EVENT_PATH;
4132
- if (!eventPath || !fs18.existsSync(eventPath)) return;
4320
+ if (!eventPath || !fs19.existsSync(eventPath)) return;
4133
4321
  let event = null;
4134
4322
  try {
4135
- event = JSON.parse(fs18.readFileSync(eventPath, "utf-8"));
4323
+ event = JSON.parse(fs19.readFileSync(eventPath, "utf-8"));
4136
4324
  } catch {
4137
4325
  return;
4138
4326
  }
@@ -4160,7 +4348,7 @@ function reactToTriggerComment(cwd) {
4160
4348
  for (let attempt = 0; attempt < 3; attempt++) {
4161
4349
  if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
4162
4350
  try {
4163
- execFileSync12("gh", args, opts);
4351
+ execFileSync14("gh", args, opts);
4164
4352
  return;
4165
4353
  } catch (err) {
4166
4354
  lastErr = err;
@@ -4173,13 +4361,13 @@ function reactToTriggerComment(cwd) {
4173
4361
  }
4174
4362
  function sleepMs(ms) {
4175
4363
  try {
4176
- execFileSync12("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
4364
+ execFileSync14("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
4177
4365
  } catch {
4178
4366
  }
4179
4367
  }
4180
4368
 
4181
4369
  // src/workflow.ts
4182
- import { execFileSync as execFileSync13 } from "child_process";
4370
+ import { execFileSync as execFileSync15 } from "child_process";
4183
4371
  var GH_TIMEOUT_MS = 3e4;
4184
4372
  function ghToken3() {
4185
4373
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
@@ -4187,7 +4375,7 @@ function ghToken3() {
4187
4375
  function gh3(args, cwd) {
4188
4376
  const token = ghToken3();
4189
4377
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
4190
- return execFileSync13("gh", args, {
4378
+ return execFileSync15("gh", args, {
4191
4379
  encoding: "utf-8",
4192
4380
  timeout: GH_TIMEOUT_MS,
4193
4381
  cwd,
@@ -4371,23 +4559,23 @@ function tryPostPr2(prNumber, body, cwd) {
4371
4559
  }
4372
4560
 
4373
4561
  // src/scripts/initFlow.ts
4374
- import { execFileSync as execFileSync14 } from "child_process";
4375
- import * as fs20 from "fs";
4376
- import * as path18 from "path";
4562
+ import { execFileSync as execFileSync16 } from "child_process";
4563
+ import * as fs21 from "fs";
4564
+ import * as path19 from "path";
4377
4565
 
4378
4566
  // src/scripts/loadQaGuide.ts
4379
- import * as fs19 from "fs";
4380
- import * as path17 from "path";
4567
+ import * as fs20 from "fs";
4568
+ import * as path18 from "path";
4381
4569
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
4382
4570
  var loadQaGuide = async (ctx) => {
4383
- const full = path17.join(ctx.cwd, QA_GUIDE_REL_PATH);
4384
- if (!fs19.existsSync(full)) {
4571
+ const full = path18.join(ctx.cwd, QA_GUIDE_REL_PATH);
4572
+ if (!fs20.existsSync(full)) {
4385
4573
  ctx.data.qaGuide = "";
4386
4574
  ctx.data.qaGuidePath = "";
4387
4575
  return;
4388
4576
  }
4389
4577
  try {
4390
- ctx.data.qaGuide = fs19.readFileSync(full, "utf-8");
4578
+ ctx.data.qaGuide = fs20.readFileSync(full, "utf-8");
4391
4579
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
4392
4580
  } catch {
4393
4581
  ctx.data.qaGuide = "";
@@ -4397,9 +4585,9 @@ var loadQaGuide = async (ctx) => {
4397
4585
 
4398
4586
  // src/scripts/initFlow.ts
4399
4587
  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";
4588
+ if (fs21.existsSync(path19.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
4589
+ if (fs21.existsSync(path19.join(cwd, "yarn.lock"))) return "yarn";
4590
+ if (fs21.existsSync(path19.join(cwd, "bun.lockb"))) return "bun";
4403
4591
  return "npm";
4404
4592
  }
4405
4593
  function qualityCommandsFor(pm) {
@@ -4412,7 +4600,7 @@ function qualityCommandsFor(pm) {
4412
4600
  function detectOwnerRepo(cwd) {
4413
4601
  let url;
4414
4602
  try {
4415
- url = execFileSync14("git", ["remote", "get-url", "origin"], {
4603
+ url = execFileSync16("git", ["remote", "get-url", "origin"], {
4416
4604
  cwd,
4417
4605
  encoding: "utf-8",
4418
4606
  stdio: ["ignore", "pipe", "pipe"]
@@ -4497,7 +4685,7 @@ jobs:
4497
4685
  `;
4498
4686
  function defaultBranchFromGit(cwd) {
4499
4687
  try {
4500
- const ref = execFileSync14("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
4688
+ const ref = execFileSync16("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
4501
4689
  cwd,
4502
4690
  encoding: "utf-8",
4503
4691
  stdio: ["ignore", "pipe", "pipe"]
@@ -4505,7 +4693,7 @@ function defaultBranchFromGit(cwd) {
4505
4693
  return ref.replace("refs/remotes/origin/", "");
4506
4694
  } catch {
4507
4695
  try {
4508
- return execFileSync14("git", ["branch", "--show-current"], {
4696
+ return execFileSync16("git", ["branch", "--show-current"], {
4509
4697
  cwd,
4510
4698
  encoding: "utf-8",
4511
4699
  stdio: ["ignore", "pipe", "pipe"]
@@ -4521,33 +4709,33 @@ function performInit(cwd, force) {
4521
4709
  const pm = detectPackageManager(cwd);
4522
4710
  const ownerRepo = detectOwnerRepo(cwd);
4523
4711
  const defaultBranch = defaultBranchFromGit(cwd);
4524
- const configPath = path18.join(cwd, "kody.config.json");
4525
- if (fs20.existsSync(configPath) && !force) {
4712
+ const configPath = path19.join(cwd, "kody.config.json");
4713
+ if (fs21.existsSync(configPath) && !force) {
4526
4714
  skipped.push("kody.config.json");
4527
4715
  } else {
4528
4716
  const cfg = makeConfig(pm, ownerRepo, defaultBranch);
4529
- fs20.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
4717
+ fs21.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
4530
4718
  `);
4531
4719
  wrote.push("kody.config.json");
4532
4720
  }
4533
- const workflowDir = path18.join(cwd, ".github", "workflows");
4534
- const workflowPath = path18.join(workflowDir, "kody.yml");
4535
- if (fs20.existsSync(workflowPath) && !force) {
4721
+ const workflowDir = path19.join(cwd, ".github", "workflows");
4722
+ const workflowPath = path19.join(workflowDir, "kody.yml");
4723
+ if (fs21.existsSync(workflowPath) && !force) {
4536
4724
  skipped.push(".github/workflows/kody.yml");
4537
4725
  } else {
4538
- fs20.mkdirSync(workflowDir, { recursive: true });
4539
- fs20.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
4726
+ fs21.mkdirSync(workflowDir, { recursive: true });
4727
+ fs21.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
4540
4728
  wrote.push(".github/workflows/kody.yml");
4541
4729
  }
4542
- const hasUi = fs20.existsSync(path18.join(cwd, "src/app")) || fs20.existsSync(path18.join(cwd, "app")) || fs20.existsSync(path18.join(cwd, "pages"));
4730
+ const hasUi = fs21.existsSync(path19.join(cwd, "src/app")) || fs21.existsSync(path19.join(cwd, "app")) || fs21.existsSync(path19.join(cwd, "pages"));
4543
4731
  if (hasUi) {
4544
- const qaGuidePath = path18.join(cwd, QA_GUIDE_REL_PATH);
4545
- if (fs20.existsSync(qaGuidePath) && !force) {
4732
+ const qaGuidePath = path19.join(cwd, QA_GUIDE_REL_PATH);
4733
+ if (fs21.existsSync(qaGuidePath) && !force) {
4546
4734
  skipped.push(QA_GUIDE_REL_PATH);
4547
4735
  } else {
4548
- fs20.mkdirSync(path18.dirname(qaGuidePath), { recursive: true });
4736
+ fs21.mkdirSync(path19.dirname(qaGuidePath), { recursive: true });
4549
4737
  const discovery = runQaDiscovery(cwd);
4550
- fs20.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
4738
+ fs21.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
4551
4739
  wrote.push(QA_GUIDE_REL_PATH);
4552
4740
  }
4553
4741
  }
@@ -4559,12 +4747,12 @@ function performInit(cwd, force) {
4559
4747
  continue;
4560
4748
  }
4561
4749
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
4562
- const target = path18.join(workflowDir, `kody-${exe.name}.yml`);
4563
- if (fs20.existsSync(target) && !force) {
4750
+ const target = path19.join(workflowDir, `kody-${exe.name}.yml`);
4751
+ if (fs21.existsSync(target) && !force) {
4564
4752
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
4565
4753
  continue;
4566
4754
  }
4567
- fs20.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
4755
+ fs21.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
4568
4756
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
4569
4757
  }
4570
4758
  let labels;
@@ -4702,8 +4890,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
4702
4890
  };
4703
4891
 
4704
4892
  // src/scripts/loadMissionFromFile.ts
4705
- import * as fs21 from "fs";
4706
- import * as path19 from "path";
4893
+ import * as fs22 from "fs";
4894
+ import * as path20 from "path";
4707
4895
  var loadMissionFromFile = async (ctx, _profile, args) => {
4708
4896
  const missionsDir = String(args?.missionsDir ?? ".kody/missions");
4709
4897
  const slugArg = String(args?.slugArg ?? "mission");
@@ -4711,11 +4899,11 @@ var loadMissionFromFile = async (ctx, _profile, args) => {
4711
4899
  if (!slug) {
4712
4900
  throw new Error(`loadMissionFromFile: ctx.args.${slugArg} must be a non-empty slug`);
4713
4901
  }
4714
- const absPath = path19.join(ctx.cwd, missionsDir, `${slug}.md`);
4715
- if (!fs21.existsSync(absPath)) {
4902
+ const absPath = path20.join(ctx.cwd, missionsDir, `${slug}.md`);
4903
+ if (!fs22.existsSync(absPath)) {
4716
4904
  throw new Error(`loadMissionFromFile: mission file not found: ${absPath}`);
4717
4905
  }
4718
- const raw = fs21.readFileSync(absPath, "utf-8");
4906
+ const raw = fs22.readFileSync(absPath, "utf-8");
4719
4907
  const { title, body } = parseMissionFile(raw, slug);
4720
4908
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, missionsDir });
4721
4909
  const loaded = await backend.load(slug);
@@ -4849,16 +5037,16 @@ var loadTaskState = async (ctx) => {
4849
5037
  };
4850
5038
 
4851
5039
  // src/scripts/loadVaultContext.ts
4852
- import * as fs22 from "fs";
4853
- import * as path20 from "path";
5040
+ import * as fs23 from "fs";
5041
+ import * as path21 from "path";
4854
5042
  var VAULT_DIR_RELATIVE = ".kody/vault";
4855
5043
  var MAX_PAGES = 8;
4856
5044
  var PER_PAGE_MAX_BYTES = 4e3;
4857
5045
  var TOTAL_MAX_BYTES2 = 24e3;
4858
5046
  var TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
4859
5047
  var loadVaultContext = async (ctx) => {
4860
- const vaultAbs = path20.join(ctx.cwd, VAULT_DIR_RELATIVE);
4861
- if (!fs22.existsSync(vaultAbs)) {
5048
+ const vaultAbs = path21.join(ctx.cwd, VAULT_DIR_RELATIVE);
5049
+ if (!fs23.existsSync(vaultAbs)) {
4862
5050
  ctx.data.vaultContext = "";
4863
5051
  return;
4864
5052
  }
@@ -4883,21 +5071,21 @@ function collectPages(vaultAbs) {
4883
5071
  walkMd(vaultAbs, (file) => {
4884
5072
  let stat;
4885
5073
  try {
4886
- stat = fs22.statSync(file);
5074
+ stat = fs23.statSync(file);
4887
5075
  } catch {
4888
5076
  return;
4889
5077
  }
4890
5078
  let raw;
4891
5079
  try {
4892
- raw = fs22.readFileSync(file, "utf-8");
5080
+ raw = fs23.readFileSync(file, "utf-8");
4893
5081
  } catch {
4894
5082
  return;
4895
5083
  }
4896
5084
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
4897
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path20.basename(file, ".md");
5085
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path21.basename(file, ".md");
4898
5086
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
4899
5087
  out.push({
4900
- relPath: path20.relative(vaultAbs, file),
5088
+ relPath: path21.relative(vaultAbs, file),
4901
5089
  title,
4902
5090
  updated,
4903
5091
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
@@ -4965,16 +5153,16 @@ function walkMd(root, visit) {
4965
5153
  const dir = stack.pop();
4966
5154
  let names;
4967
5155
  try {
4968
- names = fs22.readdirSync(dir);
5156
+ names = fs23.readdirSync(dir);
4969
5157
  } catch {
4970
5158
  continue;
4971
5159
  }
4972
5160
  for (const name of names) {
4973
5161
  if (name.startsWith(".")) continue;
4974
- const full = path20.join(dir, name);
5162
+ const full = path21.join(dir, name);
4975
5163
  let stat;
4976
5164
  try {
4977
- stat = fs22.statSync(full);
5165
+ stat = fs23.statSync(full);
4978
5166
  } catch {
4979
5167
  continue;
4980
5168
  }
@@ -4996,16 +5184,16 @@ var markFlowSuccess = async (ctx) => {
4996
5184
  };
4997
5185
 
4998
5186
  // src/scripts/memorizeFlow.ts
4999
- import { execFileSync as execFileSync15 } from "child_process";
5000
- import * as fs23 from "fs";
5001
- import * as path21 from "path";
5187
+ import { execFileSync as execFileSync17 } from "child_process";
5188
+ import * as fs24 from "fs";
5189
+ import * as path22 from "path";
5002
5190
  var VAULT_DIR_RELATIVE2 = ".kody/vault";
5003
5191
  var DEFAULT_LOOKBACK_HOURS = 36;
5004
5192
  var MAX_RECENT_PRS = 25;
5005
5193
  var MAX_VAULT_INDEX_ENTRIES = 200;
5006
5194
  var PR_BODY_TRUNC = 2e3;
5007
5195
  var memorizeFlow = async (ctx) => {
5008
- const vaultAbs = path21.join(ctx.cwd, VAULT_DIR_RELATIVE2);
5196
+ const vaultAbs = path22.join(ctx.cwd, VAULT_DIR_RELATIVE2);
5009
5197
  ensureBranch(ctx, vaultAbs);
5010
5198
  if (ctx.skipAgent) return;
5011
5199
  const sinceIso = computeSinceIso(vaultAbs);
@@ -5015,8 +5203,8 @@ var memorizeFlow = async (ctx) => {
5015
5203
  const recent = fetchRecentPrs(ctx.cwd, sinceIso);
5016
5204
  ctx.data.recentPrs = formatRecentPrs(recent);
5017
5205
  ctx.data.recentPrCount = recent.length;
5018
- if (!fs23.existsSync(vaultAbs)) {
5019
- fs23.mkdirSync(vaultAbs, { recursive: true });
5206
+ if (!fs24.existsSync(vaultAbs)) {
5207
+ fs24.mkdirSync(vaultAbs, { recursive: true });
5020
5208
  }
5021
5209
  ctx.data.vaultIndex = formatVaultIndex(vaultAbs);
5022
5210
  if (recent.length === 0) {
@@ -5048,18 +5236,18 @@ function ensureBranch(ctx, vaultAbs) {
5048
5236
  }
5049
5237
  }
5050
5238
  ctx.data.branch = branch;
5051
- if (!fs23.existsSync(vaultAbs)) {
5052
- fs23.mkdirSync(vaultAbs, { recursive: true });
5239
+ if (!fs24.existsSync(vaultAbs)) {
5240
+ fs24.mkdirSync(vaultAbs, { recursive: true });
5053
5241
  }
5054
5242
  }
5055
5243
  function computeSinceIso(vaultAbs) {
5056
5244
  const fallback = new Date(Date.now() - DEFAULT_LOOKBACK_HOURS * 60 * 60 * 1e3).toISOString();
5057
- if (!fs23.existsSync(vaultAbs)) return fallback;
5245
+ if (!fs24.existsSync(vaultAbs)) return fallback;
5058
5246
  let latest = "";
5059
5247
  walkMd2(vaultAbs, (file) => {
5060
5248
  let raw;
5061
5249
  try {
5062
- raw = fs23.readFileSync(file, "utf-8");
5250
+ raw = fs24.readFileSync(file, "utf-8");
5063
5251
  } catch {
5064
5252
  return;
5065
5253
  }
@@ -5136,10 +5324,10 @@ function formatVaultIndex(vaultAbs) {
5136
5324
  const entries = [];
5137
5325
  walkMd2(vaultAbs, (file) => {
5138
5326
  if (entries.length >= MAX_VAULT_INDEX_ENTRIES) return;
5139
- const rel = path21.relative(vaultAbs, file);
5327
+ const rel = path22.relative(vaultAbs, file);
5140
5328
  let title = rel;
5141
5329
  try {
5142
- const raw = fs23.readFileSync(file, "utf-8");
5330
+ const raw = fs24.readFileSync(file, "utf-8");
5143
5331
  const m = raw.match(/^---\s*\n([\s\S]*?)\n---/);
5144
5332
  const titleMatch = m?.[1]?.match(/^title:\s*(.+)$/m);
5145
5333
  if (titleMatch) title = `${titleMatch[1].trim()} (${rel})`;
@@ -5151,22 +5339,22 @@ function formatVaultIndex(vaultAbs) {
5151
5339
  return entries.join("\n");
5152
5340
  }
5153
5341
  function walkMd2(root, visit) {
5154
- if (!fs23.existsSync(root)) return;
5342
+ if (!fs24.existsSync(root)) return;
5155
5343
  const stack = [root];
5156
5344
  while (stack.length > 0) {
5157
5345
  const dir = stack.pop();
5158
5346
  let names;
5159
5347
  try {
5160
- names = fs23.readdirSync(dir);
5348
+ names = fs24.readdirSync(dir);
5161
5349
  } catch {
5162
5350
  continue;
5163
5351
  }
5164
5352
  for (const name of names) {
5165
5353
  if (name.startsWith(".")) continue;
5166
- const full = path21.join(dir, name);
5354
+ const full = path22.join(dir, name);
5167
5355
  let stat;
5168
5356
  try {
5169
- stat = fs23.statSync(full);
5357
+ stat = fs24.statSync(full);
5170
5358
  } catch {
5171
5359
  continue;
5172
5360
  }
@@ -5179,7 +5367,7 @@ function walkMd2(root, visit) {
5179
5367
  }
5180
5368
  }
5181
5369
  function git3(args, cwd) {
5182
- return execFileSync15("git", args, {
5370
+ return execFileSync17("git", args, {
5183
5371
  encoding: "utf-8",
5184
5372
  timeout: 3e4,
5185
5373
  cwd,
@@ -5189,7 +5377,7 @@ function git3(args, cwd) {
5189
5377
  }
5190
5378
 
5191
5379
  // src/scripts/mergeReleasePr.ts
5192
- import { execFileSync as execFileSync16 } from "child_process";
5380
+ import { execFileSync as execFileSync18 } from "child_process";
5193
5381
  var API_TIMEOUT_MS7 = 6e4;
5194
5382
  var mergeReleasePr = async (ctx) => {
5195
5383
  const state = ctx.data.taskState;
@@ -5208,7 +5396,7 @@ var mergeReleasePr = async (ctx) => {
5208
5396
  process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
5209
5397
  `);
5210
5398
  try {
5211
- const out = execFileSync16("gh", ["pr", "merge", String(prNumber), "--merge"], {
5399
+ const out = execFileSync18("gh", ["pr", "merge", String(prNumber), "--merge"], {
5212
5400
  timeout: API_TIMEOUT_MS7,
5213
5401
  cwd: ctx.cwd,
5214
5402
  stdio: ["ignore", "pipe", "pipe"]
@@ -5663,7 +5851,7 @@ REVIEW_POSTED=https://github.com/${ctx.config.github.owner}/${ctx.config.github.
5663
5851
  };
5664
5852
 
5665
5853
  // src/scripts/recordClassification.ts
5666
- import { execFileSync as execFileSync17 } from "child_process";
5854
+ import { execFileSync as execFileSync19 } from "child_process";
5667
5855
  var API_TIMEOUT_MS8 = 3e4;
5668
5856
  var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
5669
5857
  var recordClassification = async (ctx) => {
@@ -5711,7 +5899,7 @@ function parseClassification(prSummary) {
5711
5899
  }
5712
5900
  function tryAuditComment(issueNumber, body, cwd) {
5713
5901
  try {
5714
- execFileSync17("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5902
+ execFileSync19("gh", ["issue", "comment", String(issueNumber), "--body", body], {
5715
5903
  cwd,
5716
5904
  timeout: API_TIMEOUT_MS8,
5717
5905
  stdio: ["ignore", "pipe", "pipe"]
@@ -5839,7 +6027,7 @@ var resolveArtifacts = async (ctx, profile) => {
5839
6027
  };
5840
6028
 
5841
6029
  // src/scripts/resolveFlow.ts
5842
- import { execFileSync as execFileSync18 } from "child_process";
6030
+ import { execFileSync as execFileSync20 } from "child_process";
5843
6031
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
5844
6032
  var resolveFlow = async (ctx) => {
5845
6033
  const prNumber = ctx.args.pr;
@@ -5909,7 +6097,7 @@ function buildPreferBlock(prefer, baseBranch) {
5909
6097
  }
5910
6098
  function getConflictedFiles(cwd) {
5911
6099
  try {
5912
- const out = execFileSync18("git", ["diff", "--name-only", "--diff-filter=U"], {
6100
+ const out = execFileSync20("git", ["diff", "--name-only", "--diff-filter=U"], {
5913
6101
  encoding: "utf-8",
5914
6102
  cwd,
5915
6103
  env: { ...process.env, HUSKY: "0" }
@@ -5924,7 +6112,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
5924
6112
  let total = 0;
5925
6113
  for (const f of files) {
5926
6114
  try {
5927
- const content = execFileSync18("cat", [f], { encoding: "utf-8", cwd }).toString();
6115
+ const content = execFileSync20("cat", [f], { encoding: "utf-8", cwd }).toString();
5928
6116
  const snippet = `### ${f}
5929
6117
 
5930
6118
  \`\`\`
@@ -6025,7 +6213,7 @@ var resolvePreviewUrl = async (ctx) => {
6025
6213
  };
6026
6214
 
6027
6215
  // src/scripts/revertFlow.ts
6028
- import { execFileSync as execFileSync19 } from "child_process";
6216
+ import { execFileSync as execFileSync21 } from "child_process";
6029
6217
  var SHA_RE = /^[0-9a-f]{4,40}$/i;
6030
6218
  var revertFlow = async (ctx) => {
6031
6219
  const prNumber = ctx.args.pr;
@@ -6107,7 +6295,7 @@ function buildPrSummary(resolved) {
6107
6295
  return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
6108
6296
  }
6109
6297
  function git4(args, cwd) {
6110
- return execFileSync19("git", args, {
6298
+ return execFileSync21("git", args, {
6111
6299
  encoding: "utf-8",
6112
6300
  timeout: 3e4,
6113
6301
  cwd,
@@ -6117,7 +6305,7 @@ function git4(args, cwd) {
6117
6305
  }
6118
6306
  function isAncestorOfHead(sha, cwd) {
6119
6307
  try {
6120
- execFileSync19("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
6308
+ execFileSync21("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
6121
6309
  cwd,
6122
6310
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
6123
6311
  stdio: ["ignore", "ignore", "ignore"]
@@ -6270,11 +6458,11 @@ var skipAgent = async (ctx) => {
6270
6458
  };
6271
6459
 
6272
6460
  // src/scripts/stageMergeConflicts.ts
6273
- import { execFileSync as execFileSync20 } from "child_process";
6461
+ import { execFileSync as execFileSync22 } from "child_process";
6274
6462
  var stageMergeConflicts = async (ctx) => {
6275
6463
  if (ctx.data.agentDone === false) return;
6276
6464
  try {
6277
- execFileSync20("git", ["add", "-A"], {
6465
+ execFileSync22("git", ["add", "-A"], {
6278
6466
  cwd: ctx.cwd,
6279
6467
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
6280
6468
  stdio: "pipe"
@@ -6284,7 +6472,7 @@ var stageMergeConflicts = async (ctx) => {
6284
6472
  };
6285
6473
 
6286
6474
  // src/scripts/startFlow.ts
6287
- import { execFileSync as execFileSync21 } from "child_process";
6475
+ import { execFileSync as execFileSync23 } from "child_process";
6288
6476
  var API_TIMEOUT_MS9 = 3e4;
6289
6477
  var startFlow = async (ctx, profile, _agentResult, args) => {
6290
6478
  const entry = args?.entry;
@@ -6318,7 +6506,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
6318
6506
  const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
6319
6507
  const body = `@kody ${next}`;
6320
6508
  try {
6321
- execFileSync21("gh", [sub, "comment", String(targetNumber), "--body", body], {
6509
+ execFileSync23("gh", [sub, "comment", String(targetNumber), "--body", body], {
6322
6510
  timeout: API_TIMEOUT_MS9,
6323
6511
  cwd,
6324
6512
  stdio: ["ignore", "pipe", "pipe"]
@@ -6332,7 +6520,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
6332
6520
  }
6333
6521
 
6334
6522
  // src/scripts/syncFlow.ts
6335
- import { execFileSync as execFileSync22 } from "child_process";
6523
+ import { execFileSync as execFileSync24 } from "child_process";
6336
6524
  var syncFlow = async (ctx, _profile, args) => {
6337
6525
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
6338
6526
  const prNumber = ctx.args.pr;
@@ -6404,7 +6592,7 @@ function bail2(ctx, prNumber, reason) {
6404
6592
  }
6405
6593
  function revParseHead(cwd) {
6406
6594
  try {
6407
- return execFileSync22("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
6595
+ return execFileSync24("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
6408
6596
  } catch {
6409
6597
  return "";
6410
6598
  }
@@ -6412,9 +6600,9 @@ function revParseHead(cwd) {
6412
6600
  function pushBranch(branch, cwd) {
6413
6601
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
6414
6602
  try {
6415
- execFileSync22("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
6603
+ execFileSync24("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
6416
6604
  } catch {
6417
- execFileSync22("git", ["push", "--force-with-lease", "-u", "origin", branch], {
6605
+ execFileSync24("git", ["push", "--force-with-lease", "-u", "origin", branch], {
6418
6606
  cwd,
6419
6607
  env,
6420
6608
  stdio: ["ignore", "pipe", "pipe"]
@@ -6525,7 +6713,7 @@ var verify = async (ctx) => {
6525
6713
  };
6526
6714
 
6527
6715
  // src/scripts/waitForCi.ts
6528
- import { execFileSync as execFileSync23 } from "child_process";
6716
+ import { execFileSync as execFileSync25 } from "child_process";
6529
6717
  var API_TIMEOUT_MS10 = 3e4;
6530
6718
  var waitForCi = async (ctx, _profile, _agentResult, args) => {
6531
6719
  const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
@@ -6540,17 +6728,17 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
6540
6728
  return;
6541
6729
  }
6542
6730
  const fixCiAttempts = state?.core.attempts?.["fix-ci"] ?? 0;
6543
- await sleep(initialWaitSeconds * 1e3);
6731
+ await sleep2(initialWaitSeconds * 1e3);
6544
6732
  const deadline = Date.now() + timeoutMinutes * 6e4;
6545
6733
  let lastSummary = "";
6546
6734
  while (Date.now() < deadline) {
6547
6735
  const rows = fetchChecks(prNumber, ctx.cwd);
6548
6736
  if (rows === null) {
6549
- await sleep(pollSeconds * 1e3);
6737
+ await sleep2(pollSeconds * 1e3);
6550
6738
  continue;
6551
6739
  }
6552
6740
  if (rows.length === 0) {
6553
- await sleep(pollSeconds * 1e3);
6741
+ await sleep2(pollSeconds * 1e3);
6554
6742
  continue;
6555
6743
  }
6556
6744
  const summary = summarize(rows);
@@ -6593,7 +6781,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
6593
6781
  tryPostPr7(prNumber, `\u2705 kody waitForCi: all ${rows.length} checks green on PR #${prNumber}`, ctx.cwd);
6594
6782
  return;
6595
6783
  }
6596
- await sleep(pollSeconds * 1e3);
6784
+ await sleep2(pollSeconds * 1e3);
6597
6785
  }
6598
6786
  ctx.data.action = mkAction("CI_TIMEOUT", {
6599
6787
  reason: `CI did not complete within ${timeoutMinutes} minutes`,
@@ -6603,7 +6791,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
6603
6791
  };
6604
6792
  function fetchChecks(prNumber, cwd) {
6605
6793
  try {
6606
- const raw = execFileSync23("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
6794
+ const raw = execFileSync25("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
6607
6795
  encoding: "utf-8",
6608
6796
  timeout: API_TIMEOUT_MS10,
6609
6797
  cwd,
@@ -6645,7 +6833,7 @@ function tryPostPr7(prNumber, body, cwd) {
6645
6833
  } catch {
6646
6834
  }
6647
6835
  }
6648
- function sleep(ms) {
6836
+ function sleep2(ms) {
6649
6837
  return new Promise((res) => setTimeout(res, ms));
6650
6838
  }
6651
6839
 
@@ -6775,7 +6963,7 @@ var writeMissionStateFile = async (ctx, _profile, _agentResult, args) => {
6775
6963
  };
6776
6964
 
6777
6965
  // src/scripts/writeRunSummary.ts
6778
- import * as fs24 from "fs";
6966
+ import * as fs25 from "fs";
6779
6967
  var writeRunSummary = async (ctx, profile) => {
6780
6968
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
6781
6969
  if (!summaryPath) return;
@@ -6797,7 +6985,7 @@ var writeRunSummary = async (ctx, profile) => {
6797
6985
  if (reason) lines.push(`- **Reason:** ${reason}`);
6798
6986
  lines.push("");
6799
6987
  try {
6800
- fs24.appendFileSync(summaryPath, `${lines.join("\n")}
6988
+ fs25.appendFileSync(summaryPath, `${lines.join("\n")}
6801
6989
  `);
6802
6990
  } catch {
6803
6991
  }
@@ -6879,7 +7067,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
6879
7067
  ]);
6880
7068
 
6881
7069
  // src/tools.ts
6882
- import { execFileSync as execFileSync24 } from "child_process";
7070
+ import { execFileSync as execFileSync26 } from "child_process";
6883
7071
  function verifyCliTools(tools, cwd) {
6884
7072
  const out = [];
6885
7073
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -6912,7 +7100,7 @@ function verifyOne(tool, cwd) {
6912
7100
  }
6913
7101
  function runShell(cmd, cwd, timeoutMs = 3e4) {
6914
7102
  try {
6915
- execFileSync24("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
7103
+ execFileSync26("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
6916
7104
  return true;
6917
7105
  } catch {
6918
7106
  return false;
@@ -6981,9 +7169,9 @@ async function runExecutable(profileName, input) {
6981
7169
  data: {},
6982
7170
  output: { exitCode: 0 }
6983
7171
  };
6984
- const ndjsonDir = path22.join(input.cwd, ".kody");
7172
+ const ndjsonDir = path23.join(input.cwd, ".kody");
6985
7173
  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);
7174
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path23.isAbsolute(p) ? p : path23.resolve(profile.dir, p)).filter((p) => p.length > 0);
6987
7175
  const syntheticPath = ctx.data.syntheticPluginPath;
6988
7176
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
6989
7177
  return runAgent({
@@ -7078,17 +7266,17 @@ async function runExecutable(profileName, input) {
7078
7266
  function resolveProfilePath(profileName) {
7079
7267
  const found = resolveExecutable(profileName);
7080
7268
  if (found) return found;
7081
- const here = path22.dirname(new URL(import.meta.url).pathname);
7269
+ const here = path23.dirname(new URL(import.meta.url).pathname);
7082
7270
  const candidates = [
7083
- path22.join(here, "executables", profileName, "profile.json"),
7271
+ path23.join(here, "executables", profileName, "profile.json"),
7084
7272
  // same-dir sibling (dev)
7085
- path22.join(here, "..", "executables", profileName, "profile.json"),
7273
+ path23.join(here, "..", "executables", profileName, "profile.json"),
7086
7274
  // up one (prod: dist/bin → dist/executables)
7087
- path22.join(here, "..", "src", "executables", profileName, "profile.json")
7275
+ path23.join(here, "..", "src", "executables", profileName, "profile.json")
7088
7276
  // fallback
7089
7277
  ];
7090
7278
  for (const c of candidates) {
7091
- if (fs25.existsSync(c)) return c;
7279
+ if (fs26.existsSync(c)) return c;
7092
7280
  }
7093
7281
  return candidates[0];
7094
7282
  }
@@ -7192,8 +7380,8 @@ function resolveShellTimeoutMs(entry) {
7192
7380
  var SIGKILL_GRACE_MS = 5e3;
7193
7381
  async function runShellEntry(entry, ctx, profile) {
7194
7382
  const shellName = entry.shell;
7195
- const shellPath = path22.join(profile.dir, shellName);
7196
- if (!fs25.existsSync(shellPath)) {
7383
+ const shellPath = path23.join(profile.dir, shellName);
7384
+ if (!fs26.existsSync(shellPath)) {
7197
7385
  ctx.skipAgent = true;
7198
7386
  ctx.output.exitCode = 99;
7199
7387
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -7314,6 +7502,7 @@ async function runContainerLoop(profile, ctx, input) {
7314
7502
  const issueNumber = ctx.args.issue;
7315
7503
  let currentIdx = 0;
7316
7504
  let iteration = 0;
7505
+ let knownPrUrl;
7317
7506
  while (currentIdx >= 0 && currentIdx < children.length) {
7318
7507
  iteration++;
7319
7508
  if (iteration > CONTAINER_MAX_ITERATIONS) {
@@ -7328,6 +7517,7 @@ async function runContainerLoop(profile, ctx, input) {
7328
7517
  process.stderr.write(`[kody container] step ${iteration}: invoking ${child.exec}
7329
7518
  `);
7330
7519
  const priorState = readContainerState(ctx, child, reader);
7520
+ if (priorState.core?.prUrl) knownPrUrl = priorState.core.prUrl;
7331
7521
  const priorAction = priorState.executables?.[child.exec]?.lastAction;
7332
7522
  let actionType;
7333
7523
  if (priorAction && /_COMPLETED$/i.test(priorAction.type)) {
@@ -7337,8 +7527,7 @@ async function runContainerLoop(profile, ctx, input) {
7337
7527
  } else {
7338
7528
  let cliArgs;
7339
7529
  if (child.target === "pr") {
7340
- const prUrl = priorState.core?.prUrl;
7341
- const prNumber = prUrl ? parsePrNumber4(prUrl) : null;
7530
+ const prNumber = knownPrUrl ? parsePrNumber4(knownPrUrl) : null;
7342
7531
  if (!prNumber) {
7343
7532
  const reason = `container child "${child.exec}" needs --pr but state.core.prUrl is unset`;
7344
7533
  process.stderr.write(`[kody container] aborting: ${reason}
@@ -7384,6 +7573,7 @@ async function runContainerLoop(profile, ctx, input) {
7384
7573
  return;
7385
7574
  }
7386
7575
  const next = readContainerState(ctx, child, reader);
7576
+ if (next.core?.prUrl) knownPrUrl = next.core.prUrl;
7387
7577
  ctx.data.taskState = next;
7388
7578
  const actionFromState = next.core?.lastOutcome?.type;
7389
7579
  actionType = actionFromState ?? (childOut.exitCode === 0 ? "RUN_COMPLETED" : "RUN_FAILED");
@@ -7420,16 +7610,25 @@ async function runContainerLoop(profile, ctx, input) {
7420
7610
  currentIdx = nextIdx;
7421
7611
  }
7422
7612
  }
7423
- function readContainerState(ctx, _child, reader) {
7613
+ function readContainerState(ctx, child, reader) {
7424
7614
  const issueNumber = ctx.args.issue;
7615
+ const cached = ctx.data.taskState;
7616
+ const prUrl = cached?.core?.prUrl;
7617
+ const prNumber = prUrl ? parsePrNumber4(prUrl) : null;
7618
+ if (child.target === "pr" && prNumber) {
7619
+ try {
7620
+ return reader("pr", prNumber, ctx.cwd);
7621
+ } catch {
7622
+ }
7623
+ }
7425
7624
  if (issueNumber !== void 0) {
7426
7625
  try {
7427
7626
  return reader("issue", issueNumber, ctx.cwd);
7428
7627
  } catch {
7429
7628
  }
7430
7629
  }
7431
- if (ctx.data.taskState && typeof ctx.data.taskState === "object") {
7432
- return ctx.data.taskState;
7630
+ if (cached && typeof cached === "object") {
7631
+ return cached;
7433
7632
  }
7434
7633
  return {
7435
7634
  schemaVersion: 1,
@@ -7552,14 +7751,14 @@ function resolveAuthToken(env = process.env) {
7552
7751
  return token;
7553
7752
  }
7554
7753
  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";
7754
+ if (fs27.existsSync(path24.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7755
+ if (fs27.existsSync(path24.join(cwd, "yarn.lock"))) return "yarn";
7756
+ if (fs27.existsSync(path24.join(cwd, "bun.lockb"))) return "bun";
7558
7757
  return "npm";
7559
7758
  }
7560
7759
  function shellOut(cmd, args, cwd, stream = true) {
7561
7760
  try {
7562
- execFileSync25(cmd, args, {
7761
+ execFileSync27(cmd, args, {
7563
7762
  cwd,
7564
7763
  stdio: stream ? "inherit" : "pipe",
7565
7764
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -7572,7 +7771,7 @@ function shellOut(cmd, args, cwd, stream = true) {
7572
7771
  }
7573
7772
  function isOnPath(bin) {
7574
7773
  try {
7575
- execFileSync25("which", [bin], { stdio: "pipe" });
7774
+ execFileSync27("which", [bin], { stdio: "pipe" });
7576
7775
  return true;
7577
7776
  } catch {
7578
7777
  return false;
@@ -7606,7 +7805,7 @@ function installLitellmIfNeeded(cwd) {
7606
7805
  } catch {
7607
7806
  }
7608
7807
  try {
7609
- execFileSync25("python3", ["-c", "import litellm"], { stdio: "pipe" });
7808
+ execFileSync27("python3", ["-c", "import litellm"], { stdio: "pipe" });
7610
7809
  process.stdout.write("\u2192 kody: litellm already installed\n");
7611
7810
  return 0;
7612
7811
  } catch {
@@ -7616,16 +7815,16 @@ function installLitellmIfNeeded(cwd) {
7616
7815
  }
7617
7816
  function configureGitIdentity(cwd) {
7618
7817
  try {
7619
- const name = execFileSync25("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
7818
+ const name = execFileSync27("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
7620
7819
  if (name) return;
7621
7820
  } catch {
7622
7821
  }
7623
7822
  try {
7624
- execFileSync25("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
7823
+ execFileSync27("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
7625
7824
  } catch {
7626
7825
  }
7627
7826
  try {
7628
- execFileSync25("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
7827
+ execFileSync27("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
7629
7828
  cwd,
7630
7829
  stdio: "pipe"
7631
7830
  });
@@ -7634,11 +7833,11 @@ function configureGitIdentity(cwd) {
7634
7833
  }
7635
7834
  function postFailureTail(issueNumber, cwd, reason) {
7636
7835
  if (!issueNumber) return;
7637
- const logPath = path23.join(cwd, ".kody", "last-run.jsonl");
7836
+ const logPath = path24.join(cwd, ".kody", "last-run.jsonl");
7638
7837
  let tail = "";
7639
7838
  try {
7640
- if (fs26.existsSync(logPath)) {
7641
- const content = fs26.readFileSync(logPath, "utf-8");
7839
+ if (fs27.existsSync(logPath)) {
7840
+ const content = fs27.readFileSync(logPath, "utf-8");
7642
7841
  tail = content.slice(-3e3);
7643
7842
  }
7644
7843
  } catch {
@@ -7663,7 +7862,7 @@ async function runCi(argv) {
7663
7862
  return 0;
7664
7863
  }
7665
7864
  const args = parseCiArgs(argv);
7666
- const cwd = args.cwd ? path23.resolve(args.cwd) : process.cwd();
7865
+ const cwd = args.cwd ? path24.resolve(args.cwd) : process.cwd();
7667
7866
  let earlyConfig;
7668
7867
  try {
7669
7868
  earlyConfig = loadConfig(cwd);
@@ -7673,9 +7872,9 @@ async function runCi(argv) {
7673
7872
  const eventName = process.env.GITHUB_EVENT_NAME;
7674
7873
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
7675
7874
  let manualWorkflowDispatch = false;
7676
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs26.existsSync(dispatchEventPath)) {
7875
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs27.existsSync(dispatchEventPath)) {
7677
7876
  try {
7678
- const evt = JSON.parse(fs26.readFileSync(dispatchEventPath, "utf-8"));
7877
+ const evt = JSON.parse(fs27.readFileSync(dispatchEventPath, "utf-8"));
7679
7878
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
7680
7879
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
7681
7880
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -7890,15 +8089,15 @@ function parseChatArgs(argv, env = process.env) {
7890
8089
  return result;
7891
8090
  }
7892
8091
  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)));
8092
+ const sessionFile = path25.relative(cwd, sessionFilePath(cwd, sessionId));
8093
+ const eventsFile = path25.relative(cwd, eventsFilePath(cwd, sessionId));
8094
+ const paths = [sessionFile, eventsFile].filter((p) => fs28.existsSync(path25.join(cwd, p)));
7896
8095
  if (paths.length === 0) return;
7897
8096
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
7898
8097
  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);
8098
+ execFileSync28("git", ["add", "-f", ...paths], opts);
8099
+ execFileSync28("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
8100
+ execFileSync28("git", ["push", "--quiet", "origin", "HEAD"], opts);
7902
8101
  } catch (err) {
7903
8102
  const msg = err instanceof Error ? err.message : String(err);
7904
8103
  process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
@@ -7930,7 +8129,7 @@ async function runChat(argv) {
7930
8129
  ${CHAT_HELP}`);
7931
8130
  return 64;
7932
8131
  }
7933
- const cwd = args.cwd ? path24.resolve(args.cwd) : process.cwd();
8132
+ const cwd = args.cwd ? path25.resolve(args.cwd) : process.cwd();
7934
8133
  const sessionId = args.sessionId;
7935
8134
  const unpackedSecrets = unpackAllSecrets();
7936
8135
  if (unpackedSecrets > 0) {
@@ -7974,7 +8173,21 @@ ${CHAT_HELP}`);
7974
8173
  const sessionFile = sessionFilePath(cwd, sessionId);
7975
8174
  if (args.initMessage) seedInitialMessage(sessionFile, args.initMessage);
7976
8175
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
8176
+ const meta = readMeta(sessionFile);
7977
8177
  try {
8178
+ if (meta?.mode === "interactive") {
8179
+ const result2 = await runInteractiveMode({
8180
+ sessionId,
8181
+ cwd,
8182
+ model,
8183
+ litellmUrl: litellm?.url ?? null,
8184
+ sink,
8185
+ meta,
8186
+ verbose: args.verbose,
8187
+ quiet: args.quiet
8188
+ });
8189
+ return result2.exitCode;
8190
+ }
7978
8191
  const result = await runChatTurn({
7979
8192
  sessionId,
7980
8193
  sessionFile,