@kody-ade/kody-engine 0.4.97 → 0.4.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/kody.js CHANGED
@@ -19,8 +19,8 @@ __export(events_exports, {
19
19
  resolveRunId: () => resolveRunId
20
20
  });
21
21
  import * as crypto from "crypto";
22
- import * as fs4 from "fs";
23
- import * as path4 from "path";
22
+ import * as fs5 from "fs";
23
+ import * as path5 from "path";
24
24
  function resolveRunId() {
25
25
  if (cachedRunId) return cachedRunId;
26
26
  if (process.env.KODY_RUN_ID) {
@@ -49,17 +49,17 @@ function emitEvent(cwd, ev) {
49
49
  runId,
50
50
  ...ev
51
51
  };
52
- const eventsPath2 = path4.join(cwd, ".kody", "runs", runId, "events.jsonl");
53
- fs4.mkdirSync(path4.dirname(eventsPath2), { recursive: true });
54
- fs4.appendFileSync(eventsPath2, `${JSON.stringify(fullEvent)}
52
+ const eventsPath2 = path5.join(cwd, ".kody", "runs", runId, "events.jsonl");
53
+ fs5.mkdirSync(path5.dirname(eventsPath2), { recursive: true });
54
+ fs5.appendFileSync(eventsPath2, `${JSON.stringify(fullEvent)}
55
55
  `);
56
56
  } catch {
57
57
  }
58
58
  }
59
59
  function readEvents(cwd, runId) {
60
- const eventsPath2 = path4.join(cwd, ".kody", "runs", runId, "events.jsonl");
61
- if (!fs4.existsSync(eventsPath2)) return [];
62
- const lines = fs4.readFileSync(eventsPath2, "utf-8").split("\n");
60
+ const eventsPath2 = path5.join(cwd, ".kody", "runs", runId, "events.jsonl");
61
+ if (!fs5.existsSync(eventsPath2)) return [];
62
+ const lines = fs5.readFileSync(eventsPath2, "utf-8").split("\n");
63
63
  const out = [];
64
64
  for (const line of lines) {
65
65
  const trimmed = line.trim();
@@ -72,11 +72,11 @@ function readEvents(cwd, runId) {
72
72
  return out;
73
73
  }
74
74
  function listRuns(cwd) {
75
- const runsDir = path4.join(cwd, ".kody", "runs");
76
- if (!fs4.existsSync(runsDir)) return [];
77
- return fs4.readdirSync(runsDir).filter((name) => {
75
+ const runsDir = path5.join(cwd, ".kody", "runs");
76
+ if (!fs5.existsSync(runsDir)) return [];
77
+ return fs5.readdirSync(runsDir).filter((name) => {
78
78
  try {
79
- return fs4.statSync(path4.join(runsDir, name)).isDirectory();
79
+ return fs5.statSync(path5.join(runsDir, name)).isDirectory();
80
80
  } catch {
81
81
  return false;
82
82
  }
@@ -469,16 +469,16 @@ var init_issue = __esm({
469
469
  });
470
470
 
471
471
  // src/prompt.ts
472
- import * as fs17 from "fs";
473
- import * as path15 from "path";
472
+ import * as fs18 from "fs";
473
+ import * as path16 from "path";
474
474
  function loadProjectConventions(projectDir) {
475
475
  const out = [];
476
476
  for (const rel of CONVENTION_FILES) {
477
- const abs = path15.join(projectDir, rel);
478
- if (!fs17.existsSync(abs)) continue;
477
+ const abs = path16.join(projectDir, rel);
478
+ if (!fs18.existsSync(abs)) continue;
479
479
  let content;
480
480
  try {
481
- content = fs17.readFileSync(abs, "utf-8");
481
+ content = fs18.readFileSync(abs, "utf-8");
482
482
  } catch {
483
483
  continue;
484
484
  }
@@ -626,28 +626,28 @@ var loadMemoryContext_exports = {};
626
626
  __export(loadMemoryContext_exports, {
627
627
  loadMemoryContext: () => loadMemoryContext
628
628
  });
629
- import * as fs32 from "fs";
630
- import * as path30 from "path";
629
+ import * as fs33 from "fs";
630
+ import * as path31 from "path";
631
631
  function collectPages(memoryAbs) {
632
632
  const out = [];
633
633
  walkMd(memoryAbs, (file) => {
634
634
  let stat;
635
635
  try {
636
- stat = fs32.statSync(file);
636
+ stat = fs33.statSync(file);
637
637
  } catch {
638
638
  return;
639
639
  }
640
640
  let raw;
641
641
  try {
642
- raw = fs32.readFileSync(file, "utf-8");
642
+ raw = fs33.readFileSync(file, "utf-8");
643
643
  } catch {
644
644
  return;
645
645
  }
646
646
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
647
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path30.basename(file, ".md");
647
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path31.basename(file, ".md");
648
648
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
649
649
  out.push({
650
- relPath: path30.relative(memoryAbs, file),
650
+ relPath: path31.relative(memoryAbs, file),
651
651
  title,
652
652
  updated,
653
653
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX : raw,
@@ -715,16 +715,16 @@ function walkMd(root, visit) {
715
715
  const dir = stack.pop();
716
716
  let names;
717
717
  try {
718
- names = fs32.readdirSync(dir);
718
+ names = fs33.readdirSync(dir);
719
719
  } catch {
720
720
  continue;
721
721
  }
722
722
  for (const name of names) {
723
723
  if (name.startsWith(".")) continue;
724
- const full = path30.join(dir, name);
724
+ const full = path31.join(dir, name);
725
725
  let stat;
726
726
  try {
727
- stat = fs32.statSync(full);
727
+ stat = fs33.statSync(full);
728
728
  } catch {
729
729
  continue;
730
730
  }
@@ -747,8 +747,8 @@ var init_loadMemoryContext = __esm({
747
747
  TRUNCATED_SUFFIX = "\n\n\u2026 (truncated)";
748
748
  loadMemoryContext = async (ctx) => {
749
749
  if (typeof ctx.data.memoryContext === "string") return;
750
- const memoryAbs = path30.join(ctx.cwd, MEMORY_DIR_RELATIVE);
751
- if (!fs32.existsSync(memoryAbs)) {
750
+ const memoryAbs = path31.join(ctx.cwd, MEMORY_DIR_RELATIVE);
751
+ if (!fs33.existsSync(memoryAbs)) {
752
752
  ctx.data.memoryContext = "";
753
753
  return;
754
754
  }
@@ -877,7 +877,7 @@ var init_loadPriorArt = __esm({
877
877
  // package.json
878
878
  var package_default = {
879
879
  name: "@kody-ade/kody-engine",
880
- version: "0.4.97",
880
+ version: "0.4.100",
881
881
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
882
882
  license: "MIT",
883
883
  type: "module",
@@ -931,9 +931,9 @@ var package_default = {
931
931
  };
932
932
 
933
933
  // src/chat-cli.ts
934
- import { execFileSync as execFileSync31 } from "child_process";
935
- import * as fs38 from "fs";
936
- import * as path35 from "path";
934
+ import { execFileSync as execFileSync32 } from "child_process";
935
+ import * as fs39 from "fs";
936
+ import * as path36 from "path";
937
937
 
938
938
  // src/chat/events.ts
939
939
  import * as fs from "fs";
@@ -999,18 +999,106 @@ function makeRunId(sessionId, suffix) {
999
999
  }
1000
1000
 
1001
1001
  // src/chat/loop.ts
1002
- import * as fs8 from "fs";
1002
+ import * as fs9 from "fs";
1003
+
1004
+ // src/task-artifacts.ts
1005
+ import fs2 from "fs";
1006
+ import path2 from "path";
1007
+ var TASK_ARTIFACT_FILES = [
1008
+ "context.json",
1009
+ "memory-recs.json",
1010
+ "followups.json",
1011
+ "handoff-notes.md"
1012
+ ];
1013
+ function prepareTaskArtifactsDir(cwd, taskId) {
1014
+ const safeId = String(taskId).replace(/[^a-zA-Z0-9._-]/g, "_");
1015
+ const relDir = path2.join(".kody", "tasks", safeId);
1016
+ const absDir = path2.join(cwd, relDir);
1017
+ fs2.mkdirSync(absDir, { recursive: true });
1018
+ return { taskId: safeId, absDir, relDir };
1019
+ }
1020
+ function verifyTaskArtifacts(absDir) {
1021
+ const missing = [];
1022
+ for (const name of TASK_ARTIFACT_FILES) {
1023
+ const full = path2.join(absDir, name);
1024
+ try {
1025
+ const stat = fs2.statSync(full);
1026
+ if (!stat.isFile() || stat.size === 0) missing.push(name);
1027
+ } catch {
1028
+ missing.push(name);
1029
+ }
1030
+ }
1031
+ return missing;
1032
+ }
1033
+ function taskArtifactsPromptAddendum(opts) {
1034
+ return [
1035
+ "## Per-task artifacts (REQUIRED before your final response)",
1036
+ "",
1037
+ `Before you finish, write these four files into \`${opts.relDir}/\`:`,
1038
+ "",
1039
+ `1. **context.json** \u2014 task header. Shape:`,
1040
+ " ```json",
1041
+ " {",
1042
+ ` "taskId": "${opts.taskId}",`,
1043
+ ` "taskType": "${opts.taskType}",`,
1044
+ ` "target": "<issue/PR number, session id, or job slug>",`,
1045
+ ` "outcome": "success" | "failure" | "partial",`,
1046
+ ` "exitCode": <number>,`,
1047
+ ` "reason": "<one-line summary of why you exited>",`,
1048
+ ` "prUrl": "<url or null>",`,
1049
+ ` "runUrl": "<url or null>",`,
1050
+ ` "filesTouched": ["path/from/repo/root.ts", ...],`,
1051
+ ` "sessionLog": ".kody/sessions/<id>.jsonl",`,
1052
+ ` "startedAt": "<ISO>",`,
1053
+ ` "finishedAt": "<ISO>"`,
1054
+ " }",
1055
+ " ```",
1056
+ "",
1057
+ `2. **memory-recs.json** \u2014 array of sticky-note candidates worth promoting`,
1058
+ ` to long-term \`.kody/memory/\`. Each item:`,
1059
+ " ```json",
1060
+ " {",
1061
+ ` "type": "preference" | "decision" | "lesson",`,
1062
+ ` "name": "kebab-case-slug",`,
1063
+ ` "hook": "one-line summary for INDEX.md",`,
1064
+ ` "body": "markdown body \u2014 explain the rule plus a Why: line",`,
1065
+ ` "why": "the load-bearing reason a future session needs this",`,
1066
+ ` "confidence": 0.0 to 1.0`,
1067
+ " }",
1068
+ " ```",
1069
+ ` Use \`[]\` if nothing in this task is worth remembering. Forced`,
1070
+ ` filler is worse than nothing \u2014 only record what would be lost`,
1071
+ ` otherwise.`,
1072
+ "",
1073
+ `3. **followups.json** \u2014 array of TODOs uncovered but not fixed.`,
1074
+ " ```json",
1075
+ " {",
1076
+ ` "title": "short summary",`,
1077
+ ` "body": "what the operator should do, and where",`,
1078
+ ` "rationale": "why this matters",`,
1079
+ ` "priority": "low" | "medium" | "high"`,
1080
+ " }",
1081
+ " ```",
1082
+ ` Use \`[]\` if nothing surfaced.`,
1083
+ "",
1084
+ `4. **handoff-notes.md** \u2014 short prose (\u2264200 words), no frontmatter:`,
1085
+ ` what you did and why, so the next person/agent can pick up cold.`,
1086
+ "",
1087
+ "Skipping any of the four files is an error. Empty arrays are fine;",
1088
+ "skipping the file is not."
1089
+ ].join("\n");
1090
+ }
1003
1091
 
1004
1092
  // src/agent.ts
1005
- import * as fs5 from "fs";
1006
- import * as path5 from "path";
1093
+ import * as fs6 from "fs";
1094
+ import * as path6 from "path";
1007
1095
  import { query } from "@anthropic-ai/claude-agent-sdk";
1008
1096
 
1009
1097
  // src/claudeBinary.ts
1010
- import * as fs2 from "fs";
1098
+ import * as fs3 from "fs";
1011
1099
  import { createRequire } from "module";
1012
1100
  import * as os from "os";
1013
- import * as path2 from "path";
1101
+ import * as path3 from "path";
1014
1102
  var SDK_PKG = "@anthropic-ai/claude-agent-sdk";
1015
1103
  function candidateSpecs(platform, arch) {
1016
1104
  const ext = platform === "win32" ? ".exe" : "";
@@ -1020,8 +1108,8 @@ function candidateSpecs(platform, arch) {
1020
1108
  function readSdkVersion(req) {
1021
1109
  try {
1022
1110
  const entry = req.resolve(SDK_PKG);
1023
- const pkgDir = path2.dirname(entry);
1024
- const raw = fs2.readFileSync(path2.join(pkgDir, "package.json"), "utf8");
1111
+ const pkgDir = path3.dirname(entry);
1112
+ const raw = fs3.readFileSync(path3.join(pkgDir, "package.json"), "utf8");
1025
1113
  const v = JSON.parse(raw)?.version;
1026
1114
  return typeof v === "string" && v.length > 0 ? v : "unknown";
1027
1115
  } catch {
@@ -1043,24 +1131,24 @@ function ensureStableClaudeBinary() {
1043
1131
  } catch {
1044
1132
  }
1045
1133
  }
1046
- if (!source || !fs2.existsSync(source)) {
1134
+ if (!source || !fs3.existsSync(source)) {
1047
1135
  cached = null;
1048
1136
  return cached;
1049
1137
  }
1050
1138
  const ext = process.platform === "win32" ? ".exe" : "";
1051
1139
  const version = readSdkVersion(req);
1052
- const destDir = path2.join(os.tmpdir(), "kody-claude-sdk", version);
1053
- const dest = path2.join(destDir, `claude${ext}`);
1054
- const srcSize = fs2.statSync(source).size;
1055
- if (fs2.existsSync(dest) && fs2.statSync(dest).size === srcSize) {
1140
+ const destDir = path3.join(os.tmpdir(), "kody-claude-sdk", version);
1141
+ const dest = path3.join(destDir, `claude${ext}`);
1142
+ const srcSize = fs3.statSync(source).size;
1143
+ if (fs3.existsSync(dest) && fs3.statSync(dest).size === srcSize) {
1056
1144
  cached = dest;
1057
1145
  return cached;
1058
1146
  }
1059
- fs2.mkdirSync(destDir, { recursive: true });
1060
- const tmp = path2.join(destDir, `.claude.${process.pid}.${Date.now()}.tmp`);
1061
- fs2.copyFileSync(source, tmp);
1062
- fs2.chmodSync(tmp, 493);
1063
- fs2.renameSync(tmp, dest);
1147
+ fs3.mkdirSync(destDir, { recursive: true });
1148
+ const tmp = path3.join(destDir, `.claude.${process.pid}.${Date.now()}.tmp`);
1149
+ fs3.copyFileSync(source, tmp);
1150
+ fs3.chmodSync(tmp, 493);
1151
+ fs3.renameSync(tmp, dest);
1064
1152
  cached = dest;
1065
1153
  return cached;
1066
1154
  } catch {
@@ -1070,8 +1158,8 @@ function ensureStableClaudeBinary() {
1070
1158
  }
1071
1159
 
1072
1160
  // src/config.ts
1073
- import * as fs3 from "fs";
1074
- import * as path3 from "path";
1161
+ import * as fs4 from "fs";
1162
+ import * as path4 from "path";
1075
1163
  var LITELLM_DEFAULT_PORT = 4e3;
1076
1164
  var LITELLM_DEFAULT_URL = `http://localhost:${LITELLM_DEFAULT_PORT}`;
1077
1165
  function parseProviderModel(s) {
@@ -1089,13 +1177,13 @@ function needsLitellmProxy(model) {
1089
1177
  return model.provider !== "claude" && model.provider !== "anthropic";
1090
1178
  }
1091
1179
  function loadConfig(projectDir = process.cwd()) {
1092
- const configPath = path3.join(projectDir, "kody.config.json");
1093
- if (!fs3.existsSync(configPath)) {
1180
+ const configPath = path4.join(projectDir, "kody.config.json");
1181
+ if (!fs4.existsSync(configPath)) {
1094
1182
  throw new Error(`kody.config.json not found at ${configPath}`);
1095
1183
  }
1096
1184
  let raw;
1097
1185
  try {
1098
- raw = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1186
+ raw = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
1099
1187
  } catch (err) {
1100
1188
  const msg = err instanceof Error ? err.message : String(err);
1101
1189
  throw new Error(`kody.config.json is invalid JSON: ${msg}`);
@@ -1358,10 +1446,10 @@ function resolveTurnTimeoutMs(opts) {
1358
1446
  return DEFAULT_TURN_TIMEOUT_MS;
1359
1447
  }
1360
1448
  async function runAgent(opts) {
1361
- const ndjsonDir = opts.ndjsonDir ?? path5.join(opts.cwd, ".kody");
1362
- fs5.mkdirSync(ndjsonDir, { recursive: true });
1363
- const ndjsonPath = path5.join(ndjsonDir, "last-run.jsonl");
1364
- const fullLog = fs5.createWriteStream(ndjsonPath, { flags: "w" });
1449
+ const ndjsonDir = opts.ndjsonDir ?? path6.join(opts.cwd, ".kody");
1450
+ fs6.mkdirSync(ndjsonDir, { recursive: true });
1451
+ const ndjsonPath = path6.join(ndjsonDir, "last-run.jsonl");
1452
+ const fullLog = fs6.createWriteStream(ndjsonPath, { flags: "w" });
1365
1453
  const env = {
1366
1454
  ...process.env,
1367
1455
  SKIP_HOOKS: "1",
@@ -1592,48 +1680,48 @@ async function runAgent(opts) {
1592
1680
  }
1593
1681
 
1594
1682
  // src/registry.ts
1595
- import * as fs6 from "fs";
1596
- import * as path6 from "path";
1683
+ import * as fs7 from "fs";
1684
+ import * as path7 from "path";
1597
1685
  function getExecutablesRoot() {
1598
- const here = path6.dirname(new URL(import.meta.url).pathname);
1686
+ const here = path7.dirname(new URL(import.meta.url).pathname);
1599
1687
  const candidates = [
1600
- path6.join(here, "executables"),
1688
+ path7.join(here, "executables"),
1601
1689
  // dev: src/
1602
- path6.join(here, "..", "executables"),
1690
+ path7.join(here, "..", "executables"),
1603
1691
  // built: dist/bin → dist/executables
1604
- path6.join(here, "..", "src", "executables")
1692
+ path7.join(here, "..", "src", "executables")
1605
1693
  // fallback
1606
1694
  ];
1607
1695
  for (const c of candidates) {
1608
- if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
1696
+ if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
1609
1697
  }
1610
1698
  return candidates[0];
1611
1699
  }
1612
1700
  function getProjectExecutablesRoot() {
1613
- return path6.join(process.cwd(), ".kody", "executables");
1701
+ return path7.join(process.cwd(), ".kody", "executables");
1614
1702
  }
1615
1703
  function getBuiltinJobsRoot() {
1616
- const here = path6.dirname(new URL(import.meta.url).pathname);
1704
+ const here = path7.dirname(new URL(import.meta.url).pathname);
1617
1705
  const candidates = [
1618
- path6.join(here, "jobs"),
1706
+ path7.join(here, "jobs"),
1619
1707
  // dev: src/
1620
- path6.join(here, "..", "jobs"),
1708
+ path7.join(here, "..", "jobs"),
1621
1709
  // built: dist/bin → dist/jobs
1622
- path6.join(here, "..", "src", "jobs")
1710
+ path7.join(here, "..", "src", "jobs")
1623
1711
  // fallback
1624
1712
  ];
1625
1713
  for (const c of candidates) {
1626
- if (fs6.existsSync(c) && fs6.statSync(c).isDirectory()) return c;
1714
+ if (fs7.existsSync(c) && fs7.statSync(c).isDirectory()) return c;
1627
1715
  }
1628
1716
  return candidates[0];
1629
1717
  }
1630
1718
  function listBuiltinJobs(root = getBuiltinJobsRoot()) {
1631
- if (!fs6.existsSync(root) || !fs6.statSync(root).isDirectory()) return [];
1719
+ if (!fs7.existsSync(root) || !fs7.statSync(root).isDirectory()) return [];
1632
1720
  const out = [];
1633
- for (const ent of fs6.readdirSync(root, { withFileTypes: true })) {
1721
+ for (const ent of fs7.readdirSync(root, { withFileTypes: true })) {
1634
1722
  if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
1635
1723
  const slug = ent.name.slice(0, -3);
1636
- out.push({ slug, filePath: path6.join(root, ent.name) });
1724
+ out.push({ slug, filePath: path7.join(root, ent.name) });
1637
1725
  }
1638
1726
  out.sort((a, b) => a.slug.localeCompare(b.slug));
1639
1727
  return out;
@@ -1646,13 +1734,13 @@ function listExecutables(roots = getExecutableRoots()) {
1646
1734
  const seen = /* @__PURE__ */ new Set();
1647
1735
  const out = [];
1648
1736
  for (const root of rootList) {
1649
- if (!fs6.existsSync(root)) continue;
1650
- const entries = fs6.readdirSync(root, { withFileTypes: true });
1737
+ if (!fs7.existsSync(root)) continue;
1738
+ const entries = fs7.readdirSync(root, { withFileTypes: true });
1651
1739
  for (const ent of entries) {
1652
1740
  if (!ent.isDirectory()) continue;
1653
1741
  if (seen.has(ent.name)) continue;
1654
- const profilePath = path6.join(root, ent.name, "profile.json");
1655
- if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
1742
+ const profilePath = path7.join(root, ent.name, "profile.json");
1743
+ if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
1656
1744
  out.push({ name: ent.name, profilePath });
1657
1745
  seen.add(ent.name);
1658
1746
  }
@@ -1664,8 +1752,8 @@ function resolveExecutable(name, roots = getExecutableRoots()) {
1664
1752
  if (!isSafeName(name)) return null;
1665
1753
  const rootList = typeof roots === "string" ? [roots] : roots;
1666
1754
  for (const root of rootList) {
1667
- const profilePath = path6.join(root, name, "profile.json");
1668
- if (fs6.existsSync(profilePath) && fs6.statSync(profilePath).isFile()) {
1755
+ const profilePath = path7.join(root, name, "profile.json");
1756
+ if (fs7.existsSync(profilePath) && fs7.statSync(profilePath).isFile()) {
1669
1757
  return profilePath;
1670
1758
  }
1671
1759
  }
@@ -1681,7 +1769,7 @@ function getProfileInputs(name, roots = getExecutableRoots()) {
1681
1769
  const profilePath = resolveExecutable(name, roots);
1682
1770
  if (!profilePath) return null;
1683
1771
  try {
1684
- const raw = JSON.parse(fs6.readFileSync(profilePath, "utf-8"));
1772
+ const raw = JSON.parse(fs7.readFileSync(profilePath, "utf-8"));
1685
1773
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.inputs)) return [];
1686
1774
  return raw.inputs;
1687
1775
  } catch {
@@ -1711,14 +1799,14 @@ function parseGenericFlags(argv) {
1711
1799
  }
1712
1800
 
1713
1801
  // src/chat/session.ts
1714
- import * as fs7 from "fs";
1715
- import * as path7 from "path";
1802
+ import * as fs8 from "fs";
1803
+ import * as path8 from "path";
1716
1804
  function sessionFilePath(cwd, sessionId) {
1717
- return path7.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
1805
+ return path8.join(cwd, ".kody", "sessions", `${sessionId}.jsonl`);
1718
1806
  }
1719
1807
  function readMeta(file) {
1720
- if (!fs7.existsSync(file)) return null;
1721
- const raw = fs7.readFileSync(file, "utf-8");
1808
+ if (!fs8.existsSync(file)) return null;
1809
+ const raw = fs8.readFileSync(file, "utf-8");
1722
1810
  const firstLine2 = raw.split("\n", 1)[0]?.trim();
1723
1811
  if (!firstLine2) return null;
1724
1812
  try {
@@ -1731,8 +1819,8 @@ function readMeta(file) {
1731
1819
  }
1732
1820
  }
1733
1821
  function readSession(file) {
1734
- if (!fs7.existsSync(file)) return [];
1735
- const raw = fs7.readFileSync(file, "utf-8").trim();
1822
+ if (!fs8.existsSync(file)) return [];
1823
+ const raw = fs8.readFileSync(file, "utf-8").trim();
1736
1824
  if (!raw) return [];
1737
1825
  const turns = [];
1738
1826
  for (const line of raw.split("\n")) {
@@ -1748,14 +1836,14 @@ function readSession(file) {
1748
1836
  return turns;
1749
1837
  }
1750
1838
  function appendTurn(file, turn) {
1751
- fs7.mkdirSync(path7.dirname(file), { recursive: true });
1839
+ fs8.mkdirSync(path8.dirname(file), { recursive: true });
1752
1840
  const line = JSON.stringify({
1753
1841
  role: turn.role,
1754
1842
  content: turn.content,
1755
1843
  timestamp: turn.timestamp,
1756
1844
  toolCalls: turn.toolCalls ?? []
1757
1845
  });
1758
- fs7.appendFileSync(file, `${line}
1846
+ fs8.appendFileSync(file, `${line}
1759
1847
  `);
1760
1848
  }
1761
1849
  function seedInitialMessage(file, message) {
@@ -1865,7 +1953,7 @@ function buildExecutableCatalog() {
1865
1953
  const entries = [];
1866
1954
  for (const { name, profilePath } of discovered) {
1867
1955
  try {
1868
- const raw = JSON.parse(fs8.readFileSync(profilePath, "utf-8"));
1956
+ const raw = JSON.parse(fs9.readFileSync(profilePath, "utf-8"));
1869
1957
  const describe = typeof raw.describe === "string" ? raw.describe : "";
1870
1958
  const firstSentence = describe.split(/(?<=[.!?])\s+/, 1)[0] ?? "";
1871
1959
  entries.push({ name, describe: firstSentence.trim() });
@@ -1902,9 +1990,13 @@ async function runChatTurn(opts) {
1902
1990
  }
1903
1991
  const basePrompt = opts.systemPrompt ?? CHAT_SYSTEM_PROMPT;
1904
1992
  const catalog = buildExecutableCatalog();
1905
- const systemPrompt = catalog ? `${basePrompt}
1906
-
1907
- ${catalog}` : basePrompt;
1993
+ const taskArtifactsPaths = prepareTaskArtifactsDir(opts.cwd, opts.sessionId);
1994
+ const artifactAddendum = taskArtifactsPromptAddendum({
1995
+ taskId: taskArtifactsPaths.taskId,
1996
+ taskType: "chat",
1997
+ relDir: taskArtifactsPaths.relDir
1998
+ });
1999
+ const systemPrompt = [basePrompt, catalog, artifactAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n");
1908
2000
  const prompt = buildPrompt(turns);
1909
2001
  let progressSeq = 0;
1910
2002
  const invoke = opts.invokeAgent ?? ((p) => runAgent({
@@ -1970,6 +2062,16 @@ ${catalog}` : basePrompt;
1970
2062
  timestamp: now
1971
2063
  });
1972
2064
  await emit(opts.sink, "chat.done", opts.sessionId, "done", { sessionId: opts.sessionId });
2065
+ try {
2066
+ const missing = verifyTaskArtifacts(taskArtifactsPaths.absDir);
2067
+ if (missing.length > 0) {
2068
+ process.stderr.write(
2069
+ `[task-artifacts] chat session ${taskArtifactsPaths.taskId} missing: ${missing.join(", ")}
2070
+ `
2071
+ );
2072
+ }
2073
+ } catch {
2074
+ }
1973
2075
  return { exitCode: 0, reply };
1974
2076
  }
1975
2077
  function buildPrompt(turns) {
@@ -1990,8 +2092,8 @@ async function emit(sink, type, sessionId, suffix, payload) {
1990
2092
  // src/chat/modes/interactive.ts
1991
2093
  init_issue();
1992
2094
  import { execFileSync as execFileSync3 } from "child_process";
1993
- import * as fs9 from "fs";
1994
- import * as path8 from "path";
2095
+ import * as fs10 from "fs";
2096
+ import * as path9 from "path";
1995
2097
 
1996
2098
  // src/chat/inbox.ts
1997
2099
  import { execFileSync as execFileSync2 } from "child_process";
@@ -2150,9 +2252,9 @@ function findNextUserTurn(turns, fromIdx) {
2150
2252
  return -1;
2151
2253
  }
2152
2254
  function commitTurn(cwd, sessionId, _verbose) {
2153
- const sessionRel = path8.relative(cwd, sessionFilePath(cwd, sessionId));
2154
- const eventsRel = path8.relative(cwd, eventsFilePath(cwd, sessionId));
2155
- const rels = [sessionRel, eventsRel].filter((p) => fs9.existsSync(path8.join(cwd, p)));
2255
+ const sessionRel = path9.relative(cwd, sessionFilePath(cwd, sessionId));
2256
+ const eventsRel = path9.relative(cwd, eventsFilePath(cwd, sessionId));
2257
+ const rels = [sessionRel, eventsRel].filter((p) => fs10.existsSync(path9.join(cwd, p)));
2156
2258
  if (rels.length === 0) return;
2157
2259
  const repository = process.env.GITHUB_REPOSITORY;
2158
2260
  if (!repository) {
@@ -2164,8 +2266,8 @@ function commitTurn(cwd, sessionId, _verbose) {
2164
2266
  }
2165
2267
  const branch = defaultBranch(cwd) ?? "main";
2166
2268
  for (const rel of rels) {
2167
- const repoPath = rel.split(path8.sep).join("/");
2168
- const localText = fs9.readFileSync(path8.join(cwd, rel), "utf-8");
2269
+ const repoPath = rel.split(path9.sep).join("/");
2270
+ const localText = fs10.readFileSync(path9.join(cwd, rel), "utf-8");
2169
2271
  putJsonlViaContents(repository, branch, repoPath, localText, sessionId, cwd);
2170
2272
  }
2171
2273
  }
@@ -2254,12 +2356,12 @@ async function emit2(sink, type, sessionId, suffix, payload) {
2254
2356
  }
2255
2357
 
2256
2358
  // src/kody-cli.ts
2257
- import { execFileSync as execFileSync30 } from "child_process";
2258
- import * as fs37 from "fs";
2259
- import * as path34 from "path";
2359
+ import { execFileSync as execFileSync31 } from "child_process";
2360
+ import * as fs38 from "fs";
2361
+ import * as path35 from "path";
2260
2362
 
2261
2363
  // src/dispatch.ts
2262
- import * as fs10 from "fs";
2364
+ import * as fs11 from "fs";
2263
2365
 
2264
2366
  // src/cron-match.ts
2265
2367
  var FIELD_BOUNDS = [
@@ -2343,10 +2445,10 @@ function autoDispatch(opts) {
2343
2445
  }
2344
2446
  const eventName = process.env.GITHUB_EVENT_NAME;
2345
2447
  const eventPath = process.env.GITHUB_EVENT_PATH;
2346
- if (!eventName || !eventPath || !fs10.existsSync(eventPath)) return null;
2448
+ if (!eventName || !eventPath || !fs11.existsSync(eventPath)) return null;
2347
2449
  let event = {};
2348
2450
  try {
2349
- event = JSON.parse(fs10.readFileSync(eventPath, "utf-8"));
2451
+ event = JSON.parse(fs11.readFileSync(eventPath, "utf-8"));
2350
2452
  } catch {
2351
2453
  return null;
2352
2454
  }
@@ -2419,7 +2521,7 @@ function autoDispatchTyped(opts) {
2419
2521
  if (legacy) return { kind: "route", ...legacy };
2420
2522
  const eventName = process.env.GITHUB_EVENT_NAME;
2421
2523
  const eventPath = process.env.GITHUB_EVENT_PATH;
2422
- if (!eventName || !eventPath || !fs10.existsSync(eventPath)) {
2524
+ if (!eventName || !eventPath || !fs11.existsSync(eventPath)) {
2423
2525
  return { kind: "silent", reason: "no GHA event context" };
2424
2526
  }
2425
2527
  if (eventName !== "issue_comment") {
@@ -2427,7 +2529,7 @@ function autoDispatchTyped(opts) {
2427
2529
  }
2428
2530
  let event = {};
2429
2531
  try {
2430
- event = JSON.parse(fs10.readFileSync(eventPath, "utf-8"));
2532
+ event = JSON.parse(fs11.readFileSync(eventPath, "utf-8"));
2431
2533
  } catch {
2432
2534
  return { kind: "silent", reason: "GHA event payload unreadable" };
2433
2535
  }
@@ -2461,7 +2563,7 @@ function dispatchScheduledWatches(opts) {
2461
2563
  for (const exe of listExecutables()) {
2462
2564
  let raw;
2463
2565
  try {
2464
- raw = fs10.readFileSync(exe.profilePath, "utf-8");
2566
+ raw = fs11.readFileSync(exe.profilePath, "utf-8");
2465
2567
  } catch {
2466
2568
  continue;
2467
2569
  }
@@ -2577,17 +2679,17 @@ function coerceBare(spec, value) {
2577
2679
  init_issue();
2578
2680
 
2579
2681
  // src/executor.ts
2580
- import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
2581
- import * as fs36 from "fs";
2582
- import * as path33 from "path";
2682
+ import { execFileSync as execFileSync30, spawn as spawn6 } from "child_process";
2683
+ import * as fs37 from "fs";
2684
+ import * as path34 from "path";
2583
2685
  init_events();
2584
2686
 
2585
2687
  // src/lifecycleLabels.ts
2586
2688
  init_issue();
2587
2689
 
2588
2690
  // src/profile.ts
2589
- import * as fs11 from "fs";
2590
- import * as path9 from "path";
2691
+ import * as fs12 from "fs";
2692
+ import * as path10 from "path";
2591
2693
 
2592
2694
  // src/profile-error.ts
2593
2695
  var ProfileError = class extends Error {
@@ -2755,12 +2857,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
2755
2857
  "preloadContext"
2756
2858
  ]);
2757
2859
  function loadProfile(profilePath) {
2758
- if (!fs11.existsSync(profilePath)) {
2860
+ if (!fs12.existsSync(profilePath)) {
2759
2861
  throw new ProfileError(profilePath, "file not found");
2760
2862
  }
2761
2863
  let raw;
2762
2864
  try {
2763
- raw = JSON.parse(fs11.readFileSync(profilePath, "utf-8"));
2865
+ raw = JSON.parse(fs12.readFileSync(profilePath, "utf-8"));
2764
2866
  } catch (err) {
2765
2867
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
2766
2868
  }
@@ -2771,7 +2873,7 @@ function loadProfile(profilePath) {
2771
2873
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
2772
2874
  if (unknownKeys.length > 0) {
2773
2875
  process.stderr.write(
2774
- `[kody profile] ${path9.basename(path9.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
2876
+ `[kody profile] ${path10.basename(path10.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
2775
2877
  `
2776
2878
  );
2777
2879
  }
@@ -2832,7 +2934,7 @@ function loadProfile(profilePath) {
2832
2934
  // Phase 5 in-process handoff opt-in. Default false; containers
2833
2935
  // flip to true after end-to-end verification.
2834
2936
  preloadContext: r.preloadContext === true,
2835
- dir: path9.dirname(profilePath)
2937
+ dir: path10.dirname(profilePath)
2836
2938
  };
2837
2939
  if (lifecycle) {
2838
2940
  applyLifecycle(profile, profilePath);
@@ -3202,9 +3304,9 @@ function errMsg(err) {
3202
3304
 
3203
3305
  // src/litellm.ts
3204
3306
  import { execFileSync as execFileSync4, spawn as spawn2 } from "child_process";
3205
- import * as fs12 from "fs";
3307
+ import * as fs13 from "fs";
3206
3308
  import * as os2 from "os";
3207
- import * as path10 from "path";
3309
+ import * as path11 from "path";
3208
3310
  async function checkLitellmHealth(url) {
3209
3311
  try {
3210
3312
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -3251,20 +3353,20 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3251
3353
  throw new Error("litellm not installed \u2014 run: pip install 'litellm[proxy]'");
3252
3354
  }
3253
3355
  }
3254
- const configPath = path10.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3255
- fs12.writeFileSync(configPath, generateLitellmConfigYaml(model));
3356
+ const configPath = path11.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
3357
+ fs13.writeFileSync(configPath, generateLitellmConfigYaml(model));
3256
3358
  const portMatch = url.match(/:(\d+)/);
3257
3359
  const port = portMatch ? portMatch[1] : "4000";
3258
3360
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
3259
3361
  const dotenvVars = readDotenvApiKeys(projectDir);
3260
- const logPath = path10.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3261
- const outFd = fs12.openSync(logPath, "w");
3362
+ const logPath = path11.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
3363
+ const outFd = fs13.openSync(logPath, "w");
3262
3364
  const child = spawn2(cmd, args, {
3263
3365
  stdio: ["ignore", outFd, outFd],
3264
3366
  detached: true,
3265
3367
  env: stripBlockingEnv({ ...process.env, ...dotenvVars })
3266
3368
  });
3267
- fs12.closeSync(outFd);
3369
+ fs13.closeSync(outFd);
3268
3370
  const timeoutMs = resolveLitellmTimeoutMs();
3269
3371
  const deadline = Date.now() + timeoutMs;
3270
3372
  while (Date.now() < deadline) {
@@ -3283,7 +3385,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3283
3385
  }
3284
3386
  let logTail = "";
3285
3387
  try {
3286
- logTail = fs12.readFileSync(logPath, "utf-8").slice(-2e3);
3388
+ logTail = fs13.readFileSync(logPath, "utf-8").slice(-2e3);
3287
3389
  } catch {
3288
3390
  }
3289
3391
  try {
@@ -3295,10 +3397,10 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
3295
3397
  ${logTail}`);
3296
3398
  }
3297
3399
  function readDotenvApiKeys(projectDir) {
3298
- const dotenvPath = path10.join(projectDir, ".env");
3299
- if (!fs12.existsSync(dotenvPath)) return {};
3400
+ const dotenvPath = path11.join(projectDir, ".env");
3401
+ if (!fs13.existsSync(dotenvPath)) return {};
3300
3402
  const result = {};
3301
- for (const rawLine of fs12.readFileSync(dotenvPath, "utf-8").split("\n")) {
3403
+ for (const rawLine of fs13.readFileSync(dotenvPath, "utf-8").split("\n")) {
3302
3404
  const line = rawLine.trim();
3303
3405
  if (!line || line.startsWith("#")) continue;
3304
3406
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -3321,9 +3423,87 @@ function stripBlockingEnv(env) {
3321
3423
  }
3322
3424
 
3323
3425
  // src/commit.ts
3426
+ import { execFileSync as execFileSync6 } from "child_process";
3427
+
3428
+ // src/pushWithRetry.ts
3324
3429
  import { execFileSync as execFileSync5 } from "child_process";
3325
- import * as fs13 from "fs";
3326
- import * as path11 from "path";
3430
+ var DEFAULT_MAX_RETRIES = 3;
3431
+ var DEFAULT_BACKOFF_MS = 1e3;
3432
+ var MAX_BACKOFF_MS = 6e4;
3433
+ var NON_FAST_FORWARD_RE = /non-fast-forward|fetch first|\(rejected\)|! \[rejected\]/i;
3434
+ function sleepSync(ms) {
3435
+ if (ms <= 0) return;
3436
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
3437
+ }
3438
+ function runGit(args, cwd) {
3439
+ try {
3440
+ const stdout = execFileSync5("git", args, {
3441
+ cwd,
3442
+ encoding: "utf-8",
3443
+ env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
3444
+ stdio: ["ignore", "pipe", "pipe"]
3445
+ });
3446
+ return { ok: true, stdout: stdout?.toString() ?? "", stderr: "" };
3447
+ } catch (err) {
3448
+ const e = err;
3449
+ const stderr = e.stderr?.toString() ?? e.message ?? "";
3450
+ const stdout = e.stdout?.toString() ?? "";
3451
+ return { ok: false, stdout, stderr };
3452
+ }
3453
+ }
3454
+ function resolveBranch(cwd, explicit) {
3455
+ if (explicit && explicit.trim()) return explicit.trim();
3456
+ const r = runGit(["symbolic-ref", "--short", "HEAD"], cwd);
3457
+ return r.ok ? r.stdout.trim() : "";
3458
+ }
3459
+ function pushWithRetry(opts = {}) {
3460
+ const cwd = opts.cwd ?? process.cwd();
3461
+ const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
3462
+ const baseBackoff = opts.backoffMs ?? DEFAULT_BACKOFF_MS;
3463
+ const branch = resolveBranch(cwd, opts.branch);
3464
+ if (!branch) {
3465
+ return { ok: false, reason: "could not determine current branch (detached HEAD?)", attempts: 0 };
3466
+ }
3467
+ const pushArgs = opts.setUpstream ? ["push", "-u", "origin", `HEAD:${branch}`] : ["push", "origin", "HEAD"];
3468
+ let lastError = "";
3469
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
3470
+ const push = runGit(pushArgs, cwd);
3471
+ if (push.ok) return { ok: true, attempts: attempt };
3472
+ lastError = push.stderr || push.stdout || "(no error detail)";
3473
+ if (!NON_FAST_FORWARD_RE.test(lastError)) {
3474
+ return { ok: false, reason: `push failed (not retryable): ${lastError.trim().slice(-400)}`, attempts: attempt };
3475
+ }
3476
+ if (attempt === maxRetries) break;
3477
+ const fetch2 = runGit(["fetch", "origin", branch], cwd);
3478
+ if (!fetch2.ok) {
3479
+ return {
3480
+ ok: false,
3481
+ reason: `fetch failed during retry: ${(fetch2.stderr || fetch2.stdout).trim().slice(-400)}`,
3482
+ attempts: attempt
3483
+ };
3484
+ }
3485
+ const rebase = runGit(["rebase", `origin/${branch}`], cwd);
3486
+ if (!rebase.ok) {
3487
+ runGit(["rebase", "--abort"], cwd);
3488
+ return {
3489
+ ok: false,
3490
+ reason: `rebase onto origin/${branch} failed (conflict?): ${(rebase.stderr || rebase.stdout).trim().slice(-400)}`,
3491
+ attempts: attempt
3492
+ };
3493
+ }
3494
+ const delay = Math.min(baseBackoff * 2 ** (attempt - 1), MAX_BACKOFF_MS);
3495
+ sleepSync(delay);
3496
+ }
3497
+ return {
3498
+ ok: false,
3499
+ reason: `push rejected after ${maxRetries} attempts: ${lastError.trim().slice(-400)}`,
3500
+ attempts: maxRetries
3501
+ };
3502
+ }
3503
+
3504
+ // src/commit.ts
3505
+ import * as fs14 from "fs";
3506
+ import * as path12 from "path";
3327
3507
  var FORBIDDEN_PATH_PREFIXES = [
3328
3508
  ".kody/",
3329
3509
  ".kody-engine/",
@@ -3350,13 +3530,9 @@ var CONVENTIONAL_PREFIXES = [
3350
3530
  "build:",
3351
3531
  "revert:"
3352
3532
  ];
3353
- var PUSH_RETRY_DELAYS_MS = [2e3, 4e3, 8e3];
3354
- function sleepSync(ms) {
3355
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
3356
- }
3357
3533
  function git(args, cwd) {
3358
3534
  try {
3359
- return execFileSync5("git", args, {
3535
+ return execFileSync6("git", args, {
3360
3536
  encoding: "utf-8",
3361
3537
  timeout: 12e4,
3362
3538
  cwd,
@@ -3383,18 +3559,18 @@ function tryGit(args, cwd) {
3383
3559
  }
3384
3560
  function abortUnfinishedGitOps(cwd) {
3385
3561
  const aborted = [];
3386
- const gitDir = path11.join(cwd ?? process.cwd(), ".git");
3387
- if (!fs13.existsSync(gitDir)) return aborted;
3388
- if (fs13.existsSync(path11.join(gitDir, "MERGE_HEAD"))) {
3562
+ const gitDir = path12.join(cwd ?? process.cwd(), ".git");
3563
+ if (!fs14.existsSync(gitDir)) return aborted;
3564
+ if (fs14.existsSync(path12.join(gitDir, "MERGE_HEAD"))) {
3389
3565
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
3390
3566
  }
3391
- if (fs13.existsSync(path11.join(gitDir, "CHERRY_PICK_HEAD"))) {
3567
+ if (fs14.existsSync(path12.join(gitDir, "CHERRY_PICK_HEAD"))) {
3392
3568
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
3393
3569
  }
3394
- if (fs13.existsSync(path11.join(gitDir, "REVERT_HEAD"))) {
3570
+ if (fs14.existsSync(path12.join(gitDir, "REVERT_HEAD"))) {
3395
3571
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
3396
3572
  }
3397
- if (fs13.existsSync(path11.join(gitDir, "rebase-merge")) || fs13.existsSync(path11.join(gitDir, "rebase-apply"))) {
3573
+ if (fs14.existsSync(path12.join(gitDir, "rebase-merge")) || fs14.existsSync(path12.join(gitDir, "rebase-apply"))) {
3398
3574
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
3399
3575
  }
3400
3576
  try {
@@ -3415,7 +3591,7 @@ function isForbiddenPath(p) {
3415
3591
  return false;
3416
3592
  }
3417
3593
  function listChangedFiles(cwd) {
3418
- const raw = execFileSync5("git", ["status", "--porcelain=v1", "-z"], {
3594
+ const raw = execFileSync6("git", ["status", "--porcelain=v1", "-z"], {
3419
3595
  encoding: "utf-8",
3420
3596
  cwd,
3421
3597
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -3427,7 +3603,7 @@ function listChangedFiles(cwd) {
3427
3603
  }
3428
3604
  function listFilesInCommit(ref = "HEAD", cwd) {
3429
3605
  try {
3430
- const raw = execFileSync5("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
3606
+ const raw = execFileSync6("git", ["show", "--name-only", "--pretty=format:", "-z", ref], {
3431
3607
  encoding: "utf-8",
3432
3608
  cwd,
3433
3609
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
@@ -3450,7 +3626,7 @@ function normalizeCommitMessage(raw) {
3450
3626
  function commitAndPush(branch, agentMessage, cwd) {
3451
3627
  const allChanged = listChangedFiles(cwd);
3452
3628
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
3453
- const mergeHeadExists = fs13.existsSync(path11.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3629
+ const mergeHeadExists = fs14.existsSync(path12.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3454
3630
  if (allowedFiles.length === 0 && !mergeHeadExists) {
3455
3631
  return { committed: false, pushed: false, sha: "", message: "" };
3456
3632
  }
@@ -3471,28 +3647,11 @@ function commitAndPush(branch, agentMessage, cwd) {
3471
3647
  throw err;
3472
3648
  }
3473
3649
  const sha = git(["rev-parse", "HEAD"], cwd).slice(0, 7);
3474
- let pushError = "push failed (no error detail)";
3475
- for (let attempt = 0; attempt <= PUSH_RETRY_DELAYS_MS.length; attempt++) {
3476
- try {
3477
- git(["push", "-u", "origin", branch], cwd);
3478
- return { committed: true, pushed: true, sha, message };
3479
- } catch (firstErr) {
3480
- try {
3481
- git(["push", "--force-with-lease", "-u", "origin", branch], cwd);
3482
- return { committed: true, pushed: true, sha, message };
3483
- } catch (secondErr) {
3484
- const tail = (secondErr instanceof Error ? secondErr.message : String(secondErr)).slice(-400);
3485
- const initial = firstErr instanceof Error ? firstErr.message : String(firstErr);
3486
- pushError = `push failed: ${initial.slice(-200)} | force-with-lease failed: ${tail}`;
3487
- const delay = PUSH_RETRY_DELAYS_MS[attempt];
3488
- if (delay === void 0) break;
3489
- process.stderr.write(`[kody:commit] push failed (attempt ${attempt + 1}); retrying in ${delay}ms
3490
- `);
3491
- sleepSync(delay);
3492
- }
3493
- }
3650
+ const pushResult = pushWithRetry({ cwd, branch, setUpstream: true });
3651
+ if (pushResult.ok) {
3652
+ return { committed: true, pushed: true, sha, message };
3494
3653
  }
3495
- return { committed: true, pushed: false, sha, message, pushError };
3654
+ return { committed: true, pushed: false, sha, message, pushError: pushResult.reason };
3496
3655
  }
3497
3656
  function hasCommitsAhead(branch, defaultBranch2, cwd) {
3498
3657
  try {
@@ -3519,10 +3678,10 @@ var abortUnfinishedGitOps2 = async (ctx) => {
3519
3678
  };
3520
3679
 
3521
3680
  // src/scripts/advanceFlow.ts
3522
- import { execFileSync as execFileSync7 } from "child_process";
3681
+ import { execFileSync as execFileSync8 } from "child_process";
3523
3682
 
3524
3683
  // src/state.ts
3525
- import { execFileSync as execFileSync6 } from "child_process";
3684
+ import { execFileSync as execFileSync7 } from "child_process";
3526
3685
  var STATE_BEGIN = "<!-- kody:state:v1:begin -->";
3527
3686
  var STATE_END = "<!-- kody:state:v1:end -->";
3528
3687
  var HISTORY_MAX_ENTRIES = 20;
@@ -3548,7 +3707,7 @@ function ghToken2() {
3548
3707
  function gh2(args, input, cwd) {
3549
3708
  const token = ghToken2();
3550
3709
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
3551
- return execFileSync6("gh", args, {
3710
+ return execFileSync7("gh", args, {
3552
3711
  encoding: "utf-8",
3553
3712
  timeout: API_TIMEOUT_MS2,
3554
3713
  cwd,
@@ -3751,7 +3910,7 @@ var advanceFlow = async (ctx, profile) => {
3751
3910
  }
3752
3911
  const body = `@kody ${flow.name}`;
3753
3912
  try {
3754
- execFileSync7("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
3913
+ execFileSync8("gh", ["issue", "comment", String(flow.issueNumber), "--body", body], {
3755
3914
  timeout: API_TIMEOUT_MS3,
3756
3915
  cwd: ctx.cwd,
3757
3916
  stdio: ["ignore", "pipe", "pipe"]
@@ -3766,20 +3925,20 @@ var advanceFlow = async (ctx, profile) => {
3766
3925
 
3767
3926
  // src/scripts/brainServe.ts
3768
3927
  import { createServer } from "http";
3769
- import * as fs15 from "fs";
3770
- import * as path13 from "path";
3928
+ import * as fs16 from "fs";
3929
+ import * as path14 from "path";
3771
3930
 
3772
3931
  // src/scripts/brainTurnLog.ts
3773
- import * as fs14 from "fs";
3774
- import * as path12 from "path";
3932
+ import * as fs15 from "fs";
3933
+ import * as path13 from "path";
3775
3934
  var live = /* @__PURE__ */ new Map();
3776
3935
  function eventsPath(dir, chatId) {
3777
- return path12.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
3936
+ return path13.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
3778
3937
  }
3779
3938
  function lastPersistedSeq(dir, chatId) {
3780
3939
  const p = eventsPath(dir, chatId);
3781
- if (!fs14.existsSync(p)) return 0;
3782
- const lines = fs14.readFileSync(p, "utf-8").split("\n").filter(Boolean);
3940
+ if (!fs15.existsSync(p)) return 0;
3941
+ const lines = fs15.readFileSync(p, "utf-8").split("\n").filter(Boolean);
3783
3942
  if (lines.length === 0) return 0;
3784
3943
  try {
3785
3944
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -3789,9 +3948,9 @@ function lastPersistedSeq(dir, chatId) {
3789
3948
  }
3790
3949
  function readSince(dir, chatId, since) {
3791
3950
  const p = eventsPath(dir, chatId);
3792
- if (!fs14.existsSync(p)) return [];
3951
+ if (!fs15.existsSync(p)) return [];
3793
3952
  const out = [];
3794
- for (const line of fs14.readFileSync(p, "utf-8").split("\n")) {
3953
+ for (const line of fs15.readFileSync(p, "utf-8").split("\n")) {
3795
3954
  if (!line) continue;
3796
3955
  try {
3797
3956
  const rec = JSON.parse(line);
@@ -3817,12 +3976,12 @@ function beginTurn(dir, chatId) {
3817
3976
  };
3818
3977
  live.set(chatId, state);
3819
3978
  const p = eventsPath(dir, chatId);
3820
- fs14.mkdirSync(path12.dirname(p), { recursive: true });
3979
+ fs15.mkdirSync(path13.dirname(p), { recursive: true });
3821
3980
  return (event) => {
3822
3981
  state.seq += 1;
3823
3982
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
3824
3983
  try {
3825
- fs14.appendFileSync(p, JSON.stringify(rec) + "\n");
3984
+ fs15.appendFileSync(p, JSON.stringify(rec) + "\n");
3826
3985
  } catch (err) {
3827
3986
  process.stderr.write(
3828
3987
  `[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
@@ -3860,7 +4019,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
3860
4019
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
3861
4020
  };
3862
4021
  try {
3863
- fs14.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
4022
+ fs15.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
3864
4023
  } catch {
3865
4024
  }
3866
4025
  state.status = "ended";
@@ -4077,7 +4236,7 @@ async function handleChatTurn(req, res, chatId, opts) {
4077
4236
  return;
4078
4237
  }
4079
4238
  const sessionFile = sessionFilePath(opts.cwd, chatId);
4080
- fs15.mkdirSync(path13.dirname(sessionFile), { recursive: true });
4239
+ fs16.mkdirSync(path14.dirname(sessionFile), { recursive: true });
4081
4240
  appendTurn(sessionFile, {
4082
4241
  role: "user",
4083
4242
  content: message,
@@ -4217,21 +4376,21 @@ var brainServe = async (ctx) => {
4217
4376
  };
4218
4377
 
4219
4378
  // src/scripts/buildSyntheticPlugin.ts
4220
- import * as fs16 from "fs";
4379
+ import * as fs17 from "fs";
4221
4380
  import * as os3 from "os";
4222
- import * as path14 from "path";
4381
+ import * as path15 from "path";
4223
4382
  function getPluginsCatalogRoot() {
4224
- const here = path14.dirname(new URL(import.meta.url).pathname);
4383
+ const here = path15.dirname(new URL(import.meta.url).pathname);
4225
4384
  const candidates = [
4226
- path14.join(here, "..", "plugins"),
4385
+ path15.join(here, "..", "plugins"),
4227
4386
  // dev: src/scripts → src/plugins
4228
- path14.join(here, "..", "..", "plugins"),
4387
+ path15.join(here, "..", "..", "plugins"),
4229
4388
  // built: dist/scripts → dist/plugins
4230
- path14.join(here, "..", "..", "src", "plugins")
4389
+ path15.join(here, "..", "..", "src", "plugins")
4231
4390
  // fallback
4232
4391
  ];
4233
4392
  for (const c of candidates) {
4234
- if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
4393
+ if (fs17.existsSync(c) && fs17.statSync(c).isDirectory()) return c;
4235
4394
  }
4236
4395
  return candidates[0];
4237
4396
  }
@@ -4241,52 +4400,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4241
4400
  if (!needsSynthetic) return;
4242
4401
  const catalog = getPluginsCatalogRoot();
4243
4402
  const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4244
- const root = path14.join(os3.tmpdir(), `kody-synth-${runId}`);
4245
- fs16.mkdirSync(path14.join(root, ".claude-plugin"), { recursive: true });
4403
+ const root = path15.join(os3.tmpdir(), `kody-synth-${runId}`);
4404
+ fs17.mkdirSync(path15.join(root, ".claude-plugin"), { recursive: true });
4246
4405
  const resolvePart = (bucket, entry) => {
4247
- const local = path14.join(profile.dir, bucket, entry);
4248
- if (fs16.existsSync(local)) return local;
4249
- const central = path14.join(catalog, bucket, entry);
4250
- if (fs16.existsSync(central)) return central;
4406
+ const local = path15.join(profile.dir, bucket, entry);
4407
+ if (fs17.existsSync(local)) return local;
4408
+ const central = path15.join(catalog, bucket, entry);
4409
+ if (fs17.existsSync(central)) return central;
4251
4410
  throw new Error(
4252
4411
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
4253
4412
  );
4254
4413
  };
4255
4414
  if (cc.skills.length > 0) {
4256
- const dst = path14.join(root, "skills");
4257
- fs16.mkdirSync(dst, { recursive: true });
4415
+ const dst = path15.join(root, "skills");
4416
+ fs17.mkdirSync(dst, { recursive: true });
4258
4417
  for (const name of cc.skills) {
4259
- copyDir(resolvePart("skills", name), path14.join(dst, name));
4418
+ copyDir(resolvePart("skills", name), path15.join(dst, name));
4260
4419
  }
4261
4420
  }
4262
4421
  if (cc.commands.length > 0) {
4263
- const dst = path14.join(root, "commands");
4264
- fs16.mkdirSync(dst, { recursive: true });
4422
+ const dst = path15.join(root, "commands");
4423
+ fs17.mkdirSync(dst, { recursive: true });
4265
4424
  for (const name of cc.commands) {
4266
- fs16.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
4425
+ fs17.copyFileSync(resolvePart("commands", `${name}.md`), path15.join(dst, `${name}.md`));
4267
4426
  }
4268
4427
  }
4269
4428
  if (cc.subagents.length > 0) {
4270
- const dst = path14.join(root, "agents");
4271
- fs16.mkdirSync(dst, { recursive: true });
4429
+ const dst = path15.join(root, "agents");
4430
+ fs17.mkdirSync(dst, { recursive: true });
4272
4431
  for (const name of cc.subagents) {
4273
- fs16.copyFileSync(resolvePart("agents", `${name}.md`), path14.join(dst, `${name}.md`));
4432
+ fs17.copyFileSync(resolvePart("agents", `${name}.md`), path15.join(dst, `${name}.md`));
4274
4433
  }
4275
4434
  }
4276
4435
  if (cc.hooks.length > 0) {
4277
- const dst = path14.join(root, "hooks");
4278
- fs16.mkdirSync(dst, { recursive: true });
4436
+ const dst = path15.join(root, "hooks");
4437
+ fs17.mkdirSync(dst, { recursive: true });
4279
4438
  const merged = { hooks: {} };
4280
4439
  for (const name of cc.hooks) {
4281
4440
  const src = resolvePart("hooks", `${name}.json`);
4282
- const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
4441
+ const parsed = JSON.parse(fs17.readFileSync(src, "utf-8"));
4283
4442
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
4284
4443
  if (!Array.isArray(entries)) continue;
4285
4444
  if (!merged.hooks[event]) merged.hooks[event] = [];
4286
4445
  merged.hooks[event].push(...entries);
4287
4446
  }
4288
4447
  }
4289
- fs16.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4448
+ fs17.writeFileSync(path15.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4290
4449
  `);
4291
4450
  }
4292
4451
  const manifest = {
@@ -4297,22 +4456,22 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4297
4456
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
4298
4457
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
4299
4458
  if (cc.subagents.length > 0) manifest.agents = cc.subagents.map((n) => `./agents/${n}.md`);
4300
- fs16.writeFileSync(path14.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
4459
+ fs17.writeFileSync(path15.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
4301
4460
  `);
4302
4461
  ctx.data.syntheticPluginPath = root;
4303
4462
  };
4304
4463
  function copyDir(src, dst) {
4305
- fs16.mkdirSync(dst, { recursive: true });
4306
- for (const ent of fs16.readdirSync(src, { withFileTypes: true })) {
4307
- const s = path14.join(src, ent.name);
4308
- const d = path14.join(dst, ent.name);
4464
+ fs17.mkdirSync(dst, { recursive: true });
4465
+ for (const ent of fs17.readdirSync(src, { withFileTypes: true })) {
4466
+ const s = path15.join(src, ent.name);
4467
+ const d = path15.join(dst, ent.name);
4309
4468
  if (ent.isDirectory()) copyDir(s, d);
4310
- else if (ent.isFile()) fs16.copyFileSync(s, d);
4469
+ else if (ent.isFile()) fs17.copyFileSync(s, d);
4311
4470
  }
4312
4471
  }
4313
4472
 
4314
4473
  // src/coverage.ts
4315
- import { execFileSync as execFileSync8 } from "child_process";
4474
+ import { execFileSync as execFileSync9 } from "child_process";
4316
4475
  function patternToRegex(pattern) {
4317
4476
  let s = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
4318
4477
  s = s.replace(/\*\*\//g, "\xA7S").replace(/\*\*/g, "\xA7A").replace(/\*/g, "[^/]*");
@@ -4330,7 +4489,7 @@ function renderSiblingPath(file, requireSibling) {
4330
4489
  }
4331
4490
  function safeGit(args, cwd) {
4332
4491
  try {
4333
- return execFileSync8("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
4492
+ return execFileSync9("git", args, { encoding: "utf-8", cwd, env: { ...process.env, HUSKY: "0" } }).trim();
4334
4493
  } catch {
4335
4494
  return "";
4336
4495
  }
@@ -4448,13 +4607,13 @@ function defaultLabelMap() {
4448
4607
  }
4449
4608
 
4450
4609
  // src/scripts/commitAndPush.ts
4451
- import * as fs18 from "fs";
4452
- import * as path16 from "path";
4610
+ import * as fs19 from "fs";
4611
+ import * as path17 from "path";
4453
4612
  init_events();
4454
4613
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
4455
4614
  function sentinelPathForStage(cwd, profileName) {
4456
4615
  const runId = resolveRunId();
4457
- return path16.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4616
+ return path17.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4458
4617
  }
4459
4618
  var commitAndPush2 = async (ctx, profile) => {
4460
4619
  const branch = ctx.data.branch;
@@ -4464,9 +4623,9 @@ var commitAndPush2 = async (ctx, profile) => {
4464
4623
  }
4465
4624
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
4466
4625
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
4467
- if (sentinel && fs18.existsSync(sentinel)) {
4626
+ if (sentinel && fs19.existsSync(sentinel)) {
4468
4627
  try {
4469
- const replay = JSON.parse(fs18.readFileSync(sentinel, "utf-8"));
4628
+ const replay = JSON.parse(fs19.readFileSync(sentinel, "utf-8"));
4470
4629
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
4471
4630
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
4472
4631
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -4519,8 +4678,8 @@ var commitAndPush2 = async (ctx, profile) => {
4519
4678
  const result = ctx.data.commitResult;
4520
4679
  if (sentinel && result?.committed) {
4521
4680
  try {
4522
- fs18.mkdirSync(path16.dirname(sentinel), { recursive: true });
4523
- fs18.writeFileSync(
4681
+ fs19.mkdirSync(path17.dirname(sentinel), { recursive: true });
4682
+ fs19.writeFileSync(
4524
4683
  sentinel,
4525
4684
  JSON.stringify(
4526
4685
  {
@@ -4540,14 +4699,14 @@ var commitAndPush2 = async (ctx, profile) => {
4540
4699
  };
4541
4700
 
4542
4701
  // src/scripts/commitGoalState.ts
4543
- import { execFileSync as execFileSync9 } from "child_process";
4544
- import * as path17 from "path";
4702
+ import { execFileSync as execFileSync10 } from "child_process";
4703
+ import * as path18 from "path";
4545
4704
  var commitGoalState = async (ctx) => {
4546
4705
  const goal = ctx.data.goal;
4547
4706
  if (!goal) return;
4548
- const stateRel = path17.posix.join(".kody", "goals", goal.id, "state.json");
4707
+ const stateRel = path18.posix.join(".kody", "goals", goal.id, "state.json");
4549
4708
  try {
4550
- execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
4709
+ execFileSync10("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
4551
4710
  } catch (err) {
4552
4711
  process.stderr.write(
4553
4712
  `[goal-tick] commitGoalState: git add failed: ${err instanceof Error ? err.message : String(err)}
@@ -4556,13 +4715,13 @@ var commitGoalState = async (ctx) => {
4556
4715
  return;
4557
4716
  }
4558
4717
  try {
4559
- execFileSync9("git", ["diff", "--cached", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
4718
+ execFileSync10("git", ["diff", "--cached", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
4560
4719
  return;
4561
4720
  } catch {
4562
4721
  }
4563
4722
  const msg = describeCommitMessage(goal);
4564
4723
  try {
4565
- execFileSync9("git", ["commit", "-m", msg, "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
4724
+ execFileSync10("git", ["commit", "-m", msg, "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
4566
4725
  } catch (err) {
4567
4726
  process.stderr.write(
4568
4727
  `[goal-tick] commitGoalState: git commit failed: ${err instanceof Error ? err.message : String(err)}
@@ -4570,10 +4729,10 @@ var commitGoalState = async (ctx) => {
4570
4729
  );
4571
4730
  return;
4572
4731
  }
4573
- try {
4574
- execFileSync9("git", ["push", "--quiet"], { cwd: ctx.cwd, stdio: "pipe" });
4575
- } catch {
4576
- process.stderr.write("[goal-tick] commitGoalState: push failed (will retry next tick)\n");
4732
+ const result = pushWithRetry({ cwd: ctx.cwd });
4733
+ if (!result.ok) {
4734
+ process.stderr.write(`[goal-tick] commitGoalState: push failed (${result.reason}); will retry next tick
4735
+ `);
4577
4736
  }
4578
4737
  };
4579
4738
  function describeCommitMessage(goal) {
@@ -4590,20 +4749,20 @@ function describeCommitMessage(goal) {
4590
4749
  }
4591
4750
 
4592
4751
  // src/scripts/composePrompt.ts
4593
- import * as fs19 from "fs";
4594
- import * as path18 from "path";
4752
+ import * as fs20 from "fs";
4753
+ import * as path19 from "path";
4595
4754
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
4596
4755
  var composePrompt = async (ctx, profile) => {
4597
4756
  const explicit = ctx.data.promptTemplate;
4598
4757
  const mode = ctx.args.mode;
4599
4758
  const candidates = [
4600
- explicit ? path18.join(profile.dir, explicit) : null,
4601
- mode ? path18.join(profile.dir, "prompts", `${mode}.md`) : null,
4602
- path18.join(profile.dir, "prompt.md")
4759
+ explicit ? path19.join(profile.dir, explicit) : null,
4760
+ mode ? path19.join(profile.dir, "prompts", `${mode}.md`) : null,
4761
+ path19.join(profile.dir, "prompt.md")
4603
4762
  ].filter(Boolean);
4604
4763
  let templatePath = "";
4605
4764
  for (const c of candidates) {
4606
- if (fs19.existsSync(c)) {
4765
+ if (fs20.existsSync(c)) {
4607
4766
  templatePath = c;
4608
4767
  break;
4609
4768
  }
@@ -4611,7 +4770,7 @@ var composePrompt = async (ctx, profile) => {
4611
4770
  if (!templatePath) {
4612
4771
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
4613
4772
  }
4614
- const template = fs19.readFileSync(templatePath, "utf-8");
4773
+ const template = fs20.readFileSync(templatePath, "utf-8");
4615
4774
  const tokens = {
4616
4775
  ...stringifyAll(ctx.args, "args."),
4617
4776
  ...stringifyAll(ctx.data, ""),
@@ -4689,9 +4848,9 @@ function formatToolsUsage(profile) {
4689
4848
 
4690
4849
  // src/scripts/createQaGoal.ts
4691
4850
  init_issue();
4692
- import { execFileSync as execFileSync10 } from "child_process";
4693
- import * as fs20 from "fs";
4694
- import * as path19 from "path";
4851
+ import { execFileSync as execFileSync11 } from "child_process";
4852
+ import * as fs21 from "fs";
4853
+ import * as path20 from "path";
4695
4854
 
4696
4855
  // src/scripts/postReviewResult.ts
4697
4856
  init_issue();
@@ -4944,8 +5103,8 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
4944
5103
  return { number: Number(m[1]), created: true };
4945
5104
  }
4946
5105
  function writeStateFile(cwd, goalId, lastDispatchedIssue) {
4947
- const dir = path19.join(cwd, ".kody", "goals", goalId);
4948
- fs20.mkdirSync(dir, { recursive: true });
5106
+ const dir = path20.join(cwd, ".kody", "goals", goalId);
5107
+ fs21.mkdirSync(dir, { recursive: true });
4949
5108
  const state = {
4950
5109
  version: 1,
4951
5110
  state: "active",
@@ -4953,15 +5112,15 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
4953
5112
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4954
5113
  ...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
4955
5114
  };
4956
- const filePath = path19.join(dir, "state.json");
4957
- fs20.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
5115
+ const filePath = path20.join(dir, "state.json");
5116
+ fs21.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
4958
5117
  `);
4959
5118
  return filePath;
4960
5119
  }
4961
5120
  function gitTry(args, cwd) {
4962
5121
  const env = { ...process.env, SKIP_HOOKS: "1", HUSKY: "0" };
4963
5122
  try {
4964
- execFileSync10("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], env });
5123
+ execFileSync11("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], env });
4965
5124
  return { ok: true, stderr: "" };
4966
5125
  } catch (err) {
4967
5126
  const e = err;
@@ -5462,16 +5621,16 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
5462
5621
  }
5463
5622
 
5464
5623
  // src/scripts/diagMcp.ts
5465
- import { execFileSync as execFileSync11 } from "child_process";
5466
- import * as fs21 from "fs";
5624
+ import { execFileSync as execFileSync12 } from "child_process";
5625
+ import * as fs22 from "fs";
5467
5626
  import * as os4 from "os";
5468
- import * as path20 from "path";
5627
+ import * as path21 from "path";
5469
5628
  var diagMcp = async (_ctx) => {
5470
5629
  const home = os4.homedir();
5471
- const cacheDir = path20.join(home, ".cache", "ms-playwright");
5630
+ const cacheDir = path21.join(home, ".cache", "ms-playwright");
5472
5631
  let entries = [];
5473
5632
  try {
5474
- entries = fs21.readdirSync(cacheDir);
5633
+ entries = fs22.readdirSync(cacheDir);
5475
5634
  } catch {
5476
5635
  }
5477
5636
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -5482,7 +5641,7 @@ var diagMcp = async (_ctx) => {
5482
5641
  process.stderr.write(`[kody diag] chromium present: ${hasChromium ? "yes" : "no"}
5483
5642
  `);
5484
5643
  try {
5485
- const v = execFileSync11("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
5644
+ const v = execFileSync12("npx", ["-y", "--package=@playwright/mcp@latest", "--", "playwright-mcp", "--version"], {
5486
5645
  stdio: "pipe",
5487
5646
  timeout: 6e4,
5488
5647
  encoding: "utf8"
@@ -5497,17 +5656,17 @@ var diagMcp = async (_ctx) => {
5497
5656
  };
5498
5657
 
5499
5658
  // src/scripts/discoverQaContext.ts
5500
- import * as fs23 from "fs";
5501
- import * as path22 from "path";
5659
+ import * as fs24 from "fs";
5660
+ import * as path23 from "path";
5502
5661
 
5503
5662
  // src/scripts/frameworkDetectors.ts
5504
- import * as fs22 from "fs";
5505
- import * as path21 from "path";
5663
+ import * as fs23 from "fs";
5664
+ import * as path22 from "path";
5506
5665
  function detectFrameworks(cwd) {
5507
5666
  const out = [];
5508
5667
  let deps = {};
5509
5668
  try {
5510
- const pkg = JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
5669
+ const pkg = JSON.parse(fs23.readFileSync(path22.join(cwd, "package.json"), "utf-8"));
5511
5670
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
5512
5671
  } catch {
5513
5672
  return out;
@@ -5544,7 +5703,7 @@ function detectFrameworks(cwd) {
5544
5703
  }
5545
5704
  function findFile(cwd, candidates) {
5546
5705
  for (const c of candidates) {
5547
- if (fs22.existsSync(path21.join(cwd, c))) return c;
5706
+ if (fs23.existsSync(path22.join(cwd, c))) return c;
5548
5707
  }
5549
5708
  return null;
5550
5709
  }
@@ -5557,18 +5716,18 @@ var COLLECTION_DIRS = [
5557
5716
  function discoverPayloadCollections(cwd) {
5558
5717
  const out = [];
5559
5718
  for (const dir of COLLECTION_DIRS) {
5560
- const full = path21.join(cwd, dir);
5561
- if (!fs22.existsSync(full)) continue;
5719
+ const full = path22.join(cwd, dir);
5720
+ if (!fs23.existsSync(full)) continue;
5562
5721
  let files;
5563
5722
  try {
5564
- files = fs22.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5723
+ files = fs23.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5565
5724
  } catch {
5566
5725
  continue;
5567
5726
  }
5568
5727
  for (const file of files) {
5569
5728
  try {
5570
- const filePath = path21.join(full, file);
5571
- const content = fs22.readFileSync(filePath, "utf-8").slice(0, 1e4);
5729
+ const filePath = path22.join(full, file);
5730
+ const content = fs23.readFileSync(filePath, "utf-8").slice(0, 1e4);
5572
5731
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
5573
5732
  if (!slugMatch) continue;
5574
5733
  const slug = slugMatch[1];
@@ -5582,7 +5741,7 @@ function discoverPayloadCollections(cwd) {
5582
5741
  out.push({
5583
5742
  name,
5584
5743
  slug,
5585
- filePath: path21.relative(cwd, filePath),
5744
+ filePath: path22.relative(cwd, filePath),
5586
5745
  fields: fields.slice(0, 20),
5587
5746
  hasAdmin
5588
5747
  });
@@ -5596,28 +5755,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
5596
5755
  function discoverAdminComponents(cwd, collections) {
5597
5756
  const out = [];
5598
5757
  for (const dir of ADMIN_COMPONENT_DIRS) {
5599
- const full = path21.join(cwd, dir);
5600
- if (!fs22.existsSync(full)) continue;
5758
+ const full = path22.join(cwd, dir);
5759
+ if (!fs23.existsSync(full)) continue;
5601
5760
  let entries;
5602
5761
  try {
5603
- entries = fs22.readdirSync(full, { withFileTypes: true });
5762
+ entries = fs23.readdirSync(full, { withFileTypes: true });
5604
5763
  } catch {
5605
5764
  continue;
5606
5765
  }
5607
5766
  for (const entry of entries) {
5608
- const entryPath = path21.join(full, entry.name);
5767
+ const entryPath = path22.join(full, entry.name);
5609
5768
  let name;
5610
5769
  let filePath;
5611
5770
  if (entry.isDirectory()) {
5612
5771
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
5613
- (f) => fs22.existsSync(path21.join(entryPath, f))
5772
+ (f) => fs23.existsSync(path22.join(entryPath, f))
5614
5773
  );
5615
5774
  if (!indexFile) continue;
5616
5775
  name = entry.name;
5617
- filePath = path21.relative(cwd, path21.join(entryPath, indexFile));
5776
+ filePath = path22.relative(cwd, path22.join(entryPath, indexFile));
5618
5777
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
5619
5778
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
5620
- filePath = path21.relative(cwd, entryPath);
5779
+ filePath = path22.relative(cwd, entryPath);
5621
5780
  } else {
5622
5781
  continue;
5623
5782
  }
@@ -5625,7 +5784,7 @@ function discoverAdminComponents(cwd, collections) {
5625
5784
  if (collections) {
5626
5785
  for (const col of collections) {
5627
5786
  try {
5628
- const colContent = fs22.readFileSync(path21.join(cwd, col.filePath), "utf-8");
5787
+ const colContent = fs23.readFileSync(path22.join(cwd, col.filePath), "utf-8");
5629
5788
  if (colContent.includes(name)) {
5630
5789
  usedInCollection = col.slug;
5631
5790
  break;
@@ -5644,8 +5803,8 @@ function scanApiRoutes(cwd) {
5644
5803
  const out = [];
5645
5804
  const appDirs = ["src/app", "app"];
5646
5805
  for (const appDir of appDirs) {
5647
- const apiDir = path21.join(cwd, appDir, "api");
5648
- if (!fs22.existsSync(apiDir)) continue;
5806
+ const apiDir = path22.join(cwd, appDir, "api");
5807
+ if (!fs23.existsSync(apiDir)) continue;
5649
5808
  walkApiRoutes(apiDir, "/api", cwd, out);
5650
5809
  break;
5651
5810
  }
@@ -5654,14 +5813,14 @@ function scanApiRoutes(cwd) {
5654
5813
  function walkApiRoutes(dir, prefix, cwd, out) {
5655
5814
  let entries;
5656
5815
  try {
5657
- entries = fs22.readdirSync(dir, { withFileTypes: true });
5816
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
5658
5817
  } catch {
5659
5818
  return;
5660
5819
  }
5661
5820
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
5662
5821
  if (routeFile) {
5663
5822
  try {
5664
- const content = fs22.readFileSync(path21.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
5823
+ const content = fs23.readFileSync(path22.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
5665
5824
  const methods = HTTP_METHODS.filter(
5666
5825
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
5667
5826
  );
@@ -5669,7 +5828,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5669
5828
  out.push({
5670
5829
  path: prefix,
5671
5830
  methods,
5672
- filePath: path21.relative(cwd, path21.join(dir, routeFile.name))
5831
+ filePath: path22.relative(cwd, path22.join(dir, routeFile.name))
5673
5832
  });
5674
5833
  }
5675
5834
  } catch {
@@ -5680,7 +5839,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5680
5839
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5681
5840
  let segment = entry.name;
5682
5841
  if (segment.startsWith("(") && segment.endsWith(")")) {
5683
- walkApiRoutes(path21.join(dir, entry.name), prefix, cwd, out);
5842
+ walkApiRoutes(path22.join(dir, entry.name), prefix, cwd, out);
5684
5843
  continue;
5685
5844
  }
5686
5845
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -5688,7 +5847,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5688
5847
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
5689
5848
  segment = `:${segment.slice(1, -1)}`;
5690
5849
  }
5691
- walkApiRoutes(path21.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
5850
+ walkApiRoutes(path22.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
5692
5851
  }
5693
5852
  }
5694
5853
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -5708,10 +5867,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
5708
5867
  function scanEnvVars(cwd) {
5709
5868
  const candidates = [".env.example", ".env.local.example", ".env.template"];
5710
5869
  for (const envFile of candidates) {
5711
- const envPath = path21.join(cwd, envFile);
5712
- if (!fs22.existsSync(envPath)) continue;
5870
+ const envPath = path22.join(cwd, envFile);
5871
+ if (!fs23.existsSync(envPath)) continue;
5713
5872
  try {
5714
- const content = fs22.readFileSync(envPath, "utf-8");
5873
+ const content = fs23.readFileSync(envPath, "utf-8");
5715
5874
  const vars = [];
5716
5875
  for (const line of content.split("\n")) {
5717
5876
  const trimmed = line.trim();
@@ -5759,9 +5918,9 @@ function runQaDiscovery(cwd) {
5759
5918
  }
5760
5919
  function detectDevServer(cwd, out) {
5761
5920
  try {
5762
- const pkg = JSON.parse(fs23.readFileSync(path22.join(cwd, "package.json"), "utf-8"));
5921
+ const pkg = JSON.parse(fs24.readFileSync(path23.join(cwd, "package.json"), "utf-8"));
5763
5922
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
5764
- const pm = fs23.existsSync(path22.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs23.existsSync(path22.join(cwd, "yarn.lock")) ? "yarn" : fs23.existsSync(path22.join(cwd, "bun.lockb")) ? "bun" : "npm";
5923
+ const pm = fs24.existsSync(path23.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs24.existsSync(path23.join(cwd, "yarn.lock")) ? "yarn" : fs24.existsSync(path23.join(cwd, "bun.lockb")) ? "bun" : "npm";
5765
5924
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
5766
5925
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
5767
5926
  else if (allDeps.vite) out.devPort = 5173;
@@ -5771,8 +5930,8 @@ function detectDevServer(cwd, out) {
5771
5930
  function scanFrontendRoutes(cwd, out) {
5772
5931
  const appDirs = ["src/app", "app"];
5773
5932
  for (const appDir of appDirs) {
5774
- const full = path22.join(cwd, appDir);
5775
- if (!fs23.existsSync(full)) continue;
5933
+ const full = path23.join(cwd, appDir);
5934
+ if (!fs24.existsSync(full)) continue;
5776
5935
  walkFrontendRoutes(full, "", out);
5777
5936
  break;
5778
5937
  }
@@ -5780,7 +5939,7 @@ function scanFrontendRoutes(cwd, out) {
5780
5939
  function walkFrontendRoutes(dir, prefix, out) {
5781
5940
  let entries;
5782
5941
  try {
5783
- entries = fs23.readdirSync(dir, { withFileTypes: true });
5942
+ entries = fs24.readdirSync(dir, { withFileTypes: true });
5784
5943
  } catch {
5785
5944
  return;
5786
5945
  }
@@ -5797,7 +5956,7 @@ function walkFrontendRoutes(dir, prefix, out) {
5797
5956
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5798
5957
  let segment = entry.name;
5799
5958
  if (segment.startsWith("(") && segment.endsWith(")")) {
5800
- walkFrontendRoutes(path22.join(dir, entry.name), prefix, out);
5959
+ walkFrontendRoutes(path23.join(dir, entry.name), prefix, out);
5801
5960
  continue;
5802
5961
  }
5803
5962
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -5805,7 +5964,7 @@ function walkFrontendRoutes(dir, prefix, out) {
5805
5964
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
5806
5965
  segment = `:${segment.slice(1, -1)}`;
5807
5966
  }
5808
- walkFrontendRoutes(path22.join(dir, entry.name), `${prefix}/${segment}`, out);
5967
+ walkFrontendRoutes(path23.join(dir, entry.name), `${prefix}/${segment}`, out);
5809
5968
  }
5810
5969
  }
5811
5970
  function detectAuthFiles(cwd, out) {
@@ -5822,23 +5981,23 @@ function detectAuthFiles(cwd, out) {
5822
5981
  "src/app/api/oauth"
5823
5982
  ];
5824
5983
  for (const c of candidates) {
5825
- if (fs23.existsSync(path22.join(cwd, c))) out.authFiles.push(c);
5984
+ if (fs24.existsSync(path23.join(cwd, c))) out.authFiles.push(c);
5826
5985
  }
5827
5986
  }
5828
5987
  function detectRoles(cwd, out) {
5829
5988
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
5830
5989
  for (const rp of rolePaths) {
5831
- const dir = path22.join(cwd, rp);
5832
- if (!fs23.existsSync(dir)) continue;
5990
+ const dir = path23.join(cwd, rp);
5991
+ if (!fs24.existsSync(dir)) continue;
5833
5992
  let files;
5834
5993
  try {
5835
- files = fs23.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5994
+ files = fs24.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5836
5995
  } catch {
5837
5996
  continue;
5838
5997
  }
5839
5998
  for (const f of files) {
5840
5999
  try {
5841
- const content = fs23.readFileSync(path22.join(dir, f), "utf-8").slice(0, 5e3);
6000
+ const content = fs24.readFileSync(path23.join(dir, f), "utf-8").slice(0, 5e3);
5842
6001
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
5843
6002
  if (roleMatches) {
5844
6003
  for (const m of roleMatches) {
@@ -5984,7 +6143,7 @@ var discoverQaContext = async (ctx) => {
5984
6143
  };
5985
6144
 
5986
6145
  // src/scripts/dispatch.ts
5987
- import { execFileSync as execFileSync12 } from "child_process";
6146
+ import { execFileSync as execFileSync13 } from "child_process";
5988
6147
  var API_TIMEOUT_MS4 = 3e4;
5989
6148
  var dispatch = async (ctx, _profile, _agentResult, args) => {
5990
6149
  const next = args?.next;
@@ -6020,7 +6179,7 @@ var dispatch = async (ctx, _profile, _agentResult, args) => {
6020
6179
  const sub = usePr ? "pr" : "issue";
6021
6180
  const body = `@kody ${next}`;
6022
6181
  try {
6023
- execFileSync12("gh", [sub, "comment", String(targetNumber), "--body", body], {
6182
+ execFileSync13("gh", [sub, "comment", String(targetNumber), "--body", body], {
6024
6183
  timeout: API_TIMEOUT_MS4,
6025
6184
  cwd: ctx.cwd,
6026
6185
  stdio: ["ignore", "pipe", "pipe"]
@@ -6040,7 +6199,7 @@ function parsePr(url) {
6040
6199
  }
6041
6200
 
6042
6201
  // src/scripts/dispatchClassified.ts
6043
- import { execFileSync as execFileSync13 } from "child_process";
6202
+ import { execFileSync as execFileSync14 } from "child_process";
6044
6203
  var API_TIMEOUT_MS5 = 3e4;
6045
6204
  var VALID_CLASSES2 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
6046
6205
  var dispatchClassified = async (ctx) => {
@@ -6048,17 +6207,30 @@ var dispatchClassified = async (ctx) => {
6048
6207
  if (!issueNumber) return;
6049
6208
  const classification = ctx.data.classification;
6050
6209
  if (!classification || !VALID_CLASSES2.has(classification)) return;
6210
+ const action = ctx.data.action;
6211
+ if (!action) return;
6051
6212
  const baseArg = typeof ctx.args.base === "string" && ctx.args.base.length > 0 ? ` --base ${ctx.args.base}` : "";
6052
- const body = `@kody ${classification}${baseArg}`;
6213
+ const dispatchLine = `@kody ${classification}${baseArg}`;
6214
+ const auditLine = ctx.data.classificationAudit ?? `\u{1F50E} kody classified as \`${classification}\``;
6215
+ const state = ctx.data.taskState ?? emptyState();
6216
+ const nextState = reduce(state, "classify", action, void 0);
6217
+ const stateBody = renderStateComment(nextState);
6218
+ ctx.data.taskState = nextState;
6219
+ ctx.data.taskStateRendered = stateBody;
6220
+ const body = `${dispatchLine}
6221
+
6222
+ ${auditLine}
6223
+
6224
+ ${stateBody}`;
6053
6225
  try {
6054
- execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", body], {
6226
+ execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
6055
6227
  cwd: ctx.cwd,
6056
6228
  timeout: API_TIMEOUT_MS5,
6057
6229
  stdio: ["ignore", "pipe", "pipe"]
6058
6230
  });
6059
6231
  } catch (err) {
6060
6232
  process.stderr.write(
6061
- `[kody dispatchClassified] failed to dispatch ${body}: ${err instanceof Error ? err.message : String(err)}
6233
+ `[kody dispatchClassified] failed to dispatch ${dispatchLine}: ${err instanceof Error ? err.message : String(err)}
6062
6234
  `
6063
6235
  );
6064
6236
  ctx.data.action = failedAction3("dispatch post failed");
@@ -6071,8 +6243,8 @@ function failedAction3(reason) {
6071
6243
  }
6072
6244
 
6073
6245
  // src/scripts/dispatchJobFileTicks.ts
6074
- import * as fs25 from "fs";
6075
- import * as path24 from "path";
6246
+ import * as fs26 from "fs";
6247
+ import * as path25 from "path";
6076
6248
 
6077
6249
  // src/scripts/jobFrontmatter.ts
6078
6250
  var SCHEDULE_EVERY_VALUES = [
@@ -6333,8 +6505,8 @@ var ContentsApiBackend = class {
6333
6505
  };
6334
6506
 
6335
6507
  // src/scripts/jobState/localFileBackend.ts
6336
- import * as fs24 from "fs";
6337
- import * as path23 from "path";
6508
+ import * as fs25 from "fs";
6509
+ import * as path24 from "path";
6338
6510
  var LocalFileBackend = class {
6339
6511
  name = "local-file";
6340
6512
  cwd;
@@ -6349,7 +6521,7 @@ var LocalFileBackend = class {
6349
6521
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
6350
6522
  this.cwd = opts.cwd;
6351
6523
  this.jobsDir = opts.jobsDir;
6352
- this.absDir = path23.join(opts.cwd, opts.jobsDir);
6524
+ this.absDir = path24.join(opts.cwd, opts.jobsDir);
6353
6525
  this.owner = opts.owner;
6354
6526
  this.repo = opts.repo;
6355
6527
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -6364,7 +6536,7 @@ var LocalFileBackend = class {
6364
6536
  `);
6365
6537
  return;
6366
6538
  }
6367
- fs24.mkdirSync(this.absDir, { recursive: true });
6539
+ fs25.mkdirSync(this.absDir, { recursive: true });
6368
6540
  const prefix = this.cacheKeyPrefix();
6369
6541
  const probeKey = `${prefix}probe-${Date.now()}`;
6370
6542
  try {
@@ -6393,7 +6565,7 @@ var LocalFileBackend = class {
6393
6565
  `);
6394
6566
  return;
6395
6567
  }
6396
- if (!fs24.existsSync(this.absDir)) {
6568
+ if (!fs25.existsSync(this.absDir)) {
6397
6569
  return;
6398
6570
  }
6399
6571
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -6409,11 +6581,11 @@ var LocalFileBackend = class {
6409
6581
  }
6410
6582
  load(slug) {
6411
6583
  const relPath = stateFilePath(this.jobsDir, slug);
6412
- const absPath = path23.join(this.cwd, relPath);
6413
- if (!fs24.existsSync(absPath)) {
6584
+ const absPath = path24.join(this.cwd, relPath);
6585
+ if (!fs25.existsSync(absPath)) {
6414
6586
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
6415
6587
  }
6416
- const raw = fs24.readFileSync(absPath, "utf-8");
6588
+ const raw = fs25.readFileSync(absPath, "utf-8");
6417
6589
  let parsed;
6418
6590
  try {
6419
6591
  parsed = JSON.parse(raw);
@@ -6430,10 +6602,10 @@ var LocalFileBackend = class {
6430
6602
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
6431
6603
  return false;
6432
6604
  }
6433
- const absPath = path23.join(this.cwd, loaded.path);
6434
- fs24.mkdirSync(path23.dirname(absPath), { recursive: true });
6605
+ const absPath = path24.join(this.cwd, loaded.path);
6606
+ fs25.mkdirSync(path24.dirname(absPath), { recursive: true });
6435
6607
  const body = JSON.stringify(next, null, 2) + "\n";
6436
- fs24.writeFileSync(absPath, body, "utf-8");
6608
+ fs25.writeFileSync(absPath, body, "utf-8");
6437
6609
  return true;
6438
6610
  }
6439
6611
  cacheKeyPrefix() {
@@ -6511,7 +6683,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
6511
6683
  await backend.hydrate();
6512
6684
  }
6513
6685
  try {
6514
- const slugs = listJobSlugs(path24.join(ctx.cwd, jobsDir));
6686
+ const slugs = listJobSlugs(path25.join(ctx.cwd, jobsDir));
6515
6687
  ctx.data.jobSlugCount = slugs.length;
6516
6688
  if (slugs.length === 0) {
6517
6689
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -6624,17 +6796,17 @@ function formatAgo(ms) {
6624
6796
  }
6625
6797
  function readJobFrontmatter(cwd, jobsDir, slug) {
6626
6798
  try {
6627
- const raw = fs25.readFileSync(path24.join(cwd, jobsDir, `${slug}.md`), "utf-8");
6799
+ const raw = fs26.readFileSync(path25.join(cwd, jobsDir, `${slug}.md`), "utf-8");
6628
6800
  return splitFrontmatter(raw).frontmatter;
6629
6801
  } catch {
6630
6802
  return {};
6631
6803
  }
6632
6804
  }
6633
6805
  function listJobSlugs(absDir) {
6634
- if (!fs25.existsSync(absDir)) return [];
6806
+ if (!fs26.existsSync(absDir)) return [];
6635
6807
  let entries;
6636
6808
  try {
6637
- entries = fs25.readdirSync(absDir, { withFileTypes: true });
6809
+ entries = fs26.readdirSync(absDir, { withFileTypes: true });
6638
6810
  } catch {
6639
6811
  return [];
6640
6812
  }
@@ -7148,7 +7320,7 @@ var finalizeTerminal = async (ctx) => {
7148
7320
 
7149
7321
  // src/scripts/finishFlow.ts
7150
7322
  init_issue();
7151
- import { execFileSync as execFileSync14 } from "child_process";
7323
+ import { execFileSync as execFileSync15 } from "child_process";
7152
7324
  var TERMINAL_PHASE = {
7153
7325
  "review-passed": { phase: "shipped", status: "succeeded" },
7154
7326
  "fix-applied": { phase: "shipped", status: "succeeded" },
@@ -7188,7 +7360,7 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
7188
7360
  **PR:** ${state.core.prUrl}` : "";
7189
7361
  const body = `${icon} kody flow \`${flowName}\` finished \u2014 \`${reason}\`${prSuffix}`;
7190
7362
  try {
7191
- execFileSync14("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7363
+ execFileSync15("gh", ["issue", "comment", String(issueNumber), "--body", body], {
7192
7364
  timeout: API_TIMEOUT_MS6,
7193
7365
  cwd: ctx.cwd,
7194
7366
  stdio: ["ignore", "pipe", "pipe"]
@@ -7218,9 +7390,9 @@ var finishFlow = async (ctx, profile, _agentResult, args) => {
7218
7390
  };
7219
7391
 
7220
7392
  // src/branch.ts
7221
- import { execFileSync as execFileSync15 } from "child_process";
7393
+ import { execFileSync as execFileSync16 } from "child_process";
7222
7394
  function git2(args, cwd) {
7223
- return execFileSync15("git", args, {
7395
+ return execFileSync16("git", args, {
7224
7396
  encoding: "utf-8",
7225
7397
  timeout: 3e4,
7226
7398
  cwd,
@@ -7237,11 +7409,11 @@ function getCurrentBranch(cwd) {
7237
7409
  }
7238
7410
  function resetWorkingTree(cwd) {
7239
7411
  try {
7240
- execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7412
+ execFileSync16("git", ["reset", "--hard", "HEAD"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7241
7413
  } catch {
7242
7414
  }
7243
7415
  try {
7244
- execFileSync15("git", ["clean", "-fd"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7416
+ execFileSync16("git", ["clean", "-fd"], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7245
7417
  } catch {
7246
7418
  }
7247
7419
  }
@@ -7253,14 +7425,14 @@ function checkoutPrBranch(prNumber, cwd) {
7253
7425
  GH_TOKEN: process.env.GH_PAT?.trim() || process.env.GH_TOKEN || ""
7254
7426
  };
7255
7427
  try {
7256
- execFileSync15("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7428
+ execFileSync16("git", ["reset", "--hard", "HEAD"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7257
7429
  } catch {
7258
7430
  }
7259
7431
  try {
7260
- execFileSync15("git", ["clean", "-fd"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7432
+ execFileSync16("git", ["clean", "-fd"], { cwd, env, stdio: ["ignore", "pipe", "pipe"], timeout: 3e4 });
7261
7433
  } catch {
7262
7434
  }
7263
- execFileSync15("gh", ["pr", "checkout", String(prNumber)], {
7435
+ execFileSync16("gh", ["pr", "checkout", String(prNumber)], {
7264
7436
  cwd,
7265
7437
  env,
7266
7438
  stdio: ["ignore", "pipe", "pipe"],
@@ -7386,8 +7558,8 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
7386
7558
  }
7387
7559
 
7388
7560
  // src/gha.ts
7389
- import { execFileSync as execFileSync16 } from "child_process";
7390
- import * as fs26 from "fs";
7561
+ import { execFileSync as execFileSync17 } from "child_process";
7562
+ import * as fs27 from "fs";
7391
7563
  function getRunUrl() {
7392
7564
  const server = process.env.GITHUB_SERVER_URL;
7393
7565
  const repo = process.env.GITHUB_REPOSITORY;
@@ -7398,10 +7570,10 @@ function getRunUrl() {
7398
7570
  function reactToTriggerComment(cwd) {
7399
7571
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
7400
7572
  const eventPath = process.env.GITHUB_EVENT_PATH;
7401
- if (!eventPath || !fs26.existsSync(eventPath)) return;
7573
+ if (!eventPath || !fs27.existsSync(eventPath)) return;
7402
7574
  let event = null;
7403
7575
  try {
7404
- event = JSON.parse(fs26.readFileSync(eventPath, "utf-8"));
7576
+ event = JSON.parse(fs27.readFileSync(eventPath, "utf-8"));
7405
7577
  } catch {
7406
7578
  return;
7407
7579
  }
@@ -7429,7 +7601,7 @@ function reactToTriggerComment(cwd) {
7429
7601
  for (let attempt = 0; attempt < 3; attempt++) {
7430
7602
  if (attempt > 0) sleepMs(attempt === 1 ? 500 : 1500);
7431
7603
  try {
7432
- execFileSync16("gh", args, opts);
7604
+ execFileSync17("gh", args, opts);
7433
7605
  return;
7434
7606
  } catch (err) {
7435
7607
  lastErr = err;
@@ -7442,7 +7614,7 @@ function reactToTriggerComment(cwd) {
7442
7614
  }
7443
7615
  function sleepMs(ms) {
7444
7616
  try {
7445
- execFileSync16("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
7617
+ execFileSync17("sleep", [(ms / 1e3).toString()], { stdio: "ignore", timeout: ms + 1e3 });
7446
7618
  } catch {
7447
7619
  }
7448
7620
  }
@@ -7451,7 +7623,7 @@ function sleepMs(ms) {
7451
7623
  init_issue();
7452
7624
 
7453
7625
  // src/workflow.ts
7454
- import { execFileSync as execFileSync17 } from "child_process";
7626
+ import { execFileSync as execFileSync18 } from "child_process";
7455
7627
  var GH_TIMEOUT_MS = 3e4;
7456
7628
  function ghToken3() {
7457
7629
  return process.env.GH_PAT?.trim() || process.env.GH_TOKEN;
@@ -7459,7 +7631,7 @@ function ghToken3() {
7459
7631
  function gh3(args, cwd) {
7460
7632
  const token = ghToken3();
7461
7633
  const env = token ? { ...process.env, GH_TOKEN: token } : { ...process.env };
7462
- return execFileSync17("gh", args, {
7634
+ return execFileSync18("gh", args, {
7463
7635
  encoding: "utf-8",
7464
7636
  timeout: GH_TIMEOUT_MS,
7465
7637
  cwd,
@@ -7693,23 +7865,23 @@ var handleAbandonedGoal = async (ctx) => {
7693
7865
  };
7694
7866
 
7695
7867
  // src/scripts/initFlow.ts
7696
- import { execFileSync as execFileSync18 } from "child_process";
7697
- import * as fs28 from "fs";
7698
- import * as path26 from "path";
7868
+ import { execFileSync as execFileSync19 } from "child_process";
7869
+ import * as fs29 from "fs";
7870
+ import * as path27 from "path";
7699
7871
 
7700
7872
  // src/scripts/loadQaGuide.ts
7701
- import * as fs27 from "fs";
7702
- import * as path25 from "path";
7873
+ import * as fs28 from "fs";
7874
+ import * as path26 from "path";
7703
7875
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
7704
7876
  var loadQaGuide = async (ctx) => {
7705
- const full = path25.join(ctx.cwd, QA_GUIDE_REL_PATH);
7706
- if (!fs27.existsSync(full)) {
7877
+ const full = path26.join(ctx.cwd, QA_GUIDE_REL_PATH);
7878
+ if (!fs28.existsSync(full)) {
7707
7879
  ctx.data.qaGuide = "";
7708
7880
  ctx.data.qaGuidePath = "";
7709
7881
  return;
7710
7882
  }
7711
7883
  try {
7712
- ctx.data.qaGuide = fs27.readFileSync(full, "utf-8");
7884
+ ctx.data.qaGuide = fs28.readFileSync(full, "utf-8");
7713
7885
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
7714
7886
  } catch {
7715
7887
  ctx.data.qaGuide = "";
@@ -7719,9 +7891,9 @@ var loadQaGuide = async (ctx) => {
7719
7891
 
7720
7892
  // src/scripts/initFlow.ts
7721
7893
  function detectPackageManager(cwd) {
7722
- if (fs28.existsSync(path26.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7723
- if (fs28.existsSync(path26.join(cwd, "yarn.lock"))) return "yarn";
7724
- if (fs28.existsSync(path26.join(cwd, "bun.lockb"))) return "bun";
7894
+ if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7895
+ if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
7896
+ if (fs29.existsSync(path27.join(cwd, "bun.lockb"))) return "bun";
7725
7897
  return "npm";
7726
7898
  }
7727
7899
  function qualityCommandsFor(pm) {
@@ -7734,7 +7906,7 @@ function qualityCommandsFor(pm) {
7734
7906
  function detectOwnerRepo(cwd) {
7735
7907
  let url;
7736
7908
  try {
7737
- url = execFileSync18("git", ["remote", "get-url", "origin"], {
7909
+ url = execFileSync19("git", ["remote", "get-url", "origin"], {
7738
7910
  cwd,
7739
7911
  encoding: "utf-8",
7740
7912
  stdio: ["ignore", "pipe", "pipe"]
@@ -7819,7 +7991,7 @@ jobs:
7819
7991
  `;
7820
7992
  function defaultBranchFromGit(cwd) {
7821
7993
  try {
7822
- const ref = execFileSync18("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
7994
+ const ref = execFileSync19("git", ["symbolic-ref", "refs/remotes/origin/HEAD"], {
7823
7995
  cwd,
7824
7996
  encoding: "utf-8",
7825
7997
  stdio: ["ignore", "pipe", "pipe"]
@@ -7827,7 +7999,7 @@ function defaultBranchFromGit(cwd) {
7827
7999
  return ref.replace("refs/remotes/origin/", "");
7828
8000
  } catch {
7829
8001
  try {
7830
- return execFileSync18("git", ["branch", "--show-current"], {
8002
+ return execFileSync19("git", ["branch", "--show-current"], {
7831
8003
  cwd,
7832
8004
  encoding: "utf-8",
7833
8005
  stdio: ["ignore", "pipe", "pipe"]
@@ -7843,48 +8015,48 @@ function performInit(cwd, force) {
7843
8015
  const pm = detectPackageManager(cwd);
7844
8016
  const ownerRepo = detectOwnerRepo(cwd);
7845
8017
  const defaultBranch2 = defaultBranchFromGit(cwd);
7846
- const configPath = path26.join(cwd, "kody.config.json");
7847
- if (fs28.existsSync(configPath) && !force) {
8018
+ const configPath = path27.join(cwd, "kody.config.json");
8019
+ if (fs29.existsSync(configPath) && !force) {
7848
8020
  skipped.push("kody.config.json");
7849
8021
  } else {
7850
8022
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
7851
- fs28.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
8023
+ fs29.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7852
8024
  `);
7853
8025
  wrote.push("kody.config.json");
7854
8026
  }
7855
- const workflowDir = path26.join(cwd, ".github", "workflows");
7856
- const workflowPath = path26.join(workflowDir, "kody.yml");
7857
- if (fs28.existsSync(workflowPath) && !force) {
8027
+ const workflowDir = path27.join(cwd, ".github", "workflows");
8028
+ const workflowPath = path27.join(workflowDir, "kody.yml");
8029
+ if (fs29.existsSync(workflowPath) && !force) {
7858
8030
  skipped.push(".github/workflows/kody.yml");
7859
8031
  } else {
7860
- fs28.mkdirSync(workflowDir, { recursive: true });
7861
- fs28.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
8032
+ fs29.mkdirSync(workflowDir, { recursive: true });
8033
+ fs29.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
7862
8034
  wrote.push(".github/workflows/kody.yml");
7863
8035
  }
7864
- const hasUi = fs28.existsSync(path26.join(cwd, "src/app")) || fs28.existsSync(path26.join(cwd, "app")) || fs28.existsSync(path26.join(cwd, "pages"));
8036
+ const hasUi = fs29.existsSync(path27.join(cwd, "src/app")) || fs29.existsSync(path27.join(cwd, "app")) || fs29.existsSync(path27.join(cwd, "pages"));
7865
8037
  if (hasUi) {
7866
- const qaGuidePath = path26.join(cwd, QA_GUIDE_REL_PATH);
7867
- if (fs28.existsSync(qaGuidePath) && !force) {
8038
+ const qaGuidePath = path27.join(cwd, QA_GUIDE_REL_PATH);
8039
+ if (fs29.existsSync(qaGuidePath) && !force) {
7868
8040
  skipped.push(QA_GUIDE_REL_PATH);
7869
8041
  } else {
7870
- fs28.mkdirSync(path26.dirname(qaGuidePath), { recursive: true });
8042
+ fs29.mkdirSync(path27.dirname(qaGuidePath), { recursive: true });
7871
8043
  const discovery = runQaDiscovery(cwd);
7872
- fs28.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
8044
+ fs29.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
7873
8045
  wrote.push(QA_GUIDE_REL_PATH);
7874
8046
  }
7875
8047
  }
7876
8048
  const builtinJobs = listBuiltinJobs();
7877
8049
  if (builtinJobs.length > 0) {
7878
- const jobsDir = path26.join(cwd, ".kody", "jobs");
7879
- fs28.mkdirSync(jobsDir, { recursive: true });
8050
+ const jobsDir = path27.join(cwd, ".kody", "jobs");
8051
+ fs29.mkdirSync(jobsDir, { recursive: true });
7880
8052
  for (const job of builtinJobs) {
7881
- const rel = path26.join(".kody", "jobs", `${job.slug}.md`);
7882
- const target = path26.join(cwd, rel);
7883
- if (fs28.existsSync(target) && !force) {
8053
+ const rel = path27.join(".kody", "jobs", `${job.slug}.md`);
8054
+ const target = path27.join(cwd, rel);
8055
+ if (fs29.existsSync(target) && !force) {
7884
8056
  skipped.push(rel);
7885
8057
  continue;
7886
8058
  }
7887
- fs28.writeFileSync(target, fs28.readFileSync(job.filePath, "utf-8"));
8059
+ fs29.writeFileSync(target, fs29.readFileSync(job.filePath, "utf-8"));
7888
8060
  wrote.push(rel);
7889
8061
  }
7890
8062
  }
@@ -7896,12 +8068,12 @@ function performInit(cwd, force) {
7896
8068
  continue;
7897
8069
  }
7898
8070
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
7899
- const target = path26.join(workflowDir, `kody-${exe.name}.yml`);
7900
- if (fs28.existsSync(target) && !force) {
8071
+ const target = path27.join(workflowDir, `kody-${exe.name}.yml`);
8072
+ if (fs29.existsSync(target) && !force) {
7901
8073
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
7902
8074
  continue;
7903
8075
  }
7904
- fs28.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
8076
+ fs29.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
7905
8077
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
7906
8078
  }
7907
8079
  let labels;
@@ -7979,14 +8151,14 @@ init_loadConventions();
7979
8151
  init_loadCoverageRules();
7980
8152
 
7981
8153
  // src/goal/state.ts
7982
- import * as fs29 from "fs";
7983
- import * as path27 from "path";
8154
+ import * as fs30 from "fs";
8155
+ import * as path28 from "path";
7984
8156
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
7985
8157
  var GoalStateError = class extends Error {
7986
- constructor(path36, message) {
7987
- super(`Invalid goal state at ${path36}:
8158
+ constructor(path37, message) {
8159
+ super(`Invalid goal state at ${path37}:
7988
8160
  ${message}`);
7989
- this.path = path36;
8161
+ this.path = path37;
7990
8162
  this.name = "GoalStateError";
7991
8163
  }
7992
8164
  path;
@@ -8034,16 +8206,16 @@ function serializeGoalState(s) {
8034
8206
  `;
8035
8207
  }
8036
8208
  function goalStatePath(cwd, goalId) {
8037
- return path27.join(cwd, ".kody", "goals", goalId, "state.json");
8209
+ return path28.join(cwd, ".kody", "goals", goalId, "state.json");
8038
8210
  }
8039
8211
  function readGoalState(cwd, goalId) {
8040
8212
  const file = goalStatePath(cwd, goalId);
8041
- if (!fs29.existsSync(file)) {
8213
+ if (!fs30.existsSync(file)) {
8042
8214
  throw new GoalStateError(file, "file not found");
8043
8215
  }
8044
8216
  let raw;
8045
8217
  try {
8046
- raw = JSON.parse(fs29.readFileSync(file, "utf-8"));
8218
+ raw = JSON.parse(fs30.readFileSync(file, "utf-8"));
8047
8219
  } catch (err) {
8048
8220
  throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
8049
8221
  }
@@ -8051,8 +8223,8 @@ function readGoalState(cwd, goalId) {
8051
8223
  }
8052
8224
  function writeGoalState(cwd, goalId, state) {
8053
8225
  const file = goalStatePath(cwd, goalId);
8054
- fs29.mkdirSync(path27.dirname(file), { recursive: true });
8055
- fs29.writeFileSync(file, serializeGoalState(state), "utf-8");
8226
+ fs30.mkdirSync(path28.dirname(file), { recursive: true });
8227
+ fs30.writeFileSync(file, serializeGoalState(state), "utf-8");
8056
8228
  }
8057
8229
  function nowIso() {
8058
8230
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -8149,8 +8321,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
8149
8321
  };
8150
8322
 
8151
8323
  // src/scripts/loadJobFromFile.ts
8152
- import * as fs30 from "fs";
8153
- import * as path28 from "path";
8324
+ import * as fs31 from "fs";
8325
+ import * as path29 from "path";
8154
8326
  var loadJobFromFile = async (ctx, _profile, args) => {
8155
8327
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
8156
8328
  const workersDir = String(args?.workersDir ?? ".kody/workers");
@@ -8159,23 +8331,23 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8159
8331
  if (!slug) {
8160
8332
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
8161
8333
  }
8162
- const absPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
8163
- if (!fs30.existsSync(absPath)) {
8334
+ const absPath = path29.join(ctx.cwd, jobsDir, `${slug}.md`);
8335
+ if (!fs31.existsSync(absPath)) {
8164
8336
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
8165
8337
  }
8166
- const raw = fs30.readFileSync(absPath, "utf-8");
8338
+ const raw = fs31.readFileSync(absPath, "utf-8");
8167
8339
  const { title, body } = parseJobFile(raw, slug);
8168
8340
  const workerSlug = (splitFrontmatter(raw).frontmatter.worker ?? "").trim();
8169
8341
  let workerTitle = "";
8170
8342
  let workerPersona = "";
8171
8343
  if (workerSlug) {
8172
- const workerPath = path28.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8173
- if (!fs30.existsSync(workerPath)) {
8344
+ const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8345
+ if (!fs31.existsSync(workerPath)) {
8174
8346
  throw new Error(
8175
8347
  `loadJobFromFile: job '${slug}' declares worker '${workerSlug}' but ${workerPath} does not exist`
8176
8348
  );
8177
8349
  }
8178
- const workerRaw = fs30.readFileSync(workerPath, "utf-8");
8350
+ const workerRaw = fs31.readFileSync(workerPath, "utf-8");
8179
8351
  const parsed = parseJobFile(workerRaw, workerSlug);
8180
8352
  workerTitle = parsed.title;
8181
8353
  workerPersona = parsed.body;
@@ -8213,19 +8385,19 @@ function humanizeSlug(slug) {
8213
8385
  }
8214
8386
 
8215
8387
  // src/scripts/loadWorkerAdhoc.ts
8216
- import * as fs31 from "fs";
8217
- import * as path29 from "path";
8388
+ import * as fs32 from "fs";
8389
+ import * as path30 from "path";
8218
8390
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
8219
8391
  const workersDir = String(args?.workersDir ?? ".kody/workers");
8220
8392
  const workerSlug = String(ctx.args.worker ?? "").trim();
8221
8393
  if (!workerSlug) {
8222
8394
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
8223
8395
  }
8224
- const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8225
- if (!fs31.existsSync(workerPath)) {
8396
+ const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8397
+ if (!fs32.existsSync(workerPath)) {
8226
8398
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
8227
8399
  }
8228
- const { title, body } = parsePersona(fs31.readFileSync(workerPath, "utf-8"), workerSlug);
8400
+ const { title, body } = parsePersona(fs32.readFileSync(workerPath, "utf-8"), workerSlug);
8229
8401
  const message = resolveMessage(ctx.args.message);
8230
8402
  if (!message) {
8231
8403
  throw new Error(
@@ -8245,9 +8417,9 @@ function resolveMessage(messageArg) {
8245
8417
  }
8246
8418
  function readCommentBody() {
8247
8419
  const eventPath = process.env.GITHUB_EVENT_PATH;
8248
- if (!eventPath || !fs31.existsSync(eventPath)) return "";
8420
+ if (!eventPath || !fs32.existsSync(eventPath)) return "";
8249
8421
  try {
8250
- const event = JSON.parse(fs31.readFileSync(eventPath, "utf-8"));
8422
+ const event = JSON.parse(fs32.readFileSync(eventPath, "utf-8"));
8251
8423
  return String(event.comment?.body ?? "");
8252
8424
  } catch {
8253
8425
  return "";
@@ -8293,8 +8465,8 @@ init_loadPriorArt();
8293
8465
  init_events();
8294
8466
 
8295
8467
  // src/taskContext.ts
8296
- import * as fs33 from "fs";
8297
- import * as path31 from "path";
8468
+ import * as fs34 from "fs";
8469
+ import * as path32 from "path";
8298
8470
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
8299
8471
  function buildTaskContext(args) {
8300
8472
  return {
@@ -8310,10 +8482,10 @@ function buildTaskContext(args) {
8310
8482
  }
8311
8483
  function persistTaskContext(cwd, ctx) {
8312
8484
  try {
8313
- const dir = path31.join(cwd, ".kody", "runs", ctx.runId);
8314
- fs33.mkdirSync(dir, { recursive: true });
8315
- const file = path31.join(dir, "task-context.json");
8316
- fs33.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8485
+ const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
8486
+ fs34.mkdirSync(dir, { recursive: true });
8487
+ const file = path32.join(dir, "task-context.json");
8488
+ fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8317
8489
  `);
8318
8490
  return file;
8319
8491
  } catch (err) {
@@ -8369,7 +8541,7 @@ var markFlowSuccess = async (ctx) => {
8369
8541
  };
8370
8542
 
8371
8543
  // src/scripts/mergeReleasePr.ts
8372
- import { execFileSync as execFileSync19 } from "child_process";
8544
+ import { execFileSync as execFileSync20 } from "child_process";
8373
8545
  var API_TIMEOUT_MS7 = 6e4;
8374
8546
  var mergeReleasePr = async (ctx) => {
8375
8547
  const state = ctx.data.taskState;
@@ -8388,7 +8560,7 @@ var mergeReleasePr = async (ctx) => {
8388
8560
  process.stderr.write(`[kody mergeReleasePr] merging PR #${prNumber} (${prUrl})
8389
8561
  `);
8390
8562
  try {
8391
- const out = execFileSync19("gh", ["pr", "merge", String(prNumber), "--merge"], {
8563
+ const out = execFileSync20("gh", ["pr", "merge", String(prNumber), "--merge"], {
8392
8564
  timeout: API_TIMEOUT_MS7,
8393
8565
  cwd: ctx.cwd,
8394
8566
  stdio: ["ignore", "pipe", "pipe"]
@@ -9023,7 +9195,7 @@ ${body}`;
9023
9195
  }
9024
9196
 
9025
9197
  // src/scripts/recordClassification.ts
9026
- import { execFileSync as execFileSync20 } from "child_process";
9198
+ import { execFileSync as execFileSync21 } from "child_process";
9027
9199
  var API_TIMEOUT_MS8 = 3e4;
9028
9200
  var VALID_CLASSES3 = /* @__PURE__ */ new Set(["feature", "bug", "spec", "chore"]);
9029
9201
  var recordClassification = async (ctx) => {
@@ -9051,7 +9223,6 @@ var recordClassification = async (ctx) => {
9051
9223
  ctx.output.reason = "classify: no decision";
9052
9224
  return;
9053
9225
  }
9054
- tryAuditComment(issueNumber, `\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`, ctx.cwd);
9055
9226
  ctx.data.action = makeAction3(`CLASSIFIED_AS_${classification.toUpperCase()}`, {
9056
9227
  classification,
9057
9228
  reason: reason ?? "",
@@ -9059,6 +9230,7 @@ var recordClassification = async (ctx) => {
9059
9230
  });
9060
9231
  ctx.data.classification = classification;
9061
9232
  ctx.data.classificationReason = reason ?? "";
9233
+ ctx.data.classificationAudit = `\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`;
9062
9234
  };
9063
9235
  function parseClassification(prSummary) {
9064
9236
  if (!prSummary) return null;
@@ -9071,7 +9243,7 @@ function parseClassification(prSummary) {
9071
9243
  }
9072
9244
  function tryAuditComment(issueNumber, body, cwd) {
9073
9245
  try {
9074
- execFileSync20("gh", ["issue", "comment", String(issueNumber), "--body", body], {
9246
+ execFileSync21("gh", ["issue", "comment", String(issueNumber), "--body", body], {
9075
9247
  cwd,
9076
9248
  timeout: API_TIMEOUT_MS8,
9077
9249
  stdio: ["ignore", "pipe", "pipe"]
@@ -9185,7 +9357,7 @@ var resolveArtifacts = async (ctx, profile) => {
9185
9357
  };
9186
9358
 
9187
9359
  // src/scripts/resolveFlow.ts
9188
- import { execFileSync as execFileSync21 } from "child_process";
9360
+ import { execFileSync as execFileSync22 } from "child_process";
9189
9361
  init_issue();
9190
9362
  var CONFLICT_DIFF_MAX_BYTES = 4e4;
9191
9363
  var resolveFlow = async (ctx) => {
@@ -9279,7 +9451,7 @@ function buildPreferBlock(prefer, baseBranch) {
9279
9451
  }
9280
9452
  function getConflictedFiles(cwd) {
9281
9453
  try {
9282
- const out = execFileSync21("git", ["diff", "--name-only", "--diff-filter=U"], {
9454
+ const out = execFileSync22("git", ["diff", "--name-only", "--diff-filter=U"], {
9283
9455
  encoding: "utf-8",
9284
9456
  cwd,
9285
9457
  env: { ...process.env, HUSKY: "0" }
@@ -9294,7 +9466,7 @@ function getConflictMarkersPreview(files, cwd, maxBytes = CONFLICT_DIFF_MAX_BYTE
9294
9466
  let total = 0;
9295
9467
  for (const f of files) {
9296
9468
  try {
9297
- const content = execFileSync21("cat", [f], { encoding: "utf-8", cwd }).toString();
9469
+ const content = execFileSync22("cat", [f], { encoding: "utf-8", cwd }).toString();
9298
9470
  const snippet = `### ${f}
9299
9471
 
9300
9472
  \`\`\`
@@ -9318,12 +9490,12 @@ function tryPostPr3(prNumber, body, cwd) {
9318
9490
  function pushEmptyCommit(branch, cwd) {
9319
9491
  const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
9320
9492
  try {
9321
- execFileSync21(
9493
+ execFileSync22(
9322
9494
  "git",
9323
9495
  ["commit", "--allow-empty", "-m", "chore: kody resolve refresh \u2014 empty commit to recompute mergeable status"],
9324
9496
  { cwd, env, stdio: ["ignore", "pipe", "pipe"] }
9325
9497
  );
9326
- execFileSync21("git", ["push", "-u", "origin", branch], {
9498
+ execFileSync22("git", ["push", "-u", "origin", branch], {
9327
9499
  cwd,
9328
9500
  env,
9329
9501
  stdio: ["ignore", "pipe", "pipe"]
@@ -9414,10 +9586,10 @@ var resolvePreviewUrl = async (ctx) => {
9414
9586
  };
9415
9587
 
9416
9588
  // src/scripts/resolveQaUrl.ts
9417
- import { execFileSync as execFileSync22 } from "child_process";
9589
+ import { execFileSync as execFileSync23 } from "child_process";
9418
9590
  function ghQuery(args, cwd) {
9419
9591
  try {
9420
- const out = execFileSync22("gh", args, {
9592
+ const out = execFileSync23("gh", args, {
9421
9593
  cwd,
9422
9594
  stdio: ["ignore", "pipe", "pipe"],
9423
9595
  encoding: "utf-8",
@@ -9487,7 +9659,7 @@ var resolveQaUrl = async (ctx) => {
9487
9659
  };
9488
9660
 
9489
9661
  // src/scripts/revertFlow.ts
9490
- import { execFileSync as execFileSync23 } from "child_process";
9662
+ import { execFileSync as execFileSync24 } from "child_process";
9491
9663
  init_issue();
9492
9664
  var SHA_RE = /^[0-9a-f]{4,40}$/i;
9493
9665
  var revertFlow = async (ctx) => {
@@ -9570,7 +9742,7 @@ function buildPrSummary(resolved) {
9570
9742
  return resolved.map((r) => `- Reverted \`${r.full.slice(0, 7)}\`${r.subject ? ` \u2014 ${r.subject}` : ""}`).join("\n");
9571
9743
  }
9572
9744
  function git3(args, cwd) {
9573
- return execFileSync23("git", args, {
9745
+ return execFileSync24("git", args, {
9574
9746
  encoding: "utf-8",
9575
9747
  timeout: 3e4,
9576
9748
  cwd,
@@ -9580,7 +9752,7 @@ function git3(args, cwd) {
9580
9752
  }
9581
9753
  function isAncestorOfHead(sha, cwd) {
9582
9754
  try {
9583
- execFileSync23("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
9755
+ execFileSync24("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
9584
9756
  cwd,
9585
9757
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
9586
9758
  stdio: ["ignore", "ignore", "ignore"]
@@ -9676,8 +9848,8 @@ function resolveBaseOverride(value) {
9676
9848
 
9677
9849
  // src/scripts/runTickScript.ts
9678
9850
  import { spawnSync } from "child_process";
9679
- import * as fs34 from "fs";
9680
- import * as path32 from "path";
9851
+ import * as fs35 from "fs";
9852
+ import * as path33 from "path";
9681
9853
  var runTickScript = async (ctx, _profile, args) => {
9682
9854
  ctx.skipAgent = true;
9683
9855
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
@@ -9689,13 +9861,13 @@ var runTickScript = async (ctx, _profile, args) => {
9689
9861
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
9690
9862
  return;
9691
9863
  }
9692
- const jobPath = path32.join(ctx.cwd, jobsDir, `${slug}.md`);
9693
- if (!fs34.existsSync(jobPath)) {
9864
+ const jobPath = path33.join(ctx.cwd, jobsDir, `${slug}.md`);
9865
+ if (!fs35.existsSync(jobPath)) {
9694
9866
  ctx.output.exitCode = 99;
9695
9867
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
9696
9868
  return;
9697
9869
  }
9698
- const raw = fs34.readFileSync(jobPath, "utf-8");
9870
+ const raw = fs35.readFileSync(jobPath, "utf-8");
9699
9871
  const { frontmatter } = splitFrontmatter(raw);
9700
9872
  const tickScript = frontmatter.tickScript;
9701
9873
  if (!tickScript) {
@@ -9703,8 +9875,8 @@ var runTickScript = async (ctx, _profile, args) => {
9703
9875
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
9704
9876
  return;
9705
9877
  }
9706
- const scriptPath = path32.isAbsolute(tickScript) ? tickScript : path32.join(ctx.cwd, tickScript);
9707
- if (!fs34.existsSync(scriptPath)) {
9878
+ const scriptPath = path33.isAbsolute(tickScript) ? tickScript : path33.join(ctx.cwd, tickScript);
9879
+ if (!fs35.existsSync(scriptPath)) {
9708
9880
  ctx.output.exitCode = 99;
9709
9881
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
9710
9882
  return;
@@ -9999,11 +10171,11 @@ var skipAgent = async (ctx) => {
9999
10171
  };
10000
10172
 
10001
10173
  // src/scripts/stageMergeConflicts.ts
10002
- import { execFileSync as execFileSync24 } from "child_process";
10174
+ import { execFileSync as execFileSync25 } from "child_process";
10003
10175
  var stageMergeConflicts = async (ctx) => {
10004
10176
  if (ctx.data.agentDone === false) return;
10005
10177
  try {
10006
- execFileSync24("git", ["add", "-A"], {
10178
+ execFileSync25("git", ["add", "-A"], {
10007
10179
  cwd: ctx.cwd,
10008
10180
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" },
10009
10181
  stdio: "pipe"
@@ -10014,7 +10186,7 @@ var stageMergeConflicts = async (ctx) => {
10014
10186
 
10015
10187
  // src/scripts/startFlow.ts
10016
10188
  init_issue();
10017
- import { execFileSync as execFileSync25 } from "child_process";
10189
+ import { execFileSync as execFileSync26 } from "child_process";
10018
10190
  var API_TIMEOUT_MS9 = 3e4;
10019
10191
  var startFlow = async (ctx, profile, _agentResult, args) => {
10020
10192
  const entry = args?.entry;
@@ -10048,7 +10220,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
10048
10220
  const sub = target === "pr" && state?.core.prUrl ? "pr" : "issue";
10049
10221
  const body = `@kody ${next}`;
10050
10222
  try {
10051
- execFileSync25("gh", [sub, "comment", String(targetNumber), "--body", body], {
10223
+ execFileSync26("gh", [sub, "comment", String(targetNumber), "--body", body], {
10052
10224
  timeout: API_TIMEOUT_MS9,
10053
10225
  cwd,
10054
10226
  stdio: ["ignore", "pipe", "pipe"]
@@ -10062,7 +10234,7 @@ function postKodyComment(target, issueNumber, state, next, cwd) {
10062
10234
  }
10063
10235
 
10064
10236
  // src/scripts/syncFlow.ts
10065
- import { execFileSync as execFileSync26 } from "child_process";
10237
+ import { execFileSync as execFileSync27 } from "child_process";
10066
10238
  init_issue();
10067
10239
  var syncFlow = async (ctx, _profile, args) => {
10068
10240
  const announceOnSuccess = Boolean(args?.announceOnSuccess);
@@ -10127,21 +10299,15 @@ function bail2(ctx, prNumber, reason) {
10127
10299
  }
10128
10300
  function revParseHead(cwd) {
10129
10301
  try {
10130
- return execFileSync26("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
10302
+ return execFileSync27("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
10131
10303
  } catch {
10132
10304
  return "";
10133
10305
  }
10134
10306
  }
10135
10307
  function pushBranch(branch, cwd) {
10136
- const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
10137
- try {
10138
- execFileSync26("git", ["push", "-u", "origin", branch], { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
10139
- } catch {
10140
- execFileSync26("git", ["push", "--force-with-lease", "-u", "origin", branch], {
10141
- cwd,
10142
- env,
10143
- stdio: ["ignore", "pipe", "pipe"]
10144
- });
10308
+ const result = pushWithRetry({ cwd: cwd ?? process.cwd(), branch, setUpstream: true });
10309
+ if (!result.ok) {
10310
+ throw new Error(result.reason);
10145
10311
  }
10146
10312
  }
10147
10313
  function tryPostPr6(prNumber, body, cwd) {
@@ -10390,7 +10556,7 @@ var verifyWithRetry = async (ctx) => {
10390
10556
 
10391
10557
  // src/scripts/waitForCi.ts
10392
10558
  init_issue();
10393
- import { execFileSync as execFileSync27 } from "child_process";
10559
+ import { execFileSync as execFileSync28 } from "child_process";
10394
10560
  var API_TIMEOUT_MS10 = 3e4;
10395
10561
  var waitForCi = async (ctx, _profile, _agentResult, args) => {
10396
10562
  const timeoutMinutes = numArg(args, "timeoutMinutes", 30);
@@ -10468,7 +10634,7 @@ var waitForCi = async (ctx, _profile, _agentResult, args) => {
10468
10634
  };
10469
10635
  function fetchChecks(prNumber, cwd) {
10470
10636
  try {
10471
- const raw = execFileSync27("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
10637
+ const raw = execFileSync28("gh", ["pr", "checks", String(prNumber), "--json", "bucket,state,name,workflow,link"], {
10472
10638
  encoding: "utf-8",
10473
10639
  timeout: API_TIMEOUT_MS10,
10474
10640
  cwd,
@@ -10726,7 +10892,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
10726
10892
  };
10727
10893
 
10728
10894
  // src/scripts/writeRunSummary.ts
10729
- import * as fs35 from "fs";
10895
+ import * as fs36 from "fs";
10730
10896
  var writeRunSummary = async (ctx, profile) => {
10731
10897
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
10732
10898
  if (!summaryPath) return;
@@ -10748,7 +10914,7 @@ var writeRunSummary = async (ctx, profile) => {
10748
10914
  if (reason) lines.push(`- **Reason:** ${reason}`);
10749
10915
  lines.push("");
10750
10916
  try {
10751
- fs35.appendFileSync(summaryPath, `${lines.join("\n")}
10917
+ fs36.appendFileSync(summaryPath, `${lines.join("\n")}
10752
10918
  `);
10753
10919
  } catch {
10754
10920
  }
@@ -10847,7 +11013,7 @@ var allScriptNames = /* @__PURE__ */ new Set([
10847
11013
  ]);
10848
11014
 
10849
11015
  // src/tools.ts
10850
- import { execFileSync as execFileSync28 } from "child_process";
11016
+ import { execFileSync as execFileSync29 } from "child_process";
10851
11017
  function verifyCliTools(tools, cwd) {
10852
11018
  const out = [];
10853
11019
  for (const t of tools) out.push(verifyOne(t, cwd));
@@ -10880,7 +11046,7 @@ function verifyOne(tool2, cwd) {
10880
11046
  }
10881
11047
  function runShell(cmd, cwd, timeoutMs = 3e4) {
10882
11048
  try {
10883
- execFileSync28("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
11049
+ execFileSync29("sh", ["-c", cmd], { cwd, stdio: "pipe", timeout: timeoutMs });
10884
11050
  return true;
10885
11051
  } catch {
10886
11052
  return false;
@@ -10974,9 +11140,23 @@ async function runExecutable(profileName, input) {
10974
11140
  data: { ...input.preloadedData ?? {} },
10975
11141
  output: { exitCode: 0 }
10976
11142
  };
10977
- const ndjsonDir = path33.join(input.cwd, ".kody");
11143
+ const taskTarget = args.issue ?? args.pr;
11144
+ const taskArtifacts = typeof taskTarget === "number" && Number.isFinite(taskTarget) ? (() => {
11145
+ const taskType = args.issue ? "issue" : "pr";
11146
+ const paths = prepareTaskArtifactsDir(input.cwd, taskTarget);
11147
+ return {
11148
+ ...paths,
11149
+ taskType,
11150
+ promptAddendum: taskArtifactsPromptAddendum({
11151
+ taskId: paths.taskId,
11152
+ taskType,
11153
+ relDir: paths.relDir
11154
+ })
11155
+ };
11156
+ })() : null;
11157
+ const ndjsonDir = path34.join(input.cwd, ".kody");
10978
11158
  const invokeAgent = async (prompt) => {
10979
- const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path33.isAbsolute(p) ? p : path33.resolve(profile.dir, p)).filter((p) => p.length > 0);
11159
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path34.isAbsolute(p) ? p : path34.resolve(profile.dir, p)).filter((p) => p.length > 0);
10980
11160
  const syntheticPath = ctx.data.syntheticPluginPath;
10981
11161
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
10982
11162
  return runAgent({
@@ -10994,7 +11174,7 @@ async function runExecutable(profileName, input) {
10994
11174
  maxTurns: profile.claudeCode.maxTurns,
10995
11175
  maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
10996
11176
  maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
10997
- systemPromptAppend: profile.claudeCode.systemPromptAppend,
11177
+ systemPromptAppend: [profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
10998
11178
  cacheable: profile.claudeCode.cacheable,
10999
11179
  enableVerifyTool: profile.claudeCode.enableVerifyTool,
11000
11180
  verifyToolMaxAttempts: profile.claudeCode.verifyAttempts ?? null,
@@ -11149,6 +11329,18 @@ async function runExecutable(profileName, input) {
11149
11329
  });
11150
11330
  } finally {
11151
11331
  clearStampedLifecycleLabels(profile, ctx);
11332
+ if (taskArtifacts) {
11333
+ try {
11334
+ const missing2 = verifyTaskArtifacts(taskArtifacts.absDir);
11335
+ if (missing2.length > 0) {
11336
+ process.stderr.write(
11337
+ `[task-artifacts] task ${taskArtifacts.taskId} missing: ${missing2.join(", ")}
11338
+ `
11339
+ );
11340
+ }
11341
+ } catch {
11342
+ }
11343
+ }
11152
11344
  try {
11153
11345
  litellm?.kill();
11154
11346
  } catch {
@@ -11171,7 +11363,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
11171
11363
  function getProfileInputsForChild(profileName, _cwd) {
11172
11364
  try {
11173
11365
  const profilePath = resolveProfilePath(profileName);
11174
- if (!fs36.existsSync(profilePath)) return null;
11366
+ if (!fs37.existsSync(profilePath)) return null;
11175
11367
  return loadProfile(profilePath).inputs;
11176
11368
  } catch {
11177
11369
  return null;
@@ -11180,17 +11372,17 @@ function getProfileInputsForChild(profileName, _cwd) {
11180
11372
  function resolveProfilePath(profileName) {
11181
11373
  const found = resolveExecutable(profileName);
11182
11374
  if (found) return found;
11183
- const here = path33.dirname(new URL(import.meta.url).pathname);
11375
+ const here = path34.dirname(new URL(import.meta.url).pathname);
11184
11376
  const candidates = [
11185
- path33.join(here, "executables", profileName, "profile.json"),
11377
+ path34.join(here, "executables", profileName, "profile.json"),
11186
11378
  // same-dir sibling (dev)
11187
- path33.join(here, "..", "executables", profileName, "profile.json"),
11379
+ path34.join(here, "..", "executables", profileName, "profile.json"),
11188
11380
  // up one (prod: dist/bin → dist/executables)
11189
- path33.join(here, "..", "src", "executables", profileName, "profile.json")
11381
+ path34.join(here, "..", "src", "executables", profileName, "profile.json")
11190
11382
  // fallback
11191
11383
  ];
11192
11384
  for (const c of candidates) {
11193
- if (fs36.existsSync(c)) return c;
11385
+ if (fs37.existsSync(c)) return c;
11194
11386
  }
11195
11387
  return candidates[0];
11196
11388
  }
@@ -11290,8 +11482,8 @@ function resolveShellTimeoutMs(entry) {
11290
11482
  var SIGKILL_GRACE_MS = 5e3;
11291
11483
  async function runShellEntry(entry, ctx, profile) {
11292
11484
  const shellName = entry.shell;
11293
- const shellPath = path33.join(profile.dir, shellName);
11294
- if (!fs36.existsSync(shellPath)) {
11485
+ const shellPath = path34.join(profile.dir, shellName);
11486
+ if (!fs37.existsSync(shellPath)) {
11295
11487
  ctx.skipAgent = true;
11296
11488
  ctx.output.exitCode = 99;
11297
11489
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -11618,7 +11810,7 @@ async function runContainerLoop(profile, ctx, input) {
11618
11810
  }
11619
11811
  function resetWorkingTree2(cwd) {
11620
11812
  try {
11621
- execFileSync29("git", ["reset", "--hard", "HEAD"], {
11813
+ execFileSync30("git", ["reset", "--hard", "HEAD"], {
11622
11814
  cwd,
11623
11815
  stdio: ["ignore", "pipe", "pipe"],
11624
11816
  timeout: 3e4
@@ -11770,14 +11962,14 @@ function resolveAuthToken(env = process.env) {
11770
11962
  return token;
11771
11963
  }
11772
11964
  function detectPackageManager2(cwd) {
11773
- if (fs37.existsSync(path34.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
11774
- if (fs37.existsSync(path34.join(cwd, "yarn.lock"))) return "yarn";
11775
- if (fs37.existsSync(path34.join(cwd, "bun.lockb"))) return "bun";
11965
+ if (fs38.existsSync(path35.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
11966
+ if (fs38.existsSync(path35.join(cwd, "yarn.lock"))) return "yarn";
11967
+ if (fs38.existsSync(path35.join(cwd, "bun.lockb"))) return "bun";
11776
11968
  return "npm";
11777
11969
  }
11778
11970
  function shellOut(cmd, args, cwd, stream = true) {
11779
11971
  try {
11780
- execFileSync30(cmd, args, {
11972
+ execFileSync31(cmd, args, {
11781
11973
  cwd,
11782
11974
  stdio: stream ? "inherit" : "pipe",
11783
11975
  env: { ...process.env, HUSKY: "0", SKIP_HOOKS: "1", CI: process.env.CI ?? "1" }
@@ -11790,7 +11982,7 @@ function shellOut(cmd, args, cwd, stream = true) {
11790
11982
  }
11791
11983
  function isOnPath(bin) {
11792
11984
  try {
11793
- execFileSync30("which", [bin], { stdio: "pipe" });
11985
+ execFileSync31("which", [bin], { stdio: "pipe" });
11794
11986
  return true;
11795
11987
  } catch {
11796
11988
  return false;
@@ -11831,7 +12023,7 @@ function installLitellmIfNeeded(cwd) {
11831
12023
  } catch {
11832
12024
  }
11833
12025
  try {
11834
- execFileSync30("python3", ["-c", "import litellm"], { stdio: "pipe" });
12026
+ execFileSync31("python3", ["-c", "import litellm"], { stdio: "pipe" });
11835
12027
  process.stdout.write("\u2192 kody: litellm already installed\n");
11836
12028
  return 0;
11837
12029
  } catch {
@@ -11841,16 +12033,16 @@ function installLitellmIfNeeded(cwd) {
11841
12033
  }
11842
12034
  function configureGitIdentity(cwd) {
11843
12035
  try {
11844
- const name = execFileSync30("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
12036
+ const name = execFileSync31("git", ["config", "user.name"], { cwd, stdio: "pipe", encoding: "utf-8" }).trim();
11845
12037
  if (name) return;
11846
12038
  } catch {
11847
12039
  }
11848
12040
  try {
11849
- execFileSync30("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
12041
+ execFileSync31("git", ["config", "user.name", "github-actions[bot]"], { cwd, stdio: "pipe" });
11850
12042
  } catch {
11851
12043
  }
11852
12044
  try {
11853
- execFileSync30("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
12045
+ execFileSync31("git", ["config", "user.email", "41898282+github-actions[bot]@users.noreply.github.com"], {
11854
12046
  cwd,
11855
12047
  stdio: "pipe"
11856
12048
  });
@@ -11859,11 +12051,11 @@ function configureGitIdentity(cwd) {
11859
12051
  }
11860
12052
  function postFailureTail(issueNumber, cwd, reason) {
11861
12053
  if (!issueNumber) return;
11862
- const logPath = path34.join(cwd, ".kody", "last-run.jsonl");
12054
+ const logPath = path35.join(cwd, ".kody", "last-run.jsonl");
11863
12055
  let tail = "";
11864
12056
  try {
11865
- if (fs37.existsSync(logPath)) {
11866
- const content = fs37.readFileSync(logPath, "utf-8");
12057
+ if (fs38.existsSync(logPath)) {
12058
+ const content = fs38.readFileSync(logPath, "utf-8");
11867
12059
  tail = content.slice(-3e3);
11868
12060
  }
11869
12061
  } catch {
@@ -11888,7 +12080,7 @@ async function runCi(argv) {
11888
12080
  return 0;
11889
12081
  }
11890
12082
  const args = parseCiArgs(argv);
11891
- const cwd = args.cwd ? path34.resolve(args.cwd) : process.cwd();
12083
+ const cwd = args.cwd ? path35.resolve(args.cwd) : process.cwd();
11892
12084
  let earlyConfig;
11893
12085
  try {
11894
12086
  earlyConfig = loadConfig(cwd);
@@ -11898,9 +12090,9 @@ async function runCi(argv) {
11898
12090
  const eventName = process.env.GITHUB_EVENT_NAME;
11899
12091
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
11900
12092
  let manualWorkflowDispatch = false;
11901
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs37.existsSync(dispatchEventPath)) {
12093
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs38.existsSync(dispatchEventPath)) {
11902
12094
  try {
11903
- const evt = JSON.parse(fs37.readFileSync(dispatchEventPath, "utf-8"));
12095
+ const evt = JSON.parse(fs38.readFileSync(dispatchEventPath, "utf-8"));
11904
12096
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
11905
12097
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
11906
12098
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -12159,19 +12351,28 @@ function parseChatArgs(argv, env = process.env) {
12159
12351
  return result;
12160
12352
  }
12161
12353
  function commitChatFiles(cwd, sessionId, verbose) {
12162
- const sessionFile = path35.relative(cwd, sessionFilePath(cwd, sessionId));
12163
- const eventsFile = path35.relative(cwd, eventsFilePath(cwd, sessionId));
12164
- const paths = [sessionFile, eventsFile].filter((p) => fs38.existsSync(path35.join(cwd, p)));
12354
+ const sessionFile = path36.relative(cwd, sessionFilePath(cwd, sessionId));
12355
+ const eventsFile = path36.relative(cwd, eventsFilePath(cwd, sessionId));
12356
+ const safeSession = sessionId.replace(/[^a-zA-Z0-9._-]/g, "_");
12357
+ const tasksDir = path36.join(".kody", "tasks", safeSession);
12358
+ const candidatePaths = [sessionFile, eventsFile, tasksDir];
12359
+ const paths = candidatePaths.filter((p) => fs39.existsSync(path36.join(cwd, p)));
12165
12360
  if (paths.length === 0) return;
12166
12361
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
12167
12362
  try {
12168
- execFileSync31("git", ["add", "-f", ...paths], opts);
12169
- execFileSync31("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
12170
- execFileSync31("git", ["push", "--quiet", "origin", "HEAD"], opts);
12363
+ execFileSync32("git", ["add", "-f", ...paths], opts);
12364
+ execFileSync32("git", ["commit", "--quiet", "-m", `chat: reply for ${sessionId}`], opts);
12171
12365
  } catch (err) {
12172
12366
  const msg = err instanceof Error ? err.message : String(err);
12173
- process.stderr.write(`[kody:chat] commit/push skipped: ${msg}
12367
+ process.stderr.write(`[kody:chat] commit skipped: ${msg}
12174
12368
  `);
12369
+ return;
12370
+ }
12371
+ const result = pushWithRetry({ cwd });
12372
+ if (!result.ok) {
12373
+ process.stderr.write(`[kody:chat] push FAILED after ${result.attempts} attempt(s): ${result.reason}
12374
+ `);
12375
+ throw new Error(`chat push failed: ${result.reason}`);
12175
12376
  }
12176
12377
  }
12177
12378
  function tryLoadConfig(cwd) {
@@ -12199,7 +12400,7 @@ async function runChat(argv) {
12199
12400
  ${CHAT_HELP}`);
12200
12401
  return 64;
12201
12402
  }
12202
- const cwd = args.cwd ? path35.resolve(args.cwd) : process.cwd();
12403
+ const cwd = args.cwd ? path36.resolve(args.cwd) : process.cwd();
12203
12404
  const sessionId = args.sessionId;
12204
12405
  const unpackedSecrets = unpackAllSecrets();
12205
12406
  if (unpackedSecrets > 0) {
@@ -12251,7 +12452,7 @@ ${CHAT_HELP}`);
12251
12452
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
12252
12453
  const meta = readMeta(sessionFile);
12253
12454
  process.stdout.write(
12254
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs38.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
12455
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs39.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
12255
12456
  `
12256
12457
  );
12257
12458
  try {