@kody-ade/kody-engine 0.4.40 → 0.4.41

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 +425 -275
  2. package/package.json +1 -1
package/dist/bin/kody.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.4.40",
6
+ version: "0.4.41",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -52,8 +52,8 @@ var package_default = {
52
52
 
53
53
  // src/chat-cli.ts
54
54
  import { execFileSync as execFileSync31 } from "child_process";
55
- import * as fs30 from "fs";
56
- import * as path28 from "path";
55
+ import * as fs31 from "fs";
56
+ import * as path29 from "path";
57
57
 
58
58
  // src/chat/events.ts
59
59
  import * as fs from "fs";
@@ -417,6 +417,9 @@ async function runAgent(opts) {
417
417
  const resultTexts = [];
418
418
  let outcome = "failed";
419
419
  let errorMessage;
420
+ const tokens = { input: 0, output: 0, cacheRead: 0, cacheCreate: 0 };
421
+ let messageCount = 0;
422
+ const startedAt = Date.now();
420
423
  try {
421
424
  const queryOptions = {
422
425
  model: opts.model.model,
@@ -454,6 +457,7 @@ async function runAgent(opts) {
454
457
  options: queryOptions
455
458
  });
456
459
  for await (const msg of result) {
460
+ messageCount++;
457
461
  try {
458
462
  fullLog.write(`${JSON.stringify(msg)}
459
463
  `);
@@ -463,6 +467,17 @@ async function runAgent(opts) {
463
467
  if (line) process.stdout.write(`${line}
464
468
  `);
465
469
  const m = msg;
470
+ const usage = m.usage;
471
+ if (usage && typeof usage === "object") {
472
+ const i = Number(usage.input_tokens ?? 0);
473
+ const o = Number(usage.output_tokens ?? 0);
474
+ const cr = Number(usage.cache_read_input_tokens ?? 0);
475
+ const cc = Number(usage.cache_creation_input_tokens ?? 0);
476
+ if (Number.isFinite(i)) tokens.input += i;
477
+ if (Number.isFinite(o)) tokens.output += o;
478
+ if (Number.isFinite(cr)) tokens.cacheRead += cr;
479
+ if (Number.isFinite(cc)) tokens.cacheCreate += cc;
480
+ }
466
481
  if (m.type === "result") {
467
482
  if (m.subtype === "success") {
468
483
  outcome = "completed";
@@ -484,7 +499,15 @@ async function runAgent(opts) {
484
499
  }
485
500
  }
486
501
  const finalText = resultTexts.join("\n\n---\n\n");
487
- return { outcome, finalText, error: errorMessage, ndjsonPath };
502
+ return {
503
+ outcome,
504
+ finalText,
505
+ error: errorMessage,
506
+ ndjsonPath,
507
+ durationMs: Date.now() - startedAt,
508
+ tokens,
509
+ messageCount
510
+ };
488
511
  }
489
512
 
490
513
  // src/chat/session.ts
@@ -913,8 +936,8 @@ async function emit2(sink, type, sessionId, suffix, payload) {
913
936
 
914
937
  // src/kody-cli.ts
915
938
  import { execFileSync as execFileSync30 } from "child_process";
916
- import * as fs29 from "fs";
917
- import * as path27 from "path";
939
+ import * as fs30 from "fs";
940
+ import * as path28 from "path";
918
941
 
919
942
  // src/dispatch.ts
920
943
  import * as fs7 from "fs";
@@ -1493,12 +1516,49 @@ function postPrReviewComment(prNumber, body, cwd) {
1493
1516
 
1494
1517
  // src/executor.ts
1495
1518
  import { execFileSync as execFileSync29, spawn as spawn5 } from "child_process";
1496
- import * as fs28 from "fs";
1497
- import * as path26 from "path";
1519
+ import * as fs29 from "fs";
1520
+ import * as path27 from "path";
1498
1521
 
1499
- // src/profile.ts
1522
+ // src/events.ts
1523
+ import * as crypto from "crypto";
1500
1524
  import * as fs8 from "fs";
1501
1525
  import * as path7 from "path";
1526
+ var cachedRunId = null;
1527
+ function resolveRunId() {
1528
+ if (cachedRunId) return cachedRunId;
1529
+ if (process.env.KODY_RUN_ID) {
1530
+ cachedRunId = process.env.KODY_RUN_ID;
1531
+ return cachedRunId;
1532
+ }
1533
+ if (process.env.GITHUB_RUN_ID) {
1534
+ const attempt = process.env.GITHUB_RUN_ATTEMPT ?? "1";
1535
+ cachedRunId = `gh-${process.env.GITHUB_RUN_ID}-${attempt}`;
1536
+ } else {
1537
+ cachedRunId = `${Date.now().toString(36)}-${crypto.randomBytes(4).toString("hex")}`;
1538
+ }
1539
+ process.env.KODY_RUN_ID = cachedRunId;
1540
+ return cachedRunId;
1541
+ }
1542
+ function emitEvent(cwd, ev) {
1543
+ if (process.env.KODY_EVENTS === "0") return;
1544
+ try {
1545
+ const runId = resolveRunId();
1546
+ const fullEvent = {
1547
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1548
+ runId,
1549
+ ...ev
1550
+ };
1551
+ const eventsPath = path7.join(cwd, ".kody", "runs", runId, "events.jsonl");
1552
+ fs8.mkdirSync(path7.dirname(eventsPath), { recursive: true });
1553
+ fs8.appendFileSync(eventsPath, `${JSON.stringify(fullEvent)}
1554
+ `);
1555
+ } catch {
1556
+ }
1557
+ }
1558
+
1559
+ // src/profile.ts
1560
+ import * as fs9 from "fs";
1561
+ import * as path8 from "path";
1502
1562
  var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
1503
1563
  var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
1504
1564
  var VALID_ROLES = /* @__PURE__ */ new Set(["primitive", "orchestrator", "container", "watch", "utility"]);
@@ -1514,12 +1574,12 @@ var ProfileError = class extends Error {
1514
1574
  profilePath;
1515
1575
  };
1516
1576
  function loadProfile(profilePath) {
1517
- if (!fs8.existsSync(profilePath)) {
1577
+ if (!fs9.existsSync(profilePath)) {
1518
1578
  throw new ProfileError(profilePath, "file not found");
1519
1579
  }
1520
1580
  let raw;
1521
1581
  try {
1522
- raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
1582
+ raw = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
1523
1583
  } catch (err) {
1524
1584
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
1525
1585
  }
@@ -1558,7 +1618,7 @@ function loadProfile(profilePath) {
1558
1618
  inputArtifacts: parseInputArtifacts(profilePath, r.input),
1559
1619
  outputArtifacts: parseOutputArtifacts(profilePath, r.output),
1560
1620
  children,
1561
- dir: path7.dirname(profilePath)
1621
+ dir: path8.dirname(profilePath)
1562
1622
  };
1563
1623
  return profile;
1564
1624
  }
@@ -1921,9 +1981,9 @@ function errMsg(err) {
1921
1981
 
1922
1982
  // src/litellm.ts
1923
1983
  import { execFileSync as execFileSync4, spawn } from "child_process";
1924
- import * as fs9 from "fs";
1984
+ import * as fs10 from "fs";
1925
1985
  import * as os from "os";
1926
- import * as path8 from "path";
1986
+ import * as path9 from "path";
1927
1987
  async function checkLitellmHealth(url) {
1928
1988
  try {
1929
1989
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -1963,20 +2023,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
1963
2023
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
1964
2024
  }
1965
2025
  }
1966
- const configPath = path8.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
1967
- fs9.writeFileSync(configPath, generateLitellmConfigYaml(model));
2026
+ const configPath = path9.join(os.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
2027
+ fs10.writeFileSync(configPath, generateLitellmConfigYaml(model));
1968
2028
  const portMatch = url.match(/:(\d+)/);
1969
2029
  const port = portMatch ? portMatch[1] : "4000";
1970
2030
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
1971
2031
  const dotenvVars = readDotenvApiKeys(projectDir);
1972
- const logPath = path8.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
1973
- const outFd = fs9.openSync(logPath, "w");
2032
+ const logPath = path9.join(os.tmpdir(), `kody-litellm-${Date.now()}.log`);
2033
+ const outFd = fs10.openSync(logPath, "w");
1974
2034
  const child = spawn(cmd, args, {
1975
2035
  stdio: ["ignore", outFd, outFd],
1976
2036
  detached: true,
1977
2037
  env: stripBlockingEnv({ ...process.env, ...dotenvVars })
1978
2038
  });
1979
- fs9.closeSync(outFd);
2039
+ fs10.closeSync(outFd);
1980
2040
  for (let i = 0; i < 30; i++) {
1981
2041
  await new Promise((r) => setTimeout(r, 2e3));
1982
2042
  if (await checkLitellmHealth(url)) {
@@ -1993,7 +2053,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
1993
2053
  }
1994
2054
  let logTail = "";
1995
2055
  try {
1996
- logTail = fs9.readFileSync(logPath, "utf-8").slice(-2e3);
2056
+ logTail = fs10.readFileSync(logPath, "utf-8").slice(-2e3);
1997
2057
  } catch {
1998
2058
  }
1999
2059
  try {
@@ -2004,10 +2064,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
2004
2064
  ${logTail}`);
2005
2065
  }
2006
2066
  function readDotenvApiKeys(projectDir) {
2007
- const dotenvPath = path8.join(projectDir, ".env");
2008
- if (!fs9.existsSync(dotenvPath)) return {};
2067
+ const dotenvPath = path9.join(projectDir, ".env");
2068
+ if (!fs10.existsSync(dotenvPath)) return {};
2009
2069
  const result = {};
2010
- for (const rawLine of fs9.readFileSync(dotenvPath, "utf-8").split("\n")) {
2070
+ for (const rawLine of fs10.readFileSync(dotenvPath, "utf-8").split("\n")) {
2011
2071
  const line = rawLine.trim();
2012
2072
  if (!line || line.startsWith("#")) continue;
2013
2073
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -2031,8 +2091,8 @@ function stripBlockingEnv(env) {
2031
2091
 
2032
2092
  // src/commit.ts
2033
2093
  import { execFileSync as execFileSync5 } from "child_process";
2034
- import * as fs10 from "fs";
2035
- import * as path9 from "path";
2094
+ import * as fs11 from "fs";
2095
+ import * as path10 from "path";
2036
2096
  var FORBIDDEN_PATH_PREFIXES = [
2037
2097
  ".kody/",
2038
2098
  ".kody-engine/",
@@ -2088,18 +2148,18 @@ function tryGit(args, cwd) {
2088
2148
  }
2089
2149
  function abortUnfinishedGitOps(cwd) {
2090
2150
  const aborted = [];
2091
- const gitDir = path9.join(cwd ?? process.cwd(), ".git");
2092
- if (!fs10.existsSync(gitDir)) return aborted;
2093
- if (fs10.existsSync(path9.join(gitDir, "MERGE_HEAD"))) {
2151
+ const gitDir = path10.join(cwd ?? process.cwd(), ".git");
2152
+ if (!fs11.existsSync(gitDir)) return aborted;
2153
+ if (fs11.existsSync(path10.join(gitDir, "MERGE_HEAD"))) {
2094
2154
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
2095
2155
  }
2096
- if (fs10.existsSync(path9.join(gitDir, "CHERRY_PICK_HEAD"))) {
2156
+ if (fs11.existsSync(path10.join(gitDir, "CHERRY_PICK_HEAD"))) {
2097
2157
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
2098
2158
  }
2099
- if (fs10.existsSync(path9.join(gitDir, "REVERT_HEAD"))) {
2159
+ if (fs11.existsSync(path10.join(gitDir, "REVERT_HEAD"))) {
2100
2160
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
2101
2161
  }
2102
- if (fs10.existsSync(path9.join(gitDir, "rebase-merge")) || fs10.existsSync(path9.join(gitDir, "rebase-apply"))) {
2162
+ if (fs11.existsSync(path10.join(gitDir, "rebase-merge")) || fs11.existsSync(path10.join(gitDir, "rebase-apply"))) {
2103
2163
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
2104
2164
  }
2105
2165
  try {
@@ -2155,7 +2215,7 @@ function normalizeCommitMessage(raw) {
2155
2215
  function commitAndPush(branch, agentMessage, cwd) {
2156
2216
  const allChanged = listChangedFiles(cwd);
2157
2217
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
2158
- const mergeHeadExists = fs10.existsSync(path9.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
2218
+ const mergeHeadExists = fs11.existsSync(path10.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
2159
2219
  if (allowedFiles.length === 0 && !mergeHeadExists) {
2160
2220
  return { committed: false, pushed: false, sha: "", message: "" };
2161
2221
  }
@@ -2467,21 +2527,21 @@ var advanceFlow = async (ctx, profile) => {
2467
2527
  };
2468
2528
 
2469
2529
  // src/scripts/buildSyntheticPlugin.ts
2470
- import * as fs11 from "fs";
2530
+ import * as fs12 from "fs";
2471
2531
  import * as os2 from "os";
2472
- import * as path10 from "path";
2532
+ import * as path11 from "path";
2473
2533
  function getPluginsCatalogRoot() {
2474
- const here = path10.dirname(new URL(import.meta.url).pathname);
2534
+ const here = path11.dirname(new URL(import.meta.url).pathname);
2475
2535
  const candidates = [
2476
- path10.join(here, "..", "plugins"),
2536
+ path11.join(here, "..", "plugins"),
2477
2537
  // dev: src/scripts → src/plugins
2478
- path10.join(here, "..", "..", "plugins"),
2538
+ path11.join(here, "..", "..", "plugins"),
2479
2539
  // built: dist/scripts → dist/plugins
2480
- path10.join(here, "..", "..", "src", "plugins")
2540
+ path11.join(here, "..", "..", "src", "plugins")
2481
2541
  // fallback
2482
2542
  ];
2483
2543
  for (const c of candidates) {
2484
- if (fs11.existsSync(c) && fs11.statSync(c).isDirectory()) return c;
2544
+ if (fs12.existsSync(c) && fs12.statSync(c).isDirectory()) return c;
2485
2545
  }
2486
2546
  return candidates[0];
2487
2547
  }
@@ -2491,52 +2551,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
2491
2551
  if (!needsSynthetic) return;
2492
2552
  const catalog = getPluginsCatalogRoot();
2493
2553
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2494
- const root = path10.join(os2.tmpdir(), `kody-synth-${runId}`);
2495
- fs11.mkdirSync(path10.join(root, ".claude-plugin"), { recursive: true });
2554
+ const root = path11.join(os2.tmpdir(), `kody-synth-${runId}`);
2555
+ fs12.mkdirSync(path11.join(root, ".claude-plugin"), { recursive: true });
2496
2556
  const resolvePart = (bucket, entry) => {
2497
- const local = path10.join(profile.dir, bucket, entry);
2498
- if (fs11.existsSync(local)) return local;
2499
- const central = path10.join(catalog, bucket, entry);
2500
- if (fs11.existsSync(central)) return central;
2557
+ const local = path11.join(profile.dir, bucket, entry);
2558
+ if (fs12.existsSync(local)) return local;
2559
+ const central = path11.join(catalog, bucket, entry);
2560
+ if (fs12.existsSync(central)) return central;
2501
2561
  throw new Error(
2502
2562
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
2503
2563
  );
2504
2564
  };
2505
2565
  if (cc.skills.length > 0) {
2506
- const dst = path10.join(root, "skills");
2507
- fs11.mkdirSync(dst, { recursive: true });
2566
+ const dst = path11.join(root, "skills");
2567
+ fs12.mkdirSync(dst, { recursive: true });
2508
2568
  for (const name of cc.skills) {
2509
- copyDir(resolvePart("skills", name), path10.join(dst, name));
2569
+ copyDir(resolvePart("skills", name), path11.join(dst, name));
2510
2570
  }
2511
2571
  }
2512
2572
  if (cc.commands.length > 0) {
2513
- const dst = path10.join(root, "commands");
2514
- fs11.mkdirSync(dst, { recursive: true });
2573
+ const dst = path11.join(root, "commands");
2574
+ fs12.mkdirSync(dst, { recursive: true });
2515
2575
  for (const name of cc.commands) {
2516
- fs11.copyFileSync(resolvePart("commands", `${name}.md`), path10.join(dst, `${name}.md`));
2576
+ fs12.copyFileSync(resolvePart("commands", `${name}.md`), path11.join(dst, `${name}.md`));
2517
2577
  }
2518
2578
  }
2519
2579
  if (cc.subagents.length > 0) {
2520
- const dst = path10.join(root, "agents");
2521
- fs11.mkdirSync(dst, { recursive: true });
2580
+ const dst = path11.join(root, "agents");
2581
+ fs12.mkdirSync(dst, { recursive: true });
2522
2582
  for (const name of cc.subagents) {
2523
- fs11.copyFileSync(resolvePart("agents", `${name}.md`), path10.join(dst, `${name}.md`));
2583
+ fs12.copyFileSync(resolvePart("agents", `${name}.md`), path11.join(dst, `${name}.md`));
2524
2584
  }
2525
2585
  }
2526
2586
  if (cc.hooks.length > 0) {
2527
- const dst = path10.join(root, "hooks");
2528
- fs11.mkdirSync(dst, { recursive: true });
2587
+ const dst = path11.join(root, "hooks");
2588
+ fs12.mkdirSync(dst, { recursive: true });
2529
2589
  const merged = { hooks: {} };
2530
2590
  for (const name of cc.hooks) {
2531
2591
  const src = resolvePart("hooks", `${name}.json`);
2532
- const parsed = JSON.parse(fs11.readFileSync(src, "utf-8"));
2592
+ const parsed = JSON.parse(fs12.readFileSync(src, "utf-8"));
2533
2593
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
2534
2594
  if (!Array.isArray(entries)) continue;
2535
2595
  if (!merged.hooks[event]) merged.hooks[event] = [];
2536
2596
  merged.hooks[event].push(...entries);
2537
2597
  }
2538
2598
  }
2539
- fs11.writeFileSync(path10.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2599
+ fs12.writeFileSync(path11.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
2540
2600
  `);
2541
2601
  }
2542
2602
  const manifest = {
@@ -2547,17 +2607,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
2547
2607
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
2548
2608
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
2549
2609
  if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
2550
- fs11.writeFileSync(path10.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2610
+ fs12.writeFileSync(path11.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
2551
2611
  `);
2552
2612
  ctx.data.syntheticPluginPath = root;
2553
2613
  };
2554
2614
  function copyDir(src, dst) {
2555
- fs11.mkdirSync(dst, { recursive: true });
2556
- for (const ent of fs11.readdirSync(src, { withFileTypes: true })) {
2557
- const s = path10.join(src, ent.name);
2558
- const d = path10.join(dst, ent.name);
2615
+ fs12.mkdirSync(dst, { recursive: true });
2616
+ for (const ent of fs12.readdirSync(src, { withFileTypes: true })) {
2617
+ const s = path11.join(src, ent.name);
2618
+ const d = path11.join(dst, ent.name);
2559
2619
  if (ent.isDirectory()) copyDir(s, d);
2560
- else if (ent.isFile()) fs11.copyFileSync(s, d);
2620
+ else if (ent.isFile()) fs12.copyFileSync(s, d);
2561
2621
  }
2562
2622
  }
2563
2623
 
@@ -2623,18 +2683,18 @@ function formatMissesForFeedback(misses) {
2623
2683
  }
2624
2684
 
2625
2685
  // src/prompt.ts
2626
- import * as fs12 from "fs";
2627
- import * as path11 from "path";
2686
+ import * as fs13 from "fs";
2687
+ import * as path12 from "path";
2628
2688
  var CONVENTIONS_PER_FILE_MAX_BYTES = 3e4;
2629
2689
  var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md"];
2630
2690
  function loadProjectConventions(projectDir) {
2631
2691
  const out = [];
2632
2692
  for (const rel of CONVENTION_FILES) {
2633
- const abs = path11.join(projectDir, rel);
2634
- if (!fs12.existsSync(abs)) continue;
2693
+ const abs = path12.join(projectDir, rel);
2694
+ if (!fs13.existsSync(abs)) continue;
2635
2695
  let content;
2636
2696
  try {
2637
- content = fs12.readFileSync(abs, "utf-8");
2697
+ content = fs13.readFileSync(abs, "utf-8");
2638
2698
  } catch {
2639
2699
  continue;
2640
2700
  }
@@ -2846,11 +2906,11 @@ var commitAndPush2 = async (ctx) => {
2846
2906
 
2847
2907
  // src/scripts/commitGoalState.ts
2848
2908
  import { execFileSync as execFileSync9 } from "child_process";
2849
- import * as path12 from "path";
2909
+ import * as path13 from "path";
2850
2910
  var commitGoalState = async (ctx) => {
2851
2911
  const goal = ctx.data.goal;
2852
2912
  if (!goal) return;
2853
- const stateRel = path12.posix.join(".kody", "goals", goal.id, "state.json");
2913
+ const stateRel = path13.posix.join(".kody", "goals", goal.id, "state.json");
2854
2914
  try {
2855
2915
  execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
2856
2916
  } catch (err) {
@@ -2894,20 +2954,20 @@ function describeCommitMessage(goal) {
2894
2954
  }
2895
2955
 
2896
2956
  // src/scripts/composePrompt.ts
2897
- import * as fs13 from "fs";
2898
- import * as path13 from "path";
2957
+ import * as fs14 from "fs";
2958
+ import * as path14 from "path";
2899
2959
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
2900
2960
  var composePrompt = async (ctx, profile) => {
2901
2961
  const explicit = ctx.data.promptTemplate;
2902
2962
  const mode = ctx.args.mode;
2903
2963
  const candidates = [
2904
- explicit ? path13.join(profile.dir, explicit) : null,
2905
- mode ? path13.join(profile.dir, "prompts", `${mode}.md`) : null,
2906
- path13.join(profile.dir, "prompt.md")
2964
+ explicit ? path14.join(profile.dir, explicit) : null,
2965
+ mode ? path14.join(profile.dir, "prompts", `${mode}.md`) : null,
2966
+ path14.join(profile.dir, "prompt.md")
2907
2967
  ].filter(Boolean);
2908
2968
  let templatePath = "";
2909
2969
  for (const c of candidates) {
2910
- if (fs13.existsSync(c)) {
2970
+ if (fs14.existsSync(c)) {
2911
2971
  templatePath = c;
2912
2972
  break;
2913
2973
  }
@@ -2915,7 +2975,7 @@ var composePrompt = async (ctx, profile) => {
2915
2975
  if (!templatePath) {
2916
2976
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
2917
2977
  }
2918
- const template = fs13.readFileSync(templatePath, "utf-8");
2978
+ const template = fs14.readFileSync(templatePath, "utf-8");
2919
2979
  const tokens = {
2920
2980
  ...stringifyAll(ctx.args, "args."),
2921
2981
  ...stringifyAll(ctx.data, ""),
@@ -2993,8 +3053,8 @@ function formatToolsUsage(profile) {
2993
3053
 
2994
3054
  // src/scripts/createQaGoal.ts
2995
3055
  import { execFileSync as execFileSync10 } from "child_process";
2996
- import * as fs14 from "fs";
2997
- import * as path14 from "path";
3056
+ import * as fs15 from "fs";
3057
+ import * as path15 from "path";
2998
3058
 
2999
3059
  // src/scripts/postReviewResult.ts
3000
3060
  function detectVerdict(body) {
@@ -3246,8 +3306,8 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
3246
3306
  return { number: Number(m[1]), created: true };
3247
3307
  }
3248
3308
  function writeStateFile(cwd, goalId, lastDispatchedIssue) {
3249
- const dir = path14.join(cwd, ".kody", "goals", goalId);
3250
- fs14.mkdirSync(dir, { recursive: true });
3309
+ const dir = path15.join(cwd, ".kody", "goals", goalId);
3310
+ fs15.mkdirSync(dir, { recursive: true });
3251
3311
  const state = {
3252
3312
  version: 1,
3253
3313
  state: "active",
@@ -3255,8 +3315,8 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
3255
3315
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3256
3316
  ...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
3257
3317
  };
3258
- const filePath = path14.join(dir, "state.json");
3259
- fs14.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
3318
+ const filePath = path15.join(dir, "state.json");
3319
+ fs15.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
3260
3320
  `);
3261
3321
  return filePath;
3262
3322
  }
@@ -3667,6 +3727,14 @@ function markPrReady(prNumber, cwd) {
3667
3727
  return fail(err);
3668
3728
  }
3669
3729
  }
3730
+ function fetchDefaultBranch(cwd) {
3731
+ try {
3732
+ const out = gh(["api", "repos/{owner}/{repo}", "--jq", ".default_branch"], { cwd });
3733
+ return { ok: true, value: out.trim() };
3734
+ } catch (err) {
3735
+ return fail(err);
3736
+ }
3737
+ }
3670
3738
 
3671
3739
  // src/goal/phase.ts
3672
3740
  function derivePhase(snap) {
@@ -3690,6 +3758,15 @@ function pickNextDispatchable(snap) {
3690
3758
  var deriveGoalPhase = async (ctx) => {
3691
3759
  const goal = ctx.data.goal;
3692
3760
  if (!goal) return;
3761
+ const defaultBranchResult = fetchDefaultBranch(ctx.cwd);
3762
+ if (defaultBranchResult.ok && defaultBranchResult.value) {
3763
+ goal.defaultBranch = defaultBranchResult.value;
3764
+ } else if (defaultBranchResult.error) {
3765
+ process.stderr.write(
3766
+ `[goal-tick] deriveGoalPhase: fetchDefaultBranch failed (${defaultBranchResult.error}); falling back to ${goal.defaultBranch}
3767
+ `
3768
+ );
3769
+ }
3693
3770
  const issues = listGoalIssues(goal.id, ctx.cwd);
3694
3771
  if (!issues.ok) {
3695
3772
  process.stderr.write(`[goal-tick] deriveGoalPhase: list issues failed: ${issues.error}
@@ -3738,15 +3815,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
3738
3815
 
3739
3816
  // src/scripts/diagMcp.ts
3740
3817
  import { execFileSync as execFileSync11 } from "child_process";
3741
- import * as fs15 from "fs";
3818
+ import * as fs16 from "fs";
3742
3819
  import * as os3 from "os";
3743
- import * as path15 from "path";
3820
+ import * as path16 from "path";
3744
3821
  var diagMcp = async (_ctx) => {
3745
3822
  const home = os3.homedir();
3746
- const cacheDir = path15.join(home, ".cache", "ms-playwright");
3823
+ const cacheDir = path16.join(home, ".cache", "ms-playwright");
3747
3824
  let entries = [];
3748
3825
  try {
3749
- entries = fs15.readdirSync(cacheDir);
3826
+ entries = fs16.readdirSync(cacheDir);
3750
3827
  } catch {
3751
3828
  }
3752
3829
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -3772,17 +3849,17 @@ var diagMcp = async (_ctx) => {
3772
3849
  };
3773
3850
 
3774
3851
  // src/scripts/discoverQaContext.ts
3775
- import * as fs17 from "fs";
3776
- import * as path17 from "path";
3852
+ import * as fs18 from "fs";
3853
+ import * as path18 from "path";
3777
3854
 
3778
3855
  // src/scripts/frameworkDetectors.ts
3779
- import * as fs16 from "fs";
3780
- import * as path16 from "path";
3856
+ import * as fs17 from "fs";
3857
+ import * as path17 from "path";
3781
3858
  function detectFrameworks(cwd) {
3782
3859
  const out = [];
3783
3860
  let deps = {};
3784
3861
  try {
3785
- const pkg = JSON.parse(fs16.readFileSync(path16.join(cwd, "package.json"), "utf-8"));
3862
+ const pkg = JSON.parse(fs17.readFileSync(path17.join(cwd, "package.json"), "utf-8"));
3786
3863
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
3787
3864
  } catch {
3788
3865
  return out;
@@ -3819,7 +3896,7 @@ function detectFrameworks(cwd) {
3819
3896
  }
3820
3897
  function findFile(cwd, candidates) {
3821
3898
  for (const c of candidates) {
3822
- if (fs16.existsSync(path16.join(cwd, c))) return c;
3899
+ if (fs17.existsSync(path17.join(cwd, c))) return c;
3823
3900
  }
3824
3901
  return null;
3825
3902
  }
@@ -3832,18 +3909,18 @@ var COLLECTION_DIRS = [
3832
3909
  function discoverPayloadCollections(cwd) {
3833
3910
  const out = [];
3834
3911
  for (const dir of COLLECTION_DIRS) {
3835
- const full = path16.join(cwd, dir);
3836
- if (!fs16.existsSync(full)) continue;
3912
+ const full = path17.join(cwd, dir);
3913
+ if (!fs17.existsSync(full)) continue;
3837
3914
  let files;
3838
3915
  try {
3839
- files = fs16.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
3916
+ files = fs17.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
3840
3917
  } catch {
3841
3918
  continue;
3842
3919
  }
3843
3920
  for (const file of files) {
3844
3921
  try {
3845
- const filePath = path16.join(full, file);
3846
- const content = fs16.readFileSync(filePath, "utf-8").slice(0, 1e4);
3922
+ const filePath = path17.join(full, file);
3923
+ const content = fs17.readFileSync(filePath, "utf-8").slice(0, 1e4);
3847
3924
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
3848
3925
  if (!slugMatch) continue;
3849
3926
  const slug = slugMatch[1];
@@ -3857,7 +3934,7 @@ function discoverPayloadCollections(cwd) {
3857
3934
  out.push({
3858
3935
  name,
3859
3936
  slug,
3860
- filePath: path16.relative(cwd, filePath),
3937
+ filePath: path17.relative(cwd, filePath),
3861
3938
  fields: fields.slice(0, 20),
3862
3939
  hasAdmin
3863
3940
  });
@@ -3871,28 +3948,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
3871
3948
  function discoverAdminComponents(cwd, collections) {
3872
3949
  const out = [];
3873
3950
  for (const dir of ADMIN_COMPONENT_DIRS) {
3874
- const full = path16.join(cwd, dir);
3875
- if (!fs16.existsSync(full)) continue;
3951
+ const full = path17.join(cwd, dir);
3952
+ if (!fs17.existsSync(full)) continue;
3876
3953
  let entries;
3877
3954
  try {
3878
- entries = fs16.readdirSync(full, { withFileTypes: true });
3955
+ entries = fs17.readdirSync(full, { withFileTypes: true });
3879
3956
  } catch {
3880
3957
  continue;
3881
3958
  }
3882
3959
  for (const entry of entries) {
3883
- const entryPath = path16.join(full, entry.name);
3960
+ const entryPath = path17.join(full, entry.name);
3884
3961
  let name;
3885
3962
  let filePath;
3886
3963
  if (entry.isDirectory()) {
3887
3964
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
3888
- (f) => fs16.existsSync(path16.join(entryPath, f))
3965
+ (f) => fs17.existsSync(path17.join(entryPath, f))
3889
3966
  );
3890
3967
  if (!indexFile) continue;
3891
3968
  name = entry.name;
3892
- filePath = path16.relative(cwd, path16.join(entryPath, indexFile));
3969
+ filePath = path17.relative(cwd, path17.join(entryPath, indexFile));
3893
3970
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
3894
3971
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
3895
- filePath = path16.relative(cwd, entryPath);
3972
+ filePath = path17.relative(cwd, entryPath);
3896
3973
  } else {
3897
3974
  continue;
3898
3975
  }
@@ -3900,7 +3977,7 @@ function discoverAdminComponents(cwd, collections) {
3900
3977
  if (collections) {
3901
3978
  for (const col of collections) {
3902
3979
  try {
3903
- const colContent = fs16.readFileSync(path16.join(cwd, col.filePath), "utf-8");
3980
+ const colContent = fs17.readFileSync(path17.join(cwd, col.filePath), "utf-8");
3904
3981
  if (colContent.includes(name)) {
3905
3982
  usedInCollection = col.slug;
3906
3983
  break;
@@ -3919,8 +3996,8 @@ function scanApiRoutes(cwd) {
3919
3996
  const out = [];
3920
3997
  const appDirs = ["src/app", "app"];
3921
3998
  for (const appDir of appDirs) {
3922
- const apiDir = path16.join(cwd, appDir, "api");
3923
- if (!fs16.existsSync(apiDir)) continue;
3999
+ const apiDir = path17.join(cwd, appDir, "api");
4000
+ if (!fs17.existsSync(apiDir)) continue;
3924
4001
  walkApiRoutes(apiDir, "/api", cwd, out);
3925
4002
  break;
3926
4003
  }
@@ -3929,14 +4006,14 @@ function scanApiRoutes(cwd) {
3929
4006
  function walkApiRoutes(dir, prefix, cwd, out) {
3930
4007
  let entries;
3931
4008
  try {
3932
- entries = fs16.readdirSync(dir, { withFileTypes: true });
4009
+ entries = fs17.readdirSync(dir, { withFileTypes: true });
3933
4010
  } catch {
3934
4011
  return;
3935
4012
  }
3936
4013
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
3937
4014
  if (routeFile) {
3938
4015
  try {
3939
- const content = fs16.readFileSync(path16.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
4016
+ const content = fs17.readFileSync(path17.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
3940
4017
  const methods = HTTP_METHODS.filter(
3941
4018
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
3942
4019
  );
@@ -3944,7 +4021,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
3944
4021
  out.push({
3945
4022
  path: prefix,
3946
4023
  methods,
3947
- filePath: path16.relative(cwd, path16.join(dir, routeFile.name))
4024
+ filePath: path17.relative(cwd, path17.join(dir, routeFile.name))
3948
4025
  });
3949
4026
  }
3950
4027
  } catch {
@@ -3955,7 +4032,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
3955
4032
  if (entry.name === "node_modules" || entry.name === ".next") continue;
3956
4033
  let segment = entry.name;
3957
4034
  if (segment.startsWith("(") && segment.endsWith(")")) {
3958
- walkApiRoutes(path16.join(dir, entry.name), prefix, cwd, out);
4035
+ walkApiRoutes(path17.join(dir, entry.name), prefix, cwd, out);
3959
4036
  continue;
3960
4037
  }
3961
4038
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -3963,7 +4040,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
3963
4040
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
3964
4041
  segment = `:${segment.slice(1, -1)}`;
3965
4042
  }
3966
- walkApiRoutes(path16.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
4043
+ walkApiRoutes(path17.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
3967
4044
  }
3968
4045
  }
3969
4046
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -3983,10 +4060,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
3983
4060
  function scanEnvVars(cwd) {
3984
4061
  const candidates = [".env.example", ".env.local.example", ".env.template"];
3985
4062
  for (const envFile of candidates) {
3986
- const envPath = path16.join(cwd, envFile);
3987
- if (!fs16.existsSync(envPath)) continue;
4063
+ const envPath = path17.join(cwd, envFile);
4064
+ if (!fs17.existsSync(envPath)) continue;
3988
4065
  try {
3989
- const content = fs16.readFileSync(envPath, "utf-8");
4066
+ const content = fs17.readFileSync(envPath, "utf-8");
3990
4067
  const vars = [];
3991
4068
  for (const line of content.split("\n")) {
3992
4069
  const trimmed = line.trim();
@@ -4034,9 +4111,9 @@ function runQaDiscovery(cwd) {
4034
4111
  }
4035
4112
  function detectDevServer(cwd, out) {
4036
4113
  try {
4037
- const pkg = JSON.parse(fs17.readFileSync(path17.join(cwd, "package.json"), "utf-8"));
4114
+ const pkg = JSON.parse(fs18.readFileSync(path18.join(cwd, "package.json"), "utf-8"));
4038
4115
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
4039
- const pm = fs17.existsSync(path17.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs17.existsSync(path17.join(cwd, "yarn.lock")) ? "yarn" : fs17.existsSync(path17.join(cwd, "bun.lockb")) ? "bun" : "npm";
4116
+ const pm = fs18.existsSync(path18.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs18.existsSync(path18.join(cwd, "yarn.lock")) ? "yarn" : fs18.existsSync(path18.join(cwd, "bun.lockb")) ? "bun" : "npm";
4040
4117
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
4041
4118
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
4042
4119
  else if (allDeps.vite) out.devPort = 5173;
@@ -4046,8 +4123,8 @@ function detectDevServer(cwd, out) {
4046
4123
  function scanFrontendRoutes(cwd, out) {
4047
4124
  const appDirs = ["src/app", "app"];
4048
4125
  for (const appDir of appDirs) {
4049
- const full = path17.join(cwd, appDir);
4050
- if (!fs17.existsSync(full)) continue;
4126
+ const full = path18.join(cwd, appDir);
4127
+ if (!fs18.existsSync(full)) continue;
4051
4128
  walkFrontendRoutes(full, "", out);
4052
4129
  break;
4053
4130
  }
@@ -4055,7 +4132,7 @@ function scanFrontendRoutes(cwd, out) {
4055
4132
  function walkFrontendRoutes(dir, prefix, out) {
4056
4133
  let entries;
4057
4134
  try {
4058
- entries = fs17.readdirSync(dir, { withFileTypes: true });
4135
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
4059
4136
  } catch {
4060
4137
  return;
4061
4138
  }
@@ -4072,7 +4149,7 @@ function walkFrontendRoutes(dir, prefix, out) {
4072
4149
  if (entry.name === "node_modules" || entry.name === ".next") continue;
4073
4150
  let segment = entry.name;
4074
4151
  if (segment.startsWith("(") && segment.endsWith(")")) {
4075
- walkFrontendRoutes(path17.join(dir, entry.name), prefix, out);
4152
+ walkFrontendRoutes(path18.join(dir, entry.name), prefix, out);
4076
4153
  continue;
4077
4154
  }
4078
4155
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -4080,7 +4157,7 @@ function walkFrontendRoutes(dir, prefix, out) {
4080
4157
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
4081
4158
  segment = `:${segment.slice(1, -1)}`;
4082
4159
  }
4083
- walkFrontendRoutes(path17.join(dir, entry.name), `${prefix}/${segment}`, out);
4160
+ walkFrontendRoutes(path18.join(dir, entry.name), `${prefix}/${segment}`, out);
4084
4161
  }
4085
4162
  }
4086
4163
  function detectAuthFiles(cwd, out) {
@@ -4097,23 +4174,23 @@ function detectAuthFiles(cwd, out) {
4097
4174
  "src/app/api/oauth"
4098
4175
  ];
4099
4176
  for (const c of candidates) {
4100
- if (fs17.existsSync(path17.join(cwd, c))) out.authFiles.push(c);
4177
+ if (fs18.existsSync(path18.join(cwd, c))) out.authFiles.push(c);
4101
4178
  }
4102
4179
  }
4103
4180
  function detectRoles(cwd, out) {
4104
4181
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
4105
4182
  for (const rp of rolePaths) {
4106
- const dir = path17.join(cwd, rp);
4107
- if (!fs17.existsSync(dir)) continue;
4183
+ const dir = path18.join(cwd, rp);
4184
+ if (!fs18.existsSync(dir)) continue;
4108
4185
  let files;
4109
4186
  try {
4110
- files = fs17.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4187
+ files = fs18.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
4111
4188
  } catch {
4112
4189
  continue;
4113
4190
  }
4114
4191
  for (const f of files) {
4115
4192
  try {
4116
- const content = fs17.readFileSync(path17.join(dir, f), "utf-8").slice(0, 5e3);
4193
+ const content = fs18.readFileSync(path18.join(dir, f), "utf-8").slice(0, 5e3);
4117
4194
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
4118
4195
  if (roleMatches) {
4119
4196
  for (const m of roleMatches) {
@@ -4344,8 +4421,8 @@ function failedAction3(reason) {
4344
4421
  }
4345
4422
 
4346
4423
  // src/scripts/dispatchJobFileTicks.ts
4347
- import * as fs19 from "fs";
4348
- import * as path19 from "path";
4424
+ import * as fs20 from "fs";
4425
+ import * as path20 from "path";
4349
4426
 
4350
4427
  // src/scripts/jobFrontmatter.ts
4351
4428
  var SCHEDULE_EVERY_VALUES = [
@@ -4596,8 +4673,8 @@ var ContentsApiBackend = class {
4596
4673
  };
4597
4674
 
4598
4675
  // src/scripts/jobState/localFileBackend.ts
4599
- import * as fs18 from "fs";
4600
- import * as path18 from "path";
4676
+ import * as fs19 from "fs";
4677
+ import * as path19 from "path";
4601
4678
  var LocalFileBackend = class {
4602
4679
  name = "local-file";
4603
4680
  cwd;
@@ -4612,7 +4689,7 @@ var LocalFileBackend = class {
4612
4689
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
4613
4690
  this.cwd = opts.cwd;
4614
4691
  this.jobsDir = opts.jobsDir;
4615
- this.absDir = path18.join(opts.cwd, opts.jobsDir);
4692
+ this.absDir = path19.join(opts.cwd, opts.jobsDir);
4616
4693
  this.owner = opts.owner;
4617
4694
  this.repo = opts.repo;
4618
4695
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -4627,7 +4704,7 @@ var LocalFileBackend = class {
4627
4704
  `);
4628
4705
  return;
4629
4706
  }
4630
- fs18.mkdirSync(this.absDir, { recursive: true });
4707
+ fs19.mkdirSync(this.absDir, { recursive: true });
4631
4708
  const prefix = this.cacheKeyPrefix();
4632
4709
  const probeKey = `${prefix}probe-${Date.now()}`;
4633
4710
  try {
@@ -4656,7 +4733,7 @@ var LocalFileBackend = class {
4656
4733
  `);
4657
4734
  return;
4658
4735
  }
4659
- if (!fs18.existsSync(this.absDir)) {
4736
+ if (!fs19.existsSync(this.absDir)) {
4660
4737
  return;
4661
4738
  }
4662
4739
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -4672,11 +4749,11 @@ var LocalFileBackend = class {
4672
4749
  }
4673
4750
  load(slug) {
4674
4751
  const relPath = stateFilePath(this.jobsDir, slug);
4675
- const absPath = path18.join(this.cwd, relPath);
4676
- if (!fs18.existsSync(absPath)) {
4752
+ const absPath = path19.join(this.cwd, relPath);
4753
+ if (!fs19.existsSync(absPath)) {
4677
4754
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
4678
4755
  }
4679
- const raw = fs18.readFileSync(absPath, "utf-8");
4756
+ const raw = fs19.readFileSync(absPath, "utf-8");
4680
4757
  let parsed;
4681
4758
  try {
4682
4759
  parsed = JSON.parse(raw);
@@ -4693,10 +4770,10 @@ var LocalFileBackend = class {
4693
4770
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
4694
4771
  return false;
4695
4772
  }
4696
- const absPath = path18.join(this.cwd, loaded.path);
4697
- fs18.mkdirSync(path18.dirname(absPath), { recursive: true });
4773
+ const absPath = path19.join(this.cwd, loaded.path);
4774
+ fs19.mkdirSync(path19.dirname(absPath), { recursive: true });
4698
4775
  const body = JSON.stringify(next, null, 2) + "\n";
4699
- fs18.writeFileSync(absPath, body, "utf-8");
4776
+ fs19.writeFileSync(absPath, body, "utf-8");
4700
4777
  return true;
4701
4778
  }
4702
4779
  cacheKeyPrefix() {
@@ -4773,7 +4850,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
4773
4850
  await backend.hydrate();
4774
4851
  }
4775
4852
  try {
4776
- const slugs = listJobSlugs(path19.join(ctx.cwd, jobsDir));
4853
+ const slugs = listJobSlugs(path20.join(ctx.cwd, jobsDir));
4777
4854
  ctx.data.jobSlugCount = slugs.length;
4778
4855
  if (slugs.length === 0) {
4779
4856
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -4872,17 +4949,17 @@ function formatAgo(ms) {
4872
4949
  }
4873
4950
  function readJobFrontmatter(cwd, jobsDir, slug) {
4874
4951
  try {
4875
- const raw = fs19.readFileSync(path19.join(cwd, jobsDir, `${slug}.md`), "utf-8");
4952
+ const raw = fs20.readFileSync(path20.join(cwd, jobsDir, `${slug}.md`), "utf-8");
4876
4953
  return splitFrontmatter(raw).frontmatter;
4877
4954
  } catch {
4878
4955
  return {};
4879
4956
  }
4880
4957
  }
4881
4958
  function listJobSlugs(absDir) {
4882
- if (!fs19.existsSync(absDir)) return [];
4959
+ if (!fs20.existsSync(absDir)) return [];
4883
4960
  let entries;
4884
4961
  try {
4885
- entries = fs19.readdirSync(absDir, { withFileTypes: true });
4962
+ entries = fs20.readdirSync(absDir, { withFileTypes: true });
4886
4963
  } catch {
4887
4964
  return [];
4888
4965
  }
@@ -5483,7 +5560,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch, cwd, baseBranch)
5483
5560
 
5484
5561
  // src/gha.ts
5485
5562
  import { execFileSync as execFileSync16 } from "child_process";
5486
- import * as fs20 from "fs";
5563
+ import * as fs21 from "fs";
5487
5564
  function getRunUrl() {
5488
5565
  const server = process.env.GITHUB_SERVER_URL;
5489
5566
  const repo = process.env.GITHUB_REPOSITORY;
@@ -5494,10 +5571,10 @@ function getRunUrl() {
5494
5571
  function reactToTriggerComment(cwd) {
5495
5572
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
5496
5573
  const eventPath = process.env.GITHUB_EVENT_PATH;
5497
- if (!eventPath || !fs20.existsSync(eventPath)) return;
5574
+ if (!eventPath || !fs21.existsSync(eventPath)) return;
5498
5575
  let event = null;
5499
5576
  try {
5500
- event = JSON.parse(fs20.readFileSync(eventPath, "utf-8"));
5577
+ event = JSON.parse(fs21.readFileSync(eventPath, "utf-8"));
5501
5578
  } catch {
5502
5579
  return;
5503
5580
  }
@@ -5782,22 +5859,22 @@ var handleAbandonedGoal = async (ctx) => {
5782
5859
 
5783
5860
  // src/scripts/initFlow.ts
5784
5861
  import { execFileSync as execFileSync18 } from "child_process";
5785
- import * as fs22 from "fs";
5786
- import * as path21 from "path";
5862
+ import * as fs23 from "fs";
5863
+ import * as path22 from "path";
5787
5864
 
5788
5865
  // src/scripts/loadQaGuide.ts
5789
- import * as fs21 from "fs";
5790
- import * as path20 from "path";
5866
+ import * as fs22 from "fs";
5867
+ import * as path21 from "path";
5791
5868
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
5792
5869
  var loadQaGuide = async (ctx) => {
5793
- const full = path20.join(ctx.cwd, QA_GUIDE_REL_PATH);
5794
- if (!fs21.existsSync(full)) {
5870
+ const full = path21.join(ctx.cwd, QA_GUIDE_REL_PATH);
5871
+ if (!fs22.existsSync(full)) {
5795
5872
  ctx.data.qaGuide = "";
5796
5873
  ctx.data.qaGuidePath = "";
5797
5874
  return;
5798
5875
  }
5799
5876
  try {
5800
- ctx.data.qaGuide = fs21.readFileSync(full, "utf-8");
5877
+ ctx.data.qaGuide = fs22.readFileSync(full, "utf-8");
5801
5878
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
5802
5879
  } catch {
5803
5880
  ctx.data.qaGuide = "";
@@ -5807,9 +5884,9 @@ var loadQaGuide = async (ctx) => {
5807
5884
 
5808
5885
  // src/scripts/initFlow.ts
5809
5886
  function detectPackageManager(cwd) {
5810
- if (fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
5811
- if (fs22.existsSync(path21.join(cwd, "yarn.lock"))) return "yarn";
5812
- if (fs22.existsSync(path21.join(cwd, "bun.lockb"))) return "bun";
5887
+ if (fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
5888
+ if (fs23.existsSync(path22.join(cwd, "yarn.lock"))) return "yarn";
5889
+ if (fs23.existsSync(path22.join(cwd, "bun.lockb"))) return "bun";
5813
5890
  return "npm";
5814
5891
  }
5815
5892
  function qualityCommandsFor(pm) {
@@ -5931,48 +6008,48 @@ function performInit(cwd, force) {
5931
6008
  const pm = detectPackageManager(cwd);
5932
6009
  const ownerRepo = detectOwnerRepo(cwd);
5933
6010
  const defaultBranch = defaultBranchFromGit(cwd);
5934
- const configPath = path21.join(cwd, "kody.config.json");
5935
- if (fs22.existsSync(configPath) && !force) {
6011
+ const configPath = path22.join(cwd, "kody.config.json");
6012
+ if (fs23.existsSync(configPath) && !force) {
5936
6013
  skipped.push("kody.config.json");
5937
6014
  } else {
5938
6015
  const cfg = makeConfig(pm, ownerRepo, defaultBranch);
5939
- fs22.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
6016
+ fs23.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
5940
6017
  `);
5941
6018
  wrote.push("kody.config.json");
5942
6019
  }
5943
- const workflowDir = path21.join(cwd, ".github", "workflows");
5944
- const workflowPath = path21.join(workflowDir, "kody.yml");
5945
- if (fs22.existsSync(workflowPath) && !force) {
6020
+ const workflowDir = path22.join(cwd, ".github", "workflows");
6021
+ const workflowPath = path22.join(workflowDir, "kody.yml");
6022
+ if (fs23.existsSync(workflowPath) && !force) {
5946
6023
  skipped.push(".github/workflows/kody.yml");
5947
6024
  } else {
5948
- fs22.mkdirSync(workflowDir, { recursive: true });
5949
- fs22.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
6025
+ fs23.mkdirSync(workflowDir, { recursive: true });
6026
+ fs23.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
5950
6027
  wrote.push(".github/workflows/kody.yml");
5951
6028
  }
5952
- const hasUi = fs22.existsSync(path21.join(cwd, "src/app")) || fs22.existsSync(path21.join(cwd, "app")) || fs22.existsSync(path21.join(cwd, "pages"));
6029
+ const hasUi = fs23.existsSync(path22.join(cwd, "src/app")) || fs23.existsSync(path22.join(cwd, "app")) || fs23.existsSync(path22.join(cwd, "pages"));
5953
6030
  if (hasUi) {
5954
- const qaGuidePath = path21.join(cwd, QA_GUIDE_REL_PATH);
5955
- if (fs22.existsSync(qaGuidePath) && !force) {
6031
+ const qaGuidePath = path22.join(cwd, QA_GUIDE_REL_PATH);
6032
+ if (fs23.existsSync(qaGuidePath) && !force) {
5956
6033
  skipped.push(QA_GUIDE_REL_PATH);
5957
6034
  } else {
5958
- fs22.mkdirSync(path21.dirname(qaGuidePath), { recursive: true });
6035
+ fs23.mkdirSync(path22.dirname(qaGuidePath), { recursive: true });
5959
6036
  const discovery = runQaDiscovery(cwd);
5960
- fs22.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
6037
+ fs23.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
5961
6038
  wrote.push(QA_GUIDE_REL_PATH);
5962
6039
  }
5963
6040
  }
5964
6041
  const builtinJobs = listBuiltinJobs();
5965
6042
  if (builtinJobs.length > 0) {
5966
- const jobsDir = path21.join(cwd, ".kody", "jobs");
5967
- fs22.mkdirSync(jobsDir, { recursive: true });
6043
+ const jobsDir = path22.join(cwd, ".kody", "jobs");
6044
+ fs23.mkdirSync(jobsDir, { recursive: true });
5968
6045
  for (const job of builtinJobs) {
5969
- const rel = path21.join(".kody", "jobs", `${job.slug}.md`);
5970
- const target = path21.join(cwd, rel);
5971
- if (fs22.existsSync(target) && !force) {
6046
+ const rel = path22.join(".kody", "jobs", `${job.slug}.md`);
6047
+ const target = path22.join(cwd, rel);
6048
+ if (fs23.existsSync(target) && !force) {
5972
6049
  skipped.push(rel);
5973
6050
  continue;
5974
6051
  }
5975
- fs22.writeFileSync(target, fs22.readFileSync(job.filePath, "utf-8"));
6052
+ fs23.writeFileSync(target, fs23.readFileSync(job.filePath, "utf-8"));
5976
6053
  wrote.push(rel);
5977
6054
  }
5978
6055
  }
@@ -5984,12 +6061,12 @@ function performInit(cwd, force) {
5984
6061
  continue;
5985
6062
  }
5986
6063
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
5987
- const target = path21.join(workflowDir, `kody-${exe.name}.yml`);
5988
- if (fs22.existsSync(target) && !force) {
6064
+ const target = path22.join(workflowDir, `kody-${exe.name}.yml`);
6065
+ if (fs23.existsSync(target) && !force) {
5989
6066
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
5990
6067
  continue;
5991
6068
  }
5992
- fs22.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
6069
+ fs23.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
5993
6070
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
5994
6071
  }
5995
6072
  let labels;
@@ -6078,14 +6155,14 @@ var loadCoverageRules = async (ctx) => {
6078
6155
  };
6079
6156
 
6080
6157
  // src/goal/state.ts
6081
- import * as fs23 from "fs";
6082
- import * as path22 from "path";
6158
+ import * as fs24 from "fs";
6159
+ import * as path23 from "path";
6083
6160
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "done"]);
6084
6161
  var GoalStateError = class extends Error {
6085
- constructor(path29, message) {
6086
- super(`Invalid goal state at ${path29}:
6162
+ constructor(path30, message) {
6163
+ super(`Invalid goal state at ${path30}:
6087
6164
  ${message}`);
6088
- this.path = path29;
6165
+ this.path = path30;
6089
6166
  this.name = "GoalStateError";
6090
6167
  }
6091
6168
  path;
@@ -6129,16 +6206,16 @@ function serializeGoalState(s) {
6129
6206
  `;
6130
6207
  }
6131
6208
  function goalStatePath(cwd, goalId) {
6132
- return path22.join(cwd, ".kody", "goals", goalId, "state.json");
6209
+ return path23.join(cwd, ".kody", "goals", goalId, "state.json");
6133
6210
  }
6134
6211
  function readGoalState(cwd, goalId) {
6135
6212
  const file = goalStatePath(cwd, goalId);
6136
- if (!fs23.existsSync(file)) {
6213
+ if (!fs24.existsSync(file)) {
6137
6214
  throw new GoalStateError(file, "file not found");
6138
6215
  }
6139
6216
  let raw;
6140
6217
  try {
6141
- raw = JSON.parse(fs23.readFileSync(file, "utf-8"));
6218
+ raw = JSON.parse(fs24.readFileSync(file, "utf-8"));
6142
6219
  } catch (err) {
6143
6220
  throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
6144
6221
  }
@@ -6146,8 +6223,8 @@ function readGoalState(cwd, goalId) {
6146
6223
  }
6147
6224
  function writeGoalState(cwd, goalId, state) {
6148
6225
  const file = goalStatePath(cwd, goalId);
6149
- fs23.mkdirSync(path22.dirname(file), { recursive: true });
6150
- fs23.writeFileSync(file, serializeGoalState(state), "utf-8");
6226
+ fs24.mkdirSync(path23.dirname(file), { recursive: true });
6227
+ fs24.writeFileSync(file, serializeGoalState(state), "utf-8");
6151
6228
  }
6152
6229
  function nowIso() {
6153
6230
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -6241,8 +6318,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
6241
6318
  };
6242
6319
 
6243
6320
  // src/scripts/loadJobFromFile.ts
6244
- import * as fs24 from "fs";
6245
- import * as path23 from "path";
6321
+ import * as fs25 from "fs";
6322
+ import * as path24 from "path";
6246
6323
  var loadJobFromFile = async (ctx, _profile, args) => {
6247
6324
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
6248
6325
  const slugArg = String(args?.slugArg ?? "job");
@@ -6250,11 +6327,11 @@ var loadJobFromFile = async (ctx, _profile, args) => {
6250
6327
  if (!slug) {
6251
6328
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
6252
6329
  }
6253
- const absPath = path23.join(ctx.cwd, jobsDir, `${slug}.md`);
6254
- if (!fs24.existsSync(absPath)) {
6330
+ const absPath = path24.join(ctx.cwd, jobsDir, `${slug}.md`);
6331
+ if (!fs25.existsSync(absPath)) {
6255
6332
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
6256
6333
  }
6257
- const raw = fs24.readFileSync(absPath, "utf-8");
6334
+ const raw = fs25.readFileSync(absPath, "utf-8");
6258
6335
  const { title, body } = parseJobFile(raw, slug);
6259
6336
  const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
6260
6337
  const loaded = await backend.load(slug);
@@ -6286,16 +6363,16 @@ function humanizeSlug(slug) {
6286
6363
  }
6287
6364
 
6288
6365
  // src/scripts/loadMemoryContext.ts
6289
- import * as fs25 from "fs";
6290
- import * as path24 from "path";
6366
+ import * as fs26 from "fs";
6367
+ import * as path25 from "path";
6291
6368
  var MEMORY_DIR_RELATIVE = ".kody/memory";
6292
6369
  var MAX_PAGES = 8;
6293
6370
  var PER_PAGE_MAX_BYTES = 4e3;
6294
6371
  var TOTAL_MAX_BYTES = 24e3;
6295
6372
  var TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
6296
6373
  var loadMemoryContext = async (ctx) => {
6297
- const memoryAbs = path24.join(ctx.cwd, MEMORY_DIR_RELATIVE);
6298
- if (!fs25.existsSync(memoryAbs)) {
6374
+ const memoryAbs = path25.join(ctx.cwd, MEMORY_DIR_RELATIVE);
6375
+ if (!fs26.existsSync(memoryAbs)) {
6299
6376
  ctx.data.memoryContext = "";
6300
6377
  return;
6301
6378
  }
@@ -6320,21 +6397,21 @@ function collectPages(memoryAbs) {
6320
6397
  walkMd(memoryAbs, (file) => {
6321
6398
  let stat;
6322
6399
  try {
6323
- stat = fs25.statSync(file);
6400
+ stat = fs26.statSync(file);
6324
6401
  } catch {
6325
6402
  return;
6326
6403
  }
6327
6404
  let raw;
6328
6405
  try {
6329
- raw = fs25.readFileSync(file, "utf-8");
6406
+ raw = fs26.readFileSync(file, "utf-8");
6330
6407
  } catch {
6331
6408
  return;
6332
6409
  }
6333
6410
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
6334
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path24.basename(file, ".md");
6411
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path25.basename(file, ".md");
6335
6412
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
6336
6413
  out.push({
6337
- relPath: path24.relative(memoryAbs, file),
6414
+ relPath: path25.relative(memoryAbs, file),
6338
6415
  title,
6339
6416
  updated,
6340
6417
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
@@ -6402,16 +6479,16 @@ function walkMd(root, visit) {
6402
6479
  const dir = stack.pop();
6403
6480
  let names;
6404
6481
  try {
6405
- names = fs25.readdirSync(dir);
6482
+ names = fs26.readdirSync(dir);
6406
6483
  } catch {
6407
6484
  continue;
6408
6485
  }
6409
6486
  for (const name of names) {
6410
6487
  if (name.startsWith(".")) continue;
6411
- const full = path24.join(dir, name);
6488
+ const full = path25.join(dir, name);
6412
6489
  let stat;
6413
6490
  try {
6414
- stat = fs25.statSync(full);
6491
+ stat = fs26.statSync(full);
6415
6492
  } catch {
6416
6493
  continue;
6417
6494
  }
@@ -7249,30 +7326,21 @@ var recordOutcome = async (ctx, profile) => {
7249
7326
 
7250
7327
  // src/scripts/requireFeedbackActions.ts
7251
7328
  var MIN_ITEMS = 1;
7252
- var requireFeedbackActions = async (ctx, profile) => {
7329
+ var requireFeedbackActions = async (ctx) => {
7253
7330
  if (!ctx.data.agentDone) return;
7254
7331
  const actions = String(ctx.data.feedbackActions ?? "").trim();
7255
7332
  const items = countActionItems(actions);
7256
7333
  ctx.data.feedbackAgentItemCount = items;
7257
7334
  if (items < MIN_ITEMS) {
7258
- fail2(
7259
- ctx,
7260
- profile,
7261
- actions.length === 0 ? "agent omitted required FEEDBACK_ACTIONS block \u2014 cannot verify that review feedback was addressed" : "agent FEEDBACK_ACTIONS block listed no items \u2014 cannot verify that review feedback was addressed"
7335
+ const reason = actions.length === 0 ? "FEEDBACK_ACTIONS block missing" : "FEEDBACK_ACTIONS block listed no items";
7336
+ process.stderr.write(
7337
+ `[kody requireFeedbackActions] warning: ${reason} \u2014 proceeding anyway (verifyFixAlignment + tests are the real gate)
7338
+ `
7262
7339
  );
7340
+ ctx.data.feedbackActionsOmitted = actions.length === 0;
7341
+ ctx.data.feedbackActionsMalformed = actions.length > 0;
7263
7342
  }
7264
7343
  };
7265
- function fail2(ctx, profile, reason) {
7266
- ctx.data.agentDone = false;
7267
- ctx.data.agentFailureReason = reason;
7268
- const modeSeg = profile.name.replace(/-/g, "_").toUpperCase();
7269
- const failedAction6 = {
7270
- type: `${modeSeg}_FAILED`,
7271
- payload: { reason },
7272
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
7273
- };
7274
- ctx.data.action = failedAction6;
7275
- }
7276
7344
  function countActionItems(block) {
7277
7345
  if (!block.trim()) return 0;
7278
7346
  let count = 0;
@@ -7829,8 +7897,8 @@ function resolveBaseOverride(value) {
7829
7897
 
7830
7898
  // src/scripts/runTickScript.ts
7831
7899
  import { spawnSync } from "child_process";
7832
- import * as fs26 from "fs";
7833
- import * as path25 from "path";
7900
+ import * as fs27 from "fs";
7901
+ import * as path26 from "path";
7834
7902
  var runTickScript = async (ctx, _profile, args) => {
7835
7903
  ctx.skipAgent = true;
7836
7904
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
@@ -7842,13 +7910,13 @@ var runTickScript = async (ctx, _profile, args) => {
7842
7910
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
7843
7911
  return;
7844
7912
  }
7845
- const jobPath = path25.join(ctx.cwd, jobsDir, `${slug}.md`);
7846
- if (!fs26.existsSync(jobPath)) {
7913
+ const jobPath = path26.join(ctx.cwd, jobsDir, `${slug}.md`);
7914
+ if (!fs27.existsSync(jobPath)) {
7847
7915
  ctx.output.exitCode = 99;
7848
7916
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
7849
7917
  return;
7850
7918
  }
7851
- const raw = fs26.readFileSync(jobPath, "utf-8");
7919
+ const raw = fs27.readFileSync(jobPath, "utf-8");
7852
7920
  const { frontmatter } = splitFrontmatter(raw);
7853
7921
  const tickScript = frontmatter.tickScript;
7854
7922
  if (!tickScript) {
@@ -7856,8 +7924,8 @@ var runTickScript = async (ctx, _profile, args) => {
7856
7924
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
7857
7925
  return;
7858
7926
  }
7859
- const scriptPath = path25.isAbsolute(tickScript) ? tickScript : path25.join(ctx.cwd, tickScript);
7860
- if (!fs26.existsSync(scriptPath)) {
7927
+ const scriptPath = path26.isAbsolute(tickScript) ? tickScript : path26.join(ctx.cwd, tickScript);
7928
+ if (!fs27.existsSync(scriptPath)) {
7861
7929
  ctx.output.exitCode = 99;
7862
7930
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
7863
7931
  return;
@@ -8794,7 +8862,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
8794
8862
  };
8795
8863
 
8796
8864
  // src/scripts/writeRunSummary.ts
8797
- import * as fs27 from "fs";
8865
+ import * as fs28 from "fs";
8798
8866
  var writeRunSummary = async (ctx, profile) => {
8799
8867
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
8800
8868
  if (!summaryPath) return;
@@ -8816,7 +8884,7 @@ var writeRunSummary = async (ctx, profile) => {
8816
8884
  if (reason) lines.push(`- **Reason:** ${reason}`);
8817
8885
  lines.push("");
8818
8886
  try {
8819
- fs27.appendFileSync(summaryPath, `${lines.join("\n")}
8887
+ fs28.appendFileSync(summaryPath, `${lines.join("\n")}
8820
8888
  `);
8821
8889
  } catch {
8822
8890
  }
@@ -8952,22 +9020,42 @@ function runShell(cmd, cwd, timeoutMs = 3e4) {
8952
9020
  // src/executor.ts
8953
9021
  var CONTAINER_MAX_ITERATIONS = 50;
8954
9022
  async function runExecutable(profileName, input) {
9023
+ const stageStartedAt = Date.now();
9024
+ emitEvent(input.cwd, { executable: profileName, kind: "stage_start" });
9025
+ const finishAndEnd = (out) => {
9026
+ emitEvent(input.cwd, {
9027
+ executable: profileName,
9028
+ kind: "stage_end",
9029
+ durationMs: Date.now() - stageStartedAt,
9030
+ outcome: out.exitCode === 0 ? "ok" : "failed",
9031
+ meta: {
9032
+ exitCode: out.exitCode,
9033
+ ...out.reason ? { reason: out.reason } : {},
9034
+ ...out.prUrl ? { prUrl: out.prUrl } : {}
9035
+ }
9036
+ });
9037
+ if (out.prUrl) process.stdout.write(`PR_URL=${out.prUrl}
9038
+ `);
9039
+ else if (out.reason) process.stdout.write(`PR_URL=FAILED: ${out.reason}
9040
+ `);
9041
+ return out;
9042
+ };
8955
9043
  const profilePath = resolveProfilePath(profileName);
8956
9044
  const profile = loadProfile(profilePath);
8957
9045
  const missing = validateScriptReferences(profile, allScriptNames);
8958
9046
  if (missing.length > 0) {
8959
- return finish({ exitCode: 99, reason: `profile references unknown scripts: ${missing.join(", ")}` });
9047
+ return finishAndEnd({ exitCode: 99, reason: `profile references unknown scripts: ${missing.join(", ")}` });
8960
9048
  }
8961
9049
  let args;
8962
9050
  try {
8963
9051
  args = validateInputs(profile.inputs, input.cliArgs);
8964
9052
  } catch (err) {
8965
- return finish({ exitCode: 64, reason: err instanceof Error ? err.message : String(err) });
9053
+ return finishAndEnd({ exitCode: 64, reason: err instanceof Error ? err.message : String(err) });
8966
9054
  }
8967
9055
  const toolResults = verifyCliTools(profile.cliTools, input.cwd);
8968
9056
  const firstFail = firstRequiredFailure(toolResults, profile.cliTools);
8969
9057
  if (firstFail) {
8970
- return finish({ exitCode: 99, reason: `required CLI tool check failed: ${firstFail.error}` });
9058
+ return finishAndEnd({ exitCode: 99, reason: `required CLI tool check failed: ${firstFail.error}` });
8971
9059
  }
8972
9060
  let config;
8973
9061
  if (input.config) {
@@ -8983,7 +9071,7 @@ async function runExecutable(profileName, input) {
8983
9071
  try {
8984
9072
  config = loadConfig(input.cwd);
8985
9073
  } catch (err) {
8986
- return finish({ exitCode: 99, reason: `config error: ${err instanceof Error ? err.message : String(err)}` });
9074
+ return finishAndEnd({ exitCode: 99, reason: `config error: ${err instanceof Error ? err.message : String(err)}` });
8987
9075
  }
8988
9076
  }
8989
9077
  const modelSpec = profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
@@ -8991,13 +9079,13 @@ async function runExecutable(profileName, input) {
8991
9079
  try {
8992
9080
  model = parseProviderModel(modelSpec);
8993
9081
  } catch (err) {
8994
- return finish({ exitCode: 99, reason: `agent.model invalid: ${err instanceof Error ? err.message : String(err)}` });
9082
+ return finishAndEnd({ exitCode: 99, reason: `agent.model invalid: ${err instanceof Error ? err.message : String(err)}` });
8995
9083
  }
8996
9084
  let litellm = null;
8997
9085
  try {
8998
9086
  litellm = await startLitellmIfNeeded(model, input.cwd);
8999
9087
  } catch (err) {
9000
- return finish({
9088
+ return finishAndEnd({
9001
9089
  exitCode: 99,
9002
9090
  reason: `litellm startup failed: ${err instanceof Error ? err.message : String(err)}`
9003
9091
  });
@@ -9011,9 +9099,9 @@ async function runExecutable(profileName, input) {
9011
9099
  data: {},
9012
9100
  output: { exitCode: 0 }
9013
9101
  };
9014
- const ndjsonDir = path26.join(input.cwd, ".kody");
9102
+ const ndjsonDir = path27.join(input.cwd, ".kody");
9015
9103
  const invokeAgent = async (prompt) => {
9016
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path26.isAbsolute(p) ? p : path26.resolve(profile.dir, p)).filter((p) => p.length > 0);
9104
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path27.isAbsolute(p) ? p : path27.resolve(profile.dir, p)).filter((p) => p.length > 0);
9017
9105
  const syntheticPath = ctx.data.syntheticPluginPath;
9018
9106
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
9019
9107
  return runAgent({
@@ -9037,15 +9125,39 @@ async function runExecutable(profileName, input) {
9037
9125
  ctx.data.__invokeAgent = invokeAgent;
9038
9126
  try {
9039
9127
  for (const entry of profile.scripts.preflight) {
9040
- if (!shouldRun(entry, ctx)) continue;
9128
+ const preLabel = entry.script ?? entry.shell ?? "<unknown>";
9129
+ if (!shouldRun(entry, ctx)) {
9130
+ emitEvent(input.cwd, {
9131
+ executable: profileName,
9132
+ kind: "preflight",
9133
+ name: preLabel,
9134
+ outcome: "skipped"
9135
+ });
9136
+ continue;
9137
+ }
9138
+ const t0 = Date.now();
9041
9139
  if (entry.shell) {
9042
9140
  await runShellEntry(entry, ctx, profile);
9141
+ emitEvent(input.cwd, {
9142
+ executable: profileName,
9143
+ kind: "preflight",
9144
+ name: preLabel,
9145
+ durationMs: Date.now() - t0,
9146
+ outcome: ctx.output.exitCode && ctx.output.exitCode !== 0 ? "failed" : "ok"
9147
+ });
9043
9148
  } else {
9044
9149
  const fn = preflightScripts[entry.script];
9045
- if (!fn) return finish({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
9150
+ if (!fn) return finishAndEnd({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
9046
9151
  await fn(ctx, profile, entry.with);
9152
+ emitEvent(input.cwd, {
9153
+ executable: profileName,
9154
+ kind: "preflight",
9155
+ name: preLabel,
9156
+ durationMs: Date.now() - t0,
9157
+ outcome: ctx.skipAgent && ctx.output.exitCode && ctx.output.exitCode !== 0 ? "failed" : "ok"
9158
+ });
9047
9159
  if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
9048
- return finish(ctx.output);
9160
+ return finishAndEnd(ctx.output);
9049
9161
  }
9050
9162
  }
9051
9163
  }
@@ -9056,9 +9168,21 @@ async function runExecutable(profileName, input) {
9056
9168
  } else if (!ctx.skipAgent) {
9057
9169
  const prompt = ctx.data.prompt;
9058
9170
  if (!prompt) {
9059
- return finish({ exitCode: 99, reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)" });
9171
+ return finishAndEnd({ exitCode: 99, reason: "composePrompt did not produce a prompt (ctx.data.prompt missing)" });
9060
9172
  }
9173
+ emitEvent(input.cwd, { executable: profileName, kind: "agent_start" });
9061
9174
  agentResult = await invokeAgent(prompt);
9175
+ emitEvent(input.cwd, {
9176
+ executable: profileName,
9177
+ kind: "agent_end",
9178
+ durationMs: agentResult.durationMs,
9179
+ outcome: agentResult.outcome === "completed" ? "ok" : "failed",
9180
+ meta: {
9181
+ ...agentResult.tokens ? { tokens: agentResult.tokens } : {},
9182
+ ...typeof agentResult.messageCount === "number" ? { messageCount: agentResult.messageCount } : {},
9183
+ ...agentResult.error ? { error: agentResult.error } : {}
9184
+ }
9185
+ });
9062
9186
  }
9063
9187
  for (const entry of profile.scripts.postflight) {
9064
9188
  const entryLabel = entry.script ?? entry.shell ?? "<unknown>";
@@ -9073,18 +9197,27 @@ async function runExecutable(profileName, input) {
9073
9197
  process.stderr.write(`[kody postflight] skip ${entryLabel}: ${reasons.join("; ")}
9074
9198
  `);
9075
9199
  }
9200
+ emitEvent(input.cwd, {
9201
+ executable: profileName,
9202
+ kind: "postflight",
9203
+ name: entryLabel,
9204
+ outcome: "skipped"
9205
+ });
9076
9206
  continue;
9077
9207
  }
9078
9208
  const label = entryLabel;
9209
+ const t0 = Date.now();
9210
+ let postOutcome = "ok";
9079
9211
  try {
9080
9212
  if (entry.shell) {
9081
9213
  await runShellEntry(entry, ctx, profile);
9082
9214
  } else {
9083
9215
  const fn = postflightScripts[entry.script];
9084
- if (!fn) return finish({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
9216
+ if (!fn) return finishAndEnd({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
9085
9217
  await fn(ctx, profile, agentResult, entry.with);
9086
9218
  }
9087
9219
  } catch (err) {
9220
+ postOutcome = "failed";
9088
9221
  const msg = err instanceof Error ? err.message : String(err);
9089
9222
  process.stderr.write(`[kody] postflight "${label}" crashed: ${msg}
9090
9223
  `);
@@ -9092,8 +9225,15 @@ async function runExecutable(profileName, input) {
9092
9225
  ctx.output.reason = ctx.output.reason ? `${ctx.output.reason}; ${summary}` : summary;
9093
9226
  if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
9094
9227
  }
9228
+ emitEvent(input.cwd, {
9229
+ executable: profileName,
9230
+ kind: "postflight",
9231
+ name: label,
9232
+ durationMs: Date.now() - t0,
9233
+ outcome: postOutcome
9234
+ });
9095
9235
  }
9096
- return finish({
9236
+ return finishAndEnd({
9097
9237
  exitCode: ctx.output.exitCode ?? 0,
9098
9238
  prUrl: ctx.output.prUrl,
9099
9239
  reason: ctx.output.reason
@@ -9122,17 +9262,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
9122
9262
  function resolveProfilePath(profileName) {
9123
9263
  const found = resolveExecutable(profileName);
9124
9264
  if (found) return found;
9125
- const here = path26.dirname(new URL(import.meta.url).pathname);
9265
+ const here = path27.dirname(new URL(import.meta.url).pathname);
9126
9266
  const candidates = [
9127
- path26.join(here, "executables", profileName, "profile.json"),
9267
+ path27.join(here, "executables", profileName, "profile.json"),
9128
9268
  // same-dir sibling (dev)
9129
- path26.join(here, "..", "executables", profileName, "profile.json"),
9269
+ path27.join(here, "..", "executables", profileName, "profile.json"),
9130
9270
  // up one (prod: dist/bin → dist/executables)
9131
- path26.join(here, "..", "src", "executables", profileName, "profile.json")
9271
+ path27.join(here, "..", "src", "executables", profileName, "profile.json")
9132
9272
  // fallback
9133
9273
  ];
9134
9274
  for (const c of candidates) {
9135
- if (fs28.existsSync(c)) return c;
9275
+ if (fs29.existsSync(c)) return c;
9136
9276
  }
9137
9277
  return candidates[0];
9138
9278
  }
@@ -9215,13 +9355,6 @@ function resolveDottedPath(root, key) {
9215
9355
  }
9216
9356
  return cur;
9217
9357
  }
9218
- function finish(out) {
9219
- if (out.prUrl) process.stdout.write(`PR_URL=${out.prUrl}
9220
- `);
9221
- else if (out.reason) process.stdout.write(`PR_URL=FAILED: ${out.reason}
9222
- `);
9223
- return out;
9224
- }
9225
9358
  var DEFAULT_SHELL_TIMEOUT_MS = 3e5;
9226
9359
  function resolveShellTimeoutMs(entry) {
9227
9360
  if (typeof entry.timeoutSec === "number" && entry.timeoutSec > 0) {
@@ -9236,8 +9369,8 @@ function resolveShellTimeoutMs(entry) {
9236
9369
  var SIGKILL_GRACE_MS = 5e3;
9237
9370
  async function runShellEntry(entry, ctx, profile) {
9238
9371
  const shellName = entry.shell;
9239
- const shellPath = path26.join(profile.dir, shellName);
9240
- if (!fs28.existsSync(shellPath)) {
9372
+ const shellPath = path27.join(profile.dir, shellName);
9373
+ if (!fs29.existsSync(shellPath)) {
9241
9374
  ctx.skipAgent = true;
9242
9375
  ctx.output.exitCode = 99;
9243
9376
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -9412,6 +9545,7 @@ async function runContainerLoop(profile, ctx, input) {
9412
9545
  cliArgs = { issue: issueNumber };
9413
9546
  }
9414
9547
  let childOut;
9548
+ const childStartedAt = Date.now();
9415
9549
  try {
9416
9550
  childOut = await runChild(child.exec, {
9417
9551
  cliArgs,
@@ -9421,7 +9555,23 @@ async function runContainerLoop(profile, ctx, input) {
9421
9555
  verbose: input.verbose,
9422
9556
  quiet: input.quiet
9423
9557
  });
9558
+ emitEvent(input.cwd, {
9559
+ executable: profile.name,
9560
+ kind: "container_child",
9561
+ name: child.exec,
9562
+ durationMs: Date.now() - childStartedAt,
9563
+ outcome: childOut.exitCode === 0 ? "ok" : "failed",
9564
+ meta: { exitCode: childOut.exitCode, iteration }
9565
+ });
9424
9566
  } catch (err) {
9567
+ emitEvent(input.cwd, {
9568
+ executable: profile.name,
9569
+ kind: "container_child",
9570
+ name: child.exec,
9571
+ durationMs: Date.now() - childStartedAt,
9572
+ outcome: "failed",
9573
+ meta: { iteration, error: err instanceof Error ? err.message : String(err) }
9574
+ });
9425
9575
  const msg = err instanceof Error ? err.message : String(err);
9426
9576
  process.stderr.write(`[kody container] child "${child.exec}" crashed: ${msg}
9427
9577
  `);
@@ -9650,9 +9800,9 @@ function resolveAuthToken(env = process.env) {
9650
9800
  return token;
9651
9801
  }
9652
9802
  function detectPackageManager2(cwd) {
9653
- if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9654
- if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
9655
- if (fs29.existsSync(path27.join(cwd, "bun.lockb"))) return "bun";
9803
+ if (fs30.existsSync(path28.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9804
+ if (fs30.existsSync(path28.join(cwd, "yarn.lock"))) return "yarn";
9805
+ if (fs30.existsSync(path28.join(cwd, "bun.lockb"))) return "bun";
9656
9806
  return "npm";
9657
9807
  }
9658
9808
  function shellOut(cmd, args, cwd, stream = true) {
@@ -9739,11 +9889,11 @@ function configureGitIdentity(cwd) {
9739
9889
  }
9740
9890
  function postFailureTail(issueNumber, cwd, reason) {
9741
9891
  if (!issueNumber) return;
9742
- const logPath = path27.join(cwd, ".kody", "last-run.jsonl");
9892
+ const logPath = path28.join(cwd, ".kody", "last-run.jsonl");
9743
9893
  let tail = "";
9744
9894
  try {
9745
- if (fs29.existsSync(logPath)) {
9746
- const content = fs29.readFileSync(logPath, "utf-8");
9895
+ if (fs30.existsSync(logPath)) {
9896
+ const content = fs30.readFileSync(logPath, "utf-8");
9747
9897
  tail = content.slice(-3e3);
9748
9898
  }
9749
9899
  } catch {
@@ -9768,7 +9918,7 @@ async function runCi(argv) {
9768
9918
  return 0;
9769
9919
  }
9770
9920
  const args = parseCiArgs(argv);
9771
- const cwd = args.cwd ? path27.resolve(args.cwd) : process.cwd();
9921
+ const cwd = args.cwd ? path28.resolve(args.cwd) : process.cwd();
9772
9922
  let earlyConfig;
9773
9923
  try {
9774
9924
  earlyConfig = loadConfig(cwd);
@@ -9778,9 +9928,9 @@ async function runCi(argv) {
9778
9928
  const eventName = process.env.GITHUB_EVENT_NAME;
9779
9929
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
9780
9930
  let manualWorkflowDispatch = false;
9781
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs29.existsSync(dispatchEventPath)) {
9931
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs30.existsSync(dispatchEventPath)) {
9782
9932
  try {
9783
- const evt = JSON.parse(fs29.readFileSync(dispatchEventPath, "utf-8"));
9933
+ const evt = JSON.parse(fs30.readFileSync(dispatchEventPath, "utf-8"));
9784
9934
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
9785
9935
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
9786
9936
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -10025,9 +10175,9 @@ function parseChatArgs(argv, env = process.env) {
10025
10175
  return result;
10026
10176
  }
10027
10177
  function commitChatFiles(cwd, sessionId, verbose) {
10028
- const sessionFile = path28.relative(cwd, sessionFilePath(cwd, sessionId));
10029
- const eventsFile = path28.relative(cwd, eventsFilePath(cwd, sessionId));
10030
- const paths = [sessionFile, eventsFile].filter((p) => fs30.existsSync(path28.join(cwd, p)));
10178
+ const sessionFile = path29.relative(cwd, sessionFilePath(cwd, sessionId));
10179
+ const eventsFile = path29.relative(cwd, eventsFilePath(cwd, sessionId));
10180
+ const paths = [sessionFile, eventsFile].filter((p) => fs31.existsSync(path29.join(cwd, p)));
10031
10181
  if (paths.length === 0) return;
10032
10182
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
10033
10183
  try {
@@ -10065,7 +10215,7 @@ async function runChat(argv) {
10065
10215
  ${CHAT_HELP}`);
10066
10216
  return 64;
10067
10217
  }
10068
- const cwd = args.cwd ? path28.resolve(args.cwd) : process.cwd();
10218
+ const cwd = args.cwd ? path29.resolve(args.cwd) : process.cwd();
10069
10219
  const sessionId = args.sessionId;
10070
10220
  const unpackedSecrets = unpackAllSecrets();
10071
10221
  if (unpackedSecrets > 0) {
@@ -10117,7 +10267,7 @@ ${CHAT_HELP}`);
10117
10267
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
10118
10268
  const meta = readMeta(sessionFile);
10119
10269
  process.stdout.write(
10120
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs30.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
10270
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs31.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
10121
10271
  `
10122
10272
  );
10123
10273
  try {