@kody-ade/kody-engine 0.4.156 → 0.4.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin/kody.js +427 -317
  2. package/package.json +1 -1
package/dist/bin/kody.js CHANGED
@@ -650,16 +650,16 @@ var init_issue = __esm({
650
650
  });
651
651
 
652
652
  // src/prompt.ts
653
- import * as fs20 from "fs";
654
- import * as path19 from "path";
653
+ import * as fs21 from "fs";
654
+ import * as path20 from "path";
655
655
  function loadProjectConventions(projectDir) {
656
656
  const out = [];
657
657
  for (const rel of CONVENTION_FILES) {
658
- const abs = path19.join(projectDir, rel);
659
- if (!fs20.existsSync(abs)) continue;
658
+ const abs = path20.join(projectDir, rel);
659
+ if (!fs21.existsSync(abs)) continue;
660
660
  let content;
661
661
  try {
662
- content = fs20.readFileSync(abs, "utf-8");
662
+ content = fs21.readFileSync(abs, "utf-8");
663
663
  } catch {
664
664
  continue;
665
665
  }
@@ -807,28 +807,28 @@ var loadMemoryContext_exports = {};
807
807
  __export(loadMemoryContext_exports, {
808
808
  loadMemoryContext: () => loadMemoryContext
809
809
  });
810
- import * as fs32 from "fs";
811
- import * as path30 from "path";
810
+ import * as fs33 from "fs";
811
+ import * as path31 from "path";
812
812
  function collectPages(memoryAbs) {
813
813
  const out = [];
814
814
  walkMd(memoryAbs, (file) => {
815
815
  let stat;
816
816
  try {
817
- stat = fs32.statSync(file);
817
+ stat = fs33.statSync(file);
818
818
  } catch {
819
819
  return;
820
820
  }
821
821
  let raw;
822
822
  try {
823
- raw = fs32.readFileSync(file, "utf-8");
823
+ raw = fs33.readFileSync(file, "utf-8");
824
824
  } catch {
825
825
  return;
826
826
  }
827
827
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
828
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path30.basename(file, ".md");
828
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path31.basename(file, ".md");
829
829
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
830
830
  out.push({
831
- relPath: path30.relative(memoryAbs, file),
831
+ relPath: path31.relative(memoryAbs, file),
832
832
  title,
833
833
  updated,
834
834
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
@@ -896,16 +896,16 @@ function walkMd(root, visit) {
896
896
  const dir = stack.pop();
897
897
  let names;
898
898
  try {
899
- names = fs32.readdirSync(dir);
899
+ names = fs33.readdirSync(dir);
900
900
  } catch {
901
901
  continue;
902
902
  }
903
903
  for (const name of names) {
904
904
  if (name.startsWith(".")) continue;
905
- const full = path30.join(dir, name);
905
+ const full = path31.join(dir, name);
906
906
  let stat;
907
907
  try {
908
- stat = fs32.statSync(full);
908
+ stat = fs33.statSync(full);
909
909
  } catch {
910
910
  continue;
911
911
  }
@@ -928,8 +928,8 @@ var init_loadMemoryContext = __esm({
928
928
  TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
929
929
  loadMemoryContext = async (ctx) => {
930
930
  if (typeof ctx.data.memoryContext === "string") return;
931
- const memoryAbs = path30.join(ctx.cwd, MEMORY_DIR_RELATIVE);
932
- if (!fs32.existsSync(memoryAbs)) {
931
+ const memoryAbs = path31.join(ctx.cwd, MEMORY_DIR_RELATIVE);
932
+ if (!fs33.existsSync(memoryAbs)) {
933
933
  ctx.data.memoryContext = "";
934
934
  return;
935
935
  }
@@ -1061,7 +1061,7 @@ var init_loadPriorArt = __esm({
1061
1061
  // package.json
1062
1062
  var package_default = {
1063
1063
  name: "@kody-ade/kody-engine",
1064
- version: "0.4.156",
1064
+ version: "0.4.158",
1065
1065
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
1066
1066
  license: "MIT",
1067
1067
  type: "module",
@@ -1118,8 +1118,8 @@ var package_default = {
1118
1118
 
1119
1119
  // src/chat-cli.ts
1120
1120
  import { execFileSync as execFileSync31 } from "child_process";
1121
- import * as fs42 from "fs";
1122
- import * as path38 from "path";
1121
+ import * as fs43 from "fs";
1122
+ import * as path39 from "path";
1123
1123
 
1124
1124
  // src/chat/events.ts
1125
1125
  import * as fs from "fs";
@@ -1185,8 +1185,8 @@ function makeRunId(sessionId, suffix) {
1185
1185
  }
1186
1186
 
1187
1187
  // src/chat/loop.ts
1188
- import * as fs10 from "fs";
1189
- import * as path10 from "path";
1188
+ import * as fs11 from "fs";
1189
+ import * as path11 from "path";
1190
1190
 
1191
1191
  // src/task-artifacts.ts
1192
1192
  import fs2 from "fs";
@@ -2156,6 +2156,65 @@ function seedInitialMessage(file, message) {
2156
2156
  return true;
2157
2157
  }
2158
2158
 
2159
+ // src/chat/attachments.ts
2160
+ import * as fs10 from "fs";
2161
+ import * as path10 from "path";
2162
+ var INLINE_ATTACHMENT_RE = /(?:\[(?:Image|File): ([^\]]*)\]\n)?data:([\w.+-]+\/[\w.+-]+);base64,([A-Za-z0-9+/=]+)/g;
2163
+ var EXT_BY_MIME = {
2164
+ "image/png": "png",
2165
+ "image/jpeg": "jpg",
2166
+ "image/jpg": "jpg",
2167
+ "image/gif": "gif",
2168
+ "image/webp": "webp",
2169
+ "image/bmp": "bmp",
2170
+ "image/svg+xml": "svg",
2171
+ "image/avif": "avif"
2172
+ };
2173
+ function extFor(mime) {
2174
+ return EXT_BY_MIME[mime.toLowerCase()] ?? mime.split("/")[1]?.replace(/[^\w]/g, "") ?? "bin";
2175
+ }
2176
+ function attachmentsDir(cwd, sessionId) {
2177
+ return path10.join(cwd, ".kody", "tmp", "attachments", sessionId);
2178
+ }
2179
+ function prepareAttachments(turns, cwd, sessionId) {
2180
+ const lastUserIdx = (() => {
2181
+ for (let i = turns.length - 1; i >= 0; i--) {
2182
+ if (turns[i].role === "user") return i;
2183
+ }
2184
+ return -1;
2185
+ })();
2186
+ const imagePaths = [];
2187
+ let imageCounter = 0;
2188
+ const rewritten = turns.map((turn, idx) => {
2189
+ if (!turn.content.includes("base64,")) return turn;
2190
+ const isLastUser = idx === lastUserIdx;
2191
+ const newContent = turn.content.replace(
2192
+ INLINE_ATTACHMENT_RE,
2193
+ (_match, label, mime, data) => {
2194
+ const name = (label ?? "").trim() || "attachment";
2195
+ const isImage = mime.toLowerCase().startsWith("image/");
2196
+ if (!isLastUser || !isImage) {
2197
+ return `[${isImage ? "Image" : "File"}: ${name}${isLastUser ? "" : " \u2014 omitted from history"}]`;
2198
+ }
2199
+ try {
2200
+ const dir = attachmentsDir(cwd, sessionId);
2201
+ fs10.mkdirSync(dir, { recursive: true });
2202
+ const filePath = path10.join(dir, `${imageCounter}.${extFor(mime)}`);
2203
+ fs10.writeFileSync(filePath, Buffer.from(data, "base64"));
2204
+ imageCounter += 1;
2205
+ imagePaths.push(filePath);
2206
+ return `[Image "${name}" is attached \u2014 saved to ${filePath}. Use the Read tool on that exact path to view it before answering.]`;
2207
+ } catch {
2208
+ return `[Image: ${name} (could not be materialised)]`;
2209
+ }
2210
+ }
2211
+ );
2212
+ if (newContent === turn.content) return turn;
2213
+ return { ...turn, content: newContent };
2214
+ });
2215
+ return { turns: rewritten, imagePaths };
2216
+ }
2217
+
2159
2218
  // src/chat/loop.ts
2160
2219
  var CHAT_SYSTEM_PROMPT = [
2161
2220
  "You are Kody, an AI assistant for the Kody Operations Dashboard. Reply to the",
@@ -2259,7 +2318,7 @@ function buildExecutableCatalog() {
2259
2318
  const entries = [];
2260
2319
  for (const { name, profilePath } of discovered) {
2261
2320
  try {
2262
- const raw = JSON.parse(fs10.readFileSync(profilePath, "utf-8"));
2321
+ const raw = JSON.parse(fs11.readFileSync(profilePath, "utf-8"));
2263
2322
  const describe = typeof raw.describe === "string" ? raw.describe : "";
2264
2323
  const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
2265
2324
  entries.push({ name, describe: firstSentence.trim() });
@@ -2294,6 +2353,7 @@ async function runChatTurn(opts) {
2294
2353
  await emit(opts.sink, "chat.error", opts.sessionId, "error", { error });
2295
2354
  return { exitCode: 64, error };
2296
2355
  }
2356
+ const { turns: promptTurns, imagePaths } = prepareAttachments(turns, opts.cwd, opts.sessionId);
2297
2357
  const basePrompt = opts.systemPrompt ?? CHAT_SYSTEM_PROMPT;
2298
2358
  const catalog = buildExecutableCatalog();
2299
2359
  const taskArtifactsPaths = prepareTaskArtifactsDir(opts.cwd, opts.sessionId);
@@ -2306,16 +2366,25 @@ async function runChatTurn(opts) {
2306
2366
  const memoryBlock = readMemoryIndexBlock(opts.cwd);
2307
2367
  const instructionsBlock = readInstructionsBlock(opts.cwd);
2308
2368
  const crossRepoBlock = opts.reposRoot ? CROSS_REPO_PROMPT : null;
2369
+ const imageBlock = imagePaths.length > 0 ? [
2370
+ "# Attached images",
2371
+ "The user attached one or more images on this turn. They are saved as",
2372
+ "files in this workspace and referenced inline in the conversation as",
2373
+ '`[Image "\u2026" is attached \u2014 saved to <path>]`. You CAN view them: call',
2374
+ "the Read tool on each of those exact paths BEFORE answering. Never tell",
2375
+ "the user you cannot see images \u2014 Read the file and describe what you see."
2376
+ ].join("\n") : null;
2309
2377
  const systemPrompt = [
2310
2378
  basePrompt,
2311
2379
  contextBlock,
2312
2380
  memoryBlock,
2313
2381
  instructionsBlock,
2314
2382
  crossRepoBlock,
2383
+ imageBlock,
2315
2384
  catalog,
2316
2385
  artifactAddendum
2317
2386
  ].filter((s) => typeof s === "string" && s.length > 0).join("\n\n");
2318
- const prompt = buildPrompt(turns);
2387
+ const prompt = buildPrompt(promptTurns);
2319
2388
  let progressSeq = 0;
2320
2389
  const invoke = opts.invokeAgent ?? ((p) => runAgent({
2321
2390
  prompt: p,
@@ -2417,10 +2486,10 @@ async function emit(sink, type, sessionId, suffix, payload) {
2417
2486
  var MEMORY_INDEX_REL = ".kody/memory/INDEX.md";
2418
2487
  var MAX_INDEX_BYTES = 8e3;
2419
2488
  function readMemoryIndexBlock(cwd) {
2420
- const indexPath = path10.join(cwd, MEMORY_INDEX_REL);
2489
+ const indexPath = path11.join(cwd, MEMORY_INDEX_REL);
2421
2490
  let raw;
2422
2491
  try {
2423
- raw = fs10.readFileSync(indexPath, "utf-8");
2492
+ raw = fs11.readFileSync(indexPath, "utf-8");
2424
2493
  } catch {
2425
2494
  return "";
2426
2495
  }
@@ -2438,17 +2507,17 @@ function readMemoryIndexBlock(cwd) {
2438
2507
  var CONTEXT_DIR_REL = ".kody/context";
2439
2508
  var MAX_CONTEXT_BYTES = 12e3;
2440
2509
  function readContextBlock(cwd) {
2441
- const dir = path10.join(cwd, CONTEXT_DIR_REL);
2510
+ const dir = path11.join(cwd, CONTEXT_DIR_REL);
2442
2511
  let files;
2443
2512
  try {
2444
- files = fs10.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
2513
+ files = fs11.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
2445
2514
  } catch {
2446
2515
  return "";
2447
2516
  }
2448
2517
  const sections = [];
2449
2518
  for (const file of files) {
2450
2519
  try {
2451
- const content = fs10.readFileSync(path10.join(dir, file), "utf-8").trim();
2520
+ const content = fs11.readFileSync(path11.join(dir, file), "utf-8").trim();
2452
2521
  if (content) sections.push(`### ${file.replace(/\.md$/, "")}
2453
2522
 
2454
2523
  ${content}`);
@@ -2469,10 +2538,10 @@ ${content}`);
2469
2538
  var INSTRUCTIONS_REL = ".kody/instructions.md";
2470
2539
  var MAX_INSTRUCTIONS_BYTES = 8e3;
2471
2540
  function readInstructionsBlock(cwd) {
2472
- const instructionsPath = path10.join(cwd, INSTRUCTIONS_REL);
2541
+ const instructionsPath = path11.join(cwd, INSTRUCTIONS_REL);
2473
2542
  let raw;
2474
2543
  try {
2475
- raw = fs10.readFileSync(instructionsPath, "utf-8");
2544
+ raw = fs11.readFileSync(instructionsPath, "utf-8");
2476
2545
  } catch {
2477
2546
  return "";
2478
2547
  }
@@ -2491,8 +2560,8 @@ function readInstructionsBlock(cwd) {
2491
2560
  // src/chat/modes/interactive.ts
2492
2561
  init_issue();
2493
2562
  import { execFileSync as execFileSync3 } from "child_process";
2494
- import * as fs11 from "fs";
2495
- import * as path11 from "path";
2563
+ import * as fs12 from "fs";
2564
+ import * as path12 from "path";
2496
2565
 
2497
2566
  // src/chat/inbox.ts
2498
2567
  import { execFileSync as execFileSync2 } from "child_process";
@@ -2651,9 +2720,9 @@ function findNextUserTurn(turns, fromIdx) {
2651
2720
  return -1;
2652
2721
  }
2653
2722
  function commitTurn(cwd, sessionId, _verbose) {
2654
- const sessionRel = path11.relative(cwd, sessionFilePath(cwd, sessionId));
2655
- const eventsRel = path11.relative(cwd, eventsFilePath(cwd, sessionId));
2656
- const rels = [sessionRel, eventsRel].filter((p) => fs11.existsSync(path11.join(cwd, p)));
2723
+ const sessionRel = path12.relative(cwd, sessionFilePath(cwd, sessionId));
2724
+ const eventsRel = path12.relative(cwd, eventsFilePath(cwd, sessionId));
2725
+ const rels = [sessionRel, eventsRel].filter((p) => fs12.existsSync(path12.join(cwd, p)));
2657
2726
  if (rels.length === 0) return;
2658
2727
  const repository = process.env.GITHUB_REPOSITORY;
2659
2728
  if (!repository) {
@@ -2665,8 +2734,8 @@ function commitTurn(cwd, sessionId, _verbose) {
2665
2734
  }
2666
2735
  const branch = defaultBranch(cwd) ?? "main";
2667
2736
  for (const rel of rels) {
2668
- const repoPath = rel.split(path11.sep).join("/");
2669
- const localText = fs11.readFileSync(path11.join(cwd, rel), "utf-8");
2737
+ const repoPath = rel.split(path12.sep).join("/");
2738
+ const localText = fs12.readFileSync(path12.join(cwd, rel), "utf-8");
2670
2739
  putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
2671
2740
  }
2672
2741
  }
@@ -2756,8 +2825,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
2756
2825
 
2757
2826
  // src/kody-cli.ts
2758
2827
  import { execFileSync as execFileSync30 } from "child_process";
2759
- import * as fs41 from "fs";
2760
- import * as path37 from "path";
2828
+ import * as fs42 from "fs";
2829
+ import * as path38 from "path";
2761
2830
 
2762
2831
  // src/app-auth.ts
2763
2832
  import { createSign } from "crypto";
@@ -2830,7 +2899,7 @@ async function mintAppInstallationToken(creds) {
2830
2899
  }
2831
2900
 
2832
2901
  // src/dispatch.ts
2833
- import * as fs12 from "fs";
2902
+ import * as fs13 from "fs";
2834
2903
 
2835
2904
  // src/cron-match.ts
2836
2905
  var FIELD_BOUNDS = [
@@ -2914,10 +2983,10 @@ function autoDispatch(opts) {
2914
2983
  }
2915
2984
  const eventName = process.env.GITHUB_EVENT_NAME;
2916
2985
  const eventPath = process.env.GITHUB_EVENT_PATH;
2917
- if (!eventName || !eventPath || !fs12.existsSync(eventPath)) return null;
2986
+ if (!eventName || !eventPath || !fs13.existsSync(eventPath)) return null;
2918
2987
  let event = {};
2919
2988
  try {
2920
- event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
2989
+ event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
2921
2990
  } catch {
2922
2991
  return null;
2923
2992
  }
@@ -2935,7 +3004,7 @@ function autoDispatch(opts) {
2935
3004
  const authorLogin = String(event.comment?.user?.login ?? "");
2936
3005
  const authorType = String(event.comment?.user?.type ?? "");
2937
3006
  if (!rawBody.toLowerCase().includes("@kody")) return null;
2938
- if (authorLogin === "kody-bot" || authorType === "Bot") return null;
3007
+ const isBotAuthor = authorLogin === "kody-bot" || authorType === "Bot";
2939
3008
  if (!associationAllowed(event, opts?.config)) return null;
2940
3009
  const body = rawBody.toLowerCase();
2941
3010
  const targetNum = Number(event.issue?.number ?? 0);
@@ -2962,6 +3031,13 @@ function autoDispatch(opts) {
2962
3031
  if (!executable && !firstToken) {
2963
3032
  executable = isPr ? opts?.config?.defaultPrExecutable ?? "fix" : opts?.config?.defaultExecutable ?? null;
2964
3033
  }
3034
+ if (isBotAuthor && !consumedFirstToken) {
3035
+ process.stderr.write(
3036
+ `[kody] dispatch: ignoring bot comment without an explicit command (author=${authorLogin || authorType}, firstToken=${firstToken ?? "<none>"})
3037
+ `
3038
+ );
3039
+ return null;
3040
+ }
2965
3041
  if (!executable) {
2966
3042
  const profileMissing = aliased ? getProfileInputs(aliased) === null : true;
2967
3043
  process.stderr.write(
@@ -2991,7 +3067,7 @@ function autoDispatchTyped(opts) {
2991
3067
  if (legacy) return { kind: "route", ...legacy };
2992
3068
  const eventName = process.env.GITHUB_EVENT_NAME;
2993
3069
  const eventPath = process.env.GITHUB_EVENT_PATH;
2994
- if (!eventName || !eventPath || !fs12.existsSync(eventPath)) {
3070
+ if (!eventName || !eventPath || !fs13.existsSync(eventPath)) {
2995
3071
  return { kind: "silent", reason: "no GHA event context" };
2996
3072
  }
2997
3073
  if (eventName !== "issue_comment") {
@@ -2999,7 +3075,7 @@ function autoDispatchTyped(opts) {
2999
3075
  }
3000
3076
  let event = {};
3001
3077
  try {
3002
- event = JSON.parse(fs12.readFileSync(eventPath, "utf-8"));
3078
+ event = JSON.parse(fs13.readFileSync(eventPath, "utf-8"));
3003
3079
  } catch {
3004
3080
  return { kind: "silent", reason: "GHA event payload unreadable" };
3005
3081
  }
@@ -3037,7 +3113,7 @@ function dispatchScheduledWatches(opts) {
3037
3113
  for (const exe of listExecutables()) {
3038
3114
  let raw;
3039
3115
  try {
3040
- raw = fs12.readFileSync(exe.profilePath, "utf-8");
3116
+ raw = fs13.readFileSync(exe.profilePath, "utf-8");
3041
3117
  } catch {
3042
3118
  continue;
3043
3119
  }
@@ -3157,8 +3233,8 @@ function coerceBare(spec, value) {
3157
3233
 
3158
3234
  // src/executor.ts
3159
3235
  import { execFileSync as execFileSync29, spawn as spawn9 } from "child_process";
3160
- import * as fs40 from "fs";
3161
- import * as path36 from "path";
3236
+ import * as fs41 from "fs";
3237
+ import * as path37 from "path";
3162
3238
 
3163
3239
  // src/discipline.ts
3164
3240
  var DISCIPLINE = `# Working discipline (applies to this entire task)
@@ -3209,8 +3285,8 @@ init_events();
3209
3285
  init_issue();
3210
3286
 
3211
3287
  // src/profile.ts
3212
- import * as fs13 from "fs";
3213
- import * as path12 from "path";
3288
+ import * as fs14 from "fs";
3289
+ import * as path13 from "path";
3214
3290
 
3215
3291
  // src/profile-error.ts
3216
3292
  var ProfileError = class extends Error {
@@ -3378,12 +3454,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
3378
3454
  "preloadContext"
3379
3455
  ]);
3380
3456
  function loadProfile(profilePath) {
3381
- if (!fs13.existsSync(profilePath)) {
3457
+ if (!fs14.existsSync(profilePath)) {
3382
3458
  throw new ProfileError(profilePath, "file not found");
3383
3459
  }
3384
3460
  let raw;
3385
3461
  try {
3386
- raw = JSON.parse(fs13.readFileSync(profilePath, "utf-8"));
3462
+ raw = JSON.parse(fs14.readFileSync(profilePath, "utf-8"));
3387
3463
  } catch (err) {
3388
3464
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
3389
3465
  }
@@ -3394,7 +3470,7 @@ function loadProfile(profilePath) {
3394
3470
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
3395
3471
  if (unknownKeys.length > 0) {
3396
3472
  process.stderr.write(
3397
- `[kody profile] ${path12.basename(path12.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
3473
+ `[kody profile] ${path13.basename(path13.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
3398
3474
  `
3399
3475
  );
3400
3476
  }
@@ -3455,7 +3531,7 @@ function loadProfile(profilePath) {
3455
3531
  // Phase 5 in-process handoff opt-in. Default false; containers
3456
3532
  // flip to true after end-to-end verification.
3457
3533
  preloadContext: r.preloadContext === true,
3458
- dir: path12.dirname(profilePath)
3534
+ dir: path13.dirname(profilePath)
3459
3535
  };
3460
3536
  if (lifecycle) {
3461
3537
  applyLifecycle(profile, profilePath);
@@ -3826,9 +3902,9 @@ function errMsg(err) {
3826
3902
 
3827
3903
  // src/litellm.ts
3828
3904
  import { execFileSync as execFileSync4, spawn as spawn3 } from "child_process";
3829
- import * as fs14 from "fs";
3905
+ import * as fs15 from "fs";
3830
3906
  import * as os2 from "os";
3831
- import * as path13 from "path";
3907
+ import * as path14 from "path";
3832
3908
  async function checkLitellmHealth(url) {
3833
3909
  try {
3834
3910
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -3880,13 +3956,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3880
3956
  let child;
3881
3957
  let logPath;
3882
3958
  const spawnProxy = () => {
3883
- const configPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3884
- fs14.writeFileSync(configPath, generateLitellmConfigYaml(model));
3959
+ const configPath = path14.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3960
+ fs15.writeFileSync(configPath, generateLitellmConfigYaml(model));
3885
3961
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
3886
- const nextLogPath = path13.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3887
- const outFd = fs14.openSync(nextLogPath, "w");
3962
+ const nextLogPath = path14.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3963
+ const outFd = fs15.openSync(nextLogPath, "w");
3888
3964
  child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
3889
- fs14.closeSync(outFd);
3965
+ fs15.closeSync(outFd);
3890
3966
  logPath = nextLogPath;
3891
3967
  };
3892
3968
  const waitForHealth = async () => {
@@ -3900,7 +3976,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3900
3976
  const readLogTail = () => {
3901
3977
  if (!logPath) return "";
3902
3978
  try {
3903
- return fs14.readFileSync(logPath, "utf-8").slice(-2e3);
3979
+ return fs15.readFileSync(logPath, "utf-8").slice(-2e3);
3904
3980
  } catch {
3905
3981
  return "";
3906
3982
  }
@@ -3939,10 +4015,10 @@ ${tail}`
3939
4015
  return { url, kill: killChild, ensureHealthy };
3940
4016
  }
3941
4017
  function readDotenvApiKeys(projectDir) {
3942
- const dotenvPath = path13.join(projectDir, ".env");
3943
- if (!fs14.existsSync(dotenvPath)) return {};
4018
+ const dotenvPath = path14.join(projectDir, ".env");
4019
+ if (!fs15.existsSync(dotenvPath)) return {};
3944
4020
  const result = {};
3945
- for (const rawLine of fs14.readFileSync(dotenvPath, "utf-8").split("\n")) {
4021
+ for (const rawLine of fs15.readFileSync(dotenvPath, "utf-8").split("\n")) {
3946
4022
  const line = rawLine.trim();
3947
4023
  if (!line || line.startsWith("#")) continue;
3948
4024
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -3965,25 +4041,25 @@ function stripBlockingEnv(env) {
3965
4041
  }
3966
4042
 
3967
4043
  // src/subagents.ts
3968
- import * as fs16 from "fs";
3969
- import * as path15 from "path";
4044
+ import * as fs17 from "fs";
4045
+ import * as path16 from "path";
3970
4046
 
3971
4047
  // src/scripts/buildSyntheticPlugin.ts
3972
- import * as fs15 from "fs";
4048
+ import * as fs16 from "fs";
3973
4049
  import * as os3 from "os";
3974
- import * as path14 from "path";
4050
+ import * as path15 from "path";
3975
4051
  function getPluginsCatalogRoot() {
3976
- const here = path14.dirname(new URL(import.meta.url).pathname);
4052
+ const here = path15.dirname(new URL(import.meta.url).pathname);
3977
4053
  const candidates = [
3978
- path14.join(here, "..", "plugins"),
4054
+ path15.join(here, "..", "plugins"),
3979
4055
  // dev: src/scripts → src/plugins
3980
- path14.join(here, "..", "..", "plugins"),
4056
+ path15.join(here, "..", "..", "plugins"),
3981
4057
  // built: dist/scripts → dist/plugins
3982
- path14.join(here, "..", "..", "src", "plugins")
4058
+ path15.join(here, "..", "..", "src", "plugins")
3983
4059
  // fallback
3984
4060
  ];
3985
4061
  for (const c of candidates) {
3986
- if (fs15.existsSync(c) && fs15.statSync(c).isDirectory()) return c;
4062
+ if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
3987
4063
  }
3988
4064
  return candidates[0];
3989
4065
  }
@@ -3993,45 +4069,45 @@ var buildSyntheticPlugin = async (ctx, profile) => {
3993
4069
  if (!needsSynthetic) return;
3994
4070
  const catalog = getPluginsCatalogRoot();
3995
4071
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3996
- const root = path14.join(os3.tmpdir(), `kody-synth-${runId}`);
3997
- fs15.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
4072
+ const root = path15.join(os3.tmpdir(), `kody-synth-${runId}`);
4073
+ fs16.mkdirSync(path15.join(root, ".claude-plugin"), { recursive: true });
3998
4074
  const resolvePart = (bucket, entry) => {
3999
- const local = path14.join(profile.dir, bucket, entry);
4000
- if (fs15.existsSync(local)) return local;
4001
- const central = path14.join(catalog, bucket, entry);
4002
- if (fs15.existsSync(central)) return central;
4075
+ const local = path15.join(profile.dir, bucket, entry);
4076
+ if (fs16.existsSync(local)) return local;
4077
+ const central = path15.join(catalog, bucket, entry);
4078
+ if (fs16.existsSync(central)) return central;
4003
4079
  throw new Error(
4004
4080
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
4005
4081
  );
4006
4082
  };
4007
4083
  if (cc.skills.length > 0) {
4008
- const dst = path14.join(root, "skills");
4009
- fs15.mkdirSync(dst, { recursive: true });
4084
+ const dst = path15.join(root, "skills");
4085
+ fs16.mkdirSync(dst, { recursive: true });
4010
4086
  for (const name of cc.skills) {
4011
- copyDir(resolvePart("skills", name), path14.join(dst, name));
4087
+ copyDir(resolvePart("skills", name), path15.join(dst, name));
4012
4088
  }
4013
4089
  }
4014
4090
  if (cc.commands.length > 0) {
4015
- const dst = path14.join(root, "commands");
4016
- fs15.mkdirSync(dst, { recursive: true });
4091
+ const dst = path15.join(root, "commands");
4092
+ fs16.mkdirSync(dst, { recursive: true });
4017
4093
  for (const name of cc.commands) {
4018
- fs15.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
4094
+ fs16.copyFileSync(resolvePart("commands", `${name}.md`), path15.join(dst, `${name}.md`));
4019
4095
  }
4020
4096
  }
4021
4097
  if (cc.hooks.length > 0) {
4022
- const dst = path14.join(root, "hooks");
4023
- fs15.mkdirSync(dst, { recursive: true });
4098
+ const dst = path15.join(root, "hooks");
4099
+ fs16.mkdirSync(dst, { recursive: true });
4024
4100
  const merged = { hooks: {} };
4025
4101
  for (const name of cc.hooks) {
4026
4102
  const src = resolvePart("hooks", `${name}.json`);
4027
- const parsed = JSON.parse(fs15.readFileSync(src, "utf-8"));
4103
+ const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
4028
4104
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
4029
4105
  if (!Array.isArray(entries)) continue;
4030
4106
  if (!merged.hooks[event]) merged.hooks[event] = [];
4031
4107
  merged.hooks[event].push(...entries);
4032
4108
  }
4033
4109
  }
4034
- fs15.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4110
+ fs16.writeFileSync(path15.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4035
4111
  `);
4036
4112
  }
4037
4113
  const manifest = {
@@ -4041,17 +4117,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4041
4117
  };
4042
4118
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
4043
4119
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
4044
- fs15.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
4120
+ fs16.writeFileSync(path15.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
4045
4121
  `);
4046
4122
  ctx.data.syntheticPluginPath = root;
4047
4123
  };
4048
4124
  function copyDir(src, dst) {
4049
- fs15.mkdirSync(dst, { recursive: true });
4050
- for (const ent of fs15.readdirSync(src, { withFileTypes: true })) {
4051
- const s = path14.join(src, ent.name);
4052
- const d = path14.join(dst, ent.name);
4125
+ fs16.mkdirSync(dst, { recursive: true });
4126
+ for (const ent of fs16.readdirSync(src, { withFileTypes: true })) {
4127
+ const s = path15.join(src, ent.name);
4128
+ const d = path15.join(dst, ent.name);
4053
4129
  if (ent.isDirectory()) copyDir(s, d);
4054
- else if (ent.isFile()) fs15.copyFileSync(s, d);
4130
+ else if (ent.isFile()) fs16.copyFileSync(s, d);
4055
4131
  }
4056
4132
  }
4057
4133
 
@@ -4068,10 +4144,10 @@ function splitFrontmatter(raw) {
4068
4144
  return { fm, body: (match[2] ?? "").trim() };
4069
4145
  }
4070
4146
  function resolveAgentFile(profileDir, name) {
4071
- const local = path15.join(profileDir, "agents", `${name}.md`);
4072
- if (fs16.existsSync(local)) return local;
4073
- const central = path15.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
4074
- if (fs16.existsSync(central)) return central;
4147
+ const local = path16.join(profileDir, "agents", `${name}.md`);
4148
+ if (fs17.existsSync(local)) return local;
4149
+ const central = path16.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
4150
+ if (fs17.existsSync(central)) return central;
4075
4151
  throw new Error(
4076
4152
  `loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`
4077
4153
  );
@@ -4081,7 +4157,7 @@ function loadSubagents(profile) {
4081
4157
  if (!names || names.length === 0) return void 0;
4082
4158
  const agents = {};
4083
4159
  for (const name of names) {
4084
- const { fm, body } = splitFrontmatter(fs16.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
4160
+ const { fm, body } = splitFrontmatter(fs17.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
4085
4161
  if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
4086
4162
  const def = {
4087
4163
  description: fm.description ?? `Subagent ${name}`,
@@ -4177,8 +4253,8 @@ function pushWithRetry(opts = {}) {
4177
4253
  }
4178
4254
 
4179
4255
  // src/commit.ts
4180
- import * as fs17 from "fs";
4181
- import * as path16 from "path";
4256
+ import * as fs18 from "fs";
4257
+ import * as path17 from "path";
4182
4258
  var FORBIDDEN_PATH_PREFIXES = [
4183
4259
  ".kody/",
4184
4260
  ".kody-engine/",
@@ -4234,18 +4310,18 @@ function tryGit(args, cwd) {
4234
4310
  }
4235
4311
  function abortUnfinishedGitOps(cwd) {
4236
4312
  const aborted = [];
4237
- const gitDir = path16.join(cwd ?? process.cwd(), ".git");
4238
- if (!fs17.existsSync(gitDir)) return aborted;
4239
- if (fs17.existsSync(path16.join(gitDir, "MERGE_HEAD"))) {
4313
+ const gitDir = path17.join(cwd ?? process.cwd(), ".git");
4314
+ if (!fs18.existsSync(gitDir)) return aborted;
4315
+ if (fs18.existsSync(path17.join(gitDir, "MERGE_HEAD"))) {
4240
4316
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
4241
4317
  }
4242
- if (fs17.existsSync(path16.join(gitDir, "CHERRY_PICK_HEAD"))) {
4318
+ if (fs18.existsSync(path17.join(gitDir, "CHERRY_PICK_HEAD"))) {
4243
4319
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
4244
4320
  }
4245
- if (fs17.existsSync(path16.join(gitDir, "REVERT_HEAD"))) {
4321
+ if (fs18.existsSync(path17.join(gitDir, "REVERT_HEAD"))) {
4246
4322
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
4247
4323
  }
4248
- if (fs17.existsSync(path16.join(gitDir, "rebase-merge")) || fs17.existsSync(path16.join(gitDir, "rebase-apply"))) {
4324
+ if (fs18.existsSync(path17.join(gitDir, "rebase-merge")) || fs18.existsSync(path17.join(gitDir, "rebase-apply"))) {
4249
4325
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
4250
4326
  }
4251
4327
  try {
@@ -4301,7 +4377,7 @@ function normalizeCommitMessage(raw) {
4301
4377
  function commitAndPush(branch, agentMessage, cwd) {
4302
4378
  const allChanged = listChangedFiles(cwd);
4303
4379
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
4304
- const mergeHeadExists = fs17.existsSync(path16.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
4380
+ const mergeHeadExists = fs18.existsSync(path17.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
4305
4381
  if (allowedFiles.length === 0 && !mergeHeadExists) {
4306
4382
  return { committed: false, pushed: false, sha: "", message: "" };
4307
4383
  }
@@ -4562,59 +4638,92 @@ function writeTaskState(target, number, state, cwd) {
4562
4638
 
4563
4639
  // src/scripts/advanceFlow.ts
4564
4640
  var API_TIMEOUT_MS3 = 3e4;
4641
+ var FLOW_HOP_CAP = 25;
4642
+ function ghComment(issueNumber, body, cwd, label) {
4643
+ try {
4644
+ execFileSync8("gh", ["issue", "comment", String(issueNumber), "--body", body], {
4645
+ timeout: API_TIMEOUT_MS3,
4646
+ cwd,
4647
+ stdio: ["ignore", "pipe", "pipe"]
4648
+ });
4649
+ } catch (err) {
4650
+ process.stderr.write(
4651
+ `[kody advanceFlow] ${label} on issue #${issueNumber} failed: ${err instanceof Error ? err.message : String(err)}
4652
+ `
4653
+ );
4654
+ }
4655
+ }
4565
4656
  var advanceFlow = async (ctx, profile) => {
4566
4657
  const state = ctx.data.taskState;
4567
4658
  const flow = state?.flow;
4568
4659
  if (!flow?.issueNumber) return;
4660
+ const curState = state;
4661
+ let issueState;
4662
+ try {
4663
+ issueState = readTaskState("issue", flow.issueNumber, ctx.cwd);
4664
+ } catch {
4665
+ issueState = curState;
4666
+ }
4569
4667
  const targetType = ctx.data.commentTargetType;
4570
4668
  const action = ctx.data.action;
4669
+ let nextIssueState = issueState;
4571
4670
  if (targetType === "pr" && action) {
4671
+ nextIssueState = reduce(issueState, profile.name, action, profile.phase);
4672
+ if (state?.core.prUrl && !nextIssueState.core.prUrl) nextIssueState.core.prUrl = state.core.prUrl;
4673
+ }
4674
+ const prevHops = issueState.flow?.hops ?? flow.hops ?? 0;
4675
+ const hops = prevHops + 1;
4676
+ if (hops > FLOW_HOP_CAP) {
4677
+ nextIssueState.flow = void 0;
4572
4678
  try {
4573
- const issueState = readTaskState("issue", flow.issueNumber, ctx.cwd);
4574
- issueState.flow = flow;
4575
- const next = reduce(issueState, profile.name, action, profile.phase);
4576
- if (state?.core.prUrl && !next.core.prUrl) next.core.prUrl = state.core.prUrl;
4577
- next.flow = flow;
4578
- writeTaskState("issue", flow.issueNumber, next, ctx.cwd);
4679
+ writeTaskState("issue", flow.issueNumber, nextIssueState, ctx.cwd);
4579
4680
  } catch (err) {
4580
4681
  process.stderr.write(
4581
- `[kody advanceFlow] failed to mirror action to issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
4682
+ `[kody advanceFlow] failed to clear looping flow on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
4582
4683
  `
4583
4684
  );
4584
4685
  }
4686
+ ghComment(
4687
+ flow.issueNumber,
4688
+ `\u26A0\uFE0F kody: flow \`${flow.name}\` stopped after ${FLOW_HOP_CAP} steps without completing (loop guard). Re-trigger manually if this was intended.`,
4689
+ ctx.cwd,
4690
+ "loop-guard notice"
4691
+ );
4692
+ process.stderr.write(
4693
+ `[kody advanceFlow] flow '${flow.name}' on issue #${flow.issueNumber} hit hop cap ${FLOW_HOP_CAP}; stopping
4694
+ `
4695
+ );
4696
+ return;
4585
4697
  }
4586
- const body = `@kody ${flow.name}`;
4698
+ nextIssueState.flow = { ...flow, hops };
4587
4699
  try {
4588
- execFileSync8("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
4589
- timeout: API_TIMEOUT_MS3,
4590
- cwd: ctx.cwd,
4591
- stdio: ["ignore", "pipe", "pipe"]
4592
- });
4700
+ writeTaskState("issue", flow.issueNumber, nextIssueState, ctx.cwd);
4593
4701
  } catch (err) {
4594
4702
  process.stderr.write(
4595
- `[kody advanceFlow] failed to re-trigger orchestrator on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
4703
+ `[kody advanceFlow] failed to persist hop count on issue #${flow.issueNumber}: ${err instanceof Error ? err.message : String(err)}
4596
4704
  `
4597
4705
  );
4598
4706
  }
4707
+ ghComment(flow.issueNumber, `@kody ${flow.name}`, ctx.cwd, "re-trigger orchestrator");
4599
4708
  };
4600
4709
 
4601
4710
  // src/scripts/brainServe.ts
4602
4711
  init_repoWorkspace();
4603
4712
  import { createServer } from "http";
4604
- import * as fs19 from "fs";
4605
- import * as path18 from "path";
4713
+ import * as fs20 from "fs";
4714
+ import * as path19 from "path";
4606
4715
 
4607
4716
  // src/scripts/brainTurnLog.ts
4608
- import * as fs18 from "fs";
4609
- import * as path17 from "path";
4717
+ import * as fs19 from "fs";
4718
+ import * as path18 from "path";
4610
4719
  var live = /* @__PURE__ */ new Map();
4611
4720
  function eventsPath(dir, chatId) {
4612
- return path17.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
4721
+ return path18.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
4613
4722
  }
4614
4723
  function lastPersistedSeq(dir, chatId) {
4615
4724
  const p = eventsPath(dir, chatId);
4616
- if (!fs18.existsSync(p)) return 0;
4617
- const lines = fs18.readFileSync(p, "utf-8").split("\n").filter(Boolean);
4725
+ if (!fs19.existsSync(p)) return 0;
4726
+ const lines = fs19.readFileSync(p, "utf-8").split("\n").filter(Boolean);
4618
4727
  if (lines.length === 0) return 0;
4619
4728
  try {
4620
4729
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -4624,9 +4733,9 @@ function lastPersistedSeq(dir, chatId) {
4624
4733
  }
4625
4734
  function readSince(dir, chatId, since) {
4626
4735
  const p = eventsPath(dir, chatId);
4627
- if (!fs18.existsSync(p)) return [];
4736
+ if (!fs19.existsSync(p)) return [];
4628
4737
  const out = [];
4629
- for (const line of fs18.readFileSync(p, "utf-8").split("\n")) {
4738
+ for (const line of fs19.readFileSync(p, "utf-8").split("\n")) {
4630
4739
  if (!line) continue;
4631
4740
  try {
4632
4741
  const rec = JSON.parse(line);
@@ -4652,12 +4761,12 @@ function beginTurn(dir, chatId) {
4652
4761
  };
4653
4762
  live.set(chatId, state);
4654
4763
  const p = eventsPath(dir, chatId);
4655
- fs18.mkdirSync(path17.dirname(p), { recursive: true });
4764
+ fs19.mkdirSync(path18.dirname(p), { recursive: true });
4656
4765
  return (event) => {
4657
4766
  state.seq += 1;
4658
4767
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
4659
4768
  try {
4660
- fs18.appendFileSync(p, JSON.stringify(rec) + "\n");
4769
+ fs19.appendFileSync(p, JSON.stringify(rec) + "\n");
4661
4770
  } catch (err) {
4662
4771
  process.stderr.write(
4663
4772
  `[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
@@ -4695,7 +4804,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
4695
4804
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
4696
4805
  };
4697
4806
  try {
4698
- fs18.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
4807
+ fs19.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
4699
4808
  } catch {
4700
4809
  }
4701
4810
  state.status = "ended";
@@ -4921,7 +5030,7 @@ async function handleChatTurn(req, res, chatId, opts) {
4921
5030
  const repo = strField(body, "repo");
4922
5031
  const repoToken = strField(body, "repoToken");
4923
5032
  const sessionFile = sessionFilePath(opts.cwd, chatId);
4924
- fs19.mkdirSync(path18.dirname(sessionFile), { recursive: true });
5033
+ fs20.mkdirSync(path19.dirname(sessionFile), { recursive: true });
4925
5034
  appendTurn(sessionFile, {
4926
5035
  role: "user",
4927
5036
  content: message,
@@ -4968,7 +5077,7 @@ async function handleChatTurn(req, res, chatId, opts) {
4968
5077
  function buildServer(opts) {
4969
5078
  const runTurn = opts.runTurn ?? runChatTurn;
4970
5079
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
4971
- const reposRoot = opts.reposRoot ?? path18.join(path18.dirname(path18.resolve(opts.cwd)), "repos");
5080
+ const reposRoot = opts.reposRoot ?? path19.join(path19.dirname(path19.resolve(opts.cwd)), "repos");
4972
5081
  return createServer(async (req, res) => {
4973
5082
  if (!req.method || !req.url) {
4974
5083
  sendJson(res, 400, { error: "bad request" });
@@ -5223,13 +5332,13 @@ function defaultLabelMap() {
5223
5332
  }
5224
5333
 
5225
5334
  // src/scripts/commitAndPush.ts
5226
- import * as fs21 from "fs";
5227
- import * as path20 from "path";
5335
+ import * as fs22 from "fs";
5336
+ import * as path21 from "path";
5228
5337
  init_events();
5229
5338
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
5230
5339
  function sentinelPathForStage(cwd, profileName) {
5231
5340
  const runId = resolveRunId();
5232
- return path20.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
5341
+ return path21.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
5233
5342
  }
5234
5343
  var commitAndPush2 = async (ctx, profile) => {
5235
5344
  const branch = ctx.data.branch;
@@ -5239,9 +5348,9 @@ var commitAndPush2 = async (ctx, profile) => {
5239
5348
  }
5240
5349
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
5241
5350
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
5242
- if (sentinel && fs21.existsSync(sentinel)) {
5351
+ if (sentinel && fs22.existsSync(sentinel)) {
5243
5352
  try {
5244
- const replay = JSON.parse(fs21.readFileSync(sentinel, "utf-8"));
5353
+ const replay = JSON.parse(fs22.readFileSync(sentinel, "utf-8"));
5245
5354
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
5246
5355
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
5247
5356
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -5294,8 +5403,8 @@ var commitAndPush2 = async (ctx, profile) => {
5294
5403
  const result = ctx.data.commitResult;
5295
5404
  if (sentinel && result?.committed) {
5296
5405
  try {
5297
- fs21.mkdirSync(path20.dirname(sentinel), { recursive: true });
5298
- fs21.writeFileSync(
5406
+ fs22.mkdirSync(path21.dirname(sentinel), { recursive: true });
5407
+ fs22.writeFileSync(
5299
5408
  sentinel,
5300
5409
  JSON.stringify(
5301
5410
  {
@@ -5356,14 +5465,14 @@ function ensureStateBranch(owner, repo, cwd) {
5356
5465
  }
5357
5466
 
5358
5467
  // src/goal/state.ts
5359
- import * as fs22 from "fs";
5360
- import * as path21 from "path";
5468
+ import * as fs23 from "fs";
5469
+ import * as path22 from "path";
5361
5470
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
5362
5471
  var GoalStateError = class extends Error {
5363
- constructor(path39, message) {
5364
- super(`Invalid goal state at ${path39}:
5472
+ constructor(path40, message) {
5473
+ super(`Invalid goal state at ${path40}:
5365
5474
  ${message}`);
5366
- this.path = path39;
5475
+ this.path = path40;
5367
5476
  this.name = "GoalStateError";
5368
5477
  }
5369
5478
  path;
@@ -5503,20 +5612,20 @@ function describeCommitMessage(goal) {
5503
5612
  }
5504
5613
 
5505
5614
  // src/scripts/composePrompt.ts
5506
- import * as fs23 from "fs";
5507
- import * as path22 from "path";
5615
+ import * as fs24 from "fs";
5616
+ import * as path23 from "path";
5508
5617
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
5509
5618
  var composePrompt = async (ctx, profile) => {
5510
5619
  const explicit = ctx.data.promptTemplate;
5511
5620
  const mode = ctx.args.mode;
5512
5621
  const candidates = [
5513
- explicit ? path22.join(profile.dir, explicit) : null,
5514
- mode ? path22.join(profile.dir, "prompts", `${mode}.md`) : null,
5515
- path22.join(profile.dir, "prompt.md")
5622
+ explicit ? path23.join(profile.dir, explicit) : null,
5623
+ mode ? path23.join(profile.dir, "prompts", `${mode}.md`) : null,
5624
+ path23.join(profile.dir, "prompt.md")
5516
5625
  ].filter(Boolean);
5517
5626
  let templatePath = "";
5518
5627
  for (const c of candidates) {
5519
- if (fs23.existsSync(c)) {
5628
+ if (fs24.existsSync(c)) {
5520
5629
  templatePath = c;
5521
5630
  break;
5522
5631
  }
@@ -5524,7 +5633,7 @@ var composePrompt = async (ctx, profile) => {
5524
5633
  if (!templatePath) {
5525
5634
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
5526
5635
  }
5527
- const template = fs23.readFileSync(templatePath, "utf-8");
5636
+ const template = fs24.readFileSync(templatePath, "utf-8");
5528
5637
  const tokens = {
5529
5638
  ...stringifyAll(ctx.args, "args."),
5530
5639
  ...stringifyAll(ctx.data, ""),
@@ -6302,15 +6411,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
6302
6411
 
6303
6412
  // src/scripts/diagMcp.ts
6304
6413
  import { execFileSync as execFileSync10 } from "child_process";
6305
- import * as fs24 from "fs";
6414
+ import * as fs25 from "fs";
6306
6415
  import * as os4 from "os";
6307
- import * as path23 from "path";
6416
+ import * as path24 from "path";
6308
6417
  var diagMcp = async (_ctx) => {
6309
6418
  const home = os4.homedir();
6310
- const cacheDir = path23.join(home, ".cache", "ms-playwright");
6419
+ const cacheDir = path24.join(home, ".cache", "ms-playwright");
6311
6420
  let entries = [];
6312
6421
  try {
6313
- entries = fs24.readdirSync(cacheDir);
6422
+ entries = fs25.readdirSync(cacheDir);
6314
6423
  } catch {
6315
6424
  }
6316
6425
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -6336,17 +6445,17 @@ var diagMcp = async (_ctx) => {
6336
6445
  };
6337
6446
 
6338
6447
  // src/scripts/discoverQaContext.ts
6339
- import * as fs26 from "fs";
6340
- import * as path25 from "path";
6448
+ import * as fs27 from "fs";
6449
+ import * as path26 from "path";
6341
6450
 
6342
6451
  // src/scripts/frameworkDetectors.ts
6343
- import * as fs25 from "fs";
6344
- import * as path24 from "path";
6452
+ import * as fs26 from "fs";
6453
+ import * as path25 from "path";
6345
6454
  function detectFrameworks(cwd) {
6346
6455
  const out = [];
6347
6456
  let deps = {};
6348
6457
  try {
6349
- const pkg = JSON.parse(fs25.readFileSync(path24.join(cwd, "package.json"), "utf-8"));
6458
+ const pkg = JSON.parse(fs26.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
6350
6459
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
6351
6460
  } catch {
6352
6461
  return out;
@@ -6383,7 +6492,7 @@ function detectFrameworks(cwd) {
6383
6492
  }
6384
6493
  function findFile(cwd, candidates) {
6385
6494
  for (const c of candidates) {
6386
- if (fs25.existsSync(path24.join(cwd, c))) return c;
6495
+ if (fs26.existsSync(path25.join(cwd, c))) return c;
6387
6496
  }
6388
6497
  return null;
6389
6498
  }
@@ -6396,18 +6505,18 @@ var COLLECTION_DIRS = [
6396
6505
  function discoverPayloadCollections(cwd) {
6397
6506
  const out = [];
6398
6507
  for (const dir of COLLECTION_DIRS) {
6399
- const full = path24.join(cwd, dir);
6400
- if (!fs25.existsSync(full)) continue;
6508
+ const full = path25.join(cwd, dir);
6509
+ if (!fs26.existsSync(full)) continue;
6401
6510
  let files;
6402
6511
  try {
6403
- files = fs25.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6512
+ files = fs26.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6404
6513
  } catch {
6405
6514
  continue;
6406
6515
  }
6407
6516
  for (const file of files) {
6408
6517
  try {
6409
- const filePath = path24.join(full, file);
6410
- const content = fs25.readFileSync(filePath, "utf-8").slice(0, 1e4);
6518
+ const filePath = path25.join(full, file);
6519
+ const content = fs26.readFileSync(filePath, "utf-8").slice(0, 1e4);
6411
6520
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
6412
6521
  if (!slugMatch) continue;
6413
6522
  const slug = slugMatch[1];
@@ -6421,7 +6530,7 @@ function discoverPayloadCollections(cwd) {
6421
6530
  out.push({
6422
6531
  name,
6423
6532
  slug,
6424
- filePath: path24.relative(cwd, filePath),
6533
+ filePath: path25.relative(cwd, filePath),
6425
6534
  fields: fields.slice(0, 20),
6426
6535
  hasAdmin
6427
6536
  });
@@ -6435,28 +6544,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
6435
6544
  function discoverAdminComponents(cwd, collections) {
6436
6545
  const out = [];
6437
6546
  for (const dir of ADMIN_COMPONENT_DIRS) {
6438
- const full = path24.join(cwd, dir);
6439
- if (!fs25.existsSync(full)) continue;
6547
+ const full = path25.join(cwd, dir);
6548
+ if (!fs26.existsSync(full)) continue;
6440
6549
  let entries;
6441
6550
  try {
6442
- entries = fs25.readdirSync(full, { withFileTypes: true });
6551
+ entries = fs26.readdirSync(full, { withFileTypes: true });
6443
6552
  } catch {
6444
6553
  continue;
6445
6554
  }
6446
6555
  for (const entry of entries) {
6447
- const entryPath = path24.join(full, entry.name);
6556
+ const entryPath = path25.join(full, entry.name);
6448
6557
  let name;
6449
6558
  let filePath;
6450
6559
  if (entry.isDirectory()) {
6451
6560
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
6452
- (f) => fs25.existsSync(path24.join(entryPath, f))
6561
+ (f) => fs26.existsSync(path25.join(entryPath, f))
6453
6562
  );
6454
6563
  if (!indexFile) continue;
6455
6564
  name = entry.name;
6456
- filePath = path24.relative(cwd, path24.join(entryPath, indexFile));
6565
+ filePath = path25.relative(cwd, path25.join(entryPath, indexFile));
6457
6566
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
6458
6567
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
6459
- filePath = path24.relative(cwd, entryPath);
6568
+ filePath = path25.relative(cwd, entryPath);
6460
6569
  } else {
6461
6570
  continue;
6462
6571
  }
@@ -6464,7 +6573,7 @@ function discoverAdminComponents(cwd, collections) {
6464
6573
  if (collections) {
6465
6574
  for (const col of collections) {
6466
6575
  try {
6467
- const colContent = fs25.readFileSync(path24.join(cwd, col.filePath), "utf-8");
6576
+ const colContent = fs26.readFileSync(path25.join(cwd, col.filePath), "utf-8");
6468
6577
  if (colContent.includes(name)) {
6469
6578
  usedInCollection = col.slug;
6470
6579
  break;
@@ -6483,8 +6592,8 @@ function scanApiRoutes(cwd) {
6483
6592
  const out = [];
6484
6593
  const appDirs = ["src/app", "app"];
6485
6594
  for (const appDir of appDirs) {
6486
- const apiDir = path24.join(cwd, appDir, "api");
6487
- if (!fs25.existsSync(apiDir)) continue;
6595
+ const apiDir = path25.join(cwd, appDir, "api");
6596
+ if (!fs26.existsSync(apiDir)) continue;
6488
6597
  walkApiRoutes(apiDir, "/api", cwd, out);
6489
6598
  break;
6490
6599
  }
@@ -6493,14 +6602,14 @@ function scanApiRoutes(cwd) {
6493
6602
  function walkApiRoutes(dir, prefix, cwd, out) {
6494
6603
  let entries;
6495
6604
  try {
6496
- entries = fs25.readdirSync(dir, { withFileTypes: true });
6605
+ entries = fs26.readdirSync(dir, { withFileTypes: true });
6497
6606
  } catch {
6498
6607
  return;
6499
6608
  }
6500
6609
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
6501
6610
  if (routeFile) {
6502
6611
  try {
6503
- const content = fs25.readFileSync(path24.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
6612
+ const content = fs26.readFileSync(path25.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
6504
6613
  const methods = HTTP_METHODS.filter(
6505
6614
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
6506
6615
  );
@@ -6508,7 +6617,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6508
6617
  out.push({
6509
6618
  path: prefix,
6510
6619
  methods,
6511
- filePath: path24.relative(cwd, path24.join(dir, routeFile.name))
6620
+ filePath: path25.relative(cwd, path25.join(dir, routeFile.name))
6512
6621
  });
6513
6622
  }
6514
6623
  } catch {
@@ -6519,7 +6628,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6519
6628
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6520
6629
  let segment = entry.name;
6521
6630
  if (segment.startsWith("(") && segment.endsWith(")")) {
6522
- walkApiRoutes(path24.join(dir, entry.name), prefix, cwd, out);
6631
+ walkApiRoutes(path25.join(dir, entry.name), prefix, cwd, out);
6523
6632
  continue;
6524
6633
  }
6525
6634
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -6527,7 +6636,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
6527
6636
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
6528
6637
  segment = `:${segment.slice(1, -1)}`;
6529
6638
  }
6530
- walkApiRoutes(path24.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
6639
+ walkApiRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
6531
6640
  }
6532
6641
  }
6533
6642
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -6547,10 +6656,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
6547
6656
  function scanEnvVars(cwd) {
6548
6657
  const candidates = [".env.example", ".env.local.example", ".env.template"];
6549
6658
  for (const envFile of candidates) {
6550
- const envPath = path24.join(cwd, envFile);
6551
- if (!fs25.existsSync(envPath)) continue;
6659
+ const envPath = path25.join(cwd, envFile);
6660
+ if (!fs26.existsSync(envPath)) continue;
6552
6661
  try {
6553
- const content = fs25.readFileSync(envPath, "utf-8");
6662
+ const content = fs26.readFileSync(envPath, "utf-8");
6554
6663
  const vars = [];
6555
6664
  for (const line of content.split("\n")) {
6556
6665
  const trimmed = line.trim();
@@ -6598,9 +6707,9 @@ function runQaDiscovery(cwd) {
6598
6707
  }
6599
6708
  function detectDevServer(cwd, out) {
6600
6709
  try {
6601
- const pkg = JSON.parse(fs26.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
6710
+ const pkg = JSON.parse(fs27.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
6602
6711
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
6603
- const pm = fs26.existsSync(path25.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs26.existsSync(path25.join(cwd, "yarn.lock")) ? "yarn" : fs26.existsSync(path25.join(cwd, "bun.lockb")) ? "bun" : "npm";
6712
+ const pm = fs27.existsSync(path26.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs27.existsSync(path26.join(cwd, "yarn.lock")) ? "yarn" : fs27.existsSync(path26.join(cwd, "bun.lockb")) ? "bun" : "npm";
6604
6713
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
6605
6714
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
6606
6715
  else if (allDeps.vite) out.devPort = 5173;
@@ -6610,8 +6719,8 @@ function detectDevServer(cwd, out) {
6610
6719
  function scanFrontendRoutes(cwd, out) {
6611
6720
  const appDirs = ["src/app", "app"];
6612
6721
  for (const appDir of appDirs) {
6613
- const full = path25.join(cwd, appDir);
6614
- if (!fs26.existsSync(full)) continue;
6722
+ const full = path26.join(cwd, appDir);
6723
+ if (!fs27.existsSync(full)) continue;
6615
6724
  walkFrontendRoutes(full, "", out);
6616
6725
  break;
6617
6726
  }
@@ -6619,7 +6728,7 @@ function scanFrontendRoutes(cwd, out) {
6619
6728
  function walkFrontendRoutes(dir, prefix, out) {
6620
6729
  let entries;
6621
6730
  try {
6622
- entries = fs26.readdirSync(dir, { withFileTypes: true });
6731
+ entries = fs27.readdirSync(dir, { withFileTypes: true });
6623
6732
  } catch {
6624
6733
  return;
6625
6734
  }
@@ -6636,7 +6745,7 @@ function walkFrontendRoutes(dir, prefix, out) {
6636
6745
  if (entry.name === "node_modules" || entry.name === ".next") continue;
6637
6746
  let segment = entry.name;
6638
6747
  if (segment.startsWith("(") && segment.endsWith(")")) {
6639
- walkFrontendRoutes(path25.join(dir, entry.name), prefix, out);
6748
+ walkFrontendRoutes(path26.join(dir, entry.name), prefix, out);
6640
6749
  continue;
6641
6750
  }
6642
6751
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -6644,7 +6753,7 @@ function walkFrontendRoutes(dir, prefix, out) {
6644
6753
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
6645
6754
  segment = `:${segment.slice(1, -1)}`;
6646
6755
  }
6647
- walkFrontendRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, out);
6756
+ walkFrontendRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, out);
6648
6757
  }
6649
6758
  }
6650
6759
  function detectAuthFiles(cwd, out) {
@@ -6661,23 +6770,23 @@ function detectAuthFiles(cwd, out) {
6661
6770
  "src/app/api/oauth"
6662
6771
  ];
6663
6772
  for (const c of candidates) {
6664
- if (fs26.existsSync(path25.join(cwd, c))) out.authFiles.push(c);
6773
+ if (fs27.existsSync(path26.join(cwd, c))) out.authFiles.push(c);
6665
6774
  }
6666
6775
  }
6667
6776
  function detectRoles(cwd, out) {
6668
6777
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
6669
6778
  for (const rp of rolePaths) {
6670
- const dir = path25.join(cwd, rp);
6671
- if (!fs26.existsSync(dir)) continue;
6779
+ const dir = path26.join(cwd, rp);
6780
+ if (!fs27.existsSync(dir)) continue;
6672
6781
  let files;
6673
6782
  try {
6674
- files = fs26.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6783
+ files = fs27.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
6675
6784
  } catch {
6676
6785
  continue;
6677
6786
  }
6678
6787
  for (const f of files) {
6679
6788
  try {
6680
- const content = fs26.readFileSync(path25.join(dir, f), "utf-8").slice(0, 5e3);
6789
+ const content = fs27.readFileSync(path26.join(dir, f), "utf-8").slice(0, 5e3);
6681
6790
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
6682
6791
  if (roleMatches) {
6683
6792
  for (const m of roleMatches) {
@@ -6861,8 +6970,8 @@ function failedAction3(reason) {
6861
6970
  }
6862
6971
 
6863
6972
  // src/scripts/dispatchJobFileTicks.ts
6864
- import * as fs28 from "fs";
6865
- import * as path27 from "path";
6973
+ import * as fs29 from "fs";
6974
+ import * as path28 from "path";
6866
6975
 
6867
6976
  // src/scripts/jobFrontmatter.ts
6868
6977
  var SCHEDULE_EVERY_VALUES = [
@@ -7131,8 +7240,8 @@ var ContentsApiBackend = class {
7131
7240
  };
7132
7241
 
7133
7242
  // src/scripts/jobState/localFileBackend.ts
7134
- import * as fs27 from "fs";
7135
- import * as path26 from "path";
7243
+ import * as fs28 from "fs";
7244
+ import * as path27 from "path";
7136
7245
  var LocalFileBackend = class {
7137
7246
  name = "local-file";
7138
7247
  cwd;
@@ -7147,7 +7256,7 @@ var LocalFileBackend = class {
7147
7256
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
7148
7257
  this.cwd = opts.cwd;
7149
7258
  this.jobsDir = opts.jobsDir;
7150
- this.absDir = path26.join(opts.cwd, opts.jobsDir);
7259
+ this.absDir = path27.join(opts.cwd, opts.jobsDir);
7151
7260
  this.owner = opts.owner;
7152
7261
  this.repo = opts.repo;
7153
7262
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -7162,7 +7271,7 @@ var LocalFileBackend = class {
7162
7271
  `);
7163
7272
  return;
7164
7273
  }
7165
- fs27.mkdirSync(this.absDir, { recursive: true });
7274
+ fs28.mkdirSync(this.absDir, { recursive: true });
7166
7275
  const prefix = this.cacheKeyPrefix();
7167
7276
  const probeKey = `${prefix}probe-${Date.now()}`;
7168
7277
  try {
@@ -7191,7 +7300,7 @@ var LocalFileBackend = class {
7191
7300
  `);
7192
7301
  return;
7193
7302
  }
7194
- if (!fs27.existsSync(this.absDir)) {
7303
+ if (!fs28.existsSync(this.absDir)) {
7195
7304
  return;
7196
7305
  }
7197
7306
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -7207,11 +7316,11 @@ var LocalFileBackend = class {
7207
7316
  }
7208
7317
  load(slug) {
7209
7318
  const relPath = stateFilePath(this.jobsDir, slug);
7210
- const absPath = path26.join(this.cwd, relPath);
7211
- if (!fs27.existsSync(absPath)) {
7319
+ const absPath = path27.join(this.cwd, relPath);
7320
+ if (!fs28.existsSync(absPath)) {
7212
7321
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
7213
7322
  }
7214
- const raw = fs27.readFileSync(absPath, "utf-8");
7323
+ const raw = fs28.readFileSync(absPath, "utf-8");
7215
7324
  let parsed;
7216
7325
  try {
7217
7326
  parsed = JSON.parse(raw);
@@ -7228,10 +7337,10 @@ var LocalFileBackend = class {
7228
7337
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
7229
7338
  return false;
7230
7339
  }
7231
- const absPath = path26.join(this.cwd, loaded.path);
7232
- fs27.mkdirSync(path26.dirname(absPath), { recursive: true });
7340
+ const absPath = path27.join(this.cwd, loaded.path);
7341
+ fs28.mkdirSync(path27.dirname(absPath), { recursive: true });
7233
7342
  const body = JSON.stringify(next, null, 2) + "\n";
7234
- fs27.writeFileSync(absPath, body, "utf-8");
7343
+ fs28.writeFileSync(absPath, body, "utf-8");
7235
7344
  return true;
7236
7345
  }
7237
7346
  cacheKeyPrefix() {
@@ -7309,7 +7418,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
7309
7418
  await backend.hydrate();
7310
7419
  }
7311
7420
  try {
7312
- const slugs = listJobSlugs(path27.join(ctx.cwd, jobsDir));
7421
+ const slugs = listJobSlugs(path28.join(ctx.cwd, jobsDir));
7313
7422
  ctx.data.jobSlugCount = slugs.length;
7314
7423
  if (slugs.length === 0) {
7315
7424
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -7422,17 +7531,17 @@ function formatAgo(ms) {
7422
7531
  }
7423
7532
  function readJobFrontmatter(cwd, jobsDir, slug) {
7424
7533
  try {
7425
- const raw = fs28.readFileSync(path27.join(cwd, jobsDir, `${slug}.md`), "utf-8");
7534
+ const raw = fs29.readFileSync(path28.join(cwd, jobsDir, `${slug}.md`), "utf-8");
7426
7535
  return splitFrontmatter2(raw).frontmatter;
7427
7536
  } catch {
7428
7537
  return {};
7429
7538
  }
7430
7539
  }
7431
7540
  function listJobSlugs(absDir) {
7432
- if (!fs28.existsSync(absDir)) return [];
7541
+ if (!fs29.existsSync(absDir)) return [];
7433
7542
  let entries;
7434
7543
  try {
7435
- entries = fs28.readdirSync(absDir, { withFileTypes: true });
7544
+ entries = fs29.readdirSync(absDir, { withFileTypes: true });
7436
7545
  } catch {
7437
7546
  return [];
7438
7547
  }
@@ -8225,7 +8334,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
8225
8334
 
8226
8335
  // src/gha.ts
8227
8336
  import { execFileSync as execFileSync16 } from "child_process";
8228
- import * as fs29 from "fs";
8337
+ import * as fs30 from "fs";
8229
8338
  function getRunUrl() {
8230
8339
  const server = process.env.GITHUB_SERVER_URL;
8231
8340
  const repo = process.env.GITHUB_REPOSITORY;
@@ -8236,10 +8345,10 @@ function getRunUrl() {
8236
8345
  function reactToTriggerComment(cwd) {
8237
8346
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
8238
8347
  const eventPath = process.env.GITHUB_EVENT_PATH;
8239
- if (!eventPath || !fs29.existsSync(eventPath)) return;
8348
+ if (!eventPath || !fs30.existsSync(eventPath)) return;
8240
8349
  let event = null;
8241
8350
  try {
8242
- event = JSON.parse(fs29.readFileSync(eventPath, "utf-8"));
8351
+ event = JSON.parse(fs30.readFileSync(eventPath, "utf-8"));
8243
8352
  } catch {
8244
8353
  return;
8245
8354
  }
@@ -8533,12 +8642,12 @@ var handleAbandonedGoal = async (ctx) => {
8533
8642
 
8534
8643
  // src/scripts/initFlow.ts
8535
8644
  import { execFileSync as execFileSync18 } from "child_process";
8536
- import * as fs30 from "fs";
8537
- import * as path28 from "path";
8645
+ import * as fs31 from "fs";
8646
+ import * as path29 from "path";
8538
8647
  function detectPackageManager(cwd) {
8539
- if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
8540
- if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
8541
- if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
8648
+ if (fs31.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
8649
+ if (fs31.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
8650
+ if (fs31.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
8542
8651
  return "npm";
8543
8652
  }
8544
8653
  function qualityCommandsFor(pm) {
@@ -8667,36 +8776,36 @@ function performInit(cwd, force) {
8667
8776
  const pm = detectPackageManager(cwd);
8668
8777
  const ownerRepo = detectOwnerRepo(cwd);
8669
8778
  const defaultBranch2 = defaultBranchFromGit(cwd);
8670
- const configPath = path28.join(cwd, "kody.config.json");
8671
- if (fs30.existsSync(configPath) && !force) {
8779
+ const configPath = path29.join(cwd, "kody.config.json");
8780
+ if (fs31.existsSync(configPath) && !force) {
8672
8781
  skipped.push("kody.config.json");
8673
8782
  } else {
8674
8783
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
8675
- fs30.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
8784
+ fs31.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
8676
8785
  `);
8677
8786
  wrote.push("kody.config.json");
8678
8787
  }
8679
- const workflowDir = path28.join(cwd, ".github", "workflows");
8680
- const workflowPath = path28.join(workflowDir, "kody.yml");
8681
- if (fs30.existsSync(workflowPath) && !force) {
8788
+ const workflowDir = path29.join(cwd, ".github", "workflows");
8789
+ const workflowPath = path29.join(workflowDir, "kody.yml");
8790
+ if (fs31.existsSync(workflowPath) && !force) {
8682
8791
  skipped.push(".github/workflows/kody.yml");
8683
8792
  } else {
8684
- fs30.mkdirSync(workflowDir, { recursive: true });
8685
- fs30.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
8793
+ fs31.mkdirSync(workflowDir, { recursive: true });
8794
+ fs31.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
8686
8795
  wrote.push(".github/workflows/kody.yml");
8687
8796
  }
8688
8797
  const builtinJobs = listBuiltinJobs();
8689
8798
  if (builtinJobs.length > 0) {
8690
- const jobsDir = path28.join(cwd, ".kody", "duties");
8691
- fs30.mkdirSync(jobsDir, { recursive: true });
8799
+ const jobsDir = path29.join(cwd, ".kody", "duties");
8800
+ fs31.mkdirSync(jobsDir, { recursive: true });
8692
8801
  for (const job of builtinJobs) {
8693
- const rel = path28.join(".kody", "duties", `${job.slug}.md`);
8694
- const target = path28.join(cwd, rel);
8695
- if (fs30.existsSync(target) && !force) {
8802
+ const rel = path29.join(".kody", "duties", `${job.slug}.md`);
8803
+ const target = path29.join(cwd, rel);
8804
+ if (fs31.existsSync(target) && !force) {
8696
8805
  skipped.push(rel);
8697
8806
  continue;
8698
8807
  }
8699
- fs30.writeFileSync(target, fs30.readFileSync(job.filePath, "utf-8"));
8808
+ fs31.writeFileSync(target, fs31.readFileSync(job.filePath, "utf-8"));
8700
8809
  wrote.push(rel);
8701
8810
  }
8702
8811
  }
@@ -8708,12 +8817,12 @@ function performInit(cwd, force) {
8708
8817
  continue;
8709
8818
  }
8710
8819
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
8711
- const target = path28.join(workflowDir, `kody-${exe.name}.yml`);
8712
- if (fs30.existsSync(target) && !force) {
8820
+ const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
8821
+ if (fs31.existsSync(target) && !force) {
8713
8822
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
8714
8823
  continue;
8715
8824
  }
8716
- fs30.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
8825
+ fs31.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
8717
8826
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
8718
8827
  }
8719
8828
  let labels;
@@ -8897,8 +9006,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
8897
9006
  };
8898
9007
 
8899
9008
  // src/scripts/loadJobFromFile.ts
8900
- import * as fs31 from "fs";
8901
- import * as path29 from "path";
9009
+ import * as fs32 from "fs";
9010
+ import * as path30 from "path";
8902
9011
  var loadJobFromFile = async (ctx, _profile, args) => {
8903
9012
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
8904
9013
  const workersDir = String(args?.workersDir ?? ".kody/staff");
@@ -8907,11 +9016,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8907
9016
  if (!slug) {
8908
9017
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
8909
9018
  }
8910
- const absPath = path29.join(ctx.cwd, jobsDir, `${slug}.md`);
8911
- if (!fs31.existsSync(absPath)) {
9019
+ const absPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
9020
+ if (!fs32.existsSync(absPath)) {
8912
9021
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
8913
9022
  }
8914
- const raw = fs31.readFileSync(absPath, "utf-8");
9023
+ const raw = fs32.readFileSync(absPath, "utf-8");
8915
9024
  const { title, body } = parseJobFile(raw, slug);
8916
9025
  const frontmatter = splitFrontmatter2(raw).frontmatter;
8917
9026
  const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
@@ -8919,13 +9028,13 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8919
9028
  let workerTitle = "";
8920
9029
  let workerPersona = "";
8921
9030
  if (workerSlug) {
8922
- const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8923
- if (!fs31.existsSync(workerPath)) {
9031
+ const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9032
+ if (!fs32.existsSync(workerPath)) {
8924
9033
  throw new Error(
8925
9034
  `loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`
8926
9035
  );
8927
9036
  }
8928
- const workerRaw = fs31.readFileSync(workerPath, "utf-8");
9037
+ const workerRaw = fs32.readFileSync(workerPath, "utf-8");
8929
9038
  const parsed = parseJobFile(workerRaw, workerSlug);
8930
9039
  workerTitle = parsed.title;
8931
9040
  workerPersona = parsed.body;
@@ -8968,18 +9077,18 @@ init_loadMemoryContext();
8968
9077
  init_loadPriorArt();
8969
9078
 
8970
9079
  // src/scripts/loadQaContext.ts
8971
- import * as fs34 from "fs";
8972
- import * as path32 from "path";
9080
+ import * as fs35 from "fs";
9081
+ import * as path33 from "path";
8973
9082
 
8974
9083
  // src/scripts/kodyVariables.ts
8975
- import * as fs33 from "fs";
8976
- import * as path31 from "path";
9084
+ import * as fs34 from "fs";
9085
+ import * as path32 from "path";
8977
9086
  var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
8978
9087
  function readKodyVariables(cwd) {
8979
- const full = path31.join(cwd, KODY_VARIABLES_REL_PATH);
9088
+ const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
8980
9089
  let raw;
8981
9090
  try {
8982
- raw = fs33.readFileSync(full, "utf-8");
9091
+ raw = fs34.readFileSync(full, "utf-8");
8983
9092
  } catch {
8984
9093
  return {};
8985
9094
  }
@@ -9028,18 +9137,18 @@ function readProfileStaff(raw) {
9028
9137
  return { staff: staff ?? legacy ?? ["kody"], body };
9029
9138
  }
9030
9139
  function readProfile(cwd) {
9031
- const dir = path32.join(cwd, CONTEXT_DIR_REL_PATH);
9032
- if (!fs34.existsSync(dir)) return "";
9140
+ const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
9141
+ if (!fs35.existsSync(dir)) return "";
9033
9142
  let entries;
9034
9143
  try {
9035
- entries = fs34.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
9144
+ entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
9036
9145
  } catch {
9037
9146
  return "";
9038
9147
  }
9039
9148
  const blocks = [];
9040
9149
  for (const file of entries) {
9041
9150
  try {
9042
- const raw = fs34.readFileSync(path32.join(dir, file), "utf-8");
9151
+ const raw = fs35.readFileSync(path33.join(dir, file), "utf-8");
9043
9152
  const { staff, body } = readProfileStaff(raw);
9044
9153
  if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
9045
9154
  blocks.push(`## ${file}
@@ -9076,8 +9185,8 @@ var loadQaContext = async (ctx) => {
9076
9185
  init_events();
9077
9186
 
9078
9187
  // src/taskContext.ts
9079
- import * as fs35 from "fs";
9080
- import * as path33 from "path";
9188
+ import * as fs36 from "fs";
9189
+ import * as path34 from "path";
9081
9190
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
9082
9191
  function buildTaskContext(args) {
9083
9192
  return {
@@ -9093,10 +9202,10 @@ function buildTaskContext(args) {
9093
9202
  }
9094
9203
  function persistTaskContext(cwd, ctx) {
9095
9204
  try {
9096
- const dir = path33.join(cwd, ".kody", "runs", ctx.runId);
9097
- fs35.mkdirSync(dir, { recursive: true });
9098
- const file = path33.join(dir, "task-context.json");
9099
- fs35.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
9205
+ const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
9206
+ fs36.mkdirSync(dir, { recursive: true });
9207
+ const file = path34.join(dir, "task-context.json");
9208
+ fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
9100
9209
  `);
9101
9210
  return file;
9102
9211
  } catch (err) {
@@ -9144,19 +9253,19 @@ var loadTaskState = async (ctx) => {
9144
9253
  };
9145
9254
 
9146
9255
  // src/scripts/loadWorkerAdhoc.ts
9147
- import * as fs36 from "fs";
9148
- import * as path34 from "path";
9256
+ import * as fs37 from "fs";
9257
+ import * as path35 from "path";
9149
9258
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
9150
9259
  const workersDir = String(args?.workersDir ?? ".kody/staff");
9151
9260
  const workerSlug = String(ctx.args.worker ?? "").trim();
9152
9261
  if (!workerSlug) {
9153
9262
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
9154
9263
  }
9155
- const workerPath = path34.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9156
- if (!fs36.existsSync(workerPath)) {
9264
+ const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9265
+ if (!fs37.existsSync(workerPath)) {
9157
9266
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
9158
9267
  }
9159
- const { title, body } = parsePersona(fs36.readFileSync(workerPath, "utf-8"), workerSlug);
9268
+ const { title, body } = parsePersona(fs37.readFileSync(workerPath, "utf-8"), workerSlug);
9160
9269
  const message = resolveMessage(ctx.args.message);
9161
9270
  if (!message) {
9162
9271
  throw new Error(
@@ -9176,9 +9285,9 @@ function resolveMessage(messageArg) {
9176
9285
  }
9177
9286
  function readCommentBody() {
9178
9287
  const eventPath = process.env.GITHUB_EVENT_PATH;
9179
- if (!eventPath || !fs36.existsSync(eventPath)) return "";
9288
+ if (!eventPath || !fs37.existsSync(eventPath)) return "";
9180
9289
  try {
9181
- const event = JSON.parse(fs36.readFileSync(eventPath, "utf-8"));
9290
+ const event = JSON.parse(fs37.readFileSync(eventPath, "utf-8"));
9182
9291
  return String(event.comment?.body ?? "");
9183
9292
  } catch {
9184
9293
  return "";
@@ -9848,8 +9957,8 @@ var FlyClient = class {
9848
9957
  get fetch() {
9849
9958
  return this.opts.fetchImpl ?? fetch;
9850
9959
  }
9851
- async call(path39, init = {}) {
9852
- const res = await this.fetch(`${FLY_API_BASE}${path39}`, {
9960
+ async call(path40, init = {}) {
9961
+ const res = await this.fetch(`${FLY_API_BASE}${path40}`, {
9853
9962
  method: init.method ?? "GET",
9854
9963
  headers: {
9855
9964
  Authorization: `Bearer ${this.opts.token}`,
@@ -9860,7 +9969,7 @@ var FlyClient = class {
9860
9969
  if (res.status === 404 && init.allow404) return null;
9861
9970
  if (!res.ok) {
9862
9971
  const text = await res.text().catch(() => "");
9863
- throw new Error(`Fly API ${res.status} on ${path39}: ${text.slice(0, 200) || res.statusText}`);
9972
+ throw new Error(`Fly API ${res.status} on ${path40}: ${text.slice(0, 200) || res.statusText}`);
9864
9973
  }
9865
9974
  if (res.status === 204) return null;
9866
9975
  const raw = await res.text();
@@ -11536,7 +11645,7 @@ function resolveBaseOverride(value) {
11536
11645
  // src/scripts/runnerServe.ts
11537
11646
  import { spawn as spawn5 } from "child_process";
11538
11647
  import { createServer as createServer3 } from "http";
11539
- import * as fs37 from "fs";
11648
+ import * as fs38 from "fs";
11540
11649
  var DEFAULT_PORT2 = 8080;
11541
11650
  var DEFAULT_WORKDIR = "/workspace/repo";
11542
11651
  function getApiKey2() {
@@ -11617,8 +11726,8 @@ async function defaultRunJob(job) {
11617
11726
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
11618
11727
  const branch = job.ref ?? "main";
11619
11728
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
11620
- fs37.rmSync(workdir, { recursive: true, force: true });
11621
- fs37.mkdirSync(workdir, { recursive: true });
11729
+ fs38.rmSync(workdir, { recursive: true, force: true });
11730
+ fs38.mkdirSync(workdir, { recursive: true });
11622
11731
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
11623
11732
  const interactive = job.mode === "interactive";
11624
11733
  const scheduled = job.mode === "scheduled";
@@ -11759,8 +11868,8 @@ var runnerServe = async (ctx) => {
11759
11868
 
11760
11869
  // src/scripts/runTickScript.ts
11761
11870
  import { spawnSync as spawnSync2 } from "child_process";
11762
- import * as fs38 from "fs";
11763
- import * as path35 from "path";
11871
+ import * as fs39 from "fs";
11872
+ import * as path36 from "path";
11764
11873
  var runTickScript = async (ctx, _profile, args) => {
11765
11874
  ctx.skipAgent = true;
11766
11875
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -11772,13 +11881,13 @@ var runTickScript = async (ctx, _profile, args) => {
11772
11881
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
11773
11882
  return;
11774
11883
  }
11775
- const jobPath = path35.join(ctx.cwd, jobsDir, `${slug}.md`);
11776
- if (!fs38.existsSync(jobPath)) {
11884
+ const jobPath = path36.join(ctx.cwd, jobsDir, `${slug}.md`);
11885
+ if (!fs39.existsSync(jobPath)) {
11777
11886
  ctx.output.exitCode = 99;
11778
11887
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
11779
11888
  return;
11780
11889
  }
11781
- const raw = fs38.readFileSync(jobPath, "utf-8");
11890
+ const raw = fs39.readFileSync(jobPath, "utf-8");
11782
11891
  const { frontmatter } = splitFrontmatter2(raw);
11783
11892
  const tickScript = frontmatter.tickScript;
11784
11893
  if (!tickScript) {
@@ -11786,8 +11895,8 @@ var runTickScript = async (ctx, _profile, args) => {
11786
11895
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
11787
11896
  return;
11788
11897
  }
11789
- const scriptPath = path35.isAbsolute(tickScript) ? tickScript : path35.join(ctx.cwd, tickScript);
11790
- if (!fs38.existsSync(scriptPath)) {
11898
+ const scriptPath = path36.isAbsolute(tickScript) ? tickScript : path36.join(ctx.cwd, tickScript);
11899
+ if (!fs39.existsSync(scriptPath)) {
11791
11900
  ctx.output.exitCode = 99;
11792
11901
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
11793
11902
  return;
@@ -12124,7 +12233,8 @@ var startFlow = async (ctx, profile, _agentResult, args) => {
12124
12233
  name: flowName,
12125
12234
  step: entry,
12126
12235
  issueNumber,
12127
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
12236
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
12237
+ hops: 0
12128
12238
  };
12129
12239
  }
12130
12240
  postKodyComment(target, issueNumber, state, entry, ctx.cwd);
@@ -12910,7 +13020,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
12910
13020
  };
12911
13021
 
12912
13022
  // src/scripts/writeRunSummary.ts
12913
- import * as fs39 from "fs";
13023
+ import * as fs40 from "fs";
12914
13024
  var writeRunSummary = async (ctx, profile) => {
12915
13025
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
12916
13026
  if (!summaryPath) return;
@@ -12932,7 +13042,7 @@ var writeRunSummary = async (ctx, profile) => {
12932
13042
  if (reason) lines.push(`- **Reason:** ${reason}`);
12933
13043
  lines.push("");
12934
13044
  try {
12935
- fs39.appendFileSync(summaryPath, `${lines.join("\n")}
13045
+ fs40.appendFileSync(summaryPath, `${lines.join("\n")}
12936
13046
  `);
12937
13047
  } catch {
12938
13048
  }
@@ -13200,9 +13310,9 @@ async function runExecutable(profileName, input) {
13200
13310
  })
13201
13311
  };
13202
13312
  })() : null;
13203
- const ndjsonDir = path36.join(input.cwd, ".kody");
13313
+ const ndjsonDir = path37.join(input.cwd, ".kody");
13204
13314
  const invokeAgent = async (prompt) => {
13205
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path36.isAbsolute(p) ? p : path36.resolve(profile.dir, p)).filter((p) => p.length > 0);
13315
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path37.isAbsolute(p) ? p : path37.resolve(profile.dir, p)).filter((p) => p.length > 0);
13206
13316
  const syntheticPath = ctx.data.syntheticPluginPath;
13207
13317
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
13208
13318
  const agents = loadSubagents(profile);
@@ -13416,7 +13526,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
13416
13526
  function getProfileInputsForChild(profileName, _cwd) {
13417
13527
  try {
13418
13528
  const profilePath = resolveProfilePath(profileName);
13419
- if (!fs40.existsSync(profilePath)) return null;
13529
+ if (!fs41.existsSync(profilePath)) return null;
13420
13530
  return loadProfile(profilePath).inputs;
13421
13531
  } catch {
13422
13532
  return null;
@@ -13425,17 +13535,17 @@ function getProfileInputsForChild(profileName, _cwd) {
13425
13535
  function resolveProfilePath(profileName) {
13426
13536
  const found = resolveExecutable(profileName);
13427
13537
  if (found) return found;
13428
- const here = path36.dirname(new URL(import.meta.url).pathname);
13538
+ const here = path37.dirname(new URL(import.meta.url).pathname);
13429
13539
  const candidates = [
13430
- path36.join(here, "executables", profileName, "profile.json"),
13540
+ path37.join(here, "executables", profileName, "profile.json"),
13431
13541
  // same-dir sibling (dev)
13432
- path36.join(here, "..", "executables", profileName, "profile.json"),
13542
+ path37.join(here, "..", "executables", profileName, "profile.json"),
13433
13543
  // up one (prod: dist/bin → dist/executables)
13434
- path36.join(here, "..", "src", "executables", profileName, "profile.json")
13544
+ path37.join(here, "..", "src", "executables", profileName, "profile.json")
13435
13545
  // fallback
13436
13546
  ];
13437
13547
  for (const c of candidates) {
13438
- if (fs40.existsSync(c)) return c;
13548
+ if (fs41.existsSync(c)) return c;
13439
13549
  }
13440
13550
  return candidates[0];
13441
13551
  }
@@ -13535,8 +13645,8 @@ function resolveShellTimeoutMs(entry) {
13535
13645
  var SIGKILL_GRACE_MS = 5e3;
13536
13646
  async function runShellEntry(entry, ctx, profile) {
13537
13647
  const shellName = entry.shell;
13538
- const shellPath = path36.join(profile.dir, shellName);
13539
- if (!fs40.existsSync(shellPath)) {
13648
+ const shellPath = path37.join(profile.dir, shellName);
13649
+ if (!fs41.existsSync(shellPath)) {
13540
13650
  ctx.skipAgent = true;
13541
13651
  ctx.output.exitCode = 99;
13542
13652
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -14042,9 +14152,9 @@ async function resolveAuthToken(env = process.env) {
14042
14152
  return void 0;
14043
14153
  }
14044
14154
  function detectPackageManager2(cwd) {
14045
- if (fs41.existsSync(path37.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
14046
- if (fs41.existsSync(path37.join(cwd, "yarn.lock"))) return "yarn";
14047
- if (fs41.existsSync(path37.join(cwd, "bun.lockb"))) return "bun";
14155
+ if (fs42.existsSync(path38.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
14156
+ if (fs42.existsSync(path38.join(cwd, "yarn.lock"))) return "yarn";
14157
+ if (fs42.existsSync(path38.join(cwd, "bun.lockb"))) return "bun";
14048
14158
  return "npm";
14049
14159
  }
14050
14160
  function shellOut(cmd, args, cwd, stream = true) {
@@ -14131,11 +14241,11 @@ function configureGitIdentity(cwd) {
14131
14241
  }
14132
14242
  function postFailureTail(issueNumber, cwd, reason) {
14133
14243
  if (!issueNumber) return;
14134
- const logPath = path37.join(cwd, ".kody", "last-run.jsonl");
14244
+ const logPath = path38.join(cwd, ".kody", "last-run.jsonl");
14135
14245
  let tail = "";
14136
14246
  try {
14137
- if (fs41.existsSync(logPath)) {
14138
- const content = fs41.readFileSync(logPath, "utf-8");
14247
+ if (fs42.existsSync(logPath)) {
14248
+ const content = fs42.readFileSync(logPath, "utf-8");
14139
14249
  tail = content.slice(-3e3);
14140
14250
  }
14141
14251
  } catch {
@@ -14160,7 +14270,7 @@ async function runCi(argv) {
14160
14270
  return 0;
14161
14271
  }
14162
14272
  const args = parseCiArgs(argv);
14163
- const cwd = args.cwd ? path37.resolve(args.cwd) : process.cwd();
14273
+ const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
14164
14274
  let earlyConfig;
14165
14275
  try {
14166
14276
  earlyConfig = loadConfig(cwd);
@@ -14170,9 +14280,9 @@ async function runCi(argv) {
14170
14280
  const eventName = process.env.GITHUB_EVENT_NAME;
14171
14281
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
14172
14282
  let manualWorkflowDispatch = false;
14173
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs41.existsSync(dispatchEventPath)) {
14283
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs42.existsSync(dispatchEventPath)) {
14174
14284
  try {
14175
- const evt = JSON.parse(fs41.readFileSync(dispatchEventPath, "utf-8"));
14285
+ const evt = JSON.parse(fs42.readFileSync(dispatchEventPath, "utf-8"));
14176
14286
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
14177
14287
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
14178
14288
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -14433,12 +14543,12 @@ function parseChatArgs(argv, env = process.env) {
14433
14543
  return result;
14434
14544
  }
14435
14545
  function commitChatFiles(cwd, sessionId, verbose) {
14436
- const sessionFile = path38.relative(cwd, sessionFilePath(cwd, sessionId));
14437
- const eventsFile = path38.relative(cwd, eventsFilePath(cwd, sessionId));
14546
+ const sessionFile = path39.relative(cwd, sessionFilePath(cwd, sessionId));
14547
+ const eventsFile = path39.relative(cwd, eventsFilePath(cwd, sessionId));
14438
14548
  const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
14439
- const tasksDir = path38.join(".kody", "tasks", safeSession);
14549
+ const tasksDir = path39.join(".kody", "tasks", safeSession);
14440
14550
  const candidatePaths = [sessionFile, eventsFile, tasksDir];
14441
- const paths = candidatePaths.filter((p) => fs42.existsSync(path38.join(cwd, p)));
14551
+ const paths = candidatePaths.filter((p) => fs43.existsSync(path39.join(cwd, p)));
14442
14552
  if (paths.length === 0) return;
14443
14553
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
14444
14554
  try {
@@ -14482,7 +14592,7 @@ async function runChat(argv) {
14482
14592
  ${CHAT_HELP}`);
14483
14593
  return 64;
14484
14594
  }
14485
- const cwd = args.cwd ? path38.resolve(args.cwd) : process.cwd();
14595
+ const cwd = args.cwd ? path39.resolve(args.cwd) : process.cwd();
14486
14596
  const sessionId = args.sessionId;
14487
14597
  const unpackedSecrets = unpackAllSecrets();
14488
14598
  if (unpackedSecrets > 0) {
@@ -14534,7 +14644,7 @@ ${CHAT_HELP}`);
14534
14644
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
14535
14645
  const meta = readMeta(sessionFile);
14536
14646
  process.stdout.write(
14537
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs42.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
14647
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs43.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
14538
14648
  `
14539
14649
  );
14540
14650
  try {