@kody-ade/kody-engine 0.4.96 → 0.4.99

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.96",
880
+ version: "0.4.99",
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",
@@ -932,8 +932,8 @@ var package_default = {
932
932
 
933
933
  // src/chat-cli.ts
934
934
  import { execFileSync as execFileSync31 } from "child_process";
935
- import * as fs38 from "fs";
936
- import * as path35 from "path";
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
  }
@@ -2255,11 +2357,11 @@ async function emit2(sink, type, sessionId, suffix, payload) {
2255
2357
 
2256
2358
  // src/kody-cli.ts
2257
2359
  import { execFileSync as execFileSync30 } from "child_process";
2258
- import * as fs37 from "fs";
2259
- import * as path34 from "path";
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
  }
@@ -2578,16 +2680,16 @@ init_issue();
2578
2680
 
2579
2681
  // src/executor.ts
2580
2682
  import { execFileSync as execFileSync29, spawn as spawn6 } from "child_process";
2581
- import * as fs36 from "fs";
2582
- import * as path33 from "path";
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)=(.*)$/);
@@ -3322,8 +3424,8 @@ function stripBlockingEnv(env) {
3322
3424
 
3323
3425
  // src/commit.ts
3324
3426
  import { execFileSync as execFileSync5 } from "child_process";
3325
- import * as fs13 from "fs";
3326
- import * as path11 from "path";
3427
+ import * as fs14 from "fs";
3428
+ import * as path12 from "path";
3327
3429
  var FORBIDDEN_PATH_PREFIXES = [
3328
3430
  ".kody/",
3329
3431
  ".kody-engine/",
@@ -3383,18 +3485,18 @@ function tryGit(args, cwd) {
3383
3485
  }
3384
3486
  function abortUnfinishedGitOps(cwd) {
3385
3487
  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"))) {
3488
+ const gitDir = path12.join(cwd ?? process.cwd(), ".git");
3489
+ if (!fs14.existsSync(gitDir)) return aborted;
3490
+ if (fs14.existsSync(path12.join(gitDir, "MERGE_HEAD"))) {
3389
3491
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
3390
3492
  }
3391
- if (fs13.existsSync(path11.join(gitDir, "CHERRY_PICK_HEAD"))) {
3493
+ if (fs14.existsSync(path12.join(gitDir, "CHERRY_PICK_HEAD"))) {
3392
3494
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
3393
3495
  }
3394
- if (fs13.existsSync(path11.join(gitDir, "REVERT_HEAD"))) {
3496
+ if (fs14.existsSync(path12.join(gitDir, "REVERT_HEAD"))) {
3395
3497
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
3396
3498
  }
3397
- if (fs13.existsSync(path11.join(gitDir, "rebase-merge")) || fs13.existsSync(path11.join(gitDir, "rebase-apply"))) {
3499
+ if (fs14.existsSync(path12.join(gitDir, "rebase-merge")) || fs14.existsSync(path12.join(gitDir, "rebase-apply"))) {
3398
3500
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
3399
3501
  }
3400
3502
  try {
@@ -3450,7 +3552,7 @@ function normalizeCommitMessage(raw) {
3450
3552
  function commitAndPush(branch, agentMessage, cwd) {
3451
3553
  const allChanged = listChangedFiles(cwd);
3452
3554
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
3453
- const mergeHeadExists = fs13.existsSync(path11.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3555
+ const mergeHeadExists = fs14.existsSync(path12.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
3454
3556
  if (allowedFiles.length === 0 && !mergeHeadExists) {
3455
3557
  return { committed: false, pushed: false, sha: "", message: "" };
3456
3558
  }
@@ -3766,20 +3868,20 @@ var advanceFlow = async (ctx, profile) => {
3766
3868
 
3767
3869
  // src/scripts/brainServe.ts
3768
3870
  import { createServer } from "http";
3769
- import * as fs15 from "fs";
3770
- import * as path13 from "path";
3871
+ import * as fs16 from "fs";
3872
+ import * as path14 from "path";
3771
3873
 
3772
3874
  // src/scripts/brainTurnLog.ts
3773
- import * as fs14 from "fs";
3774
- import * as path12 from "path";
3875
+ import * as fs15 from "fs";
3876
+ import * as path13 from "path";
3775
3877
  var live = /* @__PURE__ */ new Map();
3776
3878
  function eventsPath(dir, chatId) {
3777
- return path12.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
3879
+ return path13.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
3778
3880
  }
3779
3881
  function lastPersistedSeq(dir, chatId) {
3780
3882
  const p = eventsPath(dir, chatId);
3781
- if (!fs14.existsSync(p)) return 0;
3782
- const lines = fs14.readFileSync(p, "utf-8").split("\n").filter(Boolean);
3883
+ if (!fs15.existsSync(p)) return 0;
3884
+ const lines = fs15.readFileSync(p, "utf-8").split("\n").filter(Boolean);
3783
3885
  if (lines.length === 0) return 0;
3784
3886
  try {
3785
3887
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -3789,9 +3891,9 @@ function lastPersistedSeq(dir, chatId) {
3789
3891
  }
3790
3892
  function readSince(dir, chatId, since) {
3791
3893
  const p = eventsPath(dir, chatId);
3792
- if (!fs14.existsSync(p)) return [];
3894
+ if (!fs15.existsSync(p)) return [];
3793
3895
  const out = [];
3794
- for (const line of fs14.readFileSync(p, "utf-8").split("\n")) {
3896
+ for (const line of fs15.readFileSync(p, "utf-8").split("\n")) {
3795
3897
  if (!line) continue;
3796
3898
  try {
3797
3899
  const rec = JSON.parse(line);
@@ -3817,12 +3919,12 @@ function beginTurn(dir, chatId) {
3817
3919
  };
3818
3920
  live.set(chatId, state);
3819
3921
  const p = eventsPath(dir, chatId);
3820
- fs14.mkdirSync(path12.dirname(p), { recursive: true });
3922
+ fs15.mkdirSync(path13.dirname(p), { recursive: true });
3821
3923
  return (event) => {
3822
3924
  state.seq += 1;
3823
3925
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
3824
3926
  try {
3825
- fs14.appendFileSync(p, JSON.stringify(rec) + "\n");
3927
+ fs15.appendFileSync(p, JSON.stringify(rec) + "\n");
3826
3928
  } catch (err) {
3827
3929
  process.stderr.write(
3828
3930
  `[brain-turn-log] append failed for ${chatId}: ${err instanceof Error ? err.message : String(err)}
@@ -3860,7 +3962,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
3860
3962
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
3861
3963
  };
3862
3964
  try {
3863
- fs14.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
3965
+ fs15.appendFileSync(eventsPath(dir, chatId), JSON.stringify(rec) + "\n");
3864
3966
  } catch {
3865
3967
  }
3866
3968
  state.status = "ended";
@@ -4077,7 +4179,7 @@ async function handleChatTurn(req, res, chatId, opts) {
4077
4179
  return;
4078
4180
  }
4079
4181
  const sessionFile = sessionFilePath(opts.cwd, chatId);
4080
- fs15.mkdirSync(path13.dirname(sessionFile), { recursive: true });
4182
+ fs16.mkdirSync(path14.dirname(sessionFile), { recursive: true });
4081
4183
  appendTurn(sessionFile, {
4082
4184
  role: "user",
4083
4185
  content: message,
@@ -4217,21 +4319,21 @@ var brainServe = async (ctx) => {
4217
4319
  };
4218
4320
 
4219
4321
  // src/scripts/buildSyntheticPlugin.ts
4220
- import * as fs16 from "fs";
4322
+ import * as fs17 from "fs";
4221
4323
  import * as os3 from "os";
4222
- import * as path14 from "path";
4324
+ import * as path15 from "path";
4223
4325
  function getPluginsCatalogRoot() {
4224
- const here = path14.dirname(new URL(import.meta.url).pathname);
4326
+ const here = path15.dirname(new URL(import.meta.url).pathname);
4225
4327
  const candidates = [
4226
- path14.join(here, "..", "plugins"),
4328
+ path15.join(here, "..", "plugins"),
4227
4329
  // dev: src/scripts → src/plugins
4228
- path14.join(here, "..", "..", "plugins"),
4330
+ path15.join(here, "..", "..", "plugins"),
4229
4331
  // built: dist/scripts → dist/plugins
4230
- path14.join(here, "..", "..", "src", "plugins")
4332
+ path15.join(here, "..", "..", "src", "plugins")
4231
4333
  // fallback
4232
4334
  ];
4233
4335
  for (const c of candidates) {
4234
- if (fs16.existsSync(c) && fs16.statSync(c).isDirectory()) return c;
4336
+ if (fs17.existsSync(c) && fs17.statSync(c).isDirectory()) return c;
4235
4337
  }
4236
4338
  return candidates[0];
4237
4339
  }
@@ -4241,52 +4343,52 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4241
4343
  if (!needsSynthetic) return;
4242
4344
  const catalog = getPluginsCatalogRoot();
4243
4345
  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 });
4346
+ const root = path15.join(os3.tmpdir(), `kody-synth-${runId}`);
4347
+ fs17.mkdirSync(path15.join(root, ".claude-plugin"), { recursive: true });
4246
4348
  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;
4349
+ const local = path15.join(profile.dir, bucket, entry);
4350
+ if (fs17.existsSync(local)) return local;
4351
+ const central = path15.join(catalog, bucket, entry);
4352
+ if (fs17.existsSync(central)) return central;
4251
4353
  throw new Error(
4252
4354
  `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
4253
4355
  );
4254
4356
  };
4255
4357
  if (cc.skills.length > 0) {
4256
- const dst = path14.join(root, "skills");
4257
- fs16.mkdirSync(dst, { recursive: true });
4358
+ const dst = path15.join(root, "skills");
4359
+ fs17.mkdirSync(dst, { recursive: true });
4258
4360
  for (const name of cc.skills) {
4259
- copyDir(resolvePart("skills", name), path14.join(dst, name));
4361
+ copyDir(resolvePart("skills", name), path15.join(dst, name));
4260
4362
  }
4261
4363
  }
4262
4364
  if (cc.commands.length > 0) {
4263
- const dst = path14.join(root, "commands");
4264
- fs16.mkdirSync(dst, { recursive: true });
4365
+ const dst = path15.join(root, "commands");
4366
+ fs17.mkdirSync(dst, { recursive: true });
4265
4367
  for (const name of cc.commands) {
4266
- fs16.copyFileSync(resolvePart("commands", `${name}.md`), path14.join(dst, `${name}.md`));
4368
+ fs17.copyFileSync(resolvePart("commands", `${name}.md`), path15.join(dst, `${name}.md`));
4267
4369
  }
4268
4370
  }
4269
4371
  if (cc.subagents.length > 0) {
4270
- const dst = path14.join(root, "agents");
4271
- fs16.mkdirSync(dst, { recursive: true });
4372
+ const dst = path15.join(root, "agents");
4373
+ fs17.mkdirSync(dst, { recursive: true });
4272
4374
  for (const name of cc.subagents) {
4273
- fs16.copyFileSync(resolvePart("agents", `${name}.md`), path14.join(dst, `${name}.md`));
4375
+ fs17.copyFileSync(resolvePart("agents", `${name}.md`), path15.join(dst, `${name}.md`));
4274
4376
  }
4275
4377
  }
4276
4378
  if (cc.hooks.length > 0) {
4277
- const dst = path14.join(root, "hooks");
4278
- fs16.mkdirSync(dst, { recursive: true });
4379
+ const dst = path15.join(root, "hooks");
4380
+ fs17.mkdirSync(dst, { recursive: true });
4279
4381
  const merged = { hooks: {} };
4280
4382
  for (const name of cc.hooks) {
4281
4383
  const src = resolvePart("hooks", `${name}.json`);
4282
- const parsed = JSON.parse(fs16.readFileSync(src, "utf-8"));
4384
+ const parsed = JSON.parse(fs17.readFileSync(src, "utf-8"));
4283
4385
  for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
4284
4386
  if (!Array.isArray(entries)) continue;
4285
4387
  if (!merged.hooks[event]) merged.hooks[event] = [];
4286
4388
  merged.hooks[event].push(...entries);
4287
4389
  }
4288
4390
  }
4289
- fs16.writeFileSync(path14.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4391
+ fs17.writeFileSync(path15.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
4290
4392
  `);
4291
4393
  }
4292
4394
  const manifest = {
@@ -4297,17 +4399,17 @@ var buildSyntheticPlugin = async (ctx, profile) => {
4297
4399
  if (cc.skills.length > 0) manifest.skills = ["./skills/"];
4298
4400
  if (cc.commands.length > 0) manifest.commands = ["./commands/"];
4299
4401
  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)}
4402
+ fs17.writeFileSync(path15.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
4301
4403
  `);
4302
4404
  ctx.data.syntheticPluginPath = root;
4303
4405
  };
4304
4406
  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);
4407
+ fs17.mkdirSync(dst, { recursive: true });
4408
+ for (const ent of fs17.readdirSync(src, { withFileTypes: true })) {
4409
+ const s = path15.join(src, ent.name);
4410
+ const d = path15.join(dst, ent.name);
4309
4411
  if (ent.isDirectory()) copyDir(s, d);
4310
- else if (ent.isFile()) fs16.copyFileSync(s, d);
4412
+ else if (ent.isFile()) fs17.copyFileSync(s, d);
4311
4413
  }
4312
4414
  }
4313
4415
 
@@ -4448,13 +4550,13 @@ function defaultLabelMap() {
4448
4550
  }
4449
4551
 
4450
4552
  // src/scripts/commitAndPush.ts
4451
- import * as fs18 from "fs";
4452
- import * as path16 from "path";
4553
+ import * as fs19 from "fs";
4554
+ import * as path17 from "path";
4453
4555
  init_events();
4454
4556
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
4455
4557
  function sentinelPathForStage(cwd, profileName) {
4456
4558
  const runId = resolveRunId();
4457
- return path16.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4559
+ return path17.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
4458
4560
  }
4459
4561
  var commitAndPush2 = async (ctx, profile) => {
4460
4562
  const branch = ctx.data.branch;
@@ -4464,9 +4566,9 @@ var commitAndPush2 = async (ctx, profile) => {
4464
4566
  }
4465
4567
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
4466
4568
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
4467
- if (sentinel && fs18.existsSync(sentinel)) {
4569
+ if (sentinel && fs19.existsSync(sentinel)) {
4468
4570
  try {
4469
- const replay = JSON.parse(fs18.readFileSync(sentinel, "utf-8"));
4571
+ const replay = JSON.parse(fs19.readFileSync(sentinel, "utf-8"));
4470
4572
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
4471
4573
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
4472
4574
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -4519,8 +4621,8 @@ var commitAndPush2 = async (ctx, profile) => {
4519
4621
  const result = ctx.data.commitResult;
4520
4622
  if (sentinel && result?.committed) {
4521
4623
  try {
4522
- fs18.mkdirSync(path16.dirname(sentinel), { recursive: true });
4523
- fs18.writeFileSync(
4624
+ fs19.mkdirSync(path17.dirname(sentinel), { recursive: true });
4625
+ fs19.writeFileSync(
4524
4626
  sentinel,
4525
4627
  JSON.stringify(
4526
4628
  {
@@ -4541,11 +4643,11 @@ var commitAndPush2 = async (ctx, profile) => {
4541
4643
 
4542
4644
  // src/scripts/commitGoalState.ts
4543
4645
  import { execFileSync as execFileSync9 } from "child_process";
4544
- import * as path17 from "path";
4646
+ import * as path18 from "path";
4545
4647
  var commitGoalState = async (ctx) => {
4546
4648
  const goal = ctx.data.goal;
4547
4649
  if (!goal) return;
4548
- const stateRel = path17.posix.join(".kody", "goals", goal.id, "state.json");
4650
+ const stateRel = path18.posix.join(".kody", "goals", goal.id, "state.json");
4549
4651
  try {
4550
4652
  execFileSync9("git", ["add", stateRel], { cwd: ctx.cwd, stdio: "pipe" });
4551
4653
  } catch (err) {
@@ -4590,20 +4692,20 @@ function describeCommitMessage(goal) {
4590
4692
  }
4591
4693
 
4592
4694
  // src/scripts/composePrompt.ts
4593
- import * as fs19 from "fs";
4594
- import * as path18 from "path";
4695
+ import * as fs20 from "fs";
4696
+ import * as path19 from "path";
4595
4697
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
4596
4698
  var composePrompt = async (ctx, profile) => {
4597
4699
  const explicit = ctx.data.promptTemplate;
4598
4700
  const mode = ctx.args.mode;
4599
4701
  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")
4702
+ explicit ? path19.join(profile.dir, explicit) : null,
4703
+ mode ? path19.join(profile.dir, "prompts", `${mode}.md`) : null,
4704
+ path19.join(profile.dir, "prompt.md")
4603
4705
  ].filter(Boolean);
4604
4706
  let templatePath = "";
4605
4707
  for (const c of candidates) {
4606
- if (fs19.existsSync(c)) {
4708
+ if (fs20.existsSync(c)) {
4607
4709
  templatePath = c;
4608
4710
  break;
4609
4711
  }
@@ -4611,7 +4713,7 @@ var composePrompt = async (ctx, profile) => {
4611
4713
  if (!templatePath) {
4612
4714
  throw new Error(`profile at ${profile.dir}: no prompt template found (tried ${candidates.join(", ")})`);
4613
4715
  }
4614
- const template = fs19.readFileSync(templatePath, "utf-8");
4716
+ const template = fs20.readFileSync(templatePath, "utf-8");
4615
4717
  const tokens = {
4616
4718
  ...stringifyAll(ctx.args, "args."),
4617
4719
  ...stringifyAll(ctx.data, ""),
@@ -4690,8 +4792,8 @@ function formatToolsUsage(profile) {
4690
4792
  // src/scripts/createQaGoal.ts
4691
4793
  init_issue();
4692
4794
  import { execFileSync as execFileSync10 } from "child_process";
4693
- import * as fs20 from "fs";
4694
- import * as path19 from "path";
4795
+ import * as fs21 from "fs";
4796
+ import * as path20 from "path";
4695
4797
 
4696
4798
  // src/scripts/postReviewResult.ts
4697
4799
  init_issue();
@@ -4944,8 +5046,8 @@ function createOrUpdateManifestIssue(number, manifest, cwd) {
4944
5046
  return { number: Number(m[1]), created: true };
4945
5047
  }
4946
5048
  function writeStateFile(cwd, goalId, lastDispatchedIssue) {
4947
- const dir = path19.join(cwd, ".kody", "goals", goalId);
4948
- fs20.mkdirSync(dir, { recursive: true });
5049
+ const dir = path20.join(cwd, ".kody", "goals", goalId);
5050
+ fs21.mkdirSync(dir, { recursive: true });
4949
5051
  const state = {
4950
5052
  version: 1,
4951
5053
  state: "active",
@@ -4953,8 +5055,8 @@ function writeStateFile(cwd, goalId, lastDispatchedIssue) {
4953
5055
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4954
5056
  ...typeof lastDispatchedIssue === "number" ? { lastDispatchedIssue } : {}
4955
5057
  };
4956
- const filePath = path19.join(dir, "state.json");
4957
- fs20.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
5058
+ const filePath = path20.join(dir, "state.json");
5059
+ fs21.writeFileSync(filePath, `${JSON.stringify(state, null, 2)}
4958
5060
  `);
4959
5061
  return filePath;
4960
5062
  }
@@ -5463,15 +5565,15 @@ function filterGoalTaskPrs(prs, taskIssueNumbers) {
5463
5565
 
5464
5566
  // src/scripts/diagMcp.ts
5465
5567
  import { execFileSync as execFileSync11 } from "child_process";
5466
- import * as fs21 from "fs";
5568
+ import * as fs22 from "fs";
5467
5569
  import * as os4 from "os";
5468
- import * as path20 from "path";
5570
+ import * as path21 from "path";
5469
5571
  var diagMcp = async (_ctx) => {
5470
5572
  const home = os4.homedir();
5471
- const cacheDir = path20.join(home, ".cache", "ms-playwright");
5573
+ const cacheDir = path21.join(home, ".cache", "ms-playwright");
5472
5574
  let entries = [];
5473
5575
  try {
5474
- entries = fs21.readdirSync(cacheDir);
5576
+ entries = fs22.readdirSync(cacheDir);
5475
5577
  } catch {
5476
5578
  }
5477
5579
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -5497,17 +5599,17 @@ var diagMcp = async (_ctx) => {
5497
5599
  };
5498
5600
 
5499
5601
  // src/scripts/discoverQaContext.ts
5500
- import * as fs23 from "fs";
5501
- import * as path22 from "path";
5602
+ import * as fs24 from "fs";
5603
+ import * as path23 from "path";
5502
5604
 
5503
5605
  // src/scripts/frameworkDetectors.ts
5504
- import * as fs22 from "fs";
5505
- import * as path21 from "path";
5606
+ import * as fs23 from "fs";
5607
+ import * as path22 from "path";
5506
5608
  function detectFrameworks(cwd) {
5507
5609
  const out = [];
5508
5610
  let deps = {};
5509
5611
  try {
5510
- const pkg = JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
5612
+ const pkg = JSON.parse(fs23.readFileSync(path22.join(cwd, "package.json"), "utf-8"));
5511
5613
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
5512
5614
  } catch {
5513
5615
  return out;
@@ -5544,7 +5646,7 @@ function detectFrameworks(cwd) {
5544
5646
  }
5545
5647
  function findFile(cwd, candidates) {
5546
5648
  for (const c of candidates) {
5547
- if (fs22.existsSync(path21.join(cwd, c))) return c;
5649
+ if (fs23.existsSync(path22.join(cwd, c))) return c;
5548
5650
  }
5549
5651
  return null;
5550
5652
  }
@@ -5557,18 +5659,18 @@ var COLLECTION_DIRS = [
5557
5659
  function discoverPayloadCollections(cwd) {
5558
5660
  const out = [];
5559
5661
  for (const dir of COLLECTION_DIRS) {
5560
- const full = path21.join(cwd, dir);
5561
- if (!fs22.existsSync(full)) continue;
5662
+ const full = path22.join(cwd, dir);
5663
+ if (!fs23.existsSync(full)) continue;
5562
5664
  let files;
5563
5665
  try {
5564
- files = fs22.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5666
+ files = fs23.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5565
5667
  } catch {
5566
5668
  continue;
5567
5669
  }
5568
5670
  for (const file of files) {
5569
5671
  try {
5570
- const filePath = path21.join(full, file);
5571
- const content = fs22.readFileSync(filePath, "utf-8").slice(0, 1e4);
5672
+ const filePath = path22.join(full, file);
5673
+ const content = fs23.readFileSync(filePath, "utf-8").slice(0, 1e4);
5572
5674
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
5573
5675
  if (!slugMatch) continue;
5574
5676
  const slug = slugMatch[1];
@@ -5582,7 +5684,7 @@ function discoverPayloadCollections(cwd) {
5582
5684
  out.push({
5583
5685
  name,
5584
5686
  slug,
5585
- filePath: path21.relative(cwd, filePath),
5687
+ filePath: path22.relative(cwd, filePath),
5586
5688
  fields: fields.slice(0, 20),
5587
5689
  hasAdmin
5588
5690
  });
@@ -5596,28 +5698,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
5596
5698
  function discoverAdminComponents(cwd, collections) {
5597
5699
  const out = [];
5598
5700
  for (const dir of ADMIN_COMPONENT_DIRS) {
5599
- const full = path21.join(cwd, dir);
5600
- if (!fs22.existsSync(full)) continue;
5701
+ const full = path22.join(cwd, dir);
5702
+ if (!fs23.existsSync(full)) continue;
5601
5703
  let entries;
5602
5704
  try {
5603
- entries = fs22.readdirSync(full, { withFileTypes: true });
5705
+ entries = fs23.readdirSync(full, { withFileTypes: true });
5604
5706
  } catch {
5605
5707
  continue;
5606
5708
  }
5607
5709
  for (const entry of entries) {
5608
- const entryPath = path21.join(full, entry.name);
5710
+ const entryPath = path22.join(full, entry.name);
5609
5711
  let name;
5610
5712
  let filePath;
5611
5713
  if (entry.isDirectory()) {
5612
5714
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
5613
- (f) => fs22.existsSync(path21.join(entryPath, f))
5715
+ (f) => fs23.existsSync(path22.join(entryPath, f))
5614
5716
  );
5615
5717
  if (!indexFile) continue;
5616
5718
  name = entry.name;
5617
- filePath = path21.relative(cwd, path21.join(entryPath, indexFile));
5719
+ filePath = path22.relative(cwd, path22.join(entryPath, indexFile));
5618
5720
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
5619
5721
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
5620
- filePath = path21.relative(cwd, entryPath);
5722
+ filePath = path22.relative(cwd, entryPath);
5621
5723
  } else {
5622
5724
  continue;
5623
5725
  }
@@ -5625,7 +5727,7 @@ function discoverAdminComponents(cwd, collections) {
5625
5727
  if (collections) {
5626
5728
  for (const col of collections) {
5627
5729
  try {
5628
- const colContent = fs22.readFileSync(path21.join(cwd, col.filePath), "utf-8");
5730
+ const colContent = fs23.readFileSync(path22.join(cwd, col.filePath), "utf-8");
5629
5731
  if (colContent.includes(name)) {
5630
5732
  usedInCollection = col.slug;
5631
5733
  break;
@@ -5644,8 +5746,8 @@ function scanApiRoutes(cwd) {
5644
5746
  const out = [];
5645
5747
  const appDirs = ["src/app", "app"];
5646
5748
  for (const appDir of appDirs) {
5647
- const apiDir = path21.join(cwd, appDir, "api");
5648
- if (!fs22.existsSync(apiDir)) continue;
5749
+ const apiDir = path22.join(cwd, appDir, "api");
5750
+ if (!fs23.existsSync(apiDir)) continue;
5649
5751
  walkApiRoutes(apiDir, "/api", cwd, out);
5650
5752
  break;
5651
5753
  }
@@ -5654,14 +5756,14 @@ function scanApiRoutes(cwd) {
5654
5756
  function walkApiRoutes(dir, prefix, cwd, out) {
5655
5757
  let entries;
5656
5758
  try {
5657
- entries = fs22.readdirSync(dir, { withFileTypes: true });
5759
+ entries = fs23.readdirSync(dir, { withFileTypes: true });
5658
5760
  } catch {
5659
5761
  return;
5660
5762
  }
5661
5763
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
5662
5764
  if (routeFile) {
5663
5765
  try {
5664
- const content = fs22.readFileSync(path21.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
5766
+ const content = fs23.readFileSync(path22.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
5665
5767
  const methods = HTTP_METHODS.filter(
5666
5768
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
5667
5769
  );
@@ -5669,7 +5771,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5669
5771
  out.push({
5670
5772
  path: prefix,
5671
5773
  methods,
5672
- filePath: path21.relative(cwd, path21.join(dir, routeFile.name))
5774
+ filePath: path22.relative(cwd, path22.join(dir, routeFile.name))
5673
5775
  });
5674
5776
  }
5675
5777
  } catch {
@@ -5680,7 +5782,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5680
5782
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5681
5783
  let segment = entry.name;
5682
5784
  if (segment.startsWith("(") && segment.endsWith(")")) {
5683
- walkApiRoutes(path21.join(dir, entry.name), prefix, cwd, out);
5785
+ walkApiRoutes(path22.join(dir, entry.name), prefix, cwd, out);
5684
5786
  continue;
5685
5787
  }
5686
5788
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -5688,7 +5790,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
5688
5790
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
5689
5791
  segment = `:${segment.slice(1, -1)}`;
5690
5792
  }
5691
- walkApiRoutes(path21.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
5793
+ walkApiRoutes(path22.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
5692
5794
  }
5693
5795
  }
5694
5796
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -5708,10 +5810,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
5708
5810
  function scanEnvVars(cwd) {
5709
5811
  const candidates = [".env.example", ".env.local.example", ".env.template"];
5710
5812
  for (const envFile of candidates) {
5711
- const envPath = path21.join(cwd, envFile);
5712
- if (!fs22.existsSync(envPath)) continue;
5813
+ const envPath = path22.join(cwd, envFile);
5814
+ if (!fs23.existsSync(envPath)) continue;
5713
5815
  try {
5714
- const content = fs22.readFileSync(envPath, "utf-8");
5816
+ const content = fs23.readFileSync(envPath, "utf-8");
5715
5817
  const vars = [];
5716
5818
  for (const line of content.split("\n")) {
5717
5819
  const trimmed = line.trim();
@@ -5759,9 +5861,9 @@ function runQaDiscovery(cwd) {
5759
5861
  }
5760
5862
  function detectDevServer(cwd, out) {
5761
5863
  try {
5762
- const pkg = JSON.parse(fs23.readFileSync(path22.join(cwd, "package.json"), "utf-8"));
5864
+ const pkg = JSON.parse(fs24.readFileSync(path23.join(cwd, "package.json"), "utf-8"));
5763
5865
  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";
5866
+ 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
5867
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
5766
5868
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
5767
5869
  else if (allDeps.vite) out.devPort = 5173;
@@ -5771,8 +5873,8 @@ function detectDevServer(cwd, out) {
5771
5873
  function scanFrontendRoutes(cwd, out) {
5772
5874
  const appDirs = ["src/app", "app"];
5773
5875
  for (const appDir of appDirs) {
5774
- const full = path22.join(cwd, appDir);
5775
- if (!fs23.existsSync(full)) continue;
5876
+ const full = path23.join(cwd, appDir);
5877
+ if (!fs24.existsSync(full)) continue;
5776
5878
  walkFrontendRoutes(full, "", out);
5777
5879
  break;
5778
5880
  }
@@ -5780,7 +5882,7 @@ function scanFrontendRoutes(cwd, out) {
5780
5882
  function walkFrontendRoutes(dir, prefix, out) {
5781
5883
  let entries;
5782
5884
  try {
5783
- entries = fs23.readdirSync(dir, { withFileTypes: true });
5885
+ entries = fs24.readdirSync(dir, { withFileTypes: true });
5784
5886
  } catch {
5785
5887
  return;
5786
5888
  }
@@ -5797,7 +5899,7 @@ function walkFrontendRoutes(dir, prefix, out) {
5797
5899
  if (entry.name === "node_modules" || entry.name === ".next") continue;
5798
5900
  let segment = entry.name;
5799
5901
  if (segment.startsWith("(") && segment.endsWith(")")) {
5800
- walkFrontendRoutes(path22.join(dir, entry.name), prefix, out);
5902
+ walkFrontendRoutes(path23.join(dir, entry.name), prefix, out);
5801
5903
  continue;
5802
5904
  }
5803
5905
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -5805,7 +5907,7 @@ function walkFrontendRoutes(dir, prefix, out) {
5805
5907
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
5806
5908
  segment = `:${segment.slice(1, -1)}`;
5807
5909
  }
5808
- walkFrontendRoutes(path22.join(dir, entry.name), `${prefix}/${segment}`, out);
5910
+ walkFrontendRoutes(path23.join(dir, entry.name), `${prefix}/${segment}`, out);
5809
5911
  }
5810
5912
  }
5811
5913
  function detectAuthFiles(cwd, out) {
@@ -5822,23 +5924,23 @@ function detectAuthFiles(cwd, out) {
5822
5924
  "src/app/api/oauth"
5823
5925
  ];
5824
5926
  for (const c of candidates) {
5825
- if (fs23.existsSync(path22.join(cwd, c))) out.authFiles.push(c);
5927
+ if (fs24.existsSync(path23.join(cwd, c))) out.authFiles.push(c);
5826
5928
  }
5827
5929
  }
5828
5930
  function detectRoles(cwd, out) {
5829
5931
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
5830
5932
  for (const rp of rolePaths) {
5831
- const dir = path22.join(cwd, rp);
5832
- if (!fs23.existsSync(dir)) continue;
5933
+ const dir = path23.join(cwd, rp);
5934
+ if (!fs24.existsSync(dir)) continue;
5833
5935
  let files;
5834
5936
  try {
5835
- files = fs23.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5937
+ files = fs24.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5836
5938
  } catch {
5837
5939
  continue;
5838
5940
  }
5839
5941
  for (const f of files) {
5840
5942
  try {
5841
- const content = fs23.readFileSync(path22.join(dir, f), "utf-8").slice(0, 5e3);
5943
+ const content = fs24.readFileSync(path23.join(dir, f), "utf-8").slice(0, 5e3);
5842
5944
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
5843
5945
  if (roleMatches) {
5844
5946
  for (const m of roleMatches) {
@@ -6048,8 +6150,21 @@ var dispatchClassified = async (ctx) => {
6048
6150
  if (!issueNumber) return;
6049
6151
  const classification = ctx.data.classification;
6050
6152
  if (!classification || !VALID_CLASSES2.has(classification)) return;
6153
+ const action = ctx.data.action;
6154
+ if (!action) return;
6051
6155
  const baseArg = typeof ctx.args.base === "string" && ctx.args.base.length > 0 ? ` --base ${ctx.args.base}` : "";
6052
- const body = `@kody ${classification}${baseArg}`;
6156
+ const dispatchLine = `@kody ${classification}${baseArg}`;
6157
+ const auditLine = ctx.data.classificationAudit ?? `\u{1F50E} kody classified as \`${classification}\``;
6158
+ const state = ctx.data.taskState ?? emptyState();
6159
+ const nextState = reduce(state, "classify", action, void 0);
6160
+ const stateBody = renderStateComment(nextState);
6161
+ ctx.data.taskState = nextState;
6162
+ ctx.data.taskStateRendered = stateBody;
6163
+ const body = `${dispatchLine}
6164
+
6165
+ ${auditLine}
6166
+
6167
+ ${stateBody}`;
6053
6168
  try {
6054
6169
  execFileSync13("gh", ["issue", "comment", String(issueNumber), "--body", body], {
6055
6170
  cwd: ctx.cwd,
@@ -6058,7 +6173,7 @@ var dispatchClassified = async (ctx) => {
6058
6173
  });
6059
6174
  } catch (err) {
6060
6175
  process.stderr.write(
6061
- `[kody dispatchClassified] failed to dispatch ${body}: ${err instanceof Error ? err.message : String(err)}
6176
+ `[kody dispatchClassified] failed to dispatch ${dispatchLine}: ${err instanceof Error ? err.message : String(err)}
6062
6177
  `
6063
6178
  );
6064
6179
  ctx.data.action = failedAction3("dispatch post failed");
@@ -6071,8 +6186,8 @@ function failedAction3(reason) {
6071
6186
  }
6072
6187
 
6073
6188
  // src/scripts/dispatchJobFileTicks.ts
6074
- import * as fs25 from "fs";
6075
- import * as path24 from "path";
6189
+ import * as fs26 from "fs";
6190
+ import * as path25 from "path";
6076
6191
 
6077
6192
  // src/scripts/jobFrontmatter.ts
6078
6193
  var SCHEDULE_EVERY_VALUES = [
@@ -6333,8 +6448,8 @@ var ContentsApiBackend = class {
6333
6448
  };
6334
6449
 
6335
6450
  // src/scripts/jobState/localFileBackend.ts
6336
- import * as fs24 from "fs";
6337
- import * as path23 from "path";
6451
+ import * as fs25 from "fs";
6452
+ import * as path24 from "path";
6338
6453
  var LocalFileBackend = class {
6339
6454
  name = "local-file";
6340
6455
  cwd;
@@ -6349,7 +6464,7 @@ var LocalFileBackend = class {
6349
6464
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
6350
6465
  this.cwd = opts.cwd;
6351
6466
  this.jobsDir = opts.jobsDir;
6352
- this.absDir = path23.join(opts.cwd, opts.jobsDir);
6467
+ this.absDir = path24.join(opts.cwd, opts.jobsDir);
6353
6468
  this.owner = opts.owner;
6354
6469
  this.repo = opts.repo;
6355
6470
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -6364,7 +6479,7 @@ var LocalFileBackend = class {
6364
6479
  `);
6365
6480
  return;
6366
6481
  }
6367
- fs24.mkdirSync(this.absDir, { recursive: true });
6482
+ fs25.mkdirSync(this.absDir, { recursive: true });
6368
6483
  const prefix = this.cacheKeyPrefix();
6369
6484
  const probeKey = `${prefix}probe-${Date.now()}`;
6370
6485
  try {
@@ -6393,7 +6508,7 @@ var LocalFileBackend = class {
6393
6508
  `);
6394
6509
  return;
6395
6510
  }
6396
- if (!fs24.existsSync(this.absDir)) {
6511
+ if (!fs25.existsSync(this.absDir)) {
6397
6512
  return;
6398
6513
  }
6399
6514
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -6409,11 +6524,11 @@ var LocalFileBackend = class {
6409
6524
  }
6410
6525
  load(slug) {
6411
6526
  const relPath = stateFilePath(this.jobsDir, slug);
6412
- const absPath = path23.join(this.cwd, relPath);
6413
- if (!fs24.existsSync(absPath)) {
6527
+ const absPath = path24.join(this.cwd, relPath);
6528
+ if (!fs25.existsSync(absPath)) {
6414
6529
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
6415
6530
  }
6416
- const raw = fs24.readFileSync(absPath, "utf-8");
6531
+ const raw = fs25.readFileSync(absPath, "utf-8");
6417
6532
  let parsed;
6418
6533
  try {
6419
6534
  parsed = JSON.parse(raw);
@@ -6430,10 +6545,10 @@ var LocalFileBackend = class {
6430
6545
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
6431
6546
  return false;
6432
6547
  }
6433
- const absPath = path23.join(this.cwd, loaded.path);
6434
- fs24.mkdirSync(path23.dirname(absPath), { recursive: true });
6548
+ const absPath = path24.join(this.cwd, loaded.path);
6549
+ fs25.mkdirSync(path24.dirname(absPath), { recursive: true });
6435
6550
  const body = JSON.stringify(next, null, 2) + "\n";
6436
- fs24.writeFileSync(absPath, body, "utf-8");
6551
+ fs25.writeFileSync(absPath, body, "utf-8");
6437
6552
  return true;
6438
6553
  }
6439
6554
  cacheKeyPrefix() {
@@ -6511,7 +6626,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
6511
6626
  await backend.hydrate();
6512
6627
  }
6513
6628
  try {
6514
- const slugs = listJobSlugs(path24.join(ctx.cwd, jobsDir));
6629
+ const slugs = listJobSlugs(path25.join(ctx.cwd, jobsDir));
6515
6630
  ctx.data.jobSlugCount = slugs.length;
6516
6631
  if (slugs.length === 0) {
6517
6632
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -6624,17 +6739,17 @@ function formatAgo(ms) {
6624
6739
  }
6625
6740
  function readJobFrontmatter(cwd, jobsDir, slug) {
6626
6741
  try {
6627
- const raw = fs25.readFileSync(path24.join(cwd, jobsDir, `${slug}.md`), "utf-8");
6742
+ const raw = fs26.readFileSync(path25.join(cwd, jobsDir, `${slug}.md`), "utf-8");
6628
6743
  return splitFrontmatter(raw).frontmatter;
6629
6744
  } catch {
6630
6745
  return {};
6631
6746
  }
6632
6747
  }
6633
6748
  function listJobSlugs(absDir) {
6634
- if (!fs25.existsSync(absDir)) return [];
6749
+ if (!fs26.existsSync(absDir)) return [];
6635
6750
  let entries;
6636
6751
  try {
6637
- entries = fs25.readdirSync(absDir, { withFileTypes: true });
6752
+ entries = fs26.readdirSync(absDir, { withFileTypes: true });
6638
6753
  } catch {
6639
6754
  return [];
6640
6755
  }
@@ -7387,7 +7502,7 @@ function ensureFeatureBranch(issueNumber, title, defaultBranch2, cwd, baseBranch
7387
7502
 
7388
7503
  // src/gha.ts
7389
7504
  import { execFileSync as execFileSync16 } from "child_process";
7390
- import * as fs26 from "fs";
7505
+ import * as fs27 from "fs";
7391
7506
  function getRunUrl() {
7392
7507
  const server = process.env.GITHUB_SERVER_URL;
7393
7508
  const repo = process.env.GITHUB_REPOSITORY;
@@ -7398,10 +7513,10 @@ function getRunUrl() {
7398
7513
  function reactToTriggerComment(cwd) {
7399
7514
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
7400
7515
  const eventPath = process.env.GITHUB_EVENT_PATH;
7401
- if (!eventPath || !fs26.existsSync(eventPath)) return;
7516
+ if (!eventPath || !fs27.existsSync(eventPath)) return;
7402
7517
  let event = null;
7403
7518
  try {
7404
- event = JSON.parse(fs26.readFileSync(eventPath, "utf-8"));
7519
+ event = JSON.parse(fs27.readFileSync(eventPath, "utf-8"));
7405
7520
  } catch {
7406
7521
  return;
7407
7522
  }
@@ -7694,22 +7809,22 @@ var handleAbandonedGoal = async (ctx) => {
7694
7809
 
7695
7810
  // src/scripts/initFlow.ts
7696
7811
  import { execFileSync as execFileSync18 } from "child_process";
7697
- import * as fs28 from "fs";
7698
- import * as path26 from "path";
7812
+ import * as fs29 from "fs";
7813
+ import * as path27 from "path";
7699
7814
 
7700
7815
  // src/scripts/loadQaGuide.ts
7701
- import * as fs27 from "fs";
7702
- import * as path25 from "path";
7816
+ import * as fs28 from "fs";
7817
+ import * as path26 from "path";
7703
7818
  var QA_GUIDE_REL_PATH = ".kody/qa-guide.md";
7704
7819
  var loadQaGuide = async (ctx) => {
7705
- const full = path25.join(ctx.cwd, QA_GUIDE_REL_PATH);
7706
- if (!fs27.existsSync(full)) {
7820
+ const full = path26.join(ctx.cwd, QA_GUIDE_REL_PATH);
7821
+ if (!fs28.existsSync(full)) {
7707
7822
  ctx.data.qaGuide = "";
7708
7823
  ctx.data.qaGuidePath = "";
7709
7824
  return;
7710
7825
  }
7711
7826
  try {
7712
- ctx.data.qaGuide = fs27.readFileSync(full, "utf-8");
7827
+ ctx.data.qaGuide = fs28.readFileSync(full, "utf-8");
7713
7828
  ctx.data.qaGuidePath = QA_GUIDE_REL_PATH;
7714
7829
  } catch {
7715
7830
  ctx.data.qaGuide = "";
@@ -7719,9 +7834,9 @@ var loadQaGuide = async (ctx) => {
7719
7834
 
7720
7835
  // src/scripts/initFlow.ts
7721
7836
  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";
7837
+ if (fs29.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
7838
+ if (fs29.existsSync(path27.join(cwd, "yarn.lock"))) return "yarn";
7839
+ if (fs29.existsSync(path27.join(cwd, "bun.lockb"))) return "bun";
7725
7840
  return "npm";
7726
7841
  }
7727
7842
  function qualityCommandsFor(pm) {
@@ -7843,48 +7958,48 @@ function performInit(cwd, force) {
7843
7958
  const pm = detectPackageManager(cwd);
7844
7959
  const ownerRepo = detectOwnerRepo(cwd);
7845
7960
  const defaultBranch2 = defaultBranchFromGit(cwd);
7846
- const configPath = path26.join(cwd, "kody.config.json");
7847
- if (fs28.existsSync(configPath) && !force) {
7961
+ const configPath = path27.join(cwd, "kody.config.json");
7962
+ if (fs29.existsSync(configPath) && !force) {
7848
7963
  skipped.push("kody.config.json");
7849
7964
  } else {
7850
7965
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
7851
- fs28.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7966
+ fs29.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
7852
7967
  `);
7853
7968
  wrote.push("kody.config.json");
7854
7969
  }
7855
- const workflowDir = path26.join(cwd, ".github", "workflows");
7856
- const workflowPath = path26.join(workflowDir, "kody.yml");
7857
- if (fs28.existsSync(workflowPath) && !force) {
7970
+ const workflowDir = path27.join(cwd, ".github", "workflows");
7971
+ const workflowPath = path27.join(workflowDir, "kody.yml");
7972
+ if (fs29.existsSync(workflowPath) && !force) {
7858
7973
  skipped.push(".github/workflows/kody.yml");
7859
7974
  } else {
7860
- fs28.mkdirSync(workflowDir, { recursive: true });
7861
- fs28.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
7975
+ fs29.mkdirSync(workflowDir, { recursive: true });
7976
+ fs29.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
7862
7977
  wrote.push(".github/workflows/kody.yml");
7863
7978
  }
7864
- const hasUi = fs28.existsSync(path26.join(cwd, "src/app")) || fs28.existsSync(path26.join(cwd, "app")) || fs28.existsSync(path26.join(cwd, "pages"));
7979
+ const hasUi = fs29.existsSync(path27.join(cwd, "src/app")) || fs29.existsSync(path27.join(cwd, "app")) || fs29.existsSync(path27.join(cwd, "pages"));
7865
7980
  if (hasUi) {
7866
- const qaGuidePath = path26.join(cwd, QA_GUIDE_REL_PATH);
7867
- if (fs28.existsSync(qaGuidePath) && !force) {
7981
+ const qaGuidePath = path27.join(cwd, QA_GUIDE_REL_PATH);
7982
+ if (fs29.existsSync(qaGuidePath) && !force) {
7868
7983
  skipped.push(QA_GUIDE_REL_PATH);
7869
7984
  } else {
7870
- fs28.mkdirSync(path26.dirname(qaGuidePath), { recursive: true });
7985
+ fs29.mkdirSync(path27.dirname(qaGuidePath), { recursive: true });
7871
7986
  const discovery = runQaDiscovery(cwd);
7872
- fs28.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
7987
+ fs29.writeFileSync(qaGuidePath, generateQaGuideTemplate(discovery));
7873
7988
  wrote.push(QA_GUIDE_REL_PATH);
7874
7989
  }
7875
7990
  }
7876
7991
  const builtinJobs = listBuiltinJobs();
7877
7992
  if (builtinJobs.length > 0) {
7878
- const jobsDir = path26.join(cwd, ".kody", "jobs");
7879
- fs28.mkdirSync(jobsDir, { recursive: true });
7993
+ const jobsDir = path27.join(cwd, ".kody", "jobs");
7994
+ fs29.mkdirSync(jobsDir, { recursive: true });
7880
7995
  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) {
7996
+ const rel = path27.join(".kody", "jobs", `${job.slug}.md`);
7997
+ const target = path27.join(cwd, rel);
7998
+ if (fs29.existsSync(target) && !force) {
7884
7999
  skipped.push(rel);
7885
8000
  continue;
7886
8001
  }
7887
- fs28.writeFileSync(target, fs28.readFileSync(job.filePath, "utf-8"));
8002
+ fs29.writeFileSync(target, fs29.readFileSync(job.filePath, "utf-8"));
7888
8003
  wrote.push(rel);
7889
8004
  }
7890
8005
  }
@@ -7896,12 +8011,12 @@ function performInit(cwd, force) {
7896
8011
  continue;
7897
8012
  }
7898
8013
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
7899
- const target = path26.join(workflowDir, `kody-${exe.name}.yml`);
7900
- if (fs28.existsSync(target) && !force) {
8014
+ const target = path27.join(workflowDir, `kody-${exe.name}.yml`);
8015
+ if (fs29.existsSync(target) && !force) {
7901
8016
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
7902
8017
  continue;
7903
8018
  }
7904
- fs28.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
8019
+ fs29.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
7905
8020
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
7906
8021
  }
7907
8022
  let labels;
@@ -7979,14 +8094,14 @@ init_loadConventions();
7979
8094
  init_loadCoverageRules();
7980
8095
 
7981
8096
  // src/goal/state.ts
7982
- import * as fs29 from "fs";
7983
- import * as path27 from "path";
8097
+ import * as fs30 from "fs";
8098
+ import * as path28 from "path";
7984
8099
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
7985
8100
  var GoalStateError = class extends Error {
7986
- constructor(path36, message) {
7987
- super(`Invalid goal state at ${path36}:
8101
+ constructor(path37, message) {
8102
+ super(`Invalid goal state at ${path37}:
7988
8103
  ${message}`);
7989
- this.path = path36;
8104
+ this.path = path37;
7990
8105
  this.name = "GoalStateError";
7991
8106
  }
7992
8107
  path;
@@ -8034,16 +8149,16 @@ function serializeGoalState(s) {
8034
8149
  `;
8035
8150
  }
8036
8151
  function goalStatePath(cwd, goalId) {
8037
- return path27.join(cwd, ".kody", "goals", goalId, "state.json");
8152
+ return path28.join(cwd, ".kody", "goals", goalId, "state.json");
8038
8153
  }
8039
8154
  function readGoalState(cwd, goalId) {
8040
8155
  const file = goalStatePath(cwd, goalId);
8041
- if (!fs29.existsSync(file)) {
8156
+ if (!fs30.existsSync(file)) {
8042
8157
  throw new GoalStateError(file, "file not found");
8043
8158
  }
8044
8159
  let raw;
8045
8160
  try {
8046
- raw = JSON.parse(fs29.readFileSync(file, "utf-8"));
8161
+ raw = JSON.parse(fs30.readFileSync(file, "utf-8"));
8047
8162
  } catch (err) {
8048
8163
  throw new GoalStateError(file, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
8049
8164
  }
@@ -8051,8 +8166,8 @@ function readGoalState(cwd, goalId) {
8051
8166
  }
8052
8167
  function writeGoalState(cwd, goalId, state) {
8053
8168
  const file = goalStatePath(cwd, goalId);
8054
- fs29.mkdirSync(path27.dirname(file), { recursive: true });
8055
- fs29.writeFileSync(file, serializeGoalState(state), "utf-8");
8169
+ fs30.mkdirSync(path28.dirname(file), { recursive: true });
8170
+ fs30.writeFileSync(file, serializeGoalState(state), "utf-8");
8056
8171
  }
8057
8172
  function nowIso() {
8058
8173
  return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -8149,8 +8264,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
8149
8264
  };
8150
8265
 
8151
8266
  // src/scripts/loadJobFromFile.ts
8152
- import * as fs30 from "fs";
8153
- import * as path28 from "path";
8267
+ import * as fs31 from "fs";
8268
+ import * as path29 from "path";
8154
8269
  var loadJobFromFile = async (ctx, _profile, args) => {
8155
8270
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
8156
8271
  const workersDir = String(args?.workersDir ?? ".kody/workers");
@@ -8159,23 +8274,23 @@ var loadJobFromFile = async (ctx, _profile, args) => {
8159
8274
  if (!slug) {
8160
8275
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
8161
8276
  }
8162
- const absPath = path28.join(ctx.cwd, jobsDir, `${slug}.md`);
8163
- if (!fs30.existsSync(absPath)) {
8277
+ const absPath = path29.join(ctx.cwd, jobsDir, `${slug}.md`);
8278
+ if (!fs31.existsSync(absPath)) {
8164
8279
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
8165
8280
  }
8166
- const raw = fs30.readFileSync(absPath, "utf-8");
8281
+ const raw = fs31.readFileSync(absPath, "utf-8");
8167
8282
  const { title, body } = parseJobFile(raw, slug);
8168
8283
  const workerSlug = (splitFrontmatter(raw).frontmatter.worker ?? "").trim();
8169
8284
  let workerTitle = "";
8170
8285
  let workerPersona = "";
8171
8286
  if (workerSlug) {
8172
- const workerPath = path28.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8173
- if (!fs30.existsSync(workerPath)) {
8287
+ const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8288
+ if (!fs31.existsSync(workerPath)) {
8174
8289
  throw new Error(
8175
8290
  `loadJobFromFile: job '${slug}' declares worker '${workerSlug}' but ${workerPath} does not exist`
8176
8291
  );
8177
8292
  }
8178
- const workerRaw = fs30.readFileSync(workerPath, "utf-8");
8293
+ const workerRaw = fs31.readFileSync(workerPath, "utf-8");
8179
8294
  const parsed = parseJobFile(workerRaw, workerSlug);
8180
8295
  workerTitle = parsed.title;
8181
8296
  workerPersona = parsed.body;
@@ -8213,19 +8328,19 @@ function humanizeSlug(slug) {
8213
8328
  }
8214
8329
 
8215
8330
  // src/scripts/loadWorkerAdhoc.ts
8216
- import * as fs31 from "fs";
8217
- import * as path29 from "path";
8331
+ import * as fs32 from "fs";
8332
+ import * as path30 from "path";
8218
8333
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
8219
8334
  const workersDir = String(args?.workersDir ?? ".kody/workers");
8220
8335
  const workerSlug = String(ctx.args.worker ?? "").trim();
8221
8336
  if (!workerSlug) {
8222
8337
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
8223
8338
  }
8224
- const workerPath = path29.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8225
- if (!fs31.existsSync(workerPath)) {
8339
+ const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
8340
+ if (!fs32.existsSync(workerPath)) {
8226
8341
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
8227
8342
  }
8228
- const { title, body } = parsePersona(fs31.readFileSync(workerPath, "utf-8"), workerSlug);
8343
+ const { title, body } = parsePersona(fs32.readFileSync(workerPath, "utf-8"), workerSlug);
8229
8344
  const message = resolveMessage(ctx.args.message);
8230
8345
  if (!message) {
8231
8346
  throw new Error(
@@ -8245,9 +8360,9 @@ function resolveMessage(messageArg) {
8245
8360
  }
8246
8361
  function readCommentBody() {
8247
8362
  const eventPath = process.env.GITHUB_EVENT_PATH;
8248
- if (!eventPath || !fs31.existsSync(eventPath)) return "";
8363
+ if (!eventPath || !fs32.existsSync(eventPath)) return "";
8249
8364
  try {
8250
- const event = JSON.parse(fs31.readFileSync(eventPath, "utf-8"));
8365
+ const event = JSON.parse(fs32.readFileSync(eventPath, "utf-8"));
8251
8366
  return String(event.comment?.body ?? "");
8252
8367
  } catch {
8253
8368
  return "";
@@ -8293,8 +8408,8 @@ init_loadPriorArt();
8293
8408
  init_events();
8294
8409
 
8295
8410
  // src/taskContext.ts
8296
- import * as fs33 from "fs";
8297
- import * as path31 from "path";
8411
+ import * as fs34 from "fs";
8412
+ import * as path32 from "path";
8298
8413
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
8299
8414
  function buildTaskContext(args) {
8300
8415
  return {
@@ -8310,10 +8425,10 @@ function buildTaskContext(args) {
8310
8425
  }
8311
8426
  function persistTaskContext(cwd, ctx) {
8312
8427
  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)}
8428
+ const dir = path32.join(cwd, ".kody", "runs", ctx.runId);
8429
+ fs34.mkdirSync(dir, { recursive: true });
8430
+ const file = path32.join(dir, "task-context.json");
8431
+ fs34.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
8317
8432
  `);
8318
8433
  return file;
8319
8434
  } catch (err) {
@@ -9051,7 +9166,6 @@ var recordClassification = async (ctx) => {
9051
9166
  ctx.output.reason = "classify: no decision";
9052
9167
  return;
9053
9168
  }
9054
- tryAuditComment(issueNumber, `\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`, ctx.cwd);
9055
9169
  ctx.data.action = makeAction3(`CLASSIFIED_AS_${classification.toUpperCase()}`, {
9056
9170
  classification,
9057
9171
  reason: reason ?? "",
@@ -9059,6 +9173,7 @@ var recordClassification = async (ctx) => {
9059
9173
  });
9060
9174
  ctx.data.classification = classification;
9061
9175
  ctx.data.classificationReason = reason ?? "";
9176
+ ctx.data.classificationAudit = `\u{1F50E} kody classified as \`${classification}\`${reason ? ` \u2014 ${reason}` : ""}`;
9062
9177
  };
9063
9178
  function parseClassification(prSummary) {
9064
9179
  if (!prSummary) return null;
@@ -9676,8 +9791,8 @@ function resolveBaseOverride(value) {
9676
9791
 
9677
9792
  // src/scripts/runTickScript.ts
9678
9793
  import { spawnSync } from "child_process";
9679
- import * as fs34 from "fs";
9680
- import * as path32 from "path";
9794
+ import * as fs35 from "fs";
9795
+ import * as path33 from "path";
9681
9796
  var runTickScript = async (ctx, _profile, args) => {
9682
9797
  ctx.skipAgent = true;
9683
9798
  const jobsDir = String(args?.jobsDir ?? ".kody/jobs");
@@ -9689,13 +9804,13 @@ var runTickScript = async (ctx, _profile, args) => {
9689
9804
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
9690
9805
  return;
9691
9806
  }
9692
- const jobPath = path32.join(ctx.cwd, jobsDir, `${slug}.md`);
9693
- if (!fs34.existsSync(jobPath)) {
9807
+ const jobPath = path33.join(ctx.cwd, jobsDir, `${slug}.md`);
9808
+ if (!fs35.existsSync(jobPath)) {
9694
9809
  ctx.output.exitCode = 99;
9695
9810
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
9696
9811
  return;
9697
9812
  }
9698
- const raw = fs34.readFileSync(jobPath, "utf-8");
9813
+ const raw = fs35.readFileSync(jobPath, "utf-8");
9699
9814
  const { frontmatter } = splitFrontmatter(raw);
9700
9815
  const tickScript = frontmatter.tickScript;
9701
9816
  if (!tickScript) {
@@ -9703,8 +9818,8 @@ var runTickScript = async (ctx, _profile, args) => {
9703
9818
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
9704
9819
  return;
9705
9820
  }
9706
- const scriptPath = path32.isAbsolute(tickScript) ? tickScript : path32.join(ctx.cwd, tickScript);
9707
- if (!fs34.existsSync(scriptPath)) {
9821
+ const scriptPath = path33.isAbsolute(tickScript) ? tickScript : path33.join(ctx.cwd, tickScript);
9822
+ if (!fs35.existsSync(scriptPath)) {
9708
9823
  ctx.output.exitCode = 99;
9709
9824
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
9710
9825
  return;
@@ -10726,7 +10841,7 @@ var writeJobStateFile = async (ctx, _profile, _agentResult, args) => {
10726
10841
  };
10727
10842
 
10728
10843
  // src/scripts/writeRunSummary.ts
10729
- import * as fs35 from "fs";
10844
+ import * as fs36 from "fs";
10730
10845
  var writeRunSummary = async (ctx, profile) => {
10731
10846
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
10732
10847
  if (!summaryPath) return;
@@ -10748,7 +10863,7 @@ var writeRunSummary = async (ctx, profile) => {
10748
10863
  if (reason) lines.push(`- **Reason:** ${reason}`);
10749
10864
  lines.push("");
10750
10865
  try {
10751
- fs35.appendFileSync(summaryPath, `${lines.join("\n")}
10866
+ fs36.appendFileSync(summaryPath, `${lines.join("\n")}
10752
10867
  `);
10753
10868
  } catch {
10754
10869
  }
@@ -10974,9 +11089,23 @@ async function runExecutable(profileName, input) {
10974
11089
  data: { ...input.preloadedData ?? {} },
10975
11090
  output: { exitCode: 0 }
10976
11091
  };
10977
- const ndjsonDir = path33.join(input.cwd, ".kody");
11092
+ const taskTarget = args.issue ?? args.pr;
11093
+ const taskArtifacts = typeof taskTarget === "number" && Number.isFinite(taskTarget) ? (() => {
11094
+ const taskType = args.issue ? "issue" : "pr";
11095
+ const paths = prepareTaskArtifactsDir(input.cwd, taskTarget);
11096
+ return {
11097
+ ...paths,
11098
+ taskType,
11099
+ promptAddendum: taskArtifactsPromptAddendum({
11100
+ taskId: paths.taskId,
11101
+ taskType,
11102
+ relDir: paths.relDir
11103
+ })
11104
+ };
11105
+ })() : null;
11106
+ const ndjsonDir = path34.join(input.cwd, ".kody");
10978
11107
  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);
11108
+ const externalPlugins = (profile.claudeCode.plugins ?? []).map((p) => path34.isAbsolute(p) ? p : path34.resolve(profile.dir, p)).filter((p) => p.length > 0);
10980
11109
  const syntheticPath = ctx.data.syntheticPluginPath;
10981
11110
  const pluginPaths = [...externalPlugins, ...syntheticPath ? [syntheticPath] : []];
10982
11111
  return runAgent({
@@ -10994,7 +11123,7 @@ async function runExecutable(profileName, input) {
10994
11123
  maxTurns: profile.claudeCode.maxTurns,
10995
11124
  maxThinkingTokens: profile.claudeCode.maxThinkingTokens,
10996
11125
  maxTurnTimeoutMs: typeof profile.claudeCode.maxTurnTimeoutSec === "number" ? Math.floor(profile.claudeCode.maxTurnTimeoutSec * 1e3) : void 0,
10997
- systemPromptAppend: profile.claudeCode.systemPromptAppend,
11126
+ systemPromptAppend: [profile.claudeCode.systemPromptAppend, taskArtifacts?.promptAddendum].filter((s) => typeof s === "string" && s.length > 0).join("\n\n") || void 0,
10998
11127
  cacheable: profile.claudeCode.cacheable,
10999
11128
  enableVerifyTool: profile.claudeCode.enableVerifyTool,
11000
11129
  verifyToolMaxAttempts: profile.claudeCode.verifyAttempts ?? null,
@@ -11149,6 +11278,18 @@ async function runExecutable(profileName, input) {
11149
11278
  });
11150
11279
  } finally {
11151
11280
  clearStampedLifecycleLabels(profile, ctx);
11281
+ if (taskArtifacts) {
11282
+ try {
11283
+ const missing2 = verifyTaskArtifacts(taskArtifacts.absDir);
11284
+ if (missing2.length > 0) {
11285
+ process.stderr.write(
11286
+ `[task-artifacts] task ${taskArtifacts.taskId} missing: ${missing2.join(", ")}
11287
+ `
11288
+ );
11289
+ }
11290
+ } catch {
11291
+ }
11292
+ }
11152
11293
  try {
11153
11294
  litellm?.kill();
11154
11295
  } catch {
@@ -11171,7 +11312,7 @@ function clearStampedLifecycleLabels(profile, ctx) {
11171
11312
  function getProfileInputsForChild(profileName, _cwd) {
11172
11313
  try {
11173
11314
  const profilePath = resolveProfilePath(profileName);
11174
- if (!fs36.existsSync(profilePath)) return null;
11315
+ if (!fs37.existsSync(profilePath)) return null;
11175
11316
  return loadProfile(profilePath).inputs;
11176
11317
  } catch {
11177
11318
  return null;
@@ -11180,17 +11321,17 @@ function getProfileInputsForChild(profileName, _cwd) {
11180
11321
  function resolveProfilePath(profileName) {
11181
11322
  const found = resolveExecutable(profileName);
11182
11323
  if (found) return found;
11183
- const here = path33.dirname(new URL(import.meta.url).pathname);
11324
+ const here = path34.dirname(new URL(import.meta.url).pathname);
11184
11325
  const candidates = [
11185
- path33.join(here, "executables", profileName, "profile.json"),
11326
+ path34.join(here, "executables", profileName, "profile.json"),
11186
11327
  // same-dir sibling (dev)
11187
- path33.join(here, "..", "executables", profileName, "profile.json"),
11328
+ path34.join(here, "..", "executables", profileName, "profile.json"),
11188
11329
  // up one (prod: dist/bin → dist/executables)
11189
- path33.join(here, "..", "src", "executables", profileName, "profile.json")
11330
+ path34.join(here, "..", "src", "executables", profileName, "profile.json")
11190
11331
  // fallback
11191
11332
  ];
11192
11333
  for (const c of candidates) {
11193
- if (fs36.existsSync(c)) return c;
11334
+ if (fs37.existsSync(c)) return c;
11194
11335
  }
11195
11336
  return candidates[0];
11196
11337
  }
@@ -11290,8 +11431,8 @@ function resolveShellTimeoutMs(entry) {
11290
11431
  var SIGKILL_GRACE_MS = 5e3;
11291
11432
  async function runShellEntry(entry, ctx, profile) {
11292
11433
  const shellName = entry.shell;
11293
- const shellPath = path33.join(profile.dir, shellName);
11294
- if (!fs36.existsSync(shellPath)) {
11434
+ const shellPath = path34.join(profile.dir, shellName);
11435
+ if (!fs37.existsSync(shellPath)) {
11295
11436
  ctx.skipAgent = true;
11296
11437
  ctx.output.exitCode = 99;
11297
11438
  ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
@@ -11770,9 +11911,9 @@ function resolveAuthToken(env = process.env) {
11770
11911
  return token;
11771
11912
  }
11772
11913
  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";
11914
+ if (fs38.existsSync(path35.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
11915
+ if (fs38.existsSync(path35.join(cwd, "yarn.lock"))) return "yarn";
11916
+ if (fs38.existsSync(path35.join(cwd, "bun.lockb"))) return "bun";
11776
11917
  return "npm";
11777
11918
  }
11778
11919
  function shellOut(cmd, args, cwd, stream = true) {
@@ -11859,11 +12000,11 @@ function configureGitIdentity(cwd) {
11859
12000
  }
11860
12001
  function postFailureTail(issueNumber, cwd, reason) {
11861
12002
  if (!issueNumber) return;
11862
- const logPath = path34.join(cwd, ".kody", "last-run.jsonl");
12003
+ const logPath = path35.join(cwd, ".kody", "last-run.jsonl");
11863
12004
  let tail = "";
11864
12005
  try {
11865
- if (fs37.existsSync(logPath)) {
11866
- const content = fs37.readFileSync(logPath, "utf-8");
12006
+ if (fs38.existsSync(logPath)) {
12007
+ const content = fs38.readFileSync(logPath, "utf-8");
11867
12008
  tail = content.slice(-3e3);
11868
12009
  }
11869
12010
  } catch {
@@ -11888,7 +12029,7 @@ async function runCi(argv) {
11888
12029
  return 0;
11889
12030
  }
11890
12031
  const args = parseCiArgs(argv);
11891
- const cwd = args.cwd ? path34.resolve(args.cwd) : process.cwd();
12032
+ const cwd = args.cwd ? path35.resolve(args.cwd) : process.cwd();
11892
12033
  let earlyConfig;
11893
12034
  try {
11894
12035
  earlyConfig = loadConfig(cwd);
@@ -11898,9 +12039,9 @@ async function runCi(argv) {
11898
12039
  const eventName = process.env.GITHUB_EVENT_NAME;
11899
12040
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
11900
12041
  let manualWorkflowDispatch = false;
11901
- if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs37.existsSync(dispatchEventPath)) {
12042
+ if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs38.existsSync(dispatchEventPath)) {
11902
12043
  try {
11903
- const evt = JSON.parse(fs37.readFileSync(dispatchEventPath, "utf-8"));
12044
+ const evt = JSON.parse(fs38.readFileSync(dispatchEventPath, "utf-8"));
11904
12045
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
11905
12046
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
11906
12047
  manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
@@ -12159,9 +12300,9 @@ function parseChatArgs(argv, env = process.env) {
12159
12300
  return result;
12160
12301
  }
12161
12302
  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)));
12303
+ const sessionFile = path36.relative(cwd, sessionFilePath(cwd, sessionId));
12304
+ const eventsFile = path36.relative(cwd, eventsFilePath(cwd, sessionId));
12305
+ const paths = [sessionFile, eventsFile].filter((p) => fs39.existsSync(path36.join(cwd, p)));
12165
12306
  if (paths.length === 0) return;
12166
12307
  const opts = { cwd, stdio: verbose ? "inherit" : "pipe" };
12167
12308
  try {
@@ -12199,7 +12340,7 @@ async function runChat(argv) {
12199
12340
  ${CHAT_HELP}`);
12200
12341
  return 64;
12201
12342
  }
12202
- const cwd = args.cwd ? path35.resolve(args.cwd) : process.cwd();
12343
+ const cwd = args.cwd ? path36.resolve(args.cwd) : process.cwd();
12203
12344
  const sessionId = args.sessionId;
12204
12345
  const unpackedSecrets = unpackAllSecrets();
12205
12346
  if (unpackedSecrets > 0) {
@@ -12251,7 +12392,7 @@ ${CHAT_HELP}`);
12251
12392
  const sink = buildSink(cwd, sessionId, args.dashboardUrl);
12252
12393
  const meta = readMeta(sessionFile);
12253
12394
  process.stdout.write(
12254
- `\u2192 kody:chat: session file=${sessionFile} exists=${fs38.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
12395
+ `\u2192 kody:chat: session file=${sessionFile} exists=${fs39.existsSync(sessionFile)} meta=${meta ? meta.mode : "none"}
12255
12396
  `
12256
12397
  );
12257
12398
  try {