@kody-ade/kody-engine 0.4.204-next.2 → 0.4.204-next.4

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
@@ -1072,16 +1072,16 @@ var init_fetchRepoMcp = __esm({
1072
1072
  });
1073
1073
 
1074
1074
  // src/prompt.ts
1075
- import * as fs15 from "fs";
1076
- import * as path14 from "path";
1075
+ import * as fs17 from "fs";
1076
+ import * as path16 from "path";
1077
1077
  function loadProjectConventions(projectDir) {
1078
1078
  const out = [];
1079
1079
  for (const rel of CONVENTION_FILES) {
1080
- const abs = path14.join(projectDir, rel);
1081
- if (!fs15.existsSync(abs)) continue;
1080
+ const abs = path16.join(projectDir, rel);
1081
+ if (!fs17.existsSync(abs)) continue;
1082
1082
  let content;
1083
1083
  try {
1084
- content = fs15.readFileSync(abs, "utf-8");
1084
+ content = fs17.readFileSync(abs, "utf-8");
1085
1085
  } catch {
1086
1086
  continue;
1087
1087
  }
@@ -1316,28 +1316,28 @@ var loadMemoryContext_exports = {};
1316
1316
  __export(loadMemoryContext_exports, {
1317
1317
  loadMemoryContext: () => loadMemoryContext
1318
1318
  });
1319
- import * as fs16 from "fs";
1320
- import * as path15 from "path";
1319
+ import * as fs18 from "fs";
1320
+ import * as path17 from "path";
1321
1321
  function collectPages(memoryAbs) {
1322
1322
  const out = [];
1323
1323
  walkMd(memoryAbs, (file) => {
1324
1324
  let stat;
1325
1325
  try {
1326
- stat = fs16.statSync(file);
1326
+ stat = fs18.statSync(file);
1327
1327
  } catch {
1328
1328
  return;
1329
1329
  }
1330
1330
  let raw;
1331
1331
  try {
1332
- raw = fs16.readFileSync(file, "utf-8");
1332
+ raw = fs18.readFileSync(file, "utf-8");
1333
1333
  } catch {
1334
1334
  return;
1335
1335
  }
1336
1336
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---/);
1337
- const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path15.basename(file, ".md");
1337
+ const title = fm?.[1]?.match(/^title:\s*(.+)$/m)?.[1]?.trim() ?? path17.basename(file, ".md");
1338
1338
  const updated = fm?.[1]?.match(/^updated:\s*([0-9T:.+\-Z]+)/m)?.[1]?.trim() ?? "";
1339
1339
  out.push({
1340
- relPath: path15.relative(memoryAbs, file),
1340
+ relPath: path17.relative(memoryAbs, file),
1341
1341
  title,
1342
1342
  updated,
1343
1343
  content: raw.length > PER_PAGE_MAX_BYTES ? raw.slice(0, PER_PAGE_MAX_BYTES) + TRUNCATED_SUFFIX2 : raw,
@@ -1405,16 +1405,16 @@ function walkMd(root, visit) {
1405
1405
  const dir = stack.pop();
1406
1406
  let names;
1407
1407
  try {
1408
- names = fs16.readdirSync(dir);
1408
+ names = fs18.readdirSync(dir);
1409
1409
  } catch {
1410
1410
  continue;
1411
1411
  }
1412
1412
  for (const name of names) {
1413
1413
  if (name.startsWith(".")) continue;
1414
- const full = path15.join(dir, name);
1414
+ const full = path17.join(dir, name);
1415
1415
  let stat;
1416
1416
  try {
1417
- stat = fs16.statSync(full);
1417
+ stat = fs18.statSync(full);
1418
1418
  } catch {
1419
1419
  continue;
1420
1420
  }
@@ -1437,8 +1437,8 @@ var init_loadMemoryContext = __esm({
1437
1437
  TRUNCATED_SUFFIX2 = "\n\n\u2026 (truncated)";
1438
1438
  loadMemoryContext = async (ctx) => {
1439
1439
  if (typeof ctx.data.memoryContext === "string") return;
1440
- const memoryAbs = path15.join(ctx.cwd, MEMORY_DIR_RELATIVE);
1441
- if (!fs16.existsSync(memoryAbs)) {
1440
+ const memoryAbs = path17.join(ctx.cwd, MEMORY_DIR_RELATIVE);
1441
+ if (!fs18.existsSync(memoryAbs)) {
1442
1442
  ctx.data.memoryContext = "";
1443
1443
  return;
1444
1444
  }
@@ -1483,7 +1483,7 @@ var init_loadCoverageRules = __esm({
1483
1483
  // package.json
1484
1484
  var package_default = {
1485
1485
  name: "@kody-ade/kody-engine",
1486
- version: "0.4.204-next.2",
1486
+ version: "0.4.204-next.4",
1487
1487
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
1488
1488
  license: "MIT",
1489
1489
  type: "module",
@@ -3726,11 +3726,11 @@ import * as path39 from "path";
3726
3726
  // src/container.ts
3727
3727
  init_events();
3728
3728
  import { execFileSync as execFileSync5 } from "child_process";
3729
- import * as fs17 from "fs";
3729
+ import * as fs19 from "fs";
3730
3730
 
3731
3731
  // src/profile.ts
3732
- import * as fs14 from "fs";
3733
- import * as path13 from "path";
3732
+ import * as fs16 from "fs";
3733
+ import * as path15 from "path";
3734
3734
 
3735
3735
  // src/profile-error.ts
3736
3736
  var ProfileError = class extends Error {
@@ -3870,6 +3870,150 @@ function applyLifecycle(profile, profilePath) {
3870
3870
  expander(profile, profilePath);
3871
3871
  }
3872
3872
 
3873
+ // src/subagents.ts
3874
+ import * as fs15 from "fs";
3875
+ import * as path14 from "path";
3876
+
3877
+ // src/scripts/buildSyntheticPlugin.ts
3878
+ import * as fs14 from "fs";
3879
+ import * as os2 from "os";
3880
+ import * as path13 from "path";
3881
+ function getPluginsCatalogRoot() {
3882
+ const here = path13.dirname(new URL(import.meta.url).pathname);
3883
+ const candidates = [
3884
+ path13.join(here, "..", "plugins"),
3885
+ // dev: src/scripts → src/plugins
3886
+ path13.join(here, "..", "..", "plugins"),
3887
+ // built: dist/scripts → dist/plugins
3888
+ path13.join(here, "..", "..", "src", "plugins")
3889
+ // fallback
3890
+ ];
3891
+ for (const c of candidates) {
3892
+ if (fs14.existsSync(c) && fs14.statSync(c).isDirectory()) return c;
3893
+ }
3894
+ return candidates[0];
3895
+ }
3896
+ var buildSyntheticPlugin = async (ctx, profile) => {
3897
+ const cc = profile.claudeCode;
3898
+ const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
3899
+ if (!needsSynthetic) return;
3900
+ const catalog = getPluginsCatalogRoot();
3901
+ const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3902
+ const root = path13.join(os2.tmpdir(), `kody-synth-${runId}`);
3903
+ fs14.mkdirSync(path13.join(root, ".claude-plugin"), { recursive: true });
3904
+ const resolvePart = (bucket, entry) => {
3905
+ const local = path13.join(profile.dir, bucket, entry);
3906
+ if (fs14.existsSync(local)) return local;
3907
+ const central = path13.join(catalog, bucket, entry);
3908
+ if (fs14.existsSync(central)) return central;
3909
+ throw new Error(
3910
+ `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
3911
+ );
3912
+ };
3913
+ if (cc.skills.length > 0) {
3914
+ const dst = path13.join(root, "skills");
3915
+ fs14.mkdirSync(dst, { recursive: true });
3916
+ for (const name of cc.skills) {
3917
+ copyDir(resolvePart("skills", name), path13.join(dst, name));
3918
+ }
3919
+ }
3920
+ if (cc.commands.length > 0) {
3921
+ const dst = path13.join(root, "commands");
3922
+ fs14.mkdirSync(dst, { recursive: true });
3923
+ for (const name of cc.commands) {
3924
+ fs14.copyFileSync(resolvePart("commands", `${name}.md`), path13.join(dst, `${name}.md`));
3925
+ }
3926
+ }
3927
+ if (cc.hooks.length > 0) {
3928
+ const dst = path13.join(root, "hooks");
3929
+ fs14.mkdirSync(dst, { recursive: true });
3930
+ const merged = { hooks: {} };
3931
+ for (const name of cc.hooks) {
3932
+ const src = resolvePart("hooks", `${name}.json`);
3933
+ const parsed = JSON.parse(fs14.readFileSync(src, "utf-8"));
3934
+ for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
3935
+ if (!Array.isArray(entries)) continue;
3936
+ if (!merged.hooks[event]) merged.hooks[event] = [];
3937
+ merged.hooks[event].push(...entries);
3938
+ }
3939
+ }
3940
+ fs14.writeFileSync(path13.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
3941
+ `);
3942
+ }
3943
+ const manifest = {
3944
+ name: `kody-synth-${profile.name}`,
3945
+ version: "1.0.0",
3946
+ description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
3947
+ };
3948
+ if (cc.skills.length > 0) manifest.skills = ["./skills/"];
3949
+ if (cc.commands.length > 0) manifest.commands = ["./commands/"];
3950
+ fs14.writeFileSync(path13.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
3951
+ `);
3952
+ ctx.data.syntheticPluginPath = root;
3953
+ };
3954
+ function copyDir(src, dst) {
3955
+ fs14.mkdirSync(dst, { recursive: true });
3956
+ for (const ent of fs14.readdirSync(src, { withFileTypes: true })) {
3957
+ const s = path13.join(src, ent.name);
3958
+ const d = path13.join(dst, ent.name);
3959
+ if (ent.isDirectory()) copyDir(s, d);
3960
+ else if (ent.isFile()) fs14.copyFileSync(s, d);
3961
+ }
3962
+ }
3963
+
3964
+ // src/subagents.ts
3965
+ function splitFrontmatter(raw) {
3966
+ const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
3967
+ if (!match) return { fm: {}, body: raw.trim() };
3968
+ const fm = {};
3969
+ for (const line of match[1].split("\n")) {
3970
+ const idx = line.indexOf(":");
3971
+ if (idx === -1) continue;
3972
+ fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
3973
+ }
3974
+ return { fm, body: (match[2] ?? "").trim() };
3975
+ }
3976
+ function resolveAgentFile(profileDir, name) {
3977
+ const local = path14.join(profileDir, "agents", `${name}.md`);
3978
+ if (fs15.existsSync(local)) return local;
3979
+ const central = path14.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
3980
+ if (fs15.existsSync(central)) return central;
3981
+ throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
3982
+ }
3983
+ function captureSubagentTemplates(profile) {
3984
+ const names = profile.claudeCode.subagents;
3985
+ if (!names || names.length === 0) return {};
3986
+ const out = {};
3987
+ for (const name of names) {
3988
+ try {
3989
+ out[name] = fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
3990
+ } catch {
3991
+ }
3992
+ }
3993
+ return out;
3994
+ }
3995
+ function loadSubagents(profile) {
3996
+ const names = profile.claudeCode.subagents;
3997
+ if (!names || names.length === 0) return void 0;
3998
+ const agents = {};
3999
+ for (const name of names) {
4000
+ const raw = profile.subagentTemplates?.[name] ?? fs15.readFileSync(resolveAgentFile(profile.dir, name), "utf-8");
4001
+ const { fm, body } = splitFrontmatter(raw);
4002
+ if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
4003
+ const def = {
4004
+ description: fm.description ?? `Subagent ${name}`,
4005
+ prompt: body
4006
+ };
4007
+ if (fm.tools) {
4008
+ const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
4009
+ if (tools.length > 0) def.tools = tools;
4010
+ }
4011
+ if (fm.model) def.model = fm.model;
4012
+ agents[fm.name || name] = def;
4013
+ }
4014
+ return agents;
4015
+ }
4016
+
3873
4017
  // src/profile.ts
3874
4018
  var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
3875
4019
  var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
@@ -3879,6 +4023,7 @@ var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementin
3879
4023
  var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
3880
4024
  "name",
3881
4025
  "staff",
4026
+ "every",
3882
4027
  "describe",
3883
4028
  "role",
3884
4029
  "kind",
@@ -3902,12 +4047,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
3902
4047
  "preloadContext"
3903
4048
  ]);
3904
4049
  function loadProfile(profilePath) {
3905
- if (!fs14.existsSync(profilePath)) {
4050
+ if (!fs16.existsSync(profilePath)) {
3906
4051
  throw new ProfileError(profilePath, "file not found");
3907
4052
  }
3908
4053
  let raw;
3909
4054
  try {
3910
- raw = JSON.parse(fs14.readFileSync(profilePath, "utf-8"));
4055
+ raw = JSON.parse(fs16.readFileSync(profilePath, "utf-8"));
3911
4056
  } catch (err) {
3912
4057
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
3913
4058
  }
@@ -3918,7 +4063,7 @@ function loadProfile(profilePath) {
3918
4063
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
3919
4064
  if (unknownKeys.length > 0) {
3920
4065
  process.stderr.write(
3921
- `[kody profile] ${path13.basename(path13.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
4066
+ `[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
3922
4067
  `
3923
4068
  );
3924
4069
  }
@@ -3960,6 +4105,8 @@ function loadProfile(profilePath) {
3960
4105
  describe: typeof r.describe === "string" ? r.describe : "",
3961
4106
  // Optional persona to run as. Empty/blank string → undefined (no persona).
3962
4107
  staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
4108
+ // Optional recurrence cadence (scheduled duty). Blank → undefined (on-demand).
4109
+ every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
3963
4110
  role,
3964
4111
  kind,
3965
4112
  schedule: typeof r.schedule === "string" ? r.schedule : void 0,
@@ -3981,27 +4128,28 @@ function loadProfile(profilePath) {
3981
4128
  // Phase 5 in-process handoff opt-in. Default false; containers
3982
4129
  // flip to true after end-to-end verification.
3983
4130
  preloadContext: r.preloadContext === true,
3984
- dir: path13.dirname(profilePath),
3985
- promptTemplates: readPromptTemplates(path13.dirname(profilePath))
4131
+ dir: path15.dirname(profilePath),
4132
+ promptTemplates: readPromptTemplates(path15.dirname(profilePath))
3986
4133
  };
3987
4134
  if (lifecycle) {
3988
4135
  applyLifecycle(profile, profilePath);
3989
4136
  }
4137
+ profile.subagentTemplates = captureSubagentTemplates(profile);
3990
4138
  return profile;
3991
4139
  }
3992
4140
  function readPromptTemplates(dir) {
3993
4141
  const out = {};
3994
4142
  const read = (p) => {
3995
4143
  try {
3996
- out[p] = fs14.readFileSync(p, "utf-8");
4144
+ out[p] = fs16.readFileSync(p, "utf-8");
3997
4145
  } catch {
3998
4146
  }
3999
4147
  };
4000
- read(path13.join(dir, "prompt.md"));
4148
+ read(path15.join(dir, "prompt.md"));
4001
4149
  try {
4002
- const promptsDir = path13.join(dir, "prompts");
4003
- for (const ent of fs14.readdirSync(promptsDir)) {
4004
- if (ent.endsWith(".md")) read(path13.join(promptsDir, ent));
4150
+ const promptsDir = path15.join(dir, "prompts");
4151
+ for (const ent of fs16.readdirSync(promptsDir)) {
4152
+ if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
4005
4153
  }
4006
4154
  } catch {
4007
4155
  }
@@ -4482,7 +4630,7 @@ var CONTAINER_MAX_ITERATIONS = 50;
4482
4630
  function getProfileInputsForChild(profileName, _cwd) {
4483
4631
  try {
4484
4632
  const profilePath = resolveProfilePath(profileName);
4485
- if (!fs17.existsSync(profilePath)) return null;
4633
+ if (!fs19.existsSync(profilePath)) return null;
4486
4634
  return loadProfile(profilePath).inputs;
4487
4635
  } catch {
4488
4636
  return null;
@@ -4926,9 +5074,9 @@ function errMsg(err) {
4926
5074
 
4927
5075
  // src/litellm.ts
4928
5076
  import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
4929
- import * as fs18 from "fs";
4930
- import * as os2 from "os";
4931
- import * as path16 from "path";
5077
+ import * as fs20 from "fs";
5078
+ import * as os3 from "os";
5079
+ import * as path18 from "path";
4932
5080
  async function checkLitellmHealth(url) {
4933
5081
  try {
4934
5082
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -4980,13 +5128,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4980
5128
  let child;
4981
5129
  let logPath;
4982
5130
  const spawnProxy = () => {
4983
- const configPath = path16.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
4984
- fs18.writeFileSync(configPath, generateLitellmConfigYaml(model));
5131
+ const configPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
5132
+ fs20.writeFileSync(configPath, generateLitellmConfigYaml(model));
4985
5133
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
4986
- const nextLogPath = path16.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
4987
- const outFd = fs18.openSync(nextLogPath, "w");
5134
+ const nextLogPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.log`);
5135
+ const outFd = fs20.openSync(nextLogPath, "w");
4988
5136
  child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
4989
- fs18.closeSync(outFd);
5137
+ fs20.closeSync(outFd);
4990
5138
  logPath = nextLogPath;
4991
5139
  };
4992
5140
  const waitForHealth = async () => {
@@ -5000,7 +5148,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
5000
5148
  const readLogTail = () => {
5001
5149
  if (!logPath) return "";
5002
5150
  try {
5003
- return fs18.readFileSync(logPath, "utf-8").slice(-2e3);
5151
+ return fs20.readFileSync(logPath, "utf-8").slice(-2e3);
5004
5152
  } catch {
5005
5153
  return "";
5006
5154
  }
@@ -5052,10 +5200,10 @@ ${tail}`
5052
5200
  return { url, kill: killChild, isHealthy, ensureHealthy };
5053
5201
  }
5054
5202
  function readDotenvApiKeys(projectDir) {
5055
- const dotenvPath = path16.join(projectDir, ".env");
5056
- if (!fs18.existsSync(dotenvPath)) return {};
5203
+ const dotenvPath = path18.join(projectDir, ".env");
5204
+ if (!fs20.existsSync(dotenvPath)) return {};
5057
5205
  const result = {};
5058
- for (const rawLine of fs18.readFileSync(dotenvPath, "utf-8").split("\n")) {
5206
+ for (const rawLine of fs20.readFileSync(dotenvPath, "utf-8").split("\n")) {
5059
5207
  const line = rawLine.trim();
5060
5208
  if (!line || line.startsWith("#")) continue;
5061
5209
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -5157,8 +5305,8 @@ function pushWithRetry(opts = {}) {
5157
5305
  }
5158
5306
 
5159
5307
  // src/commit.ts
5160
- import * as fs19 from "fs";
5161
- import * as path17 from "path";
5308
+ import * as fs21 from "fs";
5309
+ import * as path19 from "path";
5162
5310
  var FORBIDDEN_PATH_PREFIXES = [
5163
5311
  ".kody/",
5164
5312
  ".kody-engine/",
@@ -5219,18 +5367,18 @@ function tryGit(args, cwd) {
5219
5367
  }
5220
5368
  function abortUnfinishedGitOps(cwd) {
5221
5369
  const aborted = [];
5222
- const gitDir = path17.join(cwd ?? process.cwd(), ".git");
5223
- if (!fs19.existsSync(gitDir)) return aborted;
5224
- if (fs19.existsSync(path17.join(gitDir, "MERGE_HEAD"))) {
5370
+ const gitDir = path19.join(cwd ?? process.cwd(), ".git");
5371
+ if (!fs21.existsSync(gitDir)) return aborted;
5372
+ if (fs21.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
5225
5373
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
5226
5374
  }
5227
- if (fs19.existsSync(path17.join(gitDir, "CHERRY_PICK_HEAD"))) {
5375
+ if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
5228
5376
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
5229
5377
  }
5230
- if (fs19.existsSync(path17.join(gitDir, "REVERT_HEAD"))) {
5378
+ if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
5231
5379
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
5232
5380
  }
5233
- if (fs19.existsSync(path17.join(gitDir, "rebase-merge")) || fs19.existsSync(path17.join(gitDir, "rebase-apply"))) {
5381
+ if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
5234
5382
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
5235
5383
  }
5236
5384
  try {
@@ -5286,7 +5434,7 @@ function normalizeCommitMessage(raw) {
5286
5434
  function commitAndPush(branch, agentMessage, cwd) {
5287
5435
  const allChanged = listChangedFiles(cwd);
5288
5436
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
5289
- const mergeHeadExists = fs19.existsSync(path17.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
5437
+ const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
5290
5438
  if (allowedFiles.length === 0 && !mergeHeadExists) {
5291
5439
  return { committed: false, pushed: false, sha: "", message: "" };
5292
5440
  }
@@ -5418,7 +5566,7 @@ var advanceFlow = async (ctx, profile) => {
5418
5566
 
5419
5567
  // src/gha.ts
5420
5568
  import { execFileSync as execFileSync10 } from "child_process";
5421
- import * as fs20 from "fs";
5569
+ import * as fs22 from "fs";
5422
5570
  function getRunUrl() {
5423
5571
  const server = process.env.GITHUB_SERVER_URL;
5424
5572
  const repo = process.env.GITHUB_REPOSITORY;
@@ -5429,10 +5577,10 @@ function getRunUrl() {
5429
5577
  function reactToTriggerComment(cwd) {
5430
5578
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
5431
5579
  const eventPath = process.env.GITHUB_EVENT_PATH;
5432
- if (!eventPath || !fs20.existsSync(eventPath)) return;
5580
+ if (!eventPath || !fs22.existsSync(eventPath)) return;
5433
5581
  let event = null;
5434
5582
  try {
5435
- event = JSON.parse(fs20.readFileSync(eventPath, "utf-8"));
5583
+ event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
5436
5584
  } catch {
5437
5585
  return;
5438
5586
  }
@@ -5584,22 +5732,22 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
5584
5732
  };
5585
5733
 
5586
5734
  // src/scripts/brainServe.ts
5587
- import * as fs22 from "fs";
5735
+ import * as fs24 from "fs";
5588
5736
  import { createServer } from "http";
5589
- import * as path19 from "path";
5737
+ import * as path21 from "path";
5590
5738
  init_repoWorkspace();
5591
5739
 
5592
5740
  // src/scripts/brainTurnLog.ts
5593
- import * as fs21 from "fs";
5594
- import * as path18 from "path";
5741
+ import * as fs23 from "fs";
5742
+ import * as path20 from "path";
5595
5743
  var live = /* @__PURE__ */ new Map();
5596
5744
  function eventsPath(dir, chatId) {
5597
- return path18.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
5745
+ return path20.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
5598
5746
  }
5599
5747
  function lastPersistedSeq(dir, chatId) {
5600
5748
  const p = eventsPath(dir, chatId);
5601
- if (!fs21.existsSync(p)) return 0;
5602
- const lines = fs21.readFileSync(p, "utf-8").split("\n").filter(Boolean);
5749
+ if (!fs23.existsSync(p)) return 0;
5750
+ const lines = fs23.readFileSync(p, "utf-8").split("\n").filter(Boolean);
5603
5751
  if (lines.length === 0) return 0;
5604
5752
  try {
5605
5753
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -5609,9 +5757,9 @@ function lastPersistedSeq(dir, chatId) {
5609
5757
  }
5610
5758
  function readSince(dir, chatId, since) {
5611
5759
  const p = eventsPath(dir, chatId);
5612
- if (!fs21.existsSync(p)) return [];
5760
+ if (!fs23.existsSync(p)) return [];
5613
5761
  const out = [];
5614
- for (const line of fs21.readFileSync(p, "utf-8").split("\n")) {
5762
+ for (const line of fs23.readFileSync(p, "utf-8").split("\n")) {
5615
5763
  if (!line) continue;
5616
5764
  try {
5617
5765
  const rec = JSON.parse(line);
@@ -5637,12 +5785,12 @@ function beginTurn(dir, chatId) {
5637
5785
  };
5638
5786
  live.set(chatId, state);
5639
5787
  const p = eventsPath(dir, chatId);
5640
- fs21.mkdirSync(path18.dirname(p), { recursive: true });
5788
+ fs23.mkdirSync(path20.dirname(p), { recursive: true });
5641
5789
  return (event) => {
5642
5790
  state.seq += 1;
5643
5791
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
5644
5792
  try {
5645
- fs21.appendFileSync(p, `${JSON.stringify(rec)}
5793
+ fs23.appendFileSync(p, `${JSON.stringify(rec)}
5646
5794
  `);
5647
5795
  } catch (err) {
5648
5796
  process.stderr.write(
@@ -5681,7 +5829,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
5681
5829
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
5682
5830
  };
5683
5831
  try {
5684
- fs21.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
5832
+ fs23.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
5685
5833
  `);
5686
5834
  } catch {
5687
5835
  }
@@ -5912,7 +6060,7 @@ async function handleChatTurn(req, res, chatId, opts) {
5912
6060
  const repo = strField(body, "repo");
5913
6061
  const repoToken = strField(body, "repoToken");
5914
6062
  const sessionFile = sessionFilePath(opts.cwd, chatId);
5915
- fs22.mkdirSync(path19.dirname(sessionFile), { recursive: true });
6063
+ fs24.mkdirSync(path21.dirname(sessionFile), { recursive: true });
5916
6064
  appendTurn(sessionFile, {
5917
6065
  role: "user",
5918
6066
  content: message,
@@ -5959,7 +6107,7 @@ async function handleChatTurn(req, res, chatId, opts) {
5959
6107
  function buildServer(opts) {
5960
6108
  const runTurn = opts.runTurn ?? runChatTurn;
5961
6109
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
5962
- const reposRoot = opts.reposRoot ?? path19.join(path19.dirname(path19.resolve(opts.cwd)), "repos");
6110
+ const reposRoot = opts.reposRoot ?? path21.join(path21.dirname(path21.resolve(opts.cwd)), "repos");
5963
6111
  return createServer(async (req, res) => {
5964
6112
  if (!req.method || !req.url) {
5965
6113
  sendJson(res, 400, { error: "bad request" });
@@ -6060,93 +6208,6 @@ var brainServe = async (ctx) => {
6060
6208
  });
6061
6209
  };
6062
6210
 
6063
- // src/scripts/buildSyntheticPlugin.ts
6064
- import * as fs23 from "fs";
6065
- import * as os3 from "os";
6066
- import * as path20 from "path";
6067
- function getPluginsCatalogRoot() {
6068
- const here = path20.dirname(new URL(import.meta.url).pathname);
6069
- const candidates = [
6070
- path20.join(here, "..", "plugins"),
6071
- // dev: src/scripts → src/plugins
6072
- path20.join(here, "..", "..", "plugins"),
6073
- // built: dist/scripts → dist/plugins
6074
- path20.join(here, "..", "..", "src", "plugins")
6075
- // fallback
6076
- ];
6077
- for (const c of candidates) {
6078
- if (fs23.existsSync(c) && fs23.statSync(c).isDirectory()) return c;
6079
- }
6080
- return candidates[0];
6081
- }
6082
- var buildSyntheticPlugin = async (ctx, profile) => {
6083
- const cc = profile.claudeCode;
6084
- const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
6085
- if (!needsSynthetic) return;
6086
- const catalog = getPluginsCatalogRoot();
6087
- const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
6088
- const root = path20.join(os3.tmpdir(), `kody-synth-${runId}`);
6089
- fs23.mkdirSync(path20.join(root, ".claude-plugin"), { recursive: true });
6090
- const resolvePart = (bucket, entry) => {
6091
- const local = path20.join(profile.dir, bucket, entry);
6092
- if (fs23.existsSync(local)) return local;
6093
- const central = path20.join(catalog, bucket, entry);
6094
- if (fs23.existsSync(central)) return central;
6095
- throw new Error(
6096
- `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
6097
- );
6098
- };
6099
- if (cc.skills.length > 0) {
6100
- const dst = path20.join(root, "skills");
6101
- fs23.mkdirSync(dst, { recursive: true });
6102
- for (const name of cc.skills) {
6103
- copyDir(resolvePart("skills", name), path20.join(dst, name));
6104
- }
6105
- }
6106
- if (cc.commands.length > 0) {
6107
- const dst = path20.join(root, "commands");
6108
- fs23.mkdirSync(dst, { recursive: true });
6109
- for (const name of cc.commands) {
6110
- fs23.copyFileSync(resolvePart("commands", `${name}.md`), path20.join(dst, `${name}.md`));
6111
- }
6112
- }
6113
- if (cc.hooks.length > 0) {
6114
- const dst = path20.join(root, "hooks");
6115
- fs23.mkdirSync(dst, { recursive: true });
6116
- const merged = { hooks: {} };
6117
- for (const name of cc.hooks) {
6118
- const src = resolvePart("hooks", `${name}.json`);
6119
- const parsed = JSON.parse(fs23.readFileSync(src, "utf-8"));
6120
- for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
6121
- if (!Array.isArray(entries)) continue;
6122
- if (!merged.hooks[event]) merged.hooks[event] = [];
6123
- merged.hooks[event].push(...entries);
6124
- }
6125
- }
6126
- fs23.writeFileSync(path20.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
6127
- `);
6128
- }
6129
- const manifest = {
6130
- name: `kody-synth-${profile.name}`,
6131
- version: "1.0.0",
6132
- description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
6133
- };
6134
- if (cc.skills.length > 0) manifest.skills = ["./skills/"];
6135
- if (cc.commands.length > 0) manifest.commands = ["./commands/"];
6136
- fs23.writeFileSync(path20.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
6137
- `);
6138
- ctx.data.syntheticPluginPath = root;
6139
- };
6140
- function copyDir(src, dst) {
6141
- fs23.mkdirSync(dst, { recursive: true });
6142
- for (const ent of fs23.readdirSync(src, { withFileTypes: true })) {
6143
- const s = path20.join(src, ent.name);
6144
- const d = path20.join(dst, ent.name);
6145
- if (ent.isDirectory()) copyDir(s, d);
6146
- else if (ent.isFile()) fs23.copyFileSync(s, d);
6147
- }
6148
- }
6149
-
6150
6211
  // src/coverage.ts
6151
6212
  import { execFileSync as execFileSync11 } from "child_process";
6152
6213
  function patternToRegex(pattern) {
@@ -6295,13 +6356,13 @@ function defaultLabelMap() {
6295
6356
  }
6296
6357
 
6297
6358
  // src/scripts/commitAndPush.ts
6298
- import * as fs24 from "fs";
6299
- import * as path21 from "path";
6359
+ import * as fs25 from "fs";
6360
+ import * as path22 from "path";
6300
6361
  init_events();
6301
6362
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
6302
6363
  function sentinelPathForStage(cwd, profileName) {
6303
6364
  const runId = resolveRunId();
6304
- return path21.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
6365
+ return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
6305
6366
  }
6306
6367
  var commitAndPush2 = async (ctx, profile) => {
6307
6368
  const branch = ctx.data.branch;
@@ -6311,9 +6372,9 @@ var commitAndPush2 = async (ctx, profile) => {
6311
6372
  }
6312
6373
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
6313
6374
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
6314
- if (sentinel && fs24.existsSync(sentinel)) {
6375
+ if (sentinel && fs25.existsSync(sentinel)) {
6315
6376
  try {
6316
- const replay = JSON.parse(fs24.readFileSync(sentinel, "utf-8"));
6377
+ const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
6317
6378
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
6318
6379
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
6319
6380
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -6366,8 +6427,8 @@ var commitAndPush2 = async (ctx, profile) => {
6366
6427
  const result = ctx.data.commitResult;
6367
6428
  if (sentinel && result?.committed) {
6368
6429
  try {
6369
- fs24.mkdirSync(path21.dirname(sentinel), { recursive: true });
6370
- fs24.writeFileSync(
6430
+ fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
6431
+ fs25.writeFileSync(
6371
6432
  sentinel,
6372
6433
  JSON.stringify(
6373
6434
  {
@@ -6390,8 +6451,8 @@ var commitAndPush2 = async (ctx, profile) => {
6390
6451
  init_issue();
6391
6452
 
6392
6453
  // src/goal/state.ts
6393
- import * as fs25 from "fs";
6394
- import * as path22 from "path";
6454
+ import * as fs26 from "fs";
6455
+ import * as path23 from "path";
6395
6456
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
6396
6457
  var GoalStateError = class extends Error {
6397
6458
  constructor(path42, message) {
@@ -6537,16 +6598,16 @@ function describeCommitMessage(goal) {
6537
6598
  }
6538
6599
 
6539
6600
  // src/scripts/composePrompt.ts
6540
- import * as fs26 from "fs";
6541
- import * as path23 from "path";
6601
+ import * as fs27 from "fs";
6602
+ import * as path24 from "path";
6542
6603
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
6543
6604
  var composePrompt = async (ctx, profile) => {
6544
6605
  const explicit = ctx.data.promptTemplate;
6545
6606
  const mode = ctx.args.mode;
6546
6607
  const candidates = [
6547
- explicit ? path23.join(profile.dir, explicit) : null,
6548
- mode ? path23.join(profile.dir, "prompts", `${mode}.md`) : null,
6549
- path23.join(profile.dir, "prompt.md")
6608
+ explicit ? path24.join(profile.dir, explicit) : null,
6609
+ mode ? path24.join(profile.dir, "prompts", `${mode}.md`) : null,
6610
+ path24.join(profile.dir, "prompt.md")
6550
6611
  ].filter(Boolean);
6551
6612
  let templatePath = "";
6552
6613
  let template = "";
@@ -6559,7 +6620,7 @@ var composePrompt = async (ctx, profile) => {
6559
6620
  break;
6560
6621
  }
6561
6622
  try {
6562
- template = fs26.readFileSync(c, "utf-8");
6623
+ template = fs27.readFileSync(c, "utf-8");
6563
6624
  templatePath = c;
6564
6625
  break;
6565
6626
  } catch (err) {
@@ -6570,7 +6631,7 @@ var composePrompt = async (ctx, profile) => {
6570
6631
  if (!templatePath) {
6571
6632
  let dirState;
6572
6633
  try {
6573
- dirState = `dir contents: [${fs26.readdirSync(profile.dir).join(", ")}]`;
6634
+ dirState = `dir contents: [${fs27.readdirSync(profile.dir).join(", ")}]`;
6574
6635
  } catch (err) {
6575
6636
  dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
6576
6637
  }
@@ -7395,15 +7456,15 @@ var deriveQaScopeFromIssue = async (ctx) => {
7395
7456
 
7396
7457
  // src/scripts/diagMcp.ts
7397
7458
  import { execFileSync as execFileSync12 } from "child_process";
7398
- import * as fs27 from "fs";
7459
+ import * as fs28 from "fs";
7399
7460
  import * as os4 from "os";
7400
- import * as path24 from "path";
7461
+ import * as path25 from "path";
7401
7462
  var diagMcp = async (_ctx) => {
7402
7463
  const home = os4.homedir();
7403
- const cacheDir = path24.join(home, ".cache", "ms-playwright");
7464
+ const cacheDir = path25.join(home, ".cache", "ms-playwright");
7404
7465
  let entries = [];
7405
7466
  try {
7406
- entries = fs27.readdirSync(cacheDir);
7467
+ entries = fs28.readdirSync(cacheDir);
7407
7468
  } catch {
7408
7469
  }
7409
7470
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -7429,17 +7490,17 @@ var diagMcp = async (_ctx) => {
7429
7490
  };
7430
7491
 
7431
7492
  // src/scripts/discoverQaContext.ts
7432
- import * as fs29 from "fs";
7433
- import * as path26 from "path";
7493
+ import * as fs30 from "fs";
7494
+ import * as path27 from "path";
7434
7495
 
7435
7496
  // src/scripts/frameworkDetectors.ts
7436
- import * as fs28 from "fs";
7437
- import * as path25 from "path";
7497
+ import * as fs29 from "fs";
7498
+ import * as path26 from "path";
7438
7499
  function detectFrameworks(cwd) {
7439
7500
  const out = [];
7440
7501
  let deps = {};
7441
7502
  try {
7442
- const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
7503
+ const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
7443
7504
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
7444
7505
  } catch {
7445
7506
  return out;
@@ -7476,7 +7537,7 @@ function detectFrameworks(cwd) {
7476
7537
  }
7477
7538
  function findFile(cwd, candidates) {
7478
7539
  for (const c of candidates) {
7479
- if (fs28.existsSync(path25.join(cwd, c))) return c;
7540
+ if (fs29.existsSync(path26.join(cwd, c))) return c;
7480
7541
  }
7481
7542
  return null;
7482
7543
  }
@@ -7489,18 +7550,18 @@ var COLLECTION_DIRS = [
7489
7550
  function discoverPayloadCollections(cwd) {
7490
7551
  const out = [];
7491
7552
  for (const dir of COLLECTION_DIRS) {
7492
- const full = path25.join(cwd, dir);
7493
- if (!fs28.existsSync(full)) continue;
7553
+ const full = path26.join(cwd, dir);
7554
+ if (!fs29.existsSync(full)) continue;
7494
7555
  let files;
7495
7556
  try {
7496
- files = fs28.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7557
+ files = fs29.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7497
7558
  } catch {
7498
7559
  continue;
7499
7560
  }
7500
7561
  for (const file of files) {
7501
7562
  try {
7502
- const filePath = path25.join(full, file);
7503
- const content = fs28.readFileSync(filePath, "utf-8").slice(0, 1e4);
7563
+ const filePath = path26.join(full, file);
7564
+ const content = fs29.readFileSync(filePath, "utf-8").slice(0, 1e4);
7504
7565
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
7505
7566
  if (!slugMatch) continue;
7506
7567
  const slug = slugMatch[1];
@@ -7514,7 +7575,7 @@ function discoverPayloadCollections(cwd) {
7514
7575
  out.push({
7515
7576
  name,
7516
7577
  slug,
7517
- filePath: path25.relative(cwd, filePath),
7578
+ filePath: path26.relative(cwd, filePath),
7518
7579
  fields: fields.slice(0, 20),
7519
7580
  hasAdmin
7520
7581
  });
@@ -7528,28 +7589,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
7528
7589
  function discoverAdminComponents(cwd, collections) {
7529
7590
  const out = [];
7530
7591
  for (const dir of ADMIN_COMPONENT_DIRS) {
7531
- const full = path25.join(cwd, dir);
7532
- if (!fs28.existsSync(full)) continue;
7592
+ const full = path26.join(cwd, dir);
7593
+ if (!fs29.existsSync(full)) continue;
7533
7594
  let entries;
7534
7595
  try {
7535
- entries = fs28.readdirSync(full, { withFileTypes: true });
7596
+ entries = fs29.readdirSync(full, { withFileTypes: true });
7536
7597
  } catch {
7537
7598
  continue;
7538
7599
  }
7539
7600
  for (const entry of entries) {
7540
- const entryPath = path25.join(full, entry.name);
7601
+ const entryPath = path26.join(full, entry.name);
7541
7602
  let name;
7542
7603
  let filePath;
7543
7604
  if (entry.isDirectory()) {
7544
7605
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
7545
- (f) => fs28.existsSync(path25.join(entryPath, f))
7606
+ (f) => fs29.existsSync(path26.join(entryPath, f))
7546
7607
  );
7547
7608
  if (!indexFile) continue;
7548
7609
  name = entry.name;
7549
- filePath = path25.relative(cwd, path25.join(entryPath, indexFile));
7610
+ filePath = path26.relative(cwd, path26.join(entryPath, indexFile));
7550
7611
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
7551
7612
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
7552
- filePath = path25.relative(cwd, entryPath);
7613
+ filePath = path26.relative(cwd, entryPath);
7553
7614
  } else {
7554
7615
  continue;
7555
7616
  }
@@ -7557,7 +7618,7 @@ function discoverAdminComponents(cwd, collections) {
7557
7618
  if (collections) {
7558
7619
  for (const col of collections) {
7559
7620
  try {
7560
- const colContent = fs28.readFileSync(path25.join(cwd, col.filePath), "utf-8");
7621
+ const colContent = fs29.readFileSync(path26.join(cwd, col.filePath), "utf-8");
7561
7622
  if (colContent.includes(name)) {
7562
7623
  usedInCollection = col.slug;
7563
7624
  break;
@@ -7576,8 +7637,8 @@ function scanApiRoutes(cwd) {
7576
7637
  const out = [];
7577
7638
  const appDirs = ["src/app", "app"];
7578
7639
  for (const appDir of appDirs) {
7579
- const apiDir = path25.join(cwd, appDir, "api");
7580
- if (!fs28.existsSync(apiDir)) continue;
7640
+ const apiDir = path26.join(cwd, appDir, "api");
7641
+ if (!fs29.existsSync(apiDir)) continue;
7581
7642
  walkApiRoutes(apiDir, "/api", cwd, out);
7582
7643
  break;
7583
7644
  }
@@ -7586,14 +7647,14 @@ function scanApiRoutes(cwd) {
7586
7647
  function walkApiRoutes(dir, prefix, cwd, out) {
7587
7648
  let entries;
7588
7649
  try {
7589
- entries = fs28.readdirSync(dir, { withFileTypes: true });
7650
+ entries = fs29.readdirSync(dir, { withFileTypes: true });
7590
7651
  } catch {
7591
7652
  return;
7592
7653
  }
7593
7654
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
7594
7655
  if (routeFile) {
7595
7656
  try {
7596
- const content = fs28.readFileSync(path25.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
7657
+ const content = fs29.readFileSync(path26.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
7597
7658
  const methods = HTTP_METHODS.filter(
7598
7659
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
7599
7660
  );
@@ -7601,7 +7662,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7601
7662
  out.push({
7602
7663
  path: prefix,
7603
7664
  methods,
7604
- filePath: path25.relative(cwd, path25.join(dir, routeFile.name))
7665
+ filePath: path26.relative(cwd, path26.join(dir, routeFile.name))
7605
7666
  });
7606
7667
  }
7607
7668
  } catch {
@@ -7612,7 +7673,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7612
7673
  if (entry.name === "node_modules" || entry.name === ".next") continue;
7613
7674
  let segment = entry.name;
7614
7675
  if (segment.startsWith("(") && segment.endsWith(")")) {
7615
- walkApiRoutes(path25.join(dir, entry.name), prefix, cwd, out);
7676
+ walkApiRoutes(path26.join(dir, entry.name), prefix, cwd, out);
7616
7677
  continue;
7617
7678
  }
7618
7679
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -7620,7 +7681,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7620
7681
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
7621
7682
  segment = `:${segment.slice(1, -1)}`;
7622
7683
  }
7623
- walkApiRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
7684
+ walkApiRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
7624
7685
  }
7625
7686
  }
7626
7687
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -7640,10 +7701,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
7640
7701
  function scanEnvVars(cwd) {
7641
7702
  const candidates = [".env.example", ".env.local.example", ".env.template"];
7642
7703
  for (const envFile of candidates) {
7643
- const envPath = path25.join(cwd, envFile);
7644
- if (!fs28.existsSync(envPath)) continue;
7704
+ const envPath = path26.join(cwd, envFile);
7705
+ if (!fs29.existsSync(envPath)) continue;
7645
7706
  try {
7646
- const content = fs28.readFileSync(envPath, "utf-8");
7707
+ const content = fs29.readFileSync(envPath, "utf-8");
7647
7708
  const vars = [];
7648
7709
  for (const line of content.split("\n")) {
7649
7710
  const trimmed = line.trim();
@@ -7691,9 +7752,9 @@ function runQaDiscovery(cwd) {
7691
7752
  }
7692
7753
  function detectDevServer(cwd, out) {
7693
7754
  try {
7694
- const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
7755
+ const pkg = JSON.parse(fs30.readFileSync(path27.join(cwd, "package.json"), "utf-8"));
7695
7756
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
7696
- const pm = fs29.existsSync(path26.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs29.existsSync(path26.join(cwd, "yarn.lock")) ? "yarn" : fs29.existsSync(path26.join(cwd, "bun.lockb")) ? "bun" : "npm";
7757
+ const pm = fs30.existsSync(path27.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs30.existsSync(path27.join(cwd, "yarn.lock")) ? "yarn" : fs30.existsSync(path27.join(cwd, "bun.lockb")) ? "bun" : "npm";
7697
7758
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
7698
7759
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
7699
7760
  else if (allDeps.vite) out.devPort = 5173;
@@ -7703,8 +7764,8 @@ function detectDevServer(cwd, out) {
7703
7764
  function scanFrontendRoutes(cwd, out) {
7704
7765
  const appDirs = ["src/app", "app"];
7705
7766
  for (const appDir of appDirs) {
7706
- const full = path26.join(cwd, appDir);
7707
- if (!fs29.existsSync(full)) continue;
7767
+ const full = path27.join(cwd, appDir);
7768
+ if (!fs30.existsSync(full)) continue;
7708
7769
  walkFrontendRoutes(full, "", out);
7709
7770
  break;
7710
7771
  }
@@ -7712,7 +7773,7 @@ function scanFrontendRoutes(cwd, out) {
7712
7773
  function walkFrontendRoutes(dir, prefix, out) {
7713
7774
  let entries;
7714
7775
  try {
7715
- entries = fs29.readdirSync(dir, { withFileTypes: true });
7776
+ entries = fs30.readdirSync(dir, { withFileTypes: true });
7716
7777
  } catch {
7717
7778
  return;
7718
7779
  }
@@ -7729,7 +7790,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7729
7790
  if (entry.name === "node_modules" || entry.name === ".next") continue;
7730
7791
  let segment = entry.name;
7731
7792
  if (segment.startsWith("(") && segment.endsWith(")")) {
7732
- walkFrontendRoutes(path26.join(dir, entry.name), prefix, out);
7793
+ walkFrontendRoutes(path27.join(dir, entry.name), prefix, out);
7733
7794
  continue;
7734
7795
  }
7735
7796
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -7737,7 +7798,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7737
7798
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
7738
7799
  segment = `:${segment.slice(1, -1)}`;
7739
7800
  }
7740
- walkFrontendRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, out);
7801
+ walkFrontendRoutes(path27.join(dir, entry.name), `${prefix}/${segment}`, out);
7741
7802
  }
7742
7803
  }
7743
7804
  function detectAuthFiles(cwd, out) {
@@ -7754,23 +7815,23 @@ function detectAuthFiles(cwd, out) {
7754
7815
  "src/app/api/oauth"
7755
7816
  ];
7756
7817
  for (const c of candidates) {
7757
- if (fs29.existsSync(path26.join(cwd, c))) out.authFiles.push(c);
7818
+ if (fs30.existsSync(path27.join(cwd, c))) out.authFiles.push(c);
7758
7819
  }
7759
7820
  }
7760
7821
  function detectRoles(cwd, out) {
7761
7822
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
7762
7823
  for (const rp of rolePaths) {
7763
- const dir = path26.join(cwd, rp);
7764
- if (!fs29.existsSync(dir)) continue;
7824
+ const dir = path27.join(cwd, rp);
7825
+ if (!fs30.existsSync(dir)) continue;
7765
7826
  let files;
7766
7827
  try {
7767
- files = fs29.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7828
+ files = fs30.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7768
7829
  } catch {
7769
7830
  continue;
7770
7831
  }
7771
7832
  for (const f of files) {
7772
7833
  try {
7773
- const content = fs29.readFileSync(path26.join(dir, f), "utf-8").slice(0, 5e3);
7834
+ const content = fs30.readFileSync(path27.join(dir, f), "utf-8").slice(0, 5e3);
7774
7835
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
7775
7836
  if (roleMatches) {
7776
7837
  for (const m of roleMatches) {
@@ -7953,8 +8014,8 @@ ${stateBody}`;
7953
8014
  };
7954
8015
 
7955
8016
  // src/scripts/dispatchJobFileTicks.ts
7956
- import * as fs31 from "fs";
7957
- import * as path28 from "path";
8017
+ import * as fs32 from "fs";
8018
+ import * as path29 from "path";
7958
8019
 
7959
8020
  // src/scripts/jobFrontmatter.ts
7960
8021
  var SCHEDULE_EVERY_VALUES = [
@@ -7970,7 +8031,7 @@ var SCHEDULE_EVERY_VALUES = [
7970
8031
  "manual"
7971
8032
  ];
7972
8033
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
7973
- function splitFrontmatter(raw) {
8034
+ function splitFrontmatter2(raw) {
7974
8035
  const match = FRONTMATTER_RE.exec(raw);
7975
8036
  if (!match) return { frontmatter: {}, body: raw };
7976
8037
  const inner = match[1] ?? "";
@@ -8247,8 +8308,8 @@ function isShaConflict(err) {
8247
8308
  }
8248
8309
 
8249
8310
  // src/scripts/jobState/localFileBackend.ts
8250
- import * as fs30 from "fs";
8251
- import * as path27 from "path";
8311
+ import * as fs31 from "fs";
8312
+ import * as path28 from "path";
8252
8313
  var LocalFileBackend = class {
8253
8314
  name = "local-file";
8254
8315
  cwd;
@@ -8263,7 +8324,7 @@ var LocalFileBackend = class {
8263
8324
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
8264
8325
  this.cwd = opts.cwd;
8265
8326
  this.jobsDir = opts.jobsDir;
8266
- this.absDir = path27.join(opts.cwd, opts.jobsDir);
8327
+ this.absDir = path28.join(opts.cwd, opts.jobsDir);
8267
8328
  this.owner = opts.owner;
8268
8329
  this.repo = opts.repo;
8269
8330
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -8278,7 +8339,7 @@ var LocalFileBackend = class {
8278
8339
  `);
8279
8340
  return;
8280
8341
  }
8281
- fs30.mkdirSync(this.absDir, { recursive: true });
8342
+ fs31.mkdirSync(this.absDir, { recursive: true });
8282
8343
  const prefix = this.cacheKeyPrefix();
8283
8344
  const probeKey = `${prefix}probe-${Date.now()}`;
8284
8345
  try {
@@ -8307,7 +8368,7 @@ var LocalFileBackend = class {
8307
8368
  `);
8308
8369
  return;
8309
8370
  }
8310
- if (!fs30.existsSync(this.absDir)) {
8371
+ if (!fs31.existsSync(this.absDir)) {
8311
8372
  return;
8312
8373
  }
8313
8374
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -8323,11 +8384,11 @@ var LocalFileBackend = class {
8323
8384
  }
8324
8385
  load(slug) {
8325
8386
  const relPath = stateFilePath(this.jobsDir, slug);
8326
- const absPath = path27.join(this.cwd, relPath);
8327
- if (!fs30.existsSync(absPath)) {
8387
+ const absPath = path28.join(this.cwd, relPath);
8388
+ if (!fs31.existsSync(absPath)) {
8328
8389
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
8329
8390
  }
8330
- const raw = fs30.readFileSync(absPath, "utf-8");
8391
+ const raw = fs31.readFileSync(absPath, "utf-8");
8331
8392
  let parsed;
8332
8393
  try {
8333
8394
  parsed = JSON.parse(raw);
@@ -8344,13 +8405,13 @@ var LocalFileBackend = class {
8344
8405
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
8345
8406
  return false;
8346
8407
  }
8347
- const absPath = path27.join(this.cwd, loaded.path);
8348
- fs30.mkdirSync(path27.dirname(absPath), { recursive: true });
8408
+ const absPath = path28.join(this.cwd, loaded.path);
8409
+ fs31.mkdirSync(path28.dirname(absPath), { recursive: true });
8349
8410
  const body = `${JSON.stringify(next, null, 2)}
8350
8411
  `;
8351
8412
  const tmpPath = `${absPath}.${process.pid}.tmp`;
8352
- fs30.writeFileSync(tmpPath, body, "utf-8");
8353
- fs30.renameSync(tmpPath, absPath);
8413
+ fs31.writeFileSync(tmpPath, body, "utf-8");
8414
+ fs31.renameSync(tmpPath, absPath);
8354
8415
  return true;
8355
8416
  }
8356
8417
  cacheKeyPrefix() {
@@ -8428,7 +8489,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
8428
8489
  await backend.hydrate();
8429
8490
  }
8430
8491
  try {
8431
- const slugs = listJobSlugs(path28.join(ctx.cwd, jobsDir));
8492
+ const slugs = listJobSlugs(path29.join(ctx.cwd, jobsDir));
8432
8493
  ctx.data.jobSlugCount = slugs.length;
8433
8494
  if (slugs.length === 0) {
8434
8495
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -8479,6 +8540,56 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
8479
8540
  } catch (err) {
8480
8541
  const msg = err instanceof Error ? err.message : String(err);
8481
8542
  process.stderr.write(`[jobs] tick ${slug} crashed: ${msg}
8543
+ `);
8544
+ results.push({ slug, exitCode: 99, reason: msg });
8545
+ }
8546
+ }
8547
+ const folderSlugs = listFolderDutySlugs(path29.join(ctx.cwd, jobsDir));
8548
+ for (const slug of folderSlugs) {
8549
+ let every;
8550
+ let staff;
8551
+ try {
8552
+ const profile = loadProfile(path29.join(ctx.cwd, jobsDir, slug, "profile.json"));
8553
+ every = profile.every;
8554
+ staff = profile.staff;
8555
+ } catch (err) {
8556
+ process.stderr.write(`[jobs] \u23ED skip folder-duty ${slug}: profile load failed: ${String(err)}
8557
+ `);
8558
+ continue;
8559
+ }
8560
+ if (!every) continue;
8561
+ if (!staff || staff.trim().length === 0) {
8562
+ process.stderr.write(`[jobs] \u23ED skip ${slug}: scheduled duty has no staff
8563
+ `);
8564
+ results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
8565
+ continue;
8566
+ }
8567
+ const decision = await decideShouldFire(every, slug, backend, now);
8568
+ if (decision.skip) {
8569
+ process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
8570
+ `);
8571
+ results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
8572
+ continue;
8573
+ }
8574
+ await stampFired(backend, slug, now);
8575
+ process.stdout.write(`[jobs] \u2192 run scheduled duty ${slug} (one-shot, as ${staff})
8576
+ `);
8577
+ try {
8578
+ const out = await runExecutable(slug, {
8579
+ cliArgs: {},
8580
+ cwd: ctx.cwd,
8581
+ config: ctx.config,
8582
+ verbose: ctx.verbose,
8583
+ quiet: ctx.quiet
8584
+ });
8585
+ results.push({ slug, exitCode: out.exitCode, reason: out.reason });
8586
+ if (out.exitCode !== 0) {
8587
+ process.stderr.write(`[jobs] scheduled duty ${slug} failed (exit ${out.exitCode}): ${out.reason ?? ""}
8588
+ `);
8589
+ }
8590
+ } catch (err) {
8591
+ const msg = err instanceof Error ? err.message : String(err);
8592
+ process.stderr.write(`[jobs] scheduled duty ${slug} crashed: ${msg}
8482
8593
  `);
8483
8594
  results.push({ slug, exitCode: 99, reason: msg });
8484
8595
  }
@@ -8539,22 +8650,42 @@ function formatAgo(ms) {
8539
8650
  }
8540
8651
  function readJobFrontmatter(cwd, jobsDir, slug) {
8541
8652
  try {
8542
- const raw = fs31.readFileSync(path28.join(cwd, jobsDir, `${slug}.md`), "utf-8");
8543
- return splitFrontmatter(raw).frontmatter;
8653
+ const raw = fs32.readFileSync(path29.join(cwd, jobsDir, `${slug}.md`), "utf-8");
8654
+ return splitFrontmatter2(raw).frontmatter;
8544
8655
  } catch {
8545
8656
  return {};
8546
8657
  }
8547
8658
  }
8548
8659
  function listJobSlugs(absDir) {
8549
- if (!fs31.existsSync(absDir)) return [];
8660
+ if (!fs32.existsSync(absDir)) return [];
8550
8661
  let entries;
8551
8662
  try {
8552
- entries = fs31.readdirSync(absDir, { withFileTypes: true });
8663
+ entries = fs32.readdirSync(absDir, { withFileTypes: true });
8553
8664
  } catch {
8554
8665
  return [];
8555
8666
  }
8556
8667
  return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, "")).filter((slug) => slug.length > 0 && !slug.startsWith("_") && !slug.startsWith(".")).sort();
8557
8668
  }
8669
+ function listFolderDutySlugs(absDir) {
8670
+ if (!fs32.existsSync(absDir)) return [];
8671
+ let entries;
8672
+ try {
8673
+ entries = fs32.readdirSync(absDir, { withFileTypes: true });
8674
+ } catch {
8675
+ return [];
8676
+ }
8677
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith("_") && !e.name.startsWith(".")).filter((e) => fs32.existsSync(path29.join(absDir, e.name, "profile.json"))).map((e) => e.name).sort();
8678
+ }
8679
+ async function stampFired(backend, slug, now) {
8680
+ try {
8681
+ const loaded = await backend.load(slug);
8682
+ const nextData = { ...loaded.state.data ?? {}, lastFiredAt: new Date(now).toISOString() };
8683
+ await backend.save(loaded, { ...loaded.state, data: nextData });
8684
+ } catch (err) {
8685
+ process.stderr.write(`[jobs] failed to stamp lastFiredAt for ${slug}: ${String(err)}
8686
+ `);
8687
+ }
8688
+ }
8558
8689
 
8559
8690
  // src/scripts/dispatchJobTicks.ts
8560
8691
  init_issue();
@@ -9608,12 +9739,12 @@ var handleAbandonedGoal = async (ctx) => {
9608
9739
 
9609
9740
  // src/scripts/initFlow.ts
9610
9741
  import { execFileSync as execFileSync18 } from "child_process";
9611
- import * as fs32 from "fs";
9612
- import * as path29 from "path";
9742
+ import * as fs33 from "fs";
9743
+ import * as path30 from "path";
9613
9744
  function detectPackageManager(cwd) {
9614
- if (fs32.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9615
- if (fs32.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
9616
- if (fs32.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
9745
+ if (fs33.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9746
+ if (fs33.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
9747
+ if (fs33.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
9617
9748
  return "npm";
9618
9749
  }
9619
9750
  function qualityCommandsFor(pm) {
@@ -9742,36 +9873,36 @@ function performInit(cwd, force) {
9742
9873
  const pm = detectPackageManager(cwd);
9743
9874
  const ownerRepo = detectOwnerRepo(cwd);
9744
9875
  const defaultBranch2 = defaultBranchFromGit(cwd);
9745
- const configPath = path29.join(cwd, "kody.config.json");
9746
- if (fs32.existsSync(configPath) && !force) {
9876
+ const configPath = path30.join(cwd, "kody.config.json");
9877
+ if (fs33.existsSync(configPath) && !force) {
9747
9878
  skipped.push("kody.config.json");
9748
9879
  } else {
9749
9880
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
9750
- fs32.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9881
+ fs33.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9751
9882
  `);
9752
9883
  wrote.push("kody.config.json");
9753
9884
  }
9754
- const workflowDir = path29.join(cwd, ".github", "workflows");
9755
- const workflowPath = path29.join(workflowDir, "kody.yml");
9756
- if (fs32.existsSync(workflowPath) && !force) {
9885
+ const workflowDir = path30.join(cwd, ".github", "workflows");
9886
+ const workflowPath = path30.join(workflowDir, "kody.yml");
9887
+ if (fs33.existsSync(workflowPath) && !force) {
9757
9888
  skipped.push(".github/workflows/kody.yml");
9758
9889
  } else {
9759
- fs32.mkdirSync(workflowDir, { recursive: true });
9760
- fs32.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9890
+ fs33.mkdirSync(workflowDir, { recursive: true });
9891
+ fs33.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9761
9892
  wrote.push(".github/workflows/kody.yml");
9762
9893
  }
9763
9894
  const builtinJobs = listBuiltinJobs();
9764
9895
  if (builtinJobs.length > 0) {
9765
- const jobsDir = path29.join(cwd, ".kody", "duties");
9766
- fs32.mkdirSync(jobsDir, { recursive: true });
9896
+ const jobsDir = path30.join(cwd, ".kody", "duties");
9897
+ fs33.mkdirSync(jobsDir, { recursive: true });
9767
9898
  for (const job of builtinJobs) {
9768
- const rel = path29.join(".kody", "duties", `${job.slug}.md`);
9769
- const target = path29.join(cwd, rel);
9770
- if (fs32.existsSync(target) && !force) {
9899
+ const rel = path30.join(".kody", "duties", `${job.slug}.md`);
9900
+ const target = path30.join(cwd, rel);
9901
+ if (fs33.existsSync(target) && !force) {
9771
9902
  skipped.push(rel);
9772
9903
  continue;
9773
9904
  }
9774
- fs32.writeFileSync(target, fs32.readFileSync(job.filePath, "utf-8"));
9905
+ fs33.writeFileSync(target, fs33.readFileSync(job.filePath, "utf-8"));
9775
9906
  wrote.push(rel);
9776
9907
  }
9777
9908
  }
@@ -9783,12 +9914,12 @@ function performInit(cwd, force) {
9783
9914
  continue;
9784
9915
  }
9785
9916
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
9786
- const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
9787
- if (fs32.existsSync(target) && !force) {
9917
+ const target = path30.join(workflowDir, `kody-${exe.name}.yml`);
9918
+ if (fs33.existsSync(target) && !force) {
9788
9919
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
9789
9920
  continue;
9790
9921
  }
9791
- fs32.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9922
+ fs33.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9792
9923
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
9793
9924
  }
9794
9925
  let labels;
@@ -9973,8 +10104,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
9973
10104
 
9974
10105
  // src/scripts/loadJobFromFile.ts
9975
10106
  init_dutyMcp();
9976
- import * as fs33 from "fs";
9977
- import * as path30 from "path";
10107
+ import * as fs34 from "fs";
10108
+ import * as path31 from "path";
9978
10109
  var DUTY_TOOL_PALETTE = new Set(DUTY_MCP_TOOL_NAMES);
9979
10110
  var loadJobFromFile = async (ctx, profile, args) => {
9980
10111
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -9984,23 +10115,23 @@ var loadJobFromFile = async (ctx, profile, args) => {
9984
10115
  if (!slug) {
9985
10116
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
9986
10117
  }
9987
- const absPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
9988
- if (!fs33.existsSync(absPath)) {
10118
+ const absPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
10119
+ if (!fs34.existsSync(absPath)) {
9989
10120
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
9990
10121
  }
9991
- const raw = fs33.readFileSync(absPath, "utf-8");
10122
+ const raw = fs34.readFileSync(absPath, "utf-8");
9992
10123
  const { title, body } = parseJobFile(raw, slug);
9993
- const frontmatter = splitFrontmatter(raw).frontmatter;
10124
+ const frontmatter = splitFrontmatter2(raw).frontmatter;
9994
10125
  const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
9995
10126
  const workerSlug = (frontmatter.staff ?? "").trim();
9996
10127
  let workerTitle = "";
9997
10128
  let workerPersona = "";
9998
10129
  if (workerSlug) {
9999
- const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10000
- if (!fs33.existsSync(workerPath)) {
10130
+ const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10131
+ if (!fs34.existsSync(workerPath)) {
10001
10132
  throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
10002
10133
  }
10003
- const workerRaw = fs33.readFileSync(workerPath, "utf-8");
10134
+ const workerRaw = fs34.readFileSync(workerPath, "utf-8");
10004
10135
  const parsed = parseJobFile(workerRaw, workerSlug);
10005
10136
  workerTitle = parsed.title;
10006
10137
  workerPersona = parsed.body;
@@ -10084,18 +10215,18 @@ init_loadMemoryContext();
10084
10215
  init_loadPriorArt();
10085
10216
 
10086
10217
  // src/scripts/loadQaContext.ts
10087
- import * as fs35 from "fs";
10088
- import * as path32 from "path";
10218
+ import * as fs36 from "fs";
10219
+ import * as path33 from "path";
10089
10220
 
10090
10221
  // src/scripts/kodyVariables.ts
10091
- import * as fs34 from "fs";
10092
- import * as path31 from "path";
10222
+ import * as fs35 from "fs";
10223
+ import * as path32 from "path";
10093
10224
  var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
10094
10225
  function readKodyVariables(cwd) {
10095
- const full = path31.join(cwd, KODY_VARIABLES_REL_PATH);
10226
+ const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
10096
10227
  let raw;
10097
10228
  try {
10098
- raw = fs34.readFileSync(full, "utf-8");
10229
+ raw = fs35.readFileSync(full, "utf-8");
10099
10230
  } catch {
10100
10231
  return {};
10101
10232
  }
@@ -10146,18 +10277,18 @@ function readProfileStaff(raw) {
10146
10277
  return { staff: staff ?? legacy ?? ["kody"], body };
10147
10278
  }
10148
10279
  function readProfile(cwd) {
10149
- const dir = path32.join(cwd, CONTEXT_DIR_REL_PATH);
10150
- if (!fs35.existsSync(dir)) return "";
10280
+ const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
10281
+ if (!fs36.existsSync(dir)) return "";
10151
10282
  let entries;
10152
10283
  try {
10153
- entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
10284
+ entries = fs36.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
10154
10285
  } catch {
10155
10286
  return "";
10156
10287
  }
10157
10288
  const blocks = [];
10158
10289
  for (const file of entries) {
10159
10290
  try {
10160
- const raw = fs35.readFileSync(path32.join(dir, file), "utf-8");
10291
+ const raw = fs36.readFileSync(path33.join(dir, file), "utf-8");
10161
10292
  const { staff, body } = readProfileStaff(raw);
10162
10293
  if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
10163
10294
  blocks.push(`## ${file}
@@ -10194,8 +10325,8 @@ var loadQaContext = async (ctx) => {
10194
10325
  init_events();
10195
10326
 
10196
10327
  // src/taskContext.ts
10197
- import * as fs36 from "fs";
10198
- import * as path33 from "path";
10328
+ import * as fs37 from "fs";
10329
+ import * as path34 from "path";
10199
10330
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
10200
10331
  function buildTaskContext(args) {
10201
10332
  return {
@@ -10211,10 +10342,10 @@ function buildTaskContext(args) {
10211
10342
  }
10212
10343
  function persistTaskContext(cwd, ctx) {
10213
10344
  try {
10214
- const dir = path33.join(cwd, ".kody", "runs", ctx.runId);
10215
- fs36.mkdirSync(dir, { recursive: true });
10216
- const file = path33.join(dir, "task-context.json");
10217
- fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
10345
+ const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
10346
+ fs37.mkdirSync(dir, { recursive: true });
10347
+ const file = path34.join(dir, "task-context.json");
10348
+ fs37.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
10218
10349
  `);
10219
10350
  return file;
10220
10351
  } catch (err) {
@@ -10280,19 +10411,19 @@ var loadTaskState = async (ctx) => {
10280
10411
  };
10281
10412
 
10282
10413
  // src/scripts/loadWorkerAdhoc.ts
10283
- import * as fs37 from "fs";
10284
- import * as path34 from "path";
10414
+ import * as fs38 from "fs";
10415
+ import * as path35 from "path";
10285
10416
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
10286
10417
  const workersDir = String(args?.workersDir ?? ".kody/staff");
10287
10418
  const workerSlug = String(ctx.args.worker ?? "").trim();
10288
10419
  if (!workerSlug) {
10289
10420
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
10290
10421
  }
10291
- const workerPath = path34.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10292
- if (!fs37.existsSync(workerPath)) {
10422
+ const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10423
+ if (!fs38.existsSync(workerPath)) {
10293
10424
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
10294
10425
  }
10295
- const { title, body } = parsePersona(fs37.readFileSync(workerPath, "utf-8"), workerSlug);
10426
+ const { title, body } = parsePersona(fs38.readFileSync(workerPath, "utf-8"), workerSlug);
10296
10427
  const message = resolveMessage(ctx.args.message);
10297
10428
  if (!message) {
10298
10429
  throw new Error(
@@ -10312,9 +10443,9 @@ function resolveMessage(messageArg) {
10312
10443
  }
10313
10444
  function readCommentBody() {
10314
10445
  const eventPath = process.env.GITHUB_EVENT_PATH;
10315
- if (!eventPath || !fs37.existsSync(eventPath)) return "";
10446
+ if (!eventPath || !fs38.existsSync(eventPath)) return "";
10316
10447
  try {
10317
- const event = JSON.parse(fs37.readFileSync(eventPath, "utf-8"));
10448
+ const event = JSON.parse(fs38.readFileSync(eventPath, "utf-8"));
10318
10449
  return String(event.comment?.body ?? "");
10319
10450
  } catch {
10320
10451
  return "";
@@ -10338,7 +10469,7 @@ function stripDirective(body) {
10338
10469
  return lines.slice(start).join("\n").trim();
10339
10470
  }
10340
10471
  function parsePersona(raw, slug) {
10341
- const stripped = splitFrontmatter(raw).body;
10472
+ const stripped = splitFrontmatter2(raw).body;
10342
10473
  const trimmed = stripped.trim();
10343
10474
  const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
10344
10475
  const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
@@ -12633,7 +12764,7 @@ function resolveBaseOverride(value) {
12633
12764
 
12634
12765
  // src/scripts/runnerServe.ts
12635
12766
  import { spawn as spawn5 } from "child_process";
12636
- import * as fs38 from "fs";
12767
+ import * as fs39 from "fs";
12637
12768
  import { createServer as createServer3 } from "http";
12638
12769
  var DEFAULT_PORT2 = 8080;
12639
12770
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -12713,8 +12844,8 @@ async function defaultRunJob(job) {
12713
12844
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
12714
12845
  const branch = job.ref ?? "main";
12715
12846
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
12716
- fs38.rmSync(workdir, { recursive: true, force: true });
12717
- fs38.mkdirSync(workdir, { recursive: true });
12847
+ fs39.rmSync(workdir, { recursive: true, force: true });
12848
+ fs39.mkdirSync(workdir, { recursive: true });
12718
12849
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
12719
12850
  const interactive = job.mode === "interactive";
12720
12851
  const scheduled = job.mode === "scheduled";
@@ -12847,7 +12978,7 @@ var runnerServe = async (ctx) => {
12847
12978
 
12848
12979
  // src/scripts/runPreviewBuild.ts
12849
12980
  import { copyFile, writeFile } from "fs/promises";
12850
- import * as path35 from "path";
12981
+ import * as path36 from "path";
12851
12982
  import { fileURLToPath } from "url";
12852
12983
 
12853
12984
  // src/scripts/previewBuildHelpers.ts
@@ -12996,9 +13127,9 @@ var FLY_MACHINES = "https://api.machines.dev/v1";
12996
13127
  var FLY_GRAPHQL = "https://api.fly.io/graphql";
12997
13128
  var REQ_TIMEOUT_MS2 = 3e4;
12998
13129
  function bundledDockerfilePath(mode) {
12999
- const here = path35.dirname(fileURLToPath(import.meta.url));
13130
+ const here = path36.dirname(fileURLToPath(import.meta.url));
13000
13131
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
13001
- return path35.join(here, "preview-build-templates", file);
13132
+ return path36.join(here, "preview-build-templates", file);
13002
13133
  }
13003
13134
  function required(name) {
13004
13135
  const v = (process.env[name] ?? "").trim();
@@ -13247,10 +13378,10 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13247
13378
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
13248
13379
  if (Object.keys(buildEnv).length > 0) {
13249
13380
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
13250
- await writeFile(path35.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
13381
+ await writeFile(path36.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
13251
13382
  `, "utf8");
13252
13383
  }
13253
- const consumerDockerfile = path35.join(ctx.cwd, "Dockerfile.preview");
13384
+ const consumerDockerfile = path36.join(ctx.cwd, "Dockerfile.preview");
13254
13385
  const { stat } = await import("fs/promises");
13255
13386
  let hasConsumerDockerfile = false;
13256
13387
  try {
@@ -13350,8 +13481,8 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13350
13481
 
13351
13482
  // src/scripts/runTickScript.ts
13352
13483
  import { spawnSync as spawnSync2 } from "child_process";
13353
- import * as fs39 from "fs";
13354
- import * as path36 from "path";
13484
+ import * as fs40 from "fs";
13485
+ import * as path37 from "path";
13355
13486
  var runTickScript = async (ctx, _profile, args) => {
13356
13487
  ctx.skipAgent = true;
13357
13488
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -13363,22 +13494,22 @@ var runTickScript = async (ctx, _profile, args) => {
13363
13494
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
13364
13495
  return;
13365
13496
  }
13366
- const jobPath = path36.join(ctx.cwd, jobsDir, `${slug}.md`);
13367
- if (!fs39.existsSync(jobPath)) {
13497
+ const jobPath = path37.join(ctx.cwd, jobsDir, `${slug}.md`);
13498
+ if (!fs40.existsSync(jobPath)) {
13368
13499
  ctx.output.exitCode = 99;
13369
13500
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
13370
13501
  return;
13371
13502
  }
13372
- const raw = fs39.readFileSync(jobPath, "utf-8");
13373
- const { frontmatter } = splitFrontmatter(raw);
13503
+ const raw = fs40.readFileSync(jobPath, "utf-8");
13504
+ const { frontmatter } = splitFrontmatter2(raw);
13374
13505
  const tickScript = frontmatter.tickScript;
13375
13506
  if (!tickScript) {
13376
13507
  ctx.output.exitCode = 99;
13377
13508
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
13378
13509
  return;
13379
13510
  }
13380
- const scriptPath = path36.isAbsolute(tickScript) ? tickScript : path36.join(ctx.cwd, tickScript);
13381
- if (!fs39.existsSync(scriptPath)) {
13511
+ const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
13512
+ if (!fs40.existsSync(scriptPath)) {
13382
13513
  ctx.output.exitCode = 99;
13383
13514
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
13384
13515
  return;
@@ -14424,7 +14555,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
14424
14555
  };
14425
14556
 
14426
14557
  // src/scripts/writeRunSummary.ts
14427
- import * as fs40 from "fs";
14558
+ import * as fs41 from "fs";
14428
14559
  var writeRunSummary = async (ctx, profile) => {
14429
14560
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
14430
14561
  if (!summaryPath) return;
@@ -14446,7 +14577,7 @@ var writeRunSummary = async (ctx, profile) => {
14446
14577
  if (reason) lines.push(`- **Reason:** ${reason}`);
14447
14578
  lines.push("");
14448
14579
  try {
14449
- fs40.appendFileSync(summaryPath, `${lines.join("\n")}
14580
+ fs41.appendFileSync(summaryPath, `${lines.join("\n")}
14450
14581
  `);
14451
14582
  } catch {
14452
14583
  }
@@ -14554,8 +14685,8 @@ var allScriptNames = /* @__PURE__ */ new Set([
14554
14685
  ]);
14555
14686
 
14556
14687
  // src/staff.ts
14557
- import * as fs41 from "fs";
14558
- import * as path37 from "path";
14688
+ import * as fs42 from "fs";
14689
+ import * as path38 from "path";
14559
14690
  var DEFAULT_STAFF_DIR = ".kody/staff";
14560
14691
  function stripFrontmatter(raw) {
14561
14692
  const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
@@ -14564,11 +14695,11 @@ function stripFrontmatter(raw) {
14564
14695
  function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
14565
14696
  const trimmed = slug.trim();
14566
14697
  if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
14567
- const staffPath = path37.join(cwd, staffDir, `${trimmed}.md`);
14568
- if (!fs41.existsSync(staffPath)) {
14698
+ const staffPath = path38.join(cwd, staffDir, `${trimmed}.md`);
14699
+ if (!fs42.existsSync(staffPath)) {
14569
14700
  throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
14570
14701
  }
14571
- const body = stripFrontmatter(fs41.readFileSync(staffPath, "utf-8"));
14702
+ const body = stripFrontmatter(fs42.readFileSync(staffPath, "utf-8"));
14572
14703
  if (!body) throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
14573
14704
  return body;
14574
14705
  }
@@ -14585,48 +14716,6 @@ function framePersona(slug, persona) {
14585
14716
  ].join("\n");
14586
14717
  }
14587
14718
 
14588
- // src/subagents.ts
14589
- import * as fs42 from "fs";
14590
- import * as path38 from "path";
14591
- function splitFrontmatter2(raw) {
14592
- const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
14593
- if (!match) return { fm: {}, body: raw.trim() };
14594
- const fm = {};
14595
- for (const line of match[1].split("\n")) {
14596
- const idx = line.indexOf(":");
14597
- if (idx === -1) continue;
14598
- fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
14599
- }
14600
- return { fm, body: (match[2] ?? "").trim() };
14601
- }
14602
- function resolveAgentFile(profileDir, name) {
14603
- const local = path38.join(profileDir, "agents", `${name}.md`);
14604
- if (fs42.existsSync(local)) return local;
14605
- const central = path38.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
14606
- if (fs42.existsSync(central)) return central;
14607
- throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
14608
- }
14609
- function loadSubagents(profile) {
14610
- const names = profile.claudeCode.subagents;
14611
- if (!names || names.length === 0) return void 0;
14612
- const agents = {};
14613
- for (const name of names) {
14614
- const { fm, body } = splitFrontmatter2(fs42.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
14615
- if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
14616
- const def = {
14617
- description: fm.description ?? `Subagent ${name}`,
14618
- prompt: body
14619
- };
14620
- if (fm.tools) {
14621
- const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
14622
- if (tools.length > 0) def.tools = tools;
14623
- }
14624
- if (fm.model) def.model = fm.model;
14625
- agents[fm.name || name] = def;
14626
- }
14627
- return agents;
14628
- }
14629
-
14630
14719
  // src/tools.ts
14631
14720
  import { execFileSync as execFileSync27 } from "child_process";
14632
14721
  function verifyCliTools(tools, cwd) {