@kody-ade/kody-engine 0.4.646 → 0.4.647

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
@@ -15,7 +15,7 @@ var init_package = __esm({
15
15
  "package.json"() {
16
16
  package_default = {
17
17
  name: "@kody-ade/kody-engine",
18
- version: "0.4.646",
18
+ version: "0.4.647",
19
19
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative implementation profiles.",
20
20
  license: "MIT",
21
21
  repository: {
@@ -4606,6 +4606,8 @@ async function runAgent(opts) {
4606
4606
  let tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
4607
4607
  let costUsd = 0;
4608
4608
  let messageCount = 0;
4609
+ let turns = 0;
4610
+ let modelUsage = {};
4609
4611
  let finalText = "";
4610
4612
  let getSubmitted;
4611
4613
  const invokedSubagents = /* @__PURE__ */ new Set();
@@ -4632,6 +4634,8 @@ async function runAgent(opts) {
4632
4634
  tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
4633
4635
  costUsd = 0;
4634
4636
  messageCount = 0;
4637
+ turns = 0;
4638
+ modelUsage = {};
4635
4639
  let sawMutatingTool = false;
4636
4640
  let sawTerminalSuccess = false;
4637
4641
  let sawLoginRequired = false;
@@ -4940,6 +4944,9 @@ async function runAgent(opts) {
4940
4944
  }
4941
4945
  }
4942
4946
  }
4947
+ if (m.type === "result") {
4948
+ tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
4949
+ }
4943
4950
  const usage = m.usage;
4944
4951
  if (usage && typeof usage === "object") {
4945
4952
  const i = Number(usage.input_tokens ?? 0);
@@ -4966,6 +4973,12 @@ async function runAgent(opts) {
4966
4973
  if (m.type === "result") {
4967
4974
  const reportedCost = Number(m.total_cost_usd ?? 0);
4968
4975
  if (Number.isFinite(reportedCost) && reportedCost >= 0) costUsd = reportedCost;
4976
+ const reportedTurns = Number(m.num_turns ?? 0);
4977
+ if (Number.isFinite(reportedTurns) && reportedTurns >= 0) turns = reportedTurns;
4978
+ const reportedModelUsage = m.modelUsage;
4979
+ if (reportedModelUsage && typeof reportedModelUsage === "object" && !Array.isArray(reportedModelUsage)) {
4980
+ modelUsage = structuredClone(reportedModelUsage);
4981
+ }
4969
4982
  if (m.subtype === "success") {
4970
4983
  outcome = "completed";
4971
4984
  outcomeKind = "ok";
@@ -5051,6 +5064,8 @@ async function runAgent(opts) {
5051
5064
  tokens,
5052
5065
  costUsd,
5053
5066
  messageCount,
5067
+ turns,
5068
+ modelUsage,
5054
5069
  invokedSubagents: [...invokedSubagents]
5055
5070
  };
5056
5071
  }
@@ -7360,17 +7375,155 @@ var init_state = __esm({
7360
7375
  }
7361
7376
  });
7362
7377
 
7363
- // src/prompt.ts
7378
+ // src/usage.ts
7364
7379
  import * as fs25 from "fs";
7380
+ function safeNumber(value) {
7381
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
7382
+ }
7383
+ function tokenBreakdown(tokens) {
7384
+ const input = safeNumber(tokens?.input);
7385
+ const output = safeNumber(tokens?.output);
7386
+ const cacheRead = safeNumber(tokens?.cacheRead);
7387
+ const cacheCreate = safeNumber(tokens?.cacheCreate);
7388
+ return { input, output, cacheRead, cacheCreate, total: input + output + cacheRead + cacheCreate };
7389
+ }
7390
+ function addTokenBreakdown(left, right) {
7391
+ const input = left.input + right.input;
7392
+ const output = left.output + right.output;
7393
+ const cacheRead = left.cacheRead + right.cacheRead;
7394
+ const cacheCreate = left.cacheCreate + right.cacheCreate;
7395
+ return { input, output, cacheRead, cacheCreate, total: input + output + cacheRead + cacheCreate };
7396
+ }
7397
+ function createRunUsage(tokens, costUsd, details = {}) {
7398
+ if (!tokens && costUsd === void 0 && details.turns === void 0) return void 0;
7399
+ const normalizedTokens = tokenBreakdown(tokens);
7400
+ const modelUsage = {
7401
+ tokens: normalizedTokens,
7402
+ costUsd: safeNumber(costUsd),
7403
+ agentRuns: 1,
7404
+ turns: safeNumber(details.turns)
7405
+ };
7406
+ const reportedModels = Object.entries(details.modelUsage ?? {});
7407
+ const byModel = reportedModels.length > 0 ? Object.fromEntries(
7408
+ reportedModels.map(([model, usage]) => {
7409
+ const modelTokens = tokenBreakdown({
7410
+ input: usage.inputTokens,
7411
+ output: usage.outputTokens,
7412
+ cacheRead: usage.cacheReadInputTokens,
7413
+ cacheCreate: usage.cacheCreationInputTokens
7414
+ });
7415
+ return [
7416
+ model,
7417
+ {
7418
+ tokens: modelTokens,
7419
+ costUsd: safeNumber(usage.costUSD),
7420
+ agentRuns: 1,
7421
+ turns: reportedModels.length === 1 ? safeNumber(details.turns) : 0
7422
+ }
7423
+ ];
7424
+ })
7425
+ ) : details.model ? { [details.model]: modelUsage } : {};
7426
+ return {
7427
+ version: 1,
7428
+ ...modelUsage,
7429
+ byModel
7430
+ };
7431
+ }
7432
+ function isModelRunUsage(value) {
7433
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
7434
+ const usage = value;
7435
+ if (!usage.tokens || typeof usage.tokens !== "object" || Array.isArray(usage.tokens)) return false;
7436
+ return [
7437
+ usage.tokens.input,
7438
+ usage.tokens.output,
7439
+ usage.tokens.cacheRead,
7440
+ usage.tokens.cacheCreate,
7441
+ usage.tokens.total,
7442
+ usage.costUsd,
7443
+ usage.agentRuns,
7444
+ usage.turns
7445
+ ].every((number) => typeof number === "number" && Number.isFinite(number) && number >= 0);
7446
+ }
7447
+ function parseRunUsage(value) {
7448
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
7449
+ const usage = value;
7450
+ if (usage.version !== 1 || !isModelRunUsage(usage)) return void 0;
7451
+ if (!usage.byModel || typeof usage.byModel !== "object" || Array.isArray(usage.byModel)) return void 0;
7452
+ if (!Object.values(usage.byModel).every(isModelRunUsage)) return void 0;
7453
+ return structuredClone(usage);
7454
+ }
7455
+ function mergeRunUsage(left, right) {
7456
+ if (!left) return right ? structuredClone(right) : void 0;
7457
+ if (!right) return structuredClone(left);
7458
+ const byModel = {};
7459
+ for (const model of /* @__PURE__ */ new Set([...Object.keys(left.byModel), ...Object.keys(right.byModel)])) {
7460
+ const first = left.byModel[model];
7461
+ const second = right.byModel[model];
7462
+ if (!first) {
7463
+ byModel[model] = structuredClone(second);
7464
+ } else if (!second) {
7465
+ byModel[model] = structuredClone(first);
7466
+ } else {
7467
+ byModel[model] = {
7468
+ tokens: addTokenBreakdown(first.tokens, second.tokens),
7469
+ costUsd: first.costUsd + second.costUsd,
7470
+ agentRuns: first.agentRuns + second.agentRuns,
7471
+ turns: first.turns + second.turns
7472
+ };
7473
+ }
7474
+ }
7475
+ return {
7476
+ version: 1,
7477
+ tokens: addTokenBreakdown(left.tokens, right.tokens),
7478
+ costUsd: left.costUsd + right.costUsd,
7479
+ agentRuns: left.agentRuns + right.agentRuns,
7480
+ turns: left.turns + right.turns,
7481
+ byModel
7482
+ };
7483
+ }
7484
+ function formatRunUsageMarker(subject, usage) {
7485
+ return `KODY_USAGE=${JSON.stringify({ subject, ...usage })}`;
7486
+ }
7487
+ function appendRunUsageSummary(summaryPath, subject, usage) {
7488
+ if (!summaryPath) return;
7489
+ const tokens = usage.tokens;
7490
+ const lines = [
7491
+ `### Kody usage - ${subject}`,
7492
+ "",
7493
+ `- **Tokens:** ${tokens.input.toLocaleString()} input / ${tokens.cacheRead.toLocaleString()} cache-read / ${tokens.cacheCreate.toLocaleString()} cache-create / ${tokens.output.toLocaleString()} output / ${tokens.total.toLocaleString()} total`,
7494
+ `- **Agent work:** ${usage.agentRuns.toLocaleString()} runs / ${usage.turns.toLocaleString()} turns`,
7495
+ `- **Provider-reported cost:** $${usage.costUsd.toFixed(4)}`,
7496
+ ""
7497
+ ];
7498
+ try {
7499
+ fs25.appendFileSync(summaryPath, `${lines.join("\n")}
7500
+ `);
7501
+ } catch {
7502
+ }
7503
+ }
7504
+ function publishRunUsage(subject, usage) {
7505
+ if (!usage) return;
7506
+ process.stdout.write(`${formatRunUsageMarker(subject, usage)}
7507
+ `);
7508
+ appendRunUsageSummary(process.env.GITHUB_STEP_SUMMARY, subject, usage);
7509
+ }
7510
+ var init_usage = __esm({
7511
+ "src/usage.ts"() {
7512
+ "use strict";
7513
+ }
7514
+ });
7515
+
7516
+ // src/prompt.ts
7517
+ import * as fs26 from "fs";
7365
7518
  import * as path25 from "path";
7366
7519
  function loadProjectConventions(projectDir) {
7367
7520
  const out = [];
7368
7521
  for (const rel of CONVENTION_FILES) {
7369
7522
  const abs = path25.join(projectDir, rel);
7370
- if (!fs25.existsSync(abs)) continue;
7523
+ if (!fs26.existsSync(abs)) continue;
7371
7524
  let content;
7372
7525
  try {
7373
- content = fs25.readFileSync(abs, "utf-8");
7526
+ content = fs26.readFileSync(abs, "utf-8");
7374
7527
  } catch {
7375
7528
  continue;
7376
7529
  }
@@ -7621,7 +7774,7 @@ var loadMemoryContext_exports = {};
7621
7774
  __export(loadMemoryContext_exports, {
7622
7775
  loadMemoryContext: () => loadMemoryContext
7623
7776
  });
7624
- import * as fs26 from "fs";
7777
+ import * as fs27 from "fs";
7625
7778
  import * as path26 from "path";
7626
7779
  function formatBlockFromBackend(docs) {
7627
7780
  const pages = docs.flatMap((record2) => {
@@ -7645,13 +7798,13 @@ function collectPages(memoryAbs) {
7645
7798
  walkMd(memoryAbs, (file) => {
7646
7799
  let stat;
7647
7800
  try {
7648
- stat = fs26.statSync(file);
7801
+ stat = fs27.statSync(file);
7649
7802
  } catch {
7650
7803
  return;
7651
7804
  }
7652
7805
  let raw;
7653
7806
  try {
7654
- raw = fs26.readFileSync(file, "utf-8");
7807
+ raw = fs27.readFileSync(file, "utf-8");
7655
7808
  } catch {
7656
7809
  return;
7657
7810
  }
@@ -7727,7 +7880,7 @@ function walkMd(root, visit) {
7727
7880
  const dir = stack.pop();
7728
7881
  let names;
7729
7882
  try {
7730
- names = fs26.readdirSync(dir);
7883
+ names = fs27.readdirSync(dir);
7731
7884
  } catch {
7732
7885
  continue;
7733
7886
  }
@@ -7736,7 +7889,7 @@ function walkMd(root, visit) {
7736
7889
  const full = path26.join(dir, name);
7737
7890
  let stat;
7738
7891
  try {
7739
- stat = fs26.statSync(full);
7892
+ stat = fs27.statSync(full);
7740
7893
  } catch {
7741
7894
  continue;
7742
7895
  }
@@ -7772,7 +7925,7 @@ var init_loadMemoryContext = __esm({
7772
7925
  return;
7773
7926
  }
7774
7927
  const memoryAbs = path26.join(ctx.cwd, MEMORY_DIR_RELATIVE);
7775
- if (!fs26.existsSync(memoryAbs)) {
7928
+ if (!fs27.existsSync(memoryAbs)) {
7776
7929
  ctx.data.memoryContext = "";
7777
7930
  return;
7778
7931
  }
@@ -7816,11 +7969,11 @@ var init_loadCoverageRules = __esm({
7816
7969
 
7817
7970
  // src/container.ts
7818
7971
  import { execFileSync as execFileSync3 } from "child_process";
7819
- import * as fs27 from "fs";
7972
+ import * as fs28 from "fs";
7820
7973
  function getProfileInputsForChild(profileName, _cwd) {
7821
7974
  try {
7822
7975
  const profilePath = resolveProfilePath(profileName);
7823
- if (!fs27.existsSync(profilePath)) return null;
7976
+ if (!fs28.existsSync(profilePath)) return null;
7824
7977
  return loadProfile(profilePath).inputs;
7825
7978
  } catch {
7826
7979
  return null;
@@ -7952,6 +8105,7 @@ async function runContainerLoop(profile, ctx, input) {
7952
8105
  // is off, so children fall back to their own loaders.
7953
8106
  preloadedData: preloadedSnapshot
7954
8107
  });
8108
+ ctx.output.usage = mergeRunUsage(ctx.output.usage, childOut.usage);
7955
8109
  emitEvent(input.cwd, {
7956
8110
  implementation: profile.name,
7957
8111
  kind: "container_child",
@@ -8103,6 +8257,7 @@ var init_container = __esm({
8103
8257
  init_executor();
8104
8258
  init_profile();
8105
8259
  init_state();
8260
+ init_usage();
8106
8261
  CONTAINER_MAX_ITERATIONS = 50;
8107
8262
  }
8108
8263
  });
@@ -8284,7 +8439,7 @@ var init_lifecycleLabels = __esm({
8284
8439
 
8285
8440
  // src/litellm.ts
8286
8441
  import { execFileSync as execFileSync4, spawn as spawn4 } from "child_process";
8287
- import * as fs28 from "fs";
8442
+ import * as fs29 from "fs";
8288
8443
  import * as net from "net";
8289
8444
  import * as os4 from "os";
8290
8445
  import * as path27 from "path";
@@ -8396,7 +8551,7 @@ function locateLitellmScript() {
8396
8551
  }
8397
8552
  function resolveLitellmCommand() {
8398
8553
  const imageScript = "/opt/venv/bin/litellm";
8399
- if (fs28.existsSync(imageScript)) return imageScript;
8554
+ if (fs29.existsSync(imageScript)) return imageScript;
8400
8555
  try {
8401
8556
  execFileSync4("which", ["litellm"], { timeout: 3e3, stdio: "pipe" });
8402
8557
  return "litellm";
@@ -8460,12 +8615,12 @@ async function startLitellmProxy(input) {
8460
8615
  const portMatch = activeUrl.match(/:(\d+)/);
8461
8616
  const port = portMatch ? portMatch[1] : "4000";
8462
8617
  const configPath = path27.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.yaml`);
8463
- fs28.writeFileSync(configPath, input.configYaml);
8618
+ fs29.writeFileSync(configPath, input.configYaml);
8464
8619
  const args = ["--config", configPath, "--port", port];
8465
8620
  const nextLogPath = path27.join(os4.tmpdir(), `kody-local-litellm-${Date.now()}.log`);
8466
- const outFd = fs28.openSync(nextLogPath, "w");
8621
+ const outFd = fs29.openSync(nextLogPath, "w");
8467
8622
  child = spawn4(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
8468
- fs28.closeSync(outFd);
8623
+ fs29.closeSync(outFd);
8469
8624
  logPath = nextLogPath;
8470
8625
  };
8471
8626
  const waitForHealth = async () => {
@@ -8479,7 +8634,7 @@ async function startLitellmProxy(input) {
8479
8634
  const readLogTail = () => {
8480
8635
  if (!logPath) return "";
8481
8636
  try {
8482
- return fs28.readFileSync(logPath, "utf-8").slice(-2e3);
8637
+ return fs29.readFileSync(logPath, "utf-8").slice(-2e3);
8483
8638
  } catch {
8484
8639
  return "";
8485
8640
  }
@@ -8563,9 +8718,9 @@ function canListen(port, host) {
8563
8718
  }
8564
8719
  function readDotenvApiKeys(projectDir) {
8565
8720
  const dotenvPath = path27.join(projectDir, ".env");
8566
- if (!fs28.existsSync(dotenvPath)) return {};
8721
+ if (!fs29.existsSync(dotenvPath)) return {};
8567
8722
  const result = {};
8568
- for (const rawLine of fs28.readFileSync(dotenvPath, "utf-8").split("\n")) {
8723
+ for (const rawLine of fs29.readFileSync(dotenvPath, "utf-8").split("\n")) {
8569
8724
  const line = rawLine.trim();
8570
8725
  if (!line || line.startsWith("#")) continue;
8571
8726
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -8647,7 +8802,8 @@ function finalizedRunIndexRow(row, result) {
8647
8802
  status: result.status,
8648
8803
  updatedAt: result.updatedAt,
8649
8804
  summary: result.reason ?? row.summary,
8650
- ...result.output === void 0 ? {} : { output: result.output }
8805
+ ...result.output === void 0 ? {} : { output: result.output },
8806
+ ...result.usage === void 0 ? {} : { usage: result.usage }
8651
8807
  };
8652
8808
  }
8653
8809
  function runIndexRowFromJobContext(input) {
@@ -8696,7 +8852,8 @@ function runIndexRowFromJobContext(input) {
8696
8852
  reasoningEffort: stringValue(input.data.jobReasoningEffort) ?? void 0,
8697
8853
  target: input.data.jobTarget,
8698
8854
  sourceType: "job",
8699
- output: input.data.capabilityOutput
8855
+ output: input.data.capabilityOutput,
8856
+ usage: input.usage
8700
8857
  });
8701
8858
  }
8702
8859
  function runIndexRowFromGoalEvents(goalId, logPath, events) {
@@ -9239,7 +9396,7 @@ var init_pushWithRetry = __esm({
9239
9396
  // src/commit.ts
9240
9397
  import { execFileSync as execFileSync6 } from "child_process";
9241
9398
  import { isDeepStrictEqual } from "util";
9242
- import * as fs29 from "fs";
9399
+ import * as fs30 from "fs";
9243
9400
  import * as path28 from "path";
9244
9401
  function isGitHubYamlPath(filePath) {
9245
9402
  const normalized = filePath.replace(/^\.\/+/, "");
@@ -9284,17 +9441,17 @@ function ensureGitIdentity(cwd) {
9284
9441
  function abortUnfinishedGitOps(cwd) {
9285
9442
  const aborted = [];
9286
9443
  const gitDir = path28.join(cwd ?? process.cwd(), ".git");
9287
- if (!fs29.existsSync(gitDir)) return aborted;
9288
- if (fs29.existsSync(path28.join(gitDir, "MERGE_HEAD"))) {
9444
+ if (!fs30.existsSync(gitDir)) return aborted;
9445
+ if (fs30.existsSync(path28.join(gitDir, "MERGE_HEAD"))) {
9289
9446
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
9290
9447
  }
9291
- if (fs29.existsSync(path28.join(gitDir, "CHERRY_PICK_HEAD"))) {
9448
+ if (fs30.existsSync(path28.join(gitDir, "CHERRY_PICK_HEAD"))) {
9292
9449
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
9293
9450
  }
9294
- if (fs29.existsSync(path28.join(gitDir, "REVERT_HEAD"))) {
9451
+ if (fs30.existsSync(path28.join(gitDir, "REVERT_HEAD"))) {
9295
9452
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
9296
9453
  }
9297
- if (fs29.existsSync(path28.join(gitDir, "rebase-merge")) || fs29.existsSync(path28.join(gitDir, "rebase-apply"))) {
9454
+ if (fs30.existsSync(path28.join(gitDir, "rebase-merge")) || fs30.existsSync(path28.join(gitDir, "rebase-apply"))) {
9298
9455
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
9299
9456
  }
9300
9457
  try {
@@ -9377,7 +9534,7 @@ function isTrustedConfigActivationChange(filePath, deliveryPathAllowlist, delive
9377
9534
  if (filePath !== "kody.config.json" || !deliveryPathAllowlist.includes(filePath)) return false;
9378
9535
  try {
9379
9536
  const before = JSON.parse(git(["show", "HEAD:kody.config.json"], cwd));
9380
- const after = JSON.parse(fs29.readFileSync(path28.join(cwd ?? process.cwd(), filePath), "utf-8"));
9537
+ const after = JSON.parse(fs30.readFileSync(path28.join(cwd ?? process.cwd(), filePath), "utf-8"));
9381
9538
  return isSafeConfigChange(before, after, deliveryConfigAllowlist[filePath] ?? []);
9382
9539
  } catch {
9383
9540
  return false;
@@ -9440,7 +9597,7 @@ function commitAndPush(branch, agentMessage, cwd, deliveryPathAllowlist = [], de
9440
9597
  (f) => isForbiddenPath(f, deliveryPathAllowlist) && !isTrustedConfigActivationChange(f, deliveryPathAllowlist, deliveryConfigAllowlist, cwd)
9441
9598
  );
9442
9599
  const omittedFiles = forbiddenFiles.filter(isReportableDeliveryOmission);
9443
- const mergeHeadExists = fs29.existsSync(path28.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
9600
+ const mergeHeadExists = fs30.existsSync(path28.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
9444
9601
  if (allowedFiles.length === 0 && !mergeHeadExists) {
9445
9602
  return { committed: false, pushed: false, sha: "", message: "", omittedFiles };
9446
9603
  }
@@ -10096,7 +10253,7 @@ var init_state2 = __esm({
10096
10253
  });
10097
10254
 
10098
10255
  // src/goal/runLog.ts
10099
- import * as fs30 from "fs";
10256
+ import * as fs31 from "fs";
10100
10257
  function stageGoalRunLogEvent(data, goalId, event, at = nowIso()) {
10101
10258
  const logs = goalRunLogs(data);
10102
10259
  const existing = logs[goalId];
@@ -10438,8 +10595,8 @@ function readGithubEvent() {
10438
10595
  const eventPath = process.env.GITHUB_EVENT_PATH;
10439
10596
  if (!eventPath) return null;
10440
10597
  try {
10441
- if (!fs30.existsSync(eventPath)) return null;
10442
- const parsed = JSON.parse(fs30.readFileSync(eventPath, "utf-8"));
10598
+ if (!fs31.existsSync(eventPath)) return null;
10599
+ const parsed = JSON.parse(fs31.readFileSync(eventPath, "utf-8"));
10443
10600
  return recordValue3(parsed);
10444
10601
  } catch {
10445
10602
  return null;
@@ -10549,7 +10706,7 @@ var init_stateStore = __esm({
10549
10706
  });
10550
10707
 
10551
10708
  // src/goal/targetLoopResolution.ts
10552
- import * as fs31 from "fs";
10709
+ import * as fs32 from "fs";
10553
10710
  import * as path29 from "path";
10554
10711
  async function resolveActiveGoalLoopTarget(config, cwd, loopGoalId, loopGoal) {
10555
10712
  const targetId = loopGoal.loopTarget?.id.trim() ?? "";
@@ -10631,8 +10788,8 @@ function loadGoalTemplate(cwd, targetId) {
10631
10788
  return readJsonObject2(path29.join(cwd, ".kody-engine", "definitions", "goals", targetId, "state.json"));
10632
10789
  }
10633
10790
  function readJsonObject2(filePath) {
10634
- if (!fs31.existsSync(filePath)) return null;
10635
- const parsed = JSON.parse(fs31.readFileSync(filePath, "utf8"));
10791
+ if (!fs32.existsSync(filePath)) return null;
10792
+ const parsed = JSON.parse(fs32.readFileSync(filePath, "utf8"));
10636
10793
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
10637
10794
  throw new Error(`goal template ${filePath} must be a JSON object`);
10638
10795
  }
@@ -11007,7 +11164,7 @@ var init_backendStateBackend = __esm({
11007
11164
  });
11008
11165
 
11009
11166
  // src/scripts/jobState/localFileBackend.ts
11010
- import * as fs32 from "fs";
11167
+ import * as fs33 from "fs";
11011
11168
  import * as path30 from "path";
11012
11169
  function sanitizeKey(s) {
11013
11170
  return s.replace(/[^A-Za-z0-9._-]/g, "-");
@@ -11079,7 +11236,7 @@ var init_localFileBackend = __esm({
11079
11236
  `);
11080
11237
  return;
11081
11238
  }
11082
- fs32.mkdirSync(this.absDir, { recursive: true });
11239
+ fs33.mkdirSync(this.absDir, { recursive: true });
11083
11240
  const prefix = this.cacheKeyPrefix();
11084
11241
  const probeKey = `${prefix}probe-${Date.now()}`;
11085
11242
  try {
@@ -11108,7 +11265,7 @@ var init_localFileBackend = __esm({
11108
11265
  `);
11109
11266
  return;
11110
11267
  }
11111
- if (!fs32.existsSync(this.absDir)) {
11268
+ if (!fs33.existsSync(this.absDir)) {
11112
11269
  return;
11113
11270
  }
11114
11271
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -11125,10 +11282,10 @@ var init_localFileBackend = __esm({
11125
11282
  load(slug) {
11126
11283
  const relPath = stateFilePath(this.jobsDir, slug);
11127
11284
  const absPath = path30.resolve(this.cwd, relPath);
11128
- if (!fs32.existsSync(absPath)) {
11285
+ if (!fs33.existsSync(absPath)) {
11129
11286
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
11130
11287
  }
11131
- const raw = fs32.readFileSync(absPath, "utf-8");
11288
+ const raw = fs33.readFileSync(absPath, "utf-8");
11132
11289
  let parsed;
11133
11290
  try {
11134
11291
  parsed = JSON.parse(raw);
@@ -11146,12 +11303,12 @@ var init_localFileBackend = __esm({
11146
11303
  return false;
11147
11304
  }
11148
11305
  const absPath = path30.resolve(this.cwd, loaded.path);
11149
- fs32.mkdirSync(path30.dirname(absPath), { recursive: true });
11306
+ fs33.mkdirSync(path30.dirname(absPath), { recursive: true });
11150
11307
  const body = `${JSON.stringify(next, null, 2)}
11151
11308
  `;
11152
11309
  const tmpPath = `${absPath}.${process.pid}.tmp`;
11153
- fs32.writeFileSync(tmpPath, body, "utf-8");
11154
- fs32.renameSync(tmpPath, absPath);
11310
+ fs33.writeFileSync(tmpPath, body, "utf-8");
11311
+ fs33.renameSync(tmpPath, absPath);
11155
11312
  return true;
11156
11313
  }
11157
11314
  cacheKeyPrefix() {
@@ -13060,7 +13217,7 @@ var init_classifyByLabel = __esm({
13060
13217
 
13061
13218
  // src/scripts/commitAndPush.ts
13062
13219
  import { createHash as createHash5 } from "crypto";
13063
- import * as fs33 from "fs";
13220
+ import * as fs34 from "fs";
13064
13221
  import * as path32 from "path";
13065
13222
  function sentinelPathForStage(cwd, profileName, workflowExecutionKey) {
13066
13223
  const runId = resolveRunId();
@@ -13083,9 +13240,9 @@ var init_commitAndPush = __esm({
13083
13240
  }
13084
13241
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
13085
13242
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name, ctx.data.workflowExecutionKey) : null;
13086
- if (sentinel && fs33.existsSync(sentinel)) {
13243
+ if (sentinel && fs34.existsSync(sentinel)) {
13087
13244
  try {
13088
- const replay = JSON.parse(fs33.readFileSync(sentinel, "utf-8"));
13245
+ const replay = JSON.parse(fs34.readFileSync(sentinel, "utf-8"));
13089
13246
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
13090
13247
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
13091
13248
  if (Array.isArray(replay.deliveryOmissions)) ctx.data.deliveryOmissions = replay.deliveryOmissions;
@@ -13149,8 +13306,8 @@ var init_commitAndPush = __esm({
13149
13306
  const result = ctx.data.commitResult;
13150
13307
  if (sentinel && result?.committed) {
13151
13308
  try {
13152
- fs33.mkdirSync(path32.dirname(sentinel), { recursive: true });
13153
- fs33.writeFileSync(
13309
+ fs34.mkdirSync(path32.dirname(sentinel), { recursive: true });
13310
+ fs34.writeFileSync(
13154
13311
  sentinel,
13155
13312
  JSON.stringify(
13156
13313
  {
@@ -13277,7 +13434,7 @@ var init_acceptanceCriteria = __esm({
13277
13434
  });
13278
13435
 
13279
13436
  // src/scripts/composePrompt.ts
13280
- import * as fs34 from "fs";
13437
+ import * as fs35 from "fs";
13281
13438
  import * as path33 from "path";
13282
13439
  function fenceUntrusted(value) {
13283
13440
  if (value.trim().length === 0) return value;
@@ -13419,7 +13576,7 @@ var init_composePrompt = __esm({
13419
13576
  break;
13420
13577
  }
13421
13578
  try {
13422
- template = fs34.readFileSync(c, "utf-8");
13579
+ template = fs35.readFileSync(c, "utf-8");
13423
13580
  templatePath = c;
13424
13581
  break;
13425
13582
  } catch (err) {
@@ -13430,7 +13587,7 @@ var init_composePrompt = __esm({
13430
13587
  if (!templatePath) {
13431
13588
  let dirState;
13432
13589
  try {
13433
- dirState = `dir contents: [${fs34.readdirSync(profile.dir).join(", ")}]`;
13590
+ dirState = `dir contents: [${fs35.readdirSync(profile.dir).join(", ")}]`;
13434
13591
  } catch (err) {
13435
13592
  dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
13436
13593
  }
@@ -14168,7 +14325,7 @@ var init_deriveQaScopeFromIssue = __esm({
14168
14325
 
14169
14326
  // src/scripts/diagMcp.ts
14170
14327
  import { execFileSync as execFileSync9 } from "child_process";
14171
- import * as fs35 from "fs";
14328
+ import * as fs36 from "fs";
14172
14329
  import * as os5 from "os";
14173
14330
  import * as path34 from "path";
14174
14331
  var diagMcp;
@@ -14180,7 +14337,7 @@ var init_diagMcp = __esm({
14180
14337
  const cacheDir = path34.join(home, ".cache", "ms-playwright");
14181
14338
  let entries = [];
14182
14339
  try {
14183
- entries = fs35.readdirSync(cacheDir);
14340
+ entries = fs36.readdirSync(cacheDir);
14184
14341
  } catch {
14185
14342
  }
14186
14343
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -14208,13 +14365,13 @@ var init_diagMcp = __esm({
14208
14365
  });
14209
14366
 
14210
14367
  // src/scripts/frameworkDetectors.ts
14211
- import * as fs36 from "fs";
14368
+ import * as fs37 from "fs";
14212
14369
  import * as path35 from "path";
14213
14370
  function detectFrameworks(cwd) {
14214
14371
  const out = [];
14215
14372
  let deps = {};
14216
14373
  try {
14217
- const pkg = JSON.parse(fs36.readFileSync(path35.join(cwd, "package.json"), "utf-8"));
14374
+ const pkg = JSON.parse(fs37.readFileSync(path35.join(cwd, "package.json"), "utf-8"));
14218
14375
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
14219
14376
  } catch {
14220
14377
  return out;
@@ -14251,7 +14408,7 @@ function detectFrameworks(cwd) {
14251
14408
  }
14252
14409
  function findFile(cwd, candidates) {
14253
14410
  for (const c of candidates) {
14254
- if (fs36.existsSync(path35.join(cwd, c))) return c;
14411
+ if (fs37.existsSync(path35.join(cwd, c))) return c;
14255
14412
  }
14256
14413
  return null;
14257
14414
  }
@@ -14259,17 +14416,17 @@ function discoverPayloadCollections(cwd) {
14259
14416
  const out = [];
14260
14417
  for (const dir of COLLECTION_DIRS) {
14261
14418
  const full = path35.join(cwd, dir);
14262
- if (!fs36.existsSync(full)) continue;
14419
+ if (!fs37.existsSync(full)) continue;
14263
14420
  let files;
14264
14421
  try {
14265
- files = fs36.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
14422
+ files = fs37.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
14266
14423
  } catch {
14267
14424
  continue;
14268
14425
  }
14269
14426
  for (const file of files) {
14270
14427
  try {
14271
14428
  const filePath = path35.join(full, file);
14272
- const content = fs36.readFileSync(filePath, "utf-8").slice(0, 1e4);
14429
+ const content = fs37.readFileSync(filePath, "utf-8").slice(0, 1e4);
14273
14430
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
14274
14431
  if (!slugMatch) continue;
14275
14432
  const slug = slugMatch[1];
@@ -14297,10 +14454,10 @@ function discoverAdminComponents(cwd, collections) {
14297
14454
  const out = [];
14298
14455
  for (const dir of ADMIN_COMPONENT_DIRS) {
14299
14456
  const full = path35.join(cwd, dir);
14300
- if (!fs36.existsSync(full)) continue;
14457
+ if (!fs37.existsSync(full)) continue;
14301
14458
  let entries;
14302
14459
  try {
14303
- entries = fs36.readdirSync(full, { withFileTypes: true });
14460
+ entries = fs37.readdirSync(full, { withFileTypes: true });
14304
14461
  } catch {
14305
14462
  continue;
14306
14463
  }
@@ -14310,7 +14467,7 @@ function discoverAdminComponents(cwd, collections) {
14310
14467
  let filePath;
14311
14468
  if (entry.isDirectory()) {
14312
14469
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
14313
- (f) => fs36.existsSync(path35.join(entryPath, f))
14470
+ (f) => fs37.existsSync(path35.join(entryPath, f))
14314
14471
  );
14315
14472
  if (!indexFile) continue;
14316
14473
  name = entry.name;
@@ -14325,7 +14482,7 @@ function discoverAdminComponents(cwd, collections) {
14325
14482
  if (collections) {
14326
14483
  for (const col of collections) {
14327
14484
  try {
14328
- const colContent = fs36.readFileSync(path35.join(cwd, col.filePath), "utf-8");
14485
+ const colContent = fs37.readFileSync(path35.join(cwd, col.filePath), "utf-8");
14329
14486
  if (colContent.includes(name)) {
14330
14487
  usedInCollection = col.slug;
14331
14488
  break;
@@ -14344,7 +14501,7 @@ function scanApiRoutes(cwd) {
14344
14501
  const appDirs = ["src/app", "app"];
14345
14502
  for (const appDir of appDirs) {
14346
14503
  const apiDir = path35.join(cwd, appDir, "api");
14347
- if (!fs36.existsSync(apiDir)) continue;
14504
+ if (!fs37.existsSync(apiDir)) continue;
14348
14505
  walkApiRoutes(apiDir, "/api", cwd, out);
14349
14506
  break;
14350
14507
  }
@@ -14353,14 +14510,14 @@ function scanApiRoutes(cwd) {
14353
14510
  function walkApiRoutes(dir, prefix, cwd, out) {
14354
14511
  let entries;
14355
14512
  try {
14356
- entries = fs36.readdirSync(dir, { withFileTypes: true });
14513
+ entries = fs37.readdirSync(dir, { withFileTypes: true });
14357
14514
  } catch {
14358
14515
  return;
14359
14516
  }
14360
14517
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
14361
14518
  if (routeFile) {
14362
14519
  try {
14363
- const content = fs36.readFileSync(path35.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
14520
+ const content = fs37.readFileSync(path35.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
14364
14521
  const methods = HTTP_METHODS.filter(
14365
14522
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
14366
14523
  );
@@ -14394,9 +14551,9 @@ function scanEnvVars(cwd) {
14394
14551
  const candidates = [".env.example", ".env.local.example", ".env.template"];
14395
14552
  for (const envFile of candidates) {
14396
14553
  const envPath = path35.join(cwd, envFile);
14397
- if (!fs36.existsSync(envPath)) continue;
14554
+ if (!fs37.existsSync(envPath)) continue;
14398
14555
  try {
14399
- const content = fs36.readFileSync(envPath, "utf-8");
14556
+ const content = fs37.readFileSync(envPath, "utf-8");
14400
14557
  const vars = [];
14401
14558
  for (const line of content.split("\n")) {
14402
14559
  const trimmed = line.trim();
@@ -14441,7 +14598,7 @@ var init_frameworkDetectors = __esm({
14441
14598
  });
14442
14599
 
14443
14600
  // src/scripts/discoverQaContext.ts
14444
- import * as fs37 from "fs";
14601
+ import * as fs38 from "fs";
14445
14602
  import * as path36 from "path";
14446
14603
  function runQaDiscovery(cwd) {
14447
14604
  const out = {
@@ -14473,9 +14630,9 @@ function runQaDiscovery(cwd) {
14473
14630
  }
14474
14631
  function detectDevServer(cwd, out) {
14475
14632
  try {
14476
- const pkg = JSON.parse(fs37.readFileSync(path36.join(cwd, "package.json"), "utf-8"));
14633
+ const pkg = JSON.parse(fs38.readFileSync(path36.join(cwd, "package.json"), "utf-8"));
14477
14634
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
14478
- const pm = fs37.existsSync(path36.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs37.existsSync(path36.join(cwd, "yarn.lock")) ? "yarn" : fs37.existsSync(path36.join(cwd, "bun.lockb")) ? "bun" : "npm";
14635
+ const pm = fs38.existsSync(path36.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs38.existsSync(path36.join(cwd, "yarn.lock")) ? "yarn" : fs38.existsSync(path36.join(cwd, "bun.lockb")) ? "bun" : "npm";
14479
14636
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
14480
14637
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
14481
14638
  else if (allDeps.vite) out.devPort = 5173;
@@ -14486,7 +14643,7 @@ function scanFrontendRoutes(cwd, out) {
14486
14643
  const appDirs = ["src/app", "app"];
14487
14644
  for (const appDir of appDirs) {
14488
14645
  const full = path36.join(cwd, appDir);
14489
- if (!fs37.existsSync(full)) continue;
14646
+ if (!fs38.existsSync(full)) continue;
14490
14647
  walkFrontendRoutes(full, "", out);
14491
14648
  break;
14492
14649
  }
@@ -14494,7 +14651,7 @@ function scanFrontendRoutes(cwd, out) {
14494
14651
  function walkFrontendRoutes(dir, prefix, out) {
14495
14652
  let entries;
14496
14653
  try {
14497
- entries = fs37.readdirSync(dir, { withFileTypes: true });
14654
+ entries = fs38.readdirSync(dir, { withFileTypes: true });
14498
14655
  } catch {
14499
14656
  return;
14500
14657
  }
@@ -14536,23 +14693,23 @@ function detectAuthFiles(cwd, out) {
14536
14693
  "src/app/api/oauth"
14537
14694
  ];
14538
14695
  for (const c of candidates) {
14539
- if (fs37.existsSync(path36.join(cwd, c))) out.authFiles.push(c);
14696
+ if (fs38.existsSync(path36.join(cwd, c))) out.authFiles.push(c);
14540
14697
  }
14541
14698
  }
14542
14699
  function detectRoles(cwd, out) {
14543
14700
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
14544
14701
  for (const rp of rolePaths) {
14545
14702
  const dir = path36.join(cwd, rp);
14546
- if (!fs37.existsSync(dir)) continue;
14703
+ if (!fs38.existsSync(dir)) continue;
14547
14704
  let files;
14548
14705
  try {
14549
- files = fs37.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
14706
+ files = fs38.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
14550
14707
  } catch {
14551
14708
  continue;
14552
14709
  }
14553
14710
  for (const f of files) {
14554
14711
  try {
14555
- const content = fs37.readFileSync(path36.join(dir, f), "utf-8").slice(0, 5e3);
14712
+ const content = fs38.readFileSync(path36.join(dir, f), "utf-8").slice(0, 5e3);
14556
14713
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
14557
14714
  if (roleMatches) {
14558
14715
  for (const m of roleMatches) {
@@ -14813,7 +14970,7 @@ var init_dispatchClassified = __esm({
14813
14970
  });
14814
14971
 
14815
14972
  // src/loopDefinitions.ts
14816
- import * as fs38 from "fs";
14973
+ import * as fs39 from "fs";
14817
14974
  import * as path37 from "path";
14818
14975
  function normalizeLoopDefinition(value) {
14819
14976
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
@@ -14842,9 +14999,9 @@ function readLoopDefinition(cwd, id) {
14842
14999
  const roots = loopRoots(cwd);
14843
15000
  for (const root of roots) {
14844
15001
  const filePath = path37.join(root, "loops", id, "loop.json");
14845
- if (!fs38.existsSync(filePath)) continue;
15002
+ if (!fs39.existsSync(filePath)) continue;
14846
15003
  try {
14847
- const loop = normalizeLoopDefinition(JSON.parse(fs38.readFileSync(filePath, "utf8")));
15004
+ const loop = normalizeLoopDefinition(JSON.parse(fs39.readFileSync(filePath, "utf8")));
14848
15005
  if (loop?.id === id) return loop;
14849
15006
  process.stderr.write(`[kody] invalid Loop definition: ${filePath}
14850
15007
  `);
@@ -14864,13 +15021,13 @@ function listLoopDefinitions(cwd) {
14864
15021
  const byId = /* @__PURE__ */ new Map();
14865
15022
  for (const root of roots.reverse()) {
14866
15023
  const loopsDir = path37.join(root, "loops");
14867
- if (!fs38.existsSync(loopsDir)) continue;
14868
- for (const id of fs38.readdirSync(loopsDir).sort()) {
15024
+ if (!fs39.existsSync(loopsDir)) continue;
15025
+ for (const id of fs39.readdirSync(loopsDir).sort()) {
14869
15026
  if (!ID.test(id)) continue;
14870
15027
  const filePath = path37.join(loopsDir, id, "loop.json");
14871
- if (!fs38.existsSync(filePath)) continue;
15028
+ if (!fs39.existsSync(filePath)) continue;
14872
15029
  try {
14873
- const loop = normalizeLoopDefinition(JSON.parse(fs38.readFileSync(filePath, "utf8")));
15030
+ const loop = normalizeLoopDefinition(JSON.parse(fs39.readFileSync(filePath, "utf8")));
14874
15031
  if (loop?.id === id) byId.set(id, loop);
14875
15032
  } catch {
14876
15033
  process.stderr.write(`[kody] unreadable Loop definition: ${filePath}
@@ -16235,15 +16392,15 @@ var init_fixFlow = __esm({
16235
16392
  });
16236
16393
 
16237
16394
  // src/workflow-template.ts
16238
- import * as fs39 from "fs";
16395
+ import * as fs40 from "fs";
16239
16396
  import * as path38 from "path";
16240
16397
  import { fileURLToPath } from "url";
16241
16398
  function loadKodyWorkflowTemplate() {
16242
16399
  const here = path38.dirname(fileURLToPath(import.meta.url));
16243
16400
  const candidates = [path38.resolve(here, "../templates/kody.yml"), path38.resolve(here, "../../templates/kody.yml")];
16244
- const source = candidates.find((candidate) => fs39.existsSync(candidate));
16401
+ const source = candidates.find((candidate) => fs40.existsSync(candidate));
16245
16402
  if (!source) throw new Error(`Kody workflow template is missing: ${KODY_WORKFLOW_TEMPLATE_PATH}`);
16246
- return fs39.readFileSync(source, "utf8");
16403
+ return fs40.readFileSync(source, "utf8");
16247
16404
  }
16248
16405
  var KODY_WORKFLOW_TEMPLATE_PATH;
16249
16406
  var init_workflow_template = __esm({
@@ -16255,7 +16412,7 @@ var init_workflow_template = __esm({
16255
16412
 
16256
16413
  // src/scripts/initFlow.ts
16257
16414
  import { execFileSync as execFileSync14 } from "child_process";
16258
- import * as fs40 from "fs";
16415
+ import * as fs41 from "fs";
16259
16416
  import * as path39 from "path";
16260
16417
  function schemaUrlFromPkg() {
16261
16418
  const fallback = "https://raw.githubusercontent.com/aharonyaircohen/kody-engine/main/kody.config.schema.json";
@@ -16321,21 +16478,21 @@ function performInit(cwd, force, workflowOnly = false) {
16321
16478
  const configPath = path39.join(cwd, "kody.config.json");
16322
16479
  if (workflowOnly) {
16323
16480
  skipped.push("kody.config.json");
16324
- } else if (fs40.existsSync(configPath) && !force) {
16481
+ } else if (fs41.existsSync(configPath) && !force) {
16325
16482
  skipped.push("kody.config.json");
16326
16483
  } else {
16327
16484
  const cfg = makeConfig(cwd, ownerRepo, defaultBranch);
16328
- fs40.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
16485
+ fs41.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
16329
16486
  `);
16330
16487
  wrote.push("kody.config.json");
16331
16488
  }
16332
16489
  const workflowDir = path39.join(cwd, ".github", "workflows");
16333
16490
  const workflowPath = path39.join(workflowDir, "kody.yml");
16334
- if (fs40.existsSync(workflowPath) && !force) {
16491
+ if (fs41.existsSync(workflowPath) && !force) {
16335
16492
  skipped.push(".github/workflows/kody.yml");
16336
16493
  } else {
16337
- fs40.mkdirSync(workflowDir, { recursive: true });
16338
- fs40.writeFileSync(workflowPath, loadKodyWorkflowTemplate());
16494
+ fs41.mkdirSync(workflowDir, { recursive: true });
16495
+ fs41.writeFileSync(workflowPath, loadKodyWorkflowTemplate());
16339
16496
  wrote.push(".github/workflows/kody.yml");
16340
16497
  }
16341
16498
  let labels;
@@ -16388,7 +16545,7 @@ Nothing to do. All files already present. (Use --force to overwrite.)
16388
16545
  });
16389
16546
 
16390
16547
  // src/scripts/loadAgentAdhoc.ts
16391
- import * as fs41 from "fs";
16548
+ import * as fs42 from "fs";
16392
16549
  function resolveMessage(messageArg) {
16393
16550
  const fromComment = readCommentBody();
16394
16551
  if (fromComment) return stripDirective(fromComment);
@@ -16396,9 +16553,9 @@ function resolveMessage(messageArg) {
16396
16553
  }
16397
16554
  function readCommentBody() {
16398
16555
  const eventPath = process.env.GITHUB_EVENT_PATH;
16399
- if (!eventPath || !fs41.existsSync(eventPath)) return "";
16556
+ if (!eventPath || !fs42.existsSync(eventPath)) return "";
16400
16557
  try {
16401
- const event = JSON.parse(fs41.readFileSync(eventPath, "utf-8"));
16558
+ const event = JSON.parse(fs42.readFileSync(eventPath, "utf-8"));
16402
16559
  return String(event.comment?.body ?? "");
16403
16560
  } catch {
16404
16561
  return "";
@@ -16452,10 +16609,10 @@ var init_loadAgentAdhoc = __esm({
16452
16609
  throw new Error("loadAgentAdhoc: ctx.args.agent must be a non-empty slug");
16453
16610
  }
16454
16611
  const agentPath = resolveAgentFile2(ctx.cwd, agentSlug, agentsDir);
16455
- if (!fs41.existsSync(agentPath)) {
16612
+ if (!fs42.existsSync(agentPath)) {
16456
16613
  throw new Error(`loadAgentAdhoc: agent identity not found: ${agentPath}`);
16457
16614
  }
16458
- const { title, body } = parseAgentFile(fs41.readFileSync(agentPath, "utf-8"), agentSlug);
16615
+ const { title, body } = parseAgentFile(fs42.readFileSync(agentPath, "utf-8"), agentSlug);
16459
16616
  const message = resolveMessage(ctx.args.message);
16460
16617
  if (!message) {
16461
16618
  throw new Error(
@@ -16824,7 +16981,7 @@ var init_loadIssueStateComment = __esm({
16824
16981
  });
16825
16982
 
16826
16983
  // src/scripts/loadJobFromFile.ts
16827
- import * as fs42 from "fs";
16984
+ import * as fs43 from "fs";
16828
16985
  import * as path40 from "path";
16829
16986
  function parseJobFile(raw, slug) {
16830
16987
  let stripped = raw;
@@ -16877,12 +17034,12 @@ var init_loadJobFromFile = __esm({
16877
17034
  let agentIdentity = "";
16878
17035
  if (agentSlug) {
16879
17036
  const agentPath = resolveAgentFile2(ctx.cwd, agentSlug, agentsDir);
16880
- if (!fs42.existsSync(agentPath)) {
17037
+ if (!fs43.existsSync(agentPath)) {
16881
17038
  throw new Error(
16882
17039
  `loadJobFromFile: capability '${slug}' declares agent '${agentSlug}' but ${agentPath} does not exist`
16883
17040
  );
16884
17041
  }
16885
- const agentRaw = fs42.readFileSync(agentPath, "utf-8");
17042
+ const agentRaw = fs43.readFileSync(agentPath, "utf-8");
16886
17043
  const parsed = parseJobFile(agentRaw, agentSlug);
16887
17044
  agentTitle = parsed.title;
16888
17045
  agentIdentity = parsed.body;
@@ -16962,7 +17119,7 @@ ${truncate2(issue2.body, FINDING_BODY_MAX_BYTES)}`;
16962
17119
  });
16963
17120
 
16964
17121
  // src/scripts/loadLiveAgent.ts
16965
- import * as fs43 from "fs";
17122
+ import * as fs44 from "fs";
16966
17123
  function tenant(config) {
16967
17124
  const [envOwner, envRepo] = (process.env.GITHUB_REPOSITORY ?? "").split("/");
16968
17125
  const owner = config.github?.owner?.trim() || envOwner;
@@ -17005,7 +17162,7 @@ var init_loadLiveAgent = __esm({
17005
17162
  const agent = String(ctx.args.agent ?? ctx.data.jobAgent ?? "").trim();
17006
17163
  if (!agent) throw new Error("loadLiveAgent: agent is required");
17007
17164
  const file = resolveAgentFile2(ctx.cwd, agent, agentsRoot(ctx.cwd));
17008
- const raw = fs43.existsSync(file) ? fs43.readFileSync(file, "utf8") : "";
17165
+ const raw = fs44.existsSync(file) ? fs44.readFileSync(file, "utf8") : "";
17009
17166
  const metadata = frontmatter(raw);
17010
17167
  const assignedIntent = typeof metadata.primaryIntent === "string" ? metadata.primaryIntent : "";
17011
17168
  const requestedIntent = String(ctx.args.intent ?? "").trim();
@@ -17053,13 +17210,13 @@ var init_loadLiveAgent = __esm({
17053
17210
  });
17054
17211
 
17055
17212
  // src/scripts/kodyVariables.ts
17056
- import * as fs44 from "fs";
17213
+ import * as fs45 from "fs";
17057
17214
  import * as path41 from "path";
17058
17215
  function readKodyVariables(cwd) {
17059
17216
  const full = path41.join(cwd, KODY_VARIABLES_REL_PATH);
17060
17217
  let raw;
17061
17218
  try {
17062
- raw = fs44.readFileSync(full, "utf-8");
17219
+ raw = fs45.readFileSync(full, "utf-8");
17063
17220
  } catch {
17064
17221
  return {};
17065
17222
  }
@@ -17084,7 +17241,7 @@ var init_kodyVariables = __esm({
17084
17241
  });
17085
17242
 
17086
17243
  // src/scripts/loadQaContext.ts
17087
- import * as fs45 from "fs";
17244
+ import * as fs46 from "fs";
17088
17245
  import * as path42 from "path";
17089
17246
  function parseSlugList(value) {
17090
17247
  const inner = value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
@@ -17115,17 +17272,17 @@ function readProfileAgents(raw) {
17115
17272
  }
17116
17273
  function readProfile(cwd) {
17117
17274
  const dir = path42.join(cwd, CONTEXT_DIR_REL_PATH);
17118
- if (!fs45.existsSync(dir)) return "";
17275
+ if (!fs46.existsSync(dir)) return "";
17119
17276
  let entries;
17120
17277
  try {
17121
- entries = fs45.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
17278
+ entries = fs46.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
17122
17279
  } catch {
17123
17280
  return "";
17124
17281
  }
17125
17282
  const blocks = [];
17126
17283
  for (const file of entries) {
17127
17284
  try {
17128
- const raw = fs45.readFileSync(path42.join(dir, file), "utf-8");
17285
+ const raw = fs46.readFileSync(path42.join(dir, file), "utf-8");
17129
17286
  const { agent, body } = readProfileAgents(raw);
17130
17287
  if (!agent.includes(QA_AGENT) && !agent.includes(ALL_AGENTS)) continue;
17131
17288
  blocks.push(`## ${file}
@@ -17175,7 +17332,7 @@ var init_loadQaContext = __esm({
17175
17332
 
17176
17333
  // src/scripts/loadSimpleCapability.ts
17177
17334
  import { randomUUID as randomUUID2 } from "crypto";
17178
- import * as fs46 from "fs";
17335
+ import * as fs47 from "fs";
17179
17336
  import * as os6 from "os";
17180
17337
  import * as path43 from "path";
17181
17338
  function registerCapabilitySubagents(profile, toolRoot, toolFiles) {
@@ -17190,7 +17347,7 @@ function registerCapabilitySubagents(profile, toolRoot, toolFiles) {
17190
17347
  profile.subagentTemplates = {
17191
17348
  ...profile.subagentTemplates ?? {},
17192
17349
  ...Object.fromEntries(
17193
- subagentFiles.map(({ name, file }) => [name, fs46.readFileSync(path43.join(toolRoot, file), "utf-8")])
17350
+ subagentFiles.map(({ name, file }) => [name, fs47.readFileSync(path43.join(toolRoot, file), "utf-8")])
17194
17351
  )
17195
17352
  };
17196
17353
  if (!profile.claudeCode.tools.includes("Agent")) {
@@ -17231,10 +17388,10 @@ function scalar(value) {
17231
17388
  return value;
17232
17389
  }
17233
17390
  function listFiles(root) {
17234
- if (!fs46.existsSync(root)) return [];
17391
+ if (!fs47.existsSync(root)) return [];
17235
17392
  const files = [];
17236
17393
  const visit = (dir) => {
17237
- for (const entry of fs46.readdirSync(dir, { withFileTypes: true })) {
17394
+ for (const entry of fs47.readdirSync(dir, { withFileTypes: true })) {
17238
17395
  const absolute = path43.join(dir, entry.name);
17239
17396
  if (entry.isSymbolicLink()) continue;
17240
17397
  if (entry.isDirectory()) visit(absolute);
@@ -17327,7 +17484,7 @@ var init_loadSimpleCapability = __esm({
17327
17484
  ...skillFiles.flatMap((file) => [
17328
17485
  `### ${file}`,
17329
17486
  "",
17330
- fs46.readFileSync(path43.join(skillRoot, file), "utf-8"),
17487
+ fs47.readFileSync(path43.join(skillRoot, file), "utf-8"),
17331
17488
  ""
17332
17489
  ])
17333
17490
  ] : [],
@@ -17367,7 +17524,7 @@ var init_loadSimpleCapability = __esm({
17367
17524
  });
17368
17525
 
17369
17526
  // src/taskContext.ts
17370
- import * as fs47 from "fs";
17527
+ import * as fs48 from "fs";
17371
17528
  import * as path44 from "path";
17372
17529
  function buildTaskContext(args) {
17373
17530
  return {
@@ -17384,9 +17541,9 @@ function buildTaskContext(args) {
17384
17541
  function persistTaskContext(cwd, ctx) {
17385
17542
  try {
17386
17543
  const dir = runtimeStatePath(cwd, "agent-runs", ctx.runId);
17387
- fs47.mkdirSync(dir, { recursive: true });
17544
+ fs48.mkdirSync(dir, { recursive: true });
17388
17545
  const file = path44.join(dir, "task-context.json");
17389
- fs47.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
17546
+ fs48.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
17390
17547
  `);
17391
17548
  return file;
17392
17549
  } catch (err) {
@@ -18309,7 +18466,7 @@ var init_parseReproOutput = __esm({
18309
18466
  });
18310
18467
 
18311
18468
  // src/scripts/parseSimpleCapabilityOutput.ts
18312
- import * as fs48 from "fs";
18469
+ import * as fs49 from "fs";
18313
18470
  function acceptAuthoritativeCapabilityOutput(ctx, profile, output) {
18314
18471
  ctx.data.agentDone = true;
18315
18472
  delete ctx.data.agentFailureReason;
@@ -18326,11 +18483,11 @@ function stringList2(value) {
18326
18483
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
18327
18484
  }
18328
18485
  function readOutputFile(outputPath) {
18329
- if (!outputPath || !fs48.existsSync(outputPath)) return { found: false };
18486
+ if (!outputPath || !fs49.existsSync(outputPath)) return { found: false };
18330
18487
  try {
18331
- return { found: true, value: JSON.parse(fs48.readFileSync(outputPath, "utf-8")) };
18488
+ return { found: true, value: JSON.parse(fs49.readFileSync(outputPath, "utf-8")) };
18332
18489
  } finally {
18333
- fs48.rmSync(outputPath, { force: true });
18490
+ fs49.rmSync(outputPath, { force: true });
18334
18491
  }
18335
18492
  }
18336
18493
  function parseOutput(text2) {
@@ -18992,7 +19149,7 @@ var init_postResearchComment = __esm({
18992
19149
  });
18993
19150
 
18994
19151
  // src/scripts/prepareBrowserAuth.ts
18995
- import * as fs49 from "fs";
19152
+ import * as fs50 from "fs";
18996
19153
  import * as os7 from "os";
18997
19154
  import * as path45 from "path";
18998
19155
  function appendAuthMessage(ctx, message) {
@@ -19038,8 +19195,8 @@ async function githubJson(url, token, checkName) {
19038
19195
  throw new Error(`GitHub ${checkName} check failed`);
19039
19196
  }
19040
19197
  function writeKodyStorageState(input) {
19041
- const directory = fs49.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
19042
- fs49.chmodSync(directory, 448);
19198
+ const directory = fs50.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
19199
+ fs50.chmodSync(directory, 448);
19043
19200
  const file = path45.join(directory, "storage-state.json");
19044
19201
  const now = Date.now();
19045
19202
  const repoEntry = {
@@ -19071,7 +19228,7 @@ function writeKodyStorageState(input) {
19071
19228
  }
19072
19229
  ]
19073
19230
  };
19074
- fs49.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
19231
+ fs50.writeFileSync(file, JSON.stringify(storageState), { mode: 384 });
19075
19232
  return { directory, file, auth };
19076
19233
  }
19077
19234
  function parseSetCookie(value, hostname) {
@@ -19098,10 +19255,10 @@ function parseSetCookie(value, hostname) {
19098
19255
  }
19099
19256
  function writeCookieStorageState(targetUrl, setCookies) {
19100
19257
  const target = new URL(targetUrl);
19101
- const directory = fs49.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
19102
- fs49.chmodSync(directory, 448);
19258
+ const directory = fs50.mkdtempSync(path45.join(os7.tmpdir(), "kody-browser-auth-"));
19259
+ fs50.chmodSync(directory, 448);
19103
19260
  const file = path45.join(directory, "storage-state.json");
19104
- fs49.writeFileSync(
19261
+ fs50.writeFileSync(
19105
19262
  file,
19106
19263
  JSON.stringify({
19107
19264
  cookies: setCookies.map((cookie) => parseSetCookie(cookie, target.hostname)),
@@ -19122,9 +19279,9 @@ function currentStorageStatePath(args) {
19122
19279
  function browserSessionCookieHeader(profile, targetUrl) {
19123
19280
  const playwright = profile.claudeCode.mcpServers.find((server) => server.name === "playwright");
19124
19281
  const storagePath = currentStorageStatePath(playwright?.args ?? []);
19125
- if (!storagePath || !fs49.existsSync(storagePath)) return void 0;
19282
+ if (!storagePath || !fs50.existsSync(storagePath)) return void 0;
19126
19283
  const hostname = new URL(targetUrl).hostname;
19127
- const state = JSON.parse(fs49.readFileSync(storagePath, "utf-8"));
19284
+ const state = JSON.parse(fs50.readFileSync(storagePath, "utf-8"));
19128
19285
  const cookies = (state.cookies ?? []).filter((cookie) => {
19129
19286
  const domain = cookie.domain.replace(/^\./, "");
19130
19287
  return hostname === domain || hostname.endsWith(`.${domain}`);
@@ -19191,9 +19348,9 @@ async function prepareAccountModelSettings(ctx, profile, input) {
19191
19348
  return true;
19192
19349
  }
19193
19350
  function mergeStorageStates(existingPath, nextPath) {
19194
- if (existingPath === nextPath || !fs49.existsSync(existingPath)) return;
19195
- const existing = JSON.parse(fs49.readFileSync(existingPath, "utf-8"));
19196
- const next = JSON.parse(fs49.readFileSync(nextPath, "utf-8"));
19351
+ if (existingPath === nextPath || !fs50.existsSync(existingPath)) return;
19352
+ const existing = JSON.parse(fs50.readFileSync(existingPath, "utf-8"));
19353
+ const next = JSON.parse(fs50.readFileSync(nextPath, "utf-8"));
19197
19354
  const cookies = /* @__PURE__ */ new Map();
19198
19355
  for (const cookie of [...existing.cookies ?? [], ...next.cookies ?? []]) {
19199
19356
  cookies.set(`${cookie.name}\0${cookie.domain}\0${cookie.path}`, cookie);
@@ -19207,7 +19364,7 @@ function mergeStorageStates(existingPath, nextPath) {
19207
19364
  }
19208
19365
  origins.set(entry.origin, { origin: entry.origin, localStorage: [...localStorage.values()] });
19209
19366
  }
19210
- fs49.writeFileSync(nextPath, JSON.stringify({ cookies: [...cookies.values()], origins: [...origins.values()] }), {
19367
+ fs50.writeFileSync(nextPath, JSON.stringify({ cookies: [...cookies.values()], origins: [...origins.values()] }), {
19211
19368
  mode: 384
19212
19369
  });
19213
19370
  }
@@ -19302,7 +19459,7 @@ async function prepareKodyRepositoryBrowserAuth(ctx, profile, input) {
19302
19459
  configurePlaywright(profile, state.file);
19303
19460
  const authDirectory = state.directory;
19304
19461
  registerRuntimeCleanup(ctx, () => {
19305
- fs49.rmSync(authDirectory, { recursive: true, force: true });
19462
+ fs50.rmSync(authDirectory, { recursive: true, force: true });
19306
19463
  });
19307
19464
  appendAuthMessage(
19308
19465
  ctx,
@@ -19310,7 +19467,7 @@ async function prepareKodyRepositoryBrowserAuth(ctx, profile, input) {
19310
19467
  );
19311
19468
  return true;
19312
19469
  } catch (error) {
19313
- if (state) fs49.rmSync(state.directory, { recursive: true, force: true });
19470
+ if (state) fs50.rmSync(state.directory, { recursive: true, force: true });
19314
19471
  const reason = error instanceof Error ? error.message : String(error);
19315
19472
  appendAuthMessage(
19316
19473
  ctx,
@@ -19337,11 +19494,11 @@ async function prepareEmailPasswordBrowserAuth(ctx, profile, input) {
19337
19494
  state = writeCookieStorageState(input.targetUrl, cookies);
19338
19495
  configurePlaywright(profile, state.file);
19339
19496
  const authDirectory = state.directory;
19340
- registerRuntimeCleanup(ctx, () => fs49.rmSync(authDirectory, { recursive: true, force: true }));
19497
+ registerRuntimeCleanup(ctx, () => fs50.rmSync(authDirectory, { recursive: true, force: true }));
19341
19498
  ctx.data.qaAuthBlock = "Auth: the app is already signed in through an engine-provided browser session. The login credentials are not available to you; never request, reveal, or report them.";
19342
19499
  return true;
19343
19500
  } catch (error) {
19344
- if (state) fs49.rmSync(state.directory, { recursive: true, force: true });
19501
+ if (state) fs50.rmSync(state.directory, { recursive: true, force: true });
19345
19502
  const reason = error instanceof Error ? error.message : String(error);
19346
19503
  ctx.data.qaAuthBlock = `Auth: the engine could not prepare the app login (${reason}). Note this authenticated surface as a gap.`;
19347
19504
  return false;
@@ -21308,7 +21465,7 @@ var init_tickShellRunner = __esm({
21308
21465
  });
21309
21466
 
21310
21467
  // src/scripts/runScheduledImplementationTick.ts
21311
- import * as fs50 from "fs";
21468
+ import * as fs51 from "fs";
21312
21469
  import * as path48 from "path";
21313
21470
  var runScheduledImplementationTick;
21314
21471
  var init_runScheduledImplementationTick = __esm({
@@ -21337,7 +21494,7 @@ var init_runScheduledImplementationTick = __esm({
21337
21494
  return;
21338
21495
  }
21339
21496
  const shellPath = path48.join(profile.dir, shell);
21340
- if (!fs50.existsSync(shellPath)) {
21497
+ if (!fs51.existsSync(shellPath)) {
21341
21498
  ctx.output.exitCode = 99;
21342
21499
  ctx.output.reason = `runScheduledImplementationTick: shell not found: ${shell} (looked in ${profile.dir})`;
21343
21500
  return;
@@ -21396,13 +21553,13 @@ var init_runtimeConnections = __esm({
21396
21553
 
21397
21554
  // src/scripts/runSimpleCapabilityScript.ts
21398
21555
  import { spawnSync as spawnSync3 } from "child_process";
21399
- import * as fs51 from "fs";
21556
+ import * as fs52 from "fs";
21400
21557
  function formatDuration2(timeoutMs) {
21401
21558
  return timeoutMs % 6e4 === 0 ? `${timeoutMs / 6e4} minutes` : `${timeoutMs}ms`;
21402
21559
  }
21403
21560
  function isRegularFile2(filePath) {
21404
21561
  try {
21405
- const stat = fs51.lstatSync(filePath);
21562
+ const stat = fs52.lstatSync(filePath);
21406
21563
  return stat.isFile() && !stat.isSymbolicLink();
21407
21564
  } catch {
21408
21565
  return false;
@@ -21494,7 +21651,7 @@ var init_runSimpleCapabilityScript = __esm({
21494
21651
  });
21495
21652
 
21496
21653
  // src/scripts/runTickScript.ts
21497
- import * as fs52 from "fs";
21654
+ import * as fs53 from "fs";
21498
21655
  import * as path49 from "path";
21499
21656
  var runTickScript;
21500
21657
  var init_runTickScript = __esm({
@@ -21528,7 +21685,7 @@ var init_runTickScript = __esm({
21528
21685
  return;
21529
21686
  }
21530
21687
  const scriptPath = path49.isAbsolute(tickScript) ? tickScript : path49.join(ctx.cwd, tickScript);
21531
- if (!fs52.existsSync(scriptPath)) {
21688
+ if (!fs53.existsSync(scriptPath)) {
21532
21689
  ctx.output.exitCode = 99;
21533
21690
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
21534
21691
  return;
@@ -22735,7 +22892,7 @@ var init_warmupMcp = __esm({
22735
22892
  });
22736
22893
 
22737
22894
  // src/scripts/writeAgentRunSummary.ts
22738
- import * as fs53 from "fs";
22895
+ import * as fs54 from "fs";
22739
22896
  var writeAgentRunSummary;
22740
22897
  var init_writeAgentRunSummary = __esm({
22741
22898
  "src/scripts/writeAgentRunSummary.ts"() {
@@ -22761,7 +22918,7 @@ var init_writeAgentRunSummary = __esm({
22761
22918
  if (reason) lines.push(`- **Reason:** ${reason}`);
22762
22919
  lines.push("");
22763
22920
  try {
22764
- fs53.appendFileSync(summaryPath, `${lines.join("\n")}
22921
+ fs54.appendFileSync(summaryPath, `${lines.join("\n")}
22765
22922
  `);
22766
22923
  } catch {
22767
22924
  }
@@ -23103,7 +23260,7 @@ var init_scripts = __esm({
23103
23260
  });
23104
23261
 
23105
23262
  // src/stateWorkspace.ts
23106
- import * as fs54 from "fs";
23263
+ import * as fs55 from "fs";
23107
23264
  import * as path51 from "path";
23108
23265
  function tenantId(config) {
23109
23266
  const owner = config.github?.owner?.trim() || process.env.GITHUB_REPOSITORY?.split("/")[0]?.trim();
@@ -23112,8 +23269,8 @@ function tenantId(config) {
23112
23269
  }
23113
23270
  function writeRuntimeFile(cwd, relativePath, content) {
23114
23271
  const target = path51.join(cwd, RUNTIME_ROOT, relativePath);
23115
- fs54.mkdirSync(path51.dirname(target), { recursive: true });
23116
- fs54.writeFileSync(target, content, "utf8");
23272
+ fs55.mkdirSync(path51.dirname(target), { recursive: true });
23273
+ fs55.writeFileSync(target, content, "utf8");
23117
23274
  }
23118
23275
  function record(value) {
23119
23276
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -23182,7 +23339,7 @@ async function hydrateStateWorkspace(config, cwd, backendOverride) {
23182
23339
  if (hydratedWorkspaces.has(key)) return;
23183
23340
  const backend = backendOverride ?? createStateBackendFromEnv();
23184
23341
  const root = path51.join(cwd, RUNTIME_ROOT);
23185
- fs54.rmSync(root, { recursive: true, force: true });
23342
+ fs55.rmSync(root, { recursive: true, force: true });
23186
23343
  await Promise.all([
23187
23344
  hydratePrefix(backend, tenant2, cwd, "context:"),
23188
23345
  hydratePrefix(backend, tenant2, cwd, "memory:"),
@@ -23269,7 +23426,7 @@ var init_tools = __esm({
23269
23426
 
23270
23427
  // src/executor.ts
23271
23428
  import { spawn as spawn8 } from "child_process";
23272
- import * as fs55 from "fs";
23429
+ import * as fs56 from "fs";
23273
23430
  import * as os8 from "os";
23274
23431
  import * as path52 from "path";
23275
23432
  function isMutatingPostflight(scriptName) {
@@ -23367,6 +23524,7 @@ async function runImplementation(profileName, input) {
23367
23524
  `);
23368
23525
  else if (out.exitCode !== 0 && out.reason) process.stdout.write(`PR_URL=FAILED: ${out.reason}
23369
23526
  `);
23527
+ publishRunUsage(`implementation:${profileName}`, out.usage);
23370
23528
  return out;
23371
23529
  };
23372
23530
  const resolved = loadRunnableProfile(profileName, input.cwd);
@@ -23490,14 +23648,16 @@ async function runImplementation(profileName, input) {
23490
23648
  status,
23491
23649
  startedAt: runIndexStartedAt,
23492
23650
  updatedAt: finishedAt,
23493
- reason: out.reason
23651
+ reason: out.reason,
23652
+ usage: out.usage
23494
23653
  })
23495
23654
  );
23496
23655
  await finalizeStagedRunIndexRowsAsync(config, input.cwd, ctx.data, {
23497
23656
  status,
23498
23657
  updatedAt: finishedAt,
23499
23658
  reason: out.reason,
23500
- output: ctx.data.capabilityOutput
23659
+ output: ctx.data.capabilityOutput,
23660
+ usage: out.usage
23501
23661
  });
23502
23662
  };
23503
23663
  }
@@ -23725,10 +23885,11 @@ async function runImplementation(profileName, input) {
23725
23885
  reason: err instanceof Error ? err.message : String(err)
23726
23886
  });
23727
23887
  }
23728
- ctx.output.usage = {
23729
- tokens: agentResult.tokens ? agentResult.tokens.input + agentResult.tokens.output + agentResult.tokens.cacheRead + agentResult.tokens.cacheCreate : 0,
23730
- costUsd: agentResult.costUsd ?? 0
23731
- };
23888
+ ctx.output.usage = createRunUsage(agentResult.tokens, agentResult.costUsd, {
23889
+ model: `${model.provider}/${model.model}`,
23890
+ turns: agentResult.turns,
23891
+ modelUsage: agentResult.modelUsage
23892
+ });
23732
23893
  emitEvent(input.cwd, {
23733
23894
  implementation: profileName,
23734
23895
  kind: "agent_end",
@@ -23850,7 +24011,8 @@ async function runImplementation(profileName, input) {
23850
24011
  } catch (error) {
23851
24012
  return finishAndEnd({
23852
24013
  exitCode: 99,
23853
- reason: error instanceof Error ? error.message : String(error)
24014
+ reason: error instanceof Error ? error.message : String(error),
24015
+ usage: ctx.output.usage
23854
24016
  });
23855
24017
  }
23856
24018
  }
@@ -23858,6 +24020,7 @@ async function runImplementation(profileName, input) {
23858
24020
  exitCode: ctx.output.exitCode ?? 0,
23859
24021
  prUrl: ctx.output.prUrl,
23860
24022
  reason: ctx.output.reason,
24023
+ usage: ctx.output.usage,
23861
24024
  action: ctx.data.action,
23862
24025
  nextDispatch: ctx.output.nextDispatch,
23863
24026
  nextJob: ctx.output.nextJob,
@@ -23868,7 +24031,7 @@ async function runImplementation(profileName, input) {
23868
24031
  });
23869
24032
  } catch (err) {
23870
24033
  const msg = err instanceof Error ? err.message : String(err);
23871
- return finishAndEnd({ exitCode: 99, reason: ctx.output.reason ?? msg });
24034
+ return finishAndEnd({ exitCode: 99, reason: ctx.output.reason ?? msg, usage: ctx.output.usage });
23872
24035
  } finally {
23873
24036
  runRuntimeCleanup(ctx);
23874
24037
  clearStampedLifecycleLabels(profile, ctx);
@@ -23921,6 +24084,8 @@ function lastIndexOfScript(entries, names) {
23921
24084
  }
23922
24085
  async function runImplementationChain(profileName, input) {
23923
24086
  let result = await runImplementation(profileName, input);
24087
+ let aggregateUsage = result.usage;
24088
+ let followedHandoff = false;
23924
24089
  let chainConfig = input.config;
23925
24090
  const configForHandoff = () => {
23926
24091
  if (chainConfig || input.skipConfig) return chainConfig;
@@ -23932,6 +24097,7 @@ async function runImplementationChain(profileName, input) {
23932
24097
  ...result.taskState ? { taskState: result.taskState } : {}
23933
24098
  };
23934
24099
  for (let hops = 1; (result.nextDispatch || result.nextJob) && hops <= MAX_CHAIN_HOPS; hops++) {
24100
+ followedHandoff = true;
23935
24101
  if (result.nextJob) {
23936
24102
  const next2 = result.nextJob;
23937
24103
  const after = result.afterNextJob;
@@ -23947,6 +24113,7 @@ async function runImplementationChain(profileName, input) {
23947
24113
  quiet: input.quiet,
23948
24114
  preloadedData: chainData
23949
24115
  });
24116
+ aggregateUsage = mergeRunUsage(aggregateUsage, childResult.usage);
23950
24117
  if (after && childResult.exitCode === 0 && !childResult.nextDispatch && !childResult.nextJob && !childResult.afterNextJob) {
23951
24118
  chainData = {
23952
24119
  ...chainData,
@@ -23972,6 +24139,7 @@ async function runImplementationChain(profileName, input) {
23972
24139
  quiet: input.quiet,
23973
24140
  preloadedData: chainData
23974
24141
  });
24142
+ aggregateUsage = mergeRunUsage(aggregateUsage, result.usage);
23975
24143
  chainData = {
23976
24144
  ...chainData,
23977
24145
  ...result.taskState ? { taskState: result.taskState } : {}
@@ -24006,6 +24174,7 @@ async function runImplementationChain(profileName, input) {
24006
24174
  quiet: input.quiet,
24007
24175
  preloadedData: chainData
24008
24176
  });
24177
+ aggregateUsage = mergeRunUsage(aggregateUsage, result.usage);
24009
24178
  chainData = {
24010
24179
  ...chainData,
24011
24180
  ...result.taskState ? { taskState: result.taskState } : {}
@@ -24016,7 +24185,9 @@ async function runImplementationChain(profileName, input) {
24016
24185
  process.stderr.write(`[kody] in-process hand-off cap (${MAX_CHAIN_HOPS}) reached; not running ${pending}
24017
24186
  `);
24018
24187
  }
24019
- return result;
24188
+ const output = aggregateUsage ? { ...result, usage: aggregateUsage } : result;
24189
+ if (followedHandoff) publishRunUsage(`chain:${profileName}`, output.usage);
24190
+ return output;
24020
24191
  }
24021
24192
  function handoffToJob(handoff) {
24022
24193
  const capabilityOrAction = handoff.workflow ?? handoff.action ?? handoff.capability;
@@ -24063,7 +24234,7 @@ function resolveProfilePath(profileName, cwd = process.cwd()) {
24063
24234
  // fallback
24064
24235
  ];
24065
24236
  for (const c of candidates) {
24066
- if (fs55.existsSync(c)) return c;
24237
+ if (fs56.existsSync(c)) return c;
24067
24238
  }
24068
24239
  return candidates[0];
24069
24240
  }
@@ -24179,7 +24350,7 @@ function resolveShellTimeoutMs(entry) {
24179
24350
  async function runShellEntry(entry, ctx, profile) {
24180
24351
  const shellName = entry.shell;
24181
24352
  const shellPath = path52.join(profile.dir, shellName);
24182
- if (!fs55.existsSync(shellPath)) {
24353
+ if (!fs56.existsSync(shellPath)) {
24183
24354
  ctx.skipAgent = true;
24184
24355
  ctx.output.exitCode = 99;
24185
24356
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -24253,9 +24424,9 @@ async function runShellEntry(entry, ctx, profile) {
24253
24424
  }
24254
24425
  let sideChannelText = "";
24255
24426
  try {
24256
- if (fs55.existsSync(outputFile)) {
24257
- sideChannelText = fs55.readFileSync(outputFile, "utf-8");
24258
- fs55.rmSync(outputFile, { force: true });
24427
+ if (fs56.existsSync(outputFile)) {
24428
+ sideChannelText = fs56.readFileSync(outputFile, "utf-8");
24429
+ fs56.rmSync(outputFile, { force: true });
24259
24430
  }
24260
24431
  } catch {
24261
24432
  }
@@ -24321,6 +24492,7 @@ var init_executor = __esm({
24321
24492
  init_subagents();
24322
24493
  init_task_artifacts();
24323
24494
  init_tools();
24495
+ init_usage();
24324
24496
  MUTATING_POSTFLIGHTS = /* @__PURE__ */ new Set([
24325
24497
  "commitAndPush",
24326
24498
  "ensurePr",
@@ -24471,6 +24643,7 @@ function parseWorkflowRunState(raw) {
24471
24643
  const artifacts = Array.isArray(state.artifacts) ? state.artifacts.filter(
24472
24644
  (artifact) => !!artifact && typeof artifact === "object" && typeof artifact.label === "string" && (artifact.url === void 0 || typeof artifact.url === "string") && (artifact.path === void 0 || typeof artifact.path === "string")
24473
24645
  ) : [];
24646
+ const usage = parseRunUsage(state.usage);
24474
24647
  return {
24475
24648
  status: state.status,
24476
24649
  ...typeof state.currentStepId === "string" ? { currentStepId: state.currentStepId } : {},
@@ -24483,6 +24656,7 @@ function parseWorkflowRunState(raw) {
24483
24656
  facts: { ...facts },
24484
24657
  evidence: Object.fromEntries(evidenceEntries),
24485
24658
  artifacts: artifacts.map((artifact) => ({ ...artifact })),
24659
+ ...usage ? { usage } : {},
24486
24660
  ...typeof state.blocker === "string" ? { blocker: state.blocker } : {}
24487
24661
  };
24488
24662
  }
@@ -24546,6 +24720,7 @@ var init_workflowRunState = __esm({
24546
24720
  "src/workflowRunState.ts"() {
24547
24721
  "use strict";
24548
24722
  init_state_backend();
24723
+ init_usage();
24549
24724
  SAFE_ID = /^[a-z0-9][a-z0-9_-]{0,79}$/;
24550
24725
  }
24551
24726
  });
@@ -24766,6 +24941,7 @@ async function runJob(job, base) {
24766
24941
  ...parentRow,
24767
24942
  status: result.workflowState?.status === "waiting-approval" ? "waiting" : result.exitCode === 0 ? "success" : "failed",
24768
24943
  summary: result.reason,
24944
+ usage: result.usage,
24769
24945
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24770
24946
  });
24771
24947
  }
@@ -24784,6 +24960,7 @@ async function runJob(job, base) {
24784
24960
  ...Object.keys(facts).length > 0 ? { output: facts } : {}
24785
24961
  });
24786
24962
  }
24963
+ publishRunUsage(`workflow:${workflowIdentity}`, result.usage);
24787
24964
  return result;
24788
24965
  } finally {
24789
24966
  await lease?.release().catch((error) => {
@@ -24932,16 +25109,17 @@ async function runCapabilityWorkflow(parent, workflow, capability, base, checkpo
24932
25109
  return { exitCode: 64, reason: resumeBlocker, workflowState: state };
24933
25110
  }
24934
25111
  const result = isGraphWorkflow(workflow) ? await runGraphCapabilityWorkflow(parent, workflow, capability, base, checkpoint) : await runLinearCapabilityWorkflow(parent, workflow, capability, base, checkpoint);
24935
- if (workflow.report && result.workflowState) {
25112
+ const resultWithUsage = result.workflowState?.usage ? { ...result, usage: result.workflowState.usage } : result;
25113
+ if (workflow.report && resultWithUsage.workflowState) {
24936
25114
  await publishWorkflowReport({
24937
25115
  config: base.config ?? loadConfig(base.cwd),
24938
25116
  publication: workflow.report,
24939
25117
  workflowId: capability.slug,
24940
25118
  workflowTitle: capability.title,
24941
- state: result.workflowState
25119
+ state: resultWithUsage.workflowState
24942
25120
  });
24943
25121
  }
24944
- return result;
25122
+ return resultWithUsage;
24945
25123
  }
24946
25124
  async function runLinearCapabilityWorkflow(parent, workflow, capability, base, checkpoint) {
24947
25125
  const state = initialWorkflowState(parent, workflow);
@@ -25070,7 +25248,8 @@ function initialWorkflowState(parent, workflow) {
25070
25248
  ...prior.steps ? { steps: cloneWorkflowSteps(prior.steps) } : {},
25071
25249
  facts: { ...prior.facts },
25072
25250
  evidence: { ...prior.evidence },
25073
- artifacts: prior.artifacts.map((artifact) => ({ ...artifact }))
25251
+ artifacts: prior.artifacts.map((artifact) => ({ ...artifact })),
25252
+ ...prior.usage ? { usage: structuredClone(prior.usage) } : {}
25074
25253
  };
25075
25254
  }
25076
25255
  const firstStepId = workflow.startAt ?? workflow.steps[0]?.id;
@@ -25090,7 +25269,8 @@ function initialWorkflowState(parent, workflow) {
25090
25269
  ...prior?.facts ?? {}
25091
25270
  },
25092
25271
  evidence: { ...prior?.evidence ?? {} },
25093
- artifacts: (prior?.artifacts ?? []).map((artifact) => ({ ...artifact }))
25272
+ artifacts: (prior?.artifacts ?? []).map((artifact) => ({ ...artifact })),
25273
+ ...prior?.usage ? { usage: structuredClone(prior.usage) } : {}
25094
25274
  };
25095
25275
  }
25096
25276
  function workflowChainData(parent, capability, base, state) {
@@ -25479,6 +25659,7 @@ function finishWorkflowStep(state, step, result) {
25479
25659
  ...result.capabilityOutput !== void 0 ? { output: result.capabilityOutput } : {},
25480
25660
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
25481
25661
  };
25662
+ state.usage = mergeRunUsage(state.usage, result.usage);
25482
25663
  }
25483
25664
  function usesGenericCapabilityInput(action, cwd) {
25484
25665
  const inputs = getCapabilityActionInputs(action, hydratedCapabilitiesRoot(cwd));
@@ -25714,6 +25895,7 @@ var init_job = __esm({
25714
25895
  init_publishReport();
25715
25896
  init_simpleCapabilityRuntime();
25716
25897
  init_state_backend();
25898
+ init_usage();
25717
25899
  init_workflowDefinitionIdentity();
25718
25900
  init_workflowDefinitions();
25719
25901
  init_workflowRunLease();
@@ -27083,7 +27265,7 @@ async function hydrateDefinitionsFromEnv(cwd = process.cwd(), env = process.env)
27083
27265
 
27084
27266
  // src/kody-cli.ts
27085
27267
  import { execFileSync as execFileSync24 } from "child_process";
27086
- import * as fs57 from "fs";
27268
+ import * as fs58 from "fs";
27087
27269
  import * as path53 from "path";
27088
27270
 
27089
27271
  // src/app-auth.ts
@@ -27616,7 +27798,7 @@ init_loopDefinitions();
27616
27798
 
27617
27799
  // src/mergedPrLifecycle.ts
27618
27800
  init_lifecycleLabels();
27619
- import * as fs56 from "fs";
27801
+ import * as fs57 from "fs";
27620
27802
  var DONE3 = {
27621
27803
  label: "kody:done",
27622
27804
  color: "0e8a16",
@@ -27657,8 +27839,8 @@ function finalizeMergedPullRequestEvent(event, cwd, writeLabel = setKodyLabel) {
27657
27839
  }
27658
27840
  function readGitHubEvent(env = process.env) {
27659
27841
  const eventPath = env.GITHUB_EVENT_PATH;
27660
- if (!eventPath || !fs56.existsSync(eventPath)) return null;
27661
- return JSON.parse(fs56.readFileSync(eventPath, "utf-8"));
27842
+ if (!eventPath || !fs57.existsSync(eventPath)) return null;
27843
+ return JSON.parse(fs57.readFileSync(eventPath, "utf-8"));
27662
27844
  }
27663
27845
 
27664
27846
  // src/kody-cli.ts
@@ -27886,9 +28068,9 @@ async function resolveAuthToken(env = process.env) {
27886
28068
  return void 0;
27887
28069
  }
27888
28070
  function detectPackageManager(cwd) {
27889
- if (fs57.existsSync(path53.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
27890
- if (fs57.existsSync(path53.join(cwd, "yarn.lock"))) return "yarn";
27891
- if (fs57.existsSync(path53.join(cwd, "bun.lockb"))) return "bun";
28071
+ if (fs58.existsSync(path53.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
28072
+ if (fs58.existsSync(path53.join(cwd, "yarn.lock"))) return "yarn";
28073
+ if (fs58.existsSync(path53.join(cwd, "bun.lockb"))) return "bun";
27892
28074
  return "npm";
27893
28075
  }
27894
28076
  function shouldChainScheduledWatch(match) {
@@ -27929,7 +28111,7 @@ function ensurePackageManagerInstalled(pm, cwd) {
27929
28111
  return shellOut("npm", ["install", "-g", spec], cwd);
27930
28112
  }
27931
28113
  function installDeps(pm, cwd) {
27932
- if (!fs57.existsSync(path53.join(cwd, "package.json"))) {
28114
+ if (!fs58.existsSync(path53.join(cwd, "package.json"))) {
27933
28115
  process.stdout.write("\u2192 kody: no package.json found \u2014 skipping consumer dependency install\n");
27934
28116
  return 0;
27935
28117
  }
@@ -27995,8 +28177,8 @@ function postFailureTail(issueNumber, cwd, reason) {
27995
28177
  const logPath = lastRunLogPath(cwd);
27996
28178
  let tail = "";
27997
28179
  try {
27998
- if (fs57.existsSync(logPath)) {
27999
- const content = fs57.readFileSync(logPath, "utf-8");
28180
+ if (fs58.existsSync(logPath)) {
28181
+ const content = fs58.readFileSync(logPath, "utf-8");
28000
28182
  tail = content.slice(-3e3);
28001
28183
  }
28002
28184
  } catch {
@@ -28113,9 +28295,9 @@ async function runCi(argv) {
28113
28295
  forceRunCliArgs = { goal: envForceMessage };
28114
28296
  }
28115
28297
  }
28116
- if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs57.existsSync(dispatchEventPath)) {
28298
+ if (!args.issueNumber && !autoFallback && !forceRunAction && !runRequestFanOut && eventName === "workflow_dispatch" && dispatchEventPath && fs58.existsSync(dispatchEventPath)) {
28117
28299
  try {
28118
- const evt = JSON.parse(fs57.readFileSync(dispatchEventPath, "utf-8"));
28300
+ const evt = JSON.parse(fs58.readFileSync(dispatchEventPath, "utf-8"));
28119
28301
  const inputs = objectValue2(evt.inputs);
28120
28302
  const issueInput = parseInt(String(inputs?.issue_number ?? ""), 10);
28121
28303
  const sessionInput = String(inputs?.sessionId ?? "");
@@ -28531,7 +28713,7 @@ init_repoWorkspace();
28531
28713
 
28532
28714
  // src/scripts/brainTurnLog.ts
28533
28715
  init_runtimePaths();
28534
- import * as fs58 from "fs";
28716
+ import * as fs59 from "fs";
28535
28717
  import * as path54 from "path";
28536
28718
  import posixPath4 from "path/posix";
28537
28719
  var live = /* @__PURE__ */ new Map();
@@ -28540,8 +28722,8 @@ function brainEventsFilePath(dir, chatId) {
28540
28722
  }
28541
28723
  function lastPersistedSeq(dir, chatId) {
28542
28724
  const p = brainEventsFilePath(dir, chatId);
28543
- if (!fs58.existsSync(p)) return 0;
28544
- const lines = fs58.readFileSync(p, "utf-8").split("\n").filter(Boolean);
28725
+ if (!fs59.existsSync(p)) return 0;
28726
+ const lines = fs59.readFileSync(p, "utf-8").split("\n").filter(Boolean);
28545
28727
  if (lines.length === 0) return 0;
28546
28728
  try {
28547
28729
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -28551,9 +28733,9 @@ function lastPersistedSeq(dir, chatId) {
28551
28733
  }
28552
28734
  function readSince(dir, chatId, since) {
28553
28735
  const p = brainEventsFilePath(dir, chatId);
28554
- if (!fs58.existsSync(p)) return [];
28736
+ if (!fs59.existsSync(p)) return [];
28555
28737
  const out = [];
28556
- for (const line of fs58.readFileSync(p, "utf-8").split("\n")) {
28738
+ for (const line of fs59.readFileSync(p, "utf-8").split("\n")) {
28557
28739
  if (!line) continue;
28558
28740
  try {
28559
28741
  const rec = JSON.parse(line);
@@ -28579,12 +28761,12 @@ function beginTurn(dir, chatId) {
28579
28761
  };
28580
28762
  live.set(chatId, state);
28581
28763
  const p = brainEventsFilePath(dir, chatId);
28582
- fs58.mkdirSync(path54.dirname(p), { recursive: true });
28764
+ fs59.mkdirSync(path54.dirname(p), { recursive: true });
28583
28765
  return (event) => {
28584
28766
  state.seq += 1;
28585
28767
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
28586
28768
  try {
28587
- fs58.appendFileSync(p, `${JSON.stringify(rec)}
28769
+ fs59.appendFileSync(p, `${JSON.stringify(rec)}
28588
28770
  `);
28589
28771
  } catch (err) {
28590
28772
  process.stderr.write(
@@ -28623,7 +28805,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
28623
28805
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
28624
28806
  };
28625
28807
  try {
28626
- fs58.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
28808
+ fs59.appendFileSync(brainEventsFilePath(dir, chatId), `${JSON.stringify(rec)}
28627
28809
  `);
28628
28810
  } catch {
28629
28811
  }
@@ -31287,7 +31469,7 @@ async function poolServe() {
31287
31469
 
31288
31470
  // src/servers/runner-serve.ts
31289
31471
  import { spawn as spawn10 } from "child_process";
31290
- import * as fs59 from "fs";
31472
+ import * as fs60 from "fs";
31291
31473
  import { createServer as createServer6 } from "http";
31292
31474
  var DEFAULT_PORT2 = 8080;
31293
31475
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -31363,8 +31545,8 @@ async function defaultRunJob(job) {
31363
31545
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
31364
31546
  const branch = job.ref ?? "main";
31365
31547
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
31366
- fs59.rmSync(workdir, { recursive: true, force: true });
31367
- fs59.mkdirSync(workdir, { recursive: true });
31548
+ fs60.rmSync(workdir, { recursive: true, force: true });
31549
+ fs60.mkdirSync(workdir, { recursive: true });
31368
31550
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
31369
31551
  const target = job.runRequest.target;
31370
31552
  const interactive = target.type === "chat";