@kody-ade/kody-engine 0.4.39 → 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 +431 -288
  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.39",
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;
@@ -7785,17 +7853,16 @@ var runFlow = async (ctx) => {
7785
7853
  ctx.data.issue = issue;
7786
7854
  ctx.data.commentTargetType = "issue";
7787
7855
  ctx.data.commentTargetNumber = issueNumber;
7788
- const labelBase = resolveBaseFromLabels(issue.labels ?? []);
7789
7856
  const argBase = resolveBaseOverride(ctx.args.base);
7790
7857
  const baseRaw = ctx.args.base;
7791
7858
  if (baseRaw && !argBase) {
7792
- process.stderr.write(`[kody runFlow] ignoring --base "${baseRaw}" (must match /^goal-[a-z0-9-]+$/)
7859
+ process.stderr.write(`[kody runFlow] ignoring --base "${baseRaw}" (must match kody-task or goal-branch pattern)
7793
7860
  `);
7794
7861
  }
7795
- const base = labelBase ?? argBase;
7862
+ const base = argBase;
7796
7863
  if (base) {
7797
7864
  ctx.data.baseBranch = base;
7798
- process.stderr.write(`[kody runFlow] resolved base branch: ${base} (${labelBase ? "from labels" : "from --base"})
7865
+ process.stderr.write(`[kody runFlow] resolved base branch: ${base} (from --base)
7799
7866
  `);
7800
7867
  }
7801
7868
  try {
@@ -7823,21 +7890,15 @@ function tryPost(issueNumber, body, cwd) {
7823
7890
  }
7824
7891
  function resolveBaseOverride(value) {
7825
7892
  if (!value) return null;
7826
- return /^goal-[a-z0-9-]+$/.test(value) ? value : null;
7827
- }
7828
- function resolveBaseFromLabels(labels) {
7829
- if (!labels.includes("goal-runner:dispatched")) return null;
7830
- const goalLabel2 = labels.find((l) => l.startsWith("goal:"));
7831
- if (!goalLabel2) return null;
7832
- const goalId = goalLabel2.slice("goal:".length);
7833
- if (!/^[a-z0-9-]+$/.test(goalId)) return null;
7834
- return `goal-${goalId}`;
7893
+ if (/^\d+-[a-z0-9-]+$/.test(value)) return value;
7894
+ if (/^goal-[a-z0-9-]+$/.test(value)) return value;
7895
+ return null;
7835
7896
  }
7836
7897
 
7837
7898
  // src/scripts/runTickScript.ts
7838
7899
  import { spawnSync } from "child_process";
7839
- import * as fs26 from "fs";
7840
- import * as path25 from "path";
7900
+ import * as fs27 from "fs";
7901
+ import * as path26 from "path";
7841
7902
  var runTickScript = async (ctx, _profile, args) => {
7842
7903
  ctx.skipAgent = true;
7843
7904
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
@@ -7849,13 +7910,13 @@ var runTickScript = async (ctx, _profile, args) => {
7849
7910
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
7850
7911
  return;
7851
7912
  }
7852
- const jobPath = path25.join(ctx.cwd, jobsDir, `${slug}.md`);
7853
- if (!fs26.existsSync(jobPath)) {
7913
+ const jobPath = path26.join(ctx.cwd, jobsDir, `${slug}.md`);
7914
+ if (!fs27.existsSync(jobPath)) {
7854
7915
  ctx.output.exitCode = 99;
7855
7916
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
7856
7917
  return;
7857
7918
  }
7858
- const raw = fs26.readFileSync(jobPath, "utf-8");
7919
+ const raw = fs27.readFileSync(jobPath, "utf-8");
7859
7920
  const { frontmatter } = splitFrontmatter(raw);
7860
7921
  const tickScript = frontmatter.tickScript;
7861
7922
  if (!tickScript) {
@@ -7863,8 +7924,8 @@ var runTickScript = async (ctx, _profile, args) => {
7863
7924
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
7864
7925
  return;
7865
7926
  }
7866
- const scriptPath = path25.isAbsolute(tickScript) ? tickScript : path25.join(ctx.cwd, tickScript);
7867
- if (!fs26.existsSync(scriptPath)) {
7927
+ const scriptPath = path26.isAbsolute(tickScript) ? tickScript : path26.join(ctx.cwd, tickScript);
7928
+ if (!fs27.existsSync(scriptPath)) {
7868
7929
  ctx.output.exitCode = 99;
7869
7930
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
7870
7931
  return;
@@ -8801,7 +8862,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
8801
8862
  };
8802
8863
 
8803
8864
  // src/scripts/writeRunSummary.ts
8804
- import * as fs27 from "fs";
8865
+ import * as fs28 from "fs";
8805
8866
  var writeRunSummary = async (ctx, profile) => {
8806
8867
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
8807
8868
  if (!summaryPath) return;
@@ -8823,7 +8884,7 @@ var writeRunSummary = async (ctx, profile) => {
8823
8884
  if (reason) lines.push(`- **Reason:** ${reason}`);
8824
8885
  lines.push("");
8825
8886
  try {
8826
- fs27.appendFileSync(summaryPath, `${lines.join("\n")}
8887
+ fs28.appendFileSync(summaryPath, `${lines.join("\n")}
8827
8888
  `);
8828
8889
  } catch {
8829
8890
  }
@@ -8959,22 +9020,42 @@ function runShell(cmd, cwd, timeoutMs = 3e4) {
8959
9020
  // src/executor.ts
8960
9021
  var CONTAINER_MAX_ITERATIONS = 50;
8961
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
+ };
8962
9043
  const profilePath = resolveProfilePath(profileName);
8963
9044
  const profile = loadProfile(profilePath);
8964
9045
  const missing = validateScriptReferences(profile, allScriptNames);
8965
9046
  if (missing.length > 0) {
8966
- return finish({ exitCode: 99, reason: `profile references unknown scripts: ${missing.join(", ")}` });
9047
+ return finishAndEnd({ exitCode: 99, reason: `profile references unknown scripts: ${missing.join(", ")}` });
8967
9048
  }
8968
9049
  let args;
8969
9050
  try {
8970
9051
  args = validateInputs(profile.inputs, input.cliArgs);
8971
9052
  } catch (err) {
8972
- 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) });
8973
9054
  }
8974
9055
  const toolResults = verifyCliTools(profile.cliTools, input.cwd);
8975
9056
  const firstFail = firstRequiredFailure(toolResults, profile.cliTools);
8976
9057
  if (firstFail) {
8977
- 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}` });
8978
9059
  }
8979
9060
  let config;
8980
9061
  if (input.config) {
@@ -8990,7 +9071,7 @@ async function runExecutable(profileName, input) {
8990
9071
  try {
8991
9072
  config = loadConfig(input.cwd);
8992
9073
  } catch (err) {
8993
- 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)}` });
8994
9075
  }
8995
9076
  }
8996
9077
  const modelSpec = profile.claudeCode.model === "inherit" ? config.agent.model : profile.claudeCode.model;
@@ -8998,13 +9079,13 @@ async function runExecutable(profileName, input) {
8998
9079
  try {
8999
9080
  model = parseProviderModel(modelSpec);
9000
9081
  } catch (err) {
9001
- 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)}` });
9002
9083
  }
9003
9084
  let litellm = null;
9004
9085
  try {
9005
9086
  litellm = await startLitellmIfNeeded(model, input.cwd);
9006
9087
  } catch (err) {
9007
- return finish({
9088
+ return finishAndEnd({
9008
9089
  exitCode: 99,
9009
9090
  reason: `litellm startup failed: ${err instanceof Error ? err.message : String(err)}`
9010
9091
  });
@@ -9018,9 +9099,9 @@ async function runExecutable(profileName, input) {
9018
9099
  data: {},
9019
9100
  output: { exitCode: 0 }
9020
9101
  };
9021
- const ndjsonDir = path26.join(input.cwd, ".kody");
9102
+ const ndjsonDir = path27.join(input.cwd, ".kody");
9022
9103
  const invokeAgent = async (prompt) => {
9023
- 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);
9024
9105
  const syntheticPath = ctx.data.syntheticPluginPath;
9025
9106
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
9026
9107
  return runAgent({
@@ -9044,15 +9125,39 @@ async function runExecutable(profileName, input) {
9044
9125
  ctx.data.__invokeAgent = invokeAgent;
9045
9126
  try {
9046
9127
  for (const entry of profile.scripts.preflight) {
9047
- 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();
9048
9139
  if (entry.shell) {
9049
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
+ });
9050
9148
  } else {
9051
9149
  const fn = preflightScripts[entry.script];
9052
- 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}` });
9053
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
+ });
9054
9159
  if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
9055
- return finish(ctx.output);
9160
+ return finishAndEnd(ctx.output);
9056
9161
  }
9057
9162
  }
9058
9163
  }
@@ -9063,9 +9168,21 @@ async function runExecutable(profileName, input) {
9063
9168
  } else if (!ctx.skipAgent) {
9064
9169
  const prompt = ctx.data.prompt;
9065
9170
  if (!prompt) {
9066
- 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)" });
9067
9172
  }
9173
+ emitEvent(input.cwd, { executable: profileName, kind: "agent_start" });
9068
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
+ });
9069
9186
  }
9070
9187
  for (const entry of profile.scripts.postflight) {
9071
9188
  const entryLabel = entry.script ?? entry.shell ?? "<unknown>";
@@ -9080,18 +9197,27 @@ async function runExecutable(profileName, input) {
9080
9197
  process.stderr.write(`[kody postflight] skip ${entryLabel}: ${reasons.join("; ")}
9081
9198
  `);
9082
9199
  }
9200
+ emitEvent(input.cwd, {
9201
+ executable: profileName,
9202
+ kind: "postflight",
9203
+ name: entryLabel,
9204
+ outcome: "skipped"
9205
+ });
9083
9206
  continue;
9084
9207
  }
9085
9208
  const label = entryLabel;
9209
+ const t0 = Date.now();
9210
+ let postOutcome = "ok";
9086
9211
  try {
9087
9212
  if (entry.shell) {
9088
9213
  await runShellEntry(entry, ctx, profile);
9089
9214
  } else {
9090
9215
  const fn = postflightScripts[entry.script];
9091
- 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}` });
9092
9217
  await fn(ctx, profile, agentResult, entry.with);
9093
9218
  }
9094
9219
  } catch (err) {
9220
+ postOutcome = "failed";
9095
9221
  const msg = err instanceof Error ? err.message : String(err);
9096
9222
  process.stderr.write(`[kody] postflight "${label}" crashed: ${msg}
9097
9223
  `);
@@ -9099,8 +9225,15 @@ async function runExecutable(profileName, input) {
9099
9225
  ctx.output.reason = ctx.output.reason ? `${ctx.output.reason}; ${summary}` : summary;
9100
9226
  if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
9101
9227
  }
9228
+ emitEvent(input.cwd, {
9229
+ executable: profileName,
9230
+ kind: "postflight",
9231
+ name: label,
9232
+ durationMs: Date.now() - t0,
9233
+ outcome: postOutcome
9234
+ });
9102
9235
  }
9103
- return finish({
9236
+ return finishAndEnd({
9104
9237
  exitCode: ctx.output.exitCode ?? 0,
9105
9238
  prUrl: ctx.output.prUrl,
9106
9239
  reason: ctx.output.reason
@@ -9129,17 +9262,17 @@ function clearStampedLifecycleLabels(profile, ctx) {
9129
9262
  function resolveProfilePath(profileName) {
9130
9263
  const found = resolveExecutable(profileName);
9131
9264
  if (found) return found;
9132
- const here = path26.dirname(new URL(import.meta.url).pathname);
9265
+ const here = path27.dirname(new URL(import.meta.url).pathname);
9133
9266
  const candidates = [
9134
- path26.join(here, "executables", profileName, "profile.json"),
9267
+ path27.join(here, "executables", profileName, "profile.json"),
9135
9268
  // same-dir sibling (dev)
9136
- path26.join(here, "..", "executables", profileName, "profile.json"),
9269
+ path27.join(here, "..", "executables", profileName, "profile.json"),
9137
9270
  // up one (prod: dist/bin → dist/executables)
9138
- path26.join(here, "..", "src", "executables", profileName, "profile.json")
9271
+ path27.join(here, "..", "src", "executables", profileName, "profile.json")
9139
9272
  // fallback
9140
9273
  ];
9141
9274
  for (const c of candidates) {
9142
- if (fs28.existsSync(c)) return c;
9275
+ if (fs29.existsSync(c)) return c;
9143
9276
  }
9144
9277
  return candidates[0];
9145
9278
  }
@@ -9222,13 +9355,6 @@ function resolveDottedPath(root, key) {
9222
9355
  }
9223
9356
  return cur;
9224
9357
  }
9225
- function finish(out) {
9226
- if (out.prUrl) process.stdout.write(`PR_URL=${out.prUrl}
9227
- `);
9228
- else if (out.reason) process.stdout.write(`PR_URL=FAILED: ${out.reason}
9229
- `);
9230
- return out;
9231
- }
9232
9358
  var DEFAULT_SHELL_TIMEOUT_MS = 3e5;
9233
9359
  function resolveShellTimeoutMs(entry) {
9234
9360
  if (typeof entry.timeoutSec === "number" && entry.timeoutSec > 0) {
@@ -9243,8 +9369,8 @@ function resolveShellTimeoutMs(entry) {
9243
9369
  var SIGKILL_GRACE_MS = 5e3;
9244
9370
  async function runShellEntry(entry, ctx, profile) {
9245
9371
  const shellName = entry.shell;
9246
- const shellPath = path26.join(profile.dir, shellName);
9247
- if (!fs28.existsSync(shellPath)) {
9372
+ const shellPath = path27.join(profile.dir, shellName);
9373
+ if (!fs29.existsSync(shellPath)) {
9248
9374
  ctx.skipAgent = true;
9249
9375
  ctx.output.exitCode = 99;
9250
9376
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -9419,6 +9545,7 @@ async function runContainerLoop(profile, ctx, input) {
9419
9545
  cliArgs = { issue: issueNumber };
9420
9546
  }
9421
9547
  let childOut;
9548
+ const childStartedAt = Date.now();
9422
9549
  try {
9423
9550
  childOut = await runChild(child.exec, {
9424
9551
  cliArgs,
@@ -9428,7 +9555,23 @@ async function runContainerLoop(profile, ctx, input) {
9428
9555
  verbose: input.verbose,
9429
9556
  quiet: input.quiet
9430
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
+ });
9431
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
+ });
9432
9575
  const msg = err instanceof Error ? err.message : String(err);
9433
9576
  process.stderr.write(`[kody container] child "${child.exec}" crashed: ${msg}
9434
9577
  `);
@@ -9657,9 +9800,9 @@ function resolveAuthToken(env = process.env) {
9657
9800
  return token;
9658
9801
  }
9659
9802
  function detectPackageManager2(cwd) {
9660
- if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9661
- if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
9662
- 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";
9663
9806
  return "npm";
9664
9807
  }
9665
9808
  function shellOut(cmd, args, cwd, stream = true) {
@@ -9746,11 +9889,11 @@ function configureGitIdentity(cwd) {
9746
9889
  }
9747
9890
  function postFailureTail(issueNumber, cwd, reason) {
9748
9891
  if (!issueNumber) return;
9749
- const logPath = path27.join(cwd, ".kody", "last-run.jsonl");
9892
+ const logPath = path28.join(cwd, ".kody", "last-run.jsonl");
9750
9893
  let tail = "";
9751
9894
  try {
9752
- if (fs29.existsSync(logPath)) {
9753
- const content = fs29.readFileSync(logPath, "utf-8");
9895
+ if (fs30.existsSync(logPath)) {
9896
+ const content = fs30.readFileSync(logPath, "utf-8");
9754
9897
  tail = content.slice(-3e3);
9755
9898
  }
9756
9899
  } catch {
@@ -9775,7 +9918,7 @@ async function runCi(argv) {
9775
9918
  return 0;
9776
9919
  }
9777
9920
  const args = parseCiArgs(argv);
9778
- const cwd = args.cwd ? path27.resolve(args.cwd) : process.cwd();
9921
+ const cwd = args.cwd ? path28.resolve(args.cwd) : process.cwd();
9779
9922
  let earlyConfig;
9780
9923
  try {
9781
9924
  earlyConfig = loadConfig(cwd);
@@ -9785,9 +9928,9 @@ async function runCi(argv) {
9785
9928
  const eventName = process.env.GITHUB_EVENT_NAME;
9786
9929
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
9787
9930
  let manualWorkflowDispatch = false;
9788
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs29.existsSync(dispatchEventPath)) {
9931
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs30.existsSync(dispatchEventPath)) {
9789
9932
  try {
9790
- const evt = JSON.parse(fs29.readFileSync(dispatchEventPath, "utf-8"));
9933
+ const evt = JSON.parse(fs30.readFileSync(dispatchEventPath, "utf-8"));
9791
9934
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
9792
9935
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
9793
9936
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -10032,9 +10175,9 @@ function parseChatArgs(argv, env = process.env) {
10032
10175
  return result;
10033
10176
  }
10034
10177
  function commitChatFiles(cwd, sessionId, verbose) {
10035
- const sessionFile = path28.relative(cwd, sessionFilePath(cwd, sessionId));
10036
- const eventsFile = path28.relative(cwd, eventsFilePath(cwd, sessionId));
10037
- 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)));
10038
10181
  if (paths.length === 0) return;
10039
10182
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
10040
10183
  try {
@@ -10072,7 +10215,7 @@ async function runChat(argv) {
10072
10215
  ${CHAT_HELP}`);
10073
10216
  return 64;
10074
10217
  }
10075
- const cwd = args.cwd ? path28.resolve(args.cwd) : process.cwd();
10218
+ const cwd = args.cwd ? path29.resolve(args.cwd) : process.cwd();
10076
10219
  const sessionId = args.sessionId;
10077
10220
  const unpackedSecrets = unpackAllSecrets();
10078
10221
  if (unpackedSecrets > 0) {
@@ -10124,7 +10267,7 @@ ${CHAT_HELP}`);
10124
10267
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
10125
10268
  const meta = readMeta(sessionFile);
10126
10269
  process.stdout.write(
10127
- `\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"}
10128
10271
  `
10129
10272
  );
10130
10273
  try {