@kody-ade/kody-engine 0.4.204-next.0 → 0.4.204-next.10

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.0",
1486
+ version: "0.4.204-next.10",
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",
@@ -2375,6 +2375,9 @@ function getExecutablesRoot() {
2375
2375
  function getProjectExecutablesRoot() {
2376
2376
  return path7.join(process.cwd(), ".kody", "executables");
2377
2377
  }
2378
+ function getProjectDutiesRoot() {
2379
+ return path7.join(process.cwd(), ".kody", "duties");
2380
+ }
2378
2381
  function getBuiltinJobsRoot() {
2379
2382
  const here = path7.dirname(new URL(import.meta.url).pathname);
2380
2383
  const candidates = [
@@ -2402,7 +2405,7 @@ function listBuiltinJobs(root = getBuiltinJobsRoot()) {
2402
2405
  return out;
2403
2406
  }
2404
2407
  function getExecutableRoots() {
2405
- return [getProjectExecutablesRoot(), getExecutablesRoot()];
2408
+ return [getProjectDutiesRoot(), getProjectExecutablesRoot(), getExecutablesRoot()];
2406
2409
  }
2407
2410
  function listExecutables(roots = getExecutableRoots()) {
2408
2411
  const rootList = typeof roots === "string" ? [roots] : roots;
@@ -3723,11 +3726,11 @@ import * as path39 from "path";
3723
3726
  // src/container.ts
3724
3727
  init_events();
3725
3728
  import { execFileSync as execFileSync5 } from "child_process";
3726
- import * as fs17 from "fs";
3729
+ import * as fs19 from "fs";
3727
3730
 
3728
3731
  // src/profile.ts
3729
- import * as fs14 from "fs";
3730
- import * as path13 from "path";
3732
+ import * as fs16 from "fs";
3733
+ import * as path15 from "path";
3731
3734
 
3732
3735
  // src/profile-error.ts
3733
3736
  var ProfileError = class extends Error {
@@ -3867,6 +3870,150 @@ function applyLifecycle(profile, profilePath) {
3867
3870
  expander(profile, profilePath);
3868
3871
  }
3869
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
+
3870
4017
  // src/profile.ts
3871
4018
  var VALID_INPUT_TYPES = /* @__PURE__ */ new Set(["int", "string", "bool", "enum"]);
3872
4019
  var VALID_PERMISSION_MODES = /* @__PURE__ */ new Set(["default", "acceptEdits", "plan", "bypassPermissions"]);
@@ -3875,6 +4022,10 @@ var VALID_CONTAINER_CHILD_TARGETS = /* @__PURE__ */ new Set(["issue", "pr"]);
3875
4022
  var VALID_PHASES = /* @__PURE__ */ new Set(["research", "planning", "implementing", "reviewing", "shipped", "failed", "idle"]);
3876
4023
  var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
3877
4024
  "name",
4025
+ "staff",
4026
+ "every",
4027
+ "dutyTools",
4028
+ "mentions",
3878
4029
  "describe",
3879
4030
  "role",
3880
4031
  "kind",
@@ -3898,12 +4049,12 @@ var KNOWN_PROFILE_KEYS = /* @__PURE__ */ new Set([
3898
4049
  "preloadContext"
3899
4050
  ]);
3900
4051
  function loadProfile(profilePath) {
3901
- if (!fs14.existsSync(profilePath)) {
4052
+ if (!fs16.existsSync(profilePath)) {
3902
4053
  throw new ProfileError(profilePath, "file not found");
3903
4054
  }
3904
4055
  let raw;
3905
4056
  try {
3906
- raw = JSON.parse(fs14.readFileSync(profilePath, "utf-8"));
4057
+ raw = JSON.parse(fs16.readFileSync(profilePath, "utf-8"));
3907
4058
  } catch (err) {
3908
4059
  throw new ProfileError(profilePath, `invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
3909
4060
  }
@@ -3914,7 +4065,7 @@ function loadProfile(profilePath) {
3914
4065
  const unknownKeys = Object.keys(r).filter((k) => !KNOWN_PROFILE_KEYS.has(k));
3915
4066
  if (unknownKeys.length > 0) {
3916
4067
  process.stderr.write(
3917
- `[kody profile] ${path13.basename(path13.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
4068
+ `[kody profile] ${path15.basename(path15.dirname(profilePath))}: unknown top-level keys ignored: ${unknownKeys.join(", ")}
3918
4069
  `
3919
4070
  );
3920
4071
  }
@@ -3956,6 +4107,11 @@ function loadProfile(profilePath) {
3956
4107
  describe: typeof r.describe === "string" ? r.describe : "",
3957
4108
  // Optional persona to run as. Empty/blank string → undefined (no persona).
3958
4109
  staff: typeof r.staff === "string" && r.staff.trim() ? r.staff.trim() : void 0,
4110
+ // Optional recurrence cadence (scheduled duty). Blank → undefined (on-demand).
4111
+ every: typeof r.every === "string" && r.every.trim() ? r.every.trim() : void 0,
4112
+ // Locked-toolbox palette + mentions (folder-duty successors to frontmatter).
4113
+ dutyTools: Array.isArray(r.dutyTools) ? r.dutyTools.map((t) => String(t).trim()).filter(Boolean) : void 0,
4114
+ mentions: Array.isArray(r.mentions) ? r.mentions.map((m) => String(m).trim()).filter(Boolean) : void 0,
3959
4115
  role,
3960
4116
  kind,
3961
4117
  schedule: typeof r.schedule === "string" ? r.schedule : void 0,
@@ -3977,27 +4133,28 @@ function loadProfile(profilePath) {
3977
4133
  // Phase 5 in-process handoff opt-in. Default false; containers
3978
4134
  // flip to true after end-to-end verification.
3979
4135
  preloadContext: r.preloadContext === true,
3980
- dir: path13.dirname(profilePath),
3981
- promptTemplates: readPromptTemplates(path13.dirname(profilePath))
4136
+ dir: path15.dirname(profilePath),
4137
+ promptTemplates: readPromptTemplates(path15.dirname(profilePath))
3982
4138
  };
3983
4139
  if (lifecycle) {
3984
4140
  applyLifecycle(profile, profilePath);
3985
4141
  }
4142
+ profile.subagentTemplates = captureSubagentTemplates(profile);
3986
4143
  return profile;
3987
4144
  }
3988
4145
  function readPromptTemplates(dir) {
3989
4146
  const out = {};
3990
4147
  const read = (p) => {
3991
4148
  try {
3992
- out[p] = fs14.readFileSync(p, "utf-8");
4149
+ out[p] = fs16.readFileSync(p, "utf-8");
3993
4150
  } catch {
3994
4151
  }
3995
4152
  };
3996
- read(path13.join(dir, "prompt.md"));
4153
+ read(path15.join(dir, "prompt.md"));
3997
4154
  try {
3998
- const promptsDir = path13.join(dir, "prompts");
3999
- for (const ent of fs14.readdirSync(promptsDir)) {
4000
- if (ent.endsWith(".md")) read(path13.join(promptsDir, ent));
4155
+ const promptsDir = path15.join(dir, "prompts");
4156
+ for (const ent of fs16.readdirSync(promptsDir)) {
4157
+ if (ent.endsWith(".md")) read(path15.join(promptsDir, ent));
4001
4158
  }
4002
4159
  } catch {
4003
4160
  }
@@ -4337,16 +4494,17 @@ function parseStateComment(body) {
4337
4494
  flow: parsed.flow
4338
4495
  };
4339
4496
  }
4340
- function reduce(state, executable, action, phase) {
4497
+ function reduce(state, executable, action, phase, staff) {
4341
4498
  if (!action) return state;
4342
4499
  const newAttempts = { ...state.core.attempts, [executable]: (state.core.attempts[executable] ?? 0) + 1 };
4343
4500
  const newExecutables = {
4344
4501
  ...state.executables,
4345
4502
  [executable]: { ...state.executables[executable] ?? { lastAction: null }, lastAction: action }
4346
4503
  };
4504
+ const ranAsStaff = typeof staff === "string" && staff.length > 0 ? staff : void 0;
4347
4505
  const newHistory = [
4348
4506
  ...state.history,
4349
- { timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action) }
4507
+ { timestamp: action.timestamp, executable, action: action.type, note: noteFromAction(action), staff: ranAsStaff }
4350
4508
  ].slice(-HISTORY_MAX_ENTRIES);
4351
4509
  return {
4352
4510
  schemaVersion: 1,
@@ -4355,6 +4513,7 @@ function reduce(state, executable, action, phase) {
4355
4513
  attempts: newAttempts,
4356
4514
  lastOutcome: action,
4357
4515
  currentExecutable: executable,
4516
+ ranAsStaff: ranAsStaff ?? null,
4358
4517
  status: statusFromAction(action),
4359
4518
  phase: phaseFromAction(action, phase)
4360
4519
  },
@@ -4391,6 +4550,9 @@ function renderStateComment(state) {
4391
4550
  if (state.core.currentExecutable) {
4392
4551
  lines.push(`- **Last executable:** \`${state.core.currentExecutable}\``);
4393
4552
  }
4553
+ if (state.core.ranAsStaff) {
4554
+ lines.push(`- **Ran as:** \`${state.core.ranAsStaff}\``);
4555
+ }
4394
4556
  if (state.core.lastOutcome) {
4395
4557
  lines.push(`- **Last action:** \`${state.core.lastOutcome.type}\``);
4396
4558
  }
@@ -4473,7 +4635,7 @@ var CONTAINER_MAX_ITERATIONS = 50;
4473
4635
  function getProfileInputsForChild(profileName, _cwd) {
4474
4636
  try {
4475
4637
  const profilePath = resolveProfilePath(profileName);
4476
- if (!fs17.existsSync(profilePath)) return null;
4638
+ if (!fs19.existsSync(profilePath)) return null;
4477
4639
  return loadProfile(profilePath).inputs;
4478
4640
  } catch {
4479
4641
  return null;
@@ -4917,9 +5079,9 @@ function errMsg(err) {
4917
5079
 
4918
5080
  // src/litellm.ts
4919
5081
  import { execFileSync as execFileSync6, spawn as spawn3 } from "child_process";
4920
- import * as fs18 from "fs";
4921
- import * as os2 from "os";
4922
- import * as path16 from "path";
5082
+ import * as fs20 from "fs";
5083
+ import * as os3 from "os";
5084
+ import * as path18 from "path";
4923
5085
  async function checkLitellmHealth(url) {
4924
5086
  try {
4925
5087
  const response = await fetch(`${url}/health`, { signal: AbortSignal.timeout(3e3) });
@@ -4971,13 +5133,13 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4971
5133
  let child;
4972
5134
  let logPath;
4973
5135
  const spawnProxy = () => {
4974
- const configPath = path16.join(os2.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
4975
- fs18.writeFileSync(configPath, generateLitellmConfigYaml(model));
5136
+ const configPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.yaml`);
5137
+ fs20.writeFileSync(configPath, generateLitellmConfigYaml(model));
4976
5138
  const args = cmd === "litellm" ? ["--config", configPath, "--port", port] : ["-m", "litellm", "--config", configPath, "--port", port];
4977
- const nextLogPath = path16.join(os2.tmpdir(), `kody-litellm-${Date.now()}.log`);
4978
- const outFd = fs18.openSync(nextLogPath, "w");
5139
+ const nextLogPath = path18.join(os3.tmpdir(), `kody-litellm-${Date.now()}.log`);
5140
+ const outFd = fs20.openSync(nextLogPath, "w");
4979
5141
  child = spawn3(cmd, args, { stdio: ["ignore", outFd, outFd], detached: true, env: childEnv });
4980
- fs18.closeSync(outFd);
5142
+ fs20.closeSync(outFd);
4981
5143
  logPath = nextLogPath;
4982
5144
  };
4983
5145
  const waitForHealth = async () => {
@@ -4991,7 +5153,7 @@ async function startLitellmIfNeeded(model, projectDir, url = LITELLM_DEFAULT_URL
4991
5153
  const readLogTail = () => {
4992
5154
  if (!logPath) return "";
4993
5155
  try {
4994
- return fs18.readFileSync(logPath, "utf-8").slice(-2e3);
5156
+ return fs20.readFileSync(logPath, "utf-8").slice(-2e3);
4995
5157
  } catch {
4996
5158
  return "";
4997
5159
  }
@@ -5043,10 +5205,10 @@ ${tail}`
5043
5205
  return { url, kill: killChild, isHealthy, ensureHealthy };
5044
5206
  }
5045
5207
  function readDotenvApiKeys(projectDir) {
5046
- const dotenvPath = path16.join(projectDir, ".env");
5047
- if (!fs18.existsSync(dotenvPath)) return {};
5208
+ const dotenvPath = path18.join(projectDir, ".env");
5209
+ if (!fs20.existsSync(dotenvPath)) return {};
5048
5210
  const result = {};
5049
- for (const rawLine of fs18.readFileSync(dotenvPath, "utf-8").split("\n")) {
5211
+ for (const rawLine of fs20.readFileSync(dotenvPath, "utf-8").split("\n")) {
5050
5212
  const line = rawLine.trim();
5051
5213
  if (!line || line.startsWith("#")) continue;
5052
5214
  const match = line.match(/^([A-Z_][A-Z0-9_]*_API_KEY)=(.*)$/);
@@ -5148,8 +5310,8 @@ function pushWithRetry(opts = {}) {
5148
5310
  }
5149
5311
 
5150
5312
  // src/commit.ts
5151
- import * as fs19 from "fs";
5152
- import * as path17 from "path";
5313
+ import * as fs21 from "fs";
5314
+ import * as path19 from "path";
5153
5315
  var FORBIDDEN_PATH_PREFIXES = [
5154
5316
  ".kody/",
5155
5317
  ".kody-engine/",
@@ -5210,18 +5372,18 @@ function tryGit(args, cwd) {
5210
5372
  }
5211
5373
  function abortUnfinishedGitOps(cwd) {
5212
5374
  const aborted = [];
5213
- const gitDir = path17.join(cwd ?? process.cwd(), ".git");
5214
- if (!fs19.existsSync(gitDir)) return aborted;
5215
- if (fs19.existsSync(path17.join(gitDir, "MERGE_HEAD"))) {
5375
+ const gitDir = path19.join(cwd ?? process.cwd(), ".git");
5376
+ if (!fs21.existsSync(gitDir)) return aborted;
5377
+ if (fs21.existsSync(path19.join(gitDir, "MERGE_HEAD"))) {
5216
5378
  if (tryGit(["merge", "--abort"], cwd)) aborted.push("merge");
5217
5379
  }
5218
- if (fs19.existsSync(path17.join(gitDir, "CHERRY_PICK_HEAD"))) {
5380
+ if (fs21.existsSync(path19.join(gitDir, "CHERRY_PICK_HEAD"))) {
5219
5381
  if (tryGit(["cherry-pick", "--abort"], cwd)) aborted.push("cherry-pick");
5220
5382
  }
5221
- if (fs19.existsSync(path17.join(gitDir, "REVERT_HEAD"))) {
5383
+ if (fs21.existsSync(path19.join(gitDir, "REVERT_HEAD"))) {
5222
5384
  if (tryGit(["revert", "--abort"], cwd)) aborted.push("revert");
5223
5385
  }
5224
- if (fs19.existsSync(path17.join(gitDir, "rebase-merge")) || fs19.existsSync(path17.join(gitDir, "rebase-apply"))) {
5386
+ if (fs21.existsSync(path19.join(gitDir, "rebase-merge")) || fs21.existsSync(path19.join(gitDir, "rebase-apply"))) {
5225
5387
  if (tryGit(["rebase", "--abort"], cwd)) aborted.push("rebase");
5226
5388
  }
5227
5389
  try {
@@ -5277,7 +5439,7 @@ function normalizeCommitMessage(raw) {
5277
5439
  function commitAndPush(branch, agentMessage, cwd) {
5278
5440
  const allChanged = listChangedFiles(cwd);
5279
5441
  const allowedFiles = allChanged.filter((f) => !isForbiddenPath(f));
5280
- const mergeHeadExists = fs19.existsSync(path17.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
5442
+ const mergeHeadExists = fs21.existsSync(path19.join(cwd ?? process.cwd(), ".git", "MERGE_HEAD"));
5281
5443
  if (allowedFiles.length === 0 && !mergeHeadExists) {
5282
5444
  return { committed: false, pushed: false, sha: "", message: "" };
5283
5445
  }
@@ -5368,7 +5530,7 @@ var advanceFlow = async (ctx, profile) => {
5368
5530
  const action = ctx.data.action;
5369
5531
  let nextIssueState = issueState;
5370
5532
  if (targetType === "pr" && action) {
5371
- nextIssueState = reduce(issueState, profile.name, action, profile.phase);
5533
+ nextIssueState = reduce(issueState, profile.name, action, profile.phase, profile.staff);
5372
5534
  if (state?.core.prUrl && !nextIssueState.core.prUrl) nextIssueState.core.prUrl = state.core.prUrl;
5373
5535
  }
5374
5536
  const prevHops = issueState.flow?.hops ?? flow.hops ?? 0;
@@ -5409,7 +5571,7 @@ var advanceFlow = async (ctx, profile) => {
5409
5571
 
5410
5572
  // src/gha.ts
5411
5573
  import { execFileSync as execFileSync10 } from "child_process";
5412
- import * as fs20 from "fs";
5574
+ import * as fs22 from "fs";
5413
5575
  function getRunUrl() {
5414
5576
  const server = process.env.GITHUB_SERVER_URL;
5415
5577
  const repo = process.env.GITHUB_REPOSITORY;
@@ -5420,10 +5582,10 @@ function getRunUrl() {
5420
5582
  function reactToTriggerComment(cwd) {
5421
5583
  if (process.env.GITHUB_EVENT_NAME !== "issue_comment") return;
5422
5584
  const eventPath = process.env.GITHUB_EVENT_PATH;
5423
- if (!eventPath || !fs20.existsSync(eventPath)) return;
5585
+ if (!eventPath || !fs22.existsSync(eventPath)) return;
5424
5586
  let event = null;
5425
5587
  try {
5426
- event = JSON.parse(fs20.readFileSync(eventPath, "utf-8"));
5588
+ event = JSON.parse(fs22.readFileSync(eventPath, "utf-8"));
5427
5589
  } catch {
5428
5590
  return;
5429
5591
  }
@@ -5575,22 +5737,22 @@ var appendCompanyActivity = async (ctx, _profile, agentResult) => {
5575
5737
  };
5576
5738
 
5577
5739
  // src/scripts/brainServe.ts
5578
- import * as fs22 from "fs";
5740
+ import * as fs24 from "fs";
5579
5741
  import { createServer } from "http";
5580
- import * as path19 from "path";
5742
+ import * as path21 from "path";
5581
5743
  init_repoWorkspace();
5582
5744
 
5583
5745
  // src/scripts/brainTurnLog.ts
5584
- import * as fs21 from "fs";
5585
- import * as path18 from "path";
5746
+ import * as fs23 from "fs";
5747
+ import * as path20 from "path";
5586
5748
  var live = /* @__PURE__ */ new Map();
5587
5749
  function eventsPath(dir, chatId) {
5588
- return path18.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
5750
+ return path20.join(dir, ".kody", "brain-events", `${chatId}.jsonl`);
5589
5751
  }
5590
5752
  function lastPersistedSeq(dir, chatId) {
5591
5753
  const p = eventsPath(dir, chatId);
5592
- if (!fs21.existsSync(p)) return 0;
5593
- const lines = fs21.readFileSync(p, "utf-8").split("\n").filter(Boolean);
5754
+ if (!fs23.existsSync(p)) return 0;
5755
+ const lines = fs23.readFileSync(p, "utf-8").split("\n").filter(Boolean);
5594
5756
  if (lines.length === 0) return 0;
5595
5757
  try {
5596
5758
  return JSON.parse(lines[lines.length - 1]).seq || 0;
@@ -5600,9 +5762,9 @@ function lastPersistedSeq(dir, chatId) {
5600
5762
  }
5601
5763
  function readSince(dir, chatId, since) {
5602
5764
  const p = eventsPath(dir, chatId);
5603
- if (!fs21.existsSync(p)) return [];
5765
+ if (!fs23.existsSync(p)) return [];
5604
5766
  const out = [];
5605
- for (const line of fs21.readFileSync(p, "utf-8").split("\n")) {
5767
+ for (const line of fs23.readFileSync(p, "utf-8").split("\n")) {
5606
5768
  if (!line) continue;
5607
5769
  try {
5608
5770
  const rec = JSON.parse(line);
@@ -5628,12 +5790,12 @@ function beginTurn(dir, chatId) {
5628
5790
  };
5629
5791
  live.set(chatId, state);
5630
5792
  const p = eventsPath(dir, chatId);
5631
- fs21.mkdirSync(path18.dirname(p), { recursive: true });
5793
+ fs23.mkdirSync(path20.dirname(p), { recursive: true });
5632
5794
  return (event) => {
5633
5795
  state.seq += 1;
5634
5796
  const rec = { seq: state.seq, turn, ts: Date.now(), event };
5635
5797
  try {
5636
- fs21.appendFileSync(p, `${JSON.stringify(rec)}
5798
+ fs23.appendFileSync(p, `${JSON.stringify(rec)}
5637
5799
  `);
5638
5800
  } catch (err) {
5639
5801
  process.stderr.write(
@@ -5672,7 +5834,7 @@ function endTurnIfUnterminated(dir, chatId, errMessage) {
5672
5834
  event: { type: "error", error: errMessage || "turn ended unexpectedly", chatId }
5673
5835
  };
5674
5836
  try {
5675
- fs21.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
5837
+ fs23.appendFileSync(eventsPath(dir, chatId), `${JSON.stringify(rec)}
5676
5838
  `);
5677
5839
  } catch {
5678
5840
  }
@@ -5903,7 +6065,7 @@ async function handleChatTurn(req, res, chatId, opts) {
5903
6065
  const repo = strField(body, "repo");
5904
6066
  const repoToken = strField(body, "repoToken");
5905
6067
  const sessionFile = sessionFilePath(opts.cwd, chatId);
5906
- fs22.mkdirSync(path19.dirname(sessionFile), { recursive: true });
6068
+ fs24.mkdirSync(path21.dirname(sessionFile), { recursive: true });
5907
6069
  appendTurn(sessionFile, {
5908
6070
  role: "user",
5909
6071
  content: message,
@@ -5950,7 +6112,7 @@ async function handleChatTurn(req, res, chatId, opts) {
5950
6112
  function buildServer(opts) {
5951
6113
  const runTurn = opts.runTurn ?? runChatTurn;
5952
6114
  const cloneRepo = opts.cloneRepo ?? defaultCloneRepo;
5953
- const reposRoot = opts.reposRoot ?? path19.join(path19.dirname(path19.resolve(opts.cwd)), "repos");
6115
+ const reposRoot = opts.reposRoot ?? path21.join(path21.dirname(path21.resolve(opts.cwd)), "repos");
5954
6116
  return createServer(async (req, res) => {
5955
6117
  if (!req.method || !req.url) {
5956
6118
  sendJson(res, 400, { error: "bad request" });
@@ -6051,93 +6213,6 @@ var brainServe = async (ctx) => {
6051
6213
  });
6052
6214
  };
6053
6215
 
6054
- // src/scripts/buildSyntheticPlugin.ts
6055
- import * as fs23 from "fs";
6056
- import * as os3 from "os";
6057
- import * as path20 from "path";
6058
- function getPluginsCatalogRoot() {
6059
- const here = path20.dirname(new URL(import.meta.url).pathname);
6060
- const candidates = [
6061
- path20.join(here, "..", "plugins"),
6062
- // dev: src/scripts → src/plugins
6063
- path20.join(here, "..", "..", "plugins"),
6064
- // built: dist/scripts → dist/plugins
6065
- path20.join(here, "..", "..", "src", "plugins")
6066
- // fallback
6067
- ];
6068
- for (const c of candidates) {
6069
- if (fs23.existsSync(c) && fs23.statSync(c).isDirectory()) return c;
6070
- }
6071
- return candidates[0];
6072
- }
6073
- var buildSyntheticPlugin = async (ctx, profile) => {
6074
- const cc = profile.claudeCode;
6075
- const needsSynthetic = cc.skills.length > 0 || cc.commands.length > 0 || cc.hooks.length > 0;
6076
- if (!needsSynthetic) return;
6077
- const catalog = getPluginsCatalogRoot();
6078
- const runId = `${profile.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
6079
- const root = path20.join(os3.tmpdir(), `kody-synth-${runId}`);
6080
- fs23.mkdirSync(path20.join(root, ".claude-plugin"), { recursive: true });
6081
- const resolvePart = (bucket, entry) => {
6082
- const local = path20.join(profile.dir, bucket, entry);
6083
- if (fs23.existsSync(local)) return local;
6084
- const central = path20.join(catalog, bucket, entry);
6085
- if (fs23.existsSync(central)) return central;
6086
- throw new Error(
6087
- `buildSyntheticPlugin: ${bucket} entry '${entry}' not found in executable dir (${profile.dir}/${bucket}/) or catalog (${catalog}/${bucket}/)`
6088
- );
6089
- };
6090
- if (cc.skills.length > 0) {
6091
- const dst = path20.join(root, "skills");
6092
- fs23.mkdirSync(dst, { recursive: true });
6093
- for (const name of cc.skills) {
6094
- copyDir(resolvePart("skills", name), path20.join(dst, name));
6095
- }
6096
- }
6097
- if (cc.commands.length > 0) {
6098
- const dst = path20.join(root, "commands");
6099
- fs23.mkdirSync(dst, { recursive: true });
6100
- for (const name of cc.commands) {
6101
- fs23.copyFileSync(resolvePart("commands", `${name}.md`), path20.join(dst, `${name}.md`));
6102
- }
6103
- }
6104
- if (cc.hooks.length > 0) {
6105
- const dst = path20.join(root, "hooks");
6106
- fs23.mkdirSync(dst, { recursive: true });
6107
- const merged = { hooks: {} };
6108
- for (const name of cc.hooks) {
6109
- const src = resolvePart("hooks", `${name}.json`);
6110
- const parsed = JSON.parse(fs23.readFileSync(src, "utf-8"));
6111
- for (const [event, entries] of Object.entries(parsed.hooks ?? {})) {
6112
- if (!Array.isArray(entries)) continue;
6113
- if (!merged.hooks[event]) merged.hooks[event] = [];
6114
- merged.hooks[event].push(...entries);
6115
- }
6116
- }
6117
- fs23.writeFileSync(path20.join(dst, "hooks.json"), `${JSON.stringify(merged, null, 2)}
6118
- `);
6119
- }
6120
- const manifest = {
6121
- name: `kody-synth-${profile.name}`,
6122
- version: "1.0.0",
6123
- description: `Synthetic plugin assembled by Kody for profile '${profile.name}' at runtime.`
6124
- };
6125
- if (cc.skills.length > 0) manifest.skills = ["./skills/"];
6126
- if (cc.commands.length > 0) manifest.commands = ["./commands/"];
6127
- fs23.writeFileSync(path20.join(root, ".claude-plugin", "plugin.json"), `${JSON.stringify(manifest, null, 2)}
6128
- `);
6129
- ctx.data.syntheticPluginPath = root;
6130
- };
6131
- function copyDir(src, dst) {
6132
- fs23.mkdirSync(dst, { recursive: true });
6133
- for (const ent of fs23.readdirSync(src, { withFileTypes: true })) {
6134
- const s = path20.join(src, ent.name);
6135
- const d = path20.join(dst, ent.name);
6136
- if (ent.isDirectory()) copyDir(s, d);
6137
- else if (ent.isFile()) fs23.copyFileSync(s, d);
6138
- }
6139
- }
6140
-
6141
6216
  // src/coverage.ts
6142
6217
  import { execFileSync as execFileSync11 } from "child_process";
6143
6218
  function patternToRegex(pattern) {
@@ -6286,13 +6361,13 @@ function defaultLabelMap() {
6286
6361
  }
6287
6362
 
6288
6363
  // src/scripts/commitAndPush.ts
6289
- import * as fs24 from "fs";
6290
- import * as path21 from "path";
6364
+ import * as fs25 from "fs";
6365
+ import * as path22 from "path";
6291
6366
  init_events();
6292
6367
  var DEFAULT_COMMIT_MESSAGE = "chore: kody changes";
6293
6368
  function sentinelPathForStage(cwd, profileName) {
6294
6369
  const runId = resolveRunId();
6295
- return path21.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
6370
+ return path22.join(cwd, ".kody", "runs", runId, `commit-${profileName}.lock`);
6296
6371
  }
6297
6372
  var commitAndPush2 = async (ctx, profile) => {
6298
6373
  const branch = ctx.data.branch;
@@ -6302,9 +6377,9 @@ var commitAndPush2 = async (ctx, profile) => {
6302
6377
  }
6303
6378
  const idempotencyEnabled = process.env.KODY_COMMIT_IDEMPOTENCY !== "0";
6304
6379
  const sentinel = idempotencyEnabled ? sentinelPathForStage(ctx.cwd, profile.name) : null;
6305
- if (sentinel && fs24.existsSync(sentinel)) {
6380
+ if (sentinel && fs25.existsSync(sentinel)) {
6306
6381
  try {
6307
- const replay = JSON.parse(fs24.readFileSync(sentinel, "utf-8"));
6382
+ const replay = JSON.parse(fs25.readFileSync(sentinel, "utf-8"));
6308
6383
  ctx.data.commitResult = replay.commitResult ?? { committed: false, pushed: false };
6309
6384
  if (Array.isArray(replay.changedFiles)) ctx.data.changedFiles = replay.changedFiles;
6310
6385
  if (typeof replay.hasCommitsAhead === "boolean") ctx.data.hasCommitsAhead = replay.hasCommitsAhead;
@@ -6357,8 +6432,8 @@ var commitAndPush2 = async (ctx, profile) => {
6357
6432
  const result = ctx.data.commitResult;
6358
6433
  if (sentinel && result?.committed) {
6359
6434
  try {
6360
- fs24.mkdirSync(path21.dirname(sentinel), { recursive: true });
6361
- fs24.writeFileSync(
6435
+ fs25.mkdirSync(path22.dirname(sentinel), { recursive: true });
6436
+ fs25.writeFileSync(
6362
6437
  sentinel,
6363
6438
  JSON.stringify(
6364
6439
  {
@@ -6381,8 +6456,8 @@ var commitAndPush2 = async (ctx, profile) => {
6381
6456
  init_issue();
6382
6457
 
6383
6458
  // src/goal/state.ts
6384
- import * as fs25 from "fs";
6385
- import * as path22 from "path";
6459
+ import * as fs26 from "fs";
6460
+ import * as path23 from "path";
6386
6461
  var VALID_STATES = /* @__PURE__ */ new Set(["active", "abandoned", "closed", "awaiting-merge", "done"]);
6387
6462
  var GoalStateError = class extends Error {
6388
6463
  constructor(path42, message) {
@@ -6528,16 +6603,16 @@ function describeCommitMessage(goal) {
6528
6603
  }
6529
6604
 
6530
6605
  // src/scripts/composePrompt.ts
6531
- import * as fs26 from "fs";
6532
- import * as path23 from "path";
6606
+ import * as fs27 from "fs";
6607
+ import * as path24 from "path";
6533
6608
  var MUSTACHE = /\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g;
6534
6609
  var composePrompt = async (ctx, profile) => {
6535
6610
  const explicit = ctx.data.promptTemplate;
6536
6611
  const mode = ctx.args.mode;
6537
6612
  const candidates = [
6538
- explicit ? path23.join(profile.dir, explicit) : null,
6539
- mode ? path23.join(profile.dir, "prompts", `${mode}.md`) : null,
6540
- path23.join(profile.dir, "prompt.md")
6613
+ explicit ? path24.join(profile.dir, explicit) : null,
6614
+ mode ? path24.join(profile.dir, "prompts", `${mode}.md`) : null,
6615
+ path24.join(profile.dir, "prompt.md")
6541
6616
  ].filter(Boolean);
6542
6617
  let templatePath = "";
6543
6618
  let template = "";
@@ -6550,7 +6625,7 @@ var composePrompt = async (ctx, profile) => {
6550
6625
  break;
6551
6626
  }
6552
6627
  try {
6553
- template = fs26.readFileSync(c, "utf-8");
6628
+ template = fs27.readFileSync(c, "utf-8");
6554
6629
  templatePath = c;
6555
6630
  break;
6556
6631
  } catch (err) {
@@ -6561,7 +6636,7 @@ var composePrompt = async (ctx, profile) => {
6561
6636
  if (!templatePath) {
6562
6637
  let dirState;
6563
6638
  try {
6564
- dirState = `dir contents: [${fs26.readdirSync(profile.dir).join(", ")}]`;
6639
+ dirState = `dir contents: [${fs27.readdirSync(profile.dir).join(", ")}]`;
6565
6640
  } catch (err) {
6566
6641
  dirState = `readdir(${profile.dir}) failed: ${err?.code ?? String(err)}`;
6567
6642
  }
@@ -7386,15 +7461,15 @@ var deriveQaScopeFromIssue = async (ctx) => {
7386
7461
 
7387
7462
  // src/scripts/diagMcp.ts
7388
7463
  import { execFileSync as execFileSync12 } from "child_process";
7389
- import * as fs27 from "fs";
7464
+ import * as fs28 from "fs";
7390
7465
  import * as os4 from "os";
7391
- import * as path24 from "path";
7466
+ import * as path25 from "path";
7392
7467
  var diagMcp = async (_ctx) => {
7393
7468
  const home = os4.homedir();
7394
- const cacheDir = path24.join(home, ".cache", "ms-playwright");
7469
+ const cacheDir = path25.join(home, ".cache", "ms-playwright");
7395
7470
  let entries = [];
7396
7471
  try {
7397
- entries = fs27.readdirSync(cacheDir);
7472
+ entries = fs28.readdirSync(cacheDir);
7398
7473
  } catch {
7399
7474
  }
7400
7475
  const hasChromium = entries.some((e) => e.startsWith("chromium"));
@@ -7420,17 +7495,17 @@ var diagMcp = async (_ctx) => {
7420
7495
  };
7421
7496
 
7422
7497
  // src/scripts/discoverQaContext.ts
7423
- import * as fs29 from "fs";
7424
- import * as path26 from "path";
7498
+ import * as fs30 from "fs";
7499
+ import * as path27 from "path";
7425
7500
 
7426
7501
  // src/scripts/frameworkDetectors.ts
7427
- import * as fs28 from "fs";
7428
- import * as path25 from "path";
7502
+ import * as fs29 from "fs";
7503
+ import * as path26 from "path";
7429
7504
  function detectFrameworks(cwd) {
7430
7505
  const out = [];
7431
7506
  let deps = {};
7432
7507
  try {
7433
- const pkg = JSON.parse(fs28.readFileSync(path25.join(cwd, "package.json"), "utf-8"));
7508
+ const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
7434
7509
  deps = { ...pkg.dependencies, ...pkg.devDependencies };
7435
7510
  } catch {
7436
7511
  return out;
@@ -7467,7 +7542,7 @@ function detectFrameworks(cwd) {
7467
7542
  }
7468
7543
  function findFile(cwd, candidates) {
7469
7544
  for (const c of candidates) {
7470
- if (fs28.existsSync(path25.join(cwd, c))) return c;
7545
+ if (fs29.existsSync(path26.join(cwd, c))) return c;
7471
7546
  }
7472
7547
  return null;
7473
7548
  }
@@ -7480,18 +7555,18 @@ var COLLECTION_DIRS = [
7480
7555
  function discoverPayloadCollections(cwd) {
7481
7556
  const out = [];
7482
7557
  for (const dir of COLLECTION_DIRS) {
7483
- const full = path25.join(cwd, dir);
7484
- if (!fs28.existsSync(full)) continue;
7558
+ const full = path26.join(cwd, dir);
7559
+ if (!fs29.existsSync(full)) continue;
7485
7560
  let files;
7486
7561
  try {
7487
- files = fs28.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7562
+ files = fs29.readdirSync(full).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7488
7563
  } catch {
7489
7564
  continue;
7490
7565
  }
7491
7566
  for (const file of files) {
7492
7567
  try {
7493
- const filePath = path25.join(full, file);
7494
- const content = fs28.readFileSync(filePath, "utf-8").slice(0, 1e4);
7568
+ const filePath = path26.join(full, file);
7569
+ const content = fs29.readFileSync(filePath, "utf-8").slice(0, 1e4);
7495
7570
  const slugMatch = content.match(/slug:\s*['"]([a-z0-9-]+)['"]/);
7496
7571
  if (!slugMatch) continue;
7497
7572
  const slug = slugMatch[1];
@@ -7505,7 +7580,7 @@ function discoverPayloadCollections(cwd) {
7505
7580
  out.push({
7506
7581
  name,
7507
7582
  slug,
7508
- filePath: path25.relative(cwd, filePath),
7583
+ filePath: path26.relative(cwd, filePath),
7509
7584
  fields: fields.slice(0, 20),
7510
7585
  hasAdmin
7511
7586
  });
@@ -7519,28 +7594,28 @@ var ADMIN_COMPONENT_DIRS = ["src/ui/admin", "src/admin/components", "src/compone
7519
7594
  function discoverAdminComponents(cwd, collections) {
7520
7595
  const out = [];
7521
7596
  for (const dir of ADMIN_COMPONENT_DIRS) {
7522
- const full = path25.join(cwd, dir);
7523
- if (!fs28.existsSync(full)) continue;
7597
+ const full = path26.join(cwd, dir);
7598
+ if (!fs29.existsSync(full)) continue;
7524
7599
  let entries;
7525
7600
  try {
7526
- entries = fs28.readdirSync(full, { withFileTypes: true });
7601
+ entries = fs29.readdirSync(full, { withFileTypes: true });
7527
7602
  } catch {
7528
7603
  continue;
7529
7604
  }
7530
7605
  for (const entry of entries) {
7531
- const entryPath = path25.join(full, entry.name);
7606
+ const entryPath = path26.join(full, entry.name);
7532
7607
  let name;
7533
7608
  let filePath;
7534
7609
  if (entry.isDirectory()) {
7535
7610
  const indexFile = ["index.tsx", "index.ts", "index.jsx", "index.js"].find(
7536
- (f) => fs28.existsSync(path25.join(entryPath, f))
7611
+ (f) => fs29.existsSync(path26.join(entryPath, f))
7537
7612
  );
7538
7613
  if (!indexFile) continue;
7539
7614
  name = entry.name;
7540
- filePath = path25.relative(cwd, path25.join(entryPath, indexFile));
7615
+ filePath = path26.relative(cwd, path26.join(entryPath, indexFile));
7541
7616
  } else if (/\.(tsx?|jsx?)$/.test(entry.name)) {
7542
7617
  name = entry.name.replace(/\.(tsx?|jsx?)$/, "");
7543
- filePath = path25.relative(cwd, entryPath);
7618
+ filePath = path26.relative(cwd, entryPath);
7544
7619
  } else {
7545
7620
  continue;
7546
7621
  }
@@ -7548,7 +7623,7 @@ function discoverAdminComponents(cwd, collections) {
7548
7623
  if (collections) {
7549
7624
  for (const col of collections) {
7550
7625
  try {
7551
- const colContent = fs28.readFileSync(path25.join(cwd, col.filePath), "utf-8");
7626
+ const colContent = fs29.readFileSync(path26.join(cwd, col.filePath), "utf-8");
7552
7627
  if (colContent.includes(name)) {
7553
7628
  usedInCollection = col.slug;
7554
7629
  break;
@@ -7567,8 +7642,8 @@ function scanApiRoutes(cwd) {
7567
7642
  const out = [];
7568
7643
  const appDirs = ["src/app", "app"];
7569
7644
  for (const appDir of appDirs) {
7570
- const apiDir = path25.join(cwd, appDir, "api");
7571
- if (!fs28.existsSync(apiDir)) continue;
7645
+ const apiDir = path26.join(cwd, appDir, "api");
7646
+ if (!fs29.existsSync(apiDir)) continue;
7572
7647
  walkApiRoutes(apiDir, "/api", cwd, out);
7573
7648
  break;
7574
7649
  }
@@ -7577,14 +7652,14 @@ function scanApiRoutes(cwd) {
7577
7652
  function walkApiRoutes(dir, prefix, cwd, out) {
7578
7653
  let entries;
7579
7654
  try {
7580
- entries = fs28.readdirSync(dir, { withFileTypes: true });
7655
+ entries = fs29.readdirSync(dir, { withFileTypes: true });
7581
7656
  } catch {
7582
7657
  return;
7583
7658
  }
7584
7659
  const routeFile = entries.find((e) => e.isFile() && /^route\.(ts|js|tsx|jsx)$/.test(e.name));
7585
7660
  if (routeFile) {
7586
7661
  try {
7587
- const content = fs28.readFileSync(path25.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
7662
+ const content = fs29.readFileSync(path26.join(dir, routeFile.name), "utf-8").slice(0, 5e3);
7588
7663
  const methods = HTTP_METHODS.filter(
7589
7664
  (m) => new RegExp(`export\\s+(?:async\\s+)?function\\s+${m}\\b`).test(content)
7590
7665
  );
@@ -7592,7 +7667,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7592
7667
  out.push({
7593
7668
  path: prefix,
7594
7669
  methods,
7595
- filePath: path25.relative(cwd, path25.join(dir, routeFile.name))
7670
+ filePath: path26.relative(cwd, path26.join(dir, routeFile.name))
7596
7671
  });
7597
7672
  }
7598
7673
  } catch {
@@ -7603,7 +7678,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7603
7678
  if (entry.name === "node_modules" || entry.name === ".next") continue;
7604
7679
  let segment = entry.name;
7605
7680
  if (segment.startsWith("(") && segment.endsWith(")")) {
7606
- walkApiRoutes(path25.join(dir, entry.name), prefix, cwd, out);
7681
+ walkApiRoutes(path26.join(dir, entry.name), prefix, cwd, out);
7607
7682
  continue;
7608
7683
  }
7609
7684
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -7611,7 +7686,7 @@ function walkApiRoutes(dir, prefix, cwd, out) {
7611
7686
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
7612
7687
  segment = `:${segment.slice(1, -1)}`;
7613
7688
  }
7614
- walkApiRoutes(path25.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
7689
+ walkApiRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, cwd, out);
7615
7690
  }
7616
7691
  }
7617
7692
  var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
@@ -7631,10 +7706,10 @@ var BUILTIN_ENV_VARS = /* @__PURE__ */ new Set([
7631
7706
  function scanEnvVars(cwd) {
7632
7707
  const candidates = [".env.example", ".env.local.example", ".env.template"];
7633
7708
  for (const envFile of candidates) {
7634
- const envPath = path25.join(cwd, envFile);
7635
- if (!fs28.existsSync(envPath)) continue;
7709
+ const envPath = path26.join(cwd, envFile);
7710
+ if (!fs29.existsSync(envPath)) continue;
7636
7711
  try {
7637
- const content = fs28.readFileSync(envPath, "utf-8");
7712
+ const content = fs29.readFileSync(envPath, "utf-8");
7638
7713
  const vars = [];
7639
7714
  for (const line of content.split("\n")) {
7640
7715
  const trimmed = line.trim();
@@ -7682,9 +7757,9 @@ function runQaDiscovery(cwd) {
7682
7757
  }
7683
7758
  function detectDevServer(cwd, out) {
7684
7759
  try {
7685
- const pkg = JSON.parse(fs29.readFileSync(path26.join(cwd, "package.json"), "utf-8"));
7760
+ const pkg = JSON.parse(fs30.readFileSync(path27.join(cwd, "package.json"), "utf-8"));
7686
7761
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
7687
- 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";
7762
+ 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";
7688
7763
  if (pkg.scripts?.dev) out.devCommand = `${pm} dev`;
7689
7764
  if (allDeps.next || allDeps.nuxt) out.devPort = 3e3;
7690
7765
  else if (allDeps.vite) out.devPort = 5173;
@@ -7694,8 +7769,8 @@ function detectDevServer(cwd, out) {
7694
7769
  function scanFrontendRoutes(cwd, out) {
7695
7770
  const appDirs = ["src/app", "app"];
7696
7771
  for (const appDir of appDirs) {
7697
- const full = path26.join(cwd, appDir);
7698
- if (!fs29.existsSync(full)) continue;
7772
+ const full = path27.join(cwd, appDir);
7773
+ if (!fs30.existsSync(full)) continue;
7699
7774
  walkFrontendRoutes(full, "", out);
7700
7775
  break;
7701
7776
  }
@@ -7703,7 +7778,7 @@ function scanFrontendRoutes(cwd, out) {
7703
7778
  function walkFrontendRoutes(dir, prefix, out) {
7704
7779
  let entries;
7705
7780
  try {
7706
- entries = fs29.readdirSync(dir, { withFileTypes: true });
7781
+ entries = fs30.readdirSync(dir, { withFileTypes: true });
7707
7782
  } catch {
7708
7783
  return;
7709
7784
  }
@@ -7720,7 +7795,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7720
7795
  if (entry.name === "node_modules" || entry.name === ".next") continue;
7721
7796
  let segment = entry.name;
7722
7797
  if (segment.startsWith("(") && segment.endsWith(")")) {
7723
- walkFrontendRoutes(path26.join(dir, entry.name), prefix, out);
7798
+ walkFrontendRoutes(path27.join(dir, entry.name), prefix, out);
7724
7799
  continue;
7725
7800
  }
7726
7801
  if (segment.startsWith("[[") && segment.endsWith("]]")) {
@@ -7728,7 +7803,7 @@ function walkFrontendRoutes(dir, prefix, out) {
7728
7803
  } else if (segment.startsWith("[") && segment.endsWith("]")) {
7729
7804
  segment = `:${segment.slice(1, -1)}`;
7730
7805
  }
7731
- walkFrontendRoutes(path26.join(dir, entry.name), `${prefix}/${segment}`, out);
7806
+ walkFrontendRoutes(path27.join(dir, entry.name), `${prefix}/${segment}`, out);
7732
7807
  }
7733
7808
  }
7734
7809
  function detectAuthFiles(cwd, out) {
@@ -7745,23 +7820,23 @@ function detectAuthFiles(cwd, out) {
7745
7820
  "src/app/api/oauth"
7746
7821
  ];
7747
7822
  for (const c of candidates) {
7748
- if (fs29.existsSync(path26.join(cwd, c))) out.authFiles.push(c);
7823
+ if (fs30.existsSync(path27.join(cwd, c))) out.authFiles.push(c);
7749
7824
  }
7750
7825
  }
7751
7826
  function detectRoles(cwd, out) {
7752
7827
  const rolePaths = ["src/types", "src/lib", "src/utils", "src/constants", "src/access", "src/collections"];
7753
7828
  for (const rp of rolePaths) {
7754
- const dir = path26.join(cwd, rp);
7755
- if (!fs29.existsSync(dir)) continue;
7829
+ const dir = path27.join(cwd, rp);
7830
+ if (!fs30.existsSync(dir)) continue;
7756
7831
  let files;
7757
7832
  try {
7758
- files = fs29.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7833
+ files = fs30.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
7759
7834
  } catch {
7760
7835
  continue;
7761
7836
  }
7762
7837
  for (const f of files) {
7763
7838
  try {
7764
- const content = fs29.readFileSync(path26.join(dir, f), "utf-8").slice(0, 5e3);
7839
+ const content = fs30.readFileSync(path27.join(dir, f), "utf-8").slice(0, 5e3);
7765
7840
  const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
7766
7841
  if (roleMatches) {
7767
7842
  for (const m of roleMatches) {
@@ -7944,8 +8019,8 @@ ${stateBody}`;
7944
8019
  };
7945
8020
 
7946
8021
  // src/scripts/dispatchJobFileTicks.ts
7947
- import * as fs31 from "fs";
7948
- import * as path28 from "path";
8022
+ import * as fs32 from "fs";
8023
+ import * as path29 from "path";
7949
8024
 
7950
8025
  // src/scripts/jobFrontmatter.ts
7951
8026
  var SCHEDULE_EVERY_VALUES = [
@@ -7961,7 +8036,7 @@ var SCHEDULE_EVERY_VALUES = [
7961
8036
  "manual"
7962
8037
  ];
7963
8038
  var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
7964
- function splitFrontmatter(raw) {
8039
+ function splitFrontmatter2(raw) {
7965
8040
  const match = FRONTMATTER_RE.exec(raw);
7966
8041
  if (!match) return { frontmatter: {}, body: raw };
7967
8042
  const inner = match[1] ?? "";
@@ -8238,8 +8313,8 @@ function isShaConflict(err) {
8238
8313
  }
8239
8314
 
8240
8315
  // src/scripts/jobState/localFileBackend.ts
8241
- import * as fs30 from "fs";
8242
- import * as path27 from "path";
8316
+ import * as fs31 from "fs";
8317
+ import * as path28 from "path";
8243
8318
  var LocalFileBackend = class {
8244
8319
  name = "local-file";
8245
8320
  cwd;
@@ -8254,7 +8329,7 @@ var LocalFileBackend = class {
8254
8329
  if (!opts.owner || !opts.repo) throw new Error("LocalFileBackend: owner and repo are required");
8255
8330
  this.cwd = opts.cwd;
8256
8331
  this.jobsDir = opts.jobsDir;
8257
- this.absDir = path27.join(opts.cwd, opts.jobsDir);
8332
+ this.absDir = path28.join(opts.cwd, opts.jobsDir);
8258
8333
  this.owner = opts.owner;
8259
8334
  this.repo = opts.repo;
8260
8335
  this.cache = opts.cache ?? defaultCacheAdapter();
@@ -8269,7 +8344,7 @@ var LocalFileBackend = class {
8269
8344
  `);
8270
8345
  return;
8271
8346
  }
8272
- fs30.mkdirSync(this.absDir, { recursive: true });
8347
+ fs31.mkdirSync(this.absDir, { recursive: true });
8273
8348
  const prefix = this.cacheKeyPrefix();
8274
8349
  const probeKey = `${prefix}probe-${Date.now()}`;
8275
8350
  try {
@@ -8298,7 +8373,7 @@ var LocalFileBackend = class {
8298
8373
  `);
8299
8374
  return;
8300
8375
  }
8301
- if (!fs30.existsSync(this.absDir)) {
8376
+ if (!fs31.existsSync(this.absDir)) {
8302
8377
  return;
8303
8378
  }
8304
8379
  const key = `${this.cacheKeyPrefix()}${process.env.GITHUB_RUN_ID ?? "norunid"}-${Date.now()}`;
@@ -8314,11 +8389,11 @@ var LocalFileBackend = class {
8314
8389
  }
8315
8390
  load(slug) {
8316
8391
  const relPath = stateFilePath(this.jobsDir, slug);
8317
- const absPath = path27.join(this.cwd, relPath);
8318
- if (!fs30.existsSync(absPath)) {
8392
+ const absPath = path28.join(this.cwd, relPath);
8393
+ if (!fs31.existsSync(absPath)) {
8319
8394
  return { path: relPath, handle: null, state: initialStateEnvelope("seed"), created: true };
8320
8395
  }
8321
- const raw = fs30.readFileSync(absPath, "utf-8");
8396
+ const raw = fs31.readFileSync(absPath, "utf-8");
8322
8397
  let parsed;
8323
8398
  try {
8324
8399
  parsed = JSON.parse(raw);
@@ -8335,13 +8410,13 @@ var LocalFileBackend = class {
8335
8410
  if (!loaded.created && isStateUnchanged(loaded.state, next)) {
8336
8411
  return false;
8337
8412
  }
8338
- const absPath = path27.join(this.cwd, loaded.path);
8339
- fs30.mkdirSync(path27.dirname(absPath), { recursive: true });
8413
+ const absPath = path28.join(this.cwd, loaded.path);
8414
+ fs31.mkdirSync(path28.dirname(absPath), { recursive: true });
8340
8415
  const body = `${JSON.stringify(next, null, 2)}
8341
8416
  `;
8342
8417
  const tmpPath = `${absPath}.${process.pid}.tmp`;
8343
- fs30.writeFileSync(tmpPath, body, "utf-8");
8344
- fs30.renameSync(tmpPath, absPath);
8418
+ fs31.writeFileSync(tmpPath, body, "utf-8");
8419
+ fs31.renameSync(tmpPath, absPath);
8345
8420
  return true;
8346
8421
  }
8347
8422
  cacheKeyPrefix() {
@@ -8419,7 +8494,7 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
8419
8494
  await backend.hydrate();
8420
8495
  }
8421
8496
  try {
8422
- const slugs = listJobSlugs(path28.join(ctx.cwd, jobsDir));
8497
+ const slugs = listJobSlugs(path29.join(ctx.cwd, jobsDir));
8423
8498
  ctx.data.jobSlugCount = slugs.length;
8424
8499
  if (slugs.length === 0) {
8425
8500
  process.stdout.write(`[jobs] no job files in ${jobsDir}
@@ -8430,6 +8505,55 @@ var dispatchJobFileTicks = async (ctx, _profile, args) => {
8430
8505
  `);
8431
8506
  const results = [];
8432
8507
  const now = Date.now();
8508
+ const scheduledDuties = listFolderDutySlugs(path29.join(ctx.cwd, jobsDir)).map((slug) => {
8509
+ try {
8510
+ const p = loadProfile(path29.join(ctx.cwd, jobsDir, slug, "profile.json"));
8511
+ return { slug, every: p.every, staff: p.staff };
8512
+ } catch (err) {
8513
+ process.stderr.write(`[jobs] \u23ED skip folder-duty ${slug}: profile load failed: ${String(err)}
8514
+ `);
8515
+ return null;
8516
+ }
8517
+ }).filter((d) => d !== null && Boolean(d.every));
8518
+ process.stdout.write(`[jobs] ${scheduledDuties.length} scheduled folder-dut(y/ies) to consider
8519
+ `);
8520
+ for (const { slug, every, staff } of scheduledDuties) {
8521
+ if (!staff || staff.trim().length === 0) {
8522
+ process.stderr.write(`[jobs] \u23ED skip ${slug}: scheduled duty has no staff
8523
+ `);
8524
+ results.push({ slug, exitCode: 0, skipped: true, reason: "no staff assigned" });
8525
+ continue;
8526
+ }
8527
+ const decision = await decideShouldFire(every, slug, backend, now);
8528
+ if (decision.skip) {
8529
+ process.stdout.write(`[jobs] \u23ED skip ${slug}: ${decision.reason}
8530
+ `);
8531
+ results.push({ slug, exitCode: 0, skipped: true, reason: decision.reason });
8532
+ continue;
8533
+ }
8534
+ await stampFired(backend, slug, now);
8535
+ process.stdout.write(`[jobs] \u2192 run scheduled duty ${slug} (one-shot, as ${staff})
8536
+ `);
8537
+ try {
8538
+ const out = await runExecutable(slug, {
8539
+ cliArgs: {},
8540
+ cwd: ctx.cwd,
8541
+ config: ctx.config,
8542
+ verbose: ctx.verbose,
8543
+ quiet: ctx.quiet
8544
+ });
8545
+ results.push({ slug, exitCode: out.exitCode, reason: out.reason });
8546
+ if (out.exitCode !== 0) {
8547
+ process.stderr.write(`[jobs] scheduled duty ${slug} failed (exit ${out.exitCode}): ${out.reason ?? ""}
8548
+ `);
8549
+ }
8550
+ } catch (err) {
8551
+ const msg = err instanceof Error ? err.message : String(err);
8552
+ process.stderr.write(`[jobs] scheduled duty ${slug} crashed: ${msg}
8553
+ `);
8554
+ results.push({ slug, exitCode: 99, reason: msg });
8555
+ }
8556
+ }
8433
8557
  for (const slug of slugs) {
8434
8558
  const frontmatter = readJobFrontmatter(ctx.cwd, jobsDir, slug);
8435
8559
  if (frontmatter.disabled === true) {
@@ -8530,22 +8654,42 @@ function formatAgo(ms) {
8530
8654
  }
8531
8655
  function readJobFrontmatter(cwd, jobsDir, slug) {
8532
8656
  try {
8533
- const raw = fs31.readFileSync(path28.join(cwd, jobsDir, `${slug}.md`), "utf-8");
8534
- return splitFrontmatter(raw).frontmatter;
8657
+ const raw = fs32.readFileSync(path29.join(cwd, jobsDir, `${slug}.md`), "utf-8");
8658
+ return splitFrontmatter2(raw).frontmatter;
8535
8659
  } catch {
8536
8660
  return {};
8537
8661
  }
8538
8662
  }
8539
8663
  function listJobSlugs(absDir) {
8540
- if (!fs31.existsSync(absDir)) return [];
8664
+ if (!fs32.existsSync(absDir)) return [];
8541
8665
  let entries;
8542
8666
  try {
8543
- entries = fs31.readdirSync(absDir, { withFileTypes: true });
8667
+ entries = fs32.readdirSync(absDir, { withFileTypes: true });
8544
8668
  } catch {
8545
8669
  return [];
8546
8670
  }
8547
8671
  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();
8548
8672
  }
8673
+ function listFolderDutySlugs(absDir) {
8674
+ if (!fs32.existsSync(absDir)) return [];
8675
+ let entries;
8676
+ try {
8677
+ entries = fs32.readdirSync(absDir, { withFileTypes: true });
8678
+ } catch {
8679
+ return [];
8680
+ }
8681
+ 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();
8682
+ }
8683
+ async function stampFired(backend, slug, now) {
8684
+ try {
8685
+ const loaded = await backend.load(slug);
8686
+ const nextData = { ...loaded.state.data ?? {}, lastFiredAt: new Date(now).toISOString() };
8687
+ await backend.save(loaded, { ...loaded.state, data: nextData });
8688
+ } catch (err) {
8689
+ process.stderr.write(`[jobs] failed to stamp lastFiredAt for ${slug}: ${String(err)}
8690
+ `);
8691
+ }
8692
+ }
8549
8693
 
8550
8694
  // src/scripts/dispatchJobTicks.ts
8551
8695
  init_issue();
@@ -9599,12 +9743,12 @@ var handleAbandonedGoal = async (ctx) => {
9599
9743
 
9600
9744
  // src/scripts/initFlow.ts
9601
9745
  import { execFileSync as execFileSync18 } from "child_process";
9602
- import * as fs32 from "fs";
9603
- import * as path29 from "path";
9746
+ import * as fs33 from "fs";
9747
+ import * as path30 from "path";
9604
9748
  function detectPackageManager(cwd) {
9605
- if (fs32.existsSync(path29.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9606
- if (fs32.existsSync(path29.join(cwd, "yarn.lock"))) return "yarn";
9607
- if (fs32.existsSync(path29.join(cwd, "bun.lockb"))) return "bun";
9749
+ if (fs33.existsSync(path30.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
9750
+ if (fs33.existsSync(path30.join(cwd, "yarn.lock"))) return "yarn";
9751
+ if (fs33.existsSync(path30.join(cwd, "bun.lockb"))) return "bun";
9608
9752
  return "npm";
9609
9753
  }
9610
9754
  function qualityCommandsFor(pm) {
@@ -9733,36 +9877,36 @@ function performInit(cwd, force) {
9733
9877
  const pm = detectPackageManager(cwd);
9734
9878
  const ownerRepo = detectOwnerRepo(cwd);
9735
9879
  const defaultBranch2 = defaultBranchFromGit(cwd);
9736
- const configPath = path29.join(cwd, "kody.config.json");
9737
- if (fs32.existsSync(configPath) && !force) {
9880
+ const configPath = path30.join(cwd, "kody.config.json");
9881
+ if (fs33.existsSync(configPath) && !force) {
9738
9882
  skipped.push("kody.config.json");
9739
9883
  } else {
9740
9884
  const cfg = makeConfig(pm, ownerRepo, defaultBranch2);
9741
- fs32.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9885
+ fs33.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
9742
9886
  `);
9743
9887
  wrote.push("kody.config.json");
9744
9888
  }
9745
- const workflowDir = path29.join(cwd, ".github", "workflows");
9746
- const workflowPath = path29.join(workflowDir, "kody.yml");
9747
- if (fs32.existsSync(workflowPath) && !force) {
9889
+ const workflowDir = path30.join(cwd, ".github", "workflows");
9890
+ const workflowPath = path30.join(workflowDir, "kody.yml");
9891
+ if (fs33.existsSync(workflowPath) && !force) {
9748
9892
  skipped.push(".github/workflows/kody.yml");
9749
9893
  } else {
9750
- fs32.mkdirSync(workflowDir, { recursive: true });
9751
- fs32.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9894
+ fs33.mkdirSync(workflowDir, { recursive: true });
9895
+ fs33.writeFileSync(workflowPath, WORKFLOW_TEMPLATE);
9752
9896
  wrote.push(".github/workflows/kody.yml");
9753
9897
  }
9754
9898
  const builtinJobs = listBuiltinJobs();
9755
9899
  if (builtinJobs.length > 0) {
9756
- const jobsDir = path29.join(cwd, ".kody", "duties");
9757
- fs32.mkdirSync(jobsDir, { recursive: true });
9900
+ const jobsDir = path30.join(cwd, ".kody", "duties");
9901
+ fs33.mkdirSync(jobsDir, { recursive: true });
9758
9902
  for (const job of builtinJobs) {
9759
- const rel = path29.join(".kody", "duties", `${job.slug}.md`);
9760
- const target = path29.join(cwd, rel);
9761
- if (fs32.existsSync(target) && !force) {
9903
+ const rel = path30.join(".kody", "duties", `${job.slug}.md`);
9904
+ const target = path30.join(cwd, rel);
9905
+ if (fs33.existsSync(target) && !force) {
9762
9906
  skipped.push(rel);
9763
9907
  continue;
9764
9908
  }
9765
- fs32.writeFileSync(target, fs32.readFileSync(job.filePath, "utf-8"));
9909
+ fs33.writeFileSync(target, fs33.readFileSync(job.filePath, "utf-8"));
9766
9910
  wrote.push(rel);
9767
9911
  }
9768
9912
  }
@@ -9774,12 +9918,12 @@ function performInit(cwd, force) {
9774
9918
  continue;
9775
9919
  }
9776
9920
  if (profile.kind !== "scheduled" || !profile.schedule) continue;
9777
- const target = path29.join(workflowDir, `kody-${exe.name}.yml`);
9778
- if (fs32.existsSync(target) && !force) {
9921
+ const target = path30.join(workflowDir, `kody-${exe.name}.yml`);
9922
+ if (fs33.existsSync(target) && !force) {
9779
9923
  skipped.push(`.github/workflows/kody-${exe.name}.yml`);
9780
9924
  continue;
9781
9925
  }
9782
- fs32.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9926
+ fs33.writeFileSync(target, renderScheduledWorkflow(exe.name, profile.schedule));
9783
9927
  wrote.push(`.github/workflows/kody-${exe.name}.yml`);
9784
9928
  }
9785
9929
  let labels;
@@ -9964,8 +10108,8 @@ var loadIssueStateComment = async (ctx, _profile, args) => {
9964
10108
 
9965
10109
  // src/scripts/loadJobFromFile.ts
9966
10110
  init_dutyMcp();
9967
- import * as fs33 from "fs";
9968
- import * as path30 from "path";
10111
+ import * as fs34 from "fs";
10112
+ import * as path31 from "path";
9969
10113
  var DUTY_TOOL_PALETTE = new Set(DUTY_MCP_TOOL_NAMES);
9970
10114
  var loadJobFromFile = async (ctx, profile, args) => {
9971
10115
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -9975,23 +10119,23 @@ var loadJobFromFile = async (ctx, profile, args) => {
9975
10119
  if (!slug) {
9976
10120
  throw new Error(`loadJobFromFile: ctx.args.${slugArg} must be a non-empty slug`);
9977
10121
  }
9978
- const absPath = path30.join(ctx.cwd, jobsDir, `${slug}.md`);
9979
- if (!fs33.existsSync(absPath)) {
10122
+ const absPath = path31.join(ctx.cwd, jobsDir, `${slug}.md`);
10123
+ if (!fs34.existsSync(absPath)) {
9980
10124
  throw new Error(`loadJobFromFile: job file not found: ${absPath}`);
9981
10125
  }
9982
- const raw = fs33.readFileSync(absPath, "utf-8");
10126
+ const raw = fs34.readFileSync(absPath, "utf-8");
9983
10127
  const { title, body } = parseJobFile(raw, slug);
9984
- const frontmatter = splitFrontmatter(raw).frontmatter;
10128
+ const frontmatter = splitFrontmatter2(raw).frontmatter;
9985
10129
  const mentions = (frontmatter.mentions ?? []).map((login) => `@${login}`).join(" ");
9986
10130
  const workerSlug = (frontmatter.staff ?? "").trim();
9987
10131
  let workerTitle = "";
9988
10132
  let workerPersona = "";
9989
10133
  if (workerSlug) {
9990
- const workerPath = path30.join(ctx.cwd, workersDir, `${workerSlug}.md`);
9991
- if (!fs33.existsSync(workerPath)) {
10134
+ const workerPath = path31.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10135
+ if (!fs34.existsSync(workerPath)) {
9992
10136
  throw new Error(`loadJobFromFile: duty '${slug}' declares staff '${workerSlug}' but ${workerPath} does not exist`);
9993
10137
  }
9994
- const workerRaw = fs33.readFileSync(workerPath, "utf-8");
10138
+ const workerRaw = fs34.readFileSync(workerPath, "utf-8");
9995
10139
  const parsed = parseJobFile(workerRaw, workerSlug);
9996
10140
  workerTitle = parsed.title;
9997
10141
  workerPersona = parsed.body;
@@ -10044,6 +10188,36 @@ function humanizeSlug(slug) {
10044
10188
  return slug.split(/[-_]+/).filter((s) => s.length > 0).map((s) => s[0].toUpperCase() + s.slice(1)).join(" ");
10045
10189
  }
10046
10190
 
10191
+ // src/scripts/loadDutyState.ts
10192
+ init_dutyMcp();
10193
+ var DUTY_TOOL_PALETTE2 = new Set(DUTY_MCP_TOOL_NAMES);
10194
+ var loadDutyState = async (ctx, profile, args) => {
10195
+ const jobsDir = String(args?.jobsDir ?? ".kody/duties");
10196
+ const slug = profile.name;
10197
+ const backend = resolveBackend({ config: ctx.config, cwd: ctx.cwd, jobsDir });
10198
+ if (backend.hydrate) await backend.hydrate();
10199
+ const loaded = await backend.load(slug);
10200
+ ctx.data.jobSlug = slug;
10201
+ ctx.data.jobState = loaded;
10202
+ ctx.data.jobStateJson = JSON.stringify(loaded.state, null, 2);
10203
+ const mentions = (profile.mentions ?? []).map((l) => `@${l}`).join(" ");
10204
+ ctx.data.mentions = mentions;
10205
+ const declaredTools = profile.dutyTools ?? [];
10206
+ if (declaredTools.length > 0) {
10207
+ const unknown = declaredTools.filter((name) => !DUTY_TOOL_PALETTE2.has(name));
10208
+ if (unknown.length > 0) {
10209
+ throw new Error(
10210
+ `loadDutyState: duty '${slug}' declared dutyTools not in the kody-duty palette: ${unknown.join(", ")}. Available: ${[...DUTY_MCP_TOOL_NAMES].join(", ")}`
10211
+ );
10212
+ }
10213
+ ctx.data.dutyTools = declaredTools;
10214
+ ctx.data.dutyToolsList = declaredTools.map((name) => `- \`${name}\``).join("\n");
10215
+ ctx.data.dutyOperatorMention = mentions;
10216
+ const mcpToolNames = declaredTools.map((name) => `mcp__kody-duty__${name}`);
10217
+ profile.claudeCode.tools = [...mcpToolNames, "mcp__kody-submit__submit_state"];
10218
+ }
10219
+ };
10220
+
10047
10221
  // src/scripts/loadLinkedFinding.ts
10048
10222
  init_issue();
10049
10223
  var FINDING_BODY_MAX_BYTES = 4e3;
@@ -10075,18 +10249,18 @@ init_loadMemoryContext();
10075
10249
  init_loadPriorArt();
10076
10250
 
10077
10251
  // src/scripts/loadQaContext.ts
10078
- import * as fs35 from "fs";
10079
- import * as path32 from "path";
10252
+ import * as fs36 from "fs";
10253
+ import * as path33 from "path";
10080
10254
 
10081
10255
  // src/scripts/kodyVariables.ts
10082
- import * as fs34 from "fs";
10083
- import * as path31 from "path";
10256
+ import * as fs35 from "fs";
10257
+ import * as path32 from "path";
10084
10258
  var KODY_VARIABLES_REL_PATH = ".kody/variables.json";
10085
10259
  function readKodyVariables(cwd) {
10086
- const full = path31.join(cwd, KODY_VARIABLES_REL_PATH);
10260
+ const full = path32.join(cwd, KODY_VARIABLES_REL_PATH);
10087
10261
  let raw;
10088
10262
  try {
10089
- raw = fs34.readFileSync(full, "utf-8");
10263
+ raw = fs35.readFileSync(full, "utf-8");
10090
10264
  } catch {
10091
10265
  return {};
10092
10266
  }
@@ -10137,18 +10311,18 @@ function readProfileStaff(raw) {
10137
10311
  return { staff: staff ?? legacy ?? ["kody"], body };
10138
10312
  }
10139
10313
  function readProfile(cwd) {
10140
- const dir = path32.join(cwd, CONTEXT_DIR_REL_PATH);
10141
- if (!fs35.existsSync(dir)) return "";
10314
+ const dir = path33.join(cwd, CONTEXT_DIR_REL_PATH);
10315
+ if (!fs36.existsSync(dir)) return "";
10142
10316
  let entries;
10143
10317
  try {
10144
- entries = fs35.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
10318
+ entries = fs36.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
10145
10319
  } catch {
10146
10320
  return "";
10147
10321
  }
10148
10322
  const blocks = [];
10149
10323
  for (const file of entries) {
10150
10324
  try {
10151
- const raw = fs35.readFileSync(path32.join(dir, file), "utf-8");
10325
+ const raw = fs36.readFileSync(path33.join(dir, file), "utf-8");
10152
10326
  const { staff, body } = readProfileStaff(raw);
10153
10327
  if (!staff.includes(QA_STAFF) && !staff.includes(ALL_STAFF)) continue;
10154
10328
  blocks.push(`## ${file}
@@ -10185,8 +10359,8 @@ var loadQaContext = async (ctx) => {
10185
10359
  init_events();
10186
10360
 
10187
10361
  // src/taskContext.ts
10188
- import * as fs36 from "fs";
10189
- import * as path33 from "path";
10362
+ import * as fs37 from "fs";
10363
+ import * as path34 from "path";
10190
10364
  var TASK_CONTEXT_SCHEMA_VERSION = 1;
10191
10365
  function buildTaskContext(args) {
10192
10366
  return {
@@ -10202,10 +10376,10 @@ function buildTaskContext(args) {
10202
10376
  }
10203
10377
  function persistTaskContext(cwd, ctx) {
10204
10378
  try {
10205
- const dir = path33.join(cwd, ".kody", "runs", ctx.runId);
10206
- fs36.mkdirSync(dir, { recursive: true });
10207
- const file = path33.join(dir, "task-context.json");
10208
- fs36.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
10379
+ const dir = path34.join(cwd, ".kody", "runs", ctx.runId);
10380
+ fs37.mkdirSync(dir, { recursive: true });
10381
+ const file = path34.join(dir, "task-context.json");
10382
+ fs37.writeFileSync(file, `${JSON.stringify(ctx, null, 2)}
10209
10383
  `);
10210
10384
  return file;
10211
10385
  } catch (err) {
@@ -10271,19 +10445,19 @@ var loadTaskState = async (ctx) => {
10271
10445
  };
10272
10446
 
10273
10447
  // src/scripts/loadWorkerAdhoc.ts
10274
- import * as fs37 from "fs";
10275
- import * as path34 from "path";
10448
+ import * as fs38 from "fs";
10449
+ import * as path35 from "path";
10276
10450
  var loadWorkerAdhoc = async (ctx, _profile, args) => {
10277
10451
  const workersDir = String(args?.workersDir ?? ".kody/staff");
10278
10452
  const workerSlug = String(ctx.args.worker ?? "").trim();
10279
10453
  if (!workerSlug) {
10280
10454
  throw new Error("loadWorkerAdhoc: ctx.args.worker must be a non-empty slug");
10281
10455
  }
10282
- const workerPath = path34.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10283
- if (!fs37.existsSync(workerPath)) {
10456
+ const workerPath = path35.join(ctx.cwd, workersDir, `${workerSlug}.md`);
10457
+ if (!fs38.existsSync(workerPath)) {
10284
10458
  throw new Error(`loadWorkerAdhoc: worker persona not found: ${workerPath}`);
10285
10459
  }
10286
- const { title, body } = parsePersona(fs37.readFileSync(workerPath, "utf-8"), workerSlug);
10460
+ const { title, body } = parsePersona(fs38.readFileSync(workerPath, "utf-8"), workerSlug);
10287
10461
  const message = resolveMessage(ctx.args.message);
10288
10462
  if (!message) {
10289
10463
  throw new Error(
@@ -10303,9 +10477,9 @@ function resolveMessage(messageArg) {
10303
10477
  }
10304
10478
  function readCommentBody() {
10305
10479
  const eventPath = process.env.GITHUB_EVENT_PATH;
10306
- if (!eventPath || !fs37.existsSync(eventPath)) return "";
10480
+ if (!eventPath || !fs38.existsSync(eventPath)) return "";
10307
10481
  try {
10308
- const event = JSON.parse(fs37.readFileSync(eventPath, "utf-8"));
10482
+ const event = JSON.parse(fs38.readFileSync(eventPath, "utf-8"));
10309
10483
  return String(event.comment?.body ?? "");
10310
10484
  } catch {
10311
10485
  return "";
@@ -10329,7 +10503,7 @@ function stripDirective(body) {
10329
10503
  return lines.slice(start).join("\n").trim();
10330
10504
  }
10331
10505
  function parsePersona(raw, slug) {
10332
- const stripped = splitFrontmatter(raw).body;
10506
+ const stripped = splitFrontmatter2(raw).body;
10333
10507
  const trimmed = stripped.trim();
10334
10508
  const firstLine2 = trimmed.split("\n", 1)[0] ?? "";
10335
10509
  const h1 = /^#\s+(.+?)\s*$/.exec(firstLine2);
@@ -12624,7 +12798,7 @@ function resolveBaseOverride(value) {
12624
12798
 
12625
12799
  // src/scripts/runnerServe.ts
12626
12800
  import { spawn as spawn5 } from "child_process";
12627
- import * as fs38 from "fs";
12801
+ import * as fs39 from "fs";
12628
12802
  import { createServer as createServer3 } from "http";
12629
12803
  var DEFAULT_PORT2 = 8080;
12630
12804
  var DEFAULT_WORKDIR = "/workspace/repo";
@@ -12704,8 +12878,8 @@ async function defaultRunJob(job) {
12704
12878
  const workdir = process.env.RUNNER_WORKDIR ?? DEFAULT_WORKDIR;
12705
12879
  const branch = job.ref ?? "main";
12706
12880
  const authUrl = `https://x-access-token:${job.githubToken}@github.com/${job.repo}.git`;
12707
- fs38.rmSync(workdir, { recursive: true, force: true });
12708
- fs38.mkdirSync(workdir, { recursive: true });
12881
+ fs39.rmSync(workdir, { recursive: true, force: true });
12882
+ fs39.mkdirSync(workdir, { recursive: true });
12709
12883
  const allSecrets = typeof job.allSecrets === "string" ? job.allSecrets : JSON.stringify(job.allSecrets ?? {});
12710
12884
  const interactive = job.mode === "interactive";
12711
12885
  const scheduled = job.mode === "scheduled";
@@ -12838,7 +13012,7 @@ var runnerServe = async (ctx) => {
12838
13012
 
12839
13013
  // src/scripts/runPreviewBuild.ts
12840
13014
  import { copyFile, writeFile } from "fs/promises";
12841
- import * as path35 from "path";
13015
+ import * as path36 from "path";
12842
13016
  import { fileURLToPath } from "url";
12843
13017
 
12844
13018
  // src/scripts/previewBuildHelpers.ts
@@ -12987,9 +13161,9 @@ var FLY_MACHINES = "https://api.machines.dev/v1";
12987
13161
  var FLY_GRAPHQL = "https://api.fly.io/graphql";
12988
13162
  var REQ_TIMEOUT_MS2 = 3e4;
12989
13163
  function bundledDockerfilePath(mode) {
12990
- const here = path35.dirname(fileURLToPath(import.meta.url));
13164
+ const here = path36.dirname(fileURLToPath(import.meta.url));
12991
13165
  const file = mode === "dev" ? "default-Dockerfile.preview.dev" : "default-Dockerfile.preview.prod";
12992
- return path35.join(here, "preview-build-templates", file);
13166
+ return path36.join(here, "preview-build-templates", file);
12993
13167
  }
12994
13168
  function required(name) {
12995
13169
  const v = (process.env[name] ?? "").trim();
@@ -13238,10 +13412,10 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13238
13412
  console.log(`[preview-build] vault: ${Object.keys(buildEnv).length} secrets, mode=${buildMode}`);
13239
13413
  if (Object.keys(buildEnv).length > 0) {
13240
13414
  const lines = Object.entries(buildEnv).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
13241
- await writeFile(path35.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
13415
+ await writeFile(path36.join(ctx.cwd, ".env.production.local"), `${lines.join("\n")}
13242
13416
  `, "utf8");
13243
13417
  }
13244
- const consumerDockerfile = path35.join(ctx.cwd, "Dockerfile.preview");
13418
+ const consumerDockerfile = path36.join(ctx.cwd, "Dockerfile.preview");
13245
13419
  const { stat } = await import("fs/promises");
13246
13420
  let hasConsumerDockerfile = false;
13247
13421
  try {
@@ -13341,8 +13515,8 @@ var runPreviewBuild = async (ctx, _profile, _args) => {
13341
13515
 
13342
13516
  // src/scripts/runTickScript.ts
13343
13517
  import { spawnSync as spawnSync2 } from "child_process";
13344
- import * as fs39 from "fs";
13345
- import * as path36 from "path";
13518
+ import * as fs40 from "fs";
13519
+ import * as path37 from "path";
13346
13520
  var runTickScript = async (ctx, _profile, args) => {
13347
13521
  ctx.skipAgent = true;
13348
13522
  const jobsDir = String(args?.jobsDir ?? ".kody/duties");
@@ -13354,22 +13528,22 @@ var runTickScript = async (ctx, _profile, args) => {
13354
13528
  ctx.output.reason = `runTickScript: ctx.args.${slugArg} must be a non-empty slug`;
13355
13529
  return;
13356
13530
  }
13357
- const jobPath = path36.join(ctx.cwd, jobsDir, `${slug}.md`);
13358
- if (!fs39.existsSync(jobPath)) {
13531
+ const jobPath = path37.join(ctx.cwd, jobsDir, `${slug}.md`);
13532
+ if (!fs40.existsSync(jobPath)) {
13359
13533
  ctx.output.exitCode = 99;
13360
13534
  ctx.output.reason = `runTickScript: job file not found: ${jobPath}`;
13361
13535
  return;
13362
13536
  }
13363
- const raw = fs39.readFileSync(jobPath, "utf-8");
13364
- const { frontmatter } = splitFrontmatter(raw);
13537
+ const raw = fs40.readFileSync(jobPath, "utf-8");
13538
+ const { frontmatter } = splitFrontmatter2(raw);
13365
13539
  const tickScript = frontmatter.tickScript;
13366
13540
  if (!tickScript) {
13367
13541
  ctx.output.exitCode = 99;
13368
13542
  ctx.output.reason = `runTickScript: job ${slug} has no \`tickScript:\` frontmatter \u2014 route via job-tick instead`;
13369
13543
  return;
13370
13544
  }
13371
- const scriptPath = path36.isAbsolute(tickScript) ? tickScript : path36.join(ctx.cwd, tickScript);
13372
- if (!fs39.existsSync(scriptPath)) {
13545
+ const scriptPath = path37.isAbsolute(tickScript) ? tickScript : path37.join(ctx.cwd, tickScript);
13546
+ if (!fs40.existsSync(scriptPath)) {
13373
13547
  ctx.output.exitCode = 99;
13374
13548
  ctx.output.reason = `runTickScript: tickScript not found: ${scriptPath}`;
13375
13549
  return;
@@ -13498,7 +13672,7 @@ var saveTaskState = async (ctx, profile) => {
13498
13672
  if (!target || !number || !state) return;
13499
13673
  const executable = profile.name;
13500
13674
  const action = ctx.data.action ?? synthesizeAction(ctx);
13501
- const next = reduce(state, executable, action, profile.phase);
13675
+ const next = reduce(state, executable, action, profile.phase, profile.staff);
13502
13676
  if (ctx.output.prUrl) next.core.prUrl = ctx.output.prUrl;
13503
13677
  if (typeof ctx.data.runUrl === "string") next.core.runUrl = ctx.data.runUrl;
13504
13678
  writeTaskState(target, number, next, ctx.cwd);
@@ -14415,7 +14589,7 @@ var writeJobStateFile = async (ctx, _profile, agentResult, args) => {
14415
14589
  };
14416
14590
 
14417
14591
  // src/scripts/writeRunSummary.ts
14418
- import * as fs40 from "fs";
14592
+ import * as fs41 from "fs";
14419
14593
  var writeRunSummary = async (ctx, profile) => {
14420
14594
  const summaryPath = process.env.GITHUB_STEP_SUMMARY;
14421
14595
  if (!summaryPath) return;
@@ -14437,7 +14611,7 @@ var writeRunSummary = async (ctx, profile) => {
14437
14611
  if (reason) lines.push(`- **Reason:** ${reason}`);
14438
14612
  lines.push("");
14439
14613
  try {
14440
- fs40.appendFileSync(summaryPath, `${lines.join("\n")}
14614
+ fs41.appendFileSync(summaryPath, `${lines.join("\n")}
14441
14615
  `);
14442
14616
  } catch {
14443
14617
  }
@@ -14459,6 +14633,7 @@ var preflightScripts = {
14459
14633
  loadIssueContext,
14460
14634
  loadIssueStateComment,
14461
14635
  loadJobFromFile,
14636
+ loadDutyState,
14462
14637
  loadWorkerAdhoc,
14463
14638
  loadConventions,
14464
14639
  loadCoverageRules,
@@ -14545,8 +14720,8 @@ var allScriptNames = /* @__PURE__ */ new Set([
14545
14720
  ]);
14546
14721
 
14547
14722
  // src/staff.ts
14548
- import * as fs41 from "fs";
14549
- import * as path37 from "path";
14723
+ import * as fs42 from "fs";
14724
+ import * as path38 from "path";
14550
14725
  var DEFAULT_STAFF_DIR = ".kody/staff";
14551
14726
  function stripFrontmatter(raw) {
14552
14727
  const match = /^---\n[\s\S]*?\n---\n?([\s\S]*)$/.exec(raw);
@@ -14555,11 +14730,11 @@ function stripFrontmatter(raw) {
14555
14730
  function loadStaffPersona(cwd, slug, staffDir = DEFAULT_STAFF_DIR) {
14556
14731
  const trimmed = slug.trim();
14557
14732
  if (!trimmed) throw new Error("loadStaffPersona: empty staff slug");
14558
- const staffPath = path37.join(cwd, staffDir, `${trimmed}.md`);
14559
- if (!fs41.existsSync(staffPath)) {
14733
+ const staffPath = path38.join(cwd, staffDir, `${trimmed}.md`);
14734
+ if (!fs42.existsSync(staffPath)) {
14560
14735
  throw new Error(`loadStaffPersona: staff '${trimmed}' declared but ${staffPath} does not exist`);
14561
14736
  }
14562
- const body = stripFrontmatter(fs41.readFileSync(staffPath, "utf-8"));
14737
+ const body = stripFrontmatter(fs42.readFileSync(staffPath, "utf-8"));
14563
14738
  if (!body) throw new Error(`loadStaffPersona: staff '${trimmed}' persona body is empty (${staffPath})`);
14564
14739
  return body;
14565
14740
  }
@@ -14576,48 +14751,6 @@ function framePersona(slug, persona) {
14576
14751
  ].join("\n");
14577
14752
  }
14578
14753
 
14579
- // src/subagents.ts
14580
- import * as fs42 from "fs";
14581
- import * as path38 from "path";
14582
- function splitFrontmatter2(raw) {
14583
- const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw);
14584
- if (!match) return { fm: {}, body: raw.trim() };
14585
- const fm = {};
14586
- for (const line of match[1].split("\n")) {
14587
- const idx = line.indexOf(":");
14588
- if (idx === -1) continue;
14589
- fm[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
14590
- }
14591
- return { fm, body: (match[2] ?? "").trim() };
14592
- }
14593
- function resolveAgentFile(profileDir, name) {
14594
- const local = path38.join(profileDir, "agents", `${name}.md`);
14595
- if (fs42.existsSync(local)) return local;
14596
- const central = path38.join(getPluginsCatalogRoot(), "agents", `${name}.md`);
14597
- if (fs42.existsSync(central)) return central;
14598
- throw new Error(`loadSubagents: agent '${name}' not found in ${profileDir}/agents/ or shared catalog`);
14599
- }
14600
- function loadSubagents(profile) {
14601
- const names = profile.claudeCode.subagents;
14602
- if (!names || names.length === 0) return void 0;
14603
- const agents = {};
14604
- for (const name of names) {
14605
- const { fm, body } = splitFrontmatter2(fs42.readFileSync(resolveAgentFile(profile.dir, name), "utf-8"));
14606
- if (!body) throw new Error(`loadSubagents: agent '${name}' has an empty prompt body`);
14607
- const def = {
14608
- description: fm.description ?? `Subagent ${name}`,
14609
- prompt: body
14610
- };
14611
- if (fm.tools) {
14612
- const tools = fm.tools.split(",").map((t) => t.trim()).filter(Boolean);
14613
- if (tools.length > 0) def.tools = tools;
14614
- }
14615
- if (fm.model) def.model = fm.model;
14616
- agents[fm.name || name] = def;
14617
- }
14618
- return agents;
14619
- }
14620
-
14621
14754
  // src/tools.ts
14622
14755
  import { execFileSync as execFileSync27 } from "child_process";
14623
14756
  function verifyCliTools(tools, cwd) {
@@ -15536,16 +15669,63 @@ async function runCi(argv) {
15536
15669
  const eventName = process.env.GITHUB_EVENT_NAME;
15537
15670
  const dispatchEventPath = process.env.GITHUB_EVENT_PATH;
15538
15671
  let manualWorkflowDispatch = false;
15672
+ let forceRunDuty = null;
15539
15673
  if (!args.issueNumber && !autoFallback && eventName === "workflow_dispatch" && dispatchEventPath && fs44.existsSync(dispatchEventPath)) {
15540
15674
  try {
15541
15675
  const evt = JSON.parse(fs44.readFileSync(dispatchEventPath, "utf-8"));
15542
15676
  const issueInput = parseInt(String(evt?.inputs?.issue_number ?? ""), 10);
15543
15677
  const sessionInput = String(evt?.inputs?.sessionId ?? "");
15544
- manualWorkflowDispatch = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
15678
+ const exeInput = String(evt?.inputs?.executable ?? "").trim();
15679
+ const noTarget = !sessionInput && !(Number.isFinite(issueInput) && issueInput > 0);
15680
+ if (noTarget && exeInput) forceRunDuty = exeInput;
15681
+ else manualWorkflowDispatch = noTarget;
15545
15682
  } catch {
15546
15683
  manualWorkflowDispatch = false;
15547
15684
  }
15548
15685
  }
15686
+ if (forceRunDuty) {
15687
+ const config = earlyConfig ?? loadConfig(cwd);
15688
+ process.stdout.write(`\u2192 kody: manual one-shot run of duty ${forceRunDuty}
15689
+
15690
+ `);
15691
+ try {
15692
+ const n = unpackAllSecrets();
15693
+ if (n > 0) process.stdout.write(`\u2192 kody: unpacked ${n} secret(s)
15694
+ `);
15695
+ await resolveAuthToken();
15696
+ const pm = args.packageManager ?? detectPackageManager2(cwd);
15697
+ if (!args.skipInstall) {
15698
+ const code = installDeps(pm, cwd);
15699
+ if (code !== 0) {
15700
+ process.stderr.write(`[kody] dependency install failed (exit ${code})
15701
+ `);
15702
+ return 99;
15703
+ }
15704
+ }
15705
+ if (!args.skipLitellm) {
15706
+ const code = installLitellmIfNeeded(cwd);
15707
+ if (code !== 0) {
15708
+ process.stderr.write(`[kody] litellm install failed (exit ${code})
15709
+ `);
15710
+ return 99;
15711
+ }
15712
+ }
15713
+ configureGitIdentity(cwd);
15714
+ } catch (err) {
15715
+ process.stderr.write(`[kody] manual duty preflight crashed: ${String(err)}
15716
+ `);
15717
+ return 99;
15718
+ }
15719
+ const result = await runExecutableChain(forceRunDuty, {
15720
+ cliArgs: {},
15721
+ cwd,
15722
+ config,
15723
+ verbose: args.verbose,
15724
+ quiet: args.quiet
15725
+ });
15726
+ const ec = result.exitCode;
15727
+ return ec === 0 || ec === 1 || ec === 2 ? ec : 99;
15728
+ }
15549
15729
  if (!args.issueNumber && !autoFallback && (eventName === "schedule" || manualWorkflowDispatch)) {
15550
15730
  return runScheduledFanOut(cwd, args, { force: manualWorkflowDispatch });
15551
15731
  }